Skip to content

Commit

Permalink
Feature: Support Inlay Hint (#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
aspeddro authored Jul 23, 2022
1 parent 8dd86d1 commit 26c8df8
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 30 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
## master

#### :rocket: New Feature

- Inlay Hints (experimetal). `rescript.settings.inlayHints.enable: true`
## v1.4.2

#### :bug: Bug Fix
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,18 @@ The extension will look for the existence of a `/node_modules/.bin/rescript` fil

To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.binaryPath`.

### Inlay Hints (experimental)

This allows an editor to place annotations inline with text to display type hints.

```jsonc
// Enable (experimental) inlay hints.
rescript.settings.inlayHints.enable: true

// Maximum length of character for inlay hints. Set to null to have an unlimited length. Inlay hints that exceed the maximum length will not be shown
rescript.settings.inlayHints.maxLength: 25
```

### Hide generated files

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.
Expand Down
14 changes: 7 additions & 7 deletions analysis/examples/larger-project/.merlin
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
####{BSB GENERATED: NO EDIT
FLG -ppx '/Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/darwin/bsc.exe -as-ppx -bs-jsx 3'
S /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
B /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
FLG -ppx '/home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/linux/bsc.exe -as-ppx -bs-jsx 3'
S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/rescript/lib/ocaml
FLG -w +a-4-9-20-40-41-42-50-61-102
S /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
B /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
S /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
B /Users/cristianocalcagno/GitHub/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@rescript/react/lib/ocaml
S /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
B /home/pedro/Desktop/Projects/rescript-vscode/analysis/examples/larger-project/node_modules/@glennsl/bs-json/lib/ocaml
S src
B lib/bs/src
S src/exception
Expand Down
9 changes: 9 additions & 0 deletions analysis/src/Cli.ml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ API examples:
./rescript-editor-analysis.exe references src/MyFile.res 10 2
./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo
./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res
/rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25

Dev-time examples:
./rescript-editor-analysis.exe dump src/MyFile.res src/MyFile2.res
Expand Down Expand Up @@ -65,6 +66,10 @@ Options:

./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res

inlayHint: get all inlay Hint between line 0 and 3 declared in MyFile.res. Last argument is maximum of character length for inlay hints

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

test: run tests specified by special comments in file src/MyFile.res

./rescript-editor-analysis.exe test src/src/MyFile.res
Expand All @@ -89,6 +94,10 @@ let main () =
Commands.hover ~path
~pos:(int_of_string line, int_of_string col)
~currentFile ~debug:false
| [_; "inlayHint"; path; line_start; line_end; maxLength] ->
Commands.inlayhint ~path
~pos:(int_of_string line_start, int_of_string line_end)
~maxLength ~debug:false
| [_; "codeAction"; path; line; col; currentFile] ->
Commands.codeAction ~path
~pos:(int_of_string line, int_of_string col)
Expand Down
9 changes: 9 additions & 0 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ let completion ~debug ~path ~pos ~currentFile =
|> List.map Protocol.stringifyCompletionItem
|> Protocol.array)

let inlayhint ~path ~pos ~maxLength ~debug =
let result = Hint.inlay ~path ~pos ~maxLength ~debug |> Protocol.array in
print_endline result

let hover ~path ~pos ~currentFile ~debug =
let result =
match Cmt.fullFromPath ~path with
Expand Down Expand Up @@ -382,6 +386,11 @@ let test ~path =
(Protocol.stringifyRange range)
indent indent newText)))
| "dia" -> diagnosticSyntax ~path
| "hin" -> (
let line_start = 0 in
let line_end = 6 in
print_endline ("Inlay Hint " ^ path ^ " " ^ string_of_int line_start ^ ":" ^ string_of_int line_end);
inlayhint ~path ~pos:(line_start, line_end) ~maxLength:"25" ~debug:false)
| _ -> ());
print_newline ())
in
Expand Down
122 changes: 122 additions & 0 deletions analysis/src/Hint.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
open SharedTypes

type inlayHintKind = Type | Parameter
let inlayKindToNumber = function
| Type -> 1
| Parameter -> 2

let locItemToTypeHint ~full:{file; package} locItem =
match locItem.locType with
| Constant t ->
Some
(match t with
| Const_int _ -> "int"
| Const_char _ -> "char"
| Const_string _ -> "string"
| Const_float _ -> "float"
| Const_int32 _ -> "int32"
| Const_int64 _ -> "int64"
| Const_nativeint _ -> "int")
| Typed (_, t, locKind) ->
let fromType typ =
typ |> Shared.typeToString
|> Str.global_replace (Str.regexp "[\r\n\t]") ""
in
Some
(match References.definedForLoc ~file ~package locKind with
| None -> fromType t
| Some (_, res) -> (
match res with
| `Declared -> fromType t
| `Constructor _ -> fromType t
| `Field -> fromType t))
| _ -> None

