Add Ghostty terminal theme picker in Settings#699
Add Ghostty terminal theme picker in Settings#699lcamargof wants to merge 1 commit intomanaflow-ai:mainfrom
Conversation
|
@lcamargof is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis pull request implements terminal theme selection for Ghostty by adding theme enumeration, CMUX override loading, and a Settings UI picker. A new Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Settings as Settings UI
participant Config as GhosttyConfig
participant App as GhosttyApp
participant C as Ghostty C API
User->>Settings: Select theme in picker
Settings->>Settings: Update terminalTheme (AppStorage)
Settings->>Config: Invalidate load cache
Settings->>App: Reload configuration
App->>Config: Call availableThemeNames()
Config-->>App: Return theme list
App->>App: loadCmuxThemeOverrideIfNeeded()
App->>App: Create temp config file<br/>(theme = selected_name)
App->>C: ghostty_config_load_file(temp_path)
C-->>App: Config loaded
App->>App: Delete temp file
App-->>User: Theme applied
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
Sources/GhosttyConfig.swift (1)
412-449: Consider filtering out directories from theme enumeration.
FileManager.contentsOfDirectory(atPath:)returns both files and subdirectories. If a user has a subdirectory in their themes folder (e.g., for organization or backups), it will appear as a selectable theme option but fail to load.🔧 Optional fix to filter directories
for dir in themeDirs { guard let entries = try? fm.contentsOfDirectory(atPath: dir) else { continue } for entry in entries { guard !entry.hasPrefix("."), !seen.contains(entry) else { continue } + let fullPath = (dir as NSString).appendingPathComponent(entry) + var isDirectory: ObjCBool = false + guard fm.fileExists(atPath: fullPath, isDirectory: &isDirectory), !isDirectory.boolValue else { continue } seen.insert(entry) names.append(entry) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/GhosttyConfig.swift` around lines 412 - 449, In availableThemeNames, when iterating entries from FileManager.contentsOfDirectory(atPath:), filter out non-directory entries so only directories are treated as themes; for each entry build the full path (e.g., dir + "/" + entry) and use FileManager.fileExists(atPath:isDirectory:) or attributesOfItem to check isDirectory, skipping entries that are not directories or are symbolic links to non-directories; keep the existing deduplication with seen and sorting logic intact (change only the loop that processes entries).Sources/GhosttyTerminalView.swift (1)
623-634: Consider improving error visibility and theme name validation.Two observations:
Silent error swallowing: The empty
catch {}makes it difficult to diagnose failures when the theme override doesn't apply. Consider at minimum logging the error in DEBUG builds.Theme name interpolation: The theme name is directly interpolated into the config content. If a malicious or malformed value is stored in UserDefaults (e.g., containing newlines), it could inject additional config directives.
♻️ Suggested improvements
private func loadCmuxThemeOverrideIfNeeded(_ config: ghostty_config_t) { guard let themeName = TerminalThemeSettings.effectiveThemeName() else { return } + // Validate theme name doesn't contain characters that could break config format + guard !themeName.contains(where: { $0.isNewline || $0 == "\"" || $0 == "\\" }) else { + `#if` DEBUG + Self.initLog("loadCmuxThemeOverrideIfNeeded: invalid theme name characters") + `#endif` + return + } let tmpPath = "/tmp/cmux-theme-override-\(UUID().uuidString).conf" let content = "theme = \(themeName)\n" do { try content.write(toFile: tmpPath, atomically: true, encoding: .utf8) tmpPath.withCString { path in ghostty_config_load_file(config, path) } try? FileManager.default.removeItem(atPath: tmpPath) - } catch {} + } catch { + `#if` DEBUG + Self.initLog("loadCmuxThemeOverrideIfNeeded: failed to write temp config: \(error)") + `#endif` + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/GhosttyTerminalView.swift` around lines 623 - 634, The loadCmuxThemeOverrideIfNeeded function silently swallows errors and injects themeName without validation; update it to validate/sanitize TerminalThemeSettings.effectiveThemeName() (e.g., reject or escape newlines and other control characters) before composing content, and replace the empty catch with a debug-only log that records the caught error and tmpPath (use `#if` DEBUG and a logger/NSLog) while still attempting to remove the tmp file via FileManager.default.removeItem(atPath:). Also ensure ghostty_config_load_file is only called with the sanitized tmpPath/content.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Sources/GhosttyConfig.swift`:
- Around line 412-449: In availableThemeNames, when iterating entries from
FileManager.contentsOfDirectory(atPath:), filter out non-directory entries so
only directories are treated as themes; for each entry build the full path
(e.g., dir + "/" + entry) and use FileManager.fileExists(atPath:isDirectory:) or
attributesOfItem to check isDirectory, skipping entries that are not directories
or are symbolic links to non-directories; keep the existing deduplication with
seen and sorting logic intact (change only the loop that processes entries).
In `@Sources/GhosttyTerminalView.swift`:
- Around line 623-634: The loadCmuxThemeOverrideIfNeeded function silently
swallows errors and injects themeName without validation; update it to
validate/sanitize TerminalThemeSettings.effectiveThemeName() (e.g., reject or
escape newlines and other control characters) before composing content, and
replace the empty catch with a debug-only log that records the caught error and
tmpPath (use `#if` DEBUG and a logger/NSLog) while still attempting to remove the
tmp file via FileManager.default.removeItem(atPath:). Also ensure
ghostty_config_load_file is only called with the sanitized tmpPath/content.
Greptile SummaryAdds a "Terminal Theme" picker to Settings that overrides the user's Ghostty config theme setting.
Confidence Score: 5/5
Important Files Changed
Last reviewed commit: 38c5c1d |
| for entry in entries { | ||
| guard !entry.hasPrefix("."), !seen.contains(entry) else { continue } | ||
| seen.insert(entry) | ||
| names.append(entry) | ||
| } |
There was a problem hiding this comment.
Directory entries could appear in the theme picker since contentsOfDirectory returns both files and directories. Consider filtering to only include files:
| for entry in entries { | |
| guard !entry.hasPrefix("."), !seen.contains(entry) else { continue } | |
| seen.insert(entry) | |
| names.append(entry) | |
| } | |
| for entry in entries { | |
| guard !entry.hasPrefix("."), !seen.contains(entry) else { continue } | |
| let fullPath = (dir as NSString).appendingPathComponent(entry) | |
| var isDirectory: ObjCBool = false | |
| guard fm.fileExists(atPath: fullPath, isDirectory: &isDirectory), !isDirectory.boolValue else { continue } | |
| seen.insert(entry) | |
| names.append(entry) | |
| } |
Summary
~/.config/ghostty/themes)themesetting; "Default" falls back to user's ghostty configHow it works
TerminalThemeSettingsstores a single theme name in UserDefaults. On config load, if a theme is set, it writes a temptheme = <name>config file and loads it viaghostty_config_load_file— overriding whatever the user's ghostty config specifies.Test plan
Test
Summary by CodeRabbit