1 /** 2 * This file is part of DCD, a development tool for the D programming language. 3 * Copyright (C) 2014 Brian Schott 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 module server.autocomplete; 20 21 import std.algorithm; 22 import std.experimental.allocator; 23 import std.experimental.logger; 24 import std.array; 25 import std.conv; 26 import std.experimental.logger; 27 import std.file; 28 import std.path; 29 import std.range; 30 import std.stdio; 31 import std.string; 32 import std.typecons; 33 import std.uni; 34 35 import dparse.ast; 36 import dparse.lexer; 37 import dparse.parser; 38 39 import dsymbol.conversion; 40 import dsymbol.modulecache; 41 import dsymbol.string_interning; 42 import dsymbol.symbol; 43 import dsymbol.scope_; 44 import dsymbol.builtin.names; 45 import dsymbol.builtin.symbols; 46 47 import common.constants; 48 import common.messages; 49 50 /** 51 * Gets documentation for the symbol at the cursor 52 * Params: 53 * request = the autocompletion request 54 * Returns: 55 * the autocompletion response 56 */ 57 public AutocompleteResponse getDoc(const AutocompleteRequest request, 58 ref ModuleCache moduleCache) 59 { 60 // trace("Getting doc comments"); 61 AutocompleteResponse response; 62 auto allocator = scoped!(ASTAllocator)(); 63 auto cache = StringCache(StringCache.defaultBucketCount); 64 SymbolStuff stuff = getSymbolsForCompletion(request, CompletionType.ddoc, 65 allocator, cache, moduleCache); 66 if (stuff.symbols.length == 0) 67 warning("Could not find symbol"); 68 else 69 { 70 struct Escaper(O) 71 { 72 this(O* or) 73 { 74 this.outputRange = or; 75 } 76 77 void put(string s) 78 { 79 foreach (c; s) 80 put(c); 81 } 82 83 void put(char c) 84 { 85 switch (c) 86 { 87 case '\n': 88 outputRange.put('\\'); 89 outputRange.put('n'); 90 break; 91 default: 92 outputRange.put(c); 93 break; 94 } 95 } 96 97 O* outputRange; 98 } 99 100 auto app = appender!(char[])(); 101 auto e = Escaper!(typeof(app))(&app); 102 foreach (symbol; stuff.symbols.filter!(a => !a.doc.empty)) 103 { 104 app.clear(); 105 foreach(c; symbol.doc) 106 e.put(c); 107 response.docComments ~= cast(string) app.data; 108 } 109 } 110 return response; 111 } 112 113 /** 114 * Finds the declaration of the symbol at the cursor position. 115 * Params: 116 * request = the autocompletion request 117 * Returns: 118 * the autocompletion response 119 */ 120 public AutocompleteResponse findDeclaration(const AutocompleteRequest request, 121 ref ModuleCache moduleCache) 122 { 123 AutocompleteResponse response; 124 auto allocator = scoped!(ASTAllocator)(); 125 auto cache = StringCache(StringCache.defaultBucketCount); 126 SymbolStuff stuff = getSymbolsForCompletion(request, 127 CompletionType.location, allocator, cache, moduleCache); 128 scope(exit) stuff.destroy(); 129 if (stuff.symbols.length > 0) 130 { 131 response.symbolLocation = stuff.symbols[0].location; 132 response.symbolFilePath = stuff.symbols[0].symbolFile.idup; 133 } 134 else 135 warning("Could not find symbol declaration"); 136 return response; 137 } 138 139 /** 140 * Handles autocompletion 141 * Params: 142 * request = the autocompletion request 143 * Returns: 144 * the autocompletion response 145 */ 146 public AutocompleteResponse complete(const AutocompleteRequest request, 147 ref ModuleCache moduleCache) 148 { 149 const(Token)[] tokenArray; 150 auto stringCache = StringCache(StringCache.defaultBucketCount); 151 auto beforeTokens = getTokensBeforeCursor(request.sourceCode, 152 request.cursorPosition, stringCache, tokenArray); 153 if (beforeTokens.length >= 2) 154 { 155 if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"[") 156 { 157 return parenCompletion(beforeTokens, tokenArray, request.cursorPosition, 158 moduleCache); 159 } 160 else if (beforeTokens[$ - 1] == tok!",") 161 { 162 immutable size_t end = goBackToOpenParen(beforeTokens); 163 if (end != size_t.max) 164 return parenCompletion(beforeTokens[0 .. end], tokenArray, 165 request.cursorPosition, moduleCache); 166 } 167 else 168 { 169 ImportKind kind = determineImportKind(beforeTokens); 170 if (kind == ImportKind.neither) 171 return dotCompletion(beforeTokens, tokenArray, request.cursorPosition, 172 moduleCache); 173 else 174 return importCompletion(beforeTokens, kind, moduleCache); 175 } 176 } 177 return dotCompletion(beforeTokens, tokenArray, request.cursorPosition, moduleCache); 178 } 179 180 /** 181 * 182 */ 183 public AutocompleteResponse symbolSearch(const AutocompleteRequest request, 184 ref ModuleCache moduleCache) 185 { 186 import containers.ttree : TTree; 187 188 LexerConfig config; 189 config.fileName = ""; 190 auto cache = StringCache(StringCache.defaultBucketCount); 191 const(Token)[] tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode, 192 config, &cache); 193 auto allocator = scoped!(ASTAllocator)(); 194 ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, 195 request.cursorPosition, moduleCache); 196 scope(exit) pair.destroy(); 197 198 static struct SearchResults 199 { 200 void put(const(DSymbol)* symbol) 201 { 202 tree.insert(SearchResult(symbol)); 203 } 204 205 static struct SearchResult 206 { 207 const(DSymbol)* symbol; 208 209 int opCmp(ref const SearchResult other) const pure nothrow 210 { 211 if (other.symbol.symbolFile < symbol.symbolFile) 212 return -1; 213 if (other.symbol.symbolFile > symbol.symbolFile) 214 return 1; 215 if (other.symbol.location < symbol.location) 216 return -1; 217 return other.symbol.location > symbol.location; 218 } 219 } 220 221 TTree!(SearchResult) tree; 222 } 223 224 SearchResults results; 225 foreach (symbol; pair.scope_.symbols) 226 symbol.getAllPartsNamed(request.searchName, results); 227 foreach (s; moduleCache.getAllSymbols()) 228 s.symbol.getAllPartsNamed(request.searchName, results); 229 230 AutocompleteResponse response; 231 foreach (result; results.tree[]) 232 { 233 response.locations ~= result.symbol.location; 234 response.completionKinds ~= result.symbol.kind; 235 response.completions ~= result.symbol.symbolFile; 236 } 237 238 return response; 239 } 240 241 /******************************************************************************/ 242 private: 243 244 enum ImportKind : ubyte 245 { 246 selective, 247 normal, 248 neither 249 } 250 251 /** 252 * Handles dot completion for identifiers and types. 253 * Params: 254 * beforeTokens = the tokens before the cursor 255 * tokenArray = all tokens in the file 256 * cursorPosition = the cursor position in bytes 257 * Returns: 258 * the autocompletion response 259 */ 260 AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray, 261 size_t cursorPosition, ref ModuleCache moduleCache) 262 { 263 AutocompleteResponse response; 264 265 // Partial symbol name appearing after the dot character and before the 266 // cursor. 267 string partial; 268 269 // Type of the token before the dot, or identifier if the cursor was at 270 // an identifier. 271 IdType significantTokenType; 272 273 if (beforeTokens.length >= 1 && beforeTokens[$ - 1] == tok!"identifier") 274 { 275 // Set partial to the slice of the identifier between the beginning 276 // of the identifier and the cursor. This improves the completion 277 // responses when the cursor is in the middle of an identifier instead 278 // of at the end 279 auto t = beforeTokens[$ - 1]; 280 if (cursorPosition - t.index >= 0 && cursorPosition - t.index <= t.text.length) 281 partial = t.text[0 .. cursorPosition - t.index]; 282 significantTokenType = tok!"identifier"; 283 beforeTokens = beforeTokens[0 .. $ - 1]; 284 } 285 else if (beforeTokens.length >= 2 && beforeTokens[$ - 1] == tok!".") 286 significantTokenType = beforeTokens[$ - 2].type; 287 else 288 return response; 289 290 switch (significantTokenType) 291 { 292 case tok!"stringLiteral": 293 case tok!"wstringLiteral": 294 case tok!"dstringLiteral": 295 foreach (symbol; arraySymbols) 296 { 297 response.completionKinds ~= symbol.kind; 298 response.completions ~= symbol.name.dup; 299 } 300 response.completionType = CompletionType.identifiers; 301 break; 302 case tok!"int": 303 case tok!"uint": 304 case tok!"long": 305 case tok!"ulong": 306 case tok!"char": 307 case tok!"wchar": 308 case tok!"dchar": 309 case tok!"bool": 310 case tok!"byte": 311 case tok!"ubyte": 312 case tok!"short": 313 case tok!"ushort": 314 case tok!"cent": 315 case tok!"ucent": 316 case tok!"float": 317 case tok!"ifloat": 318 case tok!"cfloat": 319 case tok!"idouble": 320 case tok!"cdouble": 321 case tok!"double": 322 case tok!"real": 323 case tok!"ireal": 324 case tok!"creal": 325 case tok!"identifier": 326 case tok!")": 327 case tok!"]": 328 case tok!"this": 329 case tok!"super": 330 auto allocator = scoped!(ASTAllocator)(); 331 ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, 332 cursorPosition, moduleCache); 333 scope(exit) pair.destroy(); 334 response.setCompletions(pair.scope_, getExpression(beforeTokens), 335 cursorPosition, CompletionType.identifiers, false, partial); 336 break; 337 case tok!"(": 338 case tok!"{": 339 case tok!"[": 340 case tok!";": 341 case tok!":": 342 break; 343 default: 344 break; 345 } 346 return response; 347 } 348 349 /** 350 * Params: 351 * sourceCode = the source code of the file being edited 352 * cursorPosition = the cursor position in bytes 353 * Returns: 354 * a sorted range of tokens before the cursor position 355 */ 356 auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition, 357 ref StringCache cache, out const(Token)[] tokenArray) 358 { 359 LexerConfig config; 360 config.fileName = ""; 361 tokenArray = getTokensForParser(cast(ubyte[]) sourceCode, config, &cache); 362 auto sortedTokens = assumeSorted(tokenArray); 363 return sortedTokens.lowerBound(cast(size_t) cursorPosition); 364 } 365 366 /** 367 * Params: 368 * request = the autocompletion request 369 * type = type the autocompletion type 370 * Returns: 371 * all symbols that should be considered for the autocomplete list based on 372 * the request's source code, cursor position, and completion type. 373 */ 374 SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request, 375 const CompletionType type, IAllocator allocator, ref StringCache cache, 376 ref ModuleCache moduleCache) 377 { 378 const(Token)[] tokenArray; 379 auto beforeTokens = getTokensBeforeCursor(request.sourceCode, 380 request.cursorPosition, cache, tokenArray); 381 ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, 382 request.cursorPosition, moduleCache); 383 auto expression = getExpression(beforeTokens); 384 return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression, 385 request.cursorPosition, type), pair.symbol, pair.scope_); 386 } 387 388 struct SymbolStuff 389 { 390 void destroy() 391 { 392 typeid(DSymbol).destroy(symbol); 393 typeid(Scope).destroy(scope_); 394 } 395 396 DSymbol*[] symbols; 397 DSymbol* symbol; 398 Scope* scope_; 399 } 400 401 /** 402 * Handles paren completion for function calls and some keywords 403 * Params: 404 * beforeTokens = the tokens before the cursor 405 * tokenArray = all tokens in the file 406 * cursorPosition = the cursor position in bytes 407 * Returns: 408 * the autocompletion response 409 */ 410 AutocompleteResponse parenCompletion(T)(T beforeTokens, 411 const(Token)[] tokenArray, size_t cursorPosition, ref ModuleCache moduleCache) 412 { 413 AutocompleteResponse response; 414 immutable(string)[] completions; 415 switch (beforeTokens[$ - 2].type) 416 { 417 case tok!"__traits": 418 completions = traits; 419 goto fillResponse; 420 case tok!"scope": 421 completions = scopes; 422 goto fillResponse; 423 case tok!"version": 424 completions = predefinedVersions; 425 goto fillResponse; 426 case tok!"extern": 427 completions = linkages; 428 goto fillResponse; 429 case tok!"pragma": 430 completions = pragmas; 431 fillResponse: 432 response.completionType = CompletionType.identifiers; 433 foreach (completion; completions) 434 { 435 response.completions ~= completion; 436 response.completionKinds ~= CompletionKind.keyword; 437 } 438 break; 439 case tok!"characterLiteral": 440 case tok!"doubleLiteral": 441 case tok!"dstringLiteral": 442 case tok!"floatLiteral": 443 case tok!"identifier": 444 case tok!"idoubleLiteral": 445 case tok!"ifloatLiteral": 446 case tok!"intLiteral": 447 case tok!"irealLiteral": 448 case tok!"longLiteral": 449 case tok!"realLiteral": 450 case tok!"stringLiteral": 451 case tok!"uintLiteral": 452 case tok!"ulongLiteral": 453 case tok!"wstringLiteral": 454 case tok!"this": 455 case tok!"super": 456 case tok!")": 457 case tok!"]": 458 auto allocator = scoped!(ASTAllocator)(); 459 ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator, 460 cursorPosition, moduleCache); 461 scope(exit) pair.destroy(); 462 auto expression = getExpression(beforeTokens[0 .. $ - 1]); 463 response.setCompletions(pair.scope_, expression, 464 cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"["); 465 break; 466 default: 467 break; 468 } 469 return response; 470 } 471 472 /** 473 * Determines if an import is selective, whole-module, or neither. 474 */ 475 ImportKind determineImportKind(T)(T tokens) 476 { 477 assert (tokens.length > 1); 478 size_t i = tokens.length - 1; 479 if (!(tokens[i] == tok!":" || tokens[i] == tok!"," || tokens[i] == tok!"." 480 || tokens[i] == tok!"identifier")) 481 return ImportKind.neither; 482 bool foundColon = false; 483 while (true) switch (tokens[i].type) 484 { 485 case tok!":": 486 foundColon = true; 487 goto case; 488 case tok!"identifier": 489 case tok!"=": 490 case tok!".": 491 case tok!",": 492 if (i == 0) 493 return ImportKind.neither; 494 else 495 i--; 496 break; 497 case tok!"import": 498 return foundColon ? ImportKind.selective : ImportKind.normal; 499 default: 500 return ImportKind.neither; 501 } 502 return ImportKind.neither; 503 } 504 505 unittest 506 { 507 import std.stdio : writeln; 508 509 Token[] t = [ 510 Token(tok!"import"), Token(tok!"identifier"), Token(tok!"."), 511 Token(tok!"identifier"), Token(tok!":"), Token(tok!"identifier"), Token(tok!",") 512 ]; 513 assert(determineImportKind(t) == ImportKind.selective); 514 Token[] t2; 515 t2 ~= Token(tok!"else"); 516 t2 ~= Token(tok!":"); 517 assert(determineImportKind(t2) == ImportKind.neither); 518 writeln("Unittest for determineImportKind() passed"); 519 } 520 521 /** 522 * Provides autocomplete for selective imports, e.g.: 523 * --- 524 * import std.algorithm: balancedParens; 525 * --- 526 */ 527 AutocompleteResponse importCompletion(T)(T beforeTokens, ImportKind kind, 528 ref ModuleCache moduleCache) 529 in 530 { 531 assert (beforeTokens.length >= 2); 532 } 533 body 534 { 535 AutocompleteResponse response; 536 if (beforeTokens.length <= 2) 537 return response; 538 539 size_t i = beforeTokens.length - 1; 540 541 if (kind == ImportKind.normal) 542 { 543 544 while (beforeTokens[i].type != tok!"," && beforeTokens[i].type != tok!"import" 545 && beforeTokens[i].type != tok!"=" ) 546 i--; 547 setImportCompletions(beforeTokens[i .. $], response, moduleCache); 548 return response; 549 } 550 551 loop: while (true) switch (beforeTokens[i].type) 552 { 553 case tok!"identifier": 554 case tok!"=": 555 case tok!",": 556 case tok!".": 557 i--; 558 break; 559 case tok!":": 560 i--; 561 while (beforeTokens[i].type == tok!"identifier" || beforeTokens[i].type == tok!".") 562 i--; 563 break loop; 564 default: 565 break loop; 566 } 567 568 size_t j = i; 569 loop2: while (j <= beforeTokens.length) switch (beforeTokens[j].type) 570 { 571 case tok!":": break loop2; 572 default: j++; break; 573 } 574 575 if (i >= j) 576 { 577 warning("Malformed import statement"); 578 return response; 579 } 580 581 immutable string path = beforeTokens[i + 1 .. j] 582 .filter!(token => token.type == tok!"identifier") 583 .map!(token => cast() token.text) 584 .joiner(dirSeparator) 585 .text(); 586 587 string resolvedLocation = moduleCache.resolveImportLocation(path); 588 if (resolvedLocation is null) 589 { 590 warning("Could not resolve location of ", path); 591 return response; 592 } 593 auto symbols = moduleCache.getModuleSymbol(internString(resolvedLocation)); 594 595 import containers.hashset : HashSet; 596 HashSet!string h; 597 598 void addSymbolToResponses(const(DSymbol)* sy) 599 { 600 auto a = DSymbol(sy.name); 601 if (!builtinSymbols.contains(&a) && sy.name !is null && !h.contains(sy.name) 602 && sy.name != CONSTRUCTOR_SYMBOL_NAME) 603 { 604 response.completionKinds ~= sy.kind; 605 response.completions ~= sy.name; 606 h.insert(sy.name); 607 } 608 } 609 610 foreach (s; symbols.opSlice()) 611 { 612 if (s.kind == CompletionKind.importSymbol && s.type !is null) 613 foreach (sy; s.type.opSlice()) 614 addSymbolToResponses(sy); 615 else 616 addSymbolToResponses(s); 617 } 618 response.completionType = CompletionType.identifiers; 619 return response; 620 } 621 622 /** 623 * Populates the response with completion information for an import statement 624 * Params: 625 * tokens = the tokens after the "import" keyword and before the cursor 626 * response = the response that should be populated 627 */ 628 void setImportCompletions(T)(T tokens, ref AutocompleteResponse response, 629 ref ModuleCache cache) 630 { 631 response.completionType = CompletionType.identifiers; 632 string partial = null; 633 if (tokens[$ - 1].type == tok!"identifier") 634 { 635 partial = tokens[$ - 1].text; 636 tokens = tokens[0 .. $ - 1]; 637 } 638 auto moduleParts = tokens.filter!(a => a.type == tok!"identifier").map!("a.text").array(); 639 string path = buildPath(moduleParts); 640 641 bool found = false; 642 643 foreach (importDirectory; cache.getImportPaths()) 644 { 645 string p = buildPath(importDirectory, path); 646 if (!exists(p)) 647 continue; 648 649 found = true; 650 651 foreach (string name; dirEntries(p, SpanMode.shallow)) 652 { 653 import std.path: baseName; 654 if (name.baseName.startsWith(".#")) 655 continue; 656 657 auto n = name.baseName(".d").baseName(".di"); 658 if (isFile(name) && (name.endsWith(".d") || name.endsWith(".di")) 659 && (partial is null || n.startsWith(partial))) 660 { 661 response.completions ~= n; 662 response.completionKinds ~= CompletionKind.moduleName; 663 } 664 else if (isDir(name)) 665 { 666 if (n[0] != '.' && (partial is null || n.startsWith(partial))) 667 { 668 response.completions ~= n; 669 response.completionKinds ~= 670 exists(buildPath(name, "package.d")) || exists(buildPath(name, "package.di")) 671 ? CompletionKind.moduleName : CompletionKind.packageName; 672 } 673 } 674 } 675 } 676 if (!found) 677 warning("Could not find ", moduleParts); 678 } 679 680 static void skip(alias O, alias C, T)(T t, ref size_t i) 681 { 682 int depth = 1; 683 while (i < t.length) switch (t[i].type) 684 { 685 case O: 686 i++; 687 depth++; 688 break; 689 case C: 690 i++; 691 depth--; 692 if (depth <= 0) 693 return; 694 break; 695 default: 696 i++; 697 break; 698 } 699 } 700 701 bool isSliceExpression(T)(T tokens, size_t index) 702 { 703 while (index < tokens.length) switch (tokens[index].type) 704 { 705 case tok!"[": 706 skip!(tok!"[", tok!"]")(tokens, index); 707 break; 708 case tok!"(": 709 skip!(tok!"(", tok!")")(tokens, index); 710 break; 711 case tok!"]": 712 case tok!"}": 713 return false; 714 case tok!"..": 715 return true; 716 default: 717 index++; 718 break; 719 } 720 return false; 721 } 722 723 /** 724 * 725 */ 726 DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope, 727 T tokens, size_t cursorPosition, CompletionType completionType) 728 { 729 // Find the symbol corresponding to the beginning of the chain 730 DSymbol*[] symbols; 731 if (tokens.length == 0) 732 return []; 733 if (tokens[0] == tok!"." && tokens.length > 1) 734 { 735 tokens = tokens[1 .. $]; 736 symbols = completionScope.getSymbolsAtGlobalScope(stringToken(tokens[0])); 737 } 738 else 739 symbols = completionScope.getSymbolsByNameAndCursor(stringToken(tokens[0]), cursorPosition); 740 741 if (symbols.length == 0) 742 { 743 warning("Could not find declaration of ", stringToken(tokens[0]), 744 " from position ", cursorPosition); 745 return []; 746 } 747 748 // If the `symbols` array contains functions, and one of them returns 749 // void and the others do not, this is a property function. For the 750 // purposes of chaining auto-complete we want to ignore the one that 751 // returns void. 752 void filterProperties() @nogc @safe 753 { 754 if (symbols.length == 0) 755 return; 756 if (symbols[0].kind == CompletionKind.functionName 757 || symbols[0].qualifier == SymbolQualifier.func) 758 { 759 int voidRets = 0; 760 int nonVoidRets = 0; 761 size_t firstNonVoidIndex = size_t.max; 762 foreach (i, sym; symbols) 763 { 764 if (sym.type is null) 765 return; 766 if (sym.type.name.ptr == getBuiltinTypeName(tok!"void").ptr) 767 voidRets++; 768 else 769 { 770 nonVoidRets++; 771 firstNonVoidIndex = min(firstNonVoidIndex, i); 772 } 773 } 774 if (voidRets > 0 && nonVoidRets > 0) 775 symbols = symbols[firstNonVoidIndex .. $]; 776 } 777 } 778 779 filterProperties(); 780 781 if (shouldSwapWithType(completionType, symbols[0].kind, 0, tokens.length - 1)) 782 { 783 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 784 if (symbols.length == 0) 785 return []; 786 } 787 788 loop: for (size_t i = 1; i < tokens.length; i++) 789 { 790 IdType open; 791 IdType close; 792 void skip() 793 { 794 i++; 795 for (int depth = 1; depth > 0 && i < tokens.length; i++) 796 { 797 if (tokens[i].type == open) 798 depth++; 799 else if (tokens[i].type == close) 800 { 801 depth--; 802 if (depth == 0) break; 803 } 804 } 805 } 806 switch (tokens[i].type) 807 { 808 case tok!"int": 809 case tok!"uint": 810 case tok!"long": 811 case tok!"ulong": 812 case tok!"char": 813 case tok!"wchar": 814 case tok!"dchar": 815 case tok!"bool": 816 case tok!"byte": 817 case tok!"ubyte": 818 case tok!"short": 819 case tok!"ushort": 820 case tok!"cent": 821 case tok!"ucent": 822 case tok!"float": 823 case tok!"ifloat": 824 case tok!"cfloat": 825 case tok!"idouble": 826 case tok!"cdouble": 827 case tok!"double": 828 case tok!"real": 829 case tok!"ireal": 830 case tok!"creal": 831 case tok!"this": 832 case tok!"super": 833 symbols = symbols[0].getPartsByName(internString(str(tokens[i].type))); 834 if (symbols.length == 0) 835 break loop; 836 break; 837 case tok!"identifier": 838 //trace(symbols[0].qualifier, " ", symbols[0].kind); 839 filterProperties(); 840 841 // Use type instead of the symbol itself for certain symbol kinds 842 while (symbols[0].qualifier == SymbolQualifier.func 843 || symbols[0].kind == CompletionKind.functionName 844 || (symbols[0].kind == CompletionKind.moduleName 845 && symbols[0].type !is null && symbols[0].type.kind == CompletionKind.importSymbol) 846 || symbols[0].kind == CompletionKind.importSymbol 847 || symbols[0].kind == CompletionKind.aliasName) 848 { 849 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 850 if (symbols.length == 0) 851 break loop; 852 } 853 854 //trace("looking for ", tokens[i].text, " in ", symbols[0].name); 855 symbols = symbols[0].getPartsByName(internString(tokens[i].text)); 856 //trace("symbols: ", symbols.map!(a => a.name)); 857 filterProperties(); 858 if (symbols.length == 0) 859 { 860 //trace("Couldn't find it."); 861 break loop; 862 } 863 if (shouldSwapWithType(completionType, symbols[0].kind, i, tokens.length - 1)) 864 { 865 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 866 if (symbols.length == 0) 867 break loop; 868 } 869 if ((symbols[0].kind == CompletionKind.aliasName 870 || symbols[0].kind == CompletionKind.moduleName) 871 && (completionType == CompletionType.identifiers 872 || i + 1 < tokens.length)) 873 { 874 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 875 } 876 if (symbols.length == 0) 877 break loop; 878 break; 879 case tok!"(": 880 open = tok!"("; 881 close = tok!")"; 882 skip(); 883 break; 884 case tok!"[": 885 open = tok!"["; 886 close = tok!"]"; 887 if (symbols[0].qualifier == SymbolQualifier.array) 888 { 889 skip(); 890 if (!isSliceExpression(tokens, i)) 891 { 892 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 893 if (symbols.length == 0) 894 break loop; 895 } 896 } 897 else if (symbols[0].qualifier == SymbolQualifier.assocArray) 898 { 899 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 900 skip(); 901 } 902 else 903 { 904 skip(); 905 DSymbol*[] overloads; 906 if (isSliceExpression(tokens, i)) 907 overloads = symbols[0].getPartsByName(internString("opSlice")); 908 else 909 overloads = symbols[0].getPartsByName(internString("opIndex")); 910 if (overloads.length > 0) 911 { 912 symbols = overloads[0].type is null ? [] : [overloads[0].type]; 913 } 914 else 915 return []; 916 } 917 break; 918 case tok!".": 919 break; 920 default: 921 break loop; 922 } 923 } 924 return symbols; 925 } 926 927 /** 928 * 929 */ 930 void setCompletions(T)(ref AutocompleteResponse response, 931 Scope* completionScope, T tokens, size_t cursorPosition, 932 CompletionType completionType, bool isBracket = false, string partial = null) 933 { 934 static void addSymToResponse(const(DSymbol)* s, ref AutocompleteResponse r, string p, 935 size_t[] circularGuard = []) 936 { 937 if (circularGuard.canFind(cast(size_t) s)) 938 return; 939 foreach (sym; s.opSlice()) 940 { 941 if (sym.name !is null && sym.name.length > 0 && sym.kind != CompletionKind.importSymbol 942 && (p is null ? true : sym.name.toUpper().startsWith(p.toUpper())) 943 && !r.completions.canFind(sym.name) 944 && sym.name[0] != '*') 945 { 946 r.completionKinds ~= sym.kind; 947 r.completions ~= sym.name.dup; 948 } 949 if (sym.kind == CompletionKind.importSymbol && !sym.skipOver && sym.type !is null) 950 addSymToResponse(sym.type, r, p, circularGuard ~ (cast(size_t) s)); 951 } 952 } 953 954 // Handle the simple case where we get all symbols in scope and filter it 955 // based on the currently entered text. 956 if (partial !is null && tokens.length == 0) 957 { 958 auto currentSymbols = completionScope.getSymbolsInCursorScope(cursorPosition); 959 foreach (s; currentSymbols.filter!(a => isPublicCompletionKind(a.kind) 960 && a.name.toUpper().startsWith(partial.toUpper()))) 961 { 962 response.completionKinds ~= s.kind; 963 response.completions ~= s.name.dup; 964 } 965 response.completionType = CompletionType.identifiers; 966 return; 967 } 968 969 if (tokens.length == 0) 970 return; 971 972 DSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens, 973 cursorPosition, completionType); 974 975 if (symbols.length == 0) 976 return; 977 978 if (completionType == CompletionType.identifiers) 979 { 980 while (symbols[0].qualifier == SymbolQualifier.func 981 || symbols[0].kind == CompletionKind.functionName 982 || symbols[0].kind == CompletionKind.importSymbol 983 || symbols[0].kind == CompletionKind.aliasName) 984 { 985 symbols = symbols[0].type is null ? [] : [symbols[0].type]; 986 if (symbols.length == 0) 987 return; 988 } 989 addSymToResponse(symbols[0], response, partial); 990 response.completionType = CompletionType.identifiers; 991 } 992 else if (completionType == CompletionType.calltips) 993 { 994 //trace("Showing call tips for ", symbols[0].name, " of kind ", symbols[0].kind); 995 if (symbols[0].kind != CompletionKind.functionName 996 && symbols[0].callTip is null) 997 { 998 if (symbols[0].kind == CompletionKind.aliasName) 999 { 1000 if (symbols[0].type is null) 1001 return; 1002 symbols = [symbols[0].type]; 1003 } 1004 if (symbols[0].kind == CompletionKind.variableName) 1005 { 1006 auto dumb = symbols[0].type; 1007 if (dumb !is null) 1008 { 1009 if (dumb.kind == CompletionKind.functionName) 1010 { 1011 symbols = [dumb]; 1012 goto setCallTips; 1013 } 1014 if (isBracket) 1015 { 1016 auto index = dumb.getPartsByName(internString("opIndex")); 1017 if (index.length > 0) 1018 { 1019 symbols = index; 1020 goto setCallTips; 1021 } 1022 } 1023 auto call = dumb.getPartsByName(internString("opCall")); 1024 if (call.length > 0) 1025 { 1026 symbols = call; 1027 goto setCallTips; 1028 } 1029 } 1030 } 1031 if (symbols[0].kind == CompletionKind.structName 1032 || symbols[0].kind == CompletionKind.className) 1033 { 1034 auto constructor = symbols[0].getPartsByName(CONSTRUCTOR_SYMBOL_NAME); 1035 if (constructor.length == 0) 1036 { 1037 // Build a call tip out of the struct fields 1038 if (symbols[0].kind == CompletionKind.structName) 1039 { 1040 response.completionType = CompletionType.calltips; 1041 response.completions = [generateStructConstructorCalltip(symbols[0])]; 1042 return; 1043 } 1044 } 1045 else 1046 { 1047 symbols = constructor; 1048 goto setCallTips; 1049 } 1050 } 1051 } 1052 setCallTips: 1053 response.completionType = CompletionType.calltips; 1054 foreach (symbol; symbols) 1055 { 1056 if (symbol.kind != CompletionKind.aliasName && symbol.callTip !is null) 1057 response.completions ~= symbol.callTip; 1058 } 1059 } 1060 } 1061 1062 string generateStructConstructorCalltip(const DSymbol* symbol) 1063 in 1064 { 1065 assert(symbol.kind == CompletionKind.structName); 1066 } 1067 body 1068 { 1069 string generatedStructConstructorCalltip = "this("; 1070 const(DSymbol)*[] fields = symbol.opSlice().filter!( 1071 a => a.kind == CompletionKind.variableName).map!(a => cast(const(DSymbol)*) a.ptr).array(); 1072 fields.sort!((a, b) => a.location < b.location); 1073 foreach (i, field; fields) 1074 { 1075 if (field.kind != CompletionKind.variableName) 1076 continue; 1077 i++; 1078 if (field.type !is null) 1079 { 1080 generatedStructConstructorCalltip ~= field.type.name; 1081 generatedStructConstructorCalltip ~= " "; 1082 } 1083 generatedStructConstructorCalltip ~= field.name; 1084 if (i < fields.length) 1085 generatedStructConstructorCalltip ~= ", "; 1086 } 1087 generatedStructConstructorCalltip ~= ")"; 1088 return generatedStructConstructorCalltip; 1089 } 1090 1091 private enum TYPE_IDENT_AND_LITERAL_CASES = q{ 1092 case tok!"int": 1093 case tok!"uint": 1094 case tok!"long": 1095 case tok!"ulong": 1096 case tok!"char": 1097 case tok!"wchar": 1098 case tok!"dchar": 1099 case tok!"bool": 1100 case tok!"byte": 1101 case tok!"ubyte": 1102 case tok!"short": 1103 case tok!"ushort": 1104 case tok!"cent": 1105 case tok!"ucent": 1106 case tok!"float": 1107 case tok!"ifloat": 1108 case tok!"cfloat": 1109 case tok!"idouble": 1110 case tok!"cdouble": 1111 case tok!"double": 1112 case tok!"real": 1113 case tok!"ireal": 1114 case tok!"creal": 1115 case tok!"this": 1116 case tok!"super": 1117 case tok!"identifier": 1118 case tok!"stringLiteral": 1119 case tok!"wstringLiteral": 1120 case tok!"dstringLiteral": 1121 }; 1122 1123 1124 /** 1125 * 1126 */ 1127 T getExpression(T)(T beforeTokens) 1128 { 1129 enum EXPRESSION_LOOP_BREAK = q{ 1130 if (i + 1 < beforeTokens.length) switch (beforeTokens[i + 1].type) 1131 { 1132 mixin (TYPE_IDENT_AND_LITERAL_CASES); 1133 i++; 1134 break expressionLoop; 1135 default: 1136 break; 1137 } 1138 }; 1139 1140 if (beforeTokens.length == 0) 1141 return beforeTokens[0 .. 0]; 1142 size_t i = beforeTokens.length - 1; 1143 size_t sliceEnd = beforeTokens.length; 1144 IdType open; 1145 IdType close; 1146 uint skipCount = 0; 1147 1148 expressionLoop: while (true) 1149 { 1150 switch (beforeTokens[i].type) 1151 { 1152 case tok!"import": 1153 break expressionLoop; 1154 mixin (TYPE_IDENT_AND_LITERAL_CASES); 1155 mixin (EXPRESSION_LOOP_BREAK); 1156 if (i > 1 && beforeTokens[i - 1] == tok!"!" 1157 && beforeTokens[i - 2] == tok!"identifier") 1158 { 1159 sliceEnd -= 2; 1160 i--; 1161 } 1162 break; 1163 case tok!".": 1164 break; 1165 case tok!")": 1166 open = tok!")"; 1167 close = tok!"("; 1168 goto skip; 1169 case tok!"]": 1170 open = tok!"]"; 1171 close = tok!"["; 1172 skip: 1173 mixin (EXPRESSION_LOOP_BREAK); 1174 immutable bookmark = i; 1175 int depth = 1; 1176 do 1177 { 1178 if (depth == 0 || i == 0) 1179 break; 1180 else 1181 i--; 1182 if (beforeTokens[i].type == open) 1183 depth++; 1184 else if (beforeTokens[i].type == close) 1185 depth--; 1186 } while (true); 1187 1188 skipCount++; 1189 1190 // check the current token after skipping parens to the left. 1191 // if it's a loop keyword, pretend we never skipped the parens. 1192 if (i > 0) switch (beforeTokens[i - 1].type) 1193 { 1194 case tok!"scope": 1195 case tok!"if": 1196 case tok!"while": 1197 case tok!"for": 1198 case tok!"foreach": 1199 case tok!"foreach_reverse": 1200 case tok!"do": 1201 case tok!"cast": 1202 case tok!"catch": 1203 i = bookmark + 1; 1204 break expressionLoop; 1205 case tok!"!": 1206 if (skipCount == 1) 1207 { 1208 sliceEnd = i - 1; 1209 i -= 2; 1210 } 1211 break expressionLoop; 1212 default: 1213 break; 1214 } 1215 break; 1216 default: 1217 i++; 1218 break expressionLoop; 1219 } 1220 if (i == 0) 1221 break; 1222 else 1223 i--; 1224 } 1225 return beforeTokens[i .. sliceEnd]; 1226 } 1227 1228 size_t goBackToOpenParen(T)(T beforeTokens) 1229 in 1230 { 1231 assert (beforeTokens.length > 0); 1232 } 1233 body 1234 { 1235 size_t i = beforeTokens.length - 1; 1236 IdType open; 1237 IdType close; 1238 while (true) switch (beforeTokens[i].type) 1239 { 1240 case tok!",": 1241 case tok!".": 1242 case tok!"*": 1243 case tok!"&": 1244 case tok!"doubleLiteral": 1245 case tok!"floatLiteral": 1246 case tok!"idoubleLiteral": 1247 case tok!"ifloatLiteral": 1248 case tok!"intLiteral": 1249 case tok!"longLiteral": 1250 case tok!"realLiteral": 1251 case tok!"irealLiteral": 1252 case tok!"uintLiteral": 1253 case tok!"ulongLiteral": 1254 case tok!"characterLiteral": 1255 mixin(TYPE_IDENT_AND_LITERAL_CASES); 1256 if (i == 0) 1257 return size_t.max; 1258 else 1259 i--; 1260 break; 1261 case tok!"(": 1262 case tok!"[": 1263 return i + 1; 1264 case tok!")": 1265 open = tok!")"; 1266 close = tok!"("; 1267 goto skip; 1268 case tok!"}": 1269 open = tok!"}"; 1270 close = tok!"{"; 1271 goto skip; 1272 case tok!"]": 1273 open = tok!"]"; 1274 close = tok!"["; 1275 skip: 1276 if (i == 0) 1277 return size_t.max; 1278 else 1279 i--; 1280 int depth = 1; 1281 do 1282 { 1283 if (depth == 0 || i == 0) 1284 break; 1285 else 1286 i--; 1287 if (beforeTokens[i].type == open) 1288 depth++; 1289 else if (beforeTokens[i].type == close) 1290 depth--; 1291 } while (true); 1292 break; 1293 default: 1294 return size_t.max; 1295 } 1296 return size_t.max; 1297 } 1298 1299 /** 1300 * Params: 1301 * completionType = the completion type being requested 1302 * kind = the kind of the current item in the completion chain 1303 * current = the index of the current item in the symbol chain 1304 * max = the number of items in the symbol chain 1305 * Returns: 1306 * true if the symbol should be swapped with its type field 1307 */ 1308 bool shouldSwapWithType(CompletionType completionType, CompletionKind kind, 1309 size_t current, size_t max) pure nothrow @safe 1310 { 1311 // packages never have types, so always return false 1312 if (kind == CompletionKind.packageName 1313 || kind == CompletionKind.className 1314 || kind == CompletionKind.structName 1315 || kind == CompletionKind.interfaceName 1316 || kind == CompletionKind.enumName 1317 || kind == CompletionKind.unionName) 1318 { 1319 return false; 1320 } 1321 // Swap out every part of a chain with its type except the last part 1322 if (current < max) 1323 return true; 1324 // Only swap out types for these kinds 1325 immutable bool isInteresting = 1326 kind == CompletionKind.variableName 1327 || kind == CompletionKind.memberVariableName 1328 || kind == CompletionKind.importSymbol 1329 || kind == CompletionKind.aliasName 1330 || kind == CompletionKind.enumMember 1331 || kind == CompletionKind.functionName; 1332 return isInteresting && (completionType == CompletionType.identifiers 1333 || (completionType == completionType.calltips && kind == CompletionKind.variableName)) ; 1334 } 1335 1336 istring stringToken()(auto ref const Token a) 1337 { 1338 return internString(a.text is null ? str(a.type) : a.text); 1339 }