[PATCH 2/2] gdb: fix filename completion in the middle of a line

Andrew Burgess aburgess@redhat.com
Sat Sep 14 11:51:49 GMT 2024


I noticed that filename completion in the middle of a line doesn't
work as I would expect it too.  For example, assuming '/tmp/filename'
exists, and is the only file in '/tmp/' then when I do the following:

  (gdb) file "/tmp/filen<TAB>

GDB completes to:

  (gdb) file "/tmp/filename"

But, if I type this:

  (gdb) file "/tmp/filen "xxx"

Then move the cursor to the end of '/tmp/filen' and press <TAB>, GDB
will complete the line to:

  (gdb) file "/tmp/filename "xxx"

But GDB will not insert the trailing double quote character.

The reason for this is found in readline/readline/complete.c in the
function append_to_match.  This is the function that appends the
trailing closing quote character, however, the closing quote is only
inserted if the cursor (rl_point) is at the end (rl_end) of the line
being completed.

In this patch, what I do instead is add the closing quote in the
function gdb_completer_file_name_quote, which is called from readline
through the rl_filename_quoting_function hook.  The docs for
rl_filename_quoting_function say (see 'info readline'):

  "... The MATCH_TYPE is either 'SINGLE_MATCH', if there is only one
  completion match, or 'MULT_MATCH'.  Some functions use this to
  decide whether or not to insert a closing quote character. ..."

This is exactly what I'm doing in this patch, and clearly this is not
an unusual choice.  Now after completing a filename that is not at the
end of the line GDB will add the closing quote character if
appropriate.

I have managed to write some tests for this.  I send a line of text to
GDB which includes a partial filename followed by a trailing string, I
then send the escape sequence to move the cursor left, and finally I
send the tab character.

Obviously, expect doesn't actually see the complete output with the
extra text "in place", instead expect sees the original line followed
by some escape sequences to reflect the cursor movement, then an
escape sequence to indicate that text is being inserted in the middle
of a line, followed by the new characters ... it's a bit messy, but I
think it holds together.
---
 gdb/completer.c                               |  30 ++++-
 .../gdb.base/filename-completion.exp          | 123 ++++++++++++++++++
 2 files changed, 149 insertions(+), 4 deletions(-)

diff --git a/gdb/completer.c b/gdb/completer.c
index 64c6611f636..4735aa5ed10 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -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.  */
@@ -393,13 +394,34 @@ gdb_completer_file_name_quote_1 (const char *text, char quote_char)
    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.  */
