Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -12,7 +12,7 @@ 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 * as assert from 'vs/base/common/assert';
// import * as assert from 'vs/base/common/assert';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import paths = require('vs/base/common/paths');
import diagnostics = require('vs/base/common/diagnostics');
@@ -31,9 +31,9 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { anonymize } from 'vs/platform/telemetry/common/telemetryUtils';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IRawTextSource } from 'vs/editor/common/model/textSource';
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
/**
* The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk.
@@ -87,10 +87,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
@IBackupFileService private backupFileService: IBackupFileService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IHashService private hashService: IHashService
) {
super(modelService, modeService);
assert.ok(resource.scheme === 'file', 'TextFileEditorModel can only handle file:// resources.');
// TODO@remote
// assert.ok(resource.scheme === 'file', 'TextFileEditorModel can only handle file:// resources.');
this.resource = resource;
this.toDispose = [];
@@ -99,6 +101,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.toDispose.push(this._onDidContentChange);
this.toDispose.push(this._onDidStateChange);
this.preferredEncoding = preferredEncoding;
this.inOrphanMode = false;
this.dirty = false;
this.versionId = 0;
this.lastSaveAttemptTime = 0;
@@ -119,16 +122,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.toDispose.push(this.fileService.onFileChanges(e => this.onFileChanges(e)));
this.toDispose.push(this.textFileService.onAutoSaveConfigurationChange(config => this.updateAutoSaveConfiguration(config)));
this.toDispose.push(this.textFileService.onFilesAssociationChange(e => this.onFilesAssociationChange()));
this.toDispose.push(this.onDidStateChange(e => {
if (e === StateChange.REVERTED) {
this.toDispose.push(this.onDidStateChange(e => this.onStateChange(e)));
}
// Cancel any content change event promises as they are no longer valid.
this.contentChangeEventScheduler.cancel();
private onStateChange(e: StateChange): void {
if (e === StateChange.REVERTED) {
// Refire state change reverted events as content change events
this._onDidContentChange.fire(StateChange.REVERTED);
}
}));
// Cancel any content change event promises as they are no longer valid.
this.contentChangeEventScheduler.cancel();
// Refire state change reverted events as content change events
this._onDidContentChange.fire(StateChange.REVERTED);
}
}
private onFileChanges(e: FileChangesEvent): void {
@@ -361,10 +366,31 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
private loadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise<TextFileEditorModel> {
diag('load() - resolved content', this.resource, new Date());
return this.doLoadWithContent(content, backup).then(model => {
// Telemetry
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: anonymize(this.resource.fsPath) });
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
if (this.isSettingsFile()) {
/* __GDPR__
"settingsRead" : {}
*/
this.telemetryService.publicLog('settingsRead'); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
} else {
/* __GDPR__
"fileGet" : {
"mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"path": { "classification": "CustomerContent", "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) });
}
return model;
});
}
private doLoadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise<TextFileEditorModel> {
diag('load() - resolved content', this.resource, new Date());
// Update our resolved disk stat model
const resolvedStat: IFileStat = {
@@ -449,10 +475,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.setDirty(false);
}
// See https://github.com/Microsoft/vscode/issues/30189
// This code has been extracted to a different method because it caused a memory leak
// where `value` was captured in the content change listener closure scope.
this._installChangeContentListener();
// Model Listeners
this.installModelListeners();
return this;
}, error => {
@@ -465,13 +489,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
return this.createTextEditorModelPromise;
}
private _installChangeContentListener(): void {
private installModelListeners(): void {
// See https://github.com/Microsoft/vscode/issues/30189
// This code has been extracted to a different method because it caused a memory leak
// where `value` was captured in the content change listener closure scope.
this.toDispose.push(this.textEditorModel.onDidChangeContent(() => {
this.onModelContentChanged();
}));
// Content Change
this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));
}
private doLoadBackup(backup: URI): TPromise<string> {
@@ -666,10 +691,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 => {
// the model was not dirty and no save participant changed the contents, so we do not have
// to write the contents to disk, as they are already on disk. we still want to trigger
// a change on the file though so that external file watchers can be notified
if (options.force && !this.dirty && options.reason === SaveReason.EXPLICIT && versionId === 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.
// 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();
}
@@ -696,8 +726,17 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Telemetry
if (this.isSettingsFile()) {
/* __GDPR__
"settingsWritten" : {}
*/
this.telemetryService.publicLog('settingsWritten'); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data
} else {
/* __GDPR__
"filePUT" : {
"mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.lastResolvedDiskStat.resource.fsPath) });
}
@@ -745,14 +784,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Check for workspace settings file
if (this.contextService.hasWorkspace()) {
return this.contextService.getWorkspace().roots.some(root => {
// {{SQL CARBON EDIT}}
return paths.isEqualOrParent(this.resource.fsPath, path.join(root.fsPath, '.sqlops'));
});
}
return false;
return this.contextService.getWorkspace().folders.some(folder => {
// {{SQL CARBON EDIT}}
return paths.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.sqlops'));
});
}
private doTouch(): TPromise<void> {

View File

@@ -17,7 +17,7 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IRevertOptions, IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult } from 'vs/workbench/common/editor';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService, IResolveContentOptions, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -85,8 +85,15 @@ export abstract class TextFileService implements ITextFileService {
const configuration = this.configurationService.getConfiguration<IFilesConfiguration>();
this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations;
this.onConfigurationChange(configuration);
this.onFilesConfigurationChange(configuration);
/* __GDPR__
"autoSave" : {
"${include}": [
"${IAutoSaveConfiguration}"
]
}
*/
this.telemetryService.publicLog('autoSave', this.getAutoSaveConfiguration());
this.registerListeners();
@@ -116,8 +123,12 @@ export abstract class TextFileService implements ITextFileService {
this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.reason)));
this.lifecycleService.onShutdown(this.dispose, this);
// Configuration changes
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(this.configurationService.getConfiguration<IFilesConfiguration>())));
// Files configuration changes
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('files')) {
this.onFilesConfigurationChange(this.configurationService.getConfiguration<IFilesConfiguration>());
}
}));
}
private beforeShutdown(reason: ShutdownReason): boolean | TPromise<boolean> {
@@ -180,7 +191,7 @@ export abstract class TextFileService implements ITextFileService {
let doBackup: boolean;
switch (reason) {
case ShutdownReason.CLOSE:
if (this.contextService.hasWorkspace() && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else if (windowCount > 1 || platform.isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
@@ -198,7 +209,7 @@ export abstract class TextFileService implements ITextFileService {
break;
case ShutdownReason.LOAD:
if (this.contextService.hasWorkspace() && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else {
doBackup = false; // do not backup because we are switching contexts
@@ -211,6 +222,13 @@ export abstract class TextFileService implements ITextFileService {
}
// Telemetry
/* __GDPR__
"hotExit:triggered" : {
"reason" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"windowCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"fileCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('hotExit:triggered', { reason, windowCount, fileCount: dirtyToBackup.length });
// Backup
@@ -302,7 +320,7 @@ export abstract class TextFileService implements ITextFileService {
return this.backupFileService.discardAllWorkspaceBackups();
}
protected onConfigurationChange(configuration: IFilesConfiguration): void {
protected onFilesConfigurationChange(configuration: IFilesConfiguration): void {
const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF);
const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF;
@@ -347,10 +365,8 @@ export abstract class TextFileService implements ITextFileService {
this._onFilesAssociationChange.fire();
}
// {{SQL CARBON EDIT}}
// Hot exit
//const hotExitMode = configuration && configuration.files ? configuration.files.hotExit : HotExitConfiguration.ON_EXIT;
const hotExitMode = HotExitConfiguration.OFF;
const hotExitMode = configuration && configuration.files ? configuration.files.hotExit : HotExitConfiguration.ON_EXIT;
if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
this.configuredHotExit = hotExitMode;
} else {
@@ -409,10 +425,14 @@ export abstract class TextFileService implements ITextFileService {
const filesToSave: URI[] = [];
const untitledToSave: URI[] = [];
toSave.forEach(s => {
if (s.scheme === Schemas.file) {
filesToSave.push(s);
} else if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === UNTITLED_SCHEMA) {
// TODO@remote
// if (s.scheme === Schemas.file) {
// filesToSave.push(s);
// } else
if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === UNTITLED_SCHEMA) {
untitledToSave.push(s);
} else {
filesToSave.push(s);
}
});
@@ -619,12 +639,14 @@ export abstract class TextFileService implements ITextFileService {
}
private suggestFileName(untitledResource: URI): string {
const root = this.historyService.getLastActiveWorkspaceRoot();
if (root) {
return URI.file(paths.join(root.fsPath, this.untitledEditorService.suggestFileName(untitledResource))).fsPath;
const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource);
const lastActiveFile = this.historyService.getLastActiveFile();
if (lastActiveFile) {
return URI.file(paths.join(paths.dirname(lastActiveFile.fsPath), untitledFileName)).fsPath;
}
return this.untitledEditorService.suggestFileName(untitledResource);
return untitledFileName;
}
public revert(resource: URI, options?: IRevertOptions): TPromise<boolean> {
@@ -714,4 +736,4 @@ export abstract class TextFileService implements ITextFileService {
// Clear all caches
this._models.clear();
}
}
}

View File

@@ -100,6 +100,13 @@ export interface IResult {
success?: boolean;
}
/* __GDPR__FRAGMENT__
"IAutoSaveConfiguration" : {
"autoSaveDelay" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"autoSaveFocusChange": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"autoSaveApplicationChange": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface IAutoSaveConfiguration {
autoSaveDelay: number;
autoSaveFocusChange: boolean;

View File

@@ -118,7 +118,7 @@ export class TextFileService extends AbstractTextFileService {
buttons.push(save, cancel, dontSave);
}
const opts: Electron.ShowMessageBoxOptions = {
const opts: Electron.MessageBoxOptions = {
title: product.nameLong,
message: message.join('\n'),
type: 'warning',
@@ -132,19 +132,17 @@ export class TextFileService extends AbstractTextFileService {
opts.defaultId = 2;
}
const choice = this.windowService.showMessageBox(opts);
const choice = this.windowService.showMessageBoxSync(opts);
return buttons[choice].result;
}
public promptForPath(defaultPath?: string): string {
return this.windowService.showSaveDialog(this.getSaveDialogOptions(defaultPath ? paths.normalize(defaultPath, true) : void 0));
return this.windowService.showSaveDialog(this.getSaveDialogOptions(defaultPath));
}
private getSaveDialogOptions(defaultPath?: string): Electron.SaveDialogOptions {
const options: Electron.SaveDialogOptions = {
defaultPath: defaultPath
};
const options: Electron.SaveDialogOptions = { defaultPath };
// Filters are only enabled on Windows where they work properly
if (!isWindows) {
@@ -154,7 +152,7 @@ export class TextFileService extends AbstractTextFileService {
interface IFilter { name: string; extensions: string[]; }
// Build the file filter by using our known languages
const ext: string = paths.extname(defaultPath);
const ext: string = defaultPath ? paths.extname(defaultPath) : void 0;
let matchingFilter: IFilter;
const filters: IFilter[] = this.modeService.getRegisteredLanguageNames().map(languageName => {
const extensions = this.modeService.getExtensions(languageName);

View File

@@ -163,7 +163,6 @@ suite('Files - TextFileEditorModelManager', () => {
let revertedCounter = 0;
let savedCounter = 0;
let encodingCounter = 0;
let orphanedCounter = 0;
let disposeCounter = 0;
let contentCounter = 0;
@@ -191,12 +190,6 @@ suite('Files - TextFileEditorModelManager', () => {
}
});
manager.onModelOrphanedChanged(e => {
if (e.resource.toString() === resource1.toString()) {
orphanedCounter++;
}
});
manager.onModelContentChanged(e => {
if (e.resource.toString() === resource1.toString()) {
contentCounter++;
@@ -232,7 +225,6 @@ suite('Files - TextFileEditorModelManager', () => {
// content change event if done async
TPromise.timeout(10).then(() => {
assert.equal(contentCounter, 2);
assert.equal(orphanedCounter, 1);
model1.dispose();
model2.dispose();

View File

@@ -19,7 +19,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace';
class ServiceAccessor {
constructor(
@@ -104,7 +104,7 @@ suite('Files - TextFileService', () => {
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.DONT_SAVE);
service.onConfigurationChange({ files: { hotExit: 'off' } });
service.onFilesConfigurationChange({ files: { hotExit: 'off' } });
model.load().done(() => {
model.textEditorModel.setValue('foo');
@@ -137,7 +137,7 @@ suite('Files - TextFileService', () => {
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.SAVE);
service.onConfigurationChange({ files: { hotExit: 'off' } });
service.onFilesConfigurationChange({ files: { hotExit: 'off' } });
model.load().done(() => {
model.textEditorModel.setValue('foo');
@@ -379,10 +379,10 @@ suite('Files - TextFileService', () => {
const service = accessor.textFileService;
// Set hot exit config
service.onConfigurationChange({ files: { hotExit: setting } });
service.onFilesConfigurationChange({ files: { hotExit: setting } });
// Set empty workspace if required
if (!workspace) {
accessor.contextService.setWorkspace(null);
accessor.contextService.setWorkspace(new Workspace('empty:1508317022751'));
}
// Set multiple windows if required
if (multipleWindows) {