diff --git a/package.json b/package.json index 1178eb7a3e..75843c603d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "contributes": { "virtualWorkspaces": { "supported": "limited", - "description": "In virtual workspaces, it is not possible to load the cspell configuration from a JavaScript file. The configuration must be in a JSON, JSONC, or YAML file. Any configuration that relies upon `node_modules` will not be loaded." + "description": "In virtual workspaces, it is not possible to load the CSpell configuration from a JavaScript file. The configuration must be in a JSON, JSONC, or YAML file. Any configuration that relies upon `node_modules` will not be loaded." }, "untrustedWorkspaces": { "supported": false diff --git a/packages/_server/src/config/documentSettings.mts b/packages/_server/src/config/documentSettings.mts index 273a28e1de..d3fd0f907f 100644 --- a/packages/_server/src/config/documentSettings.mts +++ b/packages/_server/src/config/documentSettings.mts @@ -112,12 +112,15 @@ const fileConfigsToImport = '.vscode.configs.to.import.cspell.config.json'; const fileConfigLocalImport = '.vscode.configs.import.cspell.config.json'; const fileVSCodeSettings = '.vscode.folder.settings.json'; +const holdSettingsForMs = 1000; + export class DocumentSettings { // Cache per folder settings private valuesToClearOnReset: ClearFn[] = []; private readonly fetchSettingsForUri = this.createCache((docUri: string | undefined) => this._fetchSettingsForUri(docUri)); private readonly fetchVSCodeConfiguration = this.createCache((uri?: string) => this._fetchVSCodeConfiguration(uri)); private readonly fetchRepoRootForDir = this.createCache((dir: FsPath) => findRepoRoot(dir)); + private readonly pendingUrisToRelease = new Map(); public readonly fetchWorkspaceConfiguration = this.createCache((docUri: DocumentUri) => this._fetchWorkspaceConfiguration(docUri)); private readonly _folders = this.createLazy(() => this.fetchFolders()); readonly configsToImport = new Set(); @@ -132,7 +135,7 @@ export class DocumentSettings { readonly defaultSettings: CSpellUserSettings | Promise = _defaultSettings, ) {} - async getSettings(document: TextDocumentUri): Promise { + getSettings(document: TextDocumentUri): Promise { return this.getUriSettings(document.uri); } @@ -140,6 +143,21 @@ export class DocumentSettings { return this.fetchUriSettings(uri); } + releaseUriSettings(uri: string): void { + log(`releaseUriSettings ${uri}`); + const pending = this.pendingUrisToRelease.get(uri); + if (pending !== undefined) return; + + this.pendingUrisToRelease.set( + uri, + setTimeout(() => { + log(`releasedUriSettings ${uri}`); + this.pendingUrisToRelease.delete(uri); + this.fetchSettingsForUri.delete(uri); + }, holdSettingsForMs), + ); + } + async calcIncludeExclude(uri: Uri): Promise { const _uri = handleSpecialUri(uri); const settings = await this.fetchSettingsForUri(_uri.toString()); @@ -245,6 +263,13 @@ export class DocumentSettings { } private async fetchUriSettings(uri: string | undefined): Promise { + if (uri) { + const pendingRelease = this.pendingUrisToRelease.get(uri); + if (pendingRelease !== undefined) { + clearTimeout(pendingRelease); + this.pendingUrisToRelease.delete(uri); + } + } const exSettings = await this.fetchUriSettingsEx(uri); return exSettings.settings; } diff --git a/packages/_server/src/server.mts b/packages/_server/src/server.mts index 15bafc5e11..9036279bea 100644 --- a/packages/_server/src/server.mts +++ b/packages/_server/src/server.mts @@ -54,12 +54,10 @@ import { debounce as simpleDebounce } from './utils/debounce.mjs'; import { textToWords } from './utils/index.mjs'; import { createPrecisionLogger } from './utils/logging.mjs'; import * as Validator from './validator.mjs'; -import { CSpellFileSystemProvider } from './vfs/CSpellFileSystemProvider.mjs'; +import { bindFileSystemProvider } from './vfs/CSpellFileSystemProvider.mjs'; log('Starting Spell Checker Server'); -const tds = CSpell; - const defaultCheckLimit = Validator.defaultCheckLimit; const overRideDefaults: CSpellUserSettings = { @@ -127,7 +125,7 @@ export function run(): void { ), ); - CSpell.getVirtualFS().registerFileSystemProvider(new CSpellFileSystemProvider(clientServerApi, documents)); + dd(bindFileSystemProvider(clientServerApi, documents)); const documentSettings = new DocumentSettings(connection, clientServerApi, defaultSettings); @@ -272,6 +270,7 @@ export function run(): void { validationByDoc.delete(uri); sub.unsubscribe(); } + documentSettings.releaseUriSettings(uri); // A text document was closed we clear the diagnostics catchPromise(connection.sendDiagnostics({ uri, diagnostics: [] }), 'onDidClose'); }), @@ -518,7 +517,7 @@ export function run(): void { } async function getSettingsToUseForDocument(doc: TextDocument) { - return tds.constructSettingsForText(await getBaseSettings(doc), doc.getText(), doc.languageId); + return CSpell.constructSettingsForText(await getBaseSettings(doc), doc.getText(), doc.languageId); } function isStale(doc: TextDocument, writeLog = true): boolean { diff --git a/packages/_server/src/vfs/CSpellFileSystemProvider.mts b/packages/_server/src/vfs/CSpellFileSystemProvider.mts index 9fff70724d..3a89ec9c5c 100644 --- a/packages/_server/src/vfs/CSpellFileSystemProvider.mts +++ b/packages/_server/src/vfs/CSpellFileSystemProvider.mts @@ -2,19 +2,22 @@ import { logDebug } from '@internal/common-utils/log'; import type { VProviderFileSystem } from 'cspell-io'; import { FSCapabilityFlags, urlOrReferenceToUrl, VFileType } from 'cspell-io'; import type { VFileSystemProvider } from 'cspell-lib'; -import type { TextDocuments } from 'vscode-languageserver/node.js'; +import { getVirtualFS } from 'cspell-lib'; +import type { Disposable, TextDocuments } from 'vscode-languageserver/node.js'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import type { ServerSideApi } from '../api.js'; import { FileType } from '../api.js'; -const UseCSpellForProtocol: Record = { +const debugFileProtocol = false; + +const NotHandledProtocols: Record = { 'http:': true, 'https:': true, - 'file:': true, // Use the cspell-io file system provider for performance. + 'file:': !debugFileProtocol, // Use the cspell-io file system provider for performance. }; -export class CSpellFileSystemProvider implements VFileSystemProvider { +class CSpellFileSystemProvider implements VFileSystemProvider { readonly name = 'VSCode'; constructor( private api: ServerSideApi, @@ -22,7 +25,7 @@ export class CSpellFileSystemProvider implements VFileSystemProvider { ) {} getFileSystem(url: URL): VProviderFileSystem | undefined { - if (UseCSpellForProtocol[url.protocol.toLowerCase()]) return undefined; + if (NotHandledProtocols[url.protocol.toLowerCase()]) return undefined; const vfs: VProviderFileSystem = { capabilities: FSCapabilityFlags.Read | FSCapabilityFlags.Stat | FSCapabilityFlags.ReadDir, @@ -30,7 +33,6 @@ export class CSpellFileSystemProvider implements VFileSystemProvider { const url = urlOrReferenceToUrl(urlRef); logDebug(`stat req: ${url.href}`); const stat = await this.api.clientRequest.vfsStat(url.href); - logDebug(`stat res: ${url.href}\n \t${JSON.stringify(stat)}`); return { size: stat.size, @@ -81,6 +83,15 @@ export class CSpellFileSystemProvider implements VFileSystemProvider { } } +export function bindFileSystemProvider(api: ServerSideApi, documents: TextDocuments): Disposable { + const provider = new CSpellFileSystemProvider(api, documents); + const vfs = getVirtualFS(); + if (debugFileProtocol) { + vfs.enableLogging(true); + } + return vfs.registerFileSystemProvider(provider); +} + export class VFSError extends Error { constructor( message: string, diff --git a/packages/client/src/extension.ts b/packages/client/src/extension.ts index 09670b7f37..7b1cc44d83 100644 --- a/packages/client/src/extension.ts +++ b/packages/client/src/extension.ts @@ -128,8 +128,8 @@ export async function activate(context: ExtensionContext): Promise detectPossibleCSpellConfigChange(event.files); } - function handleOpenFile(doc: vscode.TextDocument) { - detectPossibleCSpellConfigChange([doc.uri]); + function handleOpenFile(_doc: vscode.TextDocument) { + // detectPossibleCSpellConfigChange([doc.uri]); } function handleOnDidChangeActiveTextEditor(e?: vscode.TextEditor) { diff --git a/packages/client/src/settings/CSpellSettings.ts b/packages/client/src/settings/CSpellSettings.ts index 797c12c06d..5a22910147 100644 --- a/packages/client/src/settings/CSpellSettings.ts +++ b/packages/client/src/settings/CSpellSettings.ts @@ -34,6 +34,7 @@ export const configFileLocations = [ // Dynamic config is looked for last 'cspell.config.js', 'cspell.config.cjs', + 'cspell.config.mjs', // .config '.config/.cspell.json', '.config/cspell.json', @@ -51,7 +52,16 @@ export const configFileLocations = [ '.config/cspell.config.cjs', ] as const; -export const configFileLocationGlob = `**/{${configFileLocations.join(',')}}`; +const setOfConfigFilesNames = new Set(configFileLocations.map((filename) => filename.split('/').slice(-1)[0])); + +/** + * A set of files that if changed, could indicate that the cspell configuration changed. + * + * An alias of possibleConfigFiles + */ +export const configFilesToWatch: Set = Object.freeze(setOfConfigFilesNames); + +export const configFileLocationGlob = `**/{${[...setOfConfigFilesNames].join(',')}}`; type ConfigFileNames = (typeof configFileLocations)[number]; @@ -59,17 +69,8 @@ export const nestedConfigLocations = ['package.json']; export const cspellConfigDirectory = '.cspell'; -export const possibleConfigFiles = Object.freeze(new Set(configFileLocations)); - export const preferredConfigFiles: ConfigFileNames[] = ['cspell.json', 'cspell.config.yaml', 'package.json']; -/** - * A set of files that if changed, could indicate that the cspell configuration changed. - * - * An alias of possibleConfigFiles - */ -export const configFilesToWatch = possibleConfigFiles as Set; - export type CSpellSettings = CSpellUserSettings; const defaultSettings: CSpellSettings = Object.freeze({