Пример #1
0
// If the given path looks like it's relative to the working directory, then prepend that working
// directory. This operates on unescaped paths only (so a ~ means a literal ~).
wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory) {
    if (path.empty() || working_directory.empty()) return path;

    // We're going to make sure that if we want to prepend the wd, that the string has no leading
    // "/".
    bool prepend_wd = path.at(0) != L'/' && path.at(0) != HOME_DIRECTORY;
    if (!prepend_wd) {
        // No need to prepend the wd, so just return the path we were given.
        return path;
    }

    // Remove up to one "./".
    wcstring path_component = path;
    if (string_prefixes_string(L"./", path_component)) {
        path_component.erase(0, 2);
    }

    // Removing leading /s.
    while (string_prefixes_string(L"/", path_component)) {
        path_component.erase(0, 1);
    }

    // Construct and return a new path.
    wcstring new_path = working_directory;
    append_path_component(new_path, path_component);
    return new_path;
}
Пример #2
0
bool path_can_be_implicit_cd(const wcstring &path, wcstring *out_path, const wchar_t *wd,
                             const env_vars_snapshot_t &vars) {
    wcstring exp_path = path;
    expand_tilde(exp_path);

    bool result = false;
    if (string_prefixes_string(L"/", exp_path) || string_prefixes_string(L"./", exp_path) ||
        string_prefixes_string(L"../", exp_path) || string_suffixes_string(L"/", exp_path) ||
        exp_path == L"..") {
        // These paths can be implicit cd, so see if you cd to the path. Note that a single period
        // cannot (that's used for sourcing files anyways).
        result = path_get_cdpath(exp_path, out_path, wd, vars);
    }
    return result;
}
Пример #3
0
/**
   See if the process described by \c proc matches the commandline \c
   cmd
*/
static bool match_pid(const wcstring &cmd,
                      const wchar_t *proc,
                      int flags,
                      size_t *offset)
{
    /* Test for a direct match. If the proc string is empty (e.g. the user tries to complete against %), then return an offset pointing at the base command. That ensures that you don't see a bunch of dumb paths when completing against all processes. */
    if (proc[0] != L'\0' && wcsncmp(cmd.c_str(), proc, wcslen(proc)) == 0)
    {
        if (offset)
            *offset = 0;
        return true;
    }

    /* Get the command to match against. We're only interested in the last path component. */
    const wcstring base_cmd = wbasename(cmd);

    bool result = string_prefixes_string(proc, base_cmd);
    if (result)
    {
        /* It's a match. Return the offset within the full command. */
        if (offset)
            *offset = cmd.size() - base_cmd.size();
    }
    return result;
}
Пример #4
0
/* 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. */
static bool is_potential_cd_path(const wcstring &path, const wcstring &working_directory, path_flags_t flags, wcstring *out_path) {
    wcstring_list_t directories;
    
    if (string_prefixes_string(L"./", path)) {
        /* Ignore the CDPATH in this case; just use the working directory */
        directories.push_back(working_directory);
    } else {
        /* Get the CDPATH */
        env_var_t cdpath = env_get_string(L"CDPATH");
        if (cdpath.missing_or_empty())
            cdpath = L".";
        
        /* 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 "." */
            directories.push_back(apply_working_directory(next_path, working_directory));
        }
    }
    
    /* Call is_potential_path with all of these directories */
    bool result = is_potential_path(path, directories, flags | PATH_REQUIRE_DIR, out_path);
#if 0
    if (out_path) {
        printf("%ls -> %ls\n", path.c_str(), out_path->c_str());
    }
