Skip to content

Commit 85aad1b

Browse files
committed
handle new parser metadata in selection ranges
improve handing of parens fix cases where AST would produce ranges braking invariants
1 parent f7d03a2 commit 85aad1b

File tree

5 files changed

+186
-22
lines changed

5 files changed

+186
-22
lines changed

apps/language_server/lib/language_server/ast_utils.ex

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,40 @@ defmodule ElixirLS.LanguageServer.AstUtils do
1010
def node_range(node, options \\ [])
1111
def node_range(atom, _options) when is_atom(atom), do: nil
1212

13-
def node_range([{{:__block__, _, [_]} = first, _} | _] = list, _options) do
14-
case List.last(list) do
15-
{_, last} ->
16-
case {node_range(first), node_range(last)} do
17-
{range(start_line, start_character, _, _), range(_, _, end_line, end_character)} ->
18-
range(start_line, start_character, end_line, end_character)
19-
20-
_ ->
21-
nil
22-
end
13+
def node_range([{{:__block__, meta, [_]} = first, _} | _] = list, _options) do
14+
if Keyword.get(meta, :format) == :keyword or Keyword.has_key?(meta, :assoc) or
15+
Version.match?(System.version(), "< 1.18.0-dev") do
16+
case List.last(list) do
17+
{_, last} ->
18+
case {node_range(first), node_range(last)} do
19+
{range(start_line, start_character, _, _), range(_, _, end_line, end_character)} ->
20+
range(start_line, start_character, end_line, end_character)
21+
22+
_ ->
23+
nil
24+
end
2325

24-
_ ->
25-
nil
26+
_ ->
27+
nil
28+
end
2629
end
2730
end
2831

2932
def node_range(list, _options) when is_list(list), do: nil
3033

34+
def node_range({{:__block__, meta, [_]} = first, last}, _options) do
35+
if Keyword.get(meta, :format) == :keyword or Keyword.has_key?(meta, :assoc) or
36+
Version.match?(System.version(), "< 1.18.0-dev") do
37+
case {node_range(first), node_range(last)} do
38+
{range(start_line, start_character, _, _), range(_, _, end_line, end_character)} ->
39+
range(start_line, start_character, end_line, end_character)
40+
41+
_ ->
42+
nil
43+
end
44+
end
45+
end
46+
3147
def node_range({:__block__, meta, args} = _ast, _options) do
3248
line = Keyword.get(meta, :line)
3349
column = Keyword.get(meta, :column)
@@ -58,6 +74,18 @@ defmodule ElixirLS.LanguageServer.AstUtils do
5874
# 2 element tuple
5975
{end_location[:line] - 1, end_location[:column] - 1 + 1}
6076

77+
match?(kind when kind in [:atom, :keyword], Keyword.get(meta, :format)) ->
78+
[literal] = args
79+
80+
modifier =
81+
if literal in [true, false, nil] do
82+
1
83+
else
84+
0
85+
end
86+
87+
get_literal_end(literal, {line, column + modifier}, nil)
88+
6189
match?([_], args) ->
6290
[literal] = args
6391
delimiter = meta[:delimiter]
@@ -163,18 +191,24 @@ defmodule ElixirLS.LanguageServer.AstUtils do
163191
nil
164192
end
165193

166-
match?({:., _, [Kernel, :to_string]}, form) ->
167-
{line, column}
194+
match?({:., _meta, [Kernel, :to_string]}, form) ->
195+
if Keyword.get(meta, :from_interpolation) or
196+
Version.match?(System.version(), "< 1.16.0-dev") do
197+
{line, column}
198+
end
168199

169-
match?({:., _, [Access, :get]}, form) and match?([_ | _], args) ->
170-
[arg | _] = args
200+
match?({:., _meta, [Access, :get]}, form) and match?([_ | _], args) ->
201+
if Keyword.get(meta, :from_brackets) or
202+
Version.match?(System.version(), "< 1.16.0-dev") do
203+
[arg | _] = args
171204

