Skip to content

Commit 17c06c1

Browse files
committed
updating arrow button handling and visibility
1 parent e534b4c commit 17c06c1

File tree

2 files changed

+85
-86
lines changed

2 files changed

+85
-86
lines changed

src/diff/cell.ts

Lines changed: 53 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import { python } from '@codemirror/lang-python';
22
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';
3+
import { EditorView } from '@codemirror/view';
104
import { jupyterTheme } from '@jupyterlab/codemirror';
115
import { Message } from '@lumino/messaging';
126
import { Widget } from '@lumino/widgets';
@@ -58,8 +52,7 @@ class CodeMirrorSplitDiffWidget extends BaseDiffWidget {
5852
basicSetup,
5953
python(),
6054
EditorView.editable.of(false),
61-
jupyterTheme,
62-
splitDiffDecoField
55+
jupyterTheme
6356
]
6457
},
6558
b: {
@@ -69,7 +62,6 @@ class CodeMirrorSplitDiffWidget extends BaseDiffWidget {
6962
python(),
7063
EditorView.editable.of(true),
7164
jupyterTheme,
72-
splitDiffDecoField,
7365
EditorView.updateListener.of(update => {
7466
if (update.docChanged) {
7567
const newText = update.state.doc.toString();
@@ -87,107 +79,88 @@ class CodeMirrorSplitDiffWidget extends BaseDiffWidget {
8779
highlightChanges: true
8880
});
8981

90-
this._renderArrowButtons();
82+
const container = this._splitView.dom;
83+
const overlay = document.createElement('div');
84+
overlay.className = 'jp-DiffArrowOverlay';
85+
container.appendChild(overlay);
86+
this._arrowOverlay = overlay;
87+
88+
this._addScrollSync();
89+
setTimeout(() => this._renderArrowButtons(), 50);
9190
}
9291

9392
/**
9493
* Render "merge change" buttons in the diff on left editor.
9594
*/
9695
private _renderArrowButtons(): void {
96+
if (!this._splitView) {
97+
return;
98+
}
99+
97100
const paneA = this._splitView.a;
98101
const paneB = this._splitView.b;
99-
100102
const result = getChunks(paneB.state);
101103
const chunks = result?.chunks ?? [];
102104

103-
const updatedSet = new Set<string>();
104-
chunks.forEach((chunk: any) => {
105-
const id = `${chunk.fromA}-${chunk.toA}`;
106-
updatedSet.add(id);
105+
this._arrowOverlay.innerHTML = '';
107106

108-
if (!this._activeChunks.has(id)) {
109-
this._activeChunks.add(id);
110-
}
111-
});
112-
113-
for (const id of this._activeChunks) {
114-
if (!updatedSet.has(id)) {
115-
this._activeChunks.delete(id);
116-
}
117-
}
118-
const builder = new RangeSetBuilder<Decoration>();
119-
120-
chunks.forEach((chunk: any) => {
107+
chunks.forEach(chunk => {
121108
const { fromA, toA, fromB, toB } = chunk;
122-
const id = `${fromA}-${toA}`;
123-
124-
// eslint-disable-next-line @typescript-eslint/no-this-alias
125-
const diffWidget = this;
126-
127-
const arrowWidget = Decoration.widget({
128-
widget: new (class extends WidgetType {
129-
toDOM() {
130-
const arrowBtn = document.createElement('button');
131-
arrowBtn.textContent = '🡪';
132-
arrowBtn.className = 'jp-DiffMergeArrow';
133-
arrowBtn.onclick = () => {
134-
const origText = paneA.state.doc.sliceString(fromA, toA);
135-
136-
paneB.dispatch({
137-
changes: { from: fromB, to: toB, insert: origText }
138-
});
139-
140-
diffWidget._activeChunks.delete(id);
141-
diffWidget._renderArrowButtons();
142-
};
143-
return arrowBtn;
144-
}
145-
})(),
146-
side: 1
147-
});
148-
149-
builder.add(fromA, fromA, arrowWidget);
109+
const lineBlockA = paneA.lineBlockAt(fromA);
110+
const lineBlockB = paneB.lineBlockAt(fromB);
111+
const midTop = (lineBlockA.top + lineBlockB.top) / 2;
112+
113+
const connector = document.createElement('div');
114+
connector.className = 'jp-DiffConnectorLine';
115+
connector.style.top = `${midTop}px`;
116+
117+
const arrowBtn = document.createElement('button');
118+
arrowBtn.textContent = '🡪';
119+
arrowBtn.className = 'jp-DiffArrow';
120+
arrowBtn.title = 'Revert Block';
121+
122+
arrowBtn.onclick = () => {
123+
const origText = paneA.state.doc.sliceString(fromA, toA);
124+
paneB.dispatch({
125+
changes: { from: fromB, to: toB, insert: origText }
126+
});
127+
this._renderArrowButtons();
128+
};
129+
130+
connector.appendChild(arrowBtn);
131+
this._arrowOverlay.appendChild(connector);
150132
});
133+
}
151134

152-
paneA.dispatch({
153-
effects: addSplitDiffDeco.of(builder.finish())
154-
});
135+
/**
136+
* Keep arrow overlay in sync with editor scroll.
137+
*/
138+
private _addScrollSync(): void {
139+
const paneA = this._splitView.a;
140+
const paneB = this._splitView.b;
141+
const sync = () => this._renderArrowButtons();
142+
paneA.scrollDOM.addEventListener('scroll', sync);
143+
paneB.scrollDOM.addEventListener('scroll', sync);
155144
}
156145

157146
private _destroySplitView(): void {
158147
if (this._splitView) {
159148
this._splitView.destroy();
160149
this._splitView = null!;
161150
}
151+
if (this._arrowOverlay) {
152+
this._arrowOverlay.remove();
153+
}
162154
}
163155

164156
private _originalCode: string;
165157
private _modifiedCode: string;
166-
private _activeChunks = new Set<string>();
167-
158+
private _arrowOverlay!: HTMLDivElement;
168159
private _splitView!: MergeView & {
169160
a: EditorView;
170161
b: EditorView;
171162
};
172163
}
173-
174-
const addSplitDiffDeco = StateEffect.define<DecorationSet>();
175-
176-
const splitDiffDecoField = StateField.define<DecorationSet>({
177-
create() {
178-
return Decoration.none;
179-
},
180-
update(deco, tr) {
181-
for (const ef of tr.effects) {
182-
if (ef.is(addSplitDiffDeco)) {
183-
return ef.value;
184-
}
185-
}
186-
return deco.map(tr.changes);
187-
},
188-
provide: f => EditorView.decorations.from(f)
189-
});
190-
191164
export async function createCodeMirrorSplitDiffWidget(
192165
options: IDiffWidgetOptions
193166
): Promise<Widget> {

style/base.css

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,37 @@
7878
border-color: var(--jp-border-color1);
7979
}
8080

81-
.jp-DiffMergeArrow {
82-
padding: 2px 6px;
83-
border: none;
84-
background: none;
85-
font-size: 15px;
81+
.jp-DiffArrowOverlay {
82+
position: absolute;
83+
top: 0;
84+
left: 0;
85+
right: 22px;
86+
bottom: 0;
87+
pointer-events: none;
88+
z-index: 3;
89+
}
90+
91+
.jp-DiffConnectorLine {
92+
position: absolute;
93+
width: 100%;
94+
height: 30px;
95+
display: flex;
96+
align-items: center;
97+
justify-content: center;
98+
pointer-events: none;
99+
z-index: 4;
100+
}
101+
102+
.jp-DiffArrow {
103+
pointer-events: all;
104+
background: var(--jp-layout-color1);
105+
padding: 0px 6px;
86106
cursor: pointer;
87-
margin-left: 4px;
107+
border: none;
108+
font-size: 12px;
109+
transition: background-color 0.15s;
110+
}
111+
112+
.jp-DiffArrow:hover {
113+
background-color: var(--jp-layout-color3);
88114
}

0 commit comments

Comments
 (0)