diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index cd68edf2e5c8..81e2451e35b1 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -23,8 +23,7 @@ export function activate(context: vscode.ExtensionContext) { PreviewManager.viewType, { async resolveWebviewEditor({ resource }, editor: vscode.WebviewPanel): Promise { - previewManager.resolve(resource, editor); - return {}; + return previewManager.resolve(resource, editor); } })); diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 1b9c8d3f3222..310043c4d373 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -28,7 +28,7 @@ export class PreviewManager { public resolve( resource: vscode.Uri, webviewEditor: vscode.WebviewPanel, - ) { + ): vscode.WebviewEditorCapabilities { const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); @@ -42,6 +42,17 @@ export class PreviewManager { this.setActivePreview(undefined); } }); + + const onEdit = new vscode.EventEmitter<{ now: number }>(); + return { + editingCapability: { + onEdit: onEdit.event, + save: async () => { }, + hotExit: async () => { }, + applyEdits: async () => { }, + undoEdits: async (edits) => { console.log('undo', edits); }, + } + }; } public get activePreview() { return this._activePreview; } diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index ce73a59ac699..3aafeb61e60d 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -337,6 +337,9 @@ export default class BufferSyncSupport extends Disposable { private readonly _onDelete = this._register(new vscode.EventEmitter()); public readonly onDelete = this._onDelete.event; + private readonly _onWillChange = this._register(new vscode.EventEmitter()); + public readonly onWillChange = this._onWillChange.event; + public listen(): void { if (this.listening) { return; @@ -456,6 +459,8 @@ export default class BufferSyncSupport extends Disposable { return; } + this._onWillChange.fire(syncedBuffer.resource); + syncedBuffer.onContentChanged(e.contentChanges); const didTrigger = this.requestDiagnostic(syncedBuffer); diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index eccb5433e766..0ab2ae8719c6 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -24,7 +24,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('implementation', args, token, { lowPriority: true }); + const response = await this.client.execute('implementation', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 25b8d117b9c4..b926f6058ac1 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -19,7 +19,7 @@ class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvide public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise { const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('references', args, token, { lowPriority: true }); + const response = await this.client.execute('references', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 741fee0e94be..cd2d50e93ea6 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -78,6 +78,7 @@ export type TypeScriptRequests = StandardTsServerRequests & NoResponseTsServerRe export type ExecConfig = { readonly lowPriority?: boolean; readonly nonRecoverable?: boolean; + readonly cancelOnResourceChange?: vscode.Uri }; export interface ITypeScriptServiceClient { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index e1a2b10270f6..bb640ad80d34 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -35,6 +35,11 @@ export interface TsDiagnostics { readonly diagnostics: Proto.Diagnostic[]; } +interface ToCancelOnResourceChanged { + readonly resource: vscode.Uri; + cancel(): void; +} + namespace ServerState { export const enum Type { None, @@ -60,6 +65,8 @@ namespace ServerState { public tsserverVersion: string | undefined, public langaugeServiceEnabled: boolean, ) { } + + public readonly toCancelOnResourceChange = new Set(); } export class Errored { @@ -129,9 +136,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.diagnosticsManager = new DiagnosticsManager('typescript'); this.bufferSyncSupport.onDelete(resource => { + this.cancelInflightRequestsForResource(resource); this.diagnosticsManager.delete(resource); }, null, this._disposables); + this.bufferSyncSupport.onWillChange(resource => { + this.cancelInflightRequestsForResource(resource); + }); + vscode.workspace.onDidChangeConfiguration(() => { const oldConfiguration = this._configuration; this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace(); @@ -173,6 +185,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType })); } + private cancelInflightRequestsForResource(resource: vscode.Uri): void { + if (this.serverState.type !== ServerState.Type.Running) { + return; + } + + for (const request of this.serverState.toCancelOnResourceChange) { + if (request.resource.toString() === resource.toString()) { + request.cancel(); + } + } + } + public get configuration() { return this._configuration; } @@ -609,12 +633,37 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { - const execution = this.executeImpl(command, args, { - isAsync: false, - token, - expectsResult: true, - lowPriority: config?.lowPriority - }); + let execution: Promise>; + + if (config?.cancelOnResourceChange) { + const runningServerState = this.service(); + + const source = new vscode.CancellationTokenSource(); + token.onCancellationRequested(() => source.cancel()); + + const inFlight: ToCancelOnResourceChanged = { + resource: config.cancelOnResourceChange, + cancel: () => source.cancel(), + }; + runningServerState.toCancelOnResourceChange.add(inFlight); + + execution = this.executeImpl(command, args, { + isAsync: false, + token: source.token, + expectsResult: true, + ...config, + }).finally(() => { + runningServerState.toCancelOnResourceChange.delete(inFlight); + source.dispose(); + }); + } else { + execution = this.executeImpl(command, args, { + isAsync: false, + token, + expectsResult: true, + ...config, + }); + } if (config?.nonRecoverable) { execution.catch(() => this.fatalError(command)); diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 58fa1f7287ff..3af5da703672 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object'; + export interface IJSONSchema { id?: string; $id?: string; $schema?: string; - type?: string | string[]; + type?: JSONSchemaType | JSONSchemaType[]; title?: string; default?: any; definitions?: IJSONSchemaMap; diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts index fe88e82bc80b..7f1bef48d65c 100644 --- a/src/vs/base/common/lazy.ts +++ b/src/vs/base/common/lazy.ts @@ -54,6 +54,11 @@ export class Lazy { return this._value!; } + /** + * Get the wrapped value without forcing evaluation. + */ + get rawValue(): T | undefined { return this._value; } + /** * Create a new lazy value that is the result of applying `f` to the wrapped value. * diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index a105a5155ce5..cd1be6ebda90 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -13,7 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 47ba03606557..b643d0d7f237 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -30,7 +30,7 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './codeActionTrigger'; +import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger, CodeActionCommandArgs } from './types'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 91bd478b18f8..54a1946ca0d9 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -16,7 +16,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; -import { CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { isEqual } from 'vs/base/common/resources'; @@ -77,6 +77,7 @@ class CodeActionOracle extends Disposable { return Range.lift(marker); } } + return undefined; } diff --git a/src/vs/editor/contrib/codeAction/codeActionUi.ts b/src/vs/editor/contrib/codeAction/codeActionUi.ts index e616bbd206f4..7e80fdaff8cb 100644 --- a/src/vs/editor/contrib/codeAction/codeActionUi.ts +++ b/src/vs/editor/contrib/codeAction/codeActionUi.ts @@ -12,7 +12,7 @@ import { MessageController } from 'vs/editor/contrib/message/messageController'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CodeActionsState } from './codeActionModel'; -import { CodeActionAutoApply } from './codeActionTrigger'; +import { CodeActionAutoApply } from './types'; import { CodeActionWidget } from './codeActionWidget'; import { LightBulbWidget } from './lightBulbWidget'; import { IPosition } from 'vs/editor/common/core/position'; @@ -47,16 +47,14 @@ export class CodeActionUi extends Disposable { this._lightBulbWidget = new Lazy(() => { const widget = this._register(new LightBulbWidget(this._editor, quickFixActionId, preferredFixActionId, keybindingService)); - this._register(widget.onClick(this._handleLightBulbSelect, this)); + this._register(widget.onClick(e => this.showCodeActionList(e.actions, e))); return widget; }); } public async update(newState: CodeActionsState.State): Promise { if (newState.type !== CodeActionsState.Type.Triggered) { - if (this._lightBulbWidget.hasValue()) { - this._lightBulbWidget.getValue().hide(); - } + this._lightBulbWidget.rawValue?.hide(); return; } @@ -83,7 +81,7 @@ export class CodeActionUi extends Disposable { // Apply if we only have one action or requested autoApply if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && actions.actions.length === 1)) { try { - await this.delegate.applyCodeAction(actions.actions[0], false); + this.delegate.applyCodeAction(actions.actions[0], false); } finally { actions.dispose(); } @@ -104,11 +102,7 @@ export class CodeActionUi extends Disposable { } } - public async showCodeActionList(actions: CodeActionSet, at?: IAnchor | IPosition): Promise { + public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition): Promise { this._codeActionWidget.getValue().show(actions, at); } - - private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void { - this._codeActionWidget.getValue().show(e.actions, e); - } } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index 03b9b036b8e2..5cfc01cbfbe2 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -15,9 +15,10 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { CodeAction } from 'vs/editor/common/modes'; import { CodeActionSet, refactorCommandId, sourceActionCommandId, codeActionCommandId, organizeImportsCommandId, fixAllCommandId } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; interface CodeActionWidgetDelegate { onSelectCodeAction: (action: CodeAction) => Promise; @@ -43,70 +44,93 @@ export class CodeActionWidget extends Disposable { private _visible: boolean = false; private readonly _showingActions = this._register(new MutableDisposable()); + private readonly _keybindingResolver: CodeActionKeybindingResolver; + constructor( private readonly _editor: ICodeEditor, private readonly _contextMenuService: IContextMenuService, - private readonly _keybindingService: IKeybindingService, + keybindingService: IKeybindingService, private readonly _delegate: CodeActionWidgetDelegate, ) { super(); + + this._keybindingResolver = new CodeActionKeybindingResolver(keybindingService); } get isVisible(): boolean { return this._visible; } - public async show(codeActions: CodeActionSet, at?: IAnchor | IPosition): Promise { + public async show(codeActions: CodeActionSet, at: IAnchor | IPosition): Promise { if (!codeActions.actions.length) { this._visible = false; return; } + if (!this._editor.getDomNode()) { // cancel when editor went off-dom this._visible = false; - return Promise.reject(canceled()); + throw canceled(); } this._visible = true; + this._showingActions.value = codeActions; const actions = codeActions.actions.map(action => new CodeActionAction(action, () => this._delegate.onSelectCodeAction(action))); - const keyBindings = this.resolveKeybindings(actions); + const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; + const resolver = this._keybindingResolver.getResolver(); - this._showingActions.value = codeActions; this._contextMenuService.showContextMenu({ - getAnchor: () => { - if (Position.isIPosition(at)) { - at = this._toCoords(at); - } - return at || { x: 0, y: 0 }; - }, + getAnchor: () => anchor, getActions: () => actions, onHide: () => { this._visible = false; this._editor.focus(); }, autoSelectFirstItem: true, - getKeyBinding: (action): ResolvedKeybinding | undefined => { - return action instanceof CodeActionAction ? keyBindings.get(action) : undefined; - }, + getKeyBinding: action => action instanceof CodeActionAction ? resolver(action.action) : undefined, }); } - private resolveKeybindings(actions: readonly CodeActionAction[]): Map { - const codeActionCommands: readonly string[] = [ - refactorCommandId, - codeActionCommandId, - sourceActionCommandId, - organizeImportsCommandId, - fixAllCommandId - ]; + private _toCoords(position: IPosition): { x: number, y: number } { + if (!this._editor.hasModel()) { + return { x: 0, y: 0 }; + } + this._editor.revealPosition(position, ScrollType.Immediate); + this._editor.render(); + // Translate to absolute editor position + const cursorCoords = this._editor.getScrolledVisiblePosition(position); + const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); + const x = editorCoords.left + cursorCoords.left; + const y = editorCoords.top + cursorCoords.top + cursorCoords.height; + + return { x, y }; + } +} + +export class CodeActionKeybindingResolver { + private static readonly codeActionCommands: readonly string[] = [ + refactorCommandId, + codeActionCommandId, + sourceActionCommandId, + organizeImportsCommandId, + fixAllCommandId + ]; + + constructor( + private readonly _keybindingProvider: { + getKeybindings(): readonly ResolvedKeybindingItem[], + }, + ) { } + + public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined { // Lazy since we may not actually ever read the value const allCodeActionBindings = new Lazy(() => - this._keybindingService.getKeybindings() - .filter(item => codeActionCommands.indexOf(item.command!) >= 0) + this._keybindingProvider.getKeybindings() + .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0) .filter(item => item.resolvedKeybinding) .map((item): ResolveCodeActionKeybinding => { // Special case these commands since they come built-in with VS Code and don't use 'commandArgs' @@ -126,16 +150,13 @@ export class CodeActionWidget extends Disposable { }; })); - const out = new Map(); - for (const action of actions) { - if (action.action.kind) { - const binding = this.bestKeybindingForCodeAction(action.action, allCodeActionBindings.getValue()); - if (binding) { - out.set(action, binding.resolvedKeybinding); - } + return (action) => { + if (action.kind) { + const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue()); + return binding?.resolvedKeybinding; } - } - return out; + return undefined; + }; } private bestKeybindingForCodeAction( @@ -164,20 +185,4 @@ export class CodeActionWidget extends Disposable { return currentBest.kind.contains(candidate.kind) ? candidate : currentBest; }, undefined as ResolveCodeActionKeybinding | undefined); } - - private _toCoords(position: IPosition): { x: number, y: number } { - if (!this._editor.hasModel()) { - return { x: 0, y: 0 }; - } - this._editor.revealPosition(position, ScrollType.Immediate); - this._editor.render(); - - // Translate to absolute editor position - const cursorCoords = this._editor.getScrolledVisiblePosition(position); - const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); - const x = editorCoords.left + cursorCoords.left; - const y = editorCoords.top + cursorCoords.top + cursorCoords.height; - - return { x, y }; - } } diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 41517305e334..a85b4a1ea581 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; diff --git a/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts new file mode 100644 index 000000000000..44314a908605 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/test/codeActionKeybindingResolver.test.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { refactorCommandId, organizeImportsCommandId } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; +import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/codeActionWidget'; +import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; +import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; + +suite('CodeActionKeybindingResolver', () => { + const refactorKeybinding = createCodeActionKeybinding( + KeyCode.KEY_A, + refactorCommandId, + { kind: CodeActionKind.Refactor.value }); + + const refactorExtractKeybinding = createCodeActionKeybinding( + KeyCode.KEY_B, + refactorCommandId, + { kind: CodeActionKind.Refactor.append('extract').value }); + + const organizeImportsKeybinding = createCodeActionKeybinding( + KeyCode.KEY_C, + organizeImportsCommandId, + undefined); + + test('Should match refactor keybindings', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '' }), + undefined); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.QuickFix.value }), + undefined); + }); + + test('Should prefer most specific keybinding', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.value }), + refactorKeybinding.resolvedKeybinding); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.Refactor.append('extract').value }), + refactorExtractKeybinding.resolvedKeybinding); + }); + + test('Organize imports should still return a keybinding even though it does not have args', async function () { + const resolver = new CodeActionKeybindingResolver({ + getKeybindings: (): readonly ResolvedKeybindingItem[] => { + return [refactorKeybinding, refactorExtractKeybinding, organizeImportsKeybinding]; + }, + }).getResolver(); + + assert.equal( + resolver({ title: '', kind: CodeActionKind.SourceOrganizeImports.value }), + organizeImportsKeybinding.resolvedKeybinding); + }); +}); + +function createCodeActionKeybinding(keycode: KeyCode, command: string, commandArgs: any) { + return new ResolvedKeybindingItem( + new USLayoutResolvedKeybinding( + new ChordKeybinding([new SimpleKeybinding(false, true, false, false, keycode)]), + OperatingSystem.Linux), + command, + commandArgs, + undefined, + false); +} + diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/types.ts similarity index 93% rename from src/vs/editor/contrib/codeAction/codeActionTrigger.ts rename to src/vs/editor/contrib/codeAction/types.ts index 536fc575c6a7..ffbf8bbe94db 100644 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ b/src/vs/editor/contrib/codeAction/types.ts @@ -15,8 +15,8 @@ export class CodeActionKind { public static readonly QuickFix = new CodeActionKind('quickfix'); public static readonly Refactor = new CodeActionKind('refactor'); public static readonly Source = new CodeActionKind('source'); - public static readonly SourceOrganizeImports = new CodeActionKind('source.organizeImports'); - public static readonly SourceFixAll = new CodeActionKind('source.fixAll'); + public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); + public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); constructor( public readonly value: string @@ -33,6 +33,10 @@ export class CodeActionKind { public intersects(other: CodeActionKind): boolean { return this.contains(other) || other.contains(this); } + + public append(part: string): CodeActionKind { + return new CodeActionKind(this.value + CodeActionKind.sep + part); + } } export const enum CodeActionAutoApply { @@ -61,7 +65,6 @@ export function mayIncludeActionsOfKind(filter: CodeActionFilter, providedKind: return true; } - export function filtersAction(filter: CodeActionFilter, action: CodeAction): boolean { const actionKind = action.kind ? new CodeActionKind(action.kind) : undefined; diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 485175ffa7a1..4efa784f4bea 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -34,7 +34,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { QuickFixAction, QuickFixController } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 9bf1db3fe1ce..3dff068097ec 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -19,7 +19,7 @@ import { CodeAction } from 'vs/editor/common/modes'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 1ce660a06e85..5056c3feb619 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -27,6 +27,7 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; +import { CustomEditorModel } from 'vs/workbench/contrib/customEditor/browser/customEditorModel'; /** * Bi-directional map between webview handles and inputs. @@ -94,6 +95,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); + private readonly _models = new Map(); constructor( context: extHostProtocol.IExtHostContext, @@ -261,7 +263,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma canResolve: (webviewInput) => { return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput) => { + resolveWebview: async (webviewInput: CustomFileEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); this.hookupWebviewEventDelegate(handle, webviewInput); @@ -269,6 +271,18 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; + const model = new CustomEditorModel(); + webviewInput.setModel(model); + this._models.set(handle, model); + + webviewInput.onDispose(() => { + this._models.delete(handle); + }); + + model.onUndo(edit => { + this._proxy.$undoEdits(handle, [edit]); + }); + try { await this._proxy.$resolveWebviewEditor( webviewInput.getResource(), @@ -296,6 +310,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: string): void { + const webview = this.getWebviewInput(handle); + if (!(webview instanceof CustomFileEditorInput)) { + throw new Error('Webview is not a webview editor'); + } + + const model = this._models.get(handle); + if (!model) { + throw new Error('Could not find model for webview editor'); + } + + model.makeEdit(editData); + } + private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { input.webview.onDidClickLink((uri: URI) => this.onDidClickLink(handle, uri)); input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 63da5a6a6b20..9bc433a17277 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -570,6 +570,8 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; + + $onEdit(handle: WebviewPanelHandle, editJson: string): void; } export interface WebviewPanelViewStateData { @@ -587,6 +589,7 @@ export interface ExtHostWebviewsShape { $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $undoEdits(handle: WebviewPanelHandle, edits: string[]): void; } export interface MainThreadUrlsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 50fdaec003e6..7da97013142c 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -14,7 +15,7 @@ import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable } from './extHostTypes'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -91,7 +92,7 @@ export class ExtHostWebview implements vscode.Webview { } } -export class ExtHostWebviewEditor implements vscode.WebviewPanel { +export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel { private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; @@ -107,11 +108,12 @@ export class ExtHostWebviewEditor implements vscode.WebviewPanel { _isDisposed: boolean = false; - readonly _onDisposeEmitter = new Emitter(); + readonly _onDisposeEmitter = this._register(new Emitter()); public readonly onDidDispose: Event = this._onDisposeEmitter.event; - readonly _onDidChangeViewStateEmitter = new Emitter(); + readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; + _capabilities: vscode.WebviewEditorCapabilities; constructor( handle: WebviewPanelHandle, @@ -122,6 +124,7 @@ export class ExtHostWebviewEditor implements vscode.WebviewPanel { editorOptions: vscode.WebviewPanelOptions, webview: ExtHostWebview ) { + super(); this._handle = handle; this._proxy = proxy; this._viewType = viewType; @@ -135,16 +138,12 @@ export class ExtHostWebviewEditor implements vscode.WebviewPanel { if (this._isDisposed) { return; } - this._isDisposed = true; this._onDisposeEmitter.fire(); - this._proxy.$disposeWebview(this._handle); - this._webview.dispose(); - this._onDisposeEmitter.dispose(); - this._onDidChangeViewStateEmitter.dispose(); + super.dispose(); } get webview() { @@ -236,6 +235,19 @@ export class ExtHostWebviewEditor implements vscode.WebviewPanel { }); } + _setCapabilities(capabilities: vscode.WebviewEditorCapabilities) { + this._capabilities = capabilities; + if (capabilities.editingCapability) { + this._register(capabilities.editingCapability.onEdit(edit => { + this._proxy.$onEdit(this._handle, JSON.stringify(edit)); + })); + } + } + + _undoEdits(edits: string[]): void { + this._capabilities.editingCapability?.undoEdits(edits); + } + private assertNotDisposed() { if (this._isDisposed) { throw new Error('Webview is disposed'); @@ -296,7 +308,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._serializers.set(viewType, { serializer, extension }); this._proxy.$registerSerializer(viewType); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._serializers.delete(viewType); this._proxy.$unregisterSerializer(viewType); }); @@ -315,7 +327,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._editorProviders.set(viewType, { extension, provider, }); this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}); - return new Disposable(() => { + return new VSCodeDisposable(() => { this._editorProviders.delete(viewType); this._proxy.$unregisterEditorProvider(viewType); }); @@ -373,16 +385,15 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { + async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise { const panel = this.getWebviewPanel(handle); if (panel) { panel.dispose(); this._webviewPanels.delete(handle); } - return Promise.resolve(undefined); } - $deserializeWebviewPanel( + async $deserializeWebviewPanel( webviewHandle: WebviewPanelHandle, viewType: string, title: string, @@ -392,18 +403,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { ): Promise { const entry = this._serializers.get(viewType); if (!entry) { - return Promise.reject(new Error(`No serializer found for '${viewType}'`)); + throw new Error(`No serializer found for '${viewType}'`); } const { serializer, extension } = entry; const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); - return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); - } - - private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { - return this._webviewPanels.get(handle); + await serializer.deserializeWebviewPanel(revivedPanel, state); } async $resolveWebviewEditor( @@ -418,11 +425,25 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { if (!entry) { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } + const { provider, extension } = entry; const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); - await Promise.resolve(provider.resolveWebviewEditor({ resource: URI.revive(resource) }, revivedPanel)); + const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(resource) }, revivedPanel); + revivedPanel._setCapabilities(capabilities); + } + + $undoEdits(handle: WebviewPanelHandle, edits: string[]): void { + const panel = this.getWebviewPanel(handle); + if (!panel) { + return; + } + panel._undoEdits(edits); + } + + private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { + return this._webviewPanels.get(handle); } } @@ -442,7 +463,7 @@ function getDefaultLocalResourceRoots( workspace: IExtHostWorkspace | undefined, ): URI[] { return [ - ...(workspace && workspace.getWorkspaceFolders() || []).map(x => x.uri), + ...(workspace?.getWorkspaceFolders() || []).map(x => x.uri), extension.extensionLocation, ]; } diff --git a/src/vs/workbench/contrib/codeActions/common/configuration.ts b/src/vs/workbench/contrib/codeActions/common/configuration.ts index 93651a6305f2..fc402b6852a4 100644 --- a/src/vs/workbench/contrib/codeActions/common/configuration.ts +++ b/src/vs/workbench/contrib/codeActions/common/configuration.ts @@ -9,7 +9,7 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import * as nls from 'vs/nls'; import { Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 24a42571e89f..d89fcb9b26f0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -15,12 +15,14 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { CustomEditorModel } from './customEditorModel'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; + private _model?: CustomEditorModel; constructor( resource: URI, @@ -105,4 +107,16 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return this.longTitle; } } + + public setModel(model: CustomEditorModel) { + if (this._model) { + throw new Error('Model is already set'); + } + this._model = model; + this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + } + + public isDirty(): boolean { + return this._model ? this._model.isDirty() : false; + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts new file mode 100644 index 000000000000..003630bf19df --- /dev/null +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorModel.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; + +type Edit = string; + +export class CustomEditorModel extends Disposable { + + private _currentEditIndex: number = 0; + private _savePoint: number = -1; + private _edits: Array = []; + + protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; + + protected readonly _onUndo: Emitter = this._register(new Emitter()); + readonly onUndo: Event = this._onUndo.event; + + public makeEdit(data: string): void { + this._edits.splice(this._currentEditIndex, this._edits.length - this._currentEditIndex, data); + this._currentEditIndex = this._edits.length - 1; + this.updateDirty(); + } + + public isDirty(): boolean { + return this._edits.length > 0 && this._savePoint !== this._edits.length; + } + + private updateDirty() { + this._onDidChangeDirty.fire(); + } + + public save() { + this._savePoint = this._edits.length; + this.updateDirty(); + } + + public undo() { + if (this._currentEditIndex >= 0) { + const undoneEdit = this._edits[this._currentEditIndex]; + --this._currentEditIndex; + this._onUndo.fire(undoneEdit); + } + this.updateDirty(); + } +} diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 4172b7688438..c085096f8227 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -37,7 +37,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { ITextModel } from 'vs/editor/common/model'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 08d1fb90be73..6617330fbbb2 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -146,7 +146,7 @@ export class ExtensionPoint implements IExtensionPoint { } } -const extensionKindSchema = { +const extensionKindSchema: IJSONSchema = { type: 'string', enum: [ 'ui', @@ -159,7 +159,7 @@ const extensionKindSchema = { }; const schemaId = 'vscode://schemas/vscode-extensions'; -export const schema = { +export const schema: IJSONSchema = { properties: { engines: { type: 'object', @@ -413,7 +413,7 @@ export class ExtensionsRegistryImpl { const result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); this._extensionPoints.set(desc.extensionPoint, result); - schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; + schema.properties!['contributes'].properties![desc.extensionPoint] = desc.jsonSchema; schemaRegistry.registerSchema(schemaId, schema); return result; diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 85b09406b2c5..15488490b47d 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -662,7 +662,7 @@ const themeSettingsConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(themeSettingsConfiguration); -function tokenGroupSettings(description: string) { +function tokenGroupSettings(description: string): IJSONSchema { return { description, default: '#FF0000',