Skip to content

Commit 5f05c4f

Browse files
Merge branch 'main' into task/add-collapsing-jumpbar-components
2 parents 2c00fd1 + 41b590d commit 5f05c4f

File tree

25 files changed

+708
-104
lines changed

25 files changed

+708
-104
lines changed

.all-contributorsrc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,25 @@
794794
"contributions": [
795795
"code"
796796
]
797+
},
798+
{
799+
"login": "rustemd02",
800+
"name": "rustemd02",
801+
"avatar_url": "https://avatars.githubusercontent.com/u/11714456?v=4",
802+
"profile": "https://github.com/rustemd02",
803+
"contributions": [
804+
"bug",
805+
"code"
806+
]
807+
},
808+
{
809+
"login": "SimonKudsk",
810+
"name": "Simon Kudsk",
811+
"avatar_url": "https://avatars.githubusercontent.com/u/10168417?v=4",
812+
"profile": "https://github.com/SimonKudsk",
813+
"contributions": [
814+
"code"
815+
]
797816
}
798817
],
799818
"contributorsPerLine": 7,

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,7 +1622,7 @@
16221622
repositoryURL = "https://github.com/CodeEditApp/CodeEditSymbols";
16231623
requirement = {
16241624
kind = exactVersion;
1625-
version = 0.2.2;
1625+
version = 0.2.3;
16261626
};
16271627
};
16281628
287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference "SwiftLintPlugin" */ = {
@@ -1749,8 +1749,8 @@
17491749
isa = XCRemoteSwiftPackageReference;
17501750
repositoryURL = "https://github.com/CodeEditApp/CodeEditSourceEditor";
17511751
requirement = {
1752-
kind = upToNextMajorVersion;
1753-
minimumVersion = 0.12.0;
1752+
kind = exactVersion;
1753+
version = 0.13.2;
17541754
};
17551755
};
17561756
/* End XCRemoteSwiftPackageReference section */

CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ final class CodeEditDocumentController: NSDocumentController {
7171
print("Unable to open document '\(url)': \(errorMessage)")
7272
}
7373

74-
RecentProjectsStore.documentOpened(at: url)
74+
RecentProjectsStore.shared.documentOpened(at: url)
7575
completionHandler(document, documentWasAlreadyOpen, error)
7676
}
7777
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//
2+
// CodeEditWindowController+Panels.swift
3+
// CodeEdit
4+
//
5+
// Created by Simon Kudsk on 11/05/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
extension CodeEditWindowController {
11+
@objc
12+
func objcToggleFirstPanel() {
13+
toggleFirstPanel(shouldAnimate: true)
14+
}
15+
16+
/// Toggles the navigator pane, optionally without animation.
17+
func toggleFirstPanel(shouldAnimate: Bool = true) {
18+
guard let firstSplitView = splitViewController?.splitViewItems.first else { return }
19+
20+
if shouldAnimate {
21+
// Standard animated toggle
22+
firstSplitView.animator().isCollapsed.toggle()
23+
} else {
24+
// Instant toggle (no animation)
25+
firstSplitView.isCollapsed.toggle()
26+
}
27+
28+
splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)
29+
}
30+
31+
@objc
32+
func objcToggleLastPanel() {
33+
toggleLastPanel(shouldAnimate: true)
34+
}
35+
36+
func toggleLastPanel(shouldAnimate: Bool = true) {
37+
guard let lastSplitView = splitViewController?.splitViewItems.last else {
38+
return
39+
}
40+
41+
if shouldAnimate {
42+
// Standard animated toggle
43+
NSAnimationContext.runAnimationGroup { _ in
44+
lastSplitView.animator().isCollapsed.toggle()
45+
}
46+
} else {
47+
// Instant toggle (no animation)
48+
lastSplitView.isCollapsed.toggle()
49+
}
50+
51+
splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)
52+
}
53+
54+
// PanelDescriptor, used for an array of panels, for use with "Hide interface".
55+
private struct PanelDescriptor {
56+
/// Returns the current `isCollapsed` value for the panel.
57+
let isCollapsed: () -> Bool
58+
/// Returns the last stored previous state (or `nil` if none).
59+
let getPrevCollapsed: () -> Bool?
60+
/// Stores a new previous state (`nil` to clear).
61+
let setPrevCollapsed: (Bool?) -> Void
62+
/// Performs the actual toggle action for the panel.
63+
let toggle: () -> Void
64+
}
65+
66+
// The panels which "Hide interface" should interact with.
67+
private var panels: [PanelDescriptor] {
68+
[
69+
PanelDescriptor(
70+
isCollapsed: { self.navigatorCollapsed },
71+
getPrevCollapsed: { self.prevNavigatorCollapsed },
72+
setPrevCollapsed: { self.prevNavigatorCollapsed = $0 },
73+
toggle: { self.toggleFirstPanel(shouldAnimate: false) }
74+
),
75+
PanelDescriptor(
76+
isCollapsed: { self.inspectorCollapsed },
77+
getPrevCollapsed: { self.prevInspectorCollapsed },
78+
setPrevCollapsed: { self.prevInspectorCollapsed = $0 },
79+
toggle: { self.toggleLastPanel(shouldAnimate: false) }
80+
),
81+
PanelDescriptor(
82+
isCollapsed: { self.workspace?.utilityAreaModel?.isCollapsed ?? true },
83+
getPrevCollapsed: { self.prevUtilityAreaCollapsed },
84+
setPrevCollapsed: { self.prevUtilityAreaCollapsed = $0 },
85+
toggle: { CommandManager.shared.executeCommand("open.drawer") }
86+
),
87+
PanelDescriptor(
88+
isCollapsed: { self.toolbarCollapsed },
89+
getPrevCollapsed: { self.prevToolbarCollapsed },
90+
setPrevCollapsed: { self.prevToolbarCollapsed = $0 },
91+
toggle: { self.toggleToolbar() }
92+
)
93+
]
94+
}
95+
96+
/// Returns `true` if at least one panel that was visible is still collapsed, meaning the interface is still hidden
97+
func isInterfaceStillHidden() -> Bool {
98+
// Some panels do not yet have a remembered state
99+
if panels.contains(where: { $0.getPrevCollapsed() == nil }) {
100+
// Hidden only if all panels are collapsed
101+
return panels.allSatisfy { $0.isCollapsed() }
102+
}
103+
104+
// All panels have a remembered state. Check if any that were visible are still collapsed
105+
let stillHidden = panels.contains { descriptor in
106+
guard let prev = descriptor.getPrevCollapsed() else { return false }
107+
return !prev && descriptor.isCollapsed()
108+
}
109+
110+
// If the interface has been restored, reset the remembered states
111+
if !stillHidden {
112+
DispatchQueue.main.async { [weak self] in
113+
self?.resetStoredInterfaceCollapseState()
114+
}
115+
}
116+
117+
return stillHidden
118+
}
119+
120+
/// Function for toggling the interface elements on or off
121+
///
122+
/// - Parameter shouldHide: Pass `true` to hide all interface panels (and remember their current states),
123+
/// or `false` to restore them to how they were before hiding.
124+
func toggleInterface(shouldHide: Bool) {
125+
// Store the current layout before hiding
126+
if shouldHide {
127+
storeInterfaceCollapseState()
128+
}
129+
130+
// Iterate over all panels and update their state as needed
131+
for panel in panels {
132+
let targetState = determineDesiredCollapseState(
133+
shouldHide: shouldHide,
134+
currentlyCollapsed: panel.isCollapsed(),
135+
previouslyCollapsed: panel.getPrevCollapsed()
136+
)
137+
if panel.isCollapsed() != targetState {
138+
panel.toggle()
139+
}
140+
}
141+
}
142+
143+
/// Calculates the collapse state an interface element should have after a hide / show toggle.
144+
/// - Parameters:
145+
/// - shouldHide: `true` when we’re hiding the whole interface.
146+
/// - currentlyCollapsed: The panels current state
147+
/// - previouslyCollapsed: The state we saved the last time we hid the UI, if any.
148+
/// - Returns: `true` for visible element, `false` for collapsed element
149+
func determineDesiredCollapseState(shouldHide: Bool, currentlyCollapsed: Bool, previouslyCollapsed: Bool?) -> Bool {
150+
// If ShouldHide, everything should close
151+
if shouldHide {
152+
return true
153+
}
154+
155+
// If not hiding, and not currently collapsed, the panel should remain as such.
156+
if !currentlyCollapsed {
157+
return false
158+
}
159+
160+
// If the panel is currently collapsed and we are "showing" or "restoring":
161+
// Option 1: Restore to its previously remembered state if available.
162+
// Option 2: If no previously remembered state, default to making it visible (not collapsed).
163+
return previouslyCollapsed ?? false
164+
}
165+
166+
/// Function for storing the current interface visibility states
167+
func storeInterfaceCollapseState() {
168+
for panel in panels {
169+
panel.setPrevCollapsed(panel.isCollapsed())
170+
}
171+
}
172+
173+
/// Function for resetting the stored interface visibility states
174+
func resetStoredInterfaceCollapseState() {
175+
for panel in panels {
176+
panel.setPrevCollapsed(nil)
177+
}
178+
}
179+
}

CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,11 @@ extension CodeEditWindowController {
5656

5757
func toggleToolbar() {
5858
toolbarCollapsed.toggle()
59+
workspace?.addToWorkspaceState(key: .toolbarCollapsed, value: toolbarCollapsed)
5960
updateToolbarVisibility()
6061
}
6162

62-
private func updateToolbarVisibility() {
63+
func updateToolbarVisibility() {
6364
if toolbarCollapsed {
6465
window?.titleVisibility = .visible
6566
window?.title = workspace?.workspaceFileManager?.folderUrl.lastPathComponent ?? "Empty"
@@ -92,7 +93,7 @@ extension CodeEditWindowController {
9293
toolbarItem.toolTip = "Hide or show the Navigator"
9394
toolbarItem.isBordered = true
9495
toolbarItem.target = self
95-
toolbarItem.action = #selector(self.toggleFirstPanel)
96+
toolbarItem.action = #selector(self.objcToggleFirstPanel)
9697
toolbarItem.image = NSImage(
9798
systemSymbolName: "sidebar.leading",
9899
accessibilityDescription: nil
@@ -106,7 +107,7 @@ extension CodeEditWindowController {
106107
toolbarItem.toolTip = "Hide or show the Inspectors"
107108
toolbarItem.isBordered = true
108109
toolbarItem.target = self
109-
toolbarItem.action = #selector(self.toggleLastPanel)
110+
toolbarItem.action = #selector(self.objcToggleLastPanel)
110111
toolbarItem.image = NSImage(
111112
systemSymbolName: "sidebar.trailing",
112113
accessibilityDescription: nil

CodeEdit/Features/Documents/Controllers/CodeEditWindowController.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ import SwiftUI
1010
import Combine
1111

1212
final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, ObservableObject, NSWindowDelegate {
13-
@Published var navigatorCollapsed = false
14-
@Published var inspectorCollapsed = false
15-
@Published var toolbarCollapsed = false
13+
@Published var navigatorCollapsed: Bool = false
14+
@Published var inspectorCollapsed: Bool = false
15+
@Published var toolbarCollapsed: Bool = false
16+
17+
// These variables store the state of the windows when using "Hide interface"
18+
@Published var prevNavigatorCollapsed: Bool?
19+
@Published var prevInspectorCollapsed: Bool?
20+
@Published var prevUtilityAreaCollapsed: Bool?
21+
@Published var prevToolbarCollapsed: Bool?
1622

1723
private var panelOpen = false
1824

@@ -38,6 +44,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
3844
window?.delegate = self
3945
guard let workspace else { return }
4046
self.workspace = workspace
47+
self.toolbarCollapsed = workspace.getFromWorkspaceState(.toolbarCollapsed) as? Bool ?? false
4148
guard let splitViewController = setupSplitView(with: workspace) else {
4249
fatalError("Failed to set up content view.")
4350
}
@@ -67,6 +74,7 @@ final class CodeEditWindowController: NSWindowController, NSToolbarDelegate, Obs
6774
]
6875

6976
setupToolbar()
77+
updateToolbarVisibility()
7078
registerCommands()
7179
}
7280

CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,6 @@ import SwiftUI
99
import Combine
1010

1111
extension CodeEditWindowController {
12-
@objc
13-
func toggleFirstPanel() {
14-
guard let firstSplitView = splitViewController?.splitViewItems.first else { return }
15-
firstSplitView.animator().isCollapsed.toggle()
16-
splitViewController?.saveNavigatorCollapsedState(isCollapsed: firstSplitView.isCollapsed)
17-
}
18-
19-
@objc
20-
func toggleLastPanel() {
21-
guard let lastSplitView = splitViewController?.splitViewItems.last else {
22-
return
23-
}
24-
25-
NSAnimationContext.runAnimationGroup { _ in
26-
lastSplitView.animator().isCollapsed.toggle()
27-
}
28-
29-
splitViewController?.saveInspectorCollapsedState(isCollapsed: lastSplitView.isCollapsed)
30-
}
31-
3212
/// These are example items that added as commands to command palette
3313
func registerCommands() {
3414
CommandManager.shared.addCommand(

CodeEdit/Features/Documents/WorkspaceDocument/WorkspaceStateKey.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ enum WorkspaceStateKey: String {
1414
case splitViewWidth
1515
case navigatorCollapsed
1616
case inspectorCollapsed
17+
case toolbarCollapsed
1718
}

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ struct CodeFileView: View {
5050
var useSystemCursor
5151
@AppSettings(\.textEditing.showMinimap)
5252
var showMinimap
53+
@AppSettings(\.textEditing.reformatAtColumn)
54+
var reformatAtColumn
55+
@AppSettings(\.textEditing.showReformattingGuide)
56+
var showReformattingGuide
5357

5458
@Environment(\.colorScheme)
5559
private var colorScheme
@@ -135,7 +139,9 @@ struct CodeFileView: View {
135139
useSystemCursor: useSystemCursor,
136140
undoManager: undoManager,
137141
coordinators: textViewCoordinators,
138-
showMinimap: showMinimap
142+
showMinimap: showMinimap,
143+
reformatAtColumn: reformatAtColumn,
144+
showReformattingGuide: showReformattingGuide
139145
)
140146
.id(codeFile.fileURL)
141147
.background {

0 commit comments

Comments
 (0)