Skip to content

Commit 688a6ca

Browse files
authored
Emit nested document symbols. (#655)
* Emit nested document symbols. Fixes #654. The change in #629 moved to a non-deprecated representation of document symbol. The new representation is the "Ikea" version of the old one, where children symbol are not computed for you, but need to be provided. This PR computes and emits the tree of symbols using the `children` field. * indent * Refactor code for indentation. * Update CHANGELOG.md * cleanup
1 parent e9853d5 commit 688a6ca

File tree

4 files changed

+201
-112
lines changed

4 files changed

+201
-112
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545

4646
- Fix issue where jump to definition would go to the wrong place when there are aliased identifiers in submodules https://github.com/rescript-lang/rescript-vscode/pull/653
4747

48+
- Fix issue where document symbols were not shown nested https://github.com/rescript-lang/rescript-vscode/pull/655
49+
4850
## v1.8.2
4951

5052
#### :rocket: New Feature

Diff for: analysis/src/DocumentSymbol.ml

+70-20
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ let kindNumber = function
2626

2727
let command ~path =
2828
let symbols = ref [] in
29+
let addSymbol name loc kind =
30+
let range = Utils.cmtLocToRange loc in
31+
let symbol : Protocol.documentSymbolItem =
32+
{name; range; kind = kindNumber kind; children = []}
33+
in
34+
symbols := symbol :: !symbols
35+
in
2936
let rec exprKind (exp : Parsetree.expression) =
3037
match exp.pexp_desc with
3138
| Pexp_fun _ -> Function
@@ -41,43 +48,41 @@ let command ~path =
4148
| Ptype_variant constrDecls ->
4249
constrDecls
4350
|> List.iter (fun (cd : Parsetree.constructor_declaration) ->
44-
symbols := (cd.pcd_name.txt, cd.pcd_loc, EnumMember) :: !symbols)
51+
addSymbol cd.pcd_name.txt cd.pcd_loc EnumMember)
4552
| Ptype_record labelDecls ->
4653
labelDecls
4754
|> List.iter (fun (ld : Parsetree.label_declaration) ->
48-
symbols := (ld.pld_name.txt, ld.pld_loc, Property) :: !symbols)
55+
addSymbol ld.pld_name.txt ld.pld_loc Property)
4956
| _ -> ()
5057
in
5158
let processTypeDeclaration (td : Parsetree.type_declaration) =
52-
symbols := (td.ptype_name.txt, td.ptype_loc, TypeParameter) :: !symbols;
59+
addSymbol td.ptype_name.txt td.ptype_loc TypeParameter;
5360
processTypeKind td.ptype_kind
5461
in
5562
let processValueDescription (vd : Parsetree.value_description) =
56-
symbols := (vd.pval_name.txt, vd.pval_loc, Variable) :: !symbols
63+
addSymbol vd.pval_name.txt vd.pval_loc Variable
5764
in
5865
let processModuleBinding (mb : Parsetree.module_binding) =
59-
symbols := (mb.pmb_name.txt, mb.pmb_loc, Module) :: !symbols
66+
addSymbol mb.pmb_name.txt mb.pmb_loc Module
6067
in
6168
let processModuleDeclaration (md : Parsetree.module_declaration) =
62-
symbols := (md.pmd_name.txt, md.pmd_loc, Module) :: !symbols
69+
addSymbol md.pmd_name.txt md.pmd_loc Module
6370
in
6471
let processExtensionConstructor (et : Parsetree.extension_constructor) =
65-
symbols := (et.pext_name.txt, et.pext_loc, Constructor) :: !symbols
72+
addSymbol et.pext_name.txt et.pext_loc Constructor
6673
in
6774
let value_binding (iterator : Ast_iterator.iterator)
6875
(vb : Parsetree.value_binding) =
6976
(match vb.pvb_pat.ppat_desc with
7077
| Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) ->
71-
symbols := (txt, vb.pvb_loc, exprKind vb.pvb_expr) :: !symbols
78+
addSymbol txt vb.pvb_loc (exprKind vb.pvb_expr)
7279
| _ -> ());
7380
Ast_iterator.default_iterator.value_binding iterator vb
7481
in
7582
let expr (iterator : Ast_iterator.iterator) (e : Parsetree.expression) =
7683
(match e.pexp_desc with
7784
| Pexp_letmodule ({txt}, modExpr, _) ->
78-
symbols :=
79-
(txt, {e.pexp_loc with loc_end = modExpr.pmod_loc.loc_end}, Module)
80-
:: !symbols
85+
addSymbol txt {e.pexp_loc with loc_end = modExpr.pmod_loc.loc_end} Module
8186
| Pexp_letexception (ec, _) -> processExtensionConstructor ec
8287
| _ -> ());
8388
Ast_iterator.default_iterator.expr iterator e
@@ -134,12 +139,57 @@ let command ~path =
134139
let parser = Res_driver.parsingEngine.parseInterface ~forPrinter:false in
135140
let {Res_driver.parsetree = signature} = parser ~filename:path in
136141
iterator.signature iterator signature |> ignore);
137-
let result =
138-
!symbols
139-
|> List.rev_map (fun (name, loc, kind) ->
140-
let range = Utils.cmtLocToRange loc in
141-
Protocol.stringifyDocumentSymbolItem
142-
{name; range; selectionRange = range; kind = kindNumber kind})
143-
|> String.concat ",\n"
144-
in
145-
print_endline ("[\n" ^ result ^ "\n]")
142+
let isInside
143+
({
144+
range =
145+
{
146+
start = {line = sl1; character = sc1};
147+
end_ = {line = el1; character = ec1};
148+
};
149+
} :
150+
Protocol.documentSymbolItem)
151+
({
152+
range =
153+
{
154+
start = {line = sl2; character = sc2};
155+
end_ = {line = el2; character = ec2};
156+
};
157+
} :
158+
Protocol.documentSymbolItem) =
159+
(sl1 > sl2 || (sl1 = sl2 && sc1 >= sc2))
160+
&& (el1 < el2 || (el1 = el2 && ec1 <= ec2))
161+
in
162+
let compareSymbol (s1 : Protocol.documentSymbolItem)
163+
(s2 : Protocol.documentSymbolItem) =
164+
let n = compare s1.range.start.line s2.range.start.line in
165+
if n <> 0 then n
166+
else
167+
let n = compare s1.range.start.character s2.range.start.character in
168+
if n <> 0 then n
169+
else
170+
let n = compare s1.range.end_.line s2.range.end_.line in
171+
if n <> 0 then n
172+
else compare s1.range.end_.character s2.range.end_.character
173+
in
174+
let rec addSymbolToChildren ~symbol children =
175+
match children with
176+
| [] -> [symbol]
177+
| last :: rest ->
178+
if isInside symbol last then
179+
let newLast =
180+
{last with children = last.children |> addSymbolToChildren ~symbol}
181+
in
182+
newLast :: rest
183+
else symbol :: children
184+
in
185+
let rec addSortedSymbolsToChildren ~sortedSymbols children =
186+
match sortedSymbols with
187+
| [] -> children
188+
| firstSymbol :: rest ->
189+
children
190+
|> addSymbolToChildren ~symbol:firstSymbol
191+
|> addSortedSymbolsToChildren ~sortedSymbols:rest
192+
in
193+
let sortedSymbols = !symbols |> List.sort compareSymbol in
194+
let symbolsWithChildren = [] |> addSortedSymbolsToChildren ~sortedSymbols in
195+
print_endline (Protocol.stringifyDocumentSymbolItems symbolsWithChildren)

