diff --git a/src/Surface.zig b/src/Surface.zig index 24100544a..fe7fd2157 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3324,52 +3324,20 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, - .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(); + .write_screen_file => |v| try self.writeScreenFile( + .screen, + v, + ), - // 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_scrollback_file => |v| try self.writeScreenFile( + .history, + v, + ), - // 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); - - self.io.queueMessage(try termio.Message.writeReq( - self.alloc, - path, - ), .unlocked); - }, + .write_selection_file => |v| try self.writeScreenFile( + .selection, + v, + ), .new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }), @@ -3547,6 +3515,94 @@ fn closingAction(action: input.Binding.Action) bool { }; } +/// The portion of the screen to write for writeScreenFile. +const WriteScreenLoc = enum { + screen, // Full screen + history, // History (scrollback) + selection, // Selected text +}; + +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 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) { + break :history null; + } + + break :history terminal.Selection.init( + pages.getTopLeft(.history), + pages.getBottomRight(.history) orelse + break :history null, + false, + ); + }, + + .screen => screen: { + break :screen terminal.Selection.init( + pages.getTopLeft(.screen), + pages.getBottomRight(.screen) orelse + break :screen 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 = sel.start(), + .br = sel.end(), + .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. 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..213e711c9 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -203,9 +203,23 @@ 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: void, + /// 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, + + /// 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 + /// available values. + write_selection_file: WriteScreenAction, /// Open a new window. new_window: void, @@ -324,6 +338,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,