Fossil SCM

Final chunk load mode implemented, but diff table widths have somehow been broken (too wide - not sure since when). This version is suitable for playing with and feedback, but not quite yet for trunk.

stephan 2021-09-11 03:20 diff-js-refactoring
Commit 5938083e4878dd77fa20999e8d3731809f5f4e1a552fbb7c6ee1bfa53e5ad215
1 file changed +120 -56
+120 -56
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -215,11 +215,11 @@
215215
The goal is to base these controls roughly on github's, a good
216216
example of which, for use as a model, is:
217217
218218
https://github.com/msteveb/autosetup/commit/235925e914a52a542
219219
*/
220
- Diff.ChunkLoadControls = function(tr){
220
+ const ChunkLoadControls = function(tr){
221221
this.e = {/*DOM elements*/
222222
tr: tr,
223223
table: tr.parentElement/*TBODY*/.parentElement
224224
};
225225
this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/;
@@ -275,18 +275,18 @@
275275
&& ((this.pos.next.startLhs - this.pos.prev.endLhs)
276276
<= Diff.config.chunkLoadLines))){
277277
/* Place a single button to load the whole block, rather
278278
than separate up/down buttons. */
279279
btnDown = false;
280
- btnUp = this.createButton(0);
280
+ btnUp = this.createButton(this.FetchType.FillGap);
281281
}else{
282282
/* Figure out which chunk-load buttons to add... */
283283
if(this.pos.prev){
284
- btnDown = this.createButton(1);
284
+ btnDown = this.createButton(this.FetchType.PrevDown);
285285
}
286286
if(this.pos.next){
287
- btnUp = this.createButton(-1);
287
+ btnUp = this.createButton(this.FetchType.NextUp);
288288
}
289289
}
290290
//this.e.btnUp = btnUp;
291291
//this.e.btnDown = btnDown;
292292
if(btnDown) D.append(this.e.btnWrapper, btnDown);
@@ -295,47 +295,61 @@
295295
this.e.posState = D.span();
296296
D.append(this.e.btnWrapper, this.e.posState);
297297
this.updatePosDebug();
298298
};
299299
300
- Diff.ChunkLoadControls.prototype = {
300
+ ChunkLoadControls.prototype = {
301
+ /** An "enum" of values describing the types of context
302
+ fetches/operations performed by this type. The values in this
303
+ object must not be changed without modifying all logic which
304
+ relies on their relative order. */
305
+ FetchType:{
306
+ /** Append context to the bottom of the previous diff chunk. */
307
+ PrevDown: 1,
308
+ /** Fill a complete gap between the previous/next diff chunks
309
+ or at the start of the next chunk or end of the previous
310
+ chunks. */
311
+ FillGap: 0,
312
+ /** Prepend context to the start of the next diff chunk. */
313
+ NextUp: -1
314
+ },
301315
config: {
302316
/*
303317
glyphUp: '⇡', //'&#uarr;',
304318
glyphDown: '⇣' //'&#darr;'
305319
*/
306320
},
307321
308322
/**
309323
Creates and returns a button element for fetching a chunk in
310
- the given direction (as documented for fetchChunk()).
324
+ the given fetchType (as documented for fetchChunk()).
311325
*/
312
- createButton: function(direction){
326
+ createButton: function(fetchType){
313327
let b;
314
- switch(direction){
315
- case 1:
328
+ switch(fetchType){
329
+ case this.FetchType.PrevDown:
316330
b = D.append(
317331
D.addClass(D.span(), 'button', 'down'),
318332
D.span(/*glyph holder*/)
319333
);
320334
break;
321
- case 0:
335
+ case this.FetchType.FillGap:
322336
b = D.append(
323337
D.addClass(D.span(), 'button', 'up', 'down'),
324338
D.span(/*glyph holder*/)
325339
);
326340
break;
327
- case -1:
341
+ case this.FetchType.NextUp:
328342
b = D.append(
329343
D.addClass(D.span(), 'button', 'up'),
330344
D.span(/*glyph holder*/)
331345
);
332346
break;
333347
default:
334
- throw new Error("Internal API misuse: unexpected direction value "+direction);
348
+ throw new Error("Internal API misuse: unexpected fetchType value "+fetchType);
335349
}
336
- b.addEventListener('click', ()=>this.fetchChunk(direction),false);
350
+ b.addEventListener('click', ()=>this.fetchChunk(fetchType),false);
337351
return b;
338352
},
339353
340354
updatePosDebug: function(){
341355
if(this.e.posState){
@@ -353,43 +367,55 @@
353367
delete this.e.tr;
354368
delete this.e;
355369
delete this.pos;
356370
},
357371
358
- injectResponse: function f(direction/*as for fetchChunk()*/,
372
+ /**
373
+ If the gap between this.pos.endLhs/startLhs is less than or equal to
374
+ Diff.config.chunkLoadLines then this function replaces any up/down buttons
375
+ with a gap-filler button, else it's a no-op. Returns this object.
376
+ */
377
+ maybeReplaceButtons: function(){
378
+ if(this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines){
379
+ D.clearElement(this.e.btnWrapper);
380
+ D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));
381
+ }
382
+ return this;
383
+ },
384
+
385
+ /**
386
+ Callack for /jchunk responses.
387
+ */
388
+ injectResponse: function f(fetchType/*as for fetchChunk()*/,
359389
urlParam/*from fetchChunk()*/,
360390
lines/*response lines*/){
361391
if(!lines.length){
362392
/* No more data to load */
363393
this.destroy();
364394
return this;
365395
}
366
- console.debug("Loading line range ",urlParam.from,"-",urlParam.to);
396
+ console.debug("Loaded line range ",urlParam.from,"-",urlParam.to, "fetchType ",fetchType);
367397
const lineno = [],
368398
trPrev = this.e.tr.previousElementSibling,
369399
trNext = this.e.tr.nextElementSibling,
370
- doAppend = (!!trPrev && direction>=0) /* true to append to previous TR, else prepend to NEXT TR */;
371
- const tr = trPrev || trNext;
372
- if(direction<0){
373
- console.error("this case is not yet handled",arguments);
374
- return this;
375
- }
400
+ doAppend = (
401
+ !!trPrev && fetchType>=this.FetchType.FillGap
402
+ ) /* true to append to previous TR, else prepend to NEXT TR */;
403
+ const tr = doAppend ? trPrev : trNext;
376404
const joinTr = (
377
- 0===direction && trPrev && trNext
405
+ this.FetchType.FillGap===fetchType && trPrev && trNext
378406
) ? trNext : false
379407
/* Truthy if we want to combine trPrev, the new content, and
380
- trNext into trPrev and remove trNext. */;
408
+ trNext into trPrev and then remove trNext. */;
381409
let i, td;
382
-
383410
if(!f.convertLines){
384411
f.convertLines = function(li){
385412
return li.join('\n')
386413
.replaceAll('&','&amp;')
387414
.replaceAll('<','&lt;')+'\n';
388415
};
389416
}
390
-
391417
if(1){ // LHS line numbers...
392418
const selector = '.difflnl > pre';
393419
td = tr.querySelector(selector);
394420
const lnTo = Math.min(urlParam.to,
395421
urlParam.from +
@@ -435,11 +461,11 @@
435461
content.push(trNext.querySelector(selector).innerHTML);
436462
}
437463
td.innerHTML = content.join('');
438464
}
439465
440
- if(0===direction){
466
+ if(this.FetchType.FillGap===fetchType){
441467
/* Closing the whole gap between two chunks or a whole gap
442468
at the start or end of a diff. */
443469
// RHS line numbers...
444470
let startLnR = this.pos.prev
445471
? this.pos.prev.endRhs+1 /* Closing the whole gap between two chunks
@@ -462,20 +488,18 @@
462488
td.innerHTML = content.join('');
463489
if(joinTr) D.remove(joinTr);
464490
Diff.checkTableWidth(true);
465491
this.destroy();
466492
return this;
467
- }else if(1===direction){
468
- /* Expanding previous TR downwards. */
493
+ }else if(this.FetchType.PrevDown===fetchType){
494
+ /* Append context to previous TR. */
469495
// RHS line numbers...
470496
let startLnR = this.pos.prev.endRhs+1;
471497
lineno.length = lines.length;
472498
for( i = startLnR; i < startLnR + lines.length; ++i ){
473499
lineno[i-startLnR] = i;
474500
}
475
- console.debug("lineno.length =",lineno.length);
476
- console.debug("lines.length =",lineno.length);
477501
this.pos.startLhs += lines.length;
478502
this.pos.prev.endRhs += lines.length;
479503
this.pos.prev.endLhs += lines.length;
480504
const selector = '.difflnr > pre';
481505
td = tr.querySelector(selector);
@@ -487,102 +511,141 @@
487511
td.innerHTML = content.join('');
488512
if(lines.length < (urlParam.to - urlParam.from)){
489513
/* No more data. */
490514
this.destroy();
491515
}else{
516
+ this.maybeReplaceButtons();
517
+ this.updatePosDebug();
518
+ }
519
+ Diff.checkTableWidth(true);
520
+ return this;
521
+ }else if(this.FetchType.NextUp===fetchType){
522
+ /* Prepend content to next TR. */
523
+ // RHS line numbers...
524
+ if(doAppend){
525
+ throw new Error("Internal precondition violation: doAppend is true.");
526
+ }
527
+ let startLnR = this.pos.next.startRhs - lines.length;
528
+ lineno.length = lines.length;
529
+ for( i = startLnR; i < startLnR + lines.length; ++i ){
530
+ lineno[i-startLnR] = i;
531
+ }
532
+ this.pos.endLhs -= lines.length;
533
+ this.pos.next.startRhs -= lines.length;
534
+ this.pos.next.startLhs -= lines.length;
535
+ const selector = '.difflnr > pre';
536
+ td = tr.querySelector(selector);
537
+ const lineNoTxt = lineno.join('\n')+'\n';
538
+ lineno.length = 0;
539
+ td.innerHTML = lineNoTxt + td.innerHTML;
540
+ if(this.pos.endLhs<=1
541
+ || lines.length < (urlParam.to - urlParam.from)){
542
+ /* No more data. */
543
+ this.destroy();
544
+ }else{
545
+ this.maybeReplaceButtons();
492546
this.updatePosDebug();
493547
}
494548
Diff.checkTableWidth(true);
495549
return this;
496550
}else{
497
- console.debug("TODO: handle load of partial next/prev");
498
- this.updatePosDebug();
551
+ throw new Error("Unexpected 'fetchType' value.");
499552
}
500553
},
501554
502555
/**
503
- Fetches and inserts a line chunk. direction is:
504
-
505
- -1 = upwards from next chunk (this.pos.next)
506
-
507
- 0 = the whole gap between this.pos.prev and this.pos.next, or
508
- the whole gap before/after the initial/final chunk in the diff.
509
-
510
- 1 = downwards from the previous chunk (this.pos.prev)
556
+ Fetches and inserts a line chunk. fetchType is:
557
+
558
+ this.FetchType.NextUp = upwards from next chunk (this.pos.next)
559
+
560
+ this.FetchType.FillGap = the whole gap between this.pos.prev
561
+ and this.pos.next, or the whole gap before/after the
562
+ initial/final chunk in the diff.
563
+
564
+ this.FetchType.PrevDown = downwards from the previous chunk
565
+ (this.pos.prev)
511566
512567
Those values are set at the time this object is initialized but
513568
one instance of this class may have 2 buttons, one each for
514
- directions -1 and 1.
569
+ fetchTypes NextUp and PrevDown.
515570
516571
This is an async operation. While it is in transit, any calls
517572
to this function will have no effect except (possibly) to emit
518573
a warning. Returns this object.
519574
*/
520
- fetchChunk: function(direction){
575
+ fetchChunk: function(fetchType){
521576
/* Forewarning, this is a bit confusing: when fetching the
522577
previous lines, we're doing so on behalf of the *next* diff
523578
chunk (this.pos.next), and vice versa. */
524579
if(this.$isFetching){
525580
F.toast.warning("Cannot load chunk while a load is pending.");
526581
return this;
527582
}
528
- if(direction<0 && !this.pos.next
529
- || direction>0 && !this.pos.prev){
583
+ if(fetchType===this.FetchType.NextUp && !this.pos.next
584
+ || fetchType===this.FetchType.PrevDown && !this.pos.prev){
530585
console.error("Attempt to fetch diff lines but don't have any.");
531586
return this;
532587
}
533588
const fOpt = {
534589
urlParams:{
535590
name: this.fileHash, from: 0, to: 0
536591
},
537592
aftersend: ()=>delete this.$isFetching,
538
- onload: (list)=>this.injectResponse(direction,up,list)
593
+ onload: (list)=>this.injectResponse(fetchType,up,list)
539594
};
540595
const up = fOpt.urlParams;
541
- if(direction===0){
596
+ if(fetchType===this.FetchType.FillGap){
542597
/* Easiest case: filling a whole gap. */
543598
up.from = this.pos.startLhs;
544599
up.to = this.pos.endLhs;
545
- }else if(1===direction){
546
- /* Expand previous TR downwards. */
600
+ }else if(this.FetchType.PrevDown===fetchType){
601
+ /* Append to previous TR. */
547602
if(!this.pos.prev){
548603
console.error("Attempt to fetch next diff lines but don't have any.");
549604
return this;
550605
}
551606
up.from = this.pos.prev.endLhs + 1;
552607
up.to = up.from +
553608
Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/;
554609
if( this.pos.next && this.pos.next.startLhs <= up.to ){
555610
up.to = this.pos.next.startLhs - 1;
556
- direction = 0;
611
+ fetchType = this.FetchType.FillGap;
557612
}
558613
}else{
559
- /* Expand next TR upwards */
560
- console.debug("fetchChunk(",direction,")",up);
561
- return this;
614
+ /* Prepend to next TR */
615
+ if(!this.pos.next){
616
+ console.error("Attempt to fetch previous diff lines but don't have any.");
617
+ return this;
618
+ }
619
+ up.to = this.pos.next.startLhs - 1;
620
+ up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
621
+ if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
622
+ up.from = this.pos.prev.endLhs + 1;
623
+ fetchType = this.FetchType.FillGap;
624
+ }
562625
}
563626
this.$isFetching = true;
564
- console.debug("fetchChunk(",direction,")",up);
627
+ console.debug("fetchChunk(",fetchType,")",up);
565628
Diff.fetchArtifactChunk(fOpt);
566629
return this;
567630
}
568631
};
569632
570
- Diff.addDiffSkipHandlers = function(){
633
+ const addDiffSkipHandlers = function(){
571634
const tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)');
572635
/* Potential performance-related TODO: instead of installing all
573636
of these at once, install them as the corresponding TR is
574637
scrolled into view. */
575638
tables.forEach(function(table){
576639
D.addClass(table, 'diffskipped'/*avoid processing these more than once */);
577640
table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
578
- new Diff.ChunkLoadControls(D.addClass(tr, 'jchunk'));
641
+ new ChunkLoadControls(D.addClass(tr, 'jchunk'));
579642
});
580643
});
581644
return F;
582645
};
583
- Diff.addDiffSkipHandlers();
646
+ addDiffSkipHandlers();
584647
});
585648
586649
/* Refinements to the display of unified and side-by-side diffs.
587650
**
588651
** In all cases, the table columns tagged with "difftxt" are expanded,
@@ -619,10 +682,11 @@
619682
if(!f.allDiffs){
620683
f.allDiffs = document.querySelectorAll('table.diff');
621684
}
622685
w = f.lastWidth;
623686
f.allDiffs.forEach((e)=>e.style.maxWidth = w + "px");
687
+ //console.debug("checkTableWidth(",force,") f.lastWidth =",f.lastWidth);
624688
return this;
625689
};
626690
627691
const scrollLeft = function(event){
628692
//console.debug("scrollLeft",this,event);
629693
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -215,11 +215,11 @@
215 The goal is to base these controls roughly on github's, a good
216 example of which, for use as a model, is:
217
218 https://github.com/msteveb/autosetup/commit/235925e914a52a542
219 */
220 Diff.ChunkLoadControls = function(tr){
221 this.e = {/*DOM elements*/
222 tr: tr,
223 table: tr.parentElement/*TBODY*/.parentElement
224 };
225 this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/;
@@ -275,18 +275,18 @@
275 && ((this.pos.next.startLhs - this.pos.prev.endLhs)
276 <= Diff.config.chunkLoadLines))){
277 /* Place a single button to load the whole block, rather
278 than separate up/down buttons. */
279 btnDown = false;
280 btnUp = this.createButton(0);
281 }else{
282 /* Figure out which chunk-load buttons to add... */
283 if(this.pos.prev){
284 btnDown = this.createButton(1);
285 }
286 if(this.pos.next){
287 btnUp = this.createButton(-1);
288 }
289 }
290 //this.e.btnUp = btnUp;
291 //this.e.btnDown = btnDown;
292 if(btnDown) D.append(this.e.btnWrapper, btnDown);
@@ -295,47 +295,61 @@
295 this.e.posState = D.span();
296 D.append(this.e.btnWrapper, this.e.posState);
297 this.updatePosDebug();
298 };
299
300 Diff.ChunkLoadControls.prototype = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301 config: {
302 /*
303 glyphUp: '⇡', //'&#uarr;',
304 glyphDown: '⇣' //'&#darr;'
305 */
306 },
307
308 /**
309 Creates and returns a button element for fetching a chunk in
310 the given direction (as documented for fetchChunk()).
311 */
312 createButton: function(direction){
313 let b;
314 switch(direction){
315 case 1:
316 b = D.append(
317 D.addClass(D.span(), 'button', 'down'),
318 D.span(/*glyph holder*/)
319 );
320 break;
321 case 0:
322 b = D.append(
323 D.addClass(D.span(), 'button', 'up', 'down'),
324 D.span(/*glyph holder*/)
325 );
326 break;
327 case -1:
328 b = D.append(
329 D.addClass(D.span(), 'button', 'up'),
330 D.span(/*glyph holder*/)
331 );
332 break;
333 default:
334 throw new Error("Internal API misuse: unexpected direction value "+direction);
335 }
336 b.addEventListener('click', ()=>this.fetchChunk(direction),false);
337 return b;
338 },
339
340 updatePosDebug: function(){
341 if(this.e.posState){
@@ -353,43 +367,55 @@
353 delete this.e.tr;
354 delete this.e;
355 delete this.pos;
356 },
357
358 injectResponse: function f(direction/*as for fetchChunk()*/,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359 urlParam/*from fetchChunk()*/,
360 lines/*response lines*/){
361 if(!lines.length){
362 /* No more data to load */
363 this.destroy();
364 return this;
365 }
366 console.debug("Loading line range ",urlParam.from,"-",urlParam.to);
367 const lineno = [],
368 trPrev = this.e.tr.previousElementSibling,
369 trNext = this.e.tr.nextElementSibling,
370 doAppend = (!!trPrev && direction>=0) /* true to append to previous TR, else prepend to NEXT TR */;
371 const tr = trPrev || trNext;
372 if(direction<0){
373 console.error("this case is not yet handled",arguments);
374 return this;
375 }
376 const joinTr = (
377 0===direction && trPrev && trNext
378 ) ? trNext : false
379 /* Truthy if we want to combine trPrev, the new content, and
380 trNext into trPrev and remove trNext. */;
381 let i, td;
382
383 if(!f.convertLines){
384 f.convertLines = function(li){
385 return li.join('\n')
386 .replaceAll('&','&amp;')
387 .replaceAll('<','&lt;')+'\n';
388 };
389 }
390
391 if(1){ // LHS line numbers...
392 const selector = '.difflnl > pre';
393 td = tr.querySelector(selector);
394 const lnTo = Math.min(urlParam.to,
395 urlParam.from +
@@ -435,11 +461,11 @@
435 content.push(trNext.querySelector(selector).innerHTML);
436 }
437 td.innerHTML = content.join('');
438 }
439
440 if(0===direction){
441 /* Closing the whole gap between two chunks or a whole gap
442 at the start or end of a diff. */
443 // RHS line numbers...
444 let startLnR = this.pos.prev
445 ? this.pos.prev.endRhs+1 /* Closing the whole gap between two chunks
@@ -462,20 +488,18 @@
462 td.innerHTML = content.join('');
463 if(joinTr) D.remove(joinTr);
464 Diff.checkTableWidth(true);
465 this.destroy();
466 return this;
467 }else if(1===direction){
468 /* Expanding previous TR downwards. */
469 // RHS line numbers...
470 let startLnR = this.pos.prev.endRhs+1;
471 lineno.length = lines.length;
472 for( i = startLnR; i < startLnR + lines.length; ++i ){
473 lineno[i-startLnR] = i;
474 }
475 console.debug("lineno.length =",lineno.length);
476 console.debug("lines.length =",lineno.length);
477 this.pos.startLhs += lines.length;
478 this.pos.prev.endRhs += lines.length;
479 this.pos.prev.endLhs += lines.length;
480 const selector = '.difflnr > pre';
481 td = tr.querySelector(selector);
@@ -487,102 +511,141 @@
487 td.innerHTML = content.join('');
488 if(lines.length < (urlParam.to - urlParam.from)){
489 /* No more data. */
490 this.destroy();
491 }else{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492 this.updatePosDebug();
493 }
494 Diff.checkTableWidth(true);
495 return this;
496 }else{
497 console.debug("TODO: handle load of partial next/prev");
498 this.updatePosDebug();
499 }
500 },
501
502 /**
503 Fetches and inserts a line chunk. direction is:
504
505 -1 = upwards from next chunk (this.pos.next)
506
507 0 = the whole gap between this.pos.prev and this.pos.next, or
508 the whole gap before/after the initial/final chunk in the diff.
509
510 1 = downwards from the previous chunk (this.pos.prev)
 
 
511
512 Those values are set at the time this object is initialized but
513 one instance of this class may have 2 buttons, one each for
514 directions -1 and 1.
515
516 This is an async operation. While it is in transit, any calls
517 to this function will have no effect except (possibly) to emit
518 a warning. Returns this object.
519 */
520 fetchChunk: function(direction){
521 /* Forewarning, this is a bit confusing: when fetching the
522 previous lines, we're doing so on behalf of the *next* diff
523 chunk (this.pos.next), and vice versa. */
524 if(this.$isFetching){
525 F.toast.warning("Cannot load chunk while a load is pending.");
526 return this;
527 }
528 if(direction<0 && !this.pos.next
529 || direction>0 && !this.pos.prev){
530 console.error("Attempt to fetch diff lines but don't have any.");
531 return this;
532 }
533 const fOpt = {
534 urlParams:{
535 name: this.fileHash, from: 0, to: 0
536 },
537 aftersend: ()=>delete this.$isFetching,
538 onload: (list)=>this.injectResponse(direction,up,list)
539 };
540 const up = fOpt.urlParams;
541 if(direction===0){
542 /* Easiest case: filling a whole gap. */
543 up.from = this.pos.startLhs;
544 up.to = this.pos.endLhs;
545 }else if(1===direction){
546 /* Expand previous TR downwards. */
547 if(!this.pos.prev){
548 console.error("Attempt to fetch next diff lines but don't have any.");
549 return this;
550 }
551 up.from = this.pos.prev.endLhs + 1;
552 up.to = up.from +
553 Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/;
554 if( this.pos.next && this.pos.next.startLhs <= up.to ){
555 up.to = this.pos.next.startLhs - 1;
556 direction = 0;
557 }
558 }else{
559 /* Expand next TR upwards */
560 console.debug("fetchChunk(",direction,")",up);
561 return this;
 
 
 
 
 
 
 
 
562 }
563 this.$isFetching = true;
564 console.debug("fetchChunk(",direction,")",up);
565 Diff.fetchArtifactChunk(fOpt);
566 return this;
567 }
568 };
569
570 Diff.addDiffSkipHandlers = function(){
571 const tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)');
572 /* Potential performance-related TODO: instead of installing all
573 of these at once, install them as the corresponding TR is
574 scrolled into view. */
575 tables.forEach(function(table){
576 D.addClass(table, 'diffskipped'/*avoid processing these more than once */);
577 table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
578 new Diff.ChunkLoadControls(D.addClass(tr, 'jchunk'));
579 });
580 });
581 return F;
582 };
583 Diff.addDiffSkipHandlers();
584 });
585
586 /* Refinements to the display of unified and side-by-side diffs.
587 **
588 ** In all cases, the table columns tagged with "difftxt" are expanded,
@@ -619,10 +682,11 @@
619 if(!f.allDiffs){
620 f.allDiffs = document.querySelectorAll('table.diff');
621 }
622 w = f.lastWidth;
623 f.allDiffs.forEach((e)=>e.style.maxWidth = w + "px");
 
624 return this;
625 };
626
627 const scrollLeft = function(event){
628 //console.debug("scrollLeft",this,event);
629
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -215,11 +215,11 @@
215 The goal is to base these controls roughly on github's, a good
216 example of which, for use as a model, is:
217
218 https://github.com/msteveb/autosetup/commit/235925e914a52a542
219 */
220 const ChunkLoadControls = function(tr){
221 this.e = {/*DOM elements*/
222 tr: tr,
223 table: tr.parentElement/*TBODY*/.parentElement
224 };
225 this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/;
@@ -275,18 +275,18 @@
275 && ((this.pos.next.startLhs - this.pos.prev.endLhs)
276 <= Diff.config.chunkLoadLines))){
277 /* Place a single button to load the whole block, rather
278 than separate up/down buttons. */
279 btnDown = false;
280 btnUp = this.createButton(this.FetchType.FillGap);
281 }else{
282 /* Figure out which chunk-load buttons to add... */
283 if(this.pos.prev){
284 btnDown = this.createButton(this.FetchType.PrevDown);
285 }
286 if(this.pos.next){
287 btnUp = this.createButton(this.FetchType.NextUp);
288 }
289 }
290 //this.e.btnUp = btnUp;
291 //this.e.btnDown = btnDown;
292 if(btnDown) D.append(this.e.btnWrapper, btnDown);
@@ -295,47 +295,61 @@
295 this.e.posState = D.span();
296 D.append(this.e.btnWrapper, this.e.posState);
297 this.updatePosDebug();
298 };
299
300 ChunkLoadControls.prototype = {
301 /** An "enum" of values describing the types of context
302 fetches/operations performed by this type. The values in this
303 object must not be changed without modifying all logic which
304 relies on their relative order. */
305 FetchType:{
306 /** Append context to the bottom of the previous diff chunk. */
307 PrevDown: 1,
308 /** Fill a complete gap between the previous/next diff chunks
309 or at the start of the next chunk or end of the previous
310 chunks. */
311 FillGap: 0,
312 /** Prepend context to the start of the next diff chunk. */
313 NextUp: -1
314 },
315 config: {
316 /*
317 glyphUp: '⇡', //'&#uarr;',
318 glyphDown: '⇣' //'&#darr;'
319 */
320 },
321
322 /**
323 Creates and returns a button element for fetching a chunk in
324 the given fetchType (as documented for fetchChunk()).
325 */
326 createButton: function(fetchType){
327 let b;
328 switch(fetchType){
329 case this.FetchType.PrevDown:
330 b = D.append(
331 D.addClass(D.span(), 'button', 'down'),
332 D.span(/*glyph holder*/)
333 );
334 break;
335 case this.FetchType.FillGap:
336 b = D.append(
337 D.addClass(D.span(), 'button', 'up', 'down'),
338 D.span(/*glyph holder*/)
339 );
340 break;
341 case this.FetchType.NextUp:
342 b = D.append(
343 D.addClass(D.span(), 'button', 'up'),
344 D.span(/*glyph holder*/)
345 );
346 break;
347 default:
348 throw new Error("Internal API misuse: unexpected fetchType value "+fetchType);
349 }
350 b.addEventListener('click', ()=>this.fetchChunk(fetchType),false);
351 return b;
352 },
353
354 updatePosDebug: function(){
355 if(this.e.posState){
@@ -353,43 +367,55 @@
367 delete this.e.tr;
368 delete this.e;
369 delete this.pos;
370 },
371
372 /**
373 If the gap between this.pos.endLhs/startLhs is less than or equal to
374 Diff.config.chunkLoadLines then this function replaces any up/down buttons
375 with a gap-filler button, else it's a no-op. Returns this object.
376 */
377 maybeReplaceButtons: function(){
378 if(this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines){
379 D.clearElement(this.e.btnWrapper);
380 D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap));
381 }
382 return this;
383 },
384
385 /**
386 Callack for /jchunk responses.
387 */
388 injectResponse: function f(fetchType/*as for fetchChunk()*/,
389 urlParam/*from fetchChunk()*/,
390 lines/*response lines*/){
391 if(!lines.length){
392 /* No more data to load */
393 this.destroy();
394 return this;
395 }
396 console.debug("Loaded line range ",urlParam.from,"-",urlParam.to, "fetchType ",fetchType);
397 const lineno = [],
398 trPrev = this.e.tr.previousElementSibling,
399 trNext = this.e.tr.nextElementSibling,
400 doAppend = (
401 !!trPrev && fetchType>=this.FetchType.FillGap
402 ) /* true to append to previous TR, else prepend to NEXT TR */;
403 const tr = doAppend ? trPrev : trNext;
 
 
404 const joinTr = (
405 this.FetchType.FillGap===fetchType && trPrev && trNext
406 ) ? trNext : false
407 /* Truthy if we want to combine trPrev, the new content, and
408 trNext into trPrev and then remove trNext. */;
409 let i, td;
 
410 if(!f.convertLines){
411 f.convertLines = function(li){
412 return li.join('\n')
413 .replaceAll('&','&amp;')
414 .replaceAll('<','&lt;')+'\n';
415 };
416 }
 
417 if(1){ // LHS line numbers...
418 const selector = '.difflnl > pre';
419 td = tr.querySelector(selector);
420 const lnTo = Math.min(urlParam.to,
421 urlParam.from +
@@ -435,11 +461,11 @@
461 content.push(trNext.querySelector(selector).innerHTML);
462 }
463 td.innerHTML = content.join('');
464 }
465
466 if(this.FetchType.FillGap===fetchType){
467 /* Closing the whole gap between two chunks or a whole gap
468 at the start or end of a diff. */
469 // RHS line numbers...
470 let startLnR = this.pos.prev
471 ? this.pos.prev.endRhs+1 /* Closing the whole gap between two chunks
@@ -462,20 +488,18 @@
488 td.innerHTML = content.join('');
489 if(joinTr) D.remove(joinTr);
490 Diff.checkTableWidth(true);
491 this.destroy();
492 return this;
493 }else if(this.FetchType.PrevDown===fetchType){
494 /* Append context to previous TR. */
495 // RHS line numbers...
496 let startLnR = this.pos.prev.endRhs+1;
497 lineno.length = lines.length;
498 for( i = startLnR; i < startLnR + lines.length; ++i ){
499 lineno[i-startLnR] = i;
500 }
 
 
501 this.pos.startLhs += lines.length;
502 this.pos.prev.endRhs += lines.length;
503 this.pos.prev.endLhs += lines.length;
504 const selector = '.difflnr > pre';
505 td = tr.querySelector(selector);
@@ -487,102 +511,141 @@
511 td.innerHTML = content.join('');
512 if(lines.length < (urlParam.to - urlParam.from)){
513 /* No more data. */
514 this.destroy();
515 }else{
516 this.maybeReplaceButtons();
517 this.updatePosDebug();
518 }
519 Diff.checkTableWidth(true);
520 return this;
521 }else if(this.FetchType.NextUp===fetchType){
522 /* Prepend content to next TR. */
523 // RHS line numbers...
524 if(doAppend){
525 throw new Error("Internal precondition violation: doAppend is true.");
526 }
527 let startLnR = this.pos.next.startRhs - lines.length;
528 lineno.length = lines.length;
529 for( i = startLnR; i < startLnR + lines.length; ++i ){
530 lineno[i-startLnR] = i;
531 }
532 this.pos.endLhs -= lines.length;
533 this.pos.next.startRhs -= lines.length;
534 this.pos.next.startLhs -= lines.length;
535 const selector = '.difflnr > pre';
536 td = tr.querySelector(selector);
537 const lineNoTxt = lineno.join('\n')+'\n';
538 lineno.length = 0;
539 td.innerHTML = lineNoTxt + td.innerHTML;
540 if(this.pos.endLhs<=1
541 || lines.length < (urlParam.to - urlParam.from)){
542 /* No more data. */
543 this.destroy();
544 }else{
545 this.maybeReplaceButtons();
546 this.updatePosDebug();
547 }
548 Diff.checkTableWidth(true);
549 return this;
550 }else{
551 throw new Error("Unexpected 'fetchType' value.");
 
552 }
553 },
554
555 /**
556 Fetches and inserts a line chunk. fetchType is:
557
558 this.FetchType.NextUp = upwards from next chunk (this.pos.next)
559
560 this.FetchType.FillGap = the whole gap between this.pos.prev
561 and this.pos.next, or the whole gap before/after the
562 initial/final chunk in the diff.
563
564 this.FetchType.PrevDown = downwards from the previous chunk
565 (this.pos.prev)
566
567 Those values are set at the time this object is initialized but
568 one instance of this class may have 2 buttons, one each for
569 fetchTypes NextUp and PrevDown.
570
571 This is an async operation. While it is in transit, any calls
572 to this function will have no effect except (possibly) to emit
573 a warning. Returns this object.
574 */
575 fetchChunk: function(fetchType){
576 /* Forewarning, this is a bit confusing: when fetching the
577 previous lines, we're doing so on behalf of the *next* diff
578 chunk (this.pos.next), and vice versa. */
579 if(this.$isFetching){
580 F.toast.warning("Cannot load chunk while a load is pending.");
581 return this;
582 }
583 if(fetchType===this.FetchType.NextUp && !this.pos.next
584 || fetchType===this.FetchType.PrevDown && !this.pos.prev){
585 console.error("Attempt to fetch diff lines but don't have any.");
586 return this;
587 }
588 const fOpt = {
589 urlParams:{
590 name: this.fileHash, from: 0, to: 0
591 },
592 aftersend: ()=>delete this.$isFetching,
593 onload: (list)=>this.injectResponse(fetchType,up,list)
594 };
595 const up = fOpt.urlParams;
596 if(fetchType===this.FetchType.FillGap){
597 /* Easiest case: filling a whole gap. */
598 up.from = this.pos.startLhs;
599 up.to = this.pos.endLhs;
600 }else if(this.FetchType.PrevDown===fetchType){
601 /* Append to previous TR. */
602 if(!this.pos.prev){
603 console.error("Attempt to fetch next diff lines but don't have any.");
604 return this;
605 }
606 up.from = this.pos.prev.endLhs + 1;
607 up.to = up.from +
608 Diff.config.chunkLoadLines - 1/*b/c request range is inclusive*/;
609 if( this.pos.next && this.pos.next.startLhs <= up.to ){
610 up.to = this.pos.next.startLhs - 1;
611 fetchType = this.FetchType.FillGap;
612 }
613 }else{
614 /* Prepend to next TR */
615 if(!this.pos.next){
616 console.error("Attempt to fetch previous diff lines but don't have any.");
617 return this;
618 }
619 up.to = this.pos.next.startLhs - 1;
620 up.from = Math.max(1, up.to - Diff.config.chunkLoadLines + 1);
621 if( this.pos.prev && this.pos.prev.endLhs >= up.from ){
622 up.from = this.pos.prev.endLhs + 1;
623 fetchType = this.FetchType.FillGap;
624 }
625 }
626 this.$isFetching = true;
627 console.debug("fetchChunk(",fetchType,")",up);
628 Diff.fetchArtifactChunk(fOpt);
629 return this;
630 }
631 };
632
633 const addDiffSkipHandlers = function(){
634 const tables = document.querySelectorAll('table.diff[data-lefthash]:not(.diffskipped)');
635 /* Potential performance-related TODO: instead of installing all
636 of these at once, install them as the corresponding TR is
637 scrolled into view. */
638 tables.forEach(function(table){
639 D.addClass(table, 'diffskipped'/*avoid processing these more than once */);
640 table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){
641 new ChunkLoadControls(D.addClass(tr, 'jchunk'));
642 });
643 });
644 return F;
645 };
646 addDiffSkipHandlers();
647 });
648
649 /* Refinements to the display of unified and side-by-side diffs.
650 **
651 ** In all cases, the table columns tagged with "difftxt" are expanded,
@@ -619,10 +682,11 @@
682 if(!f.allDiffs){
683 f.allDiffs = document.querySelectorAll('table.diff');
684 }
685 w = f.lastWidth;
686 f.allDiffs.forEach((e)=>e.style.maxWidth = w + "px");
687 //console.debug("checkTableWidth(",force,") f.lastWidth =",f.lastWidth);
688 return this;
689 };
690
691 const scrollLeft = function(event){
692 //console.debug("scrollLeft",this,event);
693

Keyboard Shortcuts

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