1 module served.lsp.protoext;
2 
3 import served.lsp.protocol;
4 import served.lsp.textdocumentmanager;
5 
6 import workspaced.api : CodeReplacement;
7 
8 import mir.serde;
9 
10 @serdeIgnoreUnexpectedKeys:
11 
12 struct AddImportParams
13 {
14 	/// Text document to look in
15 	TextDocumentIdentifier textDocument;
16 	/// The name of the import to add
17 	string name;
18 	/// Location of cursor as standard offset
19 	int location;
20 	/// if `false`, the import will get added to the innermost block
21 	@serdeOptional bool insertOutermost = true;
22 }
23 
24 struct SortImportsParams
25 {
26 	/// Text document to look in
27 	TextDocumentIdentifier textDocument;
28 	/// Location of cursor as standard offset
29 	int location;
30 }
31 
32 struct ImplementMethodsParams
33 {
34 	/// Text document to look in
35 	TextDocumentIdentifier textDocument;
36 	/// Location of cursor as standard offset
37 	int location;
38 }
39 
40 struct UpdateSettingParams
41 {
42 	/// The configuration section to update in (e.g. "d" or "dfmt")
43 	string section;
44 	/// The value to set the configuration value to
45 	JsonValue value;
46 	/// `true` if this is a configuration change across all instances and not just the active one
47 	bool global;
48 }
49 
50 /// Represents a dependency of a dub project
51 struct DubDependency
52 {
53 	/// The name of this package
54 	string name;
55 	/// The installed version of this dependency or null if it isn't downloaded/installed yet
56 	@serdeKeys("version")
57 	string version_;
58 	/// Path to the directory in which the package resides or null if it's not stored in the local file system.
59 	string path;
60 	/** Description as given in dub package file */
61 	string description;
62 	/// Homepage as given in dub package file
63 	string homepage;
64 	/// Authors as given in dub package file
65 	const(string)[] authors;
66 	/// Copyright as given in dub package file
67 	string copyright;
68 	/// License as given in dub package file
69 	string license;
70 	/// List of the names of subPackages as defined in the package 
71 	const(string)[] subPackages;
72 	/// `true` if this dependency has other dependencies
73 	bool hasDependencies;
74 	/// `true` if no package name was given and thus this dependency is a root dependency of the active project.
75 	bool root;
76 }
77 
78 /// Parameters for a dub recipe conversion call
79 struct DubConvertRequest
80 {
81 	/// Text document to look in
82 	TextDocumentIdentifier textDocument;
83 	/// The format to convert the dub recipe to. (json, sdl)
84 	string newFormat;
85 }
86 
87 ///
88 struct SimpleTextDocumentIdentifierParams
89 {
90 	///
91 	TextDocumentIdentifier textDocument;
92 }
93 
94 ///
95 struct InstallRequest
96 {
97 	/// Name of the dub dependency
98 	string name;
99 	/// Version to install in the dub recipe file
100 	@serdeKeys("version")
101 	string version_;
102 }
103 
104 ///
105 struct UpdateRequest
106 {
107 	/// Name of the dub dependency
108 	string name;
109 	/// Version to install in the dub recipe file
110 	@serdeKeys("version")
111 	string version_;
112 }
113 
114 ///
115 struct UninstallRequest
116 {
117 	/// Name of the dub dependency
118 	string name;
119 }
120 
121 struct Task
122 {
123 	@serdeEnumProxy!string
124 	enum Group : string
125 	{
126 		clean = "clean",
127 		build = "build",
128 		rebuild = "rebuild",
129 		test = "test"
130 	}
131 
132 	/// the default JSON task
133 	JsonValue definition;
134 	/// global | workspace | uri of workspace folder
135 	@serdeKeys("scope")
136 	string scope_;
137 	/// command to execute
138 	string[] exec;
139 	/// name of the task
140 	string name;
141 	/// true if this is a background task without shown console
142 	bool isBackground;
143 	/// Task source extension name
144 	string source;
145 	/// clean | build | rebuild | test
146 	Group group;
147 	/// problem matchers to use
148 	string[] problemMatchers;
149 }
150 
151 struct DocumentSymbolParamsEx
152 {
153 	// LSP field
154 	TextDocumentIdentifier textDocument;
155 	bool verbose;
156 
157 	this(DocumentSymbolParams params)
158 	{
159 		textDocument = params.textDocument;
160 	}
161 
162 	this(TextDocumentIdentifier textDocument, bool verbose)
163 	{
164 		this.textDocument = textDocument;
165 		this.verbose = verbose;
166 	}
167 }
168 
169 /// special serve-d internal symbol kinds
170 @serdeEnumProxy!int
171 enum SymbolKindEx
172 {
173 	none = 0,
174 	/// set for unittests
175 	test,
176 	/// `debug = X` specification
177 	debugSpec,
178 	/// `version = X` specification
179 	versionSpec,
180 	/// `static this()`
181 	staticCtor,
182 	/// `shared static this()`
183 	sharedStaticCtor,
184 	/// `static ~this()`
185 	staticDtor,
186 	/// `shared static ~this()`
187 	sharedStaticDtor,
188 	/// `this(this)` in structs & classes
189 	postblit
190 }
191 
192 struct SymbolInformationEx
193 {
194 	string name;
195 	SymbolKind kind;
196 	Location location;
197 	string containerName;
198 	@serdeKeys("deprecated") bool deprecated_;
199 	TextRange range;
200 	TextRange selectionRange;
201 	SymbolKindEx extendedType;
202 	string detail;
203 
204 	SymbolInformation downcast()
205 	{
206 		SymbolInformation ret;
207 		static foreach (member; __traits(allMembers, SymbolInformationEx))
208 			static if (__traits(hasMember, SymbolInformation, member))
209 				__traits(getMember, ret, member) = __traits(getMember, this, member);
210 		return ret;
211 	}
212 }
213 
214 struct AddDependencySnippetParams
215 {
216 	string[] requiredDependencies;
217 	SerializablePlainSnippet snippet;
218 }
219 
220 struct SerializablePlainSnippet
221 {
222 	/// Grammar scopes in which to complete this snippet. Maps to workspaced.com.snippets:SnippetLevel
223 	int[] levels;
224 	/// Shortcut to type for this snippet
225 	string shortcut;
226 	/// Label for this snippet.
227 	string title;
228 	/// Text with interactive snippet locations to insert assuming global indentation.
229 	string snippet;
230 	/// Markdown documentation for this snippet
231 	string documentation;
232 	/// Plain text to insert assuming global level indentation. Optional if snippet is a simple string only using plain variables and snippet locations.
233 	@serdeOptional string plain;
234 	/// true if this snippet shouldn't be formatted before inserting.
235 	@serdeOptional bool unformatted;
236 	/// List of imports that should get imported with this snippet. (done in resolveComplete)
237 	@serdeOptional string[] imports;
238 }
239 
240 /// Parameters to pass when updating dub imports
241 struct UpdateImportsParams
242 {
243 	/// set this to false to not emit progress updates for the UI
244 	@serdeOptional bool reportProgress = true;
245 }
246 
247 /// An ini section of the dscanner.ini which is written in form [name]
248 struct DScannerIniSection
249 {
250 	/// A textual human readable description of the section
251 	string description;
252 	/// The name of the section as written in the ini
253 	string name;
254 	/// Features which are children of this section
255 	DScannerIniFeature[] features;
256 }
257 
258 /// A single feature in a dscanner.ini which can be turned on/off
259 struct DScannerIniFeature
260 {
261 	/// A textual human readable description of the value
262 	string description;
263 	/// The name of the value
264 	string name;
265 	/// disabled | enabled | skip-unittest
266 	string enabled;
267 }
268 
269 struct UnittestProject
270 {
271 	/// Workspace uri which may or may not map to an actual workspace folder
272 	/// but rather to some folder inside one.
273 	DocumentUri workspaceUri;
274 
275 	/// Package name if available
276 	string name;
277 
278 	/// List of modules, sorted by moduleName
279 	UnittestModule[] modules;
280 
281 	/// `true` if the project still needs to be opened to be loaded.
282 	bool needsLoad;
283 }
284 
285 struct UnittestModule
286 {
287 	string moduleName;
288 	DocumentUri uri;
289 	UnittestInfo[] tests;
290 }
291 
292 struct UnittestInfo
293 {
294 	string id, name;
295 	string containerName;
296 	TextRange range;
297 }
298 
299 struct RescanTestsParams
300 {
301 	string uri = null;
302 }
303 
304 /// Parameters for served/listArchTypes
305 struct ListArchTypesParams
306 {
307 	/// If true, return ArchTypeInfo[] with meanings instead of string[]
308 	@serdeOptional bool withMeaning;
309 }
310 
311 /// Returned by served/listArchTypes if request was sent with
312 /// `withMeaning: true` request parameter.
313 struct ArchTypeInfo
314 {
315 	/// The value to use with a switchArchType call / the value DUB uses.
316 	string value;
317 	/// If not null, show this string in the UI rather than value.
318 	string label;
319 }
320 
321 ///
322 @serdeFallbackStruct
323 struct ArchType
324 {
325 	/// Value to pass into other calls
326 	string value;
327 	/// UI label override or null if none
328 	string label;
329 }
330 
331 /// Converts the given workspace-d CodeReplacement to an LSP TextEdit
332 TextEdit toTextEdit(CodeReplacement replacement, scope const ref Document d)
333 {
334 	size_t lastIndex;
335 	Position lastPosition;
336 
337 	auto startPos = d.nextPositionBytes(lastPosition, lastIndex, replacement.range[0]);
338 	auto endPos = d.nextPositionBytes(lastPosition, lastIndex, replacement.range[1]);
339 
340 	return TextEdit([startPos, endPos], replacement.content);
341 }
342 
343 ///
344 @serdeFallbackStruct
345 struct ServedInfoParams
346 {
347 @serdeOptional:
348 	bool includeConfig;
349 }
350 
351 ///
352 @serdeFallbackStruct
353 struct ServedInfoResponse
354 {
355 	import served.types;
356 
357 	/// Same as in the initialized response.
358 	ServerInfo serverInfo;
359 
360 	/// Only included if `ServedInfoParams.includeConfig` is true
361 	@serdeOptional Optional!Configuration currentConfiguration;
362 
363 	/// Describes the global workspace.
364 	typeof(Workspace.init.describeState()) globalWorkspace;
365 
366 	/// Describes all available workspaces.
367 	typeof(Workspace.init.describeState())[] workspaces;
368 
369 	/// First index inside the `workspaces` array sent along this value, where
370 	/// `selected` is set to true, or -1 for global workspace.
371 	int selectedWorkspaceIndex;
372 }
373 
374 /// Mixin template that puts in a value called "value" of type `T`. Puts in a
375 /// custom deserializer that allows this struct to be both deserialized from a
376 /// full struct (if a struct has been sent) or any other value, directly getting
377 /// assigned to the `T value;` member.
378 ///
379 /// This means `T` must not be anything that deserializes from a struct, as that
380 /// branch will never be called.
381 mixin template SingleValueParams(T, string valueName = "value")
382 if (!is(T == struct) && !is(T == Dummy[string], Dummy))
383 {
384 	import mir.ion.exception;
385 	import mir.ion.value;
386 
387 	T _implicitValue;
388 
389 	alias _implicitValue this;
390 
391 	mixin("alias " ~ valueName ~ " = _implicitValue;");
392 
393 	@safe pure scope
394 	IonException deserializeFromIon(scope const char[][] symbolTable, IonDescribedValue fullValue)
395 	{
396 		import mir.deser.ion : deserializeIon;
397 		import mir.ion.type_code : IonTypeCode;
398 
399 		if (fullValue.descriptor.type == IonTypeCode.struct_)
400 		{
401 			auto struct_ = fullValue.get!(IonStruct).withSymbols(symbolTable);
402 
403 			bool hasValue = false;
404 
405 			foreach (error, key, value; struct_)
406 			{
407 				if (error)
408 					return error.ionException;
409 
410 			Switch:
411 				switch (key)
412 				{
413 				case valueName:
414 					hasValue = true;
415 					_implicitValue = deserializeIon!(typeof(_implicitValue))(
416 						symbolTable, value);
417 					break Switch;
418 
419 					static foreach (i, member; typeof(this).tupleof)
420 					{
421 						static if (__traits(identifier, member) != valueName)
422 						{
423 						case __traits(identifier, member):
424 							this.tupleof[i] = deserializeIon!(typeof(member))(
425 								symbolTable, value);
426 							break Switch;
427 						}
428 					}
429 				default:
430 					break;
431 				}
432 			}
433 
434 			if (!hasValue)
435 				return new IonException(
436 					"Missing required `{\"value\":...}` parameter or must pass "
437 					~ "value as RPC array `[value]`.");
438 
439 			return null;
440 		}
441 		else
442 		{
443 			_implicitValue = deserializeIon!T(symbolTable, fullValue);
444 			return null;
445 		}
446 	}
447 }
448 
449 /// Used in `served/switchConfig`
450 struct SwitchConfigParams
451 {
452 	mixin SingleValueParams!string;
453 }
454 
455 unittest
456 {
457 	import std.exception;
458 
459 	assert(deserializeJson!SwitchConfigParams(`{"value":"foo"}`).value == "foo");
460 	assert(deserializeJson!SwitchConfigParams(`{"value":"foo","x":"y"}`).value == "foo");
461 	assertThrown(deserializeJson!SwitchConfigParams(`{"x":"y"}`));
462 	assert(deserializeJson!SwitchConfigParams(`"foo"`).value == "foo");
463 	assertThrown(deserializeJson!SwitchConfigParams(`4`).value);
464 }
465 
466 /// Used in `served/switchArchType`
467 struct SwitchArchTypeParams
468 {
469 	mixin SingleValueParams!string;
470 }
471 
472 /// Used in `served/switchBuildType`
473 struct SwitchBuildTypeParams
474 {
475 	mixin SingleValueParams!string;
476 }
477 
478 /// Used in `served/switchCompiler`
479 struct SwitchCompilerParams
480 {
481 	mixin SingleValueParams!string;
482 }
483 
484 /// Used in `served/listDependencies`
485 struct ListDependenciesParams
486 {
487 	mixin SingleValueParams!(string, "packageName");
488 }