diff options
Diffstat (limited to 'highlight.c')
-rw-r--r-- | highlight.c | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/highlight.c b/highlight.c new file mode 100644 index 00000000..39385cad --- /dev/null +++ b/highlight.c @@ -0,0 +1,557 @@ +/** \file highlight.c + Functions for syntax highlighting +*/ +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <wchar.h> +#include <wctype.h> +#include <sys/types.h> +#include <termios.h> +#include <signal.h> + +#include "config.h" +#include "util.h" +#include "wutil.h" +#include "highlight.h" +#include "tokenizer.h" +#include "proc.h" +#include "parser.h" +#include "builtin.h" +#include "function.h" +#include "env.h" +#include "expand.h" +#include "sanity.h" +#include "common.h" +#include "complete.h" +#include "output.h" + +static void highlight_universal_internal( wchar_t * buff, + int *color, + int pos, + array_list_t *error ); + + +/** + The environment variables used to specify the color of different tokens. +*/ +static wchar_t *hightlight_var[] = +{ + L"fish_color_normal", + L"fish_color_command", + L"fish_color_subshell", + L"fish_color_redirection", + L"fish_color_end", + L"fish_color_error", + L"fish_color_param", + L"fish_color_comment", + L"fish_color_match", + L"fish_color_search_match", + L"fish_color_pager_prefix", + L"fish_color_pager_completion", + L"fish_color_pager_description", + L"fish_color_pager_progress" +} + ; + + +int highlight_get_color( int highlight ) +{ + if( highlight < 0 ) + return FISH_COLOR_NORMAL; + if( highlight >= (12) ) + return FISH_COLOR_NORMAL; + + wchar_t *val = env_get( hightlight_var[highlight]); + if( val == 0 ) + val = env_get( hightlight_var[HIGHLIGHT_NORMAL]); + + if( val == 0 ) + { + return FISH_COLOR_NORMAL; + } + + int i; + int color; + + return output_color_code( val ); + +} + + +void highlight_shell( wchar_t * buff, + int *color, + int pos, + array_list_t *error ) +{ + tokenizer tok; + int had_cmd=0; + int i; + int last_val; + wchar_t *last_cmd=0; + int len = wcslen(buff); + + if( !len ) + return; + + for( i=0; buff[i] != 0; i++ ) + color[i] = -1; + + for( tok_init( &tok, buff, TOK_SHOW_COMMENTS ); + tok_has_next( &tok ); + tok_next( &tok ) ) + { + int last_type = tok_last_type( &tok ); + int prev_argc=0; + + switch( last_type ) + { + case TOK_STRING: + { + if( had_cmd ) + { + + /*Parameter */ + wchar_t *param = tok_last( &tok ); + if( param[0] == L'-' ) + { + if( complete_is_valid_option( last_cmd, param, error )) + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_PARAM; + else + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + } + else + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_PARAM; + } + } + else + { + prev_argc=0; + + /* + Command. First check that the command actually exists. + */ + wchar_t *cmd = + (last_type == TOK_STRING) ? + expand_one(wcsdup(tok_last( &tok )),EXPAND_SKIP_SUBSHELL | EXPAND_SKIP_VARIABLES) : + wcsdup(tok_last( &tok )); + if( cmd == 0 ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + } + else + { + wchar_t *tmp; + int is_cmd = 0; + int is_subcommand = 0; + int mark = tok_get_pos( &tok ); + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMAND; + + + if( parser_is_subcommand( cmd ) ) + { + tok_next( &tok ); + if(( wcscmp( L"-h", tok_last( &tok ) ) == 0 ) || + ( wcscmp( L"--help", tok_last( &tok ) ) == 0 ) ) + { + /* + The builtin and command builtins + are normally followed by another + command, but if they are invoked + with the -h option, their help text + is displayed instead + */ + } + else + { + is_subcommand = 1; + } + tok_set_pos( &tok, mark ); + } + + if( !is_subcommand ) + { + /* + OK, this is a command, it has been + successfully expanded and everything + looks ok. Lets check if the command + exists. + */ + is_cmd |= builtin_exists( cmd ); + is_cmd |= function_exists( cmd ); + is_cmd |= (tmp=get_filename( cmd )) != 0; + + /* + Could not find the command. Maybe it is a path for a implicit cd command. + Lets check! + */ + if( !is_cmd ) + { + wchar_t *pp = parser_cdpath_get( cmd ); + if( pp ) + { + free( pp ); + is_cmd = 1; + } + } + + free(tmp); + if( is_cmd ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMAND; + } + else + { + if( error ) + al_push( error, wcsdupcat2 ( L"Unknown command \'", cmd, L"\'", 0 )); + color[ tok_get_pos( &tok ) ] = (HIGHLIGHT_ERROR); + } + had_cmd = 1; + } + free(cmd); + + if( had_cmd ) + { + if( last_cmd ) + free( last_cmd ); + last_cmd = wcsdup( tok_last( &tok ) ); + } + + } + + } + break; + } + + case TOK_REDIRECT_OUT: + case TOK_REDIRECT_IN: + case TOK_REDIRECT_APPEND: + case TOK_REDIRECT_FD: + { + if( !had_cmd ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + if( error ) + al_push( error, wcsdup ( L"Redirection without a command" ) ); + break; + } + + wchar_t *target=0; + + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_REDIRECTION; + tok_next( &tok ); + + /* + Check that we are redirecting into a file + */ + + switch( tok_last_type( &tok ) ) + { + case TOK_STRING: + { + target = expand_one( wcsdup( tok_last( &tok ) ), EXPAND_SKIP_SUBSHELL); + /* + Redirect filename may contain a subshell. + If so, it will be ignored/not flagged. + */ + } + break; + default: + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + if( error ) + al_push( error, wcsdup ( L"Invalid redirection" ) ); + } + + } + + if( target != 0 ) + { + wchar_t *dir = wcsdup( target ); + wchar_t *dir_end = wcsrchr( dir, L'/' ); + struct stat buff; + /* + If file is in directory other than '.', check + that the directory exists. + */ + if( dir_end != 0 ) + { + *dir_end = 0; + if( wstat( dir, &buff ) == -1 ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + if( error ) + al_push( error, wcsdupcat2( L"Directory \'", dir, L"\' does not exist", 0 ) ); + + } + } + free( dir ); + + /* + If the file is read from or appended to, check + if it exists. + */ + if( last_type == TOK_REDIRECT_IN || + last_type == TOK_REDIRECT_APPEND ) + { + if( wstat( target, &buff ) == -1 ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + if( error ) + al_push( error, wcsdupcat2( L"File \'", target, L"\' does not exist", 0 ) ); + } + } + free( target ); + } + break; + } + + case TOK_PIPE: + case TOK_BACKGROUND: + { + if( had_cmd ) + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_END; + had_cmd = 0; + } + else + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + if( error ) + al_push( error, wcsdup ( L"No job to put in background" ) ); + } + + break; + } + + case TOK_END: + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_END; + had_cmd = 0; + break; + } + + case TOK_COMMENT: + { + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_COMMENT; + break; + } + + case TOK_ERROR: + default: + { + /* + If the tokenizer reports an error, highlight it as such. + */ + if( error ) + al_push( error, wcsdup ( tok_last( &tok) ) ); + color[ tok_get_pos( &tok ) ] = HIGHLIGHT_ERROR; + break; + } + } + } + + if( last_cmd ) + free( last_cmd ); + + tok_destroy( &tok ); + + /* + Locate and syntax highlight subshells recursively + */ + + wchar_t *buffcpy = wcsdup( buff ); + wchar_t *subpos=buffcpy; + int done=0; + + while( 1 ) + { + wchar_t *begin, *end; + + if( expand_locate_subshell( subpos, + &begin, + &end, + 1) <= 0) + { + break; + } + + if( !*end ) + done=1; + else + *end=0; + + highlight_shell( begin+1, color +(begin-buffcpy)+1, -1, error ); + color[end-buffcpy]=HIGHLIGHT_PARAM; + + if( done ) + break; + + subpos = end+1; + } + free( buffcpy ); + + + last_val=0; + for( i=0; buff[i] != 0; i++ ) + { + if( color[i] >= 0 ) + last_val = color[i]; + else + color[i] = last_val; + } + + + highlight_universal_internal( buff, color, pos, error ); + + /* + Spaces should not be highlighted at all, since it makes cursor look funky in some terminals + */ + for( i=0; buff[i]; i++ ) + { + if( iswspace(buff[i]) ) + { + color[i]=0; + } + } +} + +/** + Perform quote and parenthesis highlighting on the specified string. +*/ +static void highlight_universal_internal( wchar_t * buff, + int *color, + int pos, + array_list_t *error ) +{ + + if( (pos >= 0) && (pos < wcslen(buff)) ) + { + + /* + Highlight matching quotes + */ + if( (buff[pos] == L'\'') || (buff[pos] == L'\"') ) + { + + array_list_t l; + al_init( &l ); + + int level=0; + wchar_t prev_q=0; + + wchar_t *str=buff; + + int match_found=0; + + while(*str) + { + switch( *str ) + { + case L'\\': + str++; + break; + case L'\"': + case L'\'': + if( level == 0 ) + { + level++; + al_push( &l, (void *)(str-buff) ); + prev_q = *str; + } + else + { + if( prev_q == *str ) + { + int pos1, pos2; + + level--; + pos1 = (int)al_pop( &l ); + pos2 = str-buff; + if( pos1==pos || pos2==pos ) + { + color[pos1]|=HIGHLIGHT_MATCH<<8; + color[pos2]|=HIGHLIGHT_MATCH<<8; + match_found = 1; + + } + prev_q = *str==L'\"'?L'\'':L'\"'; + } + else + { + level++; + al_push( &l, (void *)(str-buff) ); + prev_q = *str; + } + } + + break; + } + if( (*str == L'\0')) + break; + + str++; + } + + al_destroy( &l ); + + if( !match_found ) + color[pos] = HIGHLIGHT_ERROR<<8; + } + + /* + Highlight matching parenthesis + */ + if( wcschr( L"()[]{}", buff[pos] ) ) + { + int step = wcschr(L"({[", buff[pos])?1:-1; + wchar_t dec_char = *(wcschr( L"()[]{}", buff[pos] ) + step); + wchar_t inc_char = buff[pos]; + int level = 0; + wchar_t *str = &buff[pos]; + int match_found=0; + + + while( (str >= buff) && *str) + { + if( *str == inc_char ) + level++; + if( *str == dec_char ) + level--; + if( level == 0 ) + { + int pos2 = str-buff; + color[pos]|=HIGHLIGHT_MATCH<<8; + color[pos2]|=HIGHLIGHT_MATCH<<8; + match_found=1; + break; + } + str+= step; + } + + if( !match_found ) + color[pos] = HIGHLIGHT_ERROR<<8; + } + } +} + +void highlight_universal( wchar_t * buff, + int *color, + int pos, + array_list_t *error ) +{ + int i; + + for( i=0; buff[i] != 0; i++ ) + color[i] = 0; + + highlight_universal_internal( buff, color, pos, error ); +} |