public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Andrew Burgess <aburgess@redhat.com>
To: gdb-patches@sourceware.org
Cc: Andrew Burgess <aburgess@redhat.com>,
	Lancelot SIX <lsix@lancelotsix.com>, Eli Zaretskii <eliz@gnu.org>
Subject: [PATCHv2 6/8] gdb: add match formatter mechanism for 'complete' command output
Date: Sat, 20 Apr 2024 10:10:06 +0100	[thread overview]
Message-ID: <945fa8e0f1337f40129088484ed349ffa5f0f287.1713603416.git.aburgess@redhat.com> (raw)
In-Reply-To: <cover.1713603415.git.aburgess@redhat.com>

This commit solves a problem that existed prior to the previous
commit, but the previous commit made more common.

When completing a filename with the 'complete' command GDB will always
add a trailing quote character, even if the completion is a directory
name, in which case it would be better if the trailing quote was not
added.  Consider:

  (gdb) complete file '/tmp/xx
  file '/tmp/xxx/'

The completion offered here is really only a partial completion, we've
completed up to the end of the next directory name, but, until we have
a filename then the completion is not finished and the trailing quote
should not be added.

This would match the readline behaviour, e.g.:

  (gdb) file '/tmp/xx<TAB>
  (gdb) file '/tmp/xxx/

In this case readline completes the directory name, but doesn't add
the trailing quote character.

Remember that the 'complete' command is intended for tools like
e.g. emacs in order that they can emulate GDB's standard readline
completion when implementing a CLI of their own.  As such, not adding
the trailing quote in this case matches the readline behaviour, and
seems like the right way to go.

To achieve this, I've added a new function pointer member variable
completion_result::m_match_formatter.  This contains a pointer to a
callback function which is used by the 'complete' command to format
each result.

The default behaviour of this callback function is to just append the
quote character (the character from before the completion string) to
the end of the completion result.  This matches the current behaviour.

However, for filename completion we override the default value of
m_match_formatter, this new function checks if the completion result
is a directory or not.  If the completion result is a directory then
the closing quote is not added, instead we add a trailing '/'
character.

The code to add a trailing '/' character already exists within the
filename_completer function.  This is no longer needed in this
location, instead this code is moved into the formatter callback.

Tests are updated to handle the changes in functionality, this removes
an xfail added in the previous commit.
---
 gdb/completer.c                               | 92 ++++++++++++++-----
 gdb/completer.h                               | 45 ++++++++-
 .../gdb.base/filename-completion.exp          | 68 ++++++++++----
 3 files changed, 156 insertions(+), 49 deletions(-)

diff --git a/gdb/completer.c b/gdb/completer.c
index 4ba45a35f8f..ee016cdc7f7 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -45,6 +45,8 @@
 
 #include "completer.h"
 
+#include <filesystem>
+
 /* Forward declarations.  */
 static const char *completion_find_completion_word (completion_tracker &tracker,
 						    const char *text,
@@ -356,6 +358,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 (std::filesystem::is_directory (gdb_tilde_expand (match)))
+    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
@@ -365,6 +389,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)
     {
@@ -383,20 +409,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.get ());
-	  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));
     }
@@ -1646,10 +1658,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 ();
 }
@@ -2352,7 +2379,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
     {
@@ -2389,7 +2417,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);
     }
 }
 
@@ -2397,18 +2426,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  */
 
