terminal: jump to prompt

This commit is contained in:
Mitchell Hashimoto
2024-03-12 11:11:57 -07:00
parent 1c4fb96e49
commit 0a6735d05d
3 changed files with 123 additions and 14 deletions

View File

@ -1408,6 +1408,11 @@ pub const Scroll = union(enum) {
/// Scroll up (negative) or down (positive) by the given number of
/// rows. This is clamped to the "top" and "active" top left.
delta_row: isize,
/// Jump forwards (positive) or backwards (negative) a set number of
/// prompts. If the absolute value is greater than the number of prompts
/// in either direction, jump to the furthest prompt in that direction.
delta_prompt: isize,
};
/// Scroll the viewport. This will never create new scrollback, allocate
@ -1417,6 +1422,7 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
switch (behavior) {
.active => self.viewport = .{ .active = {} },
.top => self.viewport = .{ .top = {} },
.delta_prompt => |n| self.scrollPrompt(n),
.delta_row => |n| {
if (n == 0) return;
@ -1448,6 +1454,45 @@ pub fn scroll(self: *PageList, behavior: Scroll) void {
}
}
/// Jump the viewport forwards (positive) or backwards (negative) a set number of
/// prompts (delta).
fn scrollPrompt(self: *PageList, delta: isize) void {
// If we aren't jumping any prompts then we don't need to do anything.
if (delta == 0) return;
const delta_start: usize = @intCast(if (delta > 0) delta else -delta);
var delta_rem: usize = delta_start;
// Iterate and count the number of prompts we see.
const viewport_pin = self.getTopLeft(.viewport);
var it = viewport_pin.rowIterator(if (delta > 0) .right_down else .left_up, null);
_ = it.next(); // skip our own row
var prompt_pin: ?Pin = null;
while (it.next()) |next| {
const row = next.rowAndCell().row;
switch (row.semantic_prompt) {
.command, .unknown => {},
.prompt, .prompt_continuation, .input => {
delta_rem -= 1;
prompt_pin = next;
},
}
if (delta_rem == 0) break;
}
// If we found a prompt, we move to it. If the prompt is in the active
// area we keep our viewport as active because we can't scroll DOWN
// into the active area. Otherwise, we scroll up to the pin.
if (prompt_pin) |p| {
if (self.pinIsActive(p)) {
self.viewport = .{ .active = {} };
} else {
self.viewport_pin.* = p;
self.viewport = .{ .pin = {} };
}
}
}
/// Clear the screen by scrolling written contents up into the scrollback.
/// This will not update the viewport.
pub fn scrollClear(self: *PageList) !void {
@ -3062,6 +3107,75 @@ test "PageList scroll clear" {
}
}
test "PageList: jump zero" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 3, null);
defer s.deinit();
try s.growRows(3);
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
{
const rac = page.getRowAndCell(0, 1);
rac.row.semantic_prompt = .prompt;
}
{
const rac = page.getRowAndCell(0, 5);
rac.row.semantic_prompt = .prompt;
}
s.scroll(.{ .delta_prompt = 0 });
try testing.expect(s.viewport == .active);
}
test "Screen: jump to prompt" {
const testing = std.testing;
const alloc = testing.allocator;
var s = try init(alloc, 5, 3, null);
defer s.deinit();
try s.growRows(3);
try testing.expect(s.pages.first == s.pages.last);
const page = &s.pages.first.?.data;
{
const rac = page.getRowAndCell(0, 1);
rac.row.semantic_prompt = .prompt;
}
{
const rac = page.getRowAndCell(0, 5);
rac.row.semantic_prompt = .prompt;
}
// Jump back
{
s.scroll(.{ .delta_prompt = -1 });
try testing.expect(s.viewport == .pin);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 1,
} }, s.pointFromPin(.screen, s.pin(.{ .viewport = .{} }).?).?);
}
{
s.scroll(.{ .delta_prompt = -1 });
try testing.expect(s.viewport == .pin);
try testing.expectEqual(point.Point{ .screen = .{
.x = 0,
.y = 1,
} }, s.pointFromPin(.screen, s.pin(.{ .viewport = .{} }).?).?);
}
// Jump forward
{
s.scroll(.{ .delta_prompt = 1 });
try testing.expect(s.viewport == .active);
}
{
s.scroll(.{ .delta_prompt = 1 });
try testing.expect(s.viewport == .active);
}
}
test "PageList grow fit in capacity" {
const testing = std.testing;
const alloc = testing.allocator;

View File

@ -532,6 +532,7 @@ pub const Scroll = union(enum) {
active,
top,
delta_row: isize,
delta_prompt: isize,
};
/// Scroll the viewport of the terminal grid.
@ -545,6 +546,7 @@ pub fn scroll(self: *Screen, behavior: Scroll) void {
.active => self.pages.scroll(.{ .active = {} }),
.top => self.pages.scroll(.{ .top = {} }),
.delta_row => |v| self.pages.scroll(.{ .delta_row = v }),
.delta_prompt => |v| self.pages.scroll(.{ .delta_prompt = v }),
}
}

View File

@ -508,20 +508,13 @@ pub fn scrollViewport(self: *Exec, scroll: terminal.Terminal.ScrollViewport) !vo
/// Jump the viewport to the prompt.
pub fn jumpToPrompt(self: *Exec, delta: isize) !void {
_ = self;
_ = delta;
// TODO(paged-terminal)
// const wakeup: bool = wakeup: {
// self.renderer_state.mutex.lock();
// defer self.renderer_state.mutex.unlock();
// break :wakeup self.terminal.screen.jump(.{
// .prompt_delta = delta,
// });
// };
//
// if (wakeup) {
// try self.renderer_wakeup.notify();
// }
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.terminal.screen.scroll(.{ .delta_prompt = delta });
}
try self.renderer_wakeup.notify();
}
/// Called when the child process exited abnormally but before