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

Jump Custom Request #1374

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Add custom [`ocamllsp/merlinJump`](/ocaml-lsp-server/docs/ocamllsp/merlinJump-spec.md) request (#1374)

## Fixes

- Fix fd leak in running external processes for preprocessing (#1349)
Expand All @@ -10,6 +12,7 @@

- Add custom [`ocamllsp/getDocumentation`](/ocaml-lsp-server/docs/ocamllsp/getDocumentation-spec.md) request (#1336)


- Add support for OCaml 5.2 (#1233)

## Fixes
Expand Down
43 changes: 43 additions & 0 deletions ocaml-lsp-server/docs/ocamllsp/merlinJump-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Merlin Jump Request

## Description

This custom request allows Merlin-type code navigation in a source buffer.

## Server capability

- propert name: `handleMerlinJump`
- property type: `boolean`

## Request

```js
export interface JumpParams extends TextDocumentPositionParams
{
target: string;
}
```

- method: `ocamllsp/merlinJump`
- params:
- `TextDocumentIdentifier`: Specifies the document for which the request is sent. It includes a uri property that points to the document.
- `Position`: Specifies the position in the document for which the documentation is requested. It includes line and character properties.
More details can be found in the [TextDocumentPositionParams - LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentPositionParams).
- `Target`: A string representing the identifier within the document to search for and jump to.
PizieDust marked this conversation as resolved.
Show resolved Hide resolved
The target can be any of `fun`, `let`, `module`, `module-type`, `match`, `match-next-case`, `match-prev-case`.

## Response

```js
result: Jump | String
export interface Jump extends TextDocumentPositionParams {
}
```

- result:
- Type: Jump or string
- Description: If the jump is successful, a position and document path is returned. If no relevant jump location is found, the result will be a string "no matching target" or an error message.
- Jump:
- Type: TextDocumentPositionParams
- `Position`: The position to jump to
- `TextDocumentIdentifier`: the document path which contains this position (ideally the same document as the request)
1 change: 1 addition & 0 deletions ocaml-lsp-server/src/custom_requests/custom_request.ml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ module Typed_holes = Req_typed_holes
module Type_enclosing = Req_type_enclosing
module Wrapping_ast_node = Req_wrapping_ast_node
module Get_documentation = Req_get_documentation
module Merlin_jump = Req_merlin_jump
1 change: 1 addition & 0 deletions ocaml-lsp-server/src/custom_requests/custom_request.mli
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ module Typed_holes = Req_typed_holes
module Type_enclosing = Req_type_enclosing
module Wrapping_ast_node = Req_wrapping_ast_node
module Get_documentation = Req_get_documentation
module Merlin_jump = Req_merlin_jump
77 changes: 77 additions & 0 deletions ocaml-lsp-server/src/custom_requests/req_merlin_jump.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
open Import
module TextDocumentPositionParams = Lsp.Types.TextDocumentPositionParams

let meth = "ocamllsp/jump"
let capability = "handleJump", `Bool true

module JumpParams = struct
type t =
{ text_document : TextDocumentIdentifier.t
; position : Position.t
; target : string
}

let t_of_yojson json =
let open Yojson.Safe.Util in
let textDocumentPosition = TextDocumentPositionParams.t_of_yojson json in
let target = json |> member "target" |> to_string in
{ position = textDocumentPosition.position
; text_document = textDocumentPosition.textDocument
; target
}
;;

let yojson_of_t { text_document; position; target } =
`Assoc
[ "textDocument", TextDocumentIdentifier.yojson_of_t text_document
; "position", Position.yojson_of_t position
; "target", `String target
]
;;
end

module Jump = struct
type t = Lsp.Types.TextDocumentPositionParams.t

let yojson_of_t t = TextDocumentPositionParams.yojson_of_t t
end

type t = Jump.t

module Request_params = struct
type t = JumpParams.t

let yojson_of_t t = JumpParams.yojson_of_t t
let create ~text_document ~position ~target () : t = { text_document; position; target }
end

let dispatch ~merlin ~position ~target =
Document.Merlin.with_pipeline_exn merlin (fun pipeline ->
let pposition = Position.logical position in
let query = Query_protocol.Jump (target, pposition) in
Query_commands.dispatch pipeline query)
;;

let on_request ~params state =
Fiber.of_thunk (fun () ->
let params = (Option.value ~default:(`Assoc []) params :> Yojson.Safe.t) in
let JumpParams.{ text_document; position; target } = JumpParams.t_of_yojson params in
let uri = text_document.uri in
let doc = Document_store.get state.State.store uri in
match Document.kind doc with
| `Other -> Fiber.return `Null
| `Merlin merlin ->
Fiber.map (dispatch ~merlin ~position ~target) ~f:(fun res ->
match res with
| `Error err -> `String err
| `Found pos ->
(match Position.of_lexical_position pos with
| None -> `Null
| Some position ->
let loc =
Lsp.Types.TextDocumentPositionParams.create
~position
~textDocument:(TextDocumentIdentifier.create ~uri)
in
Jump.yojson_of_t loc)))
;;
20 changes: 20 additions & 0 deletions ocaml-lsp-server/src/custom_requests/req_merlin_jump.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
open Import

module Request_params : sig
type t

val yojson_of_t : t -> Json.t

val create
: text_document:TextDocumentIdentifier.t
-> position:Position.t
-> target:string
-> unit
-> t
end

type t

val meth : string
val capability : string * [> `Bool of bool ]
val on_request : params:Jsonrpc.Structured.t option -> State.t -> Json.t Fiber.t
1 change: 1 addition & 0 deletions ocaml-lsp-server/src/ocaml_lsp_server.ml
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ let on_request
; Req_merlin_call_compatible.meth, Req_merlin_call_compatible.on_request
; Req_type_enclosing.meth, Req_type_enclosing.on_request
; Req_get_documentation.meth, Req_get_documentation.on_request
; Req_merlin_jump.meth, Req_merlin_jump.on_request
; Req_wrapping_ast_node.meth, Req_wrapping_ast_node.on_request
; ( Semantic_highlighting.Debug.meth_request_full
, Semantic_highlighting.Debug.on_request_full )
Expand Down
1 change: 1 addition & 0 deletions ocaml-lsp-server/test/e2e-new/dune
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
test
type_enclosing
documentation
merlin_jump
with_pp
with_ppx
workspace_change_config))))
94 changes: 94 additions & 0 deletions ocaml-lsp-server/test/e2e-new/merlin_jump.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
open Test.Import
module Req = Ocaml_lsp_server.Custom_request.Merlin_jump

