Skip to content

keep the line endings and final newline of files when saving - #207

Open
nepinhum wants to merge 3 commits into
vlang:masterfrom
scheforks:fix/line-endings
Open

nepinhum wants to merge 3 commits into
vlang:masterfrom
scheforks:fix/line-endings

Conversation

@nepinhum

@nepinhum nepinhum commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Saving a file always wrote LF line endings and added a final newline, even when the original file used CRLF or had no trailing newline.

This adds a LineFormat struct to track the original line endings and whether the file ends with a newline. Both the in-place and the atomic save use it, so the format is preserved across edits and saves.

New files default to LF with a final newline. A file is detected as CRLF only when every line break is CRLF. Files with mixed line endings are not normalized: each CRLF line keeps its \r and unchanged lines keep their original endings, new lines get LF.

  • Added regression tests for LF, CRLF, mixed line endings, empty files and files without a final newline.

@medvednikov medvednikov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed a677ac5 and reproduced two regressions with focused V harnesses using the head/base save code and relevant View methods (UI/session dependencies stubbed): mixed-ending append corrupts the line contents, and an empty-file Enter/save/delete sequence panics only at the PR head. Both are noted inline. CI was excluded from this review.

Comment thread save.c.v Outdated
Comment on lines +92 to +96
mut lines := (if final_newline { text[..text.len - 1] } else { text }).split('\n')
if crlf {
for i, line in lines {
if line.ends_with('\r') {
lines[i] = line[..line.len - 1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep mixed-file CRLF separators out of the editable line text

For a file containing a\r\nb\n, this leaves view.lines[0] equal to 'a\r'. Normal editing commands then treat that CR as text: pressing A, typing X, and saving produces a\rX \nb\n, putting the appended text after the carriage return and changing the first line's terminator. J likewise concatenates the lines into a\rb, retaining a line-break character inside the joined text. I reproduced the append behavior with the head/base View and save methods; the base strips the separator before editing. Please retain mixed line separators separately from editable line contents; an unchanged load/save round trip does not cover these operations.

Comment thread view.v
// the unsaved edits with whatever is on disk.
save := if view.ved.cfg.atomic_save { write_lines_atomic } else { write_lines }
save(path, view.lines) or {
save(path, view.lines, view.line_format) or {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Clamp the cursor after the new serializer shrinks the buffer

With formatting disabled, open an existing empty text file, enter insert mode, press Enter, and save. enter() leaves lines == ['', ''] and y == 1, but the loaded final_newline: false makes this save write only '\n'. The immediate reopen() reloads one line; open_file() calculates a clamped local y = 0, then its if y > 0 skips assigning it to view.y. The cursor remains at row 1 in a one-row buffer, so returning to normal mode and pressing dd panics in view.lines.delete(view.y). A focused head/base reproduction confirms the head's out-of-bounds deletion, while the base writes '\n\n' and keeps the cursor valid. Please apply the clamped cursor position even when it is zero after this round trip.

@nepinhum
nepinhum requested a review from medvednikov October 10, 2026 14:57
@nepinhum

Copy link
Copy Markdown
Contributor Author

Updated it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants