From patchwork Sat Jan 4 18:34:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 37194 Received: (qmail 3793 invoked by alias); 4 Jan 2020 18:34:43 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 1444 invoked by uid 89); 4 Jan 2020 18:34:29 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-21.4 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_HELO_PASS autolearn=ham version=3.3.1 spammy=owned, sal, letters X-HELO: gateway36.websitewelcome.com Received: from gateway36.websitewelcome.com (HELO gateway36.websitewelcome.com) (192.185.194.2) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sat, 04 Jan 2020 18:34:18 +0000 Received: from cm10.websitewelcome.com (cm10.websitewelcome.com [100.42.49.4]) by gateway36.websitewelcome.com (Postfix) with ESMTP id 143C7400F7ECC for ; Sat, 4 Jan 2020 11:46:09 -0600 (CST) Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with SMTP id noFdipMSuHunhnoFdi0Bj3; Sat, 04 Jan 2020 12:34:17 -0600 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=teyRfNGKBDb5cBghI7o10EngJZ9/jv4gLuj2OCZuTfs=; b=M2Uyip7FjxlZEXHbEgLrQuGhV3 NaV+9URH8PAGgGyQ/sjwro7l2+uwla76HGDKS4dNwsh0YXHlkGSKSuvKPAnFLbEaZ101Lh5WvPT4F ffpYsIGqUSesfcQaMl16tKJrN; Received: from 75-166-123-50.hlrn.qwest.net ([75.166.123.50]:48942 helo=bapiya.Home) by box5379.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92) (envelope-from ) id 1inoFd-0026Lh-6j; Sat, 04 Jan 2020 11:34:17 -0700 From: Tom Tromey To: gdb-patches@sourceware.org Cc: Tom Tromey Subject: [PATCH 20/24] Allow TUI windows in Python Date: Sat, 4 Jan 2020 11:34:06 -0700 Message-Id: <20200104183410.17114-21-tom@tromey.com> In-Reply-To: <20200104183410.17114-1-tom@tromey.com> References: <20200104183410.17114-1-tom@tromey.com> This patch adds support for writing new TUI windows in Python. 2020-01-04 Tom Tromey * NEWS: Add entry for gdb.register_window_type. * tui/tui-layout.h (window_factory): New typedef. (tui_register_window): Declare. * tui/tui-layout.c (saved_tui_windows): New global. (tui_apply_current_layout): Use it. (tui_register_window): New function. * python/python.c (do_start_initialization): Call gdbpy_initialize_tui. (python_GdbMethods): Add "register_window_type" function. * python/python-internal.h (gdbpy_register_tui_window) (gdbpy_initialize_tui): Declare. * python/py-tui.c: New file. * Makefile.in (SUBDIR_PYTHON_SRCS): Add py-tui.c. gdb/doc/ChangeLog 2020-01-04 Tom Tromey * python.texi (Python API): Add menu item. (TUI Windows In Python): New node. gdb/testsuite/ChangeLog 2020-01-04 Tom Tromey * gdb.python/tui-window.exp: New file. * gdb.python/tui-window.py: New file. Change-Id: I85fbfb923a1840450a00a7dce113a05d7f048baa --- gdb/ChangeLog | 16 + gdb/Makefile.in | 1 + gdb/NEWS | 5 + gdb/doc/ChangeLog | 5 + gdb/doc/python.texi | 101 +++++ gdb/python/py-tui.c | 510 ++++++++++++++++++++++++ gdb/python/python-internal.h | 4 + gdb/python/python.c | 10 +- gdb/testsuite/ChangeLog | 5 + gdb/testsuite/gdb.python/tui-window.exp | 51 +++ gdb/testsuite/gdb.python/tui-window.py | 37 ++ gdb/tui/tui-layout.c | 28 +- gdb/tui/tui-layout.h | 10 + 13 files changed, 779 insertions(+), 4 deletions(-) create mode 100644 gdb/python/py-tui.c create mode 100644 gdb/testsuite/gdb.python/tui-window.exp create mode 100644 gdb/testsuite/gdb.python/tui-window.py diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 448a495bb3b..adfe607dc87 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -401,6 +401,7 @@ SUBDIR_PYTHON_SRCS = \ python/py-symbol.c \ python/py-symtab.c \ python/py-threadevent.c \ + python/py-tui.c \ python/py-type.c \ python/py-unwind.c \ python/py-utils.c \ diff --git a/gdb/NEWS b/gdb/NEWS index a936620c0a8..f15c5f5c15f 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -15,6 +15,11 @@ tui new-layout NAME WINDOW WEIGHT [WINDOW WEIGHT]... Define a new TUI layout, specifying its name and the windows that will be displayed. +* Python API + + ** gdb.register_window_type can be used to implement new TUI windows + in Python. + *** Changes in GDB 9 * 'thread-exited' event is now available in the annotations interface. diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi index 8124077ab33..d0ce9ea35e7 100644 --- a/gdb/doc/python.texi +++ b/gdb/doc/python.texi @@ -163,6 +163,7 @@ optional arguments while skipping others. Example: using Python. * Lazy Strings In Python:: Python representation of lazy strings. * Architectures In Python:: Python representation of architectures. +* TUI Windows In Python:: Implementing new TUI windows. @end menu @node Basic Python @@ -5673,6 +5674,106 @@ instruction in bytes. @end table @end defun +@node TUI Windows In Python +@subsubsection Implementing new TUI windows +@cindex Python TUI Windows + +New TUI (@pxref{TUI}) windows can be implemented in Python. + +@findex gdb.register_window_type +@defun gdb.register_window_type (@var{name}, @var{factory}) +Because TUI windows are created and destroyed depending on the layout +the user chooses, new window types are implemented by registering a +factory function with @value{GDBN}. + +@var{name} is the name of the new window. It's an error to try to +replace one of the built-in windows, but other window types can be +replaced. + +@var{function} is a factory function that is called to create the TUI +window. This is called with a single argument of type +@code{gdb.TuiWindow}, described below. It should return an object +that implements the TUI window protocol, also described below. +@end defun + +As mentioned above, when a factory function is called, it is passed a +an object of type @code{gdb.TuiWindow}. This object has these +methods and attributes: + +@defun TuiWindow.is_valid () +This method returns @code{True} when this window is valid. When the +user changes the TUI layout, windows no longer visible in the new +layout will be destroyed. At this point, the @code{gdb.TuiWindow} +will no longer be valid, and methods (and attributes) other than +@code{is_valid} will throw an exception. +@end defun + +@defvar TuiWindow.width +This attribute holds the width of the window. It is not writable. +@end defvar + +@defvar TuiWindow.height +This attribute holds the height of the window. It is not writable. +@end defvar + +@defvar TuiWindow.title +This attribute holds the window's title, a string. This is normally +displayed above the window. This attribute can be modified. +@end defvar + +@defun TuiWindow.erase () +Remove all the contents of the window. +@end defun + +@defun TuiWindow.write (@var{string}) +Write @var{string} to the window. @var{string} can contain ANSI +terminal escape styling sequences; @value{GDBN} will convert translate +these as appropriate for the terminal. +@end defun + +The factory function that you supply should return an object +conforming to the TUI window protocol. These are the method that can +be called on this object, which is referred to below as the ``window +object''. The methods documented below are optional; if the object +does not implement one of these methods, @value{GDBN} will not attempt +to call it. Additional new methods may be added to the window +protocol in the future. @value{GDBN} guarantees that they will begin +with a lower-case letter, so you can start implementation methods with +upper-case letters or underscore to avoid any future conflicts. + +@defun Window.close () +When the TUI window is closed, the @code{gdb.TuiWindow} object will be +put into an invalid state. At this time, @value{GDBN} will call +@code{close} method on the window object. + +After this method is called, @value{GDBN} will discard any references +it holds on this window object, and will no longer call methods on +this object. +@end defun + +@defun Window.render () +In some situations, a TUI window can change size. For example, this +can happen if the user resizes the terminal, or changes the layout. +When this happens, @value{GDBN} will call the @code{render} method on +the window object. + +If your window is intended to update in response to changes in the +inferior, you will probably also want to register event listeners and +send output to the @code{gdb.TuiWindow}. +@end defun + +@defun Window.hscroll (@var{num}) +This is a request to scroll the window horizontally. @var{num} is the +amount by which to scroll, with negative numbers meaning to scroll +right. +@end defun + +@defun Window.vscroll (@var{num}) +This is a request to scroll the window vertically. @var{num} is the +amount by which to scroll, with negative numbers meaning to scroll +backward. +@end defun + @node Python Auto-loading @subsection Python Auto-loading @cindex Python auto-loading diff --git a/gdb/python/py-tui.c b/gdb/python/py-tui.c new file mode 100644 index 00000000000..4cb86ae75da --- /dev/null +++ b/gdb/python/py-tui.c @@ -0,0 +1,510 @@ +/* TUI windows implemented in Python + + Copyright (C) 2020 Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include "defs.h" +#include "arch-utils.h" +#include "python-internal.h" +#include "gdb_curses.h" + +#ifdef TUI + +#include "tui/tui-data.h" +#include "tui/tui-io.h" +#include "tui/tui-layout.h" +#include "tui/tui-wingeneral.h" +#include "tui/tui-winsource.h" + +class tui_py_window; + +/* A PyObject representing a TUI window. */ + +struct gdbpy_tui_window +{ + PyObject_HEAD + + /* The TUI window, or nullptr if the window has been deleted. */ + tui_py_window *window; +}; + +extern PyTypeObject gdbpy_tui_window_object_type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window"); + +/* A TUI window written in Python. */ + +class tui_py_window : public tui_win_info +{ +public: + + tui_py_window (const char *name, gdbpy_ref wrapper) + : m_name (name), + m_wrapper (std::move (wrapper)) + { + m_wrapper->window = this; + } + + ~tui_py_window (); + + DISABLE_COPY_AND_ASSIGN (tui_py_window); + + /* Set the "user window" to the indicated reference. The user + window is the object returned the by user-defined window + constructor. */ + void set_user_window (gdbpy_ref<> &&user_window) + { + m_window = std::move (user_window); + } + + const char *name () const override + { + return m_name.c_str (); + } + + void rerender () override; + void do_scroll_vertical (int num_to_scroll) override; + void do_scroll_horizontal (int num_to_scroll) override; + + /* Erase and re-box the window. */ + void erase () + { + if (is_visible ()) + { + werase (handle.get ()); + check_and_display_highlight_if_needed (); + cursor_x = 0; + cursor_y = 0; + } + } + + /* Write STR to the window. */ + void output (const char *str); + + /* A helper function to compute the viewport width. */ + int viewport_width () const + { + return std::max (0, width - 2); + } + + /* A helper function to compute the viewport height. */ + int viewport_height () const + { + return std::max (0, height - 2); + } + +private: + + /* Location of the cursor. */ + int cursor_x = 0; + int cursor_y = 0; + + /* The name of this window. */ + std::string m_name; + + /* The underlying Python window object. */ + gdbpy_ref<> m_window; + + /* The Python wrapper for this object. */ + gdbpy_ref m_wrapper; +}; + +tui_py_window::~tui_py_window () +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (PyObject_HasAttrString (m_window.get (), "close")) + { + gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "close", + nullptr)); + if (result == nullptr) + gdbpy_print_stack (); + } + + /* Unlink. */ + m_wrapper->window = nullptr; + /* Explicitly free the Python references. We have to do this + manually because we need to hold the GIL while doing so. */ + m_wrapper.reset (nullptr); + m_window.reset (nullptr); +} + +void +tui_py_window::rerender () +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (PyObject_HasAttrString (m_window.get (), "render")) + { + gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "render", + nullptr)); + if (result == nullptr) + gdbpy_print_stack (); + } +} + +void +tui_py_window::do_scroll_horizontal (int num_to_scroll) +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (PyObject_HasAttrString (m_window.get (), "hscroll")) + { + gdbpy_ref<> result (PyObject_CallMethod (m_window.get(), "hscroll", + "i", num_to_scroll, nullptr)); + if (result == nullptr) + gdbpy_print_stack (); + } +} + +void +tui_py_window::do_scroll_vertical (int num_to_scroll) +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + + if (PyObject_HasAttrString (m_window.get (), "vscroll")) + { + gdbpy_ref<> result (PyObject_CallMethod (m_window.get (), "vscroll", + "i", num_to_scroll, nullptr)); + if (result == nullptr) + gdbpy_print_stack (); + } +} + +void +tui_py_window::output (const char *text) +{ + int vwidth = viewport_width (); + + while (cursor_y < viewport_height () && *text != '\0') + { + wmove (handle.get (), cursor_y + 1, cursor_x + 1); + + std::string line = tui_copy_source_line (&text, 0, 0, + vwidth - cursor_x, 0); + tui_puts (line.c_str (), handle.get ()); + + if (*text == '\n') + { + ++text; + ++cursor_y; + cursor_x = 0; + } + else + cursor_x = getcurx (handle.get ()) - 1; + } + + wrefresh (handle.get ()); +} + + + +/* A callable that is used to create a TUI window. It wraps the + user-supplied window constructor. */ + +class gdbpy_tui_window_maker +{ +public: + + explicit gdbpy_tui_window_maker (gdbpy_ref<> &&constr) + : m_constr (std::move (constr)) + { + } + + ~gdbpy_tui_window_maker (); + + gdbpy_tui_window_maker (gdbpy_tui_window_maker &&other) + : m_constr (std::move (other.m_constr)) + { + } + + gdbpy_tui_window_maker (const gdbpy_tui_window_maker &other) + { + gdbpy_enter enter_py (get_current_arch (), current_language); + m_constr = other.m_constr; + } + + gdbpy_tui_window_maker &operator= (gdbpy_tui_window_maker &&other) + { + m_constr = std::move (other.m_constr); + return *this; + } + + gdbpy_tui_window_maker &operator= (const gdbpy_tui_window_maker &other) + { + gdbpy_enter enter_py (get_current_arch (), current_language); + m_constr = other.m_constr; + return *this; + } + + tui_win_info *operator() (const char *name); + +private: + + /* A constructor that is called to make a TUI window. */ + gdbpy_ref<> m_constr; +}; + +gdbpy_tui_window_maker::~gdbpy_tui_window_maker () +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + m_constr.reset (nullptr); +} + +tui_win_info * +gdbpy_tui_window_maker::operator() (const char *win_name) +{ + gdbpy_enter enter_py (get_current_arch (), current_language); + + gdbpy_ref wrapper + (PyObject_New (gdbpy_tui_window, &gdbpy_tui_window_object_type)); + if (wrapper == nullptr) + { + gdbpy_print_stack (); + return nullptr; + } + + std::unique_ptr window + (new tui_py_window (win_name, wrapper)); + + gdbpy_ref<> user_window + (PyObject_CallFunctionObjArgs (m_constr.get (), + (PyObject *) wrapper.get (), + nullptr)); + if (user_window == nullptr) + { + gdbpy_print_stack (); + return nullptr; + } + + window->set_user_window (std::move (user_window)); + /* Window is now owned by the TUI. */ + return window.release (); +} + +/* Implement "gdb.register_window_type". */ + +PyObject * +gdbpy_register_tui_window (PyObject *self, PyObject *args, PyObject *kw) +{ + static const char *keywords[] = { "name", "constructor", nullptr }; + + const char *name; + PyObject *cons_obj; + + if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "sO", keywords, + &name, &cons_obj)) + return nullptr; + + try + { + gdbpy_tui_window_maker constr (gdbpy_ref<>::new_reference (cons_obj)); + tui_register_window (name, constr); + } + catch (const gdb_exception &except) + { + gdbpy_convert_exception (except); + return nullptr; + } + + Py_RETURN_NONE; +} + + + +/* Require that "Window" be a valid window. */ + +#define REQUIRE_WINDOW(Window) \ + do { \ + if ((Window)->window == nullptr) \ + return PyErr_Format (PyExc_RuntimeError, \ + _("TUI window is invalid.")); \ + } while (0) + +/* Python function which checks the validity of a TUI window + object. */ +static PyObject * +gdbpy_tui_is_valid (PyObject *self, PyObject *args) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + + if (win->window != nullptr) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +/* Python function that erases the TUI window. */ +static PyObject * +gdbpy_tui_erase (PyObject *self, PyObject *args) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + + REQUIRE_WINDOW (win); + + win->window->erase (); + + Py_RETURN_NONE; +} + +/* Python function that writes some text to a TUI window. */ +static PyObject * +gdbpy_tui_write (PyObject *self, PyObject *args) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + const char *text; + + if (!PyArg_ParseTuple (args, "s", &text)) + return nullptr; + + REQUIRE_WINDOW (win); + + win->window->output (text); + + Py_RETURN_NONE; +} + +/* Return the width of the TUI window. */ +static PyObject * +gdbpy_tui_width (PyObject *self, void *closure) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + REQUIRE_WINDOW (win); + return PyLong_FromLong (win->window->viewport_width ()); +} + +/* Return the height of the TUI window. */ +static PyObject * +gdbpy_tui_height (PyObject *self, void *closure) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + REQUIRE_WINDOW (win); + return PyLong_FromLong (win->window->viewport_height ()); +} + +/* Return the title of the TUI window. */ +static PyObject * +gdbpy_tui_title (PyObject *self, void *closure) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + REQUIRE_WINDOW (win); + return host_string_to_python_string (win->window->title.c_str ()).release (); +} + +/* Set the title of the TUI window. */ +static int +gdbpy_tui_set_title (PyObject *self, PyObject *newvalue, void *closure) +{ + gdbpy_tui_window *win = (gdbpy_tui_window *) self; + + if (win->window == nullptr) + { + PyErr_Format (PyExc_RuntimeError, _("TUI window is invalid.")); + return -1; + } + + if (win->window == nullptr) + { + PyErr_Format (PyExc_TypeError, _("Cannot delete \"title\" attribute.")); + return -1; + } + + gdb::unique_xmalloc_ptr value + = python_string_to_host_string (newvalue); + if (value == nullptr) + return -1; + + win->window->title = value.get (); + return 0; +} + +static gdb_PyGetSetDef tui_object_getset[] = +{ + { "width", gdbpy_tui_width, NULL, "Width of the window.", NULL }, + { "height", gdbpy_tui_height, NULL, "Height of the window.", NULL }, + { "title", gdbpy_tui_title, gdbpy_tui_set_title, "Title of the window.", + NULL }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef tui_object_methods[] = +{ + { "is_valid", gdbpy_tui_is_valid, METH_NOARGS, + "is_valid () -> Boolean\n\ +Return true if this TUI window is valid, false if not." }, + { "erase", gdbpy_tui_erase, METH_NOARGS, + "Erase the TUI window." }, + { "write", (PyCFunction) gdbpy_tui_write, METH_VARARGS, + "Append a string to the TUI window." }, + { NULL } /* Sentinel. */ +}; + +PyTypeObject gdbpy_tui_window_object_type = +{ + PyVarObject_HEAD_INIT (NULL, 0) + "gdb.TuiWindow", /*tp_name*/ + sizeof (gdbpy_tui_window), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro */ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "GDB TUI window object", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + tui_object_methods, /* tp_methods */ + 0, /* tp_members */ + tui_object_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ +}; + +#endif /* TUI */ + +/* Initialize this module. */ + +int +gdbpy_initialize_tui () +{ +#ifdef TUI + gdbpy_tui_window_object_type.tp_new = PyType_GenericNew; + if (PyType_Ready (&gdbpy_tui_window_object_type) < 0) + return -1; +#endif /* TUI */ + + return 0; +} diff --git a/gdb/python/python-internal.h b/gdb/python/python-internal.h index e2464548a7e..bbb66bd0f5c 100644 --- a/gdb/python/python-internal.h +++ b/gdb/python/python-internal.h @@ -447,6 +447,8 @@ PyObject *gdbpy_parameter_value (enum var_types type, void *var); char *gdbpy_parse_command_name (const char *name, struct cmd_list_element ***base_list, struct cmd_list_element **start_list); +PyObject *gdbpy_register_tui_window (PyObject *self, PyObject *args, + PyObject *kw); PyObject *symtab_and_line_to_sal_object (struct symtab_and_line sal); PyObject *symtab_to_symtab_object (struct symtab *symtab); @@ -543,6 +545,8 @@ int gdbpy_initialize_xmethods (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; int gdbpy_initialize_unwind (void) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; +int gdbpy_initialize_tui () + CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; /* A wrapper for PyErr_Fetch that handles reference counting for the caller. */ diff --git a/gdb/python/python.c b/gdb/python/python.c index bf214fae6e2..55103de2bd1 100644 --- a/gdb/python/python.c +++ b/gdb/python/python.c @@ -1686,7 +1686,8 @@ do_start_initialization () || gdbpy_initialize_event () < 0 || gdbpy_initialize_arch () < 0 || gdbpy_initialize_xmethods () < 0 - || gdbpy_initialize_unwind () < 0) + || gdbpy_initialize_unwind () < 0 + || gdbpy_initialize_tui () < 0) return false; #define GDB_PY_DEFINE_EVENT_TYPE(name, py_name, doc, base) \ @@ -2038,6 +2039,13 @@ or None if not set." }, "convenience_variable (NAME, VALUE) -> None.\n\ Set the value of the convenience variable $NAME." }, +#ifdef TUI + { "register_window_type", (PyCFunction) gdbpy_register_tui_window, + METH_VARARGS | METH_KEYWORDS, + "register_window_type (NAME, CONSTRUCSTOR) -> None\n\ +Register a TUI window constructor." }, +#endif /* TUI */ + {NULL, NULL, 0, NULL} }; diff --git a/gdb/testsuite/gdb.python/tui-window.exp b/gdb/testsuite/gdb.python/tui-window.exp new file mode 100644 index 00000000000..5c8909d4079 --- /dev/null +++ b/gdb/testsuite/gdb.python/tui-window.exp @@ -0,0 +1,51 @@ +# Copyright (C) 2020 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Test a TUI window implemented in Python. + +load_lib gdb-python.exp +load_lib tuiterm.exp + +# This test doesn't care about the inferior. +standard_testfile py-arch.c + +if {[build_executable "failed to prepare" ${testfile} ${srcfile}] == -1} { + return -1 +} + +Term::clean_restart 24 80 $testfile + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +set remote_python_file [gdb_remote_download host \ + ${srcdir}/${subdir}/${testfile}.py] +gdb_test_no_output "source ${remote_python_file}" \ + "source ${testfile}.py" + +gdb_test_no_output "tui new-layout test test 1 locator 0 cmd 1" + +if {![Term::enter_tui]} { + unsupported "TUI not supported" +} + +Term::command "layout test" +Term::check_contents "test title" \ + "This Is The Title" +Term::check_contents "Window display" "Test: 0" + +Term::resize 51 51 +# Remember that a resize request actually does two resizes... +Term::check_contents "Window was updated" "Test: 2" diff --git a/gdb/testsuite/gdb.python/tui-window.py b/gdb/testsuite/gdb.python/tui-window.py new file mode 100644 index 00000000000..4deb585f138 --- /dev/null +++ b/gdb/testsuite/gdb.python/tui-window.py @@ -0,0 +1,37 @@ +# Copyright (C) 2020 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# A TUI window implemented in Python. + +import gdb + +the_window = None + +class TestWindow: + def __init__(self, win): + global the_window + the_window = win + self.count = 0 + self.win = win + win.title = "This Is The Title" + + def render(self): + self.win.erase() + w = self.win.width + h = self.win.height + self.win.write("Test: " + str(self.count) + " " + str(w) + "x" + str(h)) + self.count = self.count + 1 + +gdb.register_window_type("test", TestWindow) diff --git a/gdb/tui/tui-layout.c b/gdb/tui/tui-layout.c index 797acc6e8b7..d55660f89f6 100644 --- a/gdb/tui/tui-layout.c +++ b/gdb/tui/tui-layout.c @@ -65,6 +65,11 @@ static tui_layout_split *asm_regs_layout; /* See tui-data.h. */ std::vector tui_windows; +/* When applying a layout, this is the list of all windows that were + in the previous layout. This is used to re-use windows when + changing a layout. */ +static std::vector saved_tui_windows; + /* See tui-layout.h. */ void @@ -75,10 +80,10 @@ tui_apply_current_layout () extract_display_start_addr (&gdbarch, &addr); - std::vector saved_windows = std::move (tui_windows); + saved_tui_windows = std::move (tui_windows); tui_windows.clear (); - for (tui_win_info *win_info : saved_windows) + for (tui_win_info *win_info : saved_tui_windows) win_info->make_visible (false); applied_layout->apply (0, 0, tui_term_width (), tui_term_height ()); @@ -94,7 +99,7 @@ tui_apply_current_layout () /* Now delete any window that was not re-applied. */ tui_win_info *focus = tui_win_with_focus (); - for (tui_win_info *win_info : saved_windows) + for (tui_win_info *win_info : saved_tui_windows) { if (!win_info->is_visible ()) { @@ -107,6 +112,8 @@ tui_apply_current_layout () if (gdbarch == nullptr && TUI_DISASM_WIN != nullptr) tui_get_begin_asm_address (&gdbarch, &addr); tui_update_source_windows_with_addr (gdbarch, addr); + + saved_tui_windows.clear (); } /* See tui-layout. */ @@ -395,6 +402,21 @@ initialize_known_windows () /* See tui-layout.h. */ +void +tui_register_window (const char *name, window_factory &&factory) +{ + std::string name_copy = name; + + if (name_copy == "src" || name_copy == "cmd" || name_copy == "regs" + || name_copy == "asm" || name_copy == "locator") + error (_("Window type \"%s\" is built-in"), name); + + known_window_types->emplace (std::move (name_copy), + std::move (factory)); +} + +/* See tui-layout.h. */ + std::unique_ptr tui_layout_window::clone () const { diff --git a/gdb/tui/tui-layout.h b/gdb/tui/tui-layout.h index 6607e8d40d8..90618377e17 100644 --- a/gdb/tui/tui-layout.h +++ b/gdb/tui/tui-layout.h @@ -249,4 +249,14 @@ extern void tui_apply_current_layout (); extern void tui_adjust_window_height (struct tui_win_info *win, int new_height); +/* The type of a function that is used to create a TUI window. */ + +typedef std::function window_factory; + +/* Register a new TUI window type. NAME is the name of the window + type. FACTORY is a function that can be called to instantiate the + window. */ + +extern void tui_register_window (const char *name, window_factory &&factory); + #endif /* TUI_TUI_LAYOUT_H */