Skip to content

Commit e9756ee

Browse files
authored
Merge pull request #513 from rescript-lang/functions-codelens
Code Lens
2 parents 35ac5f2 + 980beeb commit e9756ee

File tree

15 files changed

+194
-10
lines changed

15 files changed

+194
-10
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
#### :rocket: New Feature
1616

17-
- Inlay Hints (experimetal). `rescript.settings.inlayHints.enable: true`
17+
- Inlay Hints (experimetal). `rescript.settings.inlayHints.enable: true`. Turned off by default.
18+
- Code Lenses for functions (experimetal). `rescript.settings.codeLens: true`. Turned off by default.
19+
1820
## v1.4.2
1921

2022
#### :bug: Bug Fix

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ rescript.settings.inlayHints.enable: true
117117
rescript.settings.inlayHints.maxLength: 25
118118
```
119119

120+
### Code Lens (experimental)
121+
122+
This tells the editor to add code lenses to function definitions, showing its full type above the definition.
123+
124+
```jsonc
125+
// Enable (experimental) code lens.
126+
rescript.settings.codeLens: true
127+
```
128+
120129
### Hide generated files
121130

122131
You can configure VSCode to collapse the JavaScript files ReScript generates under its source ReScript file. This will "hide" the generated files in the VSCode file explorer, but still leaving them accessible by expanding the source ReScript file they belong to.

analysis/src/Cli.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ API examples:
1111
./rescript-editor-analysis.exe references src/MyFile.res 10 2
1212
./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo
1313
./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
14-
/rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25
14+
./rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25
15+
./rescript-editor-analysis.exe codeLens src/MyFile.res
1516

1617
Dev-time examples:
1718
./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res
@@ -70,6 +71,10 @@ Options:
7071

7172
./rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25
7273

74+
codeLens: get all code lens entries for file src/MyFile.res
75+
76+
./rescript-editor-analysis.exe codeLens src/MyFile.res
77+
7378
test: run tests specified by special comments in file src/MyFile.res
7479

7580
./rescript-editor-analysis.exe test src/src/MyFile.res
@@ -98,6 +103,7 @@ let main () =
98103
Commands.inlayhint ~path
99104
~pos:(int_of_string line_start, int_of_string line_end)
100105
~maxLength ~debug:false
106+
| [_; "codeLens"; path] -> Commands.codeLens ~path ~debug:false
101107
| [_; "codeAction"; path; line; col; currentFile] ->
102108
Commands.codeAction ~path
103109
~pos:(int_of_string line, int_of_string col)

analysis/src/Commands.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ let inlayhint ~path ~pos ~maxLength ~debug =
3232
let result = Hint.inlay ~path ~pos ~maxLength ~debug |> Protocol.array in
3333
print_endline result
3434

35+
let codeLens ~path ~debug =
36+
let result = Hint.codeLens ~path ~debug |> Protocol.array in
37+
print_endline result
38+
3539
let hover ~path ~pos ~currentFile ~debug =
3640
let result =
3741
match Cmt.fullFromPath ~path with
@@ -391,6 +395,9 @@ let test ~path =
391395
let line_end = 6 in
392396
print_endline ("Inlay Hint " ^ path ^ " " ^ string_of_int line_start ^ ":" ^ string_of_int line_end);
393397
inlayhint ~path ~pos:(line_start, line_end) ~maxLength:"25" ~debug:false)
398+
| "cle" ->
399+
print_endline ("Code Lens " ^ path);
400+
codeLens ~path ~debug:false
394401
| _ -> ());
395402
print_newline ())
396403
in

analysis/src/Hint.ml

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,60 @@ let inlay ~path ~pos ~maxLength ~debug =
119119
| Some value ->
120120
if String.length label > value then None else Some result
121121
| None -> Some result)
122-
| None -> None)))
122+
| None -> None)))
123+
124+
let codeLens ~path ~debug =
125+
let lenses = ref [] in
126+
let push loc =
127+
let range = Utils.cmtLocToRange loc in
128+
lenses := range :: !lenses
129+
in
130+
(* Code lenses are only emitted for functions right now. So look for value bindings that are functions,
131+
and use the loc of the value binding itself so we can look up the full function type for our code lens. *)
132+
let value_binding (iterator : Ast_iterator.iterator)
133+
(vb : Parsetree.value_binding) =
134+
(match vb with
135+
| {
136+
pvb_pat = {ppat_desc = Ppat_var _; ppat_loc};
137+
pvb_expr = {pexp_desc = Pexp_fun _};
138+
} ->
139+
push ppat_loc
140+
| _ -> ());
141+
Ast_iterator.default_iterator.value_binding iterator vb
142+
in
143+
let iterator = {Ast_iterator.default_iterator with value_binding} in
144+
(* We only print code lenses in implementation files. This is because they'd be redundant in interface files,
145+
where the definition itself will be the same thing as what would've been printed in the code lens. *)
146+
(if Filename.check_suffix path ".res" then
147+
let parser =
148+
Res_driver.parsingEngine.parseImplementation ~forPrinter:false
149+
in
150+
let {Res_driver.parsetree = structure} = parser ~filename:path in
151+
iterator.structure iterator structure |> ignore);
152+
!lenses
153+
|> List.filter_map (fun (range : Protocol.range) ->
154+
match Cmt.fullFromPath ~path with
155+
| None -> None
156+
| Some full -> (
157+
match
158+
References.getLocItem ~full
159+
~pos:(range.start.line, range.start.character + 1)
160+
~debug
161+
with
162+
| Some {locType = Typed (_, typeExpr, _)} ->
163+
Some
164+
(Protocol.stringifyCodeLens
165+
{
166+
range;
167+
command =
168+
Some
169+
{
170+
(* Code lenses can run commands. An empty command string means we just want the editor
171+
to print the text, not link to running a command. *)
172+
command = "";
173+
(* Print the type with a huge line width, because the code lens always prints on a
174+
single line in the editor. *)
175+
title = typeExpr |> Shared.typeToString ~lineWidth:400;
176+
};
177+
})
178+
| _ -> None))

analysis/src/PrintType.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
let printExpr typ =
1+
let printExpr ?(lineWidth = 60) typ =
22
Printtyp.reset_names ();
3-
Res_doc.toString ~width:60
3+
Res_doc.toString ~width:lineWidth
44
(Res_outcome_printer.printOutTypeDoc (Printtyp.tree_of_typexp false typ))
55

66
let printDecl ~recStatus name decl =

analysis/src/Protocol.ml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
type position = {line: int; character: int}
22
type range = {start: position; end_: position}
33
type markupContent = {kind: string; value: string}
4+
5+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command *)
6+
type command = {title: string; command: string}
7+
8+
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeLens *)
9+
type codeLens = {range: range; command: command option}
10+
411
type inlayHint = {
512
position: position;
613
label: string;
@@ -147,6 +154,22 @@ let stringifyHint hint =
147154
(stringifyPosition hint.position)
148155
(Json.escape hint.label) hint.kind hint.paddingLeft hint.paddingRight
149156

157+
let stringifyCommand (command : command) =
158+
Printf.sprintf {|{"title": "%s", "command": "%s"}|}
159+
(Json.escape command.title)
160+
(Json.escape command.command)
161+
162+
let stringifyCodeLens (codeLens : codeLens) =
163+
Printf.sprintf
164+
{|{
165+
"range": %s,
166+
"command": %s
167+
}|}
168+
(stringifyRange codeLens.range)
169+
(match codeLens.command with
170+
| None -> ""
171+
| Some command -> stringifyCommand command)
172+
150173
(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *)
151174
let stringifyDiagnostic d =
152175
Printf.sprintf

analysis/src/Shared.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ let declToString ?(recStatus = Types.Trec_not) name t =
4949
let cacheTypeToString = ref false
5050
let typeTbl = Hashtbl.create 1
5151

52-
let typeToString (t : Types.type_expr) =
52+
let typeToString ?lineWidth (t : Types.type_expr) =
5353
match
5454
if !cacheTypeToString then Hashtbl.find_opt typeTbl (t.id, t) else None
5555
with
5656
| None ->
57-
let s = PrintType.printExpr t in
57+
let s = PrintType.printExpr ?lineWidth t in
5858
Hashtbl.replace typeTbl (t.id, t) s;
5959
s
6060
| Some s -> s

analysis/tests/src/CodeLens.res

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
let add = (x, y) => x + y
2+
3+
let foo = (~age, ~name) => name ++ string_of_int(age)
4+
5+
let ff = (~opt1=0, ~a, ~b, (), ~opt2=0, (), ~c) => a + b + c + opt1 + opt2
6+
7+
let compFF = Completion.ff
8+
9+
@react.component
10+
let make = (~name) => React.string(name)
11+
//^cle
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Code Lens src/CodeLens.res
2+
[{
3+
"range": {"start": {"line": 9, "character": 4}, "end": {"line": 9, "character": 8}},
4+
"command": {"title": "{\"name\": string} => React.element", "command": ""}
5+
}, {
6+
"range": {"start": {"line": 4, "character": 4}, "end": {"line": 4, "character": 6}},
7+
"command": {"title": "(~opt1: int=?, ~a: int, ~b: int, unit, ~opt2: int=?, unit, ~c: int) => int", "command": ""}
8+
}, {
9+
"range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 7}},
10+
"command": {"title": "(~age: int, ~name: string) => string", "command": ""}
11+
}, {
12+
"range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 7}},
13+
"command": {"title": "(int, int) => int", "command": ""}
14+
}]
15+

0 commit comments

Comments
 (0)