Fossil SCM

Add URI query parameters "dw=" and "dc=" to control the width of columns and the lines of context on side-by-side diffs in the web interface.

drh 2012-02-03 16:15 UTC retro-sbsdiff
Commit 6474a92a87d06f6801d0906eed5e9e148b61b2cf
2 files changed +1 +43 -16
+1
--- src/diff.c
+++ src/diff.c
@@ -30,10 +30,11 @@
3030
#define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */
3131
#define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */
3232
#define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */
3333
#define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */
3434
#define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */
35
+#define DIFF_INLINE 0x0800000 /* Inline (not side-by-side) diff */
3536
3637
#endif /* INTERFACE */
3738
3839
/*
3940
** Maximum length of a line in a text file. (8192)
4041
--- src/diff.c
+++ src/diff.c
@@ -30,10 +30,11 @@
30 #define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */
31 #define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */
32 #define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */
33 #define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */
34 #define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */
 
35
36 #endif /* INTERFACE */
37
38 /*
39 ** Maximum length of a line in a text file. (8192)
40
--- src/diff.c
+++ src/diff.c
@@ -30,10 +30,11 @@
30 #define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */
31 #define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */
32 #define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */
33 #define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */
34 #define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */
35 #define DIFF_INLINE 0x0800000 /* Inline (not side-by-side) diff */
36
37 #endif /* INTERFACE */
38
39 /*
40 ** Maximum length of a line in a text file. (8192)
41
+43 -16
--- src/info.c
+++ src/info.c
@@ -285,18 +285,13 @@
285285
static void append_file_change_line(
286286
const char *zName, /* Name of the file that has changed */
287287
const char *zOld, /* blob.uuid before change. NULL for added files */
288288
const char *zNew, /* blob.uuid after change. NULL for deletes */
289289
const char *zOldName, /* Prior name. NULL if no name change. */
290
- int showDiff, /* Show edit diffs if true */
291
- int sideBySide, /* Show diffs side-by-side */
290
+ int diffFlags, /* Flags for text_diff(). Zero to omit diffs */
292291
int mperm /* executable or symlink permission for zNew */
293292
){
294
- int diffFlags = DIFF_IGNORE_EOLWS | 7;
295
- if( sideBySide ){
296
- diffFlags |= DIFF_SIDEBYSIDE;
297
- }
298293
if( !g.perm.History ){
299294
if( zNew==0 ){
300295
@ <p>Deleted %h(zName)</p>
301296
}else if( zOld==0 ){
302297
@ <p>Added %h(zName)</p>
@@ -306,11 +301,11 @@
306301
@ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared")
307302
@ for %h(zName)</p>
308303
}else{
309304
@ <p>Changes to %h(zName)</p>
310305
}
311
- if( showDiff ){
306
+ if( diffFlags ){
312307
@ <pre style="white-space:pre;">
313308
append_diff(zOld, zNew, diffFlags);
314309
@ </pre>
315310
}
316311
}else{
@@ -332,11 +327,11 @@
332327
@ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
333328
}else{
334329
@ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
335330
@ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
336331
}
337
- if( showDiff ){
332
+ if( diffFlags ){
338333
@ <pre style="white-space:pre;">
339334
append_diff(zOld, zNew, diffFlags);
340335
@ </pre>
341336
}else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
342337
@ &nbsp;&nbsp;
@@ -343,10 +338,38 @@
343338
@ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a>
344339
}
345340
@ </p>
346341
}
347342
}
343
+
344
+/*
345
+** Construct an appropriate diffFlag for text_diff() based on query
346
+** parameters and the to boolean arguments.
347
+*/
348
+static int construct_diff_flags(int showDiff, int sideBySide){
349
+ int diffFlags;
350
+ if( showDiff==0 ){
351
+ diffFlags = 0; /* Zero means do not show any diff */
352
+ }else if( sideBySide ){
353
+ int x;
354
+ diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS;
355
+
356
+ /* "dw" query parameter determines width of each column */
357
+ x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1);
358
+ if( x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK;
359
+ diffFlags += x;
360
+
361
+ /* "dc" query parameter determines lines of context */
362
+ x = atoi(PD("dc","7"));
363
+ if( x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK;
364
+ diffFlags += x;
365
+ }else{
366
+ /* In-line (non-side-by-side) diff */
367
+ diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS;
368
+ }
369
+ return diffFlags;
370
+}
348371
349372
350373
/*
351374
** WEBPAGE: vinfo
352375
** WEBPAGE: ci
@@ -364,12 +387,13 @@
364387
*/
365388
void ci_page(void){
366389
Stmt q;
367390
int rid;
368391
int isLeaf;
369
- int showDiff;
370
- int sideBySide;
392
+ int showDiff; /* True to show diffs */
393
+ int sideBySide; /* True for side-by-side diffs */
394
+ int diffFlags; /* Flag parameter for text_diff() */
371395
const char *zName; /* Name of the checkin to be displayed */
372396
const char *zUuid; /* UUID of zName */
373397
const char *zParent; /* UUID of the parent checkin (if any) */
374398
375399
login_check_credentials();
@@ -562,18 +586,18 @@
562586
" FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
563587
" WHERE mlink.mid=%d"
564588
" ORDER BY name /*sort*/",
565589
rid
566590
);
591
+ diffFlags = construct_diff_flags(showDiff, sideBySide);
567592
while( db_step(&q)==SQLITE_ROW ){
568593
const char *zName = db_column_text(&q,0);
569594
int mperm = db_column_int(&q, 1);
570595
const char *zOld = db_column_text(&q,2);
571596
const char *zNew = db_column_text(&q,3);
572597
const char *zOldName = db_column_text(&q, 4);
573
- append_file_change_line(zName, zOld, zNew, zOldName, showDiff,
574
- sideBySide, mperm);
598
+ append_file_change_line(zName, zOld, zNew, zOldName, diffFlags, mperm);
575599
}
576600
db_finalize(&q);
577601
}
578602
style_footer();
579603
}
@@ -727,10 +751,11 @@
727751
*/
728752
void vdiff_page(void){
729753
int ridFrom, ridTo;
730754
int showDetail = 0;
731755
int sideBySide = 0;
756
+ int diffFlags = 0;
732757
Manifest *pFrom, *pTo;
733758
ManifestFile *pFileFrom, *pFileTo;
734759
735760
login_check_credentials();
736761
if( !g.perm.Read ){ login_needed(); return; }
@@ -738,12 +763,13 @@
738763
739764
pFrom = vdiff_parse_manifest("from", &ridFrom);
740765
if( pFrom==0 ) return;
741766
pTo = vdiff_parse_manifest("to", &ridTo);
742767
if( pTo==0 ) return;
743
- showDetail = atoi(PD("detail","0"));
744768
sideBySide = atoi(PD("sbs","1"));
769
+ showDetail = atoi(PD("detail","0"));
770
+ if( !showDetail && sideBySide ) showDetail = 1;
745771
if( !sideBySide ){
746772
style_submenu_element("Side-by-side Diff", "sbsdiff",
747773
"%s/vdiff?from=%T&to=%T&detail=%d&sbs=1",
748774
g.zTop, P("from"), P("to"), showDetail);
749775
}else{
@@ -760,10 +786,11 @@
760786
761787
manifest_file_rewind(pFrom);
762788
pFileFrom = manifest_file_next(pFrom, 0);
763789
manifest_file_rewind(pTo);
764790
pFileTo = manifest_file_next(pTo, 0);
791
+ diffFlags = construct_diff_flags(showDetail, sideBySide);
765792
while( pFileFrom || pFileTo ){
766793
int cmp;
767794
if( pFileFrom==0 ){
768795
cmp = +1;
769796
}else if( pFileTo==0 ){
@@ -771,25 +798,25 @@
771798
}else{
772799
cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
773800
}
774801
if( cmp<0 ){
775802
append_file_change_line(pFileFrom->zName,
776
- pFileFrom->zUuid, 0, 0, 0, 0, 0);
803
+ pFileFrom->zUuid, 0, 0, 0, 0);
777804
pFileFrom = manifest_file_next(pFrom, 0);
778805
}else if( cmp>0 ){
779806
append_file_change_line(pFileTo->zName,
780
- 0, pFileTo->zUuid, 0, 0, 0,
807
+ 0, pFileTo->zUuid, 0, 0,
781808
manifest_file_mperm(pFileTo));
782809
pFileTo = manifest_file_next(pTo, 0);
783810
}else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
784811
/* No changes */
785812
pFileFrom = manifest_file_next(pFrom, 0);
786813
pFileTo = manifest_file_next(pTo, 0);
787814
}else{
788815
append_file_change_line(pFileFrom->zName,
789816
pFileFrom->zUuid,
790
- pFileTo->zUuid, 0, showDetail, sideBySide,
817
+ pFileTo->zUuid, 0, diffFlags,
791818
manifest_file_mperm(pFileTo));
792819
pFileFrom = manifest_file_next(pFrom, 0);
793820
pFileTo = manifest_file_next(pTo, 0);
794821
}
795822
}
796823
--- src/info.c
+++ src/info.c
@@ -285,18 +285,13 @@
285 static void append_file_change_line(
286 const char *zName, /* Name of the file that has changed */
287 const char *zOld, /* blob.uuid before change. NULL for added files */
288 const char *zNew, /* blob.uuid after change. NULL for deletes */
289 const char *zOldName, /* Prior name. NULL if no name change. */
290 int showDiff, /* Show edit diffs if true */
291 int sideBySide, /* Show diffs side-by-side */
292 int mperm /* executable or symlink permission for zNew */
293 ){
294 int diffFlags = DIFF_IGNORE_EOLWS | 7;
295 if( sideBySide ){
296 diffFlags |= DIFF_SIDEBYSIDE;
297 }
298 if( !g.perm.History ){
299 if( zNew==0 ){
300 @ <p>Deleted %h(zName)</p>
301 }else if( zOld==0 ){
302 @ <p>Added %h(zName)</p>
@@ -306,11 +301,11 @@
306 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared")
307 @ for %h(zName)</p>
308 }else{
309 @ <p>Changes to %h(zName)</p>
310 }
311 if( showDiff ){
312 @ <pre style="white-space:pre;">
313 append_diff(zOld, zNew, diffFlags);
314 @ </pre>
315 }
316 }else{
@@ -332,11 +327,11 @@
332 @ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
333 }else{
334 @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
335 @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
336 }
337 if( showDiff ){
338 @ <pre style="white-space:pre;">
339 append_diff(zOld, zNew, diffFlags);
340 @ </pre>
341 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
342 @ &nbsp;&nbsp;
@@ -343,10 +338,38 @@
343 @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a>
344 }
345 @ </p>
346 }
347 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
349
350 /*
351 ** WEBPAGE: vinfo
352 ** WEBPAGE: ci
@@ -364,12 +387,13 @@
364 */
365 void ci_page(void){
366 Stmt q;
367 int rid;
368 int isLeaf;
369 int showDiff;
370 int sideBySide;
 
371 const char *zName; /* Name of the checkin to be displayed */
372 const char *zUuid; /* UUID of zName */
373 const char *zParent; /* UUID of the parent checkin (if any) */
374
375 login_check_credentials();
@@ -562,18 +586,18 @@
562 " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
563 " WHERE mlink.mid=%d"
564 " ORDER BY name /*sort*/",
565 rid
566 );
 
