This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch] [python] Prompt substitution


I'll start  with a summary of the current patch, then explain why I
reduced the scope of that patch.

This patch allows for simple prompt substitution in GDB.  We install an
observer in display_gdb_prompt, and allow a call to a Python hook
function/class.  The hook function/class can then substitute the prompt
if it wishes to do so.

Originally I wanted this patch to be more expansive.  Some background.
GDB has several ways of handling prompts:

* Normal prompts set with either PROMPT (0), or set_prompt.

* So-called temporary or secondary prompts.  These are prompts that are
  passed to display_gdb_prompt, but never set.  (Command continuation
  markers (IE, ">") are the only users of this).

* prompt_push/prompt_pop which is used by annotations.  Annotations
  also does some other manipulation.

* set/show prompt, which stores the prompt in PROMPT (0), but shows the
  prompt from new_async_prompt.

* MI has its own prompt mechanisms.

I originally wanted two observers.  The first for when a prompt was
going to be set, and the second for when a prompt was going to be
displayed. To do this, I would have to remove the ability to set a
temporary prompt in display_gdb_prompt.

Secondly I would unify prompt setting to set_prompt, and removing
instances of direct prompt setting (IE, PROMPT (0) = xstrdup("blah")
(This incidentally leaks memory and in several instances, the previous
prompt was not freed)).

These operations turned out to be quite complex, if purely mechanical.
The biggest problem was the temporary prompt.  When you pass a value to
display_gdb_prompt it displays that prompt but does not set it.  This
allows internally a simple way to pop the prompt with
display_gdb_prompt (0) (which would just re-display PROMPT (0)).  The
issue here is that observers have to set a prompt for substitution with
set_prompt.  So if we wrote a prompt substitution function that replaced
">" with "command line n", when readline was finished with the command
input, it would pop the prompt with display_gdb_prompt (0). Of course
because our observer has to set the prompt with set_prompt, this would
not work. Popping the temporary prompt would  just re-display "command
line n".  Sigh.  I actually fixed this. Next I started to work on
annotations.

Annotations use pop_prompt/push_prompt.  I started rewriting this logic
to comply with substitution rules.

However, I soon realised I was re-writing most of the prompt code just
to expose an API that really should be unified. While I am in favour of
Python driving API re-design, I think the context should be meaningful
to both the patch and functionality we want to achieve.  So I was left
with this huge patch, and the actual Python API merely being about 5% of
the changes.

So I dropped support for secondary prompts.  I still want to implement
them, but I think the underlying plumbing needs to be improved first.  I
think all prompt changes should be pushed through set_prompt,
display_gdb_prompt should only ever display prompts, and instead of
cheat-popping your gdb prompt with temporary prompts, we should just use
pop/push prompt.  Annotations, I am not sure of.

I have a patch for the set_prompt/display_gdb_prompt which I will submit
later.  This is useful as it squashes some memory leaks.

