diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index ac11ead3e..727b71b58 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -17,6 +17,7 @@ const charsets = @import("charsets.zig"); const csi = @import("csi.zig"); const hyperlink = @import("hyperlink.zig"); const kitty = @import("kitty.zig"); +const osc = @import("osc.zig"); const point = @import("point.zig"); const sgr = @import("sgr.zig"); const Tabstops = @import("Tabstops.zig"); @@ -1058,6 +1059,65 @@ pub fn setProtectedMode(self: *Terminal, mode: ansi.ProtectedMode) void { } } +/// Perform a semantic prompt command. +/// +/// If there is an error, we do our best to get the terminal into +/// some coherent state, since callers typically can't handle errors +/// (since they're sending sequences via the pty). +pub fn semanticPrompt( + self: *Terminal, + cmd: osc.Command.SemanticPrompt, +) !void { + switch (cmd.action) { + .fresh_line => try self.semanticPromptFreshLine(), + .fresh_line_new_prompt => { + // "First do a fresh-line." + try self.semanticPromptFreshLine(); + + // "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)." + // TODO + + // The "aid" and "cl" options are also valid for this + // command but we don't yet handle these in any meaningful way. + }, + + else => {}, + } +} + +fn semanticPromptSet( + self: *Terminal, + mode: pagepkg.Cell.SemanticContent, +) void { + // We always reset this when we mode change. The caller can set it + // again after if they care. + self.screens.active.cursor.semantic_content_clear_eol = false; + + // Update our mode + self.screens.active.cursor.semantic_content = mode; +} + +// OSC 133;L +fn semanticPromptFreshLine(self: *Terminal) !void { + const left_margin = if (self.screens.active.cursor.x < self.scrolling_region.left) + 0 + else + self.scrolling_region.left; + + // Spec: "If the cursor is the initial column (left, assuming + // left-to-right writing), do nothing" This specification is very under + // specified. We are taking the liberty to assume that in a left/right + // margin context, if the cursor is outside of the left margin, we treat + // it as being at the left margin for the purposes of this command. + // This is arbitrary. If someone has a better reasonable idea we can + // apply it. + if (self.screens.active.cursor.x == left_margin) return; + + self.carriageReturn(); + try self.index(); +} + /// The semantic prompt type. This is used when tracking a line type and /// requires integration with the shell. By default, we mark a line as "none" /// meaning we don't know what type it is. diff --git a/src/terminal/stream_readonly.zig b/src/terminal/stream_readonly.zig index 18ed0dd42..75e7cf129 100644 --- a/src/terminal/stream_readonly.zig +++ b/src/terminal/stream_readonly.zig @@ -153,7 +153,7 @@ pub const Handler = struct { .full_reset => self.terminal.fullReset(), .start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id), .end_hyperlink => self.terminal.screens.active.endHyperlink(), - .semantic_prompt => self.semanticPrompt(value), + .semantic_prompt => try self.semanticPrompt(value), .mouse_shape => self.terminal.mouse_shape = value, .color_operation => try self.colorOperation(value.op, &value.requests), .kitty_color_report => try self.kittyColorOperation(value), @@ -212,7 +212,9 @@ pub const Handler = struct { fn semanticPrompt( self: *Handler, cmd: Action.SemanticPrompt, - ) void { + ) !void { + try self.terminal.semanticPrompt(cmd); + switch (cmd.action) { .fresh_line_new_prompt => { const kind = cmd.readOption(.prompt_kind) orelse .initial; @@ -905,3 +907,16 @@ test "palette dirty flag set on color change" { try s.nextSlice("\x1b]21;1=rgb:00/ff/00\x1b\\"); try testing.expect(t.flags.dirty.palette); } + +test "semantic prompt fresh line" { + var t: Terminal = try .init(testing.allocator, .{ .cols = 10, .rows = 10 }); + defer t.deinit(testing.allocator); + + var s: Stream = .initAlloc(testing.allocator, .init(&t)); + defer s.deinit(); + + try s.nextSlice("Hello"); + try s.nextSlice("\x1b]133;L\x07"); + try testing.expectEqual(@as(usize, 0), t.screens.active.cursor.x); + try testing.expectEqual(@as(usize, 1), t.screens.active.cursor.y); +}