1 /*
2  *Copyright (C) 2018 Laurent Tréguier
3  *
4  *This file is part of DLS.
5  *
6  *DLS is free software: you can redistribute it and/or modify
7  *it under the terms of the GNU General Public License as published by
8  *the Free Software Foundation, either version 3 of the License, or
9  *(at your option) any later version.
10  *
11  *DLS is distributed in the hope that it will be useful,
12  *but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *GNU General Public License for more details.
15  *
16  *You should have received a copy of the GNU General Public License
17  *along with DLS.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 module dls.protocol.messages.general;
22 
23 import dls.protocol.interfaces.general;
24 import std.json : JSONValue;
25 
26 @("")
27 InitializeResult initialize(InitializeParams params)
28 {
29     import dls.protocol.interfaces : CodeActionKind;
30     import dls.protocol.logger : logger;
31     import dls.protocol.state : initOptions, initState;
32     import dls.server : Server;
33     import dls.tools.analysis_tool : AnalysisTool;
34     import dls.tools.command_tool : CommandTool;
35     import dls.tools.format_tool : DfmtFormatTool, FormatTool;
36     import dls.tools.symbol_tool : SymbolTool;
37     import dls.tools.tool : Tool;
38     import dls.util.json : convertToJSON;
39     import dls.util.uri : Uri, filenameCmp, sameFile;
40     import std.algorithm : map, sort, uniq;
41     import std.array : array;
42     import dls.util.nullable: Nullable, nullable;
43 
44     initState = params;
45     logger.info("Initializing server");
46     Tool.initialize(new Tool());
47     AnalysisTool.initialize(new AnalysisTool());
48     CommandTool.initialize(new CommandTool());
49     FormatTool.initialize(new DfmtFormatTool());
50     SymbolTool.initialize(new SymbolTool());
51 
52     debug
53     {
54     }
55     else
56     {
57         import dls.updater : cleanup;
58         import std.concurrency : spawn;
59 
60         spawn(&cleanup);
61     }
62 
63     Uri[] uris;
64 
65     if (!params.rootUri.isNull)
66     {
67         uris ~= new Uri(params.rootUri);
68     }
69     else if (!params.rootPath.isNull)
70     {
71         uris ~= Uri.fromPath(params.rootPath);
72     }
73 
74     if (!params.workspaceFolders.isNull)
75     {
76         uris ~= params.workspaceFolders.map!(wf => new Uri(wf.uri)).array;
77     }
78 
79     foreach (uri; uris.sort!((a, b) => filenameCmp(a, b) < 0)
80             .uniq!sameFile)
81     {
82         Tool.instance.updateConfig(uri, JSONValue());
83         SymbolTool.instance.importPath(uri);
84         AnalysisTool.instance.addAnalysisConfig(uri);
85     }
86 
87     auto result = new InitializeResult();
88 
89     with (result.capabilities)
90     {
91         import std.json : JSONValue;
92         import dls.util.nullable: Nullable;
93 
94         textDocumentSync = new TextDocumentSyncOptions(
95             true.nullable,
96             TextDocumentSyncKind.incremental.nullable,
97             true.nullable,
98             false.nullable,
99             new SaveOptions(false.nullable).nullable
100         );
101 
102         if (!params.capabilities.textDocument.isNull)
103         {
104             const textDocCaps = params.capabilities.textDocument.get();
105 
106             if (!textDocCaps.synchronisation.isNull)
107             {
108                 if (!textDocCaps.synchronisation.willSave.isNull && textDocCaps.synchronisation.willSave.get())
109                 {
110                     textDocumentSync.save.nullify();
111                 }
112                 else if (!textDocCaps.synchronisation.didSave.isNull && textDocCaps.synchronisation.didSave.get())
113                 {
114                     textDocumentSync.willSave.nullify();
115                 }
116             }
117 
118             if (!textDocCaps.hover.isNull)
119                 hoverProvider = initOptions.capabilities.hover;
120 
121             if (!textDocCaps.completion.isNull && initOptions.capabilities.completion)
122                 completionProvider = new CompletionOptions(true.nullable, ["."].nullable);
123 
124             if (!textDocCaps.definition.isNull)
125                 definitionProvider = initOptions.capabilities.definition;
126 
127             if (!textDocCaps.typeDefinition.isNull)
128                 typeDefinitionProvider = JSONValue(initOptions.capabilities.definition);
129 
130             if (!textDocCaps.references.isNull)
131                 referencesProvider = initOptions.capabilities.references;
132 
133             if (!textDocCaps.documentHighlight.isNull)
134                 documentHighlightProvider = initOptions.capabilities.documentHighlight;
135 
136             if (!textDocCaps.documentSymbol.isNull)
137                 documentSymbolProvider = initOptions.capabilities.documentSymbol;
138 
139             if (!textDocCaps.codeAction.isNull)
140                 codeActionProvider = JSONValue(initOptions.capabilities.codeAction);
141 
142             if (!textDocCaps.formatting.isNull)
143                 documentFormattingProvider = initOptions.capabilities.documentFormatting;
144 
145             if (!textDocCaps.rangeFormatting.isNull)
146                 documentRangeFormattingProvider = initOptions.capabilities.documentRangeFormatting;
147 
148             if (!textDocCaps.onTypeFormatting.isNull
149                     && initOptions.capabilities.documentOnTypeFormatting)
150                 documentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions(";");
151 
152             if (!textDocCaps.rename.isNull)
153             {
154                 immutable prepareSupport = !textDocCaps.rename.prepareSupport.isNull
155                     && textDocCaps.rename.prepareSupport.get();
156 
157                 renameProvider = initOptions.capabilities.rename ? prepareSupport
158                     ? convertToJSON(new RenameOptions(true.nullable))
159                     : JSONValue(true) : JSONValue(false);
160             }
161         }
162 
163         if (!params.capabilities.workspace.isNull)
164         {
165             const workspaceCaps = params.capabilities.workspace.get();
166 
167             if (!workspaceCaps.symbol.isNull)
168                 workspaceSymbolProvider = initOptions.capabilities.workspaceSymbol;
169 
170             if (!workspaceCaps.executeCommand.isNull && initOptions.capabilities.codeAction)
171                 executeCommandProvider = new ExecuteCommandOptions(CommandTool.instance.commands);
172 
173             if (!workspaceCaps.workspaceFolders.isNull && workspaceCaps.workspaceFolders.get())
174                 workspace = new ServerCapabilities.Workspace(
175                         new ServerCapabilities.Workspace.WorkspaceFolders(true.nullable,
176                         JSONValue(true).nullable).nullable);
177         }
178     }
179 
180     Server.initialized = true;
181     return result;
182 }
183 
184 @("")
185 void initialized(JSONValue nothing)
186 {
187     import dls.protocol.interfaces : ConfigurationItem, ConfigurationParams,
188         DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher,
189         Registration, RegistrationParams, WatchKind;
190     import dls.protocol.jsonrpc : send;
191     import dls.protocol.logger : logger;
192     import dls.protocol.messages.methods : Client, Workspace;
193     import dls.protocol.state : initOptions, initState;
194     import dls.server : Server;
195     import dls.tools.analysis_tool : AnalysisTool;
196     import dls.tools.tool : Tool;
197     import dls.util.nullable: Nullable, nullable;
198 
199     debug
200     {
201     }
202     else
203     {
204         import dls.updater : update;
205         import std.concurrency : spawn;
206 
207         spawn(&update, initOptions.autoUpdate, initOptions.preReleaseBuilds);
208     }
209 
210     if (!initState.capabilities.workspace.isNull)
211     {
212         immutable didChangeWatchedFiles = !initState.capabilities.workspace.didChangeWatchedFiles.isNull
213             && initState.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
214         ubyte watchAllEvents = WatchKind.create + WatchKind.change + WatchKind.delete_;
215 
216         if (didChangeWatchedFiles)
217         {
218             logger.info("Registering file watchers");
219             //dfmt off
220             auto watchers = [
221                 new FileSystemWatcher("**/dub.{json,sdl}", watchAllEvents.nullable),
222                 new FileSystemWatcher("**/dub.selections.json", watchAllEvents.nullable),
223                 new FileSystemWatcher("**/.gitmodules", watchAllEvents.nullable),
224                 new FileSystemWatcher("**/*.ini", watchAllEvents.nullable),
225                 new FileSystemWatcher("**/*.{d,di}", watchAllEvents.nullable)
226             ];
227             //dfmt on
228             auto registrationOptions = new DidChangeWatchedFilesRegistrationOptions(watchers);
229             auto registration = new Registration!DidChangeWatchedFilesRegistrationOptions("dls-file-watchers",
230                     "workspace/didChangeWatchedFiles", registrationOptions.nullable);
231             send(Client.registerCapability,
232                     new RegistrationParams!DidChangeWatchedFilesRegistrationOptions([registration]));
233         }
234 
235         immutable configuration = !initState.capabilities.workspace.configuration.isNull
236             && initState.capabilities.workspace.configuration;
237 
238         if (configuration)
239         {
240             auto items = [new ConfigurationItem(Nullable!string(null))];
241 
242             foreach (uri; Tool.instance.workspacesUris)
243             {
244                 items ~= new ConfigurationItem(uri.toString().nullable);
245             }
246 
247             send(Workspace.configuration, new ConfigurationParams(items));
248         }
249     }
250 
251     AnalysisTool.instance.scanAllWorkspaces();
252 }
253 
254 @("")
255 JSONValue shutdown(JSONValue nothing)
256 {
257     import dls.protocol.definitions : TextDocumentIdentifier;
258     import dls.protocol.logger : logger;
259     import dls.server : Server;
260     import dls.tools.analysis_tool : AnalysisTool;
261     import dls.tools.command_tool : CommandTool;
262     import dls.tools.format_tool : FormatTool;
263     import dls.tools.symbol_tool : SymbolTool;
264     import dls.tools.tool : Tool;
265     import dls.util.document : Document;
266 
267     logger.info("Shutting down server");
268     Server.initialized = false;
269     AnalysisTool.shutdown();
270     CommandTool.shutdown();
271     FormatTool.shutdown();
272     SymbolTool.shutdown();
273     Tool.shutdown();
274 
275     foreach (uri; Document.uris)
276     {
277         Document.close(new TextDocumentIdentifier(uri));
278     }
279 
280     return JSONValue(null);
281 }
282 
283 @("")
284 void exit(JSONValue nothing)
285 {
286     import dls.protocol.logger : logger;
287     import dls.server : Server;
288 
289     if (Server.initialized)
290     {
291         logger.warning("Shutdown not requested prior to exit");
292         shutdown(JSONValue());
293         Server.initialized = true;
294     }
295 
296     logger.info("Exiting server");
297     Server.exit = true;
298 }
299 
300 @("$")
301 void cancelRequest(CancelParams params)
302 {
303     import dls.server : Server;
304 
305     Server.cancel(params.id);
306 }