public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Lancelot SIX <lsix@lancelotsix.com>
To: gdb-patches@sourceware.org
Cc: Lancelot SIX <lsix@lancelotsix.com>
Subject: [PATCH 1/3] gdb::option: Add support for filename option.
Date: Sat, 13 Feb 2021 22:07:50 +0000	[thread overview]
Message-ID: <20210213220752.32581-2-lsix@lancelotsix.com> (raw)
In-Reply-To: <20210213220752.32581-1-lsix@lancelotsix.com>

This commit adds support for filename option for the gdb::option parsing
framework.  It is meant to help the user enter values that could be
parsed by built_argv.

Tab completion works as expected, i.e. similar to bash's behavior.

Considering the following tree structure:

somedir/
├── a folder
└── a file

The following completions are available:

    (gdb) maintenance test-options unknown-is-error -filename somedir/a\ <tab>
    a file    a folder/

or

    (gdb) maintenance test-options unknown-is-error -filename "somedir/a <tab>
    a file    a folder/

The complete command command also provides useful completions which are
escaped or quoted depending on what the user did input initially.

    (gdb) complete maintenance test-options unknown-is-error -filename somedir/a
    maintenance test-options unknown-is-error -filename somedir/a\ file
    maintenance test-options unknown-is-error -filename somedir/a\ folder/
    (gdb) complete maintenance test-options unknown-is-error -filename 'somedir/a
    maintenance test-options unknown-is-error -filename 'somedir/a file
    maintenance test-options unknown-is-error -filename 'somedir/a folder/

Note that since entering a path is an interactive process, the completer
will not append the closing quote after a directory name. Instead it
adds a '/' and expect the user to provide further input.

Having both of those behaviors at the same time requires that the
completer knows if it has been called by readline after the user
pressed the tab key or by some other mechanism.  In the first scenario it
should display non quoted/escaped completions so the user sees actual
filenames but complete quoted/escaped filenames.  In the second scenario
it should only provide quoted/escaped completions.  For that purpose,
this commit adds a m_from_readline property to the completion_tracker
object and initializes it accordingly to the calling context.

This filename_completer has slightly different behavior than the one
available in gdb/completer.c mainly with regard to escaping/quoting.
The original completer is kept as is to maintain stable behavior for
commands whose completer might depend on this implementation.  If this is
desirable, I’ll happily replace the existing filename completer with the
one currently proposed in the gdb::options::filename namespace instead
of having both with slightly different behaviors (but I an not sure if
users are relying on the current behavior of the filename completer).

It really feels that I have had to re-implement many existing readline
behavior and I would be glad if someone has a simpler approach to obtain
the same result.

All feedback are welcome.

gdb/ChangeLog:

	* cli/cli-option.c (union option_value): Add filename option.
	(struct option_def_and_value): Free filename in dtor.
	(gdb::option::filename::quote_filename): New function.
	(gdb::option::filename::handle_completion): New function.
	(gdb::option::filename::filename_copleter): New function.
	(gdb::option::parse_option): Add support for filename.
	(gdb::option::save_option_value_in_ctx): Add support for filename.
	(gdb::option::get_val_type_str): Add support for filename.
	(gdb::option::add_setshow_cmds_for_options): Add support for filename.
	* cli/cli-option.h (struct option_def): Add filename field.
	(struct gdb::option::filename_option_def): Create.
	* completer.c (completion_tracker::discard_completions): Reset the
	m_from_readline flag.
	(gdb_completion_word_break_characters_throw): Set the m_from_readline
	flag on the completion_tracker.
	* completer.h (class completion_tracker): Add m_from_readline fileld
	and associated getter / setters.
	* maint-test-options.c (struct test_options_opts): Add a -filename
	option.

gdb/testsuite/ChangeLog:

	* gdb.base/options.exp: Add tests for the filename option.
	* lib/completion-support.exp (make_tab_completion_list_re):
	Relax number of required spaced at the end of the pattern.
