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