Revert "[lldb] Real-time console pane for output in lldb tui" (#196507)

Reverts llvm/llvm-project#177160

The new test is timing out on the AArch64 Linux buildbot
(https://lab.llvm.org/buildbot/#/builders/59/builds/34166) and on my own
machine.

I suspect something to do with the requested terminal size. If what we
get is smaller than requested, it could time out waiting for expected
program output.
This commit is contained in:
David Spickett 2026-05-08 11:16:15 +01:00 committed by GitHub
parent ca7fe08755
commit 0bdf71d5a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 23 additions and 544 deletions

View file

@ -406,8 +406,6 @@ public:
bool SetShowInlineDiagnostics(bool);
uint64_t GetGuiMaxConsoleLines() const;
bool LoadPlugin(const FileSpec &spec, Status &error);
void RunIOHandlers();

View file

@ -293,8 +293,4 @@ let Definition = "debugger", Path = "" in {
Global,
DefaultFalse,
Desc<"Controls whether diagnostics can refer directly to the command input, drawing arrows to it. If false, diagnostics will echo the input.">;
def GuiMaxConsoleLines: Property<"gui-console-max-lines", "UInt64">,
Global,
DefaultUnsignedValue<10000>,
Desc<"The maximum number of lines to keep in the console output window.">;
}

View file

@ -780,12 +780,6 @@ bool Debugger::SetShowInlineDiagnostics(bool b) {
return SetPropertyAtIndex(idx, b);
}
uint64_t Debugger::GetGuiMaxConsoleLines() const {
const uint32_t idx = ePropertyGuiMaxConsoleLines;
return GetPropertyAtIndexAs<uint64_t>(
idx, g_debugger_properties[idx].default_uint_value);
}
#pragma mark Debugger
// const DebuggerPropertiesSP &

View file

@ -327,8 +327,6 @@ public:
virtual const char *WindowDelegateGetHelpText() { return nullptr; }
virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
virtual void WindowDelegateProcessEvent(const lldb::EventSP &event_sp) {}
};
class HelpDialogDelegate : public WindowDelegate {
@ -801,13 +799,6 @@ public:
subwindow_sp->Draw(force);
}
void HandleProcessEvent(const lldb::EventSP &event_sp) {
if (m_delegate_sp)
m_delegate_sp->WindowDelegateProcessEvent(event_sp);
for (auto &subwindow_sp : m_subwindows)
subwindow_sp->HandleProcessEvent(event_sp);
}
bool CreateHelpSubwindow() {
if (m_delegate_sp) {
const char *text = m_delegate_sp->WindowDelegateGetHelpText();
@ -4394,11 +4385,6 @@ public:
ConstString broadcaster_class(
broadcaster->GetBroadcasterClass());
if (broadcaster_class == broadcaster_class_process) {
uint32_t event_type = event_sp->GetType();
if (event_type & (Process::eBroadcastBitSTDOUT |
Process::eBroadcastBitSTDERR)) {
m_window_sp->HandleProcessEvent(event_sp);
}
m_update_screen = true;
continue; // Don't get any key, just update our view
}
@ -6325,233 +6311,6 @@ HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
return eKeyHandled;
}
class ConsoleOutputWindowDelegate : public WindowDelegate {
private:
void PollProcessOutput() {
ExecutionContext exe_ctx =
m_debugger.GetCommandInterpreter().GetExecutionContext();
Process *process = exe_ctx.GetProcessPtr();
if (!process || !process->IsAlive())
return;
// Buffer for reading output.
char buffer[1024];
Status error;
// Read all available stdout.
size_t bytes;
while ((bytes = process->GetSTDOUT(buffer, sizeof(buffer), error)) > 0)
AppendOutput(buffer, bytes, false);
// Read all available stderr.
while ((bytes = process->GetSTDERR(buffer, sizeof(buffer), error)) > 0)
AppendOutput(buffer, bytes, true);
}
void AppendOutput(const char *text, size_t len, bool is_stderr) {
if (!text || len == 0)
return;
std::lock_guard<std::mutex> lock(m_output_mutex);
// Split text into lines and add to buffer.
std::string remaining = m_partial_line;
remaining.append(text, len);
size_t start = 0, pos = 0;
while ((pos = remaining.find('\n', start)) != std::string::npos) {
std::string line = remaining.substr(start, pos - start);
if (is_stderr)
line = "[stderr] " + line;
m_output_lines.push_back(line);
// Keep buffer size under limit.
size_t max_lines = m_debugger.GetGuiMaxConsoleLines();
while (m_output_lines.size() > max_lines) {
m_output_lines.pop_front();
if (m_first_visible_line > 0)
--m_first_visible_line;
}
start = pos + 1;
}
// Save any remaining partial line.
m_partial_line = remaining.substr(start);
// Auto-scroll to bottom if enabled.
if (m_auto_scroll && !m_output_lines.empty()) {
m_first_visible_line =
m_output_lines.size() > 0 ? m_output_lines.size() - 1 : 0;
}
}
public:
ConsoleOutputWindowDelegate(Debugger &debugger)
: m_debugger(debugger), m_first_visible_line(0), m_auto_scroll(true) {}
~ConsoleOutputWindowDelegate() override = default;
void WindowDelegateProcessEvent(const lldb::EventSP &event_sp) override {
if (event_sp->GetType() &
(Process::eBroadcastBitSTDOUT | Process::eBroadcastBitSTDERR))
PollProcessOutput();
}
bool WindowDelegateDraw(Window &window, bool force) override {
std::lock_guard<std::mutex> lock(m_output_mutex);
window.Erase();
window.DrawTitleBox(window.GetName());
const int width = window.GetWidth();
const int height = window.GetHeight();
// Calculate the visible range.
size_t total_lines = m_output_lines.size();
if (total_lines == 0) {
window.MoveCursor(2, 1);
window.PutCString("(no output yet)");
return true;
}
// Adjust scroll pos if needed.
if (m_first_visible_line >= total_lines) {
m_first_visible_line = total_lines > 0 ? total_lines - 1 : 0;
}
// Draw visible line.
int visible_height = height - 2;
size_t start_line = m_first_visible_line;
// If we are at the end, display last N lines.
if (m_auto_scroll || start_line + visible_height > total_lines) {
start_line = total_lines > static_cast<size_t>(visible_height)
? total_lines - visible_height
: 0;
}
for (int row = 1;
row <= visible_height && (start_line + row - 1) < total_lines; ++row) {
window.MoveCursor(2, row);
const std::string &line = m_output_lines[start_line + row - 1];
// Highlight stderr lines?.
bool is_stderr = (line.find("[stderr]") == 0);
if (is_stderr)
window.AttributeOn(COLOR_PAIR(2));
// Truncate line to fit window width.
int available_width = width - 3;
if (static_cast<int>(line.length()) > available_width)
window.PutCString(line.substr(0, available_width).c_str());
else
window.PutCString(line.c_str());
if (is_stderr)
window.AttributeOff(COLOR_PAIR(2));
}
return true;
}
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
std::lock_guard<std::mutex> lock(m_output_mutex);
size_t total_lines = m_output_lines.size();
int visible_height = window.GetHeight() - 1;
switch (key) {
case KEY_UP:
if (m_first_visible_line > 0) {
--m_first_visible_line;
m_auto_scroll = false;
}
return eKeyHandled;
case KEY_DOWN:
if (m_first_visible_line + visible_height < total_lines)
++m_first_visible_line;
// Re-enable Auto-scroll at bottom.
if (m_first_visible_line + visible_height >= total_lines)
m_auto_scroll = true;
return eKeyHandled;
case KEY_PPAGE:
if (m_first_visible_line > static_cast<size_t>(visible_height))
m_first_visible_line -= visible_height;
else
m_first_visible_line = 0;
m_auto_scroll = false;
return eKeyHandled;
case KEY_NPAGE:
m_first_visible_line += visible_height;
if (m_first_visible_line + visible_height >= total_lines) {
m_first_visible_line = total_lines > static_cast<size_t>(visible_height)
? total_lines - visible_height
: 0;
m_auto_scroll = true;
}
return eKeyHandled;
case 'a':
m_auto_scroll = !m_auto_scroll;
if (m_auto_scroll && total_lines > 0)
m_first_visible_line = total_lines > static_cast<size_t>(visible_height)
? total_lines - visible_height
: 0;
return eKeyHandled;
case 'c':
m_output_lines.clear();
m_partial_line.clear();
m_first_visible_line = 0;
return eKeyHandled;
case KEY_HOME:
m_first_visible_line = 0;
m_auto_scroll = false;
return eKeyHandled;
case KEY_END:
m_first_visible_line = total_lines > static_cast<size_t>(visible_height)
? total_lines - visible_height
: 0;
m_auto_scroll = true;
return eKeyHandled;
default:
break;
}
return eKeyNotHandled;
}
const char *WindowDelegateGetHelpText() override {
return "Console Output view shows stdout and stderr from the process.";
}
KeyHelp *WindowDelegateGetKeyHelp() override {
static curses::KeyHelp g_source_view_key_help[] = {
{KEY_UP, "Scroll up"}, {KEY_DOWN, "Scroll down"},
{KEY_PPAGE, "Page up"}, {KEY_NPAGE, "Page down"},
{KEY_HOME, "Go to top"}, {KEY_END, "Go to bottom"},
{'h', "Show help dialog"}, {'a', "Toggle auto-scroll"},
{'c', "Clear output"}, {'\0', nullptr}};
return g_source_view_key_help;
}
protected:
Debugger &m_debugger;
std::deque<std::string> m_output_lines;
std::string m_partial_line;
size_t m_first_visible_line = 0;
bool m_auto_scroll = true;
std::mutex m_output_mutex;
};
class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
public:
enum {
@ -6583,7 +6342,6 @@ public:
eMenuID_ViewSource,
eMenuID_ViewVariables,
eMenuID_ViewBreakpoints,
eMenuId_ViewConsole,
eMenuID_Help,
eMenuID_HelpGUIHelp
@ -6594,14 +6352,6 @@ public:
~ApplicationDelegate() override = default;
WindowDelegateSP GetConsoleDelegate() {
if (!m_console_delegate_sp) {
m_console_delegate_sp =
WindowDelegateSP(new ConsoleOutputWindowDelegate(m_debugger));
}
return m_console_delegate_sp;
}
bool WindowDelegateDraw(Window &window, bool force) override {
return false; // Drawing not handled, let standard window drawing happen
}
@ -6832,7 +6582,6 @@ public:
WindowSP main_window_sp = m_app.GetMainWindow();
WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
WindowSP console_window_sp = main_window_sp->FindSubWindow("Console");
WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
const Rect source_bounds = source_window_sp->GetBounds();
@ -6841,52 +6590,39 @@ public:
main_window_sp->RemoveSubWindow(variables_window_sp.get());
if (console_window_sp) {
Rect console_bounds = console_window_sp->GetBounds();
console_bounds.origin.x = variables_bounds.origin.x;
console_bounds.size.width =
variables_bounds.size.width + console_bounds.size.width;
console_window_sp->SetBounds(console_bounds);
} else if (registers_window_sp) {
if (registers_window_sp) {
// We have a registers window, so give all the area back to the
// registers window.
// registers window
Rect registers_bounds = variables_bounds;
registers_bounds.size.width = source_bounds.size.width;
registers_window_sp->SetBounds(registers_bounds);
} else {
// We have no console or registers window showing so give the bottom
// area back to the source view.
// We have no registers window showing so give the bottom area back
// to the source view
source_window_sp->Resize(source_bounds.size.width,
source_bounds.size.height +
variables_bounds.size.height);
}
} else {
Rect new_vars_rect;
if (console_window_sp) {
// Console exists, so split the area.
const Rect console_bounds = console_window_sp->GetBounds();
Rect new_console_rect;
console_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
new_console_rect);
} else if (registers_window_sp) {
Rect new_variables_rect;
if (registers_window_sp) {
// We have a registers window so split the area of the registers
// window into two columns where the left hand side will be the
// variables and the right hand side will be the registers.
const Rect registers_bounds = registers_window_sp->GetBounds();
Rect new_regs_rect;
registers_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
new_regs_rect);
registers_window_sp->SetBounds(new_regs_rect);
// variables and the right hand side will be the registers
const Rect variables_bounds = registers_window_sp->GetBounds();
Rect new_registers_rect;
variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
new_registers_rect);
registers_window_sp->SetBounds(new_registers_rect);
} else {
// No registers or console window, grab the bottom part of the source
// window.
// No registers window, grab the bottom part of the source window
Rect new_source_rect;
source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
new_vars_rect);
new_variables_rect);
source_window_sp->SetBounds(new_source_rect);
}
WindowSP new_window_sp =
main_window_sp->CreateSubWindow("Variables", new_vars_rect, false);
WindowSP new_window_sp = main_window_sp->CreateSubWindow(
"Variables", new_variables_rect, false);
new_window_sp->SetDelegate(
WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
}
@ -6906,13 +6642,13 @@ public:
const Rect variables_bounds = variables_window_sp->GetBounds();
// We have a variables window, so give all the area back to the
// variables window.
// variables window
variables_window_sp->Resize(variables_bounds.size.width +
registers_window_sp->GetWidth(),
variables_bounds.size.height);
} else {
// We have no variables window showing so give the bottom area back
// to the source view.
// to the source view
source_window_sp->Resize(source_bounds.size.width,
source_bounds.size.height +
registers_window_sp->GetHeight());
@ -6923,14 +6659,14 @@ public:
if (variables_window_sp) {
// We have a variables window, split it into two columns where the
// left hand side will be the variables and the right hand side will
// be the registers.
// be the registers
const Rect variables_bounds = variables_window_sp->GetBounds();
Rect new_vars_rect;
variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
new_regs_rect);
variables_window_sp->SetBounds(new_vars_rect);
} else {
// No variables window, grab the bottom part of the source window.
// No variables window, grab the bottom part of the source window
Rect new_source_rect;
source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
new_regs_rect);
@ -6945,66 +6681,6 @@ public:
}
return MenuActionResult::Handled;
case eMenuId_ViewConsole: {
WindowSP main_window_sp = m_app.GetMainWindow();
WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
WindowSP console_window_sp = main_window_sp->FindSubWindow("Console");
WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
const Rect source_bounds = source_window_sp->GetBounds();
if (console_window_sp) {
const Rect console_bounds = console_window_sp->GetBounds();
main_window_sp->RemoveSubWindow(console_window_sp.get());
if (variables_window_sp) {
// Variables window exists, so give Console space to Variables.
Rect variables_bounds = variables_window_sp->GetBounds();
variables_bounds.size.width =
variables_bounds.size.width + console_bounds.size.width;
variables_window_sp->SetBounds(variables_bounds);
} else if (registers_window_sp) {
// Registers window exists, so give Console space to Registers.
Rect registers_bounds = registers_window_sp->GetBounds();
registers_bounds.size.width = source_bounds.size.width;
registers_window_sp->SetBounds(registers_bounds);
} else {
// No Variables or Registers window exists.
source_window_sp->Resize(source_bounds.size.width,
source_bounds.size.height +
console_bounds.size.height);
}
} else {
Rect new_console_rect;
if (variables_window_sp) {
// Variable window exists, split area.
const Rect variables_bounds = variables_window_sp->GetBounds();
Rect new_vars_rect;
variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
new_console_rect);
variables_window_sp->SetBounds(new_vars_rect);
} else if (registers_window_sp) {
// Registers window exists, split area.
const Rect registers_bounds = registers_window_sp->GetBounds();
Rect new_regs_rect;
registers_bounds.VerticalSplitPercentage(0.50, new_console_rect,
new_regs_rect);
registers_window_sp->SetBounds(new_regs_rect);
} else {
// No Registers or Variables window exists, split source area.
Rect new_source_rect;
source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
new_console_rect);
source_window_sp->SetBounds(new_source_rect);
}
WindowSP new_window_sp =
main_window_sp->CreateSubWindow("Console", new_console_rect, false);
new_window_sp->SetDelegate(GetConsoleDelegate());
}
touchwin(stdscr);
}
return MenuActionResult::Handled;
case eMenuID_ViewBreakpoints: {
WindowSP main_window_sp = m_app.GetMainWindow();
WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
@ -7052,7 +6728,6 @@ public:
protected:
Application &m_app;
Debugger &m_debugger;
WindowDelegateSP m_console_delegate_sp;
};
class StatusBarWindowDelegate : public WindowDelegate {
@ -7960,8 +7635,6 @@ void IOHandlerCursesGUI::Activate() {
view_menu_sp->AddSubmenu(
std::make_shared<Menu>("Breakpoints", nullptr, 'b',
ApplicationDelegate::eMenuID_ViewBreakpoints));
view_menu_sp->AddSubmenu(std::make_shared<Menu>(
"Console", nullptr, 'o', ApplicationDelegate::eMenuId_ViewConsole));
MenuSP help_menu_sp(
new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
@ -7985,16 +7658,12 @@ void IOHandlerCursesGUI::Activate() {
Rect status_bounds = content_bounds.MakeStatusBar();
Rect source_bounds;
Rect variables_bounds;
Rect console_bounds;
Rect threads_bounds;
Rect source_variables_bounds;
Rect variables_console_bounds;
content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
threads_bounds);
source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
variables_console_bounds);
variables_console_bounds.VerticalSplitPercentage(0.50, variables_bounds,
console_bounds);
variables_bounds);
WindowSP menubar_window_sp =
main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
@ -8006,12 +7675,10 @@ void IOHandlerCursesGUI::Activate() {
WindowSP source_window_sp(
main_window_sp->CreateSubWindow("Source", source_bounds, true));
WindowSP threads_window_sp(
main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
WindowSP variables_window_sp(
main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
WindowSP console_window_sp(
main_window_sp->CreateSubWindow("Console", console_bounds, false));
WindowSP threads_window_sp(
main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
WindowSP status_window_sp(
main_window_sp->CreateSubWindow("Status", status_bounds, false));
status_window_sp->SetCanBeActive(
@ -8022,7 +7689,6 @@ void IOHandlerCursesGUI::Activate() {
WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
variables_window_sp->SetDelegate(
WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
console_window_sp->SetDelegate(app_delegate_sp->GetConsoleDelegate());
TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
threads_window_sp->SetDelegate(WindowDelegateSP(
new TreeWindowDelegate(m_debugger, thread_delegate_sp)));

View file

@ -1,2 +0,0 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View file

@ -1,138 +0,0 @@
"""
Test that the 'gui' console output pane displays stdout / stderr from the debugged process
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test.lldbpexpect import PExpectTest
class TestGuiConsoleOutputTest(PExpectTest):
# PExpect uses many timeouts internally and doesn't play well
# under ASAN on a loaded machine..
@skipIfAsan
@skipIfCursesSupportMissing
def test_gui_console_output(self):
"""Test that console pane prints messages"""
self.build()
self.launch(
executable=self.getBuildArtifact("a.out"),
dimensions=(100, 500),
run_under=["env", "TERM=xterm"],
)
self.expect(
'br set -o true -f main.cpp -p "// break here begin"',
substrs=["Breakpoint 1", "address ="],
)
self.expect(
'br set -o true -f main.cpp -p "// break here end"',
substrs=["Breakpoint 2", "address ="],
)
self.expect("run", substrs=["stop reason ="])
escape_key = chr(27).encode()
# Start the GUI.
self.child.sendline("gui")
# Check for gui elements in Menu bar (top of screen)
# We expect these in the order they appear to avoid consumption issues
self.child.expect_exact("Target")
self.child.expect_exact("Process")
self.child.expect_exact("View")
# Check for window titles (middle of screen)
self.child.expect_exact("Sources")
self.child.expect_exact("Console")
# The Console window show this message before continuing
self.child.expect_exact("(no output yet)")
# Continue program execution
self.child.send("c")
# Check console output for messages
self.child.expect_exact("Hello from stdout line 1")
self.child.expect_exact("Hello from stderr line 3")
# Check for large output (verify buffer draining)
self.child.expect_exact("Large output line 0")
self.child.expect_exact("Large output line 99")
# Wait for Breakpoint 2
self.child.expect_exact("stop reason")
# Press escape to quit the gui
self.child.send(escape_key)
self.expect_prompt()
self.quit()
@skipIfAsan
@skipIfCursesSupportMissing
def test_gui_console_navigate(self):
"""Test that console pane navigation works"""
self.build()
self.launch(
executable=self.getBuildArtifact("a.out"),
dimensions=(100, 500),
run_under=["env", "TERM=xterm"],
)
self.expect(
'br set -o true -f main.cpp -p "// break here begin"',
substrs=["Breakpoint 1", "address ="],
)
self.expect(
'br set -o true -f main.cpp -p "// break here end"',
substrs=["Breakpoint 2", "address ="],
)
self.expect("run", substrs=["stop reason ="])
escape_key = chr(27).encode()
tab_key = chr(9).encode()
# Start the GUI.
self.child.sendline("gui")
# Match elements in top-to-bottom order
self.child.expect_exact("Target")
self.child.expect_exact("Sources")
self.child.expect_exact("Console")
# The Console window show this message before continuing
self.child.expect_exact("(no output yet)")
# Continue program execution
self.child.send("c")
# Check console output for messages
self.child.expect_exact("Hello from stdout line 1")
# Wait for Breakpoint 2
self.child.expect_exact("stop reason")
# Tab to console
self.child.send(tab_key) # Sources -> Threads
self.child.send(tab_key) # Threads -> Variables
self.child.send(tab_key) # Variables -> Console
# Clear Console output
self.child.send("c")
# The Console window show this message after clear
self.child.expect_exact("(no output yet)")
# Press escape to quit the gui
self.child.send(escape_key)
self.expect_prompt()
self.quit()

View file

@ -1,34 +0,0 @@
#include <chrono>
#include <iostream>
#include <thread>
void generate_output() {
for (unsigned i = 1; i < 4; ++i) {
std::cout << "Hello from stdout line " << i << std::endl;
std::cerr << "Hello from stderr line " << i << std::endl;
}
}
void generate_large_output() {
for (unsigned i = 0; i < 100; ++i) {
std::cout << "Large output line " << i
<< " to test buffer draining logic in the GUI console."
<< std::endl;
}
}
int main(int argc, char *argv[]) {
int test_var = 42;
// Break before output.
int break_here = 0; // break here begin
// Generate stdout/stderr output.
generate_output();
generate_large_output();
// Wait to capture output.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 0; // break here end
}

View file

@ -256,7 +256,6 @@ Makes programs 10x faster by doing Special New Thing.
example, `breakpoint disable .` disables the just-hit breakpoint location. Another usage is to automate a
command to run at the current location: `breakpoint command add -o 'p my_var' .`.
* The `apropos` command now highlights matching keywords in its output when color is enabled.
* The TUI mode (enabled with the `gui` command) now has a real-time console output pane. stdout / stderr messages get redirected to this pane when it is enabled.
#### Deprecated APIs