mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +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;
|
||||
ghostty_env_var_s* env_vars;
|
||||
size_t env_var_count;
|
||||
const char* initial_input;
|
||||
} ghostty_surface_config_s;
|
||||
|
||||
typedef struct {
|
||||
|
@ -384,10 +384,17 @@ class AppDelegate: NSObject,
|
||||
config.workingDirectory = filename
|
||||
_ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||
} else {
|
||||
// When opening a file, open a new window with that file as the command,
|
||||
// and its parent directory as the working directory.
|
||||
config.command = filename
|
||||
// When opening a file, we want to execute the file. To do this, we
|
||||
// don't override the command directly, because it won't load the
|
||||
// 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
|
||||
|
||||
_ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ func requestIntentPermission() async -> Bool {
|
||||
|
||||
|
||||
PermissionRequest.show(
|
||||
"org.mitchellh.ghostty.shortcutsPermission",
|
||||
"com.mitchellh.ghostty.shortcutsPermission",
|
||||
message: "Allow Shortcuts to interact with Ghostty?",
|
||||
allowDuration: .forever,
|
||||
rememberDuration: nil,
|
||||
|
@ -19,7 +19,7 @@ struct NewTerminalIntent: AppIntent {
|
||||
|
||||
@Parameter(
|
||||
title: "Command",
|
||||
description: "Command to execute instead of the default shell."
|
||||
description: "Command to execute within your configured shell.",
|
||||
)
|
||||
var command: String?
|
||||
|
||||
@ -60,7 +60,12 @@ struct NewTerminalIntent: AppIntent {
|
||||
let ghostty = appDelegate.ghostty
|
||||
|
||||
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 let url = workingDirectory?.fileURL {
|
||||
|
@ -422,6 +422,9 @@ extension Ghostty {
|
||||
/// Environment variables to set for the terminal
|
||||
var environmentVariables: [String: String] = [:]
|
||||
|
||||
/// Extra input to send as stdin
|
||||
var initialInput: String? = nil
|
||||
|
||||
init() {}
|
||||
|
||||
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 {
|
||||
var config = ghostty_surface_config_new()
|
||||
config.userdata = Unmanaged.passUnretained(view).toOpaque()
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
config.platform_tag = GHOSTTY_PLATFORM_MACOS
|
||||
config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s(
|
||||
nsview: Unmanaged.passUnretained(view).toOpaque()
|
||||
))
|
||||
config.scale_factor = NSScreen.main!.backingScaleFactor
|
||||
#elseif os(iOS)
|
||||
#elseif os(iOS)
|
||||
config.platform_tag = GHOSTTY_PLATFORM_IOS
|
||||
config.platform = ghostty_platform_u(ios: ghostty_platform_ios_s(
|
||||
uiview: Unmanaged.passUnretained(view).toOpaque()
|
||||
@ -466,9 +469,9 @@ extension Ghostty {
|
||||
// probably set this to some default, then modify the scale factor through
|
||||
// libghostty APIs when a UIView is attached to a window/scene. TODO.
|
||||
config.scale_factor = UIScreen.main.scale
|
||||
#else
|
||||
#error("unsupported target")
|
||||
#endif
|
||||
#else
|
||||
#error("unsupported target")
|
||||
#endif
|
||||
|
||||
// Zero is our default value that means to inherit the font size.
|
||||
config.font_size = fontSize ?? 0
|
||||
@ -480,27 +483,31 @@ extension Ghostty {
|
||||
return try command.withCString { cCommand in
|
||||
config.command = cCommand
|
||||
|
||||
// Convert dictionary to arrays for easier processing
|
||||
let keys = Array(environmentVariables.keys)
|
||||
let values = Array(environmentVariables.values)
|
||||
return try initialInput.withCString { cInput in
|
||||
config.initial_input = cInput
|
||||
|
||||
// Create C strings for all keys and values
|
||||
return try keys.withCStrings { keyCStrings in
|
||||
return try values.withCStrings { valueCStrings in
|
||||
// Create array of ghostty_env_var_s
|
||||
var envVars = Array<ghostty_env_var_s>()
|
||||
envVars.reserveCapacity(environmentVariables.count)
|
||||
for i in 0..<environmentVariables.count {
|
||||
envVars.append(ghostty_env_var_s(
|
||||
key: keyCStrings[i],
|
||||
value: valueCStrings[i]
|
||||
))
|
||||
}
|
||||
// Convert dictionary to arrays for easier processing
|
||||
let keys = Array(environmentVariables.keys)
|
||||
let values = Array(environmentVariables.values)
|
||||
|
||||
return try envVars.withUnsafeMutableBufferPointer { buffer in
|
||||
config.env_vars = buffer.baseAddress
|
||||
config.env_var_count = environmentVariables.count
|
||||
return try body(&config)
|
||||
// Create C strings for all keys and values
|
||||
return try keys.withCStrings { keyCStrings in
|
||||
return try values.withCStrings { valueCStrings in
|
||||
// Create array of ghostty_env_var_s
|
||||
var envVars = Array<ghostty_env_var_s>()
|
||||
envVars.reserveCapacity(environmentVariables.count)
|
||||
for i in 0..<environmentVariables.count {
|
||||
envVars.append(ghostty_env_var_s(
|
||||
key: keyCStrings[i],
|
||||
value: valueCStrings[i]
|
||||
))
|
||||
}
|
||||
|
||||
return try envVars.withUnsafeMutableBufferPointer { buffer in
|
||||
config.env_vars = buffer.baseAddress
|
||||
config.env_var_count = environmentVariables.count
|
||||
return try body(&config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -430,6 +430,9 @@ pub const Surface = struct {
|
||||
/// Extra environment variables to set for the surface.
|
||||
env_vars: ?[*]EnvVar = null,
|
||||
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 {
|
||||
@ -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
|
||||
// ready to use.
|
||||
try self.core_surface.init(
|
||||
|
Reference in New Issue
Block a user