---
 gdb/cli/cli-option.c                     | 250 +++++++++++++++++++++++
 gdb/cli/cli-option.h                     |  20 ++
 gdb/completer.c                          |   3 +
 gdb/completer.h                          |  15 ++
 gdb/maint-test-options.c                 |  15 +-
 gdb/testsuite/gdb.base/options.exp       | 126 ++++++++++--
 gdb/testsuite/lib/completion-support.exp |   1 -
 7 files changed, 415 insertions(+), 15 deletions(-)

diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
index ab76f194c3d..5a6c997dcda 100644
--- a/gdb/cli/cli-option.c
+++ b/gdb/cli/cli-option.c
@@ -24,6 +24,8 @@
 #include "cli/cli-setshow.h"
 #include "command.h"
 #include <vector>
+#include <readline/readline.h>
+
 
 namespace gdb {
 namespace option {
@@ -46,6 +48,9 @@ union option_value
 
   /* For var_string options.  This is malloc-allocated.  */
   char *string;
+
+  /* For var_filename option. This is malloc-allocated.  */
+  char *filename;
 };
 
 /* Holds an options definition and its value.  */
@@ -88,6 +93,8 @@ struct option_def_and_value
       {
 	if (option.type == var_string)
 	  xfree (value->string);
+	if (option.type == var_filename)
+	  xfree (value->filename);
       }
   }
 
@@ -105,6 +112,8 @@ struct option_def_and_value
       {
 	if (option.type == var_string)
 	  value->string = nullptr;
+	if (option.type == var_filename)
+	  value->filename = nullptr;
       }
   }
 };
@@ -164,6 +173,197 @@ complete_on_options (gdb::array_view<const option_def_group> options_group,
 	}
 }
 
