Skip to content

Commit 09c6b91

Browse files
committed
Merge branch 'main' into pr/2044
2 parents 26068c6 + 445f338 commit 09c6b91

File tree

10 files changed

+204
-30
lines changed

10 files changed

+204
-30
lines changed

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/CEWorkspace/Models/CEWorkspaceFileManager.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ final class CEWorkspaceFileManager {
103103
return nil
104104
}
105105

106-
// Drill down towards the file, indexing any directories needed.
107-
// If file is not in the `workspaceSettingsFolderURL` or subdirectories, exit.
108-
guard url.absoluteString.starts(with: folderUrl.absoluteString),
109-
url.pathComponents.count > folderUrl.pathComponents.count else {
106+
// If file is not in the `folderUrl` or subdirectories, exit.
107+
guard folderUrl.containsSubPath(url) else {
110108
return nil
111109
}
110+
111+
// Drill down towards the file, indexing any directories needed.
112112
let pathComponents = url.pathComponents.dropFirst(folderUrl.pathComponents.count)
113113
var currentURL = folderUrl
114114

CodeEdit/Features/Documents/Controllers/CodeEditDocumentController.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ final class CodeEditDocumentController: NSDocumentController {
5959
display displayDocument: Bool,
6060
completionHandler: @escaping (NSDocument?, Bool, Error?) -> Void
6161
) {
62-
super.openDocument(withContentsOf: url, display: displayDocument) { document, documentWasAlreadyOpen, error in
62+
guard !openFileInExistingWorkspace(url: url) else {
63+
return
64+
}
6365

66+
super.openDocument(withContentsOf: url, display: displayDocument) { document, documentWasAlreadyOpen, error in
6467
if let document {
6568
self.addDocument(document)
6669
} else {
@@ -73,6 +76,28 @@ final class CodeEditDocumentController: NSDocumentController {
7376
}
7477
}
7578

79+
/// Attempt to open the file URL in an open workspace, finding the nearest workspace to open it in if possible.
80+
/// - Parameter url: The file URL to open.
81+
/// - Returns: True, if the document was opened in a workspace.
82+
private func openFileInExistingWorkspace(url: URL) -> Bool {
83+
guard !url.isFolder else { return false }
84+
let workspaces = documents.compactMap({ $0 as? WorkspaceDocument })
85+
86+
// Check open workspaces for the file being opened. Sorted by shared components with the url so we
87+
// open the nearest workspace possible.
88+
for workspace in workspaces.sorted(by: {
89+
($0.fileURL?.sharedComponents(url) ?? 0) > ($1.fileURL?.sharedComponents(url) ?? 0)
90+
}) {
91+
// createIfNotFound will still return `nil` if the files don't share a common ancestor.
92+
if let newFile = workspace.workspaceFileManager?.getFile(url.absolutePath, createIfNotFound: true) {
93+
workspace.editorManager?.openTab(item: newFile)
94+
workspace.showWindows()
95+
return true
96+
}
97+
}
98+
return false
99+
}
100+
76101
override func removeDocument(_ document: NSDocument) {
77102
super.removeDocument(document)
78103

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 {

CodeEdit/Features/NavigatorArea/FindNavigator/FindNavigatorResultList/FindNavigatorListViewController.swift

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -209,26 +209,69 @@ extension FindNavigatorListViewController: NSOutlineViewDelegate {
209209
}
210210

211211
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
212-
if let item = item as? SearchResultMatchModel {
213-
let tempView = NSTextField(wrappingLabelWithString: item.attributedLabel().string)
214-
tempView.allowsDefaultTighteningForTruncation = false
215-
tempView.cell?.truncatesLastVisibleLine = true
212+
if let matchItem = item as? SearchResultMatchModel {
213+
guard let column = outlineView.tableColumns.first else {
214+
return rowHeight
215+
}
216+
let columnWidth = column.width
217+
let indentationLevel = outlineView.level(forItem: item)
218+
let indentationSpace = CGFloat(indentationLevel) * outlineView.indentationPerLevel
219+
let horizontalPaddingAndFixedElements: CGFloat = 24.0
220+
221+
let availableWidth = columnWidth - indentationSpace - horizontalPaddingAndFixedElements
222+
223+
guard availableWidth > 0 else {
224+
// Not enough space to display anything, return minimum height
225+
return max(rowHeight, Settings.shared.preferences.general.projectNavigatorSize.rowHeight)
226+
}
227+
228+
let attributedString = matchItem.attributedLabel()
229+
230+
let tempView = NSTextField()
231+
tempView.allowsEditingTextAttributes = true
232+
tempView.attributedStringValue = attributedString
233+
234+
tempView.isEditable = false
235+
tempView.isBordered = false
236+
tempView.drawsBackground = false
237+
tempView.alignment = .natural
238+
216239
tempView.cell?.wraps = true
217-
tempView.maximumNumberOfLines = 3
218-
tempView.attributedStringValue = item.attributedLabel()
219-
tempView.layout()
220-
let width = outlineView.frame.width - outlineView.indentationPerLevel*2 - 24
221-
return tempView.sizeThatFits(
222-
NSSize(width: width, height: CGFloat.greatestFiniteMagnitude)
223-
).height + 8
224-
} else {
225-
return rowHeight
240+
tempView.cell?.usesSingleLineMode = false
241+
tempView.lineBreakMode = .byWordWrapping
242+
tempView.maximumNumberOfLines = Settings.shared.preferences.general.findNavigatorDetail.rawValue
243+
tempView.preferredMaxLayoutWidth = availableWidth
244+
245+
var calculatedHeight = tempView.sizeThatFits(
246+
NSSize(width: availableWidth, height: .greatestFiniteMagnitude)
247+
).height
248+
249+
// Total vertical padding (top + bottom) within the cell around the text
250+
let verticalPaddingInCell: CGFloat = 8.0
251+
calculatedHeight += verticalPaddingInCell
252+
return max(calculatedHeight, self.rowHeight)
226253
}
254+
// For parent items
255+
return prefs.general.projectNavigatorSize.rowHeight
227256
}
228257

229258
func outlineViewColumnDidResize(_ notification: Notification) {
230-
let indexes = IndexSet(integersIn: 0..<searchItems.count)
231-
outlineView.noteHeightOfRows(withIndexesChanged: indexes)
259+
// Disable animations temporarily
260+
NSAnimationContext.beginGrouping()
261+
NSAnimationContext.current.duration = 0
262+
263+
var rowsToUpdate = IndexSet()
264+
for row in 0..<outlineView.numberOfRows {
265+
if let item = outlineView.item(atRow: row), item is SearchResultMatchModel {
266+
rowsToUpdate.insert(row)
267+
}
268+
}
269+
if !rowsToUpdate.isEmpty {
270+
outlineView.noteHeightOfRows(withIndexesChanged: rowsToUpdate)
271+
}
272+
273+
NSAnimationContext.endGrouping()
274+
outlineView.layoutSubtreeIfNeeded()
232275
}
233276
}
234277

CodeEdit/Features/Settings/Pages/TextEditingSettings/Models/TextEditingSettings.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ extension SettingsData {
2929
"Bracket Pair Emphasis",
3030
"Bracket Pair Highlight",
3131
"Show Minimap",
32+
"Reformat at Column",
33+
"Show Reformatting Guide",
3234
]
3335
if #available(macOS 14.0, *) {
3436
keys.append("System Cursor")
@@ -74,6 +76,12 @@ extension SettingsData {
7476
/// Toggle the minimap in the editor.
7577
var showMinimap: Bool = true
7678

79+
/// The column at which to reformat text
80+
var reformatAtColumn: Int = 80
81+
82+
/// Show the reformatting guide in the editor
83+
var showReformattingGuide: Bool = false
84+
7785
/// Default initializer
7886
init() {
7987
self.populateCommands()
@@ -123,6 +131,11 @@ extension SettingsData {
123131
}
124132

125133
self.showMinimap = try container.decodeIfPresent(Bool.self, forKey: .showMinimap) ?? true
134+
self.reformatAtColumn = try container.decodeIfPresent(Int.self, forKey: .reformatAtColumn) ?? 80
135+
self.showReformattingGuide = try container.decodeIfPresent(
136+
Bool.self,
137+
forKey: .showReformattingGuide
138+
) ?? false
126139

127140
self.populateCommands()
128141
}

CodeEdit/Features/Settings/Pages/TextEditingSettings/TextEditingSettingsView.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct TextEditingSettingsView: View {
2121
useSystemCursor
2222
overscroll
2323
showMinimap
24+
reformatSettings
2425
}
2526
Section {
2627
fontSelector
@@ -206,4 +207,21 @@ private extension TextEditingSettingsView {
206207
// swiftlint:disable:next line_length
207208
.help("The minimap gives you a high-level summary of your source code, with controls to quickly navigate your document.")
208209
}
210+
211+
@ViewBuilder private var reformatSettings: some View {
212+
Stepper(
213+
"Reformat at Column",
214+
value: Binding<Double>(
215+
get: { Double(textEditing.reformatAtColumn) },
216+
set: { textEditing.reformatAtColumn = Int($0) }
217+
),
218+
in: 40...200,
219+
step: 1,
220+
format: .number
221+
)
222+
.help("The column at which text should be reformatted")
223+
224+
Toggle("Show Reformatting Guide", isOn: $textEditing.showReformattingGuide)
225+
.help("Shows a vertical guide at the reformat column")
226+
}
209227
}

CodeEdit/Utils/Extensions/URL/URL+componentCompare.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,39 @@ extension URL {
1515
func componentCompare(_ other: URL) -> Bool {
1616
return self.pathComponents == other.pathComponents
1717
}
18+
19+
/// Determines if another URL is lower in the file system than this URL.
20+
///
21+
/// Examples:
22+
/// ```
23+
/// URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/file.txt")) // true
24+
/// URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/")) // false
25+
/// URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/")) // false
26+
/// URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/Folder")) // true
27+
/// ```
28+
///
29+
/// - Parameter other: The URL to compare.
30+
/// - Returns: True, if the other URL is lower in the file system.
31+
func containsSubPath(_ other: URL) -> Bool {
32+
other.absoluteString.starts(with: absoluteString)
33+
&& other.pathComponents.count > pathComponents.count
34+
}
35+
36+
/// Compares this url with another, counting the number of shared path components. Stops counting once a
37+
/// different component is found.
38+
///
39+
/// - Note: URL treats a leading `/` as a component, so `/Users` and `/` will return `1`.
40+
/// - Parameter other: The URL to compare against.
41+
/// - Returns: The number of shared components.
42+
func sharedComponents(_ other: URL) -> Int {
43+
var count = 0
44+
for (component, otherComponent) in zip(pathComponents, other.pathComponents) {
45+
if component == otherComponent {
46+
count += 1
47+
} else {
48+
return count
49+
}
50+
}
51+
return count
52+
}
1853
}

CodeEditTests/Utils/UnitTests_Extensions.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,38 @@ final class CodeEditUtilsExtensionsUnitTests: XCTestCase {
196196
let path = #"/Hello World/ With Spaces/ And " Characters "#
197197
XCTAssertEqual(path.escapedDirectory(), #""/Hello World/ With Spaces/ And \" Characters ""#)
198198
}
199+
200+
// MARK: - URL + Contains
201+
202+
func testURLContainsSubPath() {
203+
XCTAssertTrue(URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/file.txt")))
204+
XCTAssertFalse(URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/")))
205+
XCTAssertFalse(URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/")))
206+
XCTAssertTrue(URL(filePath: "/Users/Bob/Desktop").containsSubPath(URL(filePath: "/Users/Bob/Desktop/Folder")))
207+
}
208+
209+
func testURLSharedComponentsCount() {
210+
// URL Treats the leading `/` as a component, so these all appear to have + 1 but are correct.
211+
XCTAssertEqual(
212+
URL(filePath: "/Users/Bob/Desktop").sharedComponents(URL(filePath: "/Users/Bob/Desktop/file.txt")),
213+
4
214+
)
215+
XCTAssertEqual(
216+
URL(filePath: "/Users/Bob/Desktop").sharedComponents(URL(filePath: "/Users/Bob/Desktop/")),
217+
4
218+
)
219+
XCTAssertEqual(
220+
URL(filePath: "/Users/Bob/Desktop").sharedComponents(URL(filePath: "/Users/Bob/")),
221+
3
222+
)
223+
XCTAssertEqual(
224+
URL(filePath: "/Users/Bob/Desktop").sharedComponents(URL(filePath: "/Users/Bob/Desktop/Folder")),
225+
4
226+
)
227+
228+
XCTAssertEqual(
229+
URL(filePath: "/Users/Bob/Desktop").sharedComponents(URL(filePath: "/Some Other/ Path ")),
230+
1
231+
)
232+
}
199233
}

0 commit comments

Comments
 (0)