Skip to content

Commit

Permalink
fix: Reduce traffic volume between client/server (#3256)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed May 13, 2024
1 parent 9c1a345 commit 9d46a5b
Show file tree
Hide file tree
Showing 29 changed files with 447 additions and 174 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3903,7 +3903,7 @@
"build-production": "npm --workspaces --if-present run build-production",
"build-release": "npm run build && npm run package-extension",
"gen-docs": "npm --workspace=@internal/docs run gen-docs",
"package-extension": "vsce package -o ./build",
"package-extension": "vsce package --pre-release -o ./build",
"prettier:check": "prettier \"**/*.{ts,tsx,js,json,md,svelte}\" -c",
"prettier:fix": "npm run prettier:check -- -w",
"prettier:fix-schema": "prettier -w \"**/*.schema.json\"",
Expand Down
2 changes: 1 addition & 1 deletion packages/_integrationTests/src/extension.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('Launch code spell extension', function () {
expect(docContextMaybe).to.not.be.undefined;
const docContext = isDefined(docContextMaybe);

const config = await ext.extApi.cSpellClient().getConfigurationForDocument(docContext.doc);
const config = await ext.extApi.cSpellClient().getConfigurationForDocument(docContext.doc, {});

const { excludedBy, fileEnabled, configFiles } = config;
log('config: %o', { excludedBy, fileEnabled, configFiles });
Expand Down
28 changes: 28 additions & 0 deletions packages/_server/spell-checker-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,34 @@
"scope": "resource",
"type": "array"
},
"cSpell.languageStatusFields": {
"additionalProperties": false,
"default": {
"fileType": true,
"issues": true,
"scheme": true
},
"description": "Select which fields to display in the language status bar.",
"markdownDescription": "Select which fields to display in the language status bar.",
"properties": {
"fileType": {
"type": "boolean"
},
"issues": {
"type": "boolean"
},
"scheme": {
"type": "boolean"
}
},
"required": [
"fileType",
"scheme",
"issues"
],
"scope": "machine",
"type": "object"
},
"cSpell.overrides": {
"description": "Overrides are used to apply settings for specific files in your project.\n\n**Example:**\n\n```jsonc \"cSpell.overrides\": [ // Force `*.hrr` and `*.crr` files to be treated as `cpp` files: { \"filename\": \"**/{*.hrr,*.crr}\", \"languageId\": \"cpp\" }, // Force `dutch/**/*.txt` to be treated as Dutch (dictionary needs to be installed separately): { \"filename\": \"**/dutch/**/*.txt\", \"language\": \"nl\" } ] ```",
"items": {
Expand Down
5 changes: 4 additions & 1 deletion packages/_server/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createClientApi, createServerApi } from 'json-rpc-api';
import type {
CheckDocumentOptions,
CheckDocumentResult,
ConfigurationFields,
GetConfigurationForDocumentRequest,
GetConfigurationForDocumentResult,
GetSpellCheckingOffsetsResult,
Expand All @@ -36,7 +37,9 @@ export type { Logger, MessageConnection } from 'json-rpc-api';

/** Requests that can be made to the server */
export interface ServerRequestsAPI {
getConfigurationForDocument(req: GetConfigurationForDocumentRequest): GetConfigurationForDocumentResult;
getConfigurationForDocument<Fields extends ConfigurationFields>(
req: GetConfigurationForDocumentRequest<Fields>,
): GetConfigurationForDocumentResult<Fields>;
isSpellCheckEnabled(req: TextDocumentInfo): IsSpellCheckEnabledResult;
splitTextIntoWords(req: string): SplitTextIntoWordsResult;
spellingSuggestions(word: string, doc?: TextDocumentInfo): SpellingSuggestionsResult;
Expand Down
27 changes: 21 additions & 6 deletions packages/_server/src/api/apiModels.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { PublishDiagnosticsParams } from 'vscode-languageserver';

import type { ConfigScopeVScode, ConfigTarget } from '../config/configTargets.mjs';
import type * as config from '../config/cspellConfig/index.mjs';
import type { CSpellUserSettings } from '../config/cspellConfig/index.mjs';
import type { CheckDocumentIssue } from './models/Diagnostic.mjs';
import type { Suggestion } from './models/Suggestion.mjs';
import type { ExtensionId } from './models/types.mjs';
Expand Down Expand Up @@ -79,15 +79,30 @@ export interface GetSpellCheckingOffsetsResult {
offsets: number[];
}

export interface GetConfigurationForDocumentRequest extends Partial<TextDocumentInfo> {
export type ConfigurationFields = keyof CSpellUserSettings;
export type ConfigFieldSelector<T extends ConfigurationFields> = Readonly<Record<T, true>>;

export interface GetConfigurationForDocumentRequest<Fields extends ConfigurationFields> extends Partial<TextDocumentInfo> {
/** used to calculate configTargets, configTargets will be empty if undefined. */
workspaceConfig?: WorkspaceConfigForDocument;
/** List of Settings fields to return. */
fields: ConfigFieldSelector<Fields>;
}

export interface GetConfigurationForDocumentResult extends IsSpellCheckEnabledResult {
settings: config.CSpellUserSettings | undefined;
docSettings: config.CSpellUserSettings | undefined;
export type AllowUndefined<T> = {
[P in keyof T]: T[P] | undefined;
};

export type PartialCSpellUserSettings<T extends ConfigurationFields> = Pick<CSpellUserSettings, T> & { _fields?: ConfigFieldSelector<T> };

export interface GetConfigurationForDocumentResult<T extends ConfigurationFields> extends IsSpellCheckEnabledResult {
/** Merged configuration settings. Does NOT include in-document directives. */
settings: PartialCSpellUserSettings<T> | undefined;
/** Merged configuration settings, including in-document directives. */
docSettings: PartialCSpellUserSettings<T> | undefined;
/** Configuration files used. */
configFiles: UriString[];
/** Possible configuration targets. */
configTargets: ConfigTarget[];
}

Expand Down Expand Up @@ -213,7 +228,7 @@ export type {
} from '../config/cspellConfig/index.mjs';

export type VSCodeSettingsCspell = {
[key in ExtensionId]?: config.CSpellUserSettings;
[key in ExtensionId]?: CSpellUserSettings;
};

export type PublishDiagnostics = Required<PublishDiagnosticsParams>;
Expand Down
13 changes: 13 additions & 0 deletions packages/_server/src/config/cspellConfig/SpellCheckerSettings.mts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ export interface SpellCheckerSettings
*/
showStatusAlignment?: 'Left' | 'Right';

/**
* Select which fields to display in the language status bar.
* @scope machine
* @default { "fileType": true, "scheme": true, "issues": true }
*/
languageStatusFields?: LanguageStatusFields;

/**
* Show CSpell in-document directives as you type.
*
Expand Down Expand Up @@ -467,3 +474,9 @@ export interface AddToTargets extends AddToDictionaryTarget {
*/
cspell?: AutoOrBoolean;
}

export interface LanguageStatusFields {
fileType: boolean;
scheme: boolean;
issues: boolean;
}
78 changes: 78 additions & 0 deletions packages/_server/src/config/cspellConfig/configFields.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ConfigFields as CSpellConfigFields } from '@cspell/cspell-types';

import type { CSpellUserSettings } from './cspellConfig.mjs';

export { ConfigFields as CSpellConfigFields } from '@cspell/cspell-types';

export type ConfigKeys = Exclude<
keyof CSpellUserSettings,
'$schema' | 'version' | 'id' | 'experimental.enableRegexpView' | 'experimental.enableSettingsViewerV2'
>;

type CSpellUserSettingsFields = {
[key in ConfigKeys]: key;
};

export const ConfigFields: CSpellUserSettingsFields = {
...CSpellConfigFields,
'advanced.feature.useReferenceProviderRemove': 'advanced.feature.useReferenceProviderRemove',
'advanced.feature.useReferenceProviderWithRename': 'advanced.feature.useReferenceProviderWithRename',
autoFormatConfigFile: 'autoFormatConfigFile',
blockCheckingWhenAverageChunkSizeGreaterThan: 'blockCheckingWhenAverageChunkSizeGreaterThan',
blockCheckingWhenLineLengthGreaterThan: 'blockCheckingWhenLineLengthGreaterThan',
blockCheckingWhenTextChunkSizeGreaterThan: 'blockCheckingWhenTextChunkSizeGreaterThan',
checkLimit: 'checkLimit',
checkOnlyEnabledFileTypes: 'checkOnlyEnabledFileTypes',
customDictionaries: 'customDictionaries',
customFolderDictionaries: 'customFolderDictionaries',
customUserDictionaries: 'customUserDictionaries',
customWorkspaceDictionaries: 'customWorkspaceDictionaries',
diagnosticLevel: 'diagnosticLevel',
diagnosticLevelFlaggedWords: 'diagnosticLevelFlaggedWords',
fixSpellingWithRenameProvider: 'fixSpellingWithRenameProvider',
hideAddToDictionaryCodeActions: 'hideAddToDictionaryCodeActions',
languageStatusFields: 'languageStatusFields',
logLevel: 'logLevel',
logFile: 'logFile',
maxDuplicateProblems: 'maxDuplicateProblems',
maxNumberOfProblems: 'maxNumberOfProblems',
mergeCSpellSettings: 'mergeCSpellSettings',
mergeCSpellSettingsFields: 'mergeCSpellSettingsFields',
noSuggestDictionaries: 'noSuggestDictionaries',
showAutocompleteDirectiveSuggestions: 'showAutocompleteDirectiveSuggestions',
showCommandsInEditorContextMenu: 'showCommandsInEditorContextMenu',
showStatus: 'showStatus',
showStatusAlignment: 'showStatusAlignment',
showSuggestionsLinkInEditorContextMenu: 'showSuggestionsLinkInEditorContextMenu',
spellCheckDelayMs: 'spellCheckDelayMs',
spellCheckOnlyWorkspaceFiles: 'spellCheckOnlyWorkspaceFiles',
suggestionMenuType: 'suggestionMenuType',
suggestionNumChanges: 'suggestionNumChanges',
suggestionsTimeout: 'suggestionsTimeout',
useLocallyInstalledCSpellDictionaries: 'useLocallyInstalledCSpellDictionaries',
workspaceRootPath: 'workspaceRootPath',
trustedWorkspace: 'trustedWorkspace',
// File Types and Schemes
allowedSchemas: 'allowedSchemas',
enabledFileTypes: 'enabledFileTypes',
enabledLanguageIds: 'enabledLanguageIds',
enabledSchemes: 'enabledSchemes',
// Behavior
hideIssuesWhileTyping: 'hideIssuesWhileTyping',
revealIssuesAfterDelayMS: 'revealIssuesAfterDelayMS',
// Appearance
useCustomDecorations: 'useCustomDecorations',
doNotUseCustomDecorationForScheme: 'doNotUseCustomDecorationForScheme',
textDecoration: 'textDecoration',
textDecorationLine: 'textDecorationLine',
textDecorationStyle: 'textDecorationStyle',
textDecorationThickness: 'textDecorationThickness',
textDecorationColor: 'textDecorationColor',
textDecorationColorFlagged: 'textDecorationColorFlagged',
textDecorationColorSuggestion: 'textDecorationColorSuggestion',
overviewRulerColor: 'overviewRulerColor',
dark: 'dark',
light: 'light',
} as const;

// export const ConfigKeysNames = Object.values(ConfigKeysByField);
1 change: 1 addition & 0 deletions packages/_server/src/config/cspellConfig/index.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { ConfigFields } from './configFields.mjs';
export type {
AllSpellCheckerSettingsInVSCode,
AllSpellCheckerSettingsInVSCodeWithPrefix,
Expand Down
4 changes: 1 addition & 3 deletions packages/_server/src/config/documentSettings.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { stat } from 'node:fs/promises';

import { opConcatMap, opFilter, pipe } from '@cspell/cspell-pipe/sync';
import type {
CSpellSettingsWithSourceTrace,
Expand Down Expand Up @@ -40,6 +38,7 @@ import type { DocumentUri, ServerSideApi, VSCodeSettingsCspell, WorkspaceConfigF
import { extensionId } from '../constants.mjs';
import { uniqueFilter } from '../utils/index.mjs';
import { findMatchingFoldersForUri } from '../utils/matchingFoldersForUri.mjs';
import { stat } from '../vfs/index.mjs';
import type { VSConfigAdvanced } from './cspellConfig/cspellConfig.mjs';
import { filterMergeFields } from './cspellConfig/cspellMergeFields.mjs';
import type { EnabledSchemes } from './cspellConfig/FileTypesAndSchemeSettings.mjs';
Expand Down Expand Up @@ -789,7 +788,6 @@ export function isExcluded(settings: ExtSettings, uri: Uri): boolean {
}

async function filterUrl(uri: Uri): Promise<Uri | undefined> {
if (uri.scheme !== 'file' && uri.scheme !== 'vscode-vfs') return undefined;
const url = new URL(uri.toString());
try {
const stats = await stat(url);
Expand Down
71 changes: 71 additions & 0 deletions packages/_server/src/config/sanitizeSettings.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { isDefined } from '@internal/common-utils';
import type { DictionaryDefinition, DictionaryDefinitionCustom } from 'cspell-lib';

import type { CSpellUserSettings } from './cspellConfig/index.mjs';
import { ConfigFields } from './cspellConfig/index.mjs';

type ConfigFieldKeys = keyof CSpellUserSettings;
type ConfigFieldFilter = Record<ConfigFieldKeys, boolean | undefined>;

export function sanitizeSettings(settings: CSpellUserSettings, fields: ConfigFieldFilter): CSpellUserSettings;
export function sanitizeSettings(settings: CSpellUserSettings | undefined, fields: ConfigFieldFilter): CSpellUserSettings | undefined;
export function sanitizeSettings(settings: CSpellUserSettings | undefined, fields: ConfigFieldFilter): CSpellUserSettings | undefined {
if (!settings) return settings;
const includeKeys = new Set<string>(
Object.entries(fields)
.filter(([, value]) => value)
.map(([key]) => key),
);
const excludeKeys = new Set<string>((['words', 'ignoreWords', 'userWords'] as const).filter((key) => !includeKeys.has(key)));
const s = Object.fromEntries(
Object.entries(settings).filter(([key]) => key in ConfigFields && !excludeKeys.has(key) && includeKeys.has(key)),
);
if ('dictionaryDefinitions' in s) {
s.dictionaryDefinitions = sanitizeDictionaryDefinitions(s.dictionaryDefinitions);
}
return s;
}

function sanitizeDictionaryDefinitions(defs: CSpellUserSettings['dictionaryDefinitions']): CSpellUserSettings['dictionaryDefinitions'] {
if (!defs) return defs;
return defs.map((def) => sanitizeDictionaryDefinition(def)).filter(isDefined);
}

function sanitizeDictionaryDefinition(def: DictionaryDefinition | undefined): DictionaryDefinitionCustom | undefined {
if (!def) return def;
// if (!def.path) return undefined;
const { name, path, description, addWords } = def as DictionaryDefinitionCustom;
return {
name,
path,
description,
addWords: addWords || false,
};
}

export function objectKeysNested(obj: unknown): string[] {
const visited = new Set<unknown>();

function _objectKeysNested(obj: unknown, depthRemaining: number): string[] {
if (!depthRemaining || visited.has(obj) || !obj || typeof obj !== 'object') return [];
visited.add(obj);
const keys = new Set<string>();
if (Array.isArray(obj)) {
const nested = obj.flatMap((o) => _objectKeysNested(o, depthRemaining - 1));
nested.map((k) => keys.add('[*].' + k));
} else {
for (const [key, entry] of Object.entries(obj)) {
keys.add(key);
_objectKeysNested(entry, depthRemaining - 1).map((k) => keys.add(key + '.' + k));
}
}
return [...keys];
}

return _objectKeysNested(obj, 4);
}

export function objectFieldSizes(obj: object): object {
if (!obj) return obj;
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, JSON.stringify(value)?.length || 0]));
}
1 change: 1 addition & 0 deletions packages/_server/src/lib/index.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type { EnabledFileTypes, EnabledSchemes } from '../config/cspellConfig/FileTypesAndSchemeSettings.mjs';
export { ConfigFields } from '../config/cspellConfig/index.mjs';
export {
extractEnabledFileTypes,
extractEnabledSchemeList,
Expand Down
30 changes: 20 additions & 10 deletions packages/_server/src/server.mts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
stringifyPatterns,
} from './config/documentSettings.mjs';
import { isLanguageEnabled } from './config/extractEnabledFileTypes.mjs';
import { objectFieldSizes, objectKeysNested, sanitizeSettings } from './config/sanitizeSettings.mjs';
import type { TextDocumentUri } from './config/vscode.config.mjs';
import { defaultCheckLimit } from './constants.mjs';
import { DocumentValidationController } from './DocumentValidationController.mjs';
Expand Down Expand Up @@ -366,27 +367,36 @@ export function run(): void {
}

async function handleGetConfigurationForDocument(
params: Api.GetConfigurationForDocumentRequest,
): Promise<Api.GetConfigurationForDocumentResult> {
params: Api.GetConfigurationForDocumentRequest<Api.ConfigurationFields>,
): Promise<Api.GetConfigurationForDocumentResult<Api.ConfigurationFields>> {
return _handleGetConfigurationForDocument(params);
}

async function __handleGetConfigurationForDocument(
params: Api.GetConfigurationForDocumentRequest,
): Promise<Api.GetConfigurationForDocumentResult> {
params: Api.GetConfigurationForDocumentRequest<Api.ConfigurationFields>,
): Promise<Api.GetConfigurationForDocumentResult<Api.ConfigurationFields>> {
log('handleGetConfigurationForDocument', params.uri);
const { uri, workspaceConfig } = params;
const { uri, workspaceConfig, fields } = params;
const doc = (uri && documents.get(uri)) || undefined;
// console.warn('__handleGetConfigurationForDocument: %o', {
// params,
// doc: { uri: doc?.uri, languageId: doc?.languageId, version: doc?.version },
// });
const docSettings = stringifyPatterns((doc && (await getSettingsToUseForDocument(doc))) || undefined);
const activeSettings = await getActiveUriSettings(uri);
const settings = stringifyPatterns(activeSettings);
const docSettingsRaw = (doc && (await getSettingsToUseForDocument(doc))) || undefined;
const settingsRaw = await getActiveUriSettings(uri);
const configFiles = uri ? (await documentSettings.findCSpellConfigurationFilesForUri(uri)).map((uri) => uri.toString()) : [];
const configTargets = workspaceConfig ? await calculateConfigTargets(settings, workspaceConfig, configFiles) : [];
const ieInfo = await calcIncludeExcludeInfo(activeSettings, params);
const configTargets = workspaceConfig ? await calculateConfigTargets(settingsRaw, workspaceConfig, configFiles) : [];
const ieInfo = await calcIncludeExcludeInfo(settingsRaw, params);

const docSettings = stringifyPatterns(sanitizeSettings(docSettingsRaw, fields));
const settings = stringifyPatterns(sanitizeSettings(settingsRaw, fields));

console.warn('%o', {
fields,
settingsKeys: objectKeysNested(settings),
settingsSize: JSON.stringify(settings).length,
sizes: objectFieldSizes(settings),
});

return {
configFiles,
Expand Down

0 comments on commit 9d46a5b

Please sign in to comment.