Make the TUI command window support the mouse
authorPedro Alves <pedro@palves.net>
Thu, 17 Jun 2021 10:57:56 +0000 (11:57 +0100)
committerPedro Alves <pedro@palves.net>
Thu, 17 Jun 2021 10:57:56 +0000 (11:57 +0100)
Currently, when the focus is on the command window, we disable the
keypad.  This means that when the command window has the focus, keys
such as up/down/home/end etc. are not processed by curses, and their
escape sequences go straight to readline.

A side effect of disabling keypad mode is that wgetch no longer
processes mouse escape sequences, with the end result being the mouse
doesn't work, and worse, the raw mouse escape sequences are printed on
the terminal.

This commit makes the TUI command window support the mouse as well, by
always enabling the keypad, and then to avoid losing support for
up/down browsing the command history, home/end/left/right moving the
cursor position, etc., we forward those keys as raw escape sequences
to readline.  Note we don't make an effort to pass down to readline
all keys returned by curses, only the common ones that readline
understands by default.  Given users can specify their own readline
bindings (inputrc file, bind utility), this approach is good in
practice, though not 100% transparent or perfect.

Note that the patch makes it so that CTLC-L is always passed to
readline even if the command window does not have the focus.  It was
simpler to implement that way, and it just seems correct to me.  I
don't know of a reason we shouldn't do that.

The patch improves the TUI behavior in a related way.  Now we can pass
special keys to readline irrespective of which window has the focus.
First, we try to dispatch the key to a window, via
tui_displatch_ctrl_char.  If the key is dispatched, then we don't pass
it to readline.  E.g., pressing "up" when you have the source window
in focus results in scrolling the source window, and nothing else.  If
however, you press ctrl-del instead, that results in killing the next
word in the command window, no matter which window has has focus.
Before, it would only work if you had the command window in focus.
Similarly, ctrl-left/ctrl-right to move between words, etc.

Similarly, the previous spot where we handled mouse events was
incorrect.  It was never reached if the window with focus can't
scroll, which is the case for the command window.  Mouse scrolling
affects the window under the mouse cursor, not the window in focus.
We now always try to dispatch mouse events.

One last bit in the patch -- now if we don't recognize the non-8-bit
curses key, then we don't pass it down to readline at all.  Before
that would result in bogus characters in the input line.

gdb/ChangeLog:
yyyy-mm-dd  Pedro Alves  <pedro@palves.net>

* tui/tui-io.c (tui_dispatch_mouse_event): New, factored out from
...
(tui_dispatch_ctrl_char): ... this.  Move CTRL-L handling to
tui_getc_1.
(cur_seq, start_sequence): New.
(tui_getc_1): Pass key escape sequences for curses control keys to
readline.  Handle mouse and ctrl-l here.
(tui_resize_all): Disable/reenable the keypad if the command
window has the focus too.
* tui/tui-win.c (tui_set_focus_command): Don't change keypad
setting.
* tui/tui.c (tui_rl_other_window): Don't change keypad setting.

Change-Id: Ie0a7d849943cfb47f4a6589e1c73341563740fa9

gdb/ChangeLog
gdb/tui/tui-io.c
gdb/tui/tui-win.c
gdb/tui/tui.c

index 2444773bde9353752e5319bfc094c694f04f0652..497d9f6aee48b3a5c9b864194659919a269a2574 100644 (file)
@@ -1,3 +1,18 @@
+2021-06-17  Pedro Alves  <pedro@palves.net>
+
+       * tui/tui-io.c (tui_dispatch_mouse_event): New, factored out from
+       ...
+       (tui_dispatch_ctrl_char): ... this.  Move CTRL-L handling to
+       tui_getc_1.
+       (cur_seq, start_sequence): New.
+       (tui_getc_1): Pass key escape sequences for curses control keys to
+       readline.  Handle mouse and ctrl-l here.
+       (tui_resize_all): Disable/reenable the keypad if the command
+       window has the focus too.
+       * tui/tui-win.c (tui_set_focus_command): Don't change keypad
+       setting.
+       * tui/tui.c (tui_rl_other_window): Don't change keypad setting.
+
 2021-06-16  Simon Marchi  <simon.marchi@polymtl.ca>
 
        * silent-rules.mk (ECHO_CCLD, ECHO_AR, ECHO_RANLIB): New.
