https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/145823
>From 0c722624e71814eee9a4c493de0a75f7392cfebe Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Wed, 25 Jun 2025 17:18:22 -0700 Subject: [PATCH] [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 --- lldb/include/lldb/Core/Debugger.h | 2 ++ lldb/include/lldb/Core/IOHandler.h | 7 +++++ lldb/include/lldb/Host/Editline.h | 3 ++ lldb/include/lldb/Host/Terminal.h | 5 ++++ lldb/source/Core/Debugger.cpp | 8 +++++ lldb/source/Core/IOHandler.cpp | 8 +++++ lldb/source/Core/Statusline.cpp | 33 ++++++++++++++++----- lldb/source/Host/common/Editline.cpp | 44 ++++++++++++++++++---------- 8 files changed, 87 insertions(+), 23 deletions(-) diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index 2087ef2a11562..46f70024fd65b 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -133,6 +133,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>, void SetAsyncExecution(bool async); + CursorPosition GetIOHandlerCursorPosition(); + lldb::FileSP GetInputFileSP() { return m_input_file_sp; } File &GetInputFile() { return *m_input_file_sp; } diff --git a/lldb/include/lldb/Core/IOHandler.h b/lldb/include/lldb/Core/IOHandler.h index 2fb3d7a7c9cc3..9d17e3a45846a 100644 --- a/lldb/include/lldb/Core/IOHandler.h +++ b/lldb/include/lldb/Core/IOHandler.h @@ -10,6 +10,7 @@ #define LLDB_CORE_IOHANDLER_H #include "lldb/Host/Config.h" +#include "lldb/Host/Terminal.h" #include "lldb/Utility/CompletionRequest.h" #include "lldb/Utility/Flags.h" #include "lldb/Utility/Predicate.h" @@ -113,6 +114,10 @@ class IOHandler { virtual const char *GetHelpPrologue() { return nullptr; } + virtual CursorPosition GetCursorPosition() const { + return {std::nullopt, std::nullopt}; + } + int GetInputFD(); int GetOutputFD(); @@ -404,6 +409,8 @@ class IOHandlerEditline : public IOHandler { void PrintAsync(const char *s, size_t len, bool is_stdout) override; + virtual CursorPosition GetCursorPosition() const override; + private: #if LLDB_ENABLE_LIBEDIT bool IsInputCompleteCallback(Editline *editline, StringList &lines); diff --git a/lldb/include/lldb/Host/Editline.h b/lldb/include/lldb/Host/Editline.h index c202a76758e13..5672212421687 100644 --- a/lldb/include/lldb/Host/Editline.h +++ b/lldb/include/lldb/Host/Editline.h @@ -35,6 +35,7 @@ #include <vector> #include "lldb/Host/StreamFile.h" +#include "lldb/Host/Terminal.h" #include "lldb/lldb-private.h" #if !defined(_WIN32) && !defined(__ANDROID__) @@ -267,6 +268,8 @@ class Editline { size_t GetTerminalHeight() { return m_terminal_height; } + CursorPosition GetCursorPosition(); + private: /// Sets the lowest line number for multi-line editing sessions. A value of /// zero suppresses line number printing in the prompt. diff --git a/lldb/include/lldb/Host/Terminal.h b/lldb/include/lldb/Host/Terminal.h index da0d05e8bd265..29cd45a4c4034 100644 --- a/lldb/include/lldb/Host/Terminal.h +++ b/lldb/include/lldb/Host/Terminal.h @@ -169,6 +169,11 @@ class TerminalState { lldb::pid_t m_process_group = -1; ///< Cached process group information. }; +struct CursorPosition { + std::optional<size_t> cols; + std::optional<size_t> rows; +}; + } // namespace lldb_private #endif // LLDB_HOST_TERMINAL_H diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index 445baf1f63785..bee2a6ac60396 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -1240,6 +1240,14 @@ void Debugger::DispatchInputEndOfFile() { reader_sp->GotEOF(); } +CursorPosition Debugger::GetIOHandlerCursorPosition() { + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + IOHandlerSP reader_sp(m_io_handler_stack.Top()); + if (reader_sp) + return reader_sp->GetCursorPosition(); + return {std::nullopt, std::nullopt}; +} + void Debugger::ClearIOHandlers() { // The bottom input reader should be the main debugger input reader. We do // not want to close that one here. diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp index 8aac507eaa0c2..b3f89def98701 100644 --- a/lldb/source/Core/IOHandler.cpp +++ b/lldb/source/Core/IOHandler.cpp @@ -634,6 +634,14 @@ void IOHandlerEditline::GotEOF() { #endif } +CursorPosition IOHandlerEditline::GetCursorPosition() const { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + return m_editline_up->GetCursorPosition(); +#endif + return {std::nullopt, std::nullopt}; +} + void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) { #if LLDB_ENABLE_LIBEDIT if (m_editline_up) { diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp index 8a8640805cac0..e9702bd68ec15 100644 --- a/lldb/source/Core/Statusline.cpp +++ b/lldb/source/Core/Statusline.cpp @@ -28,6 +28,7 @@ #define ANSI_TO_START_OF_ROW ESCAPE "[%u;1f" #define ANSI_REVERSE_VIDEO ESCAPE "[7m" #define ANSI_UP_ROWS ESCAPE "[%dA" +#define ANSI_SET_COLUMN_N ESCAPE "[%uG" using namespace lldb; using namespace lldb_private; @@ -102,20 +103,36 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) { const unsigned scroll_height = (mode == DisableStatusline) ? m_terminal_height : m_terminal_height - 1; + CursorPosition cursor_position = m_debugger.GetIOHandlerCursorPosition(); + LockedStreamFile locked_stream = stream_sp->Lock(); + + if (mode == EnableStatusline) { + // Move everything on the screen up to make space for the statusline. This + // is going to move the cursor to the start of the next line which we need + // to undo. + locked_stream << '\n'; + + // First move the cursor back up. We can't use ANSI_SAVE/RESTORE_CURSOR + // here, because the old and new position differ if everything on the screen + // moved up. + locked_stream.Printf(ANSI_UP_ROWS, 1); + + // Finally move the cursor back to the correct column, if the IOHandler was + // able to tell us where that was. + if (cursor_position.cols) + locked_stream.Printf(ANSI_SET_COLUMN_N, + static_cast<unsigned>(*cursor_position.cols)); + } + + // Adjust the scroll window. locked_stream << ANSI_SAVE_CURSOR; locked_stream.Printf(ANSI_SET_SCROLL_ROWS, scroll_height); locked_stream << ANSI_RESTORE_CURSOR; - switch (mode) { - case EnableStatusline: - // Move everything on the screen up. - locked_stream.Printf(ANSI_UP_ROWS, 1); - locked_stream << '\n'; - break; - case DisableStatusline: + + if (mode == DisableStatusline) { // Clear the screen below to hide the old statusline. locked_stream << ANSI_CLEAR_BELOW; - break; } } diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp index b251ded6c3793..11bfc368dbd91 100644 --- a/lldb/source/Host/common/Editline.cpp +++ b/lldb/source/Host/common/Editline.cpp @@ -122,22 +122,21 @@ static int GetOperation(HistoryOperation op) { // - The H_FIRST returns the most recent entry in the history. // // The naming of the enum entries match the semantic meaning. - switch(op) { - case HistoryOperation::Oldest: - return H_LAST; - case HistoryOperation::Older: - return H_NEXT; - case HistoryOperation::Current: - return H_CURR; - case HistoryOperation::Newer: - return H_PREV; - case HistoryOperation::Newest: - return H_FIRST; + switch (op) { + case HistoryOperation::Oldest: + return H_LAST; + case HistoryOperation::Older: + return H_NEXT; + case HistoryOperation::Current: + return H_CURR; + case HistoryOperation::Newer: + return H_PREV; + case HistoryOperation::Newest: + return H_FIRST; } llvm_unreachable("Fully covered switch!"); } - EditLineStringType CombineLines(const std::vector<EditLineStringType> &lines) { EditLineStringStreamType combined_stream; for (EditLineStringType line : lines) { @@ -313,8 +312,8 @@ class EditlineHistory { /// Path to the history file. std::string m_path; }; -} -} +} // namespace line_editor +} // namespace lldb_private // Editline private methods @@ -398,6 +397,20 @@ int Editline::GetLineIndexForLocation(CursorLocation location, int cursor_row) { return line; } +CursorPosition Editline::GetCursorPosition() { + if (!m_editline) + return {}; + + const LineInfoW *info = el_wline(m_editline); + if (!info) + return {}; + + const size_t editline_cursor_col = + (int)((info->cursor - info->buffer) + GetPromptWidth()) + 1; + + return {editline_cursor_col, std::nullopt}; +} + void Editline::MoveCursor(CursorLocation from, CursorLocation to) { const LineInfoW *info = el_wline(m_editline); int editline_cursor_position = @@ -1151,7 +1164,8 @@ unsigned char Editline::TabCommand(int ch) { to_add.push_back(' '); el_deletestr(m_editline, request.GetCursorArgumentPrefix().size()); el_insertstr(m_editline, to_add.c_str()); - // Clear all the autosuggestion parts if the only single space can be completed. + // Clear all the autosuggestion parts if the only single space can be + // completed. if (to_add == " ") return CC_REDISPLAY; return CC_REFRESH; _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits