diff --git a/builtin.c b/builtin.c index cd412bdb0..6b0b99794 100644 --- a/builtin.c +++ b/builtin.c @@ -402,28 +402,183 @@ static void builtin_missing_argument( const wchar_t *cmd, const wchar_t *opt ) #include "builtin_ulimit.c" #include "builtin_jobs.c" +static void builtin_bind_list() +{ + array_list_t lst; + int i; + + + al_init( &lst ); + input_mapping_get_names( &lst ); + + for( i=0; ibind [OPTIONS] [BINDINGS...] - -The bind builtin causes fish to add the readline style bindings specified by BINDINGS to the list of key bindings, as if they appeared in your ~/.fish_inputrc file. - -For more information on the syntax keyboard bindings, use man -readline to access the readline documentation. The available commands -are listed in the Command Line Editor section -of the fish manual - but you may also use any fish command! To write such -commands, see the commandline builtin. It's good -practice to put the code into a function -b -and bind to the function name. +bind [OPTIONS] SEQUENCE COMMAND \subsection bind-description Description -- -M MODE or --set-mode=MODE sets the current input mode to MODE. + +The bind builtin causes fish to add a key binding from the specified sequence. + +SEQUENCE is the character sequence to bind to. Usually, one would use +fish escape sequences to express them. For example, Alt-w can be +written as \\ew, and Control-x can be written as +\\cx. + +If the -k switch is used, the name of the key (such as down, up or +backspace) is used instead of a sequence. The names used are the same +as the corresponding curses variables, but without the 'key_' +prefix. (See man 5 terminfo for more information, or use bind +--names for a list of all available named keys) + +COMMAND can be any fish command, but it can also be one of a set of +special input functions. These include functions for moving the +cursor, operating on the kill-ring, performing tab completion, +etc. Use 'bind -N' for a complete list of these input functions. + +When COMMAND is a shellscript command, it is a good practice to put +the actual code into a function and simply +bind to the function name. + +- -a or --all If --print-key-names is specified, show all key names, not only the ones that actually are defined for the current terminal. If erase mode is specified, this switch will cause all current bindings to be erased. +- -e or --erase Erase mode. All non-switch arguments are interpreted as character sequences and any commands associated with those sequences are erased. +- -h or --help Display help and exit +- -k or --key Specify a key name, such as 'left' or 'backspace' instead of a character sequence +- -K or --key-names Display a list of available key names +- -f or --function-names Display a list of available input functions + \subsection bind-example Example -bind -M vi changes to the vi input mode +bind \cd 'exit' causes fish to exit on Control-d + +bind -k ppage history-search-backward Causes fish to perform a history search when the page up key is pressed -bind '"\\M-j": jobs' Binds the jobs command to the Alt-j keyboard shortcut diff --git a/etc/fish_inputrc b/etc/fish_inputrc deleted file mode 100644 index 981b0abec..000000000 --- a/etc/fish_inputrc +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file contains key bindings for fish -# - -# Include system-wide inputrc file before including fish-specific key -# bindings if it exists - -$include /etc/inputrc - -$if fish - "\M-l": __fish_list_current_token - "\M-w": set tok (commandline -pt); if test $tok[1]; whatis $tok[1]; commandline -f repaint; end - "\C-l": clear; commandline -f repaint - "\C-c": delete-line - "\C-u": backward-kill-line - "\M-d": kill-word - "\C-w": backward-kill-word - "\M-k": dump-functions - "\M-d": if test -z (commandline); dirh; commandline -f repaint; else; commandline -f kill-word; end - "\C-d": delete-or-exit -# This will make sure the output of the current command is paged using the less pager when you press Meta-p - "\M-p": if commandline -j|grep -v 'less *$' >/dev/null; commandline -aj "|less;"; end -$endif - -# Include user-specific inputrc file after including fish-specific -# bindings so that they will override fish defaults - -$include ~/.inputrc diff --git a/input.c b/input.c index 8b913e01c..d0b898408 100644 --- a/input.c +++ b/input.c @@ -70,31 +70,43 @@ implementation in fish is as of yet incomplete. #include "output.h" #include "intern.h" - -static void input_read_inputrc( wchar_t *fn ); +#include "halloc.h" +#include "halloc_util.h" /** - Array containing characters which have been peeked by the escape - sequence matching functions and returned + Add a new terminfo mapping */ +#define TERMINFO_ADD(key) \ + { \ + terminfo_mapping_t *m = halloc( terminfo_mappings, sizeof( terminfo_mapping_t ) ); \ + m->name = halloc_wcsdup( terminfo_mappings, (L ## #key)+4 ); \ + m->seq = key; \ + al_push( terminfo_mappings, m ); \ + } + +/** + Struct representing a keybinding. Returned by input_get_mappings. + */ typedef struct { const wchar_t *seq; /**< Character sequence which generates this event */ - const wchar_t *seq_desc; /**< Description of the character sequence suitable for printing on-screen */ const wchar_t *command; /**< command that should be evaluated by this mapping */ } - mapping; + input_mapping_t; /** - Symbolic names for some acces-modifiers used when parsing symbolic sequences -*/ -#define CTRL_SYMBOL L"Control-" -/** - Symbolic names for some acces-modifiers used when parsing symbolic sequences -*/ -#define META_SYMBOL L"Meta-" + A struct representing the mapping from a terminfo key name to a terminfo character sequence + */ +typedef struct +{ + const wchar_t *name; /**< Name of key */ + const char *seq; /**< Character sequence generated on keypress */ + +} + terminfo_mapping_t; + /** Names of all the readline functions supported @@ -179,6 +191,7 @@ static const wchar_t *desc_arr[] = } ; */ + /** Internal code for each supported readline function */ @@ -224,68 +237,24 @@ static const wchar_t code_arr[] = ; -/** - List of all key bindings, as mappings from one sequence to either a character or a command -*/ -static hash_table_t all_mappings; - /** Mappings for the current input mode */ -static array_list_t *current_mode_mappings; -/** - Mappings for the current application -*/ -static array_list_t *current_application_mappings; -/** - Global mappings -*/ -static array_list_t *global_mappings; +static array_list_t mappings = {0,0,0}; -/** - Number of nested conditional statement levels that are not evaluated -*/ -static int inputrc_skip_block_count=0; -/** - Number of nested conditional statements that have evaluated to true -*/ -static int inputrc_block_count=0; +static array_list_t *terminfo_mappings = 0; -/** - True if syntax errors were found in the inputrc file -*/ -static int inputrc_error = 0; /** Set to one when the input subsytem has been initialized. */ static int is_init = 0; -/** - This is the variable telling us how many timew the next command - should bne repeated. Only actually used in vi-mode. -*/ -static int repeat_count = 1; +static void input_terminfo_init(); +static void input_terminfo_destroy(); -/** - This is the type of the first command in a vi-mode two-part combo - like 'dw' or '3d3l'. -*/ -static wint_t first_command = 0; -wchar_t input_get_code( const wchar_t *name ) -{ - int i; - for( i = 0; i<(sizeof( code_arr )/sizeof(wchar_t)) ; i++ ) - { - if( wcscmp( name, name_arr[i] ) == 0 ) - { - return code_arr[i]; - } - } - return -1; -} /** Returns the function name for the given function code. @@ -323,1064 +292,33 @@ static const wchar_t *input_get_desc( wchar_t c ) return 0; } */ -void input_set_mode( wchar_t *name ) -{ - current_mode_mappings = (array_list_t *)hash_get( &all_mappings, name ); -} - -void input_set_application( wchar_t *name ) -{ - current_application_mappings = (array_list_t *)hash_get( &all_mappings, name ); -} - -/** - Get the mapping with the specified name -*/ -static array_list_t *get_mapping( const wchar_t *mode ) -{ - - array_list_t * mappings = (array_list_t *)hash_get( &all_mappings, mode ); - - if( !mappings ) - { - mappings = malloc( sizeof( array_list_t )); - al_init( mappings ); - - hash_put( &all_mappings, wcsdup(mode), mappings ); - - } - return mappings; - -} -void add_mapping( const wchar_t *mode, - const wchar_t *s, - const wchar_t *d, - const wchar_t *c ) +void input_mapping_add( const wchar_t *sequence, + const wchar_t *command ) { int i; - array_list_t *mappings; - - if( s == 0 ) - return; + CHECK( sequence, ); + CHECK( command, ); - if( mode == 0 ) - return; + // debug( 0, L"Add mapping from %ls to %ls", escape(sequence, 1), escape(command, 1 ) ); - mappings = get_mapping( mode ); - - for( i=0; iseq, s ) == 0 ) + input_mapping_t *m = (input_mapping_t *)al_get( &mappings, i ); + if( wcscmp( m->seq, sequence ) == 0 ) { - m->seq_desc = intern(d); - m->command = intern(c); + m->command = intern(command); return; } } - mapping *m = malloc( sizeof( mapping ) ); - m->seq = intern( s ); - m->seq_desc = intern(d ); - m->command = intern(c); - al_push( mappings, m ); -} - -/** - Compare sort order for two keyboard mappings. This function is made - to be suitable for use with the qsort method. -*/ -/* -static int mapping_compare( const void *a, const void *b ) -{ - mapping *c = *(mapping **)a; - mapping *d = *(mapping **)b; - -// fwprintf( stderr, L"%ls %ls\n", c->seq_desc, d->seq_desc ); - - return wcscmp( c->seq_desc, d->seq_desc ); - -} -*/ - - -/** - Print a listing of all keybindings and a description of each - function. This is used by the dump-functions readline function. -*/ -static void dump_functions() -{ -/* int i; - fwprintf( stdout, L"\n" ); - - qsort(current_mappings.arr, - al_get_count( &mappings), - sizeof( void*), - &mapping_compare ); - - for( i=0; iseq ); -fwprintf( stdout, -L"%ls: %ls\n", -m->seq_desc, -m->command ); -} -repaint();*/ -} - - -/** - Parse special character from the specified inputrc-style key binding. - - Control-a is expanded to 1, etc. -*/ - -static wchar_t *input_symbolic_sequence( const wchar_t *in ) -{ - wchar_t *res=0; - - if( !*in || *in == L'\n' ) - return 0; - - debug( 4, L"Try to parse symbolic sequence %ls", in ); - - if( wcsncmp( in, CTRL_SYMBOL, wcslen(CTRL_SYMBOL) ) == 0 ) - { - int has_meta=0; - - in += wcslen(CTRL_SYMBOL); - - /* - Control-Meta- Should be rearranged to Meta-Control, this - special-case must be handled manually. - */ - if( wcsncmp( in, META_SYMBOL, wcslen(META_SYMBOL) ) == 0 ) - { - in += wcslen(META_SYMBOL); - has_meta=1; - } - - wchar_t c = towlower( *in ); - in++; - if( c < L'a' || c > L'z' ) - { - debug( 1, _( L"Invalid Control sequence" ) ); - return 0; - } - if( has_meta ) - { - res = wcsdup( L"\x1ba" ); - res[1]=1+c-L'a'; - } - else - { - res = wcsdup( L"a" ); - res[0]=1+c-L'a'; - } - debug( 4, L"Got control sequence %d", res[0] ); - - } - else if( wcsncmp( in, META_SYMBOL, wcslen(META_SYMBOL) ) == 0 ) - { - in += wcslen(META_SYMBOL); - res = wcsdup( L"\x1b" ); - debug( 4, L"Got meta" ); - } - else - { - int i; - struct - { - wchar_t *in; - char *out; - } - map[]= - { - { - L"rubout", - key_backspace - } - , - { - L"del", - key_dc - } - , - { - L"esc", - "\x1b" - } - , - { - L"lfd", - "\r" - } - , - { - L"newline", - "\n" - } - , - { - L"ret", - "\n" - } - , - { - L"return", - "\n" - } - , - { - L"spc", - " " - } - , - { - L"space", - " " - } - , - { - L"tab", - "\t" - } - , - { - 0, - 0 - } - } - ; - - for( i=0; map[i].in; i++ ) - { - if( wcsncasecmp( in, map[i].in, wcslen(map[i].in) )==0 ) - { - in+= wcslen( map[i].in ); - res = str2wcs( map[i].out ); - - break; - } - } - - if( !res ) - { - if( iswalnum( *in ) || iswpunct( *in ) ) - { - res = wcsdup( L"a" ); - *res = *in++; - debug( 4, L"Got character %lc", *res ); - } - } - } - if( !res ) - { - debug( 1, _( L"Could not parse sequence '%ls'" ), in ); - return 0; - } - if( !*in || *in == L'\n') - { - debug( 4, L"Finished parsing sequence" ); - return res; - } - - wchar_t *res2 = input_symbolic_sequence( in ); - if( !res2 ) - { - free( res ); - return 0; - } - wchar_t *res3 = wcsdupcat( res, res2 ); - free( res); - free(res2); - - return res3; -} - -/** - Unescape special character from the specified inputrc-style key sequence. - - \\C-a is expanded to 1, etc. -*/ -static wchar_t *input_expand_sequence( const wchar_t *in ) -{ - const wchar_t *in_orig=in; - wchar_t *res = malloc( sizeof( wchar_t)*(4*wcslen(in)+1)); - wchar_t *out=res; - int error = 0; - - while( *in && !error) - { - switch( *in ) - { - case L'\\': - { - in++; - switch( *in ) - { - case L'\0': - error = 1; - break; - - case L'e': - *(out++)=L'\x1b'; - break; - - case L'\\': - case L'\"': - case L'\'': - *(out++)=*in; - break; - - case L'b': - *(out++)=L'\b'; - break; - - case L'd': - { - wchar_t *str = str2wcs( key_dc ); - wchar_t *p=str; - if( p ) - { - while( *p ) - { - *(out++)=*(p++); - } - free( str ); - } - break; - } - - case L'f': - *(out++)=L'\f'; - break; - - case L'n': - *(out++)=L'\n'; - break; - - case L'r': - *(out++)=L'\r'; - break; - - case L't': - *(out++)=L'\t'; - break; - - case L'v': - *(out++)=L'\v'; - break; - - /* - Parse numeric backslash escape - */ - case L'u': - case L'U': - case L'x': - case L'o': - { - int i; - wchar_t res=0; - int chars=2; - int base=16; - - switch( *in++ ) - { - case L'u': - base=16; - chars=4; - break; - - case L'U': - base=16; - chars=8; - break; - - case L'x': - base=16; - chars=2; - break; - - case L'o': - base=8; - chars=3; - break; - - } - - for( i=0; i= L'a') && - (*in < L'a'+32) ) - { - if( has_escape ) - *(out++)=L'\x1b'; - *(out++)=*in-L'a'+1; - break; - } - - if( (*in >= L'A') && - (*in < L'A'+32) ) - { - if( has_escape ) - *(out++)=L'\x1b'; - *(out++)=*in-L'A'+1; - break; - } - debug( 1, _( L"Invalid sequence - Control-nothing?\n" ) ); - error = 1; - - break; - } - - /* - Parse meta sequence - */ - case L'M': - { - in++; - if( *in != L'-' ) - { - error=1; - debug( 1, _( L"Invalid sequence - no dash after meta\n" ) ); - break; - } - if( !*(in+1) ) - { - debug( 1, _( L"Invalid sequence - Meta-nothing?" ) ); - error=1; - break; - } - *(out++)=L'\x1b'; - - break; - } - - default: - { - *(out++)=*in; - break; - } - - } - - break; - } - default: - { - *(out++)=*in; - break; - } - } - in++; - } - - - - if( error ) - { - free( res); - res=0; - } - else - { -// fwprintf( stderr, L"%ls translated ok\n", in_orig ); - *out = L'\0'; - } - - if( !error ) - { - if( wcslen( res ) == 0 ) - { - debug( 1, _( L"Invalid sequence - '%ls' expanded to zero characters" ), in_orig ); - error =1; - res = 0; - } - } - - return res; -} - - -void input_parse_inputrc_line( wchar_t *cmd ) -{ - wchar_t *p=cmd; - - /* Make all whitespace into space characters */ - while( *p ) - { - if( *p == L'\t' || *p == L'\r' ) - *p=L' '; - p++; - } - - /* Remove spaces at beginning/end */ - while( *cmd == L' ' ) - cmd++; - - p = cmd + wcslen(cmd)-1; - while( (p >= cmd) && (*p == L' ') ) - { - *p=L'\0'; - p--; - } - - /* Skip comments */ - if( *cmd == L'#' ) - return; - - /* Skip empty lines */ - if( *cmd == L'\0' ) - return; - - if( wcscmp( L"$endif", cmd) == 0 ) - { - if( inputrc_skip_block_count ) - { - inputrc_skip_block_count--; -/* - if( !inputrc_skip_block_count ) - fwprintf( stderr, L"Stop skipping\n" ); - else - fwprintf( stderr, L"Decrease skipping\n" ); -*/ - } - else - { - if( inputrc_block_count ) - { - inputrc_block_count--; -// fwprintf( stderr, L"End of active block\n" ); - } - else - { - inputrc_error = 1; - debug( 1, - _( L"Mismatched $endif in inputrc file" ) ); - } - } - return; - } - - if( wcscmp( L"$else", cmd) == 0 ) - { - if( inputrc_skip_block_count ) - { - if( inputrc_skip_block_count == 1 ) - { - inputrc_skip_block_count--; - inputrc_block_count++; - } - - } - else - { - inputrc_skip_block_count++; - inputrc_block_count--; - } - - return; - } - - if( inputrc_skip_block_count ) - { - if( wcsncmp( L"$if ", cmd, wcslen( L"$if " )) == 0 ) - inputrc_skip_block_count++; -// fwprintf( stderr, L"Skip %ls\n", cmd ); - - return; - } - - if( *cmd == L'\"' ) - { - - wchar_t *key; - wchar_t *val; - wchar_t *sequence; - wchar_t prev=0; - - - cmd++; - key=cmd; - - for( prev=0; ;prev=*cmd,cmd++ ) - { - if( !*cmd ) - { - debug( 1, - _( L"Mismatched quote" ) ); - inputrc_error = 1; - return; - } - - if(( *cmd == L'\"' ) && prev != L'\\' ) - break; - - } - *cmd=0; - cmd++; - if( *cmd != L':' ) - { - debug( 1, - _( L"Expected a \':\'" ) ); - inputrc_error = 1; - return; - } - cmd++; - while( *cmd == L' ' ) - cmd++; - - val = cmd; - - sequence = input_expand_sequence( key ); - if( sequence ) - { - add_mapping( L"global", sequence, key, val ); - free( sequence ); - } - - return; - } - else if( wcsncmp( L"$include ", cmd, wcslen(L"$include ") ) == 0 ) - { - wchar_t *tmp; - - cmd += wcslen( L"$include "); - while( *cmd == L' ' ) - cmd++; - tmp=wcsdup(cmd); - tmp = expand_tilde(tmp); - if( tmp ) - input_read_inputrc( tmp ); - free(tmp); - return; - } - else if( wcsncmp( L"set", cmd, wcslen( L"set" ) ) == 0 ) - { - wchar_t *set, *key, *value, *end; - wchar_t *state; - - set = wcstok( cmd, L" \t", &state ); - key = wcstok( 0, L" \t", &state ); - value = wcstok( 0, L" \t", &state ); - end = wcstok( 0, L" \t", &state ); - - if( wcscmp( set, L"set" ) != 0 ) - { - debug( 1, _( L"I don\'t know what '%ls' means" ), set ); - } - else if( end ) - { - debug( 1, _( L"Expected end of line, got '%ls'" ), end ); - - } - else if( (!key) || (!value) ) - { - debug( 1, _( L"Syntax: set KEY VALUE" ) ); - } - else - { - if( wcscmp( key, L"editing-mode" ) == 0 ) - { - current_mode_mappings = get_mapping( value ); - } - } - - return; - } - else if( wcsncmp( L"$if ", cmd, wcslen( L"$if " )) == 0 ) - { - wchar_t *term_line = wcsdupcat( L"term=", env_get( L"TERM" ) ); - wchar_t *term_line2 = wcsdup( term_line ); - wchar_t *mode_line = L"mode=emacs"; - wchar_t *app_line = L"fish"; - - wchar_t *term_line2_end = wcschr( term_line2, L'-' ); - if( term_line2_end ) - *term_line2_end=0; - - - cmd += wcslen( L"$if "); - while( *cmd == L' ' ) - cmd++; - - if( (wcscmp( cmd, app_line )==0) || - (wcscmp( cmd, term_line )==0) || - (wcscmp( cmd, mode_line )==0) ) - { -// fwprintf( stderr, L"Conditional %ls is true\n", cmd ); - inputrc_block_count++; - } - else - { -// fwprintf( stderr, L"Conditional %ls is false\n", cmd ); - inputrc_skip_block_count++; - } - free( term_line ); - free( term_line2 ); - - return; - } - else - { - /* - This is a redular key binding, like - - Control-o: kill-word - - Or at least we hope it is, since if it isn't, we have no idea what it is. - */ - - wchar_t *key; - wchar_t *val; - wchar_t *sequence; - - key=cmd; - - cmd = wcschr( cmd, ':' ); - - if( !cmd ) - { - debug( 1, - _( L"Unable to parse key binding" ) ); - inputrc_error = 1; - return; - } - *cmd = 0; - - cmd++; - - while( *cmd == L' ' ) - cmd++; - - val = cmd; - - debug( 3, L"Map %ls to %ls\n", key, val ); - - sequence = input_symbolic_sequence( key ); - if( sequence ) - { - add_mapping( L"global", sequence, key, val ); - free( sequence ); - } - - return; - - } - - debug( 1, _( L"I don\'t know what %ls means" ), cmd ); -} - -/** - Read the specified inputrc file -*/ -static void input_read_inputrc( wchar_t *fn ) -{ - FILE *rc; - wchar_t *buff=0; - int buff_len=0; - int error=0; -// fwprintf( stderr, L"read %ls\n", fn ); - - signal_block(); - rc = wfopen( fn, "r" ); - - if( rc ) - { - while( !feof( rc ) && (!error)) - { - switch( fgetws2( &buff, &buff_len, rc ) ) - { - case -1: - { - debug( 1, - _( L"Error while reading input information from file '%ls'" ), - fn ); - - wperror( L"fgetws2 (read_ni)" ); - error=1; - break; - } - - default: - { - input_parse_inputrc_line( buff ); - - if( inputrc_error ) - { - fwprintf( stderr, L"%ls\n", buff ); - error=1; - } - } - } - } - free( buff ); - /* - Don't need to check exit status of fclose on read-only stream - */ - fclose( rc ); - } - signal_unblock(); - - inputrc_skip_block_count=0; - inputrc_block_count=0; -} - -/** - Add a char * based character string mapping. -*/ -static void add_terminfo_mapping( const wchar_t *mode, - const char *seq, - const wchar_t *desc, - const wchar_t *func ) -{ - if( seq ) - { - wchar_t *tmp; - tmp=str2wcs(seq); - if( tmp ) - { - add_mapping( mode, tmp, desc, func ); - free(tmp); - } - } - -} - -/** - Call input_expand_sequence on seq, and add the result as a mapping -*/ -static void add_escaped_mapping( const wchar_t *mode, - const wchar_t *seq, - const wchar_t *desc, - const wchar_t *func ) -{ - wchar_t *esc = input_expand_sequence( seq ); - if( esc ) - { - add_mapping( mode, esc, desc, func ); - free(esc); - } -} - -/** - Add bindings common to emacs and vi -*/ -static void add_common_bindings() -{ - static const wchar_t *name[] = - { - L"emacs", - L"vi", - L"vi-command" - } - ; - int i; - - /* - Universal bindings - */ - for( i=0; i<3; i++ ) - { - add_mapping( name[i], L"\n", L"Execute contents of commandline", L"execute" ); - - /* - This will make Meta-newline insert a newline, since - self-insert ignored the escape character unless it is the - only character of the sequence. - */ - add_mapping( name[i], L"\x1b\n", L"Meta-newline", L"self-insert" ); - /* - We need alternative keybidnings for arrowkeys, since - terminfo sometimes specifies a different sequence than what - keypresses actually generate - */ - add_mapping( name[i], L"\x1b[A", L"Up", L"up-or-search" ); - add_mapping( name[i], L"\x1b[B", L"Down", L"down-or-search" ); - add_terminfo_mapping( name[i], (key_up), L"Up", L"up-or-search" ); - add_terminfo_mapping( name[i], (key_down), L"Down", L"down-or-search" ); - - add_mapping( name[i], L"\x1b[C", L"Right", L"forward-char" ); - add_mapping( name[i], L"\x1b[D", L"Left", L"backward-char" ); - add_terminfo_mapping( name[i], (key_right), L"Right", L"forward-char" ); - add_terminfo_mapping( name[i], (key_left), L"Left", L"backward-char" ); - - add_terminfo_mapping( name[i], (key_dc), L"Delete", L"delete-char" ); - - add_terminfo_mapping( name[i], (key_backspace), L"Backspace", L"backward-delete-char" ); - add_mapping( name[i], L"\x7f", L"Backspace", L"backward-delete-char" ); - - add_mapping( name[i], L"\x1b[H", L"Home", L"beginning-of-line" ); - add_mapping( name[i], L"\x1b[F", L"End", L"end-of-line" ); - add_terminfo_mapping( name[i], (key_home), L"Home", L"beginning-of-line" ); - add_terminfo_mapping( name[i], (key_end), L"End", L"end-of-line" ); - - /* - We need lots of alternative keybidnings, since terminal - emulators can't seem to agree on what sequence to generate, - and terminfo doesn't specify what sequence should be - generated - */ - - add_mapping( name[i], L"\x1b\x1bOC", L"Alt-Right", L"nextd-or-forward-word" ); - add_mapping( name[i], L"\x1b\x1bOD", L"Alt-Left", L"prevd-or-backward-word" ); - add_mapping( name[i], L"\x1b\x1b[C", L"Alt-Right", L"nextd-or-forward-word" ); - add_mapping( name[i], L"\x1b\x1b[D", L"Alt-Left", L"prevd-or-backward-word" ); - add_mapping( name[i], L"\x1bO3C", L"Alt-Right", L"nextd-or-forward-word" ); - add_mapping( name[i], L"\x1bO3D", L"Alt-Left", L"prevd-or-backward-word" ); - add_mapping( name[i], L"\x1b[3C", L"Alt-Right", L"nextd-or-forward-word" ); - add_mapping( name[i], L"\x1b[3D", L"Alt-Left", L"prevd-or-backward-word" ); - add_mapping( name[i], L"\x1b[1;3C", L"Alt-Right", L"nextd-or-forward-word" ); - add_mapping( name[i], L"\x1b[1;3D", L"Alt-Left", L"prevd-or-backward-word" ); - - add_mapping( name[i], L"\x1b\x1bOA", L"Alt-Up", L"history-token-search-backward" ); - add_mapping( name[i], L"\x1b\x1bOB", L"Alt-Down", L"history-token-search-forward" ); - add_mapping( name[i], L"\x1b\x1b[A", L"Alt-Up", L"history-token-search-backward" ); - add_mapping( name[i], L"\x1b\x1b[B", L"Alt-Down", L"history-token-search-forward" ); - add_mapping( name[i], L"\x1bO3A", L"Alt-Up", L"history-token-search-backward" ); - add_mapping( name[i], L"\x1bO3B", L"Alt-Down", L"history-token-search-forward" ); - add_mapping( name[i], L"\x1b[3A", L"Alt-Up", L"history-token-search-backward" ); - add_mapping( name[i], L"\x1b[3B", L"Alt-Down", L"history-token-search-forward" ); - add_mapping( name[i], L"\x1b[1;3A", L"Alt-Up", L"history-token-search-backward" ); - add_mapping( name[i], L"\x1b[1;3B", L"Alt-Down", L"history-token-search-forward" ); - } - - /* - Bindings used in emacs and vi mode, but not in vi-command mode - */ - for( i=0; i<2; i++ ) - { - add_mapping( name[i], L"\t", L"Tab", L"complete" ); - add_escaped_mapping( name[i], (L"\\C-k"), L"Control-k", L"kill-line" ); - add_escaped_mapping( name[i], (L"\\C-y"), L"Control-y", L"yank" ); - add_mapping( name[i], L"", L"Any key", L"self-insert" ); - } - -} - -/** - Add emacs-specific bindings -*/ -static void add_emacs_bindings() -{ - add_escaped_mapping( L"emacs", (L"\\C-a"), L"Control-a", L"beginning-of-line" ); - add_escaped_mapping( L"emacs", (L"\\C-e"), L"Control-e", L"end-of-line" ); - add_escaped_mapping( L"emacs", (L"\\M-y"), L"Alt-y", L"yank-pop" ); - add_escaped_mapping( L"emacs", (L"\\C-h"), L"Control-h", L"backward-delete-char" ); - add_escaped_mapping( L"emacs", (L"\\C-e"), L"Control-e", L"end-of-line" ); - add_escaped_mapping( L"emacs", (L"\\C-w"), L"Control-w", L"backward-kill-word" ); - add_escaped_mapping( L"emacs", (L"\\C-p"), L"Control-p", L"history-search-backward" ); - add_escaped_mapping( L"emacs", (L"\\C-n"), L"Control-n", L"history-search-forward" ); - add_escaped_mapping( L"emacs", (L"\\C-f"), L"Control-f", L"forward-char" ); - add_escaped_mapping( L"emacs", (L"\\C-b"), L"Control-b", L"backward-char" ); - add_escaped_mapping( L"emacs", (L"\x1b\x7f"), L"Alt-backspace", L"backward-kill-word" ); - add_escaped_mapping( L"emacs", (L"\x1bb"), L"Alt-b", L"backward-word" ); - add_escaped_mapping( L"emacs", (L"\x1bf"), L"Alt-f", L"forward-word" ); - add_escaped_mapping( L"emacs", (L"\x1bd"), L"Alt-d", L"forward-kill-word" ); - add_terminfo_mapping( L"emacs", (key_ppage), L"Page Up", L"beginning-of-history" ); - add_terminfo_mapping( L"emacs", (key_npage), L"Page Down", L"end-of-history" ); - add_escaped_mapping( L"emacs", (L"\x1b<"), L"Alt-<", L"beginning-of-buffer" ); - add_escaped_mapping( L"emacs", (L"\x1b>"), L"Alt->", L"end-of-buffer" ); -} - -/** - Add vi-specific bindings -*/ -static void add_vi_bindings() -{ - add_mapping( L"vi", L"\x1b", L"Escape", L"bind -M vi-command" ); - - add_mapping( L"vi-command", L"i", L"i", L"bind -M vi" ); - add_mapping( L"vi-command", L"I", L"I", L"bind -M vi" ); - add_mapping( L"vi-command", L"k", L"k", L"history-search-backward" ); - add_mapping( L"vi-command", L"j", L"j", L"history-search-forward" ); - add_mapping( L"vi-command", L" ", L"Space", L"forward-char" ); - add_mapping( L"vi-command", L"l", L"l", L"forward-char" ); - add_mapping( L"vi-command", L"h", L"h", L"backward-char" ); - add_mapping( L"vi-command", L"$", L"$", L"end-of-line" ); - add_mapping( L"vi-command", L"^", L"^", L"beginning-of-line" ); - add_mapping( L"vi-command", L"0", L"0", L"beginning-of-line" ); - - add_mapping( L"vi-command", L"b", L"b", L"backward-word" ); - add_mapping( L"vi-command", L"B", L"B", L"backward-word" ); - add_mapping( L"vi-command", L"w", L"w", L"forward-word" ); - add_mapping( L"vi-command", L"W", L"W", L"forward-word" ); - - add_mapping( L"vi-command", L"x", L"x", L"delete-char" ); - - add_mapping( L"vi-command", L"1", L"1", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"2", L"2", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"3", L"3", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"4", L"4", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"5", L"5", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"6", L"6", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"7", L"7", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"8", L"8", L"vi-arg-digit" ); - add_mapping( L"vi-command", L"9", L"9", L"vi-arg-digit" ); - - - add_mapping( L"vi-command", L"d", L"d", L"vi-delete-to" ); - add_mapping( L"vi-command", L"D", L"D", L"vi-delete-to" ); - - -/* - movement ("h", "l"), word movement - ("b", "B", "w", "W", "e", "E"), moving to beginning and end of line - ("0", "^", "$"), and inserting and appending ("i", "I", "a", "A"), - changing and deleting ("c", "C", "d", "D"), character replacement and - deletion ("r", "x"), and finally yanking and pasting ("y", "p") -*/ + input_mapping_t *m = malloc( sizeof( input_mapping_t ) ); + m->seq = intern( sequence ); + m->command = intern(command); + al_push( &mappings, m ); } @@ -1425,8 +363,6 @@ static int interrupt_handler() int input_init() { - wchar_t *fn; - if( is_init ) return 1; @@ -1441,177 +377,67 @@ int input_init() } output_set_term( env_get( L"TERM" ) ); - hash_init( &all_mappings, &hash_wcs_func, &hash_wcs_cmp ); + input_terminfo_init(); - /* - Add the default key bindings. - Maybe some/most of these should be moved to the keybindings file? - */ - - /* - Many terminals (xterm, screen, etc.) have two different valid escape - sequences for arrow keys. One which is defined in terminfo/termcap - and one which is actually emitted by the arrow keys. The logic - escapes me, but I put in these hardcodes here for that reason. - */ - - add_common_bindings(); - add_emacs_bindings(); - add_vi_bindings(); - - current_mode_mappings = (array_list_t *)hash_get( &all_mappings, - L"emacs" ); - - - fn = env_get( L"INPUTRC" ); - - if( !fn ) - fn = L"~/.inputrc"; - - fn = expand_tilde( wcsdup( fn )); - - if( fn ) - { - input_read_inputrc( fn ); - free(fn); - } - - current_application_mappings = (array_list_t *)hash_get( &all_mappings, - L"fish" ); - global_mappings = (array_list_t *)hash_get( &all_mappings, - L"global" ); - return 1; - } -/** - Free memory used by the specified mapping -*/ -static void destroy_mapping( void *key, void *val ) -{ - array_list_t *mappings = (array_list_t *)val; - - al_foreach( mappings, &free ); - al_destroy( mappings ); - - free((void *)key); - free((void *)val); -} - - void input_destroy() { if( !is_init ) return; + is_init=0; + al_foreach( &mappings, &free ); + al_destroy( &mappings ); + input_common_destroy(); - hash_foreach( &all_mappings, &destroy_mapping ); - hash_destroy( &all_mappings ); - if( del_curterm( cur_term ) == ERR ) { debug( 0, _(L"Error while closing terminfo") ); } + + input_terminfo_destroy(); } /** Perform the action of the specified binding */ -static wint_t input_exec_binding( mapping *m, const wchar_t *seq ) +static wint_t input_exec_binding( input_mapping_t *m, const wchar_t *seq ) { - int i; - -// fwprintf( stderr, L"Binding %ls\n", m->command ); - wchar_t code = input_get_code( m->command ); + wchar_t code = input_function_get_code( m->command ); if( code != -1 ) { switch( code ) { - case R_DUMP_FUNCTIONS: - { - for( i=0; i 0 && repeat <= 9 ) - repeat_count *= repeat; - - return R_REPAINT; + return seq[0]; } - case R_VI_DELETE_TO: - { - first_command = R_VI_DELETE_TO; - return R_REPAINT; - } - default: { - - if( first_command ) - { - switch( first_command ) - { - case R_VI_DELETE_TO: - { - - break; - - } - } - } - else - { - for( i=1; icommand, 0, TOP ); - + /* We still need to return something to the caller, R_NULL tells the reader that no key press needs to be handled, @@ -1623,8 +449,8 @@ static wint_t input_exec_binding( mapping *m, const wchar_t *seq ) */ return R_NULL; + } - } @@ -1632,13 +458,16 @@ static wint_t input_exec_binding( mapping *m, const wchar_t *seq ) /** Try reading the specified function mapping */ -static wint_t input_try_mapping( mapping *m) +static wint_t input_try_mapping( input_mapping_t *m) { int j, k; wint_t c=0; + /* + Check if the actual function code of this mapping is on the stack + */ c = input_common_readch( 0 ); - if( c == input_get_code( m->command ) ) + if( c == input_function_get_code( m->command ) ) { return input_exec_binding( m, m->seq ); } @@ -1646,8 +475,9 @@ static wint_t input_try_mapping( mapping *m) if( m->seq != 0 ) { + for( j=0; m->seq[j] != L'\0' && - m->seq[j] == (c=input_common_readch( j>0 )); j++ ) + m->seq[j] == (c=input_common_readch( j>0 )); j++ ) ; if( m->seq[j] == L'\0' ) @@ -1665,7 +495,7 @@ static wint_t input_try_mapping( mapping *m) input_unreadch( m->seq[k] ); } } - } + } return 0; } @@ -1675,7 +505,6 @@ void input_unreadch( wint_t ch ) input_common_unreadch( ch ); } - wint_t input_readch() { @@ -1687,51 +516,27 @@ wint_t input_readch() Clear the interrupted flag */ reader_interrupted(); - + /* Search for sequence in various mapping tables */ while( 1 ) { - - if( current_application_mappings ) - { - for( i=0; iseq) == 0 ) { wchar_t arr[2]= @@ -1746,7 +551,228 @@ wint_t input_readch() } } + /* + No action to take on specified character, ignoer it + and move to next one. + */ input_common_readch( 0 ); } } + +void input_mapping_get_names( array_list_t *list ) +{ + int i; + + for( i=0; iseq ); + } + +} + + +int input_mapping_erase( const wchar_t *sequence ) +{ + int ok = 0; + int i; + size_t sz = al_get_count( &mappings ); + + for( i=0; iseq ) ) + { + if( i != (sz-1 ) ) + { + al_set( &mappings, i, al_get( &mappings, sz -1 ) ); + } + al_truncate( &mappings, sz-1 ); + ok = 1; + + free( m ); + + break; + + } + + } + + return ok; + +} + +const wchar_t *input_mapping_get( const wchar_t *sequence ) +{ + int i; + size_t sz = al_get_count( &mappings ); + + for( i=0; iseq ) ) + { + return m->command; + } + } + return 0; +} +/** + Add all terminfo mappings + */ +static void input_terminfo_init() +{ + terminfo_mappings = al_halloc( 0 ); + + TERMINFO_ADD( key_down ); + TERMINFO_ADD( key_up ); + TERMINFO_ADD( key_left ); + TERMINFO_ADD( key_right ); + TERMINFO_ADD( key_dc ); + TERMINFO_ADD( key_backspace ); + TERMINFO_ADD( key_home ); + TERMINFO_ADD( key_end ); + TERMINFO_ADD( key_ppage ); + TERMINFO_ADD( key_npage ); + TERMINFO_ADD( key_clear ); + TERMINFO_ADD( key_close ); + TERMINFO_ADD( key_command ); + TERMINFO_ADD( key_copy ); + TERMINFO_ADD( key_create ); + TERMINFO_ADD( key_dl ); + TERMINFO_ADD( key_enter ); + TERMINFO_ADD( key_undo ); + TERMINFO_ADD( key_suspend ); + TERMINFO_ADD( key_cancel ); +} + +static void input_terminfo_destroy() +{ + + if( terminfo_mappings ) + { + halloc_free( terminfo_mappings ); + } +} + +const wchar_t *input_terminfo_get_sequence( const wchar_t *name ) +{ + const char *res = 0; + int i; + static string_buffer_t *buff = 0; + int err = ENOENT; + + CHECK( name, 0 ); + input_init(); + + for( i=0; iname ) ) + { + res = m->seq; + err = EILSEQ; + break; + } + } + + if( !res ) + { + errno = err; + return 0; + } + + if( !buff ) + { + buff = sb_halloc( global_context ); + } + + sb_clear( buff ); + sb_printf( buff, L"%s", res ); + + return (wchar_t *)buff->buff; + +} + +const wchar_t *input_terminfo_get_name( const wchar_t *seq ) +{ + int i; + static string_buffer_t *buff = 0; + + CHECK( seq, 0 ); + input_init(); + + if( !buff ) + { + buff = sb_halloc( global_context ); + } + + for( i=0; iseq ) + { + continue; + } + + sb_clear( buff ); + sb_printf( buff, L"%s", m->seq ); + + if( !wcscmp( seq, (wchar_t *)buff->buff ) ) + { + return m->name; + } + } + + return 0; + +} + +void input_terminfo_get_names( array_list_t *lst, int skip_null ) +{ + int i; + + CHECK( lst, ); + input_init(); + + for( i=0; iseq ) + { + continue; + } + al_push( lst, m->name ); + } +} + +void input_function_get_names( array_list_t *lst ) +{ + int i; + + CHECK( lst, ); + + for( i=0; i<(sizeof(name_arr)/sizeof(wchar_t *)); i++ ) + { + al_push( lst, name_arr[i] ); + } +} + +wchar_t input_function_get_code( const wchar_t *name ) +{ + + int i; + for( i = 0; i<(sizeof( code_arr )/sizeof(wchar_t)) ; i++ ) + { + if( wcscmp( name, name_arr[i] ) == 0 ) + { + return code_arr[i]; + } + } + return -1; +} + diff --git a/input.h b/input.h index 32406b28f..575c34cbb 100644 --- a/input.h +++ b/input.h @@ -99,26 +99,42 @@ void input_unreadch( wint_t ch ); \param d a description of the sequence \param cmd an input function that will be run whenever the key sequence occurs */ -void add_mapping( const wchar_t *mode, const wchar_t *s, const wchar_t * d, const wchar_t *cmd ); +void input_mapping_add( const wchar_t *sequence, const wchar_t *cmd ); + +void input_mapping_get_names( array_list_t *list ); + +int input_mapping_erase( const wchar_t *sequence ); + +const wchar_t *input_mapping_get( const wchar_t *sequence ); /** - Sets the mode keybindings. -*/ -void input_set_mode( wchar_t *name ); + Return the sequence for the terminfo variable of the specified name. + + If no terminfo variable of the specified name could be found, return 0 and set errno to ENOENT. + If the terminfo variable does not have a value, return 0 and set errno to EILSEQ. + */ +const wchar_t *input_terminfo_get_sequence( const wchar_t *name ); /** - Sets the application keybindings -*/ -void input_set_application( wchar_t *name ); + Return the name of the terminfo variable with the specified sequence + */ +const wchar_t *input_terminfo_get_name( const wchar_t *seq ); /** - Parse a single line of inputrc information. -*/ -void input_parse_inputrc_line( wchar_t *cmd ); + Return a list of all known terminfo names + */ +void input_terminfo_get_names( array_list_t *lst, int skip_null ); + /** - Returns the function for the given function name. + Returns the input function code for the given input function name. */ -wchar_t input_get_code( const wchar_t *name ); +wchar_t input_function_get_code( const wchar_t *name ); + +/** + Returns a list of all existing input function names + */ +void input_function_get_names( array_list_t *lst ); + #endif diff --git a/share/completions/bind.fish b/share/completions/bind.fish index 0a4f23fdf..a987d3c06 100644 --- a/share/completions/bind.fish +++ b/share/completions/bind.fish @@ -1,3 +1,12 @@ +complete -c bind -s a -l all --description 'Show unavaliable key bindings/erase all bindings' +complete -c bind -s e -l erase --description 'Erase mode' +complete -c bind -s f -l function-names --description 'Print names of available functions' complete -c bind -s h -l help --description "Display help and exit" -complete -c bind -s M -l set-mode --description 'Change input mode' +complete -c bind -s k -l key --description 'Specify key name, not sequence' +complete -c bind -s K -l key-names --description 'Print names of available keys' + +complete -c bind -n __fish_bind_test1 -a '(bind --key-names)' -d 'Key name' -x +complete -c bind -n __fish_bind_test2 -a '(bind --function-names)' -d 'Function name' -x + + diff --git a/share/functions/__fish_bind_test1.fish b/share/functions/__fish_bind_test1.fish new file mode 100644 index 000000000..37f5eeb0e --- /dev/null +++ b/share/functions/__fish_bind_test1.fish @@ -0,0 +1,27 @@ + +function __fish_bind_test1 + +set -l args +set -l use_keys no +for i in (commandline -poc) + switch $i + case -k --k --ke --key + set use_keys yes + + case "-*" + + case "*" + set args $args $i + end +end + +switch $use_keys + case yes + switch (count $args) + case 1 + return 0 + end +end +return 1 + +end diff --git a/share/functions/__fish_bind_test2.fish b/share/functions/__fish_bind_test2.fish new file mode 100644 index 000000000..8530451c0 --- /dev/null +++ b/share/functions/__fish_bind_test2.fish @@ -0,0 +1,20 @@ + +function __fish_bind_test2 +set -l args +for i in (commandline -poc) + switch $i + case "-*" + + case "*" + set args $args $i + end +end + + switch (count $args) + case 2 + return 0 + end + + return 1 + +end diff --git a/share/functions/__fish_config_interactive.fish b/share/functions/__fish_config_interactive.fish index 6e100fdc1..ce51a01b2 100644 --- a/share/functions/__fish_config_interactive.fish +++ b/share/functions/__fish_config_interactive.fish @@ -99,24 +99,6 @@ function __fish_config_interactive -d "Initializations that should be performed printf (_ "Good bye\n") end - # - # Set INPUTRC to something nice - # - # We override INPUTRC if already set, since it may be set by a shell - # other than fish, which may use a different file. The new value should - # be exported, since the fish inputrc file plays nice with other files - # by including them when found. - # - - for i in $configdir/fish/fish_inputrc $__fish_sysconfdir/fish_inputrc ~/.inputrc /etc/inputrc - if test -f $i - set -xg INPUTRC $i - break - end - end - - - # # Set various defaults using these throwaway functions # @@ -202,5 +184,16 @@ function __fish_config_interactive -d "Initializations that should be performed complete -x -p "/etc/init.d/*" -a status --description 'Print service status' complete -x -p "/etc/init.d/*" -a restart --description 'Stop and then start service' complete -x -p "/etc/init.d/*" -a reload --description 'Reload service configuration' + + if not set -q fish_key_bindings + set -U fish_key_bindings fish_default_key_bindings + end + eval $fish_key_bindings + + function __fish_reload_key_bindings -d "Reload keybindings when binding variable change" + eval $fish_key_bindings + end end + + diff --git a/share/functions/fish_default_key_bindings.fish b/share/functions/fish_default_key_bindings.fish new file mode 100644 index 000000000..8db6d8037 --- /dev/null +++ b/share/functions/fish_default_key_bindings.fish @@ -0,0 +1,91 @@ + +function fish_default_key_bindings -d "Default (Emacs-like) key bindings for fish" + + # Clear earlier bindings, if any + bind --erase --all + + # This is the default binding, i.e. the one used if no other binding matches + bind "" self-insert + + bind \n execute + + bind \ck kill-line + bind \cy yank + bind \t complete + + bind \e\n "commandline -i \n" + + bind \e\[A up-or-search + bind \e\[B down-or-search + bind -k down down-or-search + bind -k up up-or-search + + bind \e\[C forward-char + bind \e\[D backward-char + bind -k right forward-char + bind -k left backward-char + + bind -k dc delete-char + bind -k backspace backward-delete-char + bind \x7f backward-delete-char + + bind \e\[H beginning-of-line + bind \e\[F end-of-line + bind -k home beginning-of-line + bind -k end end-of-line + + bind \e\eOC nextd-or-forward-word + bind \e\eOD prevd-or-backward-word + bind \e\e\[C nextd-or-forward-word + bind \e\e\[D prevd-or-backward-word + bind \eO3C nextd-or-forward-word + bind \eO3D prevd-or-backward-word + bind \e\[3C nextd-or-forward-word + bind \e\[3D prevd-or-backward-word + bind \e\[1\;3C nextd-or-forward-word + bind \e\[1\;3D prevd-or-backward-word + + bind \e\eOA history-token-search-backward + bind \e\eOB history-token-search-forward + bind \e\e\[A history-token-search-backward + bind \e\e\[B history-token-search-forward + bind \eO3A history-token-search-backward + bind \eO3B history-token-search-forward + bind \e\[3A history-token-search-backward + bind \e\[3B history-token-search-forward + bind \e\[1\;3A history-token-search-backward + bind \e\[1\;3B history-token-search-forward + + bind \ca beginning-of-line + bind \ce end-of-line + bind \ey yank-pop + bind \ch backward-delete-char + bind \cw backward-kill-word + bind \cp history-search-backward + bind \cn history-search-forward + bind \cf forward-char + bind \cb backward-char + bind \e\x7f backward-kill-word + bind \eb backward-word + bind \ef forward-word + bind \ed forward-kill-word + bind -k ppage beginning-of-history + bind -k npage end-of-history + bind \e\< beginning-of-buffer + bind \e\> end-of-buffer + + bind \el __fish_list_current_token + bind \ew 'set tok (commandline -pt); if test $tok[1]; whatis $tok[1]; commandline -f repaint; end' + bind \cl 'clear; commandline -f repaint' + bind \cc delete-line + bind \cu backward-kill-line + bind \ed kill-word + bind \cw backward-kill-word + bind \ed 'if test -z (commandline); dirh; commandline -f repaint; else; commandline -f kill-word; end' + bind \cd delete-or-exit + + # This will make sure the output of the current command is paged using the less pager when you press Meta-p + bind \ep 'if commandline -j|grep -v "less *\$" >/dev/null; commandline -aj "|less;"; end' + +end +