Exemplo n.º 1
0
void append_path_component(wcstring &path, const wcstring &component)
{
    if (path.empty() || component.empty()) {
        path.append(component);
    } else {
        size_t path_len = path.size();
        bool path_slash = path.at(path_len-1) == L'/';
        bool comp_slash = component.at(0) == L'/';
        if (! path_slash && ! comp_slash) {
            // Need a slash
            path.push_back(L'/');
        } else if (path_slash && comp_slash) {
            // Too many slashes
            path.erase(path_len - 1, 1);
        }
        path.append(component);
    }
}
Exemplo n.º 2
0
void wperror(const wcstring &s)
{
    int e = errno;
    if (!s.empty())
    {
        fwprintf(stderr, L"%ls: ", s.c_str());
    }
    fwprintf(stderr, L"%s\n", strerror(e));
}
Exemplo n.º 3
0
/// Test if the specified argument is clean, i.e. it does not contain any tokens which need to be
/// expanded or otherwise altered. Clean strings can be passed through expand_string and expand_one
/// without changing them. About two thirds of all strings are clean, so skipping expansion on them
/// actually does save a small amount of time, since it avoids multiple memory allocations during
/// the expansion process.
///
/// \param in the string to test
static bool expand_is_clean(const wcstring &in) {
    if (in.empty()) return true;

    // Test characters that have a special meaning in the first character position.
    if (wcschr(UNCLEAN_FIRST, in.at(0)) != NULL) return false;

    // Test characters that have a special meaning in any character position.
    return in.find_first_of(UNCLEAN) == wcstring::npos;
}
Exemplo n.º 4
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;
    }
}
Exemplo n.º 5
0
void kill_add(const wcstring &str)
{
    ASSERT_IS_MAIN_THREAD();
    if (str.empty())
        return;

    wcstring cmd;
    wcstring escaped_str;
    kill_list.push_front(str);

    /*
       Check to see if user has set the FISH_CLIPBOARD_CMD variable,
       and, if so, use it instead of checking the display, etc.

       I couldn't think of a safe way to allow overide of the echo
       command too, so, the command used must accept the input via stdin.
    */

    const env_var_t clipboard_wstr = env_get_string(L"FISH_CLIPBOARD_CMD");
    if (!clipboard_wstr.missing())
    {
        escaped_str = escape(str.c_str(), ESCAPE_ALL);
        cmd.assign(L"echo -n ");
        cmd.append(escaped_str);
        cmd.append(clipboard_wstr);
    }
    else
    {
        /* This is for sending the kill to the X copy-and-paste buffer */
        if (!has_xsel())
        {
            return;
        }

        const env_var_t disp_wstr = env_get_string(L"DISPLAY");
        if (!disp_wstr.missing())
        {
            escaped_str = escape(str.c_str(), ESCAPE_ALL);
            cmd.assign(L"echo -n ");
            cmd.append(escaped_str);
            cmd.append(L" | xsel -i -b");
        }
    }

    if (! cmd.empty())
    {
        if (exec_subshell(cmd, false /* do not apply exit status */) == -1)
        {
            /*
               Do nothing on failiure
            */
        }

        cut_buffer = escaped_str;
    }
}
Exemplo n.º 6
0
/// Print the backtrace and call for help that we use at the end of error messages.
void builtin_print_error_trailer(parser_t &parser, output_stream_t &b, const wchar_t *cmd) {
    b.append(L"\n");
    const wcstring stacktrace = parser.current_line();
    // Don't print two empty lines if we don't have a stacktrace.
    if (!stacktrace.empty()) {
        b.append(stacktrace);
        b.append(L"\n");
    }
    b.append_format(_(L"(Type 'help %ls' for related documentation)\n"), cmd);
}
Exemplo n.º 7
0
double C_STRTOD(wchar_t const *nptr, wchar_t **endptr)
{
    double r;

    const wcstring saved_locale = wsetlocale(LC_NUMERIC, NULL);

    if (!saved_locale.empty())
    {
        wsetlocale(LC_NUMERIC, L"C");
    }

    r = wcstod(nptr, endptr);

    if (!saved_locale.empty())
    {
        wsetlocale(LC_NUMERIC, saved_locale.c_str());
    }

    return r;
}
Exemplo n.º 8
0
bool path_is_valid(const wcstring &path, const wcstring &working_directory) {
    bool path_is_valid;
    // Some special paths are always valid.
    if (path.empty()) {
        path_is_valid = false;
    } else if (path == L"." || path == L"./") {
        path_is_valid = true;
    } else if (path == L".." || path == L"../") {
        path_is_valid = (!working_directory.empty() && working_directory != L"/");
    } else if (path.at(0) != '/') {
        // Prepend the working directory. Note that we know path is not empty here.
        wcstring tmp = working_directory;
        tmp.append(path);
        path_is_valid = (0 == waccess(tmp, F_OK));
    } else {
        // Simple check.
        path_is_valid = (0 == waccess(path, F_OK));
    }
    return path_is_valid;
}
Exemplo n.º 9
0
/// Cache the data path.
bool path_get_data(wcstring &path) {
    static bool data_path_done = false;
    static wcstring data_path(L"");

    if (!data_path_done) {
        data_path_done = true;
        path_create(data_path, L"XDG_DATA_HOME", L"data", _(L"Your history will not be saved."));
    }

    path = data_path;
    return !data_path.empty();
}
Exemplo n.º 10
0
static std::string html_colorize(const wcstring &text,
                                 const std::vector<highlight_spec_t> &colors) {
    if (text.empty()) {
        return "";
    }

    assert(colors.size() == text.size());
    wcstring html = L"<pre><code>";
    highlight_spec_t last_color = highlight_spec_normal;
    for (size_t i = 0; i < text.size(); i++) {
        // Handle colors.
        highlight_spec_t color = colors.at(i);
        if (i > 0 && color != last_color) {
            html.append(L"</span>");
        }
        if (i == 0 || color != last_color) {
            append_format(html, L"<span class=\"%ls\">", html_class_name_for_color(color));
        }
        last_color = color;

        // Handle text.
        wchar_t wc = text.at(i);
        switch (wc) {
            case L'&': {
                html.append(L"&amp;");
                break;
            }
            case L'\'': {
                html.append(L"&apos;");
                break;
            }
            case L'"': {
                html.append(L"&quot;");
                break;
            }
            case L'<': {
                html.append(L"&lt;");
                break;
            }
            case L'>': {
                html.append(L"&gt;");
                break;
            }
            default: {
                html.push_back(wc);
                break;
            }
        }
    }
    html.append(L"</span></code></pre>");
    return wcs2string(html);
}
Exemplo n.º 11
0
/// Cache the config path.
bool path_get_config(wcstring &path) {
    static bool config_path_done = false;
    static wcstring config_path(L"");

    if (!config_path_done) {
        path_create(config_path, L"XDG_CONFIG_HOME", L"config",
                    _(L"Your personal settings will not be saved."));
        config_path_done = true;
    }

    path = config_path;
    return !config_path.empty();
}
Exemplo n.º 12
0
/**
   Remove any prefix and suffix newlines from the specified
   string.
 */