index 7df0e2f1bd30f905985a8b52fe13d6abe00830ac..bd443dc0c0c76f4ccade59acb0655e0c612594cb 100644 (file)
@@ -946,6 +946,42 @@ tui_initialize_io (void)
 #endif
 }
 
+/* Dispatch the correct tui function based upon the mouse event.  */
+
+#ifdef NCURSES_MOUSE_VERSION
+
+static void
+tui_dispatch_mouse_event ()
+{
+  MEVENT mev;
+  if (getmouse (&mev) != OK)
+    return;
+
+  for (tui_win_info *wi : all_tui_windows ())
+    if (mev.x > wi->x && mev.x < wi->x + wi->width - 1
+       && mev.y > wi->y && mev.y < wi->y + wi->height - 1)
+      {
+       if ((mev.bstate & BUTTON1_CLICKED) != 0
+           || (mev.bstate & BUTTON2_CLICKED) != 0
+           || (mev.bstate & BUTTON3_CLICKED) != 0)
+         {
+           int button = (mev.bstate & BUTTON1_CLICKED) != 0 ? 1
+             :         ((mev.bstate & BUTTON2_CLICKED) != 0 ? 2
+                        : 3);
+           wi->click (mev.x - wi->x - 1, mev.y - wi->y - 1, button);
+         }
+#ifdef BUTTON5_PRESSED
+       else if ((mev.bstate & BUTTON4_PRESSED) != 0)
+         wi->backward_scroll (3);
+       else if ((mev.bstate & BUTTON5_PRESSED) != 0)
+         wi->forward_scroll (3);
+#endif
+       break;
+      }
+}
+
+#endif
+
 /* Dispatch the correct tui function based upon the control
    character.  */
 static unsigned int
@@ -953,10 +989,6 @@ tui_dispatch_ctrl_char (unsigned int ch)
 {
   struct tui_win_info *win_info = tui_win_with_focus ();
 
-  /* Handle the CTRL-L refresh for each window.  */
-  if (ch == '\f')
-    tui_refresh_all_win ();
-
   /* If no window has the focus, or if the focus window can't scroll,
      just pass the character through.  */
   if (win_info == NULL || !win_info->can_scroll ())
@@ -984,39 +1016,6 @@ tui_dispatch_ctrl_char (unsigned int ch)
     case KEY_LEFT:
       win_info->right_scroll (1);
       break;
-#ifdef NCURSES_MOUSE_VERSION
-    case KEY_MOUSE:
-       {
-         MEVENT mev;
-         if (getmouse (&mev) != OK)
-           break;
-
-         for (tui_win_info *wi : all_tui_windows ())
-           if (mev.x > wi->x && mev.x < wi->x + wi->width - 1
-               && mev.y > wi->y && mev.y < wi->y + wi->height - 1)
-             {
-               if ((mev.bstate & BUTTON1_CLICKED) != 0
-                   || (mev.bstate & BUTTON2_CLICKED) != 0
-                   || (mev.bstate & BUTTON3_CLICKED) != 0)
-                 {
-                   int button = (mev.bstate & BUTTON1_CLICKED) != 0 ? 1
-                     :         ((mev.bstate & BUTTON2_CLICKED) != 0 ? 2
-                                : 3);
-                   wi->click (mev.x - wi->x - 1, mev.y - wi->y - 1, button);
-                 }
-#ifdef BUTTON5_PRESSED
-               else if ((mev.bstate & BUTTON4_PRESSED) != 0)
-                 wi->backward_scroll (3);
-               else if ((mev.bstate & BUTTON5_PRESSED) != 0)
-                 wi->forward_scroll (3);
-#endif
-               break;
-             }
-       }
-      break;
-#endif
-    case '\f':
-      break;
     default:
       /* We didn't recognize the character as a control character, so pass it
         through.  */
@@ -1067,6 +1066,24 @@ tui_inject_newline_into_command_window ()
     }
 }
 
