Skip to content

Commit c692e48

Browse files
authored
Cleanup completion details docs (#952)
* clean up details and documentation output on completion items * implement completionResolve LSP request and resolve file module docs async * changelog
1 parent c7114a7 commit c692e48

34 files changed

+930
-652
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
#### :rocket: New Feature
1616

1717
- Extend signature help to work on constructor payloads as well. Can be turned off if wanted through settings. https://github.com/rescript-lang/rescript-vscode/pull/947
18+
- Show module docs for file modules. https://github.com/rescript-lang/rescript-vscode/pull/952
1819

1920
#### :nail_care: Polish
2021

2122
- Enhance variant constructor payload completion. https://github.com/rescript-lang/rescript-vscode/pull/946
2223
- Clean occasional dots from "insert missing fields" code action. https://github.com/rescript-lang/rescript-vscode/pull/948
2324
- Pick up code actions in incremental compilation. https://github.com/rescript-lang/rescript-vscode/pull/948
2425
- Various improvements to the signature help functionality. https://github.com/rescript-lang/rescript-vscode/pull/950
26+
- Clean up completion item "details" and "documentation". https://github.com/rescript-lang/rescript-vscode/pull/952
2527

2628
## 1.48.0
2729

analysis/bin/main.ml

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ let main () =
115115
Commands.completion ~debug ~path
116116
~pos:(int_of_string line, int_of_string col)
117117
~currentFile
118+
| [_; "completionResolve"; path; modulePath] ->
119+
Commands.completionResolve ~path ~modulePath
118120
| [_; "definition"; path; line; col] ->
119121
Commands.definition ~path
120122
~pos:(int_of_string line, int_of_string col)

analysis/src/Commands.ml

+40-6
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,42 @@ let completion ~debug ~path ~pos ~currentFile =
44
Completions.getCompletions ~debug ~path ~pos ~currentFile ~forHover:false
55
with
66
| None -> []
7-
| Some (completions, _, _) -> completions
7+
| Some (completions, full, _) ->
8+
completions
9+
|> List.map (CompletionBackEnd.completionToItem ~full)
10+
|> List.map Protocol.stringifyCompletionItem
811
in
9-
print_endline
10-
(completions
11-
|> List.map CompletionBackEnd.completionToItem
12-
|> List.map Protocol.stringifyCompletionItem
13-
|> Protocol.array)
12+
completions |> Protocol.array |> print_endline
13+
14+
let completionResolve ~path ~modulePath =
15+
(* We ignore the internal module path as of now because there's currently
16+
no use case for it. But, if we wanted to move resolving documentation
17+
for regular modules and not just file modules to the completionResolve
18+
hook as well, it'd be easy to implement here. *)
19+
let moduleName, _innerModulePath =
20+
match modulePath |> String.split_on_char '.' with
21+
| [moduleName] -> (moduleName, [])
22+
| moduleName :: rest -> (moduleName, rest)
23+
| [] -> raise (Failure "Invalid module path.")
24+
in
25+
let docstring =
26+
match Cmt.loadFullCmtFromPath ~path with
27+
| None ->
28+
if Debug.verbose () then
29+
Printf.printf "[completion_resolve] Could not load cmt\n";
30+
Protocol.null
31+
| Some full -> (
32+
match ProcessCmt.fileForModule ~package:full.package moduleName with
33+
| None ->
34+
if Debug.verbose () then
35+
Printf.printf "[completion_resolve] Did not find file for module %s\n"
36+
moduleName;
37+
Protocol.null
38+
| Some file ->
39+
file.structure.docstring |> String.concat "\n\n"
40+
|> Protocol.wrapInQuotes)
41+
in
42+
print_endline docstring
1443

1544
let inlayhint ~path ~pos ~maxLength ~debug =
1645
let result =
@@ -321,6 +350,11 @@ let test ~path =
321350
let currentFile = createCurrentFile () in
322351
completion ~debug:true ~path ~pos:(line, col) ~currentFile;
323352
Sys.remove currentFile
353+
| "cre" ->
354+
let modulePath = String.sub rest 3 (String.length rest - 3) in
355+
let modulePath = String.trim modulePath in
356+
print_endline ("Completion resolve: " ^ modulePath);
357+
completionResolve ~path ~modulePath
324358
| "dce" ->
325359
print_endline ("DCE " ^ path);
326360
Reanalyze.RunConfig.runConfig.suppress <- ["src"];

analysis/src/CompletionBackEnd.ml

+114-32
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ let completionForExporteds iterExported getDeclared ~prefix ~exact ~env
7979
res :=
8080
{
8181
(Completion.create declared.name.txt ~env
82-
~kind:(transformContents declared.item))
82+
~kind:(transformContents declared))
8383
with
8484
deprecated = declared.deprecated;
8585
docstring = declared.docstring;
@@ -90,18 +90,20 @@ let completionForExporteds iterExported getDeclared ~prefix ~exact ~env
9090

9191
let completionForExportedModules ~env ~prefix ~exact ~namesUsed =
9292
completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Module)
93-
(Stamps.findModule env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun m ->
94-
Completion.Module m)
93+
(Stamps.findModule env.file.stamps) ~prefix ~exact ~env ~namesUsed
94+
(fun declared ->
95+
Completion.Module
96+
{docstring = declared.docstring; module_ = declared.item})
9597

9698
let completionForExportedValues ~env ~prefix ~exact ~namesUsed =
9799
completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Value)
98-
(Stamps.findValue env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun v ->
99-
Completion.Value v)
100+
(Stamps.findValue env.file.stamps) ~prefix ~exact ~env ~namesUsed
101+
(fun declared -> Completion.Value declared.item)
100102

