Skip to content

Commit 3f4de79

Browse files
authored
fix(language-server): improve context reloading and error handling (#5126)
1 parent 4fe3ae5 commit 3f4de79

File tree

2 files changed

+94
-11
lines changed

2 files changed

+94
-11
lines changed

packages-integrations/language-server/src/core/context.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,42 @@ export class ContextManager {
6363

6464
async reload() {
6565
const dirs = Array.from(this.contextsMap.keys())
66-
await Promise.allSettled(dirs.map(dir => this.unloadContext(dir)))
66+
await Promise.all(dirs.map(dir => this.unloadContext(dir)))
6767

6868
this.fileContextCache.clear()
6969
this.configExistsCache.clear()
7070

71-
for (const dir of dirs)
72-
await this.loadContextInDirectory(dir)
73-
74-
if (!dirs.length)
75-
await this.loadContextInDirectory(this.cwd)
71+
if (dirs.length) {
72+
await Promise.all(dirs.map(async (dir) => {
73+
try {
74+
await this.loadContextInDirectory(dir)
75+
}
76+
catch (e: any) {
77+
this.warn(`⚠️ Failed to reload context for ${dir}: ${String(e.stack ?? e)}`)
78+
}
79+
}))
80+
}
81+
else {
82+
try {
83+
await this.loadContextInDirectory(this.cwd)
84+
}
85+
catch (e: any) {
86+
this.warn(`⚠️ Failed to reload context for ${this.cwd}: ${String(e.stack ?? e)}`)
87+
}
88+
}
7689

7790
this.events.emit('reload')
7891
}
7992

8093
async unloadContext(configDir: string) {
8194
const context = this.contextsMap.get(configDir)
82-
if (!context)
95+
if (context === undefined)
8396
return
8497

8598
this.contextsMap.delete(configDir)
99+
if (!context)
100+
return
101+
86102
this.clearFileContextCache(context)
87103
this.events.emit('contextUnload', context)
88104
this.events.emit('reload')
@@ -193,12 +209,12 @@ export class ContextManager {
193209
}
194210

195211
this.setupContextReload(context)
196-
this.events.emit('contextLoaded', context)
197212

198213
const uno = await context.uno
199214
this.logConfigInfo(sources, uno)
200-
201-
return this.finishLoading(dir, context)
215+
const result = this.finishLoading(dir, context)
216+
this.events.emit('contextLoaded', context)
217+
return result
202218
}
203219

204220
private setupYarnPnp(dir: string) {

packages-integrations/language-server/src/server.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
import type { Disposable } from 'vscode-languageserver/node'
12
import type { ServerSettings } from './types'
2-
import { fileURLToPath } from 'node:url'
3+
import path from 'node:path'
4+
import process from 'node:process'
5+
import { fileURLToPath, pathToFileURL } from 'node:url'
36
import { INCLUDE_COMMENT_IDE } from '#integration/constants'
47
import { isCssId } from '#integration/utils'
58
import { TextDocument } from 'vscode-languageserver-textdocument'
69
import {
710
createConnection,
11+
DidChangeWatchedFilesNotification,
812
ProposedFeatures,
913
TextDocuments,
1014
TextDocumentSyncKind,
15+
WatchKind,
1116
} from 'vscode-languageserver/node'
1217
import { registerColorProvider } from './capabilities/colorProvider'
1318
import { registerCompletion, resetAutoCompleteCache } from './capabilities/completion'
@@ -22,6 +27,46 @@ const documents = new TextDocuments(TextDocument)
2227

2328
let settings: ServerSettings = { ...defaultSettings }
2429
let contextManager: ContextManager
30+
let hasWatchedFilesCapability = false
31+
let serverInitialized = false
32+
let watcherDisposable: Disposable | undefined
33+
34+
async function updateConfigWatchers(): Promise<void> {
35+
if (!hasWatchedFilesCapability || !serverInitialized || !contextManager)
36+
return
37+
38+
watcherDisposable?.dispose()
39+
watcherDisposable = undefined
40+
41+
const sourceFiles = contextManager.contexts.flatMap(ctx => ctx.getConfigFileList())
42+
43+
watcherDisposable = await connection.client.register(
44+
DidChangeWatchedFilesNotification.type,
45+
{
46+
watchers: sourceFiles.length
47+
? sourceFiles.map(file => ({
48+
globPattern: {
49+
baseUri: pathToFileURL(path.dirname(file)).href,
50+
pattern: path.basename(file),
51+
},
52+
kind: WatchKind.Create | WatchKind.Change | WatchKind.Delete,
53+
}))
54+
: [{
55+
globPattern: '**/{uno,unocss,vite,svelte,astro,nuxt,iles}.config.{js,mjs,cjs,ts,mts,cts}',
56+
kind: WatchKind.Create | WatchKind.Change | WatchKind.Delete,
57+
}],
58+
},
59+
)
60+
}
61+
62+
// Don't crash the server.
63+
process.on('unhandledRejection', (reason) => {
64+
connection.console.error(`[unocss] Unhandled rejection: ${String(reason)}`)
65+
})
66+
// Same as above.
67+
process.on('uncaughtException', (err) => {
68+
connection.console.error(`[unocss] Uncaught exception: ${err.message}`)
69+
})
2570

2671
function getSettings() {
2772
return settings
@@ -32,6 +77,8 @@ function getContextManager() {
3277
}
3378

3479
connection.onInitialize((params) => {
80+
hasWatchedFilesCapability = !!params.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration
81+
3582
const workspaceFolders = params.workspaceFolders
3683
const rootUri = workspaceFolders?.[0]?.uri || params.rootUri
3784

@@ -48,6 +95,9 @@ connection.onInitialize((params) => {
4895
contextManager.events.on('unload', (ctx) => {
4996
resetAutoCompleteCache(ctx)
5097
})
98+
contextManager.events.on('contextLoaded', () => {
99+
void updateConfigWatchers()
100+
})
51101
}
52102

53103
// Register all LSP capabilities
@@ -71,9 +121,26 @@ connection.onInitialize((params) => {
71121
})
72122

73123
connection.onInitialized(async () => {
124+
serverInitialized = true
74125
if (contextManager)
75126
await contextManager.ready
76127
connection.console.log('✅ UnoCSS Language Server initialized')
128+
await updateConfigWatchers()
129+
})
130+
131+
let configReloadTimer: ReturnType<typeof setTimeout> | undefined
132+
133+
connection.onDidChangeWatchedFiles((_change) => {
134+
clearTimeout(configReloadTimer)
135+
configReloadTimer = setTimeout(async () => {
136+
if (!contextManager)
137+
return
138+
connection.console.log('🔄 Config file changed, reloading UnoCSS...')
139+
clearAllCache()
140+
await contextManager.reload()
141+
connection.console.log('🔵 Reloaded.')
142+
await updateConfigWatchers()
143+
}, 500)
77144
})
78145

79146
connection.onDidChangeConfiguration((change) => {

0 commit comments

Comments
 (0)