Fossil

Changes On Branch diff-keyboard-navigation
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch diff-keyboard-navigation Excluding Merge-Ins

This is equivalent to a diff from 9b263d87c1 to facfbbd552

2026-01-11
17:04
Modify the accordion panel style to hide only vertical overflow and keep the box shadows of selected and current timeline entries visible in the context section of /info pages. Set the CSS property to `clip' instead of `hidden' to disallow any vertical scrolling (for example, by tabbing through hyperlinks), although this is probably not relevant for accordion panels. check-in: b0188ce122 user: florian tags: trunk
2026-01-10
14:17
Sync with trunk. Leaf check-in: ceee00be59 user: florian tags: standard-cli-colors
14:10
Sync with trunk. Leaf check-in: e4556126da user: florian tags: diff-word-wrap
14:05
Sync with trunk. Leaf check-in: facfbbd552 user: florian tags: diff-keyboard-navigation
14:02
Sync with trunk. check-in: c50c8ed9d7 user: florian tags: timeline-keyboard-navigation
11:52
Do not try to use "notepad" as a text editor on non-windows systems. check-in: 9b263d87c1 user: drh tags: trunk
2026-01-09
16:44
Update the built-in SQLite to the latest trunk check-in for testing. check-in: 2b2530dd07 user: drh tags: trunk
2025-09-26
12:53
Sync with trunk. check-in: f3ff04466d user: florian tags: diff-keyboard-navigation

Changes to src/fossil.diff.js.
92
93
94
95
96
97
98






99
100




























































































































101
102
103
104
105
106
107
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237







+
+
+
+
+
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







        /* Toggle all entries to match this new state. We use click()
           instead of ckbox.checked=... so that the on-change event handler
           fires. */
        if(ckbox.checked!==show) ckbox.click();
      }
    }, false);
  }
  function resetToggles(){
    var cb = document.querySelectorAll(
                        'input[type="checkbox"].diff-toggle:not(:checked)');
    for( var i=0; i<cb.length; i++ ) cb[i].checked = true;
  }
  setTimeout(resetToggles);
});

/*
** Diff keyboard navigation shortcuts:
**
** ### NOTE: The keyboard shortcuts are listed in the /vdiff help screen. ###
**
** Ideas and TODOs:
**
**  o The `timeline-keyboard-navigation' branch removes the unload handler from
**    pages containing timeline snippets, so it's no longer necessary to reset
**    the diff toggles on back/forward navigation in case the mentioned branch
**    is merged with `diff-keyboard-navigation'.
*/
(function(){
  window.addEventListener('load',function(){
    function btnScrollIntoView(e){
      e = e.parentElement;
      var rc = e.getBoundingClientRect();
      var y = 0;
      do{
        y += e.offsetTop;
      }while( e = e.offsetParent );
      window.scrollTo(0,y-6*rc.height);
    }
    document.addEventListener('keydown',function(evt){
      if( evt.target.tagName=='INPUT' || evt.target.tagName=='SELECT' ) return;
      var
        mSHIFT = 1<<13,
        kSHOW = mSHIFT | 73 /* SHIFT+I */,
        kHIDE = 73 /* I */,
        kNEXT = 80 /* P */,
        kPREV = 79 /* O (Letter O) */,
        kUNID = 85 /* U */,
        kSBSD = mSHIFT | 85 /* SHIFT+U */,
        kNULD = 48 /* 0 (Digit Zero) */,
        kUDCD = 68 /* D */,
        mod = evt.altKey<<15|evt.ctrlKey<<14|evt.shiftKey<<13|evt.metaKey<<12,
        key = ( evt.which || evt.keyCode ) | mod;
      switch( key ){
        case kSHOW:
        case kHIDE:
        case kNEXT:
        case kPREV:
        case kUNID:
        case kSBSD:
        case kNULD:
        case kUDCD: break;
        default: return;
      }
      evt.preventDefault();
      evt.stopPropagation();
      if( key==kSHOW || key==kHIDE ){
        var btn = document.getElementsByClassName('diff-toggle');
        if( btn.length>0 ){
          var chg = 0;
          for( var i=0; i<btn.length; i++ ){
            if( btn[i].checked && key==kHIDE ){
              btn[i].click();
              chg++;
            }
            else if( !btn[i].checked && key==kSHOW ){
              btn[i].click();
              chg++;
            }
          }
          if( chg>0 ) btnScrollIntoView(btn[0]);
        }
      }
      else if( key==kNEXT || key==kPREV ){
        var btn = document.getElementsByClassName('diff-toggle');
        if( btn.length>1 ){
          var nFolded = 0, n = -2;
          for( var i=0; i<btn.length; i++ ){
            if( !btn[i].checked ) nFolded++;
          }
          if( nFolded==0 ){
            n = ( key==kNEXT ? 0 : btn.length-1 );
            for( var i=0; i<btn.length; i++ ){
              if( n!=i ) btn[i].click();
            }
          }
          else{
            for( var i=0; i<btn.length; i++ ){
              if( btn[i].checked ){
                if( n==-2 ) n = ( key==kNEXT ? i+1 : i-1 );
                if( n!=i ) btn[i].click();
              }
            }
          }
          if( n==-2 ) n = ( key==kNEXT ? 0 : btn.length-1 );
          if( n in btn ){
            if( !btn[n].checked ) btn[n].click();
            btnScrollIntoView(btn[n]);
          }
        }
        else if( btn.length>0 ){
          btn[0].click();
          btnScrollIntoView(btn[0]);
        }
      }
      else if( key==kUNID || key==kSBSD || key==kNULD ){
        var T={}; T[kUNID]='unified', T[kSBSD]='side-by-side', T[kNULD]='hide';
        var
          type = T[key],
          link = document.querySelector('.smb-'+type+'-diff')
                  || document.querySelector('.sml-'+type+'-diff'),
          href;
        if( link ){
          if( link.dataset.href ) href = link.dataset.href;   // anti-bot
          else href = link.href;
        }
        if( href && href!=location.href.slice(-href.length) ){
          location.href = href;
        }
      }
      else if( key==kUDCD ){
        if( !/[?&]udc=1/.test(location.href) ){
          var sep = /\?/.test(location.href) ? '&' : '?';
          location.href += sep + 'udc=1';
        }
      }
    }/*,true*/);
  },false);
}());

