Skip to content

Commit 5587a83

Browse files
committed
Achieve Generics Nirvana
1 parent db8e0a9 commit 5587a83

File tree

11 files changed

+94
-59
lines changed

11 files changed

+94
-59
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,8 @@
373373
6C14CEB028777D3C001468FE /* FindNavigatorListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */; };
374374
6C14CEB32877A68F001468FE /* FindNavigatorMatchListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */; };
375375
6C18620A298BF5A800C663EA /* RecentProjectsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C186209298BF5A800C663EA /* RecentProjectsListView.swift */; };
376+
6C1A7E942D5D557D001B951C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C5246732D1E612700F57F11 /* CodeEditSourceEditor */; };
377+
6C1A7E962D5D5762001B951C /* LanguageServerDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1A7E952D5D5762001B951C /* LanguageServerDocument.swift */; };
376378
6C1CC9982B1E770B0002349B /* AsyncFileIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */; };
377379
6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */; };
378380
6C1F3DA22C18C55800F6DEF6 /* ShellIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1F3DA12C18C55800F6DEF6 /* ShellIntegrationTests.swift */; };
@@ -408,7 +410,6 @@
408410
6C52466B2D1E507500F57F11 /* SemanticTokenStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C52466A2D1E506C00F57F11 /* SemanticTokenStorageTests.swift */; };
409411
6C52466D2D1E515700F57F11 /* SemanticToken+Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C52466C2D1E515700F57F11 /* SemanticToken+Position.swift */; };
410412
6C5246702D1E5CC100F57F11 /* SemanticTokenRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C52466F2D1E5CC100F57F11 /* SemanticTokenRange.swift */; };
411-
6C5246742D1E612700F57F11 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C5246732D1E612700F57F11 /* CodeEditSourceEditor */; };
412413
6C53AAD829A6C4FD00EE9ED6 /* SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C53AAD729A6C4FD00EE9ED6 /* SplitView.swift */; };
413414
6C578D8129CD294800DC73B2 /* ExtensionActivatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8029CD294800DC73B2 /* ExtensionActivatorView.swift */; };
414415
6C578D8429CD343800DC73B2 /* ExtensionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C578D8329CD343800DC73B2 /* ExtensionDetailView.swift */; };
@@ -431,7 +432,6 @@
431432
6C6BD70129CD172700235D17 /* ExtensionsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD70029CD172700235D17 /* ExtensionsListView.swift */; };
432433
6C6BD70429CD17B600235D17 /* ExtensionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD70329CD17B600235D17 /* ExtensionsManager.swift */; };
433434
6C7256D729A3D7D000C2D3E0 /* SplitViewControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7256D629A3D7D000C2D3E0 /* SplitViewControllerView.swift */; };
434-
6C73A6D32D4F1E550012D95C /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */; };
435435
6C7D6D462C9092EC00B69EE0 /* BufferingServerConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7D6D452C9092EC00B69EE0 /* BufferingServerConnection.swift */; };
436436
6C7F37FE2A3EA6FA00217B83 /* View+focusedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7F37FD2A3EA6FA00217B83 /* View+focusedValue.swift */; };
437437
6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C81916629B3E80700B75C92 /* ModifierKeysObserver.swift */; };
@@ -1083,6 +1083,7 @@
10831083
6C14CEAF28777D3C001468FE /* FindNavigatorListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorListViewController.swift; sourceTree = "<group>"; };
10841084
6C14CEB22877A68F001468FE /* FindNavigatorMatchListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorMatchListCell.swift; sourceTree = "<group>"; };
10851085
6C186209298BF5A800C663EA /* RecentProjectsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentProjectsListView.swift; sourceTree = "<group>"; };
1086+
6C1A7E952D5D5762001B951C /* LanguageServerDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageServerDocument.swift; sourceTree = "<group>"; };
10861087
6C1CC9972B1E770B0002349B /* AsyncFileIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFileIterator.swift; sourceTree = "<group>"; };
10871088
6C1CC99A2B1E7CBC0002349B /* FindNavigatorIndexBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorIndexBar.swift; sourceTree = "<group>"; };
10881089
6C1F3DA12C18C55800F6DEF6 /* ShellIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellIntegrationTests.swift; sourceTree = "<group>"; };
@@ -1365,6 +1366,7 @@
13651366
isa = PBXFrameworksBuildPhase;
13661367
buildActionMask = 2147483647;
13671368
files = (
1369+
6C1A7E942D5D557D001B951C /* CodeEditSourceEditor in Frameworks */,
13681370
6C85BB402C2105ED00EB5DEF /* CodeEditKit in Frameworks */,
13691371
6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */,
13701372
58F2EB1E292FB954004A9BDE /* Sparkle in Frameworks */,
@@ -1379,7 +1381,6 @@
13791381
6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */,
13801382
6C85BB442C210EFD00EB5DEF /* SwiftUIIntrospect in Frameworks */,
13811383
6CB446402B6DFF3A00539ED0 /* CodeEditSourceEditor in Frameworks */,
1382-
6C9AE6992D1DD84600FAE8D2 /* CodeEditSourceEditor in Frameworks */,
13831384
2816F594280CF50500DD548B /* CodeEditSymbols in Frameworks */,
13841385
30CB64942C16CA9100CC8A9E /* LanguageClient in Frameworks */,
13851386
6C6BD6F829CD14D100235D17 /* CodeEditKit in Frameworks */,
@@ -1630,6 +1631,7 @@
16301631
30B087FB2C0D53080063A882 /* LSP */ = {
16311632
isa = PBXGroup;
16321633
children = (
1634+
6C1A7E952D5D5762001B951C /* LanguageServerDocument.swift */,
16331635
30B087FA2C0D53080063A882 /* LSPUtil.swift */,
16341636
6C1A7E932D5D508A001B951C /* Features */,
16351637
6CD26C732C8EA71F00ADBA38 /* LanguageServer */,
@@ -3878,7 +3880,6 @@
38783880
6CD3CA542C8B508200D83DCD /* CodeEditSourceEditor */,
38793881
6CB94D022CA1205100E8651C /* AsyncAlgorithms */,
38803882
6CC00A8A2CBEF150004E8134 /* CodeEditSourceEditor */,
3881-
6C9AE6982D1DD84600FAE8D2 /* CodeEditSourceEditor */,
38823883
6C5246732D1E612700F57F11 /* CodeEditSourceEditor */,
38833884
);
38843885
productName = CodeEdit;
@@ -4633,6 +4634,7 @@
46334634
58822525292C280D00E83CDE /* StatusBarMenuStyle.swift in Sources */,
46344635
6C147C4229A328C10089B630 /* Editor.swift in Sources */,
46354636
B6C4F2A32B3CA74800B2B140 /* CommitDetailsView.swift in Sources */,
4637+
6C1A7E962D5D5762001B951C /* LanguageServerDocument.swift in Sources */,
46364638
6C2C155829B4F49100EA60A5 /* SplitViewItem.swift in Sources */,
46374639
6CDA84AD284C1BA000C1CC3A /* EditorTabBarContextMenu.swift in Sources */,
46384640
6C81916729B3E80700B75C92 /* ModifierKeysObserver.swift in Sources */,
@@ -5983,10 +5985,6 @@
59835985
isa = XCSwiftPackageProductDependency;
59845986
productName = CodeEditKit;
59855987
};
5986-
6C73A6D22D4F1E550012D95C /* CodeEditSourceEditor */ = {
5987-
isa = XCSwiftPackageProductDependency;
5988-
productName = CodeEditSourceEditor;
5989-
};
59905988
6C7B1C752A1D57CE005CBBFC /* SwiftLint */ = {
59915989
isa = XCSwiftPackageProductDependency;
59925990
package = 287136B1292A407E00E9F5F4 /* XCRemoteSwiftPackageReference "SwiftLintPlugin" */;

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

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +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-
56-
/// Set by ``LanguageServer`` when initialized.
57-
@Published var lspHighlightProvider: SemanticTokenHighlightProvider<SemanticTokenStorage>?
58-
5953
/// Used to override detected languages.
6054
@Published var language: CodeLanguage?
6155

@@ -68,6 +62,9 @@ final class CodeFileDocument: NSDocument, ObservableObject {
6862
/// Document-specific overridden line wrap preference.
6963
@Published var wrapLines: Bool?
7064

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

89-
/// A stable string to use when identifying documents with language servers.
90-
var languageServerURI: String? { fileURL?.absolutePath }
91-
9286
/// Specify options for opening the file such as the initial cursor positions.
9387
/// Nulled by ``CodeFileView`` on first load.
9488
var openOptions: OpenOptions?
@@ -194,6 +188,10 @@ final class CodeFileDocument: NSDocument, ObservableObject {
194188
NotificationCenter.default.post(name: Self.didCloseNotification, object: fileURL)
195189
}
196190

191+
/// Determines the code language of the document.
192+
/// Use ``CodeFileDocument/language`` for the default value before using this. That property is used to override
193+
/// the file's language.
194+
/// - Returns: The detected code language.
197195
func getLanguage() -> CodeLanguage {
198196
guard let url = fileURL else {
199197
return .default
@@ -209,3 +207,10 @@ final class CodeFileDocument: NSDocument, ObservableObject {
209207
fileURL?.findWorkspace()
210208
}
211209
}
210+
211+
// MARK: LanguageServerDocument
212+
213+
extension CodeFileDocument: LanguageServerDocument {
214+
/// A stable string to use when identifying documents with language servers.
215+
var languageServerURI: String? { fileURL?.absolutePath }
216+
}

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ struct CodeFileView: View {
6363

6464
self.textViewCoordinators = textViewCoordinators
6565
+ [codeFile.contentCoordinator]
66-
+ [codeFile.lspCoordinator].compactMap({ $0 })
66+
+ [codeFile.languageServerObjects.textCoordinator].compactMap({ $0 })
6767
self.isEditable = isEditable
6868

6969
if let openOptions = codeFile.openOptions {
@@ -162,8 +162,8 @@ struct CodeFileView: View {
162162
.onChange(of: bracketHighlight) { _ in
163163
bracketPairHighlight = getBracketPairHighlight()
164164
}
165-
.onReceive(codeFile.$lspHighlightProvider) { provider in
166-
updateHighlightProviders(provider)
165+
.onReceive(codeFile.$languageServerObjects) { languageServerObjects in
166+
updateHighlightProviders(languageServerObjects.highlightProvider)
167167
}
168168
}
169169

CodeEdit/Features/LSP/Features/DocumentSync/LSPContentCoordinator.swift

Lines changed: 3 additions & 3 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
@@ -32,11 +32,11 @@ class LSPContentCoordinator: TextViewCoordinator, TextViewDelegate {
3232
private var sequenceContinuation: AsyncStream<SequenceElement>.Continuation?
3333
private var task: Task<Void, Never>?
3434

35-
weak var languageServer: LanguageServer?
35+
weak var languageServer: LanguageServer<DocumentType>?
3636
var documentURI: String
3737

3838
/// Initializes a content coordinator, and begins an async stream of updates
39-
init(documentURI: String, languageServer: LanguageServer) {
39+
init(documentURI: String, languageServer: LanguageServer<DocumentType>) {
4040
self.documentURI = documentURI
4141
self.languageServer = languageServer
4242
self.stream = AsyncStream { continuation in

CodeEdit/Features/LSP/Features/SemanticTokens/SemanticTokenHighlightProvider.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ import CodeEditLanguages
2121
/// ``SemanticTokenHighlightProvider/applyEdit(textView:range:delta:completion:)`` method. One might expect this class
2222
/// to respond to that method immediately, but it does not. It instead stores the completion passed in that method until
2323
/// it can respond to the edit with invalidated indices.
24-
final class SemanticTokenHighlightProvider<Storage: GenericSemanticTokenStorage>: HighlightProviding {
24+
final class SemanticTokenHighlightProvider<
25+
Storage: GenericSemanticTokenStorage,
26+
DocumentType: LanguageServerDocument
27+
>: HighlightProviding {
2528
enum HighlightError: Error {
2629
case lspRangeFailure
2730
}
@@ -31,7 +34,7 @@ final class SemanticTokenHighlightProvider<Storage: GenericSemanticTokenStorage>
3134

3235
private let tokenMap: SemanticTokenMap
3336
private let documentURI: String
34-
private weak var languageServer: LanguageServer?
37+
private weak var languageServer: LanguageServer<DocumentType>?
3538
private weak var textView: TextView?
3639

3740
private var lastEditCallback: EditCallback?
@@ -42,7 +45,7 @@ final class SemanticTokenHighlightProvider<Storage: GenericSemanticTokenStorage>
4245
textView?.documentRange ?? .zero
4346
}
4447

45-
init(tokenMap: SemanticTokenMap, languageServer: LanguageServer, documentURI: String) {
48+
init(tokenMap: SemanticTokenMap, languageServer: LanguageServer<DocumentType>, documentURI: String) {
4649
self.tokenMap = tokenMap
4750
self.languageServer = languageServer
4851
self.documentURI = documentURI
@@ -88,7 +91,7 @@ final class SemanticTokenHighlightProvider<Storage: GenericSemanticTokenStorage>
8891

8992
/// Requests and applies a token delta. Requires a previous response identifier.
9093
private func requestDeltaTokens(
91-
languageServer: LanguageServer,
94+
languageServer: LanguageServer<DocumentType>,
9295
textView: TextView,
9396
lastResultId: String
9497
) async throws {
@@ -108,7 +111,7 @@ final class SemanticTokenHighlightProvider<Storage: GenericSemanticTokenStorage>
108111

109112
/// Requests and applies tokens for an entire document. This does not require a previous response id, and should be
110113
/// used in place of `requestDeltaTokens` when that's the case.
111-
private func requestTokens(languageServer: LanguageServer, textView: TextView) async throws {
114+
private func requestTokens(languageServer: LanguageServer<DocumentType>, textView: TextView) async throws {
112115
guard let response = try await languageServer.requestSemanticTokens(for: documentURI) else {
113116
return
114117
}

CodeEdit/Features/LSP/LanguageServer/Capabilities/LanguageServer+DocumentSync.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extension LanguageServer {
1212
/// Tells the language server we've opened a document and would like to begin working with it.
1313
/// - Parameter document: The code document to open.
1414
/// - Throws: Throws errors produced by the language server connection.
15-
func openDocument(_ document: CodeFileDocument) async throws {
15+
func openDocument(_ document: DocumentType) async throws {
1616
do {
1717
guard resolveOpenCloseSupport(), let content = await getIsolatedDocumentContent(document) else {
1818
return
@@ -104,7 +104,7 @@ extension LanguageServer {
104104

105105
// Let the semantic token provider know about the update.
106106
// Note for future: If a related LSP object need notifying about document changes, do it here.
107-
try await document.lspHighlightProvider?.documentDidChange()
107+
try await document.languageServerObjects.highlightProvider?.documentDidChange()
108108
} catch {
109109
logger.warning("closeDocument: Error \(error)")
110110
throw error
@@ -115,7 +115,7 @@ extension LanguageServer {
115115

116116
/// Helper function for grabbing a document's content from the main actor.
117117
@MainActor
118-
private func getIsolatedDocumentContent(_ document: CodeFileDocument) -> DocumentContent? {
118+
private func getIsolatedDocumentContent(_ document: DocumentType) -> DocumentContent? {
119119
guard let uri = document.languageServerURI,
120120
let content = document.content?.string else {
121121
return nil
@@ -124,9 +124,9 @@ extension LanguageServer {
124124
}
125125

126126
@MainActor
127-
private func updateIsolatedDocument(_ document: CodeFileDocument) {
128-
document.lspCoordinator = openFiles.contentCoordinator(for: document)
129-
document.lspHighlightProvider = openFiles.semanticHighlighter(for: document)
127+
private func updateIsolatedDocument(_ document: DocumentType) {
128+
document.languageServerObjects.textCoordinator = openFiles.contentCoordinator(for: document)
129+
document.languageServerObjects.highlightProvider = openFiles.semanticHighlighter(for: document)
130130
}
131131

132132
// swiftlint:disable line_length

CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import LanguageClient
1111
import LanguageServerProtocol
1212
import OSLog
1313

14-
class LanguageServer {
15-
static let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "LanguageServer")
14+
class LanguageServer<DocumentType: LanguageServerDocument> {
15+
static var logger: Logger {
16+
Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "LanguageServer")
17+
}
1618
let logger: Logger
1719

1820
/// Identifies which language the server belongs to
@@ -25,7 +27,7 @@ class LanguageServer {
2527
/// Tracks documents and their associated objects.
2628
/// Use this property when adding new objects that need to track file data, or have a state associated with the
2729
/// language server and a document. For example, the content coordinator.
28-
let openFiles: LanguageServerFileMap
30+
let openFiles: LanguageServerFileMap<DocumentType>
2931

3032
/// Maps the language server's highlight config to one CodeEdit can read. See ``SemanticTokenMap``.
3133
let highlightMap: SemanticTokenMap?

0 commit comments

Comments
 (0)