Skip to content

Commit 45de6a1

Browse files
committed
[lldb] Correctly restore the cursor column after resizing the statusline
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 relying on the active IOHandler to report its cursor position and if available, use that to restore the cursor column position. Fixes #134064
1 parent 00f6d6a commit 45de6a1

File tree

7 files changed

+85
-23
lines changed

7 files changed

+85
-23
lines changed

lldb/include/lldb/Core/Debugger.h

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

134134
void SetAsyncExecution(bool async);
135135

136+
std::optional<IOHandler::CursorPosition> GetIOHandlerCursorPosition();
137+
136138
lldb::FileSP GetInputFileSP() { return m_input_file_sp; }
137139
File &GetInputFile() { return *m_input_file_sp; }
138140

lldb/include/lldb/Core/IOHandler.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ class IOHandler {
113113

114114
virtual const char *GetHelpPrologue() { return nullptr; }
115115

116+
using CursorPosition = std::pair<size_t, size_t>;
117+
virtual std::optional<CursorPosition> GetCursorPosition() const {
118+
return std::nullopt;
119+
}
120+
116121
int GetInputFD();
117122

118123
int GetOutputFD();
@@ -404,6 +409,8 @@ class IOHandlerEditline : public IOHandler {
404409

405410
void PrintAsync(const char *s, size_t len, bool is_stdout) override;
406411

412+
virtual std::optional<CursorPosition> GetCursorPosition() const override;
413+
407414
private:
408415
#if LLDB_ENABLE_LIBEDIT
409416
bool IsInputCompleteCallback(Editline *editline, StringList &lines);

lldb/include/lldb/Host/Editline.h

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

268268
size_t GetTerminalHeight() { return m_terminal_height; }
269269

270+
using CursorPosition = std::pair<size_t, size_t>;
271+
std::optional<CursorPosition> GetCursorPosition();
272+
270273
private:
271274
/// Sets the lowest line number for multi-line editing sessions. A value of
272275
/// zero suppresses line number printing in the prompt.

lldb/source/Core/Debugger.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,15 @@ void Debugger::DispatchInputEndOfFile() {
12401240
reader_sp->GotEOF();
12411241
}
12421242

1243+
std::optional<IOHandler::CursorPosition>
1244+
Debugger::GetIOHandlerCursorPosition() {
1245+
std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex());
1246+
IOHandlerSP reader_sp(m_io_handler_stack.Top());
1247+
if (reader_sp)
1248+
return reader_sp->GetCursorPosition();
1249+
return std::nullopt;
1250+
}
1251+
12431252
void Debugger::ClearIOHandlers() {
12441253
// The bottom input reader should be the main debugger input reader. We do
12451254
// not want to close that one here.

lldb/source/Core/IOHandler.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,15 @@ void IOHandlerEditline::GotEOF() {
634634
#endif
635635
}
636636

637+
std::optional<IOHandler::CursorPosition>
638+
IOHandlerEditline::GetCursorPosition() const {
639+
#if LLDB_ENABLE_LIBEDIT
640+
if (m_editline_up)
641+
return m_editline_up->GetCursorPosition();
642+
#endif
643+
return std::nullopt;
644+
}
645+
637646
void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {
638647
#if LLDB_ENABLE_LIBEDIT
639648
if (m_editline_up) {

lldb/source/Core/Statusline.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define ANSI_TO_START_OF_ROW ESCAPE "[%u;1f"
2929
#define ANSI_REVERSE_VIDEO ESCAPE "[7m"
3030
#define ANSI_UP_ROWS ESCAPE "[%dA"
31+
#define ANSI_SET_COLUMN_N ESCAPE "[%uG"
3132

3233
using namespace lldb;
3334
using namespace lldb_private;
@@ -102,20 +103,37 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) {
102103
const unsigned scroll_height =
103104
(mode == DisableStatusline) ? m_terminal_height : m_terminal_height - 1;
104105

106+
std::optional<IOHandler::CursorPosition> cursor_position =
107+
m_debugger.GetIOHandlerCursorPosition();
108+
105109
LockedStreamFile locked_stream = stream_sp->Lock();
110+
111+
if (mode == EnableStatusline) {
112+
// Move everything on the screen up to make space for the statusline. This
113+
// is going to move the cursor to the start of the next line which we need
114+
// to undo.
115+
locked_stream << '\n';
116+
117+
// First move the cursor back up. We can't use ANSI_SAVE/RESTORE_CURSOR
118+
// here, because the old and new position differ if everything on the screen
119+
// moved up.
120+
locked_stream.Printf(ANSI_UP_ROWS, 1);
121+
122+
// Finally move the cursor back to the correct column, if the IOHandler was
123+
// able to tell us where that was.
124+
if (cursor_position)
125+
locked_stream.Printf(ANSI_SET_COLUMN_N,
126+
static_cast<unsigned>(cursor_position->first));
127+
}
128+
129+
// Adjust the scroll window.
106130
locked_stream << ANSI_SAVE_CURSOR;
107131
locked_stream.Printf(ANSI_SET_SCROLL_ROWS, scroll_height);
108132
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:
133+
134+
if (mode == DisableStatusline) {
116135
// Clear the screen below to hide the old statusline.
117136
locked_stream << ANSI_CLEAR_BELOW;
118-
break;
119137
}
120138
}
121139

lldb/source/Host/common/Editline.cpp

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,21 @@ static int GetOperation(HistoryOperation op) {
122122
// - The H_FIRST returns the most recent entry in the history.
123123
//
124124
// The naming of the enum entries match the semantic meaning.
125-
switch(op) {
126-
case HistoryOperation::Oldest:
127-
return H_LAST;
128-
case HistoryOperation::Older:
129-
return H_NEXT;
130-
case HistoryOperation::Current:
131-
return H_CURR;
132-
case HistoryOperation::Newer:
133-
return H_PREV;
134-
case HistoryOperation::Newest:
135-
return H_FIRST;
125+
switch (op) {
126+
case HistoryOperation::Oldest:
127+
return H_LAST;
128+
case HistoryOperation::Older:
129+
return H_NEXT;
130+
case HistoryOperation::Current:
131+
return H_CURR;
132+
case HistoryOperation::Newer:
133+
return H_PREV;
134+
case HistoryOperation::Newest:
135+
return H_FIRST;
136136
}
137137
llvm_unreachable("Fully covered switch!");
138138
}
139139

140-
141140
EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) {
142141
EditLineStringStreamType combined_stream;
143142
for (EditLineStringType line : lines) {
@@ -313,8 +312,8 @@ class EditlineHistory {
313312
/// Path to the history file.
314313
std::string m_path;
315314
};
316-
}
317-
}
315+
} // namespace line_editor
316+
} // namespace lldb_private
318317

319318
// Editline private methods
320319

@@ -398,6 +397,20 @@ int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) {
398397
return line;
399398
}
400399

400+
std::optional<Editline::CursorPosition> Editline::GetCursorPosition() {
401+
if (!m_editline)
402+
return std::nullopt;
403+
404+
const LineInfoW *info = el_wline(m_editline);
405+
if (!info)
406+
return std::nullopt;
407+
408+
const size_t editline_cursor_col =
409+
(int)((info->cursor - info->buffer) + GetPromptWidth()) + 1;
410+
411+
return {{editline_cursor_col, 0}};
412+
}
413+
401414
void Editline::MoveCursor(CursorLocation from, CursorLocation to) {
402415
const LineInfoW *info = el_wline(m_editline);
403416
int editline_cursor_position =
@@ -1151,7 +1164,8 @@ unsigned char Editline::TabCommand(int ch) {
11511164
to_add.push_back(' ');
11521165
el_deletestr(m_editline, request.GetCursorArgumentPrefix().size());
11531166
el_insertstr(m_editline, to_add.c_str());
1154-
// Clear all the autosuggestion parts if the only single space can be completed.
1167+
// Clear all the autosuggestion parts if the only single space can be
1168+
// completed.
11551169
if (to_add == " ")
11561170
return CC_REDISPLAY;
11571171
return CC_REFRESH;

0 commit comments

Comments
 (0)