diff --git a/Sources/AppBundle/config/Config.swift b/Sources/AppBundle/config/Config.swift index 6b9519a93..dd9d0e672 100644 --- a/Sources/AppBundle/config/Config.swift +++ b/Sources/AppBundle/config/Config.swift @@ -44,6 +44,7 @@ struct Config: ConvenienceCopyable { var startAtLogin: Bool = false var autoReloadConfig: Bool = false var automaticallyUnhideMacosHiddenApps: Bool = false + var configEditorAppPath: String? = nil var accordionPadding: Int = 30 var enableNormalizationOppositeOrientationForNestedContainers: Bool = true var persistentWorkspaces: OrderedSet = [] diff --git a/Sources/AppBundle/config/parseConfig.swift b/Sources/AppBundle/config/parseConfig.swift index 460279c45..72c7ae595 100644 --- a/Sources/AppBundle/config/parseConfig.swift +++ b/Sources/AppBundle/config/parseConfig.swift @@ -112,6 +112,7 @@ private let configParser: [String: any ParserProtocol] = [ "start-at-login": Parser(\.startAtLogin, parseBool), "auto-reload-config": Parser(\.autoReloadConfig, parseBool), "automatically-unhide-macos-hidden-apps": Parser(\.automaticallyUnhideMacosHiddenApps, parseBool), + "config-editor-app-path": Parser(\.configEditorAppPath, parseOptionalString), "accordion-padding": Parser(\.accordionPadding, parseInt), persistentWorkspacesKey: Parser(\.persistentWorkspaces, parsePersistentWorkspaces), "exec-on-workspace-change": Parser(\.execOnWorkspaceChange, parseArrayOfStrings), @@ -253,6 +254,10 @@ func parseString(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> Par raw.string.orFailure(expectedActualTypeError(expected: .string, actual: raw.type, backtrace)) } +func parseOptionalString(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedToml { + parseString(raw, backtrace).map { Optional($0) } +} + func parseSimpleType(_ raw: TOMLValueConvertible) -> T? { (raw.int as? T) ?? (raw.string as? T) ?? (raw.bool as? T) } diff --git a/Sources/AppBundle/ui/MenuBar.swift b/Sources/AppBundle/ui/MenuBar.swift index f87270697..a2b112e6f 100644 --- a/Sources/AppBundle/ui/MenuBar.swift +++ b/Sources/AppBundle/ui/MenuBar.swift @@ -110,8 +110,16 @@ func shortcutGroup(label: some View, content: some View) -> some View { } } -func getTextEditorToOpenConfig() -> URL { - NSWorkspace.shared.urlForApplication(toOpen: findCustomConfigUrl().urlOrNil ?? defaultConfigUrl)? +@MainActor func getTextEditorToOpenConfig() -> URL { + if let configEditorAppPath = config.configEditorAppPath { + let customEditorUrl = URL(filePath: configEditorAppPath) + if FileManager.default.fileExists(atPath: customEditorUrl.path), + customEditorUrl.pathExtension == "app" || customEditorUrl.lastPathComponent.hasSuffix(".app") + { + return customEditorUrl + } + } + return NSWorkspace.shared.urlForApplication(toOpen: findCustomConfigUrl().urlOrNil ?? defaultConfigUrl)? .takeIf { $0.lastPathComponent != "Xcode.app" } // Blacklist Xcode. It is too heavy to open plain text files ?? URL(filePath: "/System/Applications/TextEdit.app") } diff --git a/Sources/AppBundleTests/config/ConfigTest.swift b/Sources/AppBundleTests/config/ConfigTest.swift index e8c47f99b..6be256278 100644 --- a/Sources/AppBundleTests/config/ConfigTest.swift +++ b/Sources/AppBundleTests/config/ConfigTest.swift @@ -449,4 +449,25 @@ final class ConfigTest: XCTestCase { assertEquals(colemakConfig.keyMapping, KeyMapping(preset: .colemak, rawKeyNotationToKeyCode: [:])) assertEquals(colemakConfig.keyMapping.resolve()["f"], .e) } + + func testConfigEditorAppPath() { + let (config1, errors1) = parseConfig( + """ + config-editor-app-path = '/Applications/Visual Studio Code.app' + """, + ) + assertEquals(errors1, []) + assertEquals(config1.configEditorAppPath, "/Applications/Visual Studio Code.app") + + let (config2, errors2) = parseConfig("") + assertEquals(errors2, []) + assertEquals(config2.configEditorAppPath, nil) + + let (_, errors3) = parseConfig( + """ + config-editor-app-path = 123 + """, + ) + assertEquals(errors3.descriptions, ["config-editor-app-path: Expected type is \'string\'. But actual type is \'integer\'"]) + } } diff --git a/docs/config-examples/default-config.toml b/docs/config-examples/default-config.toml index 7d2fca4da..838fad01b 100644 --- a/docs/config-examples/default-config.toml +++ b/docs/config-examples/default-config.toml @@ -16,6 +16,11 @@ start-at-login = false # After setting this to true, reload once manually to start the auto-reloading auto-reload-config = false +# Override which editor should be used when opening the config file via the menu bar +# By default, macOS will decide which editor to open with TextEdit as the fallback +# Set this to the full path of your preferred editor application. +# config-editor-app-path = '/Applications/Visual Studio Code.app' + # Normalizations. See: https://nikitabobko.github.io/AeroSpace/guide#normalization enable-normalization-flatten-containers = true enable-normalization-opposite-orientation-for-nested-containers = true