Skip to content

Commit ee3d3cf

Browse files
committed
Fix initial status bar cursor position
1 parent cec6287 commit ee3d3cf

4 files changed

Lines changed: 88 additions & 5 deletions

File tree

CodeEdit/Features/Editor/Models/EditorInstance.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import CodeEditSourceEditor
1414
/// A single instance of an editor in a group with a published ``EditorInstance/cursorPositions`` variable to publish
1515
/// the user's current location in a file.
1616
class EditorInstance: ObservableObject, Hashable {
17+
private static let defaultCursorPositions = [CursorPosition(line: 1, column: 1)]
18+
1719
/// The file presented in this editor instance.
1820
let file: CEWorkspaceFile
1921

@@ -43,9 +45,10 @@ class EditorInstance: ObservableObject, Hashable {
4345
replaceText = workspace?.searchState?.replaceText
4446
replaceTextSubject = PassthroughSubject()
4547

46-
self.cursorPositions = (
47-
cursorPositions ?? editorState?.editorCursorPositions ?? [CursorPosition(line: 1, column: 1)]
48-
)
48+
let restoredCursorPositions = editorState?.editorCursorPositions
49+
self.cursorPositions = cursorPositions
50+
?? (restoredCursorPositions?.isEmpty == false ? restoredCursorPositions : nil)
51+
?? Self.defaultCursorPositions
4952
self.scrollPosition = editorState?.scrollPosition
5053

5154
// Setup listeners
@@ -124,6 +127,8 @@ class EditorInstance: ObservableObject, Hashable {
124127

125128
/// Translates ranges (eg: from a cursor position) to other information like the number of lines in a range.
126129
class RangeTranslator: TextViewCoordinator {
130+
let controllerDidAppearSubject = PassthroughSubject<Void, Never>()
131+
127132
private weak var textViewController: TextViewController?
128133

129134
init() { }
@@ -136,6 +141,7 @@ class EditorInstance: ObservableObject, Hashable {
136141
if controller.isEditable && controller.isSelectable {
137142
controller.view.window?.makeFirstResponder(controller.textView)
138143
}
144+
controllerDidAppearSubject.send()
139145
}
140146

141147
func destroy() {
@@ -158,6 +164,10 @@ class EditorInstance: ObservableObject, Hashable {
158164
return (endTextLine.index - startTextLine.index) + 1
159165
}
160166

167+
func resolveCursorPosition(_ cursorPosition: CursorPosition) -> CursorPosition {
168+
textViewController?.resolveCursorPosition(cursorPosition) ?? cursorPosition
169+
}
170+
161171
func moveLinesUp() {
162172
guard let controller = textViewController else { return }
163173
controller.moveLinesUp()

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ struct CodeFileView: View {
158158
)
159159
},
160160
set: { newState in
161-
editorInstance.cursorPositions = newState.cursorPositions ?? []
161+
if let cursorPositions = newState.cursorPositions {
162+
editorInstance.cursorPositions = cursorPositions
163+
}
162164
editorInstance.scrollPosition = newState.scrollPosition
163165
editorInstance.findText = newState.findText
164166
editorInstance.findTextSubject.send(newState.findText)

CodeEdit/Features/StatusBar/Views/StatusBarItems/StatusBarCursorPositionLabel.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct StatusBarCursorPositionLabel: View {
2424
Group {
2525
if let currentTab = tab {
2626
LineLabel(editorInstance: currentTab)
27+
.id(ObjectIdentifier(currentTab))
2728
} else {
2829
Text("").accessibilityLabel("No Selection")
2930
}
@@ -35,9 +36,17 @@ struct StatusBarCursorPositionLabel: View {
3536
.onAppear {
3637
updateSource()
3738
}
38-
.onReceive(editorManager.tabBarTabIdSubject) { _ in
39+
.onReceive(editorManager.activeEditor.objectWillChange) { _ in
40+
DispatchQueue.main.async {
41+
updateSource()
42+
}
43+
}
44+
.onReceive(editorManager.$activeEditor) { _ in
3945
updateSource()
4046
}
47+
.onChange(of: editorManager.activeEditor.selectedTab) { _, newTab in
48+
tab = newTab
49+
}
4150
}
4251

4352
struct LineLabel: View {
@@ -54,6 +63,7 @@ struct StatusBarCursorPositionLabel: View {
5463

5564
init(editorInstance: EditorInstance) {
5665
self.editorInstance = editorInstance
66+
self._cursorPositions = State(initialValue: editorInstance.cursorPositions)
5767
}
5868

5969
var body: some View {
@@ -64,6 +74,9 @@ struct StatusBarCursorPositionLabel: View {
6474
.onReceive(editorInstance.$cursorPositions) { newValue in
6575
self.cursorPositions = newValue
6676
}
77+
.onReceive(editorInstance.rangeTranslator.controllerDidAppearSubject) { _ in
78+
self.cursorPositions = editorInstance.cursorPositions
79+
}
6780
}
6881

6982
private var foregroundColor: Color {
@@ -84,6 +97,8 @@ struct StatusBarCursorPositionLabel: View {
8497
/// Create a label string for cursor positions.
8598
/// - Returns: A string describing the user's location in a document.
8699
func getLabel() -> String {
100+
let cursorPositions = cursorPositions.map(editorInstance.rangeTranslator.resolveCursorPosition)
101+
87102
if cursorPositions.isEmpty {
88103
return ""
89104
}
@@ -115,6 +130,13 @@ struct StatusBarCursorPositionLabel: View {
115130
}
116131

117132
// When there's a single cursor, display the line and column.
133+
if cursorPositions[0].start.line <= 0 || cursorPositions[0].start.column <= 0 {
134+
if cursorPositions[0].range != .notFound && cursorPositions[0].range.location > 0 {
135+
return "Char: \(cursorPositions[0].range.location) Len: 0"
136+
}
137+
return "Line: 1 Col: 1"
138+
}
139+
118140
return "Line: \(cursorPositions[0].start.line) Col: \(cursorPositions[0].start.column)"
119141
}
120142
}

CodeEditUITests/Features/NavigatorArea/ProjectNavigator/ProjectNavigatorUITests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ final class ProjectNavigatorUITests: XCTestCase {
3939
XCTAssertTrue(readmeEditor.exists)
4040
XCTAssertNotNil(readmeEditor.value as? String)
4141

42+
let cursorPositionLabel = window.staticTexts["CursorPositionLabel"]
43+
XCTAssertTrue(cursorPositionLabel.waitForExistence(timeout: 2.0), "Cursor position label not found")
44+
assertResolvedCursorPosition(cursorPositionLabel)
45+
46+
let licenseRow = Query.Navigator.getProjectNavigatorRow(fileTitle: "LICENSE.md", navigator)
47+
XCTAssertFalse(Query.Navigator.rowContainsDisclosureIndicator(licenseRow), "File has disclosure indicator")
48+
licenseRow.click()
49+
50+
let licenseTab = Query.TabBar.getTab(labeled: "LICENSE.md", tabBar)
51+
XCTAssertTrue(licenseTab.exists)
52+
53+
let licenseEditor = Query.Window.getFirstEditor(window)
54+
let licenseContent = NSPredicate(format: "value CONTAINS %@", "MIT License")
55+
expectation(for: licenseContent, evaluatedWith: licenseEditor)
56+
waitForExpectations(timeout: 2.0)
57+
58+
assertResolvedCursorPosition(cursorPositionLabel)
59+
assertCursorPositionChanges(cursorPositionLabel) {
60+
licenseEditor.coordinate(withNormalizedOffset: CGVector(dx: 0.75, dy: 0.75)).click()
61+
}
62+
4263
let rowCount = navigator.descendants(matching: .outlineRow).count
4364

4465
// Open a folder
@@ -59,4 +80,32 @@ final class ProjectNavigatorUITests: XCTestCase {
5980
XCTAssertTrue(newRowCount > finalRowCount, "Rows were not hidden after closing a folder")
6081
XCTAssertEqual(rowCount, finalRowCount, "Different Number of rows loaded")
6182
}
83+
84+
private func assertResolvedCursorPosition(_ cursorPositionLabel: XCUIElement) {
85+
let resolvedCursorPosition = NSPredicate(
86+
format: "value CONTAINS %@ AND NOT value CONTAINS %@",
87+
"Line:",
88+
"-1"
89+
)
90+
expectation(for: resolvedCursorPosition, evaluatedWith: cursorPositionLabel)
91+
waitForExpectations(timeout: 2.0)
92+
}
93+
94+
private func assertCursorPositionChanges(_ cursorPositionLabel: XCUIElement, after action: () -> Void) {
95+
guard let originalValue = cursorPositionLabel.value as? String else {
96+
XCTFail("Cursor position label value not found")
97+
return
98+
}
99+
100+
action()
101+
102+
let updatedCursorPosition = NSPredicate(
103+
format: "value CONTAINS %@ AND NOT value CONTAINS %@ AND value != %@",
104+
"Line:",
105+
"-1",
106+
originalValue
107+
)
108+
expectation(for: updatedCursorPosition, evaluatedWith: cursorPositionLabel)
109+
waitForExpectations(timeout: 2.0)
110+
}
62111
}

0 commit comments

Comments
 (0)