Skip to content

Commit 6e498f9

Browse files
committed
Signature help (#547)
* wire up server capability + command for signature help * set up analysis bin command, and needed things from the LS protocol * set up empty signature help test * another test to help distinguish * implement signature help for function applications * changelog + readme * leverage the parser to figure out the parameter offsets * include docs for signature if available * if a function has only one (unlabelled) argument, we can always highlight that as active, no matter what is picked up at the cursor * add debug utilities * get rid of hasPosInclusive * move extractExpApplyArgs to SharedTypes for real * shared function * add way to parse via raw source string in parser, and replace current temp file mechanism for parsing signature help type label * refactor logic some * refactor hover so type expansion can be shared, and integrate into signature help * gate new experimental signature help behind configuration option * comment * changelog
1 parent b8af06a commit 6e498f9

File tree

16 files changed

+731
-56
lines changed

16 files changed

+731
-56
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Add support for create interface file for JSX V4 https://github.com/rescript-lang/rescript-vscode/pull/580
1919
- Expand one level of type definition on hover. Dig into record/variant body. https://github.com/rescript-lang/rescript-vscode/pull/584
2020
- Add clickable links to type definitions in hovers. https://github.com/rescript-lang/rescript-vscode/pull/585
21+
- Add experimental signature help for function calls. https://github.com/rescript-lang/rescript-vscode/pull/547
2122

2223
#### :bug: Bug Fix
2324

Diff for: README.md

+7-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ The only 2 themes we don't (and can't) support, due to their lack of coloring, a
6868
- Find references.
6969
- Rename.
7070
- Inlay Hints.
71+
- Signature help.
7172
- Code lenses.
7273
- Snippets to ease a few syntaxes:
7374
- `external` features such as `@bs.module` and `@bs.val`
@@ -85,14 +86,15 @@ ext install chenglou92.rescript-vscode
8586
The plugin activates on `.res` and `.resi` files. If you've already got Reason-Language-Server installed, it's possible that the latter took precedence over this one. Make sure you're using this plugin ("ReScript syntax") rather than Reason-Language-Server ("BuckleScript syntax").
8687

8788
### Pre-release channel
89+
8890
There is a pre-release channel available. It is intended for testing new and therefore possibly unstable features. You can activate it by clicking on the "Switch to Pre-Release Version" button on the `rescript-vscode` extension page in VSCode. From this point on, pre-release versions will always have an odd version minor (1.5.x, 1.7.x, 2.1.x, etc.) while stable releases have even version minor numbers (1.4.x, 1.6.x, 2.0.0, etc.).
8991

9092
Even if the pre-release channel seems too experimental to you, we still suggest you to give it a try and submit any issues that you run into. In the long run it will give us a better editor experience overall.
9193

9294
## 📦 Commands
9395

9496
| Command | Description |
95-
|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
97+
| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9698
| ReScript: Create an interface file for this implementation file | Creates an interface file (`.resi`) for the current `.res` file, automatically filling in all types and values in the current file. |
9799
| ReScript: Open the compiled JS file for this implementation file | Opens the compiled JS file for the current ReScript file. |
98100
| ReScript: Switch implementation/interface | Switches between the implementation and interface file. If you're in a `.res` file, the command will open the corresponding `.resi` file (if it exists), and if you're in a `.resi` file the command will open the corresponding `.res` file. This can also be triggered with the keybinding `Alt+O`. |
@@ -104,10 +106,11 @@ You'll find all ReScript specific settings under the scope `rescript.settings`.
104106

