Skip to content

Commit

Permalink
implement IEditorInput#confirm to confirm with users that it is OK …
Browse files Browse the repository at this point in the history
…to close a merge editor despite unresolved conflicts. As a side-effect keep the merge editor dirty while it is unsed and while it has unresolved conflicts

fixes #151024
  • Loading branch information
jrieken committed Jun 25, 2022
1 parent 09c5a6e commit cdfdb50
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 2 deletions.
71 changes: 69 additions & 2 deletions src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@

import { DisposableStore } from 'vs/base/common/lifecycle';
import { isEqual } from 'vs/base/common/resources';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { localize } from 'vs/nls';
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
Expand All @@ -33,6 +36,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements

private _model?: MergeEditorModel;
private _outTextModel?: ITextFileEditorModel;
private _ignoreUnhandledConflictsForDirtyState?: true;

constructor(
public readonly base: URI,
Expand All @@ -41,6 +45,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
public readonly result: URI,
@IInstantiationService private readonly _instaService: IInstantiationService,
@ITextModelService private readonly _textModelService: ITextModelService,
@IDialogService private readonly _dialogService: IDialogService,
@IEditorService editorService: IEditorService,
@ITextFileService textFileService: ITextFileService,
@ILabelService labelService: ILabelService,
Expand Down Expand Up @@ -112,7 +117,14 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this._store.add(input1);
this._store.add(input2);
this._store.add(result);

this._store.add(autorun(reader => {
this._model?.hasUnhandledConflicts.read(reader);
this._onDidChangeDirty.fire(undefined);
}, 'drive::onDidChangeDirty'));
}

this._ignoreUnhandledConflictsForDirtyState = undefined;
return this._model;
}

Expand All @@ -129,7 +141,62 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// ---- FileEditorInput

override isDirty(): boolean {
return Boolean(this._outTextModel?.isDirty());
const textModelDirty = Boolean(this._outTextModel?.isDirty());
if (textModelDirty) {
// text model dirty -> 3wm is dirty
return true;
}
if (!this._ignoreUnhandledConflictsForDirtyState) {
// unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
// to ignore unhandled conflicts for the dirty-state. This happens only
// after confirming to ignore unhandled changes
return Boolean(this._model && this._model.hasUnhandledConflicts.get());
}
return false;
}

override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {

const inputs: MergeEditorInput[] = [this];
if (editors) {
for (const { editor } of editors) {
if (editor instanceof MergeEditorInput) {
inputs.push(editor);
}
}
}

const inputsWithUnhandledConflicts = inputs
.filter(input => input._model && input._model.hasUnhandledConflicts.get());

if (inputsWithUnhandledConflicts.length === 0) {
return ConfirmResult.SAVE;
}

const { choice } = await this._dialogService.show(
Severity.Info,
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'),
[
localize('unhandledConflicts.ignore', "Continue with Conflicts"),
localize('unhandledConflicts.cancel', "Cancel")
],
{
cancelId: 1,
detail: inputsWithUnhandledConflicts.length > 1
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputsWithUnhandledConflicts.length)
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
}
);

if (choice !== 0) {
return ConfirmResult.CANCEL;
}

// continue with conflicts, tell inputs to ignore unhandled changes
for (const input of inputsWithUnhandledConflicts) {
input._ignoreUnhandledConflictsForDirtyState = true;
}
return ConfirmResult.SAVE;
}

setLanguageId(languageId: string, _setExplicitly?: boolean): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export class MergeEditorModel extends EditorModel {
return map.size - handledCount;
});

public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0);

public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => {
const resultDiffs = this.resultDiffs.read(reader);
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount());
Expand Down

0 comments on commit cdfdb50

Please sign in to comment.