Skip to content

Commit 8cde497

Browse files
committed
Adjust LSP Document Lifetimes
1 parent f6f2b80 commit 8cde497

File tree

7 files changed

+56
-74
lines changed

7 files changed

+56
-74
lines changed

CodeEdit/Features/Editor/Views/CodeFileView.swift

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ struct CodeFileView: View {
2121

2222
/// Any coordinators passed to the view.
2323
private var textViewCoordinators: [TextViewCoordinator]
24-
25-
@State private var highlightProviders: [any HighlightProviding] = []
24+
private var highlightProviders: [any HighlightProviding] = []
2625

2726
@AppSettings(\.textEditing.defaultTabWidth)
2827
var defaultTabWidth
@@ -86,15 +85,15 @@ struct CodeFileView: View {
8685
self.textViewCoordinators = textViewCoordinators
8786
+ [editorInstance.rangeTranslator]
8887
+ [codeFile.contentCoordinator]
89-
+ [codeFile.languageServerObjects.textCoordinator].compactMap({ $0 })
88+
+ [codeFile.languageServerObjects.textCoordinator]
9089
self.isEditable = isEditable
9190

9291
if let openOptions = codeFile.openOptions {
9392
codeFile.openOptions = nil
9493
editorInstance.cursorPositions = openOptions.cursorPositions
9594
}
9695

97-
updateHighlightProviders()
96+
highlightProviders = [codeFile.languageServerObjects.highlightProvider] + [treeSitterClient]
9897

9998
codeFile
10099
.contentCoordinator
@@ -186,10 +185,6 @@ struct CodeFileView: View {
186185
.onChange(of: settingsFont) { newFontSetting in
187186
font = newFontSetting.current
188187
}
189-
.onReceive(codeFile.$languageServerObjects) { languageServerObjects in
190-
// This will not be called in single-file views (for now) but is safe to listen to either way
191-
updateHighlightProviders(lspHighlightProvider: languageServerObjects.highlightProvider)
192-
}
193188
}
194189

195190
/// Determines the style of bracket emphasis based on the `bracketEmphasis` setting and the current theme.
@@ -212,12 +207,6 @@ struct CodeFileView: View {
212207
return .underline(color: color)
213208
}
214209
}
215-
216-
/// Updates the highlight providers array.
217-
/// - Parameter lspHighlightProvider: The language server provider, if available.
218-
private func updateHighlightProviders(lspHighlightProvider: HighlightProviding? = nil) {
219-
highlightProviders = [lspHighlightProvider].compactMap({ $0 }) + [treeSitterClient]
220-
}
221210
}
222211

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

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,22 @@ class LSPContentCoordinator<DocumentType: LanguageServerDocument>: TextViewCoord
3232
private var task: Task<Void, Never>?
3333

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

