Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -5,21 +5,20 @@
'use strict';
import * as path from 'vs/base/common/paths';
import nls = require('vs/nls');
import Event, { Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors';
import { guessMimeTypes } from 'vs/base/common/mime';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import paths = require('vs/base/common/paths');
import diagnostics = require('vs/base/common/diagnostics');
import types = require('vs/base/common/types');
import * as diagnostics from 'vs/base/common/diagnostics';
import * as types from 'vs/base/common/types';
import { IMode } from 'vs/editor/common/modes';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
@@ -63,8 +62,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
private disposed: boolean;
private lastSaveAttemptTime: number;
private createTextEditorModelPromise: TPromise<TextFileEditorModel>;
private _onDidContentChange: Emitter<StateChange>;
private _onDidStateChange: Emitter<StateChange>;
private readonly _onDidContentChange: Emitter<StateChange>;
private readonly _onDidStateChange: Emitter<StateChange>;
private inConflictMode: boolean;
private inOrphanMode: boolean;
@@ -129,38 +128,51 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
private onFileChanges(e: FileChangesEvent): void {
let fileEventImpactsModel = false;
let newInOrphanModeGuess: boolean;
// Track ADD and DELETES for updates of this model to orphan-mode
const modelFileDeleted = e.contains(this.resource, FileChangeType.DELETED);
const modelFileAdded = e.contains(this.resource, FileChangeType.ADDED);
if (modelFileDeleted || modelFileAdded) {
const newInOrphanModeGuess = modelFileDeleted && !modelFileAdded;
if (this.inOrphanMode !== newInOrphanModeGuess) {
let checkOrphanedPromise: TPromise<boolean>;
if (newInOrphanModeGuess) {
// We have received reports of users seeing delete events even though the file still
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
// Since we do not want to mark the model as orphaned, we have to check if the
// file is really gone and not just a faulty file event.
checkOrphanedPromise = TPromise.timeout(100).then(() => {
if (this.disposed) {
return true;
}
return this.fileService.existsFile(this.resource).then(exists => !exists);
});
} else {
checkOrphanedPromise = TPromise.as(false);
}
checkOrphanedPromise.done(newInOrphanModeValidated => {
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
this.setOrphaned(newInOrphanModeValidated);
}
});
// If we are currently orphaned, we check if the model file was added back
if (this.inOrphanMode) {
const modelFileAdded = e.contains(this.resource, FileChangeType.ADDED);
if (modelFileAdded) {
newInOrphanModeGuess = false;
fileEventImpactsModel = true;
}
}
// Otherwise we check if the model file was deleted
else {
const modelFileDeleted = e.contains(this.resource, FileChangeType.DELETED);
if (modelFileDeleted) {
newInOrphanModeGuess = true;
fileEventImpactsModel = true;
}
}
if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) {
let checkOrphanedPromise: TPromise<boolean>;
if (newInOrphanModeGuess) {
// We have received reports of users seeing delete events even though the file still
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
// Since we do not want to mark the model as orphaned, we have to check if the
// file is really gone and not just a faulty file event.
checkOrphanedPromise = TPromise.timeout(100).then(() => {
if (this.disposed) {
return true;
}
return this.fileService.existsFile(this.resource).then(exists => !exists);
});
} else {
checkOrphanedPromise = TPromise.as(false);
}
checkOrphanedPromise.done(newInOrphanModeValidated => {
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
this.setOrphaned(newInOrphanModeValidated);
}
});
}
}
private setOrphaned(orphaned: boolean): void {
@@ -244,7 +256,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (soft) {
loadPromise = TPromise.as(this);
} else {
loadPromise = this.load(true /* force */);
loadPromise = this.load({ forceReadFromDisk: true });
}
return loadPromise.then(() => {
@@ -260,28 +272,28 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
});
}
public load(force?: boolean /* bypass any caches and really go to disk */): TPromise<TextFileEditorModel> {
public load(options?: ILoadOptions): TPromise<TextFileEditorModel> {
diag('load() - enter', this.resource, new Date());
// It is very important to not reload the model when the model is dirty. We only want to reload the model from the disk
// if no save is pending to avoid data loss. This might cause a save conflict in case the file has been modified on the disk
// meanwhile, but this is a very low risk.
if (this.dirty) {
diag('load() - exit - without loading because model is dirty', this.resource, new Date());
if (this.dirty || this.saveSequentializer.hasPendingSave()) {
diag('load() - exit - without loading because model is dirty or being saved', this.resource, new Date());
return TPromise.as(this);
}
// Only for new models we support to load from backup
if (!this.textEditorModel && !this.createTextEditorModelPromise) {
return this.loadWithBackup(force);
return this.loadWithBackup(options);
}
// Otherwise load from file resource
return this.loadFromFile(force);
return this.loadFromFile(options);
}
private loadWithBackup(force: boolean): TPromise<TextFileEditorModel> {
private loadWithBackup(options?: ILoadOptions): TPromise<TextFileEditorModel> {
return this.backupFileService.loadBackupResource(this.resource).then(backup => {
// Make sure meanwhile someone else did not suceed or start loading
@@ -293,34 +305,36 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (!!backup) {
const content: IRawTextContent = {
resource: this.resource,
name: paths.basename(this.resource.fsPath),
name: path.basename(this.resource.fsPath),
mtime: Date.now(),
etag: void 0,
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.getEncoding(this.resource, this.preferredEncoding)
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding)
};
return this.loadWithContent(content, backup);
}
// Otherwise load from file
return this.loadFromFile(force);
return this.loadFromFile(options);
});
}
private loadFromFile(force: boolean): TPromise<TextFileEditorModel> {
private loadFromFile(options?: ILoadOptions): TPromise<TextFileEditorModel> {
const forceReadFromDisk = options && options.forceReadFromDisk;
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || (options && options.allowBinary);
// Decide on etag
let etag: string;
if (force) {
etag = void 0; // bypass cache if force loading is true
if (forceReadFromDisk) {
etag = void 0; // reset ETag if we enforce to read from disk
} else if (this.lastResolvedDiskStat) {
etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching
}
// Resolve Content
return this.textFileService
.resolveTextContent(this.resource, { acceptTextOnly: true, etag, encoding: this.preferredEncoding })
.resolveTextContent(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding })
.then(content => this.handleLoadSuccess(content), error => this.handleLoadError(error));
}
@@ -370,10 +384,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
"fileGet" : {
"mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"path": { "classification": "CustomerContent", "purpose": "FeatureInsight" }
"path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: this.hashService.createSHA1(this.resource.fsPath) });
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: path.extname(this.resource.fsPath), path: this.hashService.createSHA1(this.resource.fsPath) });
}
return model;
@@ -678,16 +692,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// mark the save participant as current pending save operation
return this.saveSequentializer.setPending(versionId, saveParticipantPromise.then(newVersionId => {
// Under certain conditions a save to the model will not cause the contents to the flushed on
// disk because we can assume that the contents are already on disk. Instead, we just touch the
// file to still trigger external file watchers for example.
// Under certain conditions we do a short-cut of flushing contents to disk when we can assume that
// the file has not changed and as such was not dirty before.
// The conditions are all of:
// - a forced, explicit save (Ctrl+S)
// - the model is not dirty (otherwise we know there are changed which needs to go to the file)
// - the model is not in orphan mode (because in that case we know the file does not exist on disk)
// - the model version did not change due to save participants running
if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) {
return this.doTouch();
return this.doTouch(newVersionId);
}
// update versionId with its new value (if pre-save changes happened)
@@ -725,7 +738,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.lastResolvedDiskStat.resource.fsPath) });
this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: path.extname(this.lastResolvedDiskStat.resource.fsPath) });
}
// Update dirty state unless model has changed meanwhile
@@ -774,16 +787,20 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Check for workspace settings file
return this.contextService.getWorkspace().folders.some(folder => {
// {{SQL CARBON EDIT}}
return paths.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.sqlops'));
return path.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.sqlops'));
});
}
private doTouch(): TPromise<void> {
return this.fileService.touchFile(this.resource).then(stat => {
private doTouch(versionId: number): TPromise<void> {
return this.saveSequentializer.setPending(versionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.createSnapshot(), {
mtime: this.lastResolvedDiskStat.mtime,
encoding: this.getEncoding(),
etag: this.lastResolvedDiskStat.etag
}).then(stat => {
// Updated resolved stat with updated stat since touching it might have changed mtime
this.updateLastResolvedDiskStat(stat);
}, () => void 0 /* gracefully ignore errors if just touching */);
}, () => void 0 /* gracefully ignore errors if just touching */));
}
private setDirty(dirty: boolean): () => void {
@@ -829,8 +846,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced.
// This is essential a If-Modified-Since check on the client ot prevent race conditions from loading
// and saving. If a save comes in late after a revert was called, the mtime could be out of sync.
// This prevents race conditions from loading and saving. If a save comes in late after a revert
// was called, the mtime could be out of sync.
else if (this.lastResolvedDiskStat.mtime <= newVersionOnDiskStat.mtime) {
this.lastResolvedDiskStat = newVersionOnDiskStat;
}
@@ -923,7 +940,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.updatePreferredEncoding(encoding);
// Load
this.load(true /* force because encoding has changed */).done(null, onUnexpectedError);
this.load({
forceReadFromDisk: true // because encoding has changed
}).done(null, onUnexpectedError);
}
}
@@ -1083,10 +1102,10 @@ export class SaveSequentializer {
class DefaultSaveErrorHandler implements ISaveErrorHandler {
constructor( @INotificationService private notificationService: INotificationService) { }
constructor(@INotificationService private notificationService: INotificationService) { }
public onSaveError(error: any, model: TextFileEditorModel): void {
this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(model.getResource().fsPath), toErrorMessage(error, false)));
this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", path.basename(model.getResource().fsPath), toErrorMessage(error, false)));
}
}

