From db799d53e6313c0e1d861fd708e984f6c708bba0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:21:13 -0700 Subject: [PATCH 1/9] macos: UI for configuration errors --- macos/Ghostty.xcodeproj/project.pbxproj | 12 +++++ macos/Sources/AppDelegate.swift | 12 +++-- .../Features/Settings/ConfigurationErrors.xib | 31 ++++++++++++ .../ConfigurationErrorsController.swift | 49 +++++++++++++++++++ .../Settings/ConfigurationErrorsView.swift | 47 ++++++++++++++++++ macos/Sources/Ghostty/AppState.swift | 15 ++++++ 6 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 macos/Sources/Features/Settings/ConfigurationErrors.xib create mode 100644 macos/Sources/Features/Settings/ConfigurationErrorsController.swift create mode 100644 macos/Sources/Features/Settings/ConfigurationErrorsView.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 1a9d21e83..bf290f623 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -26,6 +26,9 @@ A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; }; A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; }; A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; }; + A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */; }; + A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */; }; + A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */; }; A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; }; A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; }; A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; }; @@ -55,6 +58,9 @@ A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; }; A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = ""; }; + A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = ""; }; + A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsController.swift; sourceTree = ""; }; + A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsView.swift; sourceTree = ""; }; A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = ""; }; A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = ""; }; A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; @@ -112,6 +118,9 @@ isa = PBXGroup; children = ( A59444F629A2ED5200725BBA /* SettingsView.swift */, + A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */, + A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */, + A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */, ); path = Settings; sourceTree = ""; @@ -249,6 +258,7 @@ files = ( A545D1A22A5772CE006E0AE4 /* shell-integration in Resources */, A5A1F8852A489D6800D1E8BC /* terminfo in Resources */, + A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */, A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */, 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */, ); @@ -265,6 +275,7 @@ 85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */, A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, + A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */, A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */, @@ -272,6 +283,7 @@ A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */, A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */, A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */, + A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */, A55685E029A03A9F004303CE /* AppError.swift in Sources */, A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 4ff6dcb51..7a5b41b30 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -60,12 +60,12 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp "ApplePressAndHoldEnabled": false, ]) - // Sync our menu shortcuts with our Ghostty config - syncMenuShortcuts() - // Let's launch our first window. // TODO: we should detect if we restored windows and if so not launch a new window. windowManager.addInitialWindow() + + // Initial config loading + configDidReload(ghostty) } func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @@ -180,7 +180,13 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp //MARK: - GhosttyAppStateDelegate func configDidReload(_ state: Ghostty.AppState) { + // Config could change keybindings, so update our menu syncMenuShortcuts() + + // If we have configuration errors, we need to show them. + let c = ConfigurationErrorsController.sharedInstance + c.model.errors = state.configErrors() + if (c.model.errors.count > 0) { c.showWindow(self) } } //MARK: - Dock Menu diff --git a/macos/Sources/Features/Settings/ConfigurationErrors.xib b/macos/Sources/Features/Settings/ConfigurationErrors.xib new file mode 100644 index 000000000..d3f94b14d --- /dev/null +++ b/macos/Sources/Features/Settings/ConfigurationErrors.xib @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift new file mode 100644 index 000000000..fba594dde --- /dev/null +++ b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift @@ -0,0 +1,49 @@ +import Foundation +import Cocoa +import SwiftUI +import Combine + +class ConfigurationErrorsController: NSWindowController, NSWindowDelegate { + /// Singleton for the errors view. + static let sharedInstance = ConfigurationErrorsController() + + override var windowNibName: NSNib.Name? { "ConfigurationErrors" } + + /// The data model for this view. Update this directly and the associated view will be updated, too. + let model = ConfigurationErrorsView.Model() + + private var cancellable: AnyCancellable? + + //MARK: - NSWindowController + + override func windowWillLoad() { + shouldCascadeWindows = false + + if let c = cancellable { c.cancel() } + cancellable = model.objectWillChange.sink { + if (self.model.errors.count == 0) { + self.window?.close() + } + } + } + + override func windowDidLoad() { + guard let window = window else { return } + window.center() + window.level = .popUpMenu + window.contentView = NSHostingView(rootView: ConfigurationErrorsView(model: model)) + window.makeKeyAndOrderFront(self) + } + + //MARK: - NSWindowDelegate + + func windowWillClose(_ notification: Notification) { + guard let window = window else { return } + window.contentView = nil + + if let cancellable = cancellable { + cancellable.cancel() + self.cancellable = nil + } + } +} diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift new file mode 100644 index 000000000..5f15e6a78 --- /dev/null +++ b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift @@ -0,0 +1,47 @@ +import SwiftUI + +struct ConfigurationErrorsView: View { + class Model: ObservableObject { + @Published var errors: [String] = [] + } + + var model: Model + + var body: some View { + VStack { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.yellow) + .font(.system(size: 52)) + .padding() + .frame(alignment: .center) + + Text(""" + ^[\(model.errors.count) error was](inflect: true) found while loading the configuration. \ + Please review the errors below and reload your configuration. + """) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + + GeometryReader { geo in + ScrollView { + VStack { + ForEach(model.errors, id: \.self) { error in + Text(error) + .lineLimit(nil) + .font(.system(size: 12).monospaced()) + .textSelection(.enabled) + .padding(.all) + .frame(maxWidth: .infinity, alignment: .topLeading) + } + Spacer() + } + .frame(height: geo.size.height) + .background(Color.white) + } + } + } + .frame(minWidth: 480, maxWidth: 960, minHeight: 270) + } +} diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 40225b2b1..9870fbcd6 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -145,6 +145,21 @@ extension Ghostty { return cfg } + /// Returns the configuration errors (if any). + func configErrors() -> [String] { + guard let cfg = self.config else { return [] } + + var errors: [String] = []; + let errCount = ghostty_config_errors_count(cfg) + for i in 0.. Date: Mon, 11 Sep 2023 13:28:48 -0700 Subject: [PATCH 2/9] macos: fix some error window styling --- .../Features/Settings/ConfigurationErrorsView.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift index 5f15e6a78..212354597 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift @@ -17,7 +17,7 @@ struct ConfigurationErrorsView: View { .frame(alignment: .center) Text(""" - ^[\(model.errors.count) error was](inflect: true) found while loading the configuration. \ + ^[\(model.errors.count) error(s) were](inflect: true) found while loading the configuration. \ Please review the errors below and reload your configuration. """) .frame(maxWidth: .infinity, alignment: .leading) @@ -26,18 +26,19 @@ struct ConfigurationErrorsView: View { GeometryReader { geo in ScrollView { - VStack { + VStack(alignment: .leading) { ForEach(model.errors, id: \.self) { error in Text(error) .lineLimit(nil) .font(.system(size: 12).monospaced()) .textSelection(.enabled) - .padding(.all) .frame(maxWidth: .infinity, alignment: .topLeading) } + Spacer() } - .frame(height: geo.size.height) + .padding(.all) + .frame(minHeight: geo.size.height) .background(Color.white) } } From 4c0871b6dcdb33b1fd18d3c3a2f93e724ceec33f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:39:29 -0700 Subject: [PATCH 3/9] macos: don't clear contentview for configuration on close --- .../Features/Settings/ConfigurationErrorsController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift index fba594dde..6e7a809cb 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift @@ -38,9 +38,6 @@ class ConfigurationErrorsController: NSWindowController, NSWindowDelegate { //MARK: - NSWindowDelegate func windowWillClose(_ notification: Notification) { - guard let window = window else { return } - window.contentView = nil - if let cancellable = cancellable { cancellable.cancel() self.cancellable = nil From 711e3a5043be8bf0df3056d478151d33858aec22 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:49:20 -0700 Subject: [PATCH 4/9] macos: add reload configuration to the Ghostty menu bar --- macos/Sources/AppDelegate.swift | 7 ++++++ macos/Sources/Ghostty/AppState.swift | 33 +++++++++++++++------------- macos/Sources/MainMenu.xib | 7 ++++++ src/config/Config.zig | 5 +++++ 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 7a5b41b30..3f81349bc 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -15,6 +15,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp @Published var confirmQuit: Bool = false /// Various menu items so that we can programmatically sync the keyboard shortcut with the Ghostty config. + @IBOutlet private var menuReloadConfig: NSMenuItem? @IBOutlet private var menuQuit: NSMenuItem? @IBOutlet private var menuNewWindow: NSMenuItem? @@ -127,6 +128,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp private func syncMenuShortcuts() { guard ghostty.config != nil else { return } + syncMenuShortcut(action: "reload_config", menuItem: self.menuReloadConfig) syncMenuShortcut(action: "quit", menuItem: self.menuQuit) syncMenuShortcut(action: "new_window", menuItem: self.menuNewWindow) @@ -186,6 +188,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp // If we have configuration errors, we need to show them. let c = ConfigurationErrorsController.sharedInstance c.model.errors = state.configErrors() + Self.logger.warning("TEST did reload, count=\(c.model.errors.count)") if (c.model.errors.count > 0) { c.showWindow(self) } } @@ -202,6 +205,10 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp //MARK: - IB Actions + @IBAction func reloadConfig(_ sender: Any?) { + ghostty.reloadConfig() + } + @IBAction func newWindow(_ sender: Any?) { windowManager.newWindow() diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 9870fbcd6..20a815ebc 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -171,6 +171,22 @@ extension Ghostty { NSApplication.shared.terminate(nil) } + func reloadConfig() { + guard let newConfig = Self.reloadConfig() else { + AppDelegate.logger.warning("failed to reload configuration") + return + } + + // Assign the new config. This will automatically free the old config. + // It is safe to free the old config from within this function call. + config = newConfig + + // If we have a delegate, notify. + if let delegate = delegate { + delegate.configDidReload(self) + } + } + /// Request that the given surface is closed. This will trigger the full normal surface close event /// cycle which will call our close surface callback. func requestClose(surface: ghostty_surface_t) { @@ -286,22 +302,9 @@ extension Ghostty { } static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { - guard let newConfig = AppState.reloadConfig() else { - AppDelegate.logger.warning("failed to reload configuration") - return nil - } - - // Assign the new config. This will automatically free the old config. - // It is safe to free the old config from within this function call. let state = Unmanaged.fromOpaque(userdata!).takeUnretainedValue() - state.config = newConfig - - // If we have a delegate, notify. - if let delegate = state.delegate { - delegate.configDidReload(state) - } - - return newConfig + state.reloadConfig() + return state.config } static func wakeup(_ userdata: UnsafeMutableRawPointer?) { diff --git a/macos/Sources/MainMenu.xib b/macos/Sources/MainMenu.xib index a65d314f2..7166541e7 100644 --- a/macos/Sources/MainMenu.xib +++ b/macos/Sources/MainMenu.xib @@ -23,6 +23,7 @@ + @@ -47,6 +48,12 @@ + + + + + + diff --git a/src/config/Config.zig b/src/config/Config.zig index 5ca6b27cd..e5b1dddcf 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -588,6 +588,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .q, .mods = .{ .super = true } }, .{ .quit = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .comma, .mods = .{ .super = true, .shift = true } }, + .{ .reload_config = {} }, + ); try result.keybind.set.put( alloc, From 6f7fdf002f9a89adcf7a35daca09af28f4f98644 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:52:47 -0700 Subject: [PATCH 5/9] macos: hook up proper data events --- .../Features/Settings/ConfigurationErrorsController.swift | 4 ++-- macos/Sources/Features/Settings/ConfigurationErrorsView.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift index 6e7a809cb..9e4b2a185 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift @@ -20,8 +20,8 @@ class ConfigurationErrorsController: NSWindowController, NSWindowDelegate { shouldCascadeWindows = false if let c = cancellable { c.cancel() } - cancellable = model.objectWillChange.sink { - if (self.model.errors.count == 0) { + cancellable = model.$errors.sink { newValue in + if (newValue.count == 0) { self.window?.close() } } diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift index 212354597..7d3aa4d83 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift @@ -5,7 +5,7 @@ struct ConfigurationErrorsView: View { @Published var errors: [String] = [] } - var model: Model + @ObservedObject var model: Model var body: some View { VStack { From b7f4c1d707e96e4545cbe092e6d354aec7e28153 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:55:06 -0700 Subject: [PATCH 6/9] config: unify some error types for now --- src/cli_args.zig | 2 +- src/config/Config.zig | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/cli_args.zig b/src/cli_args.zig index fc056f43c..7729548d5 100644 --- a/src/cli_args.zig +++ b/src/cli_args.zig @@ -90,7 +90,7 @@ pub fn parse(comptime T: type, alloc: Allocator, dst: *T, iter: anytype) !void { error.InvalidField => try dst._errors.add(arena_alloc, .{ .message = try std.fmt.allocPrintZ( arena_alloc, - "unknown field: {s}", + "{s}: unknown field", .{key}, ), }), diff --git a/src/config/Config.zig b/src/config/Config.zig index e5b1dddcf..c8a952e03 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1145,10 +1145,6 @@ pub const Color = struct { g: u8, b: u8, - pub const Error = error{ - InvalidFormat, - }; - /// Convert this to the terminal RGB struct pub fn toTerminalRGB(self: Color) terminal.color.RGB { return .{ .r = self.r, .g = self.g, .b = self.b }; @@ -1175,7 +1171,7 @@ pub const Color = struct { const trimmed = if (input.len != 0 and input[0] == '#') input[1..] else input; // We expect exactly 6 for RRGGBB - if (trimmed.len != 6) return Error.InvalidFormat; + if (trimmed.len != 6) return error.InvalidValue; // Parse the colors two at a time. var result: Color = undefined; @@ -1214,17 +1210,13 @@ pub const Palette = struct { /// The actual value that is updated as we parse. value: terminal.color.Palette = terminal.color.default, - pub const Error = error{ - InvalidFormat, - }; - pub fn parseCLI( self: *Self, input: ?[]const u8, ) !void { const value = input orelse return error.ValueRequired; const eqlIdx = std.mem.indexOf(u8, value, "=") orelse - return Error.InvalidFormat; + return error.InvalidValue; const key = try std.fmt.parseInt(u8, value[0..eqlIdx], 10); const rgb = try Color.parseCLI(value[eqlIdx + 1 ..]); @@ -1326,14 +1318,14 @@ pub const RepeatableFontVariation = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void { const input = input_ orelse return error.ValueRequired; - const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidFormat; + const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue; const whitespace = " \t"; const key = std.mem.trim(u8, input[0..eql_idx], whitespace); const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace); - if (key.len != 4) return error.InvalidFormat; + if (key.len != 4) return error.InvalidValue; try self.list.append(alloc, .{ .id = fontpkg.face.Variation.Id.init(@ptrCast(key.ptr)), - .value = std.fmt.parseFloat(f64, value) catch return error.InvalidFormat, + .value = std.fmt.parseFloat(f64, value) catch return error.InvalidValue, }); } From ffd181f10dea8901f7d6f43f5a3559942f2e0209 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 13:59:03 -0700 Subject: [PATCH 7/9] macos: don't steal focus for config errors --- macos/Sources/AppDelegate.swift | 7 +++++-- .../Features/Settings/ConfigurationErrorsController.swift | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 3f81349bc..6785c3a30 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -188,8 +188,11 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp // If we have configuration errors, we need to show them. let c = ConfigurationErrorsController.sharedInstance c.model.errors = state.configErrors() - Self.logger.warning("TEST did reload, count=\(c.model.errors.count)") - if (c.model.errors.count > 0) { c.showWindow(self) } + if (c.model.errors.count > 0) { + if (c.window == nil || !c.window!.isVisible) { + c.showWindow(self) + } + } } //MARK: - Dock Menu diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift index 9e4b2a185..fc74a2aad 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsController.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsController.swift @@ -32,7 +32,6 @@ class ConfigurationErrorsController: NSWindowController, NSWindowDelegate { window.center() window.level = .popUpMenu window.contentView = NSHostingView(rootView: ConfigurationErrorsView(model: model)) - window.makeKeyAndOrderFront(self) } //MARK: - NSWindowDelegate From 42bbcbfb9b592d9918b1a31f1d1b694a0d6f1eda Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:13:02 -0700 Subject: [PATCH 8/9] macos: add a reload configuration button --- .../Features/Settings/ConfigurationErrorsView.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift index 7d3aa4d83..4ad5cf169 100644 --- a/macos/Sources/Features/Settings/ConfigurationErrorsView.swift +++ b/macos/Sources/Features/Settings/ConfigurationErrorsView.swift @@ -42,7 +42,18 @@ struct ConfigurationErrorsView: View { .background(Color.white) } } + + HStack { + Spacer() + Button("Reload Configuration") { reloadConfig() } + .padding([.bottom, .trailing]) + } } .frame(minWidth: 480, maxWidth: 960, minHeight: 270) } + + private func reloadConfig() { + guard let delegate = NSApplication.shared.delegate as? AppDelegate else { return } + delegate.reloadConfig(nil) + } } From 2f0905b60c4609fb1b7a18f22699f59c9bfdd140 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:20:31 -0700 Subject: [PATCH 9/9] macos: reload config MUST go through Zig core --- include/ghostty.h | 1 + macos/Sources/Ghostty/AppState.swift | 36 +++++++++++++++------------- src/apprt/embedded.zig | 8 +++++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index ed66824ed..56b1fe5dd 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -315,6 +315,7 @@ void ghostty_app_free(ghostty_app_t); bool ghostty_app_tick(ghostty_app_t); void *ghostty_app_userdata(ghostty_app_t); void ghostty_app_keyboard_changed(ghostty_app_t); +void ghostty_app_reload_config(ghostty_app_t); ghostty_surface_config_s ghostty_surface_config_new(); diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 20a815ebc..c714a4f40 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -52,7 +52,7 @@ extension Ghostty { } // Initialize the global configuration. - guard let cfg = Self.reloadConfig() else { + guard let cfg = Self.loadConfig() else { readiness = .error return } @@ -109,7 +109,7 @@ extension Ghostty { } /// Initializes a new configuration and loads all the values. - static func reloadConfig() -> ghostty_config_t? { + static func loadConfig() -> ghostty_config_t? { // Initialize the global configuration. guard let cfg = ghostty_config_new() else { AppDelegate.logger.critical("ghostty_config_new failed") @@ -172,19 +172,8 @@ extension Ghostty { } func reloadConfig() { - guard let newConfig = Self.reloadConfig() else { - AppDelegate.logger.warning("failed to reload configuration") - return - } - - // Assign the new config. This will automatically free the old config. - // It is safe to free the old config from within this function call. - config = newConfig - - // If we have a delegate, notify. - if let delegate = delegate { - delegate.configDidReload(self) - } + guard let app = self.app else { return } + ghostty_app_reload_config(app) } /// Request that the given surface is closed. This will trigger the full normal surface close event @@ -302,9 +291,22 @@ extension Ghostty { } static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { + guard let newConfig = Self.loadConfig() else { + AppDelegate.logger.warning("failed to reload configuration") + return nil + } + + // Assign the new config. This will automatically free the old config. + // It is safe to free the old config from within this function call. let state = Unmanaged.fromOpaque(userdata!).takeUnretainedValue() - state.reloadConfig() - return state.config + state.config = newConfig + + // If we have a delegate, notify. + if let delegate = state.delegate { + delegate.configDidReload(state) + } + + return newConfig } static func wakeup(_ userdata: UnsafeMutableRawPointer?) { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 650bee842..e3475cc82 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -779,6 +779,14 @@ pub const CAPI = struct { }; } + /// Reload the configuration. + export fn ghostty_app_reload_config(v: *App) void { + _ = v.reloadConfig() catch |err| { + log.err("error reloading config err={}", .{err}); + return; + }; + } + /// Returns initial surface options. export fn ghostty_surface_config_new() apprt.Surface.Options { return .{};