#endif
    return result;
}
Пример #5
0
void features_t::set_from_string(const wcstring &str) {
    wcstring_list_t entries = split_string(str, L',');
    const wchar_t *whitespace = L"\t\n\v\f\r ";
    for (wcstring entry : entries) {
        if (entry.empty()) continue;

        // Trim leading and trailing whitespace
        entry.erase(0, entry.find_first_not_of(whitespace));
        entry.erase(entry.find_last_not_of(whitespace) + 1);

        const wchar_t *name = entry.c_str();
        bool value = true;
        // A "no-" prefix inverts the sense.
        if (string_prefixes_string(L"no-", name)) {
            value = false;
            name += 3;  // wcslen(L"no-")
        }
        // Look for a feature with this name. If we don't find it, assume it's a group name and set
        // all features whose group contain it. Do nothing even if the string is unrecognized; this
        // is to allow uniform invocations of fish (e.g. disable a feature that is only present in
        // future versions).
        // The special name 'all' may be used for those who like to live on the edge.
        if (const metadata_t *md = metadata_for(name)) {
            this->set(md->flag, value);
        } else {
            for (const metadata_t &md : metadata) {
                if (wcsstr(md.groups, name) || !wcscmp(name, L"all")) {
                    this->set(md.flag, value);
                }
            }
        }
    }
}
Пример #6
0
/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */
wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory)
{
    if (path.empty() || working_directory.empty())
        return path;
    
    /* We're going to make sure that if we want to prepend the wd, that the string has no leading / */
    bool prepend_wd;
    switch (path.at(0))
    {
        case L'/':
        case HOME_DIRECTORY:
            prepend_wd = false;
            break;
        default:
            prepend_wd = true;
            break;
    }
    
    if (! prepend_wd)
    {
        /* No need to prepend the wd, so just return the path we were given */
        return path;
    }
    else
    {
        /* Remove up to one ./ */
        wcstring path_component = path;
        if (string_prefixes_string(L"./", path_component))
        {
            path_component.erase(0, 2);
        }
        
        /* Removing leading /s */
        while (string_prefixes_string(L"/", path_component))
        {
            path_component.erase(0, 1);
        }
        
        /* Construct and return a new path */
        wcstring new_path = working_directory;
        append_path_component(new_path, path_component);
        return new_path;
    }
}
Пример #7
0
/** React to modifying hte given variable */
static void react_to_variable_change(const wcstring &key) {
    if(var_is_locale(key)){
        handle_locale();
    } else if (key == L"fish_term256") {
        update_fish_term256();
        reader_react_to_color_change();
    } else if (string_prefixes_string(L"fish_color_", key)) {
        reader_react_to_color_change();
    }
}
Пример #8
0
// Validate the given path `list`. If there are any entries referring to invalid directories which
// contain a colon, then complain. Return true if any path element was valid, false if not.
static bool validate_path_warning_on_colons(const wchar_t *cmd,
                                            const wchar_t *key,  //!OCLINT(npath complexity)
                                            const wcstring_list_t &list, io_streams_t &streams,
                                            const environment_t &vars) {
    // Always allow setting an empty value.
    if (list.empty()) return true;

    bool any_success = false;

    // Don't bother validating (or complaining about) values that are already present. When
    // determining already-present values, use ENV_DEFAULT instead of the passed-in scope because
    // in:
    //
    //   set -l PATH stuff $PATH
    //
    // where we are temporarily shadowing a variable, we want to compare against the shadowed value,
    // not the (missing) local value. Also don't bother to complain about relative paths, which
    // don't start with /.
    wcstring_list_t existing_values;
    const auto existing_variable = vars.get(key, ENV_DEFAULT);
    if (!existing_variable.missing_or_empty()) existing_variable->to_list(existing_values);

    for (const wcstring &dir : list) {
        if (!string_prefixes_string(L"/", dir) || contains(existing_values, dir)) {
            any_success = true;
            continue;
        }

        const wchar_t *colon = std::wcschr(dir.c_str(), L':');
        bool looks_like_colon_sep = colon && colon[1];
        if (!looks_like_colon_sep && any_success) {
            // Once we have one valid entry, skip the remaining ones unless we might warn.
            continue;
        }
        struct stat buff;
        bool valid = true;
        if (wstat(dir, &buff) == -1) {
            valid = false;
        } else if (!S_ISDIR(buff.st_mode)) {
            errno = ENOTDIR;
            valid = false;
        } else if (waccess(dir, X_OK) == -1) {
            valid = false;
        }
        if (valid) {
            any_success = true;
        } else if (looks_like_colon_sep) {
            streams.err.append_format(BUILTIN_SET_PATH_ERROR, cmd, key, dir.c_str(),
                                      std::strerror(errno));
            streams.err.append_format(BUILTIN_SET_PATH_HINT, cmd, key, key,
                                      std::wcschr(dir.c_str(), L':') + 1);
        }
    }
    return any_success;
}
Пример #9
0
bool history_item_t::matches_search(const wcstring &term, enum history_search_type_t type) const {
    switch (type) {
    
        case HISTORY_SEARCH_TYPE_CONTAINS:
        /* We consider equal strings to NOT match a contains search (so that you don't have to see history equal to what you typed). The length check ensures that. */
            return contents.size() > term.size() && contents.find(term) != wcstring::npos;
            
        case HISTORY_SEARCH_TYPE_PREFIX:
            /* We consider equal strings to match a prefix search, so that autosuggest will allow suggesting what you've typed */
            return string_prefixes_string(term, contents);
            
        default:
            sanity_lose();
            return false;
    }
}
Пример #10
0
/// React to modifying the given variable.
static void react_to_variable_change(const wcstring &key) {
    if (var_is_locale(key)) {
        handle_locale(key.c_str());
    } else if (var_is_curses(key)) {
        handle_curses(key.c_str());
    } else if (var_is_timezone(key)) {
        handle_timezone(key.c_str());
    } else if (key == L"fish_term256" || key == L"fish_term24bit") {
        update_fish_color_support();
        reader_react_to_color_change();
    } else if (string_prefixes_string(L"fish_color_", key)) {
        reader_react_to_color_change();
    } else if (key == L"fish_escape_delay_ms") {
        update_wait_on_escape_ms();
    }
}
Пример #11
0
int wildcard_expand_string(const wcstring &wc, const wcstring &working_directory,
                           expand_flags_t flags, std::vector<completion_t> *output) {
    assert(output != NULL);
    // Fuzzy matching only if we're doing completions.
    assert(flags.get(expand_flag::for_completions) || !flags.get(expand_flag::fuzzy_match));

    // expand_flag::special_for_cd requires expand_flag::directories_only and
    // expand_flag::for_completions and expand_flag::no_descriptions.
    assert(!(flags.get(expand_flag::special_for_cd)) ||
           ((flags.get(expand_flag::directories_only)) &&
            (flags.get(expand_flag::for_completions)) &&
            (flags.get(expand_flag::no_descriptions))));

    // Hackish fix for issue #1631. We are about to call c_str(), which will produce a string
    // truncated at any embedded nulls. We could fix this by passing around the size, etc. However
    // embedded nulls are never allowed in a filename, so we just check for them and return 0 (no
    // matches) if there is an embedded null.
    if (wc.find(L'\0') != wcstring::npos) {
        return 0;
    }

    // Compute the prefix and base dir. The prefix is what we prepend for filesystem operations
    // (i.e. the working directory), the base_dir is the part of the wildcard consumed thus far,
    // which we also have to append. The difference is that the base_dir is returned as part of the
    // expansion, and the prefix is not.
    //
    // Check for a leading slash. If we find one, we have an absolute path: the prefix is empty, the
    // base dir is /, and the wildcard is the remainder. If we don't find one, the prefix is the
    // working directory, the base dir is empty.
    wcstring prefix = L"", base_dir = L"", effective_wc;
    if (string_prefixes_string(L"/", wc)) {
        base_dir = L"/";
        effective_wc = wc.substr(1);
    } else {
        prefix = working_directory;
        effective_wc = wc;
    }

    wildcard_expander_t expander(prefix, flags, output);
    expander.expand(base_dir, effective_wc.c_str(), base_dir);
    return expander.status_code();
}
Пример #12
0
/// Does this look like the escape sequence for setting a screen name.
static bool is_screen_name_escape_seq(const wchar_t *code, size_t *resulting_length) {
    bool found = false;
    if (code[1] == L'k') {
        const env_var_t term_name = env_get_string(L"TERM");
        if (!term_name.missing() && string_prefixes_string(L"screen", term_name)) {
            const wchar_t *const screen_name_end_sentinel = L"\x1b\\";
            const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel);
            if (screen_name_end == NULL) {
                // Consider just <esc>k to be the code.
                *resulting_length = 2;
            } else {
                const wchar_t *escape_sequence_end =
                    screen_name_end + wcslen(screen_name_end_sentinel);
                *resulting_length = escape_sequence_end - code;
            }
            found = true;
        }
    }
    return found;
}
Пример #13
0
// This function does I/O
static void tokenize( const wchar_t * const buff, std::vector<int> &color, const int pos, wcstring_list_t *error, const wcstring &working_directory, const env_vars_snapshot_t &vars) {
    ASSERT_IS_BACKGROUND_THREAD();
    
	wcstring cmd;    
	int had_cmd=0;
	wcstring last_cmd;
	int len;

	int accept_switches = 1;
	
	int use_function = 1;
	int use_command = 1;
	int use_builtin = 1;
	
	CHECK( buff, );

	len = wcslen(buff);

	if( !len )
		return;

    std::fill(color.begin(), color.end(), -1); 

    tokenizer tok;
	for( tok_init( &tok, buff, TOK_SHOW_COMMENTS | TOK_SQUASH_ERRORS );
		tok_has_next( &tok );
		tok_next( &tok ) )
	{	
		int last_type = tok_last_type( &tok );
		
		switch( last_type )
		{
			case TOK_STRING:
			{
				if( had_cmd )
				{
					
					/*Parameter */
					wchar_t *param = tok_last( &tok );
					if( param[0] == L'-' )
					{
						if (wcscmp( param, L"--" ) == 0 )
						{
							accept_switches = 0;
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
						}
						else if( accept_switches )
						{
							if( complete_is_valid_option( last_cmd.c_str(), param, error, false /* no autoload */ ) )
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
							else
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
						}
						else
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
						}
					}
					else
					{
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
					}					

					if( cmd == L"cd" )
					{
                        wcstring dir = tok_last( &tok );
                        if (expand_one(dir, EXPAND_SKIP_CMDSUBST))
						{
							int is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h");
							if( !is_help && ! is_potential_cd_path(dir, working_directory, PATH_EXPAND_TILDE, NULL))
							{
                                color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;							
							}
						}
					}
					
                    /* Highlight the parameter. highlight_param wants to write one more color than we have characters (hysterical raisins) so allocate one more in the vector. But don't copy it back. */
                    const wcstring param_str = param;
                    int tok_pos = tok_get_pos(&tok);
                    
                    std::vector<int>::const_iterator where = color.begin() + tok_pos;
                    std::vector<int> subcolors(where, where + param_str.size());
                    subcolors.push_back(-1);                
                    highlight_param(param_str, subcolors, pos-tok_pos, error);
                                        
                    /* Copy the subcolors back into our colors array */
                    std::copy(subcolors.begin(), subcolors.begin() + param_str.size(), color.begin() + tok_pos);
				}
				else
				{ 
					/*
					 Command. First check that the command actually exists.
					 */
                    cmd = tok_last( &tok );
                    bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
					if (! expanded || has_expand_reserved(cmd.c_str()))
					{
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
					}
					else
					{
						bool is_cmd = false;
						int is_subcommand = 0;
						int mark = tok_get_pos( &tok );
						color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMAND;
						
						if( parser_keywords_is_subcommand( cmd ) )
						{
							
							int sw;
							
							if( cmd == L"builtin")
							{
								use_function = 0;
								use_command  = 0;
								use_builtin  = 1;
							}
							else if( cmd == L"command")
							{
								use_command  = 1;
								use_function = 0;
								use_builtin  = 0;
							}
							
							tok_next( &tok );
							
							sw = parser_keywords_is_switch( tok_last( &tok ) );
							
							if( !parser_keywords_is_block( cmd ) &&
							   sw == ARG_SWITCH )
							{
								/* 
								 The 'builtin' and 'command' builtins
								 are normally followed by another
								 command, but if they are invoked
								 with a switch, they aren't.
								 
								 */
								use_command  = 1;
								use_function = 1;
								use_builtin  = 2;
							}
							else
							{
								if( sw == ARG_SKIP )
								{
									color.at(tok_get_pos( &tok )) = HIGHLIGHT_PARAM;
									mark = tok_get_pos( &tok );
								}
								
								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.
							 */
							
							/*
							 First check if it is a builtin or
							 function, since we don't have to stat
							 any files for that
							 */
							if (! is_cmd && use_builtin )
								is_cmd = builtin_exists( cmd );
							
							if (! is_cmd && use_function )
								is_cmd = function_exists_no_autoload( cmd, vars );
							
							/*
							 Moving on to expensive tests
							 */
							
							/*
							 Check if this is a regular command
							 */
							if (! is_cmd && use_command )
                            {
								is_cmd = path_get_path( cmd, NULL, vars );
                            }
							
                            /* Maybe it is a path for a implicit cd command. */
                            if (! is_cmd)
                            {
                                if (use_builtin || (use_function && function_exists_no_autoload( L"cd", vars)))
                                    is_cmd = path_can_be_implicit_cd(cmd, NULL, working_directory.c_str(), vars);
                            }
                            
							if( is_cmd )
							{								
								color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMAND;
							}
							else
							{
								if( error ) {
                                    error->push_back(format_string(L"Unknown command \'%ls\'", cmd.c_str()));
                                }
								color.at(tok_get_pos( &tok )) = (HIGHLIGHT_ERROR);
							}
							had_cmd = 1;
						}
						
						if( had_cmd )
						{
							last_cmd = tok_last( &tok );
						}
					}
					
				}
				break;
			}
				
			case TOK_REDIRECT_NOCLOB:
			case TOK_REDIRECT_OUT:
			case TOK_REDIRECT_IN:
			case TOK_REDIRECT_APPEND:
			case TOK_REDIRECT_FD:
			{
				if( !had_cmd )
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
					if( error )
                        error->push_back(L"Redirection without a command");
					break;
				}
				
                wcstring target_str;
				const wchar_t *target=NULL;
				
				color.at(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_str = tok_last( &tok );
                        if (expand_one(target_str, EXPAND_SKIP_CMDSUBST)) {
                            target = target_str.c_str();
                        }
						/*
						 Redirect filename may contain a cmdsubst. 
						 If so, it will be ignored/not flagged.
						 */
					}
						break;
					default:
					{
                        size_t pos = tok_get_pos(&tok);
                        if (pos < color.size()) {
                            color.at(pos) = HIGHLIGHT_ERROR;
                        }
						if( error )
                            error->push_back(L"Invalid redirection");
					}
						
				}
				
				if( target != 0 )
				{
                    wcstring dir = target;
                    size_t slash_idx = dir.find_last_of(L'/');
					struct stat buff;
					/* 
					 If file is in directory other than '.', check
					 that the directory exists.
					 */
					if( slash_idx != wcstring::npos )
					{
						dir.resize(slash_idx);
						if( wstat( dir, &buff ) == -1 )
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"Directory \'%ls\' does not exist", dir.c_str()));
							
						}
					}
					
					/*
					 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.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"File \'%ls\' does not exist", target));
						}
					}
					if( last_type == TOK_REDIRECT_NOCLOB )
					{
						if( wstat( target, &buff ) != -1 )
						{
							color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
							if( error )
                                error->push_back(format_string(L"File \'%ls\' exists", target));
						}
					}
				}
				break;
			}
				
			case TOK_PIPE:
			case TOK_BACKGROUND:
			{
				if( had_cmd )
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_END;
					had_cmd = 0;
					use_command  = 1;
					use_function = 1;
					use_builtin  = 1;
					accept_switches = 1;
				}
				else
				{
					color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;					
					if( error )
                        error->push_back(L"No job to put in background" );
				}
				
				break;
			}
				
			case TOK_END:
			{
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_END;
				had_cmd = 0;
				use_command  = 1;
				use_function = 1;
				use_builtin  = 1;
				accept_switches = 1;
				break;
			}
				
			case TOK_COMMENT:
			{
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_COMMENT;
				break;
			}
				
			case TOK_ERROR:
			default:
			{
				/*
				 If the tokenizer reports an error, highlight it as such.
				 */
				if( error )
                    error->push_back(tok_last( &tok));
				color.at(tok_get_pos( &tok )) = HIGHLIGHT_ERROR;
				break;				
			}			
		}
	}
    tok_destroy( &tok );
}
Пример #14
0
/* Returns the number of characters in the escape code starting at 'code' (which should initially contain \x1b) */
size_t escape_code_length(const wchar_t *code)
{
    assert(code != NULL);

    /* The only escape codes we recognize start with \x1b */
    if (code[0] != L'\x1b')
        return 0;

    size_t resulting_length = 0;
    bool found = false;

    if (cur_term != NULL)
    {
        /*
         Detect these terminfo color escapes with parameter
         value 0..7, all of which don't move the cursor
         */
        char * const esc[] =
        {
            set_a_foreground,
            set_a_background,
            set_foreground,
            set_background,
        };

        for (size_t p=0; p < sizeof esc / sizeof *esc && !found; p++)
        {
            if (!esc[p])
                continue;

            for (size_t k=0; k<8; k++)
            {
                size_t len = try_sequence(tparm(esc[p],k), code);
                if (len)
                {
                    resulting_length = len;
                    found = true;
                    break;
                }
            }
        }
    }

    if (cur_term != NULL)
    {
        /*
         Detect these semi-common terminfo escapes without any
         parameter values, all of which don't move the cursor
         */
        char * const esc2[] =
        {
            enter_bold_mode,
            exit_attribute_mode,
            enter_underline_mode,
            exit_underline_mode,
            enter_standout_mode,
            exit_standout_mode,
            flash_screen,
            enter_subscript_mode,
            exit_subscript_mode,
            enter_superscript_mode,
            exit_superscript_mode,
            enter_blink_mode,
            enter_italics_mode,
            exit_italics_mode,
            enter_reverse_mode,
            enter_shadow_mode,
            exit_shadow_mode,
            enter_standout_mode,
            exit_standout_mode,
            enter_secure_mode
        };



        for (size_t p=0; p < sizeof esc2 / sizeof *esc2 && !found; p++)
        {
            if (!esc2[p])
                continue;
            /*
             Test both padded and unpadded version, just to
             be safe. Most versions of tparm don't actually
             seem to do anything these days.
             */
            size_t len = maxi(try_sequence(tparm(esc2[p]), code), try_sequence(esc2[p], code));
            if (len)
            {
                resulting_length = len;
                found = true;
            }
        }
    }

    if (!found)
    {
        if (code[1] == L'k')
        {
            /* This looks like the escape sequence for setting a screen name */
            const env_var_t term_name = env_get_string(L"TERM");
            if (!term_name.missing() && string_prefixes_string(L"screen", term_name))
            {
                const wchar_t * const screen_name_end_sentinel = L"\x1b\\";
                const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel);
                if (screen_name_end != NULL)
                {
                    const wchar_t *escape_sequence_end = screen_name_end + wcslen(screen_name_end_sentinel);
                    resulting_length = escape_sequence_end - code;
                }
                else
                {
                    /* Consider just <esc>k to be the code */
                    resulting_length = 2;
                }
                found = true;
            }
        }
    }

    if (! found)
    {
        /* Generic VT100 one byte sequence: CSI followed by something in the range @ through _ */
        if (code[1] == L'[' && (code[2] >= L'@' && code[2] <= L'_'))
        {
            resulting_length = 3;
            found = true;
        }
    }

    if (! found)
    {
        /* Generic VT100 CSI-style sequence. <esc>, followed by zero or more ASCII characters NOT in the range [@,_], followed by one character in that range */
        if (code[1] == L'[')
        {
            // Start at 2 to skip over <esc>[
            size_t cursor = 2;
            for (; code[cursor] != L'\0'; cursor++)
            {
                /* Consume a sequence of ASCII characters not in the range [@, ~] */
                wchar_t c = code[cursor];

                /* If we're not in ASCII, just stop */
                if (c > 127)
                    break;

                /* If we're the end character, then consume it and then stop */
                if (c >= L'@' && c <= L'~')
                {
                    cursor++;
                    break;
                }
            }
            /* curs now indexes just beyond the end of the sequence (or at the terminating zero) */
            found = true;
            resulting_length = cursor;
        }
    }

    if (! found)
    {
        /* Generic VT100 two byte sequence: <esc> followed by something in the range @ through _ */
        if (code[1] >= L'@' && code[1] <= L'_')
        {
            resulting_length = 2;
            found = true;
        }
    }

    return resulting_length;
}
Пример #15
0
bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd,
                     const env_vars_snapshot_t &env_vars) {
    int err = ENOENT;
    if (dir.empty()) return false;

    if (wd) {
        size_t len = wcslen(wd);
        assert(wd[len - 1] == L'/');
    }

    wcstring_list_t paths;
    if (dir.at(0) == L'/') {
        // Absolute path.
        paths.push_back(dir);
    } else if (string_prefixes_string(L"./", dir) || string_prefixes_string(L"../", dir) ||
               dir == L"." || dir == L"..") {
        // Path is relative to the working directory.
        wcstring path;
        if (wd) path.append(wd);
        path.append(dir);
        paths.push_back(path);
    } else {
        // Respect CDPATH.
        env_var_t path = env_vars.get(L"CDPATH");
        if (path.missing_or_empty()) path = L".";  // we'll change this to the wd if we have one

        wcstring nxt_path;
        wcstokenizer tokenizer(path, ARRAY_SEP_STR);
        while (tokenizer.next(nxt_path)) {
            if (nxt_path == L"." && wd != NULL) {
                // nxt_path is just '.', and we have a working directory, so use the wd instead.
                // TODO: if nxt_path starts with ./ we need to replace the . with the wd.
                nxt_path = wd;
            }
            expand_tilde(nxt_path);

            // debug( 2, L"woot %ls\n", expanded_path.c_str() );

            if (nxt_path.empty()) continue;

            wcstring whole_path = nxt_path;
            append_path_component(whole_path, dir);
            paths.push_back(whole_path);
        }
    }

    bool success = false;
    for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) {
        struct stat buf;
        const wcstring &dir = *iter;
        if (wstat(dir, &buf) == 0) {
            if (S_ISDIR(buf.st_mode)) {
                success = true;
                if (out) out->assign(dir);
                break;
            } else {
                err = ENOTDIR;
            }
        }
    }

    if (!success) errno = err;
    return success;
}
Пример #16
0
/**
   Call env_set. If this is a path variable, e.g. PATH, validate the
   elements. On error, print a description of the problem to stderr.
*/
static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, io_streams_t &streams)
{
    size_t i;
    int retcode = 0;
    const wchar_t *val_str=NULL;

    if (is_path_variable(key))
    {
        /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */
        bool any_success = false;

        /* Don't bother validating (or complaining about) values that are already present.
           When determining already-present values, use ENV_DEFAULT instead of the passed-in scope because in:
              set -l PATH stuff $PATH
           where we are temporarily shadowing a variable, we want to compare against the shadowed value, not the
           (missing) local value.
           Also don't bother to complain about relative paths, which don't start with /.
        */
        wcstring_list_t existing_values;
        const env_var_t existing_variable = env_get_string(key, ENV_DEFAULT);
        if (! existing_variable.missing_or_empty())
            tokenize_variable_array(existing_variable, existing_values);

        for (i=0; i< val.size() ; i++)
        {
            const wcstring &dir = val.at(i);
            if (!string_prefixes_string(L"/", dir) || list_contains_string(existing_values, dir))
            {
                any_success = true;
                continue;
            }

            bool show_perror = false;
            int show_hint = 0;
            bool error = false;

            struct stat buff;
            if (wstat(dir, &buff))
            {
                error = true;
                show_perror = true;
            }

            if (!(S_ISDIR(buff.st_mode)))
            {
                error = true;
            }

            if (!error)
            {
                any_success = true;
            }
            else
            {
                streams.err.append_format(_(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key);
                const wchar_t *colon = wcschr(dir.c_str(), L':');

                if (colon && *(colon+1))
                {
                    show_hint = 1;
                }

            }

            if (show_perror)
            {
                builtin_wperror(L"set", streams);
            }

            if (show_hint)
            {
                streams.err.append_format(_(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1);
            }

        }

        /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */
        if (! val.empty() && ! any_success)
        {
            return 1;
        }

    }

    wcstring sb;
    if (! val.empty())
    {
        for (i=0; i< val.size() ; i++)
        {
            sb.append(val[i]);
            if (i<val.size() - 1)
            {
                sb.append(ARRAY_SEP_STR);
            }
        }
        val_str = sb.c_str();
    }

    switch (env_set(key, val_str, scope | ENV_USER))
    {
        case ENV_PERM:
        {
            streams.err.append_format(_(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_SCOPE:
        {
            streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key);
            retcode=1;
            break;
        }

        case ENV_INVALID:
        {
            streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key);
            retcode=1;
            break;
        }
    }

    return retcode;
}
Пример #17
0
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) {
    ASSERT_IS_BACKGROUND_THREAD();

    bool handled = false, suggestionOK = false;

    /* Parse the string */    
    wcstring parsed_command;
    wcstring_list_t parsed_arguments;
    int parsed_last_arg_pos = -1;
    if (! autosuggest_parse_command(item.str(), &parsed_command, &parsed_arguments, &parsed_last_arg_pos))
        return false;

    if (parsed_command == L"cd" && ! parsed_arguments.empty()) {        
        /* We can possibly handle this specially */
        wcstring dir = parsed_arguments.back();
        if (expand_one(dir, EXPAND_SKIP_CMDSUBST))
        {
            handled = true;
            bool is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h");
            if (is_help) {
                suggestionOK = false;
            } else {
                wcstring path;
                bool can_cd = path_get_cdpath(dir, &path, working_directory.c_str(), vars);
                if (! can_cd) {
                    suggestionOK = false;
                } else if (paths_are_same_file(working_directory, path)) {
                    /* Don't suggest the working directory as the path! */
                    suggestionOK = false;
                } else {
                    suggestionOK = true;
                }
            }
        }        
    } 
  
    /* If not handled specially, handle it here */
    if (! handled) {
        bool cmd_ok = false;

        if (path_get_path(parsed_command, NULL))
        {
            cmd_ok = true;
        }
        else if (builtin_exists(parsed_command) || function_exists_no_autoload(parsed_command, vars))
        {
            cmd_ok = true;
        }

        if (cmd_ok) {
            const path_list_t &paths = item.get_required_paths();
            if (paths.empty()) {
                suggestionOK= true;
            }
            else {
                detector.potential_paths = paths;
                suggestionOK = detector.paths_are_valid(paths);
            }
        }
    }

    return suggestionOK;
}
Пример #18
0
static struct config_paths_t determine_config_directory_paths(const char *argv0) {
    struct config_paths_t paths;
    bool done = false;
    std::string exec_path = get_executable_path(argv0);
    if (get_realpath(exec_path)) {
        debug(2, L"exec_path: '%s', argv[0]: '%s'", exec_path.c_str(), argv0);
        // TODO: we should determine program_name from argv0 somewhere in this file

#ifdef CMAKE_BINARY_DIR
        // Detect if we're running right out of the CMAKE build directory
        if (string_prefixes_string(CMAKE_BINARY_DIR, exec_path.c_str())) {
            debug(2, "Running out of build directory, using paths relative to CMAKE_SOURCE_DIR:\n %s", CMAKE_SOURCE_DIR);

            done = true;
            paths.data = wcstring{L"" CMAKE_SOURCE_DIR} + L"/share";
            paths.sysconf = wcstring{L"" CMAKE_SOURCE_DIR} + L"/etc";
            paths.doc = wcstring{L"" CMAKE_SOURCE_DIR} + L"/user_doc/html";
            paths.bin = wcstring{L"" CMAKE_BINARY_DIR};
        }
#endif

        if (!done) {
            // The next check is that we are in a reloctable directory tree
            const char *installed_suffix = "/bin/fish";
            const char *just_a_fish = "/fish";
            const char *suffix = NULL;

            if (has_suffix(exec_path, installed_suffix, false)) {
                suffix = installed_suffix;
            } else if (has_suffix(exec_path, just_a_fish, false)) {
                debug(2, L"'fish' not in a 'bin/', trying paths relative to source tree");
                suffix = just_a_fish;
            }

            if (suffix) {
                bool seems_installed = (suffix == installed_suffix);

                wcstring base_path = str2wcstring(exec_path);
                base_path.resize(base_path.size() - std::strlen(suffix));

                paths.data = base_path + (seems_installed ? L"/share/fish" : L"/share");
                paths.sysconf = base_path + (seems_installed ? L"/etc/fish" : L"/etc");
                paths.doc = base_path + (seems_installed ? L"/share/doc/fish" : L"/user_doc/html");
                paths.bin = base_path + (seems_installed ? L"/bin" : L"");

                // Check only that the data and sysconf directories exist. Handle the doc
                // directories separately.
                struct stat buf;
                if (0 == wstat(paths.data, &buf) && 0 == wstat(paths.sysconf, &buf)) {
                    // The docs dir may not exist; in that case fall back to the compiled in path.
                    if (0 != wstat(paths.doc, &buf)) {
                        paths.doc = L"" DOCDIR;
                    }
                    done = true;
                }
            }
        }
    }

    if (!done) {
        // Fall back to what got compiled in.
        debug(2, L"Using compiled in paths:");
        paths.data = L"" DATADIR "/fish";
        paths.sysconf = L"" SYSCONFDIR "/fish";
        paths.doc = L"" DOCDIR;
        paths.bin = L"" BINDIR;
    }

    debug(2,
          L"determine_config_directory_paths() results:\npaths.data: %ls\npaths.sysconf: "
          L"%ls\npaths.doc: %ls\npaths.bin: %ls",
          paths.data.c_str(), paths.sysconf.c_str(), paths.doc.c_str(), paths.bin.c_str());
    return paths;
}