1 module dls.tools.code_completer; 2 3 import dls.protocol.interfaces : CompletionItemKind; 4 import dls.tools.tool : Tool; 5 import std.algorithm; 6 import std.path; 7 8 private immutable CompletionItemKind[char] completionKinds; 9 10 static this() 11 { 12 import dub.internal.vibecompat.core.log : LogLevel, setLogLevel; 13 14 //dfmt off 15 completionKinds = [ 16 'c' : CompletionItemKind.class_, 17 'i' : CompletionItemKind.interface_, 18 's' : CompletionItemKind.struct_, 19 'u' : CompletionItemKind.interface_, 20 'v' : CompletionItemKind.variable, 21 'm' : CompletionItemKind.field, 22 'k' : CompletionItemKind.keyword, 23 'f' : CompletionItemKind.method, 24 'g' : CompletionItemKind.enum_, 25 'e' : CompletionItemKind.enumMember, 26 'P' : CompletionItemKind.folder, 27 'M' : CompletionItemKind.module_, 28 'a' : CompletionItemKind.value, 29 'A' : CompletionItemKind.value, 30 'l' : CompletionItemKind.variable, 31 't' : CompletionItemKind.function_, 32 'T' : CompletionItemKind.function_ 33 ]; 34 //dfmt on 35 36 setLogLevel(LogLevel.none); 37 } 38 39 class CodeCompleter : Tool 40 { 41 import logger = std.experimental.logger; 42 import dls.protocol.definitions : Position; 43 import dls.tools.configuration : Configuration; 44 import dls.util.uri : Uri; 45 import dsymbol.modulecache : ASTAllocator, ModuleCache; 46 import dub.dub : Dub; 47 import dub.platform : BuildPlatform; 48 import std.array : array; 49 import std.conv : to; 50 import std.file : isFile; 51 52 version (Posix) 53 { 54 private static immutable _compilerConfigPaths = [ 55 `/etc/dmd.conf`, `/usr/local/etc/dmd.conf`, `/etc/ldc2.conf`, 56 `/usr/local/etc/ldc2.conf` 57 ]; 58 } 59 else version (Windows) 60 { 61 private static immutable _compilerConfigPaths = [`c:\D\dmd2\windows\bin\sc.ini`]; 62 } 63 else 64 { 65 private static immutable string[] _compilerConfigPaths; 66 } 67 68 private ModuleCache _cache = ModuleCache(new ASTAllocator()); 69 70 @property override void configuration(Configuration config) 71 { 72 super.configuration(config); 73 _cache.addImportPaths(_configuration.general.importPaths); 74 } 75 76 @property private auto defaultImportPaths() 77 { 78 import std.file : FileException, exists, readText; 79 import std.range : replace; 80 import std.regex : matchAll, regex; 81 82 string[] paths; 83 84 foreach (confPath; _compilerConfigPaths) 85 { 86 if (exists(confPath)) 87 { 88 try 89 { 90 readText(confPath).matchAll(regex(`-I[^\s"]+`)) 91 .each!(m => paths ~= m.hit[2 .. $].replace("%@P%", 92 confPath.dirName).asNormalizedPath().to!string); 93 break; 94 } 95 catch (FileException e) 96 { 97 // File doesn't exist or could't be read 98 } 99 } 100 } 101 102 return paths.sort().uniq().array; 103 } 104 105 this() 106 { 107 _cache.addImportPaths(defaultImportPaths); 108 } 109 110 void importPath(Uri uri) 111 { 112 logger.logf("Importing from %s", dirName(uri.path)); 113 const d = getDub(uri); 114 importDirectories(d.project.rootPackage.describe(BuildPlatform.any, null, null).importPaths); 115 } 116 117 void importSelections(Uri uri) 118 { 119 logger.logf("Importing dependencies from %s", dirName(uri.path)); 120 const d = getDub(uri); 121 const project = d.project; 122 123 foreach (dep; project.dependencies) 124 { 125 const desc = dep.describe(BuildPlatform.any, null, 126 dep.name in project.rootPackage.recipe.buildSettings.subConfigurations 127 ? project.rootPackage.recipe.buildSettings.subConfigurations[dep.name] : null); 128 129 auto newImportPaths = desc.importPaths.map!( 130 importPath => buildPath(dep.path.toString(), importPath)); 131 132 importDirectories(newImportPaths.array); 133 } 134 } 135 136 void upgradeSelections(Uri uri) 137 { 138 import dub.dub : UpgradeOptions; 139 140 logger.logf("Upgrading dependencies from %s", dirName(uri.path)); 141 auto d = getDub(uri); 142 d.upgrade(UpgradeOptions.select); 143 } 144 145 auto complete(Uri uri, Position position) 146 { 147 import dcd.common.messages : AutocompleteRequest, RequestKind; 148 import dcd.server.autocomplete : complete; 149 import dls.protocol.interfaces : CompletionItem; 150 import dls.util.document : Document; 151 152 auto request = AutocompleteRequest(); 153 auto document = Document[uri]; 154 155 request.fileName = uri.path; 156 request.kind = RequestKind.autocomplete; 157 request.sourceCode = cast(ubyte[]) document.toString(); 158 request.cursorPosition = document.bytePosition(position); 159 160 auto result = complete(request, _cache); 161 CompletionItem[] items; 162 163 foreach (res; result.completions) 164 { 165 items ~= new CompletionItem(); 166 167 with (items[$ - 1]) 168 { 169 label = res.identifier; 170 kind = completionKinds[res.kind.to!char]; 171 } 172 } 173 174 return items.sort!((a, b) => a.label < b.label).uniq!((a, b) => a.label == b.label).array; 175 } 176 177 private void importDirectories(string[] paths) 178 { 179 foreach (path; paths) 180 { 181 if (!_cache.getImportPaths().canFind(path)) 182 { 183 _cache.addImportPaths([path]); 184 } 185 } 186 } 187 188 private auto getDub(Uri uri) 189 { 190 auto d = new Dub(isFile(uri.path) ? dirName(uri.path) : uri.path); 191 d.loadPackage(); 192 return d; 193 } 194 }