提交 8c25061e 编写于 作者: M Matt Bierner

Handle when a file is renamed to an extension that is supported by a custom editor

Fixes #81832

Attempts to support the following cases:

- An file is renamed to now have an extension that is supported by a custom editor

- A custom editor's file is renamed to no longer match the custom editor

In these cases, we try to prompt the user to see what to do
上级 c28ecbea
......@@ -11,7 +11,6 @@ import { isEqual } from 'vs/base/common/resources';
import { assertIsDefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
......@@ -138,7 +137,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
return undefined;
}
return this.tryMoveWebview(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true });
return this.move(groupId, target)?.editor;
}
public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
......@@ -161,35 +160,31 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
}
move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined {
if (!this._moveHandler) {
return {
editor: this.customEditorService.createInput(newResource, this.viewType, group)
};
}
this._moveHandler(newResource);
const newEditor = this.tryMoveWebview(group, newResource);
if (!newEditor) {
return;
}
return { editor: newEditor };
}
private tryMoveWebview(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined {
const editorInfo = this.customEditorService.getCustomEditor(this.viewType);
if (editorInfo?.matches(uri)) {
const newInput = this.instantiationService.createInstance(CustomEditorInput,
uri,
if (editorInfo?.matches(newResource)) {
// We can keep using the same custom editor provider
if (!this._moveHandler) {
return {
editor: this.customEditorService.createInput(newResource, this.viewType, group),
};
}
this._moveHandler(newResource);
const newEditor = this.instantiationService.createInstance(CustomEditorInput,
newResource,
this.viewType,
this.id,
new Lazy(() => undefined!)); // this webview is replaced in the transfer call
this.transfer(newInput);
newInput.updateGroup(groupId);
return newInput;
this.transfer(newEditor);
newEditor.updateGroup(group);
return { editor: newEditor };
} else {
// const possible = this.customEditorService.getContributedCustomEditors(newResource);
return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) };
}
return undefined;
}
public undo(): void {
assertIsDefined(this._modelRef);
this.undoRedoService.undo(this.resource);
......
......@@ -14,14 +14,14 @@ import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { FileOperation, IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { EditorInput, EditorOptions, GroupIdentifier, IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, IEditorInput, IEditorPane, GroupIdentifier } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint';
import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
......@@ -30,6 +30,7 @@ import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
import { CustomEditorInput } from './customEditorInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
export const defaultEditorId = 'default';
......@@ -119,6 +120,12 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
this._register(this._editorInfoStore.onChange(() => this.updateContexts()));
this._register(this.editorService.onDidActiveEditorChange(() => this.updateContexts()));
this._register(fileService.onDidRunOperation(e => {
if (e.isOperation(FileOperation.MOVE)) {
this.handleMovedFileInOpenedFileEditors(e.resource, e.target.resource);
}
}));
this.updateContexts();
}
......@@ -140,15 +147,33 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
.map(association => this._editorInfoStore.get(association.viewType))));
}
public getAllCustomEditors(resource: URI): CustomEditorInfoCollection {
return new CustomEditorInfoCollection([
...this.getUserConfiguredCustomEditors(resource).allEditors,
...this.getContributedCustomEditors(resource).allEditors,
]);
}
public async promptOpenWith(
resource: URI,
options?: ITextEditorOptions,
group?: IEditorGroup,
): Promise<IEditorPane | undefined> {
const pick = await this.showOpenWithPrompt(resource, group);
if (!pick) {
return;
}
return this.openWith(resource, pick, options, group);
}
private showOpenWithPrompt(
resource: URI,
group?: IEditorGroup,
): Promise<string | undefined> {
const customEditors = new CustomEditorInfoCollection([
defaultEditorInfo,
...this.getUserConfiguredCustomEditors(resource).allEditors,
...this.getContributedCustomEditors(resource).allEditors,
...this.getAllCustomEditors(resource).allEditors,
]);
let currentlyOpenedEditorType: undefined | string;
......@@ -177,7 +202,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
picker.items = items;
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource));
const pick = await new Promise<string | undefined>(resolve => {
return new Promise<string | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined);
picker.dispose();
......@@ -209,12 +234,6 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
});
picker.show();
});
if (!pick) {
return;
}
return this.openWith(resource, pick, options, group);
}
public openWith(
......@@ -295,14 +314,65 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
return;
}
const possibleEditors = [
...this.getContributedCustomEditors(resource).allEditors,
...this.getUserConfiguredCustomEditors(resource).allEditors,
];
const possibleEditors = this.getAllCustomEditors(resource).allEditors;
this._customEditorContextKey.set(possibleEditors.map(x => x.id).join(','));
this._focusedCustomEditorIsEditable.set(activeEditorPane?.input instanceof CustomEditorInput);
this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0);
}
private async handleMovedFileInOpenedFileEditors(_oldResource: URI, newResource: URI): Promise<void> {
// See if the new resource can be opened in a custom editor
const possibleEditors = this.getAllCustomEditors(newResource).allEditors;
if (!possibleEditors.length) {
return;
}
// If so, check all editors to see if there are any file editors open for the new resource
const editorsToReplace = new Map<GroupIdentifier, IEditorInput[]>();
for (const group of this.editorGroupService.groups) {
for (const editor of group.editors) {
if (editor instanceof FileEditorInput
&& !(editor instanceof CustomEditorInput)
&& isEqual(editor.resource, newResource)
) {
let entry = editorsToReplace.get(group.id);
if (!entry) {
entry = [];
editorsToReplace.set(group.id, entry);
}
entry.push(editor);
}
}
}
if (!editorsToReplace.size) {
return;
}
// If there is, show a single prompt for all editors to see if the user wants to re-open them
//
// TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with
// ones that would prompt when they first become visible
await new Promise(resolve => setTimeout(resolve, 50));
const pickedViewType = await this.showOpenWithPrompt(newResource);
if (!pickedViewType) {
return;
}
for (const [group, entries] of editorsToReplace) {
this.editorService.replaceEditors(entries.map(editor => {
const replacement = this.createInput(newResource, pickedViewType, group);
return {
editor,
replacement,
options: {
preserveFocus: true,
}
};
}), group);
}
}
}
export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations';
......
......@@ -26,6 +26,7 @@ export interface ICustomEditorService {
readonly models: ICustomEditorModelManager;
getCustomEditor(viewType: string): CustomEditorInfo | undefined;
getAllCustomEditors(resource: URI): CustomEditorInfoCollection;
getContributedCustomEditors(resource: URI): CustomEditorInfoCollection;
getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册