+/* If we're passing an escape sequence to readline, this points to a
+   string holding the remaining characters of the sequence to pass.
+   We advance the pointer one character at a time until '\0' is
+   reached.  */
+static const char *cur_seq = nullptr;
+
+/* Set CUR_SEQ to point at the current sequence to pass to readline,
+   setup to call the input handler again so we complete the sequence
+   shortly, and return the first character to start the sequence.  */
+
+static int
+start_sequence (const char *seq)
+{
+  call_stdin_event_handler_again_p = 1;
+  cur_seq = seq + 1;
+  return seq[0];
+}
+
 /* Main worker for tui_getc.  Get a character from the command window.
    This is called from the readline package, but wrapped in a
    try/catch by tui_getc.  */
@@ -1084,11 +1101,115 @@ tui_getc_1 (FILE *fp)
   tui_readline_output (0, 0);
 #endif
 
-  ch = gdb_wgetch (w);
+  /* We enable keypad mode so that curses's wgetch processes mouse
+     escape sequences.  In keypad mode, wgetch also processes the
+     escape sequences for keys such as up/down etc. and returns KEY_UP
+     / KEY_DOWN etc.  When we have the focus on the command window
+     though, we want to pass the raw up/down etc. escape codes to
+     readline so readline understands them.  */
+  if (cur_seq != nullptr)
+    {
+      ch = *cur_seq++;
+
+      /* If we've reached the end of the string, we're done with the
+        sequence.  Otherwise, setup to get back here again for
+        another character.  */
+      if (*cur_seq == '\0')
+       cur_seq = nullptr;
+      else
+       call_stdin_event_handler_again_p = 1;
+      return ch;
+    }
+  else
+    ch = gdb_wgetch (w);
 
   /* Handle prev/next/up/down here.  */
   ch = tui_dispatch_ctrl_char (ch);
-  
+
+#ifdef NCURSES_MOUSE_VERSION
+  if (ch == KEY_MOUSE)
+    {
+      tui_dispatch_mouse_event ();
+      return 0;
+    }
+#endif
+
+  /* Translate curses keys back to escape sequences so that readline
+     can understand them.  We do this irrespective of which window has
+     the focus.  If e.g., we're focused on a non-command window, then
+     the up/down keys will already have been filtered by
+     tui_dispatch_ctrl_char.  Keys that haven't been intercepted will
+     be passed down to readline.  */
+  if (current_ui->command_editing)
+    {
+      /* For the standard arrow keys + home/end, hardcode sequences
+        readline understands.  See bind_arrow_keys_internal in
+        readline/readline.c.  */
+      switch (ch)
+       {
+       case KEY_UP:
+         return start_sequence ("\033[A");
+       case KEY_DOWN:
+         return start_sequence ("\033[B");
+       case KEY_RIGHT:
+         return start_sequence ("\033[C");
+       case KEY_LEFT:
+         return start_sequence ("\033[D");
+       case KEY_HOME:
+         return start_sequence ("\033[H");
+       case KEY_END:
+         return start_sequence ("\033[F");
+
+       /* del and ins are unfortunately not hardcoded in readline for
+          all systems.  */
+
+       case KEY_DC: /* del */
+#ifdef __MINGW32__
+         return start_sequence ("\340S");
+#else
+         return start_sequence ("\033[3~");
+#endif
+
+       case KEY_IC: /* ins */
+#if defined __MINGW32__
+         return start_sequence ("\340R");
+#else
+         return start_sequence ("\033[2~");
+#endif
+       }
+
+      /* Keycodes above KEY_MAX are not garanteed to be stable.
+        Compare keyname instead.  */
+      if (ch >= KEY_MAX)
+       {
+         auto name = gdb::string_view (keyname (ch));
+
+         /* The following sequences are hardcoded in readline as
+            well.  */
+
+         /* ctrl-arrow keys */
+         if (name == "kLFT5") /* ctrl-left */
+           return start_sequence ("\033[1;5D");
+         else if (name == "kRIT5") /* ctrl-right */
+           return start_sequence ("\033[1;5C");
+         else if (name == "kDC5") /* ctrl-del */
+           return start_sequence ("\033[3;5~");
+
+         /* alt-arrow keys */
+         else if (name == "kLFT3") /* alt-left */
+           return start_sequence ("\033[1;3D");
+         else if (name == "kRIT3") /* alt-right */
+           return start_sequence ("\033[1;3C");
+       }
+    }
+
+  /* Handle the CTRL-L refresh for each window.  */
+  if (ch == '\f')
+    {
+      tui_refresh_all_win ();
+      return ch;
+    }
+
   if (ch == KEY_BACKSPACE)
     return '\b';
 
