diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index f16e22ba484..ebe12cf4fc7 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DEFAULT_EDITOR_ASSOCIATION, GroupIdentifier, IEditorInput, IRevertOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, GroupIdentifier, IEditorInput, IRevertOptions, isEditorInputWithOptionsAndGroup, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { AbstractResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; import { ITextFileService, ITextFileSaveOptions, IModeSupport } from 'vs/workbench/services/textfile/common/textfiles'; @@ -16,6 +16,7 @@ import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/r import { TextResourceEditorModel } from 'vs/workbench/common/editor/textResourceEditorModel'; import { IReference } from 'vs/base/common/lifecycle'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; /** * The base class for all editor inputs that open in text editors. @@ -28,7 +29,8 @@ export abstract class AbstractTextResourceEditorInput extends AbstractResourceEd @IEditorService protected readonly editorService: IEditorService, @ITextFileService protected readonly textFileService: ITextFileService, @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IEditorResolverService private readonly editorResolverService: IEditorResolverService ) { super(resource, preferredResource, labelService, fileService); } @@ -42,14 +44,14 @@ export abstract class AbstractTextResourceEditorInput extends AbstractResourceEd } // Normal save - return this.doSave(options, false); + return this.doSave(options, false, group); } override saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSave(options, true); + return this.doSave(options, true, group); } - private async doSave(options: ITextFileSaveOptions | undefined, saveAs: boolean): Promise { + private async doSave(options: ITextFileSaveOptions | undefined, saveAs: boolean, group: GroupIdentifier | undefined): Promise { // Save / Save As let target: URI | undefined; @@ -70,7 +72,10 @@ export abstract class AbstractTextResourceEditorInput extends AbstractResourceEd target.scheme !== this.resource.scheme || (saveAs && !isEqual(target, this.preferredResource)) ) { - return this.editorService.createEditorInput({ resource: target }); + const editor = await this.editorResolverService.resolveEditor({ resource: target, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }, group); + if (isEditorInputWithOptionsAndGroup(editor)) { + return editor.editor; + } } return this; @@ -120,9 +125,10 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IEditorResolverService editorResolverService: IEditorResolverService ) { - super(resource, undefined, editorService, textFileService, labelService, fileService); + super(resource, undefined, editorService, textFileService, labelService, fileService, editorResolverService); } override getName(skipDecorate?: boolean): string { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 7478e0fc1f2..55db304fce5 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -24,7 +24,7 @@ import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/c import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @@ -38,7 +38,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { ): IEditorInput { return instantiationService.invokeFunction(accessor => { if (viewType === defaultCustomEditor.id) { - return accessor.get(IEditorService).createEditorInput({ resource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }); + return accessor.get(ITextEditorService).createTextEditor({ resource }); } // If it's an untitled file we must populate the untitledDocumentData const untitledString = accessor.get(IUntitledTextEditorService).getValue(resource); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index ce2e0d1fe7e..4efa21c7eb8 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -26,6 +26,7 @@ import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/comm import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService, IEditorType, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { ContributedCustomEditors } from '../common/contributedCustomEditors'; import { CustomEditorInput } from './customEditorInput'; @@ -57,6 +58,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ @IInstantiationService private readonly instantiationService: IInstantiationService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @ITextEditorService private readonly textEditorService: ITextEditorService ) { super(); @@ -153,8 +155,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return input instanceof EditorInput ? input : undefined; }; - const modifiedOverride = createEditorForSubInput(editor.modified, editorID, 'modified') ?? this.editorService.createEditorInput(editor.modified); - const originalOverride = createEditorForSubInput(editor.original, editorID, 'original') ?? this.editorService.createEditorInput(editor.original); + const modifiedOverride = createEditorForSubInput(editor.modified, editorID, 'modified') ?? this.textEditorService.createTextEditor(editor.modified); + const originalOverride = createEditorForSubInput(editor.original, editorID, 'original') ?? this.textEditorService.createTextEditor(editor.original); return this.instantiationService.createInstance(DiffEditorInput, undefined, undefined, originalOverride, modifiedOverride, true); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts index 028bae1ecf6..e0c2c23fb11 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorHandler.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { isEqual } from 'vs/base/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -57,7 +57,7 @@ export class FileEditorInputSerializer implements IEditorSerializer { const encoding = serializedFileEditorInput.encoding; const mode = serializedFileEditorInput.modeId; - const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, label: name, description, encoding, mode, forceFile: true }) as FileEditorInput; + const fileEditorInput = accessor.get(ITextEditorService).createTextEditor({ resource, label: name, description, encoding, mode, forceFile: true }) as FileEditorInput; if (preferredResource) { fileEditorInput.setPreferredResource(preferredResource); } @@ -71,7 +71,7 @@ export class FileEditorWorkingCopyEditorHandler extends Disposable implements IW constructor( @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, - @IEditorService private readonly editorService: IEditorService, + @ITextEditorService private readonly textEditorService: ITextEditorService, @IFileService private readonly fileService: IFileService ) { super(); @@ -86,7 +86,7 @@ export class FileEditorWorkingCopyEditorHandler extends Disposable implements IW // but because some custom editors also leverage text file based working copies // we need to do a weaker check by only comparing for the resource isOpen: (workingCopy, editor) => isEqual(workingCopy.resource, editor.resource), - createEditor: workingCopy => this.editorService.createEditorInput({ resource: workingCopy.resource, forceFile: true }) + createEditor: workingCopy => this.textEditorService.createTextEditor({ resource: workingCopy.resource, forceFile: true }) })); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index c496d8f7957..1cb0c0ac5ba 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -22,6 +22,7 @@ import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; const enum ForceOpenAs { None, @@ -90,9 +91,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements @IFileService fileService: IFileService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IEditorService editorService: IEditorService, - @IPathService private readonly pathService: IPathService + @IPathService private readonly pathService: IPathService, + @IEditorResolverService editorResolverService: IEditorResolverService ) { - super(resource, preferredResource, editorService, textFileService, labelService, fileService); + super(resource, preferredResource, editorService, textFileService, labelService, fileService, editorResolverService); this.model = this.textFileService.files.get(resource); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 7734477dee0..e6fd1a534ef 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; -import { workbenchInstantiationService, TestServiceAccessor, TestEditorService, getLastResolvedFileStat } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, getLastResolvedFileStat } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorFactoryRegistry, Verbosity, EditorExtensions, EditorInputCapabilities } from 'vs/workbench/common/editor'; import { EncodingMode, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; @@ -21,6 +21,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInputSerializer } from 'vs/workbench/contrib/files/browser/editors/fileEditorHandler'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { TextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; suite('Files - FileEditorInput', () => { @@ -31,15 +32,15 @@ suite('Files - FileEditorInput', () => { return instantiationService.createInstance(FileEditorInput, resource, preferredResource, preferredName, preferredDescription, undefined, preferredMode, preferredContents); } + class TestTextEditorService extends TextEditorService { + override createTextEditor(input: IResourceEditorInput) { + return createFileInput(input.resource); + } + } + setup(() => { instantiationService = workbenchInstantiationService({ - editorService: () => { - return new class extends TestEditorService { - override createEditorInput(input: IResourceEditorInput) { - return createFileInput(input.resource); - } - }; - } + textEditorService: instantiationService => instantiationService.createInstance(TestTextEditorService) }); accessor = instantiationService.createInstance(TestServiceAccessor); diff --git a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts index da47294029b..65dd2ea8bf6 100644 --- a/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/textFileEditorTracker.test.ts @@ -154,7 +154,7 @@ suite('Files - TextFileEditorTracker', () => { test('dirty untitled text file model opens as editor', async function () { const accessor = await createTracker(); - const untitledTextEditor = accessor.editorService.createEditorInput({ resource: undefined, forceUntitled: true }) as UntitledTextEditorInput; + const untitledTextEditor = accessor.textEditorService.createTextEditor({ resource: undefined, forceUntitled: true }) as UntitledTextEditorInput; const model = disposables.add(await untitledTextEditor.resolve()); assert.ok(!accessor.editorService.isOpened(untitledTextEditor)); @@ -174,7 +174,7 @@ suite('Files - TextFileEditorTracker', () => { const resource = toResource.call(this, '/path/index.txt'); - await accessor.editorService.openEditor(accessor.editorService.createEditorInput({ resource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } })); + await accessor.editorService.openEditor(accessor.textEditorService.createTextEditor({ resource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } })); accessor.hostService.setFocus(false); accessor.hostService.setFocus(true); diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 0072d644254..b629e91e4d3 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -22,6 +22,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; export class LogViewerInput extends TextResourceEditorInput { @@ -37,7 +38,8 @@ export class LogViewerInput extends TextResourceEditorInput { @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IEditorResolverService editorResolverService: IEditorResolverService ) { super( URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), @@ -49,7 +51,8 @@ export class LogViewerInput extends TextResourceEditorInput { textFileService, editorService, fileService, - labelService + labelService, + editorResolverService ); } } diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 9537f7da032..e4fc0e6573e 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -24,6 +24,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ByteSize, IFileService } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; import { isWeb } from 'vs/base/common/platform'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; export class PerfviewContrib { @@ -55,7 +56,8 @@ export class PerfviewInput extends TextResourceEditorInput { @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IEditorResolverService editorResolverService: IEditorResolverService ) { super( PerfviewInput.Uri, @@ -67,7 +69,8 @@ export class PerfviewInput extends TextResourceEditorInput { textFileService, editorService, fileService, - labelService + labelService, + editorResolverService ); } } diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 6965622d372..8c5ede72481 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -22,7 +22,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputWithOptions } from 'vs/workbench/common/editor'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { RegisteredEditorPriority, IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -40,7 +40,7 @@ export class PreferencesContribution implements IWorkbenchContribution { @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @IEditorService private readonly editorService: IEditorService, + @ITextEditorService private readonly textEditorService: ITextEditorService ) { this.settingsListener = this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(USE_SPLIT_JSON_SETTING) || e.affectsConfiguration(DEFAULT_SETTINGS_EDITOR_SETTING)) { @@ -94,7 +94,7 @@ export class PreferencesContribution implements IWorkbenchContribution { } } - return { editor: this.editorService.createEditorInput({ resource }), options }; + return { editor: this.textEditorService.createTextEditor({ resource }), options }; } ); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index e3e10eb5b49..139fc8d9a38 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,18 +5,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, IEditorOptions, EditorActivation, EditorResolution, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IFileEditorInput, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorFactoryRegistry, EditorExtensions, IEditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, isResourceSideBySideEditorInput, IUntypedFileEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { SideBySideEditor, IEditorInput, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup } from 'vs/workbench/common/editor'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; -import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; import { Event, Emitter, DebounceEmitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename, joinPath } from 'vs/base/common/resources'; +import { joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, isEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IUntypedEditorReplacement, IEditorService, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions, IOpenEditorsOptions, PreferredGroup, isPreferredGroup } from 'vs/workbench/services/editor/common/editorService'; @@ -28,19 +23,16 @@ import { IEditorGroupView, EditorServiceImpl } from 'vs/workbench/browser/parts/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { isUndefined, withNullAsUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; -import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { Promises, timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { indexOfPath } from 'vs/base/common/extpath'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; -import { RegisteredEditorPriority, IEditorResolverService, ResolvedStatus } from 'vs/workbench/services/editor/common/editorResolverService'; +import { IEditorResolverService, ResolvedStatus } from 'vs/workbench/services/editor/common/editorResolverService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkspaceTrustRequestService, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { findGroup } from 'vs/workbench/services/editor/common/editorGroupFinder'; - -type CachedEditorInput = TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; export class EditorService extends Disposable implements EditorServiceImpl { @@ -68,11 +60,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - private readonly fileEditorFactory = Registry.as(EditorExtensions.EditorFactory).getFileEditorFactory(); - constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -82,16 +71,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService, @IHostService private readonly hostService: IHostService, + @ITextEditorService private readonly textEditorService: ITextEditorService ) { super(); this.onConfigurationUpdated(configurationService.getValue()); this.registerListeners(); - - // Register the default editor to the editor resolver - // service so that it shows up in the editors picker - this.registerDefaultEditor(); } private registerListeners(): void { @@ -118,22 +104,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); } - private registerDefaultEditor(): void { - this._register(this.editorResolverService.registerEditor( - '*', - { - id: DEFAULT_EDITOR_ASSOCIATION.id, - label: DEFAULT_EDITOR_ASSOCIATION.displayName, - detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, - priority: RegisteredEditorPriority.builtin - }, - {}, - editor => ({ editor: this.createEditorInput(editor) }), - untitledEditor => ({ editor: this.createEditorInput(untitledEditor) }), - diffEditor => ({ editor: this.createEditorInput(diffEditor) }) - )); - } - //#region Editor & group event handlers private lastActiveEditor: IEditorInput | undefined = undefined; @@ -559,9 +529,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - // Override is disabled or did not apply + // Override is disabled or did not apply: fallback to default if (!typedEditor) { - typedEditor = isEditorInput(editor) ? editor : this.createEditorInput(editor); + typedEditor = isEditorInput(editor) ? editor : this.textEditorService.createTextEditor(editor); } // If group still isn't defined because of a disabled override we resolve it @@ -618,9 +588,9 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - // Override is disabled or did not apply + // Override is disabled or did not apply: fallback to default if (!typedEditor) { - typedEditor = isEditorInputWithOptions(editor) ? editor : { editor: this.createEditorInput(editor), options: editor.options }; + typedEditor = isEditorInputWithOptions(editor) ? editor : { editor: this.textEditorService.createTextEditor(editor), options: editor.options }; } // If group still isn't defined because of a disabled override we resolve it @@ -863,11 +833,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - // Override is disabled or did not apply + // Override is disabled or did not apply: fallback to default if (!typedReplacement) { typedReplacement = { editor: replacement.editor, - replacement: isEditorReplacement(replacement) ? replacement.replacement : this.createEditorInput(replacement.replacement), + replacement: isEditorReplacement(replacement) ? replacement.replacement : this.textEditorService.createTextEditor(replacement.replacement), options: isEditorReplacement(replacement) ? replacement.options : replacement.replacement.options, forceReplaceDirty: replacement.forceReplaceDirty }; @@ -881,174 +851,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region createEditorInput() - - private readonly editorInputCache = new ResourceMap(); - - createEditorInput(input: IEditorInput | IUntypedEditorInput): EditorInput; - createEditorInput(input: IUntypedFileEditorInput): IFileEditorInput; - createEditorInput(input: IEditorInput | IUntypedEditorInput | IUntypedFileEditorInput): EditorInput | IFileEditorInput { - - // Typed Editor Input Support (EditorInput) - if (input instanceof EditorInput) { - return input; - } - - // Diff Editor Support - if (isResourceDiffEditorInput(input)) { - const original = this.createEditorInput({ ...input.original }); - const modified = this.createEditorInput({ ...input.modified }); - - return this.instantiationService.createInstance(DiffEditorInput, input.label, input.description, original, modified, undefined); - } - - // Side by Side Editor Support - if (isResourceSideBySideEditorInput(input)) { - const primary = this.createEditorInput({ ...input.primary }); - const secondary = this.createEditorInput({ ...input.secondary }); - - return new SideBySideEditorInput(input.label, input.description, secondary, primary); - } - - // Untitled file support - const untitledInput = input as IUntitledTextResourceEditorInput; - if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource.scheme === Schemas.untitled)) { - const untitledOptions = { - mode: untitledInput.mode, - initialValue: untitledInput.contents, - encoding: untitledInput.encoding - }; - - // Untitled resource: use as hint for an existing untitled editor - let untitledModel: IUntitledTextEditorModel; - if (untitledInput.resource?.scheme === Schemas.untitled) { - untitledModel = this.untitledTextEditorService.create({ untitledResource: untitledInput.resource, ...untitledOptions }); - } - - // Other resource: use as hint for associated filepath - else { - untitledModel = this.untitledTextEditorService.create({ associatedResource: untitledInput.resource, ...untitledOptions }); - } - - return this.createOrGetCached(untitledModel.resource, () => { - - // Factory function for new untitled editor - const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledModel); - - // We dispose the untitled model once the editor - // is being disposed. Even though we may have not - // created the model initially, the lifecycle for - // untitled is tightly coupled with the editor - // lifecycle for now. - Event.once(input.onWillDispose)(() => untitledModel.dispose()); - - return input; - }) as EditorInput; - } - - // Text File/Resource Editor Support - const textResourceEditorInput = input as IUntypedFileEditorInput; - if (textResourceEditorInput.resource instanceof URI) { - - // Derive the label from the path if not provided explicitly - const label = textResourceEditorInput.label || basename(textResourceEditorInput.resource); - - // We keep track of the preferred resource this input is to be created - // with but it may be different from the canonical resource (see below) - const preferredResource = textResourceEditorInput.resource; - - // From this moment on, only operate on the canonical resource - // to ensure we reduce the chance of opening the same resource - // with different resource forms (e.g. path casing on Windows) - const canonicalResource = this.uriIdentityService.asCanonicalUri(preferredResource); - - return this.createOrGetCached(canonicalResource, () => { - - // File - if (textResourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) { - return this.fileEditorFactory.createFileEditor(canonicalResource, preferredResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.encoding, textResourceEditorInput.mode, textResourceEditorInput.contents, this.instantiationService); - } - - // Resource - return this.instantiationService.createInstance(TextResourceEditorInput, canonicalResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.mode, textResourceEditorInput.contents); - }, cachedInput => { - - // Untitled - if (cachedInput instanceof UntitledTextEditorInput) { - return; - } - - // Files - else if (!(cachedInput instanceof TextResourceEditorInput)) { - cachedInput.setPreferredResource(preferredResource); - - if (textResourceEditorInput.label) { - cachedInput.setPreferredName(textResourceEditorInput.label); - } - - if (textResourceEditorInput.description) { - cachedInput.setPreferredDescription(textResourceEditorInput.description); - } - - if (textResourceEditorInput.encoding) { - cachedInput.setPreferredEncoding(textResourceEditorInput.encoding); - } - - if (textResourceEditorInput.mode) { - cachedInput.setPreferredMode(textResourceEditorInput.mode); - } - - if (typeof textResourceEditorInput.contents === 'string') { - cachedInput.setPreferredContents(textResourceEditorInput.contents); - } - } - - // Resources - else { - if (label) { - cachedInput.setName(label); - } - - if (textResourceEditorInput.description) { - cachedInput.setDescription(textResourceEditorInput.description); - } - - if (textResourceEditorInput.mode) { - cachedInput.setPreferredMode(textResourceEditorInput.mode); - } - - if (typeof textResourceEditorInput.contents === 'string') { - cachedInput.setPreferredContents(textResourceEditorInput.contents); - } - } - }) as EditorInput; - } - - throw new Error('Unknown input type'); - } - - private createOrGetCached(resource: URI, factoryFn: () => CachedEditorInput, cachedFn?: (input: CachedEditorInput) => void): CachedEditorInput { - - // Return early if already cached - let input = this.editorInputCache.get(resource); - if (input) { - if (cachedFn) { - cachedFn(input); - } - - return input; - } - - // Otherwise create and add to cache - input = factoryFn(); - this.editorInputCache.set(resource, input); - Event.once(input.onWillDispose)(() => this.editorInputCache.delete(resource)); - - return input; - } - - //#endregion - //#region save/revert async save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 56a1bd69ec5..9f7ea14d823 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -5,12 +5,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResourceEditorInput, IEditorOptions, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, IFileEditorInput, IUntypedFileEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorPane, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, ITextDiffEditorPane, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement, isEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { URI } from 'vs/base/common/uri'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; export const IEditorService = createDecorator('editorService'); @@ -264,12 +263,6 @@ export interface IEditorService { findEditors(resource: URI, group: IEditorGroup | GroupIdentifier): readonly IEditorInput[]; findEditors(editor: IResourceEditorInputIdentifier, group: IEditorGroup | GroupIdentifier): IEditorInput | undefined; - /** - * Converts a lightweight input to a workbench editor input. - */ - createEditorInput(input: IUntypedEditorInput): EditorInput; - createEditorInput(input: IUntypedFileEditorInput): IFileEditorInput; - /** * Save the provided list of editors. * diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 33d3f632ae5..283838f47c7 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,32 +7,23 @@ import * as assert from 'assert'; import { EditorActivation, EditorResolution, IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; -import { DEFAULT_EDITOR_ASSOCIATION, EditorsOrder, IEditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, IResourceSideBySideEditorInput, isEditorInputWithOptions, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorsOrder, IEditorInputWithOptions, IEditorPane, IResourceDiffEditorInput, isEditorInputWithOptions, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestServiceAccessor, registerTestEditor, TestFileEditorInput, ITestInstantiationService, registerTestResourceEditor, registerTestSideBySideEditor, createEditorPart, registerTestFileEditor, TestEditorWithOptions, TestTextFileEditor } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { ACTIVE_GROUP, IEditorService, PreferredGroup, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; -import { toResource } from 'vs/base/test/common/utils'; -import { IFileService, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; -import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; -import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { isLinux } from 'vs/base/common/platform'; +import { FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { MockScopableContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IWorkspaceTrustRequestService, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; import { TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { UnknownErrorEditor } from 'vs/workbench/browser/parts/editor/editorPlaceholder'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -42,14 +33,6 @@ suite('EditorService', () => { const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; - class FileServiceProvider extends Disposable { - constructor(scheme: string, @IFileService fileService: IFileService) { - super(); - - this._register(fileService.registerProvider(scheme, new NullFileSystemProvider())); - } - } - const disposables = new DisposableStore(); setup(() => { @@ -1523,191 +1506,6 @@ suite('EditorService', () => { } }); - test('caching', function () { - const instantiationService = workbenchInstantiationService(); - const service = instantiationService.createInstance(EditorService); - - // Cached Input (Files) - const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); - const fileEditorInput1 = service.createEditorInput({ resource: fileResource1 }); - assert.ok(fileEditorInput1); - - const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); - const fileEditorInput2 = service.createEditorInput({ resource: fileResource2 }); - assert.ok(fileEditorInput2); - - assert.notStrictEqual(fileEditorInput1, fileEditorInput2); - - const fileEditorInput1Again = service.createEditorInput({ resource: fileResource1 }); - assert.strictEqual(fileEditorInput1Again, fileEditorInput1); - - fileEditorInput1Again.dispose(); - - assert.ok(fileEditorInput1.isDisposed()); - - const fileEditorInput1AgainAndAgain = service.createEditorInput({ resource: fileResource1 }); - assert.notStrictEqual(fileEditorInput1AgainAndAgain, fileEditorInput1); - assert.ok(!fileEditorInput1AgainAndAgain.isDisposed()); - - // Cached Input (Resource) - const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); - const input1 = service.createEditorInput({ resource: resource1 }); - assert.ok(input1); - - const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); - const input2 = service.createEditorInput({ resource: resource2 }); - assert.ok(input2); - - assert.notStrictEqual(input1, input2); - - const input1Again = service.createEditorInput({ resource: resource1 }); - assert.strictEqual(input1Again, input1); - - input1Again.dispose(); - - assert.ok(input1.isDisposed()); - - const input1AgainAndAgain = service.createEditorInput({ resource: resource1 }); - assert.notStrictEqual(input1AgainAndAgain, input1); - assert.ok(!input1AgainAndAgain.isDisposed()); - }); - - test('createEditorInput', async function () { - const instantiationService = workbenchInstantiationService(); - const service = instantiationService.createInstance(EditorService); - - const mode = 'create-input-test'; - ModesRegistry.registerLanguage({ - id: mode, - }); - - // Untyped Input (file) - let input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof FileEditorInput); - let contentInput = input; - assert.strictEqual(contentInput.resource.fsPath, toResource.call(this, '/index.html').fsPath); - - // Untyped Input (file casing) - input = service.createEditorInput({ resource: toResource.call(this, '/index.html') }); - let inputDifferentCase = service.createEditorInput({ resource: toResource.call(this, '/INDEX.html') }); - - if (!isLinux) { - assert.strictEqual(input, inputDifferentCase); - assert.strictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString()); - } else { - assert.notStrictEqual(input, inputDifferentCase); - assert.notStrictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString()); - } - - // Typed Input - assert.strictEqual(service.createEditorInput(input), input); - - // Untyped Input (file, encoding) - input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof FileEditorInput); - contentInput = input; - assert.strictEqual(contentInput.getPreferredEncoding(), 'utf16le'); - - // Untyped Input (file, mode) - input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode }); - assert(input instanceof FileEditorInput); - contentInput = input; - assert.strictEqual(contentInput.getPreferredMode(), mode); - let fileModel = (await contentInput.resolve() as ITextFileEditorModel); - assert.strictEqual(fileModel.textEditorModel?.getModeId(), mode); - - // Untyped Input (file, contents) - input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), contents: 'My contents' }); - assert(input instanceof FileEditorInput); - contentInput = input; - fileModel = (await contentInput.resolve() as ITextFileEditorModel); - assert.strictEqual(fileModel.textEditorModel?.getValue(), 'My contents'); - assert.strictEqual(fileModel.isDirty(), true); - - // Untyped Input (file, different mode) - input = service.createEditorInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); - assert(input instanceof FileEditorInput); - contentInput = input; - assert.strictEqual(contentInput.getPreferredMode(), 'text'); - - // Untyped Input (untitled) - input = service.createEditorInput({ resource: undefined, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledTextEditorInput); - - // Untyped Input (untitled with contents) - let untypedInput: any = { contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }; - input = service.createEditorInput(untypedInput); - assert.ok(isUntitledResourceEditorInput(untypedInput)); - assert(input instanceof UntitledTextEditorInput); - let model = await input.resolve() as UntitledTextEditorModel; - assert.strictEqual(model.textEditorModel?.getValue(), 'Hello Untitled'); - - // Untyped Input (untitled withtoUntyped2 - input = service.createEditorInput({ resource: undefined, mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledTextEditorInput); - model = await input.resolve() as UntitledTextEditorModel; - assert.strictEqual(model.getMode(), mode); - - // Untyped Input (untitled with file path) - input = service.createEditorInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); - - // Untyped Input (untitled with untitled resource) - untypedInput = { resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }; - assert.ok(isUntitledResourceEditorInput(untypedInput)); - input = service.createEditorInput(untypedInput); - assert(input instanceof UntitledTextEditorInput); - assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); - - // Untyped input (untitled with custom resource, but forceUntitled) - untypedInput = { resource: URI.file('/fake'), forceUntitled: true }; - assert.ok(isUntitledResourceEditorInput(untypedInput)); - input = service.createEditorInput(untypedInput); - assert(input instanceof UntitledTextEditorInput); - - // Untyped Input (untitled with custom resource) - const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); - - input = service.createEditorInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); - assert(input instanceof UntitledTextEditorInput); - assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); - - provider.dispose(); - - // Untyped Input (resource) - input = service.createEditorInput({ resource: URI.parse('custom:resource') }); - assert(input instanceof TextResourceEditorInput); - - // Untyped Input (diff) - const resourceDiffInput = { - modified: { resource: toResource.call(this, '/modified.html') }, - original: { resource: toResource.call(this, '/original.html') } - }; - assert.strictEqual(isResourceDiffEditorInput(resourceDiffInput), true); - input = service.createEditorInput(resourceDiffInput); - assert(input instanceof DiffEditorInput); - assert.strictEqual(input.original.resource?.toString(), resourceDiffInput.original.resource.toString()); - assert.strictEqual(input.modified.resource?.toString(), resourceDiffInput.modified.resource.toString()); - const untypedDiffInput = input.toUntyped() as IResourceDiffEditorInput; - assert.strictEqual(untypedDiffInput.original.resource?.toString(), resourceDiffInput.original.resource.toString()); - assert.strictEqual(untypedDiffInput.modified.resource?.toString(), resourceDiffInput.modified.resource.toString()); - - // Untyped Input (side by side) - const sideBySideResourceInput = { - primary: { resource: toResource.call(this, '/primary.html') }, - secondary: { resource: toResource.call(this, '/secondary.html') } - }; - assert.strictEqual(isResourceSideBySideEditorInput(sideBySideResourceInput), true); - input = service.createEditorInput(sideBySideResourceInput); - assert(input instanceof SideBySideEditorInput); - assert.strictEqual(input.primary.resource?.toString(), sideBySideResourceInput.primary.resource.toString()); - assert.strictEqual(input.secondary.resource?.toString(), sideBySideResourceInput.secondary.resource.toString()); - const untypedSideBySideInput = input.toUntyped() as IResourceSideBySideEditorInput; - assert.strictEqual(untypedSideBySideInput.primary.resource?.toString(), sideBySideResourceInput.primary.resource.toString()); - assert.strictEqual(untypedSideBySideInput.secondary.resource?.toString(), sideBySideResourceInput.secondary.resource.toString()); - }); - test('close editor does not dispose when editor opened in other group', async () => { const [part, service] = await createEditorService(); @@ -2390,7 +2188,10 @@ suite('EditorService', () => { test('editorResolverService - openEditor', async function () { const [, service, accessor] = await createEditorService(); const editorResolverService = accessor.editorResolverService; + const textEditorService = accessor.textEditorService; + let editorCount = 0; + const registrationDisposable = editorResolverService.registerEditor( '*.md', { @@ -2402,20 +2203,24 @@ suite('EditorService', () => { {}, (editorInput) => { editorCount++; - return ({ editor: service.createEditorInput(editorInput) }); + return ({ editor: textEditorService.createTextEditor(editorInput) }); }, undefined, - diffEditor => ({ editor: service.createEditorInput(diffEditor) }) + diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) ); assert.strictEqual(editorCount, 0); + const input1 = { resource: URI.parse('file://test/path/resource1.txt') }; const input2 = { resource: URI.parse('file://test/path/resource1.md') }; + // Open editor input 1 and it shouln't trigger override as the glob doesn't match await service.openEditor(input1); assert.strictEqual(editorCount, 0); + // Open editor input 2 and it should trigger override as the glob doesn match await service.openEditor(input2); assert.strictEqual(editorCount, 1); + // Because we specify an override we shouldn't see it triggered even if it matches await service.openEditor({ ...input2, options: { override: 'default' } }); assert.strictEqual(editorCount, 1); @@ -2426,7 +2231,10 @@ suite('EditorService', () => { test('editorResolverService - openEditors', async function () { const [, service, accessor] = await createEditorService(); const editorResolverService = accessor.editorResolverService; + const textEditorService = accessor.textEditorService; + let editorCount = 0; + const registrationDisposable = editorResolverService.registerEditor( '*.md', { @@ -2438,16 +2246,18 @@ suite('EditorService', () => { {}, (editorInput) => { editorCount++; - return ({ editor: service.createEditorInput(editorInput) }); + return ({ editor: textEditorService.createTextEditor(editorInput) }); }, undefined, - diffEditor => ({ editor: service.createEditorInput(diffEditor) }) + diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) ); assert.strictEqual(editorCount, 0); + const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource1.txt'), TEST_EDITOR_INPUT_ID); const input2 = new TestFileEditorInput(URI.parse('file://test/path/resource2.txt'), TEST_EDITOR_INPUT_ID); const input3 = new TestFileEditorInput(URI.parse('file://test/path/resource3.md'), TEST_EDITOR_INPUT_ID); const input4 = new TestFileEditorInput(URI.parse('file://test/path/resource4.md'), TEST_EDITOR_INPUT_ID); + // Open editor input 1 and it shouln't trigger override as the glob doesn't match await service.openEditors([{ editor: input1 }, { editor: input2 }, { editor: input3 }, { editor: input4 }]); assert.strictEqual(editorCount, 2); @@ -2458,7 +2268,10 @@ suite('EditorService', () => { test('editorResolverService - replaceEditors', async function () { const [part, service, accessor] = await createEditorService(); const editorResolverService = accessor.editorResolverService; + const textEditorService = accessor.textEditorService; + let editorCount = 0; + const registrationDisposable = editorResolverService.registerEditor( '*.md', { @@ -2470,21 +2283,26 @@ suite('EditorService', () => { {}, (editorInput) => { editorCount++; - return ({ editor: service.createEditorInput(editorInput) }); + return ({ editor: textEditorService.createTextEditor(editorInput) }); }, undefined, - diffEditor => ({ editor: service.createEditorInput(diffEditor) }) + diffEditor => ({ editor: textEditorService.createTextEditor(diffEditor) }) ); + assert.strictEqual(editorCount, 0); + const input1 = new TestFileEditorInput(URI.parse('file://test/path/resource2.md'), TEST_EDITOR_INPUT_ID); + // Open editor input 1 and it shouldn't trigger because I've disabled the override logic await service.openEditor(input1, { override: EditorResolution.DISABLED }); assert.strictEqual(editorCount, 0); + await service.replaceEditors([{ editor: input1, replacement: input1, }], part.activeGroup); assert.strictEqual(editorCount, 1); + registrationDisposable.dispose(); }); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 6e000a1eb84..f2a3f82736c 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -43,6 +43,7 @@ import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEdit import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultRawSettingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; const emptyEditableSettingsContent = '{\n}'; @@ -78,6 +79,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic @ILabelService private readonly labelService: ILabelService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @ICommandService private readonly commandService: ICommandService, + @ITextEditorService private readonly textEditorService: ITextEditorService ) { super(); // The default keybindings.json updates based on keyboard layouts, so here we make sure @@ -355,7 +357,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } public createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI): IEditorInput { - const editableSettingsEditorInput = this.editorService.createEditorInput({ resource }); + const editableSettingsEditorInput = this.textEditorService.createTextEditor({ resource }); const defaultPreferencesEditorInput = this.instantiationService.createInstance(TextResourceEditorInput, this.getDefaultSettingsResource(configurationTarget), undefined, undefined, undefined, undefined); return new SideBySideEditorInput(editableSettingsEditorInput.getName(), undefined, defaultPreferencesEditorInput, editableSettingsEditorInput); } @@ -400,7 +402,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private async getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): Promise { await this.createSettingsIfNotExists(target, resource); - return this.editorService.createEditorInput({ resource }); + return this.textEditorService.createTextEditor({ resource }); } private async createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, settingsUri: URI): Promise { diff --git a/src/vs/workbench/services/textfile/common/textEditorService.ts b/src/vs/workbench/services/textfile/common/textEditorService.ts new file mode 100644 index 00000000000..7688c396fe9 --- /dev/null +++ b/src/vs/workbench/services/textfile/common/textEditorService.ts @@ -0,0 +1,246 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ResourceMap } from 'vs/base/common/map'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorFactoryRegistry, IFileEditorInput, IUntypedEditorInput, IUntypedFileEditorInput, EditorExtensions, isResourceDiffEditorInput, isResourceSideBySideEditorInput, IUntitledTextResourceEditorInput, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { Schemas } from 'vs/base/common/network'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { basename } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export const ITextEditorService = createDecorator('textEditorService'); + +export interface ITextEditorService { + + readonly _serviceBrand: undefined; + + /** + * A way to create text editor inputs from an untyped editor input. Depending + * on the passed in input this will be: + * - a `IFileEditorInput` for file resources + * - a `UntitledEditorInput` for untitled resources + * - a `TextResourceEditorInput` for virtual resources + * + * @param input the untyped editor input to create a typed input from + */ + createTextEditor(input: IUntypedEditorInput): EditorInput; + createTextEditor(input: IUntypedFileEditorInput): IFileEditorInput; +} + +export class TextEditorService extends Disposable implements ITextEditorService { + + declare readonly _serviceBrand: undefined; + + private readonly editorInputCache = new ResourceMap(); + + private readonly fileEditorFactory = Registry.as(EditorExtensions.EditorFactory).getFileEditorFactory(); + + constructor( + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IFileService private readonly fileService: IFileService, + @IEditorResolverService private readonly editorResolverService: IEditorResolverService + ) { + super(); + + // Register the default editor to the editor resolver + // service so that it shows up in the editors picker + this.registerDefaultEditor(); + } + + private registerDefaultEditor(): void { + this._register(this.editorResolverService.registerEditor( + '*', + { + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + priority: RegisteredEditorPriority.builtin + }, + {}, + editor => ({ editor: this.createTextEditor(editor) }), + untitledEditor => ({ editor: this.createTextEditor(untitledEditor) }), + diffEditor => ({ editor: this.createTextEditor(diffEditor) }) + )); + } + + createTextEditor(input: IUntypedEditorInput): EditorInput; + createTextEditor(input: IUntypedFileEditorInput): IFileEditorInput; + createTextEditor(input: IUntypedEditorInput | IUntypedFileEditorInput): EditorInput | IFileEditorInput { + + // Diff Editor Support + if (isResourceDiffEditorInput(input)) { + const original = this.createTextEditor({ ...input.original }); + const modified = this.createTextEditor({ ...input.modified }); + + return this.instantiationService.createInstance(DiffEditorInput, input.label, input.description, original, modified, undefined); + } + + // Side by Side Editor Support + if (isResourceSideBySideEditorInput(input)) { + const primary = this.createTextEditor({ ...input.primary }); + const secondary = this.createTextEditor({ ...input.secondary }); + + return new SideBySideEditorInput(input.label, input.description, secondary, primary); + } + + // Untitled text file support + const untitledInput = input as IUntitledTextResourceEditorInput; + if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource.scheme === Schemas.untitled)) { + const untitledOptions = { + mode: untitledInput.mode, + initialValue: untitledInput.contents, + encoding: untitledInput.encoding + }; + + // Untitled resource: use as hint for an existing untitled editor + let untitledModel: IUntitledTextEditorModel; + if (untitledInput.resource?.scheme === Schemas.untitled) { + untitledModel = this.untitledTextEditorService.create({ untitledResource: untitledInput.resource, ...untitledOptions }); + } + + // Other resource: use as hint for associated filepath + else { + untitledModel = this.untitledTextEditorService.create({ associatedResource: untitledInput.resource, ...untitledOptions }); + } + + return this.createOrGetCached(untitledModel.resource, () => { + + // Factory function for new untitled editor + const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledModel); + + // We dispose the untitled model once the editor + // is being disposed. Even though we may have not + // created the model initially, the lifecycle for + // untitled is tightly coupled with the editor + // lifecycle for now. + Event.once(input.onWillDispose)(() => untitledModel.dispose()); + + return input; + }) as EditorInput; + } + + // Text File/Resource Editor Support + const textResourceEditorInput = input as IUntypedFileEditorInput; + if (textResourceEditorInput.resource instanceof URI) { + + // Derive the label from the path if not provided explicitly + const label = textResourceEditorInput.label || basename(textResourceEditorInput.resource); + + // We keep track of the preferred resource this input is to be created + // with but it may be different from the canonical resource (see below) + const preferredResource = textResourceEditorInput.resource; + + // From this moment on, only operate on the canonical resource + // to ensure we reduce the chance of opening the same resource + // with different resource forms (e.g. path casing on Windows) + const canonicalResource = this.uriIdentityService.asCanonicalUri(preferredResource); + + return this.createOrGetCached(canonicalResource, () => { + + // File + if (textResourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) { + return this.fileEditorFactory.createFileEditor(canonicalResource, preferredResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.encoding, textResourceEditorInput.mode, textResourceEditorInput.contents, this.instantiationService); + } + + // Resource + return this.instantiationService.createInstance(TextResourceEditorInput, canonicalResource, textResourceEditorInput.label, textResourceEditorInput.description, textResourceEditorInput.mode, textResourceEditorInput.contents); + }, cachedInput => { + + // Untitled + if (cachedInput instanceof UntitledTextEditorInput) { + return; + } + + // Files + else if (!(cachedInput instanceof TextResourceEditorInput)) { + cachedInput.setPreferredResource(preferredResource); + + if (textResourceEditorInput.label) { + cachedInput.setPreferredName(textResourceEditorInput.label); + } + + if (textResourceEditorInput.description) { + cachedInput.setPreferredDescription(textResourceEditorInput.description); + } + + if (textResourceEditorInput.encoding) { + cachedInput.setPreferredEncoding(textResourceEditorInput.encoding); + } + + if (textResourceEditorInput.mode) { + cachedInput.setPreferredMode(textResourceEditorInput.mode); + } + + if (typeof textResourceEditorInput.contents === 'string') { + cachedInput.setPreferredContents(textResourceEditorInput.contents); + } + } + + // Resources + else { + if (label) { + cachedInput.setName(label); + } + + if (textResourceEditorInput.description) { + cachedInput.setDescription(textResourceEditorInput.description); + } + + if (textResourceEditorInput.mode) { + cachedInput.setPreferredMode(textResourceEditorInput.mode); + } + + if (typeof textResourceEditorInput.contents === 'string') { + cachedInput.setPreferredContents(textResourceEditorInput.contents); + } + } + }); + } + + throw new Error(`ITextEditorService: Unable to create texteditor from ${JSON.stringify(input)}`); + } + + private createOrGetCached( + resource: URI, + factoryFn: () => TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput, + cachedFn?: (input: TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput) => void + ): TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput { + + // Return early if already cached + let input = this.editorInputCache.get(resource); + if (input) { + if (cachedFn) { + cachedFn(input); + } + + return input; + } + + // Otherwise create and add to cache + input = factoryFn(); + this.editorInputCache.set(resource, input); + Event.once(input.onWillDispose)(() => this.editorInputCache.delete(resource)); + + return input; + } +} + +registerSingleton(ITextEditorService, TextEditorService, true); diff --git a/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts new file mode 100644 index 00000000000..8ae7c1c19c4 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/browser/textEditorService.test.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { IResourceDiffEditorInput, IResourceSideBySideEditorInput, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput } from 'vs/workbench/common/editor'; +import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput, registerTestResourceEditor, registerTestSideBySideEditor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { toResource } from 'vs/base/test/common/utils'; +import { IFileService } from 'vs/platform/files/common/files'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; +import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; +import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { isLinux } from 'vs/base/common/platform'; +import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; + +suite('TextEditorService', () => { + + const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; + const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; + + class FileServiceProvider extends Disposable { + constructor(scheme: string, @IFileService fileService: IFileService) { + super(); + + this._register(fileService.registerProvider(scheme, new NullFileSystemProvider())); + } + } + + const disposables = new DisposableStore(); + + setup(() => { + disposables.add(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_EDITOR_INPUT_ID)); + disposables.add(registerTestResourceEditor()); + disposables.add(registerTestSideBySideEditor()); + }); + + teardown(() => { + disposables.clear(); + }); + + test('createTextEditor - basics', async function () { + const instantiationService = workbenchInstantiationService(); + const service = instantiationService.createInstance(TextEditorService); + + const mode = 'create-input-test'; + ModesRegistry.registerLanguage({ + id: mode, + }); + + // Untyped Input (file) + let input = service.createTextEditor({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof FileEditorInput); + let contentInput = input; + assert.strictEqual(contentInput.resource.fsPath, toResource.call(this, '/index.html').fsPath); + + // Untyped Input (file casing) + input = service.createTextEditor({ resource: toResource.call(this, '/index.html') }); + let inputDifferentCase = service.createTextEditor({ resource: toResource.call(this, '/INDEX.html') }); + + if (!isLinux) { + assert.strictEqual(input, inputDifferentCase); + assert.strictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString()); + } else { + assert.notStrictEqual(input, inputDifferentCase); + assert.notStrictEqual(input.resource?.toString(), inputDifferentCase.resource?.toString()); + } + + // Typed Input + assert.strictEqual(service.createTextEditor(input), input); + + // Untyped Input (file, encoding) + input = service.createTextEditor({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof FileEditorInput); + contentInput = input; + assert.strictEqual(contentInput.getPreferredEncoding(), 'utf16le'); + + // Untyped Input (file, mode) + input = service.createTextEditor({ resource: toResource.call(this, '/index.html'), mode }); + assert(input instanceof FileEditorInput); + contentInput = input; + assert.strictEqual(contentInput.getPreferredMode(), mode); + let fileModel = (await contentInput.resolve() as ITextFileEditorModel); + assert.strictEqual(fileModel.textEditorModel?.getModeId(), mode); + + // Untyped Input (file, contents) + input = service.createTextEditor({ resource: toResource.call(this, '/index.html'), contents: 'My contents' }); + assert(input instanceof FileEditorInput); + contentInput = input; + fileModel = (await contentInput.resolve() as ITextFileEditorModel); + assert.strictEqual(fileModel.textEditorModel?.getValue(), 'My contents'); + assert.strictEqual(fileModel.isDirty(), true); + + // Untyped Input (file, different mode) + input = service.createTextEditor({ resource: toResource.call(this, '/index.html'), mode: 'text' }); + assert(input instanceof FileEditorInput); + contentInput = input; + assert.strictEqual(contentInput.getPreferredMode(), 'text'); + + // Untyped Input (untitled) + input = service.createTextEditor({ resource: undefined, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledTextEditorInput); + + // Untyped Input (untitled with contents) + let untypedInput: any = { contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }; + input = service.createTextEditor(untypedInput); + assert.ok(isUntitledResourceEditorInput(untypedInput)); + assert(input instanceof UntitledTextEditorInput); + let model = await input.resolve() as UntitledTextEditorModel; + assert.strictEqual(model.textEditorModel?.getValue(), 'Hello Untitled'); + + // Untyped Input (untitled withtoUntyped2 + input = service.createTextEditor({ resource: undefined, mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledTextEditorInput); + model = await input.resolve() as UntitledTextEditorModel; + assert.strictEqual(model.getMode(), mode); + + // Untyped Input (untitled with file path) + input = service.createTextEditor({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + + // Untyped Input (untitled with untitled resource) + untypedInput = { resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }; + assert.ok(isUntitledResourceEditorInput(untypedInput)); + input = service.createTextEditor(untypedInput); + assert(input instanceof UntitledTextEditorInput); + assert.ok(!(input as UntitledTextEditorInput).model.hasAssociatedFilePath); + + // Untyped input (untitled with custom resource, but forceUntitled) + untypedInput = { resource: URI.file('/fake'), forceUntitled: true }; + assert.ok(isUntitledResourceEditorInput(untypedInput)); + input = service.createTextEditor(untypedInput); + assert(input instanceof UntitledTextEditorInput); + + // Untyped Input (untitled with custom resource) + const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); + + input = service.createTextEditor({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledTextEditorInput); + assert.ok((input as UntitledTextEditorInput).model.hasAssociatedFilePath); + + provider.dispose(); + + // Untyped Input (resource) + input = service.createTextEditor({ resource: URI.parse('custom:resource') }); + assert(input instanceof TextResourceEditorInput); + + // Untyped Input (diff) + const resourceDiffInput = { + modified: { resource: toResource.call(this, '/modified.html') }, + original: { resource: toResource.call(this, '/original.html') } + }; + assert.strictEqual(isResourceDiffEditorInput(resourceDiffInput), true); + input = service.createTextEditor(resourceDiffInput); + assert(input instanceof DiffEditorInput); + assert.strictEqual(input.original.resource?.toString(), resourceDiffInput.original.resource.toString()); + assert.strictEqual(input.modified.resource?.toString(), resourceDiffInput.modified.resource.toString()); + const untypedDiffInput = input.toUntyped() as IResourceDiffEditorInput; + assert.strictEqual(untypedDiffInput.original.resource?.toString(), resourceDiffInput.original.resource.toString()); + assert.strictEqual(untypedDiffInput.modified.resource?.toString(), resourceDiffInput.modified.resource.toString()); + + // Untyped Input (side by side) + const sideBySideResourceInput = { + primary: { resource: toResource.call(this, '/primary.html') }, + secondary: { resource: toResource.call(this, '/secondary.html') } + }; + assert.strictEqual(isResourceSideBySideEditorInput(sideBySideResourceInput), true); + input = service.createTextEditor(sideBySideResourceInput); + assert(input instanceof SideBySideEditorInput); + assert.strictEqual(input.primary.resource?.toString(), sideBySideResourceInput.primary.resource.toString()); + assert.strictEqual(input.secondary.resource?.toString(), sideBySideResourceInput.secondary.resource.toString()); + const untypedSideBySideInput = input.toUntyped() as IResourceSideBySideEditorInput; + assert.strictEqual(untypedSideBySideInput.primary.resource?.toString(), sideBySideResourceInput.primary.resource.toString()); + assert.strictEqual(untypedSideBySideInput.secondary.resource?.toString(), sideBySideResourceInput.secondary.resource.toString()); + }); + + test('createTextEditor- caching', function () { + const instantiationService = workbenchInstantiationService(); + const service = instantiationService.createInstance(TextEditorService); + + // Cached Input (Files) + const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); + const fileEditorInput1 = service.createTextEditor({ resource: fileResource1 }); + assert.ok(fileEditorInput1); + + const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); + const fileEditorInput2 = service.createTextEditor({ resource: fileResource2 }); + assert.ok(fileEditorInput2); + + assert.notStrictEqual(fileEditorInput1, fileEditorInput2); + + const fileEditorInput1Again = service.createTextEditor({ resource: fileResource1 }); + assert.strictEqual(fileEditorInput1Again, fileEditorInput1); + + fileEditorInput1Again.dispose(); + + assert.ok(fileEditorInput1.isDisposed()); + + const fileEditorInput1AgainAndAgain = service.createTextEditor({ resource: fileResource1 }); + assert.notStrictEqual(fileEditorInput1AgainAndAgain, fileEditorInput1); + assert.ok(!fileEditorInput1AgainAndAgain.isDisposed()); + + // Cached Input (Resource) + const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); + const input1 = service.createTextEditor({ resource: resource1 }); + assert.ok(input1); + + const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); + const input2 = service.createTextEditor({ resource: resource2 }); + assert.ok(input2); + + assert.notStrictEqual(input1, input2); + + const input1Again = service.createTextEditor({ resource: resource1 }); + assert.strictEqual(input1Again, input1); + + input1Again.dispose(); + + assert.ok(input1.isDisposed()); + + const input1AgainAndAgain = service.createTextEditor({ resource: resource1 }); + assert.notStrictEqual(input1AgainAndAgain, input1); + assert.ok(!input1AgainAndAgain.isDisposed()); + }); +}); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index 30440e20b44..6ce2f95b81d 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; import { isEqual, toLocalResource } from 'vs/base/common/resources'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -78,7 +78,7 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createEditorInput({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; + return accessor.get(ITextEditorService).createTextEditor({ resource, mode, encoding, forceUntitled: true }) as UntitledTextEditorInput; }); } } @@ -91,7 +91,7 @@ export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable imple @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IPathService private readonly pathService: IPathService, - @IEditorService private readonly editorService: IEditorService + @ITextEditorService private readonly textEditorService: ITextEditorService ) { super(); @@ -114,7 +114,7 @@ export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable imple editorInputResource = workingCopy.resource; } - return this.editorService.createEditorInput({ resource: editorInputResource, forceUntitled: true }); + return this.textEditorService.createTextEditor({ resource: editorInputResource, forceUntitled: true }); } })); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 3ea002acc63..a59c51b33ee 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -14,6 +14,7 @@ import { isEqual, toLocalResource } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; /** * An editor input to be used for untitled text buffers. @@ -39,9 +40,10 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IPathService private readonly pathService: IPathService + @IPathService private readonly pathService: IPathService, + @IEditorResolverService editorResolverService: IEditorResolverService ) { - super(model.resource, undefined, editorService, textFileService, labelService, fileService); + super(model.resource, undefined, editorService, textFileService, labelService, fileService, editorResolverService); this.registerModelListeners(model); } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7245a3d45ce..c7879ff4735 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -141,6 +141,7 @@ import { IDiffComputationResult, IEditorWorkerService } from 'vs/editor/common/s import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/modes'; import { ResourceMap } from 'vs/base/common/map'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; +import { ITextEditorService, TextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -191,6 +192,7 @@ export function workbenchInstantiationService( pathService?: (instantiationService: IInstantiationService) => IPathService, editorService?: (instantiationService: IInstantiationService) => IEditorService, contextKeyService?: (instantiationService: IInstantiationService) => IContextKeyService, + textEditorService?: (instantiationService: IInstantiationService) => ITextEditorService }, disposables: DisposableStore = new DisposableStore() ): ITestInstantiationService { @@ -255,6 +257,8 @@ export function workbenchInstantiationService( instantiationService.stub(IEditorService, editorService); instantiationService.stub(IWorkingCopyEditorService, disposables.add(instantiationService.createInstance(WorkingCopyEditorService))); instantiationService.stub(IEditorResolverService, disposables.add(instantiationService.createInstance(EditorResolverService))); + const textEditorService = overrides?.textEditorService ? overrides.textEditorService(instantiationService) : instantiationService.createInstance(TextEditorService); + instantiationService.stub(ITextEditorService, textEditorService); instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IViewletService, new TestViewletService()); instantiationService.stub(IListService, new TestListService()); @@ -272,6 +276,7 @@ export class TestServiceAccessor { constructor( @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, + @ITextEditorService public textEditorService: ITextEditorService, @IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService, @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, @IWorkspaceContextService public contextService: TestContextService, @@ -844,7 +849,6 @@ export class TestEditorService implements EditorServiceImpl { isOpened(_editor: IResourceEditorInputIdentifier): boolean { return false; } isVisible(_editor: IEditorInput): boolean { return false; } replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); } - createEditorInput(_input: any): any { throw new Error('not implemented'); } save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ca388470398..60387d471ff 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -71,6 +71,7 @@ import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/services/keybinding/browser/keybindingService'; import 'vs/workbench/services/untitled/common/untitledTextEditorService'; import 'vs/workbench/services/textresourceProperties/common/textResourcePropertiesService'; +import 'vs/workbench/services/textfile/common/textEditorService'; import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService';