diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index 882d1be2..bc1b3bcc 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -174,6 +174,10 @@ export class TW { } } + if (results.some((result) => result.status === 'fulfilled')) { + await this.updateCommonCapabilities() + } + await this.listenForEvents() } @@ -628,8 +632,6 @@ export class TW { console.log(`[Global] Initializing projects...`) - await this.updateCommonCapabilities() - // init projects for documents that are _already_ open let readyDocuments: string[] = [] let enabledProjectCount = 0 @@ -896,6 +898,7 @@ export class TW { capabilities.add(DidChangeConfigurationNotification.type, undefined) } + this.commonRegistrations?.dispose() this.commonRegistrations = await this.connection.client.register(capabilities) } @@ -907,7 +910,7 @@ export class TW { } private lastTriggerCharacters: Set | undefined - private completionRegistration: Disposable | undefined + private completionRegistration: Promise | undefined private async updateTriggerCharacters() { // If the client does not suppory dynamic registration of completions then // we cannot update the set of trigger characters @@ -938,12 +941,27 @@ export class TW { this.lastTriggerCharacters = chars - this.completionRegistration?.dispose() - this.completionRegistration = await this.connection.client.register(CompletionRequest.type, { + let current = this.completionRegistration + this.completionRegistration = this.connection.client.register(CompletionRequest.type, { documentSelector: null, resolveProvider: true, triggerCharacters: Array.from(chars), }) + + // NOTE: + // This weird setup works around a race condition where multiple projects + // with different separators update their capabilities at the same time. It + // is extremely unlikely but it could cause `CompletionRequest` to be + // registered more than once with the LSP client. + // + // We store the promises meaning everything up to this point is synchronous + // so it should be fine but really the proper fix here is to: + // - Refactor workspace folder initialization so discovery, initialization, + // file events, config watchers, etc… are all shared. + // - Remove the need for the "restart" concept in the server for as much as + // possible. Each project should be capable of reloading its modules. + await current?.then((r) => r.dispose()) + await this.completionRegistration } private getProject(document: TextDocumentIdentifier): ProjectService { @@ -1134,7 +1152,7 @@ export class TW { this.commonRegistrations = undefined this.lastTriggerCharacters.clear() - this.completionRegistration?.dispose() + this.completionRegistration?.then((r) => r.dispose()) this.completionRegistration = undefined this.disposables.forEach((d) => d.dispose())