@@ -1118,6 +1239,13 @@ tui_getc_1 (FILE *fp)
        }
     }
 
+  if (ch > 0xff)
+    {
+      /* Readline doesn't understand non-8-bit curses keys, filter
+        them out.  */
+      return 0;
+    }
+
   return ch;
 }
 
index 4e75a66a00e92cdab4db4acacb5603d12df09d27..a51e7b9f6da5ec9b2cd9f0d035e003e46855c4ee 100644 (file)
@@ -498,14 +498,11 @@ tui_resize_all (void)
   height_diff = screenheight - tui_term_height ();
   if (height_diff || width_diff)
     {
-      struct tui_win_info *win_with_focus = tui_win_with_focus ();
-
 #ifdef HAVE_RESIZE_TERM
       resize_term (screenheight, screenwidth);
 #endif      
       /* Turn keypad off while we resize.  */
-      if (win_with_focus != TUI_CMD_WIN)
-       keypad (TUI_CMD_WIN->handle.get (), FALSE);
+      keypad (TUI_CMD_WIN->handle.get (), FALSE);
       tui_update_gdb_sizes ();
       tui_set_term_height_to (screenheight);
       tui_set_term_width_to (screenwidth);
@@ -515,10 +512,8 @@ tui_resize_all (void)
       erase ();
       clearok (curscr, TRUE);
       tui_apply_current_layout ();
-      /* Turn keypad back on, unless focus is in the command
-        window.  */
-      if (win_with_focus != TUI_CMD_WIN)
-       keypad (TUI_CMD_WIN->handle.get (), TRUE);
+      /* Turn keypad back on.  */
+      keypad (TUI_CMD_WIN->handle.get (), TRUE);
     }
 }
 
@@ -703,7 +698,6 @@ tui_set_focus_command (const char *arg, int from_tty)
     error (_("Window \"%s\" is not visible"), arg);
 
   tui_set_win_focus_to (win_info);
-  keypad (TUI_CMD_WIN->handle.get (), win_info != TUI_CMD_WIN);
   printf_filtered (_("Focus set to %s window.\n"),
                   tui_win_with_focus ()->name ());
 }
index 529fc62c9ac3000c4c8c1b082d45e9311679abdf..5f0c87c05e143beb4449366eb1e6f2b414076dcc 100644 (file)
@@ -179,10 +179,7 @@ tui_rl_other_window (int count, int key)
 
   win_info = tui_next_win (tui_win_with_focus ());
   if (win_info)
-    {
-      tui_set_win_focus_to (win_info);
-      keypad (TUI_CMD_WIN->handle.get (), win_info != TUI_CMD_WIN);
-    }
+    tui_set_win_focus_to (win_info);
   return 0;
 }