Index: lldb/include/lldb/Core/DebuggerEvents.h =================================================================== --- lldb/include/lldb/Core/DebuggerEvents.h +++ lldb/include/lldb/Core/DebuggerEvents.h @@ -32,6 +32,7 @@ static const ProgressEventData *GetEventDataFromEvent(const Event *event_ptr); uint64_t GetID() const { return m_id; } + bool IsFinite() const { return m_total != UINT64_MAX; } uint64_t GetCompleted() const { return m_completed; } uint64_t GetTotal() const { return m_total; } const std::string &GetMessage() const { return m_message; } Index: lldb/source/Core/Debugger.cpp =================================================================== --- lldb/source/Core/Debugger.cpp +++ lldb/source/Core/Debugger.cpp @@ -1858,11 +1858,26 @@ output->Printf( "%s", ansi::FormatAnsiTerminalCodes(ansi_prefix, use_color).c_str()); - // Print the progress message. + // Trim the progress message if it exceeds the window's width and print it. std::string message = data->GetMessage(); - if (data->GetTotal() != UINT64_MAX) { + uint64_t progress_total = data->GetTotal(); + uint32_t term_width = GetTerminalWidth(); + + size_t prefix_width = 0; + if (data->IsFinite()) { + prefix_width += 4; // '[%PRIu64/%PRIu64] %s' + prefix_width += std::to_string(progress_total).size() * 2; + } + + const size_t suffix_width = 3; // %s... + + if (message.size() + prefix_width + suffix_width >= term_width) + message.erase(message.begin() + term_width - prefix_width - suffix_width, + message.end()); + + if (data->IsFinite()) { output->Printf("[%" PRIu64 "/%" PRIu64 "] %s...", data->GetCompleted(), - data->GetTotal(), message.c_str()); + progress_total, message.c_str()); } else { output->Printf("%s...", message.c_str()); } Index: lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py =================================================================== --- /dev/null +++ lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py @@ -0,0 +1,60 @@ +""" +Test trimming long progress report in tiny terminal windows +""" + +import os +import pexpect +import tempfile +import random +import re + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test.lldbpexpect import PExpectTest + +class TestTrimmedProgressReporting(PExpectTest): + + mydir = TestBase.compute_mydir(__file__) + + # PExpect uses many timeouts internally and doesn't play well + # under ASAN on a loaded machine.. + @skipIfAsan + @skipIfEditlineSupportMissing + @skipIf(oslist=["linux"], archs=["arm", "aarch64"]) + def test_long_progress_message(self): + self.build() + # Start with a small window + self.launch(use_colors=True) + # Set the terminal to a random width + term_width = random.randint(10, 42) + self.expect("set set show-progress true") + self.expect("set show show-progress", substrs=["show-progress (boolean) = true"]) + self.expect("set set term-width " + str(term_width)) + self.expect("set show term-width", substrs=["term-width (int) = " + str(term_width)]) + + lines = [] + with tempfile.NamedTemporaryFile() as tmpfile: + if self.TraceOn(): + print("logfile: " + tmpfile.name) + with open(tmpfile.name, 'wb') as logfile: + self.child.logfile = logfile + self.expect("file " + self.getBuildArtifact("a.out"), + substrs=["Current executable set to"]) + + with open(tmpfile.name) as logfile: + lines = list(filter(lambda line : line != '\n', + logfile.readlines())) + + self.assertGreater(len(lines), 0) + + ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') + + # Skip the first `file a.out` lines + for idx, line in enumerate(lines[2:-2]): + # Remove ASNI sequences and trailing new line character + message = ansi_escape.sub('', line)[:-1] + if len(message) == 0: + continue + self.assertLessEqual(len(message), term_width, str(idx) + " " + message) +