mirror of
https://github.com/microsoft/vscode.git
synced 2026-10-01 20:03:17 +01:00
Merge branch 'master' into misolori/icon-font-extensions
This commit is contained in:
@@ -23,8 +23,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
PreviewManager.viewType,
|
||||
{
|
||||
async resolveWebviewEditor({ resource }, editor: vscode.WebviewPanel): Promise<vscode.WebviewEditorCapabilities> {
|
||||
previewManager.resolve(resource, editor);
|
||||
return {};
|
||||
return previewManager.resolve(resource, editor);
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -337,6 +337,9 @@ export default class BufferSyncSupport extends Disposable {
|
||||
private readonly _onDelete = this._register(new vscode.EventEmitter<vscode.Uri>());
|
||||
public readonly onDelete = this._onDelete.event;
|
||||
|
||||
private readonly _onWillChange = this._register(new vscode.EventEmitter<vscode.Uri>());
|
||||
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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@ class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvide
|
||||
public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise<vscode.CodeLens> {
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<ToCancelOnResourceChanged>();
|
||||
}
|
||||
|
||||
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<ServerResponse.Response<Proto.Response>> {
|
||||
const execution = this.executeImpl(command, args, {
|
||||
isAsync: false,
|
||||
token,
|
||||
expectsResult: true,
|
||||
lowPriority: config?.lowPriority
|
||||
});
|
||||
let execution: Promise<ServerResponse.Response<Proto.Response>>;
|
||||
|
||||
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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -54,6 +54,11 @@ export class Lazy<T> {
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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<void> {
|
||||
public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition): Promise<void> {
|
||||
this._codeActionWidget.getValue().show(actions, at);
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(e: { x: number, y: number, actions: CodeActionSet }): void {
|
||||
this._codeActionWidget.getValue().show(e.actions, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<any>;
|
||||
@@ -43,70 +44,93 @@ export class CodeActionWidget extends Disposable {
|
||||
private _visible: boolean = false;
|
||||
private readonly _showingActions = this._register(new MutableDisposable<CodeActionSet>());
|
||||
|
||||
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<void> {
|
||||
public async show(codeActions: CodeActionSet, at: IAnchor | IPosition): Promise<void> {
|
||||
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<CodeActionAction, ResolvedKeybinding> {
|
||||
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<readonly ResolveCodeActionKeybinding[]>(() =>
|
||||
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<CodeActionAction, ResolvedKeybinding>();
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+6
-3
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<string, IDisposable>();
|
||||
private readonly _editorProviders = new Map<string, IDisposable>();
|
||||
private readonly _models = new Map<string, CustomEditorModel>();
|
||||
|
||||
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));
|
||||
|
||||
@@ -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<void>;
|
||||
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
|
||||
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
|
||||
$undoEdits(handle: WebviewPanelHandle, edits: string[]): void;
|
||||
}
|
||||
|
||||
export interface MainThreadUrlsShape extends IDisposable {
|
||||
|
||||
@@ -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<void>();
|
||||
readonly _onDisposeEmitter = this._register(new Emitter<void>());
|
||||
public readonly onDidDispose: Event<void> = this._onDisposeEmitter.event;
|
||||
|
||||
readonly _onDidChangeViewStateEmitter = new Emitter<vscode.WebviewPanelOnDidChangeViewStateEvent>();
|
||||
readonly _onDidChangeViewStateEmitter = this._register(new Emitter<vscode.WebviewPanelOnDidChangeViewStateEvent>());
|
||||
public readonly onDidChangeViewState: Event<vscode.WebviewPanelOnDidChangeViewStateEvent> = 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<void> {
|
||||
async $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise<void> {
|
||||
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<void> {
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Edit> = [];
|
||||
|
||||
protected readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeDirty: Event<void> = this._onDidChangeDirty.event;
|
||||
|
||||
protected readonly _onUndo: Emitter<Edit> = this._register(new Emitter<Edit>());
|
||||
readonly onUndo: Event<Edit> = 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();
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -146,7 +146,7 @@ export class ExtensionPoint<T> implements IExtensionPoint<T> {
|
||||
}
|
||||
}
|
||||
|
||||
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<T>(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;
|
||||
|
||||
@@ -662,7 +662,7 @@ const themeSettingsConfiguration: IConfigurationNode = {
|
||||
};
|
||||
configurationRegistry.registerConfiguration(themeSettingsConfiguration);
|
||||
|
||||
function tokenGroupSettings(description: string) {
|
||||
function tokenGroupSettings(description: string): IJSONSchema {
|
||||
return {
|
||||
description,
|
||||
default: '#FF0000',
|
||||
|
||||
Reference in New Issue
Block a user