Skip to content
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## [Unreleased]

### Tests

- **소프트랩 복사 동작 진단 — 코드는 이미 정상, 회귀 테스트 추가.** 소프트랩
(terminal auto-wrap · "줄내림")된 한 줄을 복사하면 wrap 지점마다 하드 개행이
끼어든다는 제보를 진단함. 복사 경로(`Screen.selectionString` ·
`Surface.copySelectionToClipboards`)는 이미 `unwrap = true` 이고,
`formatter.zig` 의 wrap 재결합 로직(`if (!row.wrap or !self.opts.unwrap)
blank_rows += 1;`)도 정상이라 소프트랩은 이미 한 줄로 재결합되고 실제 하드
개행(`\n`)만 보존됨. 기존/신규 테스트 전부 통과(82/82)하며 코드 결함을
재현할 수 없음 → **동작 변경 없음**. 제보된 시나리오를 못박는 회귀 테스트
2개 추가: 멀티-로우 소프트랩 한 줄이 개행 없이 재결합되는지(`soft wrap
multi-row rejoin`), 소프트랩 뒤 실제 하드 개행이 보존되는지(`soft wrap then
hard newline`). 사용자 측 텍스트에 실제 하드 개행이 들어있었거나 다른 터미널
설정이 원인일 가능성이 높음(void 복사 경로 자체는 올바름). (void/fix-softwrap-copy)

## [1.4.1] — 2026-05-31

### Fixed
Expand Down
66 changes: 66 additions & 0 deletions src/terminal/Screen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8984,6 +8984,72 @@ test "Screen: selectionString soft wrap" {
}
}

// Regression: a single long line that the terminal soft-wraps ("줄내림")
// across multiple visual rows must copy back as ONE continuous line with
// NO inserted newlines at the wrap points. Only a REAL hard newline (one a
// program actually emitted) may appear in the copied text. See
// github.com/dancinlab/void void/fix-softwrap-copy.
test "Screen: selectionString soft wrap multi-row rejoin" {
const testing = std.testing;
const alloc = testing.allocator;

var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
// 15 chars into a 5-col screen → one logical line soft-wrapped onto
// three visual rows (rows 0,1,2). No "\n" was emitted anywhere.
const str = "ABCDEFGHIJKLMNO";
try s.testWriteString(str);

{
// Select the entire soft-wrapped line, row 0 col 0 → row 2 col 4.
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 0 } }).?,
s.pages.pin(.{ .screen = .{ .x = 4, .y = 2 } }).?,
false,
);
const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents);
// Exactly the original 15 chars, rejoined — no '\n' inserted.
const expected = "ABCDEFGHIJKLMNO";
try testing.expectEqualStrings(expected, contents);
}
}

// Regression: a soft-wrapped line FOLLOWED by a real hard newline. The
// wrap points must rejoin, but the genuine "\n" must be preserved. This is
// the discriminator that distinguishes a soft-wrap defect (would drop or
// duplicate newlines) from correct behavior.
test "Screen: selectionString soft wrap then hard newline" {
const testing = std.testing;
const alloc = testing.allocator;

var s = try init(alloc, .{ .cols = 5, .rows = 5, .max_scrollback = 0 });
defer s.deinit();
// "ABCDEFGHIJ" soft-wraps onto rows 0,1 (one logical line). Then an
// explicit "\n" begins a new logical line "KLM" on row 2.
const str = "ABCDEFGHIJ\nKLM";
try s.testWriteString(str);

{
const sel = Selection.init(
s.pages.pin(.{ .screen = .{ .x = 0, .y = 0 } }).?,
s.pages.pin(.{ .screen = .{ .x = 2, .y = 2 } }).?,
false,
);
const contents = try s.selectionString(alloc, .{
.sel = sel,
.trim = true,
});
defer alloc.free(contents);
// Soft-wrap rejoined ("ABCDEFGHIJ"), hard newline preserved.
const expected = "ABCDEFGHIJ\nKLM";
try testing.expectEqualStrings(expected, contents);
}
}

test "Screen: selectionString wide char" {
const testing = std.testing;
const alloc = testing.allocator;
Expand Down
Loading