+namespace filename {
+
+/* Quote the filename whose name is given by TEXT using quoting char
+   QUOTECHAR. QUOTECHAR can be '\'', '"' or '\0'.
+
+   This function is meant to be used with a completion context, so the
+   closing quote is not appended (we might still be waiting for the user
+   to input more path components).  */
+
+static std::string
+quote_filename (const char *text, char quotechar)
+{
+  std::string quoted;
+  /* strlen (text) is a good first approximation of the resulting string
+     length.  */
+  quoted.reserve (strlen (text));
+
+  if (quotechar != '\0')
+    {
+      quoted.push_back (quotechar);
+      while (*text != '\0')
+	{
+	  if (*text == quotechar)
+	    quoted.push_back ('\\');
+	  quoted.push_back (*text++);
+	}
+    }
+  else
+    {
+      while (*text != '\0')
+	{
+	  /* Escape the space and quote chars.  */
+	  if (*text == ' ' || *text == '\'' || *text == '"')
+	    quoted.push_back ('\\');
+	  quoted.push_back (*text++);
+	}
+    }
+
+  return quoted;
+}
+
+/* Add the filename COMPLETION to TRACKER.  COMPLETION will be quoted
+   using QUOTECHAR, but the closing quote will be dismissed if we expect
+   more input from the user.
+
+   ONLY_COMPLETION is set to true to indicate that this function is called only
+   once (i.e. only one possible completion).  In this case, if COMPLETION is not
+   a directory, the closing quote is appended followed by a space to indicate
+   that the next option is expected.
+
+   See completer_ftype for a description of TEXT and WORD.
+
+   The following table gives examples of how COMPLETION might be passed to
+   TRACKER depending on QUOTECHAR
+
+    COMPLETION		QUOTECHAR   As registered in TRACKER
+    "/home"		'\''	    "'/home/'"
+    "/home"		'\0'	    "/home/"
+    "/home"		'"'	    "\"/home/"
+    "~/some folder"	'\''	    "'~/some folder/"
+    "~/some folder"	'\0'	    "~/some\\ folder/"
+    "~/some folder"	'"'	    "\"~/some folder/"
+    "only_compl"	'\''	    "'only_compl' "
+    "only_compl"	'\0'	    "only_compl "
+    "only_compl"	'"'	    "\"only_compl\" "
+   */
+
+static void
+handle_completion
+  (completion_tracker &tracker, std::string completion,
+   const char quotechar, bool only_completion,
+   const char *text, const char *word)
+{
+  gdb_assert (tracker.suppress_append_ws ());
+  struct stat finfo;
+  const bool isdir
+    = stat (completion.c_str (), &finfo) == 0 && S_ISDIR (finfo.st_mode);
+
+  /* Readline would add a '/' for dirs, so do is ourselves if readline is not
+     here.  */
+  if (isdir && !tracker.from_readline ())
+    completion.push_back ('/');
+
+  std::string quoted_completion = quote_filename
+    (completion.c_str (), quotechar);
+
+  /* If we only have 1 possible completion which is not a dir, then we
+     know the user will not add anything else.  We can close the quotation
+     (if any) and append a space (tracker.suppress_append_ws () is true).  */
+  if (only_completion && !isdir)
+    {
+      if (quotechar != '\0')
+        quoted_completion.push_back (quotechar);
+      quoted_completion.push_back (' ');
+    }
+
+  /* Check if we got here by the user typing
+     'complete [...] -filename ...' or '[...] -filename ...\t'
+
+     In the first case, we want the completion to show a actually
+     quotted string, while in the latter case we want to complete on
+     the quoted filename while showing the unquoted completion to the
+     user.  */
+  if (tracker.from_readline ())
+    {
+      completion_match_for_lcd match_for_lcd;
+      match_for_lcd.set_match (quoted_completion.c_str ());
+
+      tracker.add_completion
+        (std::move (gdb::unique_xmalloc_ptr<char>
+                    (xstrdup (completion.c_str ()))), &match_for_lcd,
+         text, word);
+    }
+  else
+    tracker.add_completion
+      (std::move (gdb::unique_xmalloc_ptr<char>
+		  (xstrdup (quoted_completion.c_str ()))),
+       nullptr, text, word);
+
+}
+
+/* Filename completer.  See completer_ftype for description of parameters.  */
+
+static void
+complete_filename
+  (completion_tracker &tracker, const char *text, const char *word)
+{
+  /* First call to rl_filename_completion_function should be 0, subsequent
+     calls non 0.
+
+     We also track the number of calls done to rl_filename_completion_function.
+     If it only return only one non null value a different processing should be
+     done (see handle_completion for details).  */
+  int call_count = 0;
+  std::string first_completion;
+
+  /* The logic that tells if a space is to be appended after a completion is
+     done in handle_completion, not delegated to the tracker.  */
+  tracker.set_suppress_append_ws (true);
+
+  /* Keep track of what quoting mechanism the user used so we can re-quote
+     completions accordingly.  */
+  char quotechar;
+  if (text[0] == '\'' || text[0] == '"')
+    quotechar = text[0];
+  else
+    quotechar = '\0';
+
+  const char *arg_start = text;
+  const std::string dequoted = extract_string_maybe_quoted (&arg_start);
+
+  try
+    {
+      while (true)
+	{
+	  gdb::unique_xmalloc_ptr<char> next_completion
+	    (rl_filename_completion_function
+	     (dequoted.c_str (), call_count++));
+
+	  if (next_completion == nullptr)
+	    break;
+
+	  if (call_count == 1)
+	    first_completion = next_completion.get ();
+	  else
+	    {
+	      if (call_count == 2)
+		handle_completion
+		  (tracker, first_completion, quotechar, false, text,
+		   word);
+
+	      handle_completion
+		(tracker, next_completion.get (), quotechar, false, text,
+		 word);
+	    }
+	}
+
+      /* If there was just 1 completion found, do readline job.  */
+      if (call_count == 2)
+	handle_completion
+	  (tracker, first_completion, quotechar, true, text, word);
+    }
+  catch (const gdb_exception_error &except)
+    {
+      if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
+        throw;
+    }
+}
+
+} /* namespace filename */
+
 /* See cli-option.h.  */
 
 void
