summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJason Rayne <yo@arcayne.dev>2025-07-05 13:34:47 -0700
committerJason Rayne <yo@arcayne.dev>2025-07-05 13:34:47 -0700
commit2d2df4b99fbc97ff7c58a3b24f5c4d358b7eddcf (patch)
treeb312809bc5d1cc2327d54854364c6c316e56eae9 /src
parenta727b59b2beda24ef7d79ce282a87ac5bcf84013 (diff)
parentf04a388f00199ae271a428606fd1f993c202ab64 (diff)
Merge branch 'main' into ssh-integration
Diffstat (limited to 'src')
-rw-r--r--src/Surface.zig22
-rw-r--r--src/renderer/cell.zig39
-rw-r--r--src/renderer/generic.zig4
-rw-r--r--src/shell-integration/bash/ghostty.bash6
4 files changed, 46 insertions, 25 deletions
diff --git a/src/Surface.zig b/src/Surface.zig
index dc7b0e3bf..6d0f1584b 100644
--- a/src/Surface.zig
+++ b/src/Surface.zig
@@ -1034,6 +1034,12 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
t.printString("Process exited. Press any key to close the terminal.") catch
break :terminal;
t.modes.set(.cursor_visible, false);
+
+ // We also want to ensure that normal keyboard encoding is on
+ // so that we can close the terminal. We close the terminal on
+ // any key press that encodes a character.
+ t.modes.set(.disable_keyboard, false);
+ t.screen.kitty_keyboard.set(.set, .{});
}
// Waiting after command we stop here. The terminal is updated, our
@@ -2129,14 +2135,6 @@ pub fn keyCallback(
if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed;
}
- // If our process is exited and we press a key then we close the
- // surface. We may want to eventually move this to the apprt rather
- // than in core.
- if (self.child_exited and event.action == .press) {
- self.close();
- return .closed;
- }
-
// If this input event has text, then we hide the mouse if configured.
// We only do this on pressed events to avoid hiding the mouse when we
// change focus due to a keybinding (i.e. switching tabs).
@@ -2231,6 +2229,14 @@ pub fn keyCallback(
event,
if (insp_ev) |*ev| ev else null,
)) |write_req| {
+ // If our process is exited and we press a key that results in
+ // an encoded value, we close the surface. We want to eventually
+ // move this behavior to the apprt probably.
+ if (self.child_exited) {
+ self.close();
+ return .closed;
+ }
+
errdefer write_req.deinit();
self.io.queueMessage(switch (write_req) {
.small => |v| .{ .write_small = v },
diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig
index ef7122699..97b24aa90 100644
--- a/src/renderer/cell.zig
+++ b/src/renderer/cell.zig
@@ -103,11 +103,12 @@ pub const Contents = struct {
// form a single grapheme, and multi-substitutions in fonts, the number
// of glyphs in a row is theoretically unlimited.
//
- // We have size.rows + 1 lists because index 0 is used for a special
- // list containing the cursor cell which needs to be first in the buffer.
+ // We have size.rows + 2 lists because indexes 0 and size.rows - 1 are
+ // used for special lists containing the cursor cell which need to
+ // be first and last in the buffer, respectively.
var fg_rows = try ArrayListCollection(shaderpkg.CellText).init(
alloc,
- size.rows + 1,
+ size.rows + 2,
size.columns * 3,
);
errdefer fg_rows.deinit(alloc);
@@ -118,14 +119,19 @@ pub const Contents = struct {
self.bg_cells = bg_cells;
self.fg_rows = fg_rows;
- // We don't need 3*cols worth of cells for the cursor list, so we can
- // replace it with a smaller list. This is technically a tiny bit of
+ // We don't need 3*cols worth of cells for the cursor lists, so we can
+ // replace them with smaller lists. This is technically a tiny bit of
// extra work but resize is not a hot function so it's worth it to not
// waste the memory.
self.fg_rows.lists[0].deinit(alloc);
self.fg_rows.lists[0] = try std.ArrayListUnmanaged(
shaderpkg.CellText,
).initCapacity(alloc, 1);
+
+ self.fg_rows.lists[size.rows + 1].deinit(alloc);
+ self.fg_rows.lists[size.rows + 1] = try std.ArrayListUnmanaged(
+ shaderpkg.CellText,
+ ).initCapacity(alloc, 1);
}
/// Reset the cell contents to an empty state without resizing.
@@ -135,11 +141,18 @@ pub const Contents = struct {
}
/// Set the cursor value. If the value is null then the cursor is hidden.
- pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText) void {
+ pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText, cursor_style: ?renderer.CursorStyle) void {
self.fg_rows.lists[0].clearRetainingCapacity();
+ self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity();
- if (v) |cell| {
- self.fg_rows.lists[0].appendAssumeCapacity(cell);
+ const cell = v orelse return;
+ const style = cursor_style orelse return;
+
+ switch (style) {
+ // Block cursors should be drawn first
+ .block => self.fg_rows.lists[0].appendAssumeCapacity(cell),
+ // Other cursor styles should be drawn last
+ .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell),
}
}
@@ -367,18 +380,22 @@ test Contents {
}
}
- // Add a cursor.
+ // Add a block cursor.
const cursor_cell: shaderpkg.CellText = .{
.mode = .cursor,
.grid_pos = .{ 2, 3 },
.color = .{ 0, 0, 0, 1 },
};
- c.setCursor(cursor_cell);
+ c.setCursor(cursor_cell, .block);
try testing.expectEqual(cursor_cell, c.fg_rows.lists[0].items[0]);
// And remove it.
- c.setCursor(null);
+ c.setCursor(null, null);
try testing.expectEqual(0, c.fg_rows.lists[0].items.len);
+
+ // Add a hollow cursor.
+ c.setCursor(cursor_cell, .block_hollow);
+ try testing.expectEqual(cursor_cell, c.fg_rows.lists[rows + 1].items[0]);
}
test "Contents clear retains other content" {
diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig
index e7faf633f..3a65b9ac5 100644
--- a/src/renderer/generic.zig
+++ b/src/renderer/generic.zig
@@ -2791,7 +2791,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
// Setup our cursor rendering information.
cursor: {
// By default, we don't handle cursor inversion on the shader.
- self.cells.setCursor(null);
+ self.cells.setCursor(null, null);
self.uniforms.cursor_pos = .{
std.math.maxInt(u16),
std.math.maxInt(u16),
@@ -3162,7 +3162,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
@intCast(render.glyph.offset_x),
@intCast(render.glyph.offset_y),
},
- });
+ }, cursor_style);
}
fn addPreeditCell(
diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash
index 6016e9096..f13a7d378 100644
--- a/src/shell-integration/bash/ghostty.bash
+++ b/src/shell-integration/bash/ghostty.bash
@@ -270,7 +270,6 @@ _ghostty_last_reported_cwd=""
function __ghostty_precmd() {
local ret="$?"
if test "$_ghostty_executing" != "0"; then
- _GHOSTTY_SAVE_PS0="$PS0"
_GHOSTTY_SAVE_PS1="$PS1"
_GHOSTTY_SAVE_PS2="$PS2"
@@ -287,8 +286,8 @@ function __ghostty_precmd() {
# Cursor
if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then
- PS1=$PS1'\[\e[5 q\]'
- PS0=$PS0'\[\e[0 q\]'
+ PS1=$PS1'\[\e[5 q\]' # blinking bar for input
+ builtin printf "\e[0 q" # reset to default cursor
fi
# Title (working directory)
@@ -318,7 +317,6 @@ function __ghostty_precmd() {
function __ghostty_preexec() {
builtin local cmd="$1"
- PS0="$_GHOSTTY_SAVE_PS0"
PS1="$_GHOSTTY_SAVE_PS1"
PS2="$_GHOSTTY_SAVE_PS2"