static void trim(wcstring &str)
{
    if (str.empty())
        return;

    size_t pos = str.find_first_not_of(L" \n");
    if (pos > 0)
        str.erase(0, pos);

    pos = str.find_last_not_of(L" \n");
    if (pos != wcstring::npos && pos + 1 < str.length())
        str.erase(pos + 1);
}
Exemplo n.º 13
0
 wildcard_matcher_t(const wchar_t * /*argv0*/, const wchar_t *pattern, const options_t &opts,
                    io_streams_t &streams)
     : string_matcher_t(opts, streams), wcpattern(parse_util_unescape_wildcards(pattern)) {
     if (opts.ignore_case) {
         for (size_t i = 0; i < wcpattern.length(); i++) {
             wcpattern[i] = towlower(wcpattern[i]);
         }
     }
     if (opts.entire && !wcpattern.empty()) {
         if (wcpattern.front() != ANY_STRING) wcpattern.insert(0, 1, ANY_STRING);
         if (wcpattern.back() != ANY_STRING) wcpattern.push_back(ANY_STRING);
     }
 }
Exemplo n.º 14
0
static void report_error(int err_code, const wchar_t *err_format, ...)
{
    va_list va;
    va_start(va, err_format);
    const wcstring err_text = vformat_string(err_format, va);
    va_end(va);
    
    if (! err_text.empty())
    {
        fwprintf(stderr, L"%ls: ", err_text.c_str());
    }
    fwprintf(stderr, L"%s\n", strerror(err_code));
}
Exemplo n.º 15
0
/* We have to return an escaped string here */
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outSuggestion) {
    if (str.empty())
        return false;
        
    ASSERT_IS_BACKGROUND_THREAD();
    
    /* Parse the string */
    wcstring parsed_command;
    wcstring_list_t parsed_arguments;
    int parsed_last_arg_pos = -1;
    if (! autosuggest_parse_command(str, &parsed_command, &parsed_arguments, &parsed_last_arg_pos))
        return false;
    
    bool result = false;
    if (parsed_command == L"cd" && ! parsed_arguments.empty()) {        
        /* We can possibly handle this specially */
        const wcstring escaped_dir = parsed_arguments.back();
        wcstring suggested_path;
        
        /* We always return true because we recognized the command. This prevents us from falling back to dumber algorithms; for example we won't suggest a non-directory for the cd command. */
        result = true;
        outSuggestion.clear();
        
        /* Unescape the parameter */
        wcstring unescaped_dir = escaped_dir;
        bool unescaped = unescape_string(unescaped_dir, UNESCAPE_INCOMPLETE);
        
        /* Determine the quote type we got from the input directory. */
        wchar_t quote = L'\0';
        parse_util_get_parameter_info(escaped_dir, 0, &quote, NULL, NULL);
        
        /* Big hack to avoid expanding a tilde inside quotes */
        path_flags_t path_flags = (quote == L'\0') ? PATH_EXPAND_TILDE : 0;
        if (unescaped && is_potential_cd_path(unescaped_dir, working_directory, path_flags, &suggested_path)) {
                    
            /* Note: this looks really wrong for strings that have an "unescapable" character in them, e.g. a \t, because parse_util_escape_string_with_quote will insert that character */
            wcstring escaped_suggested_path = parse_util_escape_string_with_quote(suggested_path, quote);

            /* Return it */
            outSuggestion = str;
            outSuggestion.erase(parsed_last_arg_pos);
            if (quote != L'\0') outSuggestion.push_back(quote);
            outSuggestion.append(escaped_suggested_path);
            if (quote != L'\0') outSuggestion.push_back(quote);
        }
    } else {
        /* Either an error or some other command, so we don't handle it specially */
    }
    return result;
}
Exemplo n.º 16
0
void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors, wcstring *output) const
{
    assert(output != NULL);
    if (! errors.empty())
    {
        const parse_error_t &err = errors.at(0);

        const bool is_interactive = get_is_interactive();

        // Determine if we want to try to print a caret to point at the source error
        // The err.source_start <= src.size() check is due to the nasty way that slices work,
        // which is by rewriting the source (!)
        size_t which_line = 0;
        bool skip_caret = true;
        if (err.source_start != SOURCE_LOCATION_UNKNOWN && err.source_start <= src.size())
        {
            // Determine which line we're on
            which_line = 1 + std::count(src.begin(), src.begin() + err.source_start, L'\n');

            // Don't include the caret if we're interactive, this is the first line of text, and our source is at its beginning, because then it's obvious
            skip_caret = (is_interactive && which_line == 1 && err.source_start == 0);
        }

        wcstring prefix;
        const wchar_t *filename = this->current_filename();
        if (filename)
        {
            if (which_line > 0)
            {
                prefix = format_string(_(L"%ls (line %lu): "), user_presentable_path(filename).c_str(), which_line);
            }
            else
            {
                prefix = format_string(_(L"%ls: "), user_presentable_path(filename).c_str());
            }
        }
        else
        {
            prefix = L"fish: ";
        }

        const wcstring description = err.describe_with_prefix(src, prefix, is_interactive, skip_caret);
        if (! description.empty())
        {
            output->append(description);
            output->push_back(L'\n');
        }
        output->append(this->stack_trace());
    }
}
Exemplo n.º 17
0
/**
   Load or save all variables
*/
static void load_or_save( int save)
{
	const wcstring wdir = fishd_get_config();
	char hostname[HOSTNAME_LEN];
	connection_t c;
	int fd;
	
	if (wdir.empty())
		return;
	
	std::string dir = wcs2string( wdir );
	
	gethostname( hostname, HOSTNAME_LEN );
	
    std::string name;
    name.append(dir);
    name.append("/");
    name.append(FILE);
    name.append(hostname);
	
	debug( 4, L"Open file for %s: '%s'", 
		   save?"saving":"loading", 
		   name.c_str() );
	
    /* OK to not use CLO_EXEC here because fishd is single threaded */
	fd = open(name.c_str(), save?(O_CREAT | O_TRUNC | O_WRONLY):O_RDONLY, 0600);
	
	if( fd == -1 )
	{
		debug( 1, L"Could not open load/save file. No previous saves?" );
		wperror( L"open" );
		return;		
	}
	debug( 4, L"File open on fd %d", c.fd );

	connection_init( &c, fd );

	if( save )
	{
		
		write_loop( c.fd, SAVE_MSG, strlen(SAVE_MSG) );
		enqueue_all( &c );
	}
	else
		read_message( &c );

	connection_destroy( &c );	
}
Exemplo n.º 18
0
    // Given a start point as an absolute path, for any directory that has exactly one non-hidden
    // entity in it which is itself a directory, return that. The result is a relative path. For
    // example, if start_point is '/usr' we may return 'local/bin/'.
    //
    // The result does not have a leading slash, but does have a trailing slash if non-empty.
    wcstring descend_unique_hierarchy(const wcstring &start_point) {
        assert(!start_point.empty() && start_point.at(0) == L'/');

        wcstring unique_hierarchy;
        wcstring abs_unique_hierarchy = start_point;

        bool stop_descent = false;
        DIR *dir;
        while (!stop_descent && (dir = wopendir(abs_unique_hierarchy))) {
            // We keep track of the single unique_entry entry. If we get more than one, it's not
            // unique and we stop the descent.
            wcstring unique_entry;

            bool child_is_dir;
            wcstring child_entry;
            while (wreaddir_resolving(dir, abs_unique_hierarchy, child_entry, &child_is_dir)) {
                if (child_entry.empty() || child_entry.at(0) == L'.') {
                    continue;  // either hidden, or . and .. entries -- skip them
                } else if (child_is_dir && unique_entry.empty()) {
                    unique_entry = child_entry;  // first candidate
                } else {
                    // We either have two or more candidates, or the child is not a directory. We're
                    // done.
                    stop_descent = true;
                    break;
                }
            }

            // We stop if we got two or more entries; also stop if we got zero or were interrupted
            if (unique_entry.empty() || interrupted()) {
                stop_descent = true;
            }

            if (!stop_descent) {
                // We have an entry in the unique hierarchy!
                append_path_component(unique_hierarchy, unique_entry);
                unique_hierarchy.push_back(L'/');

                append_path_component(abs_unique_hierarchy, unique_entry);
                abs_unique_hierarchy.push_back(L'/');
            }
            closedir(dir);
        }
        return unique_hierarchy;
    }
