Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#### :rocket: New Feature

- Add support for the rewatch build system for incremental compilation. https://github.com/rescript-lang/rescript-vscode/pull/965
- Code Actions: open Implementation/Interface/Compiled Js and create Interface file. https://github.com/rescript-lang/rescript-vscode/pull/767

## 1.50.0

Expand Down
26 changes: 15 additions & 11 deletions analysis/src/CodeActions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ let stringifyCodeActions codeActions =
Printf.sprintf {|%s|}
(codeActions |> List.map Protocol.stringifyCodeAction |> Protocol.array)

let make ~title ~kind ~uri ~newText ~range =
let uri = uri |> Uri.fromPath |> Uri.toString in
{
Protocol.title;
codeActionKind = kind;
edit =
{
documentChanges =
[{textDocument = {version = None; uri}; edits = [{newText; range}]}];
};
}
let make ~title ~kind ~edit ~command =
{Protocol.title; codeActionKind = kind; edit; command}

let makeEdit edits uri =
Protocol.
{
documentChanges =
[
{
textDocument =
{version = None; uri = uri |> Uri.fromPath |> Uri.toString};
edits;
};
];
}
28 changes: 16 additions & 12 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -433,18 +433,22 @@ let test ~path =
in
Sys.remove currentFile;
codeActions
|> List.iter (fun {Protocol.title; edit = {documentChanges}} ->
Printf.printf "Hit: %s\n" title;
documentChanges
|> List.iter (fun {Protocol.edits} ->
edits
|> List.iter (fun {Protocol.range; newText} ->
let indent =
String.make range.start.character ' '
in
Printf.printf "%s\nnewText:\n%s<--here\n%s%s\n"
(Protocol.stringifyRange range)
indent indent newText)))
|> List.iter (fun {Protocol.title; edit} ->
match edit with
| Some {documentChanges} ->
Printf.printf "Hit: %s\n" title;
documentChanges
|> List.iter (fun {Protocol.edits} ->
edits
|> List.iter (fun {Protocol.range; newText} ->
let indent =
String.make range.start.character ' '
in
Printf.printf
"%s\nnewText:\n%s<--here\n%s%s\n"
(Protocol.stringifyRange range)
indent indent newText))
| None -> ())
| "c-a" ->
let hint = String.sub rest 3 (String.length rest - 3) in
print_endline
Expand Down
1 change: 1 addition & 0 deletions analysis/src/Hint.ml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ let codeLens ~path ~debug =
single line in the editor. *)
title =
typeExpr |> Shared.typeToString ~lineWidth:400;
arguments = None;
};
})
| _ -> None)
Expand Down
45 changes: 31 additions & 14 deletions analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ type range = {start: position; end_: position}
type markupContent = {kind: string; value: string}

(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#command *)
type command = {title: string; command: string}
type command = {title: string; command: string; arguments: string list option}

(* https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeLens *)
type codeLens = {range: range; command: command option}
Expand Down Expand Up @@ -76,12 +76,13 @@ type textDocumentEdit = {
}

type codeActionEdit = {documentChanges: textDocumentEdit list}
type codeActionKind = RefactorRewrite
type codeActionKind = RefactorRewrite | Empty

type codeAction = {
title: string;
codeActionKind: codeActionKind;
edit: codeActionEdit;
edit: codeActionEdit option;
command: command option;
}

let null = "null"
Expand Down Expand Up @@ -224,7 +225,7 @@ let stringifyoptionalVersionedTextDocumentIdentifier td =
| Some v -> string_of_int v)
(Json.escape td.uri)

