Skip to content

Commit 638c36e

Browse files
authored
Merge pull request #296915 from mjbvz/dev/mjbvz/fit-koala
Add helper class to work with js/ts unified configs
2 parents 782dcfe + 000d29c commit 638c36e

File tree

5 files changed

+174
-63
lines changed

5 files changed

+174
-63
lines changed

extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type * as Proto from '../../tsServer/protocol/protocol';
1111
import * as PConst from '../../tsServer/protocol/protocol.const';
1212
import * as typeConverters from '../../typeConverters';
1313
import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService';
14-
import { readUnifiedConfig, unifiedConfigSection } from '../../utils/configuration';
14+
import { ResourceUnifiedConfigValue } from '../../utils/configuration';
1515
import { conditionalRegistration, requireHasModifiedUnifiedConfig, requireSomeCapability } from '../util/dependentRegistration';
1616
import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider';
1717
import { ExecutionTarget } from '../../tsServer/server';
@@ -23,31 +23,29 @@ const Config = Object.freeze({
2323
});
2424

2525
export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
26+
27+
private readonly _enabled: ResourceUnifiedConfigValue<boolean>;
28+
private readonly _showOnInterfaceMethods: ResourceUnifiedConfigValue<boolean>;
29+
private readonly _showOnAllClassMethods: ResourceUnifiedConfigValue<boolean>;
30+
2631
public constructor(
2732
client: ITypeScriptServiceClient,
2833
protected _cachedResponse: CachedResponse<Proto.NavTreeResponse>,
29-
private readonly language: LanguageDescription
3034
) {
3135
super(client, _cachedResponse);
32-
this._register(
33-
vscode.workspace.onDidChangeConfiguration(evt => {
34-
if (
35-
evt.affectsConfiguration(`${unifiedConfigSection}.${Config.enabled}`) ||
36-
evt.affectsConfiguration(`${language.id}.${Config.enabled}`) ||
37-
evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnInterfaceMethods}`) ||
38-
evt.affectsConfiguration(`${language.id}.${Config.showOnInterfaceMethods}`) ||
39-
evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnAllClassMethods}`) ||
40-
evt.affectsConfiguration(`${language.id}.${Config.showOnAllClassMethods}`)
41-
) {
42-
this.changeEmitter.fire();
43-
}
44-
})
45-
);
46-
}
4736

37+
this._enabled = this._register(new ResourceUnifiedConfigValue<boolean>(Config.enabled, false));
38+
this._register(this._enabled.onDidChange(() => this.changeEmitter.fire()));
39+
40+
this._showOnInterfaceMethods = this._register(new ResourceUnifiedConfigValue<boolean>(Config.showOnInterfaceMethods, false));
41+
this._register(this._showOnInterfaceMethods.onDidChange(() => this.changeEmitter.fire()));
42+
43+
this._showOnAllClassMethods = this._register(new ResourceUnifiedConfigValue<boolean>(Config.showOnAllClassMethods, false));
44+
this._register(this._showOnAllClassMethods.onDidChange(() => this.changeEmitter.fire()));
45+
}
4846

4947
override async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<ReferencesCodeLens[]> {
50-
const enabled = readUnifiedConfig<boolean>(Config.enabled, false, { scope: document, fallbackSection: this.language.id });
48+
const enabled = this._enabled.getValue(document);
5149
if (!enabled) {
5250
return [];
5351
}
@@ -131,7 +129,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
131129
if (
132130
item.kind === PConst.Kind.method &&
133131
parent?.kind === PConst.Kind.interface &&
134-
readUnifiedConfig<boolean>('implementationsCodeLens.showOnInterfaceMethods', false, { scope: document, fallbackSection: this.language.id })
132+
this._showOnInterfaceMethods.getValue(document)
135133
) {
136134
return getSymbolRange(document, item);
137135
}
@@ -141,7 +139,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
141139
if (
142140
item.kind === PConst.Kind.method &&
143141
parent?.kind === PConst.Kind.class &&
144-
readUnifiedConfig<boolean>('implementationsCodeLens.showOnAllClassMethods', false, { scope: document, fallbackSection: this.language.id })
142+
this._showOnAllClassMethods.getValue(document)
145143
) {
146144
// But not private ones as these can never be overridden
147145
if (/\bprivate\b/.test(item.kindModifiers ?? '')) {
@@ -165,6 +163,6 @@ export function register(
165163
requireSomeCapability(client, ClientCapability.Semantic),
166164
], () => {
167165
return vscode.languages.registerCodeLensProvider(selector.semantic,
168-
new TypeScriptImplementationsCodeLensProvider(client, cachedResponse, language));
166+
new TypeScriptImplementationsCodeLensProvider(client, cachedResponse));
169167
});
170168
}

