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 }