diff --git a/gdb/completer.c b/gdb/completer.c index 2793ce600b9..d9123e6e74a 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -52,6 +52,8 @@ static const char *completion_find_completion_word (completion_tracker &tracker, 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 @@ -367,6 +369,28 @@ gdb_completer_file_name_quote (char *text, int match_type ATTRIBUTE_UNUSED, return strdup (str.c_str ()); } +/* 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. + + Return the updated completion word as a string. */ + +static std::string +filename_match_formatter (const char *match, char quote_char) +{ + std::string result (match); + if (gdb_path_isdir (gdb_tilde_expand (match).c_str ())) + result += "/"; + else + result += quote_char; + + return result; +} + /* 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 @@ -376,6 +400,8 @@ static void filename_completer_generate_completions (completion_tracker &tracker, const char *word) { + tracker.set_match_format_func (filename_match_formatter); + int subsequent_name = 0; while (1) { @@ -394,20 +420,6 @@ filename_completer_generate_completions (completion_tracker &tracker, 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)); } @@ -1688,10 +1700,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 (); } @@ -2397,7 +2424,8 @@ completion_tracker::build_completion_result (const char *text, 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 { @@ -2434,7 +2462,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); } } @@ -2442,18 +2471,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 */ @@ -2466,10 +2500,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 */ @@ -2519,12 +2555,18 @@ completion_result::print_matches (const std::string &prefix, { this->sort_match_list (); - char buf[2] = { (char) quote_char, '\0' }; size_t off = this->number_matches == 1 ? 0 : 1; for (size_t i = 0; i < this->number_matches; i++) - printf_unfiltered ("%s%s%s\n", prefix.c_str (), - this->match_list[i + off], buf); + { + 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) { @@ -2716,10 +2758,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; diff --git a/gdb/completer.h b/gdb/completer.h index e6dc9417932..c18bd16ad26 100644 --- a/gdb/completer.h +++ b/gdb/completer.h @@ -247,12 +247,24 @@ struct completion_match_result struct completion_result { + /* The type of a function that is used to format completion results when + using the 'complete' command. MATCH is the completion word to be + printed, and QUOTE_CHAR is a trailing quote character to (possibly) + add at the end of MATCH. QUOTE_CHAR can be the null-character in + which case no trailing quote should be added. + + Return the possibly modified completion match word which should be + presented to the user. */ + using match_format_func_t = std::string (*) (const char *match, + char quote_char); + /* Create an empty result. */ completion_result (); /* Create a result. */ completion_result (char **match_list, size_t number_matches, - bool completion_suppress_append); + bool completion_suppress_append, + match_format_func_t match_format_func); /* Destroy a result. */ ~completion_result (); @@ -274,10 +286,15 @@ struct completion_result completions, otherwise, each line of output consists of PREFIX followed by one of the possible completion words. - The QUOTE_CHAR is appended after each possible completion word and - should be the quote character that appears before the completion word, - or the null-character if there is no quote before the completion - word. */ + The QUOTE_CHAR is usually appended after each possible completion + word and should be the quote character that appears before the + completion word, or the null-character if there is no quote before + the completion word. + + The QUOTE_CHAR is not always appended to the completion output. For + example, filename completions will not append QUOTE_CHAR if the + completion is a directory name. This is all handled by calling this + function. */ void print_matches (const std::string &prefix, const char *word, int quote_char); @@ -305,6 +322,12 @@ public: /* Whether readline should suppress appending a whitespace, when there's only one possible completion. */ bool completion_suppress_append; + +private: + /* A function which formats a single completion match ready for display + as part of the 'complete' command output. Different completion + functions can set different formatter functions. */ + match_format_func_t m_match_formatter; }; /* Object used by completers to build a completion match list to hand @@ -441,6 +464,14 @@ public: bool from_readline () const { return m_from_readline; } + /* Set the function used to format the completion word before displaying + it to the user to F, this is used by the 'complete' command. */ + void set_match_format_func (completion_result::match_format_func_t f) + { + gdb_assert (f != nullptr); + m_match_format_func = f; + } + private: /* The type that we place into the m_entries_hash hash table. */ @@ -535,6 +566,10 @@ private: interactively. The 'complete' command is a way to generate completions not to be displayed by readline. */ bool m_from_readline; + + /* The function used to format the completion word before it is printed + in the 'complete' command output. */ + completion_result::match_format_func_t m_match_format_func; }; /* Return a string to hand off to readline as a completion match diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp index e8acd2f85cf..39f616b45ba 100644 --- a/gdb/testsuite/gdb.base/filename-completion.exp +++ b/gdb/testsuite/gdb.base/filename-completion.exp @@ -58,6 +58,49 @@ proc setup_directory_tree {} { return $root } +# This proc started as a copy of test_gdb_complete_multiple, however, this +# version does some extra work. See the original test_gdb_complete_multiple +# for a description of all the arguments. +# +# When using the 'complete' command with filenames, GDB will add a trailing +# quote for filenames, and a trailing "/" for directory names. As the +# trailing "/" is also added in the tab-completion output the +# COMPLETION_LIST will include the "/" character, but the trailing quote is +# only added when using the 'complete' command. +# +# Pass the trailing quote will be passed as END_QUOTE_CHAR, this proc will +# run the tab completion test, and will then add the trailing quote to those +# entries in COMPLETION_LIST that don't have a trailing "/" before running +# the 'complete' command test. +proc test_gdb_complete_filename_multiple { + cmd_prefix completion_word add_completed_line completion_list + {start_quote_char ""} {end_quote_char ""} {max_completions false} + {testname ""} +} { + if { [readline_is_used] } { + test_gdb_complete_tab_multiple "$cmd_prefix$completion_word" \ + $add_completed_line $completion_list $max_completions $testname + } + + if { $start_quote_char eq "" && $end_quote_char ne "" } { + set updated_completion_list {} + + foreach entry $completion_list { + if {[string range $entry end end] ne "/"} { + set entry $entry$end_quote_char + } + lappend updated_completion_list $entry + } + + set completion_list $updated_completion_list + set end_quote_char "" + } + + test_gdb_complete_cmd_multiple $cmd_prefix $completion_word \ + $completion_list $start_quote_char $end_quote_char $max_completions \ + $testname +} + # Run filename completetion tests for those command that accept quoting and # escaping of the filename argument. # @@ -81,35 +124,22 @@ proc run_quoting_and_escaping_tests { root } { test_gdb_complete_none "$cmd ${qc}${root}/xx" \ "expand a non-existent filename" - # The following test is split into separate cmd and tab calls - # so we can xfail the cmd version. The cmd version will add a - # closing quote, it shouldn't be doing this. This will be - # fixed in a later commit. - if { $qc ne "" } { - setup_xfail "*-*-*" - } - test_gdb_complete_cmd_unique "$cmd ${qc}${root}/a" \ - "$cmd ${qc}${root}/aaa/" \ + test_gdb_complete_unique "$cmd ${qc}${root}/a" \ + "$cmd ${qc}${root}/aaa/" "" false \ "expand a unique directory name" - if { [readline_is_used] } { - test_gdb_complete_tab_unique "$cmd ${qc}${root}/a" \ - "$cmd ${qc}${root}/aaa/" "" \ - "expand a unique directory name" - } - test_gdb_complete_unique "$cmd ${qc}${root}/cc2" \ "$cmd ${qc}${root}/cc2${qc}" " " false \ "expand a unique filename" - test_gdb_complete_multiple "$cmd ${qc}${root}/" \ + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/" \ "b" "b" { "bb1/" "bb2/" } "" "${qc}" false \ "expand multiple directory names" - test_gdb_complete_multiple "$cmd ${qc}${root}/" \ + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/" \ "c" "c" { "cc1/" "cc2" @@ -121,14 +151,14 @@ proc run_quoting_and_escaping_tests { root } { if { $qc ne "" } { set sp " " - test_gdb_complete_multiple "$cmd ${qc}${root}/aaa/" \ + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/aaa/" \ "a" "a${sp}" { "aa bb" "aa cc" } "" "${qc}" false \ "expand filenames containing spaces" - test_gdb_complete_multiple "$cmd ${qc}${root}/bb1/" \ + test_gdb_complete_filename_multiple "$cmd ${qc}${root}/bb1/" \ "a" "a" { "aa\"bb" "aa'bb"