Skip to content

Commit f248030

Browse files
committed
add whitespaces so that cursor is in correct position
1 parent fb867ea commit f248030

File tree

2 files changed

+89
-42
lines changed

2 files changed

+89
-42
lines changed

lib/elixir_sense/core/source.ex

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -159,23 +159,84 @@ defmodule ElixirSense.Core.Source do
159159
{prefix, suffix}
160160
end
161161

162+
@doc """
163+
Splits the given source `src` into a list of lines using all common newline forms.
164+
"""
165+
def split_lines(src, opts \\ []) do
166+
String.split(src, ["\r\n", "\r", "\n"], opts)
167+
end
168+
169+
@doc """
170+
Splits `src` at the given `line` and `col`.
171+
172+
If the specified column is past the end of the line,
173+
the text before the split is padded with spaces.
174+
"""
162175
@spec split_at(String.t(), pos_integer, pos_integer) :: {String.t(), String.t()}
163-
def split_at(code, line, col) do
164-
pos = find_position(code, max(line, 1), max(col, 1), {0, 1, 1})
165-
String.split_at(code, pos)
176+
def split_at(src, line, col) do
177+
# Ensure minimum line and column are 1.
178+
line = max(line, 1)
179+
col = max(col, 1)
180+
lines = split_lines(src, trim: false)
181+
{prefix, target, suffix} = extract_line(lines, line)
182+
183+
target_len = String.length(target)
184+
185+
{before_target, after_target} =
186+
if col - 1 <= target_len do
187+
{
188+
String.slice(target, 0, col - 1),
189+
String.slice(target, col - 1, target_len - (col - 1))
190+
}
191+
else
192+
pad = String.duplicate(" ", col - 1 - target_len)
193+
{target <> pad, ""}
194+
end
195+
196+
before_part =
197+
case prefix do
198+
[] -> before_target
199+
_ -> Enum.join(prefix, "\n") <> "\n" <> before_target
200+
end
201+
202+
after_part =
203+
if suffix == [] do
204+
after_target
205+
else
206+
after_target <> "\n" <> Enum.join(suffix, "\n")
207+
end
208+
209+
{before_part, after_part}
210+
end
211+
212+
# If the requested line is beyond the number of lines,
213+
# we use the last line as the target.
214+
defp extract_line(lines, line) do
215+
if line > length(lines) do
216+
{Enum.slice(lines, 0, length(lines) - 1), List.last(lines) || "", []}
217+
else
218+
{prefix, [target | suffix]} = Enum.split(lines, line - 1)
219+
{prefix, target, suffix}
220+
end
166221
end
167222

223+
@doc """
224+
Splits `src` into segments at multiple positions.
225+
226+
The positions should be given as a list of `{line, col}` tuples.
227+
Splitting is performed from the end of the source backwards,
228+
so the final result is a list of segments.
229+
"""
168230
@spec split_at(String.t(), list({pos_integer, pos_integer})) :: list(String.t())
169-
def split_at(code, list) do
170-
do_split_at(code, Enum.reverse(list), [])
231+
def split_at(src, positions) when is_list(positions) do
232+
do_split_at(src, Enum.reverse(positions), [])
171233
end
172234

173-
defp do_split_at(code, [], acc), do: [code | acc]
235+
defp do_split_at(src, [], acc), do: [src | acc]
174236

175-
defp do_split_at(code, [{line, col} | rest], acc) do
176-
pos = find_position(code, max(line, 1), max(col, 1), {0, 1, 1})
177-
{text_before, text_after} = String.split_at(code, pos)
178-
do_split_at(text_before, rest, [text_after | acc])
237+
defp do_split_at(src, [{line, col} | rest], acc) do
238+
{before, after_cursor} = split_at(src, line, col)
239+
do_split_at(before, rest, [after_cursor | acc])
179240
end
180241

181242
@spec text_before(String.t(), pos_integer, pos_integer) :: String.t()
@@ -415,33 +476,6 @@ defmodule ElixirSense.Core.Source do
415476
end
416477
end
417478

418-
defp find_position(_text, line, col, {pos, line, col}) do
419-
pos
420-
end
421-
422-
defp find_position(text, line, col, {pos, current_line, current_col}) do
423-
case String.next_grapheme(text) do
424-
{grapheme, rest} ->
425-
{new_pos, new_line, new_col} =
426-
if grapheme in @line_break do
427-
if current_line == line do
428-
# this is the line we're lookin for
429-
# but it's shorter than expected
430-
{pos, current_line, col}
431-
else
432-
{pos + 1, current_line + 1, 1}
433-
end
434-
else
435-
{pos + 1, current_line, current_col + 1}
436-
end
437-
438-
find_position(rest, line, col, {new_pos, new_line, new_col})
439-
440-
nil ->
441-
pos
442-
end
443-
end
444-
445479
# since elixir 1.15 previous lines are kept in blocks
446480
# https://github.com/elixir-lang/elixir/commit/faf81cd92c7d6668d2e8115744cc8d06f9bfecba
447481
# skip as __block__ is not a call we are interested in
@@ -681,6 +715,7 @@ defmodule ElixirSense.Core.Source do
681715
end
682716
end
683717

718+
# TODO remove
684719
def concat_module_parts([{:__MODULE__, _, nil} | rest], current_module, aliases)
685720
when is_atom(current_module) and current_module != nil do
686721
case concat_module_parts(rest, current_module, aliases) do
@@ -704,10 +739,6 @@ defmodule ElixirSense.Core.Source do
704739

705740
def concat_module_parts([], _, _), do: :error
706741

707-
def split_lines(src, opts \\ []) do
708-
String.split(src, ["\r\n", "\r", "\n"], opts)
709-
end
710-
711742
def bitstring_options(prefix) do
712743
tokens = Tokenizer.tokenize(prefix)
713744

test/elixir_sense/core/source_test.exs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,22 @@ defmodule ElixirSense.Core.SourceTest do
781781
assert Enum.join(parts) == code
782782
end
783783

784+
test "one element list past line length" do
785+
code = """
786+
defmodule Abcd do
787+
def go do
788+
:ok
789+
end
790+
end
791+
"""
792+
793+
parts = split_at(code, [{2, 12}])
794+
assert parts == ["defmodule Abcd do\n def go do", "\n :ok\n end\nend\n"]
795+
796+
parts = split_at(code, [{2, 13}])
797+
assert parts == ["defmodule Abcd do\n def go do ", "\n :ok\n end\nend\n"]
798+
end
799+
784800
test "two element list same line" do
785801
code = """
786802
defmodule Abcd do
@@ -818,7 +834,7 @@ defmodule ElixirSense.Core.SourceTest do
818834
test "handles positions beyond code length" do
819835
code = "short"
820836
positions = [{0, 0}, {10, 15}]
821-
assert split_at(code, positions) == ["", "short", ""]
837+
assert split_at(code, positions) == ["", "short ", ""]
822838
end
823839
end
824840

0 commit comments

Comments
 (0)