editors - reload error placeholder when file is coming back

This commit is contained in:
Benjamin Pasero 2022-04-08 09:13:06 +02:00
parent cffd590665
commit 897ab9567c
No known key found for this signature in database
GPG key ID: E6380CC4C8219E65
10 changed files with 67 additions and 68 deletions

View file

@ -84,12 +84,8 @@ export function toErrorMessage(error: any = null, verbose: boolean = false): str
}
export interface IErrorOptions {
actions?: readonly IAction[];
}
export interface IErrorWithActions {
actions?: readonly IAction[];
export interface IErrorWithActions extends Error {
actions: IAction[];
}
export function isErrorWithActions(obj: unknown): obj is IErrorWithActions {
@ -98,12 +94,15 @@ export function isErrorWithActions(obj: unknown): obj is IErrorWithActions {
return candidate instanceof Error && Array.isArray(candidate.actions);
}
export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions {
const result = new Error(message);
if (options.actions) {
(result as IErrorWithActions).actions = options.actions;
export function createErrorWithActions(messageOrError: string | Error, actions: IAction[]): IErrorWithActions {
let error: IErrorWithActions;
if (typeof messageOrError === 'string') {
error = new Error(messageOrError) as IErrorWithActions;
} else {
error = messageOrError as IErrorWithActions;
}
return result;
error.actions = actions;
return error;
}

View file

@ -26,7 +26,7 @@ import { Link } from 'vs/platform/opener/browser/link';
import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';
import { editorErrorForeground, editorInfoForeground, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry';
import { Codicon } from 'vs/base/common/codicons';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { FileChangeType, FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { isErrorWithActions, toErrorMessage } from 'vs/base/common/errorMessage';
export interface IEditorPlaceholderContents {
@ -96,7 +96,7 @@ export abstract class EditorPlaceholder extends EditorPane {
// Delegate to implementation for contents
const disposables = new DisposableStore();
const { icon, label, actions } = await this.getContents(input, options);
const { icon, label, actions } = await this.getContents(input, options, disposables);
// Icon
const iconContainer = container.appendChild($('.editor-placeholder-icon-container'));
@ -126,7 +126,7 @@ export abstract class EditorPlaceholder extends EditorPane {
return disposables;
}
protected abstract getContents(input: EditorInput, options: IEditorOptions | undefined): Promise<IEditorPlaceholderContents>;
protected abstract getContents(input: EditorInput, options: IEditorOptions | undefined, disposables: DisposableStore): Promise<IEditorPlaceholderContents>;
override clearInput(): void {
if (this.container) {
@ -213,28 +213,32 @@ export class ErrorPlaceholderEditor extends EditorPlaceholder {
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IInstantiationService instantiationService: IInstantiationService
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private readonly fileService: IFileService
) {
super(ErrorPlaceholderEditor.ID, telemetryService, themeService, storageService, instantiationService);
}
protected async getContents(input: EditorInput, options: IErrorEditorPlaceholderOptions): Promise<IEditorPlaceholderContents> {
protected async getContents(input: EditorInput, options: IErrorEditorPlaceholderOptions, disposables: DisposableStore): Promise<IEditorPlaceholderContents> {
const resource = input.resource;
const group = this.group;
const error = options.error;
const isFileNotFound = (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
// Error Label
let label: string;
if ((<FileOperationError>options.error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
if (isFileNotFound) {
label = localize('unavailableResourceErrorEditorText', "The editor could not be opened because the file was not found.");
} else if (options.error) {
label = localize('unknownErrorEditorTextWithError', "The editor could not be opened due to an unexpected error: {0}", toErrorMessage(options.error));
} else if (error) {
label = localize('unknownErrorEditorTextWithError', "The editor could not be opened due to an unexpected error: {0}", toErrorMessage(error));
} else {
label = localize('unknownErrorEditorTextWithoutError', "The editor could not be opened due to an unexpected error.");
}
// Actions
let actions: IEditorPlaceholderContentsAction[] | undefined = undefined;
if (isErrorWithActions(options.error)) {
actions = options.error.actions?.map(action => {
if (isErrorWithActions(error) && error.actions.length > 0) {
actions = error.actions.map(action => {
return {
label: action.label,
run: () => action.run()
@ -244,11 +248,20 @@ export class ErrorPlaceholderEditor extends EditorPlaceholder {
actions = [
{
label: localize('retry', "Try Again"),
run: () => group.openEditor(input, { ...this.options, source: EditorOpenSource.USER /* explicit user gesture */ })
run: () => group.openEditor(input, { ...options, source: EditorOpenSource.USER /* explicit user gesture */ })
}
];
}
// Auto-reload when file is added
if (group && isFileNotFound && resource && this.fileService.hasProvider(resource)) {
disposables.add(this.fileService.onDidFilesChange(e => {
if (e.contains(resource, FileChangeType.ADDED, FileChangeType.UPDATED)) {
group.openEditor(input, options);
}
}));
}
return { icon: '$(error)', label, actions: actions ?? [] };
}
}

View file

@ -15,7 +15,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { IViewsService } from 'vs/workbench/common/views';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@ -158,7 +157,7 @@ export class DebugTaskRunner {
const errorMessage = typeof taskId === 'string'
? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId)
: nls.localize('DebugTaskNotFound', "Could not find the specified task.");
return Promise.reject(createErrorWithActions(errorMessage));
return Promise.reject(new Error(errorMessage));
}
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340

View file

@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import * as objects from 'vs/base/common/objects';
import { Action } from 'vs/base/common/actions';
import { toAction } from 'vs/base/common/actions';
import * as errors from 'vs/base/common/errors';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
@ -747,11 +747,7 @@ export class RawDebugSession implements IDisposable {
const uri = URI.parse(url);
// Use a suffixed id if uri invokes a command, so default 'Open launch.json' command is suppressed on dialog
const actionId = uri.scheme === Schemas.command ? 'debug.moreInfo.command' : 'debug.moreInfo';
return createErrorWithActions(userMessage, {
actions: [new Action(actionId, label, undefined, true, async () => {
this.openerService.open(uri, { allowCommands: true });
})]
});
return createErrorWithActions(userMessage, [toAction({ id: actionId, label, run: () => this.openerService.open(uri, { allowCommands: true }) })]);
}
if (showErrors && error && error.format && error.showUser) {
this.notificationService.error(userMessage);

View file

@ -719,11 +719,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
]
});
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [
new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
]);
this.notificationService.error(error);
return;

View file

@ -972,11 +972,9 @@ export class ExtensionsListView extends ViewPane {
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
]
});
const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [
new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
]);
this.notificationService.error(error);
return;

View file

@ -26,7 +26,7 @@ import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IErrorWithActions } from 'vs/base/common/errorMessage';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';
@ -187,8 +187,7 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
// Offer to create a file from the error if we have a file not found and the name is valid
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && await this.pathService.hasValidBasename(input.preferredResource)) {
const fileNotFoundError: FileOperationError & IErrorWithActions = new FileOperationError(localize('fileNotFoundError', "File not found"), FileOperationResult.FILE_NOT_FOUND);
fileNotFoundError.actions = [
const fileNotFoundError = createErrorWithActions(new FileOperationError(localize('fileNotFoundError', "File not found"), FileOperationResult.FILE_NOT_FOUND), [
toAction({
id: 'workbench.files.action.createMissingFile', label: localize('createFile', "Create File"), run: async () => {
await this.textFileService.create([{ resource: input.preferredResource }]);
@ -201,7 +200,7 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
});
}
})
];
]);
throw fileNotFoundError;
}

View file

@ -62,24 +62,22 @@ export class NativeTextFileEditor extends TextFileEditor {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) {
const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.textResourceConfigurationService.getValue<number>(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB);
throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", this.productService.nameShort), {
actions: [
toAction({
id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => {
return this.nativeHostService.relaunch({
addArgs: [
`--max-memory=${memoryLimit}`
]
});
}
}),
toAction({
id: 'workbench.window.action.configureMemoryLimit', label: localize('configureMemoryLimit', 'Configure Memory Limit'), run: () => {
return this.preferencesService.openUserSettings({ query: 'files.maxMemoryForLargeFilesMB' });
}
}),
]
});
throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", this.productService.nameShort), [
toAction({
id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => {
return this.nativeHostService.relaunch({
addArgs: [
`--max-memory=${memoryLimit}`
]
});
}
}),
toAction({
id: 'workbench.window.action.configureMemoryLimit', label: localize('configureMemoryLimit', 'Configure Memory Limit'), run: () => {
return this.preferencesService.openUserSettings({ query: 'files.maxMemoryForLargeFilesMB' });
}
}),
]);
}
// Fallback to handling in super type

View file

@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, toAction } from 'vs/base/common/actions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IErrorWithActions } from 'vs/base/common/errorMessage';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { extname, isEqual } from 'vs/base/common/resources';
@ -289,8 +289,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
}
}
} catch (e) {
const error: Error & IErrorWithActions = e instanceof Error ? e : new Error(e.message);
error.actions = [
const error = createErrorWithActions(e instanceof Error ? e : new Error(e.message), [
toAction({
id: 'workbench.notebook.action.openInTextEditor', label: localize('notebookOpenInTextEditor', "Open in Text Editor"), run: async () => {
const activeEditorPane = this._editorService.activeEditorPane;
@ -317,7 +316,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
return;
}
})
];
]);
throw error;
}

View file

@ -127,7 +127,7 @@ suite('Notifications', () => {
assert.strictEqual(called, 1);
// Error with Action
let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!;
let item7 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', [new Action('id', 'label')]) })!;
assert.strictEqual(item7.actions!.primary!.length, 1);
// Filter