From 6770e54beaade2921f576d953482f38999240d07 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 6 Jul 2022 10:09:43 +0200 Subject: [PATCH 1/3] add `ICodeEditorService#registerCodeEditorOpenHandler` so that 3rd parties can influence opening, e.g diff or merge editor This allows to remove editor registration for 3wm editor. --- .../services/abstractCodeEditorService.ts | 22 ++++- .../browser/services/codeEditorService.ts | 6 ++ .../browser/standaloneCodeEditorService.ts | 16 ++-- .../browser/mergeEditor.contribution.ts | 11 ++- .../mergeEditor/browser/view/mergeEditor.ts | 85 +++++++++---------- .../editor/browser/codeEditorService.ts | 9 +- 6 files changed, 88 insertions(+), 61 deletions(-) diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 761c4eee60f65..aaa31ee4ce49c 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -5,11 +5,12 @@ import * as dom from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { ICodeEditorOpenHandler, ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon'; import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -42,6 +43,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC protected _globalStyleSheet: GlobalStyleSheet | null; private readonly _decorationOptionProviders = new Map(); private readonly _editorStyleSheets = new Map(); + private readonly _codeEditorOpenHandlers = new LinkedList(); constructor( @IThemeService private readonly _themeService: IThemeService, @@ -247,7 +249,21 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC } abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + + async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + for (const handler of this._codeEditorOpenHandlers) { + const candidate = await handler(input, source, sideBySide); + if (candidate !== null) { + return candidate; + } + } + return null; + } + + registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable { + const rm = this._codeEditorOpenHandlers.unshift(handler); + return toDisposable(rm); + } } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index b56596939a8f7..40d7947efcdee 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -10,6 +10,7 @@ import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const ICodeEditorService = createDecorator('codeEditorService'); @@ -53,4 +54,9 @@ export interface ICodeEditorService { getActiveCodeEditor(): ICodeEditor | null; openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable; +} + +export interface ICodeEditorOpenHandler { + (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts index af167ba519d83..2f9e648d52d52 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts @@ -13,7 +13,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -31,6 +31,13 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService { this.onCodeEditorRemove(() => this._checkContextKey()); this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false); this._activeCodeEditor = null; + + this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => { + if (!source) { + return null; + } + return this.doOpenEditor(source, input); + }); } private _checkContextKey(): void { @@ -52,13 +59,6 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService { return this._activeCodeEditor; } - public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { - if (!source) { - return Promise.resolve(null); - } - - return Promise.resolve(this.doOpenEditor(source, input)); - } private doOpenEditor(editor: ICodeEditor, input: ITextResourceEditorInput): ICodeEditor | null { const model = this.findModel(editor, input.resource); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 8dc07e6463fa5..09206f43520e0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MergeEditorSerializer } from './mergeEditorSerializer'; Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -47,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2); registerAction2(CompareInput1WithBaseCommand); registerAction2(CompareInput2WithBaseCommand); + + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 544e07332cb94..ff44bc0116371 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -11,12 +11,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; @@ -26,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -35,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; @@ -46,7 +47,6 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -86,9 +86,9 @@ export class MergeEditor extends AbstractTextEditor { private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); - private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); - private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); + private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1)); + private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2)); + private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView)); private readonly _layoutMode: MergeEditorLayout; private readonly _ctxIsMergeEditor: IContextKey; @@ -103,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor { } constructor( - @IInstantiationService private readonly instantiation: IInstantiationService, + @IInstantiationService instantiation: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, @IMenuService private readonly _menuService: IMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -115,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor { @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IFileService fileService: IFileService, - @IEditorResolverService private readonly _editorResolverService: IEditorResolverService, ) { super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); @@ -186,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor { createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions); if (actions.length > 0) { const [first] = actions; - const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id); + const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id); toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri))); toolbarMenuDisposables.add(acceptBtn); acceptBtn.render(); @@ -296,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor { await super.setInput(input, options, context, token); this._sessionDisposables.clear(); - this._toggleEditorOverwrite(true); const model = await input.resolve(); this._model = model; @@ -373,7 +371,6 @@ export class MergeEditor extends AbstractTextEditor { super.clearInput(); this._sessionDisposables.clear(); - this._toggleEditorOverwrite(false); for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) { editor.setModel(null); @@ -405,39 +402,6 @@ export class MergeEditor extends AbstractTextEditor { } this._ctxIsMergeEditor.set(visible); - this._toggleEditorOverwrite(visible); - } - - private readonly _editorOverrideHandle = this._store.add(new MutableDisposable()); - - private _toggleEditorOverwrite(haveIt: boolean) { - if (!haveIt) { - this._editorOverrideHandle.clear(); - return; - } - // this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that - // navigating within the merge editor works, e.g navigating from the outline or breakcrumps - // or revealing a definition, reference etc - // TODO@jrieken @bpasero @lramos15 - const input = this.input; - if (input instanceof MergeEditorInput) { - this._editorOverrideHandle.value = this._editorResolverService.registerEditor( - `${input.result.scheme}:${input.result.fsPath}`, - { - id: `${this.getId()}/fake`, - label: this.input?.getName()!, - priority: RegisteredEditorPriority.exclusive - }, - {}, - (candidate): EditorInputWithOptions => { - const resource = EditorResourceAccessor.getCanonicalUri(candidate); - if (!isEqual(resource, this.model?.result.uri)) { - throw new Error(`Expected to be called WITH ${input.result.toString()}`); - } - return { editor: input }; - } - ); - } } // ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal @@ -506,6 +470,37 @@ export class MergeEditor extends AbstractTextEditor { } } +export class MergeEditorOpenHandlerContribution extends Disposable { + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @ICodeEditorService codeEditorService: ICodeEditorService, + ) { + super(); + this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this))); + } + + private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise { + const activePane = this._editorService.activeEditorPane; + if (!sideBySide + && input.options + && activePane instanceof MergeEditor + && activePane.getControl() + && activePane.input instanceof MergeEditorInput + && isEqual(input.resource, activePane.input.result) + ) { + // Special: stay inside the merge editor when it is active and when the input + // targets the result editor of the merge editor. + const targetEditor = activePane.getControl()!; + applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth); + return targetEditor; + } + + // cannot handle this + return null; + } +} + type IMergeEditorViewState = ICodeEditorViewState & { readonly input1State?: ICodeEditorViewState; readonly input2State?: ICodeEditorViewState; diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index 0aaf3980f4f26..492e5077def83 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -24,6 +24,9 @@ export class CodeEditorService extends AbstractCodeEditorService { @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(themeService); + + this.registerCodeEditorOpenHandler(this.doOpenCodeEditor.bind(this)); + this.registerCodeEditorOpenHandler(this.doOpenCodeEditorFromDiff.bind(this)); } getActiveCodeEditor(): ICodeEditor | null { @@ -44,7 +47,7 @@ export class CodeEditorService extends AbstractCodeEditorService { return null; } - async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + private async doOpenCodeEditorFromDiff(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { // Special case: If the active editor is a diff editor and the request to open originates and // targets the modified side of it, we just apply the request there to prevent opening the modified @@ -66,10 +69,10 @@ export class CodeEditorService extends AbstractCodeEditorService { return targetEditor; } - // Open using our normal editor service - return this.doOpenCodeEditor(input, source, sideBySide); + return null; } + // Open using our normal editor service private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { // Special case: we want to detect the request to open an editor that From 7c346e113b4cff07019139de115cc72005819383 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 6 Jul 2022 10:10:21 +0200 Subject: [PATCH 2/3] make sure document outline uses the code editor service from the current (code) editor --- .../codeEditor/browser/outline/documentSymbolsOutline.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 922bf591bf7f7..5f44dc67e26ff 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator void; constructor( - @IOutlineService outlineService: IOutlineService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IOutlineService outlineService: IOutlineService ) { const reg = outlineService.registerOutlineCreator(this); this.dispose = () => reg.dispose(); @@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier)); await firstLoadBarrier.wait(); return result; } From 0b9de0b7405b0f02a36389f3c23201ab79e02abd Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 7 Jul 2022 15:26:25 +0200 Subject: [PATCH 3/3] fix compile issue --- src/vs/editor/test/browser/editorTestServices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index 6eb1863c26ebb..3aad1a8654745 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -22,7 +22,7 @@ export class TestCodeEditorService extends AbstractCodeEditorService { return null; } public lastInput?: IResourceEditorInput; - openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + override openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { this.lastInput = input; return Promise.resolve(null); }