mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 03:28:33 -05:00
Merge from vscode 8a997f7321ae6612fc0e6eb3eac4f358a6233bfb
This commit is contained in:
@@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user