let inlay ~path ~pos ~maxLength ~debug =
let maxlen = try Some (int_of_string maxLength) with Failure _ -> None in
let hints = ref [] in
let start_line, end_line = pos in
let push loc kind =
let range = Utils.cmtLocToRange loc in
if start_line <= range.end_.line && end_line >= range.start.line then
hints := (range, kind) :: !hints
in
let rec processFunction (exp : Parsetree.expression) =
match exp.pexp_desc with
| Pexp_fun (_, _, pat_exp, e) -> (
match pat_exp with
| {ppat_desc = Ppat_var _} ->
push pat_exp.ppat_loc Type;
processFunction e
| _ -> processFunction e)
| _ -> ()
in
let value_binding (iterator : Ast_iterator.iterator)
(vb : Parsetree.value_binding) =
(match vb with
| {
pvb_pat = {ppat_desc = Ppat_var _};
pvb_expr =
{
pexp_desc =
( Pexp_constant _ | Pexp_tuple _ | Pexp_record _ | Pexp_variant _
| Pexp_apply _ | Pexp_match _ | Pexp_construct _ | Pexp_ifthenelse _
| Pexp_array _ | Pexp_ident _ | Pexp_try _ | Pexp_lazy _
| Pexp_send _ | Pexp_field _ | Pexp_open _ );
};
} ->
push vb.pvb_pat.ppat_loc Type
| {pvb_pat = {ppat_desc = Ppat_tuple tuples}} ->
List.iter
(fun (tuple : Parsetree.pattern) -> push tuple.ppat_loc Type)
tuples
| {
pvb_pat = {ppat_desc = Ppat_var _};
pvb_expr = {pexp_desc = Pexp_fun (_, _, pat, e)};
} ->
(match pat with
| {ppat_desc = Ppat_var _} -> push pat.ppat_loc Type
| _ -> ());
processFunction e
| _ -> ());
Ast_iterator.default_iterator.value_binding iterator vb
in
let iterator = {Ast_iterator.default_iterator with value_binding} in
(if Filename.check_suffix path ".res" then
let parser =
Res_driver.parsingEngine.parseImplementation ~forPrinter:false
in
let {Res_driver.parsetree = structure} = parser ~filename:path in
iterator.structure iterator structure |> ignore);
!hints
|> List.filter_map (fun ((range : Protocol.range), hintKind) ->
match Cmt.fullFromPath ~path with
| None -> None
| Some full -> (
match
References.getLocItem ~full
~pos:(range.start.line, range.start.character + 1)
~debug
with
| None -> None
| Some locItem -> (
let position : Protocol.position =
{line = range.start.line; character = range.end_.character}
in
match locItemToTypeHint locItem ~full with
| Some label -> (
let result =
Protocol.stringifyHint
{
kind = inlayKindToNumber hintKind;
position;
paddingLeft = true;
paddingRight = false;
label = ": " ^ label;
}
in
match maxlen with
| Some value ->
if String.length label > value then None else Some result
| None -> Some result)
| None -> None)))
19 changes: 19 additions & 0 deletions analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
type position = {line: int; character: int}
type range = {start: position; end_: position}
type markupContent = {kind: string; value: string}
type inlayHint = {
position: position;
label: string;
kind: int;
paddingLeft: bool;
paddingRight: bool;
}