window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;
  const Diff = F.diff = {
    e:{/*certain cached DOM elements*/},
    config: {
      chunkLoadLines: (
        F.config.diffContextLines * 3
Changes to src/info.c.
460
461
462
463
464
465
466
467

468
469
470
471
472
473
474
460
461
462
463
464
465
466

467
468
469
470
471
472
473
474







-
+







  }
}

/*
** Generate javascript to enhance HTML diffs.
*/
void append_diff_javascript(int diffType){
  if( diffType==0 ) return;
  /* Load fossil.diff.js even if diffType==0 to enable keyboard shortcuts. */
  builtin_fossil_js_bundle_or("diff", NULL);
}

/*
** Construct an appropriate diffFlag for text_diff() based on query
** parameters and the to boolean arguments.
*/
647
648
649
650
651
652
653





654


655

656
657


658

659
660
661
662
663
664
665
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661

662
663
664
665
666

667
668
669
670
671
672
673
674







+
+
+
+
+

+
+
-
+


+
+
-
+







  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }
  @ <div class="section" id="changes_section">Changes</div>
  DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */
  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=0 ){
    /* Class "smb-hide-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-hide-diff";
    @ %z(chref(zBtnClass,"%R?diff=0"))Hide&nbsp;Diff</a>
  }
  if( diffType!=1 ){
    /* Class "smb-unified-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-unified-diff";
    @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
    @ %z(chref(zBtnClass,"%R?diff=1%s",zW))Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    /* Class "smb-side-by-side-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-side-by-side-diff";
    @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
    @ %z(chref(zBtnClass,"%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R?diff=%d",diffType))\
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R?diff=%d&w",diffType))Ignore&nbsp;Whitespace</a>
839
840
841
842
843
844
845



846
847
848
849
850
851
852
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864







+
+
+







** when using "fossil ui --from PATH", the --from argument becomes the value
** of the exbase query parameter for the start page.  Note that if PATH
** is a pure hexadecimal string, it is decoded first before being used as
** the pathname.  Real pathnames should contain at least one directory
** separator character.
**
** Other query parameters related to diffs are also accepted.
**
** See the help screen for the /vdiff web page for a list of available
** keyboard shortcuts.
*/
void ckout_page(void){
  int vid;
  const char *zHome;          /* Home directory */
  int nHome;
  const char *zExBase;
  char *zHostname;
906
907
908
909
910
911
912



913
914
915
916
917
918
919
920
921
922
923
924
925

926
927
928
929
930
931
932
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948







+
+
+













+







**
** Display information about a particular check-in.  The exact
** same information is shown on the /info page if the name query
** parameter to /info describes a check-in.
**
** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
** or a tag or branch name that identifies the check-in.
**
** See the help screen for the /vdiff web page for a list of available
** keyboard shortcuts.
*/
void ci_page(void){
  Stmt q1, q2, q3;
  int rid;
  int isLeaf;
  int diffType;        /* 0: no diff,  1: unified,  2: side-by-side */
  const char *zName;   /* Name of the check-in to be displayed */
  const char *zUuid;   /* Hash of zName, found via blob.uuid */
  const char *zParent; /* Hash of the parent check-in (if any) */
  const char *zRe;     /* regex parameter */
  ReCompiled *pRe = 0; /* regex */
  const char *zW;               /* URL param for ignoring whitespace */
  const char *zPage = "vinfo";  /* Page that shows diffs */
  const char *zPageHide = "ci"; /* Page that hides diffs */
  const char *zBrName;          /* Branch name */
  DiffConfig DCfg,*pCfg;        /* Type of diff */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_set_current_feature("vinfo");
  zName = P("name");
1181
1182
1183
1184
1185
1186
1187






1188


1189

1190
1191
1192


1193

1194
1195
1196
1197
1198
1199
1200
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212

1213
1214
1215
1216
1217
1218

1219
1220
1221
1222
1223
1224
1225
1226







+
+
+
+
+
+

+
+
-
+



+
+
-
+







  @ <div class="accordion_panel">
  @ <div class="sectionmenu info-changes-menu">
  /* ^^^ .info-changes-menu is used by diff scroll sync */
  pCfg = construct_diff_flags(diffType, &DCfg);
  DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */
  DCfg.pRe = pRe;
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=0 ){
    /* Class "smb-hide-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-hide-diff";
    @ %z(chref(zBtnClass,"%R/%s/%T?diff=0",zPageHide,zName))\
    @ Hide&nbsp;Diff</a>
  }
  if( diffType!=1 ){
    /* Class "smb-unified-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-unified-diff";
    @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\
    @ %z(chref(zBtnClass,"%R/%s/%T?diff=1%s",zPage,zName,zW))\
    @ Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    /* Class "smb-side-by-side-diff" required by the fossil.diff.js script. */
    const char *zBtnClass = "button smb-side-by-side-diff";
    @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
    @ %z(chref(zBtnClass,"%R/%s/%T?diff=2%s",zPage,zName,zW))\
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
1407
1408
1409
1410
1411
1412
1413














1414
1415
1416
1417
1418
1419
1420
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460







+
+
+
+
+
+
+
+
+
+
+
+
+
+







**   dc=N            show N lines of context around each diff
**   w=BOOLEAN       ignore whitespace when computing diffs
**   nohdr           omit the description at the top of the page
**   nc              omit branch coloration from the header graph
**   inv             "Invert".  Exchange the roles of from= and to=
**
** Show all differences between two check-ins.
**
** Keyboard navigation shortcuts:
**
**    I     Show all file changes.
**    i     Hide all file changes.
**    p     Show only next file change.
**    o     Show only previous file change.
**    u     Reload page in Unified Diff mode.
**    U     Reload page in Side-By-Side Diff mode.
**    0     Reload page in Hidden Diff mode.
**    d     Reload page and set current Diff mode as default.
**
** The keyboard shortcuts also apply to /ckout, /vinfo, /ci and /fdiff
** pages, and to /info pages describing check-in information.
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int diffType = 0;        /* 0: none, 1: unified, 2: side-by-side */
  Manifest *pFrom, *pTo;
  ManifestFile *pFileFrom, *pFileTo;
  const char *zBranch;
1494
1495
1496
1497
1498
1499
1500



1501
1502
1503
1504
1505
1506
1507
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550







+
+
+







    blob_appendf(&qp, "&w");
  }
  cgi_check_for_malice();
  style_set_current_feature("vdiff");
  if( zBranch==0 ){
    style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
  }
  if( diffType!=0 ){
    style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob);
  }
  if( diffType!=2 ){
    style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp,
                          &qpGlob);
  }
  if( diffType!=1 ) {
    style_submenu_element("Unified Diff", "%R/vdiff?diff=1&%b%b", &qp, &qpGlob);
  }
