Skip to content

Cherrypick LLDB statusline #10379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lldb/docs/use/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ A complete list of currently supported format string variables is listed below:
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``module.file.basename`` | The basename of the current module (shared library or executable) |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``module.file.fullpath`` | The basename of the current module (shared library or executable) |
| ``module.file.fullpath`` | The path of the current module (shared library or executable) |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``process.file.basename`` | The basename of the file for the process |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``process.file.fullpath`` | The fullname of the file for the process |
| ``process.file.fullpath`` | The path of the file for the process |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``process.id`` | The process ID native to the system on which the inferior runs. |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Expand All @@ -141,6 +141,10 @@ A complete list of currently supported format string variables is listed below:
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``target.arch`` | The architecture of the current target |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``target.file.basename`` | The basename of the current target |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``target.file.fullpath`` | The path of the current target |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``script.target:python_func`` | Use a Python function to generate a piece of textual output |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``script.process:python_func`` | Use a Python function to generate a piece of textual output |
Expand Down
27 changes: 26 additions & 1 deletion lldb/include/lldb/Core/Debugger.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "lldb/Core/FormatEntity.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/Core/SourceManager.h"
#include "lldb/Core/Statusline.h"
#include "lldb/Core/UserSettingsController.h"
#include "lldb/Host/HostThread.h"
#include "lldb/Host/StreamFile.h"
Expand Down Expand Up @@ -307,6 +308,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,

bool SetShowProgress(bool show_progress);

bool GetShowStatusline() const;

const FormatEntity::Entry *GetStatuslineFormat() const;

llvm::StringRef GetShowProgressAnsiPrefix() const;

llvm::StringRef GetShowProgressAnsiSuffix() const;
Expand Down Expand Up @@ -420,6 +425,9 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
/// Decrement the "interrupt requested" counter.
void CancelInterruptRequest();

/// Redraw the statusline if enabled.
void RedrawStatusline(bool update = true);

/// This is the correct way to query the state of Interruption.
/// If you are on the RunCommandInterpreter thread, it will check the
/// command interpreter state, and if it is on another thread it will
Expand Down Expand Up @@ -607,12 +615,21 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
return m_source_file_cache;
}

struct ProgressReport {
uint64_t id;
uint64_t completed;
uint64_t total;
std::string message;
};
std::optional<ProgressReport> GetCurrentProgressReport() const;

protected:
friend class CommandInterpreter;
friend class SwiftREPL;
friend class REPL;
friend class Progress;
friend class ProgressManager;
friend class Statusline;

/// Report progress events.
///
Expand Down Expand Up @@ -665,6 +682,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
lldb::LockableStreamFileSP GetErrorStreamSP() { return m_error_stream_sp; }
/// @}

bool StatuslineSupported();

void PushIOHandler(const lldb::IOHandlerSP &reader_sp,
bool cancel_top_handler = true);

Expand Down Expand Up @@ -741,7 +760,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
IOHandlerStack m_io_handler_stack;
std::recursive_mutex m_io_handler_synchronous_mutex;

std::optional<uint64_t> m_current_event_id;
std::optional<Statusline> m_statusline;

llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers;
std::shared_ptr<CallbackLogHandler> m_callback_handler_sp;
Expand All @@ -758,6 +777,12 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
lldb::TargetSP m_dummy_target_sp;
Diagnostics::CallbackID m_diagnostics_callback_id;

/// Bookkeeping for command line progress events.
/// @{
llvm::SmallVector<ProgressReport, 4> m_progress_reports;
mutable std::mutex m_progress_reports_mutex;
/// @}

