|
1 | 1 | import { python } from '@codemirror/lang-python'; |
2 | | -import { MergeView } from '@codemirror/merge'; |
3 | | -import { EditorView } from '@codemirror/view'; |
| 2 | +import { MergeView, getChunks } from '@codemirror/merge'; |
| 3 | +import { |
| 4 | + EditorView, |
| 5 | + Decoration, |
| 6 | + WidgetType, |
| 7 | + DecorationSet |
| 8 | +} from '@codemirror/view'; |
| 9 | +import { StateEffect, StateField, RangeSetBuilder } from '@codemirror/state'; |
4 | 10 | import { jupyterTheme } from '@jupyterlab/codemirror'; |
5 | 11 | import { Message } from '@lumino/messaging'; |
6 | 12 | import { Widget } from '@lumino/widgets'; |
@@ -52,37 +58,114 @@ class CodeMirrorSplitDiffWidget extends BaseDiffWidget { |
52 | 58 | basicSetup, |
53 | 59 | python(), |
54 | 60 | EditorView.editable.of(false), |
55 | | - jupyterTheme |
| 61 | + jupyterTheme, |
| 62 | + splitDiffDecorationField |
56 | 63 | ] |
57 | 64 | }, |
58 | 65 | b: { |
59 | 66 | doc: this._modifiedCode, |
60 | 67 | extensions: [ |
61 | 68 | basicSetup, |
62 | 69 | python(), |
63 | | - EditorView.editable.of(false), |
64 | | - jupyterTheme |
| 70 | + EditorView.editable.of(true), |
| 71 | + jupyterTheme, |
| 72 | + splitDiffDecorationField |
65 | 73 | ] |
66 | 74 | }, |
67 | | - parent: this.node |
| 75 | + parent: this.node, |
| 76 | + gutter: true, |
| 77 | + highlightChanges: true |
68 | 78 | }); |
| 79 | + |
| 80 | + this._renderMergeButtons(); |
69 | 81 | } |
70 | 82 |
|
71 | 83 | /** |
72 | | - * Destroy the split view and clean up resources. |
| 84 | + * Render "merge change" buttons in the diff on left editor. |
73 | 85 | */ |
| 86 | + private _renderMergeButtons(): void { |
| 87 | + const editorA = this._splitView.a; |
| 88 | + const editorB = this._splitView.b; |
| 89 | + |
| 90 | + const result = getChunks(editorA.state); |
| 91 | + const chunks = result?.chunks; |
| 92 | + |
| 93 | + if (!chunks || chunks.length === 0) { |
| 94 | + return; |
| 95 | + } |
| 96 | + |
| 97 | + const builder = new RangeSetBuilder<Decoration>(); |
| 98 | + |
| 99 | + chunks.forEach((chunk: any) => { |
| 100 | + const { fromA, toA, fromB, toB } = chunk; |
| 101 | + |
| 102 | + const arrowWidget = Decoration.widget({ |
| 103 | + widget: new (class extends WidgetType { |
| 104 | + toDOM() { |
| 105 | + const btn = document.createElement('button'); |
| 106 | + btn.textContent = '🡪'; |
| 107 | + btn.className = 'jp-DiffMergeArrow'; |
| 108 | + btn.onclick = () => { |
| 109 | + const origText = editorA.state.doc.sliceString(fromA, toA); |
| 110 | + |
| 111 | + editorB.dispatch({ |
| 112 | + changes: { from: fromB, to: toB, insert: origText } |
| 113 | + }); |
| 114 | + editorA.dispatch({ |
| 115 | + effects: addSplitDiffDecorations.of( |
| 116 | + editorA.state.field(splitDiffDecorationField).update({ |
| 117 | + filter: (from, to, value) => from !== fromA |
| 118 | + }) |
| 119 | + ) |
| 120 | + }); |
| 121 | + }; |
| 122 | + return btn; |
| 123 | + } |
| 124 | + })(), |
| 125 | + side: 1 |
| 126 | + }); |
| 127 | + |
| 128 | + builder.add(fromA, fromA, arrowWidget); |
| 129 | + }); |
| 130 | + |
| 131 | + editorA.dispatch({ |
| 132 | + effects: addSplitDiffDecorations.of(builder.finish()) |
| 133 | + }); |
| 134 | + } |
| 135 | + |
74 | 136 | private _destroySplitView(): void { |
75 | 137 | if (this._splitView) { |
76 | 138 | this._splitView.destroy(); |
77 | | - this._splitView = null; |
| 139 | + this._splitView = null!; |
78 | 140 | } |
79 | 141 | } |
80 | 142 |
|
81 | 143 | private _originalCode: string; |
82 | 144 | private _modifiedCode: string; |
83 | | - private _splitView: MergeView | null = null; |
| 145 | + |
| 146 | + private _splitView!: MergeView & { |
| 147 | + a: EditorView; |
| 148 | + b: EditorView; |
| 149 | + }; |
84 | 150 | } |
85 | 151 |
|
| 152 | +const addSplitDiffDecorations = StateEffect.define<DecorationSet>(); |
| 153 | + |
| 154 | +const splitDiffDecorationField = StateField.define<DecorationSet>({ |
| 155 | + create() { |
| 156 | + return Decoration.none; |
| 157 | + }, |
| 158 | + update(deco, tr) { |
| 159 | + for (const ef of tr.effects) { |
| 160 | + if (ef.is(addSplitDiffDecorations)) { |
| 161 | + return ef.value; |
| 162 | + } |
| 163 | + } |
| 164 | + return deco.map(tr.changes); |
| 165 | + }, |
| 166 | + provide: f => EditorView.decorations.from(f) |
| 167 | +}); |
| 168 | + |
86 | 169 | export async function createCodeMirrorSplitDiffWidget( |
87 | 170 | options: IDiffWidgetOptions |
88 | 171 | ): Promise<Widget> { |
|
0 commit comments