diff --git a/include/ghostty.h b/include/ghostty.h index 6b058d78c..e48d7b512 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -350,6 +350,7 @@ typedef struct { double scale_factor; uint16_t font_size; const char *working_directory; + const char *command; } ghostty_surface_config_s; typedef void (*ghostty_runtime_wakeup_cb)(void *); diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist index a2066680c..4882d5b8c 100644 --- a/macos/Ghostty-Info.plist +++ b/macos/Ghostty-Info.plist @@ -4,6 +4,23 @@ CFBundleDocumentTypes + + CFBundleTypeExtensions + + command + tool + sh + zsh + csh + pl + + CFBundleTypeIconFile + AppIcon.icns + CFBundleTypeName + Terminal scripts + CFBundleTypeRole + Editor + CFBundleTypeName Folders diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 348b8aceb..1aa27387e 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -216,22 +216,23 @@ class AppDelegate: NSObject, // this way. var isDirectory = ObjCBool(true) guard FileManager.default.fileExists(atPath: filename, isDirectory: &isDirectory) else { return false } - guard isDirectory.boolValue else { - let alert = NSAlert() - alert.messageText = "Dropped File is Not a Directory" - alert.informativeText = "Ghostty can currently only open directory paths." - alert.addButton(withTitle: "OK") - alert.alertStyle = .warning - _ = alert.runModal() - return false - } - // Build our config + // Initialize the surface config which will be used to create the tab or window for the opened file. var config = Ghostty.SurfaceConfiguration() - config.workingDirectory = filename - - // Add a new tab or create a new window - terminalManager.newTab(withBaseConfig: config) + + if (isDirectory.boolValue) { + // When opening a directory, create a new tab in the main window with that as the working directory. + // If no windows exist, a new one will be created. + config.workingDirectory = filename + terminalManager.newTab(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 + config.workingDirectory = (filename as NSString).deletingLastPathComponent + terminalManager.newWindow(withBaseConfig: config) + } + return true } diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 0fb4c212d..6d3b0daf4 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -206,12 +206,16 @@ extension Ghostty { /// Explicit working directory to set var workingDirectory: String? = nil + + /// Explicit command to set + var command: String? = nil init() {} init(from config: ghostty_surface_config_s) { self.fontSize = config.font_size self.workingDirectory = String.init(cString: config.working_directory, encoding: .utf8) + self.command = String.init(cString: config.command, encoding: .utf8) } /// Returns the ghostty configuration for this surface configuration struct. The memory @@ -226,6 +230,9 @@ extension Ghostty { if let workingDirectory = workingDirectory { config.working_directory = (workingDirectory as NSString).utf8String } + if let command = command { + config.command = (command as NSString).utf8String + } return config } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index a88aa55b0..baffb02a8 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -253,6 +253,9 @@ pub const Surface = struct { /// The working directory to load into. working_directory: [*:0]const u8 = "", + + /// The command to run in the new surface. + command: [*:0]const u8 = "", }; /// This is the key event sent for ghostty_surface_key. @@ -326,6 +329,13 @@ pub const Surface = struct { config.@"working-directory" = wd; } + // If we have a command from the options then we set it. + const cm = std.mem.sliceTo(opts.command, 0); + if (cm.len > 0) { + // TODO: Maybe add some validation to this, like the working directory has? + config.command = cm; + } + // Initialize our surface right away. We're given a view that is // ready to use. try self.core_surface.init(