std::mutex m_destroy_callback_mutex;
lldb::callback_token_t m_destroy_callback_next_token = 0;
struct DestroyCallbackInfo {
Expand Down
5 changes: 4 additions & 1 deletion lldb/include/lldb/Core/FormatEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct Entry {
ScriptThread,
ThreadInfo,
TargetArch,
TargetFile,
ScriptTarget,
ModuleFile,
File,
Expand Down Expand Up @@ -99,7 +100,9 @@ struct Entry {
LineEntryColumn,
LineEntryStartAddress,
LineEntryEndAddress,
CurrentPCArrow
CurrentPCArrow,
ProgressCount,
ProgressMessage,
};

struct Definition {
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Core/IOHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ class IOHandlerEditline : public IOHandler {
std::optional<std::string> SuggestionCallback(llvm::StringRef line);

void AutoCompleteCallback(CompletionRequest &request);

void RedrawCallback();
#endif

protected:
Expand Down
67 changes: 67 additions & 0 deletions lldb/include/lldb/Core/Statusline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//===-- Statusline.h -----------------------------------------------------===//
//
// 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_STATUSLINE_H
#define LLDB_CORE_STATUSLINE_H

#include "lldb/lldb-forward.h"
#include "llvm/ADT/StringRef.h"
#include <csignal>
#include <cstdint>
#include <string>

namespace lldb_private {
class Statusline {
public:
Statusline(Debugger &debugger);
~Statusline();

/// Reduce the scroll window and draw the statusline.
void Enable();

/// Hide the statusline and extend the scroll window.
void Disable();

/// Redraw the statusline. If update is false, this will redraw the last
/// string.
void Redraw(bool update = true);

/// Inform the statusline that the terminal dimensions have changed.
void TerminalSizeChanged();

protected:
/// Pad and trim the given string to fit to the given width.
static std::string TrimAndPad(std::string str, size_t width);

private:
/// Draw the statusline with the given text.
void Draw(std::string msg);

/// Update terminal dimensions.
void UpdateTerminalProperties();

enum ScrollWindowMode {
ScrollWindowExtend,
ScrollWindowShrink,
};

/// Set the scroll window for the given mode.
void UpdateScrollWindow(ScrollWindowMode mode);

/// Clear the statusline (without redrawing the background).
void Reset();

Debugger &m_debugger;
std::string m_last_str;

volatile std::sig_atomic_t m_terminal_size_has_changed = 1;
uint64_t m_terminal_width = 0;
uint64_t m_terminal_height = 0;
};
} // namespace lldb_private
#endif // LLDB_CORE_STATUSLINE_H
8 changes: 8 additions & 0 deletions lldb/include/lldb/Host/Editline.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ using SuggestionCallbackType =

using CompleteCallbackType = llvm::unique_function<void(CompletionRequest &)>;

using RedrawCallbackType = llvm::unique_function<void()>;

/// Status used to decide when and how to start editing another line in
/// multi-line sessions.
enum class EditorStatus {
Expand Down Expand Up @@ -194,6 +196,11 @@ class Editline {
m_suggestion_callback = std::move(callback);
}

/// Register a callback for redrawing the statusline.
void SetRedrawCallback(RedrawCallbackType callback) {
m_redraw_callback = std::move(callback);
}

/// Register a callback for the tab key
void SetAutoCompleteCallback(CompleteCallbackType callback) {
m_completion_callback = std::move(callback);
Expand Down Expand Up @@ -409,6 +416,7 @@ class Editline {

CompleteCallbackType m_completion_callback;
SuggestionCallbackType m_suggestion_callback;
RedrawCallbackType m_redraw_callback;

bool m_color;
std::string m_prompt_ansi_prefix;
Expand Down
92 changes: 78 additions & 14 deletions lldb/include/lldb/Utility/AnsiTerminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@
#define ANSI_1_CTRL(ctrl1) "\033["##ctrl1 ANSI_ESC_END
#define ANSI_2_CTRL(ctrl1, ctrl2) "\033["##ctrl1 ";"##ctrl2 ANSI_ESC_END

#define ANSI_ESC_START_LEN 2

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Locale.h"

#include <string>

Expand Down Expand Up @@ -172,28 +175,89 @@ inline std::string FormatAnsiTerminalCodes(llvm::StringRef format,
return fmt;
}

inline std::tuple<llvm::StringRef, llvm::StringRef, llvm::StringRef>
FindNextAnsiSequence(llvm::StringRef str) {
llvm::StringRef left;
llvm::StringRef right = str;

while (!right.empty()) {
const size_t start = right.find(ANSI_ESC_START);

// ANSI_ESC_START not found.
if (start == llvm::StringRef::npos)
return {str, {}, {}};

// Split the string around the current ANSI_ESC_START.
left = str.take_front(left.size() + start);
llvm::StringRef escape = right.substr(start);
right = right.substr(start + ANSI_ESC_START_LEN + 1);

const size_t end = right.find_first_not_of("0123456789;");

// ANSI_ESC_END found.
if (end < right.size() && (right[end] == 'm' || right[end] == 'G'))
return {left, escape.take_front(ANSI_ESC_START_LEN + 1 + end + 1),
right.substr(end + 1)};

// Maintain the invariant that str == left + right at the start of the loop.
left = str.take_front(left.size() + ANSI_ESC_START_LEN + 1);
}

return {str, {}, {}};
}

inline std::string StripAnsiTerminalCodes(llvm::StringRef str) {
std::string stripped;
while (!str.empty()) {
llvm::StringRef left, right;

std::tie(left, right) = str.split(ANSI_ESC_START);
auto [left, escape, right] = FindNextAnsiSequence(str);
stripped += left;
str = right;
}
return stripped;
}

// ANSI_ESC_START not found.
if (left == str && right.empty())
break;
inline std::string TrimAndPad(llvm::StringRef str, size_t visible_length,
char padding = ' ') {
std::string result;
result.reserve(visible_length);
size_t result_visibile_length = 0;

size_t end = right.find_first_not_of("0123456789;");
if (end < right.size() && (right[end] == 'm' || right[end] == 'G')) {
str = right.substr(end + 1);
} else {
// ANSI_ESC_END not found.
stripped += ANSI_ESC_START;
str = right;
// Trim the string to the given visible length.
while (!str.empty()) {
auto [left, escape, right] = FindNextAnsiSequence(str);
str = right;

// Compute the length of the string without escape codes. If it fits, append
// it together with the invisible escape code.
size_t column_width = llvm::sys::locale::columnWidth(left);
if (result_visibile_length + column_width <= visible_length) {
result.append(left).append(escape);
result_visibile_length += column_width;
continue;
}

// The string might contain unicode which means it's not safe to truncate.
// Repeatedly trim the string until it its valid unicode and fits.
llvm::StringRef trimmed = left;
while (!trimmed.empty()) {
// This relies on columnWidth returning -2 for invalid/partial unicode
// characters, which after conversion to size_t will be larger than the
// visible width.
column_width = llvm::sys::locale::columnWidth(trimmed);
if (result_visibile_length + column_width <= visible_length) {
result.append(trimmed);
result_visibile_length += column_width;
break;
}
trimmed = trimmed.drop_back();
}
}
return stripped;

// Pad the string.
if (result_visibile_length < visible_length)
result.append(visible_length - result_visibile_length, padding);

return result;
}

} // namespace ansi
Expand Down
5 changes: 4 additions & 1 deletion lldb/packages/Python/lldbsuite/test/lldbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,10 @@ def setUpCommands(cls):
'settings set symbols.clang-modules-cache-path "{}"'.format(
configuration.lldb_module_cache_dir
),
# Disable colors by default.
"settings set use-color false",
# Disable the statusline by default.
"settings set show-statusline false",
# Enable the swift metadata cache in order to speed up tests.
"settings set symbols.enable-swift-metadata-cache true",
'settings set symbols.swift-metadata-cache-path "{}"'.format(
Expand All @@ -779,7 +783,6 @@ def setUpCommands(cls):
# Enable expensive validations in TypeSystemSwiftTypeRef.
"settings set symbols.swift-validate-typesystem true",
"settings set symbols.swift-typesystem-compiler-fallback true",
"settings set use-color false",
]

# Set any user-overridden settings.
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ add_lldb_library(lldbCore
Opcode.cpp
PluginManager.cpp
Progress.cpp
Statusline.cpp
RichManglingContext.cpp
SearchFilter.cpp
Section.cpp
Expand Down
8 changes: 8 additions & 0 deletions lldb/source/Core/CoreProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ let Definition = "debugger" in {
Global,
DefaultStringValue<"${ansi.normal}">,
Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.">;
def ShowStatusline: Property<"show-statusline", "Boolean">,
Global,
DefaultTrue,
Desc<"Whether to show a statusline at the bottom of the terminal.">;
def StatuslineFormat: Property<"statusline-format", "FormatEntity">,
Global,
DefaultStringValue<"${ansi.negative}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}">,
Desc<"The default statusline format string.">;
def UseSourceCache: Property<"use-source-cache", "Boolean">,
Global,
DefaultTrue,
Expand Down
Loading