Skip to content

Commit 1eb7954

Browse files
authored
[lldb] Correctly restore the cursor column after resizing the statusline (#146132)
This PR ensures we correctly restore the cursor column after resizing the statusline. To ensure we have space for the statusline, we have to emit a newline to move up everything on screen. The newline causes the cursor to move to the start of the next line, which needs to be undone. Normally, we would use escape codes to save & restore the cursor position, but that doesn't work here, as the cursor position may have (purposely) changed. Instead, we move the cursor up one line using an escape code, but we weren't restoring the column. Interestingly, Editline was able to recover from this issue through the LineInfo struct which contains the buffer and the cursor location, which allows us to compute the column. This PR addresses the bug by having Editline "refresh" the cursor position. Fixes #134064
1 parent 0d1392e commit 1eb7954

File tree

8 files changed

+57
-10
lines changed

8 files changed

+57
-10
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
227227

228228
const char *GetIOHandlerHelpPrologue();
229229

230+
void RefreshIOHandler();
231+
230232
void ClearIOHandlers();
231233

232234
bool EnableLog(llvm::StringRef channel,

lldb/include/lldb/Core/IOHandler.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ class IOHandler {
9090

9191
virtual void TerminalSizeChanged() {}
9292

93+
virtual void Refresh() {}
94+
9395
virtual const char *GetPrompt() {
9496
// Prompt support isn't mandatory
9597
return nullptr;
@@ -404,6 +406,8 @@ class IOHandlerEditline : public IOHandler {
404406

405407
void PrintAsync(const char *s, size_t len, bool is_stdout) override;
406408

409+
void Refresh() override;
410+
407411
private:
408412
#if LLDB_ENABLE_LIBEDIT
409413
bool IsInputCompleteCallback(Editline *editline, StringList &lines);

lldb/include/lldb/Host/Editline.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ class Editline {
267267

268268
size_t GetTerminalHeight() { return m_terminal_height; }
269269

270+
void Refresh();
271+
270272
private:
271273
/// Sets the lowest line number for multi-line editing sessions. A value of
272274
/// zero suppresses line number printing in the prompt.

lldb/source/Core/Debugger.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,6 +1445,13 @@ bool Debugger::PopIOHandler(const IOHandlerSP &pop_reader_sp) {
14451445
return true;
14461446
}
14471447

1448+
void Debugger::RefreshIOHandler() {
1449+
std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex());
1450+
IOHandlerSP reader_sp(m_io_handler_stack.Top());
1451+
if (reader_sp)
1452+
reader_sp->Refresh();
1453+
}
1454+
14481455
StreamUP Debugger::GetAsyncOutputStream() {
14491456
return std::make_unique<StreamAsynchronousIO>(*this,
14501457
StreamAsynchronousIO::STDOUT);

lldb/source/Core/IOHandler.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,3 +663,10 @@ void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {
663663
#endif
664664
}
665665
}
666+
667+
void IOHandlerEditline::Refresh() {
668+
#if LLDB_ENABLE_LIBEDIT
669+
if (m_editline_up)
670+
m_editline_up->Refresh();
671+
#endif
672+
}

lldb/source/Core/Statusline.cpp

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,23 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) {
103103
(mode == DisableStatusline) ? m_terminal_height : m_terminal_height - 1;
104104

105105
LockedStreamFile locked_stream = stream_sp->Lock();
106+
107+
if (mode == EnableStatusline) {
108+
// Move everything on the screen up.
109+
locked_stream << '\n';
110+
locked_stream.Printf(ANSI_UP_ROWS, 1);
111+
}
112+
106113
locked_stream << ANSI_SAVE_CURSOR;
107114
locked_stream.Printf(ANSI_SET_SCROLL_ROWS, scroll_height);
108115
locked_stream << ANSI_RESTORE_CURSOR;
109-
switch (mode) {
110-
case EnableStatusline:
111-
// Move everything on the screen up.
112-
locked_stream.Printf(ANSI_UP_ROWS, 1);
113-
locked_stream << '\n';
114-
break;
115-
case DisableStatusline:
116+
117+
if (mode == DisableStatusline) {
116118
// Clear the screen below to hide the old statusline.
117119
locked_stream << ANSI_CLEAR_BELOW;
118-
break;
119120
}
121+
122+
m_debugger.RefreshIOHandler();
120123
}
121124

122125
void Statusline::Redraw(bool update) {

lldb/source/Host/common/Editline.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,13 @@ void Editline::PrintAsync(lldb::LockableStreamFileSP stream_sp, const char *s,
17091709
}
17101710
}
17111711

1712+
void Editline::Refresh() {
1713+
if (!m_editline || !m_output_stream_sp)
1714+
return;
1715+
LockedStreamFile locked_stream = m_output_stream_sp->Lock();
1716+
MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingCursor);
1717+
}
1718+
17121719
bool Editline::CompleteCharacter(char ch, EditLineGetCharType &out) {
17131720
#if !LLDB_EDITLINE_USE_WCHAR
17141721
if (ch == (char)EOF)

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ def do_setup(self):
2727
self.expect("run", substrs=["stop reason"])
2828
self.resize()
2929

30-
def resize(self):
30+
def resize(self, height=None, width=None):
31+
height = self.TERMINAL_HEIGHT if not height else height
32+
width = self.TERMINAL_WIDTH if not width else width
3133
# Change the terminal dimensions. When we launch the tests, we reset
3234
# all the settings, leaving the terminal dimensions unset.
33-
self.child.setwinsize(self.TERMINAL_HEIGHT, self.TERMINAL_WIDTH)
35+
self.child.setwinsize(height, width)
3436

3537
def test(self):
3638
"""Basic test for the statusline."""
@@ -104,3 +106,16 @@ def test_no_target(self):
104106
self.resize()
105107

106108
self.expect("set set show-statusline true", ["no target"])
109+
110+
@skipIfEditlineSupportMissing
111+
def test_resize(self):
112+
"""Test that move the cursor when resizing."""
113+
self.launch(timeout=self.TIMEOUT)
114+
self.resize()
115+
self.expect("set set show-statusline true", ["no target"])
116+
self.resize(20, 60)
117+
# Check for the newline followed by the escape code to move the cursor
118+
# up one line.
119+
self.child.expect(re.escape("\n\x1b[1A"))
120+
# Check for the escape code to move the cursor back to column 8.
121+
self.child.expect(re.escape("\x1b[8G"))

0 commit comments

Comments
 (0)