let stringifyTextDocumentEdit tde =
let stringifyTextDocumentEdit (tde : textDocumentEdit) =
Printf.sprintf {|{
"textDocument": %s,
"edits": %s
Expand All @@ -235,15 +236,37 @@ let stringifyTextDocumentEdit tde =
let codeActionKindToString kind =
match kind with
| RefactorRewrite -> "refactor.rewrite"
| Empty -> ""

let stringifyCodeActionEdit cae =
Printf.sprintf {|{"documentChanges": %s}|}
(cae.documentChanges |> List.map stringifyTextDocumentEdit |> array)

let stringifyCodeAction ca =
Printf.sprintf {|{"title": "%s", "kind": "%s", "edit": %s}|} ca.title
(codeActionKindToString ca.codeActionKind)
(ca.edit |> stringifyCodeActionEdit)
let stringifyCommand (command : command) =
stringifyObject
[
("title", Some (wrapInQuotes command.title));
("command", Some (wrapInQuotes command.command));
( "arguments",
match command.arguments with
| None -> None
| Some args -> Some (args |> List.map wrapInQuotes |> array) );
]

let stringifyCodeAction (ca : codeAction) =
stringifyObject
[
("title", Some (wrapInQuotes ca.title));
("kind", Some (wrapInQuotes (codeActionKindToString ca.codeActionKind)));
( "edit",
match ca.edit with
| None -> None
| Some edit -> Some (edit |> stringifyCodeActionEdit) );
( "command",
match ca.command with
| None -> None
| Some command -> Some (command |> stringifyCommand) );
]

let stringifyHint hint =
Printf.sprintf
Expand All @@ -256,12 +279,6 @@ let stringifyHint hint =
}|}
(stringifyPosition hint.position)
(Json.escape hint.label) hint.kind hint.paddingLeft hint.paddingRight

let stringifyCommand (command : command) =
Printf.sprintf {|{"title": "%s", "command": "%s"}|}
(Json.escape command.title)
(Json.escape command.command)

let stringifyCodeLens (codeLens : codeLens) =
Printf.sprintf
{|{
Expand Down
109 changes: 100 additions & 9 deletions analysis/src/Xform.ml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ module IfThenElse = struct
let newText = printExpr ~range newExpr in
let codeAction =
CodeActions.make ~title:"Replace with switch" ~kind:RefactorRewrite
~uri:path ~newText ~range
~command:None
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions
end
Expand Down Expand Up @@ -176,7 +177,8 @@ module AddBracesToFn = struct
let newText = printStructureItem ~range newStructureItem in
let codeAction =
CodeActions.make ~title:"Add braces to function" ~kind:RefactorRewrite
~uri:path ~newText ~range
~command:None
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions
end
Expand Down Expand Up @@ -249,7 +251,8 @@ module AddTypeAnnotation = struct
in
let codeAction =
CodeActions.make ~title:"Add type annotation" ~kind:RefactorRewrite
~uri:path ~newText ~range
~command:None
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions
| _ -> ()))
Expand Down Expand Up @@ -384,8 +387,9 @@ module ExhaustiveSwitch = struct
printExpr ~range {expr with pexp_desc = Pexp_match (expr, cases)}
in
let codeAction =
CodeActions.make ~title:"Exhaustive switch" ~kind:RefactorRewrite
~uri:path ~newText ~range
CodeActions.make ~command:None ~title:"Exhaustive switch"
~kind:RefactorRewrite
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions))
| Some (Switch {switchExpr; completionExpr; pos}) -> (
Expand All @@ -410,8 +414,9 @@ module ExhaustiveSwitch = struct
{switchExpr with pexp_desc = Pexp_match (completionExpr, cases)}
in
let codeAction =
CodeActions.make ~title:"Exhaustive switch" ~kind:RefactorRewrite
~uri:path ~newText ~range
CodeActions.make ~title:"Exhaustive switch" ~command:None
~kind:RefactorRewrite (* ~uri:path ~newText ~range *)
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions))
end
Expand Down Expand Up @@ -508,7 +513,8 @@ module AddDocTemplate = struct
let newText = printSignatureItem ~range signatureItem in
let codeAction =
CodeActions.make ~title:"Add Documentation template"
~kind:RefactorRewrite ~uri:path ~newText ~range
~kind:RefactorRewrite ~command:None
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions
| None -> ())
Expand Down Expand Up @@ -593,13 +599,94 @@ module AddDocTemplate = struct
let newText = printStructureItem ~range structureItem in
let codeAction =
CodeActions.make ~title:"Add Documentation template"
~kind:RefactorRewrite ~uri:path ~newText ~range
~kind:RefactorRewrite ~command:None
~edit:(Some (CodeActions.makeEdit [{newText; range}] path))
in
codeActions := codeAction :: !codeActions
| None -> ())
end
end

module ExecuteCommands = struct
let openCompiled = "rescriptls/open-compiled-file"
let openInterface = "rescriptls/open-interface-file"
let openImplementation = "rescriptls/open-implementation-file"
let createInterface = "rescriptls/create-interface-file"
end

