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 }