1 module served.commands.symbol_search; 2 3 import served.extension; 4 import served.types; 5 6 import workspaced.api; 7 import workspaced.coms; 8 9 import std.algorithm : canFind, filter, map, startsWith; 10 import std.array : array, appender; 11 import std.json : JSONValue; 12 import std.path : extension, isAbsolute; 13 import std..string : toLower; 14 15 import fs = std.file; 16 import io = std.stdio; 17 18 @protocolMethod("workspace/symbol") 19 SymbolInformation[] provideWorkspaceSymbols(WorkspaceSymbolParams params) 20 { 21 SymbolInformation[] infos; 22 foreach (workspace; workspaces) 23 { 24 string workspaceRoot = workspace.folder.uri.uriToFile; 25 foreach (file; fs.dirEntries(workspaceRoot, fs.SpanMode.depth, false)) 26 { 27 if (!file.isFile || file.extension != ".d") 28 continue; 29 auto defs = provideDocumentSymbolsOld( 30 DocumentSymbolParamsEx(TextDocumentIdentifier(file.uriFromFile), false)); 31 foreach (def; defs) 32 if (def.name.toLower.startsWith(params.query.toLower)) 33 infos ~= def.downcast; 34 } 35 if (backend.has!DCDComponent(workspace.folder.uri.uriToFile)) 36 { 37 auto exact = backend.get!DCDComponent(workspace.folder.uri.uriToFile) 38 .searchSymbol(params.query).getYield; 39 foreach (symbol; exact) 40 { 41 if (!symbol.file.isAbsolute) 42 continue; 43 string uri = symbol.file.uriFromFile; 44 if (infos.canFind!(a => a.location.uri == uri)) 45 continue; 46 SymbolInformation info; 47 info.name = params.query; 48 info.location.uri = uri; 49 auto doc = documents.tryGet(uri); 50 if (doc != Document.init) 51 info.location.range = TextRange(doc.bytesToPosition(symbol.position)); 52 info.kind = symbol.type.convertFromDCDSearchType; 53 infos ~= info; 54 } 55 } 56 } 57 return infos; 58 } 59 60 @protocolMethod("textDocument/documentSymbol") 61 JSONValue provideDocumentSymbols(DocumentSymbolParams params) 62 { 63 import painlessjson : toJSON; 64 65 if (capabilities.textDocument.documentSymbol.hierarchicalDocumentSymbolSupport) 66 return provideDocumentSymbolsHierarchical(params).toJSON; 67 else 68 return provideDocumentSymbolsOld(DocumentSymbolParamsEx(params)).map!"a.downcast".array.toJSON; 69 } 70 71 private struct OldSymbolsCache 72 { 73 SymbolInformationEx[] symbols; 74 SymbolInformationEx[] symbolsVerbose; 75 } 76 77 PerDocumentCache!OldSymbolsCache documentSymbolsCacheOld; 78 SymbolInformationEx[] provideDocumentSymbolsOld(DocumentSymbolParamsEx params) 79 { 80 if (!backend.hasBest!DscannerComponent(params.textDocument.uri.uriToFile)) 81 return null; 82 83 auto cached = documentSymbolsCacheOld.cached(documents, params.textDocument.uri); 84 if (cached.symbolsVerbose.length) 85 return params.verbose ? cached.symbolsVerbose : cached.symbols; 86 auto document = documents.tryGet(params.textDocument.uri); 87 auto result = backend.best!DscannerComponent(params.textDocument.uri.uriToFile) 88 .listDefinitions(uriToFile(params.textDocument.uri), document.rawText, true).getYield; 89 auto ret = appender!(SymbolInformationEx[]); 90 auto retVerbose = appender!(SymbolInformationEx[]); 91 92 size_t cacheByte = size_t.max; 93 Position cachePosition; 94 95 foreach (def; result) 96 { 97 SymbolInformationEx info; 98 info.name = def.name; 99 info.location.uri = params.textDocument.uri; 100 101 auto startPosition = document.movePositionBytes(cachePosition, cacheByte, def.range[0]); 102 auto endPosition = document.movePositionBytes(startPosition, def.range[0], def.range[1]); 103 cacheByte = def.range[1]; 104 cachePosition = endPosition; 105 106 info.location.range = TextRange(startPosition, endPosition); 107 info.kind = convertFromDscannerType(def.type, def.name); 108 info.extendedType = convertExtendedFromDscannerType(def.type); 109 if (def.type == "f" && def.name == "this") 110 info.kind = SymbolKind.constructor; 111 string* ptr; 112 auto attribs = def.attributes; 113 if ((ptr = "struct" in attribs) !is null || (ptr = "class" in attribs) !is null 114 || (ptr = "enum" in attribs) !is null || (ptr = "union" in attribs) !is null) 115 info.containerName = *ptr; 116 if ("deprecation" in attribs) 117 info.deprecated_ = true; 118 if (auto name = "name" in attribs) 119 info.detail = *name; 120 121 if (!def.isVerboseType) 122 ret.put(info); 123 retVerbose.put(info); 124 } 125 documentSymbolsCacheOld.store(document, OldSymbolsCache(ret.data, retVerbose.data)); 126 127 return params.verbose ? retVerbose.data : ret.data; 128 } 129 130 PerDocumentCache!(DocumentSymbol[]) documentSymbolsCacheHierarchical; 131 DocumentSymbol[] provideDocumentSymbolsHierarchical(DocumentSymbolParams params) 132 { 133 auto cached = documentSymbolsCacheHierarchical.cached(documents, params.textDocument.uri); 134 if (cached.length) 135 return cached; 136 DocumentSymbol[] all; 137 auto symbols = provideDocumentSymbolsOld(DocumentSymbolParamsEx(params)); 138 foreach (symbol; symbols) 139 { 140 DocumentSymbol sym; 141 static foreach (member; __traits(allMembers, SymbolInformationEx)) 142 static if (__traits(hasMember, DocumentSymbol, member)) 143 __traits(getMember, sym, member) = __traits(getMember, symbol, member); 144 sym.parent = symbol.containerName; 145 sym.range = sym.selectionRange = symbol.location.range; 146 sym.selectionRange.end.line = sym.selectionRange.start.line; 147 if (sym.selectionRange.end.character < sym.selectionRange.start.character) 148 sym.selectionRange.end.character = sym.selectionRange.start.character; 149 all ~= sym; 150 } 151 152 foreach (ref sym; all) 153 { 154 if (sym.parent.length) 155 { 156 foreach (ref other; all) 157 { 158 if (other.name == sym.parent) 159 { 160 other.children ~= sym; 161 break; 162 } 163 } 164 } 165 } 166 167 DocumentSymbol[] ret = all.filter!(a => a.parent.length == 0).array; 168 documentSymbolsCacheHierarchical.store(documents.tryGet(params.textDocument.uri), ret); 169 return ret; 170 }