diff --git a/packages/_server/src/api/api.ts b/packages/_server/src/api/api.ts index 594fcf88fc..6ee9f651f3 100644 --- a/packages/_server/src/api/api.ts +++ b/packages/_server/src/api/api.ts @@ -45,9 +45,6 @@ export interface ServerNotificationsAPI { * Note: RPC requests to the client/extension is rare. */ export interface ClientRequestsAPI { - // addWordsToVSCodeSettingsFromServer: (words: string[], documentUri: string, target: ConfigurationTarget) => void; - // addWordsToDictionaryFileFromServer: (words: string[], documentUri: string, dict: { uri: string; name: string }) => void; - // addWordsToConfigFileFromServer: (words: string[], documentUri: string, config: { uri: string; name: string }) => void; onWorkspaceConfigForDocumentRequest: (req: WorkspaceConfigForDocumentRequest) => WorkspaceConfigForDocumentResponse; } diff --git a/packages/_server/src/codeActions.mts b/packages/_server/src/codeActions.mts index ae8251111c..7f498a1b13 100644 --- a/packages/_server/src/codeActions.mts +++ b/packages/_server/src/codeActions.mts @@ -3,11 +3,13 @@ import { capitalize } from '@internal/common-utils/util.js'; import type { SpellingDictionary } from 'cspell-lib'; import { constructSettingsForText, getDictionary, IssueType, Text } from 'cspell-lib'; import { format } from 'util'; +import type { CodeActionParams, Range as LangServerRange, TextDocuments } from 'vscode-languageserver/node.js'; +import { Command as LangServerCommand } from 'vscode-languageserver/node.js'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import type { Diagnostic } from 'vscode-languageserver-types'; import { CodeAction, CodeActionKind, TextEdit } from 'vscode-languageserver-types'; -import type { ServerSideApi } from './api.js'; +import type { UriString, WorkspaceConfigForDocument } from './api.js'; import { clientCommands as cc } from './commands.mjs'; import type { ConfigScope, ConfigTarget, ConfigTargetCSpell, ConfigTargetDictionary, ConfigTargetVSCode } from './config/configTargets.mjs'; import { ConfigKinds, ConfigScopes } from './config/configTargets.mjs'; @@ -21,8 +23,6 @@ import { SuggestionGenerator } from './SuggestionsGenerator.mjs'; import { uniqueFilter } from './utils/index.mjs'; import * as range from './utils/range.mjs'; import * as Validator from './validator.mjs'; -import type { CodeActionParams, Range as LangServerRange, TextDocuments } from './vscodeLanguageServer/index.cjs'; -import { Command as LangServerCommand } from './vscodeLanguageServer/index.cjs'; const createCommand = LangServerCommand.create; @@ -38,13 +38,17 @@ function extractDiagnosticData(diag: Diagnostic): DiagnosticData { return data as DiagnosticData; } +export interface CodeActionHandlerDependencies { + fetchSettings: (doc: TextDocument) => Promise; + getSettingsVersion: (doc: TextDocument) => number; + fetchWorkspaceConfigForDocument: (uri: UriString) => Promise; +} + export function onCodeActionHandler( documents: TextDocuments, - fnSettings: (doc: TextDocument) => Promise, - fnSettingsVersion: (doc: TextDocument) => number, - clientApi: ServerSideApi, + dependencies: CodeActionHandlerDependencies, ): (params: CodeActionParams) => Promise { - const codeActionHandler = new CodeActionHandler(documents, fnSettings, fnSettingsVersion, clientApi); + const codeActionHandler = new CodeActionHandler(documents, dependencies); return (params) => codeActionHandler.handler(params); } @@ -62,9 +66,7 @@ class CodeActionHandler { constructor( readonly documents: TextDocuments, - readonly fnSettings: (doc: TextDocument) => Promise, - readonly fnSettingsVersion: (doc: TextDocument) => number, - readonly clientApi: ServerSideApi, + readonly dependencies: CodeActionHandlerDependencies, ) { this.settingsCache = new Map(); this.sugGen = new SuggestionGenerator((doc) => this.getSettings(doc)); @@ -72,7 +74,7 @@ class CodeActionHandler { async getSettings(doc: TextDocument): Promise { const cached = this.settingsCache.get(doc.uri); - const settingsVersion = this.fnSettingsVersion(doc); + const settingsVersion = this.dependencies.getSettingsVersion(doc); if (cached?.docVersion === doc.version && cached.settingsVersion === settingsVersion) { return cached.settings; } @@ -82,7 +84,7 @@ class CodeActionHandler { } private async constructSettings(doc: TextDocument): Promise { - const settings = constructSettingsForText(await this.fnSettings(doc), doc.getText(), doc.languageId); + const settings = constructSettingsForText(await this.dependencies.fetchSettings(doc), doc.getText(), doc.languageId); const dictionary = await getDictionary(settings); return { settings, dictionary }; } @@ -134,7 +136,7 @@ class CodeActionHandler { log(`CodeAction Uri Not allowed: ${uri}`); return []; } - const pWorkspaceConfig = this.clientApi.clientRequest.onWorkspaceConfigForDocumentRequest({ uri }); + const pWorkspaceConfig = this.dependencies.fetchWorkspaceConfigForDocument(uri); function replaceText(range: LangServerRange, text?: string) { return TextEdit.replace(range, text || ''); @@ -194,7 +196,7 @@ class CodeActionHandler { if (eslintSpellCheckerDiags.length > 1) return []; const { settings: docSetting, dictionary } = await this.getSettings(textDocument); - const pWorkspaceConfig = this.clientApi.clientRequest.onWorkspaceConfigForDocumentRequest({ uri }); + const pWorkspaceConfig = this.dependencies.fetchWorkspaceConfigForDocument(uri); async function genCodeActions(_dictionary: SpellingDictionary) { const word = extractText(textDocument, params.range); diff --git a/packages/_server/src/commands.mts b/packages/_server/src/commands.mts index 23bfcf750c..7613769ae1 100644 --- a/packages/_server/src/commands.mts +++ b/packages/_server/src/commands.mts @@ -1,5 +1,6 @@ +import { Command } from 'vscode-languageserver/node.js'; + import type { CommandsToClient } from './api.js'; -import { Command } from './vscodeLanguageServer/index.cjs'; const prefix = 'cSpell.'; diff --git a/packages/_server/src/config/WorkspacePathResolver.mts b/packages/_server/src/config/WorkspacePathResolver.mts index db32429c59..2c36982145 100644 --- a/packages/_server/src/config/WorkspacePathResolver.mts +++ b/packages/_server/src/config/WorkspacePathResolver.mts @@ -2,9 +2,9 @@ import { logError } from '@internal/common-utils/log.js'; import type { BaseSetting, Glob, GlobDef } from 'cspell-lib'; import * as os from 'os'; import * as Path from 'path'; +import type { WorkspaceFolder } from 'vscode-languageserver/node.js'; import { URI as Uri } from 'vscode-uri'; -import type { WorkspaceFolder } from '../vscodeLanguageServer/index.cjs'; import type { CSpellUserSettings } from './cspellConfig/index.mjs'; import { extractDictionaryDefinitions, extractDictionaryList } from './customDictionaries.mjs'; diff --git a/packages/_server/src/config/WorkspacePathResolver.test.mts b/packages/_server/src/config/WorkspacePathResolver.test.mts index a6141a91e8..1da8ca774e 100644 --- a/packages/_server/src/config/WorkspacePathResolver.test.mts +++ b/packages/_server/src/config/WorkspacePathResolver.test.mts @@ -1,9 +1,9 @@ import { logError } from '@internal/common-utils/log.js'; import * as Path from 'path'; import { describe, expect, type Mock, test, vi } from 'vitest'; +import type { WorkspaceFolder } from 'vscode-languageserver/node.js'; import { URI as Uri } from 'vscode-uri'; -import type { WorkspaceFolder } from '../vscodeLanguageServer/index.cjs'; import type { CSpellUserSettings, CustomDictionaries } from './cspellConfig/index.mjs'; import { createWorkspaceNamesResolver, debugExports, resolveSettings } from './WorkspacePathResolver.mjs'; diff --git a/packages/_server/src/config/configWatcher.mts b/packages/_server/src/config/configWatcher.mts index 84a4ec01a5..20fe4c00ad 100644 --- a/packages/_server/src/config/configWatcher.mts +++ b/packages/_server/src/config/configWatcher.mts @@ -1,8 +1,8 @@ import type { CSpellUserSettings } from '@cspell/cspell-types'; import { getSources } from 'cspell-lib'; +import type { Disposable } from 'vscode-languageserver/node.js'; import { FileWatcher } from '../utils/fileWatcher.mjs'; -import type { Disposable } from '../vscodeLanguageServer/index.cjs'; export class ConfigWatcher extends FileWatcher implements Disposable { constructor() { diff --git a/packages/_server/src/config/dictionaryWatcher.mts b/packages/_server/src/config/dictionaryWatcher.mts index e1f7a5688c..1793fe5598 100644 --- a/packages/_server/src/config/dictionaryWatcher.mts +++ b/packages/_server/src/config/dictionaryWatcher.mts @@ -1,7 +1,7 @@ import type { CSpellUserSettings } from '@cspell/cspell-types'; +import type { Disposable } from 'vscode-languageserver/node.js'; import { FileWatcher } from '../utils/fileWatcher.mjs'; -import type { Disposable } from '../vscodeLanguageServer/index.cjs'; export type Listener = (eventType?: string, filename?: string) => void; diff --git a/packages/_server/src/config/documentSettings.mts b/packages/_server/src/config/documentSettings.mts index 5039e11870..465128b0f0 100644 --- a/packages/_server/src/config/documentSettings.mts +++ b/packages/_server/src/config/documentSettings.mts @@ -24,7 +24,7 @@ import { ExclusionHelper, getSources, mergeSettings, - readSettingsFiles as cspellReadSettingsFiles, + readSettings as cspellReadSettingsFile, searchForConfig, } from 'cspell-lib'; import * as fs from 'fs'; @@ -32,12 +32,12 @@ import type { Sequence } from 'gensequence'; import { genSequence } from 'gensequence'; import * as os from 'os'; import * as path from 'path'; +import type { Connection, WorkspaceFolder } from 'vscode-languageserver/node.js'; import { URI as Uri, Utils as UriUtils } from 'vscode-uri'; -import type { VSCodeSettingsCspell } from '../api.js'; +import type { DocumentUri, ServerSideApi, VSCodeSettingsCspell, WorkspaceConfigForDocument } from '../api.js'; import { extensionId } from '../constants.mjs'; import { uniqueFilter } from '../utils/index.mjs'; -import type { Connection, WorkspaceFolder } from '../vscodeLanguageServer/index.cjs'; import type { CSpellUserSettings } from './cspellConfig/index.mjs'; import { canAddWordsToDictionary } from './customDictionaries.mjs'; import { handleSpecialUri } from './docUriHelper.mjs'; @@ -100,20 +100,21 @@ const schemeBlockList = ['git', 'output', 'debug']; const defaultRootUri = toFileUri(process.cwd()).toString(); -const _defaultSettings: CSpellUserSettings = Object.freeze({}); +const _defaultSettings: CSpellUserSettings = Object.freeze(Object.create(null)); const defaultCheckOnlyEnabledFileTypes = true; interface Clearable { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - clear: () => any; + clear: () => void; } + export class DocumentSettings { // Cache per folder settings private cachedValues: Clearable[] = []; - private readonly fetchSettingsForUri = this.createCache((key: string) => this._fetchSettingsForUri(key)); - private readonly fetchVSCodeConfiguration = this.createCache((key: string) => this._fetchVSCodeConfiguration(key)); + private readonly fetchSettingsForUri = this.createCache((docUri: string) => this._fetchSettingsForUri(docUri)); + private readonly fetchVSCodeConfiguration = this.createCache((uri: string) => this._fetchVSCodeConfiguration(uri)); private readonly fetchRepoRootForDir = this.createCache((dir: FsPath) => findRepoRoot(dir)); + public readonly fetchWorkspaceConfiguration = this.createCache((docUri: DocumentUri) => this._fetchWorkspaceConfiguration(docUri)); private readonly _folders = this.createLazy(() => this.fetchFolders()); readonly configsToImport = new Set(); private readonly importedSettings = this.createLazy(() => this._importSettings()); @@ -122,6 +123,7 @@ export class DocumentSettings { constructor( readonly connection: Connection, + readonly api: ServerSideApi, readonly defaultSettings: CSpellUserSettings = _defaultSettings, ) {} @@ -197,12 +199,13 @@ export class DocumentSettings { return calcExcludedBy(uri, extSettings); } - resetSettings(): void { + async resetSettings(): Promise { log('resetSettings'); - clearCachedFiles(); + const waitFor = clearCachedFiles(); this.cachedValues.forEach((cache) => cache.clear()); this._version += 1; this.gitIgnore = new GitIgnore(); + await waitFor; } get folders(): Promise { @@ -212,18 +215,22 @@ export class DocumentSettings { private _importSettings() { log('importSettings'); const importPaths = [...this.configsToImport].sort(); - return readSettingsFiles(importPaths); + return mergeSettings({}, ...readSettingsFiles(importPaths)); + } + + private async _fetchWorkspaceConfiguration(uri: DocumentUri): Promise { + return this.api.clientRequest.onWorkspaceConfigForDocumentRequest({ uri }); } get version(): number { return this._version; } - registerConfigurationFile(path: string): void { + async registerConfigurationFile(path: string): Promise { log('registerConfigurationFile:', path); this.configsToImport.add(path); this.importedSettings.clear(); - this.resetSettings(); + await this.resetSettings(); } private async fetchUriSettings(uri: string): Promise { @@ -394,7 +401,9 @@ function resolveConfigImports(config: CSpellUserSettings, folderUri: string): CS const importAbsPath = imports.map((file) => resolvePath(uriFsPath, file)); log(`resolvingConfigImports: [\n${imports.join('\n')}]`); log(`resolvingConfigImports ABS: [\n${importAbsPath.join('\n')}]`); - const { import: _import, ...result } = importAbsPath.length ? mergeSettings(readSettingsFiles([...importAbsPath]), config) : config; + const { import: _import, ...result } = importAbsPath.length + ? mergeSettings({}, ...readSettingsFiles([...importAbsPath]), config) + : config; return result; } @@ -402,7 +411,7 @@ function readSettingsFiles(paths: string[]) { // log('readSettingsFiles:', paths); const existingPaths = paths.filter((filename) => exists(filename)); log('readSettingsFiles:', existingPaths); - return existingPaths.length ? cspellReadSettingsFiles(existingPaths) : {}; + return existingPaths.map((file) => cspellReadSettingsFile(file)); } function exists(file: string): boolean { diff --git a/packages/_server/src/config/documentSettings.test.mts b/packages/_server/src/config/documentSettings.test.mts index 99cf0630e4..9cef0dd6e1 100644 --- a/packages/_server/src/config/documentSettings.test.mts +++ b/packages/_server/src/config/documentSettings.test.mts @@ -4,10 +4,11 @@ import { getDefaultSettings } from 'cspell-lib'; import * as os from 'os'; import * as Path from 'path'; import { beforeEach, describe, expect, test, vi } from 'vitest'; +import type { Connection, WorkspaceFolder } from 'vscode-languageserver/node.js'; import { URI as Uri } from 'vscode-uri'; +import { createMockServerSideApi } from '../test/test.api.js'; import { extendExpect } from '../test/test.matchers.js'; -import type { Connection, WorkspaceFolder } from '../vscodeLanguageServer/index.cjs'; import type { CSpellUserSettings } from './cspellConfig/index.mjs'; import type { ExcludedByMatch } from './documentSettings.mjs'; import { @@ -82,10 +83,10 @@ describe('Validate DocumentSettings', () => { mockGetWorkspaceFolders.mockClear(); }); - test('version', () => { + test('version', async () => { const docSettings = newDocumentSettings(); expect(docSettings.version).toEqual(0); - docSettings.resetSettings(); + await docSettings.resetSettings(); expect(docSettings.version).toEqual(1); }); @@ -111,14 +112,14 @@ describe('Validate DocumentSettings', () => { expect(folders).toBe(mockFolders); }); - test('tests register config path', () => { + test('tests register config path', async () => { const mockFolders: WorkspaceFolder[] = [workspaceFolderServer]; mockGetWorkspaceFolders.mockReturnValue(Promise.resolve(mockFolders)); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cSpell.json'); expect(docSettings.version).toEqual(0); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); expect(docSettings.version).toEqual(1); expect(docSettings.configsToImport).toContain(configFile); }); @@ -129,7 +130,7 @@ describe('Validate DocumentSettings', () => { mockGetConfiguration.mockReturnValue(Promise.resolve([cspellConfigInVsCode, {}])); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cspell-ext.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const settings = await docSettings.getSettings({ uri: Uri.file(__filename).toString() }); expect(settings.enabled).toBeUndefined(); @@ -144,7 +145,7 @@ describe('Validate DocumentSettings', () => { ); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cspell-ext.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const settings = await docSettings.getSettings({ uri: Uri.file(__filename).toString() }); expect(settings.workspaceRootPath?.toLowerCase()).toBe(pathWorkspaceClient.toLowerCase()); @@ -156,7 +157,7 @@ describe('Validate DocumentSettings', () => { mockGetConfiguration.mockReturnValue(Promise.resolve([{}, {}])); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cSpell.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const result = await docSettings.isExcluded(Uri.file(__filename).toString()); expect(result).toBe(false); @@ -170,7 +171,7 @@ describe('Validate DocumentSettings', () => { ); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cSpell.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const settings = await docSettings.getSettings({ uri: Uri.file(__filename).toString() }); expect(settings.enabledLanguageIds).not.toContain('typescript'); @@ -214,7 +215,7 @@ describe('Validate DocumentSettings', () => { mockGetConfiguration.mockReturnValue(Promise.resolve([{}, {}])); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cSpell.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const result = await docSettings.calcExcludedBy(Uri.file(__filename).toString()); expect(result).toHaveLength(0); @@ -305,7 +306,7 @@ describe('Validate DocumentSettings', () => { mockGetConfiguration.mockReturnValue(Promise.resolve([{}, {}])); const docSettings = newDocumentSettings(); const configFile = Path.join(pathSampleSourceFiles, 'cspell-exclude-tests.json'); - docSettings.registerConfigurationFile(configFile); + await docSettings.registerConfigurationFile(configFile); const uri = Uri.file(Path.resolve(pathWorkspaceRoot, filename)).toString(); const result = await docSettings.calcExcludedBy(uri); @@ -328,7 +329,7 @@ describe('Validate DocumentSettings', () => { mockGetWorkspaceFolders.mockReturnValue(Promise.resolve(mockFolders)); mockGetConfiguration.mockReturnValue(Promise.resolve([{}, {}])); const docSettings = newDocumentSettings(); - docSettings.registerConfigurationFile(Path.join(pathWorkspaceRoot, 'cSpell.json')); + await docSettings.registerConfigurationFile(Path.join(pathWorkspaceRoot, 'cSpell.json')); const uri = Uri.file(Path.resolve(pathWorkspaceRoot, filename)); const result = await docSettings.isGitIgnored(uri); @@ -431,7 +432,7 @@ describe('Validate DocumentSettings', () => { }); function newDocumentSettings(defaultSettings: CSpellUserSettings = {}) { - return new DocumentSettings({} as Connection, defaultSettings); + return new DocumentSettings({} as Connection, createMockServerSideApi(), defaultSettings); } }); diff --git a/packages/_server/src/config/vscode.config.mts b/packages/_server/src/config/vscode.config.mts index c9d7cca392..ec44b32d99 100644 --- a/packages/_server/src/config/vscode.config.mts +++ b/packages/_server/src/config/vscode.config.mts @@ -1,7 +1,7 @@ import { log } from '@internal/common-utils/log.js'; +import type { ConfigurationItem, Connection } from 'vscode-languageserver/node.js'; import { isDefined } from '../utils/index.mjs'; -import type { ConfigurationItem, Connection } from '../vscodeLanguageServer/index.cjs'; export interface TextDocumentUri { uri: string; diff --git a/packages/_server/src/config/vscode.config.test.mts b/packages/_server/src/config/vscode.config.test.mts index 76e459ce5e..87e5bc1ba0 100644 --- a/packages/_server/src/config/vscode.config.test.mts +++ b/packages/_server/src/config/vscode.config.test.mts @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from 'vitest'; +import type { Connection, WorkspaceFolder } from 'vscode-languageserver/node.js'; import { URI as Uri } from 'vscode-uri'; -import type { Connection, WorkspaceFolder } from '../vscodeLanguageServer/index.cjs'; import { getConfiguration, getWorkspaceFolders } from './vscode.config.mjs'; vi.mock('vscode-languageserver/node'); diff --git a/packages/_server/src/server.mts b/packages/_server/src/server.mts index 3dec715644..f406a917fe 100644 --- a/packages/_server/src/server.mts +++ b/packages/_server/src/server.mts @@ -1,4 +1,4 @@ -import { LogFileConnection } from '@internal/common-utils/index.js'; +import { isDefined, LogFileConnection } from '@internal/common-utils/index.js'; import { log, logError, logger, logInfo, setWorkspaceBase, setWorkspaceFolders } from '@internal/common-utils/log.js'; import { toFileUri, toUri } from '@internal/common-utils/uriHelper.js'; import type { CSpellSettingsWithSourceTrace, Glob } from 'cspell-lib'; @@ -6,8 +6,19 @@ import * as CSpell from 'cspell-lib'; import { extractImportErrors, getDefaultSettings, refreshDictionaryCache } from 'cspell-lib'; import type { Subscription } from 'rxjs'; import { interval, ReplaySubject } from 'rxjs'; -import { debounce, debounceTime, filter, mergeMap, take, tap } from 'rxjs/operators'; +import { debounceTime, filter, mergeMap, take, tap, throttle, throttleTime } from 'rxjs/operators'; +import type { DisposableLike } from 'utils-disposables'; +import { createDisposableList } from 'utils-disposables'; import { LogLevelMasks } from 'utils-logger'; +import type { + Diagnostic, + DidChangeConfigurationParams, + InitializeParams, + InitializeResult, + PublishDiagnosticsParams, + ServerCapabilities, +} from 'vscode-languageserver/node.js'; +import { CodeActionKind, createConnection, ProposedFeatures, TextDocuments, TextDocumentSyncKind } from 'vscode-languageserver/node.js'; import { TextDocument } from 'vscode-languageserver-textdocument'; import type * as Api from './api.js'; @@ -29,20 +40,11 @@ import type { TextDocumentUri } from './config/vscode.config.mjs'; import { createProgressNotifier } from './progressNotifier.mjs'; import { createServerApi } from './serverApi.mjs'; import { defaultIsTextLikelyMinifiedOptions, isTextLikelyMinified } from './utils/analysis.mjs'; +import { catchPromise } from './utils/catchPromise.mjs'; 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 type { - Diagnostic, - DidChangeConfigurationParams, - Disposable, - InitializeParams, - InitializeResult, - PublishDiagnosticsParams, - ServerCapabilities, -} from './vscodeLanguageServer/index.cjs'; -import { CodeActionKind, createConnection, ProposedFeatures, TextDocuments, TextDocumentSyncKind } from './vscodeLanguageServer/index.cjs'; log('Starting Spell Checker Server'); @@ -73,78 +75,217 @@ const dictionaryRefreshRateMs = 1000; export function run(): void { // debounce buffer + const disposables = createDisposableList(); const validationRequestStream = new ReplaySubject(1); const triggerUpdateConfig = new ReplaySubject(1); const triggerValidateAll = new ReplaySubject(1); const validationByDoc = new Map(); const blockValidation = new Map(); let isValidationBusy = false; - const disposables: Disposable[] = []; - const dictionaryWatcher = new DictionaryWatcher(); - disposables.push(dictionaryWatcher); + const dictionaryWatcher = dd(new DictionaryWatcher()); + dd(disposeValidationByDoc); const blockedFiles = new Map(); - const configWatcher = new ConfigWatcher(); - disposables.push(configWatcher); + const configWatcher = dd(new ConfigWatcher()); // Create a connection for the server. The connection uses Node's IPC as a transport log('Create Connection'); const connection = createConnection(ProposedFeatures.all); - const documentSettings = new DocumentSettings(connection, defaultSettings); - const _logger = createPrecisionLogger().setLogLevelMask(LogLevelMasks.none); - const clientServerApi = createServerApi( - connection, - { - serverNotifications: { - notifyConfigChange: onConfigChange, - registerConfigurationFile, + const clientServerApi = dd( + createServerApi( + connection, + { + serverNotifications: { + notifyConfigChange: onConfigChange, + registerConfigurationFile, + }, + serverRequests: { + getConfigurationForDocument: handleGetConfigurationForDocument, + isSpellCheckEnabled: handleIsSpellCheckEnabled, + splitTextIntoWords: handleSplitTextIntoWords, + spellingSuggestions: handleSpellingSuggestions, + }, }, - serverRequests: { - getConfigurationForDocument: handleGetConfigurationForDocument, - isSpellCheckEnabled: handleIsSpellCheckEnabled, - splitTextIntoWords: handleSplitTextIntoWords, - spellingSuggestions: handleSpellingSuggestions, - }, - }, - _logger, + _logger, + ), ); - disposables.push(clientServerApi); + const documentSettings = new DocumentSettings(connection, clientServerApi, defaultSettings); const progressNotifier = createProgressNotifier(clientServerApi); // Create a simple text document manager. const documents = new TextDocuments(TextDocument); - connection.onInitialize((params: InitializeParams): InitializeResult => { - // Hook up the logger to the connection. - log('onInitialize'); - setWorkspaceBase(params.rootUri ? params.rootUri : ''); - const capabilities: ServerCapabilities = { - // Tell the client that the server works in FULL text document sync mode - textDocumentSync: { - openClose: true, - change: TextDocumentSyncKind.Incremental, - willSave: true, - save: { includeText: true }, - }, - codeActionProvider: { - codeActionKinds: [CodeActionKind.QuickFix], - }, - }; - return { capabilities }; - }); + dd( + connection.onInitialize((params: InitializeParams): InitializeResult => { + // Hook up the logger to the connection. + log('onInitialize'); + setWorkspaceBase(params.rootUri ? params.rootUri : ''); + const capabilities: ServerCapabilities = { + // Tell the client that the server works in FULL text document sync mode + textDocumentSync: { + openClose: true, + change: TextDocumentSyncKind.Incremental, + willSave: true, + save: { includeText: true }, + }, + codeActionProvider: { + codeActionKinds: [CodeActionKind.QuickFix], + }, + }; + return { capabilities }; + }), + ); // The settings have changed. Is sent on server activation as well. - connection.onDidChangeConfiguration(onConfigChange); + dd(connection.onDidChangeConfiguration(onConfigChange)); - interface OnChangeParam extends DidChangeConfigurationParams { - settings: SettingsCspell; - } + const _getActiveUriSettings = simpleDebounce(__getActiveUriSettings, 50); + + // Listen for event messages from the client. + dd(dictionaryWatcher.listen(onDictionaryChange)); + dd(configWatcher.listen(onConfigFileChange)); + + const _handleIsSpellCheckEnabled = simpleDebounce( + __handleIsSpellCheckEnabled, + 50, + ({ uri, languageId }) => `(${uri})::(${languageId})`, + ); + + const _handleGetConfigurationForDocument = simpleDebounce(__handleGetConfigurationForDocument, 100, (params) => JSON.stringify(params)); + + // validate documents + ds( + validationRequestStream.pipe(filter((doc) => !validationByDoc.has(doc.uri))).subscribe((doc) => { + if (validationByDoc.has(doc.uri)) return; + const uri = doc.uri; + + log('Register Document Handler:', uri); + + if (isUriBlocked(uri)) { + validationByDoc.set( + doc.uri, + validationRequestStream + .pipe( + filter((doc) => uri === doc.uri), + take(1), + tap((doc) => progressNotifier.emitSpellCheckDocumentStep(doc, 'ignore')), + tap((doc) => log('Ignoring:', doc.uri)), + ) + .subscribe(), + ); + } else { + validationByDoc.set( + doc.uri, + validationRequestStream + .pipe( + filter((doc) => uri === doc.uri), + tap((doc) => progressNotifier.emitSpellCheckDocumentStep(doc, 'start')), + tap((doc) => log(`Request Validate: v${doc.version}`, doc.uri)), + ) + .pipe( + throttleTime(defaultDebounceMs, undefined, { leading: true, trailing: true }), + mergeMap(async (doc) => ({ doc, settings: await getActiveSettings(doc) }) as DocSettingPair), + tap((dsp) => progressNotifier.emitSpellCheckDocumentStep(dsp.doc, 'settings determined')), + throttle( + (dsp) => + interval(dsp.settings.spellCheckDelayMs || defaultDebounceMs).pipe(filter(() => !isValidationBusy)), + { leading: true, trailing: true }, + ), + filter((dsp) => !blockValidation.has(dsp.doc.uri)), + mergeMap(validateTextDocument), + ) + .subscribe(sendDiagnostics), + ); + } + }), + ); + + ds( + triggerUpdateConfig + .pipe( + tap(() => log('Trigger Update Config')), + throttleTime(1000, undefined, { leading: true, trailing: true }), + tap(() => log('Update Config Triggered')), + mergeMap(updateActiveSettings), + ) + .subscribe(() => {}), + ); + + ds( + triggerValidateAll.pipe(debounceTime(250)).subscribe(() => { + log('Validate all documents'); + documents.all().forEach((doc) => validationRequestStream.next(doc)); + }), + ); + + const knownErrors = new Set(); + + // Make the text document manager listen on the connection + // for open, change and close text document events + dd(documents.listen(connection)); + + disposables.push( + // The content of a text document has changed. This event is emitted + // when the text document first opened or when its content has changed. + documents.onDidChangeContent((event) => { + validationRequestStream.next(event.document); + }), + + // We want to block validation during saving. + documents.onWillSave((event) => { + const { uri, version } = event.document; + log(`onWillSave: v${version}`, uri); + blockValidation.set(uri, version); + }), + + // Enable validation once it is saved. + documents.onDidSave((event) => { + const { uri, version } = event.document; + log(`onDidSave: v${version}`, uri); + blockValidation.delete(uri); + validationRequestStream.next(event.document); + }), + + // Remove subscriptions when a document closes. + documents.onDidClose((event) => { + const uri = event.document.uri; + const sub = validationByDoc.get(uri); + if (sub) { + validationByDoc.delete(uri); + sub.unsubscribe(); + } + // A text document was closed we clear the diagnostics + catchPromise(connection.sendDiagnostics({ uri, diagnostics: [] }), 'onDidClose'); + }), + ); + + dd( + connection.onCodeAction( + onCodeActionHandler(documents, { + fetchSettings: getBaseSettings, + getSettingsVersion: () => documentSettings.version, + fetchWorkspaceConfigForDocument: (uri) => documentSettings.fetchWorkspaceConfiguration(uri), + }), + ), + ); + + // Free up the validation streams on shutdown. + connection.onShutdown(() => { + disposables.dispose(); + }); + + // Listen on the connection + connection.listen(); + + return; + + /******************************************************************* */ function onDictionaryChange(eventType?: string, filename?: string) { logInfo(`Dictionary Change ${eventType}`, filename); @@ -163,12 +304,12 @@ export function run(): void { function handleConfigChange() { triggerUpdateConfig.next(undefined); - updateLogLevel(); + catchPromise(updateLogLevel(), 'handleConfigChange'); } - function updateActiveSettings() { + async function updateActiveSettings() { log('updateActiveSettings'); - documentSettings.resetSettings(); + await documentSettings.resetSettings(); dictionaryWatcher.clear(); blockedFiles.clear(); triggerValidateAll.next(undefined); @@ -182,41 +323,24 @@ export function run(): void { return _getActiveUriSettings(uri); } - const _getActiveUriSettings = simpleDebounce(__getActiveUriSettings, 50); - function __getActiveUriSettings(uri?: string) { // Give the dictionaries a chance to refresh if they need to. log('getActiveUriSettings', uri); - refreshDictionaryCache(dictionaryRefreshRateMs); + catchPromise(refreshDictionaryCache(dictionaryRefreshRateMs), '__getActiveUriSettings'); return documentSettings.getUriSettings(uri || ''); } - function registerConfigurationFile(path: string) { - documentSettings.registerConfigurationFile(path); + async function registerConfigurationFile(path: string) { + const waitFor = documentSettings.registerConfigurationFile(path); logInfo('Register Configuration File', path); + await waitFor; triggerUpdateConfig.next(undefined); } - interface TextDocumentInfo { - uri?: string; - languageId?: string; - text?: string; - } - - // Listen for event messages from the client. - disposables.push(dictionaryWatcher.listen(onDictionaryChange)); - disposables.push(configWatcher.listen(onConfigFileChange)); - async function handleIsSpellCheckEnabled(params: TextDocumentInfo): Promise { return _handleIsSpellCheckEnabled(params); } - const _handleIsSpellCheckEnabled = simpleDebounce( - __handleIsSpellCheckEnabled, - 50, - ({ uri, languageId }) => `(${uri})::(${languageId})`, - ); - async function __handleIsSpellCheckEnabled(params: TextDocumentInfo): Promise { log('handleIsSpellCheckEnabled', params.uri); const activeSettings = await getActiveUriSettings(params.uri); @@ -229,8 +353,6 @@ export function run(): void { return _handleGetConfigurationForDocument(params); } - const _handleGetConfigurationForDocument = simpleDebounce(__handleGetConfigurationForDocument, 100, (params) => JSON.stringify(params)); - async function __handleGetConfigurationForDocument( params: Api.GetConfigurationForDocumentRequest, ): Promise { @@ -283,54 +405,6 @@ export function run(): void { return {}; } - interface DocSettingPair { - doc: TextDocument; - settings: CSpellUserSettings; - } - - // validate documents - const disposableValidate = validationRequestStream.pipe(filter((doc) => !validationByDoc.has(doc.uri))).subscribe((doc) => { - if (validationByDoc.has(doc.uri)) return; - const uri = doc.uri; - - log('Register Document Handler:', uri); - - if (isUriBlocked(uri)) { - validationByDoc.set( - doc.uri, - validationRequestStream - .pipe( - filter((doc) => uri === doc.uri), - take(1), - tap((doc) => progressNotifier.emitSpellCheckDocumentStep(doc, 'ignore')), - tap((doc) => log('Ignoring:', doc.uri)), - ) - .subscribe(), - ); - } else { - validationByDoc.set( - doc.uri, - validationRequestStream - .pipe( - filter((doc) => uri === doc.uri), - tap((doc) => progressNotifier.emitSpellCheckDocumentStep(doc, 'start')), - tap((doc) => log(`Request Validate: v${doc.version}`, doc.uri)), - ) - .pipe( - debounceTime(defaultDebounceMs), - mergeMap(async (doc) => ({ doc, settings: await getActiveSettings(doc) }) as DocSettingPair), - tap((dsp) => progressNotifier.emitSpellCheckDocumentStep(dsp.doc, 'settings determined')), - debounce((dsp) => - interval(dsp.settings.spellCheckDelayMs || defaultDebounceMs).pipe(filter(() => !isValidationBusy)), - ), - filter((dsp) => !blockValidation.has(dsp.doc.uri)), - mergeMap(validateTextDocument), - ) - .subscribe(sendDiagnostics), - ); - } - }); - function sendDiagnostics(result: ValidationResult) { log(`Send Diagnostics v${result.version}`, result.uri); const diags: Required = { @@ -338,24 +412,9 @@ export function run(): void { version: result.version, diagnostics: result.diagnostics, }; - connection.sendDiagnostics(diags); + catchPromise(connection.sendDiagnostics(diags), 'sendDiagnostics'); } - const disposableTriggerUpdateConfigStream = triggerUpdateConfig - .pipe( - tap(() => log('Trigger Update Config')), - debounceTime(100), - tap(() => log('Update Config Triggered')), - ) - .subscribe(() => { - updateActiveSettings(); - }); - - const disposableTriggerValidateAll = triggerValidateAll.pipe(debounceTime(250)).subscribe(() => { - log('Validate all documents'); - documents.all().forEach((doc) => validationRequestStream.next(doc)); - }); - async function shouldValidateDocument(textDocument: TextDocument, settings: CSpellUserSettings): Promise { const { uri, languageId } = textDocument; return ( @@ -421,7 +480,6 @@ export function run(): void { blockedReason: uri ? blockedFiles.get(uri) : undefined, }; } - async function isUriExcluded(uri: string): Promise { const ie = await calcFileIncludeExclude(uri); return !ie.include || ie.exclude || !!ie.ignored; @@ -440,10 +498,6 @@ export function run(): void { return tds.constructSettingsForText(await getBaseSettings(doc), doc.getText(), doc.languageId); } - interface ValidationResult extends PublishDiagnosticsParams { - version: number; - } - function isStale(doc: TextDocument, writeLog = true): boolean { const currDoc = documents.get(doc.uri); const stale = currDoc?.version !== doc.version; @@ -504,12 +558,6 @@ export function run(): void { return r; } - const knownErrors = new Set(); - - function isString(s: string | undefined): s is string { - return !!s; - } - function logProblemsWithSettings(settings: CSpellUserSettings) { function join(...s: (string | undefined)[]): string { return s.filter((s) => !!s).join(' '); @@ -522,7 +570,8 @@ export function run(): void { const importedBy = err.referencedBy ?.map((s) => s.filename) - .filter(isString) + .filter(isDefined) + .filter((a) => !!a) .map((s) => '"' + s + '"') || []; const fullImportBy = importedBy.length ? join(' imported by \n', ...importedBy) : ''; // const firstImportedBy = importedBy.length ? join('imported by', importedBy[0]) : ''; @@ -535,45 +584,6 @@ export function run(): void { }); } - // Make the text document manager listen on the connection - // for open, change and close text document events - documents.listen(connection); - - disposables.push( - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent((event) => { - validationRequestStream.next(event.document); - }), - - // We want to block validation during saving. - documents.onWillSave((event) => { - const { uri, version } = event.document; - log(`onWillSave: v${version}`, uri); - blockValidation.set(uri, version); - }), - - // Enable validation once it is saved. - documents.onDidSave((event) => { - const { uri, version } = event.document; - log(`onDidSave: v${version}`, uri); - blockValidation.delete(uri); - validationRequestStream.next(event.document); - }), - - // Remove subscriptions when a document closes. - documents.onDidClose((event) => { - const uri = event.document.uri; - const sub = validationByDoc.get(uri); - if (sub) { - validationByDoc.delete(uri); - sub.unsubscribe(); - } - // A text document was closed we clear the diagnostics - connection.sendDiagnostics({ uri, diagnostics: [] }); - }), - ); - async function updateLogLevel() { try { const results: string[] = await connection.workspace.getConfiguration([ @@ -618,20 +628,49 @@ export function run(): void { return folders || undefined; } - connection.onCodeAction(onCodeActionHandler(documents, getBaseSettings, () => documentSettings.version, clientServerApi)); - - // Free up the validation streams on shutdown. - connection.onShutdown(() => { - disposables.forEach((d) => d.dispose()); - disposables.length = 0; - disposableValidate.unsubscribe(); - disposableTriggerUpdateConfigStream.unsubscribe(); - disposableTriggerValidateAll.unsubscribe(); - const toDispose = [...validationByDoc.values()]; + function disposeValidationByDoc() { + const sub = [...validationByDoc.values()]; validationByDoc.clear(); - toDispose.forEach((sub) => sub.unsubscribe()); - }); + for (const s of sub) { + try { + s.unsubscribe(); + } catch (e) { + console.error(e); + } + } + } - // Listen on the connection - connection.listen(); + /** + * Record disposable to be disposed. + * @param disposable - a disposable + * @returns the disposable + */ + function dd(disposable: T): T { + disposables.push(disposable); + return disposable; + } + + function ds void }>(v: T): T { + disposables.push(() => v.unsubscribe()); + return v; + } +} + +interface TextDocumentInfo { + uri?: string; + languageId?: string; + text?: string; +} + +interface ValidationResult extends PublishDiagnosticsParams { + version: number; +} + +interface OnChangeParam extends DidChangeConfigurationParams { + settings: SettingsCspell; +} + +interface DocSettingPair { + doc: TextDocument; + settings: CSpellUserSettings; } diff --git a/packages/_server/src/serverApi.mts b/packages/_server/src/serverApi.mts index d0e137ec56..7f7561b66d 100644 --- a/packages/_server/src/serverApi.mts +++ b/packages/_server/src/serverApi.mts @@ -20,9 +20,6 @@ export function createServerApi(connection: MessageConnection, handlers: Partial ...handlers.serverNotifications, }, clientRequests: { - // addWordsToConfigFileFromServer: true, - // addWordsToDictionaryFileFromServer: true, - // addWordsToVSCodeSettingsFromServer: true, onWorkspaceConfigForDocumentRequest: true, }, clientNotifications: { diff --git a/packages/_server/src/test/test.api.ts b/packages/_server/src/test/test.api.ts new file mode 100644 index 0000000000..7f1578ff18 --- /dev/null +++ b/packages/_server/src/test/test.api.ts @@ -0,0 +1,28 @@ +import type { ExcludeDisposableHybrid } from 'utils-disposables'; +import { injectDisposable } from 'utils-disposables'; +import { vi } from 'vitest'; + +import type { ServerSideApi } from '../api.js'; + +export function createMockServerSideApi() { + const api = { + serverNotification: { + notifyConfigChange: { subscribe: vi.fn() }, + registerConfigurationFile: { subscribe: vi.fn() }, + }, + serverRequest: { + getConfigurationForDocument: { subscribe: vi.fn() }, + isSpellCheckEnabled: { subscribe: vi.fn() }, + splitTextIntoWords: { subscribe: vi.fn() }, + spellingSuggestions: { subscribe: vi.fn() }, + }, + clientNotification: { + onSpellCheckDocument: vi.fn(), + }, + clientRequest: { + onWorkspaceConfigForDocumentRequest: vi.fn(), + }, + } satisfies ExcludeDisposableHybrid; + + return vi.mocked(injectDisposable>(api, () => undefined)); +} diff --git a/packages/_server/src/utils/catchPromise.mts b/packages/_server/src/utils/catchPromise.mts new file mode 100644 index 0000000000..26a7d0fa33 --- /dev/null +++ b/packages/_server/src/utils/catchPromise.mts @@ -0,0 +1,37 @@ +type ErrorHandler = (e: unknown) => T; + +function defaultHandler(e: unknown) { + console.error(e); + return undefined; +} + +function contextHandler(context: string | undefined): (e: unknown) => undefined { + if (!context) return defaultHandler; + return (e) => { + console.error('%s: %s', context, e); + return undefined; + }; +} + +/** + * Used for catching promises that are not returned. A fire and forget situation. + * @param p - the promise to catch + * @param handler - a handler to handle the rejection. + */ +export function catchPromise(p: Promise, handler: ErrorHandler): Promise; +/** + * Used for catching promises that are not returned. A fire and forget situation. + * If the promise is rejected, it is resolved with `undefined`. + * @param p - the promise to catch + */ +export function catchPromise(p: Promise): Promise; +export function catchPromise(p: Promise, context: string): Promise; +export function catchPromise(p: Promise, handler?: ErrorHandler): Promise; +export async function catchPromise(p: Promise, handlerOrContext?: ErrorHandler | string): Promise { + const handler = typeof handlerOrContext === 'function' ? handlerOrContext : contextHandler(handlerOrContext); + try { + return await p; + } catch (e) { + return handler(e); + } +} diff --git a/packages/_server/src/utils/catchPromise.test.mts b/packages/_server/src/utils/catchPromise.test.mts new file mode 100644 index 0000000000..32f4d70647 --- /dev/null +++ b/packages/_server/src/utils/catchPromise.test.mts @@ -0,0 +1,33 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { catchPromise } from './catchPromise.mjs'; + +describe('catchPromise', () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + test('catchPromise', async () => { + const err = vi.spyOn(console, 'error').mockImplementation(() => undefined); + await expect(catchPromise(Promise.reject('error'))).resolves.toBe(undefined); + expect(err).toHaveBeenCalledWith('error'); + }); + + test('catchPromise with context', async () => { + const err = vi.spyOn(console, 'error').mockImplementation(() => undefined); + await expect(catchPromise(Promise.reject(Error('test')), 'Testing')).resolves.toBe(undefined); + expect(err).toHaveBeenCalledWith('%s: %s', 'Testing', expect.any(Error)); + }); + + test('catchPromise custom handler', async () => { + const err = vi.spyOn(console, 'error').mockImplementation(() => undefined); + await expect(catchPromise(Promise.reject('error'), () => 23)).resolves.toBe(23); + expect(err).not.toHaveBeenCalled(); + }); + + test('catchPromise resolve', async () => { + const err = vi.spyOn(console, 'error').mockImplementation(() => undefined); + await expect(catchPromise(Promise.resolve('msg'))).resolves.toBe('msg'); + expect(err).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/_server/src/utils/fileWatcher.mts b/packages/_server/src/utils/fileWatcher.mts index c4308e18f8..edf2d89848 100644 --- a/packages/_server/src/utils/fileWatcher.mts +++ b/packages/_server/src/utils/fileWatcher.mts @@ -2,8 +2,8 @@ import { logError } from '@internal/common-utils/log.js'; import { toFileUri } from '@internal/common-utils/uriHelper.js'; import type { FSWatcher } from 'fs'; import { format } from 'util'; +import type { Disposable } from 'vscode-languageserver/node.js'; -import type { Disposable } from '../vscodeLanguageServer/index.cjs'; import { nodeWatch } from './nodeWatch.cjs'; export type KnownEvents = 'change' | 'error' | 'close'; diff --git a/packages/_server/src/vscodeLanguageServer/index.cts b/packages/_server/src/vscodeLanguageServer/index.cts deleted file mode 100644 index 69347623d0..0000000000 --- a/packages/_server/src/vscodeLanguageServer/index.cts +++ /dev/null @@ -1 +0,0 @@ -export * from 'vscode-languageserver/node'; diff --git a/packages/_server/tsconfig.test.json b/packages/_server/tsconfig.test.json index 2d59377e6c..a48c780664 100644 --- a/packages/_server/tsconfig.test.json +++ b/packages/_server/tsconfig.test.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./temp/dist" + "outDir": "./temp/dist", + "types": ["node"] } } diff --git a/packages/client/src/client/server/server.ts b/packages/client/src/client/server/server.ts index 2d41b1a79a..1dfc09bfe2 100644 --- a/packages/client/src/client/server/server.ts +++ b/packages/client/src/client/server/server.ts @@ -49,10 +49,6 @@ type Disposable = { dispose: () => void; }; -// type RequestsFromServerHandlerApi = { -// [M in keyof RequestsFromServer]: (handler: Fn) => Disposable; -// }; - type RequestCodeActionResult = (Command | CodeAction)[] | null; export async function requestCodeAction(client: LanguageClient, params: CodeActionParams): Promise { @@ -77,24 +73,23 @@ export function createServerApi(client: LanguageClient): ServerApi { onSpellCheckDocument: true, }, clientRequests: { - // addWordsToConfigFileFromServer: true, - // addWordsToDictionaryFileFromServer: true, - // addWordsToVSCodeSettingsFromServer: true, onWorkspaceConfigForDocumentRequest: true, }, }; const rpcApi = createClientSideApi(client, def, { log: () => undefined }); + const { serverNotification, serverRequest, clientNotification, clientRequest } = rpcApi; + const api: ServerApi = { - isSpellCheckEnabled: log2Sfn(rpcApi.serverRequest.isSpellCheckEnabled, 'isSpellCheckEnabled'), - getConfigurationForDocument: log2Sfn(rpcApi.serverRequest.getConfigurationForDocument, 'getConfigurationForDocument'), - spellingSuggestions: log2Sfn(rpcApi.serverRequest.spellingSuggestions, 'spellingSuggestions'), - notifyConfigChange: log2Sfn(rpcApi.serverNotification.notifyConfigChange, 'notifyConfigChange'), - registerConfigurationFile: log2Sfn(rpcApi.serverNotification.registerConfigurationFile, 'registerConfigurationFile'), - onSpellCheckDocument: (fn) => rpcApi.clientNotification.onSpellCheckDocument.subscribe(log2Cfn(fn, 'onSpellCheckDocument')), + isSpellCheckEnabled: log2Sfn(serverRequest.isSpellCheckEnabled, 'isSpellCheckEnabled'), + getConfigurationForDocument: log2Sfn(serverRequest.getConfigurationForDocument, 'getConfigurationForDocument'), + spellingSuggestions: log2Sfn(serverRequest.spellingSuggestions, 'spellingSuggestions'), + notifyConfigChange: log2Sfn(serverNotification.notifyConfigChange, 'notifyConfigChange'), + registerConfigurationFile: log2Sfn(serverNotification.registerConfigurationFile, 'registerConfigurationFile'), + onSpellCheckDocument: (fn) => clientNotification.onSpellCheckDocument.subscribe(log2Cfn(fn, 'onSpellCheckDocument')), onWorkspaceConfigForDocumentRequest: (fn) => - rpcApi.clientRequest.onWorkspaceConfigForDocumentRequest.subscribe(log2Cfn(fn, 'onWorkspaceConfigForDocumentRequest')), + clientRequest.onWorkspaceConfigForDocumentRequest.subscribe(log2Cfn(fn, 'onWorkspaceConfigForDocumentRequest')), dispose: rpcApi.dispose, }; @@ -103,7 +98,7 @@ export function createServerApi(client: LanguageClient): ServerApi { } let reqNum = 0; -const debugCommunication = true; +const debugCommunication = false; const debugServerComms = true; const debugClientComms = true; diff --git a/packages/client/src/webview/AppState/store.ts b/packages/client/src/webview/AppState/store.ts index 99324b4f8a..7ba1621a38 100644 --- a/packages/client/src/webview/AppState/store.ts +++ b/packages/client/src/webview/AppState/store.ts @@ -32,7 +32,7 @@ export function getWebviewGlobalStore(): Storage { const currentDocumentSub = rx(subscribeToCurrentDocument); const currentDocument = pipe(currentDocumentSub, throttle(500), /* delayUnsubscribe(5000), */ createSubscribableView); - currentDocument.onEvent('onNotify', (event) => console.log('current document update: %o', event)); + // currentDocument.onEvent('onNotify', (event) => console.log('current document update: %o', event)); function dispose() { disposeOf(currentDocumentSub); diff --git a/packages/utils-disposables/src/DisposableList.ts b/packages/utils-disposables/src/DisposableList.ts index ebce341b83..0e00ed6723 100644 --- a/packages/utils-disposables/src/DisposableList.ts +++ b/packages/utils-disposables/src/DisposableList.ts @@ -39,11 +39,11 @@ export class DisposableList extends InheritableDisposable { super(disposables); } - public push(disposable: DisposableLike) { + public push(...disposables: DisposableLike[]) { if (this.isDisposed()) { throw new Error('Already disposed, cannot add items.'); } - this.disposables.push(disposable); + this.disposables.push(...disposables); } get length() {