|
|
|
|
@@ -145,6 +145,12 @@ focused: bool = true,
|
|
|
|
|
/// Used to determine whether to continuously scroll.
|
|
|
|
|
selection_scroll_active: bool = false,
|
|
|
|
|
|
|
|
|
|
/// True if the surface is in read-only mode. When read-only, no input
|
|
|
|
|
/// is sent to the PTY but terminal-level operations like selections,
|
|
|
|
|
/// (native) scrolling, and copy keybinds still work. Warn before quit is
|
|
|
|
|
/// always enabled in this state.
|
|
|
|
|
readonly: bool = false,
|
|
|
|
|
|
|
|
|
|
/// Used to send notifications that long running commands have finished.
|
|
|
|
|
/// Requires that shell integration be active. Should represent a nanosecond
|
|
|
|
|
/// precision timestamp. It does not necessarily need to correspond to the
|
|
|
|
|
@@ -812,6 +818,30 @@ inline fn surfaceMailbox(self: *Surface) Mailbox {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Queue a message for the IO thread.
|
|
|
|
|
///
|
|
|
|
|
/// We centralize all our logic into this spot so we can intercept
|
|
|
|
|
/// messages for example in readonly mode.
|
|
|
|
|
fn queueIo(
|
|
|
|
|
self: *Surface,
|
|
|
|
|
msg: termio.Message,
|
|
|
|
|
mutex: termio.Termio.MutexState,
|
|
|
|
|
) void {
|
|
|
|
|
// In readonly mode, we don't allow any writes through to the pty.
|
|
|
|
|
if (self.readonly) {
|
|
|
|
|
switch (msg) {
|
|
|
|
|
.write_small,
|
|
|
|
|
.write_stable,
|
|
|
|
|
.write_alloc,
|
|
|
|
|
=> return,
|
|
|
|
|
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.io.queueMessage(msg, mutex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Forces the surface to render. This is useful for when the surface
|
|
|
|
|
/// is in the middle of animation (such as a resize, etc.) or when
|
|
|
|
|
/// the render timer is managed manually by the apprt.
|
|
|
|
|
@@ -843,7 +873,7 @@ pub fn activateInspector(self: *Surface) !void {
|
|
|
|
|
|
|
|
|
|
// Notify our components we have an inspector active
|
|
|
|
|
_ = self.renderer_thread.mailbox.push(.{ .inspector = true }, .{ .forever = {} });
|
|
|
|
|
self.io.queueMessage(.{ .inspector = true }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .inspector = true }, .unlocked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Deactivate the inspector and stop collecting any information.
|
|
|
|
|
@@ -860,7 +890,7 @@ pub fn deactivateInspector(self: *Surface) void {
|
|
|
|
|
|
|
|
|
|
// Notify our components we have deactivated inspector
|
|
|
|
|
_ = self.renderer_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} });
|
|
|
|
|
self.io.queueMessage(.{ .inspector = false }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .inspector = false }, .unlocked);
|
|
|
|
|
|
|
|
|
|
// Deinit the inspector
|
|
|
|
|
insp.deinit();
|
|
|
|
|
@@ -871,6 +901,9 @@ pub fn deactivateInspector(self: *Surface) void {
|
|
|
|
|
/// True if the surface requires confirmation to quit. This should be called
|
|
|
|
|
/// by apprt to determine if the surface should confirm before quitting.
|
|
|
|
|
pub fn needsConfirmQuit(self: *Surface) bool {
|
|
|
|
|
// If the surface is in read-only mode, always require confirmation
|
|
|
|
|
if (self.readonly) return true;
|
|
|
|
|
|
|
|
|
|
// If the child has exited, then our process is certainly not alive.
|
|
|
|
|
// We check this first to avoid the locking overhead below.
|
|
|
|
|
if (self.child_exited) return false;
|
|
|
|
|
@@ -929,7 +962,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|
|
|
|
// We always use an allocating message because we don't know
|
|
|
|
|
// the length of the title and this isn't a performance critical
|
|
|
|
|
// path.
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.write_alloc = .{
|
|
|
|
|
.alloc = self.alloc,
|
|
|
|
|
.data = data,
|
|
|
|
|
@@ -1121,7 +1154,7 @@ fn selectionScrollTick(self: *Surface) !void {
|
|
|
|
|
// If our screen changed while this is happening, we stop our
|
|
|
|
|
// selection scroll.
|
|
|
|
|
if (self.mouse.left_click_screen != t.screens.active_key) {
|
|
|
|
|
self.io.queueMessage(
|
|
|
|
|
self.queueIo(
|
|
|
|
|
.{ .selection_scroll = false },
|
|
|
|
|
.locked,
|
|
|
|
|
);
|
|
|
|
|
@@ -1353,7 +1386,7 @@ fn reportColorScheme(self: *Surface, force: bool) void {
|
|
|
|
|
.dark => "\x1B[?997;1n",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = output }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .write_stable = output }, .unlocked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn searchCallback(event: terminal.search.Thread.Event, ud: ?*anyopaque) void {
|
|
|
|
|
@@ -1726,7 +1759,7 @@ pub fn updateConfig(
|
|
|
|
|
errdefer termio_config_ptr.deinit();
|
|
|
|
|
|
|
|
|
|
_ = self.renderer_thread.mailbox.push(renderer_message, .{ .forever = {} });
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.change_config = .{
|
|
|
|
|
.alloc = self.alloc,
|
|
|
|
|
.ptr = termio_config_ptr,
|
|
|
|
|
@@ -2292,7 +2325,7 @@ fn setCellSize(self: *Surface, size: rendererpkg.CellSize) !void {
|
|
|
|
|
self.balancePaddingIfNeeded();
|
|
|
|
|
|
|
|
|
|
// Notify the terminal
|
|
|
|
|
self.io.queueMessage(.{ .resize = self.size }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .resize = self.size }, .unlocked);
|
|
|
|
|
|
|
|
|
|
// Update our terminal default size if necessary.
|
|
|
|
|
self.recomputeInitialSize() catch |err| {
|
|
|
|
|
@@ -2395,7 +2428,7 @@ fn resize(self: *Surface, size: rendererpkg.ScreenSize) !void {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mail the IO thread
|
|
|
|
|
self.io.queueMessage(.{ .resize = self.size }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .resize = self.size }, .unlocked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Recalculate the balanced padding if needed.
|
|
|
|
|
@@ -2671,7 +2704,7 @@ pub fn keyCallback(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errdefer write_req.deinit();
|
|
|
|
|
self.io.queueMessage(switch (write_req) {
|
|
|
|
|
self.queueIo(switch (write_req) {
|
|
|
|
|
.small => |v| .{ .write_small = v },
|
|
|
|
|
.stable => |v| .{ .write_stable = v },
|
|
|
|
|
.alloc => |v| .{ .write_alloc = v },
|
|
|
|
|
@@ -2900,7 +2933,7 @@ fn endKeySequence(
|
|
|
|
|
if (self.keyboard.queued.items.len > 0) {
|
|
|
|
|
switch (action) {
|
|
|
|
|
.flush => for (self.keyboard.queued.items) |write_req| {
|
|
|
|
|
self.io.queueMessage(switch (write_req) {
|
|
|
|
|
self.queueIo(switch (write_req) {
|
|
|
|
|
.small => |v| .{ .write_small = v },
|
|
|
|
|
.stable => |v| .{ .write_stable = v },
|
|
|
|
|
.alloc => |v| .{ .write_alloc = v },
|
|
|
|
|
@@ -3126,7 +3159,7 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
|
|
|
|
|
self.renderer_state.mutex.lock();
|
|
|
|
|
self.io.terminal.flags.focused = focused;
|
|
|
|
|
self.renderer_state.mutex.unlock();
|
|
|
|
|
self.io.queueMessage(.{ .focused = focused }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .focused = focused }, .unlocked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -3290,7 +3323,7 @@ pub fn scrollCallback(
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
for (0..y.magnitude()) |_| {
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = seq }, .locked);
|
|
|
|
|
self.queueIo(.{ .write_stable = seq }, .locked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -3511,7 +3544,7 @@ fn mouseReport(
|
|
|
|
|
data[5] = 32 + @as(u8, @intCast(viewport_point.y)) + 1;
|
|
|
|
|
|
|
|
|
|
// Ask our IO thread to write the data
|
|
|
|
|
self.io.queueMessage(.{ .write_small = .{
|
|
|
|
|
self.queueIo(.{ .write_small = .{
|
|
|
|
|
.data = data,
|
|
|
|
|
.len = 6,
|
|
|
|
|
} }, .locked);
|
|
|
|
|
@@ -3534,7 +3567,7 @@ fn mouseReport(
|
|
|
|
|
i += try std.unicode.utf8Encode(@intCast(32 + viewport_point.y + 1), data[i..]);
|
|
|
|
|
|
|
|
|
|
// Ask our IO thread to write the data
|
|
|
|
|
self.io.queueMessage(.{ .write_small = .{
|
|
|
|
|
self.queueIo(.{ .write_small = .{
|
|
|
|
|
.data = data,
|
|
|
|
|
.len = @intCast(i),
|
|
|
|
|
} }, .locked);
|
|
|
|
|
@@ -3555,7 +3588,7 @@ fn mouseReport(
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Ask our IO thread to write the data
|
|
|
|
|
self.io.queueMessage(.{ .write_small = .{
|
|
|
|
|
self.queueIo(.{ .write_small = .{
|
|
|
|
|
.data = data,
|
|
|
|
|
.len = @intCast(resp.len),
|
|
|
|
|
} }, .locked);
|
|
|
|
|
@@ -3572,7 +3605,7 @@ fn mouseReport(
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Ask our IO thread to write the data
|
|
|
|
|
self.io.queueMessage(.{ .write_small = .{
|
|
|
|
|
self.queueIo(.{ .write_small = .{
|
|
|
|
|
.data = data,
|
|
|
|
|
.len = @intCast(resp.len),
|
|
|
|
|
} }, .locked);
|
|
|
|
|
@@ -3601,7 +3634,7 @@ fn mouseReport(
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Ask our IO thread to write the data
|
|
|
|
|
self.io.queueMessage(.{ .write_small = .{
|
|
|
|
|
self.queueIo(.{ .write_small = .{
|
|
|
|
|
.data = data,
|
|
|
|
|
.len = @intCast(resp.len),
|
|
|
|
|
} }, .locked);
|
|
|
|
|
@@ -3753,7 +3786,7 @@ pub fn mouseButtonCallback(
|
|
|
|
|
// Stop selection scrolling when releasing the left mouse button
|
|
|
|
|
// but only when selection scrolling is active.
|
|
|
|
|
if (self.selection_scroll_active) {
|
|
|
|
|
self.io.queueMessage(
|
|
|
|
|
self.queueIo(
|
|
|
|
|
.{ .selection_scroll = false },
|
|
|
|
|
.unlocked,
|
|
|
|
|
);
|
|
|
|
|
@@ -4110,7 +4143,7 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void {
|
|
|
|
|
break :arrow if (t.modes.get(.cursor_keys)) "\x1bOB" else "\x1b[B";
|
|
|
|
|
};
|
|
|
|
|
for (0..@abs(path.y)) |_| {
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = arrow }, .locked);
|
|
|
|
|
self.queueIo(.{ .write_stable = arrow }, .locked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (path.x != 0) {
|
|
|
|
|
@@ -4120,7 +4153,7 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void {
|
|
|
|
|
break :arrow if (t.modes.get(.cursor_keys)) "\x1bOC" else "\x1b[C";
|
|
|
|
|
};
|
|
|
|
|
for (0..@abs(path.x)) |_| {
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = arrow }, .locked);
|
|
|
|
|
self.queueIo(.{ .write_stable = arrow }, .locked);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -4393,7 +4426,7 @@ pub fn cursorPosCallback(
|
|
|
|
|
// Stop selection scrolling when inside the viewport within a 1px buffer
|
|
|
|
|
// for fullscreen windows, but only when selection scrolling is active.
|
|
|
|
|
if (pos.y >= 1 and self.selection_scroll_active) {
|
|
|
|
|
self.io.queueMessage(
|
|
|
|
|
self.queueIo(
|
|
|
|
|
.{ .selection_scroll = false },
|
|
|
|
|
.locked,
|
|
|
|
|
);
|
|
|
|
|
@@ -4493,7 +4526,7 @@ pub fn cursorPosCallback(
|
|
|
|
|
if ((pos.y <= 1 or pos.y > max_y - 1) and
|
|
|
|
|
!self.selection_scroll_active)
|
|
|
|
|
{
|
|
|
|
|
self.io.queueMessage(
|
|
|
|
|
self.queueIo(
|
|
|
|
|
.{ .selection_scroll = true },
|
|
|
|
|
.locked,
|
|
|
|
|
);
|
|
|
|
|
@@ -4869,7 +4902,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
.esc => try std.fmt.bufPrint(&buf, "\x1b{s}", .{data}),
|
|
|
|
|
else => unreachable,
|
|
|
|
|
};
|
|
|
|
|
self.io.queueMessage(try termio.Message.writeReq(
|
|
|
|
|
self.queueIo(try termio.Message.writeReq(
|
|
|
|
|
self.alloc,
|
|
|
|
|
full_data,
|
|
|
|
|
), .unlocked);
|
|
|
|
|
@@ -4896,7 +4929,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
self.io.queueMessage(try termio.Message.writeReq(
|
|
|
|
|
self.queueIo(try termio.Message.writeReq(
|
|
|
|
|
self.alloc,
|
|
|
|
|
text,
|
|
|
|
|
), .unlocked);
|
|
|
|
|
@@ -4929,9 +4962,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (normal) {
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = ck.normal }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .write_stable = ck.normal }, .unlocked);
|
|
|
|
|
} else {
|
|
|
|
|
self.io.queueMessage(.{ .write_stable = ck.application }, .unlocked);
|
|
|
|
|
self.queueIo(.{ .write_stable = ck.application }, .unlocked);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
@@ -5204,19 +5237,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
if (self.io.terminal.screens.active_key == .alternate) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.clear_screen = .{ .history = true },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.scroll_to_top => {
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .top = {} },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.scroll_to_bottom => {
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .bottom = {} },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
@@ -5246,14 +5279,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
|
|
|
|
|
.scroll_page_up => {
|
|
|
|
|
const rows: isize = @intCast(self.size.grid().rows);
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .delta = -1 * rows },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.scroll_page_down => {
|
|
|
|
|
const rows: isize = @intCast(self.size.grid().rows);
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .delta = rows },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
@@ -5261,19 +5294,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
.scroll_page_fractional => |fraction| {
|
|
|
|
|
const rows: f32 = @floatFromInt(self.size.grid().rows);
|
|
|
|
|
const delta: isize = @intFromFloat(@trunc(fraction * rows));
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .delta = delta },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.scroll_page_lines => |lines| {
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.scroll_viewport = .{ .delta = lines },
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.jump_to_prompt => |delta| {
|
|
|
|
|
self.io.queueMessage(.{
|
|
|
|
|
self.queueIo(.{
|
|
|
|
|
.jump_to_prompt = @intCast(delta),
|
|
|
|
|
}, .unlocked);
|
|
|
|
|
},
|
|
|
|
|
@@ -5383,6 +5416,16 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
{},
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
.toggle_readonly => {
|
|
|
|
|
self.readonly = !self.readonly;
|
|
|
|
|
_ = try self.rt_app.performAction(
|
|
|
|
|
.{ .surface = self },
|
|
|
|
|
.readonly,
|
|
|
|
|
if (self.readonly) .on else .off,
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.reset_window_size => return try self.rt_app.performAction(
|
|
|
|
|
.{ .surface = self },
|
|
|
|
|
.reset_window_size,
|
|
|
|
|
@@ -5488,7 +5531,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.io => self.io.queueMessage(.{ .crash = {} }, .unlocked),
|
|
|
|
|
.io => self.queueIo(.{ .crash = {} }, .unlocked),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.adjust_selection => |direction| {
|
|
|
|
|
@@ -5686,7 +5729,7 @@ fn writeScreenFile(
|
|
|
|
|
},
|
|
|
|
|
.url = path,
|
|
|
|
|
}),
|
|
|
|
|
.paste => self.io.queueMessage(try termio.Message.writeReq(
|
|
|
|
|
.paste => self.queueIo(try termio.Message.writeReq(
|
|
|
|
|
self.alloc,
|
|
|
|
|
path,
|
|
|
|
|
), .unlocked),
|
|
|
|
|
@@ -5826,7 +5869,7 @@ fn completeClipboardPaste(
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (vecs) |vec| if (vec.len > 0) {
|
|
|
|
|
self.io.queueMessage(try termio.Message.writeReq(
|
|
|
|
|
self.queueIo(try termio.Message.writeReq(
|
|
|
|
|
self.alloc,
|
|
|
|
|
vec,
|
|
|
|
|
), .unlocked);
|
|
|
|
|
@@ -5872,7 +5915,7 @@ fn completeClipboardReadOSC52(
|
|
|
|
|
const encoded = enc.encode(buf[prefix.len..], data);
|
|
|
|
|
assert(encoded.len == size);
|
|
|
|
|
|
|
|
|
|
self.io.queueMessage(try termio.Message.writeReq(
|
|
|
|
|
self.queueIo(try termio.Message.writeReq(
|
|
|
|
|
self.alloc,
|
|
|
|
|
buf,
|
|
|
|
|
), .unlocked);
|
|
|
|
|
|