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 }