mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-21 02:23:36 +08:00
Support for tab cyling through completions
https://github.com/fish-shell/fish-shell/issues/84
This commit is contained in:
parent
3a940781d9
commit
04ea680e9a
15
complete.cpp
15
complete.cpp
@ -279,6 +279,21 @@ completion_t &completion_t::operator=(const completion_t &him)
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool completion_t::operator < (const completion_t& rhs) const
|
||||
{
|
||||
return this->completion < rhs.completion;
|
||||
}
|
||||
|
||||
bool completion_t::operator == (const completion_t& rhs) const
|
||||
{
|
||||
return this->completion == rhs.completion;
|
||||
}
|
||||
|
||||
bool completion_t::operator != (const completion_t& rhs) const
|
||||
{
|
||||
return ! (*this == rhs);
|
||||
}
|
||||
|
||||
wcstring_list_t completions_to_wcstring_list( const std::vector<completion_t> &list )
|
||||
{
|
||||
wcstring_list_t strings;
|
||||
|
@ -134,15 +134,17 @@ public:
|
||||
*/
|
||||
int flags;
|
||||
|
||||
bool is_case_insensitive() const { return !! (flags & COMPLETE_NO_CASE); }
|
||||
|
||||
/* Construction. Note: defining these so that they are not inlined reduces the executable size. */
|
||||
completion_t(const wcstring &comp, const wcstring &desc = L"", int flags_val = 0);
|
||||
completion_t(const completion_t &);
|
||||
completion_t &operator=(const completion_t &);
|
||||
|
||||
/* The following are needed for sorting and uniquing completions */
|
||||
bool operator < (const completion_t& rhs) const { return this->completion < rhs.completion; }
|
||||
bool operator == (const completion_t& rhs) const { return this->completion == rhs.completion; }
|
||||
bool operator != (const completion_t& rhs) const { return ! (*this == rhs); }
|
||||
bool operator < (const completion_t& rhs) const;
|
||||
bool operator == (const completion_t& rhs) const;
|
||||
bool operator != (const completion_t& rhs) const;
|
||||
};
|
||||
|
||||
enum complete_type_t {
|
||||
|
134
reader.cpp
134
reader.cpp
@ -1256,21 +1256,21 @@ static void reader_flash()
|
||||
|
||||
/**
|
||||
Check if the specified string can be replaced by a case insensitive
|
||||
complition with the specified flags.
|
||||
completion with the specified flags.
|
||||
|
||||
Advanced tokens like those containing {}-style expansion can not at
|
||||
the moment be replaced, other than if the new token is already an
|
||||
exact replacement, e.g. if the COMPLETE_DONT_ESCAPE flag is set.
|
||||
*/
|
||||
|
||||
static int reader_can_replace( const wcstring &in, int flags )
|
||||
static bool reader_can_replace( const wcstring &in, int flags )
|
||||
{
|
||||
|
||||
const wchar_t * str = in.c_str();
|
||||
|
||||
if( flags & COMPLETE_DONT_ESCAPE )
|
||||
{
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
Test characters that have a special meaning in any character position
|
||||
@ -1278,11 +1278,64 @@ static int reader_can_replace( const wcstring &in, int flags )
|
||||
while( *str )
|
||||
{
|
||||
if( wcschr( REPLACE_UNCLEAN, *str ) )
|
||||
return 0;
|
||||
return false;
|
||||
str++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Compare two completions, except make the case insensitive comes larger than everyone (so they come last) */
|
||||
bool case_sensitive_completion_compare(const completion_t &a, const completion_t &b)
|
||||
{
|
||||
if (a.is_case_insensitive() != b.is_case_insensitive())
|
||||
{
|
||||
/* Case insensitive ones come last. Exactly one of a, b is case insensitive. If it's a, return false, i.e. not less than, to make it appear at the end. */
|
||||
return ! a.is_case_insensitive();
|
||||
}
|
||||
/* Compare using file comparison */
|
||||
return wcsfilecmp(a.completion.c_str(), b.completion.c_str()) < 0;
|
||||
}
|
||||
|
||||
/* Order completions such that case insensitive completions come first. */
|
||||
static void prioritize_completions(std::vector<completion_t> &comp)
|
||||
{
|
||||
sort(comp.begin(), comp.end(), case_sensitive_completion_compare);
|
||||
}
|
||||
|
||||
/* Given a list of completions, get the completion at an index past *inout_idx, and then increment it. inout_idx should be initialized to (size_t)(-1) for the first call. */
|
||||
static const completion_t *cycle_competions(const std::vector<completion_t> &comp, const wcstring &command_line, size_t *inout_idx)
|
||||
{
|
||||
const size_t size = comp.size();
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
const size_t start_idx = *inout_idx;
|
||||
size_t idx = start_idx;
|
||||
const completion_t *result = NULL;
|
||||
for (;;)
|
||||
{
|
||||
/* Bump the index */
|
||||
idx = (idx + 1) % size;
|
||||
|
||||
/* Bail if we've looped */
|
||||
if (idx == start_idx)
|
||||
break;
|
||||
|
||||
/* Get the completion */
|
||||
const completion_t &c = comp.at(idx);
|
||||
|
||||
/* Try this completion */
|
||||
if (! c.is_case_insensitive() || reader_can_replace(command_line, c.flags))
|
||||
{
|
||||
/* Success */
|
||||
result = &c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*inout_idx = idx;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1298,14 +1351,16 @@ static int reader_can_replace( const wcstring &in, int flags )
|
||||
a common prefix, call run_pager to display a list of completions. Depending on terminal size and the length of the list, run_pager may either show less than a screenfull and exit or use an interactive pager to allow the user to scroll through the completions.
|
||||
|
||||
\param comp the list of completion strings
|
||||
|
||||
Return true if we inserted text into the command line, false if we did not.
|
||||
*/
|
||||
|
||||
|
||||
static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
{
|
||||
wchar_t *base = NULL;
|
||||
size_t len = 0;
|
||||
bool done = false;
|
||||
bool success = false;
|
||||
int count = 0;
|
||||
int flags=0;
|
||||
const wchar_t *begin, *end, *buff = data->command_line.c_str();
|
||||
@ -1325,6 +1380,7 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
{
|
||||
reader_flash();
|
||||
done = true;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1340,12 +1396,12 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
the token doesn't contain evil operators
|
||||
like {}
|
||||
*/
|
||||
if( !(c.flags & COMPLETE_NO_CASE) || reader_can_replace( tok, c.flags ) )
|
||||
if( ! c.is_case_insensitive() || reader_can_replace( tok, c.flags ) )
|
||||
{
|
||||
completion_insert( c.completion.c_str(), c.flags );
|
||||
}
|
||||
done = true;
|
||||
len = 1; // I think this just means it's a true return
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1353,13 +1409,13 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
|
||||
if( !done )
|
||||
{
|
||||
/* Try to find something to insert whith the correct case */
|
||||
/* Try to find something to insert with the correct case */
|
||||
for( size_t i=0; i< comp.size() ; i++ )
|
||||
{
|
||||
const completion_t &c = comp.at( i );
|
||||
|
||||
/* Ignore case insensitive completions for now */
|
||||
if( c.flags & COMPLETE_NO_CASE )
|
||||
if( c.is_case_insensitive() )
|
||||
continue;
|
||||
|
||||
count++;
|
||||
@ -1386,6 +1442,7 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
base[len]=L'\0';
|
||||
completion_insert(base, flags);
|
||||
done = true;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1396,7 +1453,6 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
/* Try to find something to insert ignoring case */
|
||||
if( begin )
|
||||
{
|
||||
|
||||
size_t offset = tok.size();
|
||||
|
||||
count = 0;
|
||||
@ -1405,7 +1461,7 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
{
|
||||
const completion_t &c = comp.at( i );
|
||||
|
||||
if( !(c.flags & COMPLETE_NO_CASE) )
|
||||
if( ! c.is_case_insensitive() )
|
||||
continue;
|
||||
|
||||
if( !reader_can_replace( tok, c.flags ) )
|
||||
@ -1438,6 +1494,7 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
base[len]=L'\0';
|
||||
completion_insert( base, flags );
|
||||
done = 1;
|
||||
success = true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1482,9 +1539,9 @@ static bool handle_completions( const std::vector<completion_t> &comp )
|
||||
}
|
||||
s_reset( &data->screen, true);
|
||||
reader_repaint();
|
||||
|
||||
success = false;
|
||||
}
|
||||
return len > 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@ -2499,10 +2556,17 @@ const wchar_t *reader_readline()
|
||||
int last_char=0;
|
||||
size_t yank_len=0;
|
||||
const wchar_t *yank_str;
|
||||
std::vector<completion_t> comp;
|
||||
bool comp_empty = true;
|
||||
std::vector<completion_t> comp;
|
||||
int finished=0;
|
||||
struct termios old_modes;
|
||||
|
||||
/* The cycle index in our completion list */
|
||||
size_t completion_cycle_idx = (size_t)(-1);
|
||||
|
||||
/* The command line before completion */
|
||||
wcstring cycle_command_line;
|
||||
size_t cycle_cursor_pos;
|
||||
|
||||
data->search_buff.clear();
|
||||
data->search_mode = NO_SEARCH;
|
||||
@ -2577,13 +2641,7 @@ const wchar_t *reader_readline()
|
||||
if( c != 0 )
|
||||
break;
|
||||
}
|
||||
/*
|
||||
if( (last_char == R_COMPLETE) && (c != R_COMPLETE) && (!comp_empty) )
|
||||
{
|
||||
halloc_destroy( comp );
|
||||
comp = 0;
|
||||
}
|
||||
*/
|
||||
|
||||
if( last_char != R_YANK && last_char != R_YANK_POP )
|
||||
yank_len=0;
|
||||
const wchar_t *buff = data->command_line.c_str();
|
||||
@ -2662,13 +2720,31 @@ const wchar_t *reader_readline()
|
||||
if( !data->complete_func )
|
||||
break;
|
||||
|
||||
if( comp_empty || last_char != R_COMPLETE)
|
||||
if (! comp_empty && last_char == R_COMPLETE)
|
||||
{
|
||||
/* The user typed R_COMPLETE more than once in a row. Cycle through our available completions */
|
||||
const completion_t *next_comp = cycle_competions(comp, cycle_command_line, &completion_cycle_idx);
|
||||
if (next_comp != NULL)
|
||||
{
|
||||
size_t cursor_pos = cycle_cursor_pos;
|
||||
const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos);
|
||||
reader_set_buffer(new_cmd_line, cursor_pos);
|
||||
|
||||
/* Since we just inserted a completion, don't immediately do a new autosuggestion */
|
||||
data->suppress_autosuggestion = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Either the user hit tab only once, or we had no visible completion list. */
|
||||
const wchar_t *begin, *end;
|
||||
const wchar_t *token_begin, *token_end;
|
||||
const wchar_t *buff = data->command_line.c_str();
|
||||
long cursor_steps;
|
||||
|
||||
/* Clear the completion list */
|
||||
comp.clear();
|
||||
|
||||
parse_util_cmdsubst_extent( buff, data->buff_pos, &begin, &end );
|
||||
|
||||
parse_util_token_extent( begin, data->buff_pos - (begin-buff), &token_begin, &token_end, 0, 0 );
|
||||
@ -2687,11 +2763,19 @@ const wchar_t *reader_readline()
|
||||
|
||||
data->complete_func( buffcpy, comp, COMPLETE_DEFAULT, NULL);
|
||||
|
||||
/* Munge our completions */
|
||||
sort(comp.begin(), comp.end());
|
||||
remove_duplicates( comp );
|
||||
|
||||
prioritize_completions(comp);
|
||||
|
||||
/* Record our cycle_command_line */
|
||||
cycle_command_line = data->command_line;
|
||||
cycle_cursor_pos = data->buff_pos;
|
||||
|
||||
comp_empty = handle_completions( comp );
|
||||
comp.clear();
|
||||
|
||||
/* Start the cycle at the beginning */
|
||||
completion_cycle_idx = (size_t)(-1);
|
||||
}
|
||||
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user