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 : 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 std.typecons : Nullable, nullable;
43 
44     initState = params;
45     logger.info("Initializing server");
46     AnalysisTool.initialize();
47     CommandTool.initialize();
48     FormatTool.initialize();
49     SymbolTool.initialize();
50 
51     debug
52     {
53     }
54     else
55     {
56         import dls.updater : cleanup;
57         import std.concurrency : spawn;
58 
59         spawn(&cleanup);
60     }
61 
62     Uri[] uris;
63 
64     if (!params.rootUri.isNull)
65     {
66         uris ~= new Uri(params.rootUri);
67     }
68     else if (!params.rootPath.isNull)
69     {
70         uris ~= Uri.fromPath(params.rootPath);
71     }
72 
73     if (!params.workspaceFolders.isNull)
74     {
75         uris ~= params.workspaceFolders.map!(wf => new Uri(wf.uri)).array;
76     }
77 
78     foreach (uri; uris.sort!((a, b) => filenameCmp(a, b) < 0)
79             .uniq!sameFile)
80     {
81         Tool.updateConfig(uri, JSONValue());
82         SymbolTool.instance.importPath(uri);
83         AnalysisTool.instance.addAnalysisConfig(uri);
84     }
85 
86     auto result = new InitializeResult();
87 
88     with (result.capabilities)
89     {
90         import std.json : JSONValue;
91         import std.typecons : Nullable;
92 
93         textDocumentSync = new TextDocumentSyncOptions(true.nullable,
94                 TextDocumentSyncKind.incremental.nullable);
95         textDocumentSync.save = new SaveOptions(false.nullable);
96 
97         if (!params.capabilities.textDocument.isNull)
98         {
99             const textDocCaps = params.capabilities.textDocument.get();
100 
101             if (!textDocCaps.hover.isNull)
102                 hoverProvider = initOptions.capabilities.hover;
103 
104             if (!textDocCaps.completion.isNull && initOptions.capabilities.completion)
105                 completionProvider = new CompletionOptions(true.nullable, ["."].nullable);
106 
107             if (!textDocCaps.definition.isNull)
108                 definitionProvider = initOptions.capabilities.definition;
109 
110             if (!textDocCaps.typeDefinition.isNull)
111                 typeDefinitionProvider = JSONValue(initOptions.capabilities.definition);
112 
113             if (!textDocCaps.references.isNull)
114                 referencesProvider = initOptions.capabilities.references;
115 
116             if (!textDocCaps.documentHighlight.isNull)
117                 documentHighlightProvider = initOptions.capabilities.documentHighlight;
118 
119             if (!textDocCaps.documentSymbol.isNull)
120                 documentSymbolProvider = initOptions.capabilities.documentSymbol;
121 
122             if (!textDocCaps.codeAction.isNull)
123                 codeActionProvider = JSONValue(initOptions.capabilities.codeAction);
124 
125             if (!textDocCaps.formatting.isNull)
126                 documentFormattingProvider = initOptions.capabilities.documentFormatting;
127 
128             if (!textDocCaps.rangeFormatting.isNull)
129                 documentRangeFormattingProvider = initOptions.capabilities.documentRangeFormatting;
130 
131             if (!textDocCaps.onTypeFormatting.isNull
132                     && initOptions.capabilities.documentOnTypeFormatting)
133                 documentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions(";");
134 
135             if (!textDocCaps.rename.isNull)
136             {
137                 immutable prepareSupport = !textDocCaps.rename.prepareSupport.isNull
138                     && textDocCaps.rename.prepareSupport.get();
139 
140                 renameProvider = initOptions.capabilities.rename ? prepareSupport
141                     ? convertToJSON(new RenameOptions(true.nullable))
142                     : JSONValue(true) : JSONValue(false);
143             }
144         }
145 
146         if (!params.capabilities.workspace.isNull)
147         {
148             const workspaceCaps = params.capabilities.workspace.get();
149 
150             if (!workspaceCaps.symbol.isNull)
151                 workspaceSymbolProvider = initOptions.capabilities.workspaceSymbol;
152 
153             if (!workspaceCaps.executeCommand.isNull && initOptions.capabilities.codeAction)
154                 executeCommandProvider = new ExecuteCommandOptions(CommandTool.instance.commands);
155 
156             if (!workspaceCaps.workspaceFolders.isNull && workspaceCaps.workspaceFolders.get())
157                 workspace = new ServerCapabilities.Workspace(
158                         new ServerCapabilities.Workspace.WorkspaceFolders(true.nullable,
159                         JSONValue(true).nullable).nullable);
160         }
161     }
162 
163     Server.initialized = true;
164     return result;
165 }
166 
167 @("")
168 void initialized(JSONValue nothing)
169 {
170     import dls.protocol.interfaces : ConfigurationItem, ConfigurationParams,
171         DidChangeWatchedFilesRegistrationOptions, FileSystemWatcher,
172         Registration, RegistrationParams, WatchKind;
173     import dls.protocol.jsonrpc : send;
174     import dls.protocol.logger : logger;
175     import dls.protocol.messages.methods : Client, Workspace;
176     import dls.protocol.state : initOptions, initState;
177     import dls.server : Server;
178     import dls.tools.analysis_tool : AnalysisTool;
179     import dls.tools.tool : Tool;
180     import std.typecons : Nullable, nullable;
181 
182     debug
183     {
184     }
185     else
186     {
187         import dls.updater : update;
188         import std.concurrency : spawn;
189 
190         spawn(&update, initOptions.autoUpdate);
191     }
192 
193     if (!initState.capabilities.workspace.isNull)
194     {
195         immutable didChangeWatchedFiles = !initState.capabilities.workspace.didChangeWatchedFiles.isNull
196             && initState.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
197         ubyte watchAllEvents = WatchKind.create + WatchKind.change + WatchKind.delete_;
198 
199         if (didChangeWatchedFiles)
200         {
201             logger.info("Registering file watchers");
202             //dfmt off
203             auto watchers = [
204                 new FileSystemWatcher("**/dub.{json,sdl}", watchAllEvents.nullable),
205                 new FileSystemWatcher("**/dub.selections.json", watchAllEvents.nullable),
206                 new FileSystemWatcher("**/.gitmodules", watchAllEvents.nullable),
207                 new FileSystemWatcher("**/*.ini", watchAllEvents.nullable),
208                 new FileSystemWatcher("**/*.{d,di}", watchAllEvents.nullable)
209             ];
210             //dfmt on
211             auto registrationOptions = new DidChangeWatchedFilesRegistrationOptions(watchers);
212             auto registration = new Registration!DidChangeWatchedFilesRegistrationOptions("dls-file-watchers",
213                     "workspace/didChangeWatchedFiles", registrationOptions.nullable);
214             send(Client.registerCapability,
215                     new RegistrationParams!DidChangeWatchedFilesRegistrationOptions([registration]));
216         }
217 
218         immutable configuration = !initState.capabilities.workspace.configuration.isNull
219             && initState.capabilities.workspace.configuration;
220 
221         if (configuration)
222         {
223             auto items = [new ConfigurationItem(Nullable!string(null))];
224 
225             foreach (uri; Tool.workspacesUris)
226             {
227                 items ~= new ConfigurationItem(uri.toString().nullable);
228             }
229 
230             send(Workspace.configuration, new ConfigurationParams(items));
231         }
232     }
233 
234     AnalysisTool.instance.scanAllWorkspaces();
235 }
236 
237 @("")
238 JSONValue shutdown(JSONValue nothing)
239 {
240     import dls.protocol.definitions : TextDocumentIdentifier;
241     import dls.protocol.logger : logger;
242     import dls.server : Server;
243     import dls.tools.analysis_tool : AnalysisTool;
244     import dls.tools.command_tool : CommandTool;
245     import dls.tools.format_tool : FormatTool;
246     import dls.tools.symbol_tool : SymbolTool;
247     import dls.util.document : Document;
248 
249     logger.info("Shutting down server");
250     Server.initialized = false;
251     AnalysisTool.shutdown();
252     CommandTool.shutdown();
253     FormatTool.shutdown();
254     SymbolTool.shutdown();
255 
256     foreach (uri; Document.uris)
257     {
258         Document.close(new TextDocumentIdentifier(uri));
259     }
260 
261     return JSONValue(null);
262 }
263 
264 @("")
265 void exit(JSONValue nothing)
266 {
267     import dls.protocol.logger : logger;
268     import dls.server : Server;
269 
270     if (Server.initialized)
271     {
272         logger.warning("Shutdown not requested prior to exit");
273         shutdown(JSONValue());
274         Server.initialized = true;
275     }
276 
277     logger.info("Exiting server");
278     Server.exit = true;
279 }
280 
281 @("$")
282 void cancelRequest(CancelParams params)
283 {
284     import dls.server : Server;
285 
286     Server.cancel(params.id);
287 }