101103
let completionForExportedTypes ~env ~prefix ~exact ~namesUsed =
102104
completionForExporteds (Exported.iter env.QueryEnv.exported Exported.Type)
103-
(Stamps.findType env.file.stamps) ~prefix ~exact ~env ~namesUsed (fun t ->
104-
Completion.Type t)
105+
(Stamps.findType env.file.stamps) ~prefix ~exact ~env ~namesUsed
106+
(fun declared -> Completion.Type declared.item)
105107

106108
let completionsForExportedConstructors ~(env : QueryEnv.t) ~prefix ~exact
107109
~namesUsed =
@@ -224,39 +226,109 @@ let getEnvWithOpens ~scope ~(env : QueryEnv.t) ~package
224226
| None -> None
225227
| Some env -> ResolvePath.resolvePath ~env ~package ~path))
226228

229+
let rec expandTypeExpr ~env ~package typeExpr =
230+
match typeExpr |> Shared.digConstructor with
231+
| Some path -> (
232+
match References.digConstructor ~env ~package path with
233+
| None -> None
234+
| Some (env, {item = {decl = {type_manifest = Some t}}}) ->
235+
expandTypeExpr ~env ~package t
236+
| Some (_, {docstring; item}) -> Some (docstring, item))
237+
| None -> None
238+
239+
let kindToDocumentation ~env ~full ~currentDocstring name
240+
(kind : Completion.kind) =
241+
let docsFromKind =
242+
match kind with
243+
| ObjLabel _ | Label _ | FileModule _ | Snippet _ | FollowContextPath _ ->
244+
[]
245+
| Module {docstring} -> docstring
246+
| Type {decl; name} ->
247+
[decl |> Shared.declToString name |> Markdown.codeBlock]
248+
| Value typ -> (
249+
match expandTypeExpr ~env ~package:full.package typ with
250+
| None -> []
251+
| Some (docstrings, {decl; name; kind}) ->
252+
docstrings
253+
@ [
254+
(match kind with
255+
| Record _ | Tuple _ | Variant _ ->
256+
Markdown.codeBlock (Shared.declToString name decl)
257+
| _ -> "");
258+
])
259+
| Field ({typ; optional; docstring}, s) ->
260+
(* Handle optional fields. Checking for "?" is because sometimes optional
261+
fields are prefixed with "?" when completing, and at that point we don't
262+
need to _also_ add a "?" after the field name, as that looks weird. *)
263+
docstring
264+
@ [
265+
Markdown.codeBlock
266+
(if optional && Utils.startsWith name "?" = false then
267+
name ^ "?: "
268+
^ (typ |> Utils.unwrapIfOption |> Shared.typeToString)
269+
else name ^ ": " ^ (typ |> Shared.typeToString));
270+
Markdown.codeBlock s;
271+
]
272+
| Constructor (c, s) ->
273+
[Markdown.codeBlock (showConstructor c); Markdown.codeBlock s]
274+
| PolyvariantConstructor ({displayName; args}, s) ->
275+
[
276+
Markdown.codeBlock
277+
("#" ^ displayName
278+
^
279+
match args with
280+
| [] -> ""
281+
| typeExprs ->
282+
"("
283+
^ (typeExprs
284+
|> List.map (fun typeExpr -> typeExpr |> Shared.typeToString)
285+
|> String.concat ", ")
286+
^ ")");
287+
Markdown.codeBlock s;
288+
]
289+
| ExtractedType (extractedType, _) ->
290+
[Markdown.codeBlock (TypeUtils.extractedTypeToString extractedType)]
291+
in
292+
currentDocstring @ docsFromKind
293+
|> List.filter (fun s -> s <> "")
294+
|> String.concat "\n\n"
295+
227296
let kindToDetail name (kind : Completion.kind) =
228297
match kind with
229-
| Type {decl} -> decl |> Shared.declToString name
298+
| Type {name} -> "type " ^ name
230299
| Value typ -> typ |> Shared.typeToString
231300
| ObjLabel typ -> typ |> Shared.typeToString
232301
| Label typString -> typString
233-
| Module _ -> "module"
234-
| FileModule _ -> "file module"
235-
| Field ({typ; optional}, s) ->
302+
| Module _ -> "module " ^ name
303+
| FileModule f -> "module " ^ f
304+
| Field ({typ; optional}, _) ->
236305
(* Handle optional fields. Checking for "?" is because sometimes optional
237306
fields are prefixed with "?" when completing, and at that point we don't
238307
need to _also_ add a "?" after the field name, as that looks weird. *)
239308
if optional && Utils.startsWith name "?" = false then
240-
name ^ "?: "
241-
^ (typ |> Utils.unwrapIfOption |> Shared.typeToString)
242-
^ "\n\n" ^ s
243-
else name ^ ": " ^ (typ |> Shared.typeToString) ^ "\n\n" ^ s
244-
| Constructor (c, s) -> showConstructor c ^ "\n\n" ^ s
245-
| PolyvariantConstructor ({displayName; args}, s) ->
309+
typ |> Utils.unwrapIfOption |> Shared.typeToString
310+
else typ |> Shared.typeToString
311+
| Constructor (c, _) -> showConstructor c
312+
| PolyvariantConstructor ({displayName; args}, _) -> (
246313
"#" ^ displayName
247-
^ (match args with
248-
| [] -> ""
249-
| typeExprs ->
250-
"("
251-
^ (typeExprs
252-
|> List.map (fun typeExpr -> typeExpr |> Shared.typeToString)
253-
|> String.concat ", ")
254-
^ ")")
255-
^ "\n\n" ^ s
314+
^
315+
match args with
316+
| [] -> ""
317+
| typeExprs ->
318+
"("
319+
^ (typeExprs
320+
|> List.map (fun typeExpr -> typeExpr |> Shared.typeToString)
321+
|> String.concat ", ")
322+
^ ")")
256323
| Snippet s -> s
257324
| FollowContextPath _ -> ""
258325
| ExtractedType (extractedType, _) ->
259-
TypeUtils.extractedTypeToString extractedType
326+
TypeUtils.extractedTypeToString ~nameOnly:true extractedType
327+
328+
let kindToData filePath (kind : Completion.kind) =
329+
match kind with
330+
| FileModule f -> Some [("modulePath", f); ("filePath", filePath)]
331+
| _ -> None
260332

