[Lldb-commits] [lldb] [lldb] Add an interactive picker IOHandler (PR #237514)

via lldb-commits lldb-commits at lists.llvm.org
Sat Oct 31 00:41:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Med Ismail Bennani (medismailben)

<details>
<summary>Changes</summary>

This patch adds `IOHandlerPicker`, an ANSI-based IOHandler that lets the
user choose one or more items from a table. It supports single-select and
multi-select modes, arrow and vim-style navigation, and paging. It draws on
the alternate screen buffer so the user's scrollback is restored on exit,
redraws itself when the terminal is resized, and reports a canceled result
when the input is not an interactive terminal.

The statusline can't stay up underneath the picker: its scroll region
also applies to the alternate screen, and its cursor save/restore shares
the slot that leaving the alternate screen restores. This patch
addresses that by adding `Debugger::HideStatusline` and
`Debugger::ShowStatusline`, which the picker uses to remove the
statusline while it owns the screen and bring it back once it's done.

`AnsiTerminal.h` didn't have macros for the DEC private modes the picker
toggles: cursor visibility, auto-wrap, and the alternate screen buffer.
This patch adds them there, so we don't hard-code those escape sequences
in the picker.

This patch also adds a `test-picker` command, available only in builds with
assertions enabled, that shows the picker with generated items. The new
pexpect tests use it to exercise navigation, selection, cancellation,
and scrolling.

Assisted-by: Claude
Signed-off-by: Med Ismail Bennani <ismail@<!-- -->bennani.ma>

---

<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

---

Patch is 32.72 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/237514.diff


12 Files Affected:

- (modified) lldb/include/lldb/Core/Debugger.h (+6) 
- (modified) lldb/include/lldb/Core/IOHandler.h (+1) 
- (added) lldb/include/lldb/Core/IOHandlerPicker.h (+125) 
- (modified) lldb/include/lldb/Utility/AnsiTerminal.h (+12) 
- (modified) lldb/source/Commands/CMakeLists.txt (+1) 
- (added) lldb/source/Commands/CommandObjectTestPicker.cpp (+101) 
- (added) lldb/source/Commands/CommandObjectTestPicker.h (+25) 
- (modified) lldb/source/Core/CMakeLists.txt (+1) 
- (modified) lldb/source/Core/Debugger.cpp (+18-8) 
- (added) lldb/source/Core/IOHandlerPicker.cpp (+449) 
- (modified) lldb/source/Interpreter/CommandInterpreter.cpp (+6) 
- (added) lldb/test/API/iohandler/picker/TestIOHandlerPicker.py (+90) 


``````````diff
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 918a0fb71a1bd3..15d5a7605bae73 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -178,6 +178,12 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
 
   void RestoreInputTerminalState();
 
+  /// Remove the statusline and give its row back to the scroll region.
+  void HideStatusline();
+
+  /// Bring back the statusline after HideStatusline(), if it is enabled.
+  void ShowStatusline();
+
   lldb::StreamUP GetAsyncOutputStream();
 
   lldb::StreamUP GetAsyncErrorStream();
diff --git a/lldb/include/lldb/Core/IOHandler.h b/lldb/include/lldb/Core/IOHandler.h
index 2672bbe5da2b35..94e8e9816ffcfa 100644
--- a/lldb/include/lldb/Core/IOHandler.h
+++ b/lldb/include/lldb/Core/IOHandler.h
@@ -47,6 +47,7 @@ class IOHandler {
     PythonInterpreter,
     LuaInterpreter,
     PythonCode,
+    Picker,
     Other
   };
 
diff --git a/lldb/include/lldb/Core/IOHandlerPicker.h b/lldb/include/lldb/Core/IOHandlerPicker.h
new file mode 100644
index 00000000000000..12590202e7feae
--- /dev/null
+++ b/lldb/include/lldb/Core/IOHandlerPicker.h
@@ -0,0 +1,125 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_CORE_IOHANDLERPICKER_H
+#define LLDB_CORE_IOHANDLERPICKER_H
+
+#include "lldb/Core/IOHandler.h"
+#include "lldb/Host/Pipe.h"
+#include "lldb/Host/Terminal.h"
+#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/SmallVector.h"
+
+#include <optional>
+#include <string>
+
+namespace lldb_private {
+
+using PickerColumn = std::string;
+
+/// A row of the picker's table, with one value per column.
+struct PickerItem {
+  llvm::SmallVector<std::string> values;
+  bool initially_selected = false;
+};
+
+enum class PickerMode { SingleSelect, MultiSelect };
+
+struct PickerResult {
+  bool was_canceled = false;
+  /// Indices into the items given to the picker, in increasing order.
+  llvm::SmallVector<size_t> selected_indices;
+};
+
+/// An interactive ANSI-based picker IOHandler.
+///
+/// Supports single-select and multi-select modes, vim-style navigation, and
+/// scrolling. Works in any terminal that supports basic ANSI escape codes.
+/// Reports a canceled result if the input is not an interactive terminal.
+class IOHandlerPicker : public IOHandler {
+public:
+  IOHandlerPicker(Debugger &debugger, llvm::StringRef header,
+                  llvm::SmallVector<PickerColumn> columns,
+                  llvm::SmallVector<PickerItem, 0> items, PickerMode mode);
+
+  ~IOHandlerPicker() override;
+
+  void Run() override;
+  void Cancel() override;
+  bool Interrupt() override;
+  void GotEOF() override;
+  void TerminalSizeChanged() override;
+
+  const PickerResult &GetResult() const { return m_result; }
+
+private:
+  enum class Key {
+    Up,
+    Down,
+    PageUp,
+    PageDown,
+    Home,
+    End,
+    Space,
+    Enter,
+    /// A byte arrived on m_wakeup_pipe.
+    Wakeup,
+    Escape,
+    Unknown,
+  };
+
+  enum class Input { Ready, Wakeup, Error };
+
+  /// Raw mode hands every key press to ReadKey() as soon as it's typed.
+  bool EnterRawMode();
+  void RestoreTerminalMode();
+
+  Key ReadKey();
+  Input WaitForInput();
+  std::optional<char> ReadByte();
+  Key ReadEscapeSequence();
+  static Key DecodeByte(char c);
+  static Key DecodeEscapeSequence(llvm::StringRef params, char final_byte);
+  void WakeUp();
+
+  void Render();
+  void ScrollToCursor();
+  void ComputeColumnWidths();
+  void UpdateLayout();
+  size_t GetPrefixWidth() const;
+  size_t GetViewportHeight() const;
+
+  std::string m_header;
+  llvm::SmallVector<PickerColumn> m_columns;
+  llvm::SmallVector<PickerItem, 0> m_items;
+  PickerMode m_mode;
+  PickerResult m_result;
+
+  size_t m_cursor_index = 0;
+  /// Index of the first row on screen.
+  size_t m_viewport_start = 0;
+  /// Bit i is set if m_items[i] is checked.
+  llvm::BitVector m_checked;
+
+  llvm::SmallVector<size_t> m_column_widths;
+
+  /// Cached with m_column_widths so a frame never mixes two terminal sizes.
+  size_t m_terminal_width;
+  size_t m_terminal_height;
+
+  TerminalState m_saved_terminal_state;
+
+  /// Lets TerminalSizeChanged() and Cancel(), which can run on other threads,
+  /// wake ReadKey() up from select(). Open for the picker's whole lifetime,
+  /// since TerminalSizeChanged() can come before or after Run().
+  Pipe m_wakeup_pipe;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_CORE_IOHANDLERPICKER_H
diff --git a/lldb/include/lldb/Utility/AnsiTerminal.h b/lldb/include/lldb/Utility/AnsiTerminal.h
index ee581abc74e09e..213c309dd2f11d 100644
--- a/lldb/include/lldb/Utility/AnsiTerminal.h
+++ b/lldb/include/lldb/Utility/AnsiTerminal.h
@@ -84,6 +84,18 @@
 #define ANSI_CSI_ERASE_VIEWPORT ANSI_CSI_ED(2)
 // Erase scrollback.
 #define ANSI_CSI_ERASE_SCROLLBACK ANSI_CSI_ED(3)
+// DEC Private Mode Set and Reset.
+#define ANSI_CSI_DECSET(mode) ANSI_ESC_START "?" #mode "h"
+#define ANSI_CSI_DECRST(mode) ANSI_ESC_START "?" #mode "l"
+// Show or hide the cursor.
+#define ANSI_CSI_SHOW_CURSOR ANSI_CSI_DECSET(25)
+#define ANSI_CSI_HIDE_CURSOR ANSI_CSI_DECRST(25)
+// Enable or disable auto-wrap.
+#define ANSI_CSI_ENABLE_AUTO_WRAP ANSI_CSI_DECSET(7)
+#define ANSI_CSI_DISABLE_AUTO_WRAP ANSI_CSI_DECRST(7)
+// Switch to or from the alternate screen buffer.
+#define ANSI_CSI_ENTER_ALT_SCREEN ANSI_CSI_DECSET(1049)
+#define ANSI_CSI_LEAVE_ALT_SCREEN ANSI_CSI_DECRST(1049)
 
 // OSC (Operating System Commands)
 // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt
index 69e4c45f0b8e5a..ef2d8e5926e05e 100644
--- a/lldb/source/Commands/CMakeLists.txt
+++ b/lldb/source/Commands/CMakeLists.txt
@@ -33,6 +33,7 @@ add_lldb_library(lldbCommands NO_PLUGIN_DEPENDENCIES
   CommandObjectSource.cpp
   CommandObjectStats.cpp
   CommandObjectTarget.cpp
+  CommandObjectTestPicker.cpp
   CommandObjectThread.cpp
   CommandObjectThreadUtil.cpp
   CommandObjectTrace.cpp
diff --git a/lldb/source/Commands/CommandObjectTestPicker.cpp b/lldb/source/Commands/CommandObjectTestPicker.cpp
new file mode 100644
index 00000000000000..f343d577e494ee
--- /dev/null
+++ b/lldb/source/Commands/CommandObjectTestPicker.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CommandObjectTestPicker.h"
+
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/IOHandlerPicker.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+static const char *const g_names[] = {
+    "alpha",  "bravo",   "charlie", "delta",  "echo",   "foxtrot", "golf",
+    "hotel",  "india",   "juliet",  "kilo",   "lima",   "mike",    "november",
+    "oscar",  "papa",    "quebec",  "romeo",  "sierra", "tango",   "uniform",
+    "victor", "whiskey", "xray",    "yankee", "zulu",
+};
+
+/// Generates items in descending id order so that sorting has a visible
+/// effect. Avoids <random> because its output differs across standard
+/// libraries, which would make the tests non-portable.
+static llvm::SmallVector<PickerItem, 0> GenerateItems(size_t count,
+                                                      PickerMode mode) {
+  llvm::SmallVector<PickerItem, 0> items;
+  for (size_t id = count; id > 0; --id) {
+    PickerItem item;
+    item.values = {std::to_string(id), g_names[(id - 1) % std::size(g_names)],
+                   std::to_string(id * 7919 % 10000)};
+    item.initially_selected = mode == PickerMode::MultiSelect && id % 3 == 1;
+    items.push_back(std::move(item));
+  }
+  return items;
+}
+
+class CommandObjectTestPickerMode : public CommandObjectParsed {
+public:
+  CommandObjectTestPickerMode(CommandInterpreter &interpreter, PickerMode mode)
+      : CommandObjectParsed(
+            interpreter,
+            mode == PickerMode::SingleSelect ? "test-picker single"
+                                             : "test-picker multi",
+            mode == PickerMode::SingleSelect
+                ? "Test IOHandlerPicker in single-select mode."
+                : "Test IOHandlerPicker in multi-select mode.",
+            mode == PickerMode::SingleSelect ? "test-picker single [<count>]"
+                                             : "test-picker multi [<count>]"),
+        m_mode(mode) {
+    AddSimpleArgumentList(eArgTypeCount, eArgRepeatOptional);
+  }
+
+protected:
+  void DoExecute(Args &args, CommandReturnObject &result) override {
+    size_t count = 10;
+    if (args.GetArgumentCount() >= 1 && args[0].ref().getAsInteger(10, count)) {
+      result.AppendErrorWithFormatv("invalid count: '{0}'", args[0].ref());
+      return;
+    }
+
+    llvm::SmallVector<PickerColumn> columns = {"ID", "NAME", "VALUE"};
+    // Keep a copy to print the selected ids.
+    llvm::SmallVector<PickerItem, 0> items = GenerateItems(count, m_mode);
+    auto picker_sp = std::make_shared<IOHandlerPicker>(
+        GetDebugger(),
+        m_mode == PickerMode::SingleSelect ? "Select an item:"
+                                           : "Toggle items:",
+        std::move(columns), items, m_mode);
+    GetDebugger().RunIOHandlerSync(picker_sp);
+
+    const PickerResult &picked = picker_sp->GetResult();
+    if (picked.was_canceled)
+      result.AppendMessage("canceled");
+    else if (picked.selected_indices.empty())
+      result.AppendMessage("none selected");
+    for (size_t index : picked.selected_indices)
+      result.AppendMessageWithFormatv("selected: {0}", items[index].values[0]);
+    result.SetStatus(eReturnStatusSuccessFinishResult);
+  }
+
+private:
+  PickerMode m_mode;
+};
+
+CommandObjectTestPicker::CommandObjectTestPicker(
+    CommandInterpreter &interpreter)
+    : CommandObjectMultiword(interpreter, "test-picker",
+                             "Test the IOHandlerPicker interactive widget.",
+                             "test-picker <subcommand> [<count>]") {
+  LoadSubCommand("single", std::make_shared<CommandObjectTestPickerMode>(
+                               interpreter, PickerMode::SingleSelect));
+  LoadSubCommand("multi", std::make_shared<CommandObjectTestPickerMode>(
+                              interpreter, PickerMode::MultiSelect));
+}
+
+CommandObjectTestPicker::~CommandObjectTestPicker() = default;
diff --git a/lldb/source/Commands/CommandObjectTestPicker.h b/lldb/source/Commands/CommandObjectTestPicker.h
new file mode 100644
index 00000000000000..8a9f0597bd061a
--- /dev/null
+++ b/lldb/source/Commands/CommandObjectTestPicker.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTTESTPICKER_H
+#define LLDB_SOURCE_COMMANDS_COMMANDOBJECTTESTPICKER_H
+
+#include "lldb/Interpreter/CommandObjectMultiword.h"
+
+namespace lldb_private {
+
+class CommandObjectTestPicker : public CommandObjectMultiword {
+public:
+  CommandObjectTestPicker(CommandInterpreter &interpreter);
+
+  ~CommandObjectTestPicker() override;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_COMMANDS_COMMANDOBJECTTESTPICKER_H
diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt
index f06455983340c2..6d8928c3183a6a 100644
--- a/lldb/source/Core/CMakeLists.txt
+++ b/lldb/source/Core/CMakeLists.txt
@@ -45,6 +45,7 @@ add_lldb_library(lldbCore NO_PLUGIN_DEPENDENCIES
   Highlighter.cpp
   IOHandler.cpp
   IOHandlerCursesGUI.cpp
+  IOHandlerPicker.cpp
   Mangled.cpp
   Module.cpp
   ModuleChild.cpp
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index cbd4aec0ef9179..d5ec66210e91c9 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -346,14 +346,8 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx,
                g_debugger_properties[ePropertyShowStatusline].name) {
       // Statusline setting changed. If we have a statusline instance, update it
       // now. Otherwise it will get created in the default event handler.
-      std::lock_guard<std::mutex> guard(m_statusline_mutex);
-      if (StatuslineSupported()) {
-        m_statusline.emplace(*this);
-        m_statusline->Enable(
-            GetSelectedExecutionContextRef(/*adopt_dummy_target=*/true));
-      } else {
-        m_statusline.reset();
-      }
+      HideStatusline();
+      ShowStatusline();
     } else if (property_path ==
                    g_debugger_properties[ePropertyStatuslineFormat].name ||
                property_path ==
@@ -1316,6 +1310,22 @@ void Debugger::RestoreInputTerminalState() {
   }
 }
 
+void Debugger::HideStatusline() {
+  // Destroy the statusline rather than just disabling it, so that resizes and
+  // redraws from other threads can't draw it back while it's hidden.
+  std::lock_guard<std::mutex> guard(m_statusline_mutex);
+  m_statusline.reset();
+}
+
+void Debugger::ShowStatusline() {
+  std::lock_guard<std::mutex> guard(m_statusline_mutex);
+  if (!m_statusline && StatuslineSupported()) {
+    m_statusline.emplace(*this);
+    m_statusline->Enable(
+        GetSelectedExecutionContextRef(/*adopt_dummy_target=*/true));
+  }
+}
+
 void Debugger::RedrawStatusline(
     std::optional<ExecutionContextRef> exe_ctx_ref) {
   std::lock_guard<std::mutex> guard(m_statusline_mutex);
diff --git a/lldb/source/Core/IOHandlerPicker.cpp b/lldb/source/Core/IOHandlerPicker.cpp
new file mode 100644
index 00000000000000..69d3d9e6973bac
--- /dev/null
+++ b/lldb/source/Core/IOHandlerPicker.cpp
@@ -0,0 +1,449 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/IOHandlerPicker.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Host/Config.h"
+#include "lldb/Host/StreamFile.h"
+#include "lldb/Utility/AnsiTerminal.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/SelectHelper.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/Locale.h"
+
+#if LLDB_ENABLE_TERMIOS
+#include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+
+using namespace lldb;
+using namespace lldb_private;
+
+static constexpr size_t g_column_gap = 2;
+
+static constexpr llvm::StringLiteral g_checked_prefix = " [x] ";
+static constexpr llvm::StringLiteral g_unchecked_prefix = " [ ] ";
+static constexpr llvm::StringLiteral g_cursor_prefix = " > ";
+static constexpr llvm::StringLiteral g_no_cursor_prefix = "   ";
+
+/// Header, key help, column names, footer, and an empty last line so that
+/// the footer's newline doesn't scroll the screen.
+static constexpr size_t g_reserved_lines = 5;
+
+static constexpr char g_ctrl_c = 0x03;
+static constexpr char g_escape = 0x1b;
+
+static constexpr std::chrono::milliseconds g_escape_sequence_timeout(100);
+
+/// Parameter (0x30-0x3f) or intermediate (0x20-0x2f) byte, see ECMA-48 5.4.
+static bool IsCSIParameterOrIntermediateByte(char c) {
+  return c >= 0x20 && c <= 0x3f;
+}
+
+/// Falls back to the size in bytes when columnWidth() can't measure \a str.
+static size_t GetColumnWidth(llvm::StringRef str) {
+  int width = llvm::sys::locale::columnWidth(str);
+  return width < 0 ? str.size() : width;
+}
+
+IOHandlerPicker::IOHandlerPicker(Debugger &debugger, llvm::StringRef header,
+                                 llvm::SmallVector<PickerColumn> columns,
+                                 llvm::SmallVector<PickerItem, 0> items,
+                                 PickerMode mode)
+    : IOHandler(debugger, IOHandler::Type::Picker), m_header(header.str()),
+      m_columns(std::move(columns)), m_items(std::move(items)), m_mode(mode),
+      m_checked(m_items.size()), m_terminal_width(debugger.GetTerminalWidth()),
+      m_terminal_height(debugger.GetTerminalHeight()) {
+  assert(llvm::all_of(m_items,
+                      [&](const PickerItem &item) {
+                        return item.values.size() == m_columns.size();
+                      }) &&
+         "every picker item must have one value per column");
+
+  for (auto [i, item] : llvm::enumerate(m_items))
+    if (item.initially_selected)
+      m_checked.set(i);
+
+  // Without the pipe, resizes and cross-thread cancels wait for a key press.
+  if (Status error = m_wakeup_pipe.CreateNew(); error.Fail())
+    LLDB_LOG(GetLog(LLDBLog::Host), "IOHandlerPicker: {0}", error);
+}
+
+IOHandlerPicker::~IOHandlerPicker() = default;
+
+size_t IOHandlerPicker::GetPrefixWidth() const {
+  return m_mode == PickerMode::MultiSelect ? g_checked_prefix.size()
+                                           : g_cursor_prefix.size();
+}
+
+void IOHandlerPicker::ComputeColumnWidths() {
+  m_column_widths.clear();
+  for (size_t i = 0; i < m_columns.size(); ++i) {
+    size_t width = GetColumnWidth(m_columns[i]);
+    for (const PickerItem &item : m_items)
+      width = std::max(width, GetColumnWidth(item.values[i]));
+    m_column_widths.push_back(width);
+  }
+
+  size_t row_width = GetPrefixWidth();
+  for (size_t width : m_column_widths)
+    row_width += width + g_column_gap;
+  if (m_column_widths.empty() || row_width >= m_terminal_width)
+    return;
+
+  // Spread the unused width evenly, the first columns taking the remainder.
+  size_t num_cols = m_column_widths.size();
+  size_t extra = m_terminal_width - row_width;
+  for (size_t i = 0; i < num_cols; ++i) {
+    m_column_widths[i] += extra / num_cols;
+    if (i < extra % num_cols)
+      m_column_widths[i] += 1;
+  }
+}
+
+void IOHandlerPicker::UpdateLayout() {
+  m_terminal_width = m_debugger.GetTerminalWidth();
+  m_terminal_height = m_debugger.GetTerminalHeight();
+  ComputeColumnWidths();
+}
+
+size_t IOHandlerPicker::GetViewportHeight() const {
+  // Even on a tiny terminal, keep the cursor's row on screen.
+  if (m_terminal_height <= g_reserved_lines)
+    return 1;
+  return m_terminal_height - g_reserved_lines;
+}
+
+bool IOHandlerPicker::EnterRawMode() {
+#if LLDB_ENABLE_TERMIOS
+  int fd = GetInputFD();
+  if (fd < 0)
+    return false;
+
+  Terminal terminal(fd);
+  m_saved_terminal_state.Save(terminal, /*save_process_group=*/false);
+  if (auto err = terminal.SetRaw()) {
+    llvm::consumeError(std::move(err));
+    m_saved_terminal_state.Clear();
+    return false;
+  }
+  return true;
+#else
+  return false;
+#endif
+}
+
+void IOHandlerPicker::RestoreTerminalMode() {
+  if (m_saved_terminal_state.IsValid()) {
+    m_saved_terminal_state.Restore();
+    m_saved_terminal_state.Clear();
+  }
+}
+
+IOHandlerPicker::Key IOHandlerPicker::ReadKey() {
+  switch (WaitForInput()) {
+  case Input::Ready:
+    break;
+  case Input::Wakeup:
+    return Key::Wakeup;
+  case Input::Error:
+    return Key::Escape;
+  }
+
+  std::optional<char> c = ReadByte();
+  if (!c)
+    return Key::Escape;
+  if (*c == g_escape)
+    return ReadEscapeSequence();
+  return DecodeByte(*c);
+}
+
+IOHandlerPicker::Input IOHandlerPicker::WaitForInput() {
+  bool has_wakeup_pipe = m_wakeup_pipe.CanRead();
+  int wakeup_fd = m_wakeup_pipe.GetReadFileDescriptor();
+  SelectHelper wait_for_input;
+  wait_for_input.FDSetRead(GetInputFD());
+  if (has_wakeup_pipe)
+    wait_for_input....
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/237514


More information about the lldb-commits mailing list