mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
Merge pull request #1959 from kareigu/open_scrollback
Support opening scrollback file in default text editor
This commit is contained in:
144
src/Surface.zig
144
src/Surface.zig
@ -3324,52 +3324,20 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
}, .unlocked);
|
}, .unlocked);
|
||||||
},
|
},
|
||||||
|
|
||||||
.write_scrollback_file => write_scrollback_file: {
|
.write_screen_file => |v| try self.writeScreenFile(
|
||||||
// Create a temporary directory to store our scrollback.
|
.screen,
|
||||||
var tmp_dir = try internal_os.TempDir.init();
|
v,
|
||||||
errdefer tmp_dir.deinit();
|
),
|
||||||
|
|
||||||
// Open our scrollback file
|
.write_scrollback_file => |v| try self.writeScreenFile(
|
||||||
var file = try tmp_dir.dir.createFile("scrollback", .{});
|
.history,
|
||||||
defer file.close();
|
v,
|
||||||
// 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.
|
.write_selection_file => |v| try self.writeScreenFile(
|
||||||
{
|
.selection,
|
||||||
self.renderer_state.mutex.lock();
|
v,
|
||||||
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);
|
|
||||||
},
|
|
||||||
|
|
||||||
.new_window => try self.app.newWindow(self.rt_app, .{ .parent = self }),
|
.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
|
/// 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
|
/// only be called once for each request. The data is immediately copied so
|
||||||
/// it is safe to free the data after this call.
|
/// it is safe to free the data after this call.
|
||||||
|
@ -1322,7 +1322,13 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .{ .translated = .j }, .mods = inputpkg.ctrlOrSuper(.{ .shift = true }) },
|
.{ .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
|
// Expand Selection
|
||||||
|
@ -203,9 +203,23 @@ pub const Action = union(enum) {
|
|||||||
/// number of prompts to jump forward, negative is backwards.
|
/// number of prompts to jump forward, negative is backwards.
|
||||||
jump_to_prompt: i16,
|
jump_to_prompt: i16,
|
||||||
|
|
||||||
/// Write the entire scrollback into a temporary file and write the path to
|
/// Write the entire scrollback into a temporary file. The action
|
||||||
/// the file to the tty.
|
/// determines what to do with the filepath. Valid values are:
|
||||||
write_scrollback_file: void,
|
///
|
||||||
|
/// - "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.
|
/// Open a new window.
|
||||||
new_window: void,
|
new_window: void,
|
||||||
@ -324,6 +338,11 @@ pub const Action = union(enum) {
|
|||||||
u16,
|
u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const WriteScreenAction = enum {
|
||||||
|
paste,
|
||||||
|
open,
|
||||||
|
};
|
||||||
|
|
||||||
// Extern because it is used in the embedded runtime ABI.
|
// Extern because it is used in the embedded runtime ABI.
|
||||||
pub const InspectorMode = enum(c_int) {
|
pub const InspectorMode = enum(c_int) {
|
||||||
toggle,
|
toggle,
|
||||||
|
Reference in New Issue
Block a user