aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Core/Statusline.cpp
blob: bfbd190fba27cc60c148bc91aae9b82ffe40c413 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//===-- 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_CLEAR_SCREEN ESCAPE "[2J"
#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()) {}

Statusline::~Statusline() { Disable(); }

void Statusline::TerminalSizeChanged() {
  m_terminal_width = m_debugger.GetTerminalWidth();
  m_terminal_height = m_debugger.GetTerminalHeight();

  UpdateScrollWindow(ResizeStatusline);

  // Redraw the old statusline.
  Redraw(std::nullopt);
}

void Statusline::Enable(std::optional<ExecutionContextRef> exe_ctx_ref) {
  // Reduce the scroll window to make space for the status bar below.
  UpdateScrollWindow(EnableStatusline);

  // Draw the statusline.
  Redraw(exe_ctx_ref);
}

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;

  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::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 reduced_scroll_window = m_terminal_height - 1;
  LockedStreamFile locked_stream = stream_sp->Lock();

  switch (mode) {
  case EnableStatusline:
    // Move everything on the screen up.
    locked_stream << '\n';
    locked_stream.Printf(ANSI_UP_ROWS, 1);
    // Reduce the scroll window.
    locked_stream << ANSI_SAVE_CURSOR;
    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_window);
    locked_stream << ANSI_RESTORE_CURSOR;
    break;
  case DisableStatusline:
    // Reset the scroll window.
    locked_stream << ANSI_SAVE_CURSOR;
    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, 0);
    locked_stream << ANSI_RESTORE_CURSOR;
    // Clear the screen below to hide the old statusline.
    locked_stream << ANSI_CLEAR_BELOW;
    break;
  case ResizeStatusline:
    // Clear the screen and update the scroll window.
    // FIXME: Find a better solution (#146919).
    locked_stream << ANSI_CLEAR_SCREEN;
    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_window);
    break;
  }

  m_debugger.RefreshIOHandler();
}

void Statusline::Redraw(std::optional<ExecutionContextRef> exe_ctx_ref) {
  // Update the cached execution context.
  if (exe_ctx_ref)
    m_exe_ctx_ref = *exe_ctx_ref;

  // Lock the execution context.
  ExecutionContext exe_ctx =
      m_exe_ctx_ref.Lock(/*thread_and_frame_only_if_stopped=*/false);

  // Compute the symbol context if we're stopped.
  SymbolContext sym_ctx;
  llvm::Expected<StoppedExecutionContext> stopped_exe_ctx =
      GetStoppedExecutionContext(&m_exe_ctx_ref);
  if (stopped_exe_ctx) {
    // The StoppedExecutionContext only ensures that we hold the run lock.
    // The process could be in an exited or unloaded state and have no frame.
    if (auto frame_sp = stopped_exe_ctx->GetFrameSP())
      sym_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything);
  } else {
    // We can draw the statusline without being stopped.
    llvm::consumeError(stopped_exe_ctx.takeError());
  }

  StreamString stream;
  FormatEntity::Entry format = m_debugger.GetStatuslineFormat();
  FormatEntity::Format(format, stream, &sym_ctx, &exe_ctx, nullptr, nullptr,
                       false, false);

  Draw(stream.GetString().str());
}