mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Merge pull request #955 from mitchellh/text_action
Text action: input any text you want
This commit is contained in:
@ -2298,7 +2298,11 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
// If you split it across two then the shell can interpret it
|
||||
// as two literals.
|
||||
var buf: [128]u8 = undefined;
|
||||
const full_data = try std.fmt.bufPrint(&buf, "\x1b{s}{s}", .{ if (action == .csi) "[" else "", data });
|
||||
const full_data = switch (action) {
|
||||
.csi => try std.fmt.bufPrint(&buf, "\x1b[{s}", .{data}),
|
||||
.esc => try std.fmt.bufPrint(&buf, "\x1b{s}", .{data}),
|
||||
else => unreachable,
|
||||
};
|
||||
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
||||
self.alloc,
|
||||
full_data,
|
||||
@ -2315,6 +2319,34 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
}
|
||||
},
|
||||
|
||||
.text => |data| {
|
||||
// For text we always allocate just because its easier to
|
||||
// handle all cases that way.
|
||||
var buf = try self.alloc.alloc(u8, data.len);
|
||||
defer self.alloc.free(buf);
|
||||
const text = configpkg.string.parse(buf, data) catch |err| {
|
||||
log.warn(
|
||||
"error parsing text binding text={s} err={}",
|
||||
.{ data, err },
|
||||
);
|
||||
return true;
|
||||
};
|
||||
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
||||
self.alloc,
|
||||
text,
|
||||
), .{ .forever = {} });
|
||||
try self.io_thread.wakeup.notify();
|
||||
|
||||
// Text triggers a scroll.
|
||||
{
|
||||
self.renderer_state.mutex.lock();
|
||||
defer self.renderer_state.mutex.unlock();
|
||||
self.scrollToBottom() catch |err| {
|
||||
log.warn("error scrolling to bottom err={}", .{err});
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
.cursor_key => |ck| {
|
||||
// We send a different sequence depending on if we're
|
||||
// in cursor keys mode. We're in "normal" mode if cursor
|
||||
|
@ -2,6 +2,7 @@ const builtin = @import("builtin");
|
||||
|
||||
pub usingnamespace @import("config/key.zig");
|
||||
pub const Config = @import("config/Config.zig");
|
||||
pub const string = @import("config/string.zig");
|
||||
|
||||
// Field types
|
||||
pub const CopyOnSelect = Config.CopyOnSelect;
|
||||
|
67
src/config/string.zig
Normal file
67
src/config/string.zig
Normal file
@ -0,0 +1,67 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Parse a string literal into a byte array. The string can contain
|
||||
/// any valid Zig string literal escape sequences.
|
||||
///
|
||||
/// The output buffer never needs sto be larger than the input buffer.
|
||||
/// The buffers may alias.
|
||||
pub fn parse(out: []u8, bytes: []const u8) ![]u8 {
|
||||
var dst_i: usize = 0;
|
||||
var src_i: usize = 0;
|
||||
while (src_i < bytes.len) {
|
||||
if (dst_i >= out.len) return error.OutOfMemory;
|
||||
|
||||
// If this byte is not beginning an escape sequence we copy.
|
||||
const b = bytes[src_i];
|
||||
if (b != '\\') {
|
||||
out[dst_i] = b;
|
||||
dst_i += 1;
|
||||
src_i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the escape sequence
|
||||
switch (std.zig.string_literal.parseEscapeSequence(
|
||||
bytes,
|
||||
&src_i,
|
||||
)) {
|
||||
.failure => return error.InvalidString,
|
||||
.success => |cp| dst_i += try std.unicode.utf8Encode(
|
||||
cp,
|
||||
out[dst_i..],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
return out[0..dst_i];
|
||||
}
|
||||
|
||||
test "parse: empty" {
|
||||
const testing = std.testing;
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
const result = try parse(&buf, "");
|
||||
try testing.expectEqualStrings("", result);
|
||||
}
|
||||
|
||||
test "parse: no escapes" {
|
||||
const testing = std.testing;
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
const result = try parse(&buf, "hello world");
|
||||
try testing.expectEqualStrings("hello world", result);
|
||||
}
|
||||
|
||||
test "parse: escapes" {
|
||||
const testing = std.testing;
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
{
|
||||
const result = try parse(&buf, "hello\\nworld");
|
||||
try testing.expectEqualStrings("hello\nworld", result);
|
||||
}
|
||||
{
|
||||
const result = try parse(&buf, "hello\\u{1F601}world");
|
||||
try testing.expectEqualStrings("hello\u{1F601}world", result);
|
||||
}
|
||||
}
|
@ -134,6 +134,12 @@ pub const Action = union(enum) {
|
||||
/// Send an ESC sequence.
|
||||
esc: []const u8,
|
||||
|
||||
// Send the given text. Uses Zig string literal syntax. This is
|
||||
// currently not validated. If the text is invalid (i.e. contains
|
||||
// an invalid escape sequence), the error will currently only show
|
||||
// up in logs.
|
||||
text: []const u8,
|
||||
|
||||
/// Send data to the pty depending on whether cursor key mode is
|
||||
/// enabled ("application") or disabled ("normal").
|
||||
cursor_key: CursorKey,
|
||||
|
Reference in New Issue
Block a user