mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
terminal: remove clearPrompt and integrate it into resize
This commit is contained in:
@@ -1461,61 +1461,6 @@ pub fn clearUnprotectedCells(
|
||||
self.assertIntegrity();
|
||||
}
|
||||
|
||||
/// Clears the prompt lines if the cursor is currently at a prompt. This
|
||||
/// clears the entire line. This is used for resizing when the shell
|
||||
/// handles reflow.
|
||||
///
|
||||
/// The cleared cells are not colored with the current style background
|
||||
/// color like other clear functions, because this is a special case used
|
||||
/// for a specific purpose that does not want that behavior.
|
||||
pub fn clearPrompt(self: *Screen) void {
|
||||
var found: ?Pin = null;
|
||||
|
||||
// From our cursor, move up and find all prompt lines.
|
||||
var it = self.cursor.page_pin.rowIterator(
|
||||
.left_up,
|
||||
self.pages.pin(.{ .active = .{} }),
|
||||
);
|
||||
while (it.next()) |p| {
|
||||
const row = p.rowAndCell().row;
|
||||
switch (row.semantic_prompt) {
|
||||
// We are at a prompt but we're not at the start of the prompt.
|
||||
// We mark our found value and continue because the prompt
|
||||
// may be multi-line, unless this is the second time we've
|
||||
// seen an .input marker, in which case we've run into an
|
||||
// earlier prompt.
|
||||
.input => {
|
||||
if (found != null) break;
|
||||
found = p;
|
||||
},
|
||||
|
||||
// If we find the prompt then we're done. We are also done
|
||||
// if we find any prompt continuation, because the shells
|
||||
// that send this currently (zsh) cannot redraw every line.
|
||||
.prompt, .prompt_continuation => {
|
||||
found = p;
|
||||
break;
|
||||
},
|
||||
|
||||
// If we have command output, then we're most certainly not
|
||||
// at a prompt. Break out of the loop.
|
||||
.command => break,
|
||||
|
||||
// If we don't know, we keep searching.
|
||||
.unknown => {},
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a prompt, we clear it.
|
||||
if (found) |top| {
|
||||
var clear_it = top.rowIterator(.right_down, null);
|
||||
while (clear_it.next()) |p| {
|
||||
const row = p.rowAndCell().row;
|
||||
p.node.data.clearCells(row, 0, p.node.data.size.cols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean up boundary conditions where a cell will become discontiguous with
|
||||
/// a neighboring cell because either one of them will be moved and/or cleared.
|
||||
///
|
||||
@@ -1662,6 +1607,12 @@ pub const Resize = struct {
|
||||
/// smaller and the maximum scrollback size is exceeded, data will be
|
||||
/// lost from the top of the scrollback.
|
||||
reflow: bool = true,
|
||||
|
||||
/// Set this to true to enable prompt redraw on resize. This signals
|
||||
/// that the running program can redraw the prompt if the cursor is
|
||||
/// currently at a prompt. This detects OSC133 prompts lines and clears
|
||||
/// them.
|
||||
prompt_redraw: bool = false,
|
||||
};
|
||||
|
||||
/// Resize the screen. The rows or cols can be bigger or smaller.
|
||||
@@ -1729,6 +1680,37 @@ pub inline fn resize(
|
||||
};
|
||||
defer if (saved_cursor_pin) |p| self.pages.untrackPin(p);
|
||||
|
||||
// If our cursor is on a prompt line, then we clear the prompt so
|
||||
// the shell can redraw it. This works with OSC133 semantic prompts.
|
||||
if (opts.prompt_redraw and
|
||||
self.cursor.page_row.semantic_prompt2 != .no_prompt)
|
||||
prompt: {
|
||||
const start = start: {
|
||||
var it = self.cursor.page_pin.promptIterator(
|
||||
.left_up,
|
||||
null,
|
||||
);
|
||||
break :start it.next() orelse {
|
||||
// This should never happen because promptIterator should always
|
||||
// find a prompt if we already verified our row is some kind of
|
||||
// prompt.
|
||||
log.warn("cursor on prompt line but promptIterator found no prompt", .{});
|
||||
break :prompt;
|
||||
};
|
||||
};
|
||||
|
||||
// Clear cells from our start down. We replace it with spaces,
|
||||
// and do not physically erase the rows (eraseRows) because the
|
||||
// shell is going to expect this space to be available.
|
||||
var it = start.rowIterator(.right_down, null);
|
||||
while (it.next()) |pin| {
|
||||
const page = &pin.node.data;
|
||||
const row = pin.rowAndCell().row;
|
||||
const cells = page.getCells(row);
|
||||
self.clearCells(page, row, cells);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the resize operation.
|
||||
try self.pages.resize(.{
|
||||
.rows = opts.rows,
|
||||
@@ -3189,29 +3171,6 @@ pub fn testWriteString(self: *Screen, text: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Write text that's marked as a semantic prompt.
|
||||
fn testWriteSemanticString(self: *Screen, text: []const u8, semantic_prompt: Row.SemanticPrompt) !void {
|
||||
// Determine the first row using the cursor position. If we know that our
|
||||
// first write is going to start on the next line because of a pending
|
||||
// wrap, we'll proactively start there.
|
||||
const start_y = if (self.cursor.pending_wrap) self.cursor.y + 1 else self.cursor.y;
|
||||
|
||||
try self.testWriteString(text);
|
||||
|
||||
// Determine the last row that we actually wrote by inspecting the cursor's
|
||||
// position. If we're in the first column, we haven't actually written any
|
||||
// characters to it, so we end at the preceding row instead.
|
||||
const end_y = if (self.cursor.x > 0) self.cursor.y else self.cursor.y - 1;
|
||||
|
||||
// Mark the full range of written rows with our semantic prompt.
|
||||
var y = start_y;
|
||||
while (y <= end_y) {
|
||||
const pin = self.pages.pin(.{ .active = .{ .y = y } }).?;
|
||||
pin.rowAndCell().row.semantic_prompt = semantic_prompt;
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen read and write" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
@@ -3824,88 +3783,6 @@ test "Screen eraseRows active partial" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: clearPrompt single line prompt" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
|
||||
defer s.deinit();
|
||||
|
||||
// Set one of the rows to be a prompt
|
||||
try s.testWriteSemanticString("1ABCD\n", .unknown);
|
||||
try s.testWriteSemanticString("2EFGH\n", .prompt);
|
||||
try s.testWriteSemanticString("3IJKL", .input);
|
||||
|
||||
s.clearPrompt();
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .screen = .{} });
|
||||
defer alloc.free(contents);
|
||||
try testing.expectEqualStrings("1ABCD", contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: clearPrompt continuation" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 5, .rows = 4, .max_scrollback = 0 });
|
||||
defer s.deinit();
|
||||
|
||||
// Set one of the rows to be a prompt followed by a continuation row
|
||||
try s.testWriteSemanticString("1ABCD\n", .unknown);
|
||||
try s.testWriteSemanticString("2EFGH\n", .prompt);
|
||||
try s.testWriteSemanticString("3IJKL\n", .prompt_continuation);
|
||||
try s.testWriteSemanticString("4MNOP", .input);
|
||||
|
||||
s.clearPrompt();
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .screen = .{} });
|
||||
defer alloc.free(contents);
|
||||
try testing.expectEqualStrings("1ABCD\n2EFGH", contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: clearPrompt consecutive inputs" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
|
||||
defer s.deinit();
|
||||
|
||||
// Set both rows to be inputs
|
||||
try s.testWriteSemanticString("1ABCD\n", .unknown);
|
||||
try s.testWriteSemanticString("2EFGH\n", .input);
|
||||
try s.testWriteSemanticString("3IJKL", .input);
|
||||
|
||||
s.clearPrompt();
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .screen = .{} });
|
||||
defer alloc.free(contents);
|
||||
try testing.expectEqualStrings("1ABCD\n2EFGH", contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: clearPrompt no prompt" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 5, .rows = 3, .max_scrollback = 0 });
|
||||
defer s.deinit();
|
||||
const str = "1ABCD\n2EFGH\n3IJKL";
|
||||
try s.testWriteString(str);
|
||||
|
||||
s.clearPrompt();
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .screen = .{} });
|
||||
defer alloc.free(contents);
|
||||
try testing.expectEqualStrings(str, contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: cursorDown across pages preserves style" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
@@ -7289,6 +7166,87 @@ test "Screen: resize more cols requiring a wide spacer head" {
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: resize more cols with cursor at prompt" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 });
|
||||
defer s.deinit();
|
||||
|
||||
// zig fmt: off
|
||||
try s.testWriteString("ABCDE\n");
|
||||
s.cursorSetSemanticContent(.{ .prompt = .initial });
|
||||
try s.testWriteString("> ");
|
||||
s.cursorSetSemanticContent(.{ .input = .clear_eol });
|
||||
try s.testWriteString("echo");
|
||||
// zig fmt: on
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||
defer alloc.free(contents);
|
||||
const expected = "ABCDE\n> echo";
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
|
||||
try s.resize(.{
|
||||
.cols = 20,
|
||||
.rows = 3,
|
||||
.prompt_redraw = true,
|
||||
});
|
||||
|
||||
// Cursor should not move
|
||||
try testing.expectEqual(6, s.cursor.x);
|
||||
try testing.expectEqual(1, s.cursor.y);
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||
defer alloc.free(contents);
|
||||
const expected = "ABCDE";
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: resize more cols with cursor not at prompt" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
var s = try init(alloc, .{ .cols = 10, .rows = 3, .max_scrollback = 5 });
|
||||
defer s.deinit();
|
||||
|
||||
// zig fmt: off
|
||||
try s.testWriteString("ABCDE\n");
|
||||
s.cursorSetSemanticContent(.{ .prompt = .initial });
|
||||
try s.testWriteString("> ");
|
||||
s.cursorSetSemanticContent(.{ .input = .clear_eol });
|
||||
try s.testWriteString("echo\n");
|
||||
try s.testWriteString("output");
|
||||
// zig fmt: on
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||
defer alloc.free(contents);
|
||||
const expected = "ABCDE\n> echo\noutput";
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
|
||||
try s.resize(.{
|
||||
.cols = 20,
|
||||
.rows = 3,
|
||||
.prompt_redraw = true,
|
||||
});
|
||||
|
||||
// Cursor should not move
|
||||
try testing.expectEqual(6, s.cursor.x);
|
||||
try testing.expectEqual(2, s.cursor.y);
|
||||
|
||||
{
|
||||
const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} });
|
||||
defer alloc.free(contents);
|
||||
const expected = "ABCDE\n> echo\noutput";
|
||||
try testing.expectEqualStrings(expected, contents);
|
||||
}
|
||||
}
|
||||
|
||||
test "Screen: select untracked" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
@@ -80,11 +80,10 @@ mouse_shape: mouse_shape_pkg.MouseShape = .text,
|
||||
|
||||
/// These are just a packed set of flags we may set on the terminal.
|
||||
flags: packed struct {
|
||||
// This isn't a mode, this is set by OSC 133 using the "A" event.
|
||||
// If this is true, it tells us that the shell supports redrawing
|
||||
// the prompt and that when we resize, if the cursor is at a prompt,
|
||||
// then we should clear the screen below and allow the shell to redraw.
|
||||
shell_redraws_prompt: bool = false,
|
||||
// This supports a Kitty extension where programs using semantic
|
||||
// prompts (OSC133) can annotate their new prompts with `redraw=0` to
|
||||
// disable clearing the prompt on resize.
|
||||
shell_redraws_prompt: bool = true,
|
||||
|
||||
// This is set via ESC[4;2m. Any other modify key mode just sets
|
||||
// this to false and we act in mode 1 by default.
|
||||
@@ -1082,7 +1081,8 @@ pub fn semanticPrompt(
|
||||
});
|
||||
|
||||
// This is a kitty-specific flag that notes that the shell
|
||||
// is capable of redraw.
|
||||
// is NOT capable of redraw. Redraw defaults to true so this
|
||||
// usually just disables it, but either is possible.
|
||||
if (cmd.readOption(.redraw)) |v| {
|
||||
self.flags.shell_redraws_prompt = v;
|
||||
}
|
||||
@@ -2680,15 +2680,11 @@ pub fn resize(
|
||||
|
||||
// Resize primary screen, which supports reflow
|
||||
const primary = self.screens.get(.primary).?;
|
||||
if (self.screens.active_key == .primary and
|
||||
self.flags.shell_redraws_prompt)
|
||||
{
|
||||
primary.clearPrompt();
|
||||
}
|
||||
try primary.resize(.{
|
||||
.cols = cols,
|
||||
.rows = rows,
|
||||
.reflow = self.modes.get(.wraparound),
|
||||
.prompt_redraw = self.flags.shell_redraws_prompt,
|
||||
});
|
||||
|
||||
// Alternate screen, if it exists, doesn't reflow
|
||||
|
||||
Reference in New Issue
Block a user