module OpenCompiledFile = struct
let xform ~path ~codeActions =
let uri = path |> Uri.fromPath |> Uri.toString in
let codeAction =
CodeActions.make ~title:"Open Compiled JS" ~kind:Empty ~edit:None
~command:
(Some
Protocol.
{
title = "Open Compiled File";
command = ExecuteCommands.openCompiled;
arguments = Some [uri];
})
in
codeActions := codeAction :: !codeActions
end

module HandleImpltInter = struct
type t = Create | OpenImpl | OpenInter
let xform ~path ~codeActions =
match Files.classifySourceFile path with
| Res ->
let resiFile = path ^ "i" in
if Sys.file_exists resiFile then
let uri = resiFile |> Uri.fromPath |> Uri.toString in
let title = "Open " ^ Filename.basename uri in
let openResi =
CodeActions.make ~title ~kind:Empty ~edit:None
~command:
(Some
Protocol.
{
title = "Open Interface File";
command = ExecuteCommands.openInterface;
arguments = Some [uri];
})
in
codeActions := openResi :: !codeActions
else
let uri = path |> Uri.fromPath |> Uri.toString in
let title = "Create " ^ Filename.basename uri ^ "i" in
let createResi =
CodeActions.make ~title ~kind:Empty ~edit:None
~command:
(Some
Protocol.
{
title = "Create Interface File";
command = ExecuteCommands.createInterface;
arguments = Some [uri];
})
in
codeActions := createResi :: !codeActions
| Resi ->
let resFile = Filename.remove_extension path ^ ".res" in
if Sys.file_exists resFile then
let uri = resFile |> Uri.fromPath |> Uri.toString in
let title = "Open " ^ Filename.basename uri in
let openRes =
CodeActions.make ~title ~kind:Empty ~edit:None
~command:
(Some
Protocol.
{
title = "Open Implementation File";
command = ExecuteCommands.openImplementation;
arguments = Some [uri];
})
in
codeActions := openRes :: !codeActions
| Other -> ()
end

let parseImplementation ~filename =
let {Res_driver.parsetree = structure; comments} =
Res_driver.parsingEngine.parseImplementation ~forPrinter:false ~filename
Expand Down Expand Up @@ -661,6 +748,8 @@ let extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug =
AddBracesToFn.xform ~pos ~codeActions ~path ~printStructureItem structure;
AddDocTemplate.Implementation.xform ~pos ~codeActions ~path
~printStructureItem ~structure;
OpenCompiledFile.xform ~path ~codeActions;
HandleImpltInter.xform ~path ~codeActions;

(* This Code Action needs type info *)
let () =
Expand All @@ -680,5 +769,7 @@ let extractCodeActions ~path ~startPos ~endPos ~currentFile ~debug =
let signature, printSignatureItem = parseInterface ~filename:currentFile in
AddDocTemplate.Interface.xform ~pos ~codeActions ~path ~signature
~printSignatureItem;
OpenCompiledFile.xform ~path ~codeActions;
HandleImpltInter.xform ~path ~codeActions;
!codeActions
| Other -> []
20 changes: 16 additions & 4 deletions analysis/tests/src/expected/CodeLens.res.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
Code Lens src/CodeLens.res
[{
"range": {"start": {"line": 9, "character": 4}, "end": {"line": 9, "character": 8}},
"command": {"title": "{\"name\": string} => React.element", "command": ""}
"command": {
"title": "{\"name\": string} => React.element",
"command": ""
}
}, {
"range": {"start": {"line": 4, "character": 4}, "end": {"line": 4, "character": 6}},
"command": {"title": "(~opt1: int=?, ~a: int, ~b: int, unit, ~opt2: int=?, unit, ~c: int) => int", "command": ""}
"command": {
"title": "(~opt1: int=?, ~a: int, ~b: int, unit, ~opt2: int=?, unit, ~c: int) => int",
"command": ""
}
}, {
"range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 7}},
"command": {"title": "(~age: int, ~name: string) => string", "command": ""}
"command": {
"title": "(~age: int, ~name: string) => string",
"command": ""
}
}, {
"range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 7}},
"command": {"title": "(int, int) => int", "command": ""}
"command": {
"title": "(int, int) => int",
"command": ""
}
}]

Loading