diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 268ae934a..09e5d19df 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -77,6 +77,9 @@ else /// Dirty flags for the renderer. dirty: Dirty = .{}, +/// Packed flags for the screen, internal state. +flags: Flags = .{}, + /// See Terminal.Dirty. This behaves the same way. pub const Dirty = packed struct { /// Set when the selection is set or unset, regardless of if the @@ -88,6 +91,17 @@ pub const Dirty = packed struct { hyperlink_hover: bool = false, }; +/// A set of internal state that we pack for memory size. +pub const Flags = packed struct { + /// This is flipped to true when any sort of semantic content is + /// seen. In particular, this is set to true only when a `prompt` type + /// is ever set on our cursor. + /// + /// This is used to optimize away semantic content operations if we know + /// we've never seen them. + semantic_content: bool = false, +}; + /// The cursor position and style. pub const Cursor = struct { // The x/y position within the active area. @@ -152,39 +166,6 @@ pub const Cursor = struct { alloc.destroy(link); } } - - /// Modify the semantic content type of the cursor. This should - /// be preferred over setting it manually since it handles all the - /// proper accounting. - pub fn setSemanticContent(self: *Cursor, t: union(enum) { - prompt: osc.semantic_prompt.PromptKind, - output, - input: enum { clear_explicit, clear_eol }, - }) void { - switch (t) { - .output => { - self.semantic_content = .output; - self.semantic_content_clear_eol = false; - }, - - .input => |clear| { - self.semantic_content = .input; - self.semantic_content_clear_eol = switch (clear) { - .clear_explicit => false, - .clear_eol => true, - }; - }, - - .prompt => |kind| { - self.semantic_content = .prompt; - self.semantic_content_clear_eol = false; - self.page_row.semantic_prompt2 = switch (kind) { - .initial, .right => .prompt, - .continuation, .secondary => .prompt_continuation, - }; - }, - } - } }; /// Saved cursor state. @@ -397,6 +378,7 @@ pub fn reset(self: *Screen) void { self.charset = .{}; self.kitty_keyboard = .{}; self.protected_mode = .off; + self.flags = .{}; self.clearSelection(); } @@ -2363,6 +2345,42 @@ pub fn cursorSetHyperlink(self: *Screen) PageList.IncreaseCapacityError!void { } } +/// Modify the semantic content type of the cursor. This should +/// be preferred over setting it manually since it handles all the +/// proper accounting. +pub fn cursorSetSemanticContent(self: *Screen, t: union(enum) { + prompt: osc.semantic_prompt.PromptKind, + output, + input: enum { clear_explicit, clear_eol }, +}) void { + const cursor = &self.cursor; + + switch (t) { + .output => { + cursor.semantic_content = .output; + cursor.semantic_content_clear_eol = false; + }, + + .input => |clear| { + cursor.semantic_content = .input; + cursor.semantic_content_clear_eol = switch (clear) { + .clear_explicit => false, + .clear_eol => true, + }; + }, + + .prompt => |kind| { + self.flags.semantic_content = true; + cursor.semantic_content = .prompt; + cursor.semantic_content_clear_eol = false; + cursor.page_row.semantic_prompt2 = switch (kind) { + .initial, .right => .prompt, + .continuation, .secondary => .prompt_continuation, + }; + }, + } +} + /// Set the selection to the given selection. If this is a tracked selection /// then the screen will take ownership of the selection. If this is untracked /// then the screen will convert it to tracked internally. This will automatically @@ -3874,7 +3892,7 @@ test "Screen eraseRows active partial" { } } -test "Screen: clearPrompt" { +test "Screen: clearPrompt single line prompt" { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index dc649c5b1..2bbec67dc 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -1077,7 +1077,7 @@ pub fn semanticPrompt( // "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)." - self.screens.active.cursor.setSemanticContent(.{ + self.screens.active.cursorSetSemanticContent(.{ .prompt = cmd.readOption(.prompt_kind) orelse .initial, }); @@ -1109,7 +1109,7 @@ pub fn semanticPrompt( // The k (kind) option specifies the type of prompt: // regular primary prompt (k=i or default), // right-side prompts (k=r), or prompts for continuation lines (k=c or k=s). - self.screens.active.cursor.setSemanticContent(.{ + self.screens.active.cursorSetSemanticContent(.{ .prompt = cmd.readOption(.prompt_kind) orelse .initial, }); }, @@ -1117,21 +1117,21 @@ pub fn semanticPrompt( .end_prompt_start_input => { // End of prompt and start of user input, terminated by a OSC // "133;C" or another prompt (OSC "133;P"). - self.screens.active.cursor.setSemanticContent(.{ + self.screens.active.cursorSetSemanticContent(.{ .input = .clear_explicit, }); }, .end_prompt_start_input_terminate_eol => { // End of prompt and start of user input, terminated by end-of-line. - self.screens.active.cursor.setSemanticContent(.{ + self.screens.active.cursorSetSemanticContent(.{ .input = .clear_eol, }); }, .end_input_start_output => { // "End of input, and start of output." - self.screens.active.cursor.setSemanticContent(.output); + self.screens.active.cursorSetSemanticContent(.output); }, .end_command => { @@ -1139,7 +1139,7 @@ pub fn semanticPrompt( // anything. Other terminals appear to do nothing here. I think // its reasonable at this point to reset our semantic content // state but the spec doesn't really say what to do. - self.screens.active.cursor.setSemanticContent(.output); + self.screens.active.cursorSetSemanticContent(.output); }, } }