Fossil SCM

Refactored tr.diffsplit to hold enough information to allow partial chunk loads in either direction and to know where the next/previous chunks (if any) start/end. Actual loading is currently disabled, pending addition of controls which make use of this new state.

stephan 2021-09-09 15:06 diff-js-refactoring
Commit cedcd3585b4cd8bd9997bca5de7e1f94541d7f5686f95715b0251f8a06a55376
2 files changed +14 +103 -32
--- src/default.css
+++ src/default.css
@@ -549,10 +549,24 @@
549549
/* jchunk gets added from JS to diffskip rows when they are
550550
plugged into the /jchunk route and removed after that data
551551
is fetched. */
552552
background-color: rgba(127,127,127,0.5);
553553
cursor: pointer;
554
+}
555
+tr.diffskip > td.chunkctrl {
556
+ text-align: left;
557
+ font-family: monospace;
558
+ /* Border is only for visibility during development. Remove it when done. */
559
+ border-width: 1px;
560
+ border-style: dotted;
561
+}
562
+tr.diffskip > td.chunkctrl > .button {
563
+ min-width: 1.5em;
564
+ min-height: 1.5em;
565
+ max-width: 1.5em;
566
+ max-height: 1.5em;
567
+ text-align: center;
554568
}
555569
td.diffln {
556570
width: 1px;
557571
text-align: right;
558572
padding: 0 1em 0 0;
559573
--- src/default.css
+++ src/default.css
@@ -549,10 +549,24 @@
549 /* jchunk gets added from JS to diffskip rows when they are
550 plugged into the /jchunk route and removed after that data
551 is fetched. */
552 background-color: rgba(127,127,127,0.5);
553 cursor: pointer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
554 }
555 td.diffln {
556 width: 1px;
557 text-align: right;
558 padding: 0 1em 0 0;
559
--- src/default.css
+++ src/default.css
@@ -549,10 +549,24 @@
549 /* jchunk gets added from JS to diffskip rows when they are
550 plugged into the /jchunk route and removed after that data
551 is fetched. */
552 background-color: rgba(127,127,127,0.5);
553 cursor: pointer;
554 }
555 tr.diffskip > td.chunkctrl {
556 text-align: left;
557 font-family: monospace;
558 /* Border is only for visibility during development. Remove it when done. */
559 border-width: 1px;
560 border-style: dotted;
561 }
562 tr.diffskip > td.chunkctrl > .button {
563 min-width: 1.5em;
564 min-height: 1.5em;
565 max-width: 1.5em;
566 max-height: 1.5em;
567 text-align: center;
568 }
569 td.diffln {
570 width: 1px;
571 text-align: right;
572 padding: 0 1em 0 0;
573
+103 -32
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -80,10 +80,34 @@
8080
if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
8181
fetchOpt.responseType = 'json';
8282
return F.fetch('jchunk', fetchOpt);
8383
};
8484
85
+
86
+ /**
87
+ Extracts either the starting or ending line number from a
88
+ line-numer column in the given tr. isSplit must be true if tr
89
+ represents a split diff, else false. Expects its tr to be valid:
90
+ GIGO applies. Returns the starting line number if getStart, else
91
+ the ending line number. Returns the line number from the LHS file
92
+ if getLHS is true, else the RHS.
93
+ */
94
+ const extractLineNo = function f(getLHS, getStart, tr, isSplit){
95
+ if(!f.rx){
96
+ f.rx = {
97
+ start: /^\s*(\d+)/,
98
+ end: /(\d+)\n?$/
99
+ }
100
+ }
101
+ const td = tr.querySelector('td:nth-child('+(
102
+ /* TD element with the line numbers */
103
+ getLHS ? 1 : (isSplit ? 4 : 2)
104
+ )+')');
105
+ const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText);
106
+ return m ? +m[1] : undefined/*"shouldn't happen"*/;
107
+ };
108
+
85109
/**
86110
Fetches /jchunk for the given TR element then replaces the TR's
87111
contents with data from the result of that request.
88112
*/
89113
const fetchTrChunk = function(tr){
@@ -147,37 +171,18 @@
147171
let lineno = [], i;
148172
for( i = lineFrom; i <= lineTo; ++i ){
149173
lineno.push(i);
150174
}
151175
preLines[0].append(lineno.join('\n')+'\n');
152
- const code = result.join('\n')+'\n';
153
- preCode.forEach((e)=>e.innerText = code);
176
+ if(1){
177
+ const code = result.join('\n')+'\n';
178
+ preCode.forEach((e)=>e.innerText = code);
179
+ }
154180
//console.debug("Updated TR",tr);
155181
Diff.initTableDiff(table).checkTableWidth(true);
156182
/*
157
- At this point we need to:
158
-
159
- - Read the previous TR, if any, to get the preceeding LHS/RHS
160
- line numbers so that we know where to start counting.
161
-
162
- - If there is no previous TR, we're at the top and we
163
- instead need to get the LHS/RHS line numbers from the
164
- following TR's children.
165
-
166
- - D.clearElement(tr) and insert columns appropriate for the
167
- parent table's diff type.
168
-
169
- We can fish the line numbers out of the PRE columns with something
170
- like this inefficient but effective hack:
171
-
172
- theElement.innerText.split(/\n+/)
173
-
174
- (need /\n+/ instead of '\n' b/c of INS/DEL elements)
175
-
176
- Noting that the result array will end with an empty element
177
- due to the trailing \n character, so a call to pop() will be
178
- needed.
183
+ Reminders to self during development:
179184
180185
SBS diff col layout:
181186
<td.diffln.difflnl><pre>...LHS line numbers...</pre></td>
182187
<td.difftxt.difftxtl><pre>...code lines...</pre></td>
183188
<td.diffsep>empty for this case (common lines)</td>
@@ -192,34 +197,100 @@
192197
193198
C-side TODOs:
194199
195200
- If we have that data readily available, it would be a big
196201
help (simplify our line calculations) if we stored the line
197
- number ranges in the (td.diffln pre) elements as
198
- data-startln and data-endln.
202
+ number ranges in all elements which have that state handy.
199203
*/
200204
}
201205
});
202206
};
203207
208
+ /**
209
+ Installs chunk-loading controls into TR element tr. isSplit is true
210
+ if the parent table is a split diff, else false.)
211
+ */
212
+ Diff.ChunkLoadControls = function(isSplit, tr){
213
+ this.isSplit = isSplit;
214
+ this.e = {/*DOM elements*/};
215
+ this.pos = {
216
+ start: +tr.dataset.startln,
217
+ end: +tr.dataset.endln
218
+ };
219
+ this.e.tr = tr;
220
+ D.clearElement(tr);
221
+ this.e.td = D.addClass(
222
+ D.attr(D.td(tr), 'colspan', isSplit ? 5 : 4),
223
+ 'chunkctrl'
224
+ );
225
+ /**
226
+ Depending on various factors, we need one of:
227
+
228
+ - A single button to load all lines then remove this control
229
+
230
+ - A single button to load the initial chunk
231
+
232
+ - Two buttons: one to load upwards, one to load downwards
233
+ */
234
+ if(tr.nextElementSibling){
235
+ this.pos.next = {
236
+ startLhs: extractLineNo(true, true, tr.nextElementSibling, isSplit),
237
+ startRhs: extractLineNo(false, true, tr.nextElementSibling, isSplit)
238
+ };
239
+ }
240
+ if(tr.previousElementSibling){
241
+ this.pos.prev = {
242
+ endLhs: extractLineNo(true, false, tr.previousElementSibling, isSplit),
243
+ endRhs: extractLineNo(false, false, tr.previousElementSibling, isSplit)
244
+ };
245
+ }
246
+ D.append(this.e.td,"Controls pending: ",JSON.stringify(this.pos));
247
+ };
248
+
249
+ Diff.ChunkLoadControls.prototype = {
250
+ config: {
251
+ glyphUp: '&#uarr;',
252
+ glyphDown: '&#darr;'
253
+ }
254
+ };
255
+
204256
Diff.addDiffSkipHandlers = function(){
205257
const tables = document.querySelectorAll('table.diff[data-lefthash]');
206258
if(!tables.length) return F;
207
- const addDiffSkipToTr = function f(tr){
259
+ const addDiffSkipToTr = function f(isSplit, tr){
208260
D.addClass(tr, 'jchunk');
209261
if(!f._handler){
210262
f._handler = function ff(event){
211263
const e = this;
212264
e.removeEventListener('click',ff);
213265
D.removeClass(e, 'jchunk', 'diffskip');
214266
fetchTrChunk(e);
215267
};
216268
}
217
- tr.addEventListener('click', f._handler, false);
269
+ /* TODO:
270
+
271
+ Depending on tr.dataset.{startln,endln}, install one or two
272
+ controls for loading the next diff chunk. For both types of
273
+ diff, put the control(s) into tr->td[0], delete tr->td[1],
274
+ give tr->td[0] a colspan of 2. Change the click handler to
275
+ address those controls, instead of the TR element, for
276
+ purposes of figuring out which lines to fetch. Use a helper
277
+ class to encapsulate the activation and updates of the
278
+ controls (e.g. removing controls which are no longer relevant
279
+ once a chunk is fully loaded).
280
+
281
+ Good example from github to use as a model:
282
+
283
+ https://github.com/msteveb/autosetup/commit/235925e914a52a542
284
+ */
285
+ //tr.addEventListener('click', f._handler, false);
286
+ new Diff.ChunkLoadControls(isSplit, tr);
218287
};
219
- tables.forEach(function(t){
220
- t.querySelectorAll('tr.diffskip[data-startln]').forEach(addDiffSkipToTr);
288
+ tables.forEach(function(table){
289
+ table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
290
+ addDiffSkipToTr(table.classList.contains('splitdiff')/*else udiff*/, tr);
291
+ });
221292
});
222293
};
223294
224295
Diff.addDiffSkipHandlers();
225296
});
@@ -272,11 +343,11 @@
272343
273344
const scrollLeft = function(event){
274345
//console.debug("scrollLeft",this,event);
275346
const table = this.parentElement/*TD*/.parentElement/*TR*/.
276347
parentElement/*TBODY*/.parentElement/*TABLE*/;
277
- table.$txtPres.forEach((e)=>e.scrollLeft = this.scrollLeft);
348
+ table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
278349
return false;
279350
};
280351
Diff.initTableDiff = function f(diff){
281352
if(!diff){
282353
let i, diffs = document.querySelectorAll('table.splitdiff');
@@ -306,11 +377,11 @@
306377
D.addClass(diff, 'scroller');
307378
diff.addEventListener('keydown', function(e){
308379
e = e || event;
309380
const len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
310381
if( !len ) return;
311
- diff.$txtCols[0].scrollLeft += len;
382
+ this.$txtPres[0].scrollLeft += len;
312383
return false;
313384
}, false);
314385
}
315386
return this;
316387
}
317388
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -80,10 +80,34 @@
80 if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
81 fetchOpt.responseType = 'json';
82 return F.fetch('jchunk', fetchOpt);
83 };
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85 /**
86 Fetches /jchunk for the given TR element then replaces the TR's
87 contents with data from the result of that request.
88 */
89 const fetchTrChunk = function(tr){
@@ -147,37 +171,18 @@
147 let lineno = [], i;
148 for( i = lineFrom; i <= lineTo; ++i ){
149 lineno.push(i);
150 }
151 preLines[0].append(lineno.join('\n')+'\n');
152 const code = result.join('\n')+'\n';
153 preCode.forEach((e)=>e.innerText = code);
 
 
154 //console.debug("Updated TR",tr);
155 Diff.initTableDiff(table).checkTableWidth(true);
156 /*
157 At this point we need to:
158
159 - Read the previous TR, if any, to get the preceeding LHS/RHS
160 line numbers so that we know where to start counting.
161
162 - If there is no previous TR, we're at the top and we
163 instead need to get the LHS/RHS line numbers from the
164 following TR's children.
165
166 - D.clearElement(tr) and insert columns appropriate for the
167 parent table's diff type.
168
169 We can fish the line numbers out of the PRE columns with something
170 like this inefficient but effective hack:
171
172 theElement.innerText.split(/\n+/)
173
174 (need /\n+/ instead of '\n' b/c of INS/DEL elements)
175
176 Noting that the result array will end with an empty element
177 due to the trailing \n character, so a call to pop() will be
178 needed.
179
180 SBS diff col layout:
181 <td.diffln.difflnl><pre>...LHS line numbers...</pre></td>
182 <td.difftxt.difftxtl><pre>...code lines...</pre></td>
183 <td.diffsep>empty for this case (common lines)</td>
@@ -192,34 +197,100 @@
192
193 C-side TODOs:
194
195 - If we have that data readily available, it would be a big
196 help (simplify our line calculations) if we stored the line
197 number ranges in the (td.diffln pre) elements as
198 data-startln and data-endln.
199 */
200 }
201 });
202 };
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204 Diff.addDiffSkipHandlers = function(){
205 const tables = document.querySelectorAll('table.diff[data-lefthash]');
206 if(!tables.length) return F;
207 const addDiffSkipToTr = function f(tr){
208 D.addClass(tr, 'jchunk');
209 if(!f._handler){
210 f._handler = function ff(event){
211 const e = this;
212 e.removeEventListener('click',ff);
213 D.removeClass(e, 'jchunk', 'diffskip');
214 fetchTrChunk(e);
215 };
216 }
217 tr.addEventListener('click', f._handler, false);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218 };
219 tables.forEach(function(t){
220 t.querySelectorAll('tr.diffskip[data-startln]').forEach(addDiffSkipToTr);
 
 
221 });
222 };
223
224 Diff.addDiffSkipHandlers();
225 });
@@ -272,11 +343,11 @@
272
273 const scrollLeft = function(event){
274 //console.debug("scrollLeft",this,event);
275 const table = this.parentElement/*TD*/.parentElement/*TR*/.
276 parentElement/*TBODY*/.parentElement/*TABLE*/;
277 table.$txtPres.forEach((e)=>e.scrollLeft = this.scrollLeft);
278 return false;
279 };
280 Diff.initTableDiff = function f(diff){
281 if(!diff){
282 let i, diffs = document.querySelectorAll('table.splitdiff');
@@ -306,11 +377,11 @@
306 D.addClass(diff, 'scroller');
307 diff.addEventListener('keydown', function(e){
308 e = e || event;
309 const len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
310 if( !len ) return;
311 diff.$txtCols[0].scrollLeft += len;
312 return false;
313 }, false);
314 }
315 return this;
316 }
317
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -80,10 +80,34 @@
80 if(!fetchOpt.onerror) fetchOpt.onerror = Diff.config.chunkFetch.onerror;
81 fetchOpt.responseType = 'json';
82 return F.fetch('jchunk', fetchOpt);
83 };
84
85
86 /**
87 Extracts either the starting or ending line number from a
88 line-numer column in the given tr. isSplit must be true if tr
89 represents a split diff, else false. Expects its tr to be valid:
90 GIGO applies. Returns the starting line number if getStart, else
91 the ending line number. Returns the line number from the LHS file
92 if getLHS is true, else the RHS.
93 */
94 const extractLineNo = function f(getLHS, getStart, tr, isSplit){
95 if(!f.rx){
96 f.rx = {
97 start: /^\s*(\d+)/,
98 end: /(\d+)\n?$/
99 }
100 }
101 const td = tr.querySelector('td:nth-child('+(
102 /* TD element with the line numbers */
103 getLHS ? 1 : (isSplit ? 4 : 2)
104 )+')');
105 const m = f.rx[getStart ? 'start' : 'end'].exec(td.innerText);
106 return m ? +m[1] : undefined/*"shouldn't happen"*/;
107 };
108
109 /**
110 Fetches /jchunk for the given TR element then replaces the TR's
111 contents with data from the result of that request.
112 */
113 const fetchTrChunk = function(tr){
@@ -147,37 +171,18 @@
171 let lineno = [], i;
172 for( i = lineFrom; i <= lineTo; ++i ){
173 lineno.push(i);
174 }
175 preLines[0].append(lineno.join('\n')+'\n');
176 if(1){
177 const code = result.join('\n')+'\n';
178 preCode.forEach((e)=>e.innerText = code);
179 }
180 //console.debug("Updated TR",tr);
181 Diff.initTableDiff(table).checkTableWidth(true);
182 /*
183 Reminders to self during development:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
185 SBS diff col layout:
186 <td.diffln.difflnl><pre>...LHS line numbers...</pre></td>
187 <td.difftxt.difftxtl><pre>...code lines...</pre></td>
188 <td.diffsep>empty for this case (common lines)</td>
@@ -192,34 +197,100 @@
197
198 C-side TODOs:
199
200 - If we have that data readily available, it would be a big
201 help (simplify our line calculations) if we stored the line
202 number ranges in all elements which have that state handy.
 
203 */
204 }
205 });
206 };
207
208 /**
209 Installs chunk-loading controls into TR element tr. isSplit is true
210 if the parent table is a split diff, else false.)
211 */
212 Diff.ChunkLoadControls = function(isSplit, tr){
213 this.isSplit = isSplit;
214 this.e = {/*DOM elements*/};
215 this.pos = {
216 start: +tr.dataset.startln,
217 end: +tr.dataset.endln
218 };
219 this.e.tr = tr;
220 D.clearElement(tr);
221 this.e.td = D.addClass(
222 D.attr(D.td(tr), 'colspan', isSplit ? 5 : 4),
223 'chunkctrl'
224 );
225 /**
226 Depending on various factors, we need one of:
227
228 - A single button to load all lines then remove this control
229
230 - A single button to load the initial chunk
231
232 - Two buttons: one to load upwards, one to load downwards
233 */
234 if(tr.nextElementSibling){
235 this.pos.next = {
236 startLhs: extractLineNo(true, true, tr.nextElementSibling, isSplit),
237 startRhs: extractLineNo(false, true, tr.nextElementSibling, isSplit)
238 };
239 }
240 if(tr.previousElementSibling){
241 this.pos.prev = {
242 endLhs: extractLineNo(true, false, tr.previousElementSibling, isSplit),
243 endRhs: extractLineNo(false, false, tr.previousElementSibling, isSplit)
244 };
245 }
246 D.append(this.e.td,"Controls pending: ",JSON.stringify(this.pos));
247 };
248
249 Diff.ChunkLoadControls.prototype = {
250 config: {
251 glyphUp: '&#uarr;',
252 glyphDown: '&#darr;'
253 }
254 };
255
256 Diff.addDiffSkipHandlers = function(){
257 const tables = document.querySelectorAll('table.diff[data-lefthash]');
258 if(!tables.length) return F;
259 const addDiffSkipToTr = function f(isSplit, tr){
260 D.addClass(tr, 'jchunk');
261 if(!f._handler){
262 f._handler = function ff(event){
263 const e = this;
264 e.removeEventListener('click',ff);
265 D.removeClass(e, 'jchunk', 'diffskip');
266 fetchTrChunk(e);
267 };
268 }
269 /* TODO:
270
271 Depending on tr.dataset.{startln,endln}, install one or two
272 controls for loading the next diff chunk. For both types of
273 diff, put the control(s) into tr->td[0], delete tr->td[1],
274 give tr->td[0] a colspan of 2. Change the click handler to
275 address those controls, instead of the TR element, for
276 purposes of figuring out which lines to fetch. Use a helper
277 class to encapsulate the activation and updates of the
278 controls (e.g. removing controls which are no longer relevant
279 once a chunk is fully loaded).
280
281 Good example from github to use as a model:
282
283 https://github.com/msteveb/autosetup/commit/235925e914a52a542
284 */
285 //tr.addEventListener('click', f._handler, false);
286 new Diff.ChunkLoadControls(isSplit, tr);
287 };
288 tables.forEach(function(table){
289 table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
290 addDiffSkipToTr(table.classList.contains('splitdiff')/*else udiff*/, tr);
291 });
292 });
293 };
294
295 Diff.addDiffSkipHandlers();
296 });
@@ -272,11 +343,11 @@
343
344 const scrollLeft = function(event){
345 //console.debug("scrollLeft",this,event);
346 const table = this.parentElement/*TD*/.parentElement/*TR*/.
347 parentElement/*TBODY*/.parentElement/*TABLE*/;
348 table.$txtPres.forEach((e)=>(e===this) ? 1 : (e.scrollLeft = this.scrollLeft));
349 return false;
350 };
351 Diff.initTableDiff = function f(diff){
352 if(!diff){
353 let i, diffs = document.querySelectorAll('table.splitdiff');
@@ -306,11 +377,11 @@
377 D.addClass(diff, 'scroller');
378 diff.addEventListener('keydown', function(e){
379 e = e || event;
380 const len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode];
381 if( !len ) return;
382 this.$txtPres[0].scrollLeft += len;
383 return false;
384 }, false);
385 }
386 return this;
387 }
388

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button