Author: Nagesh Nazare
Date: 2026-05-07T10:46:42-07:00
New Revision: 7578a9ad9361a6589fcfd89a36a24496cb6b5ec4

URL: 
https://github.com/llvm/llvm-project/commit/7578a9ad9361a6589fcfd89a36a24496cb6b5ec4
DIFF: 
https://github.com/llvm/llvm-project/commit/7578a9ad9361a6589fcfd89a36a24496cb6b5ec4.diff

LOG: [lldb] Real-time console pane for output in lldb tui (#177160)

Creating a feature branch.

New Console Pane that -

- captures stdout/stderr msgs from debugged process
- displays output to console in real-time
- provides scrolling and navigation features
- manages 10K line circular buffer
- auto-scroll on/off feature

<img width="2940" height="1744" alt="image"
src="https://github.com/user-attachments/assets/49e9dd3e-9f6f-4383-820b-ce365a46208f";
/>

Controls -
enable / disable : F5 + o
tab to switch to Console pane
UP/ DOWN / PAGEUP/ PAGEDOWN for scrolling,
HOME/ END for top or bottom
c for clear output

Tested on Mac

Added: 
    lldb/test/API/commands/gui/console-output/Makefile
    lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py
    lldb/test/API/commands/gui/console-output/main.cpp

Modified: 
    lldb/include/lldb/Core/Debugger.h
    lldb/source/Core/CoreProperties.td
    lldb/source/Core/Debugger.cpp
    lldb/source/Core/IOHandlerCursesGUI.cpp
    llvm/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Core/Debugger.h 
b/lldb/include/lldb/Core/Debugger.h
index e53e916d78cc1..82d86f988f07f 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -406,6 +406,8 @@ class Debugger : public 
std::enable_shared_from_this<Debugger>,
 
   bool SetShowInlineDiagnostics(bool);
 
+  uint64_t GetGuiMaxConsoleLines() const;
+
   bool LoadPlugin(const FileSpec &spec, Status &error);
 
   void RunIOHandlers();

diff  --git a/lldb/source/Core/CoreProperties.td 
b/lldb/source/Core/CoreProperties.td
index 8cea0931868aa..e4a565e97b81f 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -293,4 +293,8 @@ 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.">;
 }

diff  --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 48e03881fa3b5..e9fe71108c572 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -780,6 +780,12 @@ 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 &

diff  --git a/lldb/source/Core/IOHandlerCursesGUI.cpp 
b/lldb/source/Core/IOHandlerCursesGUI.cpp
index 5b70917f11cbd..ff2cd2777af67 100644
--- a/lldb/source/Core/IOHandlerCursesGUI.cpp
+++ b/lldb/source/Core/IOHandlerCursesGUI.cpp
@@ -327,6 +327,8 @@ class WindowDelegate {
   virtual const char *WindowDelegateGetHelpText() { return nullptr; }
 
   virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
+
+  virtual void WindowDelegateProcessEvent(const lldb::EventSP &event_sp) {}
 };
 
 class HelpDialogDelegate : public WindowDelegate {
@@ -799,6 +801,13 @@ class Window : public Surface {
       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();
@@ -4385,6 +4394,11 @@ class Application {
                 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
                 }
@@ -6311,6 +6325,233 @@ 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 {
@@ -6342,6 +6583,7 @@ class ApplicationDelegate : public WindowDelegate, public 
MenuDelegate {
     eMenuID_ViewSource,
     eMenuID_ViewVariables,
     eMenuID_ViewBreakpoints,
+    eMenuId_ViewConsole,
 
     eMenuID_Help,
     eMenuID_HelpGUIHelp
@@ -6352,6 +6594,14 @@ class ApplicationDelegate : public WindowDelegate, 
public MenuDelegate {
 
   ~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
   }
@@ -6582,6 +6832,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();
 
@@ -6590,39 +6841,52 @@ 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
+          // 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
-          // to the source view
+          // 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);
+          // 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);
         } 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);
+        WindowSP new_window_sp =
+            main_window_sp->CreateSubWindow("Variables", new_vars_rect, false);
         new_window_sp->SetDelegate(
             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
       }
@@ -6642,13 +6906,13 @@ class ApplicationDelegate : public WindowDelegate, 
public MenuDelegate {
           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());
@@ -6659,14 +6923,14 @@ class ApplicationDelegate : public WindowDelegate, 
public MenuDelegate {
         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);
@@ -6681,6 +6945,66 @@ 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(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");
@@ -6728,6 +7052,7 @@ class ApplicationDelegate : public WindowDelegate, public 
MenuDelegate {
 protected:
   Application &m_app;
   Debugger &m_debugger;
+  WindowDelegateSP m_console_delegate_sp;
 };
 
 class StatusBarWindowDelegate : public WindowDelegate {
@@ -7635,6 +7960,8 @@ 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));
@@ -7658,12 +7985,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);
@@ -7675,10 +8006,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(
@@ -7689,6 +8022,7 @@ 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)));

diff  --git a/lldb/test/API/commands/gui/console-output/Makefile 
b/lldb/test/API/commands/gui/console-output/Makefile
new file mode 100644
index 0000000000000..3d0b98f13f3d7
--- /dev/null
+++ b/lldb/test/API/commands/gui/console-output/Makefile
@@ -0,0 +1,2 @@
+CXX_SOURCES := main.cpp
+include Makefile.rules

diff  --git a/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py 
b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py
new file mode 100644
index 0000000000000..c21604a752b63
--- /dev/null
+++ b/lldb/test/API/commands/gui/console-output/TestGuiConsoleOutput.py
@@ -0,0 +1,138 @@
+"""
+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()

diff  --git a/lldb/test/API/commands/gui/console-output/main.cpp 
b/lldb/test/API/commands/gui/console-output/main.cpp
new file mode 100644
index 0000000000000..9800cd50e04bd
--- /dev/null
+++ b/lldb/test/API/commands/gui/console-output/main.cpp
@@ -0,0 +1,34 @@
+#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
+}

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 9c8069b29d1e9..424e67b8b4235 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -255,6 +255,7 @@ 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
 


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to