Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codeactions): open Implementation/Interface/Compiled and create Interface #767

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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
Loading