1981
1982
1983
1984
1985
1986
1987



1988
1989
1990
1991
1992
1993
1994
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040







+
+
+







**
**      dc=N             Show N lines of context around each diff
**      patch            Use the patch diff format
**      regex=REGEX      Only show differences that match REGEX
**      sbs=BOOLEAN      Turn side-by-side diffs on and off (default: on)
**      verbose=BOOLEAN  Show more detail when describing artifacts
**      w=BOOLEAN        Ignore whitespace
**
** See the help screen for the /vdiff web page for a list of available
** keyboard shortcuts.
*/
void diff_page(void){
  int v1, v2;
  int isPatch = P("patch")!=0;
  int diffType;          /* 0: none, 1: unified,  2: side-by-side */
  char *zV1;
  char *zV2;
3319
3320
3321
3322
3323
3324
3325



3326
3327
3328
3329
3330
3331
3332
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381







+
+
+







**
** The NAME argument is any valid artifact name: an artifact hash,
** a timestamp, a tag name, etc.
**
** Because NAME can match so many different things (commit artifacts,
** wiki pages, ticket comments, forum posts...) the format of the output
** page depends on the type of artifact that NAME matches.
**
** See the help screen for the /vdiff web page for a list of available
** keyboard shortcuts (if the NAME argument refers to a check-in).
*/
void info_page(void){
  const char *zName;
  Blob uuid;
  int rid;
  int rc;
  int nLen;