Skip to content

Commit

Permalink
feat: Support inline cspell directives (#2966)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Dec 8, 2023
1 parent fb06f93 commit d1b3ac3
Show file tree
Hide file tree
Showing 13 changed files with 650 additions and 143 deletions.
2 changes: 1 addition & 1 deletion docs/_includes/generated-docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ Description
**Note:** VS Code must be restarted for this setting to take effect.

Default
: _`false`_
: _`true`_

---

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3205,7 +3205,7 @@
"type": "number"
},
"cSpell.showAutocompleteSuggestions": {
"default": false,
"default": true,
"markdownDescription": "Show CSpell in-document directives as you type.\n\n**Note:** VS Code must be restarted for this setting to take effect.",
"scope": "language-overridable",
"type": "boolean"
Expand Down
109 changes: 109 additions & 0 deletions packages/__utils/src/AutoResolve.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// import { describe, expect, test, vi } from 'vitest';

import { createAutoResolveCache, createAutoResolveWeakCache } from './AutoResolve';

describe('AutoResolve', () => {
test('createAutoResolveCache', () => {
const cache = createAutoResolveCache<string, string>();

const resolver = jest.fn((s: string) => s.toUpperCase());

expect(cache.get('hello')).toBe(undefined);
expect(cache.get('hello', resolver)).toBe('HELLO');
expect(resolver).toHaveBeenCalledTimes(1);
expect(cache.get('hello', resolver)).toBe('HELLO');
expect(resolver).toHaveBeenCalledTimes(1);
cache.set('hello', 'hello');
expect(cache.get('hello', resolver)).toBe('hello');
expect(resolver).toHaveBeenCalledTimes(1);
expect(cache.get('a', resolver)).toBe('A');
expect(resolver).toHaveBeenCalledTimes(2);
});

test('createAutoResolveWeakCache', () => {
const cache = createAutoResolveWeakCache<{ name: string }, string>();

const resolver = jest.fn((v: { name: string }) => v.name.toUpperCase());

const tagHello = { name: 'hello' };
const tagHello2 = { ...tagHello };
const tagA = { name: 'a' };
expect(cache.get(tagHello)).toBe(undefined);
expect(cache.get(tagHello, resolver)).toBe('HELLO');
expect(resolver).toHaveBeenCalledTimes(1);
expect(cache.get(tagHello, resolver)).toBe('HELLO');
expect(resolver).toHaveBeenCalledTimes(1);
cache.set(tagHello, 'hello');
expect(cache.get(tagHello, resolver)).toBe('hello');
expect(resolver).toHaveBeenCalledTimes(1);
expect(cache.get(tagA, resolver)).toBe('A');
expect(resolver).toHaveBeenCalledTimes(2);
expect(cache.get(tagHello2)).toBe(undefined);
expect(cache.get(tagHello2, resolver)).toBe('HELLO');
expect(resolver).toHaveBeenCalledTimes(3);
expect(cache.stats()).toEqual({
hits: 2,
misses: 5,
deletes: 0,
resolved: 3,
sets: 1,
disposals: 0,
clears: 0,
});
expect(cache.get(tagHello2, resolver)).toBe('HELLO');
expect(cache.stats()).toEqual({
hits: 3,
misses: 5,
deletes: 0,
resolved: 3,
sets: 1,
disposals: 0,
clears: 0,
});
cache.delete(tagHello);
expect(cache.stats()).toEqual({
hits: 3,
misses: 5,
deletes: 1,
resolved: 3,
sets: 1,
disposals: 0,
clears: 0,
});
expect(cache.get(tagHello, resolver)).toBe('HELLO');
expect(cache.stats()).toEqual({
hits: 3,
misses: 6,
deletes: 1,
resolved: 4,
sets: 1,
disposals: 0,
clears: 0,
});
const weakMap = cache.map;
cache.clear();
const weakMap2 = cache.map;
expect(weakMap2).toBe(cache.map);
expect(weakMap2).not.toBe(weakMap);
expect(cache.stats()).toEqual({
hits: 0,
misses: 0,
deletes: 0,
resolved: 0,
sets: 0,
disposals: 0,
clears: 1,
});
cache.dispose();
expect(cache.map).not.toBe(weakMap2);
expect(cache.stats()).toEqual({
hits: 0,
misses: 0,
deletes: 0,
resolved: 0,
sets: 0,
disposals: 1,
clears: 2,
});
});
});
168 changes: 168 additions & 0 deletions packages/__utils/src/AutoResolve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
interface IDisposable {
dispose(): void;
}

export function autoResolve<K, V>(map: Map<K, V>, key: K, resolve: (k: K) => V): V {
const found = map.get(key);
if (found !== undefined || map.has(key)) return found as V;
const value = resolve(key);
map.set(key, value);
return value;
}

export interface CacheStats {
hits: number;
misses: number;
resolved: number;
deletes: number;
sets: number;
clears: number;
disposals: number;
}

export type AutoResolveCacheStats = Readonly<CacheStats>;

class CacheStatsTracker implements CacheStats {
hits: number = 0;
misses: number = 0;
resolved: number = 0;
deletes: number = 0;
sets: number = 0;
clears: number = 0;
disposals: number = 0;

stats(): AutoResolveCacheStats {
return {
hits: this.hits,
misses: this.misses,
resolved: this.resolved,
deletes: this.deletes,
sets: this.sets,
clears: this.clears,
disposals: this.disposals,
};
}

clear(): void {
this.hits = 0;
this.misses = 0;
this.resolved = 0;
this.deletes = 0;
this.sets = 0;
++this.clears;
}
}

export class AutoResolveCache<K, V> implements IDisposable {
readonly map = new Map<K, V>();

get(k: K): V | undefined;
get(k: K, resolve: (k: K) => V): V;
get(k: K, resolve?: (k: K) => V): V | undefined;
get(k: K, resolve?: (k: K) => V): V | undefined {
return resolve ? autoResolve(this.map, k, resolve) : this.map.get(k);
}

has(k: K): boolean {
return this.map.has(k);
}

set(k: K, v: V): this {
this.map.set(k, v);
return this;
}

delete(k: K): boolean {
return this.map.delete(k);
}

clear(): void {
this.map.clear();
}

dispose(): void {
this.clear();
}
}

export function createAutoResolveCache<K, V>(): AutoResolveCache<K, V> {
return new AutoResolveCache();
}

export interface IWeakMap<K extends object, V> {
get(k: K): V | undefined;
set(k: K, v: V): this;
has(k: K): boolean;
delete(key: K): boolean;
}

export function autoResolveWeak<K extends object, V>(map: IWeakMap<K, V>, key: K, resolve: (k: K) => V): V {
const found = map.get(key);
if (found !== undefined || map.has(key)) return found as V;
const value = resolve(key);
map.set(key, value);
return value;
}

export class AutoResolveWeakCache<K extends object, V> implements IWeakMap<K, V> {
private _map = new WeakMap<K, V>();

private _stats = new CacheStatsTracker();

get(k: K): V | undefined;
get(k: K, resolve: (k: K) => V): V;
get(k: K, resolve?: (k: K) => V): V | undefined;
get(k: K, resolve?: (k: K) => V): V | undefined {
const map = this._map;
const found = map.get(k);
if (found !== undefined || map.has(k)) {
++this._stats.hits;
return found as V;
}
++this._stats.misses;
if (!resolve) {
return undefined;
}
++this._stats.resolved;
const value = resolve(k);
map.set(k, value);
return value;
}

get map() {
return this._map;
}

has(k: K): boolean {
return this._map.has(k);
}

set(k: K, v: V): this {
++this._stats.sets;
this._map.set(k, v);
return this;
}

clear(): void {
this._stats.clear();
this._map = new WeakMap();
}

delete(k: K): boolean {
++this._stats.deletes;
return this._map.delete(k);
}

dispose(): void {
++this._stats.disposals;
this.clear();
}

stats(): AutoResolveCacheStats {
return this._stats.stats();
}
}

export function createAutoResolveWeakCache<K extends object, V>(): AutoResolveWeakCache<K, V> {
return new AutoResolveWeakCache();
}
1 change: 1 addition & 0 deletions packages/__utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { AutoResolveCache, createAutoResolveCache, createAutoResolveWeakCache } from './AutoResolve.js';
export * from './errors.js';
export { LogFileConnection } from './logFile.js';
export * from './uriHelper.js';
Expand Down
4 changes: 3 additions & 1 deletion packages/__utils/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"compilerOptions": {},
"compilerOptions": {
"esModuleInterop": true
},
"files": [],
"references": [{ "path": "./tsconfig.cjs.json" }, { "path": "./tsconfig.esm.json" }]
}
2 changes: 1 addition & 1 deletion packages/_server/spell-checker-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2788,7 +2788,7 @@
"type": "number"
},
"cSpell.showAutocompleteSuggestions": {
"default": false,
"default": true,
"description": "Show CSpell in-document directives as you type.\n\n**Note:** VS Code must be restarted for this setting to take effect.",
"markdownDescription": "Show CSpell in-document directives as you type.\n\n**Note:** VS Code must be restarted for this setting to take effect.",
"scope": "language-overridable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export interface SpellCheckerSettings extends SpellCheckerShouldCheckDocSettings
*
* **Note:** VS Code must be restarted for this setting to take effect.
* @scope language-overridable
* @default false
* @default true
*/
showAutocompleteSuggestions?: boolean;

Expand Down
6 changes: 4 additions & 2 deletions packages/client/src/autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ vi.mock('vscode');
vi.mock('vscode-languageclient/node');

const mockedRegisterCompletionItemProvider = vi.mocked(languages.registerCompletionItemProvider);
const mockedRegisterInlineCompletionItemProvider = vi.mocked(languages.registerInlineCompletionItemProvider);

describe('autocomplete', () => {
test('registerCspellInlineCompletionProviders', async () => {
const disposables: { dispose(): any }[] = [];
await registerCspellInlineCompletionProviders(disposables);
expect(mockedRegisterCompletionItemProvider).toHaveBeenCalledTimes(4);
expect(disposables).toHaveLength(4);
expect(mockedRegisterCompletionItemProvider).toHaveBeenCalledTimes(0);
expect(mockedRegisterInlineCompletionItemProvider).toHaveBeenCalledTimes(1);
expect(disposables).toHaveLength(1);
});
});
Loading

0 comments on commit d1b3ac3

Please sign in to comment.