105107
| Setting | Description |
106108
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
107-
| Prompt to Start Build | If there's no ReScript build running already in the opened project, the extension will prompt you and ask if you want to start a build automatically. You can turn off this automatic prompt via the setting `rescript.settings.askToStartBuild`. |
109+
| Prompt to Start Build | If there's no ReScript build running already in the opened project, the extension will prompt you and ask if you want to start a build automatically. You can turn off this automatic prompt via the setting `rescript.settings.askToStartBuild`. |
108110
| ReScript Binary Path | The extension will look for the existence of a `/node_modules/.bin/rescript` file and use its directory as the `binaryPath`. If it does not find it at the project root (which is where the nearest `bsconfig.json` resides), it goes up folders in the filesystem recursively until it either finds it (often the case in monorepos) or hits the top level. To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.binaryPath` |
109-
| Inlay Hints (experimental) | This allows an editor to place annotations inline with text to display type hints. Enable using `rescript.settings.inlayHints.enable: true` |
110-
| Code Lens (experimental) | This tells the editor to add code lenses to function definitions, showing its full type above the definition. Enable using `rescript.settings.codeLens: true` |
111+
| Inlay Hints (experimental) | This allows an editor to place annotations inline with text to display type hints. Enable using `rescript.settings.inlayHints.enable: true` |
112+
| Code Lens (experimental) | This tells the editor to add code lenses to function definitions, showing its full type above the definition. Enable using `rescript.settings.codeLens: true` |
113+
| Signature Help (experimental) | This tells the editor to show signature help when you're writing function calls. Enable using `rescript.settings.signatureHelp.enable: true` |
111114
| Autostarting the Code Analyzer | The Code Analyzer needs to be started manually by default. However, you can configure the extension to start the Code Analyzer automatically via the setting `rescript.settings.autoRunCodeAnalysis`. |
112115

113116
**Default settings:**

Diff for: analysis/src/Cli.ml

+8
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ Options:
7575

7676
./rescript-editor-analysis.exe codeLens src/MyFile.res
7777

78+
signatureHelp: get signature help if available for position at line 10 column 2 in src/MyFile.res
79+
80+
./rescript-editor-analysis.exe signatureHelp src/MyFile.res 10 2
81+
7882
test: run tests specified by special comments in file src/MyFile.res
7983

8084
./rescript-editor-analysis.exe test src/src/MyFile.res
@@ -103,6 +107,10 @@ let main () =
103107
(match supportsMarkdownLinks with
104108
| "true" -> true
105109
| _ -> false)
110+
| [_; "signatureHelp"; path; line; col; currentFile] ->
111+
Commands.signatureHelp ~path
112+
~pos:(int_of_string line, int_of_string col)
113+
~currentFile ~debug:false
106114
| [_; "inlayHint"; path; line_start; line_end; maxLength] ->
107115
Commands.inlayhint ~path
108116
~pos:(int_of_string line_start, int_of_string line_end)

Diff for: analysis/src/Commands.ml

+16
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ let hover ~path ~pos ~currentFile ~debug ~supportsMarkdownLinks =
7979
in
8080
print_endline result
8181

82+
let signatureHelp ~path ~pos ~currentFile ~debug =
83+
let result =
84+
match SignatureHelp.signatureHelp ~path ~pos ~currentFile ~debug with
85+
| None ->
86+
{Protocol.signatures = []; activeSignature = None; activeParameter = None}
87+
| Some res -> res
88+
in
89+
print_endline (Protocol.stringifySignatureHelp result)
90+
8291
let codeAction ~path ~pos ~currentFile ~debug =
8392
Xform.extractCodeActions ~path ~pos ~currentFile ~debug
8493
|> CodeActions.stringifyCodeActions |> print_endline
@@ -335,6 +344,13 @@ let test ~path =
335344
hover ~supportsMarkdownLinks:true ~path ~pos:(line, col)
336345
~currentFile ~debug:true;
337346
Sys.remove currentFile
347+
| "she" ->
348+
print_endline
349+
("Signature help " ^ path ^ " " ^ string_of_int line ^ ":"
350+
^ string_of_int col);
351+
let currentFile = createCurrentFile () in
352+
signatureHelp ~path ~pos:(line, col) ~currentFile ~debug:true;
353+
Sys.remove currentFile
338354
| "int" ->
339355
print_endline ("Create Interface " ^ path);
340356
let cmiFile =

Diff for: analysis/src/CompletionFrontEnd.ml

+1-42
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,6 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
106106
in
107107
args |> processProps ~acc:[]
108108

109-
type labelled = {
110-
name: string;
111-
opt: bool;
112-
posStart: int * int;
113-
posEnd: int * int;
114-
}
115-
116-
type label = labelled option
117-
type arg = {label: label; exp: Parsetree.expression}
118-
119109
let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor
120110
~(contextPath : Completable.contextPath) ~posAfterFunExpr =
121111
let allNames =
@@ -145,37 +135,6 @@ let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor
145135
in
146136
loop args
147137

148-
let extractExpApplyArgs ~args =
149-
let rec processArgs ~acc args =
150-
match args with
151-
| (((Asttypes.Labelled s | Optional s) as label), (e : Parsetree.expression))
152-
:: rest -> (
153-
let namedArgLoc =
154-
e.pexp_attributes
155-
|> List.find_opt (fun ({Asttypes.txt}, _) -> txt = "ns.namedArgLoc")
156-
in
157-
match namedArgLoc with
158-
| Some ({loc}, _) ->
159-
let labelled =
160-
{
161-
name = s;
162-
opt =
163-
(match label with
164-
| Optional _ -> true
165-
| _ -> false);
166-
posStart = Loc.start loc;
167-
posEnd = Loc.end_ loc;
168-
}
169-
in
170-
processArgs ~acc:({label = Some labelled; exp = e} :: acc) rest
171-
| None -> processArgs ~acc rest)
172-
| (Asttypes.Nolabel, (e : Parsetree.expression)) :: rest ->
173-
if e.pexp_loc.loc_ghost then processArgs ~acc rest
174-
else processArgs ~acc:({label = None; exp = e} :: acc) rest
175-
| [] -> List.rev acc
176-
in
177-
args |> processArgs ~acc:[]
178-
179138
let rec exprToContextPath (e : Parsetree.expression) =
180139
match e.pexp_desc with
181140
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
@@ -204,7 +163,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
204163
let line, col = posCursor in
205164
(line, max 0 col - offset + offsetNoWhite)
206165
in
207-
let posBeforeCursor = (fst posCursor, max 0 (snd posCursor - 1)) in
166+
let posBeforeCursor = Pos.posBeforeCursor posCursor in
208167
let charBeforeCursor, blankAfterCursor =
209168
match Pos.positionToOffset text posCursor with
210169
| Some offset when offset > 0 -> (

Diff for: analysis/src/Pos.ml

+2
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ let positionToOffset text (line, character) =
2424
| Some bol ->
2525
if bol + character <= String.length text then Some (bol + character)
2626
else None
27+
28+
let posBeforeCursor pos = (fst pos, max 0 (snd pos - 1))

Diff for: analysis/src/Protocol.ml

+56
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@ type inlayHint = {
1616
paddingRight: bool;
1717
}
1818

19+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#parameterInformation *)
20+
type parameterInformation = {label: int * int; documentation: markupContent}
21+
22+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureInformation *)
23+
type signatureInformation = {
24+
label: string;
25+
parameters: parameterInformation list;
26+
documentation: markupContent option;
27+
}
28+
29+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureHelp *)
30+
type signatureHelp = {
31+
signatures: signatureInformation list;
32+
activeSignature: int option;
33+
activeParameter: int option;
34+
}
35+
1936
type completionItem = {
2037
label: string;
2138
kind: int;
@@ -170,6 +187,45 @@ let stringifyCodeLens (codeLens : codeLens) =
170187
| None -> ""
171188
| Some command -> stringifyCommand command)
172189

190+
let stringifyParameterInformation (parameterInformation : parameterInformation)
191+
=
192+
Printf.sprintf {|{"label": %s, "documentation": %s}|}
193+
(let line, chr = parameterInformation.label in
194+
"[" ^ string_of_int line ^ ", " ^ string_of_int chr ^ "]")
195+
(stringifyMarkupContent parameterInformation.documentation)
196+
197+
let stringifySignatureInformation (signatureInformation : signatureInformation)
198+
=
199+
Printf.sprintf
200+
{|{
201+
"label": "%s",
202+
"parameters": %s%s
203+
}|}
204+
(Json.escape signatureInformation.label)
205+
(signatureInformation.parameters
206+
|> List.map stringifyParameterInformation
207+
|> array)
208+
(match signatureInformation.documentation with
209+
| None -> ""
210+
| Some docs ->
211+
Printf.sprintf ",\n \"documentation\": %s"
212+
(stringifyMarkupContent docs))
213+
214+
let stringifySignatureHelp (signatureHelp : signatureHelp) =
215+
Printf.sprintf
216+
{|{
217+
"signatures": %s,
218+
"activeSignature": %s,
219+
"activeParameter": %s
220+
}|}
221+
(signatureHelp.signatures |> List.map stringifySignatureInformation |> array)
222+
(match signatureHelp.activeSignature with
223+
| None -> null
224+
| Some activeSignature -> activeSignature |> string_of_int)
225+
(match signatureHelp.activeParameter with
226+
| None -> null
227+
| Some activeParameter -> activeParameter |> string_of_int)
228+
173229
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *)
174230
let stringifyDiagnostic d =
175231
Printf.sprintf

0 commit comments

Comments
 (0)