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
19 changes: 19 additions & 0 deletions src/terminal/Screen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,25 @@ pub fn cursorResetWrap(self: *Screen) void {
}
}

/// Make the cursor row start a new logical line by resetting the soft wrap
/// from the row above into it. Reflow then never joins the two rows.
pub fn cursorBreakWrapIntoRow(self: *Screen) void {
const page_row = self.cursor.page_row;
if (!page_row.wrap_continuation) return;
page_row.wrap_continuation = false;

const prev = self.cursor.page_pin.up(1) orelse return;
const prev_row = prev.rowAndCell().row;
prev_row.wrap = false;

// A spacer head only exists to carry a wide character over the wrap.
const cells = prev.cells(.all);
const last = prev.node.cols() - 1;
if (cells[last].wide == .spacer_head) {
self.clearCells(prev.node.page(), prev_row, cells[last..][0..1]);
}
}

/// Options for scrolling the viewport of the terminal grid. The reason
/// we have this in addition to PageList.Scroll is because we have additional
/// scroll behaviors that are not part of the PageList.Scroll enum.
Expand Down
47 changes: 47 additions & 0 deletions src/terminal/Terminal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1973,6 +1973,13 @@ pub fn semanticPrompt(

const screen: *Screen = self.screens.active;

// A prompt that starts at column 0 of a soft-wrapped row reached
// it through padding, such as zsh PROMPT_SP overflowing spaces
// after a partial line. The shell draws and redraws this prompt
// as the start of a line, so reflow must not join it onto the
// padded row above.
if (screen.cursor.x == 0) screen.cursorBreakWrapIntoRow();

// "Subsequent text (until a OSC "133;B" or OSC "133;I" command)
// is a prompt string (as if followed by OSC 133;P;k=i\007)."
screen.cursorSetSemanticContent(.{
Expand Down Expand Up @@ -15280,6 +15287,46 @@ test "Terminal: OSC133A cl option sets click to cl value" {
try testing.expectEqual(Screen.SemanticPrompt.SemanticClick{ .cl = .multiple }, t.screens.active.semantic_prompt.click);
}

test "Terminal: OSC133A after a padded partial line keeps the prompt on its own line across resize" {
const alloc = testing.allocator;
const io_impl = testing.io;
var t = try init(io_impl, alloc, .{ .cols = 10, .rows = 5 });
defer t.deinit(alloc);

// zsh PROMPT_SP: after output with no trailing newline, the shell prints
// a mark and pads with spaces past the right edge so the terminal wraps
// to a new row, then returns to column 0 and starts the prompt there.
try t.printString("ab%");
try t.printString(" ");
t.carriageReturn();
try t.printString(" ");
t.carriageReturn();
try t.semanticPrompt(.{
.action = .fresh_line_new_prompt,
.options_unvalidated = "redraw=0",
});
try t.printString("$ ");
try t.semanticPrompt(.init(.end_prompt_start_input));
try t.printString("ls");
try testing.expectEqual(@as(size.CellCountInt, 1), t.screens.active.cursor.y);

// The shell redraws its prompt from column 0 after a resize, so reflow
// must not join the prompt onto the padded row above it.
try t.resize(alloc, .{ .cols = 20, .rows = 5 });
try testing.expectEqual(@as(size.CellCountInt, 1), t.screens.active.cursor.y);
try testing.expectEqual(@as(size.CellCountInt, 4), t.screens.active.cursor.x);
const prompt_start = t.screens.active.pages.getCell(.{ .active = .{ .x = 0, .y = 1 } }).?;
try testing.expectEqual(@as(u21, '$'), prompt_start.cell.codepoint());

try t.resize(alloc, .{ .cols = 6, .rows = 5 });
const narrowed_start = t.screens.active.pages.getCell(.{ .active = .{
.x = 0,
.y = t.screens.active.cursor.y,
} }).?;
try testing.expectEqual(@as(u21, '$'), narrowed_start.cell.codepoint());
try testing.expectEqual(@as(size.CellCountInt, 4), t.screens.active.cursor.x);
}

test "Terminal: OSC133A cl=line sets click to line" {
const alloc = testing.allocator;
const io_impl = testing.io;
Expand Down
Loading