extensions/typescript-language-features/src/languageFeatures/codeLens/referencesCodeLens.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import * as PConst from '../../tsServer/protocol/protocol.const';
1212
import { ExecutionTarget } from '../../tsServer/server';
1313
import * as typeConverters from '../../typeConverters';
1414
import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService';
15-
import { readUnifiedConfig, unifiedConfigSection } from '../../utils/configuration';
15+
import { ResourceUnifiedConfigValue } from '../../utils/configuration';
1616
import { conditionalRegistration, requireHasModifiedUnifiedConfig, requireSomeCapability } from '../util/dependentRegistration';
1717
import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider';
1818

@@ -22,28 +22,25 @@ const Config = Object.freeze({
2222
});
2323

2424
export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
25+
26+
private readonly _enabled: ResourceUnifiedConfigValue<boolean>;
27+
private readonly _showOnAllFunctions: ResourceUnifiedConfigValue<boolean>;
28+
2529
public constructor(
2630
client: ITypeScriptServiceClient,
2731
protected _cachedResponse: CachedResponse<Proto.NavTreeResponse>,
28-
private readonly language: LanguageDescription
2932
) {
3033
super(client, _cachedResponse);
31-
this._register(
32-
vscode.workspace.onDidChangeConfiguration(evt => {
33-
if (
34-
evt.affectsConfiguration(`${unifiedConfigSection}.${Config.enabled}`) ||
35-
evt.affectsConfiguration(`${language.id}.${Config.enabled}`) ||
36-
evt.affectsConfiguration(`${unifiedConfigSection}.${Config.showOnAllFunctions}`) ||
37-
evt.affectsConfiguration(`${language.id}.${Config.showOnAllFunctions}`)
38-
) {
39-
this.changeEmitter.fire();
40-
}
41-
})
42-
);
34+
35+
this._enabled = this._register(new ResourceUnifiedConfigValue<boolean>(Config.enabled, false));
36+
this._register(this._enabled.onDidChange(() => this.changeEmitter.fire()));
37+
38+
this._showOnAllFunctions = this._register(new ResourceUnifiedConfigValue<boolean>(Config.showOnAllFunctions, false));
39+
this._register(this._showOnAllFunctions.onDidChange(() => this.changeEmitter.fire()));
4340
}
4441

4542
override async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<ReferencesCodeLens[]> {
46-
const enabled = readUnifiedConfig<boolean>(Config.enabled, false, { scope: document, fallbackSection: this.language.id });
43+
const enabled = this._enabled.getValue(document);
4744
if (!enabled) {
4845
return [];
4946
}
@@ -95,7 +92,7 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens
9592

9693
switch (item.kind) {
9794
case PConst.Kind.function: {
98-
const showOnAllFunctions = readUnifiedConfig<boolean>(Config.showOnAllFunctions, false, { scope: document, fallbackSection: this.language.id });
95+
const showOnAllFunctions = this._showOnAllFunctions.getValue(document);
9996
if (showOnAllFunctions && item.nameSpan) {
10097
return getSymbolRange(document, item);
10198
}
@@ -160,6 +157,6 @@ export function register(
160157
requireSomeCapability(client, ClientCapability.Semantic),
161158
], () => {
162159
return vscode.languages.registerCodeLensProvider(selector.semantic,
163-
new TypeScriptReferencesCodeLensProvider(client, cachedResponse, language));
160+
new TypeScriptReferencesCodeLensProvider(client, cachedResponse));
164161
});
165162
}

extensions/typescript-language-features/src/languageProvider.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ClientCapability } from './typescriptService';
1818
import TypeScriptServiceClient from './typescriptServiceClient';
1919
import TypingsStatus from './ui/typingsStatus';
2020
import { Disposable } from './utils/dispose';
21-
import { readUnifiedConfig } from './utils/configuration';
21+
import { UnifiedConfigValue } from './utils/configuration';
2222
import { isWeb, isWebAndHasSharedArrayBuffers, supportsReadableByteStreams } from './utils/platform';
2323

2424

@@ -34,8 +34,16 @@ export default class LanguageProvider extends Disposable {
3434
private readonly onCompletionAccepted: (item: vscode.CompletionItem) => void,
3535
) {
3636
super();
37-
vscode.workspace.onDidChangeConfiguration(this.configurationChanged, this, this._disposables);
38-
this.configurationChanged();
37+
38+
const scope: vscode.ConfigurationScope = { languageId: this.description.languageIds[0] };
39+
40+
const validateConfig = this._register(new UnifiedConfigValue<boolean>('validate.enabled', true, { scope, fallbackSection: this.id, fallbackSubSectionNameOverride: 'validate.enable' }));
41+
this.updateValidate(validateConfig.getValue());
42+
this._register(validateConfig.onDidChange(value => this.updateValidate(value)));
43+
44+
const suggestionsConfig = this._register(new UnifiedConfigValue<boolean>('suggestionActions.enabled', true, { scope, fallbackSection: this.id }));
45+
this.updateSuggestionDiagnostics(suggestionsConfig.getValue());
46+
this._register(suggestionsConfig.onDidChange(value => this.updateSuggestionDiagnostics(value)));
3947

4048
client.onReady(() => this.registerProviders());
4149
}
@@ -91,12 +99,6 @@ export default class LanguageProvider extends Disposable {
9199
]);
92100
}
93101

