diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index db8cce1aa8..75ee9c493c 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -16,7 +16,7 @@ if grep -qi Microsoft /proc/version; then if ! [ -z "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC, get linux path for WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh - $WSL_CODE $COMMIT $QUALITY "$WIN_CODE_CMD" "$APP_NAME" "$@" + "$WSL_CODE" $COMMIT $QUALITY "$WIN_CODE_CMD" "$APP_NAME" "$@" exit $? fi fi diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 10bd10799f..af464fa42b 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -16,6 +16,9 @@ export function registerContextMenuListener(): void { y: options ? options.y : undefined, positioningItem: options ? options.positioningItem : undefined, callback: () => { + // Workaround for https://github.com/Microsoft/vscode/issues/72447 + // It turns out that the menu gets GC'ed if not referenced anymore + // As such we drag it into this scope so that it is not being GC'ed if (menu) { event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index b8174a51fd..2e4491c599 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -753,6 +753,11 @@ export enum FileKind { export const MIN_MAX_MEMORY_SIZE_MB = 2048; export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; +/** + * A hint to disable etag checking for reading/writing. + */ +export const ETAG_DISABLED = ''; + export function etag(mtime: number, size: number): string; export function etag(mtime: number | undefined, size: number | undefined): string | undefined; export function etag(mtime: number | undefined, size: number | undefined): string | undefined { diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 27d844d0b4..b605d13714 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -6,6 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IAction } from 'vs/base/common/actions'; export const IProgressService = createDecorator('progressService'); @@ -42,6 +43,12 @@ export interface IProgressOptions { cancellable?: boolean; } +export interface IProgressNotificationOptions extends IProgressOptions { + location: ProgressLocation.Notification; + primaryActions?: IAction[]; + secondaryActions?: IAction[]; +} + export interface IProgressStep { message?: string; increment?: number; diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index 492c7867ed..227ce760de 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -3,9 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProgress, IProgressService2, IProgressStep, IProgressOptions } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressService2, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { Action } from 'vs/base/common/actions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { localize } from 'vs/nls'; + +class ManageExtensionAction extends Action { + constructor(id: ExtensionIdentifier, label: string, commandService: ICommandService) { + super(id.value, label, undefined, true, () => { + return commandService.executeCommand('_extensions.manage', id.value); + }); + } +} @extHostNamedCustomer(MainContext.MainThreadProgress) export class MainThreadProgress implements MainThreadProgressShape { @@ -16,7 +28,8 @@ export class MainThreadProgress implements MainThreadProgressShape { constructor( extHostContext: IExtHostContext, - @IProgressService2 progressService: IProgressService2 + @IProgressService2 progressService: IProgressService2, + @ICommandService private readonly _commandService: ICommandService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostProgress); this._progressService = progressService; @@ -27,9 +40,19 @@ export class MainThreadProgress implements MainThreadProgressShape { this._progress.clear(); } - $startProgress(handle: number, options: IProgressOptions): void { + $startProgress(handle: number, options: IProgressOptions, extension?: IExtensionDescription): void { const task = this._createTask(handle); + if (options.location === ProgressLocation.Notification && extension && !extension.isUnderDevelopment) { + const notificationOptions: IProgressNotificationOptions = { + ...options, + location: ProgressLocation.Notification, + secondaryActions: [new ManageExtensionAction(extension.identifier, localize('manageExtension', "Manage Extension"), this._commandService)] + }; + + options = notificationOptions; + } + this._progressService.withProgress(options, task, () => this._proxy.$acceptProgressCanceled(handle)); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ad60aa19c3..8e17c06e15 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -379,7 +379,7 @@ export interface MainThreadOutputServiceShape extends IDisposable { export interface MainThreadProgressShape extends IDisposable { - $startProgress(handle: number, options: IProgressOptions): void; + $startProgress(handle: number, options: IProgressOptions, extension?: IExtensionDescription): void; $progressReport(handle: number, message: IProgressStep): void; $progressEnd(handle: number): void; } diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index 54413567e3..5bb2571d6c 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -26,7 +26,7 @@ export class ExtHostProgress implements ExtHostProgressShape { const handle = this._handles++; const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }); + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 557442c890..4aadc5bd6f 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -34,6 +34,7 @@ import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -42,18 +43,21 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IProgressService2 progressService: IProgressService2 ) { super(); - this.registerViews(); + progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => { + this.registerViews(); - this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); - this.updateOpenEditorsVisibility(); + this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); + this.updateOpenEditorsVisibility(); - this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); - this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); + this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + }); } private registerViews(): void { diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index 861135636c..aef42b2a92 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -137,12 +137,12 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const triedToMakeWriteable = isReadonly && fileOperationError.options && (fileOperationError.options as IWriteTextFileOptions).overwriteReadonly; const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; - // Save Elevated (TODO@remote cannot write elevated https://github.com/Microsoft/vscode/issues/48659) + // Save Elevated (cannot write elevated https://github.com/Microsoft/vscode/issues/48659) if (resource.scheme === Schemas.file && (isPermissionDenied || triedToMakeWriteable)) { actions.primary!.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); } - // Overwrite (TODO@remote cannot overwrite readonly https://github.com/Microsoft/vscode/issues/48659) + // Overwrite (cannot overwrite readonly https://github.com/Microsoft/vscode/issues/48659) else if (resource.scheme === Schemas.file && isReadonly) { actions.primary!.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index cc8f4b18d2..11dce2e80b 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -46,6 +46,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -441,7 +442,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IInstantiationService private instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IWindowService private windowService: IWindowService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService ) { this.toDispose = []; @@ -472,6 +474,12 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (typesArray.indexOf(DataTransfers.FILES.toLowerCase()) === -1 && typesArray.indexOf(CodeDataTransfers.FILES.toLowerCase()) === -1) { return false; } + if (this.environmentService.configuration.remoteAuthority) { + const resources = extractResources(originalEvent, true); + if (resources.some(r => r.resource.authority !== this.environmentService.configuration.remoteAuthority)) { + return false; + } + } } // Other-Tree DND diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 6eb6434f63..3420195aeb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -229,7 +229,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride: platform.Platform = platform.platform): void { const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride === platform.Platform.Windows ? platform.OperatingSystem.Windows : (platformOverride === platform.Platform.Mac ? platform.OperatingSystem.Macintosh : platform.OperatingSystem.Linux)); - mergeDefaultShellPathAndArgs(shell, (key) => this._workspaceConfigurationService.inspect(key), isWorkspaceShellAllowed); + mergeDefaultShellPathAndArgs(shell, (key) => this._workspaceConfigurationService.inspect(key), isWorkspaceShellAllowed, platformOverride); } private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number { diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index ed5b25c23f..b3d5a78aa8 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -263,13 +263,13 @@ export class WorkspaceConfiguration extends Disposable { this._workspaceIdentifier = workspaceIdentifier; if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { if (this._workspaceIdentifier.configPath.scheme === Schemas.file) { - this.switch(); + this.switch(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); } else { this.waitAndSwitch(this._workspaceIdentifier); } } - await this._workspaceConfiguration.load(this._workspaceIdentifier); this._loaded = this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration; + await this._workspaceConfiguration.load(this._workspaceIdentifier); } reload(): Promise { @@ -300,21 +300,25 @@ export class WorkspaceConfiguration extends Disposable { private async waitAndSwitch(workspaceIdentifier: IWorkspaceIdentifier): Promise { await this._configurationFileService.whenProviderRegistered(workspaceIdentifier.configPath.scheme); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { - this.switch(); + const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); + await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); + this.switch(fileServiceBasedWorkspaceConfiguration); this._loaded = true; - this.onDidWorkspaceConfigurationChange(); + this.onDidWorkspaceConfigurationChange(false); } } - private switch(): void { + private switch(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void { this._workspaceConfiguration.dispose(); this._workspaceConfigurationChangeDisposable.dispose(); - this._workspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); - this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange())); + this._workspaceConfiguration = this._register(fileServiceBasedWorkspaceConfiguration); + this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true))); } - private async onDidWorkspaceConfigurationChange(): Promise { - await this.reload(); + private async onDidWorkspaceConfigurationChange(reload: boolean): Promise { + if (reload) { + await this.reload(); + } this.updateCache(); this._onDidUpdateConfiguration.fire(); } diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index ac741f5021..839705134b 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -337,7 +337,7 @@ export class FileService extends Disposable implements IFileService { // check for size is a weaker check because it can return a false negative if the file has changed // but to the same length. This is a compromise we take to avoid having to produce checksums of // the file content for comparison which would be much slower to compute. - if (options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.mtime < stat.mtime && options.etag !== etag(stat.size, options.mtime)) { + if (options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.mtime < stat.mtime && options.etag !== etag(stat.size, options.mtime)) { throw new FileOperationError(localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE, options); } @@ -375,7 +375,7 @@ export class FileService extends Disposable implements IFileService { // due to the likelyhood of hitting a NOT_MODIFIED_SINCE result. // otherwise, we let it run in parallel to the file reading for // optimal startup performance. - if (options && options.etag) { + if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED) { await statPromise; } @@ -493,7 +493,7 @@ export class FileService extends Disposable implements IFileService { } // Return early if file not modified since - if (options && options.etag && options.etag === stat.etag) { + if (options && options.etag === stat.etag) { throw new FileOperationError(localize('fileNotModifiedError', "File not modified since"), FileOperationResult.FILE_NOT_MODIFIED_SINCE, options); } diff --git a/src/vs/workbench/services/files/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts index 241f7e8f0a..c63d7c39f8 100644 --- a/src/vs/workbench/services/files/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -1491,7 +1491,11 @@ suite('Disk File Service', () => { setTimeout(() => mkdirSync(folder.fsPath), 50); }); - test.skip('watch - folder (non recursive) - delete folder', done => { + test('watch - folder (non recursive) - delete folder', done => { + if (isWindows) { + return done(); // not happy + } + const watchDir = URI.file(join(testDir, 'watch7')); mkdirSync(watchDir.fsPath); diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index 31d2fd53e1..04ecdeee97 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/progressService2'; import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress } from 'vs/platform/progress/common/progress'; +import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; @@ -54,7 +54,7 @@ export class ProgressService2 implements IProgressService2 { switch (location) { case ProgressLocation.Notification: - return this._withNotificationProgress(options, task, onDidCancel); + return this._withNotificationProgress({ ...options, location: ProgressLocation.Notification }, task, onDidCancel); case ProgressLocation.Window: return this._withWindowProgress(options, task); case ProgressLocation.Explorer: @@ -138,7 +138,7 @@ export class ProgressService2 implements IProgressService2 { } } - private _withNotificationProgress

, R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + private _withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { const toDispose: IDisposable[] = []; const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { @@ -146,7 +146,7 @@ export class ProgressService2 implements IProgressService2 { return undefined; // we need a message at least } - const actions: INotificationActions = { primary: [] }; + const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] }; if (options.cancellable) { const cancelAction = new class extends Action { constructor() { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c43bbc9e96..1d77cd9fc3 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -16,7 +16,7 @@ import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorMo import { EncodingMode } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, etag } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -55,26 +55,35 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil get onDidStateChange(): Event { return this._onDidStateChange.event; } private resource: URI; + private contentEncoding: string; // encoding as reported from disk private preferredEncoding: string; // encoding as chosen by the user - private dirty: boolean; + private versionId: number; private bufferSavedVersionId: number; - private lastResolvedDiskStat: IFileStatWithMetadata; private blockModelContentChange: boolean; + + private createTextEditorModelPromise: Promise | null; + + private lastResolvedDiskStat: IFileStatWithMetadata; + private autoSaveAfterMillies?: number; private autoSaveAfterMilliesEnabled: boolean; private autoSaveDisposable?: IDisposable; + + private saveSequentializer: SaveSequentializer; + private lastSaveAttemptTime: number; + private contentChangeEventScheduler: RunOnceScheduler; private orphanedChangeEventScheduler: RunOnceScheduler; - private saveSequentializer: SaveSequentializer; - private disposed: boolean; - private lastSaveAttemptTime: number; - private createTextEditorModelPromise: Promise | null; + + private dirty: boolean; private inConflictMode: boolean; private inOrphanMode: boolean; private inErrorMode: boolean; + private disposed: boolean; + constructor( resource: URI, preferredEncoding: string, @@ -271,8 +280,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil name: basename(this.resource), mtime: Date.now(), size: 0, - etag: etag(Date.now(), 0), - value: createTextBufferFactory(''), /* will be filled later from backup */ + etag: ETAG_DISABLED, // always allow to save content restored from a backup (see https://github.com/Microsoft/vscode/issues/72343) + value: createTextBufferFactory(''), // will be filled later from backup encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding, isReadonly: false }; @@ -754,10 +763,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.telemetryService.publicLog('filePUT', this.getTelemetryData(options.reason)); } }, error => { - if (!error) { - error = new Error('Unknown Save Error'); // TODO@remote we should never get null as error (https://github.com/Microsoft/vscode/issues/55051) - } - this.logService.error(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource); // Flag as error state in the model diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 1bbdc265f4..979e9868cb 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -510,7 +510,6 @@ export abstract class TextFileService extends Disposable implements ITextFileSer })); } - // Soft revert the dirty source files if any await this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true });