11import { python } from '@codemirror/lang-python' ;
22import { 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' ;
104import { jupyterTheme } from '@jupyterlab/codemirror' ;
115import { Message } from '@lumino/messaging' ;
126import { 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-
191164export async function createCodeMirrorSplitDiffWidget (
192165 options : IDiffWidgetOptions
193166) : Promise < Widget > {
0 commit comments