"complete" command and completion word break characters

The linespec/locations/completer testcase added later in the series
tests every completion with both TAB completion and the "complete"
command.  This exposed problems in the "complete" command, around
determining the completion word point.

First, the complete command has a too-simple approximation of what
readline's TAB-completion code does to find the completion word point.
Unfortunately, readline doesn't expose the functionality it uses
internally, so to fix this this patch copies over the relevant code,
and adjusts it a bit to better fit the use cases we need it for.
(Specifically, our version avoids relying on the
rl_word_break_characters, etc. globals, and instead takes those as
arguments.)

A following patch will want to use this function for TAB-completion
too, but the "complete" command was a good excuse to split this to a
separate patch.

Then, notice how the complete_command does not call into the completer
for the command being completed to determine the right set of word
break characters.  It always uses the default set.  That is fixed by
having the "complete" command call into complete_line_internal for a
full handle_brkchars phase, just TAB-completion.

gdb/ChangeLog:
2017-07-17  Pedro Alves  <palves@redhat.com>

	* cli/cli-cmds.c (complete_command): Use a completion tracker
	along with completion_find_completion_word for handle_brkchars
	phase.
	* completer.c (RL_QF_SINGLE_QUOTE, RL_QF_DOUBLE_QUOTE)
	(RL_QF_BACKSLASH, RL_QF_OTHER_QUOTE): New.
	(struct gdb_rl_completion_word_info): New.
	(gdb_rl_find_completion_word): New.
	(completion_find_completion_word): New.
	* completer.h (completion_find_completion_word): Declare.
This commit is contained in:
Pedro Alves 2017-07-17 15:30:59 +01:00
parent eb3ff9a551
commit 6a2c1b8790
4 changed files with 210 additions and 19 deletions

View File

@ -1,3 +1,15 @@
2017-07-17 Pedro Alves <palves@redhat.com>
* cli/cli-cmds.c (complete_command): Use a completion tracker
along with completion_find_completion_word for handle_brkchars
phase.
* completer.c (RL_QF_SINGLE_QUOTE, RL_QF_DOUBLE_QUOTE)
(RL_QF_BACKSLASH, RL_QF_OTHER_QUOTE): New.
(struct gdb_rl_completion_word_info): New.
(gdb_rl_find_completion_word): New.
(completion_find_completion_word): New.
* completer.h (completion_find_completion_word): Declare.
2017-07-17 Pedro Alves <palves@redhat.com>
* ada-lang.c (symbol_completion_match): Adjust comments.

View File

@ -246,7 +246,6 @@ static void
complete_command (char *arg_entry, int from_tty)
{
const char *arg = arg_entry;
int argpoint;
dont_repeat ();
@ -264,36 +263,31 @@ complete_command (char *arg_entry, int from_tty)
if (arg == NULL)
arg = "";
argpoint = strlen (arg);
/* complete_line assumes that its first argument is somewhere
within, and except for filenames at the beginning of, the word to
be completed. The following crude imitation of readline's
word-breaking tries to accomodate this. */
const char *point = arg + argpoint;
while (point > arg)
{
if (strchr (rl_completer_word_break_characters, point[-1]) != 0)
break;
point--;
}
completion_tracker tracker_handle_brkchars;
completion_tracker tracker_handle_completions;
int quote_char = '\0';
const char *word;
TRY
{
complete_line (tracker_handle_completions, point, arg, strlen (arg));
word = completion_find_completion_word (tracker_handle_brkchars,
arg, &quote_char);
/* Completers must be called twice. */
complete_line (tracker_handle_completions, word, arg, strlen (arg));
}
CATCH (ex, RETURN_MASK_ALL)
{
return;
}
std::string arg_prefix (arg, point - arg);
std::string arg_prefix (arg, word - arg);
completion_result result
= (tracker_handle_completions.build_completion_result
(point, point - arg, strlen (arg)));
(word, word - arg, strlen (arg)));
if (result.number_matches != 0)
{
@ -308,16 +302,18 @@ complete_command (char *arg_entry, int from_tty)
printf_unfiltered ("%s%s",
arg_prefix.c_str (),
result.match_list[i + 1]);
if (quote_char)
printf_unfiltered ("%c", quote_char);
printf_unfiltered ("\n");
}
}
if (result.number_matches == max_completions)
{
/* ARG_PREFIX and POINT are included in the output so that emacs
/* ARG_PREFIX and WORD are included in the output so that emacs
will include the message in the output. */
printf_unfiltered (_("%s%s %s\n"),
arg_prefix.c_str (), point,
arg_prefix.c_str (), word,
get_max_completions_reached_message ());
}
}

View File

