Skip to content

Commit 288a4ae

Browse files
authored
Jump to cases within a Match statement (#1726)
from PizieDust/jump_case
2 parents 0f64255 + 6a42554 commit 288a4ae

File tree

3 files changed

+143
-6
lines changed

3 files changed

+143
-6
lines changed

CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ merlin NEXT_VERSION
55
- Add a "heap_mbytes" field to Merlin server responses to report heap usage (#1717)
66
- Add cache stats to telemetry (#1711)
77
- Add new SyntaxDocument command to find information about the node under the cursor (#1706)
8-
- Fix `FLG -pp ppx.exe -as-pp/-dump-ast` use of invalid shell redirection when
8+
- Fix `FLG -pp ppx.exe -as-pp/-dump-ast` use of invalid shell redirection when
99
direct process launch on Windows. (#1723, fixes #1722)
1010
- Add a query_num field to the `ocamlmerlin` responses to detect server crashes (#1716)
11+
- Jump to cases within a match statement (#1726)
1112
+ editor modes
1213
- vim: load merlin under the ocamlinterface and ocamllex filetypes (#1340)
1314
- Fix merlinpp not using binary file open (#1725, fixes #1724)

src/analysis/jump.ml

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ open Std
3232
open Typedtree
3333
open Browse_raw
3434

35+
type direction = Prev | Next
36+
3537
let is_node_fun = function
3638
| Expression { exp_desc = Texp_function _; _ } -> true
3739
| _ -> false
@@ -104,6 +106,8 @@ let rec find_map ~f = function
104106

105107
exception No_matching_target
106108
exception No_predicate of string
109+
exception No_next_match_case
110+
exception No_prev_match_case
107111

108112
(* Returns first node on the list matching a predicate *)
109113
let rec find_node preds nodes =
@@ -127,19 +131,50 @@ let rec skip_non_moving pos = function
127131
| [] -> []
128132
;;
129133

134+
let get_cases_from_match node =
135+
match node with
136+
| Expression { exp_desc = Texp_match (_, cases, _); _ } -> cases
137+
| _ -> []
138+
139+
let find_case_pos cases pos direction =
140+
let rec find_pos pos cases direction =
141+
match cases with
142+
| [] -> None
143+
| { c_lhs = { pat_loc; _ }; _ } :: tail ->
144+
let check =
145+
match direction with
146+
| Prev ->
147+
pos.Lexing.pos_cnum > pat_loc.loc_start.pos_cnum
148+
| Next ->
149+
pos.Lexing.pos_cnum < pat_loc.loc_start.pos_cnum
150+
in
151+
if check then
152+
Some pat_loc.loc_start
153+
else
154+
find_pos pos tail direction
155+
in
156+
let case = find_pos pos cases direction in
157+
match case with
158+
| Some location -> `Found location
159+
| None ->
160+
(match direction with
161+
| Next -> raise No_next_match_case
162+
| Prev -> raise No_prev_match_case)
163+
130164
let get typed_tree pos target =
131165
let roots = Mbrowse.of_typedtree typed_tree in
132166
let enclosings =
133167
match Mbrowse.enclosing pos [roots] with
134168
| [] -> []
135169
| l -> List.map ~f:snd l
136170
in
137-
138171
let all_preds = [
139172
"fun", fun_pred;
140173
"let", let_pred;
141174
"module", module_pred;
142175
"match", match_pred;
176+
"match-next-case", match_pred;
177+
"match-prev-case", match_pred;
143178
] in
144179
let targets = Str.split (Str.regexp "[, ]") target in
145180
try
@@ -152,17 +187,25 @@ let get typed_tree pos target =
152187
in
153188
if String.length target = 0 then
154189
`Error "Specify target"
155-
else begin
190+
else
156191
let nodes = skip_non_moving pos enclosings in
157192
let node = find_node preds nodes in
158-
let node_loc = Browse_raw.node_real_loc Location.none node in
159-
`Found node_loc.Location.loc_start
160-
end
193+
match target with
194+
| "match-next-case" -> find_case_pos (get_cases_from_match node) pos Next
195+
| "match-prev-case" ->
196+
find_case_pos (List.rev (get_cases_from_match node)) pos Prev
197+
| _ ->
198+
let node_loc = Browse_raw.node_real_loc Location.none node in
199+
`Found node_loc.Location.loc_start
161200
with
162201
| No_predicate target ->
163202
`Error ("No predicate for " ^ target)
164203
| No_matching_target ->
165204
`Error "No matching target"
205+
| No_next_match_case ->
206+
`Error "No next case found"
207+
| No_prev_match_case ->
208+
`Error "No previous case found"
166209

167210
let phrase typed_tree pos target =
168211
let roots = Mbrowse.of_typedtree typed_tree in
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
$ cat > test.ml << EOF
2+
> let find_vowel x =
3+
> match x with
4+
> | 'A' ->
5+
> true
6+
> | 'E' ->
7+
> true
8+
> | 'I' ->
9+
> true
10+
> | 'O' ->
11+
> true
12+
> | 'U' ->
13+
> true
14+
> | _ ->
15+
> false
16+
> EOF
17+
18+
Test if location of next case is given
19+
$ $MERLIN single jump -target match-next-case -position 3:3 -filename test.ml < test.ml
20+
{
21+
"class": "return",
22+
"value": {
23+
"pos": {
24+
"line": 5,
25+
"col": 2
26+
}
27+
},
28+
"notifications": []
29+
}
30+
31+
Test if location of prev case is given
32+
$ $MERLIN single jump -target match-prev-case -position 5:2 -filename test.ml < test.ml
33+
{
34+
"class": "return",
35+
"value": {
36+
"pos": {
37+
"line": 3,
38+
"col": 2
39+
}
40+
},
41+
"notifications": []
42+
}
43+
44+
Test when cursor is not in a match statement
45+
$ $MERLIN single jump -target match-prev-case -position 1:2 -filename test.ml < test.ml
46+
{
47+
"class": "return",
48+
"value": "No matching target",
49+
"notifications": []
50+
}
51+
52+
53+
Test when there's no next case
54+
$ $MERLIN single jump -target match-next-case -position 13:2 -filename test.ml < test.ml
55+
{
56+
"class": "return",
57+
"value": "No next case found",
58+
"notifications": []
59+
}
60+
61+
Test when there's no previous case
62+
$ $MERLIN single jump -target match-prev-case -position 3:2 -filename test.ml < test.ml
63+
{
64+
"class": "return",
65+
"value": "No previous case found",
66+
"notifications": []
67+
}
68+
69+
Test jump from case 'O' to the previous case
70+
$ $MERLIN single jump -target match-prev-case -position 9:2 -filename test.ml < test.ml
71+
{
72+
"class": "return",
73+
"value": {
74+
"pos": {
75+
"line": 7,
76+
"col": 2
77+
}
78+
},
79+
"notifications": []
80+
}
81+
82+
Test jump from case 'O' to the next case
83+
$ $MERLIN single jump -target match-next-case -position 9:2 -filename test.ml < test.ml
84+
{
85+
"class": "return",
86+
"value": {
87+
"pos": {
88+
"line": 11,
89+
"col": 2
90+
}
91+
},
92+
"notifications": []
93+
}

0 commit comments

Comments
 (0)