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
156 lines
4.8 KiB
C++
156 lines
4.8 KiB
C++
//===-- Statusline.cpp ---------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/Core/Statusline.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/FormatEntity.h"
|
|
#include "lldb/Host/StreamFile.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Symbol/SymbolContext.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/StackFrame.h"
|
|
#include "lldb/Utility/AnsiTerminal.h"
|
|
#include "lldb/Utility/StreamString.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/Locale.h"
|
|
|
|
#define ESCAPE "\x1b"
|
|
#define ANSI_NORMAL ESCAPE "[0m"
|
|
#define ANSI_SAVE_CURSOR ESCAPE "7"
|
|
#define ANSI_RESTORE_CURSOR ESCAPE "8"
|
|
#define ANSI_CLEAR_BELOW ESCAPE "[J"
|
|
#define ANSI_SET_SCROLL_ROWS ESCAPE "[1;%ur"
|
|
#define ANSI_TO_START_OF_ROW ESCAPE "[%u;1f"
|
|
#define ANSI_REVERSE_VIDEO ESCAPE "[7m"
|
|
#define ANSI_UP_ROWS ESCAPE "[%dA"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
Statusline::Statusline(Debugger &debugger)
|
|
: m_debugger(debugger), m_terminal_width(m_debugger.GetTerminalWidth()),
|
|
m_terminal_height(m_debugger.GetTerminalHeight()) {
|
|
Enable();
|
|
}
|
|
|
|
Statusline::~Statusline() { Disable(); }
|
|
|
|
void Statusline::TerminalSizeChanged() {
|
|
UpdateTerminalProperties();
|
|
|
|
// This definitely isn't signal safe, but the best we can do, until we
|
|
// have proper signal-catching thread.
|
|
Redraw(/*update=*/false);
|
|
}
|
|
|
|
void Statusline::Enable() {
|
|
// Reduce the scroll window to make space for the status bar below.
|
|
UpdateScrollWindow(EnableStatusline);
|
|
|
|
// Draw the statusline.
|
|
Redraw(/*update=*/true);
|
|
}
|
|
|
|
void Statusline::Disable() {
|
|
// Extend the scroll window to cover the status bar.
|
|
UpdateScrollWindow(DisableStatusline);
|
|
}
|
|
|
|
void Statusline::Draw(std::string str) {
|
|
lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
|
|
if (!stream_sp)
|
|
return;
|
|
|
|
m_last_str = str;
|
|
|
|
str = ansi::TrimAndPad(str, m_terminal_width);
|
|
|
|
LockedStreamFile locked_stream = stream_sp->Lock();
|
|
locked_stream << ANSI_SAVE_CURSOR;
|
|
locked_stream.Printf(ANSI_TO_START_OF_ROW,
|
|
static_cast<unsigned>(m_terminal_height));
|
|
|
|
// Use "reverse video" to make sure the statusline has a background. Only do
|
|
// this when colors are disabled, and rely on the statusline format otherwise.
|
|
if (!m_debugger.GetUseColor())
|
|
locked_stream << ANSI_REVERSE_VIDEO;
|
|
|
|
locked_stream << str;
|
|
locked_stream << ANSI_NORMAL;
|
|
locked_stream << ANSI_RESTORE_CURSOR;
|
|
}
|
|
|
|
void Statusline::UpdateTerminalProperties() {
|
|
UpdateScrollWindow(DisableStatusline);
|
|
m_terminal_width = m_debugger.GetTerminalWidth();
|
|
m_terminal_height = m_debugger.GetTerminalHeight();
|
|
UpdateScrollWindow(EnableStatusline);
|
|
}
|
|
|
|
void Statusline::UpdateScrollWindow(ScrollWindowMode mode) {
|
|
assert(m_terminal_width != 0 && m_terminal_height != 0);
|
|
|
|
lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
|
|
if (!stream_sp)
|
|
return;
|
|
|
|
const unsigned scroll_height =
|
|
(mode == DisableStatusline) ? m_terminal_height : m_terminal_height - 1;
|
|
|
|
LockedStreamFile locked_stream = stream_sp->Lock();
|
|
|
|
if (mode == EnableStatusline) {
|
|
// Move everything on the screen up.
|
|
locked_stream << '\n';
|
|
locked_stream.Printf(ANSI_UP_ROWS, 1);
|
|
}
|
|
|
|
locked_stream << ANSI_SAVE_CURSOR;
|
|
locked_stream.Printf(ANSI_SET_SCROLL_ROWS, scroll_height);
|
|
locked_stream << ANSI_RESTORE_CURSOR;
|
|
|
|
if (mode == DisableStatusline) {
|
|
// Clear the screen below to hide the old statusline.
|
|
locked_stream << ANSI_CLEAR_BELOW;
|
|
}
|
|
|
|
m_debugger.RefreshIOHandler();
|
|
}
|
|
|
|
void Statusline::Redraw(bool update) {
|
|
if (!update) {
|
|
Draw(m_last_str);
|
|
return;
|
|
}
|
|
|
|
ExecutionContext exe_ctx = m_debugger.GetSelectedExecutionContext();
|
|
|
|
// For colors and progress events, the format entity needs access to the
|
|
// debugger, which requires a target in the execution context.
|
|
if (!exe_ctx.HasTargetScope())
|
|
exe_ctx.SetTargetPtr(&m_debugger.GetSelectedOrDummyTarget());
|
|
|
|
SymbolContext symbol_ctx;
|
|
if (ProcessSP process_sp = exe_ctx.GetProcessSP()) {
|
|
// Check if the process is stopped, and if it is, make sure it remains
|
|
// stopped until we've computed the symbol context.
|
|
Process::StopLocker stop_locker;
|
|
if (stop_locker.TryLock(&process_sp->GetRunLock())) {
|
|
if (auto frame_sp = exe_ctx.GetFrameSP())
|
|
symbol_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything);
|
|
}
|
|
}
|
|
|
|
StreamString stream;
|
|
FormatEntity::Entry format = m_debugger.GetStatuslineFormat();
|
|
FormatEntity::Format(format, stream, &symbol_ctx, &exe_ctx, nullptr, nullptr,
|
|
false, false);
|
|
|
|
Draw(stream.GetString().str());
|
|
}
|