@@ -2421,10 +2455,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  */
@@ -2474,12 +2510,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)
     {
diff --git a/gdb/completer.h b/gdb/completer.h
index 200d8a9b3af..71303e2ae9a 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 @@ struct completion_result
   /* 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 @@ class completion_tracker
   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 @@ class completion_tracker
      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 3e3b9b29ba4..0467d5c425e 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.
 #
@@ -82,35 +125,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 "file ${qc}${root}/a" \
-		"file ${qc}${root}/aaa/" \
+	    test_gdb_complete_unique "file ${qc}${root}/a" \
+		"file ${qc}${root}/aaa/" "" false \
 		"expand a unique directory name"
 
-	    if { [readline_is_used] } {
-		test_gdb_complete_tab_unique "file ${qc}${root}/a" \
-		    "file ${qc}${root}/aaa/" "" \
-		    "expand a unique directory name"
-	    }
-
 	    test_gdb_complete_unique "file ${qc}${root}/cc2" \
 		"file ${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"
@@ -122,14 +152,14 @@ proc run_quoting_and_escaping_tests { root } {
 	    if { $qc ne "" } {
 		set sp " "
 
-		test_gdb_complete_multiple "file ${qc}${root}/aaa/" \
+		test_gdb_complete_filename_multiple "file ${qc}${root}/aaa/" \
 		    "a" "a${sp}" {
 			"aa bb"
 			"aa cc"
 		    } "" "${qc}" false \
 		    "expand filenames containing spaces"
 
-		test_gdb_complete_multiple "file ${qc}${root}/bb1/" \
+		test_gdb_complete_filename_multiple "file ${qc}${root}/bb1/" \
 		    "a" "a" {
 			"aa\"bb"
 			"aa'bb"
-- 
2.25.4


  parent reply	other threads:[~2024-04-20  9:10 UTC|newest]

Thread overview: 38+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-03-29 11:42 [PATCH 0/6] Further filename completion improvements Andrew Burgess
2024-03-29 11:42 ` [PATCH 1/6] gdb: improve escaping when completing filenames Andrew Burgess
2024-03-30 23:48   ` Lancelot SIX
2024-03-29 11:42 ` [PATCH 2/6] gdb: move display of completion results into completion_result class Andrew Burgess
2024-03-29 12:14   ` Eli Zaretskii
2024-03-30 23:30     ` Lancelot SIX
2024-03-31  5:49       ` Eli Zaretskii
2024-04-12 17:24         ` Andrew Burgess
2024-04-12 18:42           ` Eli Zaretskii
2024-04-12 22:20             ` Andrew Burgess
2024-04-13  6:36               ` Eli Zaretskii
2024-04-13  9:09                 ` Andrew Burgess
2024-04-13  9:46                   ` Eli Zaretskii
2024-04-12 17:31       ` Andrew Burgess
2024-03-29 11:42 ` [PATCH 3/6] gdb: simplify completion_result::print_matches Andrew Burgess
2024-03-30 23:48   ` Lancelot SIX
2024-03-29 11:42 ` [PATCH 4/6] gdb: add match formatter mechanism for 'complete' command output Andrew Burgess
2024-03-30 23:49   ` Lancelot SIX
2024-03-31  5:55     ` Eli Zaretskii
2024-04-12 17:42       ` Andrew Burgess
2024-04-12 18:44         ` Eli Zaretskii
2024-04-12 22:29           ` Andrew Burgess
2024-04-13  6:39             ` Eli Zaretskii
2024-03-29 11:42 ` [PATCH 5/6] gdb: apply escaping to filenames in 'complete' results Andrew Burgess
2024-03-29 11:42 ` [PATCH 6/6] gdb: improve gdb_rl_find_completion_word for quoted words Andrew Burgess
2024-04-20  9:10 ` [PATCHv2 0/8] Further filename completion improvements Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 1/8] gdb/doc: document how filename arguments are formatted Andrew Burgess
2024-04-20  9:44     ` Eli Zaretskii
2024-04-27 10:01       ` Andrew Burgess
2024-04-27 10:06         ` Eli Zaretskii
2024-04-29  9:10           ` Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 2/8] gdb: split apart two different types of filename completion Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 3/8] gdb: improve escaping when completing filenames Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 4/8] gdb: move display of completion results into completion_result class Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 5/8] gdb: simplify completion_result::print_matches Andrew Burgess
2024-04-20  9:10   ` Andrew Burgess [this message]
2024-04-20  9:10   ` [PATCHv2 7/8] gdb: apply escaping to filenames in 'complete' results Andrew Burgess
2024-04-20  9:10   ` [PATCHv2 8/8] gdb: improve gdb_rl_find_completion_word for quoted words Andrew Burgess

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=945fa8e0f1337f40129088484ed349ffa5f0f287.1713603416.git.aburgess@redhat.com \
    --to=aburgess@redhat.com \
    --cc=eliz@gnu.org \
    --cc=gdb-patches@sourceware.org \
    --cc=lsix@lancelotsix.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).