Skip to content

Commit 01a14d2

Browse files
committed
feat: improve conflict action (#2082)
* fix: turn direction status * fix: range touch condition * chore: improve actions update render & fix revoke actions * feat: implement append action render & fix mapping data * chore: rename sameRange symbol * style: improve append icon * feat: implement accept append action * chore: remove console
1 parent ce0bd46 commit 01a14d2

13 files changed

Lines changed: 332 additions & 157 deletions

packages/monaco/src/browser/contrib/merge-editor/mapping-manager.service.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Disposable } from '@opensumi/ide-core-common';
44
import { DocumentMapping } from './model/document-mapping';
55
import { LineRange } from './model/line-range';
66
import { LineRangeMapping } from './model/line-range-mapping';
7-
import { EDiffRangeTurn, EditorViewType } from './types';
7+
import { EDiffRangeTurn, EditorViewType, ETurnDirection } from './types';
88

99
@Injectable()
1010
export class MappingManagerService extends Disposable {
@@ -21,32 +21,58 @@ export class MappingManagerService extends Disposable {
2121
this.documentMappingTurnRight = this.injector.get(DocumentMapping, [EDiffRangeTurn.MODIFIED]);
2222
}
2323

24-
private revokeActionsFactory(turn: EDiffRangeTurn): (sameRange: LineRange) => void {
24+
private revokeActionsFactory(turn: EDiffRangeTurn): (oppositeRange: LineRange) => void {
2525
const mapping = turn === EDiffRangeTurn.ORIGIN ? this.documentMappingTurnLeft : this.documentMappingTurnRight;
2626

27-
return (sameRange: LineRange) => {
28-
const range = mapping.reverse(sameRange);
27+
return (oppositeRange: LineRange) => {
28+
const range = mapping.reverse(oppositeRange);
2929
if (!range) {
3030
return;
3131
}
3232

3333
range.setComplete(false);
34-
sameRange.setComplete(false);
34+
/**
35+
* 这里需要从 mapping 的 adjacentComputeRangeMap 集合里获取并修改 complete 状态,否则变量内存就不是指向同一引用
36+
*/
37+
const realOppositeRange = mapping.adjacentComputeRangeMap.get(range.id);
38+
if (realOppositeRange) {
39+
realOppositeRange.setComplete(false);
40+
}
3541
};
3642
}
3743

3844
private markCompleteFactory(turn: EDiffRangeTurn): (range: LineRange) => void {
3945
const mapping = turn === EDiffRangeTurn.ORIGIN ? this.documentMappingTurnLeft : this.documentMappingTurnRight;
4046

4147
return (range: LineRange) => {
42-
const sameRange = mapping.adjacentComputeRangeMap.get(range.id);
43-
if (!sameRange) {
48+
const oppositeRange = mapping.adjacentComputeRangeMap.get(range.id);
49+
if (!oppositeRange) {
4450
return;
4551
}
4652

4753
// 标记该 range 区域已经解决完成
4854
range.setComplete(true);
49-
sameRange.setComplete(true);
55+
oppositeRange.setComplete(true);
56+
57+
/**
58+
* 如果被标记 complete 的 range 是 merge range 合成的,则需要将另一个 mapping 的对应关系也标记成 complete
59+
*/
60+
if (oppositeRange.turnDirection === ETurnDirection.BOTH) {
61+
const reverseMapping =
62+
range.turnDirection === ETurnDirection.CURRENT ? this.documentMappingTurnRight : this.documentMappingTurnLeft;
63+
64+
const reverse = reverseMapping.reverse(oppositeRange);
65+
if (!reverse) {
66+
return;
67+
}
68+
69+
const adjacentRange = reverseMapping.adjacentComputeRangeMap.get(reverse.id);
70+
if (!adjacentRange) {
71+
return;
72+
}
73+
74+
adjacentRange.setComplete(true);
75+
}
5076
};
5177
}
5278

@@ -66,12 +92,12 @@ export class MappingManagerService extends Disposable {
6692
this.markCompleteFactory(EDiffRangeTurn.MODIFIED)(range);
6793
}
6894

69-
public revokeActionsTurnLeft(sameRange: LineRange): void {
70-
this.revokeActionsFactory(EDiffRangeTurn.ORIGIN)(sameRange);
95+
public revokeActionsTurnLeft(oppositeRange: LineRange): void {
96+
this.revokeActionsFactory(EDiffRangeTurn.ORIGIN)(oppositeRange);
7197
}
7298

73-
public revokeActionsTurnRight(sameRange: LineRange): void {
74-
this.revokeActionsFactory(EDiffRangeTurn.MODIFIED)(sameRange);
99+
public revokeActionsTurnRight(oppositeRange: LineRange): void {
100+
this.revokeActionsFactory(EDiffRangeTurn.MODIFIED)(oppositeRange);
75101
}
76102

77103
/**
@@ -82,19 +108,22 @@ export class MappingManagerService extends Disposable {
82108
[key in EditorViewType.CURRENT | EditorViewType.INCOMING]: LineRange | undefined;
83109
} {
84110
const [turnLeftRange, turnRightRange] = [this.documentMappingTurnLeft, this.documentMappingTurnRight].map(
85-
(mapping) => mapping.findNextSameRange(target),
111+
(mapping) => mapping.findNextOppositeRange(target),
86112
);
87113
return {
88114
[EditorViewType.CURRENT]: turnLeftRange,
89115
[EditorViewType.INCOMING]: turnRightRange,
90116
};
91117
}
92118

93-
public findTouchesRanges(target: LineRange): {
119+
public findTouchesRanges(
120+
target: LineRange,
121+
isAllowContact = true,
122+
): {
94123
[key in EditorViewType.CURRENT | EditorViewType.INCOMING]: LineRange | undefined;
95124
} {
96125
const [turnLeftRange, turnRightRange] = [this.documentMappingTurnLeft, this.documentMappingTurnRight].map(
97-
(mapping) => mapping.findTouchesRange(target),
126+
(mapping) => mapping.findTouchesRange(target, isAllowContact),
98127
);
99128
return {
100129
[EditorViewType.CURRENT]: turnLeftRange,

packages/monaco/src/browser/contrib/merge-editor/merge-editor.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export class MergeEditorService extends Disposable {
105105
this.incomingView.inputDiffComputingResult(changes2);
106106
this.resultView.inputDiffComputingResult(changes2, EDiffRangeTurn.ORIGIN);
107107

108-
this.currentView.updateDecorations();
109-
this.incomingView.updateDecorations();
108+
this.currentView.updateDecorations().updateActions();
109+
this.incomingView.updateDecorations().updateActions();
110110
}
111111
}

packages/monaco/src/browser/contrib/merge-editor/model/document-mapping.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,20 @@ export class DocumentMapping extends Disposable {
2727
super();
2828
}
2929

30+
private ensureSort(values: IterableIterator<LineRange>): LineRange[] {
31+
return Array.from(values).sort((a, b) => a.startLineNumber - b.startLineNumber);
32+
}
33+
3034
public getOriginalRange(): LineRange[] {
31-
return Array.from(
35+
return this.ensureSort(
3236
this.diffRangeTurn === EDiffRangeTurn.ORIGIN
3337
? this.computeRangeMap.values()
3438
: this.adjacentComputeRangeMap.values(),
3539
);
3640
}
3741

3842
public getModifiedRange(): LineRange[] {
39-
return Array.from(
43+
return this.ensureSort(
4044
this.diffRangeTurn === EDiffRangeTurn.ORIGIN
4145
? this.adjacentComputeRangeMap.values()
4246
: this.computeRangeMap.values(),
@@ -78,26 +82,26 @@ export class DocumentMapping extends Disposable {
7882
* @returns
7983
*/
8084
public deltaAdjacentQueueAfter(range: LineRange, offset: number, isContainSelf = false): void {
81-
const sameRange = this.adjacentComputeRangeMap.get(range.id);
82-
if (!sameRange) {
85+
const oppositeRange = this.adjacentComputeRangeMap.get(range.id);
86+
if (!oppositeRange) {
8387
return;
8488
}
8589

8690
for (const [key, pick] of this.adjacentComputeRangeMap.entries()) {
87-
if (pick.isAfter(sameRange)) {
91+
if (pick.isAfter(oppositeRange)) {
8892
this.adjacentComputeRangeMap.set(key, pick.delta(offset));
89-
} else if (isContainSelf && pick.id === sameRange.id) {
93+
} else if (isContainSelf && pick.id === oppositeRange.id) {
9094
this.adjacentComputeRangeMap.set(key, pick.delta(offset));
9195
}
9296
}
9397
}
9498

95-
public deltaEndAdjacentQueue(sameRange: LineRange, offset: number): void {
99+
public deltaEndAdjacentQueue(oppositeRange: LineRange, offset: number): void {
96100
for (const [key, pick] of this.adjacentComputeRangeMap.entries()) {
97-
if (pick.id === sameRange.id) {
98-
this.adjacentComputeRangeMap.set(key, sameRange.deltaEnd(offset));
99-
// 将在 sameRange 之后的 range offset 都增加
100-
} else if (pick.isAfter(sameRange)) {
101+
if (pick.id === oppositeRange.id) {
102+
this.adjacentComputeRangeMap.set(key, oppositeRange.deltaEnd(offset));
103+
// 将在 oppositeRange 之后的 range offset 都增加
104+
} else if (pick.isAfter(oppositeRange)) {
101105
this.adjacentComputeRangeMap.set(key, pick.delta(offset));
102106
}
103107
}
@@ -114,15 +118,15 @@ export class DocumentMapping extends Disposable {
114118
}
115119

116120
/**
117-
* 寻找下一个离 sameRange 最近的 sameRange
118-
* @param sameRange 对位 lineRange,不一定存在于 map 中
121+
* 寻找下一个离 oppositeRange 最近的 oppositeRange
122+
* @param oppositeRange 对位 lineRange,不一定存在于 map 中
119123
* @returns 下一个最近的 lineRange
120124
*/
121-
public findNextSameRange(sameRange: LineRange): LineRange | undefined {
122-
const values = this.adjacentComputeRangeMap.values();
125+
public findNextOppositeRange(oppositeRange: LineRange): LineRange | undefined {
126+
const values = this.ensureSort(this.adjacentComputeRangeMap.values());
123127

124128
for (const range of values) {
125-
if (range.id !== sameRange.id && range.isAfter(sameRange)) {
129+
if (range.id !== oppositeRange.id && range.isAfter(oppositeRange)) {
126130
return range;
127131
}
128132
}
@@ -131,13 +135,13 @@ export class DocumentMapping extends Disposable {
131135
}
132136

133137
/**
134-
* 找出 sameRange 是否被包裹在哪一个 lineRange 里,如果有并返回该 lineRange
138+
* 找出 oppositeRange 是否被包裹在哪一个 lineRange 里,如果有并返回该 lineRange
135139
*/
136-
public findIncludeRange(sameRange: LineRange): LineRange | undefined {
137-
const values = this.adjacentComputeRangeMap.values();
140+
public findIncludeRange(oppositeRange: LineRange): LineRange | undefined {
141+
const values = this.ensureSort(this.adjacentComputeRangeMap.values());
138142

139143
for (const range of values) {
140-
if (range.isInclude(sameRange)) {
144+
if (range.isInclude(oppositeRange)) {
141145
return range;
142146
}
143147
}
@@ -146,13 +150,14 @@ export class DocumentMapping extends Disposable {
146150
}
147151

148152
/**
149-
* 找出 sameRange 是否与哪一个 lineRange 接触
153+
* 找出 oppositeRange 是否与哪一个 lineRange 接触
154+
* @param isAllowContact: 是否将表面接触的 range 也认为是 touch
150155
*/
151-
public findTouchesRange(sameRange: LineRange): LineRange | undefined {
152-
const values = this.adjacentComputeRangeMap.values();
156+
public findTouchesRange(oppositeRange: LineRange, isAllowContact = true): LineRange | undefined {
157+
const values = this.ensureSort(this.adjacentComputeRangeMap.values());
153158

154159
for (const range of values) {
155-
if (range.isTouches(sameRange)) {
160+
if (range.isTouches(oppositeRange) && (isAllowContact ? true : !range.isContact(oppositeRange))) {
156161
return range;
157162
}
158163
}

packages/monaco/src/browser/contrib/merge-editor/model/line-range.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ export class LineRange extends MonacoLineRange implements IRangeContrast {
115115

116116
constructor(startLineNumber: number, endLineNumberExclusive: number) {
117117
super(startLineNumber, endLineNumberExclusive);
118-
this._type = 'insert';
119118
this._isComplete = false;
120119
this._id = uuid(6);
121120
this.mergeStateModel = new MergeStateModel();

packages/monaco/src/browser/contrib/merge-editor/types.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,43 @@ export interface IActionsDescription {
6868
decorationOptions: Omit<IModelDecorationOptions, 'description'>;
6969
}
7070

71+
/**
72+
* 绘制 decoration 和 line widget 线的样式类名集合
73+
*/
74+
export namespace DECORATIONS_CLASSNAME {
75+
export const combine = (...args: string[]) => args.reduce((pre, cur) => pre + ' ' + cur, ' ');
76+
// conflict 操作后虚线框的主类名
77+
export const conflict_wrap = 'conflict-wrap';
78+
// 用于处理每条 decoration 的虚线框的哪个方向需要不闭合
79+
export const stretch_top = 'stretch-top';
80+
export const stretch_bottom = 'stretch-bottom';
81+
export const stretch_left = 'stretch-left';
82+
export const stretch_right = 'stretch-right';
83+
84+
export const margin_className = 'merge-editor-margin-className';
85+
export const diff_line_background = 'merge-editor-diff-line-background';
86+
export const diff_inner_char_background = 'merge-editor-diff-inner-char-background';
87+
export const guide_underline_widget = 'merge-editor-guide-underline-widget';
88+
89+
export const offset_right = 'offset-right';
90+
export const offset_left = 'offset-left';
91+
92+
export const rotate_turn_left = 'rotate-turn-left';
93+
export const rotate_turn_right = 'rotate-turn-right';
94+
}
95+
7196
export const ACCEPT_CURRENT_ACTIONS = 'accpet_current';
7297
export const ACCEPT_COMBINATION_ACTIONS = 'accpet_combination';
7398
export const IGNORE_ACTIONS = 'ignore';
7499
export const REVOKE_ACTIONS = 'revoke';
100+
export const APPEND_ACTIONS = 'append';
75101

76102
export type TActionsType =
77103
| typeof ACCEPT_CURRENT_ACTIONS
78104
| typeof ACCEPT_COMBINATION_ACTIONS
79105
| typeof IGNORE_ACTIONS
80-
| typeof REVOKE_ACTIONS;
106+
| typeof REVOKE_ACTIONS
107+
| typeof APPEND_ACTIONS;
81108

82109
export interface IActionsProvider {
83110
onActionsClick?: (range: LineRange, actionType: TActionsType) => void;
@@ -89,45 +116,27 @@ export interface IActionsProvider {
89116
}
90117

91118
export namespace CONFLICT_ACTIONS_ICON {
92-
export const RIGHT = `conflict-actions ${ACCEPT_CURRENT_ACTIONS} ${getIcon('doubleright')}`;
93-
export const LEFT = `conflict-actions ${ACCEPT_CURRENT_ACTIONS} ${getIcon('doubleleft')}`;
94-
export const WAND = `conflict-actions ${ACCEPT_COMBINATION_ACTIONS} ${getExternalIcon('wand')}`;
95-
export const CLOSE = `conflict-actions ${IGNORE_ACTIONS} ${getIcon('close')}`;
96-
export const REVOKE = `conflict-actions ${REVOKE_ACTIONS} ${getIcon('revoke')}`;
119+
const ACTIONS = 'conflict-actions';
120+
121+
export const RIGHT = `${ACTIONS} ${ACCEPT_CURRENT_ACTIONS} ${getIcon('doubleright')}`;
122+
export const ROTATE_RIGHT = `${ACTIONS} ${APPEND_ACTIONS} ${DECORATIONS_CLASSNAME.rotate_turn_right} ${getIcon(
123+
'doubleright',
124+
)}`;
125+
export const LEFT = `${ACTIONS} ${ACCEPT_CURRENT_ACTIONS} ${getIcon('doubleleft')}`;
126+
export const ROTATE_LEFT = `${ACTIONS} ${APPEND_ACTIONS} ${DECORATIONS_CLASSNAME.rotate_turn_left} ${getIcon(
127+
'doubleleft',
128+
)}`;
129+
export const WAND = `${ACTIONS} ${ACCEPT_COMBINATION_ACTIONS} ${getExternalIcon('wand')}`;
130+
export const CLOSE = `${ACTIONS} ${IGNORE_ACTIONS} ${getIcon('close')}`;
131+
export const REVOKE = `${ACTIONS} ${REVOKE_ACTIONS} ${getIcon('revoke')}`;
97132
}
98133

99134
// 用来寻址点击事件时的标记
100135
export const ADDRESSING_TAG_CLASSNAME = 'ADDRESSING_TAG_CLASSNAME_';
101136

102-
/**
103-
* 绘制 decoration 和 line widget 线的样式类名集合
104-
*/
105-
export namespace DECORATIONS_CLASSNAME {
106-
export const combine = (...args: string[]) => args.reduce((pre, cur) => pre + ' ' + cur, ' ');
107-
// conflict 操作后虚线框的主类名
108-
export const conflict_wrap = 'conflict-wrap';
109-
// 用于处理每条 decoration 的虚线框的哪个方向需要不闭合
110-
export const stretch_top = 'stretch-top';
111-
export const stretch_bottom = 'stretch-bottom';
112-
export const stretch_left = 'stretch-left';
113-
export const stretch_right = 'stretch-right';
114-
115-
export const margin_className = 'merge-editor-margin-className';
116-
export const diff_line_background = 'merge-editor-diff-line-background';
117-
export const diff_inner_char_background = 'merge-editor-diff-inner-char-background';
118-
export const guide_underline_widget = 'merge-editor-guide-underline-widget';
119-
120-
export const offset_right = 'offset-right';
121-
export const offset_left = 'offset-left';
122-
}
123-
124137
export interface IConflictActionsEvent {
125138
range: LineRange;
126-
action:
127-
| typeof ACCEPT_CURRENT_ACTIONS
128-
| typeof ACCEPT_COMBINATION_ACTIONS
129-
| typeof IGNORE_ACTIONS
130-
| typeof REVOKE_ACTIONS;
139+
action: TActionsType;
131140
withViewType: EditorViewType;
132141
}
133142

packages/monaco/src/browser/contrib/merge-editor/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { InnerRange } from './model/inner-range';
22
import { LineRange } from './model/line-range';
33
import { LineRangeMapping } from './model/line-range-mapping';
4-
import { EditorViewType } from './types';
54

65
export const flatOriginal = (changes: LineRangeMapping[]): LineRange[] =>
76
changes.map((c) => c.originalRange as LineRange);

0 commit comments

Comments
 (0)