type completionItem = {
label: string;
Expand Down Expand Up @@ -128,6 +135,18 @@ let stringifyCodeAction ca =
(codeActionKindToString ca.codeActionKind)
(ca.edit |> stringifyCodeActionEdit)

let stringifyHint hint =
Printf.sprintf
{|{
"position": %s,
"label": "%s",
"kind": %i,
"paddingLeft": %b,
"paddingRight": %b
}|}
(stringifyPosition hint.position)
(Json.escape hint.label) hint.kind hint.paddingLeft hint.paddingRight

(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic *)
let stringifyDiagnostic d =
Printf.sprintf
Expand Down
20 changes: 20 additions & 0 deletions analysis/tests/src/InlayHint.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
let string = "ReScript"
let number = 1
let float = 1.1
let char = 'c'

let add = (x, y) => x + y

let my_sum = 3->add(1)->add(1)->add(1)->add(8)

let withAs = (~xx as yyy) => yyy + 1


@react.component
let make = (~name) => React.string(name)

let tuple = ("ReScript", "lol")

let (lang, _) = tuple

//^hin
2 changes: 1 addition & 1 deletion analysis/tests/src/expected/Dce.res.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
DCE src/Dce.res
issues:235
issues:243

3 changes: 2 additions & 1 deletion analysis/tests/src/expected/Debug.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Dependencies: @rescript/react
Source directories: ./node_modules/@rescript/react/src ./node_modules/@rescript/react/src/legacy
Source files: ./node_modules/@rescript/react/src/React.res ./node_modules/@rescript/react/src/ReactDOM.res ./node_modules/@rescript/react/src/ReactDOMServer.res ./node_modules/@rescript/react/src/ReactDOMStyle.res ./node_modules/@rescript/react/src/ReactEvent.res ./node_modules/@rescript/react/src/ReactEvent.resi ./node_modules/@rescript/react/src/ReactTestUtils.res ./node_modules/@rescript/react/src/ReactTestUtils.resi ./node_modules/@rescript/react/src/RescriptReactErrorBoundary.res ./node_modules/@rescript/react/src/RescriptReactErrorBoundary.resi ./node_modules/@rescript/react/src/RescriptReactRouter.res ./node_modules/@rescript/react/src/RescriptReactRouter.resi ./node_modules/@rescript/react/src/legacy/ReactDOMRe.res ./node_modules/@rescript/react/src/legacy/ReasonReact.res
Source directories: ./src ./src/expected
Source files: ./src/Auto.res ./src/CompletePrioritize1.res ./src/CompletePrioritize2.res ./src/Completion.res ./src/Component.res ./src/Component.resi ./src/CreateInterface.res ./src/Cross.res ./src/Dce.res ./src/Debug.res ./src/Definition.res ./src/DefinitionWithInterface.res ./src/DefinitionWithInterface.resi ./src/Div.res ./src/DocumentSymbol.res ./src/Fragment.res ./src/Highlight.res ./src/Hover.res ./src/Jsx.res ./src/Jsx.resi ./src/LongIdentTest.res ./src/Object.res ./src/Patterns.res ./src/RecModules.res ./src/RecordCompletion.res ./src/RecoveryOnProp.res ./src/References.res ./src/ReferencesWithInterface.res ./src/ReferencesWithInterface.resi ./src/Rename.res ./src/RenameWithInterface.res ./src/RenameWithInterface.resi ./src/TableclothMap.ml ./src/TableclothMap.mli ./src/TypeDefinition.res ./src/Xform.res
Source files: ./src/Auto.res ./src/CompletePrioritize1.res ./src/CompletePrioritize2.res ./src/Completion.res ./src/Component.res ./src/Component.resi ./src/CreateInterface.res ./src/Cross.res ./src/Dce.res ./src/Debug.res ./src/Definition.res ./src/DefinitionWithInterface.res ./src/DefinitionWithInterface.resi ./src/Div.res ./src/DocumentSymbol.res ./src/Fragment.res ./src/Highlight.res ./src/Hover.res ./src/InlayHint.res ./src/Jsx.res ./src/Jsx.resi ./src/LongIdentTest.res ./src/Object.res ./src/Patterns.res ./src/RecModules.res ./src/RecordCompletion.res ./src/RecoveryOnProp.res ./src/References.res ./src/ReferencesWithInterface.res ./src/ReferencesWithInterface.resi ./src/Rename.res ./src/RenameWithInterface.res ./src/RenameWithInterface.resi ./src/TableclothMap.ml ./src/TableclothMap.mli ./src/TypeDefinition.res ./src/Xform.res
Impl cmt:./lib/bs/src/Auto.cmt res:./src/Auto.res
Impl cmt:./lib/bs/src/CompletePrioritize1.cmt res:./src/CompletePrioritize1.res
Impl cmt:./lib/bs/src/CompletePrioritize2.cmt res:./src/CompletePrioritize2.res
Expand All @@ -21,6 +21,7 @@ Impl cmt:./lib/bs/src/DocumentSymbol.cmt res:./src/DocumentSymbol.res
Impl cmt:./lib/bs/src/Fragment.cmt res:./src/Fragment.res
Impl cmt:./lib/bs/src/Highlight.cmt res:./src/Highlight.res
Impl cmt:./lib/bs/src/Hover.cmt res:./src/Hover.res
Impl cmt:./lib/bs/src/InlayHint.cmt res:./src/InlayHint.res
IntfAndImpl cmti:./lib/bs/src/Jsx.cmti resi:./src/Jsx.resi cmt:./lib/bs/src/Jsx.cmt res:./src/Jsx.res
Impl cmt:./lib/bs/src/LongIdentTest.cmt res:./src/LongIdentTest.res
Impl cmt:./lib/bs/src/Object.cmt res:./src/Object.res
Expand Down
39 changes: 39 additions & 0 deletions analysis/tests/src/expected/InlayHint.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Inlay Hint src/InlayHint.res 0:6
[{
"position": {"line": 5, "character": 15},
"label": ": int",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}, {
"position": {"line": 5, "character": 12},
"label": ": int",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}, {
"position": {"line": 3, "character": 8},
"label": ": char",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}, {
"position": {"line": 2, "character": 9},
"label": ": float",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}, {
"position": {"line": 1, "character": 10},
"label": ": int",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}, {
"position": {"line": 0, "character": 10},
"label": ": string",
"kind": 1,
"paddingLeft": true,
"paddingRight": false
}]

11 changes: 11 additions & 0 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,17 @@ export function activate(context: ExtensionContext) {
// Start the client. This will also launch the server
client.start();

// Restart the language client automatically when certain configuration
// changes. These are typically settings that affect the capabilities of the
// language client, and because of that requires a full restart.
context.subscriptions.push(
workspace.onDidChangeConfiguration(({ affectsConfiguration }) => {
if (affectsConfiguration("rescript.settings.inlayHints")) {
commands.executeCommand("rescript-vscode.restart_language_server");
}
})
);

// Autostart code analysis if wanted
if (
workspace
Expand Down
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@
"default": false,
"description": "Automatically start ReScript's code analysis."
},
"rescript.settings.inlayHints.enable": {
"type": "boolean",
"default": false,
"description": "Enable (experimental) inlay hints."
},
"rescript.settings.inlayHints.maxLength": {
"markdownDescription": "Maximum length of character for inlay hints. Set to null to have an unlimited length. Inlay hints that exceed the maximum length will not be shown.",
"default": 25,
"type": [
"null",
"integer"
],
"minimum": 0
},
"rescript.settings.binaryPath": {
"type": ["string", "null"],
"default": null,
Expand Down
Loading

0 comments on commit 26c8df8

Please sign in to comment.