Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229 (#8962)

* Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229

* skip failing tests

* update mac build image
This commit is contained in:
Anthony Dresser
2020-01-27 15:28:17 -08:00
committed by Karl Burtram
parent 0eaee18dc4
commit fefe1454de
481 changed files with 12764 additions and 7836 deletions

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
import { withNullAsUndefined, assertIsDefined } from 'vs/base/common/types';
@@ -298,15 +299,6 @@ export const enum SaveReason {
WINDOW_CHANGE = 4
}
export const enum SaveContext {
/**
* Indicates that the editor is saved because it
* is being closed by the user.
*/
EDITOR_CLOSE = 1,
}
export interface ISaveOptions {
/**
@@ -314,11 +306,6 @@ export interface ISaveOptions {
*/
reason?: SaveReason;
/**
* Additional information about the context of the save.
*/
context?: SaveContext;
/**
* Forces to save the contents of the working copy
* again even if the working copy is not dirty.
@@ -361,6 +348,16 @@ export interface IEditorInput extends IDisposable {
*/
readonly onDispose: Event<void>;
/**
* Triggered when this input changes its dirty state.
*/
readonly onDidChangeDirty: Event<void>;
/**
* Triggered when this input changes its label
*/
readonly onDidChangeLabel: Event<void>;
/**
* Returns the associated resource of this input.
*/
@@ -415,23 +412,29 @@ export interface IEditorInput extends IDisposable {
isSaving(): boolean;
/**
* Saves the editor. The provided groupId helps
* implementors to e.g. preserve view state of the editor
* and re-open it in the correct group after saving.
* Saves the editor. The provided groupId helps implementors
* to e.g. preserve view state of the editor and re-open it
* in the correct group after saving.
*
* @returns the resulting editor input of this operation. Can
* be the same editor input.
*/
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean>;
save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined>;
/**
* Saves the editor to a different location. The provided groupId
* helps implementors to e.g. preserve view state of the editor
* and re-open it in the correct group after saving.
*
* @returns the resulting editor input of this operation. Typically
* a different editor input.
*/
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean>;
saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined>;
/**
* Reverts this input.
* Reverts this input from the provided group.
*/
revert(options?: IRevertOptions): Promise<boolean>;
revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean>;
/**
* Returns if the other object matches this input.
@@ -523,15 +526,15 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
return false;
}
async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
return true;
async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
return this;
}
async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
return true;
async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
return this;
}
async revert(options?: IRevertOptions): Promise<boolean> {
async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean> {
return true;
}
@@ -551,8 +554,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
}
dispose(): void {
this.disposed = true;
this._onDispose.fire();
if (!this.disposed) {
this.disposed = true;
this._onDispose.fire();
}
super.dispose();
}
@@ -573,43 +578,37 @@ export abstract class TextEditorInput extends EditorInput {
return this.resource;
}
async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
return this.textFileService.save(this.resource, options);
async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
return this.doSave(group, options, false);
}
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options));
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IEditorInput | undefined> {
return this.doSave(group, options, true);
}
protected async doSaveAs(group: GroupIdentifier, options: ISaveOptions | undefined, saveRunnable: () => Promise<URI | undefined>, replaceAllEditors?: boolean): Promise<boolean> {
private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise<IEditorInput | undefined> {
// Preserve view state by opening the editor first. In addition
// this allows the user to review the contents of the editor.
let viewState: IEditorViewState | undefined = undefined;
const editor = await this.editorService.openEditor(this, undefined, group);
if (isTextEditor(editor)) {
viewState = editor.getViewState();
// Save / Save As
let target: URI | undefined;
if (saveAs) {
target = await this.textFileService.saveAs(this.resource, undefined, options);
} else {
target = await this.textFileService.save(this.resource, options);
}
// Save as
const target = await saveRunnable();
if (!target) {
return false; // save cancelled
return undefined; // save cancelled
}
// Replace editor preserving viewstate (either across all groups or
// only selected group) if the target is different from the current resource
// and if the editor is not being saved because it is being closed
// (because in that case we do not want to open a different editor anyway)
if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(target, this.resource)) {
const replacement = this.editorService.createInput({ resource: target });
const targetGroups = replaceAllEditors ? this.editorGroupService.groups.map(group => group.id) : [group];
for (const group of targetGroups) {
await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group);
}
if (!isEqual(target, this.resource)) {
return this.editorService.createInput({ resource: target });
}
return true;
return this;
}
revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean> {
return this.textFileService.revert(this.resource, options);
}
}
@@ -682,7 +681,7 @@ export class SideBySideEditorInput extends EditorInput {
static readonly ID: string = 'workbench.editorinputs.sidebysideEditorInput';
constructor(
private readonly name: string,
protected readonly name: string | undefined,
private readonly description: string | undefined,
private readonly _details: EditorInput,
private readonly _master: EditorInput
@@ -700,6 +699,22 @@ export class SideBySideEditorInput extends EditorInput {
return this._details;
}
getTypeId(): string {
return SideBySideEditorInput.ID;
}
getName(): string {
if (!this.name) {
return localize('sideBySideLabels', "{0} - {1}", this._details.getName(), this._master.getName());
}
return this.name;
}
getDescription(): string | undefined {
return this.description;
}
isReadonly(): boolean {
return this.master.isReadonly();
}
@@ -716,16 +731,16 @@ export class SideBySideEditorInput extends EditorInput {
return this.master.isSaving();
}
save(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
return this.master.save(groupId, options);
save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
return this.master.save(group, options);
}
saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise<boolean> {
return this.master.saveAs(groupId, options);
saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
return this.master.saveAs(group, options);
}
revert(options?: IRevertOptions): Promise<boolean> {
return this.master.revert(options);
revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean> {
return this.master.revert(group, options);
}
getTelemetryDescriptor(): { [key: string]: unknown } {
@@ -760,18 +775,6 @@ export class SideBySideEditorInput extends EditorInput {
return null;
}
getTypeId(): string {
return SideBySideEditorInput.ID;
}
getName(): string {
return this.name;
}
getDescription(): string | undefined {
return this.description;
}
matches(otherInput: unknown): boolean {
if (super.matches(otherInput) === true) {
return true;

View File

@@ -7,6 +7,7 @@ import { EditorModel, EditorInput, SideBySideEditorInput, TEXT_DIFF_EDITOR_ID, B
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
import { localize } from 'vs/nls';
/**
* The base editor input for the diff editor. It is made up of two editor inputs, the original version
@@ -19,33 +20,25 @@ export class DiffEditorInput extends SideBySideEditorInput {
private cachedModel: DiffEditorModel | null = null;
constructor(
name: string,
protected name: string | undefined,
description: string | undefined,
original: EditorInput,
modified: EditorInput,
public readonly originalInput: EditorInput,
public readonly modifiedInput: EditorInput,
private readonly forceOpenAsBinary?: boolean
) {
super(name, description, original, modified);
}
matches(otherInput: unknown): boolean {
if (!super.matches(otherInput)) {
return false;
}
return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;
super(name, description, originalInput, modifiedInput);
}
getTypeId(): string {
return DiffEditorInput.ID;
}
get originalInput(): EditorInput {
return this.details;
}
getName(): string {
if (!this.name) {
return localize('sideBySideLabels', "{0} ↔ {1}", this.originalInput.getName(), this.modifiedInput.getName());
}
get modifiedInput(): EditorInput {
return this.master;
return this.name;
}
async resolve(): Promise<EditorModel> {
@@ -88,6 +81,14 @@ export class DiffEditorInput extends SideBySideEditorInput {
return new DiffEditorModel(originalEditorModel, modifiedEditorModel);
}
matches(otherInput: unknown): boolean {
if (!super.matches(otherInput)) {
return false;
}
return otherInput instanceof DiffEditorInput && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;
}
dispose(): void {
// Free the diff editor model but do not propagate the dispose() call to the two inputs

View File

@@ -10,8 +10,6 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { coalesce, firstIndex } from 'vs/base/common/arrays';
import { isEqual } from 'vs/base/common/resources';
import { IResourceInput } from 'vs/platform/editor/common/editor';
// {{SQL CARBON EDIT}}
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
@@ -579,7 +577,7 @@ export class EditorGroup extends Disposable {
return this.editors[index];
}
contains(candidate: EditorInput | IResourceInput, searchInSideBySideEditors?: boolean): boolean {
contains(candidate: EditorInput, searchInSideBySideEditors?: boolean): boolean {
for (const editor of this.editors) {
if (this.matches(editor, candidate)) {
return true;
@@ -595,18 +593,12 @@ export class EditorGroup extends Disposable {
return false;
}
private matches(editor: IEditorInput | null, candidate: IEditorInput | IResourceInput | null): boolean {
private matches(editor: IEditorInput | null, candidate: IEditorInput | null): boolean {
if (!editor || !candidate) {
return false;
}
if (candidate instanceof EditorInput) {
return editor.matches(candidate);
}
const resource = editor.getResource();
return !!(resource && isEqual(resource, (candidate as IResourceInput).resource));
return editor.matches(candidate);
}
clone(): EditorGroup {

View File

@@ -3,21 +3,21 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput, ITextEditorModel, IModeSupport, GroupIdentifier, isTextEditor } from 'vs/workbench/common/editor';
import { ITextEditorModel, IModeSupport, TextEditorInput } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { IReference } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import { basename } from 'vs/base/common/resources';
import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
/**
* A read-only text editor input whos contents are made of the provided resource that points to an existing
* code editor model.
*/
export class ResourceEditorInput extends EditorInput implements IModeSupport {
export class ResourceEditorInput extends TextEditorInput implements IModeSupport {
static readonly ID: string = 'workbench.editors.resourceEditorInput';
@@ -27,17 +27,14 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport {
constructor(
private name: string | undefined,
private description: string | undefined,
private readonly resource: URI,
resource: URI,
private preferredMode: string | undefined,
@ITextModelService private readonly textModelResolverService: ITextModelService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEditorService private readonly editorService: IEditorService
@ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super();
this.name = name;
this.description = description;
this.resource = resource;
super(resource, editorService, editorGroupService, textFileService);
}
getResource(): URI {
@@ -109,28 +106,6 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport {
return model;
}
async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
// Preserve view state by opening the editor first. In addition
// this allows the user to review the contents of the editor.
let viewState: IEditorViewState | undefined = undefined;
const editor = await this.editorService.openEditor(this, undefined, group);
if (isTextEditor(editor)) {
viewState = editor.getViewState();
}
// Save as
const target = await this.textFileService.saveAs(this.resource, undefined, options);
if (!target) {
return false; // save cancelled
}
// Open the target
await this.editorService.openEditor({ resource: target, options: { viewState, pinned: true } }, group);
return true;
}
matches(otherInput: unknown): boolean {
if (super.matches(otherInput) === true) {
return true;

View File

@@ -4,20 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { suggestFilename } from 'vs/base/common/mime';
import { createMemoizer } from 'vs/base/common/decorators';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { basenameOrAuthority, dirname, toLocalResource } from 'vs/base/common/resources';
import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor';
import { basenameOrAuthority, dirname } from 'vs/base/common/resources';
import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput } 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, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
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 { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
/**
* An editor input to be used for untitled text buffers.
@@ -32,6 +29,7 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
readonly onDidModelChangeEncoding = this._onDidModelChangeEncoding.event;
private cachedModel: UntitledTextEditorModel | null = null;
private modelResolve: Promise<UntitledTextEditorModel & IResolvedTextEditorModel> | null = null;
private preferredMode: string | undefined;
@@ -46,8 +44,7 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
@ITextFileService textFileService: ITextFileService,
@ILabelService private readonly labelService: ILabelService,
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(resource, editorService, editorGroupService, textFileService);
@@ -71,6 +68,10 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
}
getName(): string {
if (this.cachedModel) {
return this.cachedModel.name;
}
return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path;
}
@@ -165,60 +166,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
return this.hasAssociatedFilePath;
}
hasBackup(): boolean {
if (this.cachedModel) {
return this.cachedModel.hasBackup();
}
return false;
}
save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
return this.doSaveAs(group, options, async () => {
// With associated file path, save to the path that is
// associated. Make sure to convert the result using
// remote authority properly.
if (this.hasAssociatedFilePath) {
if (await this.textFileService.save(this.resource, options)) {
return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority);
}
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
}
// Without associated file path, do a normal "Save As"
return this.textFileService.saveAs(this.resource, undefined, options);
}, true /* replace editor across all groups */);
}
saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<boolean> {
return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */);
}
async revert(options?: IRevertOptions): Promise<boolean> {
if (this.cachedModel) {
this.cachedModel.revert();
}
this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it
return true;
}
suggestFileName(): string {
if (!this.hasAssociatedFilePath) {
if (this.cachedModel) {
const mode = this.cachedModel.getMode();
if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text
return suggestFilename(mode, this.getName());
}
}
}
return this.getName();
}
getEncoding(): string | undefined {
if (this.cachedModel) {
return this.cachedModel.getEncoding();
@@ -278,11 +225,20 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
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()));
return model;
// a disposed untitled text editor model renders this input disposed
this._register(model.onDispose(() => this.dispose()));
}
matches(otherInput: unknown): boolean {

View File

@@ -9,21 +9,30 @@ 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, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ITextBufferFactory } 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 } from 'vs/workbench/services/workingCopy/common/workingCopyService';
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 } from 'vs/base/common/types';
import { basenameOrAuthority } from 'vs/base/common/resources';
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 = 50;
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;
@@ -32,6 +41,19 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
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.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path;
}
private dirty = false;
private versionId = 0;
private configuredEncoding: string | undefined;
@@ -101,6 +123,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
}
}
isReadonly(): boolean {
return false;
}
isDirty(): boolean {
return this.dirty;
}
@@ -116,34 +142,31 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
this._onDidChangeDirty.fire();
}
save(options?: ISaveOptions): Promise<boolean> {
return this.textFileService.save(this.resource, options);
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<void> {
if (this.isResolved()) {
return this.backupFileService.backupResource(this.resource, this.createSnapshot(), this.versionId);
}
}
hasBackup(): boolean {
return this.backupFileService.hasBackupSync(this.resource, this.versionId);
async backup(): Promise<IWorkingCopyBackup> {
return { content: withNullAsUndefined(this.createSnapshot()) };
}
async load(): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
// Check for backups first
let backup: IResolvedBackup<object> | undefined = undefined;
const backupResource = await this.backupFileService.loadBackupResource(this.resource);
if (backupResource) {
backup = await this.backupFileService.resolveBackupContent(backupResource);
}
// Check for backups
const backup = await this.backupFileService.resolve(this.resource);
// untitled associated to file path are dirty right away as well as untitled with content
this.setDirty(this.hasAssociatedFilePath || !!backup || !!this.initialValue);
@@ -165,6 +188,11 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
this.updateTextEditorModel(untitledContents, this.preferredMode);
}
// Name
if (backup || this.initialValue) {
this.updateNameFromFirstLine();
}
// Encoding
this.configuredEncoding = this.configurationService.getValue<string>(this.resource, 'files.encoding');
@@ -172,15 +200,21 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
const textEditorModel = this.textEditorModel!;
// Listen to content changes
this._register(textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));
this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(e)));
// Listen to mode changes
this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config
// 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(): void {
private onModelContentChanged(e: IModelContentChangedEvent): void {
if (!this.isResolved()) {
return;
}
@@ -198,11 +232,35 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
this.setDirty(true);
}
// Emit as event
// Check for name change if first line changed
if (e.changes.some(change => change.range.startLineNumber === 1 || change.range.endLineNumber === 1)) {
this.updateNameFromFirstLine();
}
// Emit as general content change event
this._onDidChangeContent.fire();
}
isReadonly(): boolean {
return false;
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 }).trim();
if (firstLineText && ensureValidWordDefinition().exec(firstLineText)) {
modelFirstWordsCandidate = firstLineText;
}
if (modelFirstWordsCandidate !== this.cachedModelFirstLineWords) {
this.cachedModelFirstLineWords = modelFirstWordsCandidate;
this._onDidChangeName.fire();
}
}
}

