Skip to content

Commit f48cd13

Browse files
author
Magne Hov
committed
add libpython_ui.py extending TUI mode with Python windows
1 parent b9731c9 commit f48cd13

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

libpython_ui.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import subprocess
2+
3+
import gdb
4+
5+
6+
def highlight_python(text):
7+
"""
8+
Pipe bytes through the highlight program to add Python syntax highlighting.
9+
"""
10+
if getattr(highlight_python, "failed", False):
11+
return text
12+
result = subprocess.run(
13+
["highlight", "--syntax=python", "--out-format=ansi"],
14+
stdout=subprocess.PIPE,
15+
input=text,
16+
check=False,
17+
)
18+
if result.returncode:
19+
print("Failed to provide syntax highlighting for Python.")
20+
print("Please install the `highlight` program.")
21+
highlight_python.failed = True
22+
return text
23+
return result.stdout
24+
25+
26+
def register_window(name):
27+
"""
28+
Register a TUI window and define a new layout for it.
29+
"""
30+
31+
def decorator(cls):
32+
gdb.register_window_type(name, cls)
33+
gdb.execute(f"tui new-layout {name} {name} 1 status 1 cmd 1")
34+
return cls
35+
36+
return decorator
37+
38+
39+
class Window:
40+
title: str | None = None
41+
42+
def __init__(self, tui_window):
43+
self._tui_window = tui_window
44+
self._tui_window.title = self.title
45+
gdb.events.before_prompt.connect(self.render)
46+
47+
def get_lines(self):
48+
raise NotImplementedError()
49+
50+
def render(self):
51+
if not self._tui_window.is_valid():
52+
return
53+
54+
# Truncate output
55+
lines = self.get_lines()[:self._tui_window.height]
56+
lines = (line[:self._tui_window.width - 1] for line in lines)
57+
58+
output = "\n".join(lines)
59+
self._tui_window.write(output, True)
60+
61+
def close(self):
62+
gdb.events.before_prompt.disconnect(self.render)
63+
64+
65+
@register_window("python-source")
66+
class PythonSourceWindow(Window):
67+
title = "Python Source"
68+
69+
def get_lines(self):
70+
python_source = gdb.execute("py-list", to_string=True).encode("utf-8")
71+
return highlight_python(python_source).decode("utf-8").splitlines()
72+
73+
74+
@register_window("python-backtrace")
75+
class PythonBacktraceWindow(Window):
76+
title = "Python Backtrace"
77+
78+
def get_lines(self):
79+
return gdb.execute("py-bt", to_string=True).splitlines()
80+
81+
82+
@register_window("python-locals")
83+
class PythonLocalsWindow(Window):
84+
title = "Local Python Variables"
85+
86+
def get_lines(self):
87+
return gdb.execute("py-locals", to_string=True).splitlines()
88+
89+
90+
@register_window("python-bytecode")
91+
class PythonBytecodeWindow(Window):
92+
title = "Python Bytecode"
93+
94+
def get_lines(self):
95+
lines = gdb.execute("py-dis", to_string=True).splitlines()
96+
total_lines = len(lines)
97+
height = self._tui_window.height
98+
if total_lines < height:
99+
return lines
100+
101+
current_line = None
102+
for index, line in enumerate(lines, 1):
103+
if "-->" in line:
104+
current_line = index
105+
break
106+
else:
107+
return lines[:height]
108+
109+
first_half = height // 2
110+
second_half = height - first_half
111+
if current_line < first_half:
112+
return lines[:height]
113+
if current_line + second_half > total_lines:
114+
return lines[-height:]
115+
return lines[current_line - first_half : current_line + second_half]
116+
117+
118+
# Define a layout with all Python windows
119+
gdb.execute(
120+
" ".join(
121+
(
122+
"tui new-layout python",
123+
"python-backtrace 2",
124+
"{-horizontal python-bytecode 1 python-locals 1} 2",
125+
"python-source 2",
126+
"status 1 cmd 1",
127+
)
128+
)
129+
)

0 commit comments

Comments
 (0)