https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/121860
>From d2fdaec2cd06127d2b07e9e2a8ed68400cfd1a5d Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Mon, 13 Jan 2025 10:58:31 -0800 Subject: [PATCH] [lldb] Implement statusline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a statusline to command-line LLDB to display progress events and other information related to the current state of the debugger. The statusline is a dedicated area displayed the bottom of the screen. The contents of the status line are configurable through a setting consisting of LLDB’s format strings. The statusline is configurable through the `statusline-format` setting. The default configuration shows the target name, the current file, the stop reason and the current progress event. ``` (lldb) settings show statusline-format statusline-format (format-string) = "${ansi.bg.cyan}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}" ``` The statusline is enabled by default, but can be disabled with the following setting: ``` (lldb) settings set show-statusline false ``` The statusline supersedes the current progress reporting implementation. Consequently, the following settings no longer have any effect (but continue to exist): ``` show-progress -- Whether to show progress or not if the debugger's output is an interactive color-enabled terminal. show-progress-ansi-prefix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the progress message. show-progress-ansi-suffix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message. ``` RFC: https://discourse.llvm.org/t/rfc-lldb-statusline/83948 --- lldb/include/lldb/Core/Debugger.h | 21 ++- lldb/include/lldb/Core/FormatEntity.h | 17 +- lldb/include/lldb/Core/Statusline.h | 58 +++++++ lldb/include/lldb/Utility/AnsiTerminal.h | 27 +++ lldb/source/Core/CMakeLists.txt | 1 + lldb/source/Core/CoreProperties.td | 8 + lldb/source/Core/Debugger.cpp | 145 ++++++++-------- lldb/source/Core/FormatEntity.cpp | 97 ++++++++--- lldb/source/Core/IOHandlerCursesGUI.cpp | 11 +- lldb/source/Core/Statusline.cpp | 157 ++++++++++++++++++ lldb/source/DataFormatters/TypeSummary.cpp | 4 +- .../Plugins/Language/CPlusPlus/LibCxx.cpp | 3 +- lldb/source/Target/StackFrame.cpp | 2 +- lldb/source/Target/Thread.cpp | 3 +- lldb/unittests/Core/FormatEntityTest.cpp | 3 + 15 files changed, 443 insertions(+), 114 deletions(-) create mode 100644 lldb/include/lldb/Core/Statusline.h create mode 100644 lldb/source/Core/Statusline.cpp diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index 70f4c4216221c6..a4da5fd44c17fe 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -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" @@ -308,6 +309,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; @@ -604,6 +609,14 @@ 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 REPL; @@ -728,7 +741,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; @@ -745,6 +758,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 { diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index 36f6df4118c21f..c40594a10040ef 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -67,6 +67,7 @@ struct Entry { ScriptThread, ThreadInfo, TargetArch, + TargetFile, ScriptTarget, ModuleFile, File, @@ -99,7 +100,9 @@ struct Entry { LineEntryColumn, LineEntryStartAddress, LineEntryEndAddress, - CurrentPCArrow + CurrentPCArrow, + ProgressCount, + ProgressMessage, }; struct Definition { @@ -207,17 +210,19 @@ struct Entry { bool Format(const Entry &entry, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, const Address *addr, - ValueObject *valobj, bool function_changed, bool initial_function); + const Debugger *debugger, ValueObject *valobj, + bool function_changed, bool initial_function); bool FormatStringRef(const llvm::StringRef &format, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, - const Address *addr, ValueObject *valobj, - bool function_changed, bool initial_function); + const Address *addr, const Debugger *debugger, + ValueObject *valobj, bool function_changed, + bool initial_function); bool FormatCString(const char *format, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, const Address *addr, - ValueObject *valobj, bool function_changed, - bool initial_function); + const Debugger *debugger, ValueObject *valobj, + bool function_changed, bool initial_function); Status Parse(const llvm::StringRef &format, Entry &entry); diff --git a/lldb/include/lldb/Core/Statusline.h b/lldb/include/lldb/Core/Statusline.h new file mode 100644 index 00000000000000..aeb1ae7e6846df --- /dev/null +++ b/lldb/include/lldb/Core/Statusline.h @@ -0,0 +1,58 @@ +//===-- 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 +// +//===----------------------------------------------------------------------===// +#include "lldb/Core/Debugger.h" +#include "llvm/ADT/SmallVector.h" +#include <string> +#ifndef LLDB_CORE_STATUSBAR_H +#define LLDB_CORE_STATUSBAR_H + +namespace lldb_private { +class Statusline { +public: + Statusline(Debugger &debugger); + ~Statusline(); + + void Enable(); + void Disable(); + + void Clear(); + void Update(); + + void TerminalSizeChanged() { m_terminal_size_has_changed = 1; } + +private: + // Draw the statusline with the given text. + void Draw(llvm::StringRef msg); + + // Update terminal dimensions. + void UpdateTerminalProperties(); + + // Set the scroll window to the given height. + void SetScrollWindow(uint64_t height); + + // Write at the given column. + void AddAtPosition(uint64_t col, llvm::StringRef str); + + // Clear the statusline (without redrawing the background). + void Reset(); + + bool IsSupported() const; + + lldb::thread_result_t StatuslineThread(); + + Debugger &m_debugger; + + volatile std::sig_atomic_t m_terminal_size_has_changed = 1; + uint64_t m_terminal_width = 0; + uint64_t m_terminal_height = 0; + uint64_t m_scroll_height = 0; + + static constexpr llvm::StringLiteral k_ansi_suffix = "${ansi.normal}"; +}; +} // namespace lldb_private +#endif // LLDB_CORE_STATUSBAR_H diff --git a/lldb/include/lldb/Utility/AnsiTerminal.h b/lldb/include/lldb/Utility/AnsiTerminal.h index 67795971d2ca89..cfd10968a5e230 100644 --- a/lldb/include/lldb/Utility/AnsiTerminal.h +++ b/lldb/include/lldb/Utility/AnsiTerminal.h @@ -73,6 +73,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Regex.h" #include <string> @@ -172,6 +173,32 @@ inline std::string FormatAnsiTerminalCodes(llvm::StringRef format, return fmt; } } + +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); + stripped += left; + + // ANSI_ESC_START not found. + if (right.empty()) + break; + + std::tie(left, right) = right.split(ANSI_ESC_END); + + // ANSI_ESC_END not found. + if (right.empty()) { + stripped += ANSI_ESC_START; + stripped += left; + } + + str = right; + } + return stripped; +} + } // namespace lldb_private #endif diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index 6d14f7a87764e0..5d4576837dbe61 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -46,6 +46,7 @@ add_lldb_library(lldbCore Opcode.cpp PluginManager.cpp Progress.cpp + Statusline.cpp RichManglingContext.cpp SearchFilter.cpp Section.cpp diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index d3816c3070bbc5..0c6f93cb23e456 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -172,6 +172,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.bg.blue}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}">, + Desc<"List of statusline format entities.">; def UseSourceCache: Property<"use-source-cache", "Boolean">, Global, DefaultTrue, diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 6ceb209269c9e7..9612ca14db8dd9 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -243,6 +243,11 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx, // Prompt colors changed. Ping the prompt so it can reset the ansi // terminal codes. SetPrompt(GetPrompt()); + } else if (property_path == + g_debugger_properties[ePropertyStatuslineFormat].name) { + // Statusline format changed. Redraw the statusline. + if (m_statusline) + m_statusline->Update(); } else if (property_path == g_debugger_properties[ePropertyUseSourceCache].name) { // use-source-cache changed. Wipe out the cache contents if it was @@ -376,6 +381,8 @@ bool Debugger::SetTerminalWidth(uint64_t term_width) { if (auto handler_sp = m_io_handler_stack.Top()) handler_sp->TerminalSizeChanged(); + if (m_statusline) + m_statusline->TerminalSizeChanged(); return success; } @@ -392,6 +399,8 @@ bool Debugger::SetTerminalHeight(uint64_t term_height) { if (auto handler_sp = m_io_handler_stack.Top()) handler_sp->TerminalSizeChanged(); + if (m_statusline) + m_statusline->TerminalSizeChanged(); return success; } @@ -454,6 +463,17 @@ llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const { idx, g_debugger_properties[idx].default_cstr_value); } +bool Debugger::GetShowStatusline() const { + const uint32_t idx = ePropertyShowStatusline; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +const FormatEntity::Entry *Debugger::GetStatuslineFormat() const { + constexpr uint32_t idx = ePropertyStatuslineFormat; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + bool Debugger::GetUseAutosuggestion() const { const uint32_t idx = ePropertyShowAutosuggestion; return GetPropertyAtIndexAs<bool>( @@ -1093,12 +1113,18 @@ void Debugger::SetErrorFile(FileSP file_sp) { } void Debugger::SaveInputTerminalState() { + if (m_statusline) + m_statusline->Disable(); int fd = GetInputFile().GetDescriptor(); if (fd != File::kInvalidDescriptor) m_terminal_state.Save(fd, true); } -void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); } +void Debugger::RestoreInputTerminalState() { + m_terminal_state.Restore(); + if (m_statusline) + m_statusline->Enable(); +} ExecutionContext Debugger::GetSelectedExecutionContext() { bool adopt_selected = true; @@ -1453,7 +1479,7 @@ bool Debugger::FormatDisassemblerAddress(const FormatEntity::Entry *format, (prev_sc->function == nullptr && prev_sc->symbol == nullptr)) { initial_function = true; } - return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr, + return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr, nullptr, function_changed, initial_function); } @@ -1958,6 +1984,12 @@ lldb::thread_result_t Debugger::DefaultEventHandler() { // are now listening to all required events so no events get missed m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening); + if (!m_statusline && GetShowStatusline()) + m_statusline.emplace(*this); + + if (m_statusline) + m_statusline->Enable(); + bool done = false; while (!done) { EventSP event_sp; @@ -2016,8 +2048,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() { if (m_forward_listener_sp) m_forward_listener_sp->AddEvent(event_sp); } + if (m_statusline) + m_statusline->Update(); } } + + if (m_statusline) + m_statusline->Disable(); + return {}; } @@ -2080,84 +2118,39 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) { if (!data) return; - // Do some bookkeeping for the current event, regardless of whether we're - // going to show the progress. - const uint64_t id = data->GetID(); - if (m_current_event_id) { - Log *log = GetLog(LLDBLog::Events); - if (log && log->GetVerbose()) { - StreamString log_stream; - log_stream.AsRawOstream() - << static_cast<void *>(this) << " Debugger(" << GetID() - << ")::HandleProgressEvent( m_current_event_id = " - << *m_current_event_id << ", data = { "; - data->Dump(&log_stream); - log_stream << " } )"; - log->PutString(log_stream.GetString()); - } - if (id != *m_current_event_id) - return; - if (data->GetCompleted() == data->GetTotal()) - m_current_event_id.reset(); - } else { - m_current_event_id = id; - } - - // Decide whether we actually are going to show the progress. This decision - // can change between iterations so check it inside the loop. - if (!GetShowProgress()) - return; - - // Determine whether the current output file is an interactive terminal with - // color support. We assume that if we support ANSI escape codes we support - // vt100 escape codes. - File &file = GetOutputFile(); - if (!file.GetIsInteractive() || !file.GetIsTerminalWithColors()) - return; - - StreamSP output = GetAsyncOutputStream(); + // Make a local copy of the incoming progress report that we'll store. + ProgressReport progress_report{data->GetID(), data->GetCompleted(), + data->GetTotal(), data->GetMessage()}; - // Print over previous line, if any. - output->Printf("\r"); - - if (data->GetCompleted() == data->GetTotal()) { - // Clear the current line. - output->Printf("\x1B[2K"); - output->Flush(); - return; + // Do some bookkeeping regardless of whether we're going to display + // progress reports. + { + std::lock_guard<std::mutex> guard(m_progress_reports_mutex); + auto it = std::find_if( + m_progress_reports.begin(), m_progress_reports.end(), + [&](const auto &report) { return report.id == progress_report.id; }); + if (it != m_progress_reports.end()) { + const bool complete = data->GetCompleted() == data->GetTotal(); + if (complete) + m_progress_reports.erase(it); + else + *it = progress_report; + } else { + m_progress_reports.push_back(progress_report); + } } - // Trim the progress message if it exceeds the window's width and print it. - std::string message = data->GetMessage(); - if (data->IsFinite()) - message = llvm::formatv("[{0}/{1}] {2}", data->GetCompleted(), - data->GetTotal(), message) - .str(); - - // Trim the progress message if it exceeds the window's width and print it. - const uint32_t term_width = GetTerminalWidth(); - const uint32_t ellipsis = 3; - if (message.size() + ellipsis >= term_width) - message.resize(term_width - ellipsis); - - const bool use_color = GetUseColor(); - llvm::StringRef ansi_prefix = GetShowProgressAnsiPrefix(); - if (!ansi_prefix.empty()) - output->Printf( - "%s", ansi::FormatAnsiTerminalCodes(ansi_prefix, use_color).c_str()); - - output->Printf("%s...", message.c_str()); - - llvm::StringRef ansi_suffix = GetShowProgressAnsiSuffix(); - if (!ansi_suffix.empty()) - output->Printf( - "%s", ansi::FormatAnsiTerminalCodes(ansi_suffix, use_color).c_str()); - - // Clear until the end of the line. - output->Printf("\x1B[K\r"); + // Redraw the statusline if enabled. + if (m_statusline) + m_statusline->Update(); +} - // Flush the output. - output->Flush(); +std::optional<Debugger::ProgressReport> +Debugger::GetCurrentProgressReport() const { + std::lock_guard<std::mutex> guard(m_progress_reports_mutex); + if (m_progress_reports.empty()) + return std::nullopt; + return m_progress_reports.back(); } void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) { diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp index e13284832cf571..0767032186c083 100644 --- a/lldb/source/Core/FormatEntity.cpp +++ b/lldb/source/Core/FormatEntity.cpp @@ -162,7 +162,13 @@ constexpr Definition g_thread_child_entries[] = { Definition("completed-expression", EntryType::ThreadCompletedExpression)}; constexpr Definition g_target_child_entries[] = { - Definition("arch", EntryType::TargetArch)}; + Definition("arch", EntryType::TargetArch), + Entry::DefinitionWithChildren("file", EntryType::TargetFile, + g_file_child_entries)}; + +constexpr Definition g_progress_child_entries[] = { + Definition("count", EntryType::ProgressCount), + Definition("message", EntryType::ProgressMessage)}; #define _TO_STR2(_val) #_val #define _TO_STR(_val) _TO_STR2(_val) @@ -257,7 +263,10 @@ constexpr Definition g_top_level_entries[] = { Entry::DefinitionWithChildren("target", EntryType::Invalid, g_target_child_entries), Entry::DefinitionWithChildren("var", EntryType::Variable, - g_var_child_entries, true)}; + g_var_child_entries, true), + Entry::DefinitionWithChildren("progress", EntryType::Invalid, + g_progress_child_entries), +}; constexpr Definition g_root = Entry::DefinitionWithChildren( "<root>", EntryType::Root, g_top_level_entries); @@ -322,6 +331,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) { ENUM_TO_CSTR(ScriptThread); ENUM_TO_CSTR(ThreadInfo); ENUM_TO_CSTR(TargetArch); + ENUM_TO_CSTR(TargetFile); ENUM_TO_CSTR(ScriptTarget); ENUM_TO_CSTR(ModuleFile); ENUM_TO_CSTR(File); @@ -355,6 +365,8 @@ const char *FormatEntity::Entry::TypeToCString(Type t) { ENUM_TO_CSTR(LineEntryStartAddress); ENUM_TO_CSTR(LineEntryEndAddress); ENUM_TO_CSTR(CurrentPCArrow); + ENUM_TO_CSTR(ProgressCount); + ENUM_TO_CSTR(ProgressMessage); } return "???"; } @@ -993,8 +1005,9 @@ static bool DumpValue(Stream &s, const SymbolContext *sc, success &= item->DumpPrintableRepresentation(s, val_obj_display, custom_format); } else { - success &= FormatEntity::FormatStringRef( - special_directions, s, sc, exe_ctx, nullptr, item, false, false); + success &= + FormatEntity::FormatStringRef(special_directions, s, sc, exe_ctx, + nullptr, nullptr, item, false, false); } if (--max_num_children == 0) { @@ -1151,14 +1164,15 @@ static void FormatInlinedBlock(Stream &out_stream, Block *block) { bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, - const Address *addr, ValueObject *valobj, - bool function_changed, + const Address *addr, + const Debugger *debugger, + ValueObject *valobj, bool function_changed, bool initial_function) { if (!format_str.empty()) { FormatEntity::Entry root; Status error = FormatEntity::Parse(format_str, root); if (error.Success()) { - return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj, + return FormatEntity::Format(root, s, sc, exe_ctx, addr, debugger, valobj, function_changed, initial_function); } } @@ -1168,14 +1182,15 @@ bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s, bool FormatEntity::FormatCString(const char *format, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, - const Address *addr, ValueObject *valobj, - bool function_changed, bool initial_function) { + const Address *addr, const Debugger *debugger, + ValueObject *valobj, bool function_changed, + bool initial_function) { if (format && format[0]) { FormatEntity::Entry root; llvm::StringRef format_str(format); Status error = FormatEntity::Parse(format_str, root); if (error.Success()) { - return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj, + return FormatEntity::Format(root, s, sc, exe_ctx, addr, debugger, valobj, function_changed, initial_function); } } @@ -1185,8 +1200,17 @@ bool FormatEntity::FormatCString(const char *format, Stream &s, bool FormatEntity::Format(const Entry &entry, Stream &s, const SymbolContext *sc, const ExecutionContext *exe_ctx, const Address *addr, - ValueObject *valobj, bool function_changed, - bool initial_function) { + const Debugger *debugger, ValueObject *valobj, + bool function_changed, bool initial_function) { + auto CalculateDebugger = [&]() -> const Debugger * { + if (debugger) + return debugger; + if (exe_ctx) + if (Target *target = exe_ctx->GetTargetPtr()) + return &target->GetDebugger(); + return nullptr; + }; + switch (entry.type) { case Entry::Type::Invalid: case Entry::Type::ParentNumber: // Only used for @@ -1195,12 +1219,9 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, // FormatEntity::Entry::Definition encoding return false; case Entry::Type::EscapeCode: - if (exe_ctx) { - if (Target *target = exe_ctx->GetTargetPtr()) { - Debugger &debugger = target->GetDebugger(); - if (debugger.GetUseColor()) { - s.PutCString(entry.string); - } + if (const Debugger *debugger_ptr = CalculateDebugger()) { + if (debugger_ptr->GetUseColor()) { + s.PutCString(entry.string); } } // Always return true, so colors being disabled is transparent. @@ -1208,8 +1229,8 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, case Entry::Type::Root: for (const auto &child : entry.children) { - if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed, - initial_function)) { + if (!Format(child, s, sc, exe_ctx, addr, debugger, valobj, + function_changed, initial_function)) { return false; // If any item of root fails, then the formatting fails } } @@ -1223,7 +1244,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, StreamString scope_stream; bool success = false; for (const auto &child : entry.children) { - success = Format(child, scope_stream, sc, exe_ctx, addr, valobj, + success = Format(child, scope_stream, sc, exe_ctx, addr, debugger, valobj, function_changed, initial_function); if (!success) break; @@ -1469,6 +1490,19 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, } return false; + case Entry::Type::TargetFile: + if (exe_ctx) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) { + Module *exe_module = target->GetExecutableModulePointer(); + if (exe_module) { + if (DumpFile(s, exe_module->GetFileSpec(), (FileKind)entry.number)) + return true; + } + } + } + return false; + case Entry::Type::ScriptTarget: if (exe_ctx) { Target *target = exe_ctx->GetTargetPtr(); @@ -1898,7 +1932,28 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return true; } return false; + + case Entry::Type::ProgressCount: + if (const Debugger *debugger_ptr = CalculateDebugger()) { + if (auto progress = debugger_ptr->GetCurrentProgressReport()) { + if (progress->total != UINT64_MAX) { + s.Printf("[%llu/%llu]", progress->completed, progress->total); + return true; + } + } + } + return false; + + case Entry::Type::ProgressMessage: + if (const Debugger *debugger_ptr = CalculateDebugger()) { + if (auto progress = debugger_ptr->GetCurrentProgressReport()) { + s.PutCString(progress->message); + return true; + } + } + return false; } + return false; } diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp index 456ce7d16e102d..0e98912fe33d54 100644 --- a/lldb/source/Core/IOHandlerCursesGUI.cpp +++ b/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -5059,7 +5059,7 @@ class FrameTreeDelegate : public TreeDelegate { frame_sp->GetSymbolContext(eSymbolContextEverything); ExecutionContext exe_ctx(frame_sp); if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, - nullptr, false, false)) { + nullptr, nullptr, false, false)) { int right_pad = 1; window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); } @@ -5117,7 +5117,7 @@ class ThreadTreeDelegate : public TreeDelegate { StreamString strm; ExecutionContext exe_ctx(thread_sp); if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, - nullptr, false, false)) { + nullptr, nullptr, false, false)) { int right_pad = 1; window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); } @@ -5216,7 +5216,7 @@ class ThreadsTreeDelegate : public TreeDelegate { StreamString strm; ExecutionContext exe_ctx(process_sp); if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, - nullptr, false, false)) { + nullptr, nullptr, false, false)) { int right_pad = 1; window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); } @@ -6747,8 +6747,9 @@ class StatusBarWindowDelegate : public WindowDelegate { if (StateIsStoppedState(state, true)) { StreamString strm; - if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, - nullptr, nullptr, false, false)) { + if (thread && + FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, + nullptr, nullptr, false, false)) { window.MoveCursor(40, 0); window.PutCStringTruncated(1, strm.GetString().str().c_str()); } diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp new file mode 100644 index 00000000000000..37035b83cf84df --- /dev/null +++ b/lldb/source/Core/Statusline.cpp @@ -0,0 +1,157 @@ +//===-- Statusline.cpp ---------------------------------------------------===// +// +// 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/Statusline.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" +#include "llvm/Support/Locale.h" + +#include <sys/ioctl.h> +#include <termios.h> + +#define ESCAPE "\x1b" +#define ANSI_SAVE_CURSOR ESCAPE "7" +#define ANSI_RESTORE_CURSOR ESCAPE "8" +#define ANSI_CLEAR_BELOW ESCAPE "[J" +#define ANSI_CLEAR_LINE "\r\x1B[2K" +#define ANSI_SET_SCROLL_ROWS ESCAPE "[0;%ur" +#define ANSI_TO_START_OF_ROW ESCAPE "[%u;0f" +#define ANSI_UP_ROWS ESCAPE "[%dA" +#define ANSI_DOWN_ROWS ESCAPE "[%dB" +#define ANSI_FORWARD_COLS ESCAPE "\033[%dC" +#define ANSI_BACKWARD_COLS ESCAPE "\033[%dD" + +using namespace lldb; +using namespace lldb_private; + +static size_t ColumnWidth(llvm::StringRef str) { + std::string stripped = StripAnsiTerminalCodes(str); + return llvm::sys::locale::columnWidth(stripped); +} + +Statusline::Statusline(Debugger &debugger) : m_debugger(debugger) {} + +Statusline::~Statusline() { Disable(); } + +bool Statusline::IsSupported() const { + File &file = m_debugger.GetOutputFile(); + return file.GetIsInteractive() && file.GetIsTerminalWithColors(); +} + +void Statusline::Enable() { + if (!IsSupported()) + return; + + UpdateTerminalProperties(); + + // Reduce the scroll window to make space for the status bar below. + SetScrollWindow(m_terminal_height - 1); + + // Draw the statusline. + Update(); +} + +void Statusline::Disable() { + UpdateTerminalProperties(); + // Clear the previous status bar if any. + Clear(); + // Extend the scroll window to cover the status bar. + SetScrollWindow(m_terminal_height); +} + +void Statusline::Draw(llvm::StringRef str) { + UpdateTerminalProperties(); + + const size_t ellipsis = 3; + const size_t column_width = ColumnWidth(str); + + printf("size = %llu, width = %llu\n", str.size(), column_width); + + if (column_width + ellipsis >= m_terminal_width) + str = str.substr(0, m_terminal_width - ellipsis); + + StreamFile &out = m_debugger.GetOutputStream(); + out << ANSI_SAVE_CURSOR; + out.Printf(ANSI_TO_START_OF_ROW, static_cast<unsigned>(m_terminal_height)); + out << ANSI_CLEAR_LINE; + out << str; + out << std::string(m_terminal_width - column_width, ' '); + out << ansi::FormatAnsiTerminalCodes(k_ansi_suffix); + out << ANSI_RESTORE_CURSOR; +} + +void Statusline::Reset() { + StreamFile &out = m_debugger.GetOutputStream(); + out << ANSI_SAVE_CURSOR; + out.Printf(ANSI_TO_START_OF_ROW, static_cast<unsigned>(m_terminal_height)); + out << ANSI_CLEAR_LINE; + out << ANSI_RESTORE_CURSOR; +} + +void Statusline::Clear() { Draw(""); } + +void Statusline::UpdateTerminalProperties() { + if (m_terminal_size_has_changed == 0) + return; + + // Clear the previous statusline. + Reset(); + + // Purposely ignore the terminal settings. If the setting doesn't match + // reality and we draw the status bar over existing text, we have no way to + // recover. + struct winsize window_size; + if ((isatty(STDIN_FILENO) != 0) && + ::ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) { + m_terminal_width = window_size.ws_col; + m_terminal_height = window_size.ws_row; + } + + // Set the scroll window based on the new terminal height. + SetScrollWindow(m_terminal_height - 1); + + // Clear the flag. + m_terminal_size_has_changed = 0; +} + +void Statusline::SetScrollWindow(uint64_t height) { + StreamFile &out = m_debugger.GetOutputStream(); + out << '\n'; + out << ANSI_SAVE_CURSOR; + out.Printf(ANSI_SET_SCROLL_ROWS, static_cast<unsigned>(height)); + out << ANSI_RESTORE_CURSOR; + out.Printf(ANSI_UP_ROWS, 1); + out << ANSI_CLEAR_BELOW; + out.Flush(); + + m_scroll_height = height; +} + +void Statusline::Update() { + StreamString stream; + + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + SymbolContext symbol_ctx; + if (auto frame_sp = exe_ctx.GetFrameSP()) + symbol_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything); + + if (auto *format = m_debugger.GetStatuslineFormat()) + FormatEntity::Format(*format, stream, &symbol_ctx, &exe_ctx, nullptr, + &m_debugger, nullptr, false, false); + + Draw(stream.GetString()); +} diff --git a/lldb/source/DataFormatters/TypeSummary.cpp b/lldb/source/DataFormatters/TypeSummary.cpp index 2c863b364538f3..d9644ca85f2120 100644 --- a/lldb/source/DataFormatters/TypeSummary.cpp +++ b/lldb/source/DataFormatters/TypeSummary.cpp @@ -102,8 +102,8 @@ bool StringSummaryFormat::FormatObject(ValueObject *valobj, std::string &retval, return true; } else { if (FormatEntity::Format(m_format, s, &sc, &exe_ctx, - &sc.line_entry.range.GetBaseAddress(), valobj, - false, false)) { + &sc.line_entry.range.GetBaseAddress(), nullptr, + valobj, false, false)) { retval.assign(std::string(s.GetString())); return true; } else { diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp index 6d0ccdbbe4a71d..238d8a8740ab48 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp @@ -437,7 +437,8 @@ bool lldb_private::formatters::LibcxxContainerSummaryProvider( stream.Printf("0x%016" PRIx64 " ", value); } return FormatEntity::FormatStringRef("size=${svar%#}", stream, nullptr, - nullptr, nullptr, &valobj, false, false); + nullptr, nullptr, nullptr, &valobj, + false, false); } /// The field layout in a libc++ string (cap, side, data or data, size, cap). diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 4d068638f42b66..ad4ba5c191a4e0 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -1880,7 +1880,7 @@ bool StackFrame::DumpUsingFormat(Stream &strm, s.PutCString(frame_marker); if (format && FormatEntity::Format(*format, s, &m_sc, &exe_ctx, nullptr, - nullptr, false, false)) { + nullptr, nullptr, false, false)) { strm.PutCString(s.GetString()); return true; } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index b5261310970611..ee0815f34b8e10 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1648,7 +1648,8 @@ bool Thread::DumpUsingFormat(Stream &strm, uint32_t frame_idx, } return FormatEntity::Format(*format, strm, frame_sp ? &frame_sc : nullptr, - &exe_ctx, nullptr, nullptr, false, false); + &exe_ctx, nullptr, nullptr, nullptr, false, + false); } void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx, diff --git a/lldb/unittests/Core/FormatEntityTest.cpp b/lldb/unittests/Core/FormatEntityTest.cpp index 0a68c9340b77ae..5983c9de99ef78 100644 --- a/lldb/unittests/Core/FormatEntityTest.cpp +++ b/lldb/unittests/Core/FormatEntityTest.cpp @@ -148,6 +148,9 @@ constexpr llvm::StringRef lookupStrings[] = { "${thread.return-value}", "${thread.completed-expression}", "${target.arch}", + "${target.file.basename}", + "${target.file.dirname}", + "${target.file.fullpath}", "${var.dummy-var-to-test-wildcard}"}; TEST(FormatEntity, LookupAllEntriesInTree) { _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits