Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re #179224. Customize action for save error. #179727

Merged
merged 7 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Selection } from 'vs/editor/common/core/selection';
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorPaneSelectionChangeReason, EditorPaneSelectionCompareResult, EditorResourceAccessor, IEditorMemento, IEditorOpenContext, IEditorPaneSelection, IEditorPaneSelectionChangeEvent, createEditorOpenError } from 'vs/workbench/common/editor';
import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, EditorPaneSelectionChangeReason, EditorPaneSelectionCompareResult, EditorResourceAccessor, IEditorMemento, IEditorOpenContext, IEditorPaneSelection, IEditorPaneSelectionChangeEvent, createEditorOpenError, isEditorOpenError } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
import { INotebookEditorOptions, INotebookEditorPane, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
Expand All @@ -39,6 +39,8 @@ import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorD
import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';

const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState';

Expand Down Expand Up @@ -79,6 +81,7 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane {
@IFileService private readonly _fileService: IFileService,
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IEditorProgressService private readonly _editorProgressService: IEditorProgressService,
@INotebookService private readonly _notebookService: INotebookService,
) {
super(NotebookEditor.ID, telemetryService, themeService, storageService);
this._editorMemento = this.getEditorMemento<INotebookEditorViewState>(_editorGroupService, configurationService, NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY);
Expand Down Expand Up @@ -226,7 +229,26 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane {
}

if (model === null) {
throw new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType));
const knownProvider = this._notebookService.getViewTypeProvider(input.viewType);

if (knownProvider) {
throw createEditorOpenError(new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType)), [
toAction({
id: 'workbench.notebook.action.installMissing', label: localize('notebookOpenInstallMissingViewType', "Install extension for '{0}'", input.viewType), run: async () => {
const d = this._notebookService.onAddViewType(viewType => {
if (viewType === input.viewType) {
rebornix marked this conversation as resolved.
Show resolved Hide resolved
// serializer is registered, try to open again
this._editorService.openEditor({ resource: input.resource });
d.dispose();
}
});
await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run();
}
})
], { allowDialog: true });
} else {
throw new Error(localize('fail.noEditor', "Cannot open resource with notebook editor type '{0}', please check if you have the right extension installed and enabled.", input.viewType));
}
}

this._widgetDisposableStore.add(model.notebook.onDidChangeContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT })));
Expand Down Expand Up @@ -257,6 +279,10 @@ export class NotebookEditor extends EditorPane implements INotebookEditorPane {
this._handlePerfMark(perf, input);
} catch (e) {
console.warn(e);
rebornix marked this conversation as resolved.
Show resolved Hide resolved
if (isEditorOpenError(e)) {
throw e;
}

const error = createEditorOpenError(e instanceof Error ? e : new Error((e ? e.message : '')), [
toAction({
id: 'workbench.notebook.action.openInTextEditor', label: localize('notebookOpenInTextEditor', "Open in Text Editor"), run: async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { localize } from 'vs/nls';
import { toAction } from 'vs/base/common/actions';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { PixelRatio } from 'vs/base/browser/browser';
import { runWhenIdle } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import * as glob from 'vs/base/common/glob';
import { Iterable } from 'vs/base/common/iterator';
Expand All @@ -22,7 +26,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Memento } from 'vs/workbench/common/memento';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { INotebookEditorContribution, notebookPreloadExtensionPoint, notebookRendererExtensionPoint, notebooksExtensionPoint } from 'vs/workbench/contrib/notebook/browser/notebookExtensionPoint';
import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/common/notebookDiffEditorInput';
Expand All @@ -38,6 +42,7 @@ import { INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } fro
import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, EditorInputFactoryObject, IEditorResolverService, IEditorType, RegisteredEditorInfo, RegisteredEditorPriority, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService';
import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';

export class NotebookProviderInfoStore extends Disposable {

Expand Down Expand Up @@ -413,6 +418,9 @@ class ModelData implements IDisposable {
export class NotebookService extends Disposable implements INotebookService {

declare readonly _serviceBrand: undefined;
private static _storageNotebookViewTypeProvider = 'notebook.viewTypeProvider';
private readonly _memento: Memento;
private readonly _viewTypeCache: MementoObject;

private readonly _notebookProviders = new Map<string, SimpleNotebookProviderInfo>();
private _notebookProviderInfoStore: NotebookProviderInfoStore | undefined = undefined;
Expand Down Expand Up @@ -462,6 +470,7 @@ export class NotebookService extends Disposable implements INotebookService {
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService private readonly _storageService: IStorageService,
) {
super();

Expand Down Expand Up @@ -583,6 +592,9 @@ export class NotebookService extends Disposable implements INotebookService {
};
this._register(this._codeEditorService.onDecorationTypeRegistered(onDidAddDecorationType));
this._codeEditorService.listDecorationTypes().forEach(onDidAddDecorationType);

this._memento = new Memento(NotebookService._storageNotebookViewTypeProvider, this._storageService);
this._viewTypeCache = this._memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE);
}


Expand Down Expand Up @@ -652,13 +664,25 @@ export class NotebookService extends Disposable implements INotebookService {

registerNotebookSerializer(viewType: string, extensionData: NotebookExtensionDescription, serializer: INotebookSerializer): IDisposable {
this.notebookProviderInfoStore.get(viewType)?.update({ options: serializer.options });
this._viewTypeCache[viewType] = extensionData.id.value;
this._persistMementos();
return this._registerProviderData(viewType, new SimpleNotebookProviderInfo(viewType, serializer, extensionData));
}

async withNotebookDataProvider(viewType: string): Promise<SimpleNotebookProviderInfo> {
const selected = this.notebookProviderInfoStore.get(viewType);
if (!selected) {
throw new Error(`UNKNOWN notebook type '${viewType}'`);
const knownProvider = this.getViewTypeProvider(viewType);

const actions = knownProvider ? [
toAction({
id: 'workbench.notebook.action.installMissingViewType', label: localize('notebookOpenInstallMissingViewType', "Install extension for '{0}'", viewType), run: async () => {
await this._instantiationService.createInstance(InstallRecommendedExtensionAction, knownProvider).run();
}
})
] : [];

throw createErrorWithActions(`UNKNOWN notebook type '${viewType}'`, actions);
}
await this.canResolve(selected.id);
const result = this._notebookProviders.get(selected.id);
Expand All @@ -668,6 +692,20 @@ export class NotebookService extends Disposable implements INotebookService {
return result;
}


private _persistSoonHandle?: IDisposable;

private _persistMementos(): void {
this._persistSoonHandle?.dispose();
this._persistSoonHandle = runWhenIdle(() => {
rebornix marked this conversation as resolved.
Show resolved Hide resolved
this._memento.saveMemento();
}, 100);
}

getViewTypeProvider(viewType: string): string | undefined {
return this._viewTypeCache[viewType];
}

getRendererInfo(rendererId: string): INotebookRendererInfo | undefined {
return this._notebookRenderersInfoStore.get(rendererId);
}
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/notebook/common/notebookService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface INotebookService {
readonly _serviceBrand: undefined;
canResolve(viewType: string): Promise<boolean>;

readonly onAddViewType: Event<string>;
readonly onWillRemoveViewType: Event<string>;
readonly onDidChangeOutputRenderers: Event<void>;
readonly onWillAddNotebookDocument: Event<NotebookTextModel>;
Expand All @@ -61,6 +62,7 @@ export interface INotebookService {

getOutputMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[];

getViewTypeProvider(viewType: string): string | undefined;
getRendererInfo(id: string): INotebookRendererInfo | undefined;
getRenderers(): INotebookRendererInfo[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { hash } from 'vs/base/common/hash';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isErrorWithActions, toErrorMessage } from 'vs/base/common/errorMessage';
import { IAction, toAction } from 'vs/base/common/actions';
import { isWindows } from 'vs/base/common/platform';
import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService';
Expand Down Expand Up @@ -1069,6 +1069,20 @@ export class StoredFileWorkingCopy<M extends IStoredFileWorkingCopyModel> extend
const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED;
const canSaveElevated = this.elevatedFileService.isSupported(this.resource);

if (isErrorWithActions(error)) {
primaryActions.push(...error.actions.map(action => {
return toAction({
id: action.id,
label: action.label,
run: () => {
const result = action.run();
if (result instanceof Promise) {
bpasero marked this conversation as resolved.
Show resolved Hide resolved
}
}
});
}));
}

// Save Elevated
if (canSaveElevated && (isPermissionDenied || triedToUnlock)) {
primaryActions.push(toAction({
Expand Down