But for now, I want to concentrate this discussion on the Python
elements of prompt substitution.  We don't have an observer for prompt
changes.  And we won't until the underlying GDB API is fixed/unified.  I
decided because of these changes, exposing anymore to the Python API
now would be unwise. (We will always have a display prompt observer, so I
included that. Hence the long wall of text, and the shortened patch!

Cheers

Phil

--

2011-07-18  Phil Muldoon  <pmuldoon@redhat.com>
            Tom Tromey  <tromey@redhat.com>
	* top.c (set_prompt): Rewrite to free previous prompt, free
	asynch_new_prompt and set both on new prompts.
	* event-top.c (display_gdb_prompt): Add prompt substitution
	logic.
	* python/python.c (before_prompt_hook): New function.
	(gdbpy_get_current_prompt): Ditto.
	(gdbpy_set_current_prompt): Ditto.

2011-07-18  Phil Muldoon  <pmuldoon@redhat.com>

	* gdb.python/python.exp: Add prompt substitution tests.

2011-07-18  Phil Muldoon  <pmuldoon@redhat.com>

	* observer.texi (GDB Observers): Add before_prompt observer.
	* gdb.texinfo (Basic Python): Add documentation for setting,
	getting and prompt substitution.

--

diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 3a3a9fb..03f6c49 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -21080,6 +21080,33 @@ provided, it is decoded the way that @value{GDBN}'s inbuilt
 @code{break} or @code{edit} commands do (@pxref{Specify Location}).
 @end defun
 
+@findex gdb.get_current_prompt
+@defun get_current_prompt
+Return the current @value{GDBN} prompt as a string.
+@end defun
+
+@findex gdb.set_current_prompt
+@defun set_current_prompt @r{[}new_prompt@r{]}
+Sets the @value{GDBN} prompt to @var{new_prompt}.  @var{new_prompt}
+must be a string.  This method has no return value.
+@end defun
+
+@defop Operation {@value{GDBN}} prompt_hook [current_prompt]
+If @var{prompt_hook} exists, and is not set to @code{None},
+@value{GDBN} will call the method assigned to this operation before a
+prompt is displayed by @value{GDBN}.
+
+The parameter @code{current_prompt} contains the current @value{GDBN} 
+prompt.  This method must return a Python string, or @code{None}.  If
+a string is returned, the @value{GDBN} prompt will be set to that
+string.  If @code{None} is returned, @value{GDBN} will continue to use
+the current prompt.
+
+Some prompts cannot be substituted in @value{GDBN}.  Secondary prompts
+such as those used by readline for command input, and annotation
+related prompts are prohibited from being changed.
+@end defop
+
 @node Exception Handling
 @subsubsection Exception Handling
 @cindex python exceptions
diff --git a/gdb/doc/observer.texi b/gdb/doc/observer.texi
index d8c3924..c78b088 100644
--- a/gdb/doc/observer.texi
+++ b/gdb/doc/observer.texi
@@ -222,8 +222,12 @@ Bytes from @var{data} to @var{data} + @var{len} have been written
 to the current inferior at @var{addr}.
 @end deftypefun
 
+@deftypefun void before_prompt (const char *@var{current_prompt})
+Called before a top-level prompt is displayed. @var{current_prompt} is
+the current top-level prompt.
+@end deftypefun
+
 @deftypefun void test_notification (int @var{somearg})
 This observer is used for internal testing.  Do not use.  
 See testsuite/gdb.gdb/observer.exp.
 @end deftypefun
-
diff --git a/gdb/event-top.c b/gdb/event-top.c
index 72cbfdc..a32d1f7 100644
--- a/gdb/event-top.c
+++ b/gdb/event-top.c
@@ -33,6 +33,7 @@
 #include "cli/cli-script.h"     /* for reset_command_nest_depth */
 #include "main.h"
 #include "gdbthread.h"
+#include "observer.h"
 #include "continuations.h"
 #include "gdbcmd.h"		/* for dont_repeat() */
 
@@ -258,7 +259,7 @@ void
 display_gdb_prompt (char *new_prompt)
 {
   int prompt_length = 0;
-  char *gdb_prompt = get_prompt ();
+  char *actual_gdb_prompt = NULL;
 
   /* Reset the nesting depth used when trace-commands is set.  */
   reset_command_nest_depth ();
@@ -268,6 +269,24 @@ display_gdb_prompt (char *new_prompt)
   if (!current_interp_display_prompt_p ())
     return;
 
+  /* Get the prompt before the observers are called as observer hook
+     functions may change the prompt.  Do not call observers on an
+     explicit prompt change as passed to this function, as this forms
+     a temporary prompt, IE, displayed but not set.  */
+  if (! new_prompt)
+    {
+      char *post_gdb_prompt = NULL;
+      char *pre_gdb_prompt = get_prompt ();
+
+      observer_notify_before_prompt (pre_gdb_prompt);
+      post_gdb_prompt = get_prompt ();
+
+      /* If the observer changed the prompt, use that prompt overriding
+	 any new_prompt that was passed to this function.  */
+      if (strcmp (pre_gdb_prompt, post_gdb_prompt) != 0)
+	actual_gdb_prompt = post_gdb_prompt;
+    }
+
   if (sync_execution && is_running (inferior_ptid))
     {
       /* This is to trick readline into not trying to display the
@@ -289,27 +308,35 @@ display_gdb_prompt (char *new_prompt)
       return;
     }
 
-  if (!new_prompt)
+  /* If the observer changed the prompt, ACTUAL_GDB_PROMPT will not be
+     NULL.  Otherwise, either copy the existing prompt, or set it to
+     NEW_PROMPT.  */
+  if (! actual_gdb_prompt)
     {
-      /* Just use the top of the prompt stack.  */
-      prompt_length = strlen (PREFIX (0)) +
-	strlen (SUFFIX (0)) +
-	strlen (gdb_prompt) + 1;
-
-      new_prompt = (char *) alloca (prompt_length);
-
-      /* Prefix needs to have new line at end.  */
-      strcpy (new_prompt, PREFIX (0));
-      strcat (new_prompt, gdb_prompt);
-      /* Suffix needs to have a new line at end and \032 \032 at
-         beginning.  */
-      strcat (new_prompt, SUFFIX (0));
+      if (! new_prompt)
+	{
+	  /* Just use the top of the prompt stack.  */
+	  prompt_length = strlen (PREFIX (0)) +
+	    strlen (SUFFIX (0)) +
+	    strlen (get_prompt()) + 1;
+
+	  actual_gdb_prompt = (char *) alloca (prompt_length);
+
+	  /* Prefix needs to have new line at end.  */
+	  strcpy (actual_gdb_prompt, PREFIX (0));
+	  strcat (actual_gdb_prompt, get_prompt());
+	  /* Suffix needs to have a new line at end and \032 \032 at
+	     beginning.  */
+	  strcat (actual_gdb_prompt, SUFFIX (0));
+	}
+      else
+	actual_gdb_prompt = new_prompt;;
     }
 
   if (async_command_editing_p)
     {
       rl_callback_handler_remove ();
-      rl_callback_handler_install (new_prompt, input_handler);
+      rl_callback_handler_install (actual_gdb_prompt, input_handler);
     }
   /* new_prompt at this point can be the top of the stack or the one
      passed in.  It can't be NULL.  */
@@ -318,7 +345,7 @@ display_gdb_prompt (char *new_prompt)
       /* Don't use a _filtered function here.  It causes the assumed
          character position to be off, since the newline we read from
          the user is not accounted for.  */
-      fputs_unfiltered (new_prompt, gdb_stdout);
+      fputs_unfiltered (actual_gdb_prompt, gdb_stdout);
       gdb_flush (gdb_stdout);
     }
 }
diff --git a/gdb/python/python.c b/gdb/python/python.c
index f15936d..de9fb56 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -51,6 +51,7 @@ static int gdbpy_should_print_stack = 0;
 #include "version.h"
 #include "target.h"
 #include "gdbthread.h"
+#include "observer.h"
 
 static PyMethodDef GdbMethods[];
 
@@ -682,6 +683,89 @@ gdbpy_initialize_events (void)
     }
 }
 
