Skip to content

Commit 74417cc

Browse files
committed
[lldb] Rewrite stripping and padding and add a unit test
1 parent 0b22fb3 commit 74417cc

File tree

5 files changed

+89
-16
lines changed

5 files changed

+89
-16
lines changed

lldb/include/lldb/Core/Statusline.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "lldb/lldb-forward.h"
1313
#include "llvm/ADT/StringRef.h"
1414
#include <csignal>
15+
#include <cstdint>
1516
#include <string>
1617

1718
namespace lldb_private {
@@ -33,6 +34,10 @@ class Statusline {
3334
/// Inform the statusline that the terminal dimensions have changed.
3435
void TerminalSizeChanged();
3536

37+
protected:
38+
/// Pad and trim the given string to fit to the given width.
39+
static std::string TrimAndPad(std::string str, size_t width);
40+
3641
private:
3742
/// Draw the statusline with the given text.
3843
void Draw(std::string msg);

lldb/source/Core/Statusline.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
#include "lldb/Utility/LLDBLog.h"
1919
#include "lldb/Utility/Log.h"
2020
#include "lldb/Utility/StreamString.h"
21+
#include "llvm/ADT/StringRef.h"
2122
#include "llvm/Support/Locale.h"
23+
#include <algorithm>
24+
#include <cstdint>
2225

2326
#define ESCAPE "\x1b"
2427
#define ANSI_NORMAL ESCAPE "[0m"
@@ -70,36 +73,58 @@ void Statusline::Disable() {
7073
SetScrollWindow(m_terminal_height);
7174
}
7275

76+
std::string Statusline::TrimAndPad(std::string str, size_t max_width) {
77+
size_t column_width = ColumnWidth(str);
78+
79+
// Trim the string.
80+
if (column_width > max_width) {
81+
size_t min_width_idx = max_width;
82+
size_t min_width = column_width;
83+
84+
// Use a StringRef for more efficient slicing in the loop below.
85+
llvm::StringRef str_ref = str;
86+
87+
// Keep extending the string to find the minimum column width to make sure
88+
// we include as many ANSI escape characters or Unicode code units as
89+
// possible. This is far from the most efficient way to do this, but it's
90+
// means our stripping code doesn't need to be ANSI and Unicode aware and
91+
// should be relatively cold code path.
92+
for (size_t i = column_width; i < str.length(); ++i) {
93+
size_t stripped_width = ColumnWidth(str_ref.take_front(i));
94+
if (stripped_width <= column_width) {
95+
min_width = stripped_width;
96+
min_width_idx = i;
97+
}
98+
}
99+
100+
str = str.substr(0, min_width_idx);
101+
column_width = min_width;
102+
}
103+
104+
// Pad the string.
105+
if (column_width < max_width)
106+
str.append(max_width - column_width, ' ');
107+
108+
return str;
109+
}
110+
73111
void Statusline::Draw(std::string str) {
74112
lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
75113
if (!stream_sp)
76114
return;
77115

78-
static constexpr const size_t g_ellipsis_len = 3;
79-
80116
UpdateTerminalProperties();
81117

82118
m_last_str = str;
83119

84-
size_t column_width = ColumnWidth(str);
85-
86-
if (column_width + g_ellipsis_len >= m_terminal_width) {
87-
// FIXME: If there are hidden characters (e.g. UTF-8, ANSI escape
88-
// characters), this will strip the string more than necessary. Ideally we
89-
// want to strip until column_width == m_terminal_width.
90-
str = str.substr(0, m_terminal_width);
91-
str.replace(m_terminal_width - g_ellipsis_len, g_ellipsis_len, "...");
92-
column_width = ColumnWidth(str);
93-
}
120+
str = TrimAndPad(str, m_terminal_width);
94121

95122
LockedStreamFile locked_stream = stream_sp->Lock();
96123
locked_stream << ANSI_SAVE_CURSOR;
97124
locked_stream.Printf(ANSI_TO_START_OF_ROW,
98125
static_cast<unsigned>(m_terminal_height));
99126
locked_stream << ANSI_CLEAR_LINE;
100127
locked_stream << str;
101-
if (column_width < m_terminal_width)
102-
locked_stream << std::string(m_terminal_width - column_width, ' ');
103128
locked_stream << ANSI_NORMAL;
104129
locked_stream << ANSI_RESTORE_CURSOR;
105130
}

lldb/test/API/functionalities/statusline/TestStatusline.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def test(self):
4141
)
4242

4343
# Change the terminal dimensions and make sure it's reflected immediately.
44-
self.child.setwinsize(terminal_height, 20)
45-
self.child.expect(re.escape("a.out | main.c:2:..."))
44+
self.child.setwinsize(terminal_height, 25)
45+
self.child.expect(re.escape("a.out | main.c:2:11 | bre"))
4646
self.child.setwinsize(terminal_height, terminal_width)
4747

4848
# Change the format.

lldb/unittests/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_lldb_unittest(LLDBCoreTests
1111
RichManglingContextTest.cpp
1212
SourceLocationSpecTest.cpp
1313
SourceManagerTest.cpp
14+
StatuslineTest.cpp
1415
TelemetryTest.cpp
1516
UniqueCStringMapTest.cpp
1617

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===-- StatuslineTest.cpp ------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lldb/Core/Statusline.h"
10+
#include "gtest/gtest.h"
11+
12+
using namespace lldb_private;
13+
14+
class TestStatusline : public Statusline {
15+
public:
16+
using Statusline::TrimAndPad;
17+
};
18+
19+
TEST(StatuslineTest, TestTrimAndPad) {
20+
// Test basic ASCII.
21+
EXPECT_EQ(" ", TestStatusline::TrimAndPad("", 5));
22+
EXPECT_EQ("foo ", TestStatusline::TrimAndPad("foo", 5));
23+
EXPECT_EQ("fooba", TestStatusline::TrimAndPad("fooba", 5));
24+
EXPECT_EQ("fooba", TestStatusline::TrimAndPad("foobar", 5));
25+
26+
// Simple test that ANSI escape codes don't contribute to the visible width.
27+
EXPECT_EQ("\x1B[30m ", TestStatusline::TrimAndPad("\x1B[30m", 5));
28+
EXPECT_EQ("\x1B[30mfoo ", TestStatusline::TrimAndPad("\x1B[30mfoo", 5));
29+
EXPECT_EQ("\x1B[30mfooba", TestStatusline::TrimAndPad("\x1B[30mfooba", 5));
30+
EXPECT_EQ("\x1B[30mfooba", TestStatusline::TrimAndPad("\x1B[30mfoobar", 5));
31+
32+
// Test that we include as many escape codes as we can.
33+
EXPECT_EQ("fooba\x1B[30m", TestStatusline::TrimAndPad("fooba\x1B[30m", 5));
34+
EXPECT_EQ("fooba\x1B[30m\x1B[34m",
35+
TestStatusline::TrimAndPad("fooba\x1B[30m\x1B[34m", 5));
36+
EXPECT_EQ("fooba\x1B[30m\x1B[34m",
37+
TestStatusline::TrimAndPad("fooba\x1B[30m\x1B[34mr", 5));
38+
39+
// Test Unicode.
40+
EXPECT_EQ("❤️ ", TestStatusline::TrimAndPad("❤️", 5));
41+
EXPECT_EQ(" ❤️", TestStatusline::TrimAndPad(" ❤️", 5));
42+
}

0 commit comments

Comments
 (0)