Diff for: analysis/src/Protocol.ml

+44-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type documentSymbolItem = {
4646
name: string;
4747
kind: int;
4848
range: range;
49-
selectionRange: range;
49+
children: documentSymbolItem list;
5050
}
5151
type renameFile = {oldUri: string; newUri: string}
5252
type textEdit = {range: range; newText: string}
@@ -102,22 +102,54 @@ let stringifyCompletionItem c =
102102
| None -> null
103103
| Some doc -> stringifyMarkupContent doc)
104104

105-
let stringifyHover value = Printf.sprintf {|{"contents": %s}|} (stringifyMarkupContent {kind = "markdown"; value})
105+
let stringifyHover value =
106+
Printf.sprintf {|{"contents": %s}|}
107+
(stringifyMarkupContent {kind = "markdown"; value})
106108

107109
let stringifyLocation (h : location) =
108110
Printf.sprintf {|{"uri": "%s", "range": %s}|} (Json.escape h.uri)
109111
(stringifyRange h.range)
110112

111-
let stringifyDocumentSymbolItem (i : documentSymbolItem) =
112-
let range = stringifyRange i.range in
113-
Printf.sprintf
114-
{|{
115-
"name": "%s",
116-
"kind": %i,
117-
"range": %s,
118-
"selectionRange": %s
119-
}|}
120-
(Json.escape i.name) i.kind range range
113+
let stringifyDocumentSymbolItems items =
114+
let buf = Buffer.create 10 in
115+
let stringifyName name = Printf.sprintf "\"%s\"" (Json.escape name) in
116+
let stringifyKind kind = string_of_int kind in
117+
let emitStr = Buffer.add_string buf in
118+
let emitSep () = emitStr ",\n" in
119+
let rec emitItem ~indent item =
120+
let openBrace = Printf.sprintf "%s{\n" indent in
121+
let closeBrace = Printf.sprintf "\n%s}" indent in
122+
let indent = indent ^ " " in
123+
let emitField name s =
124+
emitStr (Printf.sprintf "%s\"%s\": %s" indent name s)
125+
in
126+
emitStr openBrace;
127+
emitField "name" (stringifyName item.name);
128+
emitSep ();
129+
emitField "kind" (stringifyKind item.kind);
130+
emitSep ();
131+
emitField "range" (stringifyRange item.range);
132+
emitSep ();
133+
emitField "selectionRange" (stringifyRange item.range);
134+
if item.children <> [] then (
135+
emitSep ();
136+
emitField "children" "[\n";
137+
emitBody ~indent (List.rev item.children);
138+
emitStr "]");
139+
emitStr closeBrace
140+
and emitBody ~indent items =
141+
match items with
142+
| [] -> ()
143+
| item :: rest ->
144+
emitItem ~indent item;
145+
if rest <> [] then emitSep ();
146+
emitBody ~indent rest
147+
in
148+
let indent = "" in
149+
emitStr "[\n";
150+
emitBody ~indent (List.rev items);
151+
emitStr "\n]";
152+
Buffer.contents buf
121153

122154
let stringifyRenameFile {oldUri; newUri} =
123155
Printf.sprintf {|{

0 commit comments

Comments
 (0)