Fossil SCM

Add the 'info commands' and 'info vars' sub-commands to the TH1 core language, with tests.

mistachkin 2015-04-05 18:17 UTC trunk
Commit 810e6c1e687c161fcd343e302e13e6f3e5d8f345
+69 -3
--- src/th.c
+++ src/th.c
@@ -7,13 +7,14 @@
77
#include "config.h"
88
#include "th.h"
99
#include <string.h>
1010
#include <assert.h>
1111
12
-typedef struct Th_Command Th_Command;
13
-typedef struct Th_Frame Th_Frame;
14
-typedef struct Th_Variable Th_Variable;
12
+typedef struct Th_Command Th_Command;
13
+typedef struct Th_Frame Th_Frame;
14
+typedef struct Th_Variable Th_Variable;
15
+typedef struct Th_InterpAndList Th_InterpAndList;
1516
1617
/*
1718
** Interpreter structure.
1819
*/
1920
struct Th_Interp {
@@ -87,10 +88,21 @@
8788
int nRef; /* Number of references to this structure */
8889
int nData; /* Number of bytes at Th_Variable.zData */
8990
char *zData; /* Data for scalar variables */
9091
Th_Hash *pHash; /* Data for array variables */
9192
};
93
+
94
+/*
95
+** This structure is used to pass complete context information to the
96
+** hash iteration callback functions that need a Th_Interp and a list
97
+** to operate on, e.g. thListAppend().
98
+*/
99
+struct Th_InterpAndList {
100
+ Th_Interp *interp; /* Associated interpreter context */
101
+ char **pzList; /* IN/OUT: Ptr to ptr to list */
102
+ int *pnList; /* IN/OUT: Current length of *pzList */
103
+};
92104
93105
/*
94106
** Hash table API:
95107
*/
96108
#define TH_HASHSIZE 257
@@ -298,10 +310,25 @@
298310
}
299311
Th_Free((Th_Interp *)pContext, pEntry->pData);
300312
pEntry->pData = 0;
301313
return 1;
302314
}
315
+
316
+/*
317
+** Argument pEntry points to an entry in a hash table. The key is
318
+** the list element to be added.
319
+**
320
+** Argument pContext is a pointer to the Th_InterpAndList structure.
321
+**
322
+** Always returns non-zero.
323
+*/
324
+static int thListAppend(Th_HashEntry *pEntry, void *pContext){
325
+ Th_InterpAndList *pInterpAndList = (Th_InterpAndList *)pContext;
326
+ Th_ListAppend(pInterpAndList->interp, pInterpAndList->pzList,
327
+ pInterpAndList->pnList, pEntry->zKey, pEntry->nKey);
328
+ return 1;
329
+}
303330
304331
/*
305332
** Push a new frame onto the stack.
306333
*/
307334
static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
@@ -2832,5 +2859,44 @@
28322859
}
28332860
28342861
*z = '\0';
28352862
return Th_SetResult(interp, zBuf, -1);
28362863
}
2864
+
2865
+/*
2866
+** Appends all currently registered command names to the specified list
2867
+** and returns TH_OK upon success. Any other return value indicates an
2868
+** error.
2869
+*/
2870
+int Th_ListAppendCommands(Th_Interp *interp, char **pzList, int *pnList){
2871
+ Th_InterpAndList *p = (Th_InterpAndList *)Th_Malloc(
2872
+ interp, sizeof(Th_InterpAndList)
2873
+ );
2874
+ p->interp = interp;
2875
+ p->pzList = pzList;
2876
+ p->pnList = pnList;
2877
+ Th_HashIterate(interp, interp->paCmd, thListAppend, p);
2878
+ Th_Free(interp, p);
2879
+ return TH_OK;
2880
+}
2881
+
2882
+/*
2883
+** Appends all variable names for the current frame to the specified list
2884
+** and returns TH_OK upon success. Any other return value indicates an
2885
+** error. If the current frame cannot be obtained, TH_ERROR is returned.
2886
+*/
2887
+int Th_ListAppendVariables(Th_Interp *interp, char **pzList, int *pnList){
2888
+ Th_Frame *pFrame = getFrame(interp, 0);
2889
+ if( pFrame ){
2890
+ Th_InterpAndList *p = (Th_InterpAndList *)Th_Malloc(
2891
+ interp, sizeof(Th_InterpAndList)
2892
+ );
2893
+ p->interp = interp;
2894
+ p->pzList = pzList;
2895
+ p->pnList = pnList;
2896
+ Th_HashIterate(interp, pFrame->paVar, thListAppend, p);
2897
+ Th_Free(interp, p);
2898
+ return TH_OK;
2899
+ }else{
2900
+ return TH_ERROR;
2901
+ }
2902
+}
28372903
--- src/th.c
+++ src/th.c
@@ -7,13 +7,14 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
12 typedef struct Th_Command Th_Command;
13 typedef struct Th_Frame Th_Frame;
14 typedef struct Th_Variable Th_Variable;
 
