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 }