261333
let findAllCompletions ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed
262334
~(completionContext : Completable.completionContext) =
@@ -366,7 +438,9 @@ let processLocalModule name loc ~prefix ~exact ~env
366438
localTables.resultRev <-
367439
{
368440
(Completion.create declared.name.txt ~env
369-
~kind:(Module declared.item))
441+
~kind:
442+
(Module
443+
{docstring = declared.docstring; module_ = declared.item}))
370444
with
371445
deprecated = declared.deprecated;
372446
docstring = declared.docstring;
@@ -579,7 +653,7 @@ let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
579653
| {kind = Type {kind = Record fields}} :: _ -> Some fields
580654
| _ -> None
581655

582-
let mkItem ~name ~kind ~detail ~deprecated ~docstring =
656+
let mkItem ?data name ~kind ~detail ~deprecated ~docstring =
583657
let docContent =
584658
(match deprecated with
585659
| None -> ""
@@ -607,6 +681,7 @@ let mkItem ~name ~kind ~detail ~deprecated ~docstring =
607681
insertText = None;
608682
insertTextFormat = None;
609683
filterText = None;
684+
data;
610685
}
611686

612687
let completionToItem
@@ -620,16 +695,23 @@ let completionToItem
620695
insertTextFormat;
621696
filterText;
622697
detail;
623-
} =
698+
env;
699+
} ~full =
624700
let item =
625-
mkItem ~name
701+
mkItem name
702+
?data:(kindToData (full.file.uri |> Uri.toPath) kind)
626703
~kind:(Completion.kindToInt kind)
627704
~deprecated
628705
~detail:
629706
(match detail with
630707
| None -> kindToDetail name kind
631708
| Some detail -> detail)
632-
~docstring
709+
~docstring:
710+
(match
711+
kindToDocumentation ~currentDocstring:docstring ~full ~env name kind
712+
with
713+
| "" -> []
714+
| docstring -> [docstring])
633715
in
634716
{item with sortText; insertText; insertTextFormat; filterText}
635717

