Merge from vscode 8a997f7321ae6612fc0e6eb3eac4f358a6233bfb

This commit is contained in:
ADS Merger
2020-02-11 07:08:19 +00:00
parent 0f934081e1
commit 085752f111
217 changed files with 2561 additions and 2063 deletions

View File

@@ -43,10 +43,10 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
private readonly toBeInstantiated: Map<LifecyclePhase, IConstructorSignature0<IWorkbenchContribution>[]> = new Map<LifecyclePhase, IConstructorSignature0<IWorkbenchContribution>[]>();
registerWorkbenchContribution<Services extends BrandedService[]>(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void {
registerWorkbenchContribution<Services extends BrandedService[]>(ctor: new (...services: Services) => IWorkbenchContribution, phase: LifecyclePhase = LifecyclePhase.Starting): void {
// Instantiate directly if we are already matching the provided phase
if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) {
this.instantiationService.createInstance(ctor);
this.instantiationService.createInstance<Services, typeof ctor, IWorkbenchContribution>(ctor);
}
// Otherwise keep contributions by lifecycle phase
@@ -57,7 +57,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
this.toBeInstantiated.set(phase, toBeInstantiated);
}
toBeInstantiated.push(ctor);
toBeInstantiated.push(ctor as IConstructorSignature0<IWorkbenchContribution>);
}
}

View File

@@ -77,6 +77,10 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
this.modelService.setMode(this.textEditorModel, this.modeService.create(mode));
}
getMode(): string | undefined {
return this.textEditorModel?.getModeId();
}
/**
* Creates the text editor model with the provided value, optional preferred mode
* (can be comma separated for multiple values) and optional resource URL.

View File

@@ -1,222 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextResourceEditorInput } from 'vs/workbench/common/editor';
import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter } from 'vs/base/common/event';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ILabelService } from 'vs/platform/label/common/label';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IFileService } from 'vs/platform/files/common/files';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
/**
* An editor input to be used for untitled text buffers.
*/
export class UntitledTextEditorInput extends TextResourceEditorInput implements IEncodingSupport, IModeSupport {
static readonly ID: string = 'workbench.editors.untitledEditorInput';
private readonly _onDidModelChangeEncoding = this._register(new Emitter<void>());
readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event;
private cachedModel: UntitledTextEditorModel | undefined = undefined;
private modelResolve: Promise<UntitledTextEditorModel & IResolvedTextEditorModel> | undefined = undefined;
private preferredMode: string | undefined;
constructor(
resource: URI,
private readonly _hasAssociatedFilePath: boolean,
preferredMode: string | undefined,
private readonly initialValue: string | undefined,
private preferredEncoding: string | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITextFileService textFileService: ITextFileService,
@ILabelService labelService: ILabelService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IFileService fileService: IFileService,
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService
) {
super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
if (preferredMode) {
this.setMode(preferredMode);
}
}
get hasAssociatedFilePath(): boolean {
return this._hasAssociatedFilePath;
}
getTypeId(): string {
return UntitledTextEditorInput.ID;
}
getName(): string {
if (this.cachedModel) {
return this.cachedModel.name;
}
return super.getName();
}
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
// Without associated path: only use if name and description differ
if (!this.hasAssociatedFilePath) {
const descriptionCandidate = this.resource.path;
if (descriptionCandidate !== this.getName()) {
return descriptionCandidate;
}
return undefined;
}
// With associated path: delegate to parent
return super.getDescription(verbosity);
}
getTitle(verbosity: Verbosity): string {
// Without associated path: check if name and description differ to decide
// if description should appear besides the name to distinguish better
if (!this.hasAssociatedFilePath) {
const name = this.getName();
const description = this.getDescription();
if (description && description !== name) {
return `${name}${description}`;
}
return name;
}
// With associated path: delegate to parent
return super.getTitle(verbosity);
}
isDirty(): boolean {
// Always trust the model first if existing
if (this.cachedModel) {
return this.cachedModel.isDirty();
}
// A disposed input is never dirty, even if it was restored from backup
if (this.isDisposed()) {
return false;
}
// A input with initial value is always dirty
if (this.initialValue && this.initialValue.length > 0) {
return true;
}
// A input with associated path is always dirty because it is the intent
// of the user to create a new file at that location through saving
return this.hasAssociatedFilePath;
}
getEncoding(): string | undefined {
if (this.cachedModel) {
return this.cachedModel.getEncoding();
}
return this.preferredEncoding;
}
setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
this.preferredEncoding = encoding;
if (this.cachedModel) {
this.cachedModel.setEncoding(encoding);
}
}
setMode(mode: string): void {
let actualMode: string | undefined = undefined;
if (mode === '${activeEditorLanguage}') {
// support the special '${activeEditorLanguage}' mode by
// looking up the language mode from the currently
// active text editor if any
actualMode = this.editorService.activeTextEditorMode;
} else {
actualMode = mode;
}
this.preferredMode = actualMode;
if (this.preferredMode && this.cachedModel) {
this.cachedModel.setMode(this.preferredMode);
}
}
getMode(): string | undefined {
if (this.cachedModel) {
return this.cachedModel.getMode();
}
return this.preferredMode;
}
resolve(): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
// Join a model resolve if we have had one before
if (this.modelResolve) {
return this.modelResolve;
}
// Otherwise Create Model and load
this.cachedModel = this.createModel();
this.modelResolve = this.cachedModel.load();
return this.modelResolve;
}
private createModel(): UntitledTextEditorModel {
const model = this._register(this.instantiationService.createInstance(UntitledTextEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding));
this.registerModelListeners(model);
return model;
}
private registerModelListeners(model: UntitledTextEditorModel): void {
// re-emit some events from the model
this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
this._register(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire()));
this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire()));
// a disposed untitled text editor model renders this input disposed
this._register(model.onDispose(() => this.dispose()));
}
matches(otherInput: unknown): boolean {
if (super.matches(otherInput) === true) {
return true;
}
// Otherwise compare by properties
if (otherInput instanceof UntitledTextEditorInput) {
return otherInput.resource.toString() === this.resource.toString();
}
return false;
}
dispose(): void {
this.cachedModel = undefined;
this.modelResolve = undefined;
super.dispose();
}
}