module Util = struct
let call_jump position target client =
let uri = DocumentUri.of_path "test.ml" in
let text_document = TextDocumentIdentifier.create ~uri in
let params =
Req.Request_params.create ~text_document ~position ~target ()
|> Req.Request_params.yojson_of_t
|> Jsonrpc.Structured.t_of_yojson
|> Option.some
in
let req = Lsp.Client_request.UnknownRequest { meth = "ocamllsp/jump"; params } in
Client.request client req
;;

let test ~line ~character ~target ~source =
let position = Position.create ~character ~line in
let request client =
let open Fiber.O in
let+ response = call_jump position target client in
Test.print_result response
in
Helpers.test source request
;;
end

let%expect_test "Get location of the next match case" =
let source =
{|
let find_vowel x =
match x with
| 'A' -> true
| 'E' -> true
| 'I' -> true
| 'O' -> true
| 'U' -> true
| _ -> false
|}
in
let line = 3 in
let character = 2 in
let target = "match-next-case" in
Util.test ~line ~character ~target ~source;
[%expect
{|
{
"position": { "character": 2, "line": 4 },
"textDocument": { "uri": "file:///test.ml" }
} |}]
;;

let%expect_test "Get location of a the module" =
let source =
{|type a = Foo | Bar

module A = struct
let f () = 10
let g = Bar
let h x = x

module B = struct
type b = Baz

let x = (Baz, 10)
let y = (Bar, Foo)
end

type t = { a : string; b : float }

let z = { a = "Hello"; b = 1.0 }
end|}
in
let line = 10 in
let character = 3 in
let target = "module" in
Util.test ~line ~character ~target ~source;
[%expect
{|
{
"position": { "character": 2, "line": 7 },
"textDocument": { "uri": "file:///test.ml" }
} |}]
;;

let%expect_test "Same line should output no locations" =
let source = {|let x = 5 |} in
let line = 1 in
let character = 5 in
let target = "let" in
Util.test ~line ~character ~target ~source;
[%expect {| "No matching target" |}]
;;
Loading