@@ -443,6 +643,40 @@ parse_option (gdb::array_view<const option_def_group> options_group,
 	return option_def_and_value {*match, match_ctx, val};
       }
 
+    case var_filename:
+      {
+	if (check_for_argument (args, "--"))
+	  {
+	    /* Treat "maint test-options -filename --" as if there
+	       was no argument after "-filename".  */
+	    error (_("-%s requires an argument"), match->name);
+	  }
+
+	/* We are actually parsing the input, so advance the pointer to
+	   after what have been parsed.  */
+        const char *arg_start = *args;
+	const std::string fname = extract_string_maybe_quoted (args);
+
+        /* If required, perform completion.  */
+	if (completion != nullptr)
+          {
+	    if (**args == '\0')
+              {
+                gdb::option::filename::complete_filename
+                  (completion->tracker, arg_start, arg_start);
+
+                return {};
+              }
+	  }
+
+	if (fname == "")
+	  error (_("-%s requires an argument"), match->name);
+
+	option_value val;
+	val.filename = xstrdup (fname.c_str ());
+	return option_def_and_value {*match, match_ctx, val};
+      }
+
     default:
       /* Not yet.  */
       gdb_assert_not_reached (_("option type not supported"));
@@ -606,6 +840,11 @@ save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov)
 	= ov->value->string;
       ov->value->string = nullptr;
       break;
+    case var_filename:
+      *ov->option.var_address.filename (ov->option, ov->ctx)
+	= ov->value->filename;
+      ov->value->filename = nullptr;
+      break;
     default:
       gdb_assert_not_reached ("unhandled option type");
     }
@@ -680,6 +919,8 @@ get_val_type_str (const option_def &opt, std::string &buffer)
       }
     case var_string:
       return "STRING";
+    case var_filename:
+      return "FILENAME";
     default:
       return nullptr;
     }
@@ -824,6 +1065,15 @@ add_setshow_cmds_for_options (command_class cmd_class,
 				  nullptr, option.show_cmd_cb,
 				  set_list, show_list);
 	}
+      else if (option.type == var_filename)
+	{
+	  add_setshow_filename_cmd (option.name, cmd_class,
+				    option.var_address.filename (option, data),
+				    option.set_doc, option.show_doc,
+				    option.help_doc,
+				    nullptr, option.show_cmd_cb,
+				    set_list, show_list);
+	}
       else
 	gdb_assert_not_reached (_("option type not handled"));
     }
diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h
index aa2ccbed5d0..fc3aca887e7 100644
--- a/gdb/cli/cli-option.h
+++ b/gdb/cli/cli-option.h
@@ -87,6 +87,7 @@ struct option_def
       int *(*integer) (const option_def &, void *ctx);
       const char **(*enumeration) (const option_def &, void *ctx);
       char **(*string) (const option_def &, void *ctx);
+      char **(*filename) (const option_def &, void *ctx);
     }
   var_address;
 
@@ -282,6 +283,25 @@ struct string_option_def : option_def
   }
 };
 
+/* A var_filename command line option.  */
+
+template <typename Context>
+struct filename_option_def : option_def
+{
+  filename_option_def (const char *long_option_,
+		       char **(*get_var_address_cb_) (Context *),
+		       show_value_ftype *show_cmd_cb_,
+		       const char *set_doc_,
+		       const char *show_doc_ = nullptr,
+		       const char *help_doc_ = nullptr)
+    : option_def (long_option_, var_filename,
+		  (erased_get_var_address_ftype *) get_var_address_cb_,
+		  show_cmd_cb_, set_doc_, show_doc_, help_doc_)
+  {
+    var_address.enumeration = detail::get_var_address<const char *, Context>;
+  }
+};
+
 /* A group of options that all share the same context pointer to pass
    to the options' get-current-value callbacks.  */
 struct option_def_group
