Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions openhands-tools/openhands/tools/file_editor/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,13 @@ def insert(
new_lines.append(line)
history_lines.append(line)

# If we are inserting after the last line (insert_line == num_lines)
# and the file does not end with a newline, the retained last line
# lacks "\n". Without it the inserted content is concatenated onto
# the last line instead of starting a new one.
if new_lines and not new_lines[-1].endswith("\n"):
new_lines[-1] = new_lines[-1] + "\n"

# Insert new content
for line in new_str_lines:
new_lines.append(line + "\n")
Expand Down
84 changes: 84 additions & 0 deletions tests/tools/file_editor/test_basic_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,3 +897,87 @@ def test_str_replace_and_insert_snippet_output_on_a_large_file(editor):
new_str="Inserted line at 500",
)
assert " 500\tInserted line at 500" in result.text


def test_insert_at_eof_without_trailing_newline(editor):
"""Inserting after the last line of a file without a trailing newline
should start the inserted content on its own line, not concatenate it
onto the last line.
"""
editor, test_file = editor
# The fixture's test file has 2 lines and no trailing newline
# ("This is a test file.\nThis file is for testing purposes.")
result = editor(
command="insert",
path=str(test_file),
insert_line=2,
new_str="New line at the end",
)

content = test_file.read_text()
assert content == (
"This is a test file.\n"
"This file is for testing purposes.\n"
"New line at the end\n"
)
# The inserted line must not be concatenated onto the previous last line
assert "testing purposes.New line" not in content
# The observation should show the new content as a separate line
assert "New line at the end" in result.text


def test_insert_multiple_lines_at_eof_without_trailing_newline(editor, tmp_path):
"""Multi-line inserts at EOF of a file without a trailing newline should
each land on their own lines, without merging into the original last line.
"""
editor, _ = editor

no_newline_file = tmp_path / "no_trailing_newline.txt"
no_newline_file.write_text("a\nb\nc") # no trailing newline
result = editor(
command="insert",
path=str(no_newline_file),
insert_line=3,
new_str="X\nY",
)

content = no_newline_file.read_text()
assert content == "a\nb\nc\nX\nY\n"
assert "cX" not in content
assert "X" in result.text and "Y" in result.text


def test_insert_at_eof_with_trailing_newline_unchanged(editor, tmp_path):
"""Inserting at EOF of a file that already ends with a newline keeps the
existing behavior: no extra blank line is introduced.
"""
editor, _ = editor

with_newline_file = tmp_path / "trailing_newline.txt"
with_newline_file.write_text("a\nb\nc\n") # trailing newline present
result = editor(
command="insert",
path=str(with_newline_file),
insert_line=3,
new_str="Z",
)

content = with_newline_file.read_text()
assert content == "a\nb\nc\nZ\n"


def test_insert_in_middle_unaffected_by_eof_fix(editor, tmp_path):
"""Inserting in the middle of a file still behaves as before."""
editor, _ = editor

mid_file = tmp_path / "middle.txt"
mid_file.write_text("a\nb\nc") # no trailing newline, but insert mid-file
editor(
command="insert",
path=str(mid_file),
insert_line=1,
new_str="MID",
)

content = mid_file.read_text()
assert content == "a\nMID\nb\nc"
Loading