Skip to content

Commit 6bdecff

Browse files
authored
advance line counter by 1 on /r/n line ending (#489)
previous version wrongly advanced it by 2 Fixes #484
1 parent 607691e commit 6bdecff

File tree

2 files changed

+174
-4
lines changed

2 files changed

+174
-4
lines changed

apps/language_server/lib/language_server/providers/formatting.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
6161

6262
def should_format?(_file_uri, _project_dir, _inputs), do: true
6363

64-
defp myers_diff_to_text_edits(myers_diff, starting_pos \\ {0, 0}) do
65-
myers_diff_to_text_edits(myers_diff, starting_pos, [])
64+
defp myers_diff_to_text_edits(myers_diff) do
65+
myers_diff_to_text_edits(myers_diff, {0, 0}, [])
6666
end
6767

6868
defp myers_diff_to_text_edits([], _pos, edits) do
@@ -92,7 +92,7 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
9292

9393
defp advance_pos({line, col}, str) do
9494
Enum.reduce(String.split(str, "", trim: true), {line, col}, fn char, {line, col} ->
95-
if char in ["\n", "\r"] do
95+
if char in ["\r\n", "\n", "\r"] do
9696
{line + 1, 0}
9797
else
9898
# LSP contentChanges positions are based on UTF-16 string representation

apps/language_server/test/providers/formatting_test.exs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
66
alias ElixirLS.LanguageServer.Test.FixtureHelpers
77

88
@tag :fixture
9-
test "Formats a file" do
9+
test "Formats a file with LF line endings" do
1010
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
1111
path = "lib/file.ex"
1212
uri = SourceFile.path_to_uri(path)
@@ -55,6 +55,176 @@ defmodule ElixirLS.LanguageServer.Providers.FormattingTest do
5555
end)
5656
end
5757

58+
@tag :fixture
59+
test "Formats a file with CRLF line endings" do
60+
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
61+
path = "lib/file.ex"
62+
uri = SourceFile.path_to_uri(path)
63+
64+
text = """
65+
defmodule MyModule do
66+
require Logger
67+
68+
def dummy_function() do
69+
Logger.info "dummy"
70+
end
71+
end
72+
"""
73+
74+
text = text |> String.replace("\n", "\r\n")
75+
76+
source_file = %SourceFile{
77+
text: text,
78+
version: 1,
79+
dirty?: true
80+
}
81+
82+
project_dir = maybe_convert_path_separators(FixtureHelpers.get_path("formatter"))
83+
84+
assert {:ok, changes} = Formatting.format(source_file, uri, project_dir)
85+
86+
assert changes == [
87+
%{
88+
"newText" => "\n",
89+
"range" => %{
90+
"end" => %{"character" => 0, "line" => 7},
91+
"start" => %{"character" => 3, "line" => 6}
92+
}
93+
},
94+
%{
95+
"newText" => "\n",
96+
"range" => %{
97+
"end" => %{"character" => 0, "line" => 6},
98+
"start" => %{"character" => 5, "line" => 5}
99+
}
100+
},
101+
%{
102+
"newText" => ")\n",
103+
"range" => %{
104+
"end" => %{"character" => 0, "line" => 5},
105+
"start" => %{"character" => 23, "line" => 4}
106+
}
107+
},
108+
%{
109+
"newText" => "(",
110+
"range" => %{
111+
"end" => %{"character" => 16, "line" => 4},
112+
"start" => %{"character" => 15, "line" => 4}
113+
}
114+
},
115+
%{
116+
"newText" => "\n",
117+
"range" => %{
118+
"end" => %{"character" => 0, "line" => 4},
119+
"start" => %{"character" => 25, "line" => 3}
120+
}
121+
},
122+
%{
123+
"newText" => "\n\n",
124+
"range" => %{
125+
"end" => %{"character" => 0, "line" => 3},
126+
"start" => %{"character" => 16, "line" => 1}
127+
}
128+
},
129+
%{
130+
"newText" => "\n",
131+
"range" => %{
132+
"end" => %{"character" => 0, "line" => 1},
133+
"start" => %{"character" => 21, "line" => 0}
134+
}
135+
}
136+
]
137+
138+
assert Enum.all?(changes, fn change ->
139+
assert_position_type(change["range"]["end"]) and
140+
assert_position_type(change["range"]["start"])
141+
end)
142+
end)
143+
end
144+
145+
@tag :fixture
146+
test "elixir formatter does not support CR line endings" do
147+
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
148+
path = "lib/file.ex"
149+
uri = SourceFile.path_to_uri(path)
150+
151+
text = """
152+
defmodule MyModule do
153+
require Logger
154+
155+
def dummy_function() do
156+
Logger.info "dummy"
157+
end
158+
end
159+
"""
160+
161+
text = text |> String.replace("\n", "\r")
162+
163+
source_file = %SourceFile{
164+
text: text,
165+
version: 1,
166+
dirty?: true
167+
}
168+
169+
project_dir = maybe_convert_path_separators(FixtureHelpers.get_path("formatter"))
170+
171+
assert {:error, :internal_error, msg} = Formatting.format(source_file, uri, project_dir)
172+
assert String.contains?(msg, "Unable to format")
173+
end)
174+
end
175+
176+
@tag :fixture
177+
test "formatting preserves line indings inside a string" do
178+
in_fixture(Path.join(__DIR__, ".."), "formatter", fn ->
179+
path = "lib/file.ex"
180+
uri = SourceFile.path_to_uri(path)
181+
182+
text = """
183+
defmodule MyModule do
184+
require Logger
185+
186+
def dummy_function() do
187+
Logger.info "dummy"
188+
end
189+
end
190+
"""
191+
192+
text = text |> String.replace("\"dummy\"", " \"du\nm\rm\r\ny\"")
193+
194+
source_file = %SourceFile{
195+
text: text,
196+
version: 1,
197+
dirty?: true
198+
}
199+
200+
project_dir = maybe_convert_path_separators(FixtureHelpers.get_path("formatter"))
201+
202+
assert {:ok, changes} = Formatting.format(source_file, uri, project_dir)
203+
204+
assert changes == [
205+
%{
206+
"newText" => ")",
207+
"range" => %{
208+
"end" => %{"character" => 2, "line" => 7},
209+
"start" => %{"character" => 2, "line" => 7}
210+
}
211+
},
212+
%{
213+
"newText" => "(",
214+
"range" => %{
215+
"end" => %{"character" => 20, "line" => 4},
216+
"start" => %{"character" => 15, "line" => 4}
217+
}
218+
}
219+
]
220+
221+
assert Enum.all?(changes, fn change ->
222+
assert_position_type(change["range"]["end"]) and
223+
assert_position_type(change["range"]["start"])
224+
end)
225+
end)
226+
end
227+
58228
defp assert_position_type(%{"character" => ch, "line" => line}),
59229
do: is_integer(ch) and is_integer(line)
60230

0 commit comments

Comments
 (0)