567 while( db_step(&q)==SQLITE_ROW ){
568 const char *zName = db_column_text(&q,0);
569 int mperm = db_column_int(&q, 1);
570 const char *zOld = db_column_text(&q,2);
571 const char *zNew = db_column_text(&q,3);
572 const char *zOldName = db_column_text(&q, 4);
573 append_file_change_line(zName, zOld, zNew, zOldName, showDiff,
574 sideBySide, mperm);
575 }
576 db_finalize(&q);
577 }
578 style_footer();
579 }
@@ -727,10 +751,11 @@
727 */
728 void vdiff_page(void){
729 int ridFrom, ridTo;
730 int showDetail = 0;
731 int sideBySide = 0;
 
732 Manifest *pFrom, *pTo;
733 ManifestFile *pFileFrom, *pFileTo;
734
735 login_check_credentials();
736 if( !g.perm.Read ){ login_needed(); return; }
@@ -738,12 +763,13 @@
738
739 pFrom = vdiff_parse_manifest("from", &ridFrom);
740 if( pFrom==0 ) return;
741 pTo = vdiff_parse_manifest("to", &ridTo);
742 if( pTo==0 ) return;
743 showDetail = atoi(PD("detail","0"));
744 sideBySide = atoi(PD("sbs","1"));
 
 
745 if( !sideBySide ){
746 style_submenu_element("Side-by-side Diff", "sbsdiff",
747 "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1",
748 g.zTop, P("from"), P("to"), showDetail);
749 }else{
@@ -760,10 +786,11 @@
760
761 manifest_file_rewind(pFrom);
762 pFileFrom = manifest_file_next(pFrom, 0);
763 manifest_file_rewind(pTo);
764 pFileTo = manifest_file_next(pTo, 0);
 
765 while( pFileFrom || pFileTo ){
766 int cmp;
767 if( pFileFrom==0 ){
768 cmp = +1;
769 }else if( pFileTo==0 ){
@@ -771,25 +798,25 @@
771 }else{
772 cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
773 }
774 if( cmp<0 ){
775 append_file_change_line(pFileFrom->zName,
776 pFileFrom->zUuid, 0, 0, 0, 0, 0);
777 pFileFrom = manifest_file_next(pFrom, 0);
778 }else if( cmp>0 ){
779 append_file_change_line(pFileTo->zName,
780 0, pFileTo->zUuid, 0, 0, 0,
781 manifest_file_mperm(pFileTo));
782 pFileTo = manifest_file_next(pTo, 0);
783 }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
784 /* No changes */
785 pFileFrom = manifest_file_next(pFrom, 0);
786 pFileTo = manifest_file_next(pTo, 0);
787 }else{
788 append_file_change_line(pFileFrom->zName,
789 pFileFrom->zUuid,
790 pFileTo->zUuid, 0, showDetail, sideBySide,
791 manifest_file_mperm(pFileTo));
792 pFileFrom = manifest_file_next(pFrom, 0);
793 pFileTo = manifest_file_next(pTo, 0);
794 }
795 }
796
--- src/info.c
+++ src/info.c
@@ -285,18 +285,13 @@
285 static void append_file_change_line(
286 const char *zName, /* Name of the file that has changed */
287 const char *zOld, /* blob.uuid before change. NULL for added files */
288 const char *zNew, /* blob.uuid after change. NULL for deletes */
289 const char *zOldName, /* Prior name. NULL if no name change. */
290 int diffFlags, /* Flags for text_diff(). Zero to omit diffs */
 
291 int mperm /* executable or symlink permission for zNew */
292 ){
 
 
 
 
293 if( !g.perm.History ){
294 if( zNew==0 ){
295 @ <p>Deleted %h(zName)</p>
296 }else if( zOld==0 ){
297 @ <p>Added %h(zName)</p>
@@ -306,11 +301,11 @@
301 @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared")
302 @ for %h(zName)</p>
303 }else{
304 @ <p>Changes to %h(zName)</p>
305 }
306 if( diffFlags ){
307 @ <pre style="white-space:pre;">
308 append_diff(zOld, zNew, diffFlags);
309 @ </pre>
310 }
311 }else{
@@ -332,11 +327,11 @@
327 @ version <a href="%s(g.zTop)/artifact/%s(zOld)">[%S(zOld)]</a>
328 }else{
329 @ <p>Added <a href="%s(g.zTop)/finfo?name=%T(zName)">%h(zName)</a>
330 @ version <a href="%s(g.zTop)/artifact/%s(zNew)">[%S(zNew)]</a>
331 }
332 if( diffFlags ){
333 @ <pre style="white-space:pre;">
334 append_diff(zOld, zNew, diffFlags);
335 @ </pre>
336 }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
337 @ &nbsp;&nbsp;
@@ -343,10 +338,38 @@
338 @ <a href="%s(g.zTop)/fdiff?v1=%S(zOld)&amp;v2=%S(zNew)">[diff]</a>
339 }
340 @ </p>
341 }
342 }
343
344 /*
345 ** Construct an appropriate diffFlag for text_diff() based on query
346 ** parameters and the to boolean arguments.
347 */
348 static int construct_diff_flags(int showDiff, int sideBySide){
349 int diffFlags;
350 if( showDiff==0 ){
351 diffFlags = 0; /* Zero means do not show any diff */
352 }else if( sideBySide ){
353 int x;
354 diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS;
355
356 /* "dw" query parameter determines width of each column */
357 x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1);
358 if( x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK;
359 diffFlags += x;
360
361 /* "dc" query parameter determines lines of context */
362 x = atoi(PD("dc","7"));
363 if( x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK;
364 diffFlags += x;
365 }else{
366 /* In-line (non-side-by-side) diff */
367 diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS;
368 }
369 return diffFlags;
370 }
371
372
373 /*
374 ** WEBPAGE: vinfo
375 ** WEBPAGE: ci
@@ -364,12 +387,13 @@
387 */
388 void ci_page(void){
389 Stmt q;
390 int rid;
391 int isLeaf;
392 int showDiff; /* True to show diffs */
393 int sideBySide; /* True for side-by-side diffs */
394 int diffFlags; /* Flag parameter for text_diff() */
395 const char *zName; /* Name of the checkin to be displayed */
396 const char *zUuid; /* UUID of zName */
397 const char *zParent; /* UUID of the parent checkin (if any) */
398
399 login_check_credentials();
@@ -562,18 +586,18 @@
586 " FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
587 " WHERE mlink.mid=%d"
588 " ORDER BY name /*sort*/",
589 rid
590 );
591 diffFlags = construct_diff_flags(showDiff, sideBySide);
592 while( db_step(&q)==SQLITE_ROW ){
593 const char *zName = db_column_text(&q,0);
594 int mperm = db_column_int(&q, 1);
595 const char *zOld = db_column_text(&q,2);
596 const char *zNew = db_column_text(&q,3);
597 const char *zOldName = db_column_text(&q, 4);
598 append_file_change_line(zName, zOld, zNew, zOldName, diffFlags, mperm);
 
599 }
600 db_finalize(&q);
601 }
602 style_footer();
603 }
@@ -727,10 +751,11 @@
751 */
752 void vdiff_page(void){
753 int ridFrom, ridTo;
754 int showDetail = 0;
755 int sideBySide = 0;
756 int diffFlags = 0;
757 Manifest *pFrom, *pTo;
758 ManifestFile *pFileFrom, *pFileTo;
759
760 login_check_credentials();
761 if( !g.perm.Read ){ login_needed(); return; }
@@ -738,12 +763,13 @@
763
764 pFrom = vdiff_parse_manifest("from", &ridFrom);
765 if( pFrom==0 ) return;
766 pTo = vdiff_parse_manifest("to", &ridTo);
767 if( pTo==0 ) return;
 
768 sideBySide = atoi(PD("sbs","1"));
769 showDetail = atoi(PD("detail","0"));
770 if( !showDetail && sideBySide ) showDetail = 1;
771 if( !sideBySide ){
772 style_submenu_element("Side-by-side Diff", "sbsdiff",
773 "%s/vdiff?from=%T&to=%T&detail=%d&sbs=1",
774 g.zTop, P("from"), P("to"), showDetail);
775 }else{
@@ -760,10 +786,11 @@
786
787 manifest_file_rewind(pFrom);
788 pFileFrom = manifest_file_next(pFrom, 0);
789 manifest_file_rewind(pTo);
790 pFileTo = manifest_file_next(pTo, 0);
791 diffFlags = construct_diff_flags(showDetail, sideBySide);
792 while( pFileFrom || pFileTo ){
793 int cmp;
794 if( pFileFrom==0 ){
795 cmp = +1;
796 }else if( pFileTo==0 ){
@@ -771,25 +798,25 @@
798 }else{
799 cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
800 }
801 if( cmp<0 ){
802 append_file_change_line(pFileFrom->zName,
803 pFileFrom->zUuid, 0, 0, 0, 0);
804 pFileFrom = manifest_file_next(pFrom, 0);
805 }else if( cmp>0 ){
806 append_file_change_line(pFileTo->zName,
807 0, pFileTo->zUuid, 0, 0,
808 manifest_file_mperm(pFileTo));
809 pFileTo = manifest_file_next(pTo, 0);
810 }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
811 /* No changes */
812 pFileFrom = manifest_file_next(pFrom, 0);
813 pFileTo = manifest_file_next(pTo, 0);
814 }else{
815 append_file_change_line(pFileFrom->zName,
816 pFileFrom->zUuid,
817 pFileTo->zUuid, 0, diffFlags,
818 manifest_file_mperm(pFileTo));
819 pFileFrom = manifest_file_next(pFrom, 0);
820 pFileTo = manifest_file_next(pTo, 0);
821 }
822 }
823

Keyboard Shortcuts

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