Exemplo n.º 19
0
/// We separate this from path_create() for two reasons. First it's only caused if there is a
/// problem, and thus is not central to the behavior of that function. Second, we only want to issue
/// the message once. If the current shell starts a new fish shell (e.g., by running `fish -c` from
/// a function) we don't want that subshell to issue the same warnings.
static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring &custom_error_msg,
                                     bool using_xdg, const wcstring &xdg_var, const wcstring &path,
                                     int saved_errno) {
    wcstring warning_var_name = L"_FISH_WARNED_" + which_dir;
    if (env_exist(warning_var_name.c_str(), ENV_GLOBAL | ENV_EXPORT)) {
        return;
    }
    env_set(warning_var_name, L"1", ENV_GLOBAL | ENV_EXPORT);

    debug(0, custom_error_msg.c_str());
    if (path.empty()) {
        debug(0, _(L"Unable to locate the %ls directory."), which_dir.c_str());
        debug(0, _(L"Please set the %ls or HOME environment variable before starting fish."),
              xdg_var.c_str());
    } else {
        const wchar_t *env_var = using_xdg ? xdg_var.c_str() : L"HOME";
        debug(0, _(L"Unable to locate %ls directory derived from $%ls: '%ls'."), which_dir.c_str(),
              env_var, path.c_str());
        debug(0, _(L"The error was '%s'."), strerror(saved_errno));
        debug(0, _(L"Please set $%ls to a directory where you have write access."), env_var);
    }
    write(STDERR_FILENO, "\n", 1);
}
Exemplo n.º 20
0
/* 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!

   We expect the path to already be unescaped.
*/
bool is_potential_path(const wcstring &const_path, const wcstring_list_t &directories, path_flags_t flags, wcstring *out_path)
{
    ASSERT_IS_BACKGROUND_THREAD();
    
    const bool require_dir = !! (flags & PATH_REQUIRE_DIR);
    wcstring clean_path;
	int has_magic = 0;
	bool result = false;
    
    wcstring path(const_path);
    if (flags & PATH_EXPAND_TILDE)
        expand_tilde(path);    
    
    //	debug( 1, L"%ls -> %ls ->%ls", path, tilde, unescaped );
    
    for( size_t i=0; i < path.size(); i++)
    {
        wchar_t c = path.at(i);
        switch( c )
        {
            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;		
            }
				
            case INTERNAL_SEPARATOR:
            {
                break;
            }
				
            default:
            {
                clean_path.push_back(c);
                break;
            }
				
        }
        
    }
    
    if( ! has_magic && ! clean_path.empty() )
    {
        /* 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;
        
        /* Keep a cache of which paths / filesystems are case sensitive */
        case_sensitivity_cache_t case_sensitivity_cache;
        
        for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++) {
            const wcstring &wd = directories.at(wd_idx);
            
            const wcstring abs_path = apply_working_directory(clean_path, wd);
            
            /* 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);
            
            /* 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'/';
            if (must_be_full_dir) 
            {
                struct stat buf;
                if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) {
                    result = true;
                    /* Return the path suffix, not the whole absolute path */
                    if (out_path)
                        *out_path = clean_path;
                }
            }
            else
            {
                DIR *dir = NULL;
                
                /* We do not end with a slash; it does not have to be a directory */
                const wcstring dir_name = wdirname(abs_path);
                const wcstring base_name = wbasename(abs_path);
                if (dir_name == L"/" && base_name == L"/")
                {
                    result = true;
                    if (out_path)
                        *out_path = clean_path;
                }
                else if ((dir = wopendir(dir_name))) {
                    // We opened the dir_name; look for a string where the base name prefixes it
                    wcstring ent;
                    
                    // Check if we're case insensitive
                    bool case_insensitive = fs_is_case_insensitive(dir_name, dirfd(dir), case_sensitivity_cache);
                    
                    // Don't ask for the is_dir value unless we care, because it can cause extra filesystem acces */
                    bool is_dir = false;
                    while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL))
                    {                    

                        /* Determine which function to call to check for prefixes */
                        bool (*prefix_func)(const wcstring &, const wcstring &);
                        if (case_insensitive) {
                            prefix_func = string_prefixes_string_case_insensitive;
                        } else {
                            prefix_func = string_prefixes_string;
                        }

                        if (prefix_func(base_name, ent) && (! require_dir || is_dir))
                        {
                            result = true;
                            if (out_path) {
                                /* We want to return the path in the same "form" as it was given. Take the given path, get its basename. Append that to the output if the basename actually prefixes the path (which it won't if the given path contains no slashes), and isn't a slash (so we don't duplicate slashes). Then append the directory entry. */
                                
                                out_path->clear();
                                const wcstring path_base = wdirname(const_path);
                                
                                
                                if (prefix_func(path_base, const_path)) {
                                    out_path->append(path_base);
                                    if (! string_suffixes_string(L"/", *out_path))
                                        out_path->push_back(L'/');
                                }
                                out_path->append(ent);
                                /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */
                                if (is_dir)
                                    out_path->push_back(L'/');
                            }
                            break;
                        }
                    }
                    closedir(dir);
                }
            }
        }
    }
    return result;
}
Exemplo n.º 21
0
/**
   Matches the string against the wildcard, and if the wildcard is a
   possible completion of the string, the remainder of the string is
   inserted into the out vector.
*/
static bool wildcard_complete_internal(const wcstring &orig,
                                       const wchar_t *str,
                                       const wchar_t *wc,
                                       bool is_first,
                                       const wchar_t *desc,
                                       wcstring(*desc_func)(const wcstring &),
                                       std::vector<completion_t> *out,
                                       expand_flags_t expand_flags,
                                       complete_flags_t flags)
{
    if (!wc || ! str || orig.empty())
    {
        debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__);
        return 0;
    }

    bool has_match = false;
    string_fuzzy_match_t fuzzy_match(fuzzy_match_exact);
    const bool at_end_of_wildcard = (*wc == L'\0');
    const wchar_t *completion_string = NULL;

    // Hack hack hack
    // Implement EXPAND_FUZZY_MATCH by short-circuiting everything if there are no remaining wildcards
    if ((expand_flags & EXPAND_FUZZY_MATCH) && ! at_end_of_wildcard && ! wildcard_has(wc, true))
    {
        string_fuzzy_match_t local_fuzzy_match = string_fuzzy_match_string(wc, str);
        if (local_fuzzy_match.type != fuzzy_match_none)
        {
            has_match = true;
            fuzzy_match = local_fuzzy_match;

            /* If we're not a prefix or exact match, then we need to replace the token. Note that in this case we're not going to call ourselves recursively, so these modified flags won't "leak" except into the completion. */
            if (match_type_requires_full_replacement(local_fuzzy_match.type))
            {
                flags |= COMPLETE_REPLACES_TOKEN;
                completion_string = orig.c_str();
            }
            else
            {
                /* Since we are not replacing the token, be careful to only store the part of the string after the wildcard */
                size_t wc_len = wcslen(wc);
                assert(wcslen(str) >= wc_len);
                completion_string = str + wcslen(wc);
            }
        }
    }

    /* Maybe we satisfied the wildcard normally */
    if (! has_match)
    {
        bool file_has_leading_dot = (is_first && str[0] == L'.');
        if (at_end_of_wildcard && ! file_has_leading_dot)
        {
            has_match = true;
            if (flags & COMPLETE_REPLACES_TOKEN)
            {
                completion_string = orig.c_str();
            }
            else
            {
                completion_string = str;
            }
        }
    }

    if (has_match)
    {
        /* Wildcard complete */
        assert(completion_string != NULL);
        wcstring out_completion = completion_string;
        wcstring out_desc = (desc ? desc : L"");

        size_t complete_sep_loc = out_completion.find(PROG_COMPLETE_SEP);
        if (complete_sep_loc != wcstring::npos)
        {
            /* This completion has an embedded description, do not use the generic description */
            out_desc.assign(out_completion, complete_sep_loc + 1, out_completion.size() - complete_sep_loc - 1);
            out_completion.resize(complete_sep_loc);
        }
        else
        {
            if (desc_func && !(expand_flags & EXPAND_NO_DESCRIPTIONS))
            {
                /*
                  A description generating function is specified, call
                  it. If it returns something, use that as the
                  description.
                */
                wcstring func_desc = desc_func(orig);
                if (! func_desc.empty())
                    out_desc = func_desc;
            }

        }

        /* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
        append_completion(out, out_completion, out_desc, flags, fuzzy_match);
        return true;
    }

    if (*wc == ANY_STRING)
    {
        bool res=false;

        /* Ignore hidden file */
        if (is_first && str[0] == L'.')
            return false;

        /* Try all submatches */
        for (size_t i=0; str[i] != L'\0'; i++)
        {
            const size_t before_count = out->size();
            if (wildcard_complete_internal(orig, str + i, wc+1, false, desc, desc_func, out, expand_flags, flags))
            {
                res = true;

                /* #929: if the recursive call gives us a prefix match, just stop. This is sloppy - what we really want to do is say, once we've seen a match of a particular type, ignore all matches of that type further down the string, such that the wildcard produces the "minimal match." */
                bool has_prefix_match = false;
                const size_t after_count = out->size();
                for (size_t j = before_count; j < after_count; j++)
                {
                    if (out->at(j).match.type <= fuzzy_match_prefix)
                    {
                        has_prefix_match = true;
                        break;
                    }
                }
                if (has_prefix_match)
                    break;
            }
        }
        return res;

    }
    else if (*wc == ANY_CHAR || *wc == *str)
    {
        return wildcard_complete_internal(orig, str+1, wc+1, false, desc, desc_func, out, expand_flags, flags);
    }
    else if (towlower(*wc) == towlower(*str))
    {
        return wildcard_complete_internal(orig, str+1, wc+1, false, desc, desc_func, out, expand_flags, flags | COMPLETE_REPLACES_TOKEN);
    }
    return false;
}
Exemplo n.º 22
0
static bool unary_primary_evaluate(test_expressions::token_t token, const wcstring &arg,
                                   wcstring_list_t &errors) {
    using namespace test_expressions;
    struct stat buf;
    long long num;
    switch (token) {
        case test_filetype_b: {  // "-b", for block special files
            return !wstat(arg, &buf) && S_ISBLK(buf.st_mode);
        }
        case test_filetype_c: {  // "-c", for character special files
            return !wstat(arg, &buf) && S_ISCHR(buf.st_mode);
        }
        case test_filetype_d: {  // "-d", for directories
            return !wstat(arg, &buf) && S_ISDIR(buf.st_mode);
        }
        case test_filetype_e: {  // "-e", for files that exist
            return !wstat(arg, &buf);
        }
        case test_filetype_f: {  // "-f", for for regular files
            return !wstat(arg, &buf) && S_ISREG(buf.st_mode);
        }
        case test_filetype_G: {  // "-G", for check effective group id
            return !wstat(arg, &buf) && getegid() == buf.st_gid;
        }
        case test_filetype_g: {  // "-g", for set-group-id
            return !wstat(arg, &buf) && (S_ISGID & buf.st_mode);
        }
        case test_filetype_h:    // "-h", for symbolic links
        case test_filetype_L: {  // "-L", same as -h
            return !lwstat(arg, &buf) && S_ISLNK(buf.st_mode);
        }
        case test_filetype_O: {  // "-O", for check effective user id
            return !wstat(arg, &buf) && geteuid() == buf.st_uid;
        }
        case test_filetype_p: {  // "-p", for FIFO
            return !wstat(arg, &buf) && S_ISFIFO(buf.st_mode);
        }
        case test_filetype_S: {  // "-S", socket
            return !wstat(arg, &buf) && S_ISSOCK(buf.st_mode);
        }
        case test_filesize_s: {  // "-s", size greater than zero
            return !wstat(arg, &buf) && buf.st_size > 0;
        }
        case test_filedesc_t: {  // "-t", whether the fd is associated with a terminal
            return parse_number(arg, &num, errors) && num == (int)num && isatty((int)num);
        }
        case test_fileperm_r: {  // "-r", read permission
            return !waccess(arg, R_OK);
        }
        case test_fileperm_u: {  // "-u", whether file is setuid
            return !wstat(arg, &buf) && (S_ISUID & buf.st_mode);
        }
        case test_fileperm_w: {  // "-w", whether file write permission is allowed
            return !waccess(arg, W_OK);
        }
        case test_fileperm_x: {  // "-x", whether file execute/search is allowed
            return !waccess(arg, X_OK);
        }
        case test_string_n: {  // "-n", non-empty string
            return !arg.empty();
        }
        case test_string_z: {  // "-z", true if length of string is 0
            return arg.empty();
        }
        default: {
            errors.push_back(format_string(L"Unknown token type in %s", __func__));
            return false;
        }
    }
}
Exemplo n.º 23
0
/* Parse a command line. Return by reference the last command, its arguments, and the offset in the string of the beginning of the last argument. This is used by autosuggestions */
static bool autosuggest_parse_command(const wcstring &str, wcstring *out_command, wcstring_list_t *out_arguments, int *out_last_arg_pos)
{
    if (str.empty())
        return false;
    
    wcstring cmd;
    wcstring_list_t args;
    int arg_pos = -1;
    
    bool had_cmd = false;
    tokenizer tok;
    for (tok_init( &tok, str.c_str(), TOK_ACCEPT_UNFINISHED | 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 to the command. We store these escaped. */
                    args.push_back(tok_last(&tok));
                    arg_pos = tok_get_pos(&tok);
                }
                else
                { 	
                    /* Command. First check that the command actually exists. */
                    wcstring local_cmd = tok_last( &tok );
                    bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
                    if (! expanded || has_expand_reserved(cmd.c_str()))
                    {
                        /* We can't expand this cmd, ignore it */
                    }
                    else
                    {
                        bool is_subcommand = false;
                        int mark = tok_get_pos(&tok);
                        
                        if (parser_keywords_is_subcommand(cmd))
                        {
                            int sw;
                            tok_next( &tok );
                            
                            sw = parser_keywords_is_switch( tok_last( &tok ) );
                            if( !parser_keywords_is_block( cmd ) &&
                               sw == ARG_SWITCH )
                            {
                                /* It's an argument to the subcommand itself */
                            }
                            else
                            {
                                if( sw == ARG_SKIP )
                                    mark = tok_get_pos( &tok );                                    
                                is_subcommand = true;
                            }
                            tok_set_pos( &tok, mark );
                        }
                        
                        if (!is_subcommand)
                        {
                            /* It's really a command */
                            had_cmd = true;
                            cmd = local_cmd;
                        }
                    }
                    
                }
                break;
            }
                
            case TOK_REDIRECT_NOCLOB:
            case TOK_REDIRECT_OUT:
            case TOK_REDIRECT_IN:
            case TOK_REDIRECT_APPEND:
            case TOK_REDIRECT_FD:
            {
                if( !had_cmd )
                {
                    break;
                }
                tok_next( &tok );				
                break;
            }
                
            case TOK_PIPE:
            case TOK_BACKGROUND:
            case TOK_END:
            {
                had_cmd = false;
                cmd.empty();
                args.empty();
                arg_pos = -1;
                break;
            }
                
            case TOK_COMMENT:                    
            case TOK_ERROR:
            default:
            {
                break;				
            }			
        }
    }
    tok_destroy( &tok );
    
    /* Remember our command if we have one */
    if (had_cmd) {
        if (out_command) out_command->swap(cmd);
        if (out_arguments) out_arguments->swap(args);
        if (out_last_arg_pos) *out_last_arg_pos = arg_pos;
    }
    return had_cmd;
}
Exemplo n.º 24
0
void kill_add(const wcstring &str) {
    ASSERT_IS_MAIN_THREAD();
    if (str.empty()) return;
    kill_list.push_front(str);
}
Exemplo n.º 25
0
void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_prompt,
             const wcstring &commandline, size_t explicit_len,
             const std::vector<highlight_spec_t> &colors, const std::vector<int> &indent,
             size_t cursor_pos, const page_rendering_t &pager, bool cursor_is_within_pager) {
    screen_data_t::cursor_t cursor_arr;

    // Turn the command line into the explicit portion and the autosuggestion.
    const wcstring explicit_command_line = commandline.substr(0, explicit_len);
    const wcstring autosuggestion = commandline.substr(explicit_len);

    // If we are using a dumb terminal, don't try any fancy stuff, just print out the text.
    // right_prompt not supported.
    if (is_dumb()) {
        const std::string prompt_narrow = wcs2string(left_prompt);
        const std::string command_line_narrow = wcs2string(explicit_command_line);

        write_loop(STDOUT_FILENO, "\r", 1);
        write_loop(STDOUT_FILENO, prompt_narrow.c_str(), prompt_narrow.size());
        write_loop(STDOUT_FILENO, command_line_narrow.c_str(), command_line_narrow.size());

        return;
    }

    s_check_status(s);
    const size_t screen_width = common_get_width();

    // Completely ignore impossibly small screens.
    if (screen_width < 4) {
        return;
    }

    // Compute a layout.
    const screen_layout_t layout = compute_layout(s, screen_width, left_prompt, right_prompt,
                                                  explicit_command_line, autosuggestion, indent);

    // Determine whether, if we have an autosuggestion, it was truncated.
    s->autosuggestion_is_truncated =
        !autosuggestion.empty() && autosuggestion != layout.autosuggestion;

    // Clear the desired screen.
    s->desired.resize(0);
    s->desired.cursor.x = s->desired.cursor.y = 0;

    // Append spaces for the left prompt.
    for (size_t i = 0; i < layout.left_prompt_space; i++) {
        s_desired_append_char(s, L' ', highlight_spec_t{}, 0, layout.left_prompt_space);
    }

    // If overflowing, give the prompt its own line to improve the situation.
    size_t first_line_prompt_space = layout.left_prompt_space;
    if (layout.prompts_get_own_line) {
        s_desired_append_char(s, L'\n', highlight_spec_t{}, 0, 0);
        first_line_prompt_space = 0;
    }

    // Reconstruct the command line.
    wcstring effective_commandline = explicit_command_line + layout.autosuggestion;

    // Output the command line.
    size_t i;
    for (i = 0; i < effective_commandline.size(); i++) {
        // Grab the current cursor's x,y position if this character matches the cursor's offset.
        if (!cursor_is_within_pager && i == cursor_pos) {
            cursor_arr = s->desired.cursor;
        }
        s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i],
                              first_line_prompt_space);
    }

    // Cursor may have been at the end too.
    if (!cursor_is_within_pager && i == cursor_pos) {
        cursor_arr = s->desired.cursor;
    }

    // Now that we've output everything, set the cursor to the position that we saved in the loop
    // above.
    s->desired.cursor = cursor_arr;

    if (cursor_is_within_pager) {
        s->desired.cursor.x = (int)cursor_pos;
        s->desired.cursor.y = (int)s->desired.line_count();
    }

    // Append pager_data (none if empty).
    s->desired.append_lines(pager.screen_data);

    s_update(s, layout.left_prompt, layout.right_prompt);
    s_save_status(s);
}
Exemplo n.º 26
0
/// The real implementation of wildcard expansion is in this function. Other functions are just
/// wrappers around this one.
///
/// This function traverses the relevant directory tree looking for matches, and recurses when
/// needed to handle wildcrards spanning multiple components and recursive wildcards.
///
/// Because this function calls itself recursively with substrings, it's important that the
/// parameters be raw pointers instead of wcstring, which would be too expensive to construct for
/// all substrings.
///
/// Args:
/// base_dir: the "working directory" against which the wildcard is to be resolved
/// wc: the wildcard string itself, e.g. foo*bar/baz (where * is acutally ANY_CHAR)
/// prefix: the string that should be prepended for completions that replace their token.
//    This is usually the same thing as the original wildcard, but for fuzzy matching, we
//    expand intermediate segments. effective_prefix is always either empty, or ends with a slash
//    Note: this is only used when doing completions (EXPAND_FOR_COMPLETIONS is true), not
//    expansions
void wildcard_expander_t::expand(const wcstring &base_dir, const wchar_t *wc,
                                 const wcstring &effective_prefix) {
    assert(wc != NULL);

    if (interrupted()) {
        return;
    }

    // Get the current segment and compute interesting properties about it.
    const size_t wc_len = std::wcslen(wc);
    const wchar_t *const next_slash = std::wcschr(wc, L'/');
    const bool is_last_segment = (next_slash == NULL);
    const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len;
    const wcstring wc_segment = wcstring(wc, wc_segment_len);
    const bool segment_has_wildcards =
        wildcard_has(wc_segment, true /* internal, i.e. look for ANY_CHAR instead of ? */);
    const wchar_t *const wc_remainder = next_slash ? next_slash + 1 : NULL;

    if (wc_segment.empty()) {
        // Handle empty segment.
        assert(!segment_has_wildcards);  //!OCLINT(multiple unary operator)
        if (is_last_segment) {
            this->expand_trailing_slash(base_dir, effective_prefix);
        } else {
            // Multiple adjacent slashes in the wildcard. Just skip them.
            this->expand(base_dir, wc_remainder, effective_prefix + L'/');
        }
    } else if (!segment_has_wildcards && !is_last_segment) {
        // Literal intermediate match. Note that we may not be able to actually read the directory
        // (issue #2099).
        assert(next_slash != NULL);

        // Absolute path of the intermediate directory
        const wcstring intermediate_dirpath = base_dir + wc_segment + L'/';

        // This just trumps everything.
        size_t before = this->resolved_completions->size();
        this->expand(intermediate_dirpath, wc_remainder, effective_prefix + wc_segment + L'/');

        // Maybe try a fuzzy match (#94) if nothing was found with the literal match. Respect
        // EXPAND_NO_DIRECTORY_ABBREVIATIONS (issue #2413).
        // Don't do fuzzy matches if the literal segment was valid (#3211)
        bool allow_fuzzy = (this->flags & (EXPAND_FUZZY_MATCH | EXPAND_NO_FUZZY_DIRECTORIES)) ==
                           EXPAND_FUZZY_MATCH;
        if (allow_fuzzy && this->resolved_completions->size() == before &&
            waccess(intermediate_dirpath, F_OK) != 0) {
            assert(this->flags & EXPAND_FOR_COMPLETIONS);
            DIR *base_dir_fd = open_dir(base_dir);
            if (base_dir_fd != NULL) {
                this->expand_literal_intermediate_segment_with_fuzz(
                    base_dir, base_dir_fd, wc_segment, wc_remainder, effective_prefix);
                closedir(base_dir_fd);
            }
        }
    } else {
        assert(!wc_segment.empty() && (segment_has_wildcards || is_last_segment));
        DIR *dir = open_dir(base_dir);
        if (dir) {
            if (is_last_segment) {
                // Last wildcard segment, nonempty wildcard.
                this->expand_last_segment(base_dir, dir, wc_segment, effective_prefix);
            } else {
                // Not the last segment, nonempty wildcard.
                assert(next_slash != NULL);
                this->expand_intermediate_segment(base_dir, dir, wc_segment, wc_remainder,
                                                  effective_prefix + wc_segment + L'/');
            }

            // Recursive wildcards require special handling.
            size_t asr_idx = wc_segment.find(ANY_STRING_RECURSIVE);
            if (asr_idx != wcstring::npos) {
                // Construct a "head + any" wildcard for matching stuff in this directory, and an
                // "any + tail" wildcard for matching stuff in subdirectories. Note that the
                // ANY_STRING_RECURSIVE character is present in both the head and the tail.
                const wcstring head_any(wc_segment, 0, asr_idx + 1);
                const wchar_t *any_tail = wc + asr_idx;
                assert(head_any.at(head_any.size() - 1) == ANY_STRING_RECURSIVE);
                assert(any_tail[0] == ANY_STRING_RECURSIVE);

                rewinddir(dir);
                this->expand_intermediate_segment(base_dir, dir, head_any, any_tail,
                                                  effective_prefix);
            }
            closedir(dir);
        }
    }
}
Exemplo n.º 27
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;
}
Exemplo n.º 28
0
/// Cache the data path.
bool path_get_data(wcstring &path) {
    static const wcstring result = path_create_data();
    path = result;
    return !result.empty();
}
Exemplo n.º 29
0
/**
   Matches the string against the wildcard, and if the wildcard is a
   possible completion of the string, the remainder of the string is
   inserted into the out vector.
*/
static bool wildcard_complete_internal(const wcstring &orig, 
									   const wchar_t *str, 
									   const wchar_t *wc, 
									   bool is_first,
									   const wchar_t *desc,
									   wcstring (*desc_func)(const wcstring &),
									   std::vector<completion_t> &out,
									   int flags )
{
	if( !wc || ! str || orig.empty())
	{
		debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ );
		return 0;		
	}

	if( *wc == 0 &&
		( (str[0] != L'.') || (!is_first)) )
	{
		wcstring out_completion;
        wcstring out_desc = (desc ? desc : L"");

		if( flags & COMPLETE_NO_CASE )
		{
			out_completion = orig;
		}
		else
		{
			out_completion = str;
		}

        size_t complete_sep_loc = out_completion.find(PROG_COMPLETE_SEP);
        if (complete_sep_loc != wcstring::npos)
		{
			/* This completion has an embedded description, do not use the generic description */
            out_desc.assign(out_completion, complete_sep_loc + 1, out_completion.size() - complete_sep_loc - 1);
            out_completion.resize(complete_sep_loc);			
		}
		else
		{
			if( desc_func )
			{
				/*
				  A description generating function is specified, call
				  it. If it returns something, use that as the
				  description.
				*/
				wcstring func_desc = desc_func( orig );
				if (! func_desc.empty())
					out_desc = func_desc;
			}
			
		}
		
        /* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
        append_completion(out, out_completion, out_desc, flags);
		return true;
	}
	
	
	if( *wc == ANY_STRING )
	{		
		bool res=false;
		
		/* Ignore hidden file */
		if( is_first && str[0] == L'.' )
			return false;
		
		/* Try all submatches */
		do
		{
			res = wildcard_complete_internal( orig, str, wc+1, 0, desc, desc_func, out, flags );
			if (res)
				break;
		}
		while (*str++ != 0);
		return res;
		
	}
	else if( *wc == ANY_CHAR )
	{
		return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags );
	}	
	else if( *wc == *str )
	{
		return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags );
	}
	else if( towlower(*wc) == towlower(*str) )
	{
		return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags | COMPLETE_NO_CASE );
	}
	return false;	
}
Exemplo n.º 30
0
static bool string_could_be_path(const wcstring &potential_path) {
    // Assume that things with leading dashes aren't paths
    if (potential_path.empty() || potential_path.at(0) == L'-')
        return false;
    return true;
}