View File

@@ -3,9 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IView } from 'vs/workbench/common/views';
import { IView, IViewPaneContainer } from 'vs/workbench/common/views';
import { IComposite } from 'vs/workbench/common/composite';
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
export interface IPaneComposite extends IComposite {
openView(id: string, focus?: boolean): IView;

View File

@@ -1,16 +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 { IAction, IActionViewItem } from 'vs/base/common/actions';
export interface IViewPaneContainer {
setVisible(visible: boolean): void;
isVisible(): boolean;
focus(): void;
getActions(): IAction[];
getSecondaryActions(): IAction[];
getActionViewItem(action: IAction): IActionViewItem | undefined;
saveState(): void;
}

View File

@@ -15,10 +15,9 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { values, keys, getOrSet } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IAction } from 'vs/base/common/actions';
import { IAction, IActionViewItem } from 'vs/base/common/actions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { flatten } from 'vs/base/common/arrays';
import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test';
@@ -188,6 +187,10 @@ export interface IViewDescriptor {
readonly canToggleVisibility?: boolean;
readonly canMoveView?: boolean;
readonly containerIcon?: string | URI;
// Applies only to newly created views
readonly hideByDefault?: boolean;
@@ -353,6 +356,8 @@ export const IViewsService = createDecorator<IViewsService>('viewsService');
export interface IViewsService {
_serviceBrand: undefined;
getActiveViewWithId(id: string): IView | null;
openView(id: string, focus?: boolean): Promise<IView | null>;
}
@@ -361,11 +366,19 @@ export interface IViewDescriptorService {
_serviceBrand: undefined;
moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void;
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void;
moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void;
getViewDescriptors(container: ViewContainer): IViewDescriptorCollection;
getViewDescriptor(viewId: string): IViewDescriptor | null;
getViewContainer(viewId: string): ViewContainer | null;
getViewLocation(viewId: string): ViewContainerLocation | null;
getDefaultContainer(viewId: string): ViewContainer | null;
}
// Custom views
@@ -495,3 +508,15 @@ export interface IEditableData {
startingValue?: string | null;
onFinish: (value: string, success: boolean) => void;
}
export interface IViewPaneContainer {
setVisible(visible: boolean): void;
isVisible(): boolean;
focus(): void;
getActions(): IAction[];
getSecondaryActions(): IAction[];
getActionViewItem(action: IAction): IActionViewItem | undefined;
getView(viewId: string): IView | undefined;
saveState(): void;
}