diff --git a/src/Surface.zig b/src/Surface.zig index e002d374e..9d7c58b75 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1081,6 +1081,27 @@ pub fn charCallback( } } + // Let's see if we should apply fixterms to this codepoint. + // At this stage of key processing, we only need to apply fixterms + // to unicode codepoints (the point of charCallback) if we have + // ctrl set. + if (mods.ctrl) { + const csi_u_mods = terminal.csi_u.Mods.fromInput(mods); + const resp = try std.fmt.bufPrint( + &data, + "\x1B[{};{}u", + .{ codepoint, csi_u_mods.seqInt() }, + ); + _ = self.io_thread.mailbox.push(.{ + .write_small = .{ + .data = data, + .len = @intCast(resp.len), + }, + }, .{ .forever = {} }); + try self.io_thread.wakeup.notify(); + return; + } + // Prefix our data with ESC if we have alt pressed. var i: u8 = 0; if (mods.alt) alt: { diff --git a/src/terminal/csi_u.zig b/src/terminal/csi_u.zig new file mode 100644 index 000000000..93b1d1e7d --- /dev/null +++ b/src/terminal/csi_u.zig @@ -0,0 +1,64 @@ +//! This file has information related to Paul Evans's "fixterms" +//! encoding, also sometimes referred to as "CSI u" encoding. +//! +//! https://www.leonerd.org.uk/hacks/fixterms/ + +const std = @import("std"); + +const input = @import("../input.zig"); + +pub const Mods = packed struct(u3) { + shift: bool = false, + alt: bool = false, + ctrl: bool = false, + + /// Convert an input mods value into the CSI u mods value. + pub fn fromInput(mods: input.Mods) Mods { + return .{ + .shift = mods.shift, + .alt = mods.alt, + .ctrl = mods.ctrl, + }; + } + + /// Returns the raw int value of this packed struct. + pub fn int(self: Mods) u3 { + return @bitCast(self); + } + + /// Returns the integer value sent as part of the CSI u sequence. + /// This adds 1 to the bitmask value as described in the spec. + pub fn seqInt(self: Mods) u4 { + const raw: u4 = @intCast(self.int()); + return raw + 1; + } +}; + +test "modifer sequence values" { + // This is all sort of trivially seen by looking at the code but + // we want to make sure we never regress this. + const testing = std.testing; + var mods: Mods = .{}; + try testing.expectEqual(@as(u4, 1), mods.seqInt()); + + mods = .{ .shift = true }; + try testing.expectEqual(@as(u4, 2), mods.seqInt()); + + mods = .{ .alt = true }; + try testing.expectEqual(@as(u4, 3), mods.seqInt()); + + mods = .{ .ctrl = true }; + try testing.expectEqual(@as(u4, 5), mods.seqInt()); + + mods = .{ .alt = true, .shift = true }; + try testing.expectEqual(@as(u4, 4), mods.seqInt()); + + mods = .{ .ctrl = true, .shift = true }; + try testing.expectEqual(@as(u4, 6), mods.seqInt()); + + mods = .{ .alt = true, .ctrl = true }; + try testing.expectEqual(@as(u4, 7), mods.seqInt()); + + mods = .{ .alt = true, .ctrl = true, .shift = true }; + try testing.expectEqual(@as(u4, 8), mods.seqInt()); +} diff --git a/src/terminal/main.zig b/src/terminal/main.zig index 3de537656..f08c97326 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -7,6 +7,7 @@ const csi = @import("csi.zig"); const sgr = @import("sgr.zig"); pub const point = @import("point.zig"); pub const color = @import("color.zig"); +pub const csi_u = @import("csi_u.zig"); pub const modes = @import("modes.zig"); pub const parse_table = @import("parse_table.zig");