172-
case node_range(arg) do
173-
range(line, column, _, _) ->
174-
{line, column}
205+
case node_range(arg) do
206+
range(line, column, _, _) ->
207+
{line, column}
175208

176-
nil ->
177-
nil
209+
nil ->
210+
nil
211+
end
178212
end
179213

180214
match?({:., _, [_ | _]}, form) ->

apps/language_server/lib/language_server/providers/selection_ranges.ex

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,53 @@ defmodule ElixirLS.LanguageServer.Providers.SelectionRanges do
397397
acc
398398
end
399399

400+
ranges_acc =
401+
case ast do
402+
{_, meta, _} ->
403+
parens_ranges =
404+
for {:parens, parens_meta} <- meta,
405+
parens_start_line = Keyword.fetch!(parens_meta, :line) - 1,
406+
parens_start_character = Keyword.fetch!(parens_meta, :column) - 1,
407+
parens_meta_closing = Keyword.fetch!(parens_meta, :closing),
408+
parens_end_line = Keyword.fetch!(parens_meta_closing, :line) - 1,
409+
parens_end_character = Keyword.fetch!(parens_meta_closing, :column),
410+
(parens_start_line < line or
411+
(parens_start_line == line and parens_start_character <= character)) and
412+
(parens_end_line > line or
413+
(parens_end_line == line and parens_end_character >= character)) do
414+
# NOTE there may be multiple parens keys
415+
outer_range =
416+
range(
417+
parens_start_line,
418+
parens_start_character,
419+
parens_end_line,
420+
parens_end_character
421+
)
422+
423+
if (parens_start_line < line or
424+
(parens_start_line == line and parens_start_character + 1 <= character)) and
425+
(parens_end_line > line or
426+
(parens_end_line == line and parens_end_character - 1 >= character)) do
427+
inner_range =
428+
range(
429+
parens_start_line,
430+
parens_start_character + 1,
431+
parens_end_line,
432+
parens_end_character - 1
433+
)
434+
435+
[outer_range, inner_range]
436+
else
437+
[outer_range]
438+
end
439+
end
440+
441+
List.flatten(parens_ranges) ++ ranges_acc
442+
443+
_ ->
444+
ranges_acc
445+
end
446+
400447
parent_acc =
401448
if match?({_, _, _}, ast) do
402449
[ast | parent_ast]
@@ -417,6 +464,8 @@ defmodule ElixirLS.LanguageServer.Providers.SelectionRanges do
417464

418465
acc
419466
|> sort_ranges_widest_to_narrowest()
467+
|> deduplicate
468+
|> fix_properties
420469
end
421470

422471
def ast_node_ranges(_, _, _, _), do: []

apps/language_server/lib/language_server/range_utils.ex

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,34 @@ defmodule ElixirLS.LanguageServer.RangeUtils do
180180
defp do_deduplicate([range | rest], acc) do
181181
do_deduplicate(rest, [range | acc])
182182
end
183+
184+
def fix_properties(ranges) do
185+
ranges
186+
|> Enum.reverse()
187+
|> Enum.reduce([], fn
188+
r, [] ->
189+
[r]
190+
191+
range(start_line, start_character, end_line, end_character),
192+
[range(last_start_line, last_start_character, last_end_line, last_end_character) | _] = acc ->
193+
new_start_line = min(start_line, last_start_line)
194+
new_end_line = max(end_line, last_end_line)
195+
196+
new_start_character =
197+
if start_line < last_start_line do
198+
start_character
199+
else
200+
min(start_character, last_start_character)
201+
end
202+
203+
new_end_character =
204+
if end_line > last_end_line do
205+
end_character
206+
else
207+
max(end_character, last_end_character)
208+
end
209+
210+
[range(new_start_line, new_start_character, new_end_line, new_end_character) | acc]
211+
end)
212+
end
183213
end

