1 /* 2 *Copyright (C) 2018 Laurent Tréguier 3 * 4 *This file is part of DLS. 5 * 6 *DLS is free software: you can redistribute it and/or modify 7 *it under the terms of the GNU General Public License as published by 8 *the Free Software Foundation, either version 3 of the License, or 9 *(at your option) any later version. 10 * 11 *DLS is distributed in the hope that it will be useful, 12 *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 *GNU General Public License for more details. 15 * 16 *You should have received a copy of the GNU General Public License 17 *along with DLS. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 module dls.tools.symbol_tool; 22 23 import dls.protocol.interfaces : CompletionItemKind, SymbolKind, 24 SymbolInformation; 25 import dls.tools.tool : Tool; 26 import dls.util.uri : Uri; 27 import dparse.ast; 28 import dsymbol.symbol : CompletionKind; 29 import std.container : RedBlackTree; 30 31 int compareLocations(inout(SymbolInformation) s1, inout(SymbolInformation) s2) 32 { 33 //dfmt off 34 return s1.location.uri < s2.location.uri ? -1 35 : s1.location.uri > s2.location.uri ? 1 36 : s1.location.range.start.line < s2.location.range.start.line ? -1 37 : s1.location.range.start.line > s2.location.range.start.line ? 1 38 : s1.location.range.start.character < s2.location.range.start.character ? -1 39 : s1.location.range.start.character > s2.location.range.start.character ? 1 40 : 0; 41 //dfmt on 42 } 43 44 alias SymbolInformationTree = RedBlackTree!(SymbolInformation, compareLocations, true); 45 46 private string[string] macros; 47 private CompletionItemKind[CompletionKind] completionKinds; 48 private SymbolKind[CompletionKind] symbolKinds; 49 50 shared static this() 51 { 52 import dub.internal.vibecompat.core.log : LogLevel, setLogLevel; 53 54 setLogLevel(LogLevel.none); 55 } 56 57 static this() 58 { 59 //dfmt off 60 macros = [ 61 "_" : "", 62 "B" : "**$0**", 63 "I" : "_$0_", 64 "U" : "$0", 65 "P" : "\n\n$0", 66 "DL" : "$0", 67 "DT" : "$0", 68 "DD" : "$0", 69 "TABLE" : "$0", 70 "TR" : "$0", 71 "TH" : "$0", 72 "TD" : "$0", 73 "OL" : "\n\n$0", 74 "UL" : "\n\n$0", 75 "LI" : "- $0", 76 "BIG" : "$0", 77 "SMALL" : "$0", 78 "BR" : "\n\n$0", 79 "LINK" : "[$0]($0)", 80 "LINK2" : "[$1]($+)", 81 "RED" : "$0", 82 "BLUE" : "$0", 83 "GREEN" : "$0", 84 "YELLOW" : "$0", 85 "BLACK" : "$0", 86 "WHITE" : "$0", 87 "D_CODE" : "$0", 88 "D_INLINE_CODE" : "$0", 89 "LF" : "\n", 90 "LPAREN" : "(", 91 "RPAREN" : ")", 92 "BACKTICK" : "`", 93 "DOLLAR" : "$", 94 "DDOC" : "$0", 95 "BIGOH" : "O($0)", 96 "D" : "$0", 97 "D_COMMENT" : "$0", 98 "D_STRING" : "\"$0\"", 99 "D_KEYWORD" : "$0", 100 "D_PSYMBOL" : "$0", 101 "D_PARAM" : "$0", 102 "LREF" : "$0", 103 "REF" : "$0", 104 "REF1" : "$0", 105 "MREF" : "$0", 106 "MREF1" : "$0" 107 ]; 108 109 completionKinds = [ 110 CompletionKind.className : CompletionItemKind.class_, 111 CompletionKind.interfaceName : CompletionItemKind.interface_, 112 CompletionKind.structName : CompletionItemKind.struct_, 113 CompletionKind.unionName : CompletionItemKind.interface_, 114 CompletionKind.variableName : CompletionItemKind.variable, 115 CompletionKind.memberVariableName : CompletionItemKind.field, 116 CompletionKind.keyword : CompletionItemKind.keyword, 117 CompletionKind.functionName : CompletionItemKind.function_, 118 CompletionKind.enumName : CompletionItemKind.enum_, 119 CompletionKind.enumMember : CompletionItemKind.enumMember, 120 CompletionKind.packageName : CompletionItemKind.folder, 121 CompletionKind.moduleName : CompletionItemKind.module_, 122 CompletionKind.aliasName : CompletionItemKind.variable, 123 CompletionKind.templateName : CompletionItemKind.function_, 124 CompletionKind.mixinTemplateName : CompletionItemKind.function_ 125 ]; 126 127 symbolKinds = [ 128 CompletionKind.className : SymbolKind.class_, 129 CompletionKind.interfaceName : SymbolKind.interface_, 130 CompletionKind.structName : SymbolKind.struct_, 131 CompletionKind.unionName : SymbolKind.interface_, 132 CompletionKind.variableName : SymbolKind.variable, 133 CompletionKind.memberVariableName : SymbolKind.field, 134 CompletionKind.keyword : SymbolKind.constant, 135 CompletionKind.functionName : SymbolKind.function_, 136 CompletionKind.enumName : SymbolKind.enum_, 137 CompletionKind.enumMember : SymbolKind.enumMember, 138 CompletionKind.packageName : SymbolKind.package_, 139 CompletionKind.moduleName : SymbolKind.module_, 140 CompletionKind.aliasName : SymbolKind.variable, 141 CompletionKind.templateName : SymbolKind.function_, 142 CompletionKind.mixinTemplateName : SymbolKind.function_ 143 ]; 144 //dfmt on 145 } 146 147 void useCompatCompletionItemKinds(CompletionItemKind[] items = []) 148 { 149 import std.algorithm : canFind; 150 151 //dfmt off 152 immutable map = [ 153 CompletionKind.structName : CompletionItemKind.class_, 154 CompletionKind.enumMember : CompletionItemKind.field, 155 CompletionKind.packageName : CompletionItemKind.module_ 156 ]; 157 //dfmt on 158 159 foreach (ck, cik; map) 160 { 161 if (!items.canFind(completionKinds[ck])) 162 { 163 completionKinds[ck] = cik; 164 } 165 } 166 } 167 168 void useCompatSymbolKinds(SymbolKind[] symbols = []) 169 { 170 import std.algorithm : canFind; 171 172 //dfmt off 173 immutable map = [ 174 CompletionKind.structName : SymbolKind.class_, 175 CompletionKind.enumMember : SymbolKind.field 176 ]; 177 //dfmt on 178 179 foreach (ck, sk; map) 180 { 181 if (!symbols.canFind(symbolKinds[ck])) 182 { 183 symbolKinds[ck] = sk; 184 } 185 } 186 } 187 188 class SymbolTool : Tool 189 { 190 import dcd.common.messages : AutocompleteRequest, RequestKind; 191 import dls.protocol.definitions : Location, MarkupContent, Position, Range, 192 WorkspaceEdit; 193 import dls.protocol.interfaces : CompletionItem, DocumentHighlight, 194 DocumentSymbol, Hover; 195 import dsymbol.modulecache : ASTAllocator, ModuleCache; 196 import dub.dub : Dub; 197 198 private static SymbolTool _instance; 199 200 static void initialize() 201 { 202 _instance = new SymbolTool(); 203 _instance.importDirectories(defaultImportPaths); 204 addConfigHook(() { 205 _instance.importDirectories(Tool.configuration.symbol.importPaths); 206 }); 207 } 208 209 static void shutdown() 210 { 211 destroy(_instance); 212 } 213 214 @property static SymbolTool instance() 215 { 216 return _instance; 217 } 218 219 version (Windows) 220 { 221 @property private static string[] _compilerConfigPaths() 222 { 223 import std.algorithm : splitter; 224 import std.file : exists; 225 import std.path : buildNormalizedPath; 226 import std.process : environment; 227 228 foreach (path; splitter(environment["PATH"], ';')) 229 { 230 if (exists(buildNormalizedPath(path, "dmd.exe"))) 231 { 232 return [buildNormalizedPath(path, "sc.ini")]; 233 } 234 } 235 236 return []; 237 } 238 } 239 else version (Posix) 240 { 241 //dfmt off 242 private static immutable _compilerConfigPaths = [ 243 "/Library/D/dmd/bin/dmd.conf", 244 "/etc/dmd.conf", 245 "/usr/local/etc/dmd.conf", 246 "/usr/local/bin/dmd.conf", 247 "/etc/ldc2.conf", 248 "/usr/local/etc/ldc2.conf", 249 "/home/linuxbrew/.linuxbrew/etc/dmd.conf" 250 ]; 251 //dfmt on 252 } 253 else 254 { 255 private static immutable string[] _compilerConfigPaths; 256 } 257 258 private string[string][string] _workspaceDependencies; 259 private ASTAllocator _allocator; 260 private ModuleCache _cache; 261 262 @property ref ModuleCache cache() 263 { 264 return _cache; 265 } 266 267 @property private static string[] defaultImportPaths() 268 { 269 import std.algorithm : each, filter, sort, splitter, uniq; 270 import std.array : array, replace; 271 import std.conv : to; 272 import std.file : FileException, exists, readText; 273 import std.path : asNormalizedPath, buildNormalizedPath, dirName; 274 import std.process : environment; 275 import std.regex : matchAll; 276 277 string[] paths; 278 279 foreach (confPath; _compilerConfigPaths) 280 { 281 if (exists(confPath)) 282 { 283 try 284 { 285 readText(confPath).matchAll(`-I[^\s"]+`) 286 .each!(m => paths ~= m.hit[2 .. $].replace("%@P%", 287 confPath.dirName).asNormalizedPath().to!string); 288 break; 289 } 290 catch (FileException e) 291 { 292 // File doesn't exist or could't be read 293 } 294 } 295 } 296 297 version (linux) 298 { 299 import std.algorithm : map; 300 301 if (paths.length == 0) 302 { 303 foreach (path; ["/snap", "/var/lib/snapd/snap"]) 304 { 305 const dmdSnapPath = buildNormalizedPath(path, "dmd"); 306 const ldcSnapIncludePath = buildNormalizedPath(path, 307 "ldc2", "current", "include", "d"); 308 309 if (exists(dmdSnapPath)) 310 { 311 paths = ["druntime", "phobos"].map!(end => buildNormalizedPath(dmdSnapPath, 312 "current", "import", end)).array; 313 break; 314 } 315 else if (exists(ldcSnapIncludePath)) 316 { 317 paths = [ldcSnapIncludePath]; 318 break; 319 } 320 } 321 } 322 } 323 324 version (Windows) 325 { 326 const pathSep = ';'; 327 const ldc = "ldc2.exe"; 328 } 329 else 330 { 331 const pathSep = ':'; 332 const ldc = "ldc2"; 333 } 334 335 if (paths.length == 0) 336 { 337 foreach (path; splitter(environment["PATH"], pathSep)) 338 { 339 if (exists(buildNormalizedPath(path, ldc))) 340 { 341 paths = [buildNormalizedPath(path, "..", "import")]; 342 } 343 } 344 } 345 346 return paths.sort().uniq().filter!exists.array; 347 } 348 349 this() 350 { 351 _allocator = new ASTAllocator(); 352 _cache = ModuleCache(_allocator); 353 } 354 355 void importPath(Uri uri) 356 { 357 import dls.protocol.messages.window : Util; 358 import dls.util.constants : Tr; 359 import dub.platform : BuildPlatform; 360 import std.algorithm : map; 361 import std.array : array; 362 import std.path : baseName, buildNormalizedPath; 363 364 auto d = getDub(uri); 365 string[string] workspaceDeps; 366 auto buildSettingsList = d.project.rootPackage.recipe.buildSettings 367 ~ d.project.rootPackage.recipe.configurations.map!q{a.buildSettings}.array; 368 369 foreach (buildSettings; buildSettingsList) 370 { 371 foreach (depName, depVersion; buildSettings.dependencies) 372 { 373 workspaceDeps[depName] = depVersion.toString(); 374 } 375 } 376 377 if (uri.path in _workspaceDependencies && _workspaceDependencies[uri.path] != workspaceDeps) 378 { 379 auto id = Util.sendMessageRequest(Tr.app_upgradeSelections, 380 [Tr.app_upgradeSelections_upgrade], [d.projectName]); 381 Util.bindMessageToRequestId(id, Tr.app_upgradeSelections, uri); 382 } 383 384 _workspaceDependencies[uri.path] = workspaceDeps; 385 auto packages = [d.project.rootPackage]; 386 387 foreach (sub; d.project.rootPackage.subPackages) 388 { 389 auto p = d.project.packageManager.getSubPackage(d.project.rootPackage, 390 baseName(sub.path), true); 391 392 if (p !is null) 393 { 394 packages ~= p; 395 } 396 } 397 398 foreach (p; packages) 399 { 400 const desc = p.describe(BuildPlatform.any, null, null); 401 importDirectories(desc.importPaths.length > 0 402 ? desc.importPaths.map!(path => buildNormalizedPath(p.path.toString(), 403 path)).array : [uri.path]); 404 importSelections(Uri.fromPath(desc.path)); 405 } 406 } 407 408 void importSelections(Uri uri) 409 { 410 import std.algorithm : map, reduce; 411 import std.array : array; 412 import std.path : buildNormalizedPath; 413 414 const d = getDub(uri); 415 416 foreach (dep; d.project.dependencies) 417 { 418 auto paths = reduce!(q{a ~ b})(cast(string[])[], 419 dep.recipe.buildSettings.sourcePaths.values); 420 importDirectories(paths.map!(path => buildNormalizedPath(dep.path.toString(), 421 path)).array); 422 } 423 } 424 425 void clearPath(Uri uri) 426 { 427 // import dls.util.logger : logger; 428 429 // logger.infof("Clearing imports from %s", uri.path); 430 // Implement ModuleCache.clear() in DCD 431 _workspaceDependencies.remove(uri.path); 432 } 433 434 void upgradeSelections(Uri uri) 435 { 436 import dls.util.logger : logger; 437 import std.concurrency : spawn; 438 import std.path : dirName; 439 440 logger.infof("Upgrading dependencies from %s", dirName(uri.path)); 441 442 spawn((string uriString) { 443 import dls.protocol.interfaces.dls : TranslationParams; 444 import dls.protocol.jsonrpc : send; 445 import dls.protocol.messages.methods : Dls; 446 import dls.protocol.messages.window : Util; 447 import dls.util.constants : Tr; 448 import dub.dub : UpgradeOptions; 449 450 send(Dls.UpgradeSelections.didStart, 451 new TranslationParams(Tr.app_upgradeSelections_upgrading)); 452 453 try 454 { 455 getDub(new Uri(uriString)).upgrade(UpgradeOptions.upgrade | UpgradeOptions.select); 456 } 457 catch (Exception e) 458 { 459 Util.sendMessage(Tr.app_upgradeSelections_error, [e.msg]); 460 } 461 finally 462 { 463 send(Dls.UpgradeSelections.didStop); 464 } 465 }, uri.toString()); 466 } 467 468 SymbolInformation[] symbol(string query) 469 { 470 import dls.util.document : Document; 471 import dls.util.logger : logger; 472 import dsymbol.string_interning : internString; 473 import dsymbol.symbol : DSymbol; 474 import std.algorithm : any, canFind, map, startsWith; 475 import std.array : appender, array; 476 import std.file : SpanMode, dirEntries; 477 import std.regex : matchFirst, regex; 478 479 logger.infof(`Fetching symbols from workspace with query "%s"`, query); 480 481 auto result = new SymbolInformationTree(); 482 483 void collectSymbolInformations(Uri symbolUri, const(DSymbol)* symbol, 484 string containerName = "") 485 { 486 import std.typecons : nullable; 487 488 if (symbol.symbolFile != symbolUri.path) 489 { 490 return; 491 } 492 493 auto name = symbol.name == "*constructor*" ? "this" : symbol.name; 494 495 if (name.matchFirst(regex(query, "i"))) 496 { 497 auto location = new Location(symbolUri, Document.get(symbolUri) 498 .wordRangeAtByte(symbol.location)); 499 result.insert(new SymbolInformation(name, 500 symbolKinds[symbol.kind], location, containerName.nullable)); 501 } 502 503 foreach (s; symbol.getPartsByName(internString(null))) 504 { 505 collectSymbolInformations(symbolUri, s, name); 506 } 507 } 508 509 auto moduleUris = appender!(Uri[]); 510 511 foreach (path; _cache.getImportPaths()) 512 { 513 if (_workspaceDependencies.byKey.any!(w => path.startsWith(w))) 514 { 515 foreach (entry; dirEntries(path, "*.{d,di}", SpanMode.breadth)) 516 { 517 moduleUris ~= Uri.fromPath(entry.name); 518 } 519 } 520 } 521 522 foreach (moduleUri; moduleUris.data) 523 { 524 if (Document.uris.map!q{a.path}.canFind(moduleUri.path)) 525 { 526 result.insert(symbol!SymbolInformation(moduleUri, query)); 527 continue; 528 } 529 530 auto moduleSymbol = _cache.cacheModule(moduleUri.path); 531 532 if (moduleSymbol !is null) 533 { 534 foreach (symbol; moduleSymbol.getPartsByName(internString(null))) 535 { 536 collectSymbolInformations(moduleUri, symbol); 537 } 538 } 539 } 540 541 return result.array; 542 } 543 544 SymbolType[] symbol(SymbolType)(Uri uri, string query) 545 if (is(SymbolType == SymbolInformation) || is(SymbolType == DocumentSymbol)) 546 { 547 import dls.util.document : Document; 548 import dls.util.logger : logger; 549 import dparse.lexer : LexerConfig, StringBehavior, StringCache, 550 getTokensForParser; 551 import dparse.parser : parseModule; 552 import dparse.rollback_allocator : RollbackAllocator; 553 import std.functional : toDelegate; 554 555 logger.infof("Fetching symbols from %s", uri.path); 556 557 static void doNothing(string, size_t, size_t, string, bool) 558 { 559 } 560 561 auto stringCache = StringCache(StringCache.defaultBucketCount); 562 auto tokens = getTokensForParser(Document.get(uri).toString(), 563 LexerConfig(uri.path, StringBehavior.source), &stringCache); 564 RollbackAllocator ra; 565 const mod = parseModule(tokens, uri.path, &ra, toDelegate(&doNothing)); 566 auto visitor = new SymbolVisitor!SymbolType(uri, query); 567 visitor.visit(mod); 568 return visitor.result; 569 } 570 571 CompletionItem[] completion(Uri uri, Position position) 572 { 573 import dcd.common.messages : AutocompleteResponse, CompletionType; 574 import dcd.server.autocomplete : complete; 575 import dls.util.logger : logger; 576 import std.algorithm : chunkBy, map, sort, uniq; 577 import std.array : array; 578 import std.conv : to; 579 import std.json : JSONValue; 580 581 logger.infof("Fetching completions for %s at position %s,%s", uri.path, 582 position.line, position.character); 583 584 auto request = getPreparedRequest(uri, position, RequestKind.autocomplete); 585 static bool compareCompletionsLess(AutocompleteResponse.Completion a, 586 AutocompleteResponse.Completion b) 587 { 588 //dfmt off 589 return a.identifier < b.identifier ? true 590 : a.identifier > b.identifier ? false 591 : a.symbolFilePath < b.symbolFilePath ? true 592 : a.symbolFilePath > b.symbolFilePath ? false 593 : a.symbolLocation < b.symbolLocation; 594 //dfmt on 595 } 596 597 static bool compareCompletionsEqual(AutocompleteResponse.Completion a, 598 AutocompleteResponse.Completion b) 599 { 600 return a.symbolFilePath == b.symbolFilePath && a.symbolLocation == b.symbolLocation; 601 } 602 603 auto result = complete(request, _cache); 604 605 if (result.completionType != CompletionType.identifiers) 606 { 607 return []; 608 } 609 610 return result.completions 611 .sort!compareCompletionsLess 612 .uniq!compareCompletionsEqual 613 .chunkBy!q{a.identifier == b.identifier} 614 .map!((resultGroup) { 615 import std.uni : toLower; 616 617 auto firstResult = resultGroup.front; 618 auto item = new CompletionItem(firstResult.identifier); 619 item.kind = completionKinds[firstResult.kind.to!CompletionKind]; 620 item.detail = firstResult.definition; 621 622 string[][] data; 623 624 foreach (res; resultGroup) 625 { 626 if (res.documentation.length > 0 && res.documentation.toLower() != "ditto") 627 { 628 data ~= [res.definition, res.documentation]; 629 } 630 } 631 632 if (data.length > 0) 633 { 634 item.data = JSONValue(data); 635 } 636 637 return item; 638 }) 639 .array; 640 } 641 642 CompletionItem completionResolve(CompletionItem item) 643 { 644 import std.algorithm : map; 645 import std.array : array; 646 647 if (!item.data.isNull) 648 { 649 item.documentation = getDocumentation( 650 item.data.array.map!q{ [a[0].str, a[1].str] }.array); 651 item.data.nullify(); 652 } 653 654 return item; 655 } 656 657 Hover hover(Uri uri, Position position) 658 { 659 import dcd.server.autocomplete : getDoc; 660 import dls.util.logger : logger; 661 import std.algorithm : filter, map, sort, uniq; 662 import std.array : array; 663 664 logger.infof("Fetching documentation for %s at position %s,%s", 665 uri.path, position.line, position.character); 666 667 auto request = getPreparedRequest(uri, position, RequestKind.doc); 668 auto result = getDoc(request, _cache); 669 auto completions = result.completions 670 .map!q{a.documentation} 671 .filter!q{a.length > 0} 672 .array 673 .sort().uniq(); 674 675 return completions.empty ? null 676 : new Hover(getDocumentation(completions.map!q{ ["", a] }.array)); 677 } 678 679 Location[] definition(Uri uri, Position position) 680 { 681 import dcd.common.messages : CompletionType; 682 import dcd.server.autocomplete.util : getSymbolsForCompletion; 683 import dls.util.document : Document; 684 import dls.util.logger : logger; 685 import dparse.lexer : StringCache; 686 import dparse.rollback_allocator : RollbackAllocator; 687 688 logger.infof("Finding declarations for %s at position %s,%s", uri.path, 689 position.line, position.character); 690 691 auto request = getPreparedRequest(uri, position, RequestKind.symbolLocation); 692 auto stringCache = StringCache(StringCache.defaultBucketCount); 693 RollbackAllocator ra; 694 auto currentFileStuff = getSymbolsForCompletion(request, 695 CompletionType.location, _allocator, &ra, stringCache, cache); 696 697 scope (exit) 698 { 699 currentFileStuff.destroy(); 700 } 701 702 Location[] result; 703 704 foreach (symbol; currentFileStuff.symbols) 705 { 706 auto symbolUri = symbol.symbolFile == "stdin" ? uri : Uri.fromPath(symbol.symbolFile); 707 auto document = Document.get(symbolUri); 708 request.fileName = symbolUri.path; 709 request.sourceCode = cast(ubyte[]) document.toString(); 710 request.cursorPosition = symbol.location + 1; 711 auto stuff = getSymbolsForCompletion(request, 712 CompletionType.location, _allocator, &ra, stringCache, cache); 713 714 scope (exit) 715 { 716 stuff.destroy(); 717 } 718 719 foreach (s; stuff.symbols) 720 { 721 result ~= new Location(symbolUri, document.wordRangeAtByte(s.location)); 722 } 723 } 724 725 return result; 726 } 727 728 Location[] typeDefinition(Uri uri, Position position) 729 { 730 import dcd.common.messages : CompletionType; 731 import dcd.server.autocomplete.util : getSymbolsForCompletion; 732 import dls.util.document : Document; 733 import dls.util.logger : logger; 734 import dparse.lexer : StringCache; 735 import dparse.rollback_allocator : RollbackAllocator; 736 import std.algorithm : filter, map, uniq; 737 738 logger.infof("Finding type declaration for %s at position %s,%s", 739 uri.path, position.line, position.character); 740 741 auto request = getPreparedRequest(uri, position, RequestKind.symbolLocation); 742 auto stringCache = StringCache(StringCache.defaultBucketCount); 743 RollbackAllocator ra; 744 auto stuff = getSymbolsForCompletion(request, CompletionType.location, 745 _allocator, &ra, stringCache, cache); 746 747 scope (exit) 748 { 749 stuff.destroy(); 750 } 751 752 Location[] result; 753 754 foreach (type; stuff.symbols 755 .map!q{a.type} 756 .filter!q{a !is null && a.symbolFile.length > 0} 757 .uniq!q{a.symbolFile == b.symbolFile && a.location == b.location}) 758 { 759 auto symbolUri = type.symbolFile == "stdin" ? uri : Uri.fromPath(type.symbolFile); 760 result ~= new Location(symbolUri, Document.get(symbolUri) 761 .wordRangeAtByte(type.location)); 762 } 763 764 return result; 765 } 766 767 Location[] references(Uri uri, Position position, bool includeDeclaration) 768 { 769 import dls.util.logger : logger; 770 import std.algorithm : filter, map, reduce; 771 import std.array : array; 772 import std.file : SpanMode, dirEntries; 773 import std.path : globMatch; 774 775 logger.infof("Finding references for %s at position %s,%s", uri.path, 776 position.line, position.character); 777 778 auto workspaceUris = _workspaceDependencies.keys 779 .map!(w => dirEntries(w, SpanMode.depth).map!q{a.name} 780 .filter!(path => globMatch(path, "*.{d,di}")) 781 .map!(Uri.fromPath) 782 .array) 783 .reduce!q{a ~ b}; 784 return referencesForFiles(uri, position, workspaceUris, includeDeclaration, false); 785 } 786 787 DocumentHighlight[] highlight(Uri uri, Position position) 788 { 789 import dls.protocol.interfaces : DocumentHighlightKind; 790 import dls.util.logger : logger; 791 import std.algorithm : any; 792 import std.path : filenameCmp; 793 import std.typecons : nullable; 794 795 logger.infof("Highlighting usages for %s at position %s,%s", uri.path, 796 position.line, position.character); 797 798 auto sources = referencesForFiles(uri, position, [uri], true, true); 799 auto locations = referencesForFiles(uri, position, [uri], true, false); 800 DocumentHighlight[] result; 801 802 foreach (location; locations) 803 { 804 auto kind = sources.any!(sourceLoc => location.range.start.line == sourceLoc.range.start.line 805 && location.range.start.character == sourceLoc.range.start.character 806 && filenameCmp(new Uri(location.uri).path, new Uri(sourceLoc.uri).path) == 0) ? DocumentHighlightKind 807 .write : DocumentHighlightKind.read; 808 result ~= new DocumentHighlight(location.range, kind.nullable); 809 } 810 811 return result; 812 } 813 814 WorkspaceEdit rename(Uri uri, Position position, string newName) 815 { 816 import dls.protocol.definitions : TextDocumentEdit, TextEdit, 817 VersionedTextDocumentIdentifier; 818 import dls.protocol.state : initState; 819 import dls.util.document : Document; 820 import dls.util.logger : logger; 821 import std.typecons : nullable; 822 823 if ((initState.capabilities.textDocument.isNull || initState.capabilities.textDocument.rename.isNull 824 || initState.capabilities.textDocument.rename.prepareSupport.isNull 825 || !initState.capabilities.textDocument.rename.prepareSupport) 826 && prepareRename(uri, position) is null) 827 { 828 return null; 829 } 830 831 logger.infof("Renaming symbol for %s at position %s,%s", uri.path, 832 position.line, position.character); 833 834 auto refs = references(uri, position, true); 835 836 if (refs.length == 0) 837 { 838 return null; 839 } 840 841 TextEdit[][string] changes; 842 TextDocumentEdit[] documentChanges; 843 844 foreach (reference; refs) 845 { 846 changes[reference.uri] ~= new TextEdit(reference.range, newName); 847 } 848 849 foreach (documentUri, textEdits; changes) 850 { 851 auto identifier = new VersionedTextDocumentIdentifier(documentUri, 852 Document.get(new Uri(documentUri)).version_); 853 documentChanges ~= new TextDocumentEdit(identifier, textEdits); 854 } 855 856 return new WorkspaceEdit(changes.nullable, documentChanges.nullable); 857 } 858 859 Range prepareRename(Uri uri, Position position) 860 { 861 import dls.util.document : Document; 862 import dls.util.logger : logger; 863 import std.algorithm : any; 864 865 logger.infof("Preparing symbol rename for %s at position %s,%s", 866 uri.path, position.line, position.character); 867 868 auto defs = definition(uri, position); 869 return defs.length == 0 870 || defs.any!(d => getWorkspace(new Uri(d.uri)) is null) ? null 871 : Document.get(uri).wordRangeAtPosition(position); 872 } 873 874 Uri getWorkspace(in Uri uri) 875 { 876 import std.algorithm : startsWith; 877 import std.array : array; 878 import std.path : buildNormalizedPath, pathSplitter; 879 880 string[] workspacePathParts; 881 882 foreach (path; _workspaceDependencies.keys) 883 { 884 auto splitter = pathSplitter(path); 885 886 if (pathSplitter(uri.path).startsWith(splitter)) 887 { 888 auto pathParts = splitter.array; 889 890 if (pathParts.length > workspacePathParts.length) 891 { 892 workspacePathParts = pathParts; 893 } 894 } 895 } 896 897 return workspacePathParts.length > 0 898 ? Uri.fromPath(buildNormalizedPath(workspacePathParts)) : null; 899 } 900 901 private void importDirectories(string[] paths) 902 { 903 import dls.util.logger : logger; 904 905 logger.infof("Importing directories: %s", paths); 906 _cache.addImportPaths(paths); 907 } 908 909 private Location[] referencesForFiles(Uri uri, Position position, Uri[] files, 910 bool includeDeclaration, bool sources) 911 { 912 import dcd.common.messages : CompletionType; 913 import dcd.server.autocomplete.util : getSymbolsForCompletion; 914 import dls.util.document : Document; 915 import dparse.lexer : LexerConfig, StringBehavior, StringCache, Token, 916 WhitespaceBehavior, getTokensForParser, tok; 917 import dparse.rollback_allocator : RollbackAllocator; 918 import dsymbol.string_interning : internString; 919 import std.range : zip; 920 921 auto request = getPreparedRequest(uri, position, RequestKind.symbolLocation); 922 auto stringCache = StringCache(StringCache.defaultBucketCount); 923 auto sourceTokens = getTokensForParser(Document.get(uri).toString(), 924 LexerConfig(uri.path, StringBehavior.compiler, WhitespaceBehavior.skip), 925 &stringCache); 926 RollbackAllocator ra; 927 auto stuff = getSymbolsForCompletion(request, CompletionType.location, 928 _allocator, &ra, stringCache, cache); 929 930 scope (exit) 931 { 932 stuff.destroy(); 933 } 934 935 const(Token)* sourceToken; 936 937 foreach (i, token; sourceTokens) 938 { 939 if (token.type == tok!"identifier" && request.cursorPosition >= token.index 940 && request.cursorPosition < token.index + token.text.length) 941 { 942 sourceToken = &sourceTokens[i]; 943 break; 944 } 945 } 946 947 if (sourceToken is null) 948 { 949 return null; 950 } 951 952 size_t[] sourceSymbolLocations; 953 string[] sourceSymbolFiles; 954 Location[] result; 955 956 foreach (s; stuff.symbols) 957 { 958 sourceSymbolLocations ~= s.location; 959 sourceSymbolFiles ~= s.symbolFile == "stdin" ? uri.path : s.symbolFile; 960 } 961 962 if (sources) 963 { 964 foreach (sourceLocation, sourceFile; zip(sourceSymbolLocations, sourceSymbolFiles)) 965 { 966 auto sourceUri = Uri.fromPath(sourceFile); 967 result ~= new Location(sourceUri.toString(), 968 Document.get(sourceUri).wordRangeAtByte(sourceLocation)); 969 } 970 971 return result; 972 } 973 974 bool checkFileAndLocation(string file, size_t location) 975 { 976 import std.path : filenameCmp; 977 978 foreach (sourceLocation, sourceFile; zip(sourceSymbolLocations, sourceSymbolFiles)) 979 { 980 if (location == sourceLocation && filenameCmp(file, sourceFile) == 0) 981 { 982 return true; 983 } 984 } 985 986 return false; 987 } 988 989 foreach (fileUri; files) 990 { 991 auto document = Document.get(fileUri); 992 request.fileName = fileUri.path; 993 request.sourceCode = cast(ubyte[]) document.toString(); 994 auto tokens = getTokensForParser(request.sourceCode, LexerConfig(fileUri.path, 995 StringBehavior.compiler, WhitespaceBehavior.skip), &stringCache); 996 997 foreach (token; tokens) 998 { 999 if (token.type == tok!"identifier" && token.text == sourceToken.text) 1000 { 1001 request.cursorPosition = token.index + 1; 1002 auto candidateStuff = getSymbolsForCompletion(request, 1003 CompletionType.location, _allocator, &ra, stringCache, cache); 1004 1005 scope (exit) 1006 { 1007 candidateStuff.destroy(); 1008 } 1009 1010 foreach (candidateSymbol; candidateStuff.symbols) 1011 { 1012 if (!includeDeclaration && checkFileAndLocation(fileUri.path, token.index)) 1013 { 1014 continue; 1015 } 1016 1017 const candidateSymbolFile = candidateSymbol.symbolFile == "stdin" 1018 ? fileUri.path : candidateSymbol.symbolFile; 1019 1020 if (checkFileAndLocation(candidateSymbolFile, candidateSymbol.location)) 1021 { 1022 result ~= new Location(fileUri.toString(), 1023 document.wordRangeAtByte(token.index)); 1024 } 1025 } 1026 } 1027 } 1028 } 1029 1030 return result; 1031 } 1032 1033 private MarkupContent getDocumentation(string[][] detailsAndDocumentations) 1034 { 1035 import ddoc : Lexer, expand; 1036 import dls.protocol.definitions : MarkupKind; 1037 import std.array : appender, replace; 1038 import std.regex : regex, split; 1039 1040 auto result = appender!string; 1041 bool putSeparator; 1042 1043 foreach (dad; detailsAndDocumentations) 1044 { 1045 if (putSeparator) 1046 { 1047 result ~= "\n\n---\n\n"; 1048 } 1049 else 1050 { 1051 putSeparator = true; 1052 } 1053 1054 auto detail = dad[0]; 1055 auto documentation = dad[1]; 1056 auto content = documentation.split(regex(`\n-+(\n|$)`)); 1057 bool isExample; 1058 1059 if (detail.length > 0 && detailsAndDocumentations.length > 1) 1060 { 1061 result ~= "### "; 1062 result ~= detail; 1063 result ~= "\n\n"; 1064 } 1065 1066 foreach (chunk; content) 1067 { 1068 if (isExample) 1069 { 1070 result ~= "```d\n"; 1071 result ~= chunk; 1072 result ~= "\n```\n"; 1073 } 1074 else 1075 { 1076 result ~= expand(Lexer(chunk.replace("\n", " ")), macros); 1077 result ~= '\n'; 1078 } 1079 1080 isExample = !isExample; 1081 } 1082 } 1083 1084 return new MarkupContent(MarkupKind.markdown, result.data); 1085 } 1086 1087 private static AutocompleteRequest getPreparedRequest(Uri uri, 1088 Position position, RequestKind kind) 1089 { 1090 import dls.protocol.jsonrpc : InvalidParamsException; 1091 import dls.util.document : Document; 1092 import std.format : format; 1093 1094 auto request = AutocompleteRequest(); 1095 auto document = Document.get(uri); 1096 1097 if (!document.validatePosition(position)) 1098 { 1099 throw new InvalidParamsException(format!"invalid position: %s %s,%s"(uri, 1100 position.line, position.character)); 1101 } 1102 1103 request.fileName = uri.path; 1104 request.kind = kind; 1105 request.sourceCode = cast(ubyte[]) document.toString(); 1106 request.cursorPosition = document.byteAtPosition(position); 1107 1108 return request; 1109 } 1110 1111 private static Dub getDub(Uri uri) 1112 { 1113 import std.file : isFile; 1114 import std.path : dirName; 1115 1116 auto d = new Dub(isFile(uri.path) ? dirName(uri.path) : uri.path); 1117 d.loadPackage(); 1118 return d; 1119 } 1120 } 1121 1122 private class SymbolVisitor(SymbolType) : ASTVisitor 1123 { 1124 import dls.protocol.definitions : Range; 1125 import dls.protocol.interfaces : DocumentSymbol; 1126 import dls.util.uri : Uri; 1127 import std.typecons : nullable; 1128 1129 SymbolType[] result; 1130 private Uri _uri; 1131 private string _query; 1132 1133 static if (is(SymbolType == SymbolInformation)) 1134 { 1135 private string container; 1136 } 1137 else 1138 { 1139 private DocumentSymbol container; 1140 } 1141 1142 this(Uri uri, string query) 1143 { 1144 _uri = uri; 1145 _query = query; 1146 } 1147 1148 override void visit(const ModuleDeclaration dec) 1149 { 1150 dec.accept(this); 1151 } 1152 1153 override void visit(const ClassDeclaration dec) 1154 { 1155 visitSymbol(dec, SymbolKind.class_, true, dec.structBody is null ? 0 1156 : dec.structBody.endLocation); 1157 } 1158 1159 override void visit(const StructDeclaration dec) 1160 { 1161 visitSymbol(dec, SymbolKind.struct_, true, dec.structBody is null ? 0 1162 : dec.structBody.endLocation); 1163 } 1164 1165 override void visit(const InterfaceDeclaration dec) 1166 { 1167 visitSymbol(dec, SymbolKind.interface_, true, dec.structBody is null ? 0 1168 : dec.structBody.endLocation); 1169 } 1170 1171 override void visit(const UnionDeclaration dec) 1172 { 1173 visitSymbol(dec, SymbolKind.interface_, true, dec.structBody is null ? 0 1174 : dec.structBody.endLocation); 1175 } 1176 1177 override void visit(const EnumDeclaration dec) 1178 { 1179 visitSymbol(dec, SymbolKind.enum_, true, dec.enumBody is null ? 0 : dec 1180 .enumBody.endLocation); 1181 } 1182 1183 override void visit(const EnumMember mem) 1184 { 1185 visitSymbol(mem, SymbolKind.enumMember, false); 1186 } 1187 1188 override void visit(const AnonymousEnumMember mem) 1189 { 1190 visitSymbol(mem, SymbolKind.enumMember, false); 1191 } 1192 1193 override void visit(const TemplateDeclaration dec) 1194 { 1195 visitSymbol(dec, SymbolKind.function_, true, dec.endLocation); 1196 } 1197 1198 override void visit(const FunctionDeclaration dec) 1199 { 1200 visitSymbol(dec, SymbolKind.function_, false, getFunctionEndLocation(dec)); 1201 } 1202 1203 override void visit(const Constructor dec) 1204 { 1205 tryInsertFunction(dec, "this"); 1206 } 1207 1208 override void visit(const Destructor dec) 1209 { 1210 tryInsertFunction(dec, "~this"); 1211 } 1212 1213 override void visit(const StaticConstructor dec) 1214 { 1215 tryInsertFunction(dec, "static this"); 1216 } 1217 1218 override void visit(const StaticDestructor dec) 1219 { 1220 tryInsertFunction(dec, "static ~this"); 1221 } 1222 1223 override void visit(const SharedStaticConstructor dec) 1224 { 1225 tryInsertFunction(dec, "shared static this"); 1226 } 1227 1228 override void visit(const SharedStaticDestructor dec) 1229 { 1230 tryInsertFunction(dec, "shared static ~this"); 1231 } 1232 1233 override void visit(const Invariant dec) 1234 { 1235 tryInsert("invariant", SymbolKind.function_, getRange(dec), 1236 dec.blockStatement.endLocation); 1237 } 1238 1239 override void visit(const VariableDeclaration dec) 1240 { 1241 foreach (d; dec.declarators) 1242 { 1243 tryInsert(d.name.text, SymbolKind.variable, getRange(d.name)); 1244 } 1245 1246 dec.accept(this); 1247 } 1248 1249 override void visit(const AutoDeclaration dec) 1250 { 1251 foreach (part; dec.parts) 1252 { 1253 tryInsert(part.identifier.text, SymbolKind.variable, getRange(part.identifier)); 1254 } 1255 1256 dec.accept(this); 1257 } 1258 1259 override void visit(const Unittest dec) 1260 { 1261 } 1262 1263 override void visit(const AliasDeclaration dec) 1264 { 1265 if (dec.declaratorIdentifierList !is null) 1266 { 1267 foreach (id; dec.declaratorIdentifierList.identifiers) 1268 { 1269 tryInsert(id.text, SymbolKind.variable, getRange(id)); 1270 } 1271 } 1272 1273 dec.accept(this); 1274 } 1275 1276 override void visit(const AliasInitializer dec) 1277 { 1278 visitSymbol(dec, SymbolKind.variable, true); 1279 } 1280 1281 override void visit(const AliasThisDeclaration dec) 1282 { 1283 tryInsert(dec.identifier.text, SymbolKind.variable, getRange(dec.identifier)); 1284 dec.accept(this); 1285 } 1286 1287 private size_t getFunctionEndLocation(A : ASTNode)(const A dec) 1288 { 1289 size_t endLocation; 1290 1291 if (dec.functionBody !is null) 1292 { 1293 endLocation = dec.functionBody.bodyStatement !is null 1294 ? dec.functionBody.bodyStatement.blockStatement.endLocation 1295 : dec.functionBody.blockStatement.endLocation; 1296 } 1297 1298 return endLocation; 1299 } 1300 1301 private void visitSymbol(A : ASTNode)(const A dec, SymbolKind kind, 1302 bool accept, size_t endLocation = 0) 1303 { 1304 tryInsert(dec.name.text.dup, kind, getRange(dec.name), endLocation); 1305 1306 if (accept) 1307 { 1308 auto oldContainer = container; 1309 1310 static if (is(SymbolType == SymbolInformation)) 1311 { 1312 container = dec.name.text.dup; 1313 } 1314 else 1315 { 1316 container = (container is null ? result : container.children)[$ - 1]; 1317 } 1318 1319 dec.accept(this); 1320 container = oldContainer; 1321 } 1322 } 1323 1324 private Range getRange(T)(T t) 1325 { 1326 import dls.util.document : Document; 1327 1328 auto document = Document.get(_uri); 1329 1330 static if (__traits(hasMember, T, "line") && __traits(hasMember, T, "column")) 1331 { 1332 return document.wordRangeAtLineAndByte(t.line - 1, t.column - 1); 1333 } 1334 else 1335 { 1336 return document.wordRangeAtByte(t.index); 1337 } 1338 } 1339 1340 private void tryInsert(string name, SymbolKind kind, Range range, size_t endLocation = 0) 1341 { 1342 import dls.protocol.definitions : Location, Position; 1343 import dls.util.document : Document; 1344 import std.regex : matchFirst, regex; 1345 import std.typecons : Nullable, nullable; 1346 1347 if (_query is null || name.matchFirst(regex(_query, "i"))) 1348 { 1349 static if (is(SymbolType == SymbolInformation)) 1350 { 1351 result ~= new SymbolInformation(name, kind, new Location(_uri, 1352 range), container.nullable); 1353 } 1354 else 1355 { 1356 auto fullRange = endLocation > 0 ? new Range(range.start, 1357 Document.get(_uri).positionAtByte(endLocation)) : range; 1358 DocumentSymbol[] children; 1359 (container is null ? result : container.children) ~= new DocumentSymbol(name, 1360 Nullable!string(), kind, Nullable!bool(), fullRange, 1361 range, children.nullable); 1362 } 1363 } 1364 } 1365 1366 private void tryInsertFunction(A : ASTNode)(const A dec, string name) 1367 { 1368 tryInsert(name, SymbolKind.function_, getRange(dec), getFunctionEndLocation(dec)); 1369 } 1370 1371 alias visit = ASTVisitor.visit; 1372 }