Skip to content

Commit 1799dcf

Browse files
xvwvoodoos
andauthored
Destruct custom request (#1583)
* Add test that illustrate `Incomplete Error Message` [tarides/ocaml-eglot#78](tarides/ocaml-eglot#78) * Add `ocamllsp/destruct` custom request * Add test for `destruct` custom request * Add CHANGE entry * Add missing documentation * Update ocaml-lsp-server/docs/ocamllsp/destruct-spec.md Co-authored-by: Ulysse <5031221+voodoos@users.noreply.github.com> * Update ocaml-lsp-server/docs/ocamllsp/destruct-spec.md Co-authored-by: Ulysse <5031221+voodoos@users.noreply.github.com> * Update ocaml-lsp-server/src/custom_requests/req_destruct.ml Co-authored-by: Ulysse <5031221+voodoos@users.noreply.github.com> * Remove residual test * Fix Changelog --------- Co-authored-by: Ulysse <5031221+voodoos@users.noreply.github.com>
1 parent 0d2f5d1 commit 1799dcf

File tree

11 files changed

+221
-101
lines changed

11 files changed

+221
-101
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Unreleased
2+
3+
## Features
4+
5+
- Add `destruct` custom request (#1583)
6+
17
# 1.25.0
28

39
## Features
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Construct Request
2+
3+
## Description
4+
5+
Provides a query that performs `case analysis` to generate or
6+
complete pattern matchings. See [Merlin's documentation](https://github.com/ocaml/merlin/blob/main/doc/dev/PROTOCOL.md#case-analysis--start-position--end-position).
7+
8+
## Client Capability
9+
10+
There is no client capability relative to this request.
11+
12+
## Server capability
13+
14+
- property name: `handleDestruct`
15+
- property type: `boolean`
16+
17+
## Request
18+
19+
- method: `ocamllsp/destruct`
20+
- params:
21+
22+
```json
23+
{
24+
"uri": TextDocumentIdentifier,
25+
"range": Range
26+
}
27+
```
28+
29+
## Response
30+
31+
```json
32+
{
33+
"range": Range,
34+
"content": string
35+
}
36+
```
37+
38+
Contains the `range` to be substituted and the new `content`.

ocaml-lsp-server/src/custom_requests/custom_request.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ module Merlin_jump = Req_merlin_jump
1313
module Phrase = Req_phrase
1414
module Type_expression = Req_type_expression
1515
module Locate = Req_locate
16+
module Destruct = Req_destruct

ocaml-lsp-server/src/custom_requests/custom_request.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ module Merlin_jump = Req_merlin_jump
1515
module Phrase = Req_phrase
1616
module Type_expression = Req_type_expression
1717
module Locate = Req_locate
18+
module Destruct = Req_destruct
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
open Import
2+
3+
let capability = "handleDestruct", `Bool true
4+
let meth = "ocamllsp/destruct"
5+
6+
module Request_params = struct
7+
type t =
8+
{ text_document : TextDocumentIdentifier.t
9+
; range : Range.t
10+
}
11+
12+
let create ~text_document ~range () = { text_document; range }
13+
14+
let yojson_of_t { text_document; range } =
15+
match TextDocumentIdentifier.yojson_of_t text_document with
16+
| `Assoc assoc -> `Assoc (("range", Range.yojson_of_t range) :: assoc)
17+
| _ -> (* unreachable *) assert false
18+
;;
19+
20+
let t_of_yojson json =
21+
let open Yojson.Safe.Util in
22+
let text_document = json |> TextDocumentIdentifier.t_of_yojson in
23+
let range = json |> member "range" |> Range.t_of_yojson in
24+
create ~text_document ~range ()
25+
;;
26+
end
27+
28+
type t =
29+
{ range : Range.t
30+
; content : string
31+
}
32+
33+
let t_of_yojson json =
34+
let open Yojson.Safe.Util in
35+
let range = json |> member "range" |> Range.t_of_yojson
36+
and content = json |> member "content" |> to_string in
37+
{ range; content }
38+
;;
39+
40+
let yojson_of_t { range; content } =
41+
`Assoc [ "range", Range.yojson_of_t range; "content", `String content ]
42+
;;
43+
44+
let with_pipeline state uri f =
45+
let doc = Document_store.get state.State.store uri in
46+
match Document.kind doc with
47+
| `Other -> Fiber.return `Null
48+
| `Merlin merlin ->
49+
(match Document.Merlin.kind merlin with
50+
| Document.Kind.Intf ->
51+
(* Destruct makes no sense if it's called from an interface. *)
52+
Fiber.return `Null
53+
| Document.Kind.Impl -> Document.Merlin.with_pipeline_exn merlin f)
54+
;;
55+
56+
let make_destruct_command start stop = Query_protocol.Case_analysis (start, stop)
57+
58+
let dispatch_destruct range pipeline =
59+
let start = range.Range.start |> Position.logical
60+
and stop = range.Range.end_ |> Position.logical in
61+
let command = make_destruct_command start stop in
62+
let loc, content = Query_commands.dispatch pipeline command in
63+
yojson_of_t { content; range = Range.of_loc loc }
64+
;;
65+
66+
let on_request ~params state =
67+
Fiber.of_thunk (fun () ->
68+
let params = (Option.value ~default:(`Assoc []) params :> Json.t) in
69+
let Request_params.{ text_document; range } = Request_params.t_of_yojson params in
70+
let uri = text_document.uri in
71+
with_pipeline state uri @@ dispatch_destruct range)
72+
;;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
open Import
2+
3+
module Request_params : sig
4+
type t
5+
6+
val create : text_document:Import.TextDocumentIdentifier.t -> range:Range.t -> unit -> t
7+
val yojson_of_t : t -> Json.t
8+
end
9+
10+
type t
11+
12+
val t_of_yojson : Json.t -> t
13+
val capability : string * Json.t
14+
val meth : string
15+
val on_request : params:Jsonrpc.Structured.t option -> State.t -> Json.t Fiber.t

ocaml-lsp-server/src/ocaml_lsp_server.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ let initialize_info (client_capabilities : ClientCapabilities.t) : InitializeRes
102102
; Req_phrase.capability
103103
; Req_type_expression.capability
104104
; Req_locate.capability
105+
; Req_destruct.capability
105106
] )
106107
]
107108
in
@@ -560,6 +561,7 @@ let on_request
560561
; Req_phrase.meth, Req_phrase.on_request
561562
; Req_type_expression.meth, Req_type_expression.on_request
562563
; Req_locate.meth, Req_locate.on_request
564+
; Req_destruct.meth, Req_destruct.on_request
563565
]
564566
meth
565567
with
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
open Test.Import
2+
module Req = Ocaml_lsp_server.Custom_request.Destruct
3+
4+
module Util = struct
5+
let call_destruct client range =
6+
let uri = DocumentUri.of_path "test.ml" in
7+
let text_document = TextDocumentIdentifier.create ~uri in
8+
let params =
9+
Req.Request_params.create ~text_document ~range ()
10+
|> Req.Request_params.yojson_of_t
11+
|> Jsonrpc.Structured.t_of_yojson
12+
|> Option.some
13+
in
14+
let req = Lsp.Client_request.UnknownRequest { meth = Req.meth; params } in
15+
Client.request client req
16+
;;
17+
18+
let test pos source =
19+
let range =
20+
match pos with
21+
| `Pos start ->
22+
Range.create ~start ~end_:Position.{ start with character = start.character + 1 }
23+
| `Range range -> range
24+
in
25+
let request client =
26+
let open Fiber.O in
27+
let+ response = call_destruct client range in
28+
Test.print_result response
29+
in
30+
Helpers.test source request
31+
;;
32+
end
33+
34+
let%expect_test "Perform `destruct` as custom request - 1" =
35+
let source =
36+
{|
37+
let _ =
38+
match (None : unit option) with
39+
| None -> ()
40+
| Some _ -> ()
41+
|}
42+
in
43+
let pos = Position.create ~line:4 ~character:11 in
44+
Util.test (`Pos pos) source;
45+
[%expect
46+
{|
47+
{
48+
"range": {
49+
"end": { "character": 12, "line": 4 },
50+
"start": { "character": 11, "line": 4 }
51+
},
52+
"content": "()"
53+
}
54+
|}]
55+
;;
56+
57+
let%expect_test "Perform `destruct` as custom request - 2" =
58+
let source =
59+
{|
60+
type t =
61+
| Foo
62+
| Bar
63+
| Baz of int option
64+
let f: t -> unit = function Foo -> ()
65+
|}
66+
in
67+
let pos =
68+
let start = Position.create ~line:5 ~character:28
69+
and end_ = Position.create ~line:5 ~character:31 in
70+
Range.create ~start ~end_
71+
in
72+
Util.test (`Range pos) source;
73+
[%expect
74+
{|
75+
{
76+
"range": {
77+
"end": { "character": 37, "line": 5 },
78+
"start": { "character": 37, "line": 5 }
79+
},
80+
"content": "\n| Bar | Baz _ -> _"
81+
}
82+
|}]
83+
;;

ocaml-lsp-server/test/e2e-new/diagnostics_filter.ml

Lines changed: 0 additions & 99 deletions
This file was deleted.

ocaml-lsp-server/test/e2e-new/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
inlay_hints
5858
jump_to_typed_hole
5959
merlin_call_compatible
60-
diagnostics_filter
60+
destruct
6161
metrics
6262
semantic_hl_data
6363
semantic_hl_helpers

0 commit comments

Comments
 (0)