diff options
Diffstat (limited to 'gdb/completer.c')
-rw-r--r-- | gdb/completer.c | 671 |
1 files changed, 590 insertions, 81 deletions
diff --git a/gdb/completer.c b/gdb/completer.c index 1008ec2..0d68e76 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -1,5 +1,5 @@ /* Line completion stuff for GDB, the GNU debugger. - Copyright (C) 2000-2024 Free Software Foundation, Inc. + Copyright (C) 2000-2025 Free Software Foundation, Inc. This file is part of GDB. @@ -31,6 +31,7 @@ #include "linespec.h" #include "cli/cli-decode.h" #include "gdbsupport/gdb_tilde_expand.h" +#include "readline/readline.h" /* FIXME: This is needed because of lookup_cmd_1 (). We should be calling a hook instead so we eliminate the CLI dependency. */ @@ -48,10 +49,13 @@ /* Forward declarations. */ static const char *completion_find_completion_word (completion_tracker &tracker, const char *text, - int *quote_char); + int *quote_char, + bool *found_any_quoting); static void set_rl_completer_word_break_characters (const char *break_chars); +static bool gdb_path_isdir (const char *filename); + /* See completer.h. */ class completion_tracker::completion_hash_entry @@ -182,6 +186,17 @@ static const char gdb_completer_file_name_break_characters[] = " \t\n*|\"';:?><"; #endif +/* When completing on file names, for commands that don't accept quoted + file names, the only character that can be used as a word separator is + the path separator. Every other character is treated as a literal + character within the filename. */ +static const char gdb_completer_path_break_characters[] = +#ifdef HAVE_DOS_BASED_FILE_SYSTEM + ";"; +#else + ":"; +#endif + /* Characters that can be used to quote expressions. Note that we can't include '"' (double quote) because the gdb C parser treats such quoted sequences as strings. */ @@ -203,14 +218,296 @@ noop_completer (struct cmd_list_element *ignore, { } -/* Complete on filenames. */ +/* Return 1 if the character at EINDEX in STRING is quoted (there is an + unclosed quoted string), or if the character at EINDEX is quoted by a + backslash. */ -void -filename_completer (struct cmd_list_element *ignore, - completion_tracker &tracker, - const char *text, const char *word) +static int +gdb_completer_file_name_char_is_quoted (char *string, int eindex) { - rl_completer_quote_characters = gdb_completer_file_name_quote_characters; + for (int i = 0; i <= eindex && string[i] != '\0'; ) + { + char c = string[i]; + + if (c == '\\') + { + /* The backslash itself is not quoted. */ + if (i >= eindex) + return 0; + ++i; + /* But the next character is. */ + if (i >= eindex) + return 1; + if (string[i] == '\0') + return 0; + ++i; + continue; + } + else if (strchr (rl_completer_quote_characters, c) != nullptr) + { + /* This assumes that extract_string_maybe_quoted can handle a + string quoted with character C. Currently this is true as the + only characters we put in rl_completer_quote_characters are + single and/or double quotes, both of which + extract_string_maybe_quoted can handle. */ + gdb_assert (c == '"' || c == '\''); + const char *tmp = &string[i]; + (void) extract_string_maybe_quoted (&tmp); + i = tmp - string; + + /* Consider any character within the string we just skipped over + as quoted, though this might not be completely correct; the + opening and closing quotes are not themselves quoted. But so + far this doesn't seem to have caused any issues. */ + if (i > eindex) + return 1; + } + else + ++i; + } + + return 0; +} + +/* Removing character escaping from FILENAME. QUOTE_CHAR is the quote + character around FILENAME or the null-character if there is no quoting + around FILENAME. */ + +static char * +gdb_completer_file_name_dequote (char *filename, int quote_char) +{ + std::string tmp; + + if (quote_char == '\'') + { + /* There is no backslash escaping within a single quoted string. In + this case we can just return the input string. */ + tmp = filename; + } + else if (quote_char == '"') + { + /* Remove escaping from a double quoted string. */ + for (const char *input = filename; + *input != '\0'; + ++input) + { + if (input[0] == '\\' + && input[1] != '\0' + && strchr ("\"\\", input[1]) != nullptr) + ++input; + tmp += *input; + } + } + else + { + gdb_assert (quote_char == '\0'); + + /* Remove escaping from an unquoted string. */ + for (const char *input = filename; + *input != '\0'; + ++input) + { + /* We allow anything to be escaped in an unquoted string. */ + if (*input == '\\') + { + ++input; + if (*input == '\0') + break; + } + + tmp += *input; + } + } + + return strdup (tmp.c_str ()); +} + +/* Implement readline's rl_directory_rewrite_hook. Remove any quoting from + the string *DIRNAME,update *DIRNAME, and return non-zero. If *DIRNAME + doesn't need updating then return zero. See readline docs for more + information. */ + +static int +gdb_completer_directory_rewrite (char **dirname) +{ + if (!rl_completion_found_quote) + return 0; + + int quote_char = rl_completion_quote_character; + char *new_dirname + = gdb_completer_file_name_dequote (*dirname, quote_char); + free (*dirname); + *dirname = new_dirname; + + return 1; +} + +/* Apply character escaping to the filename in TEXT and return a newly + allocated buffer containing the possibly updated filename. + + QUOTE_CHAR is the quote character surrounding TEXT, or the + null-character if there are no quotes around TEXT. */ + +static char * +gdb_completer_file_name_quote_1 (const char *text, char quote_char) +{ + std::string str; + + if (quote_char == '\'') + { + /* There is no backslash escaping permitted within a single quoted + string, so in this case we can just return the input string. */ + str = text; + } + else if (quote_char == '"') + { + /* Add escaping for a double quoted filename. */ + for (const char *input = text; + *input != '\0'; + ++input) + { + if (strchr ("\"\\", *input) != nullptr) + str += '\\'; + str += *input; + } + } + else + { + gdb_assert (quote_char == '\0'); + + /* Add escaping for an unquoted filename. */ + for (const char *input = text; + *input != '\0'; + ++input) + { + if (strchr (" \t\n\\\"'", *input) + != nullptr) + str += '\\'; + str += *input; + } + } + + return strdup (str.c_str ()); +} + +/* Apply character escaping to the filename in TEXT. QUOTE_PTR points to + the quote character surrounding TEXT, or points to the null-character if + there are no quotes around TEXT. MATCH_TYPE will be one of the readline + constants SINGLE_MATCH or MULTI_MATCH depending on if there is one or + many completions. + + We also add a trailing character, either a '/' of closing quote, if + MATCH_TYPE is 'SINGLE_MATCH'. We do this because readline will only + add this trailing character when completing at the end of a line. */ + +static char * +gdb_completer_file_name_quote (char *text, int match_type, char *quote_ptr) +{ + char *result = gdb_completer_file_name_quote_1 (text, *quote_ptr); + + if (match_type == SINGLE_MATCH) + { + /* Add trailing '/' if TEXT is a directory, otherwise add a closing + quote character matching *QUOTE_PTR. */ + char c = (gdb_path_isdir (gdb_tilde_expand (text).c_str ()) + ? '/' : *quote_ptr); + + /* Reallocate RESULT adding C to the end. But only if C is + interesting, otherwise we can save the reallocation. */ + if (c != '\0') + { + char buf[2] = { c, '\0' }; + result = reconcat (result, result, buf, nullptr); + } + } + + return result; +} + +/* The function is used to update the completion word MATCH before + displaying it to the user in the 'complete' command output. This + function is only used for formatting filename or directory names. + + This function checks to see if the completion word MATCH is a directory, + in which case a trailing "/" (forward-slash) is added, otherwise + QUOTE_CHAR is added as a trailing quote. + + When ADD_ESCAPES is true any special characters (e.g. whitespace, + quotes) will be escaped with a backslash. See + gdb_completer_file_name_quote_1 for full details on escaping. When + ADD_ESCAPES is false then no escaping will be added and MATCH (with the + correct trailing character) will be used unmodified. + + Return the updated completion word as a string. */ + +static std::string +filename_match_formatter_1 (const char *match, char quote_char, + bool add_escapes) +{ + std::string result; + if (add_escapes) + { + gdb::unique_xmalloc_ptr<char> quoted_match + (gdb_completer_file_name_quote_1 (match, quote_char)); + result = quoted_match.get (); + } + else + result = match; + + if (gdb_path_isdir (gdb_tilde_expand (match).c_str ())) + result += "/"; + else + result += quote_char; + + return result; +} + +/* The formatting function used to format the results of a 'complete' + command when the result is a filename, but the filename should not have + any escape characters added. Most commands that accept a filename don't + expect the filename to be quoted or to contain escape characters. + + See filename_match_formatter_1 for more argument details. */ + +static std::string +filename_unquoted_match_formatter (const char *match, char quote_char) +{ + return filename_match_formatter_1 (match, quote_char, false); +} + +/* The formatting function used to format the results of a 'complete' + command when the result is a filename, and the filename should have any + special character (e.g. whitespace, quotes) within it escaped with a + backslash. A limited number of commands accept this style of filename + argument. + + See filename_match_formatter_1 for more argument details. */ + +static std::string +filename_maybe_quoted_match_formatter (const char *match, char quote_char) +{ + return filename_match_formatter_1 (match, quote_char, true); +} + +/* Generate filename completions of WORD, storing the completions into + TRACKER. This is used for generating completions for commands that + only accept unquoted filenames as well as for commands that accept + quoted and escaped filenames. + + When QUOTE_MATCHES is true TRACKER will be given a match formatter + function which will add escape characters (if needed) in the results. + When QUOTE_MATCHES is false the match formatter provided will not add + any escaping to the results. */ + +static void +filename_completer_generate_completions (completion_tracker &tracker, + const char *word, + bool quote_matches) +{ + if (quote_matches) + tracker.set_match_format_func (filename_maybe_quoted_match_formatter); + else + tracker.set_match_format_func (filename_unquoted_match_formatter); int subsequent_name = 0; while (1) @@ -230,37 +527,68 @@ filename_completer (struct cmd_list_element *ignore, if (p[strlen (p) - 1] == '~') continue; - /* Readline appends a trailing '/' if the completion is a - directory. If this completion request originated from outside - readline (e.g. GDB's 'complete' command), then we append the - trailing '/' ourselves now. */ - if (!tracker.from_readline ()) - { - std::string expanded = gdb_tilde_expand (p_rl); - struct stat finfo; - const bool isdir = (stat (expanded.c_str (), &finfo) == 0 - && S_ISDIR (finfo.st_mode)); - if (isdir) - p_rl.reset (concat (p_rl.get (), "/", nullptr)); - } - tracker.add_completion (make_completion_match_str (std::move (p_rl), word, word)); } } -/* The corresponding completer_handle_brkchars - implementation. */ +/* The brkchars callback used when completing filenames that can be + quoted. */ static void -filename_completer_handle_brkchars (struct cmd_list_element *ignore, - completion_tracker &tracker, - const char *text, const char *word) +filename_maybe_quoted_completer_handle_brkchars + (struct cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) { set_rl_completer_word_break_characters (gdb_completer_file_name_break_characters); rl_completer_quote_characters = gdb_completer_file_name_quote_characters; + rl_char_is_quoted_p = gdb_completer_file_name_char_is_quoted; +} + +/* Complete on filenames. This is for commands that accepts possibly + quoted filenames. */ + +void +filename_maybe_quoted_completer (struct cmd_list_element *ignore, + completion_tracker &tracker, + const char *text, const char *word) +{ + filename_maybe_quoted_completer_handle_brkchars (ignore, tracker, + text, word); + filename_completer_generate_completions (tracker, word, true); +} + +/* The brkchars callback used by commands that don't accept quoted + filenames. */ + +static void +deprecated_filename_completer_handle_brkchars + (struct cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) +{ + gdb_assert (word == nullptr); + + set_rl_completer_word_break_characters (gdb_completer_path_break_characters); + rl_completer_quote_characters = nullptr; + rl_filename_quoting_desired = 0; + + tracker.set_use_custom_word_point (true); + word = advance_to_deprecated_filename_complete_word_point (tracker, text); + deprecated_filename_completer (ignore, tracker, text, word); +} + +/* See completer.h. */ + +void +deprecated_filename_completer + (struct cmd_list_element *ignore, completion_tracker &tracker, + const char *text, const char *word) +{ + gdb_assert (tracker.use_custom_word_point ()); + gdb_assert (word != nullptr); + filename_completer_generate_completions (tracker, word, false); } /* Find the bounds of the current word for completion purposes, and @@ -275,7 +603,9 @@ filename_completer_handle_brkchars (struct cmd_list_element *ignore, 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. */ + delimiter character that caused a word break. FOUND_ANY_QUOTING, if + non-null, is set to true if we found any quote characters (single or + double quotes, or a backslash) while finding the completion word. */ struct gdb_rl_completion_word_info { @@ -286,7 +616,7 @@ struct gdb_rl_completion_word_info static const char * gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, - int *qc, int *dp, + int *qc, int *dp, bool *found_any_quoting, const char *line_buffer) { int scan, end, delimiter, pass_next, isbrk; @@ -298,6 +628,8 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, the empty string. */ if (point == 0) { + if (found_any_quoting != nullptr) + *found_any_quoting = false; if (qc != NULL) *qc = '\0'; if (dp != NULL) @@ -308,6 +640,7 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, end = point; delimiter = 0; quote_char = '\0'; + bool found_quote = false; brkchars = info->word_break_characters; @@ -333,6 +666,7 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, if (quote_char != '\'' && line_buffer[scan] == '\\') { pass_next = 1; + found_quote = true; continue; } @@ -353,6 +687,7 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, /* Found start of a quoted substring. */ quote_char = line_buffer[scan]; point = scan + 1; + found_quote = true; } } } @@ -366,8 +701,22 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, { scan = line_buffer[point]; - if (strchr (brkchars, scan) != 0) - break; + if (strchr (brkchars, scan) == 0) + continue; + + /* Call the application-specific function to tell us whether + this word break character is quoted and should be skipped. + The const_cast is needed here to comply with the readline + API. The only function we register for rl_char_is_quoted_p + treats the input buffer as 'const', so we're OK. */ + if (rl_char_is_quoted_p != nullptr && found_quote + && (*rl_char_is_quoted_p) (const_cast<char *> (line_buffer), + point)) + continue; + + /* Convoluted code, but it avoids an n^2 algorithm with calls + to char_is_quoted. */ + break; } } @@ -391,6 +740,8 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, } } + if (found_any_quoting != nullptr) + *found_any_quoting = found_quote; if (qc != NULL) *qc = quote_char; if (dp != NULL) @@ -401,13 +752,23 @@ gdb_rl_find_completion_word (struct gdb_rl_completion_word_info *info, /* Find the completion word point for TEXT, emulating the algorithm readline uses to find the word point, using WORD_BREAK_CHARACTERS - as word break characters. */ + as word break characters. + + The output argument *FOUND_ANY_QUOTING is set to true if the completion + word found either has an opening quote, or contains backslash escaping + within it. Otherwise *FOUND_ANY_QUOTING is set to false. + + The output argument *QC is set to the opening quote character for the + completion word that is found, or to the null character if there is no + opening quote. */ static const char * advance_to_completion_word (completion_tracker &tracker, const char *word_break_characters, const char *quote_characters, - const char *text) + const char *text, + bool *found_any_quoting, + int *qc) { gdb_rl_completion_word_info info; @@ -417,7 +778,8 @@ advance_to_completion_word (completion_tracker &tracker, int delimiter; const char *start - = gdb_rl_find_completion_word (&info, NULL, &delimiter, text); + = gdb_rl_find_completion_word (&info, qc, &delimiter, found_any_quoting, + text); tracker.advance_custom_word_point_by (start - text); @@ -438,18 +800,54 @@ advance_to_expression_complete_word_point (completion_tracker &tracker, { const char *brk_chars = current_language->word_break_characters (); const char *quote_chars = gdb_completer_expression_quote_characters; - return advance_to_completion_word (tracker, brk_chars, quote_chars, text); + return advance_to_completion_word (tracker, brk_chars, quote_chars, + text, nullptr, nullptr); } /* See completer.h. */ const char * -advance_to_filename_complete_word_point (completion_tracker &tracker, - const char *text) +advance_to_filename_maybe_quoted_complete_word_point + (completion_tracker &tracker, const char *text) { const char *brk_chars = gdb_completer_file_name_break_characters; const char *quote_chars = gdb_completer_file_name_quote_characters; - return advance_to_completion_word (tracker, brk_chars, quote_chars, text); + rl_char_is_quoted_p = gdb_completer_file_name_char_is_quoted; + bool found_any_quoting = false; + int qc; + const char *result + = advance_to_completion_word (tracker, brk_chars, quote_chars, + text, &found_any_quoting, &qc); + rl_completion_found_quote = found_any_quoting ? 1 : 0; + if (qc != '\0') + { + tracker.set_quote_char (qc); + /* If we're completing for readline (not the 'complete' command) then + we want readline to correctly detect the opening quote. The set + of quote characters will have been set during the brkchars phase, + so now we move the word point back by one (so it's pointing at + the quote character) and now readline will correctly spot the + opening quote. For the 'complete' command setting the quote + character in the tracker is enough, so there's no need to move + the word point back here. */ + if (tracker.from_readline ()) + tracker.advance_custom_word_point_by (-1); + } + return result; +} + +/* See completer.h. */ + +const char * +advance_to_deprecated_filename_complete_word_point (completion_tracker &tracker, + const char *text) +{ + const char *brk_chars = gdb_completer_path_break_characters; + const char *quote_chars = nullptr; + rl_filename_quoting_desired = 0; + + return advance_to_completion_word (tracker, brk_chars, quote_chars, + text, nullptr, nullptr); } /* See completer.h. */ @@ -490,7 +888,8 @@ complete_nested_command_line (completion_tracker &tracker, const char *text) int quote_char = '\0'; const char *word = completion_find_completion_word (tracker, text, - "e_char); + "e_char, + nullptr); if (tracker.use_custom_word_point ()) { @@ -607,8 +1006,8 @@ complete_files_symbols (completion_tracker &tracker, symbol_start, word); /* If text includes characters which cannot appear in a file name, they cannot be asking for completion on files. */ - if (strcspn (text, - gdb_completer_file_name_break_characters) == text_len) + if (strcspn (text, gdb_completer_file_name_break_characters) + == text_len) fn_list = make_source_files_completion_list (text, text); } @@ -658,8 +1057,7 @@ complete_source_filenames (const char *text) /* If text includes characters which cannot appear in a file name, the user cannot be asking for completion on files. */ - if (strcspn (text, - gdb_completer_file_name_break_characters) + if (strcspn (text, gdb_completer_file_name_break_characters) == text_len) return make_source_files_completion_list (text, text); @@ -1260,6 +1658,7 @@ complete_line_internal_1 (completion_tracker &tracker, completing file names then we can switch to the file name quote character set (i.e., both single- and double-quotes). */ rl_completer_quote_characters = gdb_completer_expression_quote_characters; + rl_char_is_quoted_p = nullptr; /* Decide whether to complete on a list of gdb commands or on symbols. */ @@ -1479,10 +1878,25 @@ int max_completions = 200; /* Initial size of the table. It automagically grows from here. */ #define INITIAL_COMPLETION_HTAB_SIZE 200 +/* The function is used to update the completion word MATCH before + displaying it to the user in the 'complete' command output. This + default function is used in all cases except those where a completion + function overrides this function by calling set_match_format_func. + + This function returns MATCH with QUOTE_CHAR appended. If QUOTE_CHAR is + the null-character then the returned string will just contain MATCH. */ + +static std::string +default_match_formatter (const char *match, char quote_char) +{ + return std::string (match) + quote_char; +} + /* See completer.h. */ completion_tracker::completion_tracker (bool from_readline) - : m_from_readline (from_readline) + : m_from_readline (from_readline), + m_match_format_func (default_match_formatter) { discard_completions (); } @@ -1692,8 +2106,11 @@ complete (const char *line, char const **word, int *quote_char) try { + bool found_any_quoting = false; + *word = completion_find_completion_word (tracker_handle_brkchars, - line, quote_char); + line, quote_char, + &found_any_quoting); /* Completers that provide a custom word point in the handle_brkchars phase also compute their completions then. @@ -1703,6 +2120,12 @@ complete (const char *line, char const **word, int *quote_char) tracker = &tracker_handle_brkchars; else { + /* Setting this global matches what readline does within + gen_completion_matches. We need this set correctly in case + our completion function calls back into readline to perform + completion (e.g. filename_completer does this). */ + rl_completion_found_quote = found_any_quoting; + complete_line (tracker_handle_completions, *word, line, strlen (line)); tracker = &tracker_handle_completions; } @@ -1877,8 +2300,11 @@ default_completer_handle_brkchars (struct cmd_list_element *ignore, completer_handle_brkchars_ftype * completer_handle_brkchars_func_for_completer (completer_ftype *fn) { - if (fn == filename_completer) - return filename_completer_handle_brkchars; + if (fn == deprecated_filename_completer) + return deprecated_filename_completer_handle_brkchars; + + if (fn == filename_maybe_quoted_completer) + return filename_maybe_quoted_completer_handle_brkchars; if (fn == location_completer) return location_completer_handle_brkchars; @@ -1926,7 +2352,21 @@ gdb_completion_word_break_characters_throw () gdb_custom_word_point_brkchars[0] = rl_line_buffer[rl_point]; rl_completer_word_break_characters = gdb_custom_word_point_brkchars; - rl_completer_quote_characters = NULL; + + /* When performing filename completion we have two options, unquoted + filename completion, in which case the quote characters will have + already been set to nullptr, or quoted filename completion in + which case the quote characters will be set to a string of + characters. In this second case we need readline to perform the + check for a quoted string so that it sets its internal notion of + the quote character correctly, this allows readline to correctly + add the trailing quote (if necessary) after completing a + filename. + + For non-filename completion we manually add a trailing quote if + needed, so we clear the quote characters set here. */ + if (!rl_filename_completion_desired) + rl_completer_quote_characters = NULL; /* Clear this too, so that if we're completing a quoted string, readline doesn't consider the quote character a delimiter. @@ -1973,11 +2413,16 @@ gdb_completion_word_break_characters () noexcept 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. */ + '\0' otherwise. + + The argument *FOUND_ANY_QUOTING is set to true if the completion word is + either surrounded by quotes, or contains any backslash escapes, but is + only set if TRACKER.use_custom_word_point() is false, otherwise + *FOUND_ANY_QUOTING is just set to false. */ static const char * completion_find_completion_word (completion_tracker &tracker, const char *text, - int *quote_char) + int *quote_char, bool *found_any_quoting) { size_t point = strlen (text); @@ -1987,6 +2432,12 @@ completion_find_completion_word (completion_tracker &tracker, const char *text, { gdb_assert (tracker.custom_word_point () > 0); *quote_char = tracker.quote_char (); + /* If use_custom_word_point is set then the completions have already + been calculated, in which case we don't need to have this flag + set correctly, which is lucky as we don't currently have any way + to know if the completion word included any backslash escapes. */ + if (found_any_quoting != nullptr) + *found_any_quoting = false; return text + tracker.custom_word_point (); } @@ -1996,7 +2447,8 @@ completion_find_completion_word (completion_tracker &tracker, const char *text, info.quote_characters = rl_completer_quote_characters; info.basic_quote_characters = rl_basic_quote_characters; - return gdb_rl_find_completion_word (&info, quote_char, NULL, text); + return gdb_rl_find_completion_word (&info, quote_char, nullptr, + found_any_quoting, text); } /* See completer.h. */ @@ -2152,31 +2604,44 @@ completion_tracker::build_completion_result (const char *text, /* Build replacement word, based on the LCD. */ recompute_lowest_common_denominator (); - match_list[0] - = expand_preserving_ws (text, end - start, - m_lowest_common_denominator); + if (rl_filename_completion_desired) + match_list[0] = xstrdup (m_lowest_common_denominator); + else + match_list[0] + = expand_preserving_ws (text, end - start, m_lowest_common_denominator); if (m_lowest_common_denominator_unique) { - /* We don't rely on readline appending the quote char as - delimiter as then readline wouldn't append the ' ' after the - completion. */ - char buf[2] = { (char) quote_char () }; + bool completion_suppress_append; - match_list[0] = reconcat (match_list[0], match_list[0], - buf, (char *) NULL); - match_list[1] = NULL; + /* For filename completion we rely on readline to append the closing + quote. While for other types of completion we append the closing + quote here. */ + if (from_readline () && !rl_filename_completion_desired) + { + /* We don't rely on readline appending the quote char as + delimiter as then readline wouldn't append the ' ' after the + completion. */ + char buf[2] = { (char) quote_char (), '\0' }; + + match_list[0] = reconcat (match_list[0], match_list[0], buf, + (char *) nullptr); + + /* If the tracker wants to, or we already have a space at the end + of the match, tell readline to skip appending another. */ + char *match = match_list[0]; + completion_suppress_append + = (suppress_append_ws () + || (match[0] != '\0' + && match[strlen (match) - 1] == ' ')); + } + else + completion_suppress_append = false; - /* If the tracker wants to, or we already have a space at the - end of the match, tell readline to skip appending - another. */ - char *match = match_list[0]; - bool completion_suppress_append - = (suppress_append_ws () - || (match[0] != '\0' - && match[strlen (match) - 1] == ' ')); + match_list[1] = nullptr; - return completion_result (match_list, 1, completion_suppress_append); + return completion_result (match_list, 1, completion_suppress_append, + m_match_format_func); } else { @@ -2213,7 +2678,8 @@ completion_tracker::build_completion_result (const char *text, htab_traverse_noresize (m_entries_hash.get (), func, &builder); match_list[builder.index] = NULL; - return completion_result (match_list, builder.index - 1, false); + return completion_result (match_list, builder.index - 1, false, + m_match_format_func); } } @@ -2221,18 +2687,23 @@ completion_tracker::build_completion_result (const char *text, completion_result::completion_result () : match_list (NULL), number_matches (0), - completion_suppress_append (false) + completion_suppress_append (false), + m_match_formatter (default_match_formatter) {} /* See completer.h */ completion_result::completion_result (char **match_list_, size_t number_matches_, - bool completion_suppress_append_) + bool completion_suppress_append_, + match_format_func_t match_formatter_) : match_list (match_list_), number_matches (number_matches_), - completion_suppress_append (completion_suppress_append_) -{} + completion_suppress_append (completion_suppress_append_), + m_match_formatter (match_formatter_) +{ + gdb_assert (m_match_formatter != nullptr); +} /* See completer.h */ @@ -2245,10 +2716,12 @@ completion_result::~completion_result () completion_result::completion_result (completion_result &&rhs) noexcept : match_list (rhs.match_list), - number_matches (rhs.number_matches) + number_matches (rhs.number_matches), + m_match_formatter (rhs.m_match_formatter) { rhs.match_list = NULL; rhs.number_matches = 0; + rhs.m_match_formatter = default_match_formatter; } /* See completer.h */ @@ -2290,6 +2763,38 @@ completion_result::reset_match_list () } } +/* See completer.h */ + +void +completion_result::print_matches (const std::string &prefix, + const char *word, int quote_char) +{ + this->sort_match_list (); + + size_t off = this->number_matches == 1 ? 0 : 1; + + for (size_t i = 0; i < this->number_matches; i++) + { + gdb_assert (this->m_match_formatter != nullptr); + std::string formatted_match + = this->m_match_formatter (this->match_list[i + off], + (char) quote_char); + + printf_unfiltered ("%s%s\n", prefix.c_str (), + formatted_match.c_str ()); + } + + if (this->number_matches == max_completions) + { + /* PREFIX and WORD are included in the output so that emacs will + include the message in the output. */ + printf_unfiltered (_("%s%s %s\n"), + prefix.c_str (), word, + get_max_completions_reached_message ()); + } + +} + /* Helper for gdb_rl_attempted_completion_function, which does most of the work. This is called by readline to build the match list array and to determine the lowest common denominator. The real matches @@ -2469,10 +2974,10 @@ gdb_display_match_list_pager (int lines, return 0; } -/* Return non-zero if FILENAME is a directory. +/* Return true if FILENAME is a directory. Based on readline/complete.c:path_isdir. */ -static int +static bool gdb_path_isdir (const char *filename) { struct stat finfo; @@ -3008,15 +3513,19 @@ skip_over_slash_fmt (completion_tracker &tracker, const char **args) return false; } -void _initialize_completer (); -void -_initialize_completer () +INIT_GDB_FILE (completer) { /* Setup some readline completion globals. */ rl_completion_word_break_hook = gdb_completion_word_break_characters; rl_attempted_completion_function = gdb_rl_attempted_completion_function; set_rl_completer_word_break_characters (default_word_break_characters ()); + /* Setup readline globals relating to filename completion. */ + rl_filename_quote_characters = " \t\n\\\"'"; + rl_filename_dequoting_function = gdb_completer_file_name_dequote; + rl_filename_quoting_function = gdb_completer_file_name_quote; + rl_directory_rewrite_hook = gdb_completer_directory_rewrite; + add_setshow_zuinteger_unlimited_cmd ("max-completions", no_class, &max_completions, _("\ Set maximum number of completion candidates."), _("\ |