diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 76b5ba08d62..700d7955682 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -16,7 +16,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export class MergeEditorInputData { constructor( @@ -27,7 +27,7 @@ export class MergeEditorInputData { ) { } } -export class MergeEditorInput extends AbstractTextResourceEditorInput { +export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport { static readonly ID = 'mergeEditor.Input'; @@ -140,6 +140,9 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput { return Boolean(this._outTextModel?.isDirty()); } + setLanguageId(languageId: string, _setExplicitly?: boolean): void { + this._model?.setLanguageId(languageId); + } // implement get/set languageId // implement get/set encoding diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 7b5a7e42a67..f46e30b0b77 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -20,6 +20,8 @@ import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRa import { TextModelDiffChangeReason, TextModelDiffs, TextModelDiffState } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs'; import { concatArrays, leftJoin, elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { ModifiedBaseRange, ModifiedBaseRangeState } from './modifiedBaseRange'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ILanguageService } from 'vs/editor/common/languages/language'; export const enum MergeEditorModelState { initializing = 1, @@ -134,7 +136,9 @@ export class MergeEditorModel extends EditorModel { readonly input2Detail: string | undefined, readonly input2Description: string | undefined, readonly result: ITextModel, - @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService + @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, + @IModelService private readonly modelService: IModelService, + @ILanguageService private readonly languageService: ILanguageService, ) { super(); @@ -329,6 +333,10 @@ export class MergeEditorModel extends EditorModel { public setHandled(baseRange: ModifiedBaseRange, handled: boolean, tx: ITransaction): void { this.modifiedBaseRangeHandlingStateStores.get().get(baseRange)!.set(handled, tx); } + + public setLanguageId(languageId: string): void { + this.modelService.setMode(this.result, this.languageService.createById(languageId)); + } } function getEditForBase(baseRange: ModifiedBaseRange, state: ModifiedBaseRangeState): { edit: LineRangeEdit | undefined; effectiveState: ModifiedBaseRangeState } { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index add9e3bc68d..3fa40ab2dcd 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -10,10 +10,11 @@ import { IAction } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; -import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -32,7 +33,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 { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; @@ -43,6 +44,7 @@ import { ReentrancyBarrier, thenIfNotDisposed } from 'vs/workbench/contrib/merge import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; 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'; @@ -87,7 +89,8 @@ export class MergeEditor extends AbstractTextEditor { @IConfigurationService private readonly _configurationService: IConfigurationService, @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IEditorResolverService private readonly _editorResolverService: IEditorResolverService, ) { super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); @@ -230,6 +233,8 @@ 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; @@ -302,6 +307,7 @@ 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); @@ -333,24 +339,50 @@ export class MergeEditor extends AbstractTextEditor { } this._ctxIsMergeEditor.set(visible); + this._toggleEditorOverwrite(visible); } - // ---- interact with "outside world" via `getControl`, `scopedContextKeyService` + 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 override getControl(): ICodeEditor | undefined { - for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) { - if (editor.hasWidgetFocus()) { - return editor; - } - } - return undefined; + return this.inputResultView.editor; } override get scopedContextKeyService(): IContextKeyService | undefined { const control = this.getControl(); - return isCodeEditor(control) - ? control.invokeWithinContext(accessor => accessor.get(IContextKeyService)) - : undefined; + return control?.invokeWithinContext(accessor => accessor.get(IContextKeyService)); } // --- layout