fix(editing): delete last line of a file without a trailing newline

delete_lines/replace_lines address the position one line past the last line
(line == number of lines, col 0). get_index_from_line_col cannot resolve that
position when the file has no trailing newline (no closing newline to count),
so delete_text_between_positions raised InvalidTextLocationError and the delete
crashed on a very common file shape.

Clamp that past-EOF end position to end-of-file in delete_text_between_positions,
mirroring the guard insert_text_at_position already has. A file with no trailing
newline now deletes its last line identically to one with a trailing newline.

Adds TextUtils unit tests and a CHANGELOG entry.
This commit is contained in:
AmirF194 authored and Dr. Dominik Jain committed 2026-07-15 20:22:25 +02:00
1 parent 755de59f16
commit cb38f6f82a
3 files changed
+48 -2

No files matched your search

+29 -1
View File
@@ -1,4 +1,6 @@
from solidlsp.ls_utils import TextUtils
import pytest
from solidlsp.ls_utils import InvalidTextLocationError, TextUtils
class TestTextUtils:
@@ -48,3 +50,29 @@ class TestTextUtils:
new_text, l, c = TextUtils.insert_text_at_position(self.TEXT, 4, 0, insertion)
assert (l, c) == (4, len(insertion))
assert new_text == self.TEXT + "\n" + insertion
def test_delete_text_deletes_last_line_without_trailing_newline(self) -> None:
"""Deleting the final line must work whether or not the file ends in a newline.
delete_lines(k, N-1) addresses the position one line past the last line
(line N, col 0). With no trailing newline there is no closing newline to
count, so get_index_from_line_col cannot resolve it; the delete must still
remove the last line instead of raising InvalidTextLocationError.
"""
# File with 3 lines, no trailing newline: read_file (splitlines) shows 0='a',1='b',2='c'.
text = "a\nb\nc"
new_text, deleted = TextUtils.delete_text_between_positions(text, 2, 0, 3, 0)
assert new_text == "a\nb\n"
assert deleted == "c"
def test_delete_text_last_line_matches_trailing_newline_variant(self) -> None:
"""Deleting the last line yields the same result with or without a trailing newline."""
without_nl, _ = TextUtils.delete_text_between_positions("a\nb\nc", 2, 0, 3, 0)
with_nl, _ = TextUtils.delete_text_between_positions("a\nb\nc\n", 2, 0, 3, 0)
assert without_nl == with_nl == "a\nb\n"
def test_delete_text_still_raises_for_out_of_range_end(self) -> None:
"""A genuinely out-of-range end position (beyond one-past-EOF) still raises."""
with pytest.raises(InvalidTextLocationError):
# end_line = 5 is well past the one-line-past-EOF position (3) for a 3-line file.
TextUtils.delete_text_between_positions("a\nb\nc", 0, 0, 5, 0)