From 6fde42972839574d9d898a2470ef9680293b2856 Mon Sep 17 00:00:00 2001 From: karei Date: Tue, 16 Jul 2024 15:10:37 +0300 Subject: [PATCH 1/9] core: support opening scrollback file in default text editor --- src/Surface.zig | 18 +++++++++++++----- src/config/Config.zig | 8 +++++++- src/input/Binding.zig | 7 ++++++- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 24100544a..37b45df3d 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3324,7 +3324,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, - .write_scrollback_file => write_scrollback_file: { + .write_scrollback_file => |scrollback_action| write_scrollback_file: { // Create a temporary directory to store our scrollback. var tmp_dir = try internal_os.TempDir.init(); errdefer tmp_dir.deinit(); @@ -3365,10 +3365,18 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const path = try tmp_dir.dir.realpath("scrollback", &path_buf); - self.io.queueMessage(try termio.Message.writeReq( - self.alloc, - path, - ), .unlocked); + switch (scrollback_action) { + .paste => { + self.io.queueMessage(try termio.Message.writeReq( + self.alloc, + path, + ), .unlocked); + }, + .open => { + log.debug("opening scrollback file: {s}", .{path}); + try internal_os.open(self.alloc, path); + }, + } }, .new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }), diff --git a/src/config/Config.zig b/src/config/Config.zig index 72a3cb909..b2cd2f447 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1322,7 +1322,13 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { try result.keybind.set.put( alloc, .{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) }, - .{ .write_scrollback_file = {} }, + .{ .write_scrollback_file = .paste }, + ); + + try result.keybind.set.put( + alloc, + .{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true, .alt = true }) }, + .{ .write_scrollback_file = .open }, ); // Expand Selection diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 4232ea252..f5c2d1cdc 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -205,7 +205,7 @@ pub const Action = union(enum) { /// Write the entire scrollback into a temporary file and write the path to /// the file to the tty. - write_scrollback_file: void, + write_scrollback_file: WriteScrollbackAction, /// Open a new window. new_window: void, @@ -292,6 +292,11 @@ pub const Action = union(enum) { end, }; + pub const WriteScrollbackAction = enum { + paste, + open, + }; + pub const SplitDirection = enum { right, down, From 4e9639711ccbbe7b1103511d19f35b9bcfe052ad Mon Sep 17 00:00:00 2001 From: karei Date: Tue, 16 Jul 2024 20:52:55 +0300 Subject: [PATCH 2/9] apprt/gtk: add menu button for opening scrollback file --- src/apprt/gtk/App.zig | 2 ++ src/apprt/gtk/Window.zig | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index b679d1b5f..3a134e7a3 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -358,6 +358,7 @@ fn syncActionAccelerators(self: *App) !void { try self.syncActionAccelerator("app.quit", .{ .quit = {} }); try self.syncActionAccelerator("app.open_config", .{ .open_config = {} }); try self.syncActionAccelerator("app.reload_config", .{ .reload_config = {} }); + try self.syncActionAccelerator("win.open_scrollback", .{ .write_scrollback_file = .open }); try self.syncActionAccelerator("win.toggle_inspector", .{ .inspector = .toggle }); try self.syncActionAccelerator("win.close", .{ .close_surface = {} }); try self.syncActionAccelerator("win.new_window", .{ .new_window = {} }); @@ -772,6 +773,7 @@ fn initMenu(self: *App) void { defer c.g_object_unref(section); c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section))); c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector"); + c.g_menu_append(section, "Open Scrollback", "win.open_scrollback"); c.g_menu_append(section, "Open Configuration", "app.open_config"); c.g_menu_append(section, "Reload Configuration", "app.reload_config"); c.g_menu_append(section, "About Ghostty", "win.about"); diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 4dbe2a979..705f22182 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -173,6 +173,7 @@ fn initActions(self: *Window) void { .{ "split_right", >kActionSplitRight }, .{ "split_down", >kActionSplitDown }, .{ "toggle_inspector", >kActionToggleInspector }, + .{ "open_scrollback", >kActionOpenScrollback }, }; inline for (actions) |entry| { @@ -580,6 +581,19 @@ fn gtkActionToggleInspector( }; } +fn gtkActionOpenScrollback( + _: *c.GSimpleAction, + _: *c.GVariant, + ud: ?*anyopaque, +) callconv(.C) void { + const self: *Window = @ptrCast(@alignCast(ud orelse return)); + const surface = self.actionSurface() orelse return; + _ = surface.performBindingAction(.{ .write_scrollback_file = .open }) catch |err| { + log.warn("error performing binding action error={}", .{err}); + return; + }; +} + /// Returns the surface to use for an action. fn actionSurface(self: *Window) ?*CoreSurface { const page_idx = c.gtk_notebook_get_current_page(self.notebook); From f9c14c55b40d2196702653a6ed0337ac77d5184d Mon Sep 17 00:00:00 2001 From: karei Date: Wed, 17 Jul 2024 22:15:56 +0300 Subject: [PATCH 3/9] macos: add button and menu item for opening scrollback file --- macos/Sources/App/macOS/AppDelegate.swift | 2 ++ macos/Sources/App/macOS/MainMenu.xib | 7 +++++++ macos/Sources/Features/Terminal/TerminalController.swift | 5 +++++ macos/Sources/Ghostty/Ghostty.App.swift | 7 +++++++ macos/Sources/Ghostty/SurfaceView_AppKit.swift | 1 + 5 files changed, 22 insertions(+) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 8b6b064a9..121e066ca 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -49,6 +49,7 @@ class AppDelegate: NSObject, @IBOutlet private var menuDecreaseFontSize: NSMenuItem? @IBOutlet private var menuResetFontSize: NSMenuItem? @IBOutlet private var menuTerminalInspector: NSMenuItem? + @IBOutlet private var menuOpenScrollback: NSMenuItem? @IBOutlet private var menuEqualizeSplits: NSMenuItem? @IBOutlet private var menuMoveSplitDividerUp: NSMenuItem? @@ -281,6 +282,7 @@ class AppDelegate: NSObject, syncMenuShortcut(action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize) syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize) syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector) + syncMenuShortcut(action: "write_scrollback_file:open", menuItem: self.menuOpenScrollback) // This menu item is NOT synced with the configuration because it disables macOS // global fullscreen keyboard shortcut. The shortcut in the Ghostty config will continue diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib index bbfd59eae..56aa4c9b4 100644 --- a/macos/Sources/App/macOS/MainMenu.xib +++ b/macos/Sources/App/macOS/MainMenu.xib @@ -30,6 +30,7 @@ + @@ -215,6 +216,12 @@ + + + + + + diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 81b86a215..11b0ab8bf 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -604,6 +604,11 @@ class TerminalController: NSWindowController, NSWindowDelegate, guard let surface = focusedSurface?.surface else { return } ghostty.toggleTerminalInspector(surface: surface) } + + @IBAction func openScrollback(_ sender: Any) { + guard let surface = focusedSurface?.surface else { return } + ghostty.openScrollback(surface: surface) + } //MARK: - TerminalViewDelegate diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 97a4aa0da..728094ae5 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -240,6 +240,13 @@ extension Ghostty { logger.warning("action failed action=\(action)") } } + + func openScrollback(surface: ghostty_surface_t) { + let action = "write_scrollback_file:open" + if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { + logger.warning("action failed action=\(action)") + } + } #if os(iOS) // MARK: Ghostty Callbacks (iOS) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 26e41f61f..bbab1b61e 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -952,6 +952,7 @@ extension Ghostty { menu.addItem(.separator()) menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(TerminalController.toggleTerminalInspector(_:)), keyEquivalent: "") + menu.addItem(withTitle: "Open Scrollback", action: #selector(TerminalController.openScrollback(_:)), keyEquivalent: "") return menu } From fa264ca16085a53842655b066c9406b6f43f88ca Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 19:54:21 -0700 Subject: [PATCH 4/9] Revert "apprt/gtk: add menu button for opening scrollback file" This reverts commit 44dda62d68620937aa6de01ff0097df0b4ff9696. --- src/apprt/gtk/App.zig | 2 -- src/apprt/gtk/Window.zig | 14 -------------- 2 files changed, 16 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 3a134e7a3..b679d1b5f 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -358,7 +358,6 @@ fn syncActionAccelerators(self: *App) !void { try self.syncActionAccelerator("app.quit", .{ .quit = {} }); try self.syncActionAccelerator("app.open_config", .{ .open_config = {} }); try self.syncActionAccelerator("app.reload_config", .{ .reload_config = {} }); - try self.syncActionAccelerator("win.open_scrollback", .{ .write_scrollback_file = .open }); try self.syncActionAccelerator("win.toggle_inspector", .{ .inspector = .toggle }); try self.syncActionAccelerator("win.close", .{ .close_surface = {} }); try self.syncActionAccelerator("win.new_window", .{ .new_window = {} }); @@ -773,7 +772,6 @@ fn initMenu(self: *App) void { defer c.g_object_unref(section); c.g_menu_append_section(menu, null, @ptrCast(@alignCast(section))); c.g_menu_append(section, "Terminal Inspector", "win.toggle_inspector"); - c.g_menu_append(section, "Open Scrollback", "win.open_scrollback"); c.g_menu_append(section, "Open Configuration", "app.open_config"); c.g_menu_append(section, "Reload Configuration", "app.reload_config"); c.g_menu_append(section, "About Ghostty", "win.about"); diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 705f22182..4dbe2a979 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -173,7 +173,6 @@ fn initActions(self: *Window) void { .{ "split_right", >kActionSplitRight }, .{ "split_down", >kActionSplitDown }, .{ "toggle_inspector", >kActionToggleInspector }, - .{ "open_scrollback", >kActionOpenScrollback }, }; inline for (actions) |entry| { @@ -581,19 +580,6 @@ fn gtkActionToggleInspector( }; } -fn gtkActionOpenScrollback( - _: *c.GSimpleAction, - _: *c.GVariant, - ud: ?*anyopaque, -) callconv(.C) void { - const self: *Window = @ptrCast(@alignCast(ud orelse return)); - const surface = self.actionSurface() orelse return; - _ = surface.performBindingAction(.{ .write_scrollback_file = .open }) catch |err| { - log.warn("error performing binding action error={}", .{err}); - return; - }; -} - /// Returns the surface to use for an action. fn actionSurface(self: *Window) ?*CoreSurface { const page_idx = c.gtk_notebook_get_current_page(self.notebook); From 9f6e85eba7a602c2c05746766290ce4e4b6b22ff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 19:54:33 -0700 Subject: [PATCH 5/9] Revert "macos: add button and menu item for opening scrollback file" This reverts commit bc525f63a028a9aeecf659a2e0f3df178e327a60. --- macos/Sources/App/macOS/AppDelegate.swift | 2 -- macos/Sources/App/macOS/MainMenu.xib | 7 ------- macos/Sources/Features/Terminal/TerminalController.swift | 5 ----- macos/Sources/Ghostty/Ghostty.App.swift | 7 ------- macos/Sources/Ghostty/SurfaceView_AppKit.swift | 1 - 5 files changed, 22 deletions(-) diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 121e066ca..8b6b064a9 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -49,7 +49,6 @@ class AppDelegate: NSObject, @IBOutlet private var menuDecreaseFontSize: NSMenuItem? @IBOutlet private var menuResetFontSize: NSMenuItem? @IBOutlet private var menuTerminalInspector: NSMenuItem? - @IBOutlet private var menuOpenScrollback: NSMenuItem? @IBOutlet private var menuEqualizeSplits: NSMenuItem? @IBOutlet private var menuMoveSplitDividerUp: NSMenuItem? @@ -282,7 +281,6 @@ class AppDelegate: NSObject, syncMenuShortcut(action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize) syncMenuShortcut(action: "reset_font_size", menuItem: self.menuResetFontSize) syncMenuShortcut(action: "inspector:toggle", menuItem: self.menuTerminalInspector) - syncMenuShortcut(action: "write_scrollback_file:open", menuItem: self.menuOpenScrollback) // This menu item is NOT synced with the configuration because it disables macOS // global fullscreen keyboard shortcut. The shortcut in the Ghostty config will continue diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib index 56aa4c9b4..bbfd59eae 100644 --- a/macos/Sources/App/macOS/MainMenu.xib +++ b/macos/Sources/App/macOS/MainMenu.xib @@ -30,7 +30,6 @@ - @@ -216,12 +215,6 @@ - - - - - - diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 11b0ab8bf..81b86a215 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -604,11 +604,6 @@ class TerminalController: NSWindowController, NSWindowDelegate, guard let surface = focusedSurface?.surface else { return } ghostty.toggleTerminalInspector(surface: surface) } - - @IBAction func openScrollback(_ sender: Any) { - guard let surface = focusedSurface?.surface else { return } - ghostty.openScrollback(surface: surface) - } //MARK: - TerminalViewDelegate diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 728094ae5..97a4aa0da 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -240,13 +240,6 @@ extension Ghostty { logger.warning("action failed action=\(action)") } } - - func openScrollback(surface: ghostty_surface_t) { - let action = "write_scrollback_file:open" - if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { - logger.warning("action failed action=\(action)") - } - } #if os(iOS) // MARK: Ghostty Callbacks (iOS) diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index bbab1b61e..26e41f61f 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -952,7 +952,6 @@ extension Ghostty { menu.addItem(.separator()) menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(TerminalController.toggleTerminalInspector(_:)), keyEquivalent: "") - menu.addItem(withTitle: "Open Scrollback", action: #selector(TerminalController.openScrollback(_:)), keyEquivalent: "") return menu } From e3df08039aab17eb81e840d88d1047d63baa9d5f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 20:03:20 -0700 Subject: [PATCH 6/9] some bikeshedding --- src/Surface.zig | 19 +++++++------------ src/input/Binding.zig | 20 ++++++++++++-------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 37b45df3d..351f61c5f 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3324,7 +3324,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, - .write_scrollback_file => |scrollback_action| write_scrollback_file: { + .write_scrollback_file => |write_action| write_scrollback_file: { // Create a temporary directory to store our scrollback. var tmp_dir = try internal_os.TempDir.init(); errdefer tmp_dir.deinit(); @@ -3365,17 +3365,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const path = try tmp_dir.dir.realpath("scrollback", &path_buf); - switch (scrollback_action) { - .paste => { - self.io.queueMessage(try termio.Message.writeReq( - self.alloc, - path, - ), .unlocked); - }, - .open => { - log.debug("opening scrollback file: {s}", .{path}); - try internal_os.open(self.alloc, path); - }, + switch (write_action) { + .open => try internal_os.open(self.alloc, path), + .paste => self.io.queueMessage(try termio.Message.writeReq( + self.alloc, + path, + ), .unlocked), } }, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index f5c2d1cdc..02b9f79bf 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -203,9 +203,13 @@ pub const Action = union(enum) { /// number of prompts to jump forward, negative is backwards. jump_to_prompt: i16, - /// Write the entire scrollback into a temporary file and write the path to - /// the file to the tty. - write_scrollback_file: WriteScrollbackAction, + /// Write the entire scrollback into a temporary file. The action + /// determines what to do with the filepath. Valid values are: + /// + /// - "paste": Paste the file path into the terminal. + /// - "open": Open the file in the default OS editor for text files. + /// + write_scrollback_file: WriteScreenAction, /// Open a new window. new_window: void, @@ -292,11 +296,6 @@ pub const Action = union(enum) { end, }; - pub const WriteScrollbackAction = enum { - paste, - open, - }; - pub const SplitDirection = enum { right, down, @@ -329,6 +328,11 @@ pub const Action = union(enum) { u16, }; + pub const WriteScreenAction = enum { + paste, + open, + }; + // Extern because it is used in the embedded runtime ABI. pub const InspectorMode = enum(c_int) { toggle, From 10198d88dcab0d660422f9addf83e826d0890f28 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 20:16:17 -0700 Subject: [PATCH 7/9] core: make the write scrollback file logic more generic --- src/Surface.zig | 125 +++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 49 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 351f61c5f..b4fcc2a10 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3324,55 +3324,10 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, - .write_scrollback_file => |write_action| 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(); - // Screen.dumpString writes byte-by-byte, so buffer it - var buf_writer = std.io.bufferedWriter(file.writer()); - - // 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; - } - - // We only dump history if we have history. We still keep - // the file and write the empty file to the pty so that this - // command always works on the primary screen. - const pages = &self.io.terminal.screen.pages; - if (pages.getBottomRight(.active)) |br| { - const tl = pages.getTopLeft(.history); - try self.io.terminal.screen.dumpString( - buf_writer.writer(), - .{ .tl = tl, .br = br, .unwrap = true }, - ); - } - } - try buf_writer.flush(); - - // Get the final path - var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const path = try tmp_dir.dir.realpath("scrollback", &path_buf); - - switch (write_action) { - .open => try internal_os.open(self.alloc, path), - .paste => self.io.queueMessage(try termio.Message.writeReq( - self.alloc, - path, - ), .unlocked), - } - }, + .write_scrollback_file => |v| try self.writeScreenFile( + .history, + v, + ), .new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }), @@ -3550,6 +3505,78 @@ fn closingAction(action: input.Binding.Action) bool { }; } +/// The portion of the screen to write for writeScreenFile. +const WriteScreenLoc = enum { + history, +}; + +fn writeScreenFile( + self: *Surface, + loc: WriteScreenLoc, + write_action: input.Binding.Action.WriteScreenAction, +) !void { + // 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(@tagName(loc), .{}); + defer file.close(); + // Screen.dumpString writes byte-by-byte, so buffer it + var buf_writer = std.io.bufferedWriter(file.writer()); + + // Write the scrollback contents. This requires a lock. + { + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + + // We only dump history if we have history. We still keep + // the file and write the empty file to the pty so that this + // command always works on the primary screen. + const pages = &self.io.terminal.screen.pages; + const tl: terminal.Pin, const br: ?terminal.Pin = switch (loc) { + .history => history: { + // 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(); + return; + } + + break :history .{ + pages.getTopLeft(.history), + pages.getBottomRight(.history), + }; + }, + }; + + try self.io.terminal.screen.dumpString( + buf_writer.writer(), + .{ + .tl = tl, + .br = br orelse { + tmp_dir.deinit(); + return; + }, + .unwrap = true, + }, + ); + } + try buf_writer.flush(); + + // Get the final path + var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const path = try tmp_dir.dir.realpath(@tagName(loc), &path_buf); + + switch (write_action) { + .open => try internal_os.open(self.alloc, path), + .paste => self.io.queueMessage(try termio.Message.writeReq( + self.alloc, + path, + ), .unlocked), + } +} + /// Call this to complete a clipboard request sent to apprt. This should /// only be called once for each request. The data is immediately copied so /// it is safe to free the data after this call. From 55657465a772fee4691a2471375ae7cee15976e8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 20:20:49 -0700 Subject: [PATCH 8/9] add write_selection_file Based on the work by @gigamaax --- src/Surface.zig | 33 ++++++++++++++++++++++----------- src/input/Binding.zig | 6 ++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index b4fcc2a10..373c18613 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3329,6 +3329,11 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool v, ), + .write_selection_file => |v| try self.writeScreenFile( + .selection, + v, + ), + .new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }), .new_tab => { @@ -3508,6 +3513,7 @@ fn closingAction(action: input.Binding.Action) bool { /// The portion of the screen to write for writeScreenFile. const WriteScreenLoc = enum { history, + selection, }; fn writeScreenFile( @@ -3534,30 +3540,35 @@ fn writeScreenFile( // the file and write the empty file to the pty so that this // command always works on the primary screen. const pages = &self.io.terminal.screen.pages; - const tl: terminal.Pin, const br: ?terminal.Pin = switch (loc) { + const sel_: ?terminal.Selection = switch (loc) { .history => history: { // 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(); - return; + break :history null; } - break :history .{ + break :history terminal.Selection.init( pages.getTopLeft(.history), - pages.getBottomRight(.history), - }; + pages.getBottomRight(.history) orelse + break :history null, + false, + ); }, + + .selection => self.io.terminal.screen.selection, }; + const sel = sel_ orelse { + // If we have no selection we have no data so we do nothing. + tmp_dir.deinit(); + return; + }; try self.io.terminal.screen.dumpString( buf_writer.writer(), .{ - .tl = tl, - .br = br orelse { - tmp_dir.deinit(); - return; - }, + .tl = sel.start(), + .br = sel.end(), .unwrap = true, }, ); diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 02b9f79bf..37f478185 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -211,6 +211,12 @@ pub const Action = union(enum) { /// write_scrollback_file: WriteScreenAction, + /// Same as write_scrollback_file but writes the selected text. + /// If there is no selected text this does nothing (it doesn't + /// even create an empty file). See write_scrollback_file for + /// available values. + write_selection_file: WriteScreenAction, + /// Open a new window. new_window: void, From a62b76eda3529f379afc5d8040c3240edb2aaa23 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Jul 2024 20:24:51 -0700 Subject: [PATCH 9/9] core: add binding to write screen to file --- src/Surface.zig | 19 +++++++++++++++++-- src/input/Binding.zig | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 373c18613..fe7fd2157 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3324,6 +3324,11 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, + .write_screen_file => |v| try self.writeScreenFile( + .screen, + v, + ), + .write_scrollback_file => |v| try self.writeScreenFile( .history, v, @@ -3512,8 +3517,9 @@ fn closingAction(action: input.Binding.Action) bool { /// The portion of the screen to write for writeScreenFile. const WriteScreenLoc = enum { - history, - selection, + screen, // Full screen + history, // History (scrollback) + selection, // Selected text }; fn writeScreenFile( @@ -3556,6 +3562,15 @@ fn writeScreenFile( ); }, + .screen => screen: { + break :screen terminal.Selection.init( + pages.getTopLeft(.screen), + pages.getBottomRight(.screen) orelse + break :screen null, + false, + ); + }, + .selection => self.io.terminal.screen.selection, }; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 37f478185..213e711c9 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -211,6 +211,10 @@ pub const Action = union(enum) { /// write_scrollback_file: WriteScreenAction, + /// Same as write_scrollback_file but writes the full screen contents. + /// See write_scrollback_file for available values. + write_screen_file: WriteScreenAction, + /// Same as write_scrollback_file but writes the selected text. /// If there is no selected text this does nothing (it doesn't /// even create an empty file). See write_scrollback_file for