1 module served.commands.dub; 2 3 import served.extension; 4 import served.lsp.protoext; 5 import served.types; 6 import served.utils.progress; 7 import served.utils.translate; 8 9 import workspaced.api; 10 import workspaced.coms; 11 12 import core.time; 13 14 import std.algorithm : among, canFind, count, endsWith, map, remove, startsWith; 15 import std.array : array, replace, appender; 16 import std.experimental.logger; 17 import std.path : baseName, buildPath, dirName, setExtension; 18 import std.regex : regex, replaceFirst; 19 import std.string : indexOf, join, KeepTerminator, splitLines, strip, stripLeft, stripRight; 20 21 import fs = std.file; 22 import io = std.stdio; 23 24 @protocolMethod("served/listConfigurations") 25 string[] listConfigurations() 26 { 27 if (!activeInstance || !activeInstance.has!DubComponent) 28 return null; 29 return activeInstance.get!DubComponent.configurations; 30 } 31 32 @protocolMethod("served/switchConfig") 33 bool switchConfig(SwitchConfigParams value) 34 { 35 if (!activeInstance || !activeInstance.has!DubComponent) 36 return false; 37 return activeInstance.get!DubComponent.setConfiguration(value); 38 } 39 40 @protocolMethod("served/getConfig") 41 string getConfig() 42 { 43 if (!activeInstance || !activeInstance.has!DubComponent) 44 return null; 45 return activeInstance.get!DubComponent.configuration; 46 } 47 48 @protocolMethod("served/listArchTypes") 49 Variant!(string, ArchType)[] listArchTypes(ListArchTypesParams params) 50 { 51 auto ret = appender!(typeof(return)); 52 alias Item = typeof(ret.data[0]); 53 if (!activeInstance || !activeInstance.has!DubComponent) 54 return null; 55 56 auto archTypes = activeInstance.get!DubComponent.extendedArchTypes; 57 58 if (params.withMeaning) 59 { 60 foreach (archType; archTypes) 61 { 62 ret ~= Item(ArchType(archType.value, archType.label)); 63 } 64 } 65 else 66 { 67 foreach (archType; archTypes) 68 { 69 ret ~= Item(archType.value); 70 } 71 } 72 return ret.data; 73 } 74 75 @protocolMethod("served/switchArchType") 76 bool switchArchType(SwitchArchTypeParams value) 77 { 78 if (!activeInstance || !activeInstance.has!DubComponent) 79 return false; 80 return activeInstance.get!DubComponent.setArchType(value); 81 } 82 83 @protocolMethod("served/getArchType") 84 string getArchType() 85 { 86 if (!activeInstance || !activeInstance.has!DubComponent) 87 return null; 88 return activeInstance.get!DubComponent.archType; 89 } 90 91 @protocolMethod("served/listBuildTypes") 92 string[] listBuildTypes() 93 { 94 if (!activeInstance || !activeInstance.has!DubComponent) 95 return null; 96 return activeInstance.get!DubComponent.buildTypes; 97 } 98 99 @protocolMethod("served/switchBuildType") 100 bool switchBuildType(SwitchBuildTypeParams value) 101 { 102 if (!activeInstance || !activeInstance.has!DubComponent) 103 return false; 104 return activeInstance.get!DubComponent.setBuildType(value); 105 } 106 107 @protocolMethod("served/getBuildType") 108 string getBuildType() 109 { 110 if (!activeInstance || !activeInstance.has!DubComponent) 111 return null; 112 return activeInstance.get!DubComponent.buildType; 113 } 114 115 @protocolMethod("served/getCompiler") 116 string getCompiler() 117 { 118 if (!activeInstance || !activeInstance.has!DubComponent) 119 return null; 120 return activeInstance.get!DubComponent.compiler; 121 } 122 123 @protocolMethod("served/switchCompiler") 124 bool switchCompiler(SwitchCompilerParams value) 125 { 126 if (!activeInstance || !activeInstance.has!DubComponent) 127 return false; 128 return activeInstance.get!DubComponent.setCompiler(value); 129 } 130 131 /// Returns: at least 132 /// ``` 133 /// { 134 /// "packagePath": string, 135 /// "packageName": string, 136 /// "recipePath": string, 137 /// "targetPath": string, 138 /// "targetName": string, 139 /// "targetType": string, 140 /// "workingDirectory": string, 141 /// "mainSourceFile": string, 142 /// 143 /// "dflags": string[], 144 /// "lflags": string[], 145 /// "libs": string[], 146 /// "linkerFiles": string[], 147 /// "sourceFiles": string[], 148 /// "copyFiles": string[], 149 /// "versions": string[], 150 /// "debugVersions": string[], 151 /// "importPaths": string[], 152 /// "stringImportPaths": string[], 153 /// "importFiles": string[], 154 /// "stringImportFiles": string[], 155 /// "preGenerateCommands": string[], 156 /// "postGenerateCommands": string[], 157 /// "preBuildCommands": string[], 158 /// "postBuildCommands": string[], 159 /// "preRunCommands": string[], 160 /// "postRunCommands": string[], 161 /// "buildOptions": string[], 162 /// "buildRequirements": string[], 163 /// } 164 /// ``` 165 @protocolMethod("served/getActiveDubConfig") 166 StringMap!JsonValue getActiveDubConfig() 167 { 168 if (!activeInstance || !activeInstance.has!DubComponent) 169 return StringMap!JsonValue.init; 170 auto ret = activeInstance.get!DubComponent.rootPackageBuildSettings(); 171 static assert(is(typeof(ret.packagePath) : string), "API guarantee broken"); 172 static assert(is(typeof(ret.packageName) : string), "API guarantee broken"); 173 static assert(is(typeof(ret.recipePath) : string), "API guarantee broken"); 174 static assert(is(typeof(ret.targetPath) : string), "API guarantee broken"); 175 static assert(is(typeof(ret.targetName) : string), "API guarantee broken"); 176 static assert(is(typeof(ret.targetType) : string), "API guarantee broken"); 177 static assert(is(typeof(ret.workingDirectory) : string), "API guarantee broken"); 178 static assert(is(typeof(ret.mainSourceFile) : string), "API guarantee broken"); 179 static assert(is(typeof(ret.dflags) : string[]), "API guarantee broken"); 180 static assert(is(typeof(ret.lflags) : string[]), "API guarantee broken"); 181 static assert(is(typeof(ret.libs) : string[]), "API guarantee broken"); 182 static assert(is(typeof(ret.linkerFiles) : string[]), "API guarantee broken"); 183 static assert(is(typeof(ret.sourceFiles) : string[]), "API guarantee broken"); 184 static assert(is(typeof(ret.copyFiles) : string[]), "API guarantee broken"); 185 static assert(is(typeof(ret.versions) : string[]), "API guarantee broken"); 186 static assert(is(typeof(ret.debugVersions) : string[]), "API guarantee broken"); 187 static assert(is(typeof(ret.importPaths) : string[]), "API guarantee broken"); 188 static assert(is(typeof(ret.stringImportPaths) : string[]), "API guarantee broken"); 189 static assert(is(typeof(ret.importFiles) : string[]), "API guarantee broken"); 190 static assert(is(typeof(ret.stringImportFiles) : string[]), "API guarantee broken"); 191 static assert(is(typeof(ret.preGenerateCommands) : string[]), "API guarantee broken"); 192 static assert(is(typeof(ret.postGenerateCommands) : string[]), "API guarantee broken"); 193 static assert(is(typeof(ret.preBuildCommands) : string[]), "API guarantee broken"); 194 static assert(is(typeof(ret.postBuildCommands) : string[]), "API guarantee broken"); 195 static assert(is(typeof(ret.preRunCommands) : string[]), "API guarantee broken"); 196 static assert(is(typeof(ret.postRunCommands) : string[]), "API guarantee broken"); 197 static assert(is(typeof(ret.buildOptions) : string[]), "API guarantee broken"); 198 static assert(is(typeof(ret.buildRequirements) : string[]), "API guarantee broken"); 199 200 return ret.toJsonValue.get!(StringMap!JsonValue); 201 } 202 203 @protocolMethod("served/addImport") 204 auto addImport(AddImportParams params) 205 { 206 auto document = documents[params.textDocument.uri]; 207 return backend.get!ImporterComponent.add(params.name.idup, document.rawText, 208 params.location, params.insertOutermost); 209 } 210 211 @protocolMethod("served/updateImports") 212 bool updateImports(UpdateImportsParams params) 213 { 214 auto instance = activeInstance; 215 bool success; 216 217 reportProgress(params.reportProgress, ProgressType.dubReload, 0, 5, instance.cwd.uriFromFile); 218 219 if (instance.has!DubComponent) 220 { 221 success = instance.get!DubComponent.update.getYield; 222 if (success) 223 rpc.notifyMethod("coded/updateDubTree"); 224 } 225 reportProgress(params.reportProgress, ProgressType.importReload, 4, 5, instance.cwd.uriFromFile); 226 if (instance.has!DCDComponent) 227 instance.get!DCDComponent.refreshImports(); 228 reportProgress(params.reportProgress, ProgressType.importReload, 5, 5, instance.cwd.uriFromFile); 229 return success; 230 } 231 232 @protocolNotification("textDocument/didSave") 233 void onDidSaveDubRecipe(DidSaveTextDocumentParams params) 234 { 235 auto fileName = params.textDocument.uri.uriToFile.baseName; 236 if (!fileName.among!("dub.json", "dub.sdl")) 237 return; 238 239 auto workspaceUri = workspace(params.textDocument.uri).folder.uri; 240 auto workspaceRoot = workspaceUri.uriToFile; 241 242 info("Updating dependencies"); 243 reportProgress(ProgressType.importUpgrades, 0, 10, workspaceUri); 244 if (!backend.has!DubComponent(workspaceRoot)) 245 { 246 Exception err; 247 const success = backend.attach(backend.getInstance(workspaceRoot), "dub", err); 248 if (!success) 249 { 250 rpc.window.showMessage(MessageType.error, translate!"d.ext.dubUpgradeFail"); 251 error(err); 252 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 253 return; 254 } 255 } 256 else 257 { 258 if (backend.get!DubComponent(workspaceRoot).isRunning) 259 { 260 string syntaxCheck = backend.get!DubComponent(workspaceRoot) 261 .validateRecipeSyntaxOnFileSystem(); 262 263 if (syntaxCheck.length) 264 { 265 rpc.window.showMessage(MessageType.error, 266 translate!"d.ext.dubInvalidRecipeSyntax"(syntaxCheck)); 267 error(syntaxCheck); 268 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 269 return; 270 } 271 272 rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 273 .upgrade(), MessageType.warning, translate!"d.ext.dubUpgradeFail"); 274 } 275 else 276 { 277 rpc.window.showMessage(MessageType.error, translate!"d.ext.dubUpgradeFail"); 278 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 279 return; 280 } 281 } 282 reportProgress(ProgressType.importUpgrades, 6, 10, workspaceUri); 283 284 setTimeout({ 285 const successfulUpdate = rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 286 .updateImportPaths(true), MessageType.warning, translate!"d.ext.dubImportFail"); 287 if (successfulUpdate) 288 { 289 rpc.window.runOrMessage(updateImports(UpdateImportsParams(false)), 290 MessageType.warning, translate!"d.ext.dubImportFail"); 291 } 292 else 293 { 294 try 295 { 296 updateImports(UpdateImportsParams(false)); 297 } 298 catch (Exception e) 299 { 300 errorf("Failed updating imports: %s", e); 301 } 302 } 303 reportProgress(ProgressType.importUpgrades, 10, 10, workspaceUri); 304 }, 200.msecs); 305 306 setTimeout({ 307 if (!backend.get!DubComponent(workspaceRoot).isRunning) 308 { 309 Exception err; 310 if (backend.attach(backend.getInstance(workspaceRoot), "dub", err)) 311 { 312 rpc.window.runOrMessage(backend.get!DubComponent(workspaceRoot) 313 .updateImportPaths(true), MessageType.warning, 314 translate!"d.ext.dubRecipeMaybeBroken"); 315 error(err); 316 } 317 } 318 }, 500.msecs); 319 rpc.notifyMethod("coded/updateDubTree"); 320 } 321 322 @protocolMethod("served/listDependencies") 323 DubDependency[] listDependencies(ListDependenciesParams params) 324 { 325 auto instance = activeInstance; 326 DubDependency[] ret; 327 if (!instance.has!DubComponent) 328 return ret; 329 330 auto allDeps = instance.get!DubComponent.dependencies; 331 if (!params.packageName.length) 332 { 333 auto deps = instance.get!DubComponent.rootDependencies; 334 foreach (dep; deps) 335 { 336 DubDependency r; 337 r.name = dep; 338 r.root = true; 339 foreach (other; allDeps) 340 if (other.name == dep) 341 { 342 r.version_ = other.ver; 343 r.path = other.path; 344 r.description = other.description; 345 r.homepage = other.homepage; 346 r.authors = other.authors; 347 r.copyright = other.copyright; 348 r.license = other.license; 349 r.subPackages = other.subPackages.map!"a.name".array; 350 r.hasDependencies = other.dependencies.length > 0; 351 break; 352 } 353 ret ~= r; 354 } 355 } 356 else 357 { 358 string[string] aa; 359 foreach (other; allDeps) 360 if (other.name == params.packageName) 361 { 362 aa = other.dependencies; 363 break; 364 } 365 foreach (name, ver; aa) 366 { 367 DubDependency r; 368 r.name = name; 369 r.version_ = ver; 370 foreach (other; allDeps) 371 if (other.name == name) 372 { 373 r.path = other.path; 374 r.description = other.description; 375 r.homepage = other.homepage; 376 r.authors = other.authors; 377 r.copyright = other.copyright; 378 r.license = other.license; 379 r.subPackages = other.subPackages.map!"a.name".array; 380 r.hasDependencies = other.dependencies.length > 0; 381 break; 382 } 383 ret ~= r; 384 } 385 } 386 return ret; 387 } 388 389 private string[] fixEmptyArgs(string[] args) 390 { 391 return args.remove!(a => a.endsWith('=')); 392 } 393 394 __gshared bool useBuildTaskDollarCurrent = false; 395 @protocolMethod("served/buildTasks") 396 Task[] provideBuildTasks() 397 { 398 Task[] ret; 399 foreach (instance; backend.instances) 400 { 401 if (!instance.has!DubComponent) 402 continue; 403 auto dub = instance.get!DubComponent; 404 auto workspace = .workspace(instance.cwd.uriFromFile, false); 405 info("Found dub package to build at ", dub.recipePath); 406 407 JsonValue dollarMagicValue; 408 if (useBuildTaskDollarCurrent) 409 dollarMagicValue = JsonValue("$current"); 410 411 JsonValue currentValue(string prop)() 412 { 413 if (useBuildTaskDollarCurrent) 414 return JsonValue("$current"); 415 else 416 return JsonValue(__traits(getMember, dub, prop)); 417 } 418 419 auto cwd = JsonValue(dub.recipePath.dirName.replace(workspace.folder.uri.uriToFile, "${workspaceFolder}")); 420 { 421 Task t; 422 t.source = "dub"; 423 t.definition = JsonValue([ 424 "type": JsonValue("dub"), 425 "run": JsonValue(true), 426 "compiler": currentValue!"compiler", 427 "archType": currentValue!"archType", 428 "buildType": currentValue!"buildType", 429 "configuration": currentValue!"configuration", 430 "cwd": cwd 431 ]); 432 t.group = Task.Group.build; 433 t.exec = [ 434 workspace.config.d.dubPath.userPath, "run", "--compiler=" ~ dub.compiler, 435 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 436 ].fixEmptyArgs; 437 t.scope_ = workspace.folder.uri; 438 t.name = "Run " ~ dub.name; 439 ret ~= t; 440 } 441 { 442 Task t; 443 t.source = "dub"; 444 t.definition = JsonValue([ 445 "type": JsonValue("dub"), 446 "test": JsonValue(true), 447 "compiler": currentValue!"compiler", 448 "archType": currentValue!"archType", 449 "buildType": currentValue!"buildType", 450 "configuration": currentValue!"configuration", 451 "cwd": cwd 452 ]); 453 t.group = Task.Group.test; 454 t.exec = [ 455 workspace.config.d.dubPath.userPath, "test", "--compiler=" ~ dub.compiler, 456 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 457 ].fixEmptyArgs; 458 t.scope_ = workspace.folder.uri; 459 t.name = "Test " ~ dub.name; 460 ret ~= t; 461 } 462 { 463 Task t; 464 t.source = "dub"; 465 t.definition = JsonValue([ 466 "type": JsonValue("dub"), 467 "run": JsonValue(false), 468 "compiler": currentValue!"compiler", 469 "archType": currentValue!"archType", 470 "buildType": currentValue!"buildType", 471 "configuration": currentValue!"configuration", 472 "cwd": cwd 473 ]); 474 t.group = Task.Group.build; 475 t.exec = [ 476 workspace.config.d.dubPath.userPath, "build", "--compiler=" ~ dub.compiler, 477 "-a=" ~ dub.archType, "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 478 ].fixEmptyArgs; 479 t.scope_ = workspace.folder.uri; 480 t.name = "Build " ~ dub.name; 481 ret ~= t; 482 } 483 { 484 Task t; 485 t.source = "dub"; 486 t.definition = JsonValue([ 487 "type": JsonValue("dub"), 488 "run": JsonValue(false), 489 "force": JsonValue(true), 490 "compiler": currentValue!"compiler", 491 "archType": currentValue!"archType", 492 "buildType": currentValue!"buildType", 493 "configuration": currentValue!"configuration", 494 "cwd": cwd 495 ]); 496 t.group = Task.Group.rebuild; 497 t.exec = [ 498 workspace.config.d.dubPath.userPath, "build", "--force", 499 "--compiler=" ~ dub.compiler, "-a=" ~ dub.archType, 500 "-b=" ~ dub.buildType, "-c=" ~ dub.configuration 501 ].fixEmptyArgs; 502 t.scope_ = workspace.folder.uri; 503 t.name = "Rebuild " ~ dub.name; 504 ret ~= t; 505 } 506 } 507 return ret; 508 } 509 510 // === Protocol Notifications starting here === 511 512 @protocolNotification("served/convertDubFormat") 513 void convertDubFormat(DubConvertRequest req) 514 { 515 import std.process : execute, Config; 516 517 auto file = req.textDocument.uri.uriToFile; 518 if (!fs.exists(file)) 519 { 520 error("Specified file does not exist"); 521 return; 522 } 523 524 if (!file.baseName.among!("dub.json", "dub.sdl", "package.json")) 525 { 526 rpc.window.showErrorMessage(translate!"d.dub.notRecipeFile"); 527 return; 528 } 529 530 auto document = documents[req.textDocument.uri]; 531 532 auto result = execute([ 533 workspace(req.textDocument.uri).config.d.dubPath.userPath, "convert", 534 "-f", req.newFormat, "-s" 535 ], null, Config.stderrPassThrough, 1024 * 1024 * 4, file.dirName); 536 537 if (result.status != 0) 538 { 539 rpc.window.showErrorMessage(translate!"d.dub.convertFailed"); 540 return; 541 } 542 543 auto newUri = req.textDocument.uri.setExtension("." ~ req.newFormat); 544 545 WorkspaceEdit edit; 546 auto edits = [ 547 TextEdit(TextRange(Position(0, 0), document.offsetToPosition(document.length)), result.output) 548 ]; 549 550 if (capabilities 551 .workspace.orDefault 552 .workspaceEdit.orDefault 553 .resourceOperations.orDefault 554 .canFind(ResourceOperationKind.rename)) 555 { 556 edit.documentChanges = [ 557 DocumentChange(RenameFile(req.textDocument.uri, newUri)), 558 DocumentChange(TextDocumentEdit( 559 VersionedTextDocumentIdentifier(newUri, document.version_), 560 edits 561 )) 562 ]; 563 } 564 else 565 edit.changes[req.textDocument.uri] = edits; 566 567 ApplyWorkspaceEditParams params = { 568 edit: edit 569 }; 570 rpc.sendMethod("workspace/applyEdit", params); 571 } 572 573 @protocolNotification("served/installDependency") 574 void installDependency(InstallRequest req) 575 { 576 auto instance = activeInstance; 577 auto uri = instance.cwd.uriFromFile; 578 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 579 injectDependency(instance, req); 580 if (instance.has!DubComponent) 581 instance.get!DubComponent.upgrade(); 582 reportProgress(ProgressType.dubReload, 7, 10, uri); 583 updateImports(UpdateImportsParams(false)); 584 reportProgress(ProgressType.dubReload, 10, 10, uri); 585 } 586 587 @protocolNotification("served/updateDependency") 588 void updateDependency(UpdateRequest req) 589 { 590 auto instance = activeInstance; 591 auto uri = instance.cwd.uriFromFile; 592 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 593 if (changeDependency(instance, req)) 594 { 595 if (instance.has!DubComponent) 596 instance.get!DubComponent.upgrade(); 597 reportProgress(ProgressType.dubReload, 7, 10, uri); 598 updateImports(UpdateImportsParams(false)); 599 } 600 reportProgress(ProgressType.dubReload, 10, 10, uri); 601 } 602 603 @protocolNotification("served/uninstallDependency") 604 void uninstallDependency(UninstallRequest req) 605 { 606 auto instance = activeInstance; 607 auto uri = instance.cwd.uriFromFile; 608 reportProgress(ProgressType.importUpgrades, 0, 10, uri); 609 // TODO: add workspace argument 610 removeDependency(instance, req.name); 611 if (instance.has!DubComponent) 612 instance.get!DubComponent.upgrade(); 613 reportProgress(ProgressType.dubReload, 7, 10, uri); 614 updateImports(UpdateImportsParams(false)); 615 reportProgress(ProgressType.dubReload, 10, 10, uri); 616 } 617 618 void injectDependency(WorkspaceD.Instance instance, InstallRequest req) 619 { 620 auto sdl = buildPath(instance.cwd, "dub.sdl"); 621 if (fs.exists(sdl)) 622 { 623 int depth = 0; 624 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 625 auto insertAt = content.length; 626 bool gotLineEnding = false; 627 string lineEnding = "\n"; 628 foreach (i, line; content) 629 { 630 if (!gotLineEnding && line.length >= 2) 631 { 632 lineEnding = line[$ - 2 .. $]; 633 if (lineEnding[0] != '\r') 634 lineEnding = line[$ - 1 .. $]; 635 gotLineEnding = true; 636 } 637 if (depth == 0 && line.strip.startsWith("dependency ")) 638 insertAt = i + 1; 639 depth += line.count('{') - line.count('}'); 640 } 641 content = content[0 .. insertAt] ~ ((insertAt == content.length ? lineEnding 642 : "") ~ "dependency \"" ~ req.name ~ "\" version=\"~>" ~ req.version_ ~ "\"" ~ lineEnding) 643 ~ content[insertAt .. $]; 644 fs.write(sdl, content.join()); 645 } 646 else 647 { 648 auto json = buildPath(instance.cwd, "dub.json"); 649 if (!fs.exists(json)) 650 json = buildPath(instance.cwd, "package.json"); 651 if (!fs.exists(json)) 652 return; 653 auto content = fs.readText(json).splitLines(KeepTerminator.yes); 654 auto insertAt = content.length ? content.length - 1 : 0; 655 string lineEnding = "\n"; 656 bool gotLineEnding = false; 657 int depth = 0; 658 bool insertNext; 659 string indent; 660 bool foundBlock; 661 foreach (i, line; content) 662 { 663 if (!gotLineEnding && line.length >= 2) 664 { 665 lineEnding = line[$ - 2 .. $]; 666 if (lineEnding[0] != '\r') 667 lineEnding = line[$ - 1 .. $]; 668 gotLineEnding = true; 669 } 670 if (insertNext) 671 { 672 indent = line[0 .. $ - line.stripLeft.length]; 673 insertAt = i + 1; 674 break; 675 } 676 if (depth == 1 && line.strip.startsWith(`"dependencies":`)) 677 { 678 foundBlock = true; 679 if (line.strip.endsWith("{")) 680 { 681 indent = line[0 .. $ - line.stripLeft.length]; 682 insertAt = i + 1; 683 break; 684 } 685 else 686 { 687 insertNext = true; 688 } 689 } 690 depth += line.count('{') - line.count('}') + line.count('[') - line.count(']'); 691 } 692 if (foundBlock) 693 { 694 content = content[0 .. insertAt] ~ ( 695 indent ~ indent ~ `"` ~ req.name ~ `": "~>` ~ req.version_ ~ `",` ~ lineEnding) 696 ~ content[insertAt .. $]; 697 fs.write(json, content.join()); 698 } 699 else if (content.length) 700 { 701 if (content.length > 1) 702 content[$ - 2] = content[$ - 2].stripRight; 703 content = content[0 .. $ - 1] ~ ( 704 "," ~ lineEnding ~ ` "dependencies": { 705 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 706 }` ~ lineEnding) 707 ~ content[$ - 1 .. $]; 708 fs.write(json, content.join()); 709 } 710 else 711 { 712 content ~= `{ 713 "dependencies": { 714 "` ~ req.name ~ `": "~>` ~ req.version_ ~ `" 715 } 716 }`; 717 fs.write(json, content.join()); 718 } 719 } 720 } 721 722 bool changeDependency(WorkspaceD.Instance instance, UpdateRequest req) 723 { 724 auto sdl = buildPath(instance.cwd, "dub.sdl"); 725 if (fs.exists(sdl)) 726 { 727 int depth = 0; 728 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 729 size_t target = size_t.max; 730 foreach (i, line; content) 731 { 732 if (depth == 0 && line.strip.startsWith("dependency ") 733 && line.strip["dependency".length .. $].strip.startsWith('"' ~ req.name ~ '"')) 734 { 735 target = i; 736 break; 737 } 738 depth += line.count('{') - line.count('}'); 739 } 740 if (target == size_t.max) 741 return false; 742 auto ver = content[target].indexOf("version"); 743 if (ver == -1) 744 return false; 745 auto quotStart = content[target].indexOf("\"", ver); 746 if (quotStart == -1) 747 return false; 748 auto quotEnd = content[target].indexOf("\"", quotStart + 1); 749 if (quotEnd == -1) 750 return false; 751 content[target] = content[target][0 .. quotStart] ~ '"' ~ req.version_ ~ '"' 752 ~ content[target][quotEnd .. $]; 753 fs.write(sdl, content.join()); 754 return true; 755 } 756 else 757 { 758 auto json = buildPath(instance.cwd, "dub.json"); 759 if (!fs.exists(json)) 760 json = buildPath(instance.cwd, "package.json"); 761 if (!fs.exists(json)) 762 return false; 763 auto content = fs.readText(json); 764 auto replaced = content.replaceFirst(regex(`("` ~ req.name ~ `"\s*:\s*)"[^"]*"`), 765 `$1"` ~ req.version_ ~ `"`); 766 if (content == replaced) 767 return false; 768 fs.write(json, replaced); 769 return true; 770 } 771 } 772 773 bool removeDependency(WorkspaceD.Instance instance, string name) 774 { 775 auto sdl = buildPath(instance.cwd, "dub.sdl"); 776 if (fs.exists(sdl)) 777 { 778 int depth = 0; 779 auto content = fs.readText(sdl).splitLines(KeepTerminator.yes); 780 size_t target = size_t.max; 781 foreach (i, line; content) 782 { 783 if (depth == 0 && line.strip.startsWith("dependency ") 784 && line.strip["dependency".length .. $].strip.startsWith('"' ~ name ~ '"')) 785 { 786 target = i; 787 break; 788 } 789 depth += line.count('{') - line.count('}'); 790 } 791 if (target == size_t.max) 792 return false; 793 fs.write(sdl, (content[0 .. target] ~ content[target + 1 .. $]).join()); 794 return true; 795 } 796 else 797 { 798 auto json = buildPath(instance.cwd, "dub.json"); 799 if (!fs.exists(json)) 800 json = buildPath(instance.cwd, "package.json"); 801 if (!fs.exists(json)) 802 return false; 803 auto content = fs.readText(json); 804 auto replaced = content.replaceFirst(regex(`"` ~ name ~ `"\s*:\s*"[^"]*"\s*,\s*`), ""); 805 if (content == replaced) 806 replaced = content.replaceFirst(regex(`\s*,\s*"` ~ name ~ `"\s*:\s*"[^"]*"`), ""); 807 if (content == replaced) 808 replaced = content.replaceFirst(regex( 809 `"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}\s*,\s*`), ""); 810 if (content == replaced) 811 replaced = content.replaceFirst(regex( 812 `\s*,\s*"dependencies"\s*:\s*\{\s*"` ~ name ~ `"\s*:\s*"[^"]*"\s*\}`), ""); 813 if (content == replaced) 814 return false; 815 fs.write(json, replaced); 816 return true; 817 } 818 }