mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 17:26:09 +03:00
macOS: Run scripts using stdin rather than executing directly (#7654)
Fixes #7647 See #7647 for context. This commit works by extending the `input` work introduced in #7652 to libghostty so that the macOS can take advantage of it. At that point, it's just the macOS utilizing `input` in order to set the command and `exit` up similar to Terminal and iTerm2. This applies both to files opened directly by Ghostty as well as the App Intent to run a command in a new terminal.
This commit is contained in:
@ -413,6 +413,7 @@ typedef struct {
|
|||||||
const char* command;
|
const char* command;
|
||||||
ghostty_env_var_s* env_vars;
|
ghostty_env_var_s* env_vars;
|
||||||
size_t env_var_count;
|
size_t env_var_count;
|
||||||
|
const char* initial_input;
|
||||||
} ghostty_surface_config_s;
|
} ghostty_surface_config_s;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -384,10 +384,17 @@ class AppDelegate: NSObject,
|
|||||||
config.workingDirectory = filename
|
config.workingDirectory = filename
|
||||||
_ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
_ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||||
} else {
|
} else {
|
||||||
// When opening a file, open a new window with that file as the command,
|
// When opening a file, we want to execute the file. To do this, we
|
||||||
// and its parent directory as the working directory.
|
// don't override the command directly, because it won't load the
|
||||||
config.command = filename
|
// profile/rc files for the shell, which is super important on macOS
|
||||||
|
// due to things like Homebrew. Instead, we set the command to
|
||||||
|
// `<filename>; exit` which is what Terminal and iTerm2 do.
|
||||||
|
config.initialInput = "\(filename); exit\n"
|
||||||
|
|
||||||
|
// Set the parent directory to our working directory so that relative
|
||||||
|
// paths in scripts work.
|
||||||
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
||||||
|
|
||||||
_ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
_ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ func requestIntentPermission() async -> Bool {
|
|||||||
|
|
||||||
|
|
||||||
PermissionRequest.show(
|
PermissionRequest.show(
|
||||||
"org.mitchellh.ghostty.shortcutsPermission",
|
"com.mitchellh.ghostty.shortcutsPermission",
|
||||||
message: "Allow Shortcuts to interact with Ghostty?",
|
message: "Allow Shortcuts to interact with Ghostty?",
|
||||||
allowDuration: .forever,
|
allowDuration: .forever,
|
||||||
rememberDuration: nil,
|
rememberDuration: nil,
|
||||||
|
@ -19,7 +19,7 @@ struct NewTerminalIntent: AppIntent {
|
|||||||
|
|
||||||
@Parameter(
|
@Parameter(
|
||||||
title: "Command",
|
title: "Command",
|
||||||
description: "Command to execute instead of the default shell."
|
description: "Command to execute within your configured shell.",
|
||||||
)
|
)
|
||||||
var command: String?
|
var command: String?
|
||||||
|
|
||||||
@ -60,7 +60,12 @@ struct NewTerminalIntent: AppIntent {
|
|||||||
let ghostty = appDelegate.ghostty
|
let ghostty = appDelegate.ghostty
|
||||||
|
|
||||||
var config = Ghostty.SurfaceConfiguration()
|
var config = Ghostty.SurfaceConfiguration()
|
||||||
config.command = command
|
|
||||||
|
// We don't run command as "command" and instead use "initialInput" so
|
||||||
|
// that we can get all the login scripts to setup things like PATH.
|
||||||
|
if let command {
|
||||||
|
config.initialInput = "\(command); exit\n"
|
||||||
|
}
|
||||||
|
|
||||||
// If we were given a working directory then open that directory
|
// If we were given a working directory then open that directory
|
||||||
if let url = workingDirectory?.fileURL {
|
if let url = workingDirectory?.fileURL {
|
||||||
|
@ -422,6 +422,9 @@ extension Ghostty {
|
|||||||
/// Environment variables to set for the terminal
|
/// Environment variables to set for the terminal
|
||||||
var environmentVariables: [String: String] = [:]
|
var environmentVariables: [String: String] = [:]
|
||||||
|
|
||||||
|
/// Extra input to send as stdin
|
||||||
|
var initialInput: String? = nil
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
init(from config: ghostty_surface_config_s) {
|
init(from config: ghostty_surface_config_s) {
|
||||||
@ -450,13 +453,13 @@ extension Ghostty {
|
|||||||
func withCValue<T>(view: SurfaceView, _ body: (inout ghostty_surface_config_s) throws -> T) rethrows -> T {
|
func withCValue<T>(view: SurfaceView, _ body: (inout ghostty_surface_config_s) throws -> T) rethrows -> T {
|
||||||
var config = ghostty_surface_config_new()
|
var config = ghostty_surface_config_new()
|
||||||
config.userdata = Unmanaged.passUnretained(view).toOpaque()
|
config.userdata = Unmanaged.passUnretained(view).toOpaque()
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
config.platform_tag = GHOSTTY_PLATFORM_MACOS
|
config.platform_tag = GHOSTTY_PLATFORM_MACOS
|
||||||
config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s(
|
config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s(
|
||||||
nsview: Unmanaged.passUnretained(view).toOpaque()
|
nsview: Unmanaged.passUnretained(view).toOpaque()
|
||||||
))
|
))
|
||||||
config.scale_factor = NSScreen.main!.backingScaleFactor
|
config.scale_factor = NSScreen.main!.backingScaleFactor
|
||||||
#elseif os(iOS)
|
#elseif os(iOS)
|
||||||
config.platform_tag = GHOSTTY_PLATFORM_IOS
|
config.platform_tag = GHOSTTY_PLATFORM_IOS
|
||||||
config.platform = ghostty_platform_u(ios: ghostty_platform_ios_s(
|
config.platform = ghostty_platform_u(ios: ghostty_platform_ios_s(
|
||||||
uiview: Unmanaged.passUnretained(view).toOpaque()
|
uiview: Unmanaged.passUnretained(view).toOpaque()
|
||||||
@ -466,9 +469,9 @@ extension Ghostty {
|
|||||||
// probably set this to some default, then modify the scale factor through
|
// probably set this to some default, then modify the scale factor through
|
||||||
// libghostty APIs when a UIView is attached to a window/scene. TODO.
|
// libghostty APIs when a UIView is attached to a window/scene. TODO.
|
||||||
config.scale_factor = UIScreen.main.scale
|
config.scale_factor = UIScreen.main.scale
|
||||||
#else
|
#else
|
||||||
#error("unsupported target")
|
#error("unsupported target")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Zero is our default value that means to inherit the font size.
|
// Zero is our default value that means to inherit the font size.
|
||||||
config.font_size = fontSize ?? 0
|
config.font_size = fontSize ?? 0
|
||||||
@ -480,6 +483,9 @@ extension Ghostty {
|
|||||||
return try command.withCString { cCommand in
|
return try command.withCString { cCommand in
|
||||||
config.command = cCommand
|
config.command = cCommand
|
||||||
|
|
||||||
|
return try initialInput.withCString { cInput in
|
||||||
|
config.initial_input = cInput
|
||||||
|
|
||||||
// Convert dictionary to arrays for easier processing
|
// Convert dictionary to arrays for easier processing
|
||||||
let keys = Array(environmentVariables.keys)
|
let keys = Array(environmentVariables.keys)
|
||||||
let values = Array(environmentVariables.values)
|
let values = Array(environmentVariables.values)
|
||||||
@ -508,6 +514,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if canImport(AppKit)
|
#if canImport(AppKit)
|
||||||
/// When changing the split state, or going full screen (native or non), the terminal view
|
/// When changing the split state, or going full screen (native or non), the terminal view
|
||||||
|
@ -430,6 +430,9 @@ pub const Surface = struct {
|
|||||||
/// Extra environment variables to set for the surface.
|
/// Extra environment variables to set for the surface.
|
||||||
env_vars: ?[*]EnvVar = null,
|
env_vars: ?[*]EnvVar = null,
|
||||||
env_var_count: usize = 0,
|
env_var_count: usize = 0,
|
||||||
|
|
||||||
|
/// Input to send to the command after it is started.
|
||||||
|
initial_input: ?[*:0]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||||
@ -510,6 +513,19 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have an initial input then we set it.
|
||||||
|
if (opts.initial_input) |c_input| {
|
||||||
|
const alloc = config.arenaAlloc();
|
||||||
|
config.input.list.clearRetainingCapacity();
|
||||||
|
try config.input.list.append(
|
||||||
|
alloc,
|
||||||
|
.{ .raw = try alloc.dupeZ(u8, std.mem.sliceTo(
|
||||||
|
c_input,
|
||||||
|
0,
|
||||||
|
)) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize our surface right away. We're given a view that is
|
// Initialize our surface right away. We're given a view that is
|
||||||
// ready to use.
|
// ready to use.
|
||||||
try self.core_surface.init(
|
try self.core_surface.init(
|
||||||
|
Reference in New Issue
Block a user