diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 532bb352e..98b6edc4a 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -155,13 +155,14 @@ pub const RowHeader = struct { /// This is a prompt line, meaning it only contains the shell prompt. /// For poorly behaving shells, this may also be the input. prompt = 1, + prompt_continuation = 2, /// This line contains the input area. We don't currently track /// where this actually is in the line, so we just assume it is somewhere. - input = 2, + input = 3, /// This line is the start of command output. - command = 3, + command = 4, }; }; @@ -1762,7 +1763,7 @@ fn jumpPrompt(self: *Screen, delta: isize) bool { while (y >= 0 and y <= max_y and delta_rem > 0) : (y += step) { const row = self.getRow(.{ .screen = @intCast(y) }); switch (row.getSemanticPrompt()) { - .prompt, .input => delta_rem -= 1, + .prompt, .prompt_continuation, .input => delta_rem -= 1, .command, .unknown => {}, } } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 025167d74..bd32dbb83 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -40,6 +40,7 @@ pub const ScreenType = enum { /// See: https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md pub const SemanticPrompt = enum { prompt, + prompt_continuation, input, command, }; @@ -361,12 +362,18 @@ fn clearPromptForResize(self: *Terminal) void { const real_y = self.screen.cursor.y - y; const row = self.screen.getRow(.{ .active = real_y }); switch (row.getSemanticPrompt()) { - // If we're at a prompt or input area, then we are at a 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. - .prompt, - .input, - => found = real_y, + .input => found = real_y, + + // 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 = real_y; + break; + }, // If we have command output, then we're most certainly not // at a prompt. Break out of the loop. @@ -1535,6 +1542,7 @@ pub fn markSemanticPrompt(self: *Terminal, p: SemanticPrompt) void { const row = self.screen.getRow(.{ .active = self.screen.cursor.y }); row.setSemanticPrompt(switch (p) { .prompt => .prompt, + .prompt_continuation => .prompt_continuation, .input => .input, .command => .command, }); @@ -1557,6 +1565,7 @@ pub fn cursorIsAtPrompt(self: *Terminal) bool { switch (row.getSemanticPrompt()) { // If we're at a prompt or input area, then we are at a prompt. .prompt, + .prompt_continuation, .input, => return true, diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 44e4b4bb5..990962261 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -26,6 +26,7 @@ pub const Command = union(enum) { /// not all shells will send the prompt end code. prompt_start: struct { aid: ?[]const u8 = null, + kind: enum { primary, right, continuation } = .primary, redraw: bool = true, }, @@ -397,6 +398,21 @@ pub const Parser = struct { }, else => {}, } + } else if (mem.eql(u8, self.temp_state.key, "k")) { + // The "k" marks the kind of prompt, or "primary" if we don't know. + // This can be used to distinguish between the first prompt, + // a continuation, etc. + switch (self.command) { + .prompt_start => |*v| if (value.len == 1) { + v.kind = switch (value[0]) { + 'c', 's' => .continuation, + 'r' => .right, + 'i' => .primary, + else => .primary, + }; + }, + else => {}, + } } else log.info("unknown semantic prompts option: {s}", .{self.temp_state.key}); } @@ -508,6 +524,20 @@ test "OSC: prompt_start with redraw invalid value" { const cmd = p.end().?; try testing.expect(cmd == .prompt_start); try testing.expect(cmd.prompt_start.redraw); + try testing.expect(cmd.prompt_start.kind == .primary); +} + +test "OSC: prompt_start with continuation" { + const testing = std.testing; + + var p: Parser = .{}; + + const input = "133;A;k=c"; + for (input) |ch| p.next(ch); + + const cmd = p.end().?; + try testing.expect(cmd == .prompt_start); + try testing.expect(cmd.prompt_start.kind == .continuation); } test "OSC: end_of_command no exit code" { diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 57dc801b7..7504f4682 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -802,8 +802,9 @@ pub fn Stream(comptime Handler: type) type { }, .prompt_start => |v| { - if (@hasDecl(T, "promptStart")) { - try self.handler.promptStart(v.aid, v.redraw); + if (@hasDecl(T, "promptStart")) switch (v.kind) { + .primary, .right => try self.handler.promptStart(v.aid, v.redraw), + .continuation => try self.handler.promptContinuation(v.aid), } else log.warn("unimplemented OSC callback: {}", .{cmd}); }, diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 021e58fe8..175e7fa0d 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1623,6 +1623,11 @@ const StreamHandler = struct { self.terminal.flags.shell_redraws_prompt = redraw; } + pub fn promptContinuation(self: *StreamHandler, aid: ?[]const u8) !void { + _ = aid; + self.terminal.markSemanticPrompt(.prompt_continuation); + } + pub fn promptEnd(self: *StreamHandler) !void { self.terminal.markSemanticPrompt(.input); }