mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #429 from mitchellh/macos-config-err
macos: show configuration errors in GUI
This commit is contained in:
@ -315,6 +315,7 @@ void ghostty_app_free(ghostty_app_t);
|
|||||||
bool ghostty_app_tick(ghostty_app_t);
|
bool ghostty_app_tick(ghostty_app_t);
|
||||||
void *ghostty_app_userdata(ghostty_app_t);
|
void *ghostty_app_userdata(ghostty_app_t);
|
||||||
void ghostty_app_keyboard_changed(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();
|
ghostty_surface_config_s ghostty_surface_config_new();
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
||||||
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
|
||||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
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 */; };
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDB29B8009000646FDA /* SplitView.swift */; };
|
||||||
A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.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 */; };
|
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; };
|
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 = "<group>"; };
|
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||||
|
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConfigurationErrors.xib; sourceTree = "<group>"; };
|
||||||
|
A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsController.swift; sourceTree = "<group>"; };
|
||||||
|
A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationErrorsView.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
A5CEAFDB29B8009000646FDA /* SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = "<group>"; };
|
||||||
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||||
@ -112,6 +118,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A59444F629A2ED5200725BBA /* SettingsView.swift */,
|
A59444F629A2ED5200725BBA /* SettingsView.swift */,
|
||||||
|
A5CDF1902AAF9A5800513312 /* ConfigurationErrors.xib */,
|
||||||
|
A5CDF1922AAF9E0800513312 /* ConfigurationErrorsController.swift */,
|
||||||
|
A5CDF1942AAFA19600513312 /* ConfigurationErrorsView.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -249,6 +258,7 @@
|
|||||||
files = (
|
files = (
|
||||||
A545D1A22A5772CE006E0AE4 /* shell-integration in Resources */,
|
A545D1A22A5772CE006E0AE4 /* shell-integration in Resources */,
|
||||||
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
|
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */,
|
||||||
|
A5CDF1912AAF9A5800513312 /* ConfigurationErrors.xib in Resources */,
|
||||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */,
|
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */,
|
||||||
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
|
857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */,
|
||||||
);
|
);
|
||||||
@ -265,6 +275,7 @@
|
|||||||
85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */,
|
85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */,
|
||||||
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
|
||||||
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
|
||||||
|
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
||||||
A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */,
|
A5FECBD729D1FC3900022361 /* PrimaryView.swift in Sources */,
|
||||||
@ -272,6 +283,7 @@
|
|||||||
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */,
|
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */,
|
||||||
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */,
|
||||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||||
|
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
|
||||||
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
|
||||||
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */,
|
A5FECBD929D2010400022361 /* WindowAccessor.swift in Sources */,
|
||||||
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */,
|
||||||
|
@ -15,6 +15,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
@Published var confirmQuit: Bool = false
|
@Published var confirmQuit: Bool = false
|
||||||
|
|
||||||
/// Various menu items so that we can programmatically sync the keyboard shortcut with the Ghostty config.
|
/// 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 menuQuit: NSMenuItem?
|
||||||
|
|
||||||
@IBOutlet private var menuNewWindow: NSMenuItem?
|
@IBOutlet private var menuNewWindow: NSMenuItem?
|
||||||
@ -60,12 +61,12 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
"ApplePressAndHoldEnabled": false,
|
"ApplePressAndHoldEnabled": false,
|
||||||
])
|
])
|
||||||
|
|
||||||
// Sync our menu shortcuts with our Ghostty config
|
|
||||||
syncMenuShortcuts()
|
|
||||||
|
|
||||||
// Let's launch our first window.
|
// Let's launch our first window.
|
||||||
// TODO: we should detect if we restored windows and if so not launch a new window.
|
// TODO: we should detect if we restored windows and if so not launch a new window.
|
||||||
windowManager.addInitialWindow()
|
windowManager.addInitialWindow()
|
||||||
|
|
||||||
|
// Initial config loading
|
||||||
|
configDidReload(ghostty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||||
@ -127,6 +128,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
private func syncMenuShortcuts() {
|
private func syncMenuShortcuts() {
|
||||||
guard ghostty.config != nil else { return }
|
guard ghostty.config != nil else { return }
|
||||||
|
|
||||||
|
syncMenuShortcut(action: "reload_config", menuItem: self.menuReloadConfig)
|
||||||
syncMenuShortcut(action: "quit", menuItem: self.menuQuit)
|
syncMenuShortcut(action: "quit", menuItem: self.menuQuit)
|
||||||
|
|
||||||
syncMenuShortcut(action: "new_window", menuItem: self.menuNewWindow)
|
syncMenuShortcut(action: "new_window", menuItem: self.menuNewWindow)
|
||||||
@ -180,7 +182,17 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
//MARK: - GhosttyAppStateDelegate
|
//MARK: - GhosttyAppStateDelegate
|
||||||
|
|
||||||
func configDidReload(_ state: Ghostty.AppState) {
|
func configDidReload(_ state: Ghostty.AppState) {
|
||||||
|
// Config could change keybindings, so update our menu
|
||||||
syncMenuShortcuts()
|
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) {
|
||||||
|
if (c.window == nil || !c.window!.isVisible) {
|
||||||
|
c.showWindow(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Dock Menu
|
//MARK: - Dock Menu
|
||||||
@ -196,6 +208,10 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
|
|||||||
|
|
||||||
//MARK: - IB Actions
|
//MARK: - IB Actions
|
||||||
|
|
||||||
|
@IBAction func reloadConfig(_ sender: Any?) {
|
||||||
|
ghostty.reloadConfig()
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func newWindow(_ sender: Any?) {
|
@IBAction func newWindow(_ sender: Any?) {
|
||||||
windowManager.newWindow()
|
windowManager.newWindow()
|
||||||
|
|
||||||
|
31
macos/Sources/Features/Settings/ConfigurationErrors.xib
Normal file
31
macos/Sources/Features/Settings/ConfigurationErrors.xib
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="ConfigurationErrorsController" customModule="Ghostty" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="window" destination="QvC-M9-y7g" id="H7g-uf-37u"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<window title="Configuration Errors" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" userLabel="Configuration Errors">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||||
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
|
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="3008" height="1667"/>
|
||||||
|
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="-2" id="vqU-UH-2Ks"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="127.5" y="137.5"/>
|
||||||
|
</window>
|
||||||
|
</objects>
|
||||||
|
</document>
|
@ -0,0 +1,45 @@
|
|||||||
|
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.$errors.sink { newValue in
|
||||||
|
if (newValue.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - NSWindowDelegate
|
||||||
|
|
||||||
|
func windowWillClose(_ notification: Notification) {
|
||||||
|
if let cancellable = cancellable {
|
||||||
|
cancellable.cancel()
|
||||||
|
self.cancellable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConfigurationErrorsView: View {
|
||||||
|
class Model: ObservableObject {
|
||||||
|
@Published var errors: [String] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObservedObject 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(s) were](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(alignment: .leading) {
|
||||||
|
ForEach(model.errors, id: \.self) { error in
|
||||||
|
Text(error)
|
||||||
|
.lineLimit(nil)
|
||||||
|
.font(.system(size: 12).monospaced())
|
||||||
|
.textSelection(.enabled)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.frame(minHeight: geo.size.height)
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
@ -52,7 +52,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the global configuration.
|
// Initialize the global configuration.
|
||||||
guard let cfg = Self.reloadConfig() else {
|
guard let cfg = Self.loadConfig() else {
|
||||||
readiness = .error
|
readiness = .error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes a new configuration and loads all the values.
|
/// 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.
|
// Initialize the global configuration.
|
||||||
guard let cfg = ghostty_config_new() else {
|
guard let cfg = ghostty_config_new() else {
|
||||||
AppDelegate.logger.critical("ghostty_config_new failed")
|
AppDelegate.logger.critical("ghostty_config_new failed")
|
||||||
@ -145,6 +145,21 @@ extension Ghostty {
|
|||||||
return cfg
|
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..<errCount {
|
||||||
|
let err = ghostty_config_get_error(cfg, UInt32(i))
|
||||||
|
let message = String(cString: err.message)
|
||||||
|
errors.append(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
func appTick() {
|
func appTick() {
|
||||||
guard let app = self.app else { return }
|
guard let app = self.app else { return }
|
||||||
|
|
||||||
@ -156,6 +171,11 @@ extension Ghostty {
|
|||||||
NSApplication.shared.terminate(nil)
|
NSApplication.shared.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reloadConfig() {
|
||||||
|
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
|
/// Request that the given surface is closed. This will trigger the full normal surface close event
|
||||||
/// cycle which will call our close surface callback.
|
/// cycle which will call our close surface callback.
|
||||||
func requestClose(surface: ghostty_surface_t) {
|
func requestClose(surface: ghostty_surface_t) {
|
||||||
@ -271,7 +291,7 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
|
||||||
guard let newConfig = AppState.reloadConfig() else {
|
guard let newConfig = Self.loadConfig() else {
|
||||||
AppDelegate.logger.warning("failed to reload configuration")
|
AppDelegate.logger.warning("failed to reload configuration")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
<outlet property="menuPaste" destination="i27-pK-umN" id="ICc-X2-gV3"/>
|
<outlet property="menuPaste" destination="i27-pK-umN" id="ICc-X2-gV3"/>
|
||||||
<outlet property="menuPreviousSplit" destination="Lic-px-1wg" id="Rto-CG-yRe"/>
|
<outlet property="menuPreviousSplit" destination="Lic-px-1wg" id="Rto-CG-yRe"/>
|
||||||
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
|
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
|
||||||
|
<outlet property="menuReloadConfig" destination="KKH-XX-5py" id="Wvp-7J-wqX"/>
|
||||||
<outlet property="menuSelectSplitAbove" destination="0yU-hC-8xF" id="aPc-lS-own"/>
|
<outlet property="menuSelectSplitAbove" destination="0yU-hC-8xF" id="aPc-lS-own"/>
|
||||||
<outlet property="menuSelectSplitBelow" destination="QDz-d9-CBr" id="FsH-Dq-jij"/>
|
<outlet property="menuSelectSplitBelow" destination="QDz-d9-CBr" id="FsH-Dq-jij"/>
|
||||||
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
|
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
|
||||||
@ -47,6 +48,12 @@
|
|||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
|
||||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
|
||||||
|
<menuItem title="Reload Configuration" id="KKH-XX-5py">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="reloadConfig:" target="bbz-4X-AYv" id="h5x-tu-Izk"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
|
||||||
<menuItem title="Hide Ghostty" keyEquivalent="h" id="Olw-nP-bQN">
|
<menuItem title="Hide Ghostty" keyEquivalent="h" id="Olw-nP-bQN">
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -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.
|
/// Returns initial surface options.
|
||||||
export fn ghostty_surface_config_new() apprt.Surface.Options {
|
export fn ghostty_surface_config_new() apprt.Surface.Options {
|
||||||
return .{};
|
return .{};
|
||||||
|
@ -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, .{
|
error.InvalidField => try dst._errors.add(arena_alloc, .{
|
||||||
.message = try std.fmt.allocPrintZ(
|
.message = try std.fmt.allocPrintZ(
|
||||||
arena_alloc,
|
arena_alloc,
|
||||||
"unknown field: {s}",
|
"{s}: unknown field",
|
||||||
.{key},
|
.{key},
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
@ -588,6 +588,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
.{ .key = .q, .mods = .{ .super = true } },
|
.{ .key = .q, .mods = .{ .super = true } },
|
||||||
.{ .quit = {} },
|
.{ .quit = {} },
|
||||||
);
|
);
|
||||||
|
try result.keybind.set.put(
|
||||||
|
alloc,
|
||||||
|
.{ .key = .comma, .mods = .{ .super = true, .shift = true } },
|
||||||
|
.{ .reload_config = {} },
|
||||||
|
);
|
||||||
|
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
@ -1140,10 +1145,6 @@ pub const Color = struct {
|
|||||||
g: u8,
|
g: u8,
|
||||||
b: u8,
|
b: u8,
|
||||||
|
|
||||||
pub const Error = error{
|
|
||||||
InvalidFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Convert this to the terminal RGB struct
|
/// Convert this to the terminal RGB struct
|
||||||
pub fn toTerminalRGB(self: Color) terminal.color.RGB {
|
pub fn toTerminalRGB(self: Color) terminal.color.RGB {
|
||||||
return .{ .r = self.r, .g = self.g, .b = self.b };
|
return .{ .r = self.r, .g = self.g, .b = self.b };
|
||||||
@ -1170,7 +1171,7 @@ pub const Color = struct {
|
|||||||
const trimmed = if (input.len != 0 and input[0] == '#') input[1..] else input;
|
const trimmed = if (input.len != 0 and input[0] == '#') input[1..] else input;
|
||||||
|
|
||||||
// We expect exactly 6 for RRGGBB
|
// 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.
|
// Parse the colors two at a time.
|
||||||
var result: Color = undefined;
|
var result: Color = undefined;
|
||||||
@ -1209,17 +1210,13 @@ pub const Palette = struct {
|
|||||||
/// The actual value that is updated as we parse.
|
/// The actual value that is updated as we parse.
|
||||||
value: terminal.color.Palette = terminal.color.default,
|
value: terminal.color.Palette = terminal.color.default,
|
||||||
|
|
||||||
pub const Error = error{
|
|
||||||
InvalidFormat,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn parseCLI(
|
pub fn parseCLI(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
input: ?[]const u8,
|
input: ?[]const u8,
|
||||||
) !void {
|
) !void {
|
||||||
const value = input orelse return error.ValueRequired;
|
const value = input orelse return error.ValueRequired;
|
||||||
const eqlIdx = std.mem.indexOf(u8, value, "=") orelse
|
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 key = try std.fmt.parseInt(u8, value[0..eqlIdx], 10);
|
||||||
const rgb = try Color.parseCLI(value[eqlIdx + 1 ..]);
|
const rgb = try Color.parseCLI(value[eqlIdx + 1 ..]);
|
||||||
@ -1321,14 +1318,14 @@ pub const RepeatableFontVariation = struct {
|
|||||||
|
|
||||||
pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void {
|
pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void {
|
||||||
const input = input_ orelse return error.ValueRequired;
|
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 whitespace = " \t";
|
||||||
const key = std.mem.trim(u8, input[0..eql_idx], whitespace);
|
const key = std.mem.trim(u8, input[0..eql_idx], whitespace);
|
||||||
const value = std.mem.trim(u8, input[eql_idx + 1 ..], 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, .{
|
try self.list.append(alloc, .{
|
||||||
.id = fontpkg.face.Variation.Id.init(@ptrCast(key.ptr)),
|
.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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user