diff --git a/gdb/completer.c b/gdb/completer.c
index 2330ad435a8..0efc28de184 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -1622,6 +1622,8 @@ completion_tracker::discard_completions ()
 					   entry_hash_func, entry_eq_func,
 					   completion_hash_entry::deleter,
 					   xcalloc, xfree));
+
+  m_from_readline = false;
 }
 
 /* See completer.h.  */
@@ -2006,6 +2008,7 @@ gdb_completion_word_break_characters_throw ()
      start afresh.  */
   delete current_completion.tracker;
   current_completion.tracker = new completion_tracker ();
+  current_completion.tracker->set_from_readline (true);
 
   completion_tracker &tracker = *current_completion.tracker;
 
diff --git a/gdb/completer.h b/gdb/completer.h
index 240490f0c05..d9ece352547 100644
--- a/gdb/completer.h
+++ b/gdb/completer.h
@@ -405,6 +405,16 @@ class completion_tracker
   completion_result build_completion_result (const char *text,
 					     int start, int end);
 
+  /* Tells if the completion task is triggered by readline.
+     See m from_readline.  */
+  void set_from_readline (bool from_readline)
+  { m_from_readline = from_readline; }
+
+  /* Tells if the completion task is triggered by readline.
+     See m_from_readline.  */
+  bool from_readline () const
+  { return m_from_readline; }
+
 private:
 
   /* The type that we place into the m_entries_hash hash table.  */
@@ -494,6 +504,11 @@ class completion_tracker
      track the maximum possible size of the lowest common denominator,
      which we know as each completion is added.  */
   size_t m_lowest_common_denominator_max_length = 0;
+
+  /* Indicates that the completions are to be displayed by readline
+     interactively. The 'complete' command is a way to generate completions
+     not to be displayed by readline.  */
+  bool m_from_readline;
 };
 
 /* Return a string to hand off to readline as a completion match
diff --git a/gdb/maint-test-options.c b/gdb/maint-test-options.c
index 16ecd1dd7e5..93d62eecc47 100644
--- a/gdb/maint-test-options.c
+++ b/gdb/maint-test-options.c
@@ -134,6 +134,7 @@ struct test_options_opts
   unsigned int uint_opt = 0;
   int zuint_unl_opt = 0;
   char *string_opt = nullptr;
+  char *filename_opt = nullptr;
 
   test_options_opts () = default;
 
@@ -150,7 +151,8 @@ struct test_options_opts
   {
     fprintf_unfiltered (file,
 			_("-flag %d -xx1 %d -xx2 %d -bool %d "
-			  "-enum %s -uint %s -zuint-unl %s -string '%s' -- %s\n"),
+			  "-enum %s -uint %s -zuint-unl %s -string '%s' "
+			  "-filename '%s' -- %s\n"),
 			flag_opt,
 			xx1_opt,
 			xx2_opt,
@@ -165,6 +167,9 @@ struct test_options_opts
 			(string_opt != nullptr
 			 ? string_opt
 			 : ""),
+			(filename_opt != nullptr
+			 ? filename_opt
+			 : ""),
 			args);
   }
 };
@@ -237,6 +242,14 @@ static const gdb::option::option_def test_options_option_defs[] = {
     nullptr, /* show_cmd_cb */
     N_("A string option."),
   },
