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 	if (document.languageId != "d")
88 		return null;
89 
90 	auto result = backend.best!DscannerComponent(params.textDocument.uri.uriToFile)
91 		.listDefinitions(uriToFile(params.textDocument.uri), document.rawText, true).getYield;
92 	auto ret = appender!(SymbolInformationEx[]);
93 	auto retVerbose = appender!(SymbolInformationEx[]);
94 
95 	size_t cacheByte = size_t.max;
96 	Position cachePosition;
97 
98 	foreach (def; result)
99 	{
100 		SymbolInformationEx info;
101 		info.name = def.name;
102 		info.location.uri = params.textDocument.uri;
103 
104 		auto startPosition = document.movePositionBytes(cachePosition, cacheByte, def.range[0]);
105 		auto endPosition = document.movePositionBytes(startPosition, def.range[0], def.range[1]);
106 		cacheByte = def.range[1];
107 		cachePosition = endPosition;
108 
109 		info.location.range = TextRange(startPosition, endPosition);
110 		info.kind = convertFromDscannerType(def.type, def.name);
111 		info.extendedType = convertExtendedFromDscannerType(def.type);
112 		if (def.type == "f" && def.name == "this")
113 			info.kind = SymbolKind.constructor;
114 		string* ptr;
115 		auto attribs = def.attributes;
116 		if ((ptr = "struct" in attribs) !is null || (ptr = "class" in attribs) !is null
117 				|| (ptr = "enum" in attribs) !is null || (ptr = "union" in attribs) !is null)
118 			info.containerName = *ptr;
119 		if ("deprecation" in attribs)
120 			info.deprecated_ = true;
121 		if (auto name = "name" in attribs)
122 			info.detail = *name;
123 
124 		if (!def.isVerboseType)
125 			ret.put(info);
126 		retVerbose.put(info);
127 	}
128 	documentSymbolsCacheOld.store(document, OldSymbolsCache(ret.data, retVerbose.data));
129 
130 	return params.verbose ? retVerbose.data : ret.data;
131 }
132 
133 PerDocumentCache!(DocumentSymbol[]) documentSymbolsCacheHierarchical;
134 DocumentSymbol[] provideDocumentSymbolsHierarchical(DocumentSymbolParams params)
135 {
136 	auto cached = documentSymbolsCacheHierarchical.cached(documents, params.textDocument.uri);
137 	if (cached.length)
138 		return cached;
139 	DocumentSymbol[] all;
140 	auto symbols = provideDocumentSymbolsOld(DocumentSymbolParamsEx(params));
141 	foreach (symbol; symbols)
142 	{
143 		DocumentSymbol sym;
144 		static foreach (member; __traits(allMembers, SymbolInformationEx))
145 			static if (__traits(hasMember, DocumentSymbol, member))
146 				__traits(getMember, sym, member) = __traits(getMember, symbol, member);
147 		sym.parent = symbol.containerName;
148 		sym.range = sym.selectionRange = symbol.location.range;
149 		sym.selectionRange.end.line = sym.selectionRange.start.line;
150 		if (sym.selectionRange.end.character < sym.selectionRange.start.character)
151 			sym.selectionRange.end.character = sym.selectionRange.start.character;
152 		all ~= sym;
153 	}
154 
155 	foreach (ref sym; all)
156 	{
157 		if (sym.parent.length)
158 		{
159 			foreach (ref other; all)
160 			{
161 				if (other.name == sym.parent)
162 				{
163 					other.children ~= sym;
164 					break;
165 				}
166 			}
167 		}
168 	}
169 
170 	DocumentSymbol[] ret = all.filter!(a => a.parent.length == 0).array;
171 	documentSymbolsCacheHierarchical.store(documents.tryGet(params.textDocument.uri), ret);
172 	return ret;
173 }