94-
private configurationChanged(): void {
95-
const scope: vscode.ConfigurationScope = { languageId: this.description.languageIds[0] };
96-
this.updateValidate(readUnifiedConfig<boolean>('validate.enabled', true, { scope, fallbackSection: this.id, fallbackSubSectionNameOverride: 'validate.enable' }));
97-
this.updateSuggestionDiagnostics(readUnifiedConfig<boolean>('suggestionActions.enabled', true, { scope, fallbackSection: this.id }));
98-
}
99-
100102
public handlesUri(resource: vscode.Uri): boolean {
101103
const ext = extname(resource.path).slice(1).toLowerCase();
102104
return this.description.standardFileExtensions.includes(ext) || this.handlesConfigFile(resource);

extensions/typescript-language-features/src/tsServer/bufferSyncSupport.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as typeConverters from '../typeConverters';
1010
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
1111
import { inMemoryResourcePrefix } from '../typescriptServiceClient';
1212
import { coalesce } from '../utils/arrays';
13-
import { readUnifiedConfig, unifiedConfigSection } from '../utils/configuration';
13+
import { ResourceUnifiedConfigValue } from '../utils/configuration';
1414
import { Delayer, setImmediate } from '../utils/async';
1515
import { nulToken } from '../utils/cancellation';
1616
import { Disposable } from '../utils/dispose';
@@ -471,6 +471,8 @@ export default class BufferSyncSupport extends Disposable {
471471
private listening: boolean = false;
472472
private readonly synchronizer: BufferSynchronizer;
473473

474+
private readonly _validate: ResourceUnifiedConfigValue<boolean>;
475+
474476
private readonly _tabResources: TabResourceTracker;
475477

476478
constructor(
@@ -482,6 +484,8 @@ export default class BufferSyncSupport extends Disposable {
482484
this.client = client;
483485
this.modeIds = new Set<string>(modeIds);
484486

487+
this._validate = this._register(new ResourceUnifiedConfigValue<boolean>('validate.enabled', true, { fallbackSubSectionNameOverride: 'validate.enable' }));
488+
485489
this.diagnosticDelayer = new Delayer<any>(300);
486490

487491
const pathNormalizer = (path: vscode.Uri) => this.client.toTsFilePath(path);
@@ -511,14 +515,7 @@ export default class BufferSyncSupport extends Disposable {
511515
}
512516
}));
513517

514-
this._register(vscode.workspace.onDidChangeConfiguration((e) => {
515-
if (e.affectsConfiguration(`${unifiedConfigSection}.validate.enabled`)
516-
|| e.affectsConfiguration('typescript.validate.enable')
517-
|| e.affectsConfiguration('javascript.validate.enable')
518-
) {
519-
this.requestAllDiagnostics();
520-
}
521-
}));
518+
this._register(this._validate.onDidChange(() => this.requestAllDiagnostics()));
522519
}
523520

524521
private readonly _onDelete = this._register(new vscode.EventEmitter<vscode.Uri>());
@@ -769,9 +766,6 @@ export default class BufferSyncSupport extends Disposable {
769766
return false;
770767
}
771768

772-
const fallbackSection = (buffer.languageId === languageModeIds.javascript || buffer.languageId === languageModeIds.javascriptreact)
773-
? 'javascript'
774-
: 'typescript';
775-
return readUnifiedConfig<boolean>('validate.enabled', true, { scope: buffer.document, fallbackSection, fallbackSubSectionNameOverride: 'validate.enable' });
769+
return this._validate.getValue(buffer.document);
776770
}
777771
}

extensions/typescript-language-features/src/utils/configuration.ts

Lines changed: 123 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as vscode from 'vscode';
7+
import { Disposable } from './dispose';
78

89
export type UnifiedConfigurationScope = vscode.ConfigurationScope | null | undefined;
910

1011
export const unifiedConfigSection = 'js/ts';
1112

12-
export type ReadUnifiedConfigOptions = {
13-
readonly scope?: UnifiedConfigurationScope;
13+
export interface ReadUnifiedConfigOptions<Scope = UnifiedConfigurationScope> {
14+
readonly scope?: Scope;
1415
readonly fallbackSection: string;
1516
readonly fallbackSubSectionNameOverride?: string;
16-
};
17+
}
1718

