joh/issue151136 (#153101)

* Implement `ILanguageSupport#setLanguageId` for merge editor input. With this the editor langauge status shows (but doesn't do anything yet)

* Improve `MergeEditor#getControl` so that the editor language status works (knows the editor/model to operate on). This opens a can of worms as other things start to work, e.g outline and breakcrumbs and that requires some "redirect magic" to ensure opening a symbol from outline/breakcrumbs stays within the merge editor...

fyi @bpasero @lramos15 I dynamically register/unregister an editor with the editor resolver service which I think is pretty bad but the only "contained/local" way I could find
This commit is contained in:
Johannes Rieken
2022-06-24 14:34:12 +02:00
committed by GitHub
parent 165b6264ac
commit 6ab33e9bb2
3 changed files with 60 additions and 17 deletions
@@ -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
@@ -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 } {
@@ -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<any> {
@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<any> {
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<any> {
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<any> {
}
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