https://github.com/nageshnnazare updated https://github.com/llvm/llvm-project/pull/177160
>From 5b756054e45a6efc6792e031cd3d1ab33e416ac2 Mon Sep 17 00:00:00 2001 From: Nagesh Nazare <[email protected]> Date: Wed, 21 Jan 2026 01:30:23 +0530 Subject: [PATCH] Adding Console window to lldb gui This change implements the console window to output stdout / stderr messages. Native lldb gui masks the output printed by the application and it is difficult to debug if there are any required debug info messages in the app. --- lldb/source/Core/IOHandlerCursesGUI.cpp | 360 ++++++++++++++++++++++-- 1 file changed, 342 insertions(+), 18 deletions(-) diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp index 53d71db9b3b0c..437584f2361c2 100644 --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -6308,6 +6308,247 @@ 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 stdout + size_t stdout_bytes = process->GetSTDOUT(buffer, sizeof(buffer) - 1, error); + if (stdout_bytes > 0) { + buffer[stdout_bytes] = '\0'; + AppendOutput(buffer, false); + } + + // Read stderr + size_t stderr_bytes = process->GetSTDERR(buffer, sizeof(buffer) - 1, error); + if (stderr_bytes > 0) { + buffer[stderr_bytes] = '\0'; + AppendOutput(buffer, true); + } + } + + void AppendOutput(const char *text, bool is_stderr) { + if (!text || text[0] == '\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 += text; + + 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 + while (m_output_lines.size() > m_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), m_max_lines(10000) {} + + ~ConsoleOutputWindowDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + // Poll for new output + PollProcessOutput(); + + 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; + size_t m_max_lines = 10000; + std::mutex m_output_mutex; +}; + + class ApplicationDelegate : public WindowDelegate, public MenuDelegate { public: enum { @@ -6339,6 +6580,7 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { eMenuID_ViewSource, eMenuID_ViewVariables, eMenuID_ViewBreakpoints, + eMenuId_ViewConsole, eMenuID_Help, eMenuID_HelpGUIHelp @@ -6579,6 +6821,7 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 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(); @@ -6587,39 +6830,50 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { main_window_sp->RemoveSubWindow(variables_window_sp.get()); - if (registers_window_sp) { + 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) { // We have a registers window, so give all the area back to the // 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 registers window showing so give the bottom area back + // We have no console or 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_variables_rect; - if (registers_window_sp) { + 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) { // 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 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); + 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); } else { - // No registers window, grab the bottom part of the source window + // No registers or console window, grab the bottom part of the source window Rect new_source_rect; source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, - new_variables_rect); + new_vars_rect); source_window_sp->SetBounds(new_source_rect); } WindowSP new_window_sp = main_window_sp->CreateSubWindow( - "Variables", new_variables_rect, false); + "Variables", new_vars_rect, false); new_window_sp->SetDelegate( WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); } @@ -6678,6 +6932,67 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate { } 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( + WindowDelegateSP(new ConsoleOutputWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + case eMenuID_ViewBreakpoints: { WindowSP main_window_sp = m_app.GetMainWindow(); WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); @@ -7629,9 +7944,10 @@ void IOHandlerCursesGUI::Activate() { "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)); view_menu_sp->AddSubmenu(std::make_shared<Menu>( "Variables", nullptr, 'v', ApplicationDelegate::eMenuID_ViewVariables)); - view_menu_sp->AddSubmenu( - std::make_shared<Menu>("Breakpoints", nullptr, 'b', - ApplicationDelegate::eMenuID_ViewBreakpoints)); + 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)); @@ -7655,12 +7971,16 @@ 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_bounds); + variables_console_bounds); + variables_console_bounds.VerticalSplitPercentage(0.50, variables_bounds, + console_bounds); WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); @@ -7672,10 +7992,12 @@ void IOHandlerCursesGUI::Activate() { WindowSP source_window_sp( main_window_sp->CreateSubWindow("Source", source_bounds, true)); - WindowSP variables_window_sp( - main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 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 status_window_sp( main_window_sp->CreateSubWindow("Status", status_bounds, false)); status_window_sp->SetCanBeActive( @@ -7686,6 +8008,8 @@ void IOHandlerCursesGUI::Activate() { WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); variables_window_sp->SetDelegate( WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + console_window_sp->SetDelegate( + WindowDelegateSP(new ConsoleOutputWindowDelegate(m_debugger))); TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); threads_window_sp->SetDelegate(WindowDelegateSP( new TreeWindowDelegate(m_debugger, thread_delegate_sp))); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
