Skip to content

Commit 1c51e8f

Browse files
authored
Add a specialized unhandled conflicts close handler (microsoft#154471)
This is set dynamically whenever unhandled conflicts are detected. It unsets itself when merging is done so that the normal save-close-handler comes to place microsoft#152841
1 parent 0f1adf3 commit 1c51e8f

File tree

1 file changed

+46
-64
lines changed

1 file changed

+46
-64
lines changed

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

Lines changed: 46 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/text
1919
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
2020
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
2121
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
22-
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
2322
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
24-
import { assertType } from 'vs/base/common/types';
23+
import { autorun } from 'vs/base/common/observable';
2524

2625
export class MergeEditorInputData {
2726
constructor(
@@ -32,13 +31,14 @@ export class MergeEditorInputData {
3231
) { }
3332
}
3433

35-
export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport, IEditorCloseHandler {
34+
export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport {
3635

3736
static readonly ID = 'mergeEditor.Input';
3837

3938
private _model?: MergeEditorModel;
4039
private _outTextModel?: ITextFileEditorModel;
41-
private _ignoreUnhandledConflictsForDirtyState?: true;
40+
41+
override closeHandler: MergeEditorCloseHandler | undefined;
4242

4343
constructor(
4444
public readonly base: URI,
@@ -47,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
4747
public readonly result: URI,
4848
@IInstantiationService private readonly _instaService: IInstantiationService,
4949
@ITextModelService private readonly _textModelService: ITextModelService,
50-
@IDialogService private readonly _dialogService: IDialogService,
51-
@IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService,
5250
@IEditorService editorService: IEditorService,
5351
@ITextFileService textFileService: ITextFileService,
5452
@ILabelService labelService: ILabelService,
@@ -117,6 +115,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
117115
},
118116
);
119117

118+
// set/unset the closeHandler whenever unhandled conflicts are detected
119+
const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model);
120+
this._store.add(autorun('closeHandler', reader => {
121+
const value = this._model!.hasUnhandledConflicts.read(reader);
122+
this.closeHandler = value ? closeHandler : undefined;
123+
}));
124+
120125
await this._model.onInitialized;
121126

122127
this._store.add(this._model);
@@ -126,7 +131,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
126131
this._store.add(result);
127132
}
128133

129-
this._ignoreUnhandledConflictsForDirtyState = undefined;
130134
return this._model;
131135
}
132136

@@ -146,67 +150,54 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
146150
return Boolean(this._outTextModel?.isDirty());
147151
}
148152

149-
override readonly closeHandler = this;
153+
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
154+
this._model?.setLanguageId(languageId);
155+
}
156+
157+
// implement get/set languageId
158+
// implement get/set encoding
159+
}
160+
161+
class MergeEditorCloseHandler implements IEditorCloseHandler {
162+
163+
private _ignoreUnhandledConflicts: boolean = false;
164+
165+
constructor(
166+
private readonly _model: MergeEditorModel,
167+
@IDialogService private readonly _dialogService: IDialogService,
168+
) { }
150169

151170
showConfirm(): boolean {
152-
if (this.isDirty()) {
153-
// text model dirty -> 3wm is dirty
154-
return true;
155-
}
156-
if (!this._ignoreUnhandledConflictsForDirtyState) {
157-
// unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
158-
// to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
159-
return Boolean(this._model && this._model.hasUnhandledConflicts.get());
160-
}
161-
return false;
171+
// unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
172+
// to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
173+
return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
162174
}
163175

164-
async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
176+
async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
165177

166-
const inputs: MergeEditorInput[] = [this];
167-
if (editors) {
168-
for (const { editor } of editors) {
169-
if (editor instanceof MergeEditorInput) {
170-
inputs.push(editor);
171-
}
172-
}
173-
}
178+
const handler: MergeEditorCloseHandler[] = [this];
179+
editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
174180

175-
const inputsWithUnhandledConflicts = inputs
181+
const inputsWithUnhandledConflicts = handler
176182
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
177183

178184
if (inputsWithUnhandledConflicts.length === 0) {
185+
// shouldn't happen
179186
return ConfirmResult.SAVE;
180187
}
181188

182-
const actions: string[] = [];
189+
const actions: string[] = [
190+
localize('unhandledConflicts.ignore', "Continue with Conflicts"),
191+
localize('unhandledConflicts.discard', "Discard Merge Changes"),
192+
localize('unhandledConflicts.cancel', "Cancel"),
193+
];
183194
const options = {
184-
cancelId: 0,
185-
detail: inputs.length > 1
186-
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length)
195+
cancelId: 2,
196+
detail: handler.length > 1
197+
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length)
187198
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
188199
};
189200

190-
const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF;
191-
if (!isAnyAutoSave) {
192-
// manual-save: FYI and discard
193-
actions.push(
194-
localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0
195-
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
196-
localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2
197-
);
198-
199-
} else {
200-
// auto-save: only FYI
201-
actions.push(
202-
localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0
203-
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
204-
);
205-
}
206-
207-
actions.push(localize('unhandledConflicts.cancel', "Cancel"));
208-
options.cancelId = actions.length - 1;
209-
210201
const { choice } = await this._dialogService.show(
211202
Severity.Info,
212203
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1
@@ -221,8 +212,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
221212

222213
// save or revert: in both cases we tell the inputs to ignore unhandled conflicts
223214
// for the dirty state computation.
224-
for (const input of inputs) {
225-
input._ignoreUnhandledConflictsForDirtyState = true;
215+
for (const input of handler) {
216+
input._ignoreUnhandledConflicts = true;
226217
}
227218

228219
if (choice === 0) {
@@ -231,7 +222,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
231222

232223
} else if (choice === 1) {
233224
// discard: undo all changes and save original (pre-merge) state
234-
for (const input of inputs) {
225+
for (const input of handler) {
235226
input._discardMergeChanges();
236227
}
237228
return ConfirmResult.SAVE;
@@ -243,8 +234,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
243234
}
244235

245236
private _discardMergeChanges(): void {
246-
assertType(this._model !== undefined);
247-
248237
const chunks: string[] = [];
249238
while (true) {
250239
const chunk = this._model.resultSnapshot.read();
@@ -255,11 +244,4 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
255244
}
256245
this._model.result.setValue(chunks.join());
257246
}
258-
259-
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
260-
this._model?.setLanguageId(languageId);
261-
}
262-
263-
// implement get/set languageId
264-
// implement get/set encoding
265247
}

0 commit comments

Comments
 (0)