+   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 ATTRIBUTE_UNUSED,
-			       char *quote_ptr)
+gdb_completer_file_name_quote (char *text, int match_type, char *quote_ptr)
 {
-  return gdb_completer_file_name_quote_1 (text, *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
diff --git a/gdb/testsuite/gdb.base/filename-completion.exp b/gdb/testsuite/gdb.base/filename-completion.exp
index cdd597f689d..9170e2671af 100644
--- a/gdb/testsuite/gdb.base/filename-completion.exp
+++ b/gdb/testsuite/gdb.base/filename-completion.exp
@@ -113,6 +113,108 @@ proc test_gdb_complete_filename_multiple {
 	$testname
 }
 
+# Helper proc.  Returns a string containing the escape sequence to move the
+# cursor COUNT characters to the left.  There's no sanity checking performed
+# on COUNT, so the user of this proc must ensure there are more than COUNT
+# characters on the current line.
+proc c_left { count } {
+    string repeat "\033\[D" $count
+}
+
+# This proc is based off of test_gdb_complete_tab_multiple in
+# completion-support.exp library.  This proc however tests completing a
+# filename in the middle of a command line.
+#
+# INPUT_LINE is the line to complete, BACK_COUNT is the number of characters
+# to move the cursor left before sending tab to complete the line.
+# ADD_COMPLETED_LINE is what we expect to be unconditionally added the first
+# time tab is sent.  On additional tabs COMPLETION_LIST will be displayed.
+# TESTNAME is used as expected.
+proc test_tab_complete_within_line_multiple { input_line back_count \
+						  add_completed_line \
+						  completion_list \
+						  testname } {
+    global gdb_prompt
+
+    # After displaying the completion list the line will be reprinted, but
+    # now with ADD_COMPLETED_LINE inserted.  Build the regexp to match
+    # against this expanded line.  The new content will be inserted
+    # BACK_COUNT character from the end of the line.
+    set expanded_line \
+	[join [list \
+		   [string range $input_line 0 end-$back_count] \
+		   ${add_completed_line} \
+		   [string range $input_line end-[expr $back_count - 1] end]] \
+	     ""]
+    set expanded_line_re [string_to_regexp $expanded_line]
+
+    # Convert input arguments into regexp.
+    set input_line_re [string_to_regexp $input_line]
+    set add_completed_line_re [string_to_regexp $add_completed_line]
+    set completion_list_re [make_tab_completion_list_re $completion_list]
+
+    # Build regexp for the line after the cursor has moved BACK_COUNT
+    # characters back and tab has been sent.  The '\\\x08' matches a single
+    # backward (left) motion, and the '\\\x1b\\\x5bN\\x40' is the escape
+    # sequence to insert N characters into the output.
+    set after_tab_re "^$input_line_re"
+    set after_tab_re "$after_tab_re\\\x08{$back_count}"
+    set after_tab_re "$after_tab_re${completion::bell_re}"
+    set after_tab_re "$after_tab_re\\\x1b\\\x5b[string length $add_completed_line]\\\x40"
+    set after_tab_re "$after_tab_re$add_completed_line_re\$"
+
+    send_gdb "$input_line[c_left $back_count]\t"
+    gdb_test_multiple "" "$testname (first tab)" {
+	-re "$after_tab_re" {
+	    send_gdb "\t"
+	    # If we auto-completed to an ambiguous prefix, we need an
+	    # extra tab to show the matches list.
+	    if {$add_completed_line != ""} {
+		send_gdb "\t"
+		set maybe_bell ${completion::bell_re}
+	    } else {
+		set maybe_bell ""
+	    }
+	    gdb_test_multiple "" "$testname (second tab)" {
+		-re "^${maybe_bell}\r\n$completion_list_re\r\n$gdb_prompt " {
+		    gdb_test_multiple "" "$testname (second tab)" {
+			-re "^$expanded_line_re\\\x08{$back_count}$" {
+			    pass $gdb_test_name
+			}
+		    }
+		}
+		-re "${maybe_bell}\r\n.+\r\n$gdb_prompt $" {
+		    fail $gdb_test_name
+		}
+	    }
+	}
+    }
+
+    clear_input_line $testname
+}
+
+# Wrapper around test_gdb_complete_tab_unique to test completing a unique
+# item in the middle of a line.  INPUT_LINE is the line to complete.
+# BACK_COUNT is the number of characters to move left within INPUT_LINE
+# before sending tab to perform completion.  INSERT_STR is what we expect to
+# see inserted by the completion engine in GDB.
+proc test_tab_complete_within_line_unique { input_line back_count insert_str } {
+    # Build regexp for the line after the cursor has moved BACK_COUNT
+    # characters back and tab has been sent.  The '\\\x08' matches a single
+    # backward (left) motion, and the '\\\x1b\\\x5bN\\x40' is the escape
+    # sequence to insert N characters into the output.
+    set re [string_to_regexp $input_line]
+    set re $re\\\x08{$back_count}
+    set re $re\\\x1b\\\x5b[string length $insert_str]\\\x40${insert_str}
+
+    test_gdb_complete_tab_unique \
+	"${input_line}[c_left $back_count]" \
+	$re \
+	"" \
+	"complete unique file within command line"
+}
+
+
 # Run filename completetion tests for those command that accept quoting and
 # escaping of the filename argument.  CMD is the inital part of the command
 # line, paths to complete will be added after CMD.
@@ -226,6 +328,25 @@ proc run_quoting_and_escaping_tests_1 { root cmd } {
     gdb_exit
 }
 
+# Tests for completing a filename in the middle of a command line.  This
+# represents a user typing out a command line then moving the cursor left
+# (e.g. with the left arrow key), editing a filename argument, and then
+# using tab completion to try and complete the filename even though there is
+# other content on the command line after the filename.
+proc run_mid_line_completion_tests { root cmd } {
+    gdb_start
+
+    test_tab_complete_within_line_unique \
+	"$cmd \"$root/bb2/dir 1/unique fi \"xxx\"" 6 "le\""
+
+    test_tab_complete_within_line_multiple \
+	"$cmd \"$root/aaa/a \"xxx\"" 6 "a " \
+	[list "aa bb" "aa cc"] \
+	"complete filename mid-line with multiple possibilities"
+
+    gdb_exit
+}
+
 # Run filename completetion tests for those command that accept quoting and
 # escaping of the filename argument.
 #
@@ -248,6 +369,8 @@ proc run_quoting_and_escaping_tests { root } {
 	foreach_with_prefix filler { "" " \"xxx\"" " 'zzz'" " yyy"} {
 	    run_quoting_and_escaping_tests_1 $root "$cmd$filler"
 	}
+
+	run_mid_line_completion_tests $root $cmd
     }
 }
 
-- 
2.25.4



More information about the Gdb-patches mailing list