+
+
+static void
+before_prompt_hook (const char *current_gdb_prompt)
+{
+  struct cleanup *cleanup;
+  char *prompt = NULL;
+
+  cleanup = ensure_python_env (get_current_arch (), current_language);
+
+  if (PyObject_HasAttrString (gdb_module, "prompt_hook"))
+    {
+      PyObject *hook;
+
+      hook = PyObject_GetAttrString (gdb_module, "prompt_hook");
+      if (hook == NULL)
+	goto fail;
+
+      if (PyCallable_Check (hook))
+	{
+	  PyObject *result;
+	  PyObject *current_prompt;
+
+	  if (current_gdb_prompt == NULL)
+	    {
+	      current_prompt = Py_None;
+	      Py_INCREF (Py_None);
+	    }
+	  else
+	    {
+	      current_prompt =  PyString_FromString (current_gdb_prompt);
+	      if (current_prompt == NULL)
+		goto fail;
+	    }
+
+	  result = PyObject_CallFunctionObjArgs (hook, current_prompt, NULL);
+
+	  Py_DECREF (current_prompt);
+
+	  if (result == NULL)
+	    goto fail;
+
+	  make_cleanup_py_decref (result);
+
+	  /* Return type should be None, or a String.  If it is None,
+	     fall through, we will not set a prompt.  If it is a
+	     string, set  PROMPT.  Anything else, set an exception.  */
+	  if ((result != Py_None) && (! PyString_Check (result)))
+	    {
+	      PyErr_Format (PyExc_RuntimeError,
+			    _("Return from prompt_hook must " \
+			      "be either a Python string, or None"));
+	      goto fail;
+	    }
+
+	  if (result != Py_None)
+	    {
+	      prompt = python_string_to_host_string (result);
+
+	      if (prompt == NULL)
+		goto fail;
+	      else
+		make_cleanup (xfree, prompt);
+	    }
+	}
+    }
+
+  /* If a prompt has been set, PROMPT will not be NULL.  If it is
+     NULL, do not set the prompt.  */
+  if (prompt != NULL)
+    set_prompt (prompt);
+
+  do_cleanups (cleanup);
+  return;
+
+ fail:
+  gdbpy_print_stack ();
+  do_cleanups (cleanup);
+  return;
+}
+
+
+
 /* Printing.  */
 
 /* A python function to write a single string using gdb's filtered
@@ -811,6 +895,33 @@ gdbpy_progspaces (PyObject *unused1, PyObject *unused2)
   return list;
 }
 
+/* Return the current GDB prompt as a string.  */
+static PyObject *
+gdbpy_get_current_prompt (PyObject *self, PyObject *args)
+{
+  const char *prompt = get_prompt ();
+
+  if (prompt)
+    return PyString_FromString (prompt);
+
+  Py_RETURN_NONE;
+}
+
+/* Set the current GDB prompt.  Only accept strings.  */
+static PyObject *
+gdbpy_set_current_prompt (PyObject *self, PyObject *args)
+{
+  char *prompt = NULL;
+
+  if (! PyArg_ParseTuple (args, "s", &prompt))
+    return NULL;
+
+  if (prompt)
+    set_prompt (prompt);
+
+  Py_RETURN_NONE;
+}
+
 
 
 /* The "current" objfile.  This is set when gdb detects that a new
@@ -1134,6 +1245,8 @@ Enables or disables printing of Python stack traces."),
   gdbpy_initialize_exited_event ();
   gdbpy_initialize_thread_event ();
 
+  observer_attach_before_prompt (before_prompt_hook);
+
   PyRun_SimpleString ("import gdb");
   PyRun_SimpleString ("gdb.pretty_printers = []");
 
@@ -1236,6 +1349,8 @@ def GdbSetPythonDirectory (dir):\n\
 \n\
 # Install the default gdb.PYTHONDIR.\n\
 GdbSetPythonDirectory (gdb.PYTHONDIR)\n\
+# Default prompt hook does nothing.\n\
+prompt_hook = None\n\
 ");
 
   do_cleanups (cleanup);
@@ -1338,6 +1453,16 @@ Return the selected thread object." },
   { "inferiors", gdbpy_inferiors, METH_NOARGS,
     "inferiors () -> (gdb.Inferior, ...).\n\
 Return a tuple containing all inferiors." },
+  { "get_current_prompt", gdbpy_get_current_prompt, METH_NOARGS,
+    "get_current_prompt () -> String.\n\
+Return the current GDB prompt" },
+  { "set_current_prompt", (PyCFunction)gdbpy_set_current_prompt,
+    METH_VARARGS,
+    "set_current_prompt (String).\n\
+Set the current GDB prompt." },
+  { "flush", (PyCFunction)gdbpy_flush, METH_VARARGS | METH_KEYWORDS,
+    "Flush gdb's filtered stdout stream." },
+
   {NULL, NULL, 0, NULL}
 };
 
diff --git a/gdb/testsuite/gdb.python/python.exp b/gdb/testsuite/gdb.python/python.exp
index 8906bbc..1eda476 100644
--- a/gdb/testsuite/gdb.python/python.exp
+++ b/gdb/testsuite/gdb.python/python.exp
@@ -193,3 +193,104 @@ gdb_py_test_silent_cmd "set python print-stack on" \
     "Test print-backtrace set setting" 1
 gdb_test "show python print-stack" \
     "Whether Python stack will be printed on error is on.*" \
+
+# Test prompt substituion
+
+gdb_py_test_multiple "prompt substitution" \
+  "python" "" \
+  "someCounter = 0" "" \
+  "def prompt(current):" "" \
+  "   global someCounter" "" \
+  "   if (current == \"testfake \"):" "" \
+  "      return None" "" \
+  "   someCounter = someCounter + 1" "" \
+  "   return \"py prompt \" + str (someCounter) + \" \"" "" \
+  "end" ""
+
+gdb_py_test_multiple "prompt substitution readline" \
+  "python" "" \
+  "pCounter = 0" "" \
+  "def program_prompt(current):" "" \
+  "   global pCounter" "" \
+  "   if (current == \">\"):" "" \
+  "      pCounter = pCounter + 1" "" \
+  "      return \"python line \" + str (pCounter) + \": \"" "" \
+  "   return None" "" \
+  "end" ""
+
+set newprompt "py prompt 1"
+set newprompt2 "py prompt 2"
+set testfake "testfake"
+
+gdb_py_test_silent_cmd "python p = gdb.get_current_prompt\(\)" \
+    "Get the current prompt" 1
+
+gdb_test_multiple "python gdb.prompt_hook = prompt" "set the hook" {
+    -re "\[\r\n\]$newprompt $" {
+	pass "set hook"
+    }
+}
+
+gdb_test_multiple "set prompt testfake " "set testfake prompt in GDB" {
+    -re "\[\r\n\]$testfake $" {
+	pass "set prompt testfake"
+    }
+}
+
+gdb_test_multiple "show prompt" "show testfake prompt" {
+    -re "Gdb's prompt is \"$testfake \"..* $" {
+	pass "show prompt shows guarded prompt"
+    }
+}
+
+gdb_test_multiple "set prompt blah " "set blah in GDB" {
+    -re "\[\r\n\]$newprompt2 $" {
+	pass "set prompt blah overriden"
+    }
+}
+
+gdb_test_multiple "python gdb.prompt_hook = None" "Delete hook" {
+    -re "\[\r\n\]$newprompt2 $" {
+	pass "Delete old hook"
+    }
+}
+
+gdb_py_test_silent_cmd "python gdb.set_current_prompt\(p\)" \
+    "Set the default prompt" 1
+
+gdb_test_multiple "python gdb.prompt_hook = program_prompt" "set the hook" {
+    -re "\[\r\n\]$gdb_prompt $" {
+	pass "set programming hook"
+    }
+}
+
+gdb_test_multiple "python" "test we ignore substituion for seconday prompts" {
+    -re "\r\n>$" {
+	pass "readline secondary are not substituted"
+    }
+}
+
+gdb_test_multiple "end" "end programming" {
+    -re "\[\r\n\]$gdb_prompt $" {
+	pass "end programming"
+    }
+}
+
+# Test get/set prompt
+set foo "foobar "
+gdb_test_multiple "python gdb.set_current_prompt(\"$foo\")" "set prompt" {
+    -re "\[\r\n\]$foo$" {
+	pass "set prompt"
+    }
+}
+gdb_test_multiple "python curr = gdb.get_current_prompt()" "get prompt" {
+    -re "\[\r\n\]$foo$" {
+	pass "get prompt"
+    }
+}
+gdb_test_multiple "python print curr" "print prompt get prompt" {
+    -re "$foo.\[\r\n\]$foo$" {
+	pass "get prompt"
+    }
+}
+
diff --git a/gdb/top.c b/gdb/top.c
index ecb91f2..09cd1ce 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -1133,14 +1133,21 @@ get_prompt (void)
 }
 
 void
-set_prompt (char *s)
+set_prompt (const char *s)
 {
-/* ??rehrauer: I don't know why this fails, since it looks as though
-   assignments to prompt are wrapped in calls to xstrdup...
-   if (prompt != NULL)
-     xfree (prompt);
- */
-  PROMPT (0) = xstrdup (s);
+  /* If S == PROMPT then do not free it or set it.  If we did
+     that, S (which points to PROMPT), would become garbage.  */
+
+  if (s != PROMPT (0))
+    {
+      xfree (PROMPT (0));
+      PROMPT (0) =  xstrdup (s);
+
+      /* Also, free and set new_async_prompt so prompt changes sync up
+	 with set/show prompt.  */
+      xfree (new_async_prompt);
+      new_async_prompt = xstrdup (PROMPT (0));
+    }
 }
 
 
diff --git a/gdb/top.h b/gdb/top.h
index 4a6e8fb..24ec2f2 100644
--- a/gdb/top.h
+++ b/gdb/top.h
@@ -55,7 +55,7 @@ extern char *get_prompt (void);
 
 /* This function copies the specified string into the string that
    is used by gdb for its command prompt.  */
-extern void set_prompt (char *);
+extern void set_prompt (const char *);
 
 /* From random places.  */
 extern int readnow_symbol_files;


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]