diff --git a/examples/api-samples/src/browser/file-watching/sample-file-watching-contribution.ts b/examples/api-samples/src/browser/file-watching/sample-file-watching-contribution.ts index ac8ad1649bfe9..3a12e7f6b4a97 100644 --- a/examples/api-samples/src/browser/file-watching/sample-file-watching-contribution.ts +++ b/examples/api-samples/src/browser/file-watching/sample-file-watching-contribution.ts @@ -69,7 +69,7 @@ class SampleFileWatchingContribution implements FrontendApplicationContribution this.verbose = this.fileWatchingPreferences['sample.file-watching.verbose']; this.fileWatchingPreferences.onPreferenceChanged(e => { if (e.preferenceName === 'sample.file-watching.verbose') { - this.verbose = e.newValue!; + this.verbose = e.newValue; } }); } diff --git a/packages/core/src/browser/preferences/preference-proxy.ts b/packages/core/src/browser/preferences/preference-proxy.ts index 3fc56f6ba9568..f3f63b8f9bf8b 100644 --- a/packages/core/src/browser/preferences/preference-proxy.ts +++ b/packages/core/src/browser/preferences/preference-proxy.ts @@ -21,12 +21,61 @@ import { PreferenceService } from './preference-service'; import { PreferenceSchema, OverridePreferenceName } from './preference-contribution'; import { PreferenceScope } from './preference-scope'; -export interface PreferenceChangeEvent<T> { - readonly preferenceName: keyof T; - readonly newValue?: T[keyof T]; - readonly oldValue?: T[keyof T]; +/** + * It is worth explaining the type for `PreferenceChangeEvent`: + * + * // Given T: + * type T = { a: string, b: number } + * + * // We construct a new type such as: + * type U = { + * a: { + * preferenceName: 'a' + * newValue: string + * oldValue?: string + * } + * b: { + * preferenceName: 'b' + * newValue: number + * oldValue?: number + * } + * } + * + * // Then we get the union of all values of U by selecting by `keyof T`: + * type V = U[keyof T] + * + * // Implementation: + * type PreferenceChangeEvent<T> = { + * // Create a mapping where each key is a key from T, + * // -? normalizes optional typings to avoid getting + * // `undefined` as part of the final union: + * [K in keyof T]-?: { + * // In this object, K will take the value of each + * // independent key from T: + * preferenceName: K + * newValue: T[K] + * oldValue?: T[K] + * // Finally we create the union by doing so: + * }[keyof T] + * } + */ + +/** + * Union of all possible key/value pairs for a type `T` + */ +export type PreferenceChangeEvent<T> = { affects(resourceUri?: string, overrideIdentifier?: string): boolean; -} +} & { + [K in keyof T]-?: { + readonly preferenceName: K; + readonly newValue: T[K]; + /** + * Undefined if the preference is set for the first time. + */ + // TODO: Use the default value instead of undefined? + readonly oldValue?: T[K]; + } +}[keyof T]; export interface PreferenceEventEmitter<T> { readonly onPreferenceChanged: Event<PreferenceChangeEvent<T>>; diff --git a/packages/git/src/browser/diff/git-diff-widget.tsx b/packages/git/src/browser/diff/git-diff-widget.tsx index f22d82075d5fe..bf667c0dcbcbe 100644 --- a/packages/git/src/browser/diff/git-diff-widget.tsx +++ b/packages/git/src/browser/diff/git-diff-widget.tsx @@ -16,7 +16,7 @@ import { inject, injectable, postConstruct } from 'inversify'; import { - BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, Message, MessageLoop, PreferenceChangeEvent + BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, Message, MessageLoop } from '@theia/core/lib/browser'; import { EditorManager, DiffNavigatorProvider } from '@theia/editor/lib/browser'; import { GitDiffTreeModel } from './git-diff-tree-model'; @@ -25,7 +25,7 @@ import { GitDiffHeaderWidget } from './git-diff-header-widget'; import { ScmService } from '@theia/scm/lib/browser/scm-service'; import { GitRepositoryProvider } from '../git-repository-provider'; import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget'; -import { ScmPreferences, ScmConfiguration } from '@theia/scm/lib/browser/scm-preferences'; +import { ScmPreferences } from '@theia/scm/lib/browser/scm-preferences'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -76,9 +76,9 @@ export class GitDiffWidget extends BaseWidget implements StatefulWidget { this.containerLayout.addWidget(this.resourceWidget); this.updateViewMode(this.scmPreferences.get('scm.defaultViewMode')); - this.toDispose.push(this.scmPreferences.onPreferenceChanged((e: PreferenceChangeEvent<ScmConfiguration>) => { + this.toDispose.push(this.scmPreferences.onPreferenceChanged(e => { if (e.preferenceName === 'scm.defaultViewMode') { - this.updateViewMode(e.newValue!); + this.updateViewMode(e.newValue); } })); } diff --git a/packages/git/src/browser/history/git-commit-detail-widget.tsx b/packages/git/src/browser/history/git-commit-detail-widget.tsx index afba377fbad55..32e9e4bcd57fa 100644 --- a/packages/git/src/browser/history/git-commit-detail-widget.tsx +++ b/packages/git/src/browser/history/git-commit-detail-widget.tsx @@ -19,14 +19,14 @@ import { Message } from '@phosphor/messaging'; import { injectable, inject, postConstruct } from 'inversify'; import { - BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, MessageLoop, PreferenceChangeEvent + BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, MessageLoop } from '@theia/core/lib/browser'; import { GitCommitDetailWidgetOptions } from './git-commit-detail-widget-options'; import { GitCommitDetailHeaderWidget } from './git-commit-detail-header-widget'; import { ScmService } from '@theia/scm/lib/browser/scm-service'; import { GitDiffTreeModel } from '../diff/git-diff-tree-model'; import { ScmTreeWidget } from '@theia/scm/lib/browser/scm-tree-widget'; -import { ScmPreferences, ScmConfiguration } from '@theia/scm/lib/browser/scm-preferences'; +import { ScmPreferences } from '@theia/scm/lib/browser/scm-preferences'; @injectable() export class GitCommitDetailWidget extends BaseWidget implements StatefulWidget { @@ -76,9 +76,9 @@ export class GitCommitDetailWidget extends BaseWidget implements StatefulWidget this.containerLayout.addWidget(this.resourceWidget); this.updateViewMode(this.scmPreferences.get('scm.defaultViewMode')); - this.toDispose.push(this.scmPreferences.onPreferenceChanged((e: PreferenceChangeEvent<ScmConfiguration>) => { + this.toDispose.push(this.scmPreferences.onPreferenceChanged(e => { if (e.preferenceName === 'scm.defaultViewMode') { - this.updateViewMode(e.newValue!); + this.updateViewMode(e.newValue); } })); diff --git a/packages/markers/src/browser/problem/problem-decorator.ts b/packages/markers/src/browser/problem/problem-decorator.ts index d75bd15551881..697278854d95a 100644 --- a/packages/markers/src/browser/problem/problem-decorator.ts +++ b/packages/markers/src/browser/problem/problem-decorator.ts @@ -25,8 +25,7 @@ import { TreeDecorator, TreeDecoration } from '@theia/core/lib/browser/tree/tree import { FileStatNode } from '@theia/filesystem/lib/browser'; import { Marker } from '../../common/marker'; import { ProblemManager } from './problem-manager'; -import { ProblemPreferences, ProblemConfiguration } from './problem-preferences'; -import { PreferenceChangeEvent } from '@theia/core/lib/browser'; +import { ProblemPreferences } from './problem-preferences'; import { ProblemUtils } from './problem-utils'; @injectable() @@ -46,10 +45,9 @@ export class ProblemDecorator implements TreeDecorator { @postConstruct() protected init(): void { - this.problemPreferences.onPreferenceChanged((event: PreferenceChangeEvent<ProblemConfiguration>) => { - const { preferenceName } = event; - if (preferenceName === 'problems.decorations.enabled') { - this.fireDidChangeDecorations((tree: Tree) => this.collectDecorators(tree)); + this.problemPreferences.onPreferenceChanged(event => { + if (event.preferenceName === 'problems.decorations.enabled') { + this.fireDidChangeDecorations(tree => this.collectDecorators(tree)); } }); } diff --git a/packages/monaco/src/browser/textmate/monaco-textmate-service.ts b/packages/monaco/src/browser/textmate/monaco-textmate-service.ts index a21c61965f08b..75fd443dc5781 100644 --- a/packages/monaco/src/browser/textmate/monaco-textmate-service.ts +++ b/packages/monaco/src/browser/textmate/monaco-textmate-service.ts @@ -103,7 +103,7 @@ export class MonacoTextmateService implements FrontendApplicationContribution { this.tokenizerOption.lineLimit = this.preferences['editor.maxTokenizationLineLength']; this.preferences.onPreferenceChanged(e => { if (e.preferenceName === 'editor.maxTokenizationLineLength') { - this.tokenizerOption.lineLimit = this.preferences['editor.maxTokenizationLineLength']; + this.tokenizerOption.lineLimit = e.newValue; } }); diff --git a/packages/output/src/common/output-channel.ts b/packages/output/src/common/output-channel.ts index 163083a312acf..7f7ee421cfd2e 100644 --- a/packages/output/src/common/output-channel.ts +++ b/packages/output/src/common/output-channel.ts @@ -25,7 +25,6 @@ import { MonacoTextModelService, IReference } from '@theia/monaco/lib/browser/mo import { OutputUri } from './output-uri'; import { OutputResource } from '../browser/output-resource'; import { OutputPreferences } from './output-preferences'; -import { OutputConfigSchema } from './output-preferences'; @injectable() export class OutputChannelManager implements Disposable, ResourceResolver { @@ -221,9 +220,9 @@ export class OutputChannel implements Disposable { this._maxLineNumber = this.preferences['output.maxChannelHistory']; this.toDispose.push(resource); this.toDispose.push(Disposable.create(() => this.decorationIds.clear())); - this.toDispose.push(this.preferences.onPreferenceChanged(({ preferenceName, newValue }) => { - if (preferenceName === 'output.maxChannelHistory') { - const maxLineNumber = newValue ? newValue : OutputConfigSchema.properties['output.maxChannelHistory'].default; + this.toDispose.push(this.preferences.onPreferenceChanged(event => { + if (event.preferenceName === 'output.maxChannelHistory') { + const maxLineNumber = event.newValue; if (this.maxLineNumber !== maxLineNumber) { this.maxLineNumber = maxLineNumber; } diff --git a/packages/preview/src/browser/preview-widget.ts b/packages/preview/src/browser/preview-widget.ts index 32e413f514183..ff5b69c707621 100644 --- a/packages/preview/src/browser/preview-widget.ts +++ b/packages/preview/src/browser/preview-widget.ts @@ -83,7 +83,7 @@ export class PreviewWidget extends BaseWidget implements Navigatable { this.scrollBeyondLastLine = !!this.editorPreferences['editor.scrollBeyondLastLine']; this.toDispose.push(this.editorPreferences.onPreferenceChanged(e => { if (e.preferenceName === 'editor.scrollBeyondLastLine') { - this.scrollBeyondLastLine = !!e.newValue; + this.scrollBeyondLastLine = e.newValue; this.forceUpdate(); } })); diff --git a/packages/scm/src/browser/scm-widget.tsx b/packages/scm/src/browser/scm-widget.tsx index 06b1aa9cf0d14..d87f2868827d1 100644 --- a/packages/scm/src/browser/scm-widget.tsx +++ b/packages/scm/src/browser/scm-widget.tsx @@ -20,14 +20,14 @@ import { Message } from '@phosphor/messaging'; import { injectable, inject, postConstruct } from 'inversify'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { - BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, MessageLoop, PreferenceChangeEvent, CompositeTreeNode, SelectableTreeNode, + BaseWidget, Widget, StatefulWidget, Panel, PanelLayout, MessageLoop, CompositeTreeNode, SelectableTreeNode, } from '@theia/core/lib/browser'; import { ScmCommitWidget } from './scm-commit-widget'; import { ScmAmendWidget } from './scm-amend-widget'; import { ScmNoRepositoryWidget } from './scm-no-repository-widget'; import { ScmService } from './scm-service'; import { ScmTreeWidget } from './scm-tree-widget'; -import { ScmPreferences, ScmConfiguration } from './scm-preferences'; +import { ScmPreferences } from './scm-preferences'; @injectable() export class ScmWidget extends BaseWidget implements StatefulWidget { @@ -78,9 +78,9 @@ export class ScmWidget extends BaseWidget implements StatefulWidget { this.refresh(); this.toDispose.push(this.scmService.onDidChangeSelectedRepository(() => this.refresh())); this.updateViewMode(this.scmPreferences.get('scm.defaultViewMode')); - this.toDispose.push(this.scmPreferences.onPreferenceChanged((e: PreferenceChangeEvent<ScmConfiguration>) => { + this.toDispose.push(this.scmPreferences.onPreferenceChanged(e => { if (e.preferenceName === 'scm.defaultViewMode') { - this.updateViewMode(e.newValue!); + this.updateViewMode(e.newValue); } })); diff --git a/packages/terminal/src/browser/terminal-preferences.ts b/packages/terminal/src/browser/terminal-preferences.ts index 8a67f8c38ba3b..7c6b45adddee2 100644 --- a/packages/terminal/src/browser/terminal-preferences.ts +++ b/packages/terminal/src/browser/terminal-preferences.ts @@ -151,6 +151,7 @@ export const TerminalConfigSchema: PreferenceSchema = { export interface TerminalConfiguration { 'terminal.enableCopy': boolean 'terminal.enablePaste': boolean + // xterm compatible, see https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/ 'terminal.integrated.fontFamily': string 'terminal.integrated.fontSize': number 'terminal.integrated.fontWeight': FontWeight @@ -165,9 +166,9 @@ export interface TerminalConfiguration { 'terminal.integrated.cursorBlinking': boolean, 'terminal.integrated.cursorStyle': CursorStyleVSCode, 'terminal.integrated.cursorWidth': number, - 'terminal.integrated.shell.windows': string | undefined, - 'terminal.integrated.shell.osx': string | undefined, - 'terminal.integrated.shell.linux': string | undefined, + 'terminal.integrated.shell.windows': string | null | undefined, + 'terminal.integrated.shell.osx': string | null | undefined, + 'terminal.integrated.shell.linux': string | null | undefined, 'terminal.integrated.shellArgs.windows': string[], 'terminal.integrated.shellArgs.osx': string[], 'terminal.integrated.shellArgs.linux': string[], diff --git a/packages/terminal/src/browser/terminal-widget-impl.ts b/packages/terminal/src/browser/terminal-widget-impl.ts index 9f442189bf03d..373700c38482d 100644 --- a/packages/terminal/src/browser/terminal-widget-impl.ts +++ b/packages/terminal/src/browser/terminal-widget-impl.ts @@ -148,18 +148,16 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget const lastSeparator = change.preferenceName.lastIndexOf('.'); if (lastSeparator > 0) { let preferenceName = change.preferenceName.substr(lastSeparator + 1); - let preferenceValue = this.preferences[change.preferenceName]; + let preferenceValue = change.newValue; if (preferenceName === 'rendererType') { - const newRendererType: string = this.preferences[change.preferenceName] as string; + const newRendererType = preferenceValue as string; if (newRendererType !== this.getTerminalRendererType(newRendererType)) { - // given terminal renderer type is not supported or invalid + // Given terminal renderer type is not supported or invalid preferenceValue = DEFAULT_TERMINAL_RENDERER_TYPE; } - } - - // Convert the terminal preference into a valid `xterm` option. - if (preferenceName === 'cursorBlinking') { + } else if (preferenceName === 'cursorBlinking') { + // Convert the terminal preference into a valid `xterm` option preferenceName = 'cursorBlink'; } else if (preferenceName === 'cursorStyle') { preferenceValue = this.getCursorStyle(); @@ -635,9 +633,9 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget protected get shellPreferences(): IShellTerminalPreferences { return { shell: { - Windows: this.preferences['terminal.integrated.shell.windows'], - Linux: this.preferences['terminal.integrated.shell.linux'], - OSX: this.preferences['terminal.integrated.shell.osx'], + Windows: this.preferences['terminal.integrated.shell.windows'] ?? undefined, + Linux: this.preferences['terminal.integrated.shell.linux'] ?? undefined, + OSX: this.preferences['terminal.integrated.shell.osx'] ?? undefined, }, shellArgs: { Windows: this.preferences['terminal.integrated.shellArgs.windows'], diff --git a/packages/workspace/src/browser/workspace-service.ts b/packages/workspace/src/browser/workspace-service.ts index 8b792a774dcd3..5d6cf8de5e125 100644 --- a/packages/workspace/src/browser/workspace-service.ts +++ b/packages/workspace/src/browser/workspace-service.ts @@ -95,8 +95,8 @@ export class WorkspaceService implements FrontendApplicationContribution { this.updateWorkspace(); } }); - this.fsPreferences.onPreferenceChanged(e => { - if (e.preferenceName === 'files.watcherExclude') { + this.fsPreferences.onPreferenceChanged(event => { + if (event.preferenceName === 'files.watcherExclude') { this.refreshRootWatchers(); } });