2005-09-20 23:26:39 +10:00
/** \file highlight.c
2012-11-18 11:23:22 +01:00
Functions for syntax highlighting
2005-09-20 23:26:39 +10:00
*/
2006-08-11 11:18:35 +10:00
2015-07-25 23:14:25 +08:00
# include "config.h" // IWYU pragma: keep
2005-09-20 23:26:39 +10:00
# include <sys/stat.h>
# include <unistd.h>
# include <errno.h>
# include <wchar.h>
2013-10-13 16:58:40 -07:00
# include <algorithm>
2015-07-25 23:14:25 +08:00
# include <dirent.h>
# include <map>
# include <set>
# include <string>
2005-09-20 23:26:39 +10:00
2006-02-28 23:17:16 +10:00
# include "fallback.h"
2005-09-20 23:26:39 +10:00
# include "wutil.h"
# include "highlight.h"
# include "tokenizer.h"
2006-01-31 02:51:50 +10:00
# include "parse_util.h"
2015-07-25 23:14:25 +08:00
# include "parse_constants.h"
2005-09-20 23:26:39 +10:00
# include "builtin.h"
# include "function.h"
# include "env.h"
# include "expand.h"
# include "common.h"
2016-02-06 19:17:43 -08:00
# include "complete.h"
2005-09-20 23:26:39 +10:00
# include "output.h"
2006-07-28 23:52:03 +10:00
# include "wildcard.h"
2006-10-19 21:50:23 +10:00
# include "path.h"
2012-06-29 00:24:37 +05:30
# include "history.h"
2013-08-08 15:06:46 -07:00
# include "parse_tree.h"
2005-09-20 23:26:39 +10:00
2013-10-08 18:41:35 -07:00
# define CURSOR_POSITION_INVALID ((size_t)(-1))
2006-06-20 10:50:10 +10:00
/**
Number of elements in the highlight_var array
*/
2006-06-14 23:22:40 +10:00
# define VAR_COUNT ( sizeof(highlight_var) / sizeof(wchar_t *) )
2014-02-03 14:13:42 -08:00
/** The environment variables used to specify the color of different tokens. This matches the order in highlight_spec_t */
2012-11-18 11:23:22 +01:00
static const wchar_t * const highlight_var [ ] =
2005-09-20 23:26:39 +10:00
{
2012-11-18 16:30:30 -08:00
L " fish_color_normal " ,
L " fish_color_error " ,
L " fish_color_command " ,
L " fish_color_end " ,
L " fish_color_param " ,
L " fish_color_comment " ,
L " fish_color_match " ,
L " fish_color_search_match " ,
L " fish_color_operator " ,
L " fish_color_escape " ,
L " fish_color_quote " ,
L " fish_color_redirection " ,
2014-01-15 22:41:10 +01:00
L " fish_color_autosuggestion " ,
2014-03-29 14:19:45 -07:00
L " fish_color_selection " ,
2014-03-31 10:01:39 -07:00
2014-01-15 01:36:09 -08:00
L " fish_pager_color_prefix " ,
L " fish_pager_color_completion " ,
L " fish_pager_color_description " ,
L " fish_pager_color_progress " ,
L " fish_pager_color_secondary "
2012-02-06 20:14:19 -08:00
} ;
2005-09-20 23:26:39 +10:00
2012-06-16 14:08:58 -07:00
/* Determine if the filesystem containing the given fd is case insensitive. */
typedef std : : map < wcstring , bool > case_sensitivity_cache_t ;
bool fs_is_case_insensitive ( const wcstring & path , int fd , case_sensitivity_cache_t & case_sensitivity_cache )
{
/* If _PC_CASE_SENSITIVE is not defined, assume case sensitive */
bool result = false ;
# ifdef _PC_CASE_SENSITIVE
/* Try the cache first */
case_sensitivity_cache_t : : iterator cache = case_sensitivity_cache . find ( path ) ;
2012-11-18 16:30:30 -08:00
if ( cache ! = case_sensitivity_cache . end ( ) )
{
2012-06-16 14:08:58 -07:00
/* Use the cached value */
result = cache - > second ;
2012-11-18 16:30:30 -08:00
}
else
{
2012-06-16 14:08:58 -07:00
/* Ask the system. A -1 value means error (so assume case sensitive), a 1 value means case sensitive, and a 0 value means case insensitive */
long ret = fpathconf ( fd , _PC_CASE_SENSITIVE ) ;
result = ( ret = = 0 ) ;
case_sensitivity_cache [ path ] = result ;
}
# endif
return result ;
}
2012-05-13 20:19:02 -07:00
/* Tests whether the specified string cpath is the prefix of anything we could cd to. directories is a list of possible parent directories (typically either the working directory, or the cdpath). This does I/O!
2015-11-07 13:58:13 -08:00
Hack : if out_suggested_cdpath is not NULL , it returns the autosuggestion for cd . This descends the deepest unique directory hierarchy .
2012-07-06 14:34:53 -07:00
We expect the path to already be unescaped .
2012-05-13 20:19:02 -07:00
*/
2016-02-17 16:25:19 -08:00
bool is_potential_path ( const wcstring & potential_path_fragment , const wcstring_list_t & directories , path_flags_t flags )
2006-06-14 23:22:40 +10:00
{
2011-12-26 19:18:46 -08:00
ASSERT_IS_BACKGROUND_THREAD ( ) ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
const bool require_dir = ! ! ( flags & PATH_REQUIRE_DIR ) ;
2015-11-07 13:58:13 -08:00
wcstring clean_potential_path_fragment ;
2012-11-18 16:30:30 -08:00
int has_magic = 0 ;
bool result = false ;
2012-11-18 11:23:22 +01:00
2015-11-07 13:58:13 -08:00
wcstring path_with_magic ( potential_path_fragment ) ;
2012-07-06 14:34:53 -07:00
if ( flags & PATH_EXPAND_TILDE )
2015-11-07 13:58:13 -08:00
expand_tilde ( path_with_magic ) ;
2012-11-18 11:23:22 +01:00
// debug( 1, L"%ls -> %ls ->%ls", path, tilde, unescaped );
2015-11-07 13:58:13 -08:00
for ( size_t i = 0 ; i < path_with_magic . size ( ) ; i + + )
2011-12-26 19:18:46 -08:00
{
2015-11-07 13:58:13 -08:00
wchar_t c = path_with_magic . at ( i ) ;
2012-11-18 16:30:30 -08:00
switch ( c )
2011-12-26 19:18:46 -08:00
{
2012-11-19 00:31:03 -08:00
case PROCESS_EXPAND :
case VARIABLE_EXPAND :
case VARIABLE_EXPAND_SINGLE :
case BRACKET_BEGIN :
case BRACKET_END :
case BRACKET_SEP :
case ANY_CHAR :
case ANY_STRING :
case ANY_STRING_RECURSIVE :
{
has_magic = 1 ;
break ;
}
2012-11-18 11:23:22 +01:00
2012-11-19 00:31:03 -08:00
case INTERNAL_SEPARATOR :
{
break ;
}
2012-11-18 11:23:22 +01:00
2012-11-19 00:31:03 -08:00
default :
{
2015-11-07 13:58:13 -08:00
clean_potential_path_fragment . push_back ( c ) ;
2012-11-19 00:31:03 -08:00
break ;
}
2012-11-18 11:23:22 +01:00
2011-12-26 19:18:46 -08:00
}
}
2012-11-18 11:23:22 +01:00
2015-11-07 13:58:13 -08:00
if ( ! has_magic & & ! clean_potential_path_fragment . empty ( ) )
2011-12-26 19:18:46 -08:00
{
2012-05-07 17:31:24 -07:00
/* Don't test the same path multiple times, which can happen if the path is absolute and the CDPATH contains multiple entries */
std : : set < wcstring > checked_paths ;
2012-11-18 11:23:22 +01:00
2012-06-16 14:08:58 -07:00
/* Keep a cache of which paths / filesystems are case sensitive */
case_sensitivity_cache_t case_sensitivity_cache ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
for ( size_t wd_idx = 0 ; wd_idx < directories . size ( ) & & ! result ; wd_idx + + )
{
2012-05-07 17:31:24 -07:00
const wcstring & wd = directories . at ( wd_idx ) ;
2012-11-18 11:23:22 +01:00
2016-02-06 14:39:47 -08:00
const wcstring abs_path = path_apply_working_directory ( clean_potential_path_fragment , wd ) ;
2012-11-18 11:23:22 +01:00
2012-05-07 17:31:24 -07:00
/* Skip this if it's empty or we've already checked it */
if ( abs_path . empty ( ) | | checked_paths . count ( abs_path ) )
continue ;
checked_paths . insert ( abs_path ) ;
2012-11-18 11:23:22 +01:00
2012-05-07 17:31:24 -07:00
/* If we end with a slash, then it must be a directory */
bool must_be_full_dir = abs_path . at ( abs_path . size ( ) - 1 ) = = L ' / ' ;
2012-11-18 11:23:22 +01:00
if ( must_be_full_dir )
2011-12-26 19:18:46 -08:00
{
2012-05-07 17:31:24 -07:00
struct stat buf ;
2012-11-18 16:30:30 -08:00
if ( 0 = = wstat ( abs_path , & buf ) & & S_ISDIR ( buf . st_mode ) )
{
2012-05-07 17:31:24 -07:00
result = true ;
}
2011-12-26 19:18:46 -08:00
}
2012-05-07 17:31:24 -07:00
else
2011-12-26 19:18:46 -08:00
{
2012-05-07 17:31:24 -07:00
/* We do not end with a slash; it does not have to be a directory */
2016-02-04 12:45:09 -08:00
DIR * dir = NULL ;
2012-05-07 17:31:24 -07:00
const wcstring dir_name = wdirname ( abs_path ) ;
2015-11-07 13:58:13 -08:00
const wcstring filename_fragment = wbasename ( abs_path ) ;
if ( dir_name = = L " / " & & filename_fragment = = L " / " )
2011-12-26 19:18:46 -08:00
{
2015-11-07 13:58:13 -08:00
/* cd ///.... No autosuggestion. */
2012-05-07 17:31:24 -07:00
result = true ;
}
2012-11-18 16:30:30 -08:00
else if ( ( dir = wopendir ( dir_name ) ) )
{
2016-02-04 12:45:09 -08:00
// Check if we're case insensitive
const bool do_case_insensitive = fs_is_case_insensitive ( dir_name , dirfd ( dir ) , case_sensitivity_cache ) ;
wcstring matched_file ;
2012-05-13 20:19:02 -07:00
// We opened the dir_name; look for a string where the base name prefixes it
2016-02-04 12:45:09 -08:00
// Don't ask for the is_dir value unless we care, because it can cause extra filesystem access
2012-05-07 17:31:24 -07:00
wcstring ent ;
bool is_dir = false ;
while ( wreaddir_resolving ( dir , dir_name , ent , require_dir ? & is_dir : NULL ) )
2012-11-18 11:23:22 +01:00
{
2016-02-04 12:45:09 -08:00
// Maybe skip directories
if ( require_dir & & ! is_dir )
2012-11-18 16:30:30 -08:00
{
2016-02-04 12:45:09 -08:00
continue ;
2012-11-18 16:30:30 -08:00
}
2016-02-04 12:45:09 -08:00
2016-02-17 16:25:19 -08:00
if ( string_prefixes_string ( filename_fragment , ent ) | |
( do_case_insensitive & & string_prefixes_string_case_insensitive ( filename_fragment , ent ) ) )
2012-11-18 16:30:30 -08:00
{
2016-02-17 16:25:19 -08:00
// We matched
2016-02-04 12:45:09 -08:00
matched_file = ent ;
break ;
2012-06-16 14:08:58 -07:00
}
2011-12-26 19:18:46 -08:00
}
2012-05-07 17:31:24 -07:00
closedir ( dir ) ;
2016-02-17 16:25:19 -08:00
2016-02-04 12:45:09 -08:00
/* We succeeded if we found a match */
result = ! matched_file . empty ( ) ;
2011-12-26 19:18:46 -08:00
}
}
}
}
2012-05-07 17:31:24 -07:00
return result ;
}
2012-07-06 14:34:53 -07:00
/* Given a string, return whether it prefixes a path that we could cd into. Return that path in out_path. Expects path to be unescaped. */
2016-02-17 16:25:19 -08:00
static bool is_potential_cd_path ( const wcstring & path , const wcstring & working_directory , path_flags_t flags )
2012-08-23 11:21:35 -07:00
{
2012-05-07 17:31:24 -07:00
wcstring_list_t directories ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
if ( string_prefixes_string ( L " ./ " , path ) )
{
2012-05-13 20:19:02 -07:00
/* Ignore the CDPATH in this case; just use the working directory */
directories . push_back ( working_directory ) ;
2012-11-18 16:30:30 -08:00
}
else
{
2012-05-13 20:19:02 -07:00
/* Get the CDPATH */
env_var_t cdpath = env_get_string ( L " CDPATH " ) ;
if ( cdpath . missing_or_empty ( ) )
cdpath = L " . " ;
2012-11-18 11:23:22 +01:00
2012-05-13 20:19:02 -07:00
/* Tokenize it into directories */
wcstokenizer tokenizer ( cdpath , ARRAY_SEP_STR ) ;
wcstring next_path ;
while ( tokenizer . next ( next_path ) )
{
/* Ensure that we use the working directory for relative cdpaths like "." */
2016-02-06 14:39:47 -08:00
directories . push_back ( path_apply_working_directory ( next_path , working_directory ) ) ;
2012-05-13 20:19:02 -07:00
}
2012-05-07 17:31:24 -07:00
}
2012-11-18 11:23:22 +01:00
2012-05-13 20:19:02 -07:00
/* Call is_potential_path with all of these directories */
2016-02-17 16:25:19 -08:00
bool result = is_potential_path ( path , directories , flags | PATH_REQUIRE_DIR ) ;
2012-05-13 20:19:02 -07:00
return result ;
2006-06-14 23:22:40 +10:00
}
2013-10-08 15:05:30 -07:00
/* Given a plain statement node in a parse tree, get the command and return it, expanded appropriately for commands. If we succeed, return true. */
2013-10-09 02:03:50 -07:00
bool plain_statement_get_expanded_command ( const wcstring & src , const parse_node_tree_t & tree , const parse_node_t & plain_statement , wcstring * out_cmd )
2013-10-08 15:05:30 -07:00
{
assert ( plain_statement . type = = symbol_plain_statement ) ;
bool result = false ;
2014-01-15 01:40:40 -08:00
2013-10-09 15:57:10 -07:00
/* Get the command */
wcstring cmd ;
if ( tree . command_for_plain_statement ( plain_statement , src , & cmd ) )
2013-10-08 15:05:30 -07:00
{
/* Try expanding it. If we cannot, it's an error. */
if ( expand_one ( cmd , EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS ) )
{
/* Success, return the expanded string by reference */
2014-03-25 12:44:21 -07:00
out_cmd - > swap ( cmd ) ;
2013-10-08 15:05:30 -07:00
result = true ;
}
}
return result ;
}
2014-01-15 01:01:25 -08:00
rgb_color_t highlight_get_color ( highlight_spec_t highlight , bool is_background )
2012-02-11 17:07:56 -08:00
{
2014-01-15 01:01:25 -08:00
rgb_color_t result = rgb_color_t : : normal ( ) ;
2014-03-31 10:01:39 -07:00
2014-01-22 17:45:27 -08:00
/* If sloppy_background is set, then we look at the foreground color even if is_background is set */
2014-03-31 10:01:39 -07:00
bool treat_as_background = is_background & & ! ( highlight & highlight_modifier_sloppy_background ) ;
2014-01-15 01:01:25 -08:00
/* Get the primary variable */
size_t idx = highlight_get_primary ( highlight ) ;
if ( idx > = VAR_COUNT )
2012-11-18 11:23:22 +01:00
{
2014-01-15 23:33:34 -08:00
return rgb_color_t : : normal ( ) ;
2012-11-18 11:23:22 +01:00
}
2012-11-18 16:30:30 -08:00
env_var_t val_wstr = env_get_string ( highlight_var [ idx ] ) ;
2012-11-18 11:23:22 +01:00
// debug( 1, L"%d -> %d -> %ls", highlight, idx, val );
2012-11-18 16:30:30 -08:00
if ( val_wstr . missing ( ) )
val_wstr = env_get_string ( highlight_var [ 0 ] ) ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
if ( ! val_wstr . missing ( ) )
2014-01-22 17:45:27 -08:00
result = parse_color ( val_wstr , treat_as_background ) ;
2012-11-18 11:23:22 +01:00
2014-01-22 17:45:27 -08:00
/* Handle modifiers. */
2014-01-15 01:01:25 -08:00
if ( highlight & highlight_modifier_valid_path )
2012-11-18 11:23:22 +01:00
{
2012-11-18 16:30:30 -08:00
env_var_t val2_wstr = env_get_string ( L " fish_color_valid_path " ) ;
const wcstring val2 = val2_wstr . missing ( ) ? L " " : val2_wstr . c_str ( ) ;
rgb_color_t result2 = parse_color ( val2 , is_background ) ;
if ( result . is_normal ( ) )
result = result2 ;
else
{
if ( result2 . is_bold ( ) )
result . set_bold ( true ) ;
if ( result2 . is_underline ( ) )
result . set_underline ( true ) ;
}
2012-11-18 11:23:22 +01:00
}
2014-03-31 10:01:39 -07:00
2014-01-26 00:41:30 -08:00
if ( highlight & highlight_modifier_force_underline )
2012-11-18 11:23:22 +01:00
{
2014-01-26 00:41:30 -08:00
result . set_underline ( true ) ;
2012-11-18 11:23:22 +01:00
}
2014-03-31 10:01:39 -07:00
2012-11-18 16:30:30 -08:00
return result ;
2006-05-27 02:46:38 +10:00
}
2005-09-20 23:26:39 +10:00
2013-10-06 16:23:45 -07:00
static bool has_expand_reserved ( const wcstring & str )
2008-02-05 09:09:05 +10:00
{
2013-10-06 16:23:45 -07:00
bool result = false ;
for ( size_t i = 0 ; i < str . size ( ) ; i + + )
2012-11-18 11:23:22 +01:00
{
2013-10-06 16:23:45 -07:00
wchar_t wc = str . at ( i ) ;
2016-01-21 19:56:39 -08:00
if ( wc > = EXPAND_RESERVED_BASE & & wc < = EXPAND_RESERVED_END )
2012-11-18 16:30:30 -08:00
{
2013-10-06 16:23:45 -07:00
result = true ;
break ;
2012-11-18 16:30:30 -08:00
}
2012-11-18 11:23:22 +01:00
}
2013-10-06 16:23:45 -07:00
return result ;
2008-02-05 09:09:05 +10:00
}
2013-10-09 02:03:50 -07:00
/* Parse a command line. Return by reference the last command, and the last argument to that command (as a copied node), if any. This is used by autosuggestions */
static bool autosuggest_parse_command ( const wcstring & buff , wcstring * out_expanded_command , parse_node_t * out_last_arg )
2012-05-07 17:43:05 -07:00
{
2013-10-08 15:05:30 -07:00
bool result = false ;
2014-01-15 01:40:40 -08:00
2013-10-08 15:05:30 -07:00
/* Parse the buffer */
parse_node_tree_t parse_tree ;
2014-01-12 22:39:12 -08:00
parse_tree_from_string ( buff , parse_flag_continue_after_error | parse_flag_accept_incomplete_tokens , & parse_tree , NULL ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 15:05:30 -07:00
/* Find the last statement */
const parse_node_t * last_statement = parse_tree . find_last_node_of_type ( symbol_plain_statement , NULL ) ;
if ( last_statement ! = NULL )
2012-05-07 17:43:05 -07:00
{
2013-10-08 15:05:30 -07:00
if ( plain_statement_get_expanded_command ( buff , parse_tree , * last_statement , out_expanded_command ) )
2012-05-07 12:55:13 -07:00
{
2013-10-08 15:05:30 -07:00
/* We got it */
result = true ;
2014-01-15 01:40:40 -08:00
2013-10-09 02:03:50 -07:00
/* Find the last argument. If we don't get one, return an invalid node. */
const parse_node_t * last_arg = parse_tree . find_last_node_of_type ( symbol_argument , last_statement ) ;
if ( last_arg ! = NULL )
{
* out_last_arg = * last_arg ;
}
2012-05-07 12:55:13 -07:00
}
2012-05-07 17:43:05 -07:00
}
2013-10-08 15:05:30 -07:00
return result ;
2012-05-07 17:43:05 -07:00
}
2012-11-18 16:30:30 -08:00
bool autosuggest_validate_from_history ( const history_item_t & item , file_detection_context_t & detector , const wcstring & working_directory , const env_vars_snapshot_t & vars )
{
2012-02-18 18:54:36 -08:00
ASSERT_IS_BACKGROUND_THREAD ( ) ;
2012-06-29 00:24:37 +05:30
2012-05-07 12:55:13 -07:00
bool handled = false , suggestionOK = false ;
2012-05-07 17:43:05 -07:00
2012-11-18 11:23:22 +01:00
/* Parse the string */
2012-05-07 17:43:05 -07:00
wcstring parsed_command ;
2013-10-09 02:03:50 -07:00
parse_node_t last_arg_node ( token_type_invalid ) ;
2013-10-08 15:05:30 -07:00
if ( ! autosuggest_parse_command ( item . str ( ) , & parsed_command , & last_arg_node ) )
2012-05-07 17:43:05 -07:00
return false ;
2013-10-09 02:03:50 -07:00
if ( parsed_command = = L " cd " & & last_arg_node . type = = symbol_argument & & last_arg_node . has_source ( ) )
2012-11-18 16:30:30 -08:00
{
2012-05-07 12:55:13 -07:00
/* We can possibly handle this specially */
2013-10-09 02:03:50 -07:00
wcstring dir = last_arg_node . get_source ( item . str ( ) ) ;
2012-05-07 12:55:13 -07:00
if ( expand_one ( dir , EXPAND_SKIP_CMDSUBST ) )
{
handled = true ;
bool is_help = string_prefixes_string ( dir , L " --help " ) | | string_prefixes_string ( dir , L " -h " ) ;
2012-11-18 16:30:30 -08:00
if ( is_help )
{
2012-05-07 12:55:13 -07:00
suggestionOK = false ;
2012-11-18 16:30:30 -08:00
}
else
{
2012-07-20 22:11:05 -07:00
wcstring path ;
bool can_cd = path_get_cdpath ( dir , & path , working_directory . c_str ( ) , vars ) ;
2012-11-18 16:30:30 -08:00
if ( ! can_cd )
{
2012-05-07 12:55:13 -07:00
suggestionOK = false ;
2012-11-18 16:30:30 -08:00
}
else if ( paths_are_same_file ( working_directory , path ) )
{
2012-05-07 12:55:13 -07:00
/* Don't suggest the working directory as the path! */
suggestionOK = false ;
2012-11-18 16:30:30 -08:00
}
else
{
2012-05-07 12:55:13 -07:00
suggestionOK = true ;
}
}
2012-11-18 11:23:22 +01:00
}
}
2012-06-29 00:24:37 +05:30
/* If not handled specially, handle it here */
2012-11-18 16:30:30 -08:00
if ( ! handled )
{
2012-06-29 00:24:37 +05:30
bool cmd_ok = false ;
2012-07-20 20:39:31 -07:00
if ( path_get_path ( parsed_command , NULL ) )
{
2012-06-29 00:24:37 +05:30
cmd_ok = true ;
}
2012-07-20 20:39:31 -07:00
else if ( builtin_exists ( parsed_command ) | | function_exists_no_autoload ( parsed_command , vars ) )
{
2012-06-29 00:24:37 +05:30
cmd_ok = true ;
}
2012-11-18 16:30:30 -08:00
if ( cmd_ok )
{
2012-06-29 00:24:37 +05:30
const path_list_t & paths = item . get_required_paths ( ) ;
2012-11-18 16:30:30 -08:00
if ( paths . empty ( ) )
{
2012-06-29 00:24:37 +05:30
suggestionOK = true ;
}
2012-11-18 16:30:30 -08:00
else
{
2012-06-29 00:24:37 +05:30
suggestionOK = detector . paths_are_valid ( paths ) ;
}
}
2012-02-18 18:54:36 -08:00
}
2012-06-29 00:24:37 +05:30
return suggestionOK ;
2012-02-18 18:54:36 -08:00
}
2011-12-26 19:18:46 -08:00
2014-02-03 14:13:42 -08:00
/* Highlights the variable starting with 'in', setting colors within the 'colors' array. Returns the number of characters consumed. */
static size_t color_variable ( const wchar_t * in , size_t in_len , std : : vector < highlight_spec_t > : : iterator colors )
2011-12-26 19:18:46 -08:00
{
2014-02-03 14:13:42 -08:00
assert ( in_len > 0 ) ;
assert ( in [ 0 ] = = L ' $ ' ) ;
2014-03-31 10:01:39 -07:00
2014-02-03 14:13:42 -08:00
// Handle an initial run of $s.
size_t idx = 0 ;
2014-09-30 11:14:57 -07:00
size_t dollar_count = 0 ;
2014-02-03 14:13:42 -08:00
while ( in [ idx ] = = ' $ ' )
2013-08-11 00:35:00 -07:00
{
2014-02-03 14:13:42 -08:00
// Our color depends on the next char
wchar_t next = in [ idx + 1 ] ;
if ( next = = L ' $ ' | | wcsvarchr ( next ) )
2012-11-18 16:30:30 -08:00
{
2014-02-03 14:13:42 -08:00
colors [ idx ] = highlight_spec_operator ;
2012-11-18 16:30:30 -08:00
}
else
{
2014-02-03 14:13:42 -08:00
colors [ idx ] = highlight_spec_error ;
2012-02-21 11:45:13 -08:00
}
2014-02-03 14:13:42 -08:00
idx + + ;
2014-09-30 11:14:57 -07:00
dollar_count + + ;
2012-11-18 16:30:30 -08:00
}
2014-03-31 10:01:39 -07:00
2014-02-03 14:13:42 -08:00
// Handle a sequence of variable characters
while ( wcsvarchr ( in [ idx ] ) )
2012-11-18 16:30:30 -08:00
{
2014-02-03 14:13:42 -08:00
colors [ idx + + ] = highlight_spec_operator ;
2012-11-18 16:30:30 -08:00
}
2014-03-31 10:01:39 -07:00
2014-09-30 11:14:57 -07:00
// Handle a slice, up to dollar_count of them. Note that we currently don't do any validation of the slice's contents, e.g. $foo[blah] will not show an error even though it's invalid.
for ( size_t slice_count = 0 ; slice_count < dollar_count & & in [ idx ] = = L ' [ ' ; slice_count + + )
2012-11-18 16:30:30 -08:00
{
2014-02-03 14:13:42 -08:00
wchar_t * slice_begin = NULL , * slice_end = NULL ;
2014-09-30 11:14:57 -07:00
int located = parse_util_locate_slice ( in + idx , & slice_begin , & slice_end , false ) ;
if ( located = = 1 )
2012-11-18 16:30:30 -08:00
{
2014-09-30 11:14:57 -07:00
size_t slice_begin_idx = slice_begin - in , slice_end_idx = slice_end - in ;
assert ( slice_end_idx > slice_begin_idx ) ;
colors [ slice_begin_idx ] = highlight_spec_operator ;
colors [ slice_end_idx ] = highlight_spec_operator ;
idx = slice_end_idx + 1 ;
}
else if ( located = = 0 )
{
// not a slice
break ;
}
else
{
assert ( located < 0 ) ;
// syntax error
// Normally the entire token is colored red for us, but inside a double-quoted string
// that doesn't happen. As such, color the variable + the slice start red. Coloring any
// more than that looks bad, unless we're willing to try and detect where the double-quoted
// string ends, and I'd rather not do that.
std : : fill ( colors , colors + idx + 1 , ( highlight_spec_t ) highlight_spec_error ) ;
break ;
2012-11-18 16:30:30 -08:00
}
2012-11-18 11:23:22 +01:00
}
2014-02-03 14:13:42 -08:00
return idx ;
2005-09-20 23:26:39 +10:00
}
2014-02-03 14:13:42 -08:00
/* This function is a disaster badly in need of refactoring. It colors an argument, without regard to command substitutions. */
2014-01-15 01:01:25 -08:00
static void color_argument_internal ( const wcstring & buffstr , std : : vector < highlight_spec_t > : : iterator colors )
2013-08-08 15:06:46 -07:00
{
const size_t buff_len = buffstr . size ( ) ;
2014-01-15 01:01:25 -08:00
std : : fill ( colors , colors + buff_len , ( highlight_spec_t ) highlight_spec_param ) ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
enum { e_unquoted , e_single_quoted , e_double_quoted } mode = e_unquoted ;
int bracket_count = 0 ;
for ( size_t in_pos = 0 ; in_pos < buff_len ; in_pos + + )
{
const wchar_t c = buffstr . at ( in_pos ) ;
switch ( mode )
{
case e_unquoted :
{
if ( c = = L ' \\ ' )
{
2014-01-15 01:01:25 -08:00
int fill_color = highlight_spec_escape ; //may be set to highlight_error
2013-08-08 15:06:46 -07:00
const size_t backslash_pos = in_pos ;
size_t fill_end = backslash_pos ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
// Move to the escaped character
in_pos + + ;
const wchar_t escaped_char = ( in_pos < buff_len ? buffstr . at ( in_pos ) : L ' \0 ' ) ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
if ( escaped_char = = L ' \0 ' )
{
fill_end = in_pos ;
2014-01-15 01:01:25 -08:00
fill_color = highlight_spec_error ;
2013-08-08 15:06:46 -07:00
}
else if ( wcschr ( L " ~% " , escaped_char ) )
{
if ( in_pos = = 1 )
{
fill_end = in_pos + 1 ;
}
}
else if ( escaped_char = = L ' , ' )
{
if ( bracket_count )
{
fill_end = in_pos + 1 ;
}
}
else if ( wcschr ( L " abefnrtv*?$() { } [ ] ' \ " <>^ \\ #;|& " , escaped_char ) )
{
fill_end = in_pos + 1 ;
}
else if ( wcschr ( L " c " , escaped_char ) )
{
// Like \ci. So highlight three characters
fill_end = in_pos + 1 ;
}
else if ( wcschr ( L " uUxX01234567 " , escaped_char ) )
{
long long res = 0 ;
int chars = 2 ;
int base = 16 ;
wchar_t max_val = ASCII_MAX ;
switch ( escaped_char )
{
case L ' u ' :
{
chars = 4 ;
max_val = UCS2_MAX ;
in_pos + + ;
break ;
}
case L ' U ' :
{
chars = 8 ;
max_val = WCHAR_MAX ;
in_pos + + ;
break ;
}
2011-12-26 19:18:46 -08:00
2013-08-08 15:06:46 -07:00
case L ' x ' :
{
in_pos + + ;
break ;
}
case L ' X ' :
{
max_val = BYTE_MAX ;
in_pos + + ;
break ;
}
default :
{
// a digit like \12
base = 8 ;
chars = 3 ;
break ;
}
}
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
// Consume
for ( int i = 0 ; i < chars & & in_pos < buff_len ; i + + )
{
long d = convert_digit ( buffstr . at ( in_pos ) , base ) ;
if ( d < 0 )
break ;
res = ( res * base ) + d ;
in_pos + + ;
}
//in_pos is now at the first character that could not be converted (or buff_len)
assert ( in_pos > = backslash_pos & & in_pos < = buff_len ) ;
fill_end = in_pos ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
// It's an error if we exceeded the max value
if ( res > max_val )
2014-01-15 01:01:25 -08:00
fill_color = highlight_spec_error ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
// Subtract one from in_pos, so that the increment in the loop will move to the next character
in_pos - - ;
}
assert ( fill_end > = backslash_pos ) ;
std : : fill ( colors + backslash_pos , colors + fill_end , fill_color ) ;
}
else
{
// Not a backslash
switch ( c )
{
case L ' ~ ' :
case L ' % ' :
{
if ( in_pos = = 0 )
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_operator ;
2013-08-08 15:06:46 -07:00
}
break ;
}
case L ' $ ' :
{
assert ( in_pos < buff_len ) ;
2014-02-03 14:13:42 -08:00
in_pos + = color_variable ( buffstr . c_str ( ) + in_pos , buff_len - in_pos , colors + in_pos ) ;
2014-02-09 15:33:34 -08:00
/* subtract one to account for the upcoming loop increment */
in_pos - = 1 ;
2013-08-08 15:06:46 -07:00
break ;
}
case L ' * ' :
case L ' ? ' :
case L ' ( ' :
case L ' ) ' :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_operator ;
2013-08-08 15:06:46 -07:00
break ;
}
case L ' { ' :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_operator ;
2013-08-08 15:06:46 -07:00
bracket_count + + ;
break ;
}
case L ' } ' :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_operator ;
2013-08-08 15:06:46 -07:00
bracket_count - - ;
break ;
}
case L ' , ' :
{
if ( bracket_count > 0 )
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_operator ;
2013-08-08 15:06:46 -07:00
}
break ;
}
case L ' \' ' :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_quote ;
2013-08-08 15:06:46 -07:00
mode = e_single_quoted ;
break ;
}
case L ' \" ' :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_quote ;
2013-08-08 15:06:46 -07:00
mode = e_double_quoted ;
break ;
}
}
}
break ;
}
/*
Mode 1 means single quoted string , i . e ' foo '
*/
case e_single_quoted :
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_quote ;
2013-08-08 15:06:46 -07:00
if ( c = = L ' \\ ' )
{
// backslash
if ( in_pos + 1 < buff_len )
{
const wchar_t escaped_char = buffstr . at ( in_pos + 1 ) ;
if ( escaped_char = = L ' \\ ' | | escaped_char = = L ' \' ' )
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_escape ; //backslash
colors [ in_pos + 1 ] = highlight_spec_escape ; //escaped char
2013-08-08 15:06:46 -07:00
in_pos + = 1 ; //skip over backslash
}
}
}
else if ( c = = L ' \' ' )
{
mode = e_unquoted ;
}
break ;
}
/*
Mode 2 means double quoted string , i . e . " foo "
*/
case e_double_quoted :
{
2014-08-20 22:28:42 -07:00
// slices are colored in advance, past `in_pos`, and we don't want to overwrite that
if ( colors [ in_pos ] = = highlight_spec_param )
{
colors [ in_pos ] = highlight_spec_quote ;
}
2013-08-08 15:06:46 -07:00
switch ( c )
{
case L ' " ' :
{
mode = e_unquoted ;
break ;
}
case L ' \\ ' :
{
// backslash
if ( in_pos + 1 < buff_len )
{
const wchar_t escaped_char = buffstr . at ( in_pos + 1 ) ;
2014-08-20 22:31:58 -07:00
if ( wcschr ( L " \\ \" \n $ " , escaped_char ) )
2013-08-08 15:06:46 -07:00
{
2014-01-15 01:01:25 -08:00
colors [ in_pos ] = highlight_spec_escape ; //backslash
colors [ in_pos + 1 ] = highlight_spec_escape ; //escaped char
2013-08-08 15:06:46 -07:00
in_pos + = 1 ; //skip over backslash
}
}
break ;
}
case L ' $ ' :
{
2014-02-03 14:13:42 -08:00
in_pos + = color_variable ( buffstr . c_str ( ) + in_pos , buff_len - in_pos , colors + in_pos ) ;
2014-02-09 15:33:34 -08:00
/* subtract one to account for the upcoming increment in the loop */
in_pos - = 1 ;
2013-08-08 15:06:46 -07:00
break ;
}
}
break ;
}
}
}
}
2013-10-08 18:41:35 -07:00
/* Syntax highlighter helper */
class highlighter_t
{
/* The string we're highlighting. Note this is a reference memmber variable (to avoid copying)! We must not outlive this! */
const wcstring & buff ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Cursor position */
const size_t cursor_pos ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Environment variables. Again, a reference member variable! */
const env_vars_snapshot_t & vars ;
2014-03-31 10:01:39 -07:00
2014-03-26 18:49:09 -07:00
/* Whether it's OK to do I/O */
const bool io_ok ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Working directory */
const wcstring working_directory ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* The resulting colors */
2014-01-15 01:01:25 -08:00
typedef std : : vector < highlight_spec_t > color_array_t ;
2013-10-08 18:41:35 -07:00
color_array_t color_array ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* The parse tree of the buff */
parse_node_tree_t parse_tree ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Color an argument */
void color_argument ( const parse_node_t & node ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* Color a redirection */
void color_redirection ( const parse_node_t & node ) ;
2013-10-08 18:41:35 -07:00
/* Color the arguments of the given node */
void color_arguments ( const parse_node_t & list_node ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* Color the redirections of the given node */
void color_redirections ( const parse_node_t & list_node ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Color all the children of the command with the given type */
2014-02-21 18:01:40 -08:00
void color_children ( const parse_node_t & parent , parse_token_type_t type , highlight_spec_t color ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Colors the source range of a node with a given color */
2014-02-21 18:01:40 -08:00
void color_node ( const parse_node_t & node , highlight_spec_t color ) ;
2014-01-15 01:40:40 -08:00
public :
2013-10-08 18:41:35 -07:00
/* Constructor */
2014-03-26 18:49:09 -07:00
highlighter_t ( const wcstring & str , size_t pos , const env_vars_snapshot_t & ev , const wcstring & wd , bool can_do_io ) : buff ( str ) , cursor_pos ( pos ) , vars ( ev ) , io_ok ( can_do_io ) , working_directory ( wd ) , color_array ( str . size ( ) )
2013-10-08 18:41:35 -07:00
{
/* Parse the tree */
2014-01-12 22:39:12 -08:00
parse_tree_from_string ( buff , parse_flag_continue_after_error | parse_flag_include_comments , & this - > parse_tree , NULL ) ;
2013-10-08 18:41:35 -07:00
}
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Perform highlighting, returning an array of colors */
const color_array_t & highlight ( ) ;
} ;
2014-02-21 18:01:40 -08:00
void highlighter_t : : color_node ( const parse_node_t & node , highlight_spec_t color )
2013-10-08 18:41:35 -07:00
{
// Can only color nodes with valid source ranges
2014-03-28 14:39:47 -07:00
if ( ! node . has_source ( ) | | node . source_length = = 0 )
2013-10-08 18:41:35 -07:00
return ;
// Fill the color array with our color in the corresponding range
size_t source_end = node . source_start + node . source_length ;
assert ( source_end > = node . source_start ) ;
assert ( source_end < = color_array . size ( ) ) ;
std : : fill ( this - > color_array . begin ( ) + node . source_start , this - > color_array . begin ( ) + source_end , color ) ;
}
2013-10-13 16:58:40 -07:00
/* node does not necessarily have type symbol_argument here */
2013-10-08 18:41:35 -07:00
void highlighter_t : : color_argument ( const parse_node_t & node )
{
if ( ! node . has_source ( ) )
return ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
const wcstring arg_str = node . get_source ( this - > buff ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Get an iterator to the colors associated with the argument */
const size_t arg_start = node . source_start ;
const color_array_t : : iterator arg_colors = color_array . begin ( ) + arg_start ;
/* Color this argument without concern for command substitutions */
color_argument_internal ( arg_str , arg_colors ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Now do command substitutions */
size_t cmdsub_cursor = 0 , cmdsub_start = 0 , cmdsub_end = 0 ;
wcstring cmdsub_contents ;
while ( parse_util_locate_cmdsubst_range ( arg_str , & cmdsub_cursor , & cmdsub_contents , & cmdsub_start , & cmdsub_end , true /* accept incomplete */ ) > 0 )
{
/* The cmdsub_start is the open paren. cmdsub_end is either the close paren or the end of the string. cmdsub_contents extends from one past cmdsub_start to cmdsub_end */
assert ( cmdsub_end > cmdsub_start ) ;
assert ( cmdsub_end - cmdsub_start - 1 = = cmdsub_contents . size ( ) ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Found a command substitution. Compute the position of the start and end of the cmdsub contents, within our overall src. */
const size_t arg_subcmd_start = arg_start + cmdsub_start , arg_subcmd_end = arg_start + cmdsub_end ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Highlight the parens. The open paren must exist; the closed paren may not if it was incomplete. */
assert ( cmdsub_start < arg_str . size ( ) ) ;
2014-01-15 01:01:25 -08:00
this - > color_array . at ( arg_subcmd_start ) = highlight_spec_operator ;
2013-10-08 18:41:35 -07:00
if ( arg_subcmd_end < this - > buff . size ( ) )
2014-01-15 01:01:25 -08:00
this - > color_array . at ( arg_subcmd_end ) = highlight_spec_operator ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Compute the cursor's position within the cmdsub. We must be past the open paren (hence >) but can be at the end of the string or closed paren (hence <=) */
size_t cursor_subpos = CURSOR_POSITION_INVALID ;
if ( cursor_pos ! = CURSOR_POSITION_INVALID & & cursor_pos > arg_subcmd_start & & cursor_pos < = arg_subcmd_end )
{
/* The -1 because the cmdsub_contents does not include the open paren */
cursor_subpos = cursor_pos - arg_subcmd_start - 1 ;
}
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Highlight it recursively. */
2014-03-26 18:49:09 -07:00
highlighter_t cmdsub_highlighter ( cmdsub_contents , cursor_subpos , this - > vars , this - > working_directory , this - > io_ok ) ;
2013-10-08 18:41:35 -07:00
const color_array_t & subcolors = cmdsub_highlighter . highlight ( ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Copy out the subcolors back into our array */
assert ( subcolors . size ( ) = = cmdsub_contents . size ( ) ) ;
std : : copy ( subcolors . begin ( ) , subcolors . end ( ) , this - > color_array . begin ( ) + arg_subcmd_start + 1 ) ;
}
}
2013-10-06 16:23:45 -07:00
// Indicates whether the source range of the given node forms a valid path in the given working_directory
static bool node_is_potential_path ( const wcstring & src , const parse_node_t & node , const wcstring & working_directory )
{
if ( ! node . has_source ( ) )
return false ;
/* Get the node source, unescape it, and then pass it to is_potential_path along with the working directory (as a one element list) */
bool result = false ;
wcstring token ( src , node . source_start , node . source_length ) ;
2013-11-24 23:21:00 -08:00
if ( unescape_string_in_place ( & token , UNESCAPE_SPECIAL ) )
2013-10-06 16:23:45 -07:00
{
/* Big hack: is_potential_path expects a tilde, but unescape_string gives us HOME_DIRECTORY. Put it back. */
if ( ! token . empty ( ) & & token . at ( 0 ) = = HOME_DIRECTORY )
token . at ( 0 ) = L ' ~ ' ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
const wcstring_list_t working_directory_list ( 1 , working_directory ) ;
result = is_potential_path ( token , working_directory_list , PATH_EXPAND_TILDE ) ;
}
return result ;
}
2013-08-08 15:06:46 -07:00
// Color all of the arguments of the given command
2013-10-08 18:41:35 -07:00
void highlighter_t : : color_arguments ( const parse_node_t & list_node )
2013-08-08 15:06:46 -07:00
{
2013-10-08 18:41:35 -07:00
/* Hack: determine whether the parent is the cd command, so we can show errors for non-directories */
2013-10-07 03:56:09 -07:00
bool cmd_is_cd = false ;
2014-03-26 18:49:09 -07:00
if ( this - > io_ok )
2013-10-07 03:56:09 -07:00
{
2014-03-26 18:49:09 -07:00
const parse_node_t * parent = this - > parse_tree . get_parent ( list_node , symbol_plain_statement ) ;
if ( parent ! = NULL )
2013-10-07 03:56:09 -07:00
{
2014-03-26 18:49:09 -07:00
wcstring cmd_str ;
if ( plain_statement_get_expanded_command ( this - > buff , this - > parse_tree , * parent , & cmd_str ) )
{
cmd_is_cd = ( cmd_str = = L " cd " ) ;
}
2013-10-07 03:56:09 -07:00
}
}
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Find all the arguments of this list */
const parse_node_tree_t : : parse_node_list_t nodes = this - > parse_tree . find_nodes ( list_node , symbol_argument ) ;
2013-08-11 00:35:00 -07:00
2013-10-13 16:58:40 -07:00
for ( size_t i = 0 ; i < nodes . size ( ) ; i + + )
2013-08-08 15:06:46 -07:00
{
const parse_node_t * child = nodes . at ( i ) ;
assert ( child ! = NULL & & child - > type = = symbol_argument ) ;
2013-10-08 18:41:35 -07:00
this - > color_argument ( * child ) ;
2014-01-15 01:40:40 -08:00
2013-10-07 03:56:09 -07:00
if ( cmd_is_cd )
{
/* Mark this as an error if it's not 'help' and not a valid cd path */
2013-10-08 18:41:35 -07:00
wcstring param = child - > get_source ( this - > buff ) ;
2013-10-07 03:56:09 -07:00
if ( expand_one ( param , EXPAND_SKIP_CMDSUBST ) )
{
bool is_help = string_prefixes_string ( param , L " --help " ) | | string_prefixes_string ( param , L " -h " ) ;
2016-02-17 16:25:19 -08:00
if ( ! is_help & & this - > io_ok & & ! is_potential_cd_path ( param , working_directory , PATH_EXPAND_TILDE ) )
2013-10-07 03:56:09 -07:00
{
2014-01-15 01:01:25 -08:00
this - > color_node ( * child , highlight_spec_error ) ;
2013-10-07 03:56:09 -07:00
}
}
}
2013-08-08 15:06:46 -07:00
}
}
2013-10-13 16:58:40 -07:00
void highlighter_t : : color_redirection ( const parse_node_t & redirection_node )
{
assert ( redirection_node . type = = symbol_redirection ) ;
if ( ! redirection_node . has_source ( ) )
return ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
const parse_node_t * redirection_primitive = this - > parse_tree . get_child ( redirection_node , 0 , parse_token_type_redirection ) ; //like 2>
const parse_node_t * redirection_target = this - > parse_tree . get_child ( redirection_node , 1 , parse_token_type_string ) ; //like &1 or file path
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
if ( redirection_primitive ! = NULL )
{
wcstring target ;
2013-12-23 14:53:56 -08:00
const enum token_type redirect_type = this - > parse_tree . type_for_redirection ( redirection_node , this - > buff , NULL , & target ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* We may get a TOK_NONE redirection type, e.g. if the redirection is invalid */
2014-01-15 01:01:25 -08:00
this - > color_node ( * redirection_primitive , redirect_type = = TOK_NONE ? highlight_spec_error : highlight_spec_redirection ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* Check if the argument contains a command substitution. If so, highlight it as a param even though it's a command redirection, and don't try to do any other validation. */
if ( parse_util_locate_cmdsubst ( target . c_str ( ) , NULL , NULL , true ) ! = 0 )
{
if ( redirection_target ! = NULL )
2014-03-26 18:49:09 -07:00
{
2013-10-13 16:58:40 -07:00
this - > color_argument ( * redirection_target ) ;
2014-03-26 18:49:09 -07:00
}
2013-10-13 16:58:40 -07:00
}
else
{
/* No command substitution, so we can highlight the target file or fd. For example, disallow redirections into a non-existent directory */
bool target_is_valid = true ;
2014-03-26 18:49:09 -07:00
if ( ! this - > io_ok )
{
/* I/O is disallowed, so we don't have much hope of catching anything but gross errors. Assume it's valid. */
target_is_valid = true ;
}
else if ( ! expand_one ( target , EXPAND_SKIP_CMDSUBST ) )
2013-10-13 16:58:40 -07:00
{
/* Could not be expanded */
target_is_valid = false ;
}
else
{
2016-02-02 16:46:57 -08:00
/* Ok, we successfully expanded our target. Now verify that it works with this redirection. We will probably need it as a path (but not in the case of fd redirections). Note that the target is now unescaped. */
2016-02-06 14:39:47 -08:00
const wcstring target_path = path_apply_working_directory ( target , this - > working_directory ) ;
2013-10-13 16:58:40 -07:00
switch ( redirect_type )
{
case TOK_REDIRECT_FD :
{
/* target should be an fd. It must be all digits, and must not overflow. fish_wcstoi returns INT_MAX on overflow; we could instead check errno to disambiguiate this from a real INT_MAX fd, but instead we just disallow that. */
const wchar_t * target_cstr = target . c_str ( ) ;
wchar_t * end = NULL ;
int fd = fish_wcstoi ( target_cstr , & end , 10 ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* The iswdigit check ensures there's no leading whitespace, the *end check ensures the entire string was consumed, and the numeric checks ensure the fd is at least zero and there was no overflow */
target_is_valid = ( iswdigit ( target_cstr [ 0 ] ) & & * end = = L ' \0 ' & & fd > = 0 & & fd < INT_MAX ) ;
}
break ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
case TOK_REDIRECT_IN :
{
/* Input redirections must have a readable non-directory */
struct stat buf = { } ;
target_is_valid = ! waccess ( target_path , R_OK ) & & ! wstat ( target_path , & buf ) & & ! S_ISDIR ( buf . st_mode ) ;
}
break ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
case TOK_REDIRECT_OUT :
case TOK_REDIRECT_APPEND :
case TOK_REDIRECT_NOCLOB :
{
/* Test whether the file exists, and whether it's writable (possibly after creating it). access() returns failure if the file does not exist. */
bool file_exists = false , file_is_writable = false ;
int err = 0 ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
struct stat buf = { } ;
if ( wstat ( target_path , & buf ) < 0 )
{
err = errno ;
}
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
if ( string_suffixes_string ( L " / " , target ) )
{
/* Redirections to things that are directories is definitely not allowed */
file_exists = false ;
file_is_writable = false ;
}
else if ( err = = 0 )
{
/* No err. We can write to it if it's not a directory and we have permission */
file_exists = true ;
file_is_writable = ! S_ISDIR ( buf . st_mode ) & & ! waccess ( target_path , W_OK ) ;
}
else if ( err = = ENOENT )
{
/* File does not exist. Check if its parent directory is writable. */
wcstring parent = wdirname ( target_path ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* Ensure that the parent ends with the path separator. This will ensure that we get an error if the parent directory is not really a directory. */
if ( ! string_suffixes_string ( L " / " , parent ) )
parent . push_back ( L ' / ' ) ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* Now the file is considered writable if the parent directory is writable */
file_exists = false ;
file_is_writable = ( 0 = = waccess ( parent , W_OK ) ) ;
}
else
{
/* Other errors we treat as not writable. This includes things like ENOTDIR. */
file_exists = false ;
file_is_writable = false ;
}
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
/* NOCLOB means that we must not overwrite files that exist */
2014-01-15 01:40:40 -08:00
target_is_valid = file_is_writable & & ! ( file_exists & & redirect_type = = TOK_REDIRECT_NOCLOB ) ;
2013-10-13 16:58:40 -07:00
}
break ;
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
default :
/* We should not get here, since the node was marked as a redirection, but treat it as an error for paranoia */
target_is_valid = false ;
break ;
}
}
2014-01-15 01:40:40 -08:00
2013-10-13 16:58:40 -07:00
if ( redirection_target ! = NULL )
{
2014-01-15 01:01:25 -08:00
this - > color_node ( * redirection_target , target_is_valid ? highlight_spec_redirection : highlight_spec_error ) ;
2013-10-13 16:58:40 -07:00
}
}
}
}
// Color all of the redirections of the given command
void highlighter_t : : color_redirections ( const parse_node_t & list_node )
{
const parse_node_tree_t : : parse_node_list_t nodes = this - > parse_tree . find_nodes ( list_node , symbol_redirection ) ;
for ( size_t i = 0 ; i < nodes . size ( ) ; i + + )
{
this - > color_redirection ( * nodes . at ( i ) ) ;
}
}
2013-10-06 16:23:45 -07:00
/* Color all the children of the command with the given type */
2014-02-21 18:01:40 -08:00
void highlighter_t : : color_children ( const parse_node_t & parent , parse_token_type_t type , highlight_spec_t color )
2013-08-08 15:06:46 -07:00
{
for ( node_offset_t idx = 0 ; idx < parent . child_count ; idx + + )
{
2013-10-08 18:41:35 -07:00
const parse_node_t * child = this - > parse_tree . get_child ( parent , idx ) ;
2013-10-06 16:23:45 -07:00
if ( child ! = NULL & & child - > type = = type )
2013-08-08 15:06:46 -07:00
{
2013-10-08 18:41:35 -07:00
this - > color_node ( * child , color ) ;
2013-08-08 15:06:46 -07:00
}
}
}
2013-10-07 01:04:37 -07:00
/* Determine if a command is valid */
static bool command_is_valid ( const wcstring & cmd , enum parse_statement_decoration_t decoration , const wcstring & working_directory , const env_vars_snapshot_t & vars )
2013-10-06 16:23:45 -07:00
{
/* Determine which types we check, based on the decoration */
bool builtin_ok = true , function_ok = true , abbreviation_ok = true , command_ok = true , implicit_cd_ok = true ;
2014-02-13 10:08:04 -08:00
if ( decoration = = parse_statement_decoration_command | | decoration = = parse_statement_decoration_exec )
2013-10-06 16:23:45 -07:00
{
builtin_ok = false ;
function_ok = false ;
abbreviation_ok = false ;
command_ok = true ;
implicit_cd_ok = false ;
}
else if ( decoration = = parse_statement_decoration_builtin )
{
builtin_ok = true ;
function_ok = false ;
abbreviation_ok = false ;
command_ok = false ;
implicit_cd_ok = false ;
}
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Check them */
bool is_valid = false ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Builtins */
if ( ! is_valid & & builtin_ok )
is_valid = builtin_exists ( cmd ) ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Functions */
if ( ! is_valid & & function_ok )
is_valid = function_exists_no_autoload ( cmd , vars ) ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Abbreviations */
if ( ! is_valid & & abbreviation_ok )
is_valid = expand_abbreviation ( cmd , NULL ) ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Regular commands */
if ( ! is_valid & & command_ok )
is_valid = path_get_path ( cmd , NULL , vars ) ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
/* Implicit cd */
if ( ! is_valid & & implicit_cd_ok )
is_valid = path_can_be_implicit_cd ( cmd , NULL , working_directory . c_str ( ) , vars ) ;
2014-01-15 01:40:40 -08:00
2013-10-07 01:04:37 -07:00
/* Return what we got */
return is_valid ;
2013-10-06 16:23:45 -07:00
}
2013-10-08 18:41:35 -07:00
const highlighter_t : : color_array_t & highlighter_t : : highlight ( )
2013-08-08 15:06:46 -07:00
{
2014-03-26 18:49:09 -07:00
// If we are doing I/O, we must be in a background thread
if ( io_ok )
{
ASSERT_IS_BACKGROUND_THREAD ( ) ;
}
2014-01-15 01:40:40 -08:00
2013-08-08 15:06:46 -07:00
const size_t length = buff . size ( ) ;
2013-10-08 18:41:35 -07:00
assert ( this - > buff . size ( ) = = this - > color_array . size ( ) ) ;
2014-01-15 01:40:40 -08:00
2013-08-08 15:06:46 -07:00
if ( length = = 0 )
2013-10-08 18:41:35 -07:00
return color_array ;
2013-08-08 15:06:46 -07:00
2013-10-07 01:04:37 -07:00
/* Start out at zero */
2013-10-08 18:41:35 -07:00
std : : fill ( this - > color_array . begin ( ) , this - > color_array . end ( ) , 0 ) ;
2014-01-15 01:40:40 -08:00
2013-10-06 16:23:45 -07:00
#if 0
const wcstring dump = parse_dump_tree ( parse_tree , buff ) ;
fprintf ( stderr , " %ls \n " , dump . c_str ( ) ) ;
# endif
2013-08-08 15:06:46 -07:00
/* Walk the node tree */
for ( parse_node_tree_t : : const_iterator iter = parse_tree . begin ( ) ; iter ! = parse_tree . end ( ) ; + + iter )
{
const parse_node_t & node = * iter ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
switch ( node . type )
{
2014-03-31 10:01:39 -07:00
// Color direct string descendants, e.g. 'for' and 'in'.
2013-08-08 15:06:46 -07:00
case symbol_while_header :
case symbol_begin_header :
case symbol_function_header :
case symbol_if_clause :
case symbol_else_clause :
case symbol_case_item :
case symbol_boolean_statement :
case symbol_decorated_statement :
2013-08-11 00:35:00 -07:00
case symbol_if_statement :
{
2014-01-15 01:01:25 -08:00
this - > color_children ( node , parse_token_type_string , highlight_spec_command ) ;
2014-02-21 18:01:40 -08:00
}
break ;
2014-03-31 10:01:39 -07:00
2014-03-28 17:09:08 -07:00
case symbol_switch_statement :
{
const parse_node_t * literal_switch = this - > parse_tree . get_child ( node , 0 , parse_token_type_string ) ;
const parse_node_t * switch_arg = this - > parse_tree . get_child ( node , 1 , symbol_argument ) ;
this - > color_node ( * literal_switch , highlight_spec_command ) ;
this - > color_node ( * switch_arg , highlight_spec_param ) ;
}
break ;
2014-03-31 10:01:39 -07:00
2014-02-21 18:01:40 -08:00
case symbol_for_header :
{
// Color the 'for' and 'in' as commands
const parse_node_t * literal_for_node = this - > parse_tree . get_child ( node , 0 , parse_token_type_string ) ;
const parse_node_t * literal_in_node = this - > parse_tree . get_child ( node , 2 , parse_token_type_string ) ;
this - > color_node ( * literal_for_node , highlight_spec_command ) ;
this - > color_node ( * literal_in_node , highlight_spec_command ) ;
2014-03-31 10:01:39 -07:00
2014-02-21 18:20:51 -08:00
// Color the variable name as a parameter
2014-02-21 18:01:40 -08:00
const parse_node_t * var_name_node = this - > parse_tree . get_child ( node , 1 , parse_token_type_string ) ;
2014-02-21 18:20:51 -08:00
this - > color_argument ( * var_name_node ) ;
2013-08-11 00:35:00 -07:00
}
break ;
2014-08-20 18:40:14 -07:00
case parse_token_type_pipe :
2013-08-08 15:06:46 -07:00
case parse_token_type_background :
case parse_token_type_end :
2014-03-28 14:39:47 -07:00
case symbol_optional_background :
2013-10-07 01:04:37 -07:00
{
2014-01-15 01:01:25 -08:00
this - > color_node ( node , highlight_spec_statement_terminator ) ;
2013-10-07 01:04:37 -07:00
}
break ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
case symbol_plain_statement :
{
2014-03-26 18:49:09 -07:00
/* Get the decoration from the parent */
2013-10-09 02:03:50 -07:00
enum parse_statement_decoration_t decoration = parse_tree . decoration_for_plain_statement ( node ) ;
2013-08-11 00:35:00 -07:00
2013-10-07 01:04:37 -07:00
/* Color the command */
const parse_node_t * cmd_node = parse_tree . get_child ( node , 0 , parse_token_type_string ) ;
if ( cmd_node ! = NULL & & cmd_node - > has_source ( ) )
{
bool is_valid_cmd = false ;
2014-03-26 18:49:09 -07:00
if ( ! this - > io_ok )
2013-10-07 01:04:37 -07:00
{
2014-03-26 18:49:09 -07:00
/* We cannot check if the command is invalid, so just assume it's valid */
is_valid_cmd = true ;
}
else
2013-10-07 01:04:37 -07:00
{
2014-03-26 18:49:09 -07:00
/* Check to see if the command is valid */
wcstring cmd ( buff , cmd_node - > source_start , cmd_node - > source_length ) ;
/* Try expanding it. If we cannot, it's an error. */
bool expanded = expand_one ( cmd , EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES | EXPAND_SKIP_JOBS ) ;
if ( expanded & & ! has_expand_reserved ( cmd ) )
{
is_valid_cmd = command_is_valid ( cmd , decoration , working_directory , vars ) ;
}
2013-10-07 01:04:37 -07:00
}
2014-01-15 01:01:25 -08:00
this - > color_node ( * cmd_node , is_valid_cmd ? highlight_spec_command : highlight_spec_error ) ;
2013-10-07 01:04:37 -07:00
}
2013-08-08 15:06:46 -07:00
}
break ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
case symbol_arguments_or_redirections_list :
case symbol_argument_list :
2013-10-07 03:56:09 -07:00
{
/* Only work on root lists, so that we don't re-color child lists */
if ( parse_tree . argument_list_is_root ( node ) )
{
2013-10-08 18:41:35 -07:00
this - > color_arguments ( node ) ;
2013-10-13 16:58:40 -07:00
this - > color_redirections ( node ) ;
2013-10-07 03:56:09 -07:00
}
}
break ;
2014-03-31 10:01:39 -07:00
2014-02-21 18:01:40 -08:00
case symbol_end_command :
this - > color_node ( node , highlight_spec_command ) ;
break ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
case parse_special_type_parse_error :
case parse_special_type_tokenizer_error :
2014-01-15 01:01:25 -08:00
this - > color_node ( node , highlight_spec_error ) ;
2013-08-08 15:06:46 -07:00
break ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
case parse_special_type_comment :
2014-01-15 01:01:25 -08:00
this - > color_node ( node , highlight_spec_comment ) ;
2013-08-08 15:06:46 -07:00
break ;
2013-08-11 00:35:00 -07:00
2013-08-08 15:06:46 -07:00
default :
break ;
}
}
2014-01-15 01:40:40 -08:00
2014-03-26 18:49:09 -07:00
if ( this - > io_ok & & this - > cursor_pos < = this - > buff . size ( ) )
2013-10-06 16:23:45 -07:00
{
/* If the cursor is over an argument, and that argument is a valid path, underline it */
for ( parse_node_tree_t : : const_iterator iter = parse_tree . begin ( ) ; iter ! = parse_tree . end ( ) ; + + iter )
{
const parse_node_t & node = * iter ;
2013-10-07 01:04:37 -07:00
/* Must be an argument with source */
if ( node . type ! = symbol_argument | | ! node . has_source ( ) )
continue ;
2014-01-15 01:40:40 -08:00
2013-10-07 01:04:37 -07:00
/* See if this node contains the cursor. We check <= source_length so that, when backspacing (and the cursor is just beyond the last token), we may still underline it */
2013-10-08 18:41:35 -07:00
if ( this - > cursor_pos > = node . source_start & & this - > cursor_pos - node . source_start < = node . source_length )
2013-10-06 16:23:45 -07:00
{
/* See if this is a valid path */
if ( node_is_potential_path ( buff , node , working_directory ) )
{
/* It is, underline it. */
for ( size_t i = node . source_start ; i < node . source_start + node . source_length ; i + + )
{
2014-01-15 01:01:25 -08:00
/* Don't color highlight_spec_error because it looks dorky. For example, trying to cd into a non-directory would show an underline and also red. */
if ( highlight_get_primary ( this - > color_array . at ( i ) ) ! = highlight_spec_error )
2013-10-06 16:23:45 -07:00
{
2014-01-15 01:01:25 -08:00
this - > color_array . at ( i ) | = highlight_modifier_valid_path ;
2013-10-06 16:23:45 -07:00
}
}
}
}
}
}
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
return color_array ;
}
2014-02-09 21:21:59 -08:00
void highlight_shell ( const wcstring & buff , std : : vector < highlight_spec_t > & color , size_t pos , wcstring_list_t * error , const env_vars_snapshot_t & vars )
2013-10-08 18:41:35 -07:00
{
/* Do something sucky and get the current working directory on this background thread. This should really be passed in. */
const wcstring working_directory = env_get_pwd_slash ( ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Highlight it! */
2014-03-26 18:49:09 -07:00
highlighter_t highlighter ( buff , pos , vars , working_directory , true /* can do IO */ ) ;
color = highlighter . highlight ( ) ;
}
void highlight_shell_no_io ( const wcstring & buff , std : : vector < highlight_spec_t > & color , size_t pos , wcstring_list_t * error , const env_vars_snapshot_t & vars )
2013-10-08 18:41:35 -07:00
{
/* Do something sucky and get the current working directory on this background thread. This should really be passed in. */
const wcstring working_directory = env_get_pwd_slash ( ) ;
2014-01-15 01:40:40 -08:00
2013-10-08 18:41:35 -07:00
/* Highlight it! */
2014-03-26 18:49:09 -07:00
highlighter_t highlighter ( buff , pos , vars , working_directory , false /* no IO allowed */ ) ;
2013-10-08 18:41:35 -07:00
color = highlighter . highlight ( ) ;
2013-08-08 15:06:46 -07:00
}
2011-12-26 19:18:46 -08:00
2005-09-20 23:26:39 +10:00
/**
Perform quote and parenthesis highlighting on the specified string .
*/
2014-01-15 01:01:25 -08:00
static void highlight_universal_internal ( const wcstring & buffstr , std : : vector < highlight_spec_t > & color , size_t pos )
2012-11-18 11:23:22 +01:00
{
2012-02-21 17:55:56 -08:00
assert ( buffstr . size ( ) = = color . size ( ) ) ;
2012-11-18 16:30:30 -08:00
if ( pos < buffstr . size ( ) )
2012-11-18 11:23:22 +01:00
{
2012-11-18 16:30:30 -08:00
/*
Highlight matching quotes
*/
if ( ( buffstr . at ( pos ) = = L ' \' ' ) | | ( buffstr . at ( pos ) = = L ' \" ' ) )
{
2012-12-20 17:37:09 -08:00
std : : vector < size_t > lst ;
2012-11-18 16:30:30 -08:00
int level = 0 ;
wchar_t prev_q = 0 ;
2012-11-18 11:23:22 +01:00
2012-02-21 17:55:56 -08:00
const wchar_t * const buff = buffstr . c_str ( ) ;
2012-11-18 16:30:30 -08:00
const wchar_t * str = buff ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
int match_found = 0 ;
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
while ( * str )
2012-11-18 11:23:22 +01:00
{
2012-11-18 16:30:30 -08:00
switch ( * str )
2012-11-18 11:23:22 +01:00
{
2012-11-19 00:31:03 -08:00
case L ' \\ ' :
str + + ;
break ;
case L ' \" ' :
case L ' \' ' :
if ( level = = 0 )
2012-11-18 16:30:30 -08:00
{
level + + ;
lst . push_back ( str - buff ) ;
prev_q = * str ;
}
2012-11-19 00:31:03 -08:00
else
{
if ( prev_q = = * str )
{
2012-12-20 17:37:09 -08:00
size_t pos1 , pos2 ;
2012-11-18 16:30:30 -08:00
2012-11-19 00:31:03 -08:00
level - - ;
pos1 = lst . back ( ) ;
pos2 = str - buff ;
if ( pos1 = = pos | | pos2 = = pos )
{
2014-01-15 01:01:25 -08:00
color . at ( pos1 ) | = highlight_make_background ( highlight_spec_match ) ;
color . at ( pos2 ) | = highlight_make_background ( highlight_spec_match ) ;
2012-11-19 00:31:03 -08:00
match_found = 1 ;
}
prev_q = * str = = L ' \" ' ? L ' \' ' : L ' \" ' ;
}
else
{
level + + ;
lst . push_back ( str - buff ) ;
prev_q = * str ;
}
}
break ;
2012-11-18 11:23:22 +01:00
}
2012-11-18 16:30:30 -08:00
if ( ( * str = = L ' \0 ' ) )
break ;
str + + ;
2012-11-18 11:23:22 +01:00
}
2012-11-18 16:30:30 -08:00
if ( ! match_found )
2014-01-15 01:01:25 -08:00
color . at ( pos ) = highlight_make_background ( highlight_spec_error ) ;
2012-11-18 11:23:22 +01:00
}
2012-11-18 16:30:30 -08:00
/*
Highlight matching parenthesis
*/
2012-02-21 17:55:56 -08:00
const wchar_t c = buffstr . at ( pos ) ;
2012-11-18 16:30:30 -08:00
if ( wcschr ( L " ()[]{} " , c ) )
2012-11-18 11:23:22 +01:00
{
2012-11-18 16:30:30 -08:00
int step = wcschr ( L " ({[ " , c ) ? 1 : - 1 ;
wchar_t dec_char = * ( wcschr ( L " ()[]{} " , c ) + step ) ;
wchar_t inc_char = c ;
int level = 0 ;
int match_found = 0 ;
for ( long i = pos ; i > = 0 & & ( size_t ) i < buffstr . size ( ) ; i + = step )
{
const wchar_t test_char = buffstr . at ( i ) ;
if ( test_char = = inc_char )
level + + ;
if ( test_char = = dec_char )
level - - ;
if ( level = = 0 )
{
long pos2 = i ;
2014-01-15 01:01:25 -08:00
color . at ( pos ) | = highlight_spec_match < < 16 ;
color . at ( pos2 ) | = highlight_spec_match < < 16 ;
2012-11-18 16:30:30 -08:00
match_found = 1 ;
break ;
}
}
2012-11-18 11:23:22 +01:00
2012-11-18 16:30:30 -08:00
if ( ! match_found )
2014-01-15 01:01:25 -08:00
color [ pos ] = highlight_make_background ( highlight_spec_error ) ;
2012-11-18 16:30:30 -08:00
}
2012-11-18 11:23:22 +01:00
}
2005-09-20 23:26:39 +10:00
}
2014-01-15 01:01:25 -08:00
void highlight_universal ( const wcstring & buff , std : : vector < highlight_spec_t > & color , size_t pos , wcstring_list_t * error , const env_vars_snapshot_t & vars )
2005-09-20 23:26:39 +10:00
{
2012-02-21 17:55:56 -08:00
assert ( buff . size ( ) = = color . size ( ) ) ;
2012-11-18 11:23:22 +01:00
std : : fill ( color . begin ( ) , color . end ( ) , 0 ) ;
2012-11-18 16:30:30 -08:00
highlight_universal_internal ( buff , color , pos ) ;
2005-09-20 23:26:39 +10:00
}