mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
This commit is contained in:
@@ -22,16 +22,17 @@ 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 { EncodingMode } from 'vs/workbench/common/editor';
|
||||
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
|
||||
import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IFileService, IFileStat, FileOperationError, FileOperationResult, IContent, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IFileService, IFileStat, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IRawTextSource } from 'vs/editor/common/model/textSource';
|
||||
import { ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
|
||||
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -72,7 +73,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
constructor(
|
||||
resource: URI,
|
||||
preferredEncoding: string,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@@ -85,10 +86,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
@IHashService private hashService: IHashService
|
||||
) {
|
||||
super(modelService, modeService);
|
||||
|
||||
// TODO@remote
|
||||
// assert.ok(resource.scheme === 'file', 'TextFileEditorModel can only handle file:// resources.');
|
||||
|
||||
this.resource = resource;
|
||||
this.toDispose = [];
|
||||
this._onDidContentChange = new Emitter<StateChange>();
|
||||
@@ -145,8 +142,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// 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 (TODO@Ben revisit when we
|
||||
// have a more stable file watcher in place for this scenario).
|
||||
// file is really gone and not just a faulty file event.
|
||||
checkOrphanedPromise = TPromise.timeout(100).then(() => {
|
||||
if (this.disposed) {
|
||||
return true;
|
||||
@@ -193,7 +189,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLineText = this.getFirstLineText(this.textEditorModel.getValue());
|
||||
const firstLineText = this.getFirstLineText(this.textEditorModel);
|
||||
const mode = this.getOrCreateMode(this.modeService, modeId, firstLineText);
|
||||
|
||||
this.modelService.setMode(this.textEditorModel, mode);
|
||||
@@ -295,12 +291,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
// If we have a backup, continue loading with it
|
||||
if (!!backup) {
|
||||
const content: IContent = {
|
||||
const content: IRawTextContent = {
|
||||
resource: this.resource,
|
||||
name: paths.basename(this.resource.fsPath),
|
||||
mtime: Date.now(),
|
||||
etag: void 0,
|
||||
value: '', /* will be filled later from backup */
|
||||
value: createTextBufferFactory(''), /* will be filled later from backup */
|
||||
encoding: this.fileService.getEncoding(this.resource, this.preferredEncoding)
|
||||
};
|
||||
|
||||
@@ -360,7 +356,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return TPromise.wrapError<TextFileEditorModel>(error);
|
||||
}
|
||||
|
||||
private loadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise<TextFileEditorModel> {
|
||||
private loadWithContent(content: IRawTextContent, backup?: URI): TPromise<TextFileEditorModel> {
|
||||
return this.doLoadWithContent(content, backup).then(model => {
|
||||
|
||||
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
|
||||
@@ -384,7 +380,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
});
|
||||
}
|
||||
|
||||
private doLoadWithContent(content: IRawTextContent | IContent, backup?: URI): TPromise<TextFileEditorModel> {
|
||||
private doLoadWithContent(content: IRawTextContent, backup?: URI): TPromise<TextFileEditorModel> {
|
||||
diag('load() - resolved content', this.resource, new Date());
|
||||
|
||||
// Update our resolved disk stat model
|
||||
@@ -394,8 +390,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
mtime: content.mtime,
|
||||
etag: content.etag,
|
||||
isDirectory: false,
|
||||
hasChildren: false,
|
||||
children: void 0,
|
||||
isSymbolicLink: false,
|
||||
children: void 0
|
||||
};
|
||||
this.updateLastResolvedDiskStat(resolvedStat);
|
||||
|
||||
@@ -426,7 +422,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this.doCreateTextModel(content.resource, content.value, backup);
|
||||
}
|
||||
|
||||
private doUpdateTextModel(value: string | IRawTextSource): TPromise<TextFileEditorModel> {
|
||||
private doUpdateTextModel(value: ITextBufferFactory): TPromise<TextFileEditorModel> {
|
||||
diag('load() - updated text editor model', this.resource, new Date());
|
||||
|
||||
// Ensure we are not tracking a stale state
|
||||
@@ -446,11 +442,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return TPromise.as<TextFileEditorModel>(this);
|
||||
}
|
||||
|
||||
private doCreateTextModel(resource: URI, value: string | IRawTextSource, backup: URI): TPromise<TextFileEditorModel> {
|
||||
private doCreateTextModel(resource: URI, value: ITextBufferFactory, backup: URI): TPromise<TextFileEditorModel> {
|
||||
diag('load() - created text editor model', this.resource, new Date());
|
||||
|
||||
this.createTextEditorModelPromise = this.doLoadBackup(backup).then(backupContent => {
|
||||
const hasBackupContent = (typeof backupContent === 'string');
|
||||
const hasBackupContent = !!backupContent;
|
||||
|
||||
return this.createTextEditorModel(hasBackupContent ? backupContent : value, resource).then(() => {
|
||||
this.createTextEditorModelPromise = null;
|
||||
@@ -494,14 +490,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
this.toDispose.push(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));
|
||||
}
|
||||
|
||||
private doLoadBackup(backup: URI): TPromise<string> {
|
||||
private doLoadBackup(backup: URI): TPromise<ITextBufferFactory> {
|
||||
if (!backup) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return this.textFileService.resolveTextContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(backup => {
|
||||
return this.backupFileService.parseBackupContent(backup.value);
|
||||
}, error => null /* ignore errors */);
|
||||
return this.backupFileService.resolveBackupContent(backup).then(backupContent => backupContent, error => null /* ignore errors */);
|
||||
}
|
||||
|
||||
protected getOrCreateMode(modeService: IModeService, preferredModeIds: string, firstLineText?: string): TPromise<IMode> {
|
||||
@@ -708,12 +702,13 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Save to Disk
|
||||
// mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering)
|
||||
diag(`doSave(${versionId}) - before updateContent()`, this.resource, new Date());
|
||||
return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.getValue(), {
|
||||
return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.createSnapshot(), {
|
||||
overwriteReadonly: options.overwriteReadonly,
|
||||
overwriteEncoding: options.overwriteEncoding,
|
||||
mtime: this.lastResolvedDiskStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: this.lastResolvedDiskStat.etag
|
||||
etag: this.lastResolvedDiskStat.etag,
|
||||
writeElevated: options.writeElevated
|
||||
}).then(stat => {
|
||||
diag(`doSave(${versionId}) - after updateContent()`, this.resource, new Date());
|
||||
|
||||
@@ -920,7 +915,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Decode: Load with encoding
|
||||
else {
|
||||
if (this.isDirty()) {
|
||||
this.messageService.show(Severity.Info, nls.localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding."));
|
||||
this.notificationService.info(nls.localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding."));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1088,10 +1083,10 @@ export class SaveSequentializer {
|
||||
|
||||
class DefaultSaveErrorHandler implements ISaveErrorHandler {
|
||||
|
||||
constructor( @IMessageService private messageService: IMessageService) { }
|
||||
constructor( @INotificationService private notificationService: INotificationService) { }
|
||||
|
||||
public onSaveError(error: any, model: TextFileEditorModel): void {
|
||||
this.messageService.show(Severity.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}", paths.basename(model.getResource().fsPath), toErrorMessage(error, false)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import platform = require('vs/base/common/platform');
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
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 { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext } 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, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -22,14 +22,18 @@ import { IFileService, IResolveContentOptions, IFilesConfiguration, FileOperatio
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IRevertOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export interface IBackupResult {
|
||||
didBackup: boolean;
|
||||
@@ -55,6 +59,8 @@ export abstract class TextFileService implements ITextFileService {
|
||||
private configuredAutoSaveOnFocusChange: boolean;
|
||||
private configuredAutoSaveOnWindowChange: boolean;
|
||||
|
||||
private autoSaveContext: IContextKey<string>;
|
||||
|
||||
private configuredHotExit: string;
|
||||
|
||||
constructor(
|
||||
@@ -64,11 +70,13 @@ export abstract class TextFileService implements ITextFileService {
|
||||
protected fileService: IFileService,
|
||||
private untitledEditorService: IUntitledEditorService,
|
||||
private instantiationService: IInstantiationService,
|
||||
private messageService: IMessageService,
|
||||
private notificationService: INotificationService,
|
||||
protected environmentService: IEnvironmentService,
|
||||
private backupFileService: IBackupFileService,
|
||||
private windowsService: IWindowsService,
|
||||
private historyService: IHistoryService
|
||||
private historyService: IHistoryService,
|
||||
contextKeyService: IContextKeyService,
|
||||
private modelService: IModelService
|
||||
) {
|
||||
this.toUnbind = [];
|
||||
|
||||
@@ -79,6 +87,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
this.toUnbind.push(this._onFilesAssociationChange);
|
||||
|
||||
this._models = this.instantiationService.createInstance(TextFileEditorModelManager);
|
||||
this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService);
|
||||
|
||||
const configuration = this.configurationService.getValue<IFilesConfiguration>();
|
||||
this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations;
|
||||
@@ -94,9 +103,9 @@ export abstract class TextFileService implements ITextFileService {
|
||||
|
||||
abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise<IRawTextContent>;
|
||||
|
||||
abstract promptForPath(defaultPath: string): string;
|
||||
abstract promptForPath(defaultPath: string): TPromise<string>;
|
||||
|
||||
abstract confirmSave(resources?: URI[]): ConfirmResult;
|
||||
abstract confirmSave(resources?: URI[]): TPromise<ConfirmResult>;
|
||||
|
||||
public get onAutoSaveConfigurationChange(): Event<IAutoSaveConfiguration> {
|
||||
return this._onAutoSaveConfigurationChange.event;
|
||||
@@ -152,7 +161,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
return this.confirmBeforeShutdown();
|
||||
}, errors => {
|
||||
const firstError = errors[0];
|
||||
this.messageService.show(Severity.Error, nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", firstError.message));
|
||||
this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", firstError.message));
|
||||
|
||||
return true; // veto, the backups failed
|
||||
});
|
||||
@@ -222,9 +231,9 @@ export abstract class TextFileService implements ITextFileService {
|
||||
const filesToBackup: ITextFileEditorModel[] = [];
|
||||
const untitledToBackup: URI[] = [];
|
||||
dirtyToBackup.forEach(s => {
|
||||
if (s.scheme === Schemas.file) {
|
||||
if (this.fileService.canHandleResource(s)) {
|
||||
filesToBackup.push(textFileEditorModelManager.get(s));
|
||||
} else if (s.scheme === UNTITLED_SCHEMA) {
|
||||
} else if (s.scheme === Schemas.untitled) {
|
||||
untitledToBackup.push(s);
|
||||
}
|
||||
});
|
||||
@@ -235,7 +244,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
private doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): TPromise<void> {
|
||||
|
||||
// Handle file resources first
|
||||
return TPromise.join(dirtyFileModels.map(model => this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId()))).then(results => {
|
||||
return TPromise.join(dirtyFileModels.map(model => this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()))).then(results => {
|
||||
|
||||
// Handle untitled resources
|
||||
const untitledModelPromises = untitledResources
|
||||
@@ -244,7 +253,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
|
||||
return TPromise.join(untitledModelPromises).then(untitledModels => {
|
||||
const untitledBackupPromises = untitledModels.map(model => {
|
||||
return this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId());
|
||||
return this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId());
|
||||
});
|
||||
|
||||
return TPromise.join(untitledBackupPromises).then(() => void 0);
|
||||
@@ -253,35 +262,36 @@ export abstract class TextFileService implements ITextFileService {
|
||||
}
|
||||
|
||||
private confirmBeforeShutdown(): boolean | TPromise<boolean> {
|
||||
const confirm = this.confirmSave();
|
||||
return this.confirmSave().then(confirm => {
|
||||
|
||||
// Save
|
||||
if (confirm === ConfirmResult.SAVE) {
|
||||
return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => {
|
||||
if (result.results.some(r => !r.success)) {
|
||||
return true; // veto if some saves failed
|
||||
}
|
||||
// Save
|
||||
if (confirm === ConfirmResult.SAVE) {
|
||||
return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => {
|
||||
if (result.results.some(r => !r.success)) {
|
||||
return true; // veto if some saves failed
|
||||
}
|
||||
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
});
|
||||
}
|
||||
|
||||
// Don't Save
|
||||
else if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
|
||||
// Make sure to revert untitled so that they do not restore
|
||||
// see https://github.com/Microsoft/vscode/issues/29572
|
||||
this.untitledEditorService.revertAll();
|
||||
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Don't Save
|
||||
else if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
// Cancel
|
||||
else if (confirm === ConfirmResult.CANCEL) {
|
||||
return true; // veto
|
||||
}
|
||||
|
||||
// Make sure to revert untitled so that they do not restore
|
||||
// see https://github.com/Microsoft/vscode/issues/29572
|
||||
this.untitledEditorService.revertAll();
|
||||
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
}
|
||||
|
||||
// Cancel
|
||||
else if (confirm === ConfirmResult.CANCEL) {
|
||||
return true; // veto
|
||||
}
|
||||
|
||||
return void 0;
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
private noVeto(options: { cleanUpBackups: boolean }): boolean | TPromise<boolean> {
|
||||
@@ -304,6 +314,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF);
|
||||
|
||||
const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF;
|
||||
this.autoSaveContext.set(autoSaveMode);
|
||||
switch (autoSaveMode) {
|
||||
case AutoSaveConfiguration.AFTER_DELAY:
|
||||
this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay;
|
||||
@@ -379,7 +390,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
public save(resource: URI, options?: ISaveOptions): TPromise<boolean> {
|
||||
|
||||
// Run a forced save if we detect the file is not dirty so that save participants can still run
|
||||
if (options && options.force && resource.scheme === Schemas.file && !this.isDirty(resource)) {
|
||||
if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) {
|
||||
const model = this._models.get(resource);
|
||||
if (model) {
|
||||
model.save({ force: true, reason: SaveReason.EXPLICIT }).then(() => !model.isDirty());
|
||||
@@ -405,11 +416,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
const filesToSave: URI[] = [];
|
||||
const untitledToSave: URI[] = [];
|
||||
toSave.forEach(s => {
|
||||
// TODO@remote
|
||||
// if (s.scheme === Schemas.file) {
|
||||
// filesToSave.push(s);
|
||||
// } else
|
||||
if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === UNTITLED_SCHEMA) {
|
||||
if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === Schemas.untitled) {
|
||||
untitledToSave.push(s);
|
||||
} else {
|
||||
filesToSave.push(s);
|
||||
@@ -422,7 +429,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
private doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): TPromise<ITextFileOperationResult> {
|
||||
|
||||
// Handle files first that can just be saved
|
||||
return this.doSaveAllFiles(fileResources, options).then(result => {
|
||||
return this.doSaveAllFiles(fileResources, options).then(async result => {
|
||||
|
||||
// Preflight for untitled to handle cancellation from the dialog
|
||||
const targetsForUntitled: URI[] = [];
|
||||
@@ -438,7 +445,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
|
||||
// Otherwise ask user
|
||||
else {
|
||||
targetPath = this.promptForPath(this.suggestFileName(untitled));
|
||||
targetPath = await this.promptForPath(this.suggestFileName(untitled));
|
||||
if (!targetPath) {
|
||||
return TPromise.as({
|
||||
results: [...fileResources, ...untitledResources].map(r => {
|
||||
@@ -525,41 +532,49 @@ export abstract class TextFileService implements ITextFileService {
|
||||
return this.getFileModels(arg1).filter(model => model.isDirty());
|
||||
}
|
||||
|
||||
public saveAs(resource: URI, target?: URI): TPromise<URI> {
|
||||
public saveAs(resource: URI, target?: URI, options?: ISaveOptions): TPromise<URI> {
|
||||
|
||||
// Get to target resource
|
||||
if (!target) {
|
||||
let targetPromise: TPromise<URI>;
|
||||
if (target) {
|
||||
targetPromise = TPromise.wrap(target);
|
||||
} else {
|
||||
let dialogPath = resource.fsPath;
|
||||
if (resource.scheme === UNTITLED_SCHEMA) {
|
||||
if (resource.scheme === Schemas.untitled) {
|
||||
dialogPath = this.suggestFileName(resource);
|
||||
}
|
||||
|
||||
const pathRaw = this.promptForPath(dialogPath);
|
||||
if (pathRaw) {
|
||||
target = URI.file(pathRaw);
|
||||
targetPromise = this.promptForPath(dialogPath).then(pathRaw => {
|
||||
if (pathRaw) {
|
||||
return URI.file(pathRaw);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
return targetPromise.then(target => {
|
||||
if (!target) {
|
||||
return TPromise.as(null); // user canceled
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
return TPromise.as(null); // user canceled
|
||||
}
|
||||
// Just save if target is same as models own resource
|
||||
if (resource.toString() === target.toString()) {
|
||||
return this.save(resource, options).then(() => resource);
|
||||
}
|
||||
|
||||
// Just save if target is same as models own resource
|
||||
if (resource.toString() === target.toString()) {
|
||||
return this.save(resource).then(() => resource);
|
||||
}
|
||||
|
||||
// Do it
|
||||
return this.doSaveAs(resource, target);
|
||||
// Do it
|
||||
return this.doSaveAs(resource, target, options);
|
||||
});
|
||||
}
|
||||
|
||||
private doSaveAs(resource: URI, target?: URI): TPromise<URI> {
|
||||
private doSaveAs(resource: URI, target?: URI, options?: ISaveOptions): TPromise<URI> {
|
||||
|
||||
// Retrieve text model from provided resource if any
|
||||
let modelPromise: TPromise<ITextFileEditorModel | UntitledEditorModel> = TPromise.as(null);
|
||||
if (resource.scheme === Schemas.file) {
|
||||
if (this.fileService.canHandleResource(resource)) {
|
||||
modelPromise = TPromise.as(this._models.get(resource));
|
||||
} else if (resource.scheme === UNTITLED_SCHEMA && this.untitledEditorService.exists(resource)) {
|
||||
} else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) {
|
||||
modelPromise = this.untitledEditorService.loadOrCreate({ resource });
|
||||
}
|
||||
|
||||
@@ -567,7 +582,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
|
||||
// We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before)
|
||||
if (model) {
|
||||
return this.doSaveTextFileAs(model, resource, target);
|
||||
return this.doSaveTextFileAs(model, resource, target, options);
|
||||
}
|
||||
|
||||
// Otherwise we can only copy
|
||||
@@ -583,7 +598,7 @@ export abstract class TextFileService implements ITextFileService {
|
||||
});
|
||||
}
|
||||
|
||||
private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise<void> {
|
||||
private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): TPromise<void> {
|
||||
let targetModelResolver: TPromise<ITextFileEditorModel>;
|
||||
|
||||
// Prefer an existing model if it is already loaded for the given target resource
|
||||
@@ -603,15 +618,15 @@ export abstract class TextFileService implements ITextFileService {
|
||||
|
||||
// take over encoding and model value from source model
|
||||
targetModel.updatePreferredEncoding(sourceModel.getEncoding());
|
||||
targetModel.textEditorModel.setValue(sourceModel.getValue());
|
||||
this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()));
|
||||
|
||||
// save model
|
||||
return targetModel.save();
|
||||
return targetModel.save(options);
|
||||
}, error => {
|
||||
|
||||
// binary model: delete the file and run the operation again
|
||||
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
|
||||
return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target));
|
||||
return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target, options));
|
||||
}
|
||||
|
||||
return TPromise.wrapError(error);
|
||||
|
||||
@@ -9,10 +9,12 @@ import URI from 'vs/base/common/uri';
|
||||
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 } from 'vs/platform/files/common/files';
|
||||
import { IBaseStat, IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
import { IRawTextSource } from 'vs/editor/common/model/textSource';
|
||||
import { ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { IRevertOptions } from 'vs/platform/editor/common/editor';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
/**
|
||||
* The save error handler can be installed on the text text file editor model to install code that executes when save errors occur.
|
||||
@@ -89,6 +91,7 @@ export class TextFileModelChangeEvent {
|
||||
}
|
||||
|
||||
export const TEXT_FILE_SERVICE_ID = 'textFileService';
|
||||
export const AutoSaveContext = new RawContextKey<string>('config.files.autoSave', undefined);
|
||||
|
||||
export interface ITextFileOperationResult {
|
||||
results: IResult[];
|
||||
@@ -128,12 +131,7 @@ export interface IRawTextContent extends IBaseStat {
|
||||
/**
|
||||
* The line grouped content of a text file.
|
||||
*/
|
||||
value: IRawTextSource;
|
||||
|
||||
/**
|
||||
* The line grouped logical hash of a text file.
|
||||
*/
|
||||
valueLogicalHash: string;
|
||||
value: ITextBufferFactory;
|
||||
|
||||
/**
|
||||
* The encoding of the content if known.
|
||||
@@ -178,6 +176,7 @@ export interface ISaveOptions {
|
||||
overwriteReadonly?: boolean;
|
||||
overwriteEncoding?: boolean;
|
||||
skipSaveParticipants?: boolean;
|
||||
writeElevated?: boolean;
|
||||
}
|
||||
|
||||
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
|
||||
@@ -201,7 +200,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
|
||||
|
||||
revert(soft?: boolean): TPromise<void>;
|
||||
|
||||
getValue(): string;
|
||||
createSnapshot(): ITextSnapshot;
|
||||
|
||||
isDirty(): boolean;
|
||||
|
||||
@@ -210,19 +209,6 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
|
||||
isDisposed(): boolean;
|
||||
}
|
||||
|
||||
export interface IRevertOptions {
|
||||
|
||||
/**
|
||||
* Forces to load the contents from disk again even if the file is not dirty.
|
||||
*/
|
||||
force?: boolean;
|
||||
|
||||
/**
|
||||
* A soft revert will clear dirty state of a file but not attempt to load the contents from disk.
|
||||
*/
|
||||
soft?: boolean;
|
||||
}
|
||||
|
||||
export interface ITextFileService extends IDisposable {
|
||||
_serviceBrand: any;
|
||||
onAutoSaveConfigurationChange: Event<IAutoSaveConfiguration>;
|
||||
@@ -258,17 +244,20 @@ export interface ITextFileService extends IDisposable {
|
||||
* Saves the resource.
|
||||
*
|
||||
* @param resource the resource to save
|
||||
* @param options optional save options
|
||||
* @return true if the resource was saved.
|
||||
*/
|
||||
save(resource: URI, options?: ISaveOptions): TPromise<boolean>;
|
||||
|
||||
/**
|
||||
* Saves the provided resource asking the user for a file name.
|
||||
* Saves the provided resource asking the user for a file name or using the provided one.
|
||||
*
|
||||
* @param resource the resource to save as.
|
||||
* @param targetResource the optional target to save to.
|
||||
* @param options optional save options
|
||||
* @return true if the file was saved.
|
||||
*/
|
||||
saveAs(resource: URI, targetResource?: URI): TPromise<URI>;
|
||||
saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): TPromise<URI>;
|
||||
|
||||
/**
|
||||
* Saves the set of resources and returns a promise with the operation result.
|
||||
@@ -298,7 +287,7 @@ export interface ITextFileService extends IDisposable {
|
||||
* @param resources the resources of the files to ask for confirmation or null if
|
||||
* confirming for all dirty resources.
|
||||
*/
|
||||
confirmSave(resources?: URI[]): ConfirmResult;
|
||||
confirmSave(resources?: URI[]): TPromise<ConfirmResult>;
|
||||
|
||||
/**
|
||||
* Convinient fast access to the current auto save mode.
|
||||
@@ -314,4 +303,4 @@ export interface ITextFileService extends IDisposable {
|
||||
* Convinient fast access to the hot exit file setting.
|
||||
*/
|
||||
isHotExitEnabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IStringStream } from 'vs/platform/files/common/files';
|
||||
import * as crypto from 'crypto';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { IRawTextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
const AVOID_SLICED_STRINGS = true;
|
||||
|
||||
export interface ModelBuilderResult {
|
||||
readonly hash: string;
|
||||
readonly value: IRawTextSource;
|
||||
}
|
||||
|
||||
const PREALLOC_BUFFER_CHARS = 1000;
|
||||
|
||||
const emptyString = '';
|
||||
const asciiStrings: string[] = [];
|
||||
for (let i = 0; i < 128; i++) {
|
||||
asciiStrings[i] = String.fromCharCode(i);
|
||||
}
|
||||
|
||||
function optimizeStringMemory(buff: Buffer, s: string): string {
|
||||
const len = s.length;
|
||||
|
||||
if (len === 0) {
|
||||
return emptyString;
|
||||
}
|
||||
|
||||
if (len === 1) {
|
||||
const charCode = s.charCodeAt(0);
|
||||
if (charCode < 128) {
|
||||
return asciiStrings[charCode];
|
||||
}
|
||||
}
|
||||
|
||||
if (AVOID_SLICED_STRINGS) {
|
||||
// See https://bugs.chromium.org/p/v8/issues/detail?id=2869
|
||||
// See https://github.com/nodejs/help/issues/711
|
||||
|
||||
if (len < PREALLOC_BUFFER_CHARS) {
|
||||
// Use the same buffer instance that we have allocated and that can fit `PREALLOC_BUFFER_CHARS` characters
|
||||
const byteLen = buff.write(s, 0);
|
||||
return buff.toString(undefined, 0, byteLen);
|
||||
}
|
||||
|
||||
return Buffer.from(s).toString();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
class ModelLineBasedBuilder {
|
||||
|
||||
private computeHash: boolean;
|
||||
private hash: crypto.Hash;
|
||||
private buff: Buffer;
|
||||
private BOM: string;
|
||||
private lines: string[];
|
||||
private currLineIndex: number;
|
||||
|
||||
constructor(computeHash: boolean) {
|
||||
this.computeHash = computeHash;
|
||||
if (this.computeHash) {
|
||||
this.hash = crypto.createHash('sha1');
|
||||
}
|
||||
this.BOM = '';
|
||||
this.lines = [];
|
||||
this.currLineIndex = 0;
|
||||
this.buff = Buffer.alloc(3/*any UTF16 code unit could expand to up to 3 UTF8 code units*/ * PREALLOC_BUFFER_CHARS);
|
||||
}
|
||||
|
||||
public acceptLines(lines: string[]): void {
|
||||
if (this.currLineIndex === 0) {
|
||||
// Remove the BOM (if present)
|
||||
if (strings.startsWithUTF8BOM(lines[0])) {
|
||||
this.BOM = strings.UTF8_BOM_CHARACTER;
|
||||
lines[0] = lines[0].substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
this.lines[this.currLineIndex++] = optimizeStringMemory(this.buff, lines[i]);
|
||||
}
|
||||
if (this.computeHash) {
|
||||
this.hash.update(lines.join('\n') + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
public finish(length: number, carriageReturnCnt: number, containsRTL: boolean, isBasicASCII: boolean): ModelBuilderResult {
|
||||
return {
|
||||
hash: this.computeHash ? this.hash.digest('hex') : null,
|
||||
value: {
|
||||
BOM: this.BOM,
|
||||
lines: this.lines,
|
||||
length,
|
||||
containsRTL: containsRTL,
|
||||
totalCRCount: carriageReturnCnt,
|
||||
isBasicASCII,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function computeHash(rawText: IRawTextSource): string {
|
||||
let hash = crypto.createHash('sha1');
|
||||
for (let i = 0, len = rawText.lines.length; i < len; i++) {
|
||||
hash.update(rawText.lines[i] + '\n');
|
||||
}
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
export class ModelBuilder {
|
||||
|
||||
private leftoverPrevChunk: string;
|
||||
private leftoverEndsInCR: boolean;
|
||||
private totalCRCount: number;
|
||||
private lineBasedBuilder: ModelLineBasedBuilder;
|
||||
private totalLength: number;
|
||||
private containsRTL: boolean;
|
||||
private isBasicASCII: boolean;
|
||||
|
||||
public static fromStringStream(stream: IStringStream): TPromise<ModelBuilderResult> {
|
||||
return new TPromise<ModelBuilderResult>((c, e, p) => {
|
||||
let done = false;
|
||||
let builder = new ModelBuilder(false);
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
builder.acceptChunk(chunk);
|
||||
});
|
||||
|
||||
stream.on('error', (error) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
e(error);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
c(builder.finish());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
constructor(computeHash: boolean) {
|
||||
this.leftoverPrevChunk = '';
|
||||
this.leftoverEndsInCR = false;
|
||||
this.totalCRCount = 0;
|
||||
this.lineBasedBuilder = new ModelLineBasedBuilder(computeHash);
|
||||
this.totalLength = 0;
|
||||
this.containsRTL = false;
|
||||
this.isBasicASCII = true;
|
||||
}
|
||||
|
||||
private _updateCRCount(chunk: string): void {
|
||||
// Count how many \r are present in chunk to determine the majority EOL sequence
|
||||
let chunkCarriageReturnCnt = 0;
|
||||
let lastCarriageReturnIndex = -1;
|
||||
while ((lastCarriageReturnIndex = chunk.indexOf('\r', lastCarriageReturnIndex + 1)) !== -1) {
|
||||
chunkCarriageReturnCnt++;
|
||||
}
|
||||
this.totalCRCount += chunkCarriageReturnCnt;
|
||||
}
|
||||
|
||||
public acceptChunk(chunk: string): void {
|
||||
if (chunk.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.totalLength += chunk.length;
|
||||
|
||||
this._updateCRCount(chunk);
|
||||
|
||||
if (!this.containsRTL) {
|
||||
this.containsRTL = strings.containsRTL(chunk);
|
||||
}
|
||||
if (this.isBasicASCII) {
|
||||
this.isBasicASCII = strings.isBasicASCII(chunk);
|
||||
}
|
||||
|
||||
// Avoid dealing with a chunk that ends in \r (push the \r to the next chunk)
|
||||
if (this.leftoverEndsInCR) {
|
||||
chunk = '\r' + chunk;
|
||||
}
|
||||
if (chunk.charCodeAt(chunk.length - 1) === CharCode.CarriageReturn) {
|
||||
this.leftoverEndsInCR = true;
|
||||
chunk = chunk.substr(0, chunk.length - 1);
|
||||
} else {
|
||||
this.leftoverEndsInCR = false;
|
||||
}
|
||||
|
||||
let lines = chunk.split(/\r\n|\r|\n/);
|
||||
|
||||
if (lines.length === 1) {
|
||||
// no \r or \n encountered
|
||||
this.leftoverPrevChunk += lines[0];
|
||||
return;
|
||||
}
|
||||
|
||||
lines[0] = this.leftoverPrevChunk + lines[0];
|
||||
this.lineBasedBuilder.acceptLines(lines.slice(0, lines.length - 1));
|
||||
this.leftoverPrevChunk = lines[lines.length - 1];
|
||||
}
|
||||
|
||||
public finish(): ModelBuilderResult {
|
||||
let finalLines = [this.leftoverPrevChunk];
|
||||
if (this.leftoverEndsInCR) {
|
||||
finalLines.push('');
|
||||
}
|
||||
this.lineBasedBuilder.acceptLines(finalLines);
|
||||
return this.lineBasedBuilder.finish(this.totalLength, this.totalCRCount, this.containsRTL, this.isBasicASCII);
|
||||
}
|
||||
}
|
||||
@@ -20,20 +20,21 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ModelBuilder } from 'vs/workbench/services/textfile/electron-browser/modelBuilder';
|
||||
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
|
||||
import product from 'vs/platform/node/product';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { getConfirmMessage } from 'vs/workbench/services/dialogs/electron-browser/dialogs';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class TextFileService extends AbstractTextFileService {
|
||||
|
||||
private static readonly MAX_CONFIRM_FILES = 10;
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -42,61 +43,46 @@ export class TextFileService extends AbstractTextFileService {
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IBackupFileService backupFileService: IBackupFileService,
|
||||
@IWindowsService windowsService: IWindowsService,
|
||||
@IHistoryService historyService: IHistoryService
|
||||
@IHistoryService historyService: IHistoryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, messageService, environmentService, backupFileService, windowsService, historyService);
|
||||
super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, environmentService, backupFileService, windowsService, historyService, contextKeyService, modelService);
|
||||
}
|
||||
|
||||
public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise<IRawTextContent> {
|
||||
return this.fileService.resolveStreamContent(resource, options).then(streamContent => {
|
||||
return ModelBuilder.fromStringStream(streamContent.value).then(res => {
|
||||
return createTextBufferFactoryFromStream(streamContent.value).then(res => {
|
||||
const r: IRawTextContent = {
|
||||
resource: streamContent.resource,
|
||||
name: streamContent.name,
|
||||
mtime: streamContent.mtime,
|
||||
etag: streamContent.etag,
|
||||
encoding: streamContent.encoding,
|
||||
value: res.value,
|
||||
valueLogicalHash: res.hash
|
||||
value: res
|
||||
};
|
||||
return r;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public confirmSave(resources?: URI[]): ConfirmResult {
|
||||
public confirmSave(resources?: URI[]): TPromise<ConfirmResult> {
|
||||
if (this.environmentService.isExtensionDevelopment) {
|
||||
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests)
|
||||
return TPromise.wrap(ConfirmResult.DONT_SAVE); // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests)
|
||||
}
|
||||
|
||||
const resourcesToConfirm = this.getDirty(resources);
|
||||
if (resourcesToConfirm.length === 0) {
|
||||
return ConfirmResult.DONT_SAVE;
|
||||
return TPromise.wrap(ConfirmResult.DONT_SAVE);
|
||||
}
|
||||
|
||||
const message = [
|
||||
resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath)) : nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length)
|
||||
];
|
||||
|
||||
if (resourcesToConfirm.length > 1) {
|
||||
message.push('');
|
||||
message.push(...resourcesToConfirm.slice(0, TextFileService.MAX_CONFIRM_FILES).map(r => paths.basename(r.fsPath)));
|
||||
|
||||
if (resourcesToConfirm.length > TextFileService.MAX_CONFIRM_FILES) {
|
||||
if (resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES === 1) {
|
||||
message.push(nls.localize('moreFile', "...1 additional file not shown"));
|
||||
} else {
|
||||
message.push(nls.localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES));
|
||||
}
|
||||
}
|
||||
|
||||
message.push('');
|
||||
}
|
||||
const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath))
|
||||
: getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm);
|
||||
|
||||
// Button order
|
||||
// Windows: Save | Don't Save | Cancel
|
||||
@@ -118,7 +104,7 @@ export class TextFileService extends AbstractTextFileService {
|
||||
|
||||
const opts: Electron.MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
message: message.join('\n'),
|
||||
message,
|
||||
type: 'warning',
|
||||
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them."),
|
||||
buttons: buttons.map(b => b.label),
|
||||
@@ -130,12 +116,10 @@ export class TextFileService extends AbstractTextFileService {
|
||||
opts.defaultId = 2;
|
||||
}
|
||||
|
||||
const choice = this.windowService.showMessageBox(opts);
|
||||
|
||||
return buttons[choice].result;
|
||||
return this.windowService.showMessageBox(opts).then(result => buttons[result.button].result);
|
||||
}
|
||||
|
||||
public promptForPath(defaultPath: string): string {
|
||||
public promptForPath(defaultPath: string): TPromise<string> {
|
||||
return this.windowService.showSaveDialog(this.getSaveDialogOptions(defaultPath));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ModelBuilder, computeHash } from 'vs/workbench/services/textfile/electron-browser/modelBuilder';
|
||||
import { ITextModelCreationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { RawTextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
|
||||
|
||||
export function testModelBuilder(chunks: string[], opts: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS): string {
|
||||
let expectedTextSource = RawTextSource.fromString(chunks.join(''));
|
||||
let expectedHash = computeHash(expectedTextSource);
|
||||
|
||||
let builder = new ModelBuilder(true);
|
||||
for (let i = 0, len = chunks.length; i < len; i++) {
|
||||
builder.acceptChunk(chunks[i]);
|
||||
}
|
||||
let actual = builder.finish();
|
||||
|
||||
let actualTextSource = actual.value;
|
||||
let actualHash = actual.hash;
|
||||
|
||||
assert.equal(actualHash, expectedHash);
|
||||
assert.deepEqual(actualTextSource, expectedTextSource);
|
||||
|
||||
return expectedHash;
|
||||
}
|
||||
|
||||
function toTextSource(lines: string[]): IRawTextSource {
|
||||
return {
|
||||
BOM: '',
|
||||
lines: lines,
|
||||
totalCRCount: 0,
|
||||
length: 0,
|
||||
containsRTL: false,
|
||||
isBasicASCII: true
|
||||
};
|
||||
}
|
||||
|
||||
export function testDifferentHash(lines1: string[], lines2: string[]): void {
|
||||
let hash1 = computeHash(toTextSource(lines1));
|
||||
let hash2 = computeHash(toTextSource(lines2));
|
||||
assert.notEqual(hash1, hash2);
|
||||
}
|
||||
|
||||
suite('ModelBuilder', () => {
|
||||
|
||||
test('uses sha1', () => {
|
||||
// These are the sha1s of the string + \n
|
||||
assert.equal(computeHash(toTextSource([''])), 'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc');
|
||||
assert.equal(computeHash(toTextSource(['hello world'])), '22596363b3de40b06f981fb85d82312e8c0ed511');
|
||||
});
|
||||
|
||||
test('no chunks', () => {
|
||||
testModelBuilder([]);
|
||||
});
|
||||
|
||||
test('single empty chunk', () => {
|
||||
testModelBuilder(['']);
|
||||
});
|
||||
|
||||
test('single line in one chunk', () => {
|
||||
testModelBuilder(['Hello world']);
|
||||
});
|
||||
|
||||
test('single line in multiple chunks', () => {
|
||||
testModelBuilder(['Hello', ' ', 'world']);
|
||||
});
|
||||
|
||||
test('two lines in single chunk', () => {
|
||||
testModelBuilder(['Hello world\nHow are you?']);
|
||||
});
|
||||
|
||||
test('two lines in multiple chunks 1', () => {
|
||||
testModelBuilder(['Hello worl', 'd\nHow are you?']);
|
||||
});
|
||||
|
||||
test('two lines in multiple chunks 2', () => {
|
||||
testModelBuilder(['Hello worl', 'd', '\n', 'H', 'ow are you?']);
|
||||
});
|
||||
|
||||
test('two lines in multiple chunks 3', () => {
|
||||
testModelBuilder(['Hello worl', 'd', '\nHow are you?']);
|
||||
});
|
||||
|
||||
test('multiple lines in single chunks', () => {
|
||||
testModelBuilder(['Hello world\nHow are you?\nIs everything good today?\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('multiple lines in multiple chunks 1', () => {
|
||||
testModelBuilder(['Hello world\nHow are you', '?\nIs everything good today?\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('multiple lines in multiple chunks 1', () => {
|
||||
testModelBuilder(['Hello world', '\nHow are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('multiple lines in multiple chunks 1', () => {
|
||||
testModelBuilder(['Hello world\n', 'How are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('carriage return detection (1 \\r\\n 2 \\n)', () => {
|
||||
testModelBuilder(['Hello world\r\n', 'How are you', '?\nIs everything good today?', '\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('carriage return detection (2 \\r\\n 1 \\n)', () => {
|
||||
testModelBuilder(['Hello world\r\n', 'How are you', '?\r\nIs everything good today?', '\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('carriage return detection (3 \\r\\n 0 \\n)', () => {
|
||||
testModelBuilder(['Hello world\r\n', 'How are you', '?\r\nIs everything good today?', '\r\nDo you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('carriage return detection (isolated \\r)', () => {
|
||||
testModelBuilder(['Hello world', '\r', '\n', 'How are you', '?', '\r', '\n', 'Is everything good today?', '\r', '\n', 'Do you enjoy the weather?']);
|
||||
});
|
||||
|
||||
test('BOM handling', () => {
|
||||
testModelBuilder([strings.UTF8_BOM_CHARACTER + 'Hello world!']);
|
||||
});
|
||||
|
||||
test('BOM handling', () => {
|
||||
testModelBuilder([strings.UTF8_BOM_CHARACTER, 'Hello world!']);
|
||||
});
|
||||
|
||||
test('RTL handling 1', () => {
|
||||
testModelBuilder(['Hello world!', 'זוהי עובדה מבוססת שדעתו']);
|
||||
});
|
||||
|
||||
test('RTL handling 2', () => {
|
||||
testModelBuilder(['Hello world!זוהי עובדה מבוססת שדעתו']);
|
||||
});
|
||||
|
||||
test('RTL handling 3', () => {
|
||||
testModelBuilder(['Hello world!זוהי \nעובדה מבוססת שדעתו']);
|
||||
});
|
||||
|
||||
test('ASCII handling 1', () => {
|
||||
testModelBuilder(['Hello world!!\nHow do you do?']);
|
||||
});
|
||||
test('ASCII handling 1', () => {
|
||||
testModelBuilder(['Hello world!!\nHow do you do?Züricha📚📚b']);
|
||||
});
|
||||
|
||||
test('issue #32819: some special string cannot be displayed completely', () => {
|
||||
testModelBuilder(['AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA123']);
|
||||
});
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { testModelBuilder, testDifferentHash } from './modelBuilder.test';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
const GENERATE_TESTS = false;
|
||||
|
||||
suite('ModelBuilder Auto Tests', () => {
|
||||
|
||||
test('auto1', () => {
|
||||
testModelBuilder(['sarjniow', '\r', '\nbpb', 'ofb', '\njzldgxx', '\r\nkzwfjysng']);
|
||||
});
|
||||
|
||||
test('auto2', () => {
|
||||
testModelBuilder(['i', 'yyernubi\r\niimgn\n', 'ut\r']);
|
||||
});
|
||||
|
||||
test('auto3', () => {
|
||||
testDifferentHash([''], ['', '', '']);
|
||||
});
|
||||
});
|
||||
|
||||
function getRandomInt(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function getRandomEOLSequence(): string {
|
||||
let rnd = getRandomInt(1, 3);
|
||||
if (rnd === 1) {
|
||||
return '\n';
|
||||
}
|
||||
if (rnd === 2) {
|
||||
return '\r';
|
||||
}
|
||||
return '\r\n';
|
||||
}
|
||||
|
||||
function getRandomString(minLength: number, maxLength: number): string {
|
||||
let length = getRandomInt(minLength, maxLength);
|
||||
let r = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
r += String.fromCharCode(getRandomInt(CharCode.a, CharCode.z));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function generateRandomFile(): string {
|
||||
let lineCount = getRandomInt(1, 10);
|
||||
let mixedEOLSequence = getRandomInt(1, 2) === 1 ? true : false;
|
||||
let fixedEOL = getRandomEOLSequence();
|
||||
let lines: string[] = [];
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
if (i !== 0) {
|
||||
if (mixedEOLSequence) {
|
||||
lines.push(getRandomEOLSequence());
|
||||
} else {
|
||||
lines.push(fixedEOL);
|
||||
}
|
||||
}
|
||||
lines.push(getRandomString(0, 10));
|
||||
|
||||
}
|
||||
return lines.join('');
|
||||
}
|
||||
|
||||
function generateRandomChunks(file: string): string[] {
|
||||
let result: string[] = [];
|
||||
let cnt = getRandomInt(1, 20);
|
||||
|
||||
let maxOffset = file.length;
|
||||
|
||||
while (cnt > 0 && maxOffset > 0) {
|
||||
|
||||
let offset = getRandomInt(0, maxOffset);
|
||||
result.unshift(file.substring(offset, maxOffset));
|
||||
// let length = getRandomInt(0, maxOffset - offset);
|
||||
// let text = generateFile(true);
|
||||
|
||||
// result.push({
|
||||
// offset: offset,
|
||||
// length: length,
|
||||
// text: text
|
||||
// });
|
||||
|
||||
maxOffset = offset;
|
||||
cnt--;
|
||||
}
|
||||
if (maxOffset !== 0) {
|
||||
result.unshift(file.substring(0, maxOffset));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
let HASH_TO_CONTENT: { [hash: string]: string; } = {};
|
||||
|
||||
function testRandomFile(file: string): boolean {
|
||||
let tests = getRandomInt(5, 10);
|
||||
for (let i = 0; i < tests; i++) {
|
||||
let chunks = generateRandomChunks(file);
|
||||
try {
|
||||
let hash = testModelBuilder(chunks);
|
||||
let logicalContent = JSON.stringify(file.split(/\r\n|\r|\n/));
|
||||
if (HASH_TO_CONTENT.hasOwnProperty(hash)) {
|
||||
let prevLogicalContent = HASH_TO_CONTENT[hash];
|
||||
if (prevLogicalContent !== logicalContent) {
|
||||
console.log('HASH COLLISION: ');
|
||||
console.log(prevLogicalContent);
|
||||
console.log(logicalContent);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
HASH_TO_CONTENT[hash] = logicalContent;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log(JSON.stringify(chunks));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GENERATE_TESTS) {
|
||||
let number = 1;
|
||||
while (true) {
|
||||
console.log('------BEGIN NEW TEST: ' + number);
|
||||
|
||||
if (!testRandomFile(generateRandomFile())) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log('------END NEW TEST: ' + (number++));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { ITextFileService, ModelState, StateChange } from 'vs/workbench/services
|
||||
import { workbenchInstantiationService, TestTextFileService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { onError, toResource } from 'vs/base/test/common/utils';
|
||||
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
|
||||
import { FileOperationResult, FileOperationError, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileOperationResult, FileOperationError, IFileService, snapshotToString } from 'vs/platform/files/common/files';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
|
||||
class ServiceAccessor {
|
||||
@@ -284,7 +284,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.SAVED) {
|
||||
assert.equal(model.getValue(), 'bar');
|
||||
assert.equal(snapshotToString(model.createSnapshot()), 'bar');
|
||||
assert.ok(!model.isDirty());
|
||||
eventCounter++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user