Пример #1
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.
 */
static int wildcard_expand_internal(const wchar_t *wc,
                                    const wchar_t * const base_dir,
                                    expand_flags_t flags,
                                    std::vector<completion_t> *out,
                                    std::set<wcstring> &completion_set,
                                    std::set<file_id_t> &visited_files)
{
    /* Variables for traversing a directory */
    DIR *dir;

    /* The result returned */
    int res = 0;


    //  debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );

    if (is_main_thread() ? reader_interrupted() : reader_thread_job_is_stale())
    {
        return -1;
    }

    if (!wc || !base_dir)
    {
        debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__);
        return 0;
    }

    const size_t base_dir_len = wcslen(base_dir);
    const size_t wc_len = wcslen(wc);

    if (flags & ACCEPT_INCOMPLETE)
    {
        /*
           Avoid excessive number of returned matches for wc ending with a *
        */
        if (wc_len > 0 && (wc[wc_len-1]==ANY_STRING))
        {
            wchar_t * foo = wcsdup(wc);
            foo[wc_len-1]=0;
            int res = wildcard_expand_internal(foo, base_dir, flags, out, completion_set, visited_files);
            free(foo);
            return res;
        }
    }

    /* Determine if we are the last segment */
    const wchar_t * const next_slash = 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);
    
    /* Maybe this segment has no wildcards at all. If this is not the last segment, and it has no wildcards, then we don't need to match against the directory contents, and in fact we don't want to match since we may not be able to read it anyways (#2099). Don't even open the directory! */
    const bool segment_has_wildcards = wildcard_has(wc_segment, true /* internal, i.e. look for ANY_CHAR instead of ? */);
    if (! segment_has_wildcards && ! is_last_segment)
    {
        wcstring new_base_dir = make_path(base_dir, wc_segment);
        new_base_dir.push_back(L'/');
        
        /* Skip multiple separators */
        assert(next_slash != NULL);
        const wchar_t *new_wc = next_slash;
        while (*new_wc==L'/')
        {
            new_wc++;
        }
        /* Early out! */
        return wildcard_expand_internal(new_wc, new_base_dir.c_str(), flags, out, completion_set, visited_files);
    }
    
    /* Test for recursive match string in current segment */
    const bool is_recursive = (wc_segment.find(ANY_STRING_RECURSIVE) != wcstring::npos);
    

    const wchar_t *base_dir_or_cwd = (base_dir[0] == L'\0') ? L"." : base_dir;
    if (!(dir = wopendir(base_dir_or_cwd)))
    {
        return 0;
    }
    
    /*
      Is this segment of the wildcard the last?
    */
    if (is_last_segment)
    {
        /*
          Wildcard segment is the last segment,

          Insert all matching files/directories
        */
        if (wc[0]=='\0')
        {
            /*
              The last wildcard segment is empty. Insert everything if
              completing, the directory itself otherwise.
            */
            if (flags & ACCEPT_INCOMPLETE)
            {
                wcstring next;
                while (wreaddir(dir, next))
                {
                    if (next[0] != L'.')
                    {
                        wcstring long_name = make_path(base_dir, next);

                        if (test_flags(long_name.c_str(), flags))
                        {
                            wildcard_completion_allocate(out, long_name, next, L"", flags);
                        }
                    }
                }
            }
            else
            {
                res = 1;
                insert_completion_if_missing(base_dir, out, &completion_set);
            }
        }
        else
        {
            /* This is the last wildcard segment, and it is not empty. Match files/directories. */
            wcstring name_str;
            while (wreaddir(dir, name_str))
            {
                if (flags & ACCEPT_INCOMPLETE)
                {

                    const wcstring long_name = make_path(base_dir, name_str);

                    /* Test for matches before stating file, so as to minimize the number of calls to the much slower stat function. The only expand flag we care about is EXPAND_FUZZY_MATCH; we have no complete flags. */
                    std::vector<completion_t> test;
                    if (wildcard_complete(name_str, wc, L"", NULL, &test, flags & EXPAND_FUZZY_MATCH, 0))
                    {
                        if (test_flags(long_name.c_str(), flags))
                        {
                            wildcard_completion_allocate(out, long_name, name_str, wc, flags);

                        }
                    }
                }
                else
                {
                    if (wildcard_match(name_str, wc, true /* skip files with leading dots */))
                    {
                        const wcstring long_name = make_path(base_dir, name_str);
                        int skip = 0;

                        if (is_recursive)
                        {
                            /*
                              In recursive mode, we are only
                              interested in adding files -directories
                              will be added in the next pass.
                            */
                            struct stat buf;
                            if (!wstat(long_name, &buf))
                            {
                                skip = S_ISDIR(buf.st_mode);
                            }
                        }
                        if (! skip)
                        {
                            insert_completion_if_missing(long_name, out, &completion_set);
                        }
                        res = 1;
                    }
                }
            }
        }
    }

    if ((! is_last_segment) || is_recursive)
    {
        /*
          Wilcard segment is not the last segment.  Recursively call
          wildcard_expand for all matching subdirectories.
        */

        /*
          In recursive mode, we look through the directory twice. If
          so, this rewind is needed.
        */
        rewinddir(dir);

        /* new_dir is a scratch area containing the full path to a file/directory we are iterating over */
        wcstring new_dir = base_dir;

        wcstring name_str;
        while (wreaddir(dir, name_str))
        {
            /*
              Test if the file/directory name matches the whole
              wildcard element, i.e. regular matching.
            */
            bool whole_match = wildcard_match(name_str, wc_segment, true /* ignore leading dots */);
            bool partial_match = false;

            /*
               If we are doing recursive matching, also check if this
               directory matches the part up to the recusrive
               wildcard, if so, then we can search all subdirectories
               for matches.
            */
            if (is_recursive)
            {
                const wchar_t *end = wcschr(wc, ANY_STRING_RECURSIVE);
                wchar_t *wc_sub = wcsndup(wc, end-wc+1);
                partial_match = wildcard_match(name_str, wc_sub, true /* ignore leading dots */);
                free(wc_sub);
            }

            if (whole_match || partial_match)
            {
                struct stat buf;
                int new_res;

                // new_dir is base_dir + some other path components
                // Replace everything after base_dir with the new path component
                new_dir.replace(base_dir_len, wcstring::npos, name_str);

                int stat_res = wstat(new_dir, &buf);

                if (!stat_res)
                {
                    // Insert a "file ID" into visited_files
                    // If the insertion fails, we've already visited this file (i.e. a symlink loop)
                    // If we're not recursive, insert anyways (in case we loop back around in a future recursive segment), but continue on; the idea being that literal path components should still work
                    const file_id_t file_id = file_id_t::file_id_from_stat(&buf);
                    if (S_ISDIR(buf.st_mode) && (visited_files.insert(file_id).second || ! is_recursive))
                    {
                        new_dir.push_back(L'/');

                        /*
                          Regular matching
                        */
                        if (whole_match)
                        {
                            const wchar_t *new_wc = L"";
                            if (next_slash)
                            {
                                new_wc=next_slash+1;
                                /*
                                  Accept multiple '/' as a single directory separator
                                */
                                while (*new_wc==L'/')
                                {
                                    new_wc++;
                                }
                            }

                            new_res = wildcard_expand_internal(new_wc,
                                                               new_dir.c_str(),
                                                               flags,
                                                               out,
                                                               completion_set,
                                                               visited_files);

                            if (new_res == -1)
                            {
                                res = -1;
                                break;
                            }
                            res |= new_res;

                        }

                        /*
                          Recursive matching
                        */
                        if (partial_match)
                        {

                            new_res = wildcard_expand_internal(wcschr(wc, ANY_STRING_RECURSIVE),
                                                               new_dir.c_str(),
                                                               flags | WILDCARD_RECURSIVE,
                                                               out,
                                                               completion_set,
                                                               visited_files);

                            if (new_res == -1)
                            {
                                res = -1;
                                break;
                            }
                            res |= new_res;

                        }
                    }
                }
            }
        }
    }
    closedir(dir);

    return res;
}
Пример #2
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;
}
Пример #3
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);
        }
    }
}
Пример #4
0
int expand_string(const wcstring &input, std::vector<completion_t> &output, expand_flags_t flags)
{
    parser_t parser(PARSER_TYPE_ERRORS_ONLY, true /* show errors */);
    std::vector<completion_t> list1, list2;
    std::vector<completion_t> *in, *out;

    size_t i;
    int res = EXPAND_OK;

    if ((!(flags & ACCEPT_INCOMPLETE)) && expand_is_clean(input.c_str()))
    {
        output.push_back(completion_t(input));
        return EXPAND_OK;
    }

    if (EXPAND_SKIP_CMDSUBST & flags)
    {
        wchar_t *begin, *end;

        if (parse_util_locate_cmdsubst(input.c_str(),
                                       &begin,
                                       &end,
                                       1) != 0)
        {
            parser.error(CMDSUBST_ERROR, -1, L"Command substitutions not allowed");
            return EXPAND_ERROR;
        }
        list1.push_back(completion_t(input));
    }
    else
    {
        int cmdsubst_ok = expand_cmdsubst(parser, input, list1);
        if (! cmdsubst_ok)
            return EXPAND_ERROR;
    }

    in = &list1;
    out = &list2;

    for (i=0; i < in->size(); i++)
    {
        /*
         We accept incomplete strings here, since complete uses
         expand_string to expand incomplete strings from the
         commandline.
         */
        int unescape_flags = UNESCAPE_SPECIAL | UNESCAPE_INCOMPLETE;
        wcstring next = expand_unescape_string(in->at(i).completion, unescape_flags);

        if (EXPAND_SKIP_VARIABLES & flags)
        {
            for (size_t i=0; i < next.size(); i++)
            {
                if (next.at(i) == VARIABLE_EXPAND)
                {
                    next[i] = L'$';
                }
            }
            out->push_back(completion_t(next));
        }
        else
        {
            if (!expand_variables2(parser, next, *out, next.size() - 1))
            {
                return EXPAND_ERROR;
            }
        }
    }

    in->clear();

    in = &list2;
    out = &list1;

    for (i=0; i < in->size(); i++)
    {
        wcstring next = in->at(i).completion;

        if (!expand_brackets(parser, next, flags, *out))
        {
            return EXPAND_ERROR;
        }
    }
    in->clear();

    in = &list1;
    out = &list2;

    for (i=0; i < in->size(); i++)
    {
        wcstring next = in->at(i).completion;

        expand_home_directory(next);


        if (flags & ACCEPT_INCOMPLETE)
        {
            if (next[0] == PROCESS_EXPAND)
            {
                /*
                 If process expansion matches, we are not
                 interested in other completions, so we
                 short-circut and return
                 */
                if (!(flags & EXPAND_SKIP_PROCESS))
                    expand_pid(next, flags, output);
                return EXPAND_OK;
            }
            else
            {
                out->push_back(completion_t(next));
            }
        }
        else
        {
            if (!(flags & EXPAND_SKIP_PROCESS) && ! expand_pid(next, flags, *out))
            {
                return EXPAND_ERROR;
            }
        }
    }

    in->clear();

    in = &list2;
    out = &list1;

    for (i=0; i < in->size(); i++)
    {
        wcstring next_str = in->at(i).completion;
        int wc_res;

        remove_internal_separator(next_str, (EXPAND_SKIP_WILDCARDS & flags) ? true : false);
        const wchar_t *next = next_str.c_str();

        if (((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
                wildcard_has(next, 1))
        {
            const wchar_t *start, *rest;
            std::vector<completion_t> *list = out;

            if (next[0] == '/')
            {
                start = L"/";
                rest = &next[1];
            }
            else
            {
                start = L"";
                rest = next;
            }

            if (flags & ACCEPT_INCOMPLETE)
            {
                list = &output;
            }

            wc_res = wildcard_expand_string(rest, start, flags, *list);

            if (!(flags & ACCEPT_INCOMPLETE))
            {

                switch (wc_res)
                {
                    case 0:
                    {
                        if (!(flags & ACCEPT_INCOMPLETE))
                        {
                            if (res == EXPAND_OK)
                                res = EXPAND_WILDCARD_NO_MATCH;
                            break;
                        }
                    }

                    case 1:
                    {
                        size_t j;
                        res = EXPAND_WILDCARD_MATCH;
                        sort_completions(*out);

                        for (j=0; j< out->size(); j++)
                        {
                            output.push_back(out->at(j));
                        }
                        out->clear();
                        break;
                    }

                    case -1:
                    {
                        return EXPAND_ERROR;
                    }

                }
            }

        }
        else
        {
            if (flags & ACCEPT_INCOMPLETE)
            {
            }
            else
            {
                output.push_back(completion_t(next));
            }
        }

    }

    return res;
}
Пример #5
0
/**
   If command to complete is short enough, substitute
   the description with the whatis information for the executable.
*/
static void complete_cmd_desc( const wchar_t *cmd, array_list_t *comp )
{
	int i;
	const wchar_t *cmd_start;
	int cmd_len;
	wchar_t *lookup_cmd=0;
	array_list_t list;
	hash_table_t lookup;
	wchar_t *esc;
	int skip;

	if( !cmd )
		return;

	cmd_start=wcsrchr(cmd, L'/');

	if( cmd_start )
		cmd_start++;
	else
		cmd_start = cmd;

	cmd_len = wcslen(cmd_start);

	/*
	  Using apropos with a single-character search term produces far
	  to many results - require at least two characters if we don't
	  know the location of the whatis-database.
	*/
	if(cmd_len < 2 )
		return;

	if( wildcard_has( cmd_start, 0 ) )
	{
		return;
	}

	skip = 1;

	for( i=0; i<al_get_count( comp ); i++ )
	{
		completion_t *c = (completion_t *)al_get( comp, i );

		if( !wcslen( c->completion) || (c->completion[wcslen(c->completion)-1] != L'/' ))
		{
			skip = 0;
			break;
		}

	}

	if( skip )
	{
		return;
	}


	esc = escape( cmd_start, 1 );

	lookup_cmd = wcsdupcat( L"__fish_describe_command ", esc );
	free(esc);

	al_init( &list );
	hash_init( &lookup, &hash_wcs_func, &hash_wcs_cmp );


	/*
	  First locate a list of possible descriptions using a single
	  call to apropos or a direct search if we know the location
	  of the whatis database. This can take some time on slower
	  systems with a large set of manuals, but it should be ok
	  since apropos is only called once.
	*/
	if( exec_subshell( lookup_cmd, &list ) != -1 )
	{

		/*
		  Then discard anything that is not a possible completion and put
		  the result into a hashtable with the completion as key and the
		  description as value.

		  Should be reasonably fast, since no memory allocations are needed.
		*/
		for( i=0; i<al_get_count( &list); i++ )
		{
			wchar_t *el = (wchar_t *)al_get( &list, i );
			wchar_t *key, *key_end, *val_begin;

			if( !el )
				continue;

			key = el+wcslen(cmd_start);
			key_end = wcschr( key, L'\t' );

			if( !key_end )
				continue;

			*key_end = 0;
			val_begin = key_end+1;

			/*
			  And once again I make sure the first character is uppercased
			  because I like it that way, and I get to decide these
			  things.
			*/
			val_begin[0]=towupper(val_begin[0]);

			hash_put( &lookup, key, val_begin );
		}

		/*
		  Then do a lookup on every completion and if a match is found,
		  change to the new description.

		  This needs to do a reallocation for every description added, but
		  there shouldn't be that many completions, so it should be ok.
		*/
		for( i=0; i<al_get_count(comp); i++ )
		{
			completion_t *c = (completion_t *)al_get( comp, i );
			const wchar_t *el = c->completion;

			wchar_t *new_desc;

			new_desc = (wchar_t *)hash_get( &lookup,
											el );

			if( new_desc )
			{
				c->description = halloc_wcsdup( comp, new_desc );
			}
		}
	}

	hash_destroy( &lookup );
	al_foreach( &list,
				&free );
	al_destroy( &list );
	free( lookup_cmd );
}