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