apps/language_server/test/ast_utils_test.exs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,17 @@ defmodule ElixirLS.LanguageServer.AstUtilsTest do
3232
assert get_range("nil") == range(0, 0, 0, 3)
3333
end
3434

35+
if Version.match?(System.version(), ">= 1.18.0") do
36+
test "true as atom" do
37+
assert get_range(":true") == range(0, 0, 0, 5)
38+
end
39+
end
40+
3541
test "integer" do
3642
assert get_range("1234") == range(0, 0, 0, 4)
43+
44+
assert node_range({:__block__, [token: "2", line: 1, column: 10], [2]}) ==
45+
range(0, 9, 0, 10)
3746
end
3847

3948
test "float" do
@@ -246,8 +255,9 @@ defmodule ElixirLS.LanguageServer.AstUtilsTest do
246255
end
247256

248257
# Parser is simplifying the expression and not including the parens
258+
# we handle parens meta in selection ranges
249259
# test "nested binary operators with parens" do
250-
# assert get_range("var * 3 * (foo + x)") == range(0, 0, 0, 17)
260+
# assert get_range("var * 3 * (foo + x)") == range(0, 0, 0, 19)
251261
# end
252262

253263
test "nested binary and unary operators" do

apps/language_server/test/providers/selection_ranges_test.exs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,12 @@ defmodule ElixirLS.LanguageServer.Providers.SelectionRangesTest do
767767
assert_range(ranges, range(0, 0, 1, 0))
768768
# full for
769769
assert_range(ranges, range(0, 0, 0, 56))
770+
771+
if Version.match?(System.version(), ">= 1.18.0") do
772+
# do: x + y expression
773+
assert_range(ranges, range(0, 47, 0, 56))
774+
end
775+
770776
# x + y expression
771777
assert_range(ranges, range(0, 51, 0, 56))
772778
end
@@ -994,6 +1000,14 @@ defmodule ElixirLS.LanguageServer.Providers.SelectionRangesTest do
9941000
assert_range(ranges, range(0, 0, 3, 1))
9951001
# full keyword
9961002
assert_range(ranges, range(0, 6, 2, 6))
1003+
1004+
if Version.match?(System.version(), ">= 1.18.0") do
1005+
# key and value
1006+
assert_range(ranges, range(1, 2, 1, 6))
1007+
1008+
# key
1009+
assert_range(ranges, range(1, 2, 1, 4))
1010+
end
9971011
end
9981012
end
9991013

@@ -1062,4 +1076,31 @@ defmodule ElixirLS.LanguageServer.Providers.SelectionRangesTest do
10621076
assert_range(ranges, range(0, 2, 0, 78))
10631077
end
10641078
end
1079+
1080+
test "expression with parens" do
1081+
text = """
1082+
1 + (2 * (3 + (s + x)) / 1)
1083+
"""
1084+
1085+
ranges = get_ranges(text, 0, 15)
1086+
1087+
# full range
1088+
assert_range(ranges, range(0, 0, 1, 0))
1089+
# full line
1090+
assert_range(ranges, range(0, 0, 0, 27))
1091+
# outside outermost parens
1092+
assert_range(ranges, range(0, 4, 0, 27))
1093+
# inside outermost parens
1094+
assert_range(ranges, range(0, 5, 0, 26))
1095+
# outside middle parens
1096+
assert_range(ranges, range(0, 9, 0, 22))
1097+
# inside middle parens
1098+
assert_range(ranges, range(0, 10, 0, 21))
1099+
# outside innermost parens
1100+
assert_range(ranges, range(0, 14, 0, 21))
1101+
# inside innermost parens
1102+
assert_range(ranges, range(0, 15, 0, 20))
1103+
# s variable
1104+
assert_range(ranges, range(0, 15, 0, 16))
1105+
end
10651106
end

0 commit comments

Comments
 (0)