mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229 (#8962)
* Merge from vscode 3c6f6af7347d38e87bc6406024e8dcf9e9bce229 * skip failing tests * update mac build image
This commit is contained in:
committed by
Karl Burtram
parent
0eaee18dc4
commit
fefe1454de
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user