+
+  /* A filename option.  */
+  gdb::option::filename_option_def<test_options_opts> {
+    "filename",
+    [] (test_options_opts *opts) { return &opts->filename_opt; },
+    nullptr, /* show_cmd_cb */
+    N_("A filename option."),
+  },
 };
 
 /* Create an option_def_group for the test_options_opts options, with
diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp
index 44c773c3fa3..92b298064b2 100644
--- a/gdb/testsuite/gdb.base/options.exp
+++ b/gdb/testsuite/gdb.base/options.exp
@@ -95,19 +95,19 @@ proc make_cmd {variant} {
 # test-options xxx", with no flag/option set.  OPERAND is the expected
 # operand.
 proc expect_none {operand} {
-    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $operand"
+    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
 # test-options xxx", with -flag set.  OPERAND is the expected operand.
 proc expect_flag {operand} {
-    return "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $operand"
+    return "-flag 1 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
 # test-options xxx", with -bool set.  OPERAND is the expected operand.
 proc expect_bool {operand} {
-    return "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint 0 -zuint-unl 0 -string '' -- $operand"
+    return "-flag 0 -xx1 0 -xx2 0 -bool 1 -enum xxx -uint 0 -zuint-unl 0 -string '' -filename '' -- $operand"
 }
 
 # Return a string for the expected result of running "maint
@@ -116,31 +116,46 @@ proc expect_bool {operand} {
 # expected operand.
 proc expect_integer {option val operand} {
     if {$option == "uinteger"} {
-	return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint $val -zuint-unl 0 -string '' -- $operand"
+	return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint $val -zuint-unl 0 -string '' -filename '' -- $operand"
     } elseif {$option == "zuinteger-unlimited"} {
-	return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -string '' -- $operand"
+	return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl $val -string '' -filename '' -- $operand"
     } else {
 	error "unsupported option: $option"
     }
 }
 
-# Return a string for the expected result of running "maint
-# test-options xxx", with -string set to $STR.  OPERAND is the
-# expected operand.
-proc expect_string {str operand} {
-    # Dequote the string in the expected output.
+# Helper function used to dequote string litterals in expected output.
+proc dequote_expected_string {str} {
     if { ( [string range $str 0 0] == "\""
 	   && [string range $str end end] == "\"")
 	 || ([string range $str 0 0] == "'"
 	     && [string range $str end end] == "'")} {
-	set str [string range $str 1 end-1]
+	return [string range $str 1 end-1]
+    } else {
+	return $str
     }
-    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '$str' -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with -string set to $STR.  OPERAND is the
+# expected operand.
+proc expect_string {str operand} {
+    set str [dequote_expected_string $str]
+    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '$str' -filename '' -- $operand"
+}
+
+# Return a string for the expected result of running "maint
+# test-options xxx", with -filename set to $FILENAME.  OPERAND is the
+# expected operand.
+proc expect_filename {filename operand} {
+    set filename [dequote_expected_string $filename]
+    return "-flag 0 -xx1 0 -xx2 0 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -filename '$filename' -- $operand"
 }
 
 set all_options {
     "-bool"
     "-enum"
+    "-filename"
     "-flag"
     "-string"
     "-uinteger"
@@ -594,7 +609,7 @@ proc_with_prefix test-flag {variant} {
 
     # Extract twice the same flag, separated by one space.
     gdb_test "$cmd -xx1     -xx2 -xx1  -xx2 -xx1    -- non flags args" \
-	"-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -- non flags args"
+	"-flag 0 -xx1 1 -xx2 1 -bool 0 -enum xxx -uint 0 -zuint-unl 0 -string '' -filename '' -- non flags args"
 
     # Extract 2 known flags in front of unknown flags.
     gdb_test "$cmd -xx1 -xx2 -a -b -c -xx1 --" \
@@ -1013,6 +1028,90 @@ proc_with_prefix test-string {variant} {
     }
 }
 
+# filename option tests.
+proc_with_prefix test-filename {variant} {
+    global all_options
+    set tmpdir [standard_output_file ""]
+
+    set cmd [make_cmd $variant]
+
+    gdb_test "$cmd -filename --"\
+	"-filename requires an argument"
+    if {$variant == "require-delimiter"} {
+	gdb_test "$cmd -filename" [expect_none "-filename"]
+    } else {
+	gdb_test "$cmd -filename"\
+	    "-filename requires an argument"
+    }
+
+    # Check that dequoting is happening as expected (similar to string
+    # dequoting).
+    foreach_with_prefix str {
+	"STR"
+	"\"STR\""
+	"\\\"STR"
+	"'STR'"
+	"\\'STR"
+	"\"STR AAA\""
+	"'STR BBB'"
+	"\"STR 'CCC' DDD\""
+	"'STR \"EEE\" FFF'"
+	"\"STR \\\"GGG\\\" HHH\""
+	"'STR \\\'III\\\' JJJ'"
+    } {
+	gdb_test "$cmd -filename ${str} --" [expect_filename "${str}" ""]
+    }
+
+    # Create some tree structure we can use for the test.
+    set bdir "$tmpdir/testfilename"
+    file mkdir "$bdir/b"
+    file mkdir "$bdir/b c"
+    file mkdir "$bdir/b d"
+    file mkdir "$bdir/b c/d"
+    close [open "$bdir/b c/d/e" w]
+
+    # Tab completion for an escaped path.
+    test_gdb_complete_tab_multiple "$cmd -filename $bdir/b" "" {
+        "b/"
+        "b c/"
+        "b d/"
+    }
+    # Checking completions with the COMPLETE command shows escaped filenames.
+    test_gdb_complete_cmd_multiple "$cmd -filename $bdir/b" "" {
+        "/"
+        "\\ c/"
+        "\\ d/"
+    }
+    # When completing on a unique file, append a ' ' at the end.
+    test_gdb_complete_unique \
+        "$cmd -filename $bdir/b\\ c/d/" \
+        "$cmd -filename $bdir/b\\ c/d/e " ""
+
+    # Perform the same sequence with quoted filenames.
+
+    # Tab completion for a quoted path shows raw filenames.
+    test_gdb_complete_tab_multiple "$cmd -filename '$bdir/b" "" {
+        "b/"
+        "b c/"
+        "b d/"
+    }
+    # Using the COMPLETE command shows quoted filenames (without closing
+    # quote).
+    test_gdb_complete_cmd_multiple "$cmd -filename '$bdir/b" "" {
+        " c/" \
+        " d/" \
+        "/"
+    }
+    # When completing on a single option which is a file, append the
+    # closing quote as well as a space.
+    test_gdb_complete_unique \
+        "$cmd -filename '$bdir/b c/d/" \
+        "$cmd -filename '$bdir/b c/d/e' " ""
+
+    # Cleanup.
+    file delete -force "$bdir"
+}
+
 # Run the options framework tests first.
 foreach_with_prefix cmd {
     "require-delimiter"
@@ -1027,6 +1126,7 @@ foreach_with_prefix cmd {
     }
     test-enum $cmd
     test-string $cmd
+    test-filename $cmd
 }
 
 # Run the print integration tests, both as "standalone", and under
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp
index ddd3921977b..42f77887ca7 100644
--- a/gdb/testsuite/lib/completion-support.exp
+++ b/gdb/testsuite/lib/completion-support.exp
@@ -50,7 +50,6 @@ proc make_tab_completion_list_re { completion_list } {
 	append completion_list_re [string_to_regexp $c]
 	append completion_list_re $ws
     }
-    append completion_list_re $ws
 
     return $completion_list_re
 }
-- 
2.29.2


  reply	other threads:[~2021-02-13 22:08 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-02-13 22:07 [PATCH 0/3] Improve the add-inferior completer Lancelot SIX
2021-02-13 22:07 ` Lancelot SIX [this message]
2021-02-16 17:45   ` [PATCH 1/3] gdb::option: Add support for filename option Andrew Burgess
2021-02-16 18:52     ` Lancelot SIX
2021-02-17 10:20       ` Andrew Burgess
2021-02-13 22:07 ` [PATCH 2/3] gdb::option: Add support for zuinteger Lancelot SIX
2021-02-13 22:07 ` [PATCH 3/3] Add completer to the add-inferior command Lancelot SIX
2021-02-16 17:04   ` Andrew Burgess
2021-02-16 18:10     ` Lancelot SIX

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=20210213220752.32581-2-lsix@lancelotsix.com \
    --to=lsix@lancelotsix.com \
    --cc=gdb-patches@sourceware.org \
    /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).