View File

@@ -1,259 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEncodingSupport, ISaveOptions, IModeSupport } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { URI } from 'vs/base/common/uri';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Emitter } from 'vs/base/common/event';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { IResolvedTextEditorModel, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
import { ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport, IEncodingSupport, IWorkingCopy { }
export class UntitledTextEditorModel extends BaseTextEditorModel implements IUntitledTextEditorModel {
private static readonly FIRST_LINE_NAME_MAX_LENGTH = 40;
private readonly _onDidChangeContent = this._register(new Emitter<void>());
readonly onDidChangeContent = this._onDidChangeContent.event;
private readonly _onDidChangeName = this._register(new Emitter<void>());
readonly onDidChangeName = this._onDidChangeName.event;
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
private readonly _onDidChangeEncoding = this._register(new Emitter<void>());
readonly onDidChangeEncoding = this._onDidChangeEncoding.event;
readonly capabilities = WorkingCopyCapabilities.Untitled;
private cachedModelFirstLineWords: string | undefined = undefined;
get name(): string {
// Take name from first line if present and only if
// we have no associated file path. In that case we
// prefer the file name as title.
if (!this.hasAssociatedFilePath && this.cachedModelFirstLineWords) {
return this.cachedModelFirstLineWords;
}
// Otherwise fallback to resource
return this.labelService.getUriBasenameLabel(this.resource);
}
private dirty = false;
private versionId = 0;
private configuredEncoding: string | undefined;
constructor(
private readonly preferredMode: string | undefined,
public readonly resource: URI,
public readonly hasAssociatedFilePath: boolean,
private readonly initialValue: string | undefined,
private preferredEncoding: string | undefined,
@IModeService modeService: IModeService,
@IModelService modelService: IModelService,
@IBackupFileService private readonly backupFileService: IBackupFileService,
@ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService,
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
@ITextFileService private readonly textFileService: ITextFileService,
@ILabelService private readonly labelService: ILabelService
) {
super(modelService, modeService);
// Make known to working copy service
this._register(this.workingCopyService.registerWorkingCopy(this));
this.registerListeners();
}
private registerListeners(): void {
// Config Changes
this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange()));
}
private onConfigurationChange(): void {
const configuredEncoding = this.textResourceConfigurationService.getValue<string>(this.resource, 'files.encoding');
if (this.configuredEncoding !== configuredEncoding) {
this.configuredEncoding = configuredEncoding;
if (!this.preferredEncoding) {
this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set
}
}
}
getVersionId(): number {
return this.versionId;
}
getMode(): string | undefined {
if (this.textEditorModel) {
return this.textEditorModel.getModeId();
}
return this.preferredMode;
}
getEncoding(): string | undefined {
return this.preferredEncoding || this.configuredEncoding;
}
setEncoding(encoding: string): void {
const oldEncoding = this.getEncoding();
this.preferredEncoding = encoding;
// Emit if it changed
if (oldEncoding !== this.preferredEncoding) {
this._onDidChangeEncoding.fire();
}
}
isReadonly(): boolean {
return false;
}
isDirty(): boolean {
return this.dirty;
}
// {{SQL CARBON EDIT}}
// make property public
public setDirty(dirty: boolean): void {
if (this.dirty === dirty) {
return;
}
this.dirty = dirty;
this._onDidChangeDirty.fire();
}
async save(options?: ISaveOptions): Promise<boolean> {
const target = await this.textFileService.save(this.resource, options);
return !!target;
}
async revert(): Promise<boolean> {
this.setDirty(false);
// A reverted untitled model is invalid because it has
// no actual source on disk to revert to. As such we
// dispose the model.
this.dispose();
return true;
}
async backup(): Promise<IWorkingCopyBackup> {
return { content: withNullAsUndefined(this.createSnapshot()) };
}
async load(): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
// Check for backups
const backup = await this.backupFileService.resolve(this.resource);
let untitledContents: ITextBufferFactory;
if (backup) {
untitledContents = backup.value;
} else {
untitledContents = createTextBufferFactory(this.initialValue || '');
}
// Create text editor model if not yet done
if (!this.textEditorModel) {
this.createTextEditorModel(untitledContents, this.resource, this.preferredMode);
}
// Otherwise update
else {
this.updateTextEditorModel(untitledContents, this.preferredMode);
}
// Figure out encoding now that model is present
this.configuredEncoding = this.textResourceConfigurationService.getValue<string>(this.resource, 'files.encoding');
// Listen to text model events
const textEditorModel = assertIsDefined(this.textEditorModel);
this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(textEditorModel, e)));
this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config
// Name
if (backup || this.initialValue) {
this.updateNameFromFirstLine();
}
// Untitled associated to file path are dirty right away as well as untitled with content
this.setDirty(this.hasAssociatedFilePath || !!backup || !!this.initialValue);
// If we have initial contents, make sure to emit this
// as the appropiate events to the outside.
if (backup || this.initialValue) {
this._onDidChangeContent.fire();
}
return this as UntitledTextEditorModel & IResolvedTextEditorModel;
}
private onModelContentChanged(model: ITextModel, e: IModelContentChangedEvent): void {
this.versionId++;
// mark the untitled text editor as non-dirty once its content becomes empty and we do
// not have an associated path set. we never want dirty indicator in that case.
if (!this.hasAssociatedFilePath && model.getLineCount() === 1 && model.getLineContent(1) === '') {
this.setDirty(false);
}
// turn dirty otherwise
else {
this.setDirty(true);
}
// Check for name change if first line changed in the range of 0-FIRST_LINE_NAME_MAX_LENGTH columns
if (e.changes.some(change => (change.range.startLineNumber === 1 || change.range.endLineNumber === 1) && change.range.startColumn <= UntitledTextEditorModel.FIRST_LINE_NAME_MAX_LENGTH)) {
this.updateNameFromFirstLine();
}
// Emit as general content change event
this._onDidChangeContent.fire();
}
private updateNameFromFirstLine(): void {
if (this.hasAssociatedFilePath) {
return; // not in case of an associated file path
}
// Determine the first words of the model following these rules:
// - cannot be only whitespace (so we trim())
// - cannot be only non-alphanumeric characters (so we run word definition regex over it)
// - cannot be longer than FIRST_LINE_MAX_TITLE_LENGTH
let modelFirstWordsCandidate: string | undefined = undefined;
const firstLineText = this.textEditorModel?.getValueInRange({ startLineNumber: 1, endLineNumber: 1, startColumn: 1, endColumn: UntitledTextEditorModel.FIRST_LINE_NAME_MAX_LENGTH + 1 }).trim();
if (firstLineText && ensureValidWordDefinition().exec(firstLineText)) {
modelFirstWordsCandidate = firstLineText;
}
if (modelFirstWordsCandidate !== this.cachedModelFirstLineWords) {
this.cachedModelFirstLineWords = modelFirstWordsCandidate;
this._onDidChangeName.fire();
}
}
}