mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Open scrollback in your editor inside Ghostty
This adds options to `write_scrollback_file`, `write_screen_file`, and `write_selection_file` to create a new window, tab, or split and open up that file using your editor. To do so, the plumbing has been added to the core to create new surfaces with a custom configuration.
This commit is contained in:
@ -253,7 +253,7 @@ pub fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
|||||||
null;
|
null;
|
||||||
} else null;
|
} else null;
|
||||||
|
|
||||||
try rt_app.newWindow(parent);
|
try rt_app.newWindow(.{ .parent = parent, .config = msg.config });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start quitting
|
/// Start quitting
|
||||||
@ -321,6 +321,9 @@ pub const Message = union(enum) {
|
|||||||
const NewWindow = struct {
|
const NewWindow = struct {
|
||||||
/// The parent surface
|
/// The parent surface
|
||||||
parent: ?*Surface = null,
|
parent: ?*Surface = null,
|
||||||
|
|
||||||
|
/// Custom configuration to use for the new window.
|
||||||
|
config: ?*Config = null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3399,7 +3399,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
|
|
||||||
.new_tab => {
|
.new_tab => {
|
||||||
if (@hasDecl(apprt.Surface, "newTab")) {
|
if (@hasDecl(apprt.Surface, "newTab")) {
|
||||||
try self.rt_surface.newTab();
|
try self.rt_surface.newTab(.{});
|
||||||
} else log.warn("runtime doesn't implement newTab", .{});
|
} else log.warn("runtime doesn't implement newTab", .{});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -3437,14 +3437,17 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
|
|
||||||
.new_split => |direction| {
|
.new_split => |direction| {
|
||||||
if (@hasDecl(apprt.Surface, "newSplit")) {
|
if (@hasDecl(apprt.Surface, "newSplit")) {
|
||||||
try self.rt_surface.newSplit(switch (direction) {
|
try self.rt_surface.newSplit(
|
||||||
.right => .right,
|
switch (direction) {
|
||||||
.down => .down,
|
.right => .right,
|
||||||
.auto => if (self.screen_size.width > self.screen_size.height)
|
.down => .down,
|
||||||
.right
|
.auto => if (self.screen_size.width > self.screen_size.height)
|
||||||
else
|
.right
|
||||||
.down,
|
else
|
||||||
});
|
.down,
|
||||||
|
},
|
||||||
|
.{},
|
||||||
|
);
|
||||||
} else log.warn("runtime doesn't implement newSplit", .{});
|
} else log.warn("runtime doesn't implement newSplit", .{});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -3663,6 +3666,22 @@ fn writeScreenFile(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
path,
|
path,
|
||||||
), .unlocked),
|
), .unlocked),
|
||||||
|
.edit_window => {
|
||||||
|
if (@hasDecl(apprt.runtime.Surface, "openEditorWithPath"))
|
||||||
|
try self.rt_surface.openEditorWithPath(.window, path);
|
||||||
|
},
|
||||||
|
.edit_tab => {
|
||||||
|
if (@hasDecl(apprt.runtime.Surface, "openEditorWithPath"))
|
||||||
|
try self.rt_surface.openEditorWithPath(.tab, path);
|
||||||
|
},
|
||||||
|
.edit_split_right => {
|
||||||
|
if (@hasDecl(apprt.runtime.Surface, "openEditorWithPath"))
|
||||||
|
try self.rt_surface.openEditorWithPath(.split_right, path);
|
||||||
|
},
|
||||||
|
.edit_split_down => {
|
||||||
|
if (@hasDecl(apprt.runtime.Surface, "openEditorWithPath"))
|
||||||
|
try self.rt_surface.openEditorWithPath(.split_down, path);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,13 +225,18 @@ pub const App = struct {
|
|||||||
surface.queueInspectorRender();
|
surface.queueInspectorRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newWindow(self: *App, parent: ?*CoreSurface) !void {
|
pub fn newWindow(self: *App, opts: struct {
|
||||||
|
parent: ?*CoreSurface = null,
|
||||||
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
|
|
||||||
|
if (opts.config != null) log.warn("embedded runtime does not support creating new windows with custom configs", .{});
|
||||||
|
|
||||||
// Right now we only support creating a new window with a parent
|
// Right now we only support creating a new window with a parent
|
||||||
// through this code.
|
// through this code.
|
||||||
// The other case is handled by the embedding runtime.
|
// The other case is handled by the embedding runtime.
|
||||||
if (parent) |surface| {
|
if (opts.parent) |surface| {
|
||||||
try surface.rt_surface.newWindow();
|
try surface.rt_surface.newWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -474,12 +479,16 @@ pub const Surface = struct {
|
|||||||
func(self.userdata, mode);
|
func(self.userdata, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newSplit(self: *const Surface, direction: apprt.SplitDirection) !void {
|
pub fn newSplit(self: *const Surface, direction: apprt.SplitDirection, opts: struct {
|
||||||
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
const func = self.app.opts.new_split orelse {
|
const func = self.app.opts.new_split orelse {
|
||||||
log.info("runtime embedder does not support splits", .{});
|
log.info("runtime embedder does not support splits", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (opts.config != null) log.warn("embedded runtime does not support creating new splits with a custom config", .{});
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
const options = self.newSurfaceOptions();
|
||||||
func(self.userdata, direction, options);
|
func(self.userdata, direction, options);
|
||||||
}
|
}
|
||||||
@ -1035,12 +1044,16 @@ pub const Surface = struct {
|
|||||||
func(self.userdata, nonNativeFullscreen);
|
func(self.userdata, nonNativeFullscreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newTab(self: *const Surface) !void {
|
pub fn newTab(self: *const Surface, opts: struct {
|
||||||
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
const func = self.app.opts.new_tab orelse {
|
const func = self.app.opts.new_tab orelse {
|
||||||
log.info("runtime embedder does not support new_tab", .{});
|
log.info("runtime embedder does not support new_tab", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (opts.config != null) log.warn("embedded runtime does not support creating new tabs with a custom config", .{});
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
const options = self.newSurfaceOptions();
|
||||||
func(self.userdata, options);
|
func(self.userdata, options);
|
||||||
}
|
}
|
||||||
|
@ -196,22 +196,31 @@ pub const App = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new window for the app.
|
/// Create a new window for the app.
|
||||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
pub fn newWindow(self: *App, opts: struct {
|
||||||
_ = try self.newSurface(parent_);
|
parent: ?*CoreSurface = null,
|
||||||
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
|
if (opts.config != null) log.warn("glfw runtime does not support creating new windows with a custom config", .{});
|
||||||
|
_ = try self.newSurface(opts.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new tab in the parent surface.
|
/// Create a new tab in the parent surface.
|
||||||
fn newTab(self: *App, parent: *CoreSurface) !void {
|
fn newTab(self: *App, opts: struct {
|
||||||
|
parent: *CoreSurface,
|
||||||
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
if (!Darwin.enabled) {
|
if (!Darwin.enabled) {
|
||||||
log.warn("tabbing is not supported on this platform", .{});
|
log.warn("tabbing is not supported on this platform", .{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.config == null) log.warn("glfw runtime does not support creating new tabs with a custom config", .{});
|
||||||
|
|
||||||
// Create the new window
|
// Create the new window
|
||||||
const window = try self.newSurface(parent);
|
const window = try self.newSurface(opts.parent);
|
||||||
|
|
||||||
// Add the new window the parent window
|
// Add the new window the parent window
|
||||||
const parent_win = glfwNative.getCocoaWindow(parent.rt_surface.window).?;
|
const parent_win = glfwNative.getCocoaWindow(opts.parent.rt_surface.window).?;
|
||||||
const other_win = glfwNative.getCocoaWindow(window.window).?;
|
const other_win = glfwNative.getCocoaWindow(window.window).?;
|
||||||
const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 };
|
const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 };
|
||||||
const nswindow = objc.Object.fromId(parent_win);
|
const nswindow = objc.Object.fromId(parent_win);
|
||||||
@ -224,11 +233,11 @@ pub const App = struct {
|
|||||||
// our viewport size. We need to call the size callback in order to
|
// our viewport size. We need to call the size callback in order to
|
||||||
// update values. For example, we need this to set the proper mouse selection
|
// update values. For example, we need this to set the proper mouse selection
|
||||||
// point in the grid.
|
// point in the grid.
|
||||||
const size = parent.rt_surface.getSize() catch |err| {
|
const size = opts.parent.rt_surface.getSize() catch |err| {
|
||||||
log.err("error querying window size for size callback on new tab err={}", .{err});
|
log.err("error querying window size for size callback on new tab err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
parent.sizeCallback(size) catch |err| {
|
opts.parent.sizeCallback(size) catch |err| {
|
||||||
log.err("error in size callback from new tab err={}", .{err});
|
log.err("error in size callback from new tab err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -526,8 +535,10 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new tab in the window containing this surface.
|
/// Create a new tab in the window containing this surface.
|
||||||
pub fn newTab(self: *Surface) !void {
|
pub fn newTab(self: *Surface, opts: struct {
|
||||||
try self.app.newTab(&self.core_surface);
|
config: ?*Config = null,
|
||||||
|
}) !void {
|
||||||
|
try self.app.newTab(.{ .parent = &self.core_surface, .config = opts.config });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the glfw window is in fullscreen.
|
/// Checks if the glfw window is in fullscreen.
|
||||||
|
@ -565,7 +565,10 @@ pub fn redrawInspector(self: *App, surface: *Surface) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Called by CoreApp to create a new window with a new surface.
|
/// Called by CoreApp to create a new window with a new surface.
|
||||||
pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
pub fn newWindow(self: *App, opts: struct {
|
||||||
|
parent: ?*CoreSurface = null,
|
||||||
|
config: ?*configpkg.Config = null,
|
||||||
|
}) !void {
|
||||||
const alloc = self.core_app.alloc;
|
const alloc = self.core_app.alloc;
|
||||||
|
|
||||||
// Allocate a fixed pointer for our window. We try to minimize
|
// Allocate a fixed pointer for our window. We try to minimize
|
||||||
@ -578,7 +581,7 @@ pub fn newWindow(self: *App, parent_: ?*CoreSurface) !void {
|
|||||||
var window = try Window.create(alloc, self);
|
var window = try Window.create(alloc, self);
|
||||||
|
|
||||||
// Add our initial tab
|
// Add our initial tab
|
||||||
try window.newTab(parent_);
|
try window.newTab(.{ .parent = opts.parent, .config = opts.config });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quit(self: *App) void {
|
fn quit(self: *App) void {
|
||||||
|
@ -9,6 +9,7 @@ const apprt = @import("../../apprt.zig");
|
|||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
|
const Config = @import("../../config.zig").Config;
|
||||||
|
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const Tab = @import("Tab.zig");
|
const Tab = @import("Tab.zig");
|
||||||
@ -59,10 +60,13 @@ pub fn create(
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
sibling: *Surface,
|
sibling: *Surface,
|
||||||
direction: apprt.SplitDirection,
|
direction: apprt.SplitDirection,
|
||||||
|
opts: struct {
|
||||||
|
config: ?*Config = null,
|
||||||
|
},
|
||||||
) !*Split {
|
) !*Split {
|
||||||
var split = try alloc.create(Split);
|
var split = try alloc.create(Split);
|
||||||
errdefer alloc.destroy(split);
|
errdefer alloc.destroy(split);
|
||||||
try split.init(sibling, direction);
|
try split.init(sibling, direction, .{ .config = opts.config });
|
||||||
return split;
|
return split;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,11 +74,15 @@ pub fn init(
|
|||||||
self: *Split,
|
self: *Split,
|
||||||
sibling: *Surface,
|
sibling: *Surface,
|
||||||
direction: apprt.SplitDirection,
|
direction: apprt.SplitDirection,
|
||||||
|
opts: struct {
|
||||||
|
config: ?*Config = null,
|
||||||
|
},
|
||||||
) !void {
|
) !void {
|
||||||
// Create the new child surface for the other direction.
|
// Create the new child surface for the other direction.
|
||||||
const alloc = sibling.app.core_app.alloc;
|
const alloc = sibling.app.core_app.alloc;
|
||||||
var surface = try Surface.create(alloc, sibling.app, .{
|
var surface = try Surface.create(alloc, sibling.app, .{
|
||||||
.parent = &sibling.core_surface,
|
.parent = &sibling.core_surface,
|
||||||
|
.config = opts.config,
|
||||||
});
|
});
|
||||||
errdefer surface.destroy(alloc);
|
errdefer surface.destroy(alloc);
|
||||||
sibling.dimSurface();
|
sibling.dimSurface();
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
const Surface = @This();
|
const Surface = @This();
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const build_options = @import("build_options");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../../config.zig");
|
||||||
const apprt = @import("../../apprt.zig");
|
const apprt = @import("../../apprt.zig");
|
||||||
@ -34,6 +36,9 @@ pub const Options = struct {
|
|||||||
/// The parent surface to inherit settings such as font size, working
|
/// The parent surface to inherit settings such as font size, working
|
||||||
/// directory, etc. from.
|
/// directory, etc. from.
|
||||||
parent: ?*CoreSurface = null,
|
parent: ?*CoreSurface = null,
|
||||||
|
|
||||||
|
/// A custom config to use in the surface.
|
||||||
|
config: ?*configpkg.Config = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The container that this surface is directly attached to.
|
/// The container that this surface is directly attached to.
|
||||||
@ -310,6 +315,8 @@ realized: bool = false,
|
|||||||
/// True if this surface had a parent to start with.
|
/// True if this surface had a parent to start with.
|
||||||
parent_surface: bool = false,
|
parent_surface: bool = false,
|
||||||
|
|
||||||
|
config: ?*configpkg.Config = null,
|
||||||
|
|
||||||
/// The GUI container that this surface has been attached to. This
|
/// The GUI container that this surface has been attached to. This
|
||||||
/// dictates some behaviors such as new splits, etc.
|
/// dictates some behaviors such as new splits, etc.
|
||||||
container: Container = .{ .none = {} },
|
container: Container = .{ .none = {} },
|
||||||
@ -455,6 +462,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
|||||||
|
|
||||||
// Inherit the parent's font size if we have a parent.
|
// Inherit the parent's font size if we have a parent.
|
||||||
const font_size: ?font.face.DesiredSize = font_size: {
|
const font_size: ?font.face.DesiredSize = font_size: {
|
||||||
|
if (opts.config) |config| if (!config.@"window-inherit-font-size") break :font_size null;
|
||||||
if (!app.config.@"window-inherit-font-size") break :font_size null;
|
if (!app.config.@"window-inherit-font-size") break :font_size null;
|
||||||
const parent = opts.parent orelse break :font_size null;
|
const parent = opts.parent orelse break :font_size null;
|
||||||
break :font_size parent.font_size;
|
break :font_size parent.font_size;
|
||||||
@ -501,6 +509,7 @@ pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
|||||||
.core_surface = undefined,
|
.core_surface = undefined,
|
||||||
.font_size = font_size,
|
.font_size = font_size,
|
||||||
.parent_surface = opts.parent != null,
|
.parent_surface = opts.parent != null,
|
||||||
|
.config = opts.config,
|
||||||
.size = .{ .width = 800, .height = 600 },
|
.size = .{ .width = 800, .height = 600 },
|
||||||
.cursor_pos = .{ .x = 0, .y = 0 },
|
.cursor_pos = .{ .x = 0, .y = 0 },
|
||||||
.im_context = im_context,
|
.im_context = im_context,
|
||||||
@ -548,7 +557,10 @@ fn realize(self: *Surface) !void {
|
|||||||
errdefer self.app.core_app.deleteSurface(self);
|
errdefer self.app.core_app.deleteSurface(self);
|
||||||
|
|
||||||
// Get our new surface config
|
// Get our new surface config
|
||||||
var config = try apprt.surface.newConfig(self.app.core_app, &self.app.config);
|
var config = cfg: {
|
||||||
|
if (self.config) |config| break :cfg try apprt.surface.newConfig(self.app.core_app, config);
|
||||||
|
break :cfg try apprt.surface.newConfig(self.app.core_app, &self.app.config);
|
||||||
|
};
|
||||||
defer config.deinit();
|
defer config.deinit();
|
||||||
if (!self.parent_surface) {
|
if (!self.parent_surface) {
|
||||||
// A hack, see the "parent_surface" field for more information.
|
// A hack, see the "parent_surface" field for more information.
|
||||||
@ -603,6 +615,11 @@ pub fn deinit(self: *Surface) void {
|
|||||||
self.unfocused_widget = null;
|
self.unfocused_widget = null;
|
||||||
}
|
}
|
||||||
self.resize_overlay.deinit();
|
self.resize_overlay.deinit();
|
||||||
|
|
||||||
|
if (self.config) |config| {
|
||||||
|
config.deinit();
|
||||||
|
self.app.core_app.alloc.destroy(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unref removes the long-held reference to the gl_area and kicks off the
|
// unref removes the long-held reference to the gl_area and kicks off the
|
||||||
@ -749,9 +766,18 @@ pub fn getTitleLabel(self: *Surface) ?*c.GtkWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newSplit(self: *Surface, direction: apprt.SplitDirection) !void {
|
pub fn newSplit(
|
||||||
|
self: *Surface,
|
||||||
|
direction: apprt.SplitDirection,
|
||||||
|
opts: struct { config: ?*configpkg.Config = null },
|
||||||
|
) !void {
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
_ = try Split.create(alloc, self, direction);
|
_ = try Split.create(
|
||||||
|
alloc,
|
||||||
|
self,
|
||||||
|
direction,
|
||||||
|
.{ .config = opts.config },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
|
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
|
||||||
@ -781,13 +807,18 @@ pub fn equalizeSplits(self: *const Surface) void {
|
|||||||
_ = top_split.equalize();
|
_ = top_split.equalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newTab(self: *Surface) !void {
|
pub fn newTab(self: *Surface, opts: struct {
|
||||||
|
config: ?*configpkg.Config = null,
|
||||||
|
}) !void {
|
||||||
const window = self.container.window() orelse {
|
const window = self.container.window() orelse {
|
||||||
log.info("surface cannot create new tab when not attached to a window", .{});
|
log.info("surface cannot create new tab when not attached to a window", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
try window.newTab(&self.core_surface);
|
try window.newTab(.{
|
||||||
|
.parent = &self.core_surface,
|
||||||
|
.config = opts.config,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasTabs(self: *const Surface) bool {
|
pub fn hasTabs(self: *const Surface) bool {
|
||||||
@ -1986,3 +2017,34 @@ fn translateMods(state: c.GdkModifierType) input.Mods {
|
|||||||
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
if (state & c.GDK_LOCK_MASK != 0) mods.caps_lock = true;
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn openEditorWithPath(
|
||||||
|
self: *Surface,
|
||||||
|
location: enum {
|
||||||
|
window,
|
||||||
|
tab,
|
||||||
|
split_right,
|
||||||
|
split_down,
|
||||||
|
},
|
||||||
|
path: []const u8,
|
||||||
|
) !void {
|
||||||
|
const alloc = self.app.core_app.alloc;
|
||||||
|
const config = try alloc.create(configpkg.Config);
|
||||||
|
config.* = try self.app.config.clone(alloc);
|
||||||
|
|
||||||
|
const editor = try internal_os.getEditor(alloc, config);
|
||||||
|
defer alloc.free(editor);
|
||||||
|
|
||||||
|
config.command = try std.fmt.allocPrint(
|
||||||
|
config._arena.?.allocator(),
|
||||||
|
"{s} {s}",
|
||||||
|
.{ editor, path },
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (location) {
|
||||||
|
.window => try self.app.newWindow(.{ .parent = &self.core_surface, .config = config }),
|
||||||
|
.tab => try self.newTab(.{ .config = config }),
|
||||||
|
.split_right => try self.newSplit(.right, .{ .config = config }),
|
||||||
|
.split_down => try self.newSplit(.down, .{ .config = config }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ const assert = std.debug.assert;
|
|||||||
const font = @import("../../font/main.zig");
|
const font = @import("../../font/main.zig");
|
||||||
const input = @import("../../input.zig");
|
const input = @import("../../input.zig");
|
||||||
const CoreSurface = @import("../../Surface.zig");
|
const CoreSurface = @import("../../Surface.zig");
|
||||||
|
const Config = @import("../../config/Config.zig");
|
||||||
|
|
||||||
const Surface = @import("Surface.zig");
|
const Surface = @import("Surface.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
@ -37,16 +38,21 @@ elem: Surface.Container.Elem,
|
|||||||
// can easily re-focus that terminal.
|
// can easily re-focus that terminal.
|
||||||
focus_child: *Surface,
|
focus_child: *Surface,
|
||||||
|
|
||||||
pub fn create(alloc: Allocator, window: *Window, parent_: ?*CoreSurface) !*Tab {
|
const Options = struct {
|
||||||
|
parent: ?*CoreSurface = null,
|
||||||
|
config: ?*Config = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(alloc: Allocator, window: *Window, opts: Options) !*Tab {
|
||||||
var tab = try alloc.create(Tab);
|
var tab = try alloc.create(Tab);
|
||||||
errdefer alloc.destroy(tab);
|
errdefer alloc.destroy(tab);
|
||||||
try tab.init(window, parent_);
|
try tab.init(window, opts);
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the tab, create a surface, and add it to the window. "self"
|
/// Initialize the tab, create a surface, and add it to the window. "self"
|
||||||
/// needs to be a stable pointer, since it is used for GTK events.
|
/// needs to be a stable pointer, since it is used for GTK events.
|
||||||
pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
pub fn init(self: *Tab, window: *Window, opts: Options) !void {
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.window = window,
|
.window = window,
|
||||||
.label_text = undefined,
|
.label_text = undefined,
|
||||||
@ -97,7 +103,8 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void {
|
|||||||
|
|
||||||
// Create the initial surface since all tabs start as a single non-split
|
// Create the initial surface since all tabs start as a single non-split
|
||||||
var surface = try Surface.create(window.app.core_app.alloc, window.app, .{
|
var surface = try Surface.create(window.app.core_app.alloc, window.app, .{
|
||||||
.parent = parent_,
|
.parent = opts.parent,
|
||||||
|
.config = opts.config,
|
||||||
});
|
});
|
||||||
errdefer surface.unref();
|
errdefer surface.unref();
|
||||||
surface.container = .{ .tab_ = self };
|
surface.container = .{ .tab_ = self };
|
||||||
|
@ -207,9 +207,12 @@ pub fn deinit(self: *Window) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new tab to this window.
|
/// Add a new tab to this window.
|
||||||
pub fn newTab(self: *Window, parent: ?*CoreSurface) !void {
|
pub fn newTab(self: *Window, opts: struct {
|
||||||
|
parent: ?*CoreSurface = null,
|
||||||
|
config: ?*configpkg.Config = null,
|
||||||
|
}) !void {
|
||||||
const alloc = self.app.core_app.alloc;
|
const alloc = self.app.core_app.alloc;
|
||||||
_ = try Tab.create(alloc, self, parent);
|
_ = try Tab.create(alloc, self, .{ .parent = opts.parent, .config = opts.config });
|
||||||
|
|
||||||
// TODO: When this is triggered through a GTK action, the new surface
|
// TODO: When this is triggered through a GTK action, the new surface
|
||||||
// redraws correctly. When it's triggered through keyboard shortcuts, it
|
// redraws correctly. When it's triggered through keyboard shortcuts, it
|
||||||
|
@ -459,6 +459,25 @@ palette: Palette = .{},
|
|||||||
/// instance is launched and the CLI args are respected.
|
/// instance is launched and the CLI args are respected.
|
||||||
command: ?[]const u8 = null,
|
command: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// A command to use to open/edit text files in a terminal window (using
|
||||||
|
/// something like Helix, Flow, Vim, NeoVim, Emacs, or Nano). If this is not set,
|
||||||
|
/// Ghostty will check the `EDITOR` environment variable for the command. If
|
||||||
|
/// the `EDITOR` environment variable is not set, Ghostty will fall back to `vi`
|
||||||
|
/// (similar to how many Linux/Unix systems operate).
|
||||||
|
///
|
||||||
|
/// This command will be used to open/edit files when Ghostty receives a signal
|
||||||
|
/// from the operating system to open a file. Currently implemented on the GTK
|
||||||
|
/// runtime only.
|
||||||
|
///
|
||||||
|
/// The command may contain additional arguments besides the path to the
|
||||||
|
/// editor's binary. The files that are to be opened will be added to the end of
|
||||||
|
/// the command. For example, if `editor` was set to `emacs -nx` and you tried
|
||||||
|
/// to open `README` and `hello.c` in your home directory, the final command
|
||||||
|
/// would look like
|
||||||
|
///
|
||||||
|
/// emacs -nx /home/user/README /home/user/hello.c
|
||||||
|
editor: ?[]const u8 = null,
|
||||||
|
|
||||||
/// If true, keep the terminal open after the command exits. Normally, the
|
/// If true, keep the terminal open after the command exits. Normally, the
|
||||||
/// terminal window closes when the running command (such as a shell) exits.
|
/// terminal window closes when the running command (such as a shell) exits.
|
||||||
/// With this true, the terminal window will stay open until any keypress is
|
/// With this true, the terminal window will stay open until any keypress is
|
||||||
|
@ -227,22 +227,56 @@ 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. The action
|
/// Write the entire scrollback into a temporary file. The action determines
|
||||||
/// determines what to do with the filepath. Valid values are:
|
/// what to do with the file. Valid values are:
|
||||||
///
|
///
|
||||||
/// - "paste": Paste the file path into the terminal.
|
/// - `paste`: Paste the file path into the terminal.
|
||||||
/// - "open": Open the file in the default OS editor for text files.
|
/// - `open`: Open the file in the default OS editor for text files.
|
||||||
|
/// - `edit_window`: Create a new window, and open the file in your editor.
|
||||||
|
/// - `edit_tab`: Create a new tab, and open the file in your editor.
|
||||||
|
/// - `edit_split_right`: Create a new split right, and open the file in your editor.
|
||||||
|
/// - `edit_split_down`: Create a new split down, and open the file in your editor.
|
||||||
///
|
///
|
||||||
|
/// `edit_window`, `edit_tab`, `edit_split_right`, and `edit_split_down` are
|
||||||
|
/// supported on GTK only.
|
||||||
|
///
|
||||||
|
/// See the configuration setting `editor` for more information on how
|
||||||
|
/// Ghostty determines your editor.
|
||||||
write_scrollback_file: WriteScreenAction,
|
write_scrollback_file: WriteScreenAction,
|
||||||
|
|
||||||
/// Same as write_scrollback_file but writes the full screen contents.
|
/// Write the full screen contents into a temporary file. The action
|
||||||
/// See write_scrollback_file for available values.
|
/// determines what to do with the file. Valid values are:
|
||||||
|
///
|
||||||
|
/// - `paste`: Paste the file path into the terminal.
|
||||||
|
/// - `open`: Open the file in the default OS editor for text files.
|
||||||
|
/// - `edit_window`: Create a new window, and open the file in your editor.
|
||||||
|
/// - `edit_tab`: Create a new tab, and open the file in your editor.
|
||||||
|
/// - `edit_split_right`: Create a new split right, and open the file in your editor.
|
||||||
|
/// - `edit_split_down`: Create a new split down, and open the file in your editor.
|
||||||
|
///
|
||||||
|
/// `edit_window`, `edit_tab`, `edit_split_right`, and `edit_split_down` are
|
||||||
|
/// supported on GTK only.
|
||||||
|
///
|
||||||
|
/// See the configuration setting `editor` for more information on how
|
||||||
|
/// Ghostty determines your editor.
|
||||||
write_screen_file: WriteScreenAction,
|
write_screen_file: WriteScreenAction,
|
||||||
|
|
||||||
/// Same as write_scrollback_file but writes the selected text.
|
/// Writes the selected text into a temporary file. If there is no selected
|
||||||
/// If there is no selected text this does nothing (it doesn't
|
/// text this action does nothing (it doesn't even create an empty file).
|
||||||
/// even create an empty file). See write_scrollback_file for
|
/// The action determines what to do with the file. Valid values are:
|
||||||
/// available values.
|
///
|
||||||
|
/// - `paste`: Paste the file path into the terminal.
|
||||||
|
/// - `open`: Open the file in the default OS editor for text files.
|
||||||
|
/// - `edit_window`: Create a new window, and open the file in your editor.
|
||||||
|
/// - `edit_tab`: Create a new tab, and open the file in your editor.
|
||||||
|
/// - `edit_split_right`: Create a new split right, and open the file in your editor.
|
||||||
|
/// - `edit_split_down`: Create a new split down, and open the file in your editor.
|
||||||
|
///
|
||||||
|
/// `edit_window`, `edit_tab`, `edit_split_right`, and `edit_split_down` are
|
||||||
|
/// supported on GTK only.
|
||||||
|
///
|
||||||
|
/// See the configuration setting `editor` for more information on how
|
||||||
|
/// Ghostty determines your editor.
|
||||||
write_selection_file: WriteScreenAction,
|
write_selection_file: WriteScreenAction,
|
||||||
|
|
||||||
/// Open a new window.
|
/// Open a new window.
|
||||||
@ -367,6 +401,10 @@ pub const Action = union(enum) {
|
|||||||
pub const WriteScreenAction = enum {
|
pub const WriteScreenAction = enum {
|
||||||
paste,
|
paste,
|
||||||
open,
|
open,
|
||||||
|
edit_window,
|
||||||
|
edit_tab,
|
||||||
|
edit_split_right,
|
||||||
|
edit_split_down,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extern because it is used in the embedded runtime ABI.
|
// Extern because it is used in the embedded runtime ABI.
|
||||||
|
17
src/os/editor.zig
Normal file
17
src/os/editor.zig
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Config = @import("../config.zig").Config;
|
||||||
|
|
||||||
|
pub fn getEditor(alloc: std.mem.Allocator, config: *const Config) ![]const u8 {
|
||||||
|
// figure out what our editor is
|
||||||
|
if (config.editor) |editor| return try alloc.dupe(u8, editor);
|
||||||
|
switch (builtin.os.tag) {
|
||||||
|
.windows => {
|
||||||
|
if (std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("EDITOR"))) |win_editor| {
|
||||||
|
return try std.unicode.utf16leToUtf8Alloc(alloc, win_editor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => if (std.posix.getenv("EDITOR")) |editor| return alloc.dupe(u8, editor),
|
||||||
|
}
|
||||||
|
return alloc.dupe(u8, "vi");
|
||||||
|
}
|
@ -13,6 +13,7 @@ const mouse = @import("mouse.zig");
|
|||||||
const openpkg = @import("open.zig");
|
const openpkg = @import("open.zig");
|
||||||
const pipepkg = @import("pipe.zig");
|
const pipepkg = @import("pipe.zig");
|
||||||
const resourcesdir = @import("resourcesdir.zig");
|
const resourcesdir = @import("resourcesdir.zig");
|
||||||
|
const editor = @import("editor.zig");
|
||||||
|
|
||||||
// Namespaces
|
// Namespaces
|
||||||
pub const cgroup = @import("cgroup.zig");
|
pub const cgroup = @import("cgroup.zig");
|
||||||
@ -41,3 +42,4 @@ pub const clickInterval = mouse.clickInterval;
|
|||||||
pub const open = openpkg.open;
|
pub const open = openpkg.open;
|
||||||
pub const pipe = pipepkg.pipe;
|
pub const pipe = pipepkg.pipe;
|
||||||
pub const resourcesDir = resourcesdir.resourcesDir;
|
pub const resourcesDir = resourcesdir.resourcesDir;
|
||||||
|
pub const getEditor = editor.getEditor;
|
||||||
|
Reference in New Issue
Block a user