mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Some work towards refactoring how completions are inserted to support escaping in autosuggestions
This commit is contained in:
parent
fe7fa46d57
commit
47019e315a
1 changed files with 176 additions and 36 deletions
212
reader.cpp
212
reader.cpp
|
@ -771,7 +771,7 @@ static int insert_string(const wcstring &str)
|
||||||
*/
|
*/
|
||||||
static int insert_char( wchar_t c )
|
static int insert_char( wchar_t c )
|
||||||
{
|
{
|
||||||
return insert_string( wcstring(1, c) );
|
return insert_string(wcstring(&c, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -926,6 +926,135 @@ static void get_param( const wchar_t *cmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Insert the string in the given command line at the given cursor
|
||||||
|
position. The function checks if the string is quoted or not and
|
||||||
|
correctly escapes the string.
|
||||||
|
\param val the string to insert
|
||||||
|
\param flags A union of all flags describing the completion to insert. See the completion_t struct for more information on possible values.
|
||||||
|
\param command_line The command line into which we will insert
|
||||||
|
\param inout_cursor_pos On input, the location of the cursor within the command line. On output, the new desired position.
|
||||||
|
\return The completed string
|
||||||
|
*/
|
||||||
|
static wcstring completion_insert_helper(const wcstring &val_str, int flags, const wcstring &command_line, size_t *inout_cursor_pos)
|
||||||
|
{
|
||||||
|
wchar_t *replaced;
|
||||||
|
const wchar_t *val = val_str.c_str();
|
||||||
|
bool add_space = !(flags & COMPLETE_NO_SPACE);
|
||||||
|
bool do_replace = !!(flags & COMPLETE_NO_CASE);
|
||||||
|
bool do_escape = !(flags & COMPLETE_DONT_ESCAPE);
|
||||||
|
const size_t cursor_pos = *inout_cursor_pos;
|
||||||
|
|
||||||
|
// debug( 0, L"Insert completion %ls with flags %d", val, flags);
|
||||||
|
|
||||||
|
if( do_replace )
|
||||||
|
{
|
||||||
|
|
||||||
|
int move_cursor;
|
||||||
|
const wchar_t *begin, *end;
|
||||||
|
wchar_t *escaped;
|
||||||
|
|
||||||
|
const wchar_t *buff = command_line.c_str();
|
||||||
|
parse_util_token_extent( buff, cursor_pos, &begin, 0, 0, 0 );
|
||||||
|
end = buff + cursor_pos;
|
||||||
|
|
||||||
|
wcstring sb(buff, begin - buff);
|
||||||
|
|
||||||
|
if( do_escape )
|
||||||
|
{
|
||||||
|
escaped = escape( val, ESCAPE_ALL | ESCAPE_NO_QUOTED );
|
||||||
|
sb.append( escaped );
|
||||||
|
move_cursor = wcslen(escaped);
|
||||||
|
free( escaped );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append( val );
|
||||||
|
move_cursor = wcslen(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if( add_space )
|
||||||
|
{
|
||||||
|
sb.append( L" " );
|
||||||
|
move_cursor += 1;
|
||||||
|
}
|
||||||
|
sb.append( end );
|
||||||
|
|
||||||
|
size_t new_cursor_pos = (begin - buff) + move_cursor;
|
||||||
|
*inout_cursor_pos = new_cursor_pos;
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wchar_t quote = L'\0';
|
||||||
|
if( do_escape )
|
||||||
|
{
|
||||||
|
get_param(command_line.c_str(), cursor_pos, "e, 0, 0, 0);
|
||||||
|
if( quote == L'\0' )
|
||||||
|
{
|
||||||
|
replaced = escape( val, ESCAPE_ALL | ESCAPE_NO_QUOTED );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool unescapable = false;
|
||||||
|
|
||||||
|
const wchar_t *pin;
|
||||||
|
wchar_t *pout;
|
||||||
|
|
||||||
|
replaced = pout = (wchar_t *)malloc( sizeof(wchar_t)*(wcslen(val) + 1) );
|
||||||
|
|
||||||
|
for( pin=val; *pin; pin++ )
|
||||||
|
{
|
||||||
|
switch( *pin )
|
||||||
|
{
|
||||||
|
case L'\n':
|
||||||
|
case L'\t':
|
||||||
|
case L'\b':
|
||||||
|
case L'\r':
|
||||||
|
unescapable = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
*pout++ = *pin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unescapable)
|
||||||
|
{
|
||||||
|
free( replaced );
|
||||||
|
wchar_t *tmp = escape( val, ESCAPE_ALL | ESCAPE_NO_QUOTED );
|
||||||
|
replaced = wcsdupcat( L" ", tmp );
|
||||||
|
free( tmp);
|
||||||
|
replaced[0]=quote;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*pout = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
replaced = wcsdup(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
wcstring result = command_line;
|
||||||
|
result.insert(cursor_pos, replaced);
|
||||||
|
size_t new_cursor_pos = cursor_pos + wcslen(replaced);
|
||||||
|
if (add_space)
|
||||||
|
{
|
||||||
|
if (quote && (command_line.c_str()[cursor_pos] != quote))
|
||||||
|
{
|
||||||
|
/* This is a quoted parameter, first print a quote */
|
||||||
|
result.insert(new_cursor_pos++, wcstring("e, 1));
|
||||||
|
}
|
||||||
|
result.insert(new_cursor_pos++, L" ");
|
||||||
|
}
|
||||||
|
free(replaced);
|
||||||
|
*inout_cursor_pos = new_cursor_pos;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Insert the string at the current cursor position. The function
|
Insert the string at the current cursor position. The function
|
||||||
checks if the string is quoted or not and correctly escapes the
|
checks if the string is quoted or not and correctly escapes the
|
||||||
|
@ -936,6 +1065,16 @@ static void get_param( const wchar_t *cmd,
|
||||||
|
|
||||||
*/
|
*/
|
||||||
static void completion_insert( const wchar_t *val, int flags )
|
static void completion_insert( const wchar_t *val, int flags )
|
||||||
|
{
|
||||||
|
size_t cursor = data->buff_pos;
|
||||||
|
wcstring new_command_line = completion_insert_helper(val, flags, data->command_line, &cursor);
|
||||||
|
reader_set_buffer(new_command_line, cursor);
|
||||||
|
|
||||||
|
/* Since we just inserted a completion, don't immediately do a new autosuggestion */
|
||||||
|
data->suppress_autosuggestion = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void completion_insert_old( const wchar_t *val, int flags )
|
||||||
{
|
{
|
||||||
assert(data != NULL);
|
assert(data != NULL);
|
||||||
|
|
||||||
|
@ -1070,8 +1209,7 @@ static void completion_insert( const wchar_t *val, int flags )
|
||||||
|
|
||||||
free(replaced);
|
free(replaced);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1224,6 +1362,7 @@ static void run_pager( wchar_t *prefix, int is_quoted, const std::vector<complet
|
||||||
struct autosuggestion_context_t {
|
struct autosuggestion_context_t {
|
||||||
wcstring search_string;
|
wcstring search_string;
|
||||||
wcstring autosuggestion;
|
wcstring autosuggestion;
|
||||||
|
size_t cursor_pos;
|
||||||
history_search_t searcher;
|
history_search_t searcher;
|
||||||
file_detection_context_t detector;
|
file_detection_context_t detector;
|
||||||
const wcstring working_directory;
|
const wcstring working_directory;
|
||||||
|
@ -1234,8 +1373,9 @@ struct autosuggestion_context_t {
|
||||||
// don't reload more than once
|
// don't reload more than once
|
||||||
bool has_tried_reloading;
|
bool has_tried_reloading;
|
||||||
|
|
||||||
autosuggestion_context_t(history_t *history, const wcstring &term) :
|
autosuggestion_context_t(history_t *history, const wcstring &term, size_t pos) :
|
||||||
search_string(term),
|
search_string(term),
|
||||||
|
cursor_pos(pos),
|
||||||
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
|
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
|
||||||
detector(history, term),
|
detector(history, term),
|
||||||
working_directory(get_working_directory()),
|
working_directory(get_working_directory()),
|
||||||
|
@ -1254,6 +1394,11 @@ struct autosuggestion_context_t {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Let's make sure we aren't using the empty string */
|
||||||
|
if (search_string.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
while (searcher.go_backwards()) {
|
while (searcher.go_backwards()) {
|
||||||
history_item_t item = searcher.current_item();
|
history_item_t item = searcher.current_item();
|
||||||
|
|
||||||
|
@ -1287,6 +1432,15 @@ struct autosuggestion_context_t {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we do something a little funny
|
||||||
|
// If the line ends with a space, and the cursor is not at the end,
|
||||||
|
// Don't use completion autosuggestions. It ends up being pretty weird seeing stuff get spammed on the right
|
||||||
|
// While you go back to edit a line
|
||||||
|
const bool line_ends_with_space = iswspace(search_string.at(search_string.size() - 1));
|
||||||
|
const bool cursor_at_end = (this->cursor_pos == search_string.size());
|
||||||
|
if (line_ends_with_space && ! cursor_at_end)
|
||||||
|
return 0;
|
||||||
|
|
||||||
/* Try normal completions */
|
/* Try normal completions */
|
||||||
std::vector<completion_t> completions;
|
std::vector<completion_t> completions;
|
||||||
complete(search_string, completions, COMPLETE_AUTOSUGGEST, &this->commands_to_load);
|
complete(search_string, completions, COMPLETE_AUTOSUGGEST, &this->commands_to_load);
|
||||||
|
@ -1372,7 +1526,7 @@ static void update_autosuggestion(void) {
|
||||||
#else
|
#else
|
||||||
data->autosuggestion.clear();
|
data->autosuggestion.clear();
|
||||||
if (! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) {
|
if (! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) {
|
||||||
autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_line);
|
autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_line, data->buff_pos);
|
||||||
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
|
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1456,11 +1610,11 @@ static int reader_can_replace( const wcstring &in, int flags )
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
static int handle_completions( std::vector<completion_t> &comp )
|
static int handle_completions( const std::vector<completion_t> &comp )
|
||||||
{
|
{
|
||||||
wchar_t *base = 0;
|
wchar_t *base = NULL;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
int done = 0;
|
bool done = false;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int flags=0;
|
int flags=0;
|
||||||
const wchar_t *begin, *end, *buff = data->command_line.c_str();
|
const wchar_t *begin, *end, *buff = data->command_line.c_str();
|
||||||
|
@ -1473,21 +1627,17 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
/*
|
/*
|
||||||
Check trivial cases
|
Check trivial cases
|
||||||
*/
|
*/
|
||||||
switch( comp.size() )
|
switch(comp.size())
|
||||||
{
|
{
|
||||||
/*
|
/* No suitable completions found, flash screen and return */
|
||||||
No suitable completions found, flash screen and retur
|
|
||||||
*/
|
|
||||||
case 0:
|
case 0:
|
||||||
{
|
{
|
||||||
reader_flash();
|
reader_flash();
|
||||||
done = 1;
|
done = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Exactly one suitable completion found - insert it */
|
||||||
Exactly one suitable completion found - insert it
|
|
||||||
*/
|
|
||||||
case 1:
|
case 1:
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -1503,8 +1653,8 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
{
|
{
|
||||||
completion_insert( c.completion.c_str(), c.flags );
|
completion_insert( c.completion.c_str(), c.flags );
|
||||||
}
|
}
|
||||||
done = 1;
|
done = true;
|
||||||
len = 1;
|
len = 1; // I think this just means it's a true return
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1512,17 +1662,12 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
|
|
||||||
if( !done )
|
if( !done )
|
||||||
{
|
{
|
||||||
/*
|
/* Try to find something to insert whith the correct case */
|
||||||
Try to find something to insert whith the correct case
|
|
||||||
*/
|
|
||||||
for( size_t i=0; i< comp.size() ; i++ )
|
for( size_t i=0; i< comp.size() ; i++ )
|
||||||
{
|
{
|
||||||
const completion_t &c = comp.at( i );
|
const completion_t &c = comp.at( i );
|
||||||
int new_len;
|
|
||||||
|
|
||||||
/*
|
/* Ignore case insensitive completions for now */
|
||||||
Ignore case insensitive completions for now
|
|
||||||
*/
|
|
||||||
if( c.flags & COMPLETE_NO_CASE )
|
if( c.flags & COMPLETE_NO_CASE )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -1530,8 +1675,8 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
|
|
||||||
if( base )
|
if( base )
|
||||||
{
|
{
|
||||||
new_len = comp_len( base, c.completion.c_str() );
|
int new_len = comp_len( base, c.completion.c_str() );
|
||||||
len = new_len < len ? new_len: len;
|
len = mini(new_len, len);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1541,9 +1686,7 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* If we found something to insert, do it. */
|
||||||
If we found something to insert, do it.
|
|
||||||
*/
|
|
||||||
if( len > 0 )
|
if( len > 0 )
|
||||||
{
|
{
|
||||||
if( count > 1 )
|
if( count > 1 )
|
||||||
|
@ -1551,18 +1694,15 @@ static int handle_completions( std::vector<completion_t> &comp )
|
||||||
|
|
||||||
base[len]=L'\0';
|
base[len]=L'\0';
|
||||||
completion_insert(base, flags);
|
completion_insert(base, flags);
|
||||||
done = 1;
|
done = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if( !done && base == 0 )
|
if( !done && base == NULL )
|
||||||
{
|
{
|
||||||
/*
|
/* Try to find something to insert ignoring case */
|
||||||
Try to find something to insert ignoring case
|
|
||||||
*/
|
|
||||||
|
|
||||||
if( begin )
|
if( begin )
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue