Skip to content

Commit 9f60f95

Browse files
committed
Merge branch 'main' into pr/2039
2 parents b1f2b8e + 0474084 commit 9f60f95

File tree

37 files changed

+1229
-228
lines changed

37 files changed

+1229
-228
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.1;
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/CodeFileDocument/CodeFileDocument.swift

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ final class CodeFileDocument: NSDocument, ObservableObject {
5050
/// See ``CodeEditSourceEditor/CombineCoordinator``.
5151
@Published var contentCoordinator: CombineCoordinator = CombineCoordinator()
5252

53-
/// Set by ``LanguageServer`` when initialized.
54-
@Published var lspCoordinator: LSPContentCoordinator?
55-
5653
/// Used to override detected languages.
5754
@Published var language: CodeLanguage?
5855

@@ -65,6 +62,9 @@ final class CodeFileDocument: NSDocument, ObservableObject {
6562
/// Document-specific overridden line wrap preference.
6663
@Published var wrapLines: Bool?
6764

65+
/// Set up by ``LanguageServer``, conforms this type to ``LanguageServerDocument``.
66+
@Published var languageServerObjects: LanguageServerDocumentObjects<CodeFileDocument> = .init()
67+
6868
/// The type of data this file document contains.
6969
///
7070
/// If its text content is not nil, a `text` UTType is returned.
@@ -83,9 +83,6 @@ final class CodeFileDocument: NSDocument, ObservableObject {
8383
return type
8484
}
8585

86-
/// A stable string to use when identifying documents with language servers.
87-
var languageServerURI: String? { fileURL?.absolutePath }
88-
8986
/// Specify options for opening the file such as the initial cursor positions.
9087
/// Nulled by ``CodeFileView`` on first load.
9188
var openOptions: OpenOptions?
@@ -208,6 +205,10 @@ final class CodeFileDocument: NSDocument, ObservableObject {
208205
}
209206
}
210207

208+
/// Determines the code language of the document.
209+
/// Use ``CodeFileDocument/language`` for the default value before using this. That property is used to override
210+
/// the file's language.
211+
/// - Returns: The detected code language.
211212
func getLanguage() -> CodeLanguage {
212213
guard let url = fileURL else {
213214
return .default
@@ -223,3 +224,13 @@ final class CodeFileDocument: NSDocument, ObservableObject {
223224
fileURL?.findWorkspace()
224225
}
225226
}
227+
228+
// MARK: LanguageServerDocument
229+
230+
extension CodeFileDocument: LanguageServerDocument {
231+
/// A stable string to use when identifying documents with language servers.
232+
/// Needs to be a valid URI, so always returns with the `file://` prefix to indicate it's a file URI.
233+
var languageServerURI: String? {
234+
fileURL?.lspURI
235+
}
236+
}

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: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ struct CodeFileView: View {
1919
/// The current cursor positions in the view
2020
@State private var cursorPositions: [CursorPosition] = []
2121

22+
@State private var treeSitterClient: TreeSitterClient = TreeSitterClient()
23+
2224
/// Any coordinators passed to the view.
2325
private var textViewCoordinators: [TextViewCoordinator]
2426

27+
@State private var highlightProviders: [any HighlightProviding] = []
28+
2529
@AppSettings(\.textEditing.defaultTabWidth)
2630
var defaultTabWidth
2731
@AppSettings(\.textEditing.indentOption)
@@ -46,6 +50,10 @@ struct CodeFileView: View {
4650
var useSystemCursor
4751
@AppSettings(\.textEditing.showMinimap)
4852
var showMinimap
53+
@AppSettings(\.textEditing.reformatAtColumn)
54+
var reformatAtColumn
55+
@AppSettings(\.textEditing.showReformattingGuide)
56+
var showReformattingGuide
4957

5058
@Environment(\.colorScheme)
5159
private var colorScheme
@@ -62,16 +70,19 @@ struct CodeFileView: View {
6270

6371
init(codeFile: CodeFileDocument, textViewCoordinators: [TextViewCoordinator] = [], isEditable: Bool = true) {
6472
self._codeFile = .init(wrappedValue: codeFile)
73+
6574
self.textViewCoordinators = textViewCoordinators
6675
+ [codeFile.contentCoordinator]
67-
+ [codeFile.lspCoordinator].compactMap({ $0 })
76+
+ [codeFile.languageServerObjects.textCoordinator].compactMap({ $0 })
6877
self.isEditable = isEditable
6978

7079
if let openOptions = codeFile.openOptions {
7180
codeFile.openOptions = nil
7281
self.cursorPositions = openOptions.cursorPositions
7382
}
7483

84+
updateHighlightProviders()
85+
7586
codeFile
7687
.contentCoordinator
7788
.textUpdatePublisher
@@ -119,7 +130,7 @@ struct CodeFileView: View {
119130
editorOverscroll: overscroll.overscrollPercentage,
120131
cursorPositions: $cursorPositions,
121132
useThemeBackground: useThemeBackground,
122-
highlightProviders: [treeSitter],
133+
highlightProviders: highlightProviders,
123134
contentInsets: edgeInsets.nsEdgeInsets,
124135
additionalTextInsets: NSEdgeInsets(top: 2, left: 0, bottom: 0, right: 0),
125136
isEditable: isEditable,
@@ -128,7 +139,9 @@ struct CodeFileView: View {
128139
useSystemCursor: useSystemCursor,
129140
undoManager: undoManager,
130141
coordinators: textViewCoordinators,
131-
showMinimap: showMinimap
142+
showMinimap: showMinimap,
143+
reformatAtColumn: reformatAtColumn,
144+
showReformattingGuide: showReformattingGuide
132145
)
133146
.id(codeFile.fileURL)
134147
.background {
@@ -144,6 +157,10 @@ struct CodeFileView: View {
144157
.onChange(of: settingsFont) { newFontSetting in
145158
font = newFontSetting.current
146159
}
160+
.onReceive(codeFile.$languageServerObjects) { languageServerObjects in
161+
// This will not be called in single-file views (for now) but is safe to listen to either way
162+
updateHighlightProviders(lspHighlightProvider: languageServerObjects.highlightProvider)
163+
}
147164
}
148165

149166
/// Determines the style of bracket emphasis based on the `bracketEmphasis` setting and the current theme.
@@ -166,6 +183,12 @@ struct CodeFileView: View {
166183
return .underline(color: color)
167184
}
168185
}
186+
187+
/// Updates the highlight providers array.
188+
/// - Parameter lspHighlightProvider: The language server provider, if available.
189+
private func updateHighlightProviders(lspHighlightProvider: HighlightProviding? = nil) {
190+
highlightProviders = [lspHighlightProvider].compactMap({ $0 }) + [treeSitterClient]
191+
}
169192
}
170193

171194
// This extension is kept here because it should not be used elsewhere in the app and may cause confusion

CodeEdit/Features/InspectorArea/HistoryInspector/HistoryPopoverView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ struct HistoryPopoverView: View {
1818
var body: some View {
1919
VStack {
2020
CommitDetailsHeaderView(commit: commit)
21-
.padding(.horizontal)
2221

2322
Divider()
24-
.padding(.horizontal)
2523

2624
VStack(alignment: .leading, spacing: 0) {
2725
// TODO: Implementation Needed
@@ -71,6 +69,8 @@ struct HistoryPopoverView: View {
7169
}, icon: {
7270
Image(systemName: image)
7371
.frame(width: 16, alignment: .center)
72+
.padding(.leading, -2.5)
73+
.padding(.trailing, 2.5)
7474
})
7575
.frame(maxWidth: .infinity, alignment: .leading)
7676
.foregroundColor(isHovering && isEnabled ? .white : .primary)

CodeEdit/Features/LSP/Editor/LSPContentCoordinator.swift renamed to CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import LanguageServerProtocol
1919
/// Language servers expect edits to be sent in chunks (and it helps reduce processing overhead). To do this, this class
2020
/// keeps an async stream around for the duration of its lifetime. The stream is sent edit notifications, which are then
2121
/// chunked into 250ms timed groups before being sent to the ``LanguageServer``.
22-
class LSPContentCoordinator: TextViewCoordinator, TextViewDelegate {
22+
class LSPContentCoordinator<DocumentType: LanguageServerDocument>: TextViewCoordinator, TextViewDelegate {
2323
// Required to avoid a large_tuple lint error
2424
private struct SequenceElement: Sendable {
2525
let uri: String
@@ -28,25 +28,27 @@ class LSPContentCoordinator: TextViewCoordinator, TextViewDelegate {
2828
}
2929

3030
private var editedRange: LSPRange?
31-
private var stream: AsyncStream<SequenceElement>?
3231
private var sequenceContinuation: AsyncStream<SequenceElement>.Continuation?
3332
private var task: Task<Void, Never>?
3433

35-
weak var languageServer: LanguageServer?
34+
weak var languageServer: LanguageServer<DocumentType>?
3635
var documentURI: String
3736

3837
/// Initializes a content coordinator, and begins an async stream of updates
39-
init(documentURI: String, languageServer: LanguageServer) {
38+
init(documentURI: String, languageServer: LanguageServer<DocumentType>) {
4039
self.documentURI = documentURI
4140
self.languageServer = languageServer
42-
self.stream = AsyncStream { continuation in
43-
self.sequenceContinuation = continuation
44-
}
41+
42+
setUpUpdatesTask()
4543
}
4644

4745
func setUpUpdatesTask() {
4846
task?.cancel()
49-
guard let stream else { return }
47+
// Create this stream here so it's always set up when the text view is set up, rather than only once on init.
48+
let stream = AsyncStream { continuation in
49+
self.sequenceContinuation = continuation
50+
}
51+
5052
task = Task.detached { [weak self] in
5153
// Send edit events every 250ms
5254
for await events in stream.chunked(by: .repeating(every: .milliseconds(250), clock: .continuous)) {

0 commit comments

Comments
 (0)