View File

@@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions, ILoadOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResourceMap } from 'vs/base/common/map';
@@ -17,14 +17,14 @@ import { ResourceMap } from 'vs/base/common/map';
export class TextFileEditorModelManager implements ITextFileEditorModelManager {
private toUnbind: IDisposable[];
private _onModelDisposed: Emitter<URI>;
private _onModelContentChanged: Emitter<TextFileModelChangeEvent>;
private _onModelDirty: Emitter<TextFileModelChangeEvent>;
private _onModelSaveError: Emitter<TextFileModelChangeEvent>;
private _onModelSaved: Emitter<TextFileModelChangeEvent>;
private _onModelReverted: Emitter<TextFileModelChangeEvent>;
private _onModelEncodingChanged: Emitter<TextFileModelChangeEvent>;
private _onModelOrphanedChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelDisposed: Emitter<URI>;
private readonly _onModelContentChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelDirty: Emitter<TextFileModelChangeEvent>;
private readonly _onModelSaveError: Emitter<TextFileModelChangeEvent>;
private readonly _onModelSaved: Emitter<TextFileModelChangeEvent>;
private readonly _onModelReverted: Emitter<TextFileModelChangeEvent>;
private readonly _onModelEncodingChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelOrphanedChanged: Emitter<TextFileModelChangeEvent>;
private _onModelsDirtyEvent: Event<TextFileModelChangeEvent[]>;
private _onModelsSaveError: Event<TextFileModelChangeEvent[]>;
@@ -167,22 +167,27 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
return pendingLoad;
}
let modelLoadOptions: ILoadOptions;
if (options && options.allowBinary) {
modelLoadOptions = { allowBinary: true };
}
let modelPromise: TPromise<ITextFileEditorModel>;
// Model exists
let model = this.get(resource);
if (model) {
if (!options || !options.reload) {
modelPromise = TPromise.as(model);
if (options && options.reload) {
modelPromise = model.load(modelLoadOptions);
} else {
modelPromise = model.load();
modelPromise = TPromise.as(model);
}
}
// Model does not exist
else {
model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : void 0);
modelPromise = model.load();
modelPromise = model.load(modelLoadOptions);
// Install state change listener
this.mapResourceToStateChangeListener.set(resource, model.onDidStateChange(state => {

View File

@@ -7,11 +7,11 @@
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import errors = require('vs/base/common/errors');
import objects = require('vs/base/common/objects');
import Event, { Emitter } from 'vs/base/common/event';
import platform = require('vs/base/common/platform');
import * as paths from 'vs/base/common/paths';
import * as errors from 'vs/base/common/errors';
import * as objects from 'vs/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import * as platform from 'vs/base/common/platform';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles';
@@ -51,10 +51,10 @@ export abstract class TextFileService implements ITextFileService {
private toUnbind: IDisposable[];
private _models: TextFileEditorModelManager;
private _onFilesAssociationChange: Emitter<void>;
private readonly _onFilesAssociationChange: Emitter<void>;
private currentFilesAssociationConfig: { [key: string]: string; };
private _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration>;
private readonly _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration>;
private configuredAutoSaveDelay: number;
private configuredAutoSaveOnFocusChange: boolean;
private configuredAutoSaveOnWindowChange: boolean;
@@ -484,8 +484,8 @@ export abstract class TextFileService implements ITextFileService {
private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): TPromise<ITextFileOperationResult> {
const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : void 0 /* Save All */)
.filter(model => {
if (model.hasState(ModelState.CONFLICT) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) {
return false; // if model is in save conflict, do not save unless save reason is explicit or not provided at all
if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) {
return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all
}
return true;

View File

@@ -6,7 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult } from 'vs/workbench/common/editor';
import { IBaseStat, IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
@@ -140,8 +140,22 @@ export interface IRawTextContent extends IBaseStat {
}
export interface IModelLoadOrCreateOptions {
/**
* The encoding to use when resolving the model text content.
*/
encoding?: string;
/**
* Wether to reload the model if it already exists.
*/
reload?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModelManager {
@@ -179,6 +193,19 @@ export interface ISaveOptions {
writeElevated?: boolean;
}
export interface ILoadOptions {
/**
* Go to disk bypassing any cache of the model if any.
*/
forceReadFromDisk?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
onDidContentChange: Event<StateChange>;
@@ -196,7 +223,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
save(options?: ISaveOptions): TPromise<void>;
load(): TPromise<ITextFileEditorModel>;
load(options?: ILoadOptions): TPromise<ITextFileEditorModel>;
revert(soft?: boolean): TPromise<void>;