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
952 changes: 952 additions & 0 deletions Resources/Localizable.xcstrings

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Sources/CmuxSettingsJSONPathSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ extension CmuxSettingsFileStore {
"app.appIcon",
"app.menuBarOnly",
"app.newWorkspacePlacement",
"app.newSurfacePlacement",
"app.workspaceInheritWorkingDirectory",
"app.minimalMode",
"app.keepWorkspaceOpenWhenClosingLastSurface",
Expand Down
1 change: 1 addition & 0 deletions Sources/KeyboardShortcutSettingsFileStore+Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ extension CmuxSettingsFileStore {
"appIcon": AppIconSettings.defaultMode.rawValue,
"menuBarOnly": MenuBarOnlySettings.defaultMenuBarOnly,
"newWorkspacePlacement": WorkspacePlacementSettings.defaultPlacement.rawValue,
"newSurfacePlacement": SurfacePlacementSettings.defaultPlacement.rawValue,
"workspaceInheritWorkingDirectory": WorkspaceWorkingDirectoryInheritanceSettings.defaultValue,
"minimalMode": false,
"keepWorkspaceOpenWhenClosingLastSurface": !LastSurfaceCloseShortcutSettings.defaultValue,
Expand Down
7 changes: 7 additions & 0 deletions Sources/KeyboardShortcutSettingsFileStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,13 @@ final class CmuxSettingsFileStore {
}
snapshot.managedUserDefaults[WorkspacePlacementSettings.placementKey] = .string(placement.rawValue)
}
if let raw = jsonString(section["newSurfacePlacement"]) {
guard let placement = NewSurfacePlacement(rawValue: raw) else {
logInvalid("app.newSurfacePlacement", sourcePath: sourcePath)
return
}
snapshot.managedUserDefaults[SurfacePlacementSettings.placementKey] = .string(placement.rawValue)
}
if let value = jsonBool(section["workspaceInheritWorkingDirectory"]) {
snapshot.managedUserDefaults[WorkspaceWorkingDirectoryInheritanceSettings.key] = .bool(value)
} else if section.keys.contains("workspaceInheritWorkingDirectory") {
Expand Down
2 changes: 2 additions & 0 deletions Sources/SettingsNavigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ enum SettingsSearchIndex {
setting(.app, "appearance", String(localized: "settings.app.appearance", defaultValue: "Appearance"), "theme light dark system"),
setting(.app, "app-icon", String(localized: "settings.app.appIcon", defaultValue: "App Icon"), "dock icon alternate"),
setting(.app, "new-workspace-placement", String(localized: "settings.app.newWorkspacePlacement", defaultValue: "New Workspace Placement"), "workspace order position"),
setting(.app, "new-surface-placement", String(localized: "settings.app.newSurfacePlacement", defaultValue: "New Surface Placement"), "surface tab order position pane"),
setting(.app, "workspace-inherit-working-directory", String(localized: "settings.app.workspaceInheritWorkingDirectory", defaultValue: "Inherit Workspace Working Directory"), "workspace cwd directory current ghostty working-directory"),
setting(.app, "minimal-mode", String(localized: "settings.app.minimalMode", defaultValue: "Minimal Mode"), "presentation compact chrome"),
setting(.app, "keep-workspace-open", String(localized: "settings.app.closeWorkspaceOnLastSurfaceShortcut", defaultValue: "Keep Workspace Open When Closing Last Surface"), "close last surface shortcut"),
Expand Down Expand Up @@ -385,6 +386,7 @@ enum SettingsSearchIndex {
"app.appearance": settingID(for: .app, idSuffix: "appearance"),
"app.appIcon": settingID(for: .app, idSuffix: "app-icon"),
"app.newWorkspacePlacement": settingID(for: .app, idSuffix: "new-workspace-placement"),
"app.newSurfacePlacement": settingID(for: .app, idSuffix: "new-surface-placement"),
"app.workspaceInheritWorkingDirectory": settingID(for: .app, idSuffix: "workspace-inherit-working-directory"),
"app.minimalMode": settingID(for: .app, idSuffix: "minimal-mode"),
"app.keepWorkspaceOpenWhenClosingLastSurface": settingID(for: .app, idSuffix: "keep-workspace-open"),
Expand Down
1 change: 1 addition & 0 deletions Sources/SettingsSearchAliases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum SettingsSearchAliasIndex {
"app:appearance": localized("settings.search.alias.setting.app.appearance", defaultValue: "app.appearance theme color scheme light mode dark mode system mode"),
"app:app-icon": localized("settings.search.alias.setting.app.app-icon", defaultValue: "app.appIcon dock icon application icon app switcher alternate icon"),
"app:new-workspace-placement": localized("settings.search.alias.setting.app.new-workspace-placement", defaultValue: "app.newWorkspacePlacement new tab insert position order top bottom end"),
"app:new-surface-placement": localized("settings.search.alias.setting.app.new-surface-placement", defaultValue: "app.newSurfacePlacement surface tab insert position order start end after current"),
"app:workspace-inherit-working-directory": localized("settings.search.alias.setting.app.workspace-inherit-working-directory", defaultValue: "app.workspaceInheritWorkingDirectory workspace cwd directory inherit current focused ghostty working-directory"),
"app:minimal-mode": localized("settings.search.alias.setting.app.minimal-mode", defaultValue: "app.minimalMode minimal layout simple chrome compact titlebar controls"),
"app:keep-workspace-open": localized("settings.search.alias.setting.app.keep-workspace-open", defaultValue: "app.keepWorkspaceOpenWhenClosingLastSurface close last pane surface keep tab workspace"),
Expand Down
80 changes: 80 additions & 0 deletions Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,45 @@ enum NewWorkspacePlacement: String, CaseIterable, Identifiable {
}
}

enum NewSurfacePlacement: String, CaseIterable, Identifiable {
case top
case afterCurrent
case end

var id: String { rawValue }

var displayName: String {
switch self {
case .top:
return String(localized: "surface.placement.top", defaultValue: "Start")
case .afterCurrent:
return String(localized: "surface.placement.afterCurrent", defaultValue: "After current")
case .end:
return String(localized: "surface.placement.end", defaultValue: "End")
}
}

var description: String {
switch self {
case .top:
return String(
localized: "surface.placement.top.description",
defaultValue: "Insert new surfaces at the start of the pane's tab strip."
)
case .afterCurrent:
return String(
localized: "surface.placement.afterCurrent.description",
defaultValue: "Insert new surfaces directly after the active surface."
)
case .end:
return String(
localized: "surface.placement.end.description",
defaultValue: "Append new surfaces to the end of the pane's tab strip."
)
}
}
}

enum WorkspaceAutoReorderSettings {
static let key = "workspaceAutoReorderOnNotification"
static let defaultValue = true
Expand Down Expand Up @@ -280,6 +319,47 @@ enum WorkspacePlacementSettings {
}
}

enum SurfacePlacementSettings {
static let placementKey = "newSurfacePlacement"
static let defaultPlacement: NewSurfacePlacement = .afterCurrent

static func current(defaults: UserDefaults = .standard) -> NewSurfacePlacement {
guard let raw = defaults.string(forKey: placementKey),
let placement = NewSurfacePlacement(rawValue: raw) else {
return defaultPlacement
}
return placement
}

static func insertionIndex(
placement: NewSurfacePlacement,
selectedIndexBeforeCreation: Int?,
selectedWasPinned: Bool,
pinnedCount: Int,
totalCountAfterCreation: Int
) -> Int {
let clampedTotalCount = max(0, totalCountAfterCreation)
let clampedPinnedCount = max(0, min(pinnedCount, clampedTotalCount))
let lastIndex = max(0, clampedTotalCount - 1)

switch placement {
case .top:
return clampedPinnedCount
case .end:
return lastIndex
case .afterCurrent:
guard let selectedIndexBeforeCreation, clampedTotalCount > 0 else {
return lastIndex
}
if selectedWasPinned {
return clampedPinnedCount
}
let clampedSelectedIndex = max(0, min(selectedIndexBeforeCreation, lastIndex))
return max(min(clampedSelectedIndex + 1, lastIndex), clampedPinnedCount)
}
}
}

enum WorkspaceWorkingDirectoryInheritanceSettings {
static let key = "workspaceInheritWorkingDirectory"
static let defaultValue = true
Expand Down
38 changes: 16 additions & 22 deletions Sources/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5734,20 +5734,6 @@ class TerminalController {
result = .ok(payload)
}

@MainActor
func insertionIndexToRight(anchorTabId: TabID, inPane paneId: PaneID) -> Int {
let tabs = workspace.bonsplitController.tabs(inPane: paneId)
guard let anchorIndex = tabs.firstIndex(where: { $0.id == anchorTabId }) else { return tabs.count }
let pinnedCount = tabs.reduce(into: 0) { count, tab in
if let panelId = workspace.panelIdFromSurfaceId(tab.id),
workspace.isPanelPinned(panelId) {
count += 1
}
}
let rawTarget = min(anchorIndex + 1, tabs.count)
return max(rawTarget, pinnedCount)
}

@MainActor
func closeTabs(_ tabIds: [TabID]) -> (closed: Int, skippedPinned: Int) {
var closed = 0
Expand Down Expand Up @@ -5840,12 +5826,15 @@ class TerminalController {
return
}

let targetIndex = insertionIndexToRight(anchorTabId: anchorTabId, inPane: paneId)
guard let newPanel = workspace.newTerminalSurface(inPane: paneId, focus: focus) else {
let targetIndex = workspace.insertionIndexToRight(of: anchorTabId, inPane: paneId)
guard let newPanel = workspace.newTerminalSurface(
inPane: paneId,
focus: focus,
targetIndex: targetIndex
) else {
result = .err(code: "internal_error", message: "Failed to create tab", data: nil)
return
}
_ = workspace.reorderSurface(panelId: newPanel.id, toIndex: targetIndex, focus: focus)
finish([
"created_surface_id": newPanel.id.uuidString,
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id),
Expand Down Expand Up @@ -5875,12 +5864,17 @@ class TerminalController {
return
}

let targetIndex = insertionIndexToRight(anchorTabId: anchorTabId, inPane: paneId)
guard let newPanel = workspace.newBrowserSurface(inPane: paneId, url: url, focus: focus) else {
let targetIndex = workspace.insertionIndexToRight(of: anchorTabId, inPane: paneId)
guard let newPanel = workspace.newBrowserSurface(
inPane: paneId,
url: url,
focus: focus,
preferredProfileID: workspace.browserPanel(for: surfaceId)?.profileID,
targetIndex: targetIndex
) else {
result = .err(code: "internal_error", message: "Failed to create tab", data: nil)
return
}
_ = workspace.reorderSurface(panelId: newPanel.id, toIndex: targetIndex, focus: focus)
finish([
"created_surface_id": newPanel.id.uuidString,
"created_surface_ref": v2Ref(kind: .surface, uuid: newPanel.id),
Expand Down Expand Up @@ -8055,14 +8049,14 @@ class TerminalController {
var sourcePlaceholder: UUID?
var targetPlaceholder: UUID?
if workspace.bonsplitController.tabs(inPane: sourcePane).count <= 1 {
sourcePlaceholder = workspace.newTerminalSurface(inPane: sourcePane, focus: false)?.id
sourcePlaceholder = workspace.newTerminalSurface(inPane: sourcePane, focus: false, placementOverride: .end)?.id
if sourcePlaceholder == nil {
result = .err(code: "internal_error", message: "Failed to create source placeholder surface", data: nil)
return
}
}
if workspace.bonsplitController.tabs(inPane: targetPane).count <= 1 {
targetPlaceholder = workspace.newTerminalSurface(inPane: targetPane, focus: false)?.id
targetPlaceholder = workspace.newTerminalSurface(inPane: targetPane, focus: false, placementOverride: .end)?.id
if targetPlaceholder == nil {
result = .err(code: "internal_error", message: "Failed to create target placeholder surface", data: nil)
return
Expand Down
Loading
Loading