3737
/// Initializes a content coordinator, and begins an async stream of updates
38-
init(documentURI: String, languageServer: LanguageServer<DocumentType>) {
38+
init(documentURI: String? = nil, languageServer: LanguageServer<DocumentType>? = nil) {
3939
self.documentURI = documentURI
4040
self.languageServer = languageServer
4141

4242
setUpUpdatesTask()
4343
}
4444

45+
func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {
46+
languageServer = server
47+
documentURI = document.languageServerURI
48+
}
49+
50+
4551
func setUpUpdatesTask() {
4652
task?.cancel()
4753
// Create this stream here so it's always set up when the text view is set up, rather than only once on init.
@@ -76,7 +82,7 @@ class LSPContentCoordinator<DocumentType: LanguageServerDocument>: TextViewCoord
7682
}
7783

7884
func textView(_ textView: TextView, didReplaceContentsIn range: NSRange, with string: String) {
79-
guard let lspRange = editedRange else {
85+
guard let lspRange = editedRange, let documentURI else {
8086
return
8187
}
8288
self.editedRange = nil

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ final class SemanticTokenHighlightProvider<
3232
typealias EditCallback = @MainActor (Result<IndexSet, any Error>) -> Void
3333
typealias HighlightCallback = @MainActor (Result<[HighlightRange], any Error>) -> Void
3434

35-
private let tokenMap: SemanticTokenMap
36-
private let documentURI: String
35+
private var tokenMap: SemanticTokenMap?
36+
private var documentURI: String?
3737
private weak var languageServer: LanguageServer<DocumentType>?
3838
private weak var textView: TextView?
3939

@@ -45,13 +45,23 @@ final class SemanticTokenHighlightProvider<
4545
textView?.documentRange ?? .zero
4646
}
4747

48-
init(tokenMap: SemanticTokenMap, languageServer: LanguageServer<DocumentType>, documentURI: String) {
48+
init(
49+
tokenMap: SemanticTokenMap? = nil,
50+
languageServer: LanguageServer<DocumentType>? = nil,
51+
documentURI: String? = nil
52+
) {
4953
self.tokenMap = tokenMap
5054
self.languageServer = languageServer
5155
self.documentURI = documentURI
5256
self.storage = Storage()
5357
}
5458

59+
func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {
60+
languageServer = server
61+
documentURI = document.languageServerURI
62+
tokenMap = server.highlightMap
63+
}
64+
5565
// MARK: - Language Server Content Lifecycle
5666

5767
/// Called when the language server finishes sending a document update.
@@ -95,7 +105,8 @@ final class SemanticTokenHighlightProvider<
95105
textView: TextView,
96106
lastResultId: String
97107
) async throws {
98-
guard let response = try await languageServer.requestSemanticTokens(
108+
guard let documentURI,
109+
let response = try await languageServer.requestSemanticTokens(
99110
for: documentURI,
100111
previousResultId: lastResultId
101112
) else {
@@ -112,7 +123,7 @@ final class SemanticTokenHighlightProvider<
112123
/// Requests and applies tokens for an entire document. This does not require a previous response id, and should be
113124
/// used in place of `requestDeltaTokens` when that's the case.
114125
private func requestTokens(languageServer: LanguageServer<DocumentType>, textView: TextView) async throws {
115-
guard let response = try await languageServer.requestSemanticTokens(for: documentURI) else {
126+
guard let documentURI, let response = try await languageServer.requestSemanticTokens(for: documentURI) else {
116127
return
117128
}
118129
await applyEntireResponse(response, callback: lastEditCallback)
@@ -159,7 +170,7 @@ final class SemanticTokenHighlightProvider<
159170
return
160171
}
161172

162-
guard let lspRange = textView.lspRangeFrom(nsRange: range) else {
173+
guard let lspRange = textView.lspRangeFrom(nsRange: range), let tokenMap else {
163174
completion(.failure(HighlightError.lspRangeFailure))
164175
return
165176
}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ extension LanguageServer {
8686
switch resolveDocumentSyncKind() {
8787
case .full:
8888
guard let content = await getIsolatedDocumentContent(document) else {
89+
logger.error("Failed to get isolated document content")
8990
return
9091
}
9192
let changeEvent = TextDocumentContentChangeEvent(range: nil, rangeLength: nil, text: content.string)
@@ -107,7 +108,7 @@ extension LanguageServer {
107108

108109
// Let the semantic token provider know about the update.
109110
// Note for future: If a related LSP object need notifying about document changes, do it here.
110-
try await document.languageServerObjects.highlightProvider?.documentDidChange()
111+
try await document.languageServerObjects.highlightProvider.documentDidChange()
111112
} catch {
112113
logger.warning("closeDocument: Error \(error)")
113114
throw error
@@ -128,10 +129,7 @@ extension LanguageServer {
128129

129130
@MainActor
130131
private func updateIsolatedDocument(_ document: DocumentType) {
131-
document.languageServerObjects = LanguageServerDocumentObjects(
132-
textCoordinator: openFiles.contentCoordinator(for: document),
133-
highlightProvider: openFiles.semanticHighlighter(for: document)
134-
)
132+
document.languageServerObjects.setUp(server: self, document: document)
135133
}
136134

137135
@MainActor

CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class LanguageServer<DocumentType: LanguageServerDocument> {
5151
lspInstance: InitializingServer,
5252
lspPid: pid_t,
5353
serverCapabilities: ServerCapabilities,
54-
rootPath: URL
54+
rootPath: URL,
55+
logContainer: LanguageServerLogContainer
5556
) {
5657
self.languageId = languageId
5758
self.binary = binary
@@ -60,7 +61,7 @@ class LanguageServer<DocumentType: LanguageServerDocument> {
6061
self.serverCapabilities = serverCapabilities
6162
self.rootPath = rootPath
6263
self.openFiles = LanguageServerFileMap()
63-
self.logContainer = LanguageServerLogContainer(language: languageId)
64+
self.logContainer = logContainer
6465
self.logger = Logger(
6566
subsystem: Bundle.main.bundleIdentifier ?? "",
6667
category: "LanguageServer.\(languageId.rawValue)"
@@ -89,9 +90,11 @@ class LanguageServer<DocumentType: LanguageServerDocument> {
8990
environment: binary.env
9091
)
9192

93+
let logContainer = LanguageServerLogContainer(language: languageId)
9294
let (connection, process) = try makeLocalServerConnection(
9395
languageId: languageId,
94-
executionParams: executionParams
96+
executionParams: executionParams,
97+
logContainer: logContainer
9598
)
9699
let server = InitializingServer(
97100
server: connection,
@@ -105,7 +108,8 @@ class LanguageServer<DocumentType: LanguageServerDocument> {
105108
lspInstance: server,
106109
lspPid: process.processIdentifier,
107110
serverCapabilities: initializationResponse.capabilities,
108-
rootPath: URL(filePath: workspacePath)
111+
rootPath: URL(filePath: workspacePath),
112+
logContainer: logContainer
109113
)
110114
}
111115

@@ -118,13 +122,17 @@ class LanguageServer<DocumentType: LanguageServerDocument> {
118122
/// - Returns: A new connection to the language server.
119123
static func makeLocalServerConnection(
120124
languageId: LanguageIdentifier,
121-
executionParams: Process.ExecutionParameters
125+
executionParams: Process.ExecutionParameters,
126+
logContainer: LanguageServerLogContainer
122127
) throws -> (connection: JSONRPCServerConnection, process: Process) {
123128
do {
124129
let (channel, process) = try DataChannel.localProcessChannel(
125130
parameters: executionParams,
126-
terminationHandler: {
131+
terminationHandler: { [weak logContainer] in
127132
logger.debug("Terminated data channel for \(languageId.rawValue)")
133+
logContainer?.appendLog(
134+
LogMessageParams(type: .error, message: "Data Channel Terminated Unexpectedly")
135+
)
128136
}
129137
)
130138
return (JSONRPCServerConnection(dataChannel: channel), process)

CodeEdit/Features/LSP/LanguageServer/LanguageServerFileMap.swift

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ class LanguageServerFileMap<DocumentType: LanguageServerDocument> {
1616
private struct DocumentObject {
1717
let uri: String
1818
var documentVersion: Int
19-
var contentCoordinator: LSPContentCoordinator<DocumentType>
20-
var semanticHighlighter: HighlightProviderType?
2119
}
2220

2321
private var trackedDocuments: NSMapTable<NSString, DocumentType>
@@ -32,24 +30,7 @@ class LanguageServerFileMap<DocumentType: LanguageServerDocument> {
3230
func addDocument(_ document: DocumentType, for server: LanguageServer<DocumentType>) {
3331
guard let uri = document.languageServerURI else { return }
3432
trackedDocuments.setObject(document, forKey: uri as NSString)
35-
var docData = DocumentObject(
36-
uri: uri,
37-
documentVersion: 0,
38-
contentCoordinator: LSPContentCoordinator(
39-
documentURI: uri,
40-
languageServer: server
41-
),
42-
semanticHighlighter: nil
43-
)
44-
45-
if let tokenMap = server.highlightMap {
46-
docData.semanticHighlighter = HighlightProviderType(
47-
tokenMap: tokenMap,
48-
languageServer: server,
49-
documentURI: uri
50-
)
51-
}
52-
33+
let docData = DocumentObject(uri: uri, documentVersion: 0)
5334
trackedDocumentData[uri] = docData
5435
}
5536

@@ -87,22 +68,4 @@ class LanguageServerFileMap<DocumentType: LanguageServerDocument> {
8768
func documentVersion(for uri: DocumentUri) -> Int? {
8869
return trackedDocumentData[uri]?.documentVersion
8970
}
90-
91-
// MARK: - Content Coordinator
92-
93-
func contentCoordinator(for document: DocumentType) -> LSPContentCoordinator<DocumentType>? {
94-
guard let uri = document.languageServerURI else { return nil }
95-
return contentCoordinator(for: uri)
96-
}
97-
98-
func contentCoordinator(for uri: DocumentUri) -> LSPContentCoordinator<DocumentType>? {
99-
trackedDocumentData[uri]?.contentCoordinator
100-
}
101-
102-
// MARK: - Semantic Highlighter
103-
104-
func semanticHighlighter(for document: DocumentType) -> HighlightProviderType? {
105-
guard let uri = document.languageServerURI else { return nil }
106-
return trackedDocumentData[uri]?.semanticHighlighter
107-
}
10871
}

CodeEdit/Features/LSP/LanguageServerDocument.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,15 @@ import CodeEditLanguages
1010

1111
/// A set of properties a language server sets when a document is registered.
1212
struct LanguageServerDocumentObjects<DocumentType: LanguageServerDocument> {
13-
var textCoordinator: LSPContentCoordinator<DocumentType>?
14-
var highlightProvider: SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType>?
13+
var textCoordinator: LSPContentCoordinator<DocumentType> = LSPContentCoordinator()
14+
// swiftlint:disable:next line_length
15+
var highlightProvider: SemanticTokenHighlightProvider<SemanticTokenStorage, DocumentType> = SemanticTokenHighlightProvider()
16+
17+
@MainActor
18+
func setUp(server: LanguageServer<DocumentType>, document: DocumentType) {
19+
textCoordinator.setUp(server: server, document: document)
20+
highlightProvider.setUp(server: server, document: document)
21+
}
1522
}
1623

1724
/// A protocol that allows a language server to register objects on a text document.

0 commit comments

Comments
 (0)