1819
/**
1920
* Gets a configuration value, checking the unified `js/ts` setting first,
@@ -75,3 +76,122 @@ export function hasModifiedUnifiedConfig(
7576
const languageConfig = vscode.workspace.getConfiguration(options.fallbackSection, options.scope);
7677
return hasModifiedValue(languageConfig.inspect(subSectionName));
7778
}
79+
80+
/**
81+
* A cached, observable unified configuration value.
82+
*/
83+
export class UnifiedConfigValue<T> extends Disposable {
84+
85+
private _value: T;
86+
87+
private readonly _onDidChange = this._register(new vscode.EventEmitter<T>());
88+
public get onDidChange() { return this._onDidChange.event; }
89+
90+
constructor(
91+
private readonly subSectionName: string,
92+
private readonly defaultValue: T,
93+
private readonly options: ReadUnifiedConfigOptions<{ languageId: string }>,
94+
) {
95+
super();
96+
97+
this._value = this.read();
98+
99+
this._register(vscode.workspace.onDidChangeConfiguration(e => {
100+
if (e.affectsConfiguration(`${unifiedConfigSection}.${subSectionName}`, options.scope ?? undefined) ||
101+
e.affectsConfiguration(`${options.fallbackSection}.${options.fallbackSubSectionNameOverride ?? subSectionName}`, options.scope ?? undefined)
102+
) {
103+
const newValue = this.read();
104+
if (newValue !== this._value) {
105+
this._value = newValue;
106+
this._onDidChange.fire(newValue);
107+
}
108+
}
109+
}));
110+
}
111+
112+
private read(): T {
113+
return readUnifiedConfig<T>(this.subSectionName, this.defaultValue, this.options);
114+
}
115+
116+
public getValue(): T {
117+
return this._value;
118+
}
119+
}
120+
121+
export interface ResourceUnifiedConfigScope {
122+
readonly uri: vscode.Uri;
123+
readonly languageId: string;
124+
}
125+
126+
/**
127+
* A cached, observable unified configuration value that varies per workspace folder.
128+
*
129+
* Values are keyed by the workspace folder the resource belongs to, with a separate
130+
* entry for resources outside any workspace folder.
131+
*/
132+
export class ResourceUnifiedConfigValue<T> extends Disposable {
133+
134+
private readonly _cache = new Map</* workspace folder */ string, T>();
135+
136+
private readonly _onDidChange = this._register(new vscode.EventEmitter<void>());
137+
public readonly onDidChange = this._onDidChange.event;
138+
139+
constructor(
140+
private readonly subSectionName: string,
141+
private readonly defaultValue: T,
142+
private readonly options?: {
143+
readonly fallbackSubSectionNameOverride?: string;
144+
},
145+
) {
146+
super();
147+
148+
const fallbackName = options?.fallbackSubSectionNameOverride ?? subSectionName;
149+
150+
this._register(vscode.workspace.onDidChangeConfiguration(e => {
151+
if (e.affectsConfiguration(`${unifiedConfigSection}.${subSectionName}`) ||
152+
e.affectsConfiguration(`javascript.${fallbackName}`) ||
153+
e.affectsConfiguration(`typescript.${fallbackName}`)
154+
) {
155+
this._cache.clear();
156+
this._onDidChange.fire();
157+
}
158+
}));
159+
160+
this._register(vscode.workspace.onDidChangeWorkspaceFolders(() => {
161+
this._cache.clear();
162+
this._onDidChange.fire();
163+
}));
164+
}
165+
166+
public getValue(scope: ResourceUnifiedConfigScope): T {
167+
const key = this.keyFor(scope);
168+
const cached = this._cache.get(key);
169+
if (cached !== undefined) {
170+
return cached;
171+
}
172+
173+
const fallbackSection = this.fallbackSectionFor(scope.languageId);
174+
const value = readUnifiedConfig<T>(this.subSectionName, this.defaultValue, {
175+
scope: { uri: scope.uri, languageId: scope.languageId },
176+
fallbackSection,
177+
fallbackSubSectionNameOverride: this.options?.fallbackSubSectionNameOverride,
178+
});
179+
this._cache.set(key, value);
180+
return value;
181+
}
182+
183+
private fallbackSectionFor(languageId: string): string {
184+
switch (languageId) {
185+
case 'javascript':
186+
case 'javascriptreact':
187+
return 'javascript';
188+
default:
189+
return 'typescript';
190+
}
191+
}
192+
193+
private keyFor(scope: ResourceUnifiedConfigScope): string {
194+
const folder = vscode.workspace.getWorkspaceFolder(scope.uri);
195+
return folder ? folder.uri.toString() : '';
196+
}
197+
}

0 commit comments

Comments
 (0)