15
16 /*
17 ** Interpreter structure.
18 */
19 struct Th_Interp {
@@ -87,10 +88,21 @@
87 int nRef; /* Number of references to this structure */
88 int nData; /* Number of bytes at Th_Variable.zData */
89 char *zData; /* Data for scalar variables */
90 Th_Hash *pHash; /* Data for array variables */
91 };
 
 
 
 
 
 
 
 
 
 
 
92
93 /*
94 ** Hash table API:
95 */
96 #define TH_HASHSIZE 257
@@ -298,10 +310,25 @@
298 }
299 Th_Free((Th_Interp *)pContext, pEntry->pData);
300 pEntry->pData = 0;
301 return 1;
302 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
304 /*
305 ** Push a new frame onto the stack.
306 */
307 static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
@@ -2832,5 +2859,44 @@
2832 }
2833
2834 *z = '\0';
2835 return Th_SetResult(interp, zBuf, -1);
2836 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2837
--- src/th.c
+++ src/th.c
@@ -7,13 +7,14 @@
7 #include "config.h"
8 #include "th.h"
9 #include <string.h>
10 #include <assert.h>
11
12 typedef struct Th_Command Th_Command;
13 typedef struct Th_Frame Th_Frame;
14 typedef struct Th_Variable Th_Variable;
15 typedef struct Th_InterpAndList Th_InterpAndList;
16
17 /*
18 ** Interpreter structure.
19 */
20 struct Th_Interp {
@@ -87,10 +88,21 @@
88 int nRef; /* Number of references to this structure */
89 int nData; /* Number of bytes at Th_Variable.zData */
90 char *zData; /* Data for scalar variables */
91 Th_Hash *pHash; /* Data for array variables */
92 };
93
94 /*
95 ** This structure is used to pass complete context information to the
96 ** hash iteration callback functions that need a Th_Interp and a list
97 ** to operate on, e.g. thListAppend().
98 */
99 struct Th_InterpAndList {
100 Th_Interp *interp; /* Associated interpreter context */
101 char **pzList; /* IN/OUT: Ptr to ptr to list */
102 int *pnList; /* IN/OUT: Current length of *pzList */
103 };
104
105 /*
106 ** Hash table API:
107 */
108 #define TH_HASHSIZE 257
@@ -298,10 +310,25 @@
310 }
311 Th_Free((Th_Interp *)pContext, pEntry->pData);
312 pEntry->pData = 0;
313 return 1;
314 }
315
316 /*
317 ** Argument pEntry points to an entry in a hash table. The key is
318 ** the list element to be added.
319 **
320 ** Argument pContext is a pointer to the Th_InterpAndList structure.
321 **
322 ** Always returns non-zero.
323 */
324 static int thListAppend(Th_HashEntry *pEntry, void *pContext){
325 Th_InterpAndList *pInterpAndList = (Th_InterpAndList *)pContext;
326 Th_ListAppend(pInterpAndList->interp, pInterpAndList->pzList,
327 pInterpAndList->pnList, pEntry->zKey, pEntry->nKey);
328 return 1;
329 }
330
331 /*
332 ** Push a new frame onto the stack.
333 */
334 static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){
@@ -2832,5 +2859,44 @@
2859 }
2860
2861 *z = '\0';
2862 return Th_SetResult(interp, zBuf, -1);
2863 }
2864
2865 /*
2866 ** Appends all currently registered command names to the specified list
2867 ** and returns TH_OK upon success. Any other return value indicates an
2868 ** error.
2869 */
2870 int Th_ListAppendCommands(Th_Interp *interp, char **pzList, int *pnList){
2871 Th_InterpAndList *p = (Th_InterpAndList *)Th_Malloc(
2872 interp, sizeof(Th_InterpAndList)
2873 );
2874 p->interp = interp;
2875 p->pzList = pzList;
2876 p->pnList = pnList;
2877 Th_HashIterate(interp, interp->paCmd, thListAppend, p);
2878 Th_Free(interp, p);
2879 return TH_OK;
2880 }
2881
2882 /*
2883 ** Appends all variable names for the current frame to the specified list
2884 ** and returns TH_OK upon success. Any other return value indicates an
2885 ** error. If the current frame cannot be obtained, TH_ERROR is returned.
2886 */
2887 int Th_ListAppendVariables(Th_Interp *interp, char **pzList, int *pnList){
2888 Th_Frame *pFrame = getFrame(interp, 0);
2889 if( pFrame ){
2890 Th_InterpAndList *p = (Th_InterpAndList *)Th_Malloc(
2891 interp, sizeof(Th_InterpAndList)
2892 );
2893 p->interp = interp;
2894 p->pzList = pzList;
2895 p->pnList = pnList;
2896 Th_HashIterate(interp, pFrame->paVar, thListAppend, p);
2897 Th_Free(interp, p);
2898 return TH_OK;
2899 }else{
2900 return TH_ERROR;
2901 }
2902 }
2903
+6
--- src/th.h
+++ src/th.h
@@ -138,10 +138,16 @@
138138
int Th_ToInt(Th_Interp *, const char *, int, int *);
139139
int Th_ToDouble(Th_Interp *, const char *, int, double *);
140140
int Th_SetResultInt(Th_Interp *, int);
141141
int Th_SetResultDouble(Th_Interp *, double);
142142
143
+/*
144
+** Functions for handling command and variable introspection.
145
+*/
146
+int Th_ListAppendCommands(Th_Interp *, char **, int *);
147
+int Th_ListAppendVariables(Th_Interp *, char **, int *);
148
+
143149
/*
144150
** Drop in replacements for the corresponding standard library functions.
145151
*/
146152
int th_strlen(const char *);
147153
int th_isdigit(char);
148154
--- src/th.h
+++ src/th.h
@@ -138,10 +138,16 @@
138 int Th_ToInt(Th_Interp *, const char *, int, int *);
139 int Th_ToDouble(Th_Interp *, const char *, int, double *);
140 int Th_SetResultInt(Th_Interp *, int);
141 int Th_SetResultDouble(Th_Interp *, double);
142
 
 
 
 
 
 
143 /*
144 ** Drop in replacements for the corresponding standard library functions.
145 */
146 int th_strlen(const char *);
147 int th_isdigit(char);
148
--- src/th.h
+++ src/th.h
@@ -138,10 +138,16 @@
138 int Th_ToInt(Th_Interp *, const char *, int, int *);
139 int Th_ToDouble(Th_Interp *, const char *, int, double *);
140 int Th_SetResultInt(Th_Interp *, int);
141 int Th_SetResultDouble(Th_Interp *, double);
142
143 /*
144 ** Functions for handling command and variable introspection.
145 */
146 int Th_ListAppendCommands(Th_Interp *, char **, int *);
147 int Th_ListAppendVariables(Th_Interp *, char **, int *);
148
149 /*
150 ** Drop in replacements for the corresponding standard library functions.
151 */
152 int th_strlen(const char *);
153 int th_isdigit(char);
154
+52 -2
--- src/th_lang.c
+++ src/th_lang.c
@@ -848,11 +848,11 @@
848848
}
849849
850850
/*
851851
** TH Syntax:
852852
**
853
-** info exists VAR
853
+** info exists VARNAME
854854
*/
855855
static int info_exists_command(
856856
Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
857857
){
858858
int rc;
@@ -862,10 +862,56 @@
862862
}
863863
rc = Th_ExistsVar(interp, argv[2], argl[2]);
864864
Th_SetResultInt(interp, rc);
865865
return TH_OK;
866866
}
867
+
868
+/*
869
+** TH Syntax:
870
+**
871
+** info commands
872
+*/
873
+static int info_commands_command(
874
+ Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
875
+){
876
+ int rc;
877
+ char *zElem = 0;
878
+ int nElem = 0;
879
+
880
+ if( argc!=2 ){
881
+ return Th_WrongNumArgs(interp, "info commands");
882
+ }
883
+ rc = Th_ListAppendCommands(interp, &zElem, &nElem);
884
+ if( rc!=TH_OK ){
885
+ return rc;
886
+ }
887
+ Th_SetResult(interp, zElem, nElem);
888
+ return TH_OK;
889
+}
890
+
891
+/*
892
+** TH Syntax:
893
+**
894
+** info vars
895
+*/
896
+static int info_vars_command(
897
+ Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
898
+){
899
+ int rc;
900
+ char *zElem = 0;
901
+ int nElem = 0;
902
+
903
+ if( argc!=2 ){
904
+ return Th_WrongNumArgs(interp, "info vars");
905
+ }
906
+ rc = Th_ListAppendVariables(interp, &zElem, &nElem);
907
+ if( rc!=TH_OK ){
908
+ return rc;
909
+ }
910
+ Th_SetResult(interp, zElem, nElem);
911
+ return TH_OK;
912
+}
867913
868914
/*
869915
** TH Syntax:
870916
**
871917
** unset VAR
@@ -943,21 +989,25 @@
943989
}
944990
945991
/*
946992
** TH Syntax:
947993
**
994
+** info commands
948995
** info exists VARNAME
996
+** info vars
949997
*/
950998
static int info_command(
951999
Th_Interp *interp,
9521000
void *ctx,
9531001
int argc,
9541002
const char **argv,
9551003
int *argl
9561004
){
9571005
static const Th_SubCommand aSub[] = {
958
- { "exists", info_exists_command },
1006
+ { "commands", info_commands_command },
1007
+ { "exists", info_exists_command },
1008
+ { "vars", info_vars_command },
9591009
{ 0, 0 }
9601010
};
9611011
return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
9621012
}
9631013
9641014
--- src/th_lang.c
+++ src/th_lang.c
@@ -848,11 +848,11 @@
848 }
849
850 /*
851 ** TH Syntax:
852 **
853 ** info exists VAR
854 */
855 static int info_exists_command(
856 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
857 ){
858 int rc;
@@ -862,10 +862,56 @@
862 }
863 rc = Th_ExistsVar(interp, argv[2], argl[2]);
864 Th_SetResultInt(interp, rc);
865 return TH_OK;
866 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
868 /*
869 ** TH Syntax:
870 **
871 ** unset VAR
@@ -943,21 +989,25 @@
943 }
944
945 /*
946 ** TH Syntax:
947 **
 
948 ** info exists VARNAME
 
949 */
950 static int info_command(
951 Th_Interp *interp,
952 void *ctx,
953 int argc,
954 const char **argv,
955 int *argl
956 ){
957 static const Th_SubCommand aSub[] = {
958 { "exists", info_exists_command },
 
 
959 { 0, 0 }
960 };
961 return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
962 }
963
964
--- src/th_lang.c
+++ src/th_lang.c
@@ -848,11 +848,11 @@
848 }
849
850 /*
851 ** TH Syntax:
852 **
853 ** info exists VARNAME
854 */
855 static int info_exists_command(
856 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
857 ){
858 int rc;
@@ -862,10 +862,56 @@
862 }
863 rc = Th_ExistsVar(interp, argv[2], argl[2]);
864 Th_SetResultInt(interp, rc);
865 return TH_OK;
866 }
867
868 /*
869 ** TH Syntax:
870 **
871 ** info commands
872 */
873 static int info_commands_command(
874 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
875 ){
876 int rc;
877 char *zElem = 0;
878 int nElem = 0;
879
880 if( argc!=2 ){
881 return Th_WrongNumArgs(interp, "info commands");
882 }
883 rc = Th_ListAppendCommands(interp, &zElem, &nElem);
884 if( rc!=TH_OK ){
885 return rc;
886 }
887 Th_SetResult(interp, zElem, nElem);
888 return TH_OK;
889 }
890
891 /*
892 ** TH Syntax:
893 **
894 ** info vars
895 */
896 static int info_vars_command(
897 Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
898 ){
899 int rc;
900 char *zElem = 0;
901 int nElem = 0;
902
903 if( argc!=2 ){
904 return Th_WrongNumArgs(interp, "info vars");
905 }
906 rc = Th_ListAppendVariables(interp, &zElem, &nElem);
907 if( rc!=TH_OK ){
908 return rc;
909 }
910 Th_SetResult(interp, zElem, nElem);
911 return TH_OK;
912 }
913
914 /*
915 ** TH Syntax:
916 **
917 ** unset VAR
@@ -943,21 +989,25 @@
989 }
990
991 /*
992 ** TH Syntax:
993 **
994 ** info commands
995 ** info exists VARNAME
996 ** info vars
997 */
998 static int info_command(
999 Th_Interp *interp,
1000 void *ctx,
1001 int argc,
1002 const char **argv,
1003 int *argl
1004 ){
1005 static const Th_SubCommand aSub[] = {
1006 { "commands", info_commands_command },
1007 { "exists", info_exists_command },
1008 { "vars", info_vars_command },
1009 { 0, 0 }
1010 };
1011 return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
1012 }
1013
1014
--- test/th1.test
+++ test/th1.test
@@ -848,5 +848,45 @@
848848
849849
###############################################################################
850850
851851
fossil test-th-eval "reinitialize 1; globalState configuration"
852852
test th1-reinitialize-2 {$RESULT ne ""}
853
+
854
+###############################################################################
855
+
856
+#
857
+# NOTE: This test may fail if the command names do not always come
858
+# out in a deterministic order from TH1.
859
+#
860
+fossil test-th-eval "info commands"
861
+test th1-info-commands-1 {$RESULT eq {linecount htmlize date stime\
862
+enable_output uplevel http expr utime styleFooter catch if tclReady\
863
+searchable reinitialize combobox lindex query html anoncap randhex\
864
+llength for set break regexp styleHeader puts return checkout decorate\
865
+artifact trace wiki proc hascap globalState continue getParameter\
866
+hasfeature setting breakpoint upvar render repository string unset\
867
+setParameter list error info rename anycap httpize}}
868
+
869
+###############################################################################
870
+
871
+fossil test-th-eval "info vars"
872
+test th1-info-vars-1 {$RESULT eq ""}
873
+
874
+###############################################################################
875
+
876
+fossil test-th-eval "set x 1; info vars"
877
+test th1-info-vars-2 {$RESULT eq "x"}
878
+
879
+###############################################################################
880
+
881
+fossil test-th-eval "set x 1; unset x; info vars"
882
+test th1-info-vars-3 {$RESULT eq ""}
883
+
884
+###############################################################################
885
+
886
+fossil test-th-eval "proc foo {} {set x 1; info vars}; foo"
887
+test th1-info-vars-4 {$RESULT eq "x"}
888
+
889
+###############################################################################
890
+
891
+fossil test-th-eval "set y 1; proc foo {} {set x 1; uplevel 1 {info vars}}; foo"
892
+test th1-info-vars-5 {$RESULT eq "y"}
853893
--- test/th1.test
+++ test/th1.test
@@ -848,5 +848,45 @@
848
849 ###############################################################################
850
851 fossil test-th-eval "reinitialize 1; globalState configuration"
852 test th1-reinitialize-2 {$RESULT ne ""}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
--- test/th1.test
+++ test/th1.test
@@ -848,5 +848,45 @@
848
849 ###############################################################################
850
851 fossil test-th-eval "reinitialize 1; globalState configuration"
852 test th1-reinitialize-2 {$RESULT ne ""}
853
854 ###############################################################################
855
856 #
857 # NOTE: This test may fail if the command names do not always come
858 # out in a deterministic order from TH1.
859 #
860 fossil test-th-eval "info commands"
861 test th1-info-commands-1 {$RESULT eq {linecount htmlize date stime\
862 enable_output uplevel http expr utime styleFooter catch if tclReady\
863 searchable reinitialize combobox lindex query html anoncap randhex\
864 llength for set break regexp styleHeader puts return checkout decorate\
865 artifact trace wiki proc hascap globalState continue getParameter\
866 hasfeature setting breakpoint upvar render repository string unset\
867 setParameter list error info rename anycap httpize}}
868
869 ###############################################################################
870
871 fossil test-th-eval "info vars"
872 test th1-info-vars-1 {$RESULT eq ""}
873
874 ###############################################################################
875
876 fossil test-th-eval "set x 1; info vars"
877 test th1-info-vars-2 {$RESULT eq "x"}
878
879 ###############################################################################
880
881 fossil test-th-eval "set x 1; unset x; info vars"
882 test th1-info-vars-3 {$RESULT eq ""}
883
884 ###############################################################################
885
886 fossil test-th-eval "proc foo {} {set x 1; info vars}; foo"
887 test th1-info-vars-4 {$RESULT eq "x"}
888
889 ###############################################################################
890
891 fossil test-th-eval "set y 1; proc foo {} {set x 1; uplevel 1 {info vars}}; foo"
892 test th1-info-vars-5 {$RESULT eq "y"}
893
+2
--- www/th1.md
+++ www/th1.md
@@ -90,11 +90,13 @@
9090
* continue
9191
* error ?STRING?
9292
* expr EXPR
9393
* for INIT-SCRIPT TEST-EXPR NEXT-SCRIPT BODY-SCRIPT
9494
* if EXPR SCRIPT (elseif EXPR SCRIPT)* ?else SCRIPT?
95
+ * info commands
9596
* info exists VARNAME
97
+ * info vars
9698
* lindex LIST INDEX
9799
* list ARG ...
98100
* llength LIST
99101
* proc NAME ARG-LIST BODY-SCRIPT
100102
* rename OLD NEW
101103
--- www/th1.md
+++ www/th1.md
@@ -90,11 +90,13 @@
90 * continue
91 * error ?STRING?
92 * expr EXPR
93 * for INIT-SCRIPT TEST-EXPR NEXT-SCRIPT BODY-SCRIPT
94 * if EXPR SCRIPT (elseif EXPR SCRIPT)* ?else SCRIPT?
 
95 * info exists VARNAME
 
96 * lindex LIST INDEX
97 * list ARG ...
98 * llength LIST
99 * proc NAME ARG-LIST BODY-SCRIPT
100 * rename OLD NEW
101
--- www/th1.md
+++ www/th1.md
@@ -90,11 +90,13 @@
90 * continue
91 * error ?STRING?
92 * expr EXPR
93 * for INIT-SCRIPT TEST-EXPR NEXT-SCRIPT BODY-SCRIPT
94 * if EXPR SCRIPT (elseif EXPR SCRIPT)* ?else SCRIPT?
95 * info commands
96 * info exists VARNAME
97 * info vars
98 * lindex LIST INDEX
99 * list ARG ...
100 * llength LIST
101 * proc NAME ARG-LIST BODY-SCRIPT
102 * rename OLD NEW
103

Keyboard Shortcuts

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