Path: blob/main/src/vs/workbench/common/editor/textResourceEditorInput.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { DEFAULT_EDITOR_ASSOCIATION, GroupIdentifier, IRevertOptions, isResourceEditorInput, IUntypedEditorInput } from '../editor.js';6import { EditorInput } from './editorInput.js';7import { AbstractResourceEditorInput } from './resourceEditorInput.js';8import { URI } from '../../../base/common/uri.js';9import { ITextFileService, ITextFileSaveOptions, ILanguageSupport } from '../../services/textfile/common/textfiles.js';10import { IEditorService } from '../../services/editor/common/editorService.js';11import { IFileService } from '../../../platform/files/common/files.js';12import { ILabelService } from '../../../platform/label/common/label.js';13import { Schemas } from '../../../base/common/network.js';14import { isEqual } from '../../../base/common/resources.js';15import { ITextEditorModel, ITextModelService } from '../../../editor/common/services/resolverService.js';16import { TextResourceEditorModel } from './textResourceEditorModel.js';17import { IReference } from '../../../base/common/lifecycle.js';18import { createTextBufferFactory } from '../../../editor/common/model/textModel.js';19import { IFilesConfigurationService } from '../../services/filesConfiguration/common/filesConfigurationService.js';20import { ITextResourceConfigurationService } from '../../../editor/common/services/textResourceConfiguration.js';21import { ICustomEditorLabelService } from '../../services/editor/common/customEditorLabelService.js';2223/**24* The base class for all editor inputs that open in text editors.25*/26export abstract class AbstractTextResourceEditorInput extends AbstractResourceEditorInput {2728constructor(29resource: URI,30preferredResource: URI | undefined,31@IEditorService protected readonly editorService: IEditorService,32@ITextFileService protected readonly textFileService: ITextFileService,33@ILabelService labelService: ILabelService,34@IFileService fileService: IFileService,35@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,36@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,37@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService38) {39super(resource, preferredResource, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);40}4142override save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IUntypedEditorInput | undefined> {4344// If this is neither an `untitled` resource, nor a resource45// we can handle with the file service, we can only "Save As..."46if (this.resource.scheme !== Schemas.untitled && !this.fileService.hasProvider(this.resource)) {47return this.saveAs(group, options);48}4950// Normal save51return this.doSave(options, false, group);52}5354override saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise<IUntypedEditorInput | undefined> {55return this.doSave(options, true, group);56}5758private async doSave(options: ITextFileSaveOptions | undefined, saveAs: boolean, group: GroupIdentifier | undefined): Promise<IUntypedEditorInput | undefined> {5960// Save / Save As61let target: URI | undefined;62if (saveAs) {63target = await this.textFileService.saveAs(this.resource, undefined, { ...options, suggestedTarget: this.preferredResource });64} else {65target = await this.textFileService.save(this.resource, options);66}6768if (!target) {69return undefined; // save cancelled70}7172return { resource: target };73}7475override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {76await this.textFileService.revert(this.resource, options);77}78}7980/**81* A read-only text editor input whos contents are made of the provided resource that points to an existing82* code editor model.83*/84export class TextResourceEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport {8586static readonly ID: string = 'workbench.editors.resourceEditorInput';8788override get typeId(): string {89return TextResourceEditorInput.ID;90}9192override get editorId(): string | undefined {93return DEFAULT_EDITOR_ASSOCIATION.id;94}9596private cachedModel: TextResourceEditorModel | undefined = undefined;97private modelReference: Promise<IReference<ITextEditorModel>> | undefined = undefined;9899constructor(100resource: URI,101private name: string | undefined,102private description: string | undefined,103private preferredLanguageId: string | undefined,104private preferredContents: string | undefined,105@ITextModelService private readonly textModelService: ITextModelService,106@ITextFileService textFileService: ITextFileService,107@IEditorService editorService: IEditorService,108@IFileService fileService: IFileService,109@ILabelService labelService: ILabelService,110@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,111@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,112@ICustomEditorLabelService customEditorLabelService: ICustomEditorLabelService113) {114super(resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService, textResourceConfigurationService, customEditorLabelService);115}116117override getName(): string {118return this.name || super.getName();119}120121setName(name: string): void {122if (this.name !== name) {123this.name = name;124125this._onDidChangeLabel.fire();126}127}128129override getDescription(): string | undefined {130return this.description;131}132133setDescription(description: string): void {134if (this.description !== description) {135this.description = description;136137this._onDidChangeLabel.fire();138}139}140141setLanguageId(languageId: string, source?: string): void {142this.setPreferredLanguageId(languageId);143144this.cachedModel?.setLanguageId(languageId, source);145}146147setPreferredLanguageId(languageId: string): void {148this.preferredLanguageId = languageId;149}150151setPreferredContents(contents: string): void {152this.preferredContents = contents;153}154155override async resolve(): Promise<ITextEditorModel> {156157// Unset preferred contents and language after resolving158// once to prevent these properties to stick. We still159// want the user to change the language in the editor160// and want to show updated contents (if any) in future161// `resolve` calls.162const preferredContents = this.preferredContents;163const preferredLanguageId = this.preferredLanguageId;164this.preferredContents = undefined;165this.preferredLanguageId = undefined;166167if (!this.modelReference) {168this.modelReference = this.textModelService.createModelReference(this.resource);169}170171const ref = await this.modelReference;172173// Ensure the resolved model is of expected type174const model = ref.object;175if (!(model instanceof TextResourceEditorModel)) {176ref.dispose();177this.modelReference = undefined;178179throw new Error(`Unexpected model for TextResourceEditorInput: ${this.resource}`);180}181182this.cachedModel = model;183184// Set contents and language if preferred185if (typeof preferredContents === 'string' || typeof preferredLanguageId === 'string') {186model.updateTextEditorModel(typeof preferredContents === 'string' ? createTextBufferFactory(preferredContents) : undefined, preferredLanguageId);187}188189return model;190}191192override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {193if (this === otherInput) {194return true;195}196197if (otherInput instanceof TextResourceEditorInput) {198return isEqual(otherInput.resource, this.resource);199}200201if (isResourceEditorInput(otherInput)) {202return super.matches(otherInput);203}204205return false;206}207208override dispose(): void {209if (this.modelReference) {210this.modelReference.then(ref => ref.dispose());211this.modelReference = undefined;212}213214this.cachedModel = undefined;215216super.dispose();217}218}219220221