1 /* TUI windows implemented in Python
3 Copyright (C) 2020 Free Software Foundation, Inc.
5 This file is part of GDB.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
22 #include "arch-utils.h"
23 #include "python-internal.h"
27 /* Note that Python's public headers may define HAVE_NCURSES_H, so if
28 we unconditionally include this (outside the #ifdef above), then we
29 can get a compile error when ncurses is not in fact installed. See
30 PR tui/25597; or the upstream Python bug
31 https://bugs.python.org/issue20768. */
32 #include "gdb_curses.h"
34 #include "tui/tui-data.h"
35 #include "tui/tui-io.h"
36 #include "tui/tui-layout.h"
37 #include "tui/tui-wingeneral.h"
38 #include "tui/tui-winsource.h"
42 /* A PyObject representing a TUI window. */
44 struct gdbpy_tui_window
48 /* The TUI window, or nullptr if the window has been deleted. */
49 tui_py_window
*window
;
52 extern PyTypeObject gdbpy_tui_window_object_type
53 CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF ("gdbpy_tui_window");
55 /* A TUI window written in Python. */
57 class tui_py_window
: public tui_win_info
61 tui_py_window (const char *name
, gdbpy_ref
<gdbpy_tui_window
> wrapper
)
63 m_wrapper (std::move (wrapper
))
65 m_wrapper
->window
= this;
70 DISABLE_COPY_AND_ASSIGN (tui_py_window
);
72 /* Set the "user window" to the indicated reference. The user
73 window is the object returned the by user-defined window
75 void set_user_window (gdbpy_ref
<> &&user_window
)
77 m_window
= std::move (user_window
);
80 const char *name () const override
82 return m_name
.c_str ();
85 void rerender () override
;
86 void do_scroll_vertical (int num_to_scroll
) override
;
87 void do_scroll_horizontal (int num_to_scroll
) override
;
89 /* Erase and re-box the window. */
94 werase (handle
.get ());
95 check_and_display_highlight_if_needed ();
101 /* Write STR to the window. */
102 void output (const char *str
);
104 /* A helper function to compute the viewport width. */
105 int viewport_width () const
107 return std::max (0, width
- 2);
110 /* A helper function to compute the viewport height. */
111 int viewport_height () const
113 return std::max (0, height
- 2);
118 /* Location of the cursor. */
122 /* The name of this window. */
125 /* The underlying Python window object. */
126 gdbpy_ref
<> m_window
;
128 /* The Python wrapper for this object. */
129 gdbpy_ref
<gdbpy_tui_window
> m_wrapper
;
132 tui_py_window::~tui_py_window ()
134 gdbpy_enter
enter_py (get_current_arch (), current_language
);
136 if (PyObject_HasAttrString (m_window
.get (), "close"))
138 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "close",
140 if (result
== nullptr)
141 gdbpy_print_stack ();
145 m_wrapper
->window
= nullptr;
146 /* Explicitly free the Python references. We have to do this
147 manually because we need to hold the GIL while doing so. */
148 m_wrapper
.reset (nullptr);
149 m_window
.reset (nullptr);
153 tui_py_window::rerender ()
155 gdbpy_enter
enter_py (get_current_arch (), current_language
);
157 if (PyObject_HasAttrString (m_window
.get (), "render"))
159 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "render",
161 if (result
== nullptr)
162 gdbpy_print_stack ();
167 tui_py_window::do_scroll_horizontal (int num_to_scroll
)
169 gdbpy_enter
enter_py (get_current_arch (), current_language
);
171 if (PyObject_HasAttrString (m_window
.get (), "hscroll"))
173 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get(), "hscroll",
174 "i", num_to_scroll
, nullptr));
175 if (result
== nullptr)
176 gdbpy_print_stack ();
181 tui_py_window::do_scroll_vertical (int num_to_scroll
)
183 gdbpy_enter
enter_py (get_current_arch (), current_language
);
185 if (PyObject_HasAttrString (m_window
.get (), "vscroll"))
187 gdbpy_ref
<> result (PyObject_CallMethod (m_window
.get (), "vscroll",
188 "i", num_to_scroll
, nullptr));
189 if (result
== nullptr)
190 gdbpy_print_stack ();
195 tui_py_window::output (const char *text
)
197 int vwidth
= viewport_width ();
199 while (cursor_y
< viewport_height () && *text
!= '\0')
201 wmove (handle
.get (), cursor_y
+ 1, cursor_x
+ 1);
203 std::string line
= tui_copy_source_line (&text
, 0, 0,
204 vwidth
- cursor_x
, 0);
205 tui_puts (line
.c_str (), handle
.get ());
214 cursor_x
= getcurx (handle
.get ()) - 1;
217 wrefresh (handle
.get ());
222 /* A callable that is used to create a TUI window. It wraps the
223 user-supplied window constructor. */
225 class gdbpy_tui_window_maker
229 explicit gdbpy_tui_window_maker (gdbpy_ref
<> &&constr
)
230 : m_constr (std::move (constr
))
234 ~gdbpy_tui_window_maker ();
236 gdbpy_tui_window_maker (gdbpy_tui_window_maker
&&other
)
237 : m_constr (std::move (other
.m_constr
))
241 gdbpy_tui_window_maker (const gdbpy_tui_window_maker
&other
)
243 gdbpy_enter
enter_py (get_current_arch (), current_language
);
244 m_constr
= other
.m_constr
;
247 gdbpy_tui_window_maker
&operator= (gdbpy_tui_window_maker
&&other
)
249 m_constr
= std::move (other
.m_constr
);
253 gdbpy_tui_window_maker
&operator= (const gdbpy_tui_window_maker
&other
)
255 gdbpy_enter
enter_py (get_current_arch (), current_language
);
256 m_constr
= other
.m_constr
;
260 tui_win_info
*operator() (const char *name
);
264 /* A constructor that is called to make a TUI window. */
265 gdbpy_ref
<> m_constr
;
268 gdbpy_tui_window_maker::~gdbpy_tui_window_maker ()
270 gdbpy_enter
enter_py (get_current_arch (), current_language
);
271 m_constr
.reset (nullptr);
275 gdbpy_tui_window_maker::operator() (const char *win_name
)
277 gdbpy_enter
enter_py (get_current_arch (), current_language
);
279 gdbpy_ref
<gdbpy_tui_window
> wrapper
280 (PyObject_New (gdbpy_tui_window
, &gdbpy_tui_window_object_type
));
281 if (wrapper
== nullptr)
283 gdbpy_print_stack ();
287 std::unique_ptr
<tui_py_window
> window
288 (new tui_py_window (win_name
, wrapper
));
290 gdbpy_ref
<> user_window
291 (PyObject_CallFunctionObjArgs (m_constr
.get (),
292 (PyObject
*) wrapper
.get (),
294 if (user_window
== nullptr)
296 gdbpy_print_stack ();
300 window
->set_user_window (std::move (user_window
));
301 /* Window is now owned by the TUI. */
302 return window
.release ();
305 /* Implement "gdb.register_window_type". */
308 gdbpy_register_tui_window (PyObject
*self
, PyObject
*args
, PyObject
*kw
)
310 static const char *keywords
[] = { "name", "constructor", nullptr };
315 if (!gdb_PyArg_ParseTupleAndKeywords (args
, kw
, "sO", keywords
,
321 gdbpy_tui_window_maker
constr (gdbpy_ref
<>::new_reference (cons_obj
));
322 tui_register_window (name
, constr
);
324 catch (const gdb_exception
&except
)
326 gdbpy_convert_exception (except
);
335 /* Require that "Window" be a valid window. */
337 #define REQUIRE_WINDOW(Window) \
339 if ((Window)->window == nullptr) \
340 return PyErr_Format (PyExc_RuntimeError, \
341 _("TUI window is invalid.")); \
344 /* Python function which checks the validity of a TUI window
347 gdbpy_tui_is_valid (PyObject
*self
, PyObject
*args
)
349 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
351 if (win
->window
!= nullptr)
356 /* Python function that erases the TUI window. */
358 gdbpy_tui_erase (PyObject
*self
, PyObject
*args
)
360 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
362 REQUIRE_WINDOW (win
);
364 win
->window
->erase ();
369 /* Python function that writes some text to a TUI window. */
371 gdbpy_tui_write (PyObject
*self
, PyObject
*args
)
373 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
376 if (!PyArg_ParseTuple (args
, "s", &text
))
379 REQUIRE_WINDOW (win
);
381 win
->window
->output (text
);
386 /* Return the width of the TUI window. */
388 gdbpy_tui_width (PyObject
*self
, void *closure
)
390 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
391 REQUIRE_WINDOW (win
);
392 return PyLong_FromLong (win
->window
->viewport_width ());
395 /* Return the height of the TUI window. */
397 gdbpy_tui_height (PyObject
*self
, void *closure
)
399 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
400 REQUIRE_WINDOW (win
);
401 return PyLong_FromLong (win
->window
->viewport_height ());
404 /* Return the title of the TUI window. */
406 gdbpy_tui_title (PyObject
*self
, void *closure
)
408 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
409 REQUIRE_WINDOW (win
);
410 return host_string_to_python_string (win
->window
->title
.c_str ()).release ();
413 /* Set the title of the TUI window. */
415 gdbpy_tui_set_title (PyObject
*self
, PyObject
*newvalue
, void *closure
)
417 gdbpy_tui_window
*win
= (gdbpy_tui_window
*) self
;
419 if (win
->window
== nullptr)
421 PyErr_Format (PyExc_RuntimeError
, _("TUI window is invalid."));
425 if (win
->window
== nullptr)
427 PyErr_Format (PyExc_TypeError
, _("Cannot delete \"title\" attribute."));
431 gdb::unique_xmalloc_ptr
<char> value
432 = python_string_to_host_string (newvalue
);
433 if (value
== nullptr)
436 win
->window
->title
= value
.get ();
440 static gdb_PyGetSetDef tui_object_getset
[] =
442 { "width", gdbpy_tui_width
, NULL
, "Width of the window.", NULL
},
443 { "height", gdbpy_tui_height
, NULL
, "Height of the window.", NULL
},
444 { "title", gdbpy_tui_title
, gdbpy_tui_set_title
, "Title of the window.",
446 { NULL
} /* Sentinel */
449 static PyMethodDef tui_object_methods
[] =
451 { "is_valid", gdbpy_tui_is_valid
, METH_NOARGS
,
452 "is_valid () -> Boolean\n\
453 Return true if this TUI window is valid, false if not." },
454 { "erase", gdbpy_tui_erase
, METH_NOARGS
,
455 "Erase the TUI window." },
456 { "write", (PyCFunction
) gdbpy_tui_write
, METH_VARARGS
,
457 "Append a string to the TUI window." },
458 { NULL
} /* Sentinel. */
461 PyTypeObject gdbpy_tui_window_object_type
=
463 PyVarObject_HEAD_INIT (NULL
, 0)
464 "gdb.TuiWindow", /*tp_name*/
465 sizeof (gdbpy_tui_window
), /*tp_basicsize*/
474 0, /*tp_as_sequence*/
482 Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_BASETYPE
, /*tp_flags*/
483 "GDB TUI window object", /* tp_doc */
486 0, /* tp_richcompare */
487 0, /* tp_weaklistoffset */
490 tui_object_methods
, /* tp_methods */
492 tui_object_getset
, /* tp_getset */
495 0, /* tp_descr_get */
496 0, /* tp_descr_set */
497 0, /* tp_dictoffset */
504 /* Initialize this module. */
507 gdbpy_initialize_tui ()
510 gdbpy_tui_window_object_type
.tp_new
= PyType_GenericNew
;
511 if (PyType_Ready (&gdbpy_tui_window_object_type
) < 0)