@ -212,6 +212,160 @@ filename_completer_handle_brkchars (struct cmd_list_element *ignore,
(gdb_completer_file_name_break_characters);
}
/* Possible values for the found_quote flags word used by the completion
functions. It says what kind of (shell-like) quoting we found anywhere
in the line. */
#define RL_QF_SINGLE_QUOTE 0x01
#define RL_QF_DOUBLE_QUOTE 0x02
#define RL_QF_BACKSLASH 0x04
#define RL_QF_OTHER_QUOTE 0x08
/* Find the bounds of the current word for completion purposes, and
return a pointer to the end of the word. This mimics (and is a
modified version of) readline's _rl_find_completion_word internal
function.
This function skips quoted substrings (characters between matched
pairs of characters in rl_completer_quote_characters). We try to
find an unclosed quoted substring on which to do matching. If one
is not found, we use the word break characters to find the
boundaries of the current word. QC, if non-null, is set to the
opening quote character if we found an unclosed quoted substring,
'\0' otherwise. DP, if non-null, is set to the value of the
delimiter character that caused a word break. */
struct gdb_rl_completion_word_info
{
const char *word_break_characters;
const char *quote_characters;
const char *basic_quote_characters;
};
static const char *
gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info,
int *qc, int *dp,
const char *line_buffer)
{
int scan, end, found_quote, delimiter, pass_next, isbrk;
char quote_char;
const char *brkchars;
int point = strlen (line_buffer);
/* The algorithm below does '--point'. Avoid buffer underflow with
the empty string. */
if (point == 0)
{
if (qc != NULL)
*qc = '\0';
if (dp != NULL)
*dp = '\0';
return line_buffer;
}
end = point;
found_quote = delimiter = 0;
quote_char = '\0';
brkchars = info->word_break_characters;
if (info->quote_characters != NULL)
{
/* We have a list of characters which can be used in pairs to
quote substrings for the completer. Try to find the start of
an unclosed quoted substring. */
/* FOUND_QUOTE is set so we know what kind of quotes we
found. */
for (scan = pass_next = 0;
scan < end;
scan++)
{
if (pass_next)
{
pass_next = 0;
continue;
}
/* Shell-like semantics for single quotes -- don't allow
backslash to quote anything in single quotes, especially
not the closing quote. If you don't like this, take out
the check on the value of quote_char. */
if (quote_char != '\'' && line_buffer[scan] == '\\')
{
pass_next = 1;
found_quote |= RL_QF_BACKSLASH;
continue;
}
if (quote_char != '\0')
{
/* Ignore everything until the matching close quote
char. */
if (line_buffer[scan] == quote_char)
{
/* Found matching close. Abandon this
substring. */
quote_char = '\0';
point = end;
}
}
else if (strchr (info->quote_characters, line_buffer[scan]))
{
/* Found start of a quoted substring. */
quote_char = line_buffer[scan];
point = scan + 1;
/* Shell-like quoting conventions. */
if (quote_char == '\'')
found_quote |= RL_QF_SINGLE_QUOTE;
else if (quote_char == '"')
found_quote |= RL_QF_DOUBLE_QUOTE;
else
found_quote |= RL_QF_OTHER_QUOTE;
}
}
}
if (point == end && quote_char == '\0')
{
/* We didn't find an unclosed quoted substring upon which to do
completion, so use the word break characters to find the
substring on which to complete. */
while (--point)
{
scan = line_buffer[point];
if (strchr (brkchars, scan) != 0)
break;
}
}
/* If we are at an unquoted word break, then advance past it. */
scan = line_buffer[point];
if (scan)
{
isbrk = strchr (brkchars, scan) != 0;
if (isbrk)
{
/* If the character that caused the word break was a quoting
character, then remember it as the delimiter. */
if (info->basic_quote_characters
&& strchr (info->basic_quote_characters, scan)
&& (end - point) > 1)
delimiter = scan;
point++;
}
}
if (qc != NULL)
*qc = quote_char;
if (dp != NULL)
*dp = delimiter;
return line_buffer + point;
}
/* Complete on linespecs, which might be of two possible forms:
file:line
@ -1306,6 +1460,25 @@ gdb_completion_word_break_characters ()
/* See completer.h. */
const char *
completion_find_completion_word (completion_tracker &tracker, const char *text,
int *quote_char)
{
size_t point = strlen (text);
complete_line_internal (tracker, NULL, text, point, handle_brkchars);
gdb_rl_completion_word_info info;
info.word_break_characters = rl_completer_word_break_characters;
info.quote_characters = gdb_completer_quote_characters;
info.basic_quote_characters = rl_basic_quote_characters;
return gdb_rl_find_completion_word (&info, quote_char, NULL, text);
}
/* See completer.h. */
void
completion_tracker::recompute_lowest_common_denominator (const char *new_match)
{

View File

@ -203,6 +203,16 @@ extern void complete_line (completion_tracker &tracker,
const char *line_buffer,
int point);
/* Find the bounds of the word in TEXT for completion purposes, and
return a pointer to the end of the word. Calls the completion
machinery for a handle_brkchars phase (using TRACKER) to figure out
the right work break characters for the command in TEXT.
QUOTE_CHAR, if non-null, is set to the opening quote character if
we found an unclosed quoted substring, '\0' otherwise. */
extern const char *completion_find_completion_word (completion_tracker &tracker,
const char *text,
int *quote_char);
extern char **gdb_rl_attempted_completion_function (const char *text,
int start, int end);