mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 04:36:10 +03:00
Merge pull request #226 from mitchellh/copy-paste
macos: enable copy/paste menu items in "Edit"
This commit is contained in:
@ -221,6 +221,11 @@ typedef enum {
|
|||||||
GHOSTTY_KEY_RIGHT_SUPER,
|
GHOSTTY_KEY_RIGHT_SUPER,
|
||||||
} ghostty_input_key_e;
|
} ghostty_input_key_e;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_BINDING_COPY_TO_CLIPBOARD,
|
||||||
|
GHOSTTY_BINDING_PASTE_FROM_CLIPBOARD,
|
||||||
|
} ghostty_binding_action_e;
|
||||||
|
|
||||||
// Fully defined types. This MUST be kept in sync with equivalent Zig
|
// Fully defined types. This MUST be kept in sync with equivalent Zig
|
||||||
// structs. To find the Zig struct, grep for this type name. The documentation
|
// structs. To find the Zig struct, grep for this type name. The documentation
|
||||||
// for all of these types is available in the Zig source.
|
// for all of these types is available in the Zig source.
|
||||||
@ -290,6 +295,7 @@ void ghostty_surface_ime_point(ghostty_surface_t, double *, double *);
|
|||||||
void ghostty_surface_request_close(ghostty_surface_t);
|
void ghostty_surface_request_close(ghostty_surface_t);
|
||||||
void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e);
|
void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e);
|
||||||
void ghostty_surface_split_focus(ghostty_surface_t, ghostty_split_focus_direction_e);
|
void ghostty_surface_split_focus(ghostty_surface_t, ghostty_split_focus_direction_e);
|
||||||
|
void ghostty_surface_binding_action(ghostty_surface_t, ghostty_binding_action_e, void *);
|
||||||
|
|
||||||
// APIs I'd like to get rid of eventually but are still needed for now.
|
// APIs I'd like to get rid of eventually but are still needed for now.
|
||||||
// Don't use these unless you know what you're doing.
|
// Don't use these unless you know what you're doing.
|
||||||
|
@ -381,6 +381,23 @@ extension Ghostty {
|
|||||||
ghostty_surface_key(surface, action, key, unmapped_key, mods)
|
ghostty_surface_key(surface, action, key, unmapped_key, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Menu Handlers
|
||||||
|
|
||||||
|
@IBAction func copy(_ sender: Any?) {
|
||||||
|
guard let surface = self.surface else { return }
|
||||||
|
ghostty_surface_binding_action(surface, GHOSTTY_BINDING_COPY_TO_CLIPBOARD, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func paste(_ sender: Any?) {
|
||||||
|
guard let surface = self.surface else { return }
|
||||||
|
ghostty_surface_binding_action(surface, GHOSTTY_BINDING_PASTE_FROM_CLIPBOARD, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func pasteAsPlainText(_ sender: Any?) {
|
||||||
|
guard let surface = self.surface else { return }
|
||||||
|
ghostty_surface_binding_action(surface, GHOSTTY_BINDING_PASTE_FROM_CLIPBOARD, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: NSTextInputClient
|
// MARK: NSTextInputClient
|
||||||
|
|
||||||
func hasMarkedText() -> Bool {
|
func hasMarkedText() -> Bool {
|
||||||
|
@ -94,9 +94,29 @@
|
|||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Edit" id="l1C-ez-1tg">
|
<menuItem title="Edit" id="ZUG-Nx-Wkj">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Edit" id="yo0-cI-6cQ"/>
|
<menu key="submenu" title="Edit" id="iU4-OB-ccf">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Copy" keyEquivalent="c" id="Jqf-pv-Zcu">
|
||||||
|
<connections>
|
||||||
|
<action selector="copy:" target="-1" id="B4F-hg-R4T"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste" keyEquivalent="v" id="i27-pK-umN">
|
||||||
|
<connections>
|
||||||
|
<action selector="paste:" target="-1" id="ZKe-2B-mel"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Paste and Match Style" keyEquivalent="V" id="FFo-bM-GXj">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="pasteAsPlainText:" target="-1" id="Sfp-aT-ZgM"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem isSeparatorItem="YES" id="VYS-RG-uZD"/>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Window" id="aUF-d1-5bR">
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
545
src/Surface.zig
545
src/Surface.zig
@ -970,276 +970,7 @@ pub fn keyCallback(
|
|||||||
|
|
||||||
if (binding_action_) |binding_action| {
|
if (binding_action_) |binding_action| {
|
||||||
//log.warn("BINDING ACTION={}", .{binding_action});
|
//log.warn("BINDING ACTION={}", .{binding_action});
|
||||||
|
try self.performBindingAction(binding_action);
|
||||||
switch (binding_action) {
|
|
||||||
.unbind => unreachable,
|
|
||||||
.ignore => {},
|
|
||||||
|
|
||||||
.reload_config => {
|
|
||||||
_ = self.app_mailbox.push(.{
|
|
||||||
.reload_config = {},
|
|
||||||
}, .{ .instant = {} });
|
|
||||||
},
|
|
||||||
|
|
||||||
.csi => |data| {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = "\x1B[",
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = data,
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
|
|
||||||
// CSI 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
|
|
||||||
// keys mdoe is NOT set.
|
|
||||||
const normal = normal: {
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// With the lock held, we must scroll to the bottom.
|
|
||||||
// We always scroll to the bottom for these inputs.
|
|
||||||
self.scrollToBottom() catch |err| {
|
|
||||||
log.warn("error scrolling to bottom err={}", .{err});
|
|
||||||
};
|
|
||||||
|
|
||||||
break :normal !self.io.terminal.modes.cursor_keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (normal) {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = ck.normal,
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
} else {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = ck.application,
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
.copy_to_clipboard => {
|
|
||||||
// We can read from the renderer state without holding
|
|
||||||
// the lock because only we will write to this field.
|
|
||||||
if (self.io.terminal.screen.selection) |sel| {
|
|
||||||
var buf = self.io.terminal.screen.selectionString(
|
|
||||||
self.alloc,
|
|
||||||
sel,
|
|
||||||
self.config.clipboard_trim_trailing_spaces,
|
|
||||||
) catch |err| {
|
|
||||||
log.err("error reading selection string err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer self.alloc.free(buf);
|
|
||||||
|
|
||||||
self.rt_surface.setClipboardString(buf) catch |err| {
|
|
||||||
log.err("error setting clipboard string err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.paste_from_clipboard => {
|
|
||||||
const data = self.rt_surface.getClipboardString() catch |err| {
|
|
||||||
log.warn("error reading clipboard: {}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (data.len > 0) {
|
|
||||||
const bracketed = bracketed: {
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// With the lock held, we must scroll to the bottom.
|
|
||||||
// We always scroll to the bottom for these inputs.
|
|
||||||
self.scrollToBottom() catch |err| {
|
|
||||||
log.warn("error scrolling to bottom err={}", .{err});
|
|
||||||
};
|
|
||||||
|
|
||||||
break :bracketed self.io.terminal.modes.bracketed_paste;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bracketed) {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = "\x1B[200~",
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
|
||||||
self.alloc,
|
|
||||||
data,
|
|
||||||
), .{ .forever = {} });
|
|
||||||
|
|
||||||
if (bracketed) {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.write_stable = "\x1B[201~",
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.increase_font_size => |delta| {
|
|
||||||
log.debug("increase font size={}", .{delta});
|
|
||||||
|
|
||||||
var size = self.font_size;
|
|
||||||
size.points +|= delta;
|
|
||||||
self.setFontSize(size);
|
|
||||||
},
|
|
||||||
|
|
||||||
.decrease_font_size => |delta| {
|
|
||||||
log.debug("decrease font size={}", .{delta});
|
|
||||||
|
|
||||||
var size = self.font_size;
|
|
||||||
size.points = @max(1, size.points -| delta);
|
|
||||||
self.setFontSize(size);
|
|
||||||
},
|
|
||||||
|
|
||||||
.reset_font_size => {
|
|
||||||
log.debug("reset font size", .{});
|
|
||||||
|
|
||||||
var size = self.font_size;
|
|
||||||
size.points = self.config.original_font_size;
|
|
||||||
self.setFontSize(size);
|
|
||||||
},
|
|
||||||
|
|
||||||
.clear_screen => {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.clear_screen = .{ .history = true },
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
.jump_to_prompt => |delta| {
|
|
||||||
_ = self.io_thread.mailbox.push(.{
|
|
||||||
.jump_to_prompt = @intCast(delta),
|
|
||||||
}, .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
.write_scrollback_file => write_scrollback_file: {
|
|
||||||
// Create a temporary directory to store our scrollback.
|
|
||||||
var tmp_dir = try internal_os.TempDir.init();
|
|
||||||
errdefer tmp_dir.deinit();
|
|
||||||
|
|
||||||
// Open our scrollback file
|
|
||||||
var file = try tmp_dir.dir.createFile("scrollback", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
// Write the scrollback contents. This requires a lock.
|
|
||||||
{
|
|
||||||
self.renderer_state.mutex.lock();
|
|
||||||
defer self.renderer_state.mutex.unlock();
|
|
||||||
|
|
||||||
// We do not support this for alternate screens
|
|
||||||
// because they don't have scrollback anyways.
|
|
||||||
if (self.io.terminal.active_screen == .alternate) {
|
|
||||||
tmp_dir.deinit();
|
|
||||||
break :write_scrollback_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
const history_max = terminal.Screen.RowIndexTag.history.maxLen(
|
|
||||||
&self.io.terminal.screen,
|
|
||||||
);
|
|
||||||
|
|
||||||
try self.io.terminal.screen.dumpString(file.writer(), .{
|
|
||||||
.start = .{ .history = 0 },
|
|
||||||
.end = .{ .history = history_max -| 1 },
|
|
||||||
.unwrap = true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the final path
|
|
||||||
var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
||||||
const path = try tmp_dir.dir.realpath("scrollback", &path_buf);
|
|
||||||
|
|
||||||
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
|
||||||
self.alloc,
|
|
||||||
path,
|
|
||||||
), .{ .forever = {} });
|
|
||||||
try self.io_thread.wakeup.notify();
|
|
||||||
},
|
|
||||||
|
|
||||||
.toggle_dev_mode => if (DevMode.enabled) {
|
|
||||||
DevMode.instance.visible = !DevMode.instance.visible;
|
|
||||||
try self.queueRender();
|
|
||||||
} else log.warn("dev mode was not compiled into this binary", .{}),
|
|
||||||
|
|
||||||
.new_window => {
|
|
||||||
_ = self.app_mailbox.push(.{
|
|
||||||
.new_window = .{
|
|
||||||
.parent = self,
|
|
||||||
},
|
|
||||||
}, .{ .instant = {} });
|
|
||||||
},
|
|
||||||
|
|
||||||
.new_tab => {
|
|
||||||
if (@hasDecl(apprt.Surface, "newTab")) {
|
|
||||||
try self.rt_surface.newTab();
|
|
||||||
} else log.warn("runtime doesn't implement newTab", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.previous_tab => {
|
|
||||||
if (@hasDecl(apprt.Surface, "gotoPreviousTab")) {
|
|
||||||
self.rt_surface.gotoPreviousTab();
|
|
||||||
} else log.warn("runtime doesn't implement gotoPreviousTab", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.next_tab => {
|
|
||||||
if (@hasDecl(apprt.Surface, "gotoNextTab")) {
|
|
||||||
self.rt_surface.gotoNextTab();
|
|
||||||
} else log.warn("runtime doesn't implement gotoNextTab", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.goto_tab => |n| {
|
|
||||||
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
|
||||||
self.rt_surface.gotoTab(n);
|
|
||||||
} else log.warn("runtime doesn't implement gotoTab", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.new_split => |direction| {
|
|
||||||
if (@hasDecl(apprt.Surface, "newSplit")) {
|
|
||||||
try self.rt_surface.newSplit(direction);
|
|
||||||
} else log.warn("runtime doesn't implement newSplit", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.goto_split => |direction| {
|
|
||||||
if (@hasDecl(apprt.Surface, "gotoSplit")) {
|
|
||||||
self.rt_surface.gotoSplit(direction);
|
|
||||||
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.toggle_fullscreen => {
|
|
||||||
if (@hasDecl(apprt.Surface, "toggleFullscreen")) {
|
|
||||||
self.rt_surface.toggleFullscreen(self.config.macos_non_native_fullscreen);
|
|
||||||
} else log.warn("runtime doesn't implement toggleFullscreen", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.close_surface => self.close(),
|
|
||||||
|
|
||||||
.close_window => {
|
|
||||||
_ = self.app_mailbox.push(.{ .close = self }, .{ .instant = {} });
|
|
||||||
},
|
|
||||||
|
|
||||||
.quit => {
|
|
||||||
_ = self.app_mailbox.push(.{
|
|
||||||
.quit = {},
|
|
||||||
}, .{ .instant = {} });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bindings always result in us ignoring the char if printable
|
// Bindings always result in us ignoring the char if printable
|
||||||
self.ignore_char = true;
|
self.ignore_char = true;
|
||||||
@ -2152,6 +1883,280 @@ fn scrollToBottom(self: *Surface) !void {
|
|||||||
try self.queueRender();
|
try self.queueRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform a binding action. A binding is a keybinding. This function
|
||||||
|
/// must be called from the GUI thread.
|
||||||
|
pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !void {
|
||||||
|
switch (action) {
|
||||||
|
.unbind => unreachable,
|
||||||
|
.ignore => {},
|
||||||
|
|
||||||
|
.reload_config => {
|
||||||
|
_ = self.app_mailbox.push(.{
|
||||||
|
.reload_config = {},
|
||||||
|
}, .{ .instant = {} });
|
||||||
|
},
|
||||||
|
|
||||||
|
.csi => |data| {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = "\x1B[",
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = data,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
|
||||||
|
// CSI 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
|
||||||
|
// keys mdoe is NOT set.
|
||||||
|
const normal = normal: {
|
||||||
|
self.renderer_state.mutex.lock();
|
||||||
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
|
// With the lock held, we must scroll to the bottom.
|
||||||
|
// We always scroll to the bottom for these inputs.
|
||||||
|
self.scrollToBottom() catch |err| {
|
||||||
|
log.warn("error scrolling to bottom err={}", .{err});
|
||||||
|
};
|
||||||
|
|
||||||
|
break :normal !self.io.terminal.modes.cursor_keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (normal) {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = ck.normal,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
} else {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = ck.application,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
},
|
||||||
|
|
||||||
|
.copy_to_clipboard => {
|
||||||
|
// We can read from the renderer state without holding
|
||||||
|
// the lock because only we will write to this field.
|
||||||
|
if (self.io.terminal.screen.selection) |sel| {
|
||||||
|
var buf = self.io.terminal.screen.selectionString(
|
||||||
|
self.alloc,
|
||||||
|
sel,
|
||||||
|
self.config.clipboard_trim_trailing_spaces,
|
||||||
|
) catch |err| {
|
||||||
|
log.err("error reading selection string err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer self.alloc.free(buf);
|
||||||
|
|
||||||
|
self.rt_surface.setClipboardString(buf) catch |err| {
|
||||||
|
log.err("error setting clipboard string err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.paste_from_clipboard => {
|
||||||
|
const data = self.rt_surface.getClipboardString() catch |err| {
|
||||||
|
log.warn("error reading clipboard: {}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.len > 0) {
|
||||||
|
const bracketed = bracketed: {
|
||||||
|
self.renderer_state.mutex.lock();
|
||||||
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
|
// With the lock held, we must scroll to the bottom.
|
||||||
|
// We always scroll to the bottom for these inputs.
|
||||||
|
self.scrollToBottom() catch |err| {
|
||||||
|
log.warn("error scrolling to bottom err={}", .{err});
|
||||||
|
};
|
||||||
|
|
||||||
|
break :bracketed self.io.terminal.modes.bracketed_paste;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bracketed) {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = "\x1B[200~",
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
||||||
|
self.alloc,
|
||||||
|
data,
|
||||||
|
), .{ .forever = {} });
|
||||||
|
|
||||||
|
if (bracketed) {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.write_stable = "\x1B[201~",
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.increase_font_size => |delta| {
|
||||||
|
log.debug("increase font size={}", .{delta});
|
||||||
|
|
||||||
|
var size = self.font_size;
|
||||||
|
size.points +|= delta;
|
||||||
|
self.setFontSize(size);
|
||||||
|
},
|
||||||
|
|
||||||
|
.decrease_font_size => |delta| {
|
||||||
|
log.debug("decrease font size={}", .{delta});
|
||||||
|
|
||||||
|
var size = self.font_size;
|
||||||
|
size.points = @max(1, size.points -| delta);
|
||||||
|
self.setFontSize(size);
|
||||||
|
},
|
||||||
|
|
||||||
|
.reset_font_size => {
|
||||||
|
log.debug("reset font size", .{});
|
||||||
|
|
||||||
|
var size = self.font_size;
|
||||||
|
size.points = self.config.original_font_size;
|
||||||
|
self.setFontSize(size);
|
||||||
|
},
|
||||||
|
|
||||||
|
.clear_screen => {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.clear_screen = .{ .history = true },
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
},
|
||||||
|
|
||||||
|
.jump_to_prompt => |delta| {
|
||||||
|
_ = self.io_thread.mailbox.push(.{
|
||||||
|
.jump_to_prompt = @intCast(delta),
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
},
|
||||||
|
|
||||||
|
.write_scrollback_file => write_scrollback_file: {
|
||||||
|
// Create a temporary directory to store our scrollback.
|
||||||
|
var tmp_dir = try internal_os.TempDir.init();
|
||||||
|
errdefer tmp_dir.deinit();
|
||||||
|
|
||||||
|
// Open our scrollback file
|
||||||
|
var file = try tmp_dir.dir.createFile("scrollback", .{});
|
||||||
|
defer file.close();
|
||||||
|
|
||||||
|
// Write the scrollback contents. This requires a lock.
|
||||||
|
{
|
||||||
|
self.renderer_state.mutex.lock();
|
||||||
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
|
// We do not support this for alternate screens
|
||||||
|
// because they don't have scrollback anyways.
|
||||||
|
if (self.io.terminal.active_screen == .alternate) {
|
||||||
|
tmp_dir.deinit();
|
||||||
|
break :write_scrollback_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
const history_max = terminal.Screen.RowIndexTag.history.maxLen(
|
||||||
|
&self.io.terminal.screen,
|
||||||
|
);
|
||||||
|
|
||||||
|
try self.io.terminal.screen.dumpString(file.writer(), .{
|
||||||
|
.start = .{ .history = 0 },
|
||||||
|
.end = .{ .history = history_max -| 1 },
|
||||||
|
.unwrap = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the final path
|
||||||
|
var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||||
|
const path = try tmp_dir.dir.realpath("scrollback", &path_buf);
|
||||||
|
|
||||||
|
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
||||||
|
self.alloc,
|
||||||
|
path,
|
||||||
|
), .{ .forever = {} });
|
||||||
|
try self.io_thread.wakeup.notify();
|
||||||
|
},
|
||||||
|
|
||||||
|
.toggle_dev_mode => if (DevMode.enabled) {
|
||||||
|
DevMode.instance.visible = !DevMode.instance.visible;
|
||||||
|
try self.queueRender();
|
||||||
|
} else log.warn("dev mode was not compiled into this binary", .{}),
|
||||||
|
|
||||||
|
.new_window => {
|
||||||
|
_ = self.app_mailbox.push(.{
|
||||||
|
.new_window = .{
|
||||||
|
.parent = self,
|
||||||
|
},
|
||||||
|
}, .{ .instant = {} });
|
||||||
|
},
|
||||||
|
|
||||||
|
.new_tab => {
|
||||||
|
if (@hasDecl(apprt.Surface, "newTab")) {
|
||||||
|
try self.rt_surface.newTab();
|
||||||
|
} else log.warn("runtime doesn't implement newTab", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.previous_tab => {
|
||||||
|
if (@hasDecl(apprt.Surface, "gotoPreviousTab")) {
|
||||||
|
self.rt_surface.gotoPreviousTab();
|
||||||
|
} else log.warn("runtime doesn't implement gotoPreviousTab", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.next_tab => {
|
||||||
|
if (@hasDecl(apprt.Surface, "gotoNextTab")) {
|
||||||
|
self.rt_surface.gotoNextTab();
|
||||||
|
} else log.warn("runtime doesn't implement gotoNextTab", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.goto_tab => |n| {
|
||||||
|
if (@hasDecl(apprt.Surface, "gotoTab")) {
|
||||||
|
self.rt_surface.gotoTab(n);
|
||||||
|
} else log.warn("runtime doesn't implement gotoTab", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.new_split => |direction| {
|
||||||
|
if (@hasDecl(apprt.Surface, "newSplit")) {
|
||||||
|
try self.rt_surface.newSplit(direction);
|
||||||
|
} else log.warn("runtime doesn't implement newSplit", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.goto_split => |direction| {
|
||||||
|
if (@hasDecl(apprt.Surface, "gotoSplit")) {
|
||||||
|
self.rt_surface.gotoSplit(direction);
|
||||||
|
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.toggle_fullscreen => {
|
||||||
|
if (@hasDecl(apprt.Surface, "toggleFullscreen")) {
|
||||||
|
self.rt_surface.toggleFullscreen(self.config.macos_non_native_fullscreen);
|
||||||
|
} else log.warn("runtime doesn't implement toggleFullscreen", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.close_surface => self.close(),
|
||||||
|
|
||||||
|
.close_window => {
|
||||||
|
_ = self.app_mailbox.push(.{ .close = self }, .{ .instant = {} });
|
||||||
|
},
|
||||||
|
|
||||||
|
.quit => {
|
||||||
|
_ = self.app_mailbox.push(.{
|
||||||
|
.quit = {},
|
||||||
|
}, .{ .instant = {} });
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf");
|
const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf");
|
||||||
const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf");
|
const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf");
|
||||||
const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
||||||
|
@ -571,6 +571,25 @@ pub const CAPI = struct {
|
|||||||
ptr.gotoSplit(direction);
|
ptr.gotoSplit(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Invoke an action on the surface.
|
||||||
|
export fn ghostty_surface_binding_action(
|
||||||
|
ptr: *Surface,
|
||||||
|
key: input.Binding.Key,
|
||||||
|
unused: *anyopaque,
|
||||||
|
) void {
|
||||||
|
// For future arguments
|
||||||
|
_ = unused;
|
||||||
|
|
||||||
|
const action: input.Binding.Action = switch (key) {
|
||||||
|
.copy_to_clipboard => .{ .copy_to_clipboard = {} },
|
||||||
|
.paste_from_clipboard => .{ .paste_from_clipboard = {} },
|
||||||
|
};
|
||||||
|
|
||||||
|
ptr.core_surface.performBindingAction(action) catch |err| {
|
||||||
|
log.err("error performing binding action action={} err={}", .{ action, err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the window background blur on macOS to the desired value.
|
/// Sets the window background blur on macOS to the desired value.
|
||||||
/// I do this in Zig as an extern function because I don't know how to
|
/// I do this in Zig as an extern function because I don't know how to
|
||||||
/// call these functions in Swift.
|
/// call these functions in Swift.
|
||||||
|
@ -248,6 +248,13 @@ pub const Action = union(enum) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A key for the C API to execute an action. This must be kept in sync
|
||||||
|
// with include/ghostty.h.
|
||||||
|
pub const Key = enum(c_int) {
|
||||||
|
copy_to_clipboard,
|
||||||
|
paste_from_clipboard,
|
||||||
|
};
|
||||||
|
|
||||||
/// Trigger is the associated key state that can trigger an action.
|
/// Trigger is the associated key state that can trigger an action.
|
||||||
pub const Trigger = struct {
|
pub const Trigger = struct {
|
||||||
/// The key that has to be pressed for a binding to take action.
|
/// The key that has to be pressed for a binding to take action.
|
||||||
|
Reference in New Issue
Block a user