Skip to content

Commit 5f019b5

Browse files
committed
implement type hierarchy
1 parent 84a22e5 commit 5f019b5

File tree

4 files changed

+314
-1
lines changed

4 files changed

+314
-1
lines changed

src/codehelp/typehierarchy.vala

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* typehierarchy.vala
2+
*
3+
* Copyright 2022 Princeton Ferro <[email protected]>
4+
*
5+
* This file is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU Lesser General Public License as
7+
* published by the Free Software Foundation; either version 2.1 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This file is distributed in the hope that it will be useful, but
11+
* WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*
18+
* SPDX-License-Identifier: LGPL-2.1-or-later
19+
*/
20+
21+
using Vala;
22+
using Lsp;
23+
24+
namespace Vls.TypeHierarchy {
25+
TypeHierarchyItem[] get_subtypes (Project project, TypeSymbol symbol) {
26+
TypeHierarchyItem[] subtypes = {};
27+
28+
var generated_vapis = new Gee.HashSet<File> (Util.file_hash, Util.file_equal);
29+
foreach (var btarget in project.get_compilations ())
30+
generated_vapis.add_all (btarget.output);
31+
var shown_files = new Gee.HashSet<File> (Util.file_hash, Util.file_equal);
32+
foreach (var pair in SymbolReferences.get_compilations_using_symbol (project, symbol)) {
33+
foreach (var source_file in pair.first.code_context.get_source_files ()) {
34+
var gfile = File.new_for_commandline_arg (source_file.filename);
35+
// don't show symbol from generated VAPI
36+
if (gfile in generated_vapis || gfile in shown_files)
37+
continue;
38+
39+
var compilation_type_symbol = SymbolReferences.find_matching_symbol (pair.first.code_context, symbol);
40+
Vala.CodeContext.push (pair.first.code_context);
41+
var result = new NodeSearch.with_filter (
42+
source_file,
43+
compilation_type_symbol,
44+
(needle, node) => {
45+
if (needle is ObjectTypeSymbol && node is ObjectTypeSymbol)
46+
return node != needle && ((ObjectTypeSymbol)node).is_subtype_of ((ObjectTypeSymbol)needle);
47+
if (needle is Struct && node is Struct)
48+
return ((Struct)node).base_struct == (Struct)needle;
49+
return false;
50+
},
51+
true
52+
).result;
53+
foreach (var node in result)
54+
subtypes += new TypeHierarchyItem.from_symbol ((Vala.TypeSymbol)node);
55+
Vala.CodeContext.pop ();
56+
57+
shown_files.add (gfile);
58+
}
59+
}
60+
61+
return subtypes;
62+
}
63+
64+
TypeHierarchyItem[] get_supertypes (Project project, TypeSymbol symbol) {
65+
TypeHierarchyItem[] supertypes = {};
66+
67+
if (symbol is ObjectTypeSymbol) {
68+
var ots = (ObjectTypeSymbol)symbol;
69+
foreach (var iface in ots.get_interfaces ()) {
70+
var real_iface = SymbolReferences.find_real_symbol (project, iface) as TypeSymbol;
71+
if (real_iface != null)
72+
supertypes += new TypeHierarchyItem.from_symbol (real_iface);
73+
}
74+
}
75+
if (symbol is Class) {
76+
var cls = (Class)symbol;
77+
foreach (var base_type in cls.get_base_types ()) {
78+
if (base_type.type_symbol != null) {
79+
var real_type_symbol = SymbolReferences.find_real_symbol (project, base_type.type_symbol) as TypeSymbol;
80+
if (real_type_symbol != null)
81+
supertypes += new TypeHierarchyItem.from_symbol (real_type_symbol);
82+
}
83+
}
84+
} else if (symbol is Struct) {
85+
var st = (Struct)symbol;
86+
if (st.base_type != null && st.base_type.type_symbol != null) {
87+
var real_type_symbol = SymbolReferences.find_real_symbol (project, st.base_type.type_symbol) as TypeSymbol;
88+
if (real_type_symbol != null)
89+
supertypes += new TypeHierarchyItem.from_symbol (real_type_symbol);
90+
}
91+
}
92+
93+
return supertypes;
94+
}
95+
}