analysis/src/Protocol.ml

+9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type completionItem = {
5050
insertTextFormat: insertTextFormat option;
5151
insertText: string option;
5252
documentation: markupContent option;
53+
data: (string * string) list option;
5354
}
5455

5556
type location = {uri: string; range: range}
@@ -139,6 +140,14 @@ let stringifyCompletionItem c =
139140
| None -> None
140141
| Some insertTextFormat ->
141142
Some (Printf.sprintf "%i" (insertTextFormatToInt insertTextFormat)) );
143+
( "data",
144+
match c.data with
145+
| None -> None
146+
| Some fields ->
147+
Some
148+
(fields
149+
|> List.map (fun (key, value) -> (key, Some (wrapInQuotes value)))
150+
|> stringifyObject ~indentation:2) );
142151
]
143152

144153
let stringifyHover value =

analysis/src/SharedTypes.ml

+5-3
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ end
775775

776776
module Completion = struct
777777
type kind =
778-
| Module of Module.t
778+
| Module of {docstring: string list; module_: Module.t}
779779
| Value of Types.type_expr
780780
| ObjLabel of Types.type_expr
781781
| Label of string
@@ -800,10 +800,11 @@ module Completion = struct
800800
kind: kind;
801801
detail: string option;
802802
typeArgContext: typeArgContext option;
803+
data: (string * string) list option;
803804
}
804805

805-
let create ?typeArgContext ?(includesSnippets = false) ?insertText ~kind ~env
806-
?sortText ?deprecated ?filterText ?detail ?(docstring = []) name =
806+
let create ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind
807+
~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name =
807808
{
808809
name;
809810
env;
@@ -817,6 +818,7 @@ module Completion = struct
817818
filterText;
818819
detail;
819820
typeArgContext;
821+
data;
820822
}
821823

822824
(* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *)

0 commit comments

Comments
 (0)