Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
D0B1001CA1B2C3D4E5F60001 /* PaneDropRoutingSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1001DA1B2C3D4E5F60001 /* PaneDropRoutingSupport.swift */; };
D0B1001EA1B2C3D4E5F60001 /* BrowserPaneDropTargetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B1001FA1B2C3D4E5F60001 /* BrowserPaneDropTargetView.swift */; };
D0B10016A1B2C3D4E5F60001 /* FilePreviewTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10017A1B2C3D4E5F60001 /* FilePreviewTextEditor.swift */; };
D0B10024A1B2C3D4E5F60001 /* ViewZoomControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10025A1B2C3D4E5F60001 /* ViewZoomControls.swift */; };
D0B10018A1B2C3D4E5F60001 /* FileDropOverlayViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B10019A1B2C3D4E5F60001 /* FileDropOverlayViewTests.swift */; };
D7AB34300000000000000001 /* SidebarDropPlanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AB34300000000000000002 /* SidebarDropPlanner.swift */; };
D7AB34300000000000000003 /* SidebarBonsplitTabWorkspaceDropOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AB34300000000000000004 /* SidebarBonsplitTabWorkspaceDropOverlay.swift */; };
Expand Down Expand Up @@ -269,6 +270,7 @@
A50014F2 /* MarkdownPanelFileLinkResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F0 /* MarkdownPanelFileLinkResolver.swift */; };
A50014F3 /* MarkdownViewerAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F1 /* MarkdownViewerAssets.swift */; };
A50014F5 /* MarkdownWebRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F4 /* MarkdownWebRenderer.swift */; };
A50014F7 /* MarkdownTypographySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50014F6 /* MarkdownTypographySettings.swift */; };
A5001422 /* FilePreviewPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001423 /* FilePreviewPanel.swift */; };
A5001441A5001441A5001441 /* FileOpenSocketSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001440A5001440A5001440 /* FileOpenSocketSupport.swift */; };
A5001443A5001443A5001443 /* FilePreviewModeSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001442A5001442A5001442 /* FilePreviewModeSupport.swift */; };
Expand Down Expand Up @@ -684,6 +686,7 @@
D0B1001DA1B2C3D4E5F60001 /* PaneDropRoutingSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaneDropRoutingSupport.swift; sourceTree = "<group>"; };
D0B1001FA1B2C3D4E5F60001 /* BrowserPaneDropTargetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserPaneDropTargetView.swift; sourceTree = "<group>"; };
D0B10017A1B2C3D4E5F60001 /* FilePreviewTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/FilePreviewTextEditor.swift; sourceTree = "<group>"; };
D0B10025A1B2C3D4E5F60001 /* ViewZoomControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/ViewZoomControls.swift; sourceTree = "<group>"; };
A5001016 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
A5001017 /* ghostty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ghostty.h; sourceTree = "<group>"; };
A5001018 /* cmux-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "cmux-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -792,6 +795,7 @@
A50014F0 /* MarkdownPanelFileLinkResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownPanelFileLinkResolver.swift; sourceTree = "<group>"; };
A50014F1 /* MarkdownViewerAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownViewerAssets.swift; sourceTree = "<group>"; };
A50014F4 /* MarkdownWebRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownWebRenderer.swift; sourceTree = "<group>"; };
A50014F6 /* MarkdownTypographySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/MarkdownTypographySettings.swift; sourceTree = "<group>"; };
A5001423 /* FilePreviewPanel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/FilePreviewPanel.swift; sourceTree = "<group>"; };
A5001440A5001440A5001440 /* FileOpenSocketSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOpenSocketSupport.swift; sourceTree = "<group>"; };
A5001442A5001442A5001442 /* FilePreviewModeSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Panels/FilePreviewModeSupport.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1303,7 +1307,9 @@
A50014F0 /* MarkdownPanelFileLinkResolver.swift */,
A5001419 /* MarkdownPanelView.swift */,
A50014F4 /* MarkdownWebRenderer.swift */,
A50014F6 /* MarkdownTypographySettings.swift */,
A50014F1 /* MarkdownViewerAssets.swift */,
D0B10025A1B2C3D4E5F60001 /* ViewZoomControls.swift */,
A5001423 /* FilePreviewPanel.swift */,
D0B10017A1B2C3D4E5F60001 /* FilePreviewTextEditor.swift */,
A5001442A5001442A5001442 /* FilePreviewModeSupport.swift */,
Expand Down Expand Up @@ -1982,9 +1988,11 @@
A50014F2 /* MarkdownPanelFileLinkResolver.swift in Sources */,
A5001421 /* MarkdownPanelView.swift in Sources */,
A50014F5 /* MarkdownWebRenderer.swift in Sources */,
A50014F7 /* MarkdownTypographySettings.swift in Sources */,
A50014F3 /* MarkdownViewerAssets.swift in Sources */,
A5001422 /* FilePreviewPanel.swift in Sources */,
D0B10016A1B2C3D4E5F60001 /* FilePreviewTextEditor.swift in Sources */,
D0B10024A1B2C3D4E5F60001 /* ViewZoomControls.swift in Sources */,
A5001443A5001443A5001443 /* FilePreviewModeSupport.swift in Sources */,
A5001445A5001445A5001445 /* FilePreviewWorkspaceOpenSupport.swift in Sources */,
A5001447A5001447A5001447 /* FilePreviewMagnifyingPDFView.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"sourceLanguage": "en",
"version": "1.0",
"strings": {
"filePreview.pdf.zoomControls": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", "value": "Zoom Controls" } }, "ja": { "stringUnit": { "state": "translated", "value": "ズームコントロール" } } } },
"view.zoom.controls": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", "value": "View Zoom" } }, "ja": { "stringUnit": { "state": "translated", "value": "表示ズーム" } } } },
"cli.error.dismissNotificationSelector": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", "value": "dismiss-notification requires exactly one of --id or --all-read" } }, "ja": { "stringUnit": { "state": "translated", "value": "dismiss-notification には --id または --all-read のどちらか一方だけを指定してください" } } } },
"cli.error.markNotificationReadSelector": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", "value": "mark-notification-read requires exactly one selector: --id, --workspace, or --all" } }, "ja": { "stringUnit": { "state": "translated", "value": "mark-notification-read には --id、--workspace、--all のいずれか一つだけを指定してください" } } } },
"cli.error.markNotificationReadSurfaceRequiresWorkspace": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", "value": "--surface requires --workspace" } }, "ja": { "stringUnit": { "state": "translated", "value": "--surface には --workspace が必要です" } } } },
Expand Down
22 changes: 18 additions & 4 deletions Resources/markdown-viewer/shell.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,37 @@
}
.markdown-body {
background: transparent;
/* GitHub uses 16px on github.com for body text; keep that exact
metric so headings, line-height, and spacing match. */
font-size: 15px;
font-family: var(--cmux-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji");
/* cmux defaults to 15px for panel density, while cmux.json can
override the body and heading metrics below. */
font-size: var(--cmux-markdown-font-size, 15px);
/* The package sets max-width via wrapper class on github.com; we
let it span the panel and just cap it to a comfortable reading
width on very wide splits. */
max-width: 980px;
margin: 0 auto;
}
.markdown-body h1 { font-size: var(--cmux-markdown-h1-font-size, 2em); }
.markdown-body h2 { font-size: var(--cmux-markdown-h2-font-size, 1.5em); }
.markdown-body h3 { font-size: var(--cmux-markdown-h3-font-size, 1.25em); }
.markdown-body h4 { font-size: var(--cmux-markdown-h4-font-size, 1em); }
.markdown-body h5 { font-size: var(--cmux-markdown-h5-font-size, .875em); }
.markdown-body h6 { font-size: var(--cmux-markdown-h6-font-size, .85em); }
/* Fenced code blocks: github-markdown-css already styles `pre`; we
add highlight.js's hljs class so the syntax theme applies. */
.markdown-body pre code.hljs {
padding: 16px;
background: transparent;
font-size: 13.5px;
font-family: var(--cmux-markdown-code-block-font-family, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace);
font-size: var(--cmux-markdown-code-block-font-size, 13.5px);
line-height: 1.5;
}
.markdown-body pre,
.markdown-body pre code,
.cmux-frontmatter pre,
.cmux-frontmatter pre code.hljs {
font-family: var(--cmux-markdown-code-block-font-family, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace);
}
.markdown-body pre {
padding: 0; /* hljs class already pads */
overflow: auto;
Expand Down
47 changes: 37 additions & 10 deletions Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11989,16 +11989,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return true
}