src/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ vls_src = files([
1818
'codehelp/symbolextractor.vala',
1919
'codehelp/symbolreferences.vala',
2020
'codehelp/symbolvisitor.vala',
21+
'codehelp/typehierarchy.vala',
2122
'documentation/cnamemapper.vala',
2223
'documentation/doccomment.vala',
2324
'documentation/girdocumentation.vala',

src/protocol.vala

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,4 +1160,114 @@ namespace Lsp {
11601160
public bool paddingLeft { get; set; }
11611161
public bool paddingRight { get; set; }
11621162
}
1163+
1164+
class TypeHierarchyItem : Object, Json.Serializable {
1165+
/**
1166+
* The name of this item
1167+
*/
1168+
public string name { get; set; }
1169+
1170+
/**
1171+
* The kind of this item
1172+
*/
1173+
public SymbolKind kind { get; set; }
1174+
1175+
/**
1176+
* Tags for this item
1177+
*/
1178+
public SymbolTags tags { get; set; }
1179+
1180+
/**
1181+
* More detail for this item, e.g. the signature of a function.
1182+
*/
1183+
public string? detail { get; set; }
1184+
1185+
/**
1186+
* The resource identifier of this item.
1187+
*/
1188+
public string uri { get; set; }
1189+
1190+
/**
1191+
* The range enclosing this symbol not including leading/trailing
1192+
* whitespace, but everything else, e.g. comments and code.
1193+
*/
1194+
public Range range { get; set; }
1195+
1196+
/**
1197+
* The range that should be selected and revealed when this symbol
1198+
* is being picked, e.g. the name of a function. Must be contained
1199+
* by {@link TypeHierarchyItem.range}
1200+
*/
1201+
public Range selectionRange { get; set; }
1202+
1203+
private TypeHierarchyItem () {}
1204+
1205+
public TypeHierarchyItem.from_symbol (Vala.TypeSymbol symbol) {
1206+
this.name = symbol.get_full_name ();
1207+
if (symbol is Vala.Class)
1208+
this.kind = SymbolKind.Class;
1209+
else if (symbol is Vala.Delegate)
1210+
this.kind = SymbolKind.Interface;
1211+
else if (symbol is Vala.Enum)
1212+
this.kind = SymbolKind.Enum;
1213+
else if (symbol is Vala.ErrorCode)
1214+
this.kind = SymbolKind.EnumMember;
1215+
else if (symbol is Vala.ErrorDomain)
1216+
this.kind = SymbolKind.Enum;
1217+
else if (symbol is Vala.Interface)
1218+
this.kind = SymbolKind.Interface;
1219+
else if (symbol is Vala.Struct)
1220+
this.kind = SymbolKind.Struct;
1221+
else if (symbol is Vala.TypeParameter)
1222+
this.kind = SymbolKind.TypeParameter;
1223+
else {
1224+
this.kind = SymbolKind.Module;
1225+
warning ("unexpected symbol kind in type hierarchy: `%s'", symbol.type_name);
1226+
}
1227+
1228+
var version = symbol.get_attribute ("Version");
1229+
if (version != null && (version.get_bool ("deprecated") || version.get_string ("deprecated_since") != null)) {
1230+
this.tags |= SymbolTags.DEPRECATED;
1231+
}
1232+
this.detail = Vls.CodeHelp.get_symbol_representation (null, symbol, null, true);
1233+
this.uri = File.new_for_commandline_arg (symbol.source_reference.file.filename).get_uri ();
1234+
this.range = new Range.from_sourceref (symbol.source_reference);
1235+
this.selectionRange = this.range;
1236+
1237+
// widen range to include all members
1238+
if (symbol is Vala.ObjectTypeSymbol) {
1239+
foreach (var member in ((Vala.ObjectTypeSymbol)symbol).get_members ()) {
1240+
if (member.source_reference != null)
1241+
this.range = this.range.union (new Range.from_sourceref (member.source_reference));
1242+
}
1243+
} else if (symbol is Vala.Enum) {
1244+
foreach (var member in ((Vala.Enum)symbol).get_values ()) {
1245+
if (member.source_reference != null)
1246+
this.range = this.range.union (new Range.from_sourceref (member.source_reference));
1247+
}
1248+
foreach (var method in ((Vala.Enum)symbol).get_methods ()) {
1249+
if (method.source_reference != null)
1250+
this.range = this.range.union (new Range.from_sourceref (method.source_reference));
1251+
}
1252+
} else if (symbol is Vala.ErrorDomain) {
1253+
foreach (var member in ((Vala.ErrorDomain)symbol).get_codes ()) {
1254+
if (member.source_reference != null)
1255+
this.range = this.range.union (new Range.from_sourceref (member.source_reference));
1256+
}
1257+
foreach (var method in ((Vala.ErrorDomain)symbol).get_methods ()) {
1258+
if (method.source_reference != null)
1259+
this.range = this.range.union (new Range.from_sourceref (method.source_reference));
1260+
}
1261+
} else if (symbol is Vala.Struct) {
1262+
foreach (var field in ((Vala.Struct)symbol).get_fields ()) {
1263+
if (field.source_reference != null)
1264+
this.range = this.range.union (new Range.from_sourceref (field.source_reference));
1265+
}
1266+
foreach (var method in ((Vala.Struct)symbol).get_methods ()) {
1267+
if (method.source_reference != null)
1268+
this.range = this.range.union (new Range.from_sourceref (method.source_reference));
1269+
}
1270+
}
1271+
}
1272+
}
11631273
}

src/server.vala

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,18 @@ class Vls.Server : Jsonrpc.Server {
238238
show_inlay_hints (client, method, id, parameters);
239239
break;
240240

241+
case "textDocument/prepareTypeHierarchy":
242+
prepare_type_hierarchy (client, method, id, parameters);
243+
break;
244+
245+
case "typeHierarchy/supertypes":
246+
show_type_hierarchy (client, method, id, parameters, true);
247+
break;
248+
249+
case "typeHierarchy/subtypes":
250+
show_type_hierarchy (client, method, id, parameters, false);
251+
break;
252+
241253
default:
242254
warning ("unhandled call `%s'", method);
243255
return false;
@@ -363,7 +375,8 @@ class Vls.Server : Jsonrpc.Server {
363375
renameProvider: build_dict (prepareProvider: new Variant.boolean (true)),
364376
codeLensProvider: build_dict (resolveProvider: new Variant.boolean (false)),
365377
callHierarchyProvider: new Variant.boolean (true),
366-
inlayHintProvider: new Variant.boolean (true)
378+
inlayHintProvider: new Variant.boolean (true),
379+
typeHierarchyProvider: new Variant.boolean (true)
367380
),
368381
serverInfo: build_dict (
369382
name: new Variant.string ("Vala Language Server"),
@@ -2266,6 +2279,100 @@ class Vls.Server : Jsonrpc.Server {
22662279
}
22672280
}
22682281

2282+
void prepare_type_hierarchy (Jsonrpc.Client client, string method, Variant id, Variant @params) {
2283+
var p = Util.parse_variant<TextDocumentPositionParams> (@params);
2284+
2285+
Project project;
2286+
Compilation compilation;
2287+
var doc = find_file (p.textDocument.uri, out compilation, out project);
2288+
if (doc == null) {
2289+
debug ("[%s] file `%s' not found", method, p.textDocument.uri);
2290+
reply_null (id, client, method);
2291+
return;
2292+
}
2293+
2294+
Vala.CodeContext.push (compilation.code_context);
2295+
2296+
var fs = new NodeSearch (doc, p.position);
2297+
2298+
if (fs.result.size == 0) {
2299+
debug (@"[$method] no results found");
2300+
reply_null (id, client, method);
2301+
Vala.CodeContext.pop ();
2302+
return;
2303+
}
2304+
2305+
var result = get_best (fs, doc);
2306+
Vala.CodeContext.pop ();
2307+
Vala.TypeSymbol type_symbol;
2308+
2309+
if (result is Vala.TypeSymbol) {
2310+
type_symbol = (Vala.TypeSymbol)result;
2311+
} else if (result is Vala.DataType && ((Vala.DataType)result).type_symbol != null) {
2312+
type_symbol = ((Vala.DataType)result).type_symbol;
2313+
} else if (result is Vala.Expression && ((Vala.Expression)result).symbol_reference is Vala.TypeSymbol) {
2314+
type_symbol = (Vala.TypeSymbol)((Vala.Expression)result).symbol_reference;
2315+
// refine the symbol
2316+
foreach (var pair in SymbolReferences.get_visible_components_of_code_node (result)) {
2317+
var symbol = pair.first;
2318+
var range = pair.second;
2319+
if (symbol is Vala.TypeSymbol && range.contains (p.position)) {
2320+
type_symbol = (Vala.TypeSymbol)symbol;
2321+
break;
2322+
}
2323+
}
2324+
} else {
2325+
reply_null (id, client, method);
2326+
return;
2327+
}
2328+
2329+
try {
2330+
var array = new Variant.array (null, {
2331+
Util.object_to_variant (new TypeHierarchyItem.from_symbol (type_symbol))
2332+
});
2333+
client.reply (id, array, cancellable);
2334+
} catch (Error e) {
2335+
debug (@"[$method] failed to reply to client: $(e.message)");
2336+
}
2337+
}
2338+
2339+
void show_type_hierarchy (Jsonrpc.Client client, string method, Variant id, Variant @params, bool supertypes) {
2340+
var itemv = @params.lookup_value ("item", VariantType.VARDICT);
2341+
var item = Util.parse_variant<TypeHierarchyItem> (itemv);
2342+
2343+
Project project;
2344+
Compilation compilation;
2345+
Vala.SourceFile? doc = find_file (item.uri, out compilation, out project);
2346+
if (doc == null) {
2347+
debug ("[%s] file `%s' not found", method, item.uri);
2348+
reply_null (id, client, method);
2349+
return;
2350+
}
2351+
2352+
Vala.CodeContext.push (compilation.code_context);
2353+
var symbol = CodeHelp.lookup_symbol_full_name (item.name, compilation.code_context.root.scope);
2354+
if (!(symbol is Vala.TypeSymbol)) {
2355+
Vala.CodeContext.pop ();
2356+
reply_null (id, client, method);
2357+
return;
2358+
}
2359+
2360+
try {
2361+
Variant[] array = {};
2362+
if (supertypes) {
2363+
foreach (var supertype in TypeHierarchy.get_supertypes (project, (Vala.TypeSymbol)symbol))
2364+
array += Util.object_to_variant (supertype);
2365+
} else {
2366+
foreach (var subtype in TypeHierarchy.get_subtypes (project, (Vala.TypeSymbol)symbol))
2367+
array += Util.object_to_variant (subtype);
2368+
}
2369+
Vala.CodeContext.pop ();
2370+
client.reply (id, array, cancellable);
2371+
} catch (Error e) {
2372+
debug ("[%s] failed to reply to client: %s", method, e.message);
2373+
}
2374+
}
2375+
22692376
void shutdown () {
22702377
debug ("shutting down...");
22712378
this.shutting_down = true;

0 commit comments

Comments
 (0)