-
Notifications
You must be signed in to change notification settings - Fork 60
Code Lens #513
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
Code Lens #513
Changes from 7 commits
f508796
005388a
564f905
d64ed9a
c79a8f9
47cf51b
1246434
e81288b
8d76d6f
980beeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,10 @@ let inlayhint ~path ~pos ~maxLength ~debug = | |
let result = Hint.inlay ~path ~pos ~maxLength ~debug |> Protocol.array in | ||
print_endline result | ||
|
||
let codeLens ~path ~debug = | ||
let result = Hint.codeLens ~path ~debug |> Protocol.array in | ||
print_endline result | ||
|
||
let hover ~path ~pos ~currentFile ~debug = | ||
let result = | ||
match Cmt.fullFromPath ~path with | ||
|
@@ -391,6 +395,9 @@ let test ~path = | |
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) | ||
| "cle" -> | ||
print_endline ("Code Lens " ^ path); | ||
codeLens ~path ~debug:false | ||
Comment on lines
+398
to
+400
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A "gotcha" that I needed some time to figure out is that these commands that you add to the code can only be 3 chars. I had more than 3 chars and couldn't figure out why it didn't pick them up in the tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feel free to generalise that. |
||
| _ -> ()); | ||
print_newline ()) | ||
in | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -119,4 +119,58 @@ let inlay ~path ~pos ~maxLength ~debug = | |
| Some value -> | ||
if String.length label > value then None else Some result | ||
| None -> Some result) | ||
| None -> None))) | ||
| None -> None))) | ||
|
||
let codeLens ~path ~debug = | ||
let lenses = ref [] in | ||
let push loc = | ||
let range = Utils.cmtLocToRange loc in | ||
lenses := range :: !lenses | ||
in | ||
(* Code lenses are only emitted for functions right now. So look for value bindings that are functions, | ||
cristianoc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and use the loc of the value binding itself so we can look up the full function type for our code lens. *) | ||
let value_binding (iterator : Ast_iterator.iterator) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This creates an AST iterator targeting value bindings, |
||
(vb : Parsetree.value_binding) = | ||
(match vb with | ||
| { | ||
pvb_pat = {ppat_desc = Ppat_var _; ppat_loc}; | ||
pvb_expr = {pexp_desc = Pexp_fun _}; | ||
} -> | ||
Comment on lines
+135
to
+138
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pattern matches the value binding to target 2 things:
Notice that we're also extracting |
||
push ppat_loc | ||
| _ -> ()); | ||
Ast_iterator.default_iterator.value_binding iterator vb | ||
in | ||
let iterator = {Ast_iterator.default_iterator with value_binding} in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This creates an iterator, and ensures that |
||
(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); | ||
!lenses | ||
|> List.filter_map (fun (range : Protocol.range) -> | ||
match Cmt.fullFromPath ~path with | ||
| None -> None | ||
| Some full -> ( | ||
Comment on lines
+154
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This here looks up the compiler artifact for |
||
match | ||
References.getLocItem ~full | ||
~pos:(range.start.line, range.start.character + 1) | ||
~debug | ||
with | ||
| Some {locType = Typed (_, typeExpr, _)} -> | ||
mununki marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+157
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We then use a helper to get the full type via the compiler artifact for the target file, using the positions where we found variables being assigned functions. This will lead us to the full, correct type definitions for all functions assigned to variables. A type at a location can be a bunch of different things - we target |
||
Some | ||
(Protocol.stringifyCodeLens | ||
{ | ||
range; | ||
command = | ||
Some | ||
{ | ||
(* Code lenses can run commands. An empty command string means we just want the editor | ||
to print the text, not link to running a command. *) | ||
command = ""; | ||
(* Print the type with a huge line width, because the code lens always prints on a | ||
single line in the editor. *) | ||
title = typeExpr |> Shared.typeToString ~lineWidth:400; | ||
}; | ||
}) | ||
| _ -> None)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
let add = (x, y) => x + y | ||
|
||
let foo = (~age, ~name) => name ++ string_of_int(age) | ||
|
||
let ff = (~opt1=0, ~a, ~b, (), ~opt2=0, (), ~c) => a + b + c + opt1 + opt2 | ||
|
||
let compFF = Completion.ff | ||
|
||
@react.component | ||
let make = (~name) => React.string(name) | ||
//^cle |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
Code Lens src/CodeLens.res | ||
[{ | ||
"range": {"start": {"line": 9, "character": 4}, "end": {"line": 9, "character": 8}}, | ||
"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": ""} | ||
}, { | ||
"range": {"start": {"line": 2, "character": 4}, "end": {"line": 2, "character": 7}}, | ||
"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": ""} | ||
}] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
DCE src/Dce.res | ||
issues:243 | ||
issues:249 | ||
zth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import { | |
DidChangeConfigurationNotification, | ||
InitializeParams, | ||
InlayHintParams, | ||
CodeLensParams, | ||
} from "vscode-languageserver-protocol"; | ||
import * as utils from "./utils"; | ||
import * as codeActions from "./codeActions"; | ||
|
@@ -30,6 +31,7 @@ interface extensionConfiguration { | |
enable: boolean; | ||
maxLength: number | null; | ||
}; | ||
codeLens: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering whether extension config should go to its own file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean the type definition? |
||
binaryPath: string | null; | ||
} | ||
let extensionConfiguration: extensionConfiguration = { | ||
|
@@ -38,6 +40,7 @@ let extensionConfiguration: extensionConfiguration = { | |
enable: false, | ||
maxLength: 25 | ||
}, | ||
codeLens: false, | ||
zth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
binaryPath: null, | ||
}; | ||
let pullConfigurationPeriodically: NodeJS.Timeout | null = null; | ||
|
@@ -229,6 +232,9 @@ let compilerLogsWatcher = chokidar | |
if (extensionConfiguration.inlayHints.enable === true) { | ||
sendInlayHintsRefresh(); | ||
} | ||
if (extensionConfiguration.codeLens === true) { | ||
sendCodeLensRefresh(); | ||
} | ||
}); | ||
let stopWatchingCompilerLog = () => { | ||
// TODO: cleanup of compilerLogs? | ||
|
@@ -411,6 +417,27 @@ function sendInlayHintsRefresh() { | |
send(request); | ||
} | ||
|
||
function codeLens(msg: p.RequestMessage) { | ||
const params = msg.params as p.CodeLensParams; | ||
const filePath = fileURLToPath(params.textDocument.uri); | ||
|
||
const response = utils.runAnalysisCommand( | ||
filePath, | ||
["codeLens", filePath], | ||
msg | ||
); | ||
return response; | ||
} | ||
|
||
function sendCodeLensRefresh() { | ||
let request: p.RequestMessage = { | ||
jsonrpc: c.jsonrpcVersion, | ||
method: p.CodeLensRefreshRequest.method, | ||
id: serverSentRequestIdCounter++, | ||
}; | ||
send(request); | ||
} | ||
|
||
function definition(msg: p.RequestMessage) { | ||
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition | ||
let params = msg.params as p.DefinitionParams; | ||
|
@@ -1001,6 +1028,11 @@ function onMessage(msg: p.Message) { | |
full: true, | ||
}, | ||
inlayHintProvider: extensionConfiguration.inlayHints.enable, | ||
codeLensProvider: extensionConfiguration.codeLens | ||
? { | ||
workDoneProgress: false, | ||
} | ||
: undefined, | ||
}, | ||
}; | ||
let response: p.ResponseMessage = { | ||
|
@@ -1086,6 +1118,12 @@ function onMessage(msg: p.Message) { | |
if (extName === c.resExt) { | ||
send(inlayHint(msg)); | ||
} | ||
} else if (msg.method === p.CodeLensRequest.method) { | ||
let params = msg.params as CodeLensParams; | ||
let extName = path.extname(params.textDocument.uri); | ||
if (extName === c.resExt) { | ||
send(codeLens(msg)); | ||
} | ||
} else { | ||
let response: p.ResponseMessage = { | ||
jsonrpc: c.jsonrpcVersion, | ||
|
Uh oh!
There was an error while loading. Please reload this page.