summaryrefslogtreecommitdiff
path: root/src/Surface.zig
diff options
context:
space:
mode:
authorMitchell Hashimoto <m@mitchellh.com>2025-10-04 15:04:52 -0700
committerMitchell Hashimoto <m@mitchellh.com>2025-10-04 20:19:39 -0700
commit44496df8994640975720938fb150a67e7d111663 (patch)
tree40b3e2cd4f19d5fb37ddc3c1d1a2a36d8048bd09 /src/Surface.zig
parent503a25653f0608b946ddfa1742130c39c5ccf575 (diff)
input: use std.Io.Writer for key encoder, new API, expose via libghostty
This modernizes `KeyEncoder` to a new `std.Io.Writer`-based API. Additionally, instead of a single struct, it is now an `encode` function that takes a series of more focused options. This is more idiomatic Zig while also making it easier to expose via libghostty-vt. libghostty-vt also gains access to key encoding APIs.
Diffstat (limited to 'src/Surface.zig')
-rw-r--r--src/Surface.zig99
1 files changed, 52 insertions, 47 deletions
diff --git a/src/Surface.zig b/src/Surface.zig
index 5aabb2b80..b1553edff 100644
--- a/src/Surface.zig
+++ b/src/Surface.zig
@@ -271,7 +271,7 @@ const DerivedConfig = struct {
mouse_scroll_multiplier: configpkg.MouseScrollMultiplier,
mouse_shift_capture: configpkg.MouseShiftCapture,
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
- macos_option_as_alt: ?configpkg.OptionAsAlt,
+ macos_option_as_alt: ?input.OptionAsAlt,
selection_clear_on_copy: bool,
selection_clear_on_typing: bool,
vt_kam_allowed: bool,
@@ -1130,7 +1130,7 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
// 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, .{});
+ t.screen.kitty_keyboard.set(.set, .disabled);
}
// Waiting after command we stop here. The terminal is updated, our
@@ -2611,56 +2611,32 @@ fn encodeKey(
event: input.KeyEvent,
insp_ev: ?*inspectorpkg.key.Event,
) !?termio.Message.WriteReq {
- // Build up our encoder. Under different modes and
- // inputs there are many keybindings that result in no encoding
- // whatsoever.
- const enc: input.KeyEncoder = enc: {
- const option_as_alt: configpkg.OptionAsAlt = self.config.macos_option_as_alt orelse detect: {
- // Non-macOS doesn't use this value so ignore.
- if (comptime builtin.os.tag != .macos) break :detect .false;
-
- // If we don't have alt pressed, it doesn't matter what this
- // config is so we can just say "false" and break out and avoid
- // more expensive checks below.
- if (!event.mods.alt) break :detect .false;
-
- // Alt is pressed, we're on macOS. We break some encapsulation
- // here and assume libghostty for ease...
- break :detect self.rt_app.keyboardLayout().detectOptionAsAlt();
- };
-
- self.renderer_state.mutex.lock();
- defer self.renderer_state.mutex.unlock();
- const t = &self.io.terminal;
- break :enc .{
- .event = event,
- .macos_option_as_alt = option_as_alt,
- .alt_esc_prefix = t.modes.get(.alt_esc_prefix),
- .cursor_key_application = t.modes.get(.cursor_keys),
- .keypad_key_application = t.modes.get(.keypad_keys),
- .ignore_keypad_with_numlock = t.modes.get(.ignore_keypad_with_numlock),
- .modify_other_keys_state_2 = t.flags.modify_other_keys_2,
- .kitty_flags = t.screen.kitty_keyboard.current(),
- };
- };
-
const write_req: termio.Message.WriteReq = req: {
+ // Build our encoding options, which requires the lock.
+ const encoding_opts = self.encodeKeyOpts();
+
// Try to write the input into a small array. This fits almost
// every scenario. Larger situations can happen due to long
// pre-edits.
var data: termio.Message.WriteReq.Small.Array = undefined;
- if (enc.encode(&data)) |seq| {
+ var writer: std.Io.Writer = .fixed(&data);
+ if (input.key_encode.encode(
+ &writer,
+ event,
+ encoding_opts,
+ )) {
+ const written = writer.buffered();
+
// Special-case: we did nothing.
- if (seq.len == 0) return null;
+ if (written.len == 0) return null;
break :req .{ .small = .{
.data = data,
- .len = @intCast(seq.len),
+ .len = @intCast(written.len),
} };
} else |err| switch (err) {
// Means we need to allocate
- error.OutOfMemory => {},
- else => return err,
+ error.WriteFailed => {},
}
// We need to allocate. We allocate double the UTF-8 length
@@ -2669,16 +2645,23 @@ fn encodeKey(
// typing this where we don't have enough space is a long preedit,
// and in that case the size we need is exactly the UTF-8 length,
// so the double is being safe.
- const buf = try self.alloc.alloc(u8, @max(
- event.utf8.len * 2,
- data.len * 2,
- ));
- defer self.alloc.free(buf);
+ var alloc_writer: std.Io.Writer.Allocating = try .initCapacity(
+ self.alloc,
+ @max(event.utf8.len * 2, data.len * 2),
+ );
+ defer alloc_writer.deinit();
// This results in a double allocation but this is such an unlikely
// path the performance impact is unimportant.
- const seq = try enc.encode(buf);
- break :req try termio.Message.WriteReq.init(self.alloc, seq);
+ try input.key_encode.encode(
+ &alloc_writer.writer,
+ event,
+ encoding_opts,
+ );
+ break :req try termio.Message.WriteReq.init(
+ self.alloc,
+ alloc_writer.writer.buffered(),
+ );
};
// Copy the encoded data into the inspector event if we have one.
@@ -2698,6 +2681,28 @@ fn encodeKey(
return write_req;
}
+fn encodeKeyOpts(self: *const Surface) input.key_encode.Options {
+ self.renderer_state.mutex.lock();
+ defer self.renderer_state.mutex.unlock();
+ const t = &self.io.terminal;
+
+ var opts: input.key_encode.Options = .fromTerminal(t);
+ if (comptime builtin.os.tag != .macos) return opts;
+
+ opts.macos_option_as_alt = self.config.macos_option_as_alt orelse detect: {
+ // If we don't have alt pressed, it doesn't matter what this
+ // config is so we can just say "false" and break out and avoid
+ // more expensive checks below.
+ if (!self.mouse.mods.alt) break :detect .false;
+
+ // Alt is pressed, we're on macOS. We break some encapsulation
+ // here and assume libghostty for ease...
+ break :detect self.rt_app.keyboardLayout().detectOptionAsAlt();
+ };
+
+ return opts;
+}
+
/// Sends text as-is to the terminal without triggering any keyboard
/// protocol. This will treat the input text as if it was pasted
/// from the clipboard so the same logic will be applied. Namely,