if matchConfiguredShortcut(event: event, action: .browserZoomIn) {
return shortcutEventBrowserPanel(event)?.zoomIn() ?? false
}

if matchConfiguredShortcut(event: event, action: .browserZoomOut) {
return shortcutEventBrowserPanel(event)?.zoomOut() ?? false
}

if matchConfiguredShortcut(event: event, action: .browserZoomReset) {
return shortcutEventBrowserPanel(event)?.resetZoom() ?? false
if let viewZoomCommand = configuredViewZoomShortcutCommand(for: event) {
if shouldLetFocusedTerminalHandleViewZoomShortcut(event) {
return false
}
let didHandle = shortcutEventTabManager(event)?.performViewZoomCommand(viewZoomCommand) ?? false
return didHandle
}

if matchConfiguredShortcut(event: event, action: .findInDirectory) {
Expand Down Expand Up @@ -12094,6 +12090,37 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
return true
}

private func configuredViewZoomShortcutCommand(for event: NSEvent) -> ViewZoomCommand? {
if matchConfiguredShortcut(event: event, action: .browserZoomIn) {
return .zoomIn
}
if matchConfiguredShortcut(event: event, action: .browserZoomOut) {
return .zoomOut
}
if matchConfiguredShortcut(event: event, action: .browserZoomReset) {
return .reset
}
return ViewZoomControl.command(for: event)
}

private func shouldLetFocusedTerminalHandleViewZoomShortcut(_ event: NSEvent) -> Bool {
let targetWindow = resolvedShortcutEventWindow(event) ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow
guard let firstResponder = targetWindow?.firstResponder,
cmuxOwningGhosttyView(for: firstResponder) != nil else {
return false
}
return browserZoomShortcutAction(
flags: event.modifierFlags,
chars: event.charactersIgnoringModifiers ?? "",
keyCode: event.keyCode,
literalChars: event.characters
) != nil
}
Comment on lines +12106 to +12118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Terminal handoff check misses remapped zoom shortcuts

shouldLetFocusedTerminalHandleViewZoomShortcut only returns true when browserZoomShortcutAction(...) matches the literal key pattern. That bypasses custom-configured zoom bindings (which do resolve in configuredViewZoomShortcutCommand(for:)), so terminal-focused remapped zoom shortcuts can be incorrectly consumed by AppDelegate.

🔧 Suggested fix
 private func shouldLetFocusedTerminalHandleViewZoomShortcut(_ event: NSEvent) -> Bool {
     let targetWindow = resolvedShortcutEventWindow(event) ?? event.window ?? NSApp.keyWindow ?? NSApp.mainWindow
     guard let firstResponder = targetWindow?.firstResponder,
           cmuxOwningGhosttyView(for: firstResponder) != nil else {
         return false
     }
-    return browserZoomShortcutAction(
-        flags: event.modifierFlags,
-        chars: event.charactersIgnoringModifiers ?? "",
-        keyCode: event.keyCode,
-        literalChars: event.characters
-    ) != nil
+    return true
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate.swift` around lines 12106 - 12118, The focused-terminal
zoom handoff currently only checks browserZoomShortcutAction(...) which misses
user-remapped zoom shortcuts; update
shouldLetFocusedTerminalHandleViewZoomShortcut(_:) to also consult
configuredViewZoomShortcutCommand(for:) (using the same event data:
modifierFlags, charactersIgnoringModifiers, keyCode, literal chars) before
returning false, so if configuredViewZoomShortcutCommand(...) resolves to a zoom
command the method returns true and lets the terminal (cmuxOwningGhosttyView for
firstResponder / resolvedShortcutEventWindow) handle the shortcut.


private func shortcutEventTabManager(_ event: NSEvent) -> TabManager? {
preferredMainWindowContextForShortcutRouting(event: event)?.tabManager ?? tabManager
}

#if DEBUG
private func logBrowserZoomShortcutTrace(
stage: String,
Expand Down
30 changes: 18 additions & 12 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,7 @@ struct ContentView: View {
static let panelName = "panel.name"
static let panelIsBrowser = "panel.isBrowser"
static let panelIsTerminal = "panel.isTerminal"
static let panelSupportsViewZoom = "panel.supportsViewZoom"
static let panelHasPane = "panel.hasPane"
static let panelHasCustomName = "panel.hasCustomName"
static let panelShouldPin = "panel.shouldPin"
Expand Down Expand Up @@ -6046,10 +6047,15 @@ struct ContentView: View {
let workspace = panelContext.workspace
let panelId = panelContext.panelId
let panelIsTerminal = panelContext.panel.panelType == .terminal
let panelSupportsViewZoom =
panelContext.panel is BrowserPanel ||
panelContext.panel is MarkdownPanel ||
((panelContext.panel as? FilePreviewPanel)?.previewMode == .text)
snapshot.setBool(CommandPaletteContextKeys.hasFocusedPanel, true)
snapshot.setString(CommandPaletteContextKeys.panelName, panelDisplayName(workspace: workspace, panelId: panelId, fallback: panelContext.panel.displayTitle))
snapshot.setBool(CommandPaletteContextKeys.panelIsBrowser, panelContext.panel.panelType == .browser)
snapshot.setBool(CommandPaletteContextKeys.panelIsTerminal, panelIsTerminal)
snapshot.setBool(CommandPaletteContextKeys.panelSupportsViewZoom, panelSupportsViewZoom)
snapshot.setBool(CommandPaletteContextKeys.panelHasPane, workspace.paneId(forPanelId: panelId) != nil)
snapshot.setBool(CommandPaletteContextKeys.panelHasCustomName, workspace.panelCustomTitles[panelId] != nil)
snapshot.setBool(CommandPaletteContextKeys.panelShouldPin, !workspace.isPanelPinned(panelId))
Expand Down Expand Up @@ -6755,27 +6761,27 @@ struct ContentView: View {
CommandPaletteCommandContribution(
commandId: "palette.browserZoomIn",
title: constant(String(localized: "command.browserZoomIn.title", defaultValue: "Zoom In")),
subtitle: browserPanelSubtitle,
keywords: ["browser", "zoom", "in"],
when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) }
subtitle: panelSubtitle,
keywords: ["view", "zoom", "in", "browser", "markdown", "text"],
when: { $0.bool(CommandPaletteContextKeys.panelSupportsViewZoom) }
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.browserZoomOut",
title: constant(String(localized: "command.browserZoomOut.title", defaultValue: "Zoom Out")),
subtitle: browserPanelSubtitle,
keywords: ["browser", "zoom", "out"],
when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) }
subtitle: panelSubtitle,
keywords: ["view", "zoom", "out", "browser", "markdown", "text"],
when: { $0.bool(CommandPaletteContextKeys.panelSupportsViewZoom) }
)
)
contributions.append(
CommandPaletteCommandContribution(
commandId: "palette.browserZoomReset",
title: constant(String(localized: "command.browserZoomReset.title", defaultValue: "Actual Size")),
subtitle: browserPanelSubtitle,
keywords: ["browser", "zoom", "reset", "actual size"],
when: { $0.bool(CommandPaletteContextKeys.panelIsBrowser) }
subtitle: panelSubtitle,
keywords: ["view", "zoom", "reset", "actual size", "browser", "markdown", "text"],
when: { $0.bool(CommandPaletteContextKeys.panelSupportsViewZoom) }
)
)
contributions.append(
Expand Down Expand Up @@ -7438,17 +7444,17 @@ struct ContentView: View {
}
}
registry.register(commandId: "palette.browserZoomIn") {
if !tabManager.zoomInFocusedBrowser() {
if !tabManager.performViewZoomCommand(.zoomIn) {
NSSound.beep()
}
}
registry.register(commandId: "palette.browserZoomOut") {
if !tabManager.zoomOutFocusedBrowser() {
if !tabManager.performViewZoomCommand(.zoomOut) {
NSSound.beep()
}
}
registry.register(commandId: "palette.browserZoomReset") {
if !tabManager.resetZoomFocusedBrowser() {
if !tabManager.performViewZoomCommand(.reset) {
NSSound.beep()
}
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/KeyboardShortcutContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ extension KeyboardShortcutSettings.Action {
return .rightSidebarFocus
case .renameTab, .renameWorkspace:
return .nonBrowserPanel
case .browserBack, .browserForward, .browserReload, .toggleBrowserDeveloperTools, .showBrowserJavaScriptConsole,
.browserZoomIn, .browserZoomOut, .browserZoomReset:
case .browserBack, .browserForward, .browserReload, .toggleBrowserDeveloperTools, .showBrowserJavaScriptConsole:
return .browserPanel
default:
return .application
Expand Down
16 changes: 16 additions & 0 deletions Sources/KeyboardShortcutSettingsFileStore+Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ extension CmuxSettingsFileStore {
"reactGrabVersion": ReactGrabSettings.defaultVersion,
],
],
[
"markdown": [
"fontFamily": MarkdownTypographySettings.defaultFontFamily,
"fontSize": MarkdownTypographySettings.defaultFontSize,
"headingSizes": [
"h1": MarkdownTypographySettings.defaultHeadingSizes.h1,
"h2": MarkdownTypographySettings.defaultHeadingSizes.h2,
"h3": MarkdownTypographySettings.defaultHeadingSizes.h3,
"h4": MarkdownTypographySettings.defaultHeadingSizes.h4,
"h5": MarkdownTypographySettings.defaultHeadingSizes.h5,
"h6": MarkdownTypographySettings.defaultHeadingSizes.h6,
],
"codeBlockFontFamily": MarkdownTypographySettings.defaultCodeBlockFontFamily,
"codeBlockFontSize": MarkdownTypographySettings.defaultCodeBlockFontSize,
],
],
[
"shortcuts": [
"bindings": shortcutsBindings,
Expand Down
Loading