Skip to content

Commit 60b7514

Browse files
authored
implement IEditorInput#confirm (microsoft#153211)
implement `IEditorInput#confirm` to confirm with users that it is OK 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 microsoft#151024
1 parent d1b4630 commit 60b7514

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55

66
import { DisposableStore } from 'vs/base/common/lifecycle';
77
import { isEqual } from 'vs/base/common/resources';
8+
import Severity from 'vs/base/common/severity';
89
import { URI } from 'vs/base/common/uri';
910
import { ITextModelService } from 'vs/editor/common/services/resolverService';
1011
import { localize } from 'vs/nls';
12+
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
1113
import { IFileService } from 'vs/platform/files/common/files';
1214
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1315
import { ILabelService } from 'vs/platform/label/common/label';
14-
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
16+
import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
1517
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
1618
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
19+
import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
1720
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
1821
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
1922
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@@ -33,6 +36,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
3336

3437
private _model?: MergeEditorModel;
3538
private _outTextModel?: ITextFileEditorModel;
39+
private _ignoreUnhandledConflictsForDirtyState?: true;
3640

3741
constructor(
3842
public readonly base: URI,
@@ -41,6 +45,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
4145
public readonly result: URI,
4246
@IInstantiationService private readonly _instaService: IInstantiationService,
4347
@ITextModelService private readonly _textModelService: ITextModelService,
48+
@IDialogService private readonly _dialogService: IDialogService,
4449
@IEditorService editorService: IEditorService,
4550
@ITextFileService textFileService: ITextFileService,
4651
@ILabelService labelService: ILabelService,
@@ -112,7 +117,14 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
112117
this._store.add(input1);
113118
this._store.add(input2);
114119
this._store.add(result);
120+
121+
this._store.add(autorun(reader => {
122+
this._model?.hasUnhandledConflicts.read(reader);
123+
this._onDidChangeDirty.fire(undefined);
124+
}, 'drive::onDidChangeDirty'));
115125
}
126+
127+
this._ignoreUnhandledConflictsForDirtyState = undefined;
116128
return this._model;
117129
}
118130

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

131143
override isDirty(): boolean {
132-
return Boolean(this._outTextModel?.isDirty());
144+
const textModelDirty = Boolean(this._outTextModel?.isDirty());
145+
if (textModelDirty) {
146+
// text model dirty -> 3wm is dirty
147+
return true;
148+
}
149+
if (!this._ignoreUnhandledConflictsForDirtyState) {
150+
// unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
151+
// to ignore unhandled conflicts for the dirty-state. This happens only
152+
// after confirming to ignore unhandled changes
153+
return Boolean(this._model && this._model.hasUnhandledConflicts.get());
154+
}
155+
return false;
156+
}
157+
158+
override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
159+
160+
const inputs: MergeEditorInput[] = [this];
161+
if (editors) {
162+
for (const { editor } of editors) {
163+
if (editor instanceof MergeEditorInput) {
164+
inputs.push(editor);
165+
}
166+
}
167+
}
168+
169+
const inputsWithUnhandledConflicts = inputs
170+
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
171+
172+
if (inputsWithUnhandledConflicts.length === 0) {
173+
return ConfirmResult.SAVE;
174+
}
175+
176+
const { choice } = await this._dialogService.show(
177+
Severity.Info,
178+
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'),
179+
[
180+
localize('unhandledConflicts.ignore', "Continue with Conflicts"),
181+
localize('unhandledConflicts.cancel', "Cancel")
182+
],
183+
{
184+
cancelId: 1,
185+
detail: inputsWithUnhandledConflicts.length > 1
186+
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputsWithUnhandledConflicts.length)
187+
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
188+
}
189+
);
190+
191+
if (choice !== 0) {
192+
return ConfirmResult.CANCEL;
193+
}
194+
195+
// continue with conflicts, tell inputs to ignore unhandled changes
196+
for (const input of inputsWithUnhandledConflicts) {
197+
input._ignoreUnhandledConflictsForDirtyState = true;
198+
}
199+
return ConfirmResult.SAVE;
133200
}
134201

135202
setLanguageId(languageId: string, _setExplicitly?: boolean): void {

src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export class MergeEditorModel extends EditorModel {
9191
return map.size - handledCount;
9292
});
9393

94+
public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0);
95+
9496
public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => {
9597
const resultDiffs = this.resultDiffs.read(reader);
9698
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount());

0 commit comments

Comments
 (0)