1 module workspaced.com.dcdext; 2 3 import dparse.ast; 4 import dparse.lexer; 5 import dparse.parser; 6 import dparse.rollback_allocator; 7 8 import core.thread; 9 10 import std.algorithm; 11 import std.array; 12 import std.ascii; 13 import std.file; 14 import std.functional; 15 import std.json; 16 import std.range; 17 import std..string; 18 19 import workspaced.api; 20 import workspaced.dparseext; 21 import workspaced.com.dcd; 22 23 import workspaced.visitors.classifier; 24 import workspaced.visitors.methodfinder; 25 26 @component("dcdext") 27 class DCDExtComponent : ComponentWrapper 28 { 29 mixin DefaultComponentWrapper; 30 31 static immutable CodeRegionProtection[] mixableProtection = [ 32 CodeRegionProtection.public_ | CodeRegionProtection.default_, 33 CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier, 34 CodeRegionProtection.protected_, CodeRegionProtection.private_ 35 ]; 36 37 /// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}` 38 void load() 39 { 40 if (!refInstance) 41 return; 42 43 config.stringBehavior = StringBehavior.source; 44 } 45 46 /// Extracts calltips help information at a given position. 47 /// The position must be within the arguments of the function and not 48 /// outside the parentheses or inside some child call. 49 /// When generating the call parameters for a function definition, the position must be inside the normal parameters, 50 /// otherwise the template arguments will be put as normal arguments. 51 /// Returns: the position of significant locations for parameter extraction. 52 /// Params: 53 /// code = code to analyze 54 /// position = byte offset where to check for function arguments 55 /// definition = true if this hints is a function definition (templates don't have an exclamation point '!') 56 CalltipsSupport extractCallParameters(string code, int position, bool definition = false) 57 { 58 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 59 auto queuedToken = tokens.countUntil!(a => a.index >= position) - 1; 60 if (queuedToken == -2) 61 queuedToken = cast(ptrdiff_t) tokens.length - 1; 62 else if (queuedToken == -1) 63 return CalltipsSupport.init; 64 65 bool inTemplate; 66 int depth, subDepth; 67 // contains opening parentheses location for arguments or exclamation point for templates. 68 auto startParen = queuedToken; 69 while (startParen >= 0) 70 { 71 const c = tokens[startParen]; 72 const p = startParen > 0 ? tokens[startParen - 1] : Token.init; 73 74 if (c.type == tok!"{") 75 { 76 if (subDepth == 0) 77 { 78 // we went too far, probably not inside a function (or we are in a delegate, where we don't want calltips) 79 return CalltipsSupport.init; 80 } 81 else 82 subDepth--; 83 } 84 else if (c.type == tok!"}") 85 { 86 subDepth++; 87 } 88 else if (depth == 0 && !definition && c.type == tok!"!" && p.type == tok!"identifier") 89 { 90 inTemplate = true; 91 break; 92 } 93 else if (c.type == tok!")") 94 { 95 depth++; 96 } 97 else if (c.type == tok!"(") 98 { 99 if (depth == 0 && subDepth == 0) 100 { 101 if (startParen > 1 && p.type == tok!"!" && tokens[startParen - 2].type 102 == tok!"identifier") 103 { 104 startParen--; 105 inTemplate = true; 106 } 107 break; 108 } 109 else 110 depth--; 111 } 112 startParen--; 113 } 114 115 if (startParen <= 0) 116 return CalltipsSupport.init; 117 118 auto templateOpen = inTemplate ? startParen : 0; 119 auto functionOpen = inTemplate ? 0 : startParen; 120 121 if (inTemplate) 122 { 123 // go forwards to function arguments 124 if (templateOpen + 2 < tokens.length && tokens[templateOpen + 1].type != tok!"(") 125 { 126 // single template arg (can only be one token) 127 // https://dlang.org/spec/grammar.html#TemplateSingleArgument 128 if (tokens[templateOpen + 2] == tok!"(") 129 functionOpen = templateOpen + 2; 130 } 131 else 132 { 133 functionOpen = findClosingParenForward(tokens, templateOpen + 2); 134 135 if (functionOpen >= tokens.length) 136 functionOpen = 0; 137 } 138 } 139 else 140 { 141 // go backwards to template arguments 142 if (functionOpen > 0 && tokens[functionOpen - 1].type == tok!")") 143 { 144 // multi template args 145 depth = 0; 146 subDepth = 0; 147 templateOpen = functionOpen - 1; 148 while (templateOpen >= 1) 149 { 150 const c = tokens[templateOpen]; 151 152 if (c == tok!")") 153 depth++; 154 else 155 { 156 if (depth == 0 && templateOpen > 2 && c == tok!"(" && (definition 157 || (tokens[templateOpen - 1].type == tok!"!" 158 && tokens[templateOpen - 2].type == tok!"identifier"))) 159 break; 160 else if (depth == 0) 161 { 162 templateOpen = 0; 163 break; 164 } 165 166 if (c == tok!"(") 167 depth--; 168 } 169 170 templateOpen--; 171 } 172 173 if (templateOpen <= 1) 174 templateOpen = 0; 175 } 176 else 177 { 178 // single template arg (can only be one token) 179 if (functionOpen <= 2) 180 return CalltipsSupport.init; 181 182 if (tokens[functionOpen - 2] == tok!"!" && tokens[functionOpen - 3] == tok!"identifier") 183 { 184 templateOpen = functionOpen - 2; 185 } 186 } 187 } 188 189 bool hasTemplateParens = templateOpen && templateOpen == functionOpen - 2; 190 191 depth = 0; 192 subDepth = 0; 193 bool inFuncName = true; 194 auto callStart = (templateOpen ? templateOpen : functionOpen) - 1; 195 auto funcNameStart = callStart; 196 while (callStart >= 0) 197 { 198 const c = tokens[callStart]; 199 const p = callStart > 0 ? tokens[callStart - 1] : Token.init; 200 201 if (c.type == tok!"]") 202 depth++; 203 else if (c.type == tok!"[") 204 { 205 if (depth == 0) 206 { 207 // this is some sort of `foo[(4` situation 208 return CalltipsSupport.init; 209 } 210 depth--; 211 } 212 else if (c.type == tok!")") 213 subDepth++; 214 else if (c.type == tok!"(") 215 { 216 if (subDepth == 0) 217 { 218 // this is some sort of `foo((4` situation 219 return CalltipsSupport.init; 220 } 221 subDepth--; 222 } 223 else if (depth == 0) 224 { 225 226 if (c.type.isCalltipable) 227 { 228 if (c.type == tok!"identifier" && p.type == tok!"." && (callStart < 2 229 || !tokens[callStart - 2].type.among!(tok!";", tok!",", 230 tok!"{", tok!"}", tok!"("))) 231 { 232 // member function, traverse further... 233 if (inFuncName) 234 { 235 funcNameStart = callStart; 236 inFuncName = false; 237 } 238 callStart--; 239 } 240 else 241 { 242 break; 243 } 244 } 245 else 246 { 247 // this is some sort of `4(5` or `if(4` situtation 248 return CalltipsSupport.init; 249 } 250 } 251 // we ignore stuff inside brackets and parens such as `foo[4](5).bar[6](a` 252 callStart--; 253 } 254 255 if (inFuncName) 256 funcNameStart = callStart; 257 258 auto templateClose = templateOpen ? (hasTemplateParens ? (functionOpen 259 ? functionOpen - 1 : findClosingParenForward(tokens, templateOpen + 1)) : templateOpen + 2) 260 : 0; 261 auto functionClose = functionOpen ? findClosingParenForward(tokens, functionOpen) : 0; 262 263 CalltipsSupport.Argument[] templateArgs; 264 if (templateOpen) 265 templateArgs = splitArgs(tokens[templateOpen + 1 .. templateClose]); 266 267 CalltipsSupport.Argument[] functionArgs; 268 if (functionOpen) 269 functionArgs = splitArgs(tokens[functionOpen + 1 .. functionClose]); 270 271 return CalltipsSupport([ 272 tokens.tokenIndex(templateOpen), tokens.tokenIndex(templateClose) 273 ], hasTemplateParens, templateArgs, [ 274 tokens.tokenIndex(functionOpen), tokens.tokenIndex(functionClose) 275 ], functionArgs, funcNameStart != callStart, 276 tokens.tokenIndex(funcNameStart), tokens.tokenIndex(callStart)); 277 } 278 279 /// Finds the immediate surrounding code block at a position or returns CodeBlockInfo.init for none/module block. 280 /// See_Also: CodeBlockInfo 281 CodeBlockInfo getCodeBlockRange(string code, int position) 282 { 283 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 284 auto parsed = parseModule(tokens, "getCodeBlockRange_input.d", &rba); 285 auto reader = new CodeBlockInfoFinder(position); 286 reader.visit(parsed); 287 return reader.block; 288 } 289 290 /// Inserts a generic method after the corresponding block inside the scope where position is. 291 /// If it can't find a good spot it will insert the code properly indented ata fitting location. 292 // make public once usable 293 private CodeReplacement[] insertCodeInContainer(string insert, string code, 294 int position, bool insertInLastBlock = true, bool insertAtEnd = true) 295 { 296 auto container = getCodeBlockRange(code, position); 297 298 string codeBlock = code[container.innerRange[0] .. container.innerRange[1]]; 299 300 scope tokensInsert = getTokensForParser(cast(ubyte[]) insert, config, 301 &workspaced.stringCache); 302 scope parsedInsert = parseModule(tokensInsert, "insertCode_insert.d", &rba); 303 304 scope insertReader = new CodeDefinitionClassifier(insert); 305 insertReader.visit(parsedInsert); 306 scope insertRegions = insertReader.regions.sort!"a.type < b.type".uniq.array; 307 308 scope tokens = getTokensForParser(cast(ubyte[]) codeBlock, config, &workspaced.stringCache); 309 scope parsed = parseModule(tokens, "insertCode_code.d", &rba); 310 311 scope reader = new CodeDefinitionClassifier(codeBlock); 312 reader.visit(parsed); 313 scope regions = reader.regions; 314 315 CodeReplacement[] ret; 316 317 foreach (CodeDefinitionClassifier.Region toInsert; insertRegions) 318 { 319 auto insertCode = insert[toInsert.region[0] .. toInsert.region[1]]; 320 scope existing = regions.enumerate.filter!(a => a.value.sameBlockAs(toInsert)); 321 if (existing.empty) 322 { 323 const checkProtection = CodeRegionProtection.init.reduce!"a | b"( 324 mixableProtection.filter!(a => (a & toInsert.protection) != 0)); 325 326 bool inIncompatible = false; 327 bool lastFit = false; 328 int fittingProtection = -1; 329 int firstStickyProtection = -1; 330 int regionAfterFitting = -1; 331 foreach (i, stickyProtection; regions) 332 { 333 if (stickyProtection.affectsFollowing 334 && stickyProtection.protection != CodeRegionProtection.init) 335 { 336 if (firstStickyProtection == -1) 337 firstStickyProtection = cast(int) i; 338 339 if ((stickyProtection.protection & checkProtection) != 0) 340 { 341 fittingProtection = cast(int) i; 342 lastFit = true; 343 if (!insertInLastBlock) 344 break; 345 } 346 else 347 { 348 if (lastFit) 349 { 350 regionAfterFitting = cast(int) i; 351 lastFit = false; 352 } 353 inIncompatible = true; 354 } 355 } 356 } 357 assert(firstStickyProtection != -1 || !inIncompatible); 358 assert(regionAfterFitting != -1 || fittingProtection == -1 || !inIncompatible); 359 360 if (inIncompatible) 361 { 362 int insertRegion = fittingProtection == -1 ? firstStickyProtection : regionAfterFitting; 363 insertCode = indent(insertCode, regions[insertRegion].minIndentation) ~ "\n\n"; 364 auto len = cast(uint) insertCode.length; 365 366 toInsert.region[0] = regions[insertRegion].region[0]; 367 toInsert.region[1] = regions[insertRegion].region[0] + len; 368 foreach (ref r; regions[insertRegion .. $]) 369 { 370 r.region[0] += len; 371 r.region[1] += len; 372 } 373 } 374 else 375 { 376 auto lastRegion = regions.back; 377 insertCode = indent(insertCode, lastRegion.minIndentation); 378 auto len = cast(uint) insertCode.length; 379 toInsert.region[0] = lastRegion.region[1]; 380 toInsert.region[1] = lastRegion.region[1] + len; 381 } 382 regions ~= toInsert; 383 ret ~= CodeReplacement([toInsert.region[0], toInsert.region[0]], insertCode); 384 } 385 else 386 { 387 auto target = insertInLastBlock ? existing.tail(1).front : existing.front; 388 389 insertCode = "\n\n" ~ indent(insertCode, regions[target.index].minIndentation); 390 const codeLength = cast(int) insertCode.length; 391 392 if (insertAtEnd) 393 { 394 ret ~= CodeReplacement([ 395 target.value.region[1], target.value.region[1] 396 ], insertCode); 397 toInsert.region[0] = target.value.region[1]; 398 toInsert.region[1] = target.value.region[1] + codeLength; 399 regions[target.index].region[1] = toInsert.region[1]; 400 foreach (ref other; regions[target.index + 1 .. $]) 401 { 402 other.region[0] += codeLength; 403 other.region[1] += codeLength; 404 } 405 } 406 else 407 { 408 ret ~= CodeReplacement([ 409 target.value.region[0], target.value.region[0] 410 ], insertCode); 411 regions[target.index].region[1] += codeLength; 412 foreach (ref other; regions[target.index + 1 .. $]) 413 { 414 other.region[0] += codeLength; 415 other.region[1] += codeLength; 416 } 417 } 418 } 419 } 420 421 return ret; 422 } 423 424 /// Implements the interfaces or abstract classes of a specified class/interface. 425 Future!string implement(string code, int position) 426 { 427 auto ret = new Future!string; 428 threads.create({ 429 try 430 { 431 struct InterfaceTree 432 { 433 InterfaceDetails details; 434 InterfaceTree[] inherits; 435 } 436 437 auto baseInterface = getInterfaceDetails("stdin", code, position); 438 439 string[] implementedMethods = baseInterface.methods 440 .filter!"!a.needsImplementation" 441 .map!"a.identifier" 442 .array; 443 444 // start with private, add all the public ones later in traverseTree 445 FieldDetails[] availableVariables = baseInterface.fields.filter!"a.isPrivate".array; 446 InterfaceTree tree = InterfaceTree(baseInterface); 447 448 InterfaceTree* treeByName(InterfaceTree* tree, string name) 449 { 450 if (tree.details.name == name) 451 return tree; 452 foreach (ref parent; tree.inherits) 453 { 454 InterfaceTree* t = treeByName(&parent, name); 455 if (t !is null) 456 return t; 457 } 458 return null; 459 } 460 461 void traverseTree(ref InterfaceTree sub) 462 { 463 availableVariables ~= sub.details.fields.filter!"!a.isPrivate".array; 464 foreach (i, parent; sub.details.parentPositions) 465 { 466 string parentName = sub.details.normalizedParents[i]; 467 if (treeByName(&tree, parentName) is null) 468 { 469 auto details = lookupInterface(sub.details.code, parent); 470 sub.inherits ~= InterfaceTree(details); 471 } 472 } 473 foreach (ref inherit; sub.inherits) 474 traverseTree(inherit); 475 } 476 477 traverseTree(tree); 478 479 string changes; 480 void processTree(ref InterfaceTree tree) 481 { 482 auto details = tree.details; 483 if (details.methods.length) 484 { 485 bool first = true; 486 foreach (fn; details.methods) 487 { 488 if (implementedMethods.canFind(fn.identifier)) 489 continue; 490 if (!fn.needsImplementation) 491 { 492 implementedMethods ~= fn.identifier; 493 continue; 494 } 495 if (first) 496 { 497 changes ~= "// implement " ~ details.name ~ "\n\n"; 498 first = false; 499 } 500 if (details.needsOverride) 501 changes ~= "override "; 502 changes ~= fn.signature[0 .. $ - 1]; 503 changes ~= " {"; 504 if (fn.optionalImplementation) 505 { 506 changes ~= "\n\t// TODO: optional implementation\n"; 507 } 508 509 string propertySearch; 510 if (fn.signature.canFind("@property") && fn.arguments.length <= 1) 511 propertySearch = fn.name; 512 else if ((fn.name.startsWith("get") && fn.arguments.length == 0) 513 || (fn.name.startsWith("set") && fn.arguments.length == 1)) 514 propertySearch = fn.name[3 .. $]; 515 516 string foundProperty; 517 if (propertySearch) 518 { 519 foreach (variable; availableVariables) 520 { 521 if (fieldNameMatches(variable.name, propertySearch)) 522 { 523 foundProperty = variable.name; 524 break; 525 } 526 } 527 } 528 529 if (foundProperty.length) 530 { 531 changes ~= "\n\t"; 532 if (fn.returnType != "void") 533 changes ~= "return "; 534 if (fn.name.startsWith("set") || fn.arguments.length == 1) 535 changes ~= foundProperty ~ " = " ~ fn.arguments[0].name; 536 else 537 changes ~= foundProperty; 538 changes ~= ";\n"; 539 } 540 else if (fn.hasBody) 541 { 542 changes ~= "\n\t"; 543 if (fn.returnType != "void") 544 changes ~= "return "; 545 changes ~= "super." ~ fn.name; 546 if (fn.arguments.length) 547 changes ~= "(" ~ format("%(%s, %)", fn.arguments) ~ ")"; 548 else if (fn.returnType == "void") 549 changes ~= "()"; // make functions that don't return add (), otherwise they might be attributes and don't need that 550 changes ~= ";\n"; 551 } 552 else if (fn.returnType != "void") 553 { 554 changes ~= "\n\t"; 555 if (fn.isNothrowOrNogc) 556 { 557 if (fn.returnType.endsWith("[]")) 558 changes ~= "return null; // TODO: implement"; 559 else 560 changes ~= "return " ~ fn.returnType ~ ".init; // TODO: implement"; 561 } 562 else 563 changes ~= `assert(false, "Method ` ~ fn.name ~ ` not implemented");`; 564 changes ~= "\n"; 565 } 566 changes ~= "}\n\n"; 567 } 568 } 569 570 foreach (parent; tree.inherits) 571 processTree(parent); 572 } 573 574 processTree(tree); 575 576 ret.finish(changes); 577 } 578 catch (Throwable t) 579 { 580 ret.error(t); 581 } 582 }); 583 return ret; 584 } 585 586 private: 587 RollbackAllocator rba; 588 LexerConfig config; 589 590 InterfaceDetails lookupInterface(string code, int position) 591 { 592 auto data = get!DCDComponent.findDeclaration(code, position).getBlocking; 593 string file = data.file; 594 int newPosition = data.position; 595 596 if (!file.length) 597 return InterfaceDetails.init; 598 599 string newCode = code; 600 if (file != "stdin") 601 newCode = readText(file); 602 603 return getInterfaceDetails(file, newCode, newPosition); 604 } 605 606 InterfaceDetails getInterfaceDetails(string file, string code, int position) 607 { 608 auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache); 609 auto parsed = parseModule(tokens, file, &rba); 610 auto reader = new InterfaceMethodFinder(code, position); 611 reader.visit(parsed); 612 return reader.details; 613 } 614 } 615 616 /// 617 enum CodeRegionType : int 618 { 619 /// null region (unset) 620 init, 621 /// Imports inside the block 622 imports = 1 << 0, 623 /// Aliases `alias foo this;`, `alias Type = Other;` 624 aliases = 1 << 1, 625 /// Nested classes/structs/unions/etc. 626 types = 1 << 2, 627 /// Raw variables `Type name;` 628 fields = 1 << 3, 629 /// Normal constructors `this(Args args)` 630 ctor = 1 << 4, 631 /// Copy constructors `this(this)` 632 copyctor = 1 << 5, 633 /// Destructors `~this()` 634 dtor = 1 << 6, 635 /// Properties (functions annotated with `@property`) 636 properties = 1 << 7, 637 /// Regular functions 638 methods = 1 << 8, 639 } 640 641 /// 642 enum CodeRegionProtection : int 643 { 644 /// null protection (unset) 645 init, 646 /// default (unmarked) protection 647 default_ = 1 << 0, 648 /// public protection 649 public_ = 1 << 1, 650 /// package (automatic) protection 651 package_ = 1 << 2, 652 /// package (manual package name) protection 653 packageIdentifier = 1 << 3, 654 /// protected protection 655 protected_ = 1 << 4, 656 /// private protection 657 private_ = 1 << 5, 658 } 659 660 /// 661 enum CodeRegionStatic : int 662 { 663 /// null static (unset) 664 init, 665 /// non-static code 666 instanced = 1 << 0, 667 /// static code 668 static_ = 1 << 1, 669 } 670 671 /// Represents a class/interface/struct/union/template with body. 672 struct CodeBlockInfo 673 { 674 /// 675 enum Type : int 676 { 677 // keep the underlines in these values for range checking properly 678 679 /// 680 class_, 681 /// 682 interface_, 683 /// 684 struct_, 685 /// 686 union_, 687 /// 688 template_, 689 } 690 691 static immutable string[] typePrefixes = [ 692 "class ", "interface ", "struct ", "union ", "template " 693 ]; 694 695 /// 696 Type type; 697 /// 698 string name; 699 /// Outer range inside the code spanning curly braces and name but not type keyword. 700 uint[2] outerRange; 701 /// Inner range of body of the block touching, but not spanning curly braces. 702 uint[2] innerRange; 703 704 string prefix() @property 705 { 706 return typePrefixes[cast(int) type]; 707 } 708 } 709 710 /// 711 struct CalltipsSupport 712 { 713 /// 714 struct Argument 715 { 716 /// Ranges of type, name and value not including commas or parentheses, but being right next to them. For calls this is the only important and accurate value. 717 int[2] contentRange; 718 /// Range of just the type, or for templates also `alias` 719 int[2] typeRange; 720 /// Range of just the name 721 int[2] nameRange; 722 /// Range of just the default value 723 int[2] valueRange; 724 725 /// Creates Argument(range, range, range, 0) 726 static Argument templateType(int[2] range) 727 { 728 return Argument(range, range, range); 729 } 730 731 /// Creates Argument(range, 0, range, range) 732 static Argument templateValue(int[2] range) 733 { 734 return Argument(range, typeof(range).init, range, range); 735 } 736 } 737 738 bool hasTemplate() @property 739 { 740 return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init; 741 } 742 743 /// Range starting inclusive at exclamation point until exclusive at closing bracket or function opening bracket. 744 int[2] templateArgumentsRange; 745 /// 746 bool hasTemplateParens; 747 /// 748 Argument[] templateArgs; 749 /// Range starting inclusive at opening parentheses until exclusive at closing parentheses. 750 int[2] functionParensRange; 751 /// 752 Argument[] functionArgs; 753 /// True if the function is UFCS or a member function of some object or namespace. 754 /// False if this is a global function call. 755 bool hasParent; 756 /// Start of the function itself. 757 int functionStart; 758 /// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents) 759 int parentStart; 760 } 761 762 private: 763 764 bool isCalltipable(IdType type) 765 { 766 return type == tok!"identifier" || type == tok!"assert" || type == tok!"import" 767 || type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits"; 768 } 769 770 int[2] tokenRange(const Token token) 771 { 772 return [cast(int) token.index, cast(int)(token.index + token.text.length)]; 773 } 774 775 int tokenEnd(const Token token) 776 { 777 return cast(int)(token.index + token.text.length); 778 } 779 780 int tokenIndex(const(Token)[] tokens, ptrdiff_t i) 781 { 782 if (i > 0 && i == tokens.length) 783 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 784 return i >= 0 ? cast(int) tokens[i].index : 0; 785 } 786 787 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i) 788 { 789 if (i > 0 && i == tokens.length) 790 return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length); 791 return i >= 0 ? cast(int)(tokens[i].index + tokens[i].text.length) : 0; 792 } 793 794 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open) 795 in(tokens[open].type == tok!"(") 796 { 797 if (open >= tokens.length || open < 0) 798 return open; 799 800 open++; 801 802 int depth = 1; 803 int subDepth = 0; 804 while (open < tokens.length) 805 { 806 const c = tokens[open]; 807 808 if (c == tok!"(") 809 depth++; 810 else if (c == tok!"{") 811 subDepth++; 812 else if (c == tok!"}") 813 { 814 if (subDepth == 0) 815 break; 816 subDepth--; 817 } 818 else 819 { 820 if (c == tok!";" && subDepth == 0) 821 break; 822 823 if (c == tok!")") 824 depth--; 825 826 if (depth == 0) 827 break; 828 } 829 830 open++; 831 } 832 return open; 833 } 834 835 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens) 836 { 837 auto ret = appender!(CalltipsSupport.Argument[]); 838 size_t start = 0; 839 size_t valueStart = 0; 840 841 int depth, subDepth; 842 bool gotValue; 843 844 void putArg(size_t end) 845 { 846 if (start >= end || start >= tokens.length) 847 return; 848 849 CalltipsSupport.Argument arg; 850 851 auto typename = tokens[start .. end]; 852 arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 853 if (typename.length == 1) 854 { 855 auto t = typename[0]; 856 if (t.type == tok!"identifier" || t.type.isBasicType) 857 arg = CalltipsSupport.Argument.templateType(t.tokenRange); 858 else 859 arg = CalltipsSupport.Argument.templateValue(t.tokenRange); 860 } 861 else 862 { 863 if (gotValue && valueStart > start && valueStart <= end) 864 { 865 typename = tokens[start .. valueStart]; 866 auto val = tokens[valueStart .. end]; 867 if (val.length) 868 arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd]; 869 } 870 871 else if (typename.length == 1) 872 { 873 auto t = typename[0]; 874 if (t.type == tok!"identifier" || t.type.isBasicType) 875 arg.typeRange = arg.nameRange = t.tokenRange; 876 else 877 arg.typeRange = t.tokenRange; 878 } 879 else if (typename.length) 880 { 881 if (typename[$ - 1].type == tok!"identifier") 882 { 883 arg.nameRange = typename[$ - 1].tokenRange; 884 typename = typename[0 .. $ - 1]; 885 } 886 arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd]; 887 } 888 } 889 890 ret.put(arg); 891 892 gotValue = false; 893 start = end + 1; 894 } 895 896 foreach (i, token; tokens) 897 { 898 if (token.type == tok!"{") 899 subDepth++; 900 else if (token.type == tok!"}") 901 { 902 if (subDepth == 0) 903 break; 904 subDepth--; 905 } 906 else if (token.type == tok!"(" || token.type == tok!"[") 907 depth++; 908 else if (token.type == tok!")" || token.type == tok!"]") 909 { 910 if (depth == 0) 911 break; 912 depth--; 913 } 914 915 if (token.type == tok!",") 916 putArg(i); 917 else if (token.type == tok!":" || token.type == tok!"=") 918 { 919 if (!gotValue) 920 { 921 valueStart = i + 1; 922 gotValue = true; 923 } 924 } 925 } 926 putArg(tokens.length); 927 928 return ret.data; 929 } 930 931 string indent(string code, string indentation) 932 { 933 return code.lineSplitter!(KeepTerminator.yes) 934 .map!(a => a.length ? indentation ~ a : a) 935 .join; 936 } 937 938 bool fieldNameMatches(string field, in char[] expected) 939 { 940 import std.uni : sicmp; 941 942 if (field.startsWith("_")) 943 field = field[1 .. $]; 944 else if (field.startsWith("m_")) 945 field = field[2 .. $]; 946 else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper) 947 field = field[1 .. $]; 948 949 return field.sicmp(expected) == 0; 950 } 951 952 final class CodeBlockInfoFinder : ASTVisitor 953 { 954 this(int targetPosition) 955 { 956 this.targetPosition = targetPosition; 957 } 958 959 override void visit(const ClassDeclaration dec) 960 { 961 visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody); 962 } 963 964 override void visit(const InterfaceDeclaration dec) 965 { 966 visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody); 967 } 968 969 override void visit(const StructDeclaration dec) 970 { 971 visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody); 972 } 973 974 override void visit(const UnionDeclaration dec) 975 { 976 visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody); 977 } 978 979 override void visit(const TemplateDeclaration dec) 980 { 981 if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation) 982 { 983 block = CodeBlockInfo.init; 984 block.type = CodeBlockInfo.Type.template_; 985 block.name = dec.name.text; 986 block.outerRange = [ 987 cast(uint) dec.name.index, cast(uint) dec.endLocation + 1 988 ]; 989 block.innerRange = [ 990 cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation 991 ]; 992 dec.accept(this); 993 } 994 } 995 996 private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody) 997 { 998 if (!structBody) 999 return; 1000 if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation) 1001 { 1002 block = CodeBlockInfo.init; 1003 block.type = type; 1004 block.name = name.text; 1005 block.outerRange = [ 1006 cast(uint) name.index, cast(uint) structBody.endLocation + 1 1007 ]; 1008 block.innerRange = [ 1009 cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation 1010 ]; 1011 structBody.accept(this); 1012 } 1013 } 1014 1015 alias visit = ASTVisitor.visit; 1016 1017 CodeBlockInfo block; 1018 int targetPosition; 1019 } 1020 1021 version (unittest) static immutable string SimpleClassTestCode = q{ 1022 module foo; 1023 1024 class FooBar 1025 { 1026 public: 1027 int i; // default instanced fields 1028 string s; 1029 long l; 1030 1031 public this() // public instanced ctor 1032 { 1033 i = 4; 1034 } 1035 1036 protected: 1037 int x; // protected instanced field 1038 1039 private: 1040 static const int foo() @nogc nothrow pure @system // private static methods 1041 { 1042 if (s == "a") 1043 { 1044 i = 5; 1045 } 1046 } 1047 1048 static void bar1() {} 1049 1050 void bar2() {} // private instanced methods 1051 void bar3() {} 1052 }}; 1053 1054 unittest 1055 { 1056 auto backend = new WorkspaceD(); 1057 auto workspace = makeTemporaryTestingWorkspace; 1058 auto instance = backend.addInstance(workspace.directory); 1059 backend.register!DCDExtComponent; 1060 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1061 1062 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_, 1063 "FooBar", [20, SimpleClassTestCode.length], [ 1064 28, SimpleClassTestCode.length - 1 1065 ])); 1066 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init); 1067 assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init); 1068 1069 auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}", 1070 SimpleClassTestCode, 123); 1071 import std.stdio; 1072 1073 stderr.writeln(replacements); 1074 } 1075 1076 unittest 1077 { 1078 import std.conv; 1079 1080 auto backend = new WorkspaceD(); 1081 auto workspace = makeTemporaryTestingWorkspace; 1082 auto instance = backend.addInstance(workspace.directory); 1083 backend.register!DCDExtComponent; 1084 DCDExtComponent dcdext = instance.get!DCDExtComponent; 1085 1086 import std.stdio; 1087 1088 auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23); 1089 assert(!extract.hasTemplate); 1090 assert(extract.parentStart == 7); 1091 assert(extract.functionStart == 11); 1092 assert(extract.functionParensRange[0] == 14); 1093 assert(extract.functionParensRange[1] <= 31); 1094 assert(extract.functionArgs.length == 2); 1095 assert(extract.functionArgs[0].contentRange == [15, 16]); 1096 assert(extract.functionArgs[1].contentRange[0] == 18); 1097 assert(extract.functionArgs[1].contentRange[1] <= 31); 1098 1099 extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23); 1100 assert(!extract.hasTemplate); 1101 assert(extract.parentStart == 7); 1102 assert(extract.functionStart == 11); 1103 assert(extract.functionParensRange == [14, 23]); 1104 assert(extract.functionArgs.length == 2); 1105 assert(extract.functionArgs[0].contentRange == [15, 16]); 1106 assert(extract.functionArgs[1].contentRange == [18, 23]); 1107 }