1 //===-- IOHandlerCursesGUI.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "lldb/Core/IOHandlerCursesGUI.h"
10 #include "lldb/Host/Config.h"
11
12 #if LLDB_ENABLE_CURSES
13 #if CURSES_HAVE_NCURSES_CURSES_H
14 #include <ncurses/curses.h>
15 #include <ncurses/panel.h>
16 #else
17 #include <curses.h>
18 #include <panel.h>
19 #endif
20 #endif
21
22 #if defined(__APPLE__)
23 #include <deque>
24 #endif
25 #include <string>
26
27 #include "lldb/Core/Debugger.h"
28 #include "lldb/Core/StreamFile.h"
29 #include "lldb/Host/File.h"
30 #include "lldb/Utility/Predicate.h"
31 #include "lldb/Utility/Status.h"
32 #include "lldb/Utility/StreamString.h"
33 #include "lldb/Utility/StringList.h"
34 #include "lldb/lldb-forward.h"
35
36 #include "lldb/Interpreter/CommandCompletions.h"
37 #include "lldb/Interpreter/CommandInterpreter.h"
38
39 #if LLDB_ENABLE_CURSES
40 #include "lldb/Breakpoint/BreakpointLocation.h"
41 #include "lldb/Core/Module.h"
42 #include "lldb/Core/ValueObject.h"
43 #include "lldb/Core/ValueObjectRegister.h"
44 #include "lldb/Symbol/Block.h"
45 #include "lldb/Symbol/Function.h"
46 #include "lldb/Symbol/Symbol.h"
47 #include "lldb/Symbol/VariableList.h"
48 #include "lldb/Target/Process.h"
49 #include "lldb/Target/RegisterContext.h"
50 #include "lldb/Target/StackFrame.h"
51 #include "lldb/Target/StopInfo.h"
52 #include "lldb/Target/Target.h"
53 #include "lldb/Target/Thread.h"
54 #include "lldb/Utility/State.h"
55 #endif
56
57 #include "llvm/ADT/StringRef.h"
58
59 #ifdef _WIN32
60 #include "lldb/Host/windows/windows.h"
61 #endif
62
63 #include <memory>
64 #include <mutex>
65
66 #include <assert.h>
67 #include <ctype.h>
68 #include <errno.h>
69 #include <locale.h>
70 #include <stdint.h>
71 #include <stdio.h>
72 #include <string.h>
73 #include <type_traits>
74
75 using namespace lldb;
76 using namespace lldb_private;
77 using llvm::None;
78 using llvm::Optional;
79 using llvm::StringRef;
80
81 // we may want curses to be disabled for some builds for instance, windows
82 #if LLDB_ENABLE_CURSES
83
84 #define KEY_RETURN 10
85 #define KEY_ESCAPE 27
86
87 namespace curses {
88 class Menu;
89 class MenuDelegate;
90 class Window;
91 class WindowDelegate;
92 typedef std::shared_ptr<Menu> MenuSP;
93 typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
94 typedef std::shared_ptr<Window> WindowSP;
95 typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
96 typedef std::vector<MenuSP> Menus;
97 typedef std::vector<WindowSP> Windows;
98 typedef std::vector<WindowDelegateSP> WindowDelegates;
99
100 #if 0
101 type summary add -s "x=${var.x}, y=${var.y}" curses::Point
102 type summary add -s "w=${var.width}, h=${var.height}" curses::Size
103 type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
104 #endif
105
106 struct Point {
107 int x;
108 int y;
109
Pointcurses::Point110 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
111
Clearcurses::Point112 void Clear() {
113 x = 0;
114 y = 0;
115 }
116
operator +=curses::Point117 Point &operator+=(const Point &rhs) {
118 x += rhs.x;
119 y += rhs.y;
120 return *this;
121 }
122
Dumpcurses::Point123 void Dump() { printf("(x=%i, y=%i)\n", x, y); }
124 };
125
operator ==(const Point & lhs,const Point & rhs)126 bool operator==(const Point &lhs, const Point &rhs) {
127 return lhs.x == rhs.x && lhs.y == rhs.y;
128 }
129
operator !=(const Point & lhs,const Point & rhs)130 bool operator!=(const Point &lhs, const Point &rhs) {
131 return lhs.x != rhs.x || lhs.y != rhs.y;
132 }
133
134 struct Size {
135 int width;
136 int height;
Sizecurses::Size137 Size(int w = 0, int h = 0) : width(w), height(h) {}
138
Clearcurses::Size139 void Clear() {
140 width = 0;
141 height = 0;
142 }
143
Dumpcurses::Size144 void Dump() { printf("(w=%i, h=%i)\n", width, height); }
145 };
146
operator ==(const Size & lhs,const Size & rhs)147 bool operator==(const Size &lhs, const Size &rhs) {
148 return lhs.width == rhs.width && lhs.height == rhs.height;
149 }
150
operator !=(const Size & lhs,const Size & rhs)151 bool operator!=(const Size &lhs, const Size &rhs) {
152 return lhs.width != rhs.width || lhs.height != rhs.height;
153 }
154
155 struct Rect {
156 Point origin;
157 Size size;
158
Rectcurses::Rect159 Rect() : origin(), size() {}
160
Rectcurses::Rect161 Rect(const Point &p, const Size &s) : origin(p), size(s) {}
162
Clearcurses::Rect163 void Clear() {
164 origin.Clear();
165 size.Clear();
166 }
167
Dumpcurses::Rect168 void Dump() {
169 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
170 size.height);
171 }
172
Insetcurses::Rect173 void Inset(int w, int h) {
174 if (size.width > w * 2)
175 size.width -= w * 2;
176 origin.x += w;
177
178 if (size.height > h * 2)
179 size.height -= h * 2;
180 origin.y += h;
181 }
182
183 // Return a status bar rectangle which is the last line of this rectangle.
184 // This rectangle will be modified to not include the status bar area.
MakeStatusBarcurses::Rect185 Rect MakeStatusBar() {
186 Rect status_bar;
187 if (size.height > 1) {
188 status_bar.origin.x = origin.x;
189 status_bar.origin.y = size.height;
190 status_bar.size.width = size.width;
191 status_bar.size.height = 1;
192 --size.height;
193 }
194 return status_bar;
195 }
196
197 // Return a menubar rectangle which is the first line of this rectangle. This
198 // rectangle will be modified to not include the menubar area.
MakeMenuBarcurses::Rect199 Rect MakeMenuBar() {
200 Rect menubar;
201 if (size.height > 1) {
202 menubar.origin.x = origin.x;
203 menubar.origin.y = origin.y;
204 menubar.size.width = size.width;
205 menubar.size.height = 1;
206 ++origin.y;
207 --size.height;
208 }
209 return menubar;
210 }
211
HorizontalSplitPercentagecurses::Rect212 void HorizontalSplitPercentage(float top_percentage, Rect &top,
213 Rect &bottom) const {
214 float top_height = top_percentage * size.height;
215 HorizontalSplit(top_height, top, bottom);
216 }
217
HorizontalSplitcurses::Rect218 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
219 top = *this;
220 if (top_height < size.height) {
221 top.size.height = top_height;
222 bottom.origin.x = origin.x;
223 bottom.origin.y = origin.y + top.size.height;
224 bottom.size.width = size.width;
225 bottom.size.height = size.height - top.size.height;
226 } else {
227 bottom.Clear();
228 }
229 }
230
VerticalSplitPercentagecurses::Rect231 void VerticalSplitPercentage(float left_percentage, Rect &left,
232 Rect &right) const {
233 float left_width = left_percentage * size.width;
234 VerticalSplit(left_width, left, right);
235 }
236
VerticalSplitcurses::Rect237 void VerticalSplit(int left_width, Rect &left, Rect &right) const {
238 left = *this;
239 if (left_width < size.width) {
240 left.size.width = left_width;
241 right.origin.x = origin.x + left.size.width;
242 right.origin.y = origin.y;
243 right.size.width = size.width - left.size.width;
244 right.size.height = size.height;
245 } else {
246 right.Clear();
247 }
248 }
249 };
250
operator ==(const Rect & lhs,const Rect & rhs)251 bool operator==(const Rect &lhs, const Rect &rhs) {
252 return lhs.origin == rhs.origin && lhs.size == rhs.size;
253 }
254
operator !=(const Rect & lhs,const Rect & rhs)255 bool operator!=(const Rect &lhs, const Rect &rhs) {
256 return lhs.origin != rhs.origin || lhs.size != rhs.size;
257 }
258
259 enum HandleCharResult {
260 eKeyNotHandled = 0,
261 eKeyHandled = 1,
262 eQuitApplication = 2
263 };
264
265 enum class MenuActionResult {
266 Handled,
267 NotHandled,
268 Quit // Exit all menus and quit
269 };
270
271 struct KeyHelp {
272 int ch;
273 const char *description;
274 };
275
276 // COLOR_PAIR index names
277 enum {
278 // First 16 colors are 8 black background and 8 blue background colors,
279 // needed by OutputColoredStringTruncated().
280 BlackOnBlack = 1,
281 RedOnBlack,
282 GreenOnBlack,
283 YellowOnBlack,
284 BlueOnBlack,
285 MagentaOnBlack,
286 CyanOnBlack,
287 WhiteOnBlack,
288 BlackOnBlue,
289 RedOnBlue,
290 GreenOnBlue,
291 YellowOnBlue,
292 BlueOnBlue,
293 MagentaOnBlue,
294 CyanOnBlue,
295 WhiteOnBlue,
296 // Other colors, as needed.
297 BlackOnWhite,
298 MagentaOnWhite,
299 LastColorPairIndex = MagentaOnWhite
300 };
301
302 class WindowDelegate {
303 public:
304 virtual ~WindowDelegate() = default;
305
WindowDelegateDraw(Window & window,bool force)306 virtual bool WindowDelegateDraw(Window &window, bool force) {
307 return false; // Drawing not handled
308 }
309
WindowDelegateHandleChar(Window & window,int key)310 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
311 return eKeyNotHandled;
312 }
313
WindowDelegateGetHelpText()314 virtual const char *WindowDelegateGetHelpText() { return nullptr; }
315
WindowDelegateGetKeyHelp()316 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
317 };
318
319 class HelpDialogDelegate : public WindowDelegate {
320 public:
321 HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
322
323 ~HelpDialogDelegate() override;
324
325 bool WindowDelegateDraw(Window &window, bool force) override;
326
327 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
328
GetNumLines() const329 size_t GetNumLines() const { return m_text.GetSize(); }
330
GetMaxLineLength() const331 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
332
333 protected:
334 StringList m_text;
335 int m_first_visible_line;
336 };
337
338 class Window {
339 public:
Window(const char * name)340 Window(const char *name)
341 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
342 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
343 m_prev_active_window_idx(UINT32_MAX), m_delete(false),
344 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
345
Window(const char * name,WINDOW * w,bool del=true)346 Window(const char *name, WINDOW *w, bool del = true)
347 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
348 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
349 m_prev_active_window_idx(UINT32_MAX), m_delete(del),
350 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
351 if (w)
352 Reset(w);
353 }
354
Window(const char * name,const Rect & bounds)355 Window(const char *name, const Rect &bounds)
356 : m_name(name), m_window(nullptr), m_parent(nullptr), m_subwindows(),
357 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
358 m_prev_active_window_idx(UINT32_MAX), m_delete(true),
359 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
360 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
361 bounds.origin.y));
362 }
363
~Window()364 virtual ~Window() {
365 RemoveSubWindows();
366 Reset();
367 }
368
Reset(WINDOW * w=nullptr,bool del=true)369 void Reset(WINDOW *w = nullptr, bool del = true) {
370 if (m_window == w)
371 return;
372
373 if (m_panel) {
374 ::del_panel(m_panel);
375 m_panel = nullptr;
376 }
377 if (m_window && m_delete) {
378 ::delwin(m_window);
379 m_window = nullptr;
380 m_delete = false;
381 }
382 if (w) {
383 m_window = w;
384 m_panel = ::new_panel(m_window);
385 m_delete = del;
386 }
387 }
388
AttributeOn(attr_t attr)389 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
AttributeOff(attr_t attr)390 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
Box(chtype v_char=ACS_VLINE,chtype h_char=ACS_HLINE)391 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
392 ::box(m_window, v_char, h_char);
393 }
Clear()394 void Clear() { ::wclear(m_window); }
Erase()395 void Erase() { ::werase(m_window); }
GetBounds() const396 Rect GetBounds() const {
397 return Rect(GetParentOrigin(), GetSize());
398 } // Get the rectangle in our parent window
GetChar()399 int GetChar() { return ::wgetch(m_window); }
GetCursorX() const400 int GetCursorX() const { return getcurx(m_window); }
GetCursorY() const401 int GetCursorY() const { return getcury(m_window); }
GetFrame() const402 Rect GetFrame() const {
403 return Rect(Point(), GetSize());
404 } // Get our rectangle in our own coordinate system
GetParentOrigin() const405 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
GetSize() const406 Size GetSize() const { return Size(GetWidth(), GetHeight()); }
GetParentX() const407 int GetParentX() const { return getparx(m_window); }
GetParentY() const408 int GetParentY() const { return getpary(m_window); }
GetMaxX() const409 int GetMaxX() const { return getmaxx(m_window); }
GetMaxY() const410 int GetMaxY() const { return getmaxy(m_window); }
GetWidth() const411 int GetWidth() const { return GetMaxX(); }
GetHeight() const412 int GetHeight() const { return GetMaxY(); }
MoveCursor(int x,int y)413 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
MoveWindow(int x,int y)414 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
Resize(int w,int h)415 void Resize(int w, int h) { ::wresize(m_window, h, w); }
Resize(const Size & size)416 void Resize(const Size &size) {
417 ::wresize(m_window, size.height, size.width);
418 }
PutChar(int ch)419 void PutChar(int ch) { ::waddch(m_window, ch); }
PutCString(const char * s,int len=-1)420 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
SetBackground(int color_pair_idx)421 void SetBackground(int color_pair_idx) {
422 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
423 }
424
PutCStringTruncated(int right_pad,const char * s,int len=-1)425 void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
426 int bytes_left = GetWidth() - GetCursorX();
427 if (bytes_left > right_pad) {
428 bytes_left -= right_pad;
429 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
430 }
431 }
432
MoveWindow(const Point & origin)433 void MoveWindow(const Point &origin) {
434 const bool moving_window = origin != GetParentOrigin();
435 if (m_is_subwin && moving_window) {
436 // Can't move subwindows, must delete and re-create
437 Size size = GetSize();
438 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
439 origin.x),
440 true);
441 } else {
442 ::mvwin(m_window, origin.y, origin.x);
443 }
444 }
445
SetBounds(const Rect & bounds)446 void SetBounds(const Rect &bounds) {
447 const bool moving_window = bounds.origin != GetParentOrigin();
448 if (m_is_subwin && moving_window) {
449 // Can't move subwindows, must delete and re-create
450 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
451 bounds.origin.y, bounds.origin.x),
452 true);
453 } else {
454 if (moving_window)
455 MoveWindow(bounds.origin);
456 Resize(bounds.size);
457 }
458 }
459
Printf(const char * format,...)460 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
461 va_list args;
462 va_start(args, format);
463 vwprintw(m_window, format, args);
464 va_end(args);
465 }
466
PrintfTruncated(int right_pad,const char * format,...)467 void PrintfTruncated(int right_pad, const char *format, ...)
468 __attribute__((format(printf, 3, 4))) {
469 va_list args;
470 va_start(args, format);
471 StreamString strm;
472 strm.PrintfVarArg(format, args);
473 va_end(args);
474 PutCStringTruncated(right_pad, strm.GetData());
475 }
476
LimitLengthToRestOfLine(size_t length) const477 size_t LimitLengthToRestOfLine(size_t length) const {
478 return std::min<size_t>(length, std::max(0, GetWidth() - GetCursorX() - 1));
479 }
480
481 // Curses doesn't allow direct output of color escape sequences, but that's
482 // how we get source lines from the Highligher class. Read the line and
483 // convert color escape sequences to curses color attributes. Use
484 // first_skip_count to skip leading visible characters. Returns false if all
485 // visible characters were skipped due to first_skip_count.
OutputColoredStringTruncated(int right_pad,StringRef string,size_t skip_first_count,bool use_blue_background)486 bool OutputColoredStringTruncated(int right_pad, StringRef string,
487 size_t skip_first_count,
488 bool use_blue_background) {
489 attr_t saved_attr;
490 short saved_pair;
491 bool result = false;
492 wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
493 if (use_blue_background)
494 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
495 while (!string.empty()) {
496 size_t esc_pos = string.find('\x1b');
497 if (esc_pos == StringRef::npos) {
498 string = string.substr(skip_first_count);
499 if (!string.empty()) {
500 PutCStringTruncated(right_pad, string.data(), string.size());
501 result = true;
502 }
503 break;
504 }
505 if (esc_pos > 0) {
506 if (skip_first_count > 0) {
507 int skip = std::min(esc_pos, skip_first_count);
508 string = string.substr(skip);
509 skip_first_count -= skip;
510 esc_pos -= skip;
511 }
512 if (esc_pos > 0) {
513 PutCStringTruncated(right_pad, string.data(), esc_pos);
514 result = true;
515 string = string.drop_front(esc_pos);
516 }
517 }
518 bool consumed = string.consume_front("\x1b");
519 assert(consumed);
520 UNUSED_IF_ASSERT_DISABLED(consumed);
521 // This is written to match our Highlighter classes, which seem to
522 // generate only foreground color escape sequences. If necessary, this
523 // will need to be extended.
524 if (!string.consume_front("[")) {
525 llvm::errs() << "Missing '[' in color escape sequence.\n";
526 continue;
527 }
528 // Only 8 basic foreground colors and reset, our Highlighter doesn't use
529 // anything else.
530 int value;
531 if (!!string.consumeInteger(10, value) || // Returns false on success.
532 !(value == 0 || (value >= 30 && value <= 37))) {
533 llvm::errs() << "No valid color code in color escape sequence.\n";
534 continue;
535 }
536 if (!string.consume_front("m")) {
537 llvm::errs() << "Missing 'm' in color escape sequence.\n";
538 continue;
539 }
540 if (value == 0) { // Reset.
541 wattr_set(m_window, saved_attr, saved_pair, nullptr);
542 if (use_blue_background)
543 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
544 } else {
545 // Mapped directly to first 16 color pairs (black/blue background).
546 ::wattron(m_window,
547 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
548 }
549 }
550 wattr_set(m_window, saved_attr, saved_pair, nullptr);
551 return result;
552 }
553
Touch()554 void Touch() {
555 ::touchwin(m_window);
556 if (m_parent)
557 m_parent->Touch();
558 }
559
CreateSubWindow(const char * name,const Rect & bounds,bool make_active)560 WindowSP CreateSubWindow(const char *name, const Rect &bounds,
561 bool make_active) {
562 auto get_window = [this, &bounds]() {
563 return m_window
564 ? ::subwin(m_window, bounds.size.height, bounds.size.width,
565 bounds.origin.y, bounds.origin.x)
566 : ::newwin(bounds.size.height, bounds.size.width,
567 bounds.origin.y, bounds.origin.x);
568 };
569 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
570 subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
571 subwindow_sp->m_parent = this;
572 if (make_active) {
573 m_prev_active_window_idx = m_curr_active_window_idx;
574 m_curr_active_window_idx = m_subwindows.size();
575 }
576 m_subwindows.push_back(subwindow_sp);
577 ::top_panel(subwindow_sp->m_panel);
578 m_needs_update = true;
579 return subwindow_sp;
580 }
581
RemoveSubWindow(Window * window)582 bool RemoveSubWindow(Window *window) {
583 Windows::iterator pos, end = m_subwindows.end();
584 size_t i = 0;
585 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
586 if ((*pos).get() == window) {
587 if (m_prev_active_window_idx == i)
588 m_prev_active_window_idx = UINT32_MAX;
589 else if (m_prev_active_window_idx != UINT32_MAX &&
590 m_prev_active_window_idx > i)
591 --m_prev_active_window_idx;
592
593 if (m_curr_active_window_idx == i)
594 m_curr_active_window_idx = UINT32_MAX;
595 else if (m_curr_active_window_idx != UINT32_MAX &&
596 m_curr_active_window_idx > i)
597 --m_curr_active_window_idx;
598 window->Erase();
599 m_subwindows.erase(pos);
600 m_needs_update = true;
601 if (m_parent)
602 m_parent->Touch();
603 else
604 ::touchwin(stdscr);
605 return true;
606 }
607 }
608 return false;
609 }
610
FindSubWindow(const char * name)611 WindowSP FindSubWindow(const char *name) {
612 Windows::iterator pos, end = m_subwindows.end();
613 size_t i = 0;
614 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
615 if ((*pos)->m_name == name)
616 return *pos;
617 }
618 return WindowSP();
619 }
620
RemoveSubWindows()621 void RemoveSubWindows() {
622 m_curr_active_window_idx = UINT32_MAX;
623 m_prev_active_window_idx = UINT32_MAX;
624 for (Windows::iterator pos = m_subwindows.begin();
625 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
626 (*pos)->Erase();
627 }
628 if (m_parent)
629 m_parent->Touch();
630 else
631 ::touchwin(stdscr);
632 }
633
get()634 WINDOW *get() { return m_window; }
635
operator WINDOW*()636 operator WINDOW *() { return m_window; }
637
638 // Window drawing utilities
DrawTitleBox(const char * title,const char * bottom_message=nullptr)639 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
640 attr_t attr = 0;
641 if (IsActive())
642 attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
643 else
644 attr = 0;
645 if (attr)
646 AttributeOn(attr);
647
648 Box();
649 MoveCursor(3, 0);
650
651 if (title && title[0]) {
652 PutChar('<');
653 PutCString(title);
654 PutChar('>');
655 }
656
657 if (bottom_message && bottom_message[0]) {
658 int bottom_message_length = strlen(bottom_message);
659 int x = GetWidth() - 3 - (bottom_message_length + 2);
660
661 if (x > 0) {
662 MoveCursor(x, GetHeight() - 1);
663 PutChar('[');
664 PutCString(bottom_message);
665 PutChar(']');
666 } else {
667 MoveCursor(1, GetHeight() - 1);
668 PutChar('[');
669 PutCStringTruncated(1, bottom_message);
670 }
671 }
672 if (attr)
673 AttributeOff(attr);
674 }
675
Draw(bool force)676 virtual void Draw(bool force) {
677 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
678 return;
679
680 for (auto &subwindow_sp : m_subwindows)
681 subwindow_sp->Draw(force);
682 }
683
CreateHelpSubwindow()684 bool CreateHelpSubwindow() {
685 if (m_delegate_sp) {
686 const char *text = m_delegate_sp->WindowDelegateGetHelpText();
687 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
688 if ((text && text[0]) || key_help) {
689 std::unique_ptr<HelpDialogDelegate> help_delegate_up(
690 new HelpDialogDelegate(text, key_help));
691 const size_t num_lines = help_delegate_up->GetNumLines();
692 const size_t max_length = help_delegate_up->GetMaxLineLength();
693 Rect bounds = GetBounds();
694 bounds.Inset(1, 1);
695 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
696 bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
697 bounds.size.width = max_length + 4;
698 } else {
699 if (bounds.size.width > 100) {
700 const int inset_w = bounds.size.width / 4;
701 bounds.origin.x += inset_w;
702 bounds.size.width -= 2 * inset_w;
703 }
704 }
705
706 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
707 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
708 bounds.size.height = num_lines + 2;
709 } else {
710 if (bounds.size.height > 100) {
711 const int inset_h = bounds.size.height / 4;
712 bounds.origin.y += inset_h;
713 bounds.size.height -= 2 * inset_h;
714 }
715 }
716 WindowSP help_window_sp;
717 Window *parent_window = GetParent();
718 if (parent_window)
719 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
720 else
721 help_window_sp = CreateSubWindow("Help", bounds, true);
722 help_window_sp->SetDelegate(
723 WindowDelegateSP(help_delegate_up.release()));
724 return true;
725 }
726 }
727 return false;
728 }
729
HandleChar(int key)730 virtual HandleCharResult HandleChar(int key) {
731 // Always check the active window first
732 HandleCharResult result = eKeyNotHandled;
733 WindowSP active_window_sp = GetActiveWindow();
734 if (active_window_sp) {
735 result = active_window_sp->HandleChar(key);
736 if (result != eKeyNotHandled)
737 return result;
738 }
739
740 if (m_delegate_sp) {
741 result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
742 if (result != eKeyNotHandled)
743 return result;
744 }
745
746 // Then check for any windows that want any keys that weren't handled. This
747 // is typically only for a menubar. Make a copy of the subwindows in case
748 // any HandleChar() functions muck with the subwindows. If we don't do
749 // this, we can crash when iterating over the subwindows.
750 Windows subwindows(m_subwindows);
751 for (auto subwindow_sp : subwindows) {
752 if (!subwindow_sp->m_can_activate) {
753 HandleCharResult result = subwindow_sp->HandleChar(key);
754 if (result != eKeyNotHandled)
755 return result;
756 }
757 }
758
759 return eKeyNotHandled;
760 }
761
GetActiveWindow()762 WindowSP GetActiveWindow() {
763 if (!m_subwindows.empty()) {
764 if (m_curr_active_window_idx >= m_subwindows.size()) {
765 if (m_prev_active_window_idx < m_subwindows.size()) {
766 m_curr_active_window_idx = m_prev_active_window_idx;
767 m_prev_active_window_idx = UINT32_MAX;
768 } else if (IsActive()) {
769 m_prev_active_window_idx = UINT32_MAX;
770 m_curr_active_window_idx = UINT32_MAX;
771
772 // Find first window that wants to be active if this window is active
773 const size_t num_subwindows = m_subwindows.size();
774 for (size_t i = 0; i < num_subwindows; ++i) {
775 if (m_subwindows[i]->GetCanBeActive()) {
776 m_curr_active_window_idx = i;
777 break;
778 }
779 }
780 }
781 }
782
783 if (m_curr_active_window_idx < m_subwindows.size())
784 return m_subwindows[m_curr_active_window_idx];
785 }
786 return WindowSP();
787 }
788
GetCanBeActive() const789 bool GetCanBeActive() const { return m_can_activate; }
790
SetCanBeActive(bool b)791 void SetCanBeActive(bool b) { m_can_activate = b; }
792
SetDelegate(const WindowDelegateSP & delegate_sp)793 void SetDelegate(const WindowDelegateSP &delegate_sp) {
794 m_delegate_sp = delegate_sp;
795 }
796
GetParent() const797 Window *GetParent() const { return m_parent; }
798
IsActive() const799 bool IsActive() const {
800 if (m_parent)
801 return m_parent->GetActiveWindow().get() == this;
802 else
803 return true; // Top level window is always active
804 }
805
SelectNextWindowAsActive()806 void SelectNextWindowAsActive() {
807 // Move active focus to next window
808 const int num_subwindows = m_subwindows.size();
809 int start_idx = 0;
810 if (m_curr_active_window_idx != UINT32_MAX) {
811 m_prev_active_window_idx = m_curr_active_window_idx;
812 start_idx = m_curr_active_window_idx + 1;
813 }
814 for (int idx = start_idx; idx < num_subwindows; ++idx) {
815 if (m_subwindows[idx]->GetCanBeActive()) {
816 m_curr_active_window_idx = idx;
817 return;
818 }
819 }
820 for (int idx = 0; idx < start_idx; ++idx) {
821 if (m_subwindows[idx]->GetCanBeActive()) {
822 m_curr_active_window_idx = idx;
823 break;
824 }
825 }
826 }
827
SelectPreviousWindowAsActive()828 void SelectPreviousWindowAsActive() {
829 // Move active focus to previous window
830 const int num_subwindows = m_subwindows.size();
831 int start_idx = num_subwindows - 1;
832 if (m_curr_active_window_idx != UINT32_MAX) {
833 m_prev_active_window_idx = m_curr_active_window_idx;
834 start_idx = m_curr_active_window_idx - 1;
835 }
836 for (int idx = start_idx; idx >= 0; --idx) {
837 if (m_subwindows[idx]->GetCanBeActive()) {
838 m_curr_active_window_idx = idx;
839 return;
840 }
841 }
842 for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
843 if (m_subwindows[idx]->GetCanBeActive()) {
844 m_curr_active_window_idx = idx;
845 break;
846 }
847 }
848 }
849
GetName() const850 const char *GetName() const { return m_name.c_str(); }
851
852 protected:
853 std::string m_name;
854 WINDOW *m_window;
855 PANEL *m_panel;
856 Window *m_parent;
857 Windows m_subwindows;
858 WindowDelegateSP m_delegate_sp;
859 uint32_t m_curr_active_window_idx;
860 uint32_t m_prev_active_window_idx;
861 bool m_delete;
862 bool m_needs_update;
863 bool m_can_activate;
864 bool m_is_subwin;
865
866 private:
867 Window(const Window &) = delete;
868 const Window &operator=(const Window &) = delete;
869 };
870
871 class MenuDelegate {
872 public:
873 virtual ~MenuDelegate() = default;
874
875 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
876 };
877
878 class Menu : public WindowDelegate {
879 public:
880 enum class Type { Invalid, Bar, Item, Separator };
881
882 // Menubar or separator constructor
883 Menu(Type type);
884
885 // Menuitem constructor
886 Menu(const char *name, const char *key_name, int key_value,
887 uint64_t identifier);
888
889 ~Menu() override = default;
890
GetDelegate() const891 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
892
SetDelegate(const MenuDelegateSP & delegate_sp)893 void SetDelegate(const MenuDelegateSP &delegate_sp) {
894 m_delegate_sp = delegate_sp;
895 }
896
897 void RecalculateNameLengths();
898
899 void AddSubmenu(const MenuSP &menu_sp);
900
901 int DrawAndRunMenu(Window &window);
902
903 void DrawMenuTitle(Window &window, bool highlight);
904
905 bool WindowDelegateDraw(Window &window, bool force) override;
906
907 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
908
ActionPrivate(Menu & menu)909 MenuActionResult ActionPrivate(Menu &menu) {
910 MenuActionResult result = MenuActionResult::NotHandled;
911 if (m_delegate_sp) {
912 result = m_delegate_sp->MenuDelegateAction(menu);
913 if (result != MenuActionResult::NotHandled)
914 return result;
915 } else if (m_parent) {
916 result = m_parent->ActionPrivate(menu);
917 if (result != MenuActionResult::NotHandled)
918 return result;
919 }
920 return m_canned_result;
921 }
922
Action()923 MenuActionResult Action() {
924 // Call the recursive action so it can try to handle it with the menu
925 // delegate, and if not, try our parent menu
926 return ActionPrivate(*this);
927 }
928
SetCannedResult(MenuActionResult result)929 void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
930
GetSubmenus()931 Menus &GetSubmenus() { return m_submenus; }
932
GetSubmenus() const933 const Menus &GetSubmenus() const { return m_submenus; }
934
GetSelectedSubmenuIndex() const935 int GetSelectedSubmenuIndex() const { return m_selected; }
936
SetSelectedSubmenuIndex(int idx)937 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
938
GetType() const939 Type GetType() const { return m_type; }
940
GetStartingColumn() const941 int GetStartingColumn() const { return m_start_col; }
942
SetStartingColumn(int col)943 void SetStartingColumn(int col) { m_start_col = col; }
944
GetKeyValue() const945 int GetKeyValue() const { return m_key_value; }
946
GetName()947 std::string &GetName() { return m_name; }
948
GetDrawWidth() const949 int GetDrawWidth() const {
950 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
951 }
952
GetIdentifier() const953 uint64_t GetIdentifier() const { return m_identifier; }
954
SetIdentifier(uint64_t identifier)955 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
956
957 protected:
958 std::string m_name;
959 std::string m_key_name;
960 uint64_t m_identifier;
961 Type m_type;
962 int m_key_value;
963 int m_start_col;
964 int m_max_submenu_name_length;
965 int m_max_submenu_key_name_length;
966 int m_selected;
967 Menu *m_parent;
968 Menus m_submenus;
969 WindowSP m_menu_window_sp;
970 MenuActionResult m_canned_result;
971 MenuDelegateSP m_delegate_sp;
972 };
973
974 // Menubar or separator constructor
Menu(Type type)975 Menu::Menu(Type type)
976 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
977 m_start_col(0), m_max_submenu_name_length(0),
978 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
979 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
980 m_delegate_sp() {}
981
982 // Menuitem constructor
Menu(const char * name,const char * key_name,int key_value,uint64_t identifier)983 Menu::Menu(const char *name, const char *key_name, int key_value,
984 uint64_t identifier)
985 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
986 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
987 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
988 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
989 m_delegate_sp() {
990 if (name && name[0]) {
991 m_name = name;
992 m_type = Type::Item;
993 if (key_name && key_name[0])
994 m_key_name = key_name;
995 } else {
996 m_type = Type::Separator;
997 }
998 }
999
RecalculateNameLengths()1000 void Menu::RecalculateNameLengths() {
1001 m_max_submenu_name_length = 0;
1002 m_max_submenu_key_name_length = 0;
1003 Menus &submenus = GetSubmenus();
1004 const size_t num_submenus = submenus.size();
1005 for (size_t i = 0; i < num_submenus; ++i) {
1006 Menu *submenu = submenus[i].get();
1007 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
1008 m_max_submenu_name_length = submenu->m_name.size();
1009 if (static_cast<size_t>(m_max_submenu_key_name_length) <
1010 submenu->m_key_name.size())
1011 m_max_submenu_key_name_length = submenu->m_key_name.size();
1012 }
1013 }
1014
AddSubmenu(const MenuSP & menu_sp)1015 void Menu::AddSubmenu(const MenuSP &menu_sp) {
1016 menu_sp->m_parent = this;
1017 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
1018 m_max_submenu_name_length = menu_sp->m_name.size();
1019 if (static_cast<size_t>(m_max_submenu_key_name_length) <
1020 menu_sp->m_key_name.size())
1021 m_max_submenu_key_name_length = menu_sp->m_key_name.size();
1022 m_submenus.push_back(menu_sp);
1023 }
1024
DrawMenuTitle(Window & window,bool highlight)1025 void Menu::DrawMenuTitle(Window &window, bool highlight) {
1026 if (m_type == Type::Separator) {
1027 window.MoveCursor(0, window.GetCursorY());
1028 window.PutChar(ACS_LTEE);
1029 int width = window.GetWidth();
1030 if (width > 2) {
1031 width -= 2;
1032 for (int i = 0; i < width; ++i)
1033 window.PutChar(ACS_HLINE);
1034 }
1035 window.PutChar(ACS_RTEE);
1036 } else {
1037 const int shortcut_key = m_key_value;
1038 bool underlined_shortcut = false;
1039 const attr_t highlight_attr = A_REVERSE;
1040 if (highlight)
1041 window.AttributeOn(highlight_attr);
1042 if (llvm::isPrint(shortcut_key)) {
1043 size_t lower_pos = m_name.find(tolower(shortcut_key));
1044 size_t upper_pos = m_name.find(toupper(shortcut_key));
1045 const char *name = m_name.c_str();
1046 size_t pos = std::min<size_t>(lower_pos, upper_pos);
1047 if (pos != std::string::npos) {
1048 underlined_shortcut = true;
1049 if (pos > 0) {
1050 window.PutCString(name, pos);
1051 name += pos;
1052 }
1053 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
1054 window.AttributeOn(shortcut_attr);
1055 window.PutChar(name[0]);
1056 window.AttributeOff(shortcut_attr);
1057 name++;
1058 if (name[0])
1059 window.PutCString(name);
1060 }
1061 }
1062
1063 if (!underlined_shortcut) {
1064 window.PutCString(m_name.c_str());
1065 }
1066
1067 if (highlight)
1068 window.AttributeOff(highlight_attr);
1069
1070 if (m_key_name.empty()) {
1071 if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
1072 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
1073 window.Printf(" (%c)", m_key_value);
1074 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
1075 }
1076 } else {
1077 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
1078 window.Printf(" (%s)", m_key_name.c_str());
1079 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
1080 }
1081 }
1082 }
1083
WindowDelegateDraw(Window & window,bool force)1084 bool Menu::WindowDelegateDraw(Window &window, bool force) {
1085 Menus &submenus = GetSubmenus();
1086 const size_t num_submenus = submenus.size();
1087 const int selected_idx = GetSelectedSubmenuIndex();
1088 Menu::Type menu_type = GetType();
1089 switch (menu_type) {
1090 case Menu::Type::Bar: {
1091 window.SetBackground(BlackOnWhite);
1092 window.MoveCursor(0, 0);
1093 for (size_t i = 0; i < num_submenus; ++i) {
1094 Menu *menu = submenus[i].get();
1095 if (i > 0)
1096 window.PutChar(' ');
1097 menu->SetStartingColumn(window.GetCursorX());
1098 window.PutCString("| ");
1099 menu->DrawMenuTitle(window, false);
1100 }
1101 window.PutCString(" |");
1102 } break;
1103
1104 case Menu::Type::Item: {
1105 int y = 1;
1106 int x = 3;
1107 // Draw the menu
1108 int cursor_x = 0;
1109 int cursor_y = 0;
1110 window.Erase();
1111 window.SetBackground(BlackOnWhite);
1112 window.Box();
1113 for (size_t i = 0; i < num_submenus; ++i) {
1114 const bool is_selected = (i == static_cast<size_t>(selected_idx));
1115 window.MoveCursor(x, y + i);
1116 if (is_selected) {
1117 // Remember where we want the cursor to be
1118 cursor_x = x - 1;
1119 cursor_y = y + i;
1120 }
1121 submenus[i]->DrawMenuTitle(window, is_selected);
1122 }
1123 window.MoveCursor(cursor_x, cursor_y);
1124 } break;
1125
1126 default:
1127 case Menu::Type::Separator:
1128 break;
1129 }
1130 return true; // Drawing handled...
1131 }
1132
WindowDelegateHandleChar(Window & window,int key)1133 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
1134 HandleCharResult result = eKeyNotHandled;
1135
1136 Menus &submenus = GetSubmenus();
1137 const size_t num_submenus = submenus.size();
1138 const int selected_idx = GetSelectedSubmenuIndex();
1139 Menu::Type menu_type = GetType();
1140 if (menu_type == Menu::Type::Bar) {
1141 MenuSP run_menu_sp;
1142 switch (key) {
1143 case KEY_DOWN:
1144 case KEY_UP:
1145 // Show last menu or first menu
1146 if (selected_idx < static_cast<int>(num_submenus))
1147 run_menu_sp = submenus[selected_idx];
1148 else if (!submenus.empty())
1149 run_menu_sp = submenus.front();
1150 result = eKeyHandled;
1151 break;
1152
1153 case KEY_RIGHT:
1154 ++m_selected;
1155 if (m_selected >= static_cast<int>(num_submenus))
1156 m_selected = 0;
1157 if (m_selected < static_cast<int>(num_submenus))
1158 run_menu_sp = submenus[m_selected];
1159 else if (!submenus.empty())
1160 run_menu_sp = submenus.front();
1161 result = eKeyHandled;
1162 break;
1163
1164 case KEY_LEFT:
1165 --m_selected;
1166 if (m_selected < 0)
1167 m_selected = num_submenus - 1;
1168 if (m_selected < static_cast<int>(num_submenus))
1169 run_menu_sp = submenus[m_selected];
1170 else if (!submenus.empty())
1171 run_menu_sp = submenus.front();
1172 result = eKeyHandled;
1173 break;
1174
1175 default:
1176 for (size_t i = 0; i < num_submenus; ++i) {
1177 if (submenus[i]->GetKeyValue() == key) {
1178 SetSelectedSubmenuIndex(i);
1179 run_menu_sp = submenus[i];
1180 result = eKeyHandled;
1181 break;
1182 }
1183 }
1184 break;
1185 }
1186
1187 if (run_menu_sp) {
1188 // Run the action on this menu in case we need to populate the menu with
1189 // dynamic content and also in case check marks, and any other menu
1190 // decorations need to be calculated
1191 if (run_menu_sp->Action() == MenuActionResult::Quit)
1192 return eQuitApplication;
1193
1194 Rect menu_bounds;
1195 menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
1196 menu_bounds.origin.y = 1;
1197 menu_bounds.size.width = run_menu_sp->GetDrawWidth();
1198 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
1199 if (m_menu_window_sp)
1200 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
1201
1202 m_menu_window_sp = window.GetParent()->CreateSubWindow(
1203 run_menu_sp->GetName().c_str(), menu_bounds, true);
1204 m_menu_window_sp->SetDelegate(run_menu_sp);
1205 }
1206 } else if (menu_type == Menu::Type::Item) {
1207 switch (key) {
1208 case KEY_DOWN:
1209 if (m_submenus.size() > 1) {
1210 const int start_select = m_selected;
1211 while (++m_selected != start_select) {
1212 if (static_cast<size_t>(m_selected) >= num_submenus)
1213 m_selected = 0;
1214 if (m_submenus[m_selected]->GetType() == Type::Separator)
1215 continue;
1216 else
1217 break;
1218 }
1219 return eKeyHandled;
1220 }
1221 break;
1222
1223 case KEY_UP:
1224 if (m_submenus.size() > 1) {
1225 const int start_select = m_selected;
1226 while (--m_selected != start_select) {
1227 if (m_selected < static_cast<int>(0))
1228 m_selected = num_submenus - 1;
1229 if (m_submenus[m_selected]->GetType() == Type::Separator)
1230 continue;
1231 else
1232 break;
1233 }
1234 return eKeyHandled;
1235 }
1236 break;
1237
1238 case KEY_RETURN:
1239 if (static_cast<size_t>(selected_idx) < num_submenus) {
1240 if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
1241 return eQuitApplication;
1242 window.GetParent()->RemoveSubWindow(&window);
1243 return eKeyHandled;
1244 }
1245 break;
1246
1247 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
1248 // case other chars are entered for escaped sequences
1249 window.GetParent()->RemoveSubWindow(&window);
1250 return eKeyHandled;
1251
1252 default:
1253 for (size_t i = 0; i < num_submenus; ++i) {
1254 Menu *menu = submenus[i].get();
1255 if (menu->GetKeyValue() == key) {
1256 SetSelectedSubmenuIndex(i);
1257 window.GetParent()->RemoveSubWindow(&window);
1258 if (menu->Action() == MenuActionResult::Quit)
1259 return eQuitApplication;
1260 return eKeyHandled;
1261 }
1262 }
1263 break;
1264 }
1265 } else if (menu_type == Menu::Type::Separator) {
1266 }
1267 return result;
1268 }
1269
1270 class Application {
1271 public:
Application(FILE * in,FILE * out)1272 Application(FILE *in, FILE *out)
1273 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
1274
~Application()1275 ~Application() {
1276 m_window_delegates.clear();
1277 m_window_sp.reset();
1278 if (m_screen) {
1279 ::delscreen(m_screen);
1280 m_screen = nullptr;
1281 }
1282 }
1283
Initialize()1284 void Initialize() {
1285 ::setlocale(LC_ALL, "");
1286 ::setlocale(LC_CTYPE, "");
1287 m_screen = ::newterm(nullptr, m_out, m_in);
1288 ::start_color();
1289 ::curs_set(0);
1290 ::noecho();
1291 ::keypad(stdscr, TRUE);
1292 }
1293
Terminate()1294 void Terminate() { ::endwin(); }
1295
Run(Debugger & debugger)1296 void Run(Debugger &debugger) {
1297 bool done = false;
1298 int delay_in_tenths_of_a_second = 1;
1299
1300 // Alas the threading model in curses is a bit lame so we need to resort to
1301 // polling every 0.5 seconds. We could poll for stdin ourselves and then
1302 // pass the keys down but then we need to translate all of the escape
1303 // sequences ourselves. So we resort to polling for input because we need
1304 // to receive async process events while in this loop.
1305
1306 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
1307 // of seconds seconds when calling
1308 // Window::GetChar()
1309
1310 ListenerSP listener_sp(
1311 Listener::MakeListener("lldb.IOHandler.curses.Application"));
1312 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
1313 debugger.EnableForwardEvents(listener_sp);
1314
1315 m_update_screen = true;
1316 #if defined(__APPLE__)
1317 std::deque<int> escape_chars;
1318 #endif
1319
1320 while (!done) {
1321 if (m_update_screen) {
1322 m_window_sp->Draw(false);
1323 // All windows should be calling Window::DeferredRefresh() instead of
1324 // Window::Refresh() so we can do a single update and avoid any screen
1325 // blinking
1326 update_panels();
1327
1328 // Cursor hiding isn't working on MacOSX, so hide it in the top left
1329 // corner
1330 m_window_sp->MoveCursor(0, 0);
1331
1332 doupdate();
1333 m_update_screen = false;
1334 }
1335
1336 #if defined(__APPLE__)
1337 // Terminal.app doesn't map its function keys correctly, F1-F4 default
1338 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
1339 // possible
1340 int ch;
1341 if (escape_chars.empty())
1342 ch = m_window_sp->GetChar();
1343 else {
1344 ch = escape_chars.front();
1345 escape_chars.pop_front();
1346 }
1347 if (ch == KEY_ESCAPE) {
1348 int ch2 = m_window_sp->GetChar();
1349 if (ch2 == 'O') {
1350 int ch3 = m_window_sp->GetChar();
1351 switch (ch3) {
1352 case 'P':
1353 ch = KEY_F(1);
1354 break;
1355 case 'Q':
1356 ch = KEY_F(2);
1357 break;
1358 case 'R':
1359 ch = KEY_F(3);
1360 break;
1361 case 'S':
1362 ch = KEY_F(4);
1363 break;
1364 default:
1365 escape_chars.push_back(ch2);
1366 if (ch3 != -1)
1367 escape_chars.push_back(ch3);
1368 break;
1369 }
1370 } else if (ch2 != -1)
1371 escape_chars.push_back(ch2);
1372 }
1373 #else
1374 int ch = m_window_sp->GetChar();
1375
1376 #endif
1377 if (ch == -1) {
1378 if (feof(m_in) || ferror(m_in)) {
1379 done = true;
1380 } else {
1381 // Just a timeout from using halfdelay(), check for events
1382 EventSP event_sp;
1383 while (listener_sp->PeekAtNextEvent()) {
1384 listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
1385
1386 if (event_sp) {
1387 Broadcaster *broadcaster = event_sp->GetBroadcaster();
1388 if (broadcaster) {
1389 // uint32_t event_type = event_sp->GetType();
1390 ConstString broadcaster_class(
1391 broadcaster->GetBroadcasterClass());
1392 if (broadcaster_class == broadcaster_class_process) {
1393 debugger.GetCommandInterpreter().UpdateExecutionContext(
1394 nullptr);
1395 m_update_screen = true;
1396 continue; // Don't get any key, just update our view
1397 }
1398 }
1399 }
1400 }
1401 }
1402 } else {
1403 HandleCharResult key_result = m_window_sp->HandleChar(ch);
1404 switch (key_result) {
1405 case eKeyHandled:
1406 debugger.GetCommandInterpreter().UpdateExecutionContext(nullptr);
1407 m_update_screen = true;
1408 break;
1409 case eKeyNotHandled:
1410 if (ch == 12) { // Ctrl+L, force full redraw
1411 redrawwin(m_window_sp->get());
1412 m_update_screen = true;
1413 }
1414 break;
1415 case eQuitApplication:
1416 done = true;
1417 break;
1418 }
1419 }
1420 }
1421
1422 debugger.CancelForwardEvents(listener_sp);
1423 }
1424
GetMainWindow()1425 WindowSP &GetMainWindow() {
1426 if (!m_window_sp)
1427 m_window_sp = std::make_shared<Window>("main", stdscr, false);
1428 return m_window_sp;
1429 }
1430
TerminalSizeChanged()1431 void TerminalSizeChanged() {
1432 ::endwin();
1433 ::refresh();
1434 Rect content_bounds = m_window_sp->GetFrame();
1435 m_window_sp->SetBounds(content_bounds);
1436 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
1437 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
1438 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
1439 status_window_sp->SetBounds(content_bounds.MakeStatusBar());
1440
1441 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
1442 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
1443 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
1444 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
1445
1446 Rect threads_bounds;
1447 Rect source_variables_bounds;
1448 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
1449 threads_bounds);
1450 if (threads_window_sp)
1451 threads_window_sp->SetBounds(threads_bounds);
1452 else
1453 source_variables_bounds = content_bounds;
1454
1455 Rect source_bounds;
1456 Rect variables_registers_bounds;
1457 source_variables_bounds.HorizontalSplitPercentage(
1458 0.70, source_bounds, variables_registers_bounds);
1459 if (variables_window_sp || registers_window_sp) {
1460 if (variables_window_sp && registers_window_sp) {
1461 Rect variables_bounds;
1462 Rect registers_bounds;
1463 variables_registers_bounds.VerticalSplitPercentage(
1464 0.50, variables_bounds, registers_bounds);
1465 variables_window_sp->SetBounds(variables_bounds);
1466 registers_window_sp->SetBounds(registers_bounds);
1467 } else if (variables_window_sp) {
1468 variables_window_sp->SetBounds(variables_registers_bounds);
1469 } else {
1470 registers_window_sp->SetBounds(variables_registers_bounds);
1471 }
1472 } else {
1473 source_bounds = source_variables_bounds;
1474 }
1475
1476 source_window_sp->SetBounds(source_bounds);
1477
1478 touchwin(stdscr);
1479 redrawwin(m_window_sp->get());
1480 m_update_screen = true;
1481 }
1482
1483 protected:
1484 WindowSP m_window_sp;
1485 WindowDelegates m_window_delegates;
1486 SCREEN *m_screen;
1487 FILE *m_in;
1488 FILE *m_out;
1489 bool m_update_screen = false;
1490 };
1491
1492 } // namespace curses
1493
1494 using namespace curses;
1495
1496 struct Row {
1497 ValueObjectManager value;
1498 Row *parent;
1499 // The process stop ID when the children were calculated.
1500 uint32_t children_stop_id = 0;
1501 int row_idx = 0;
1502 int x = 1;
1503 int y = 1;
1504 bool might_have_children;
1505 bool expanded = false;
1506 bool calculated_children = false;
1507 std::vector<Row> children;
1508
RowRow1509 Row(const ValueObjectSP &v, Row *p)
1510 : value(v, lldb::eDynamicDontRunTarget, true), parent(p),
1511 might_have_children(v ? v->MightHaveChildren() : false) {}
1512
GetDepthRow1513 size_t GetDepth() const {
1514 if (parent)
1515 return 1 + parent->GetDepth();
1516 return 0;
1517 }
1518
ExpandRow1519 void Expand() { expanded = true; }
1520
GetChildrenRow1521 std::vector<Row> &GetChildren() {
1522 ProcessSP process_sp = value.GetProcessSP();
1523 auto stop_id = process_sp->GetStopID();
1524 if (process_sp && stop_id != children_stop_id) {
1525 children_stop_id = stop_id;
1526 calculated_children = false;
1527 }
1528 if (!calculated_children) {
1529 children.clear();
1530 calculated_children = true;
1531 ValueObjectSP valobj = value.GetSP();
1532 if (valobj) {
1533 const size_t num_children = valobj->GetNumChildren();
1534 for (size_t i = 0; i < num_children; ++i) {
1535 children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
1536 }
1537 }
1538 }
1539 return children;
1540 }
1541
UnexpandRow1542 void Unexpand() {
1543 expanded = false;
1544 calculated_children = false;
1545 children.clear();
1546 }
1547
DrawTreeRow1548 void DrawTree(Window &window) {
1549 if (parent)
1550 parent->DrawTreeForChild(window, this, 0);
1551
1552 if (might_have_children) {
1553 // It we can get UTF8 characters to work we should try to use the
1554 // "symbol" UTF8 string below
1555 // const char *symbol = "";
1556 // if (row.expanded)
1557 // symbol = "\xe2\x96\xbd ";
1558 // else
1559 // symbol = "\xe2\x96\xb7 ";
1560 // window.PutCString (symbol);
1561
1562 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
1563 // or '>' character...
1564 // if (expanded)
1565 // window.PutChar (ACS_DARROW);
1566 // else
1567 // window.PutChar (ACS_RARROW);
1568 // Since we can't find any good looking right arrow/down arrow symbols,
1569 // just use a diamond...
1570 window.PutChar(ACS_DIAMOND);
1571 window.PutChar(ACS_HLINE);
1572 }
1573 }
1574
DrawTreeForChildRow1575 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
1576 if (parent)
1577 parent->DrawTreeForChild(window, this, reverse_depth + 1);
1578
1579 if (&GetChildren().back() == child) {
1580 // Last child
1581 if (reverse_depth == 0) {
1582 window.PutChar(ACS_LLCORNER);
1583 window.PutChar(ACS_HLINE);
1584 } else {
1585 window.PutChar(' ');
1586 window.PutChar(' ');
1587 }
1588 } else {
1589 if (reverse_depth == 0) {
1590 window.PutChar(ACS_LTEE);
1591 window.PutChar(ACS_HLINE);
1592 } else {
1593 window.PutChar(ACS_VLINE);
1594 window.PutChar(' ');
1595 }
1596 }
1597 }
1598 };
1599
1600 struct DisplayOptions {
1601 bool show_types;
1602 };
1603
1604 class TreeItem;
1605
1606 class TreeDelegate {
1607 public:
1608 TreeDelegate() = default;
1609 virtual ~TreeDelegate() = default;
1610
1611 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
1612 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
1613 virtual bool TreeDelegateItemSelected(
1614 TreeItem &item) = 0; // Return true if we need to update views
1615 };
1616
1617 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
1618
1619 class TreeItem {
1620 public:
TreeItem(TreeItem * parent,TreeDelegate & delegate,bool might_have_children)1621 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
1622 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
1623 m_identifier(0), m_row_idx(-1), m_children(),
1624 m_might_have_children(might_have_children), m_is_expanded(false) {}
1625
operator =(const TreeItem & rhs)1626 TreeItem &operator=(const TreeItem &rhs) {
1627 if (this != &rhs) {
1628 m_parent = rhs.m_parent;
1629 m_delegate = rhs.m_delegate;
1630 m_user_data = rhs.m_user_data;
1631 m_identifier = rhs.m_identifier;
1632 m_row_idx = rhs.m_row_idx;
1633 m_children = rhs.m_children;
1634 m_might_have_children = rhs.m_might_have_children;
1635 m_is_expanded = rhs.m_is_expanded;
1636 }
1637 return *this;
1638 }
1639
1640 TreeItem(const TreeItem &) = default;
1641
GetDepth() const1642 size_t GetDepth() const {
1643 if (m_parent)
1644 return 1 + m_parent->GetDepth();
1645 return 0;
1646 }
1647
GetRowIndex() const1648 int GetRowIndex() const { return m_row_idx; }
1649
ClearChildren()1650 void ClearChildren() { m_children.clear(); }
1651
Resize(size_t n,const TreeItem & t)1652 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
1653
operator [](size_t i)1654 TreeItem &operator[](size_t i) { return m_children[i]; }
1655
SetRowIndex(int row_idx)1656 void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
1657
GetNumChildren()1658 size_t GetNumChildren() {
1659 m_delegate.TreeDelegateGenerateChildren(*this);
1660 return m_children.size();
1661 }
1662
ItemWasSelected()1663 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
1664
CalculateRowIndexes(int & row_idx)1665 void CalculateRowIndexes(int &row_idx) {
1666 SetRowIndex(row_idx);
1667 ++row_idx;
1668
1669 const bool expanded = IsExpanded();
1670
1671 // The root item must calculate its children, or we must calculate the
1672 // number of children if the item is expanded
1673 if (m_parent == nullptr || expanded)
1674 GetNumChildren();
1675
1676 for (auto &item : m_children) {
1677 if (expanded)
1678 item.CalculateRowIndexes(row_idx);
1679 else
1680 item.SetRowIndex(-1);
1681 }
1682 }
1683
GetParent()1684 TreeItem *GetParent() { return m_parent; }
1685
IsExpanded() const1686 bool IsExpanded() const { return m_is_expanded; }
1687
Expand()1688 void Expand() { m_is_expanded = true; }
1689
Unexpand()1690 void Unexpand() { m_is_expanded = false; }
1691
Draw(Window & window,const int first_visible_row,const uint32_t selected_row_idx,int & row_idx,int & num_rows_left)1692 bool Draw(Window &window, const int first_visible_row,
1693 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
1694 if (num_rows_left <= 0)
1695 return false;
1696
1697 if (m_row_idx >= first_visible_row) {
1698 window.MoveCursor(2, row_idx + 1);
1699
1700 if (m_parent)
1701 m_parent->DrawTreeForChild(window, this, 0);
1702
1703 if (m_might_have_children) {
1704 // It we can get UTF8 characters to work we should try to use the
1705 // "symbol" UTF8 string below
1706 // const char *symbol = "";
1707 // if (row.expanded)
1708 // symbol = "\xe2\x96\xbd ";
1709 // else
1710 // symbol = "\xe2\x96\xb7 ";
1711 // window.PutCString (symbol);
1712
1713 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
1714 // 'v' or '>' character...
1715 // if (expanded)
1716 // window.PutChar (ACS_DARROW);
1717 // else
1718 // window.PutChar (ACS_RARROW);
1719 // Since we can't find any good looking right arrow/down arrow symbols,
1720 // just use a diamond...
1721 window.PutChar(ACS_DIAMOND);
1722 window.PutChar(ACS_HLINE);
1723 }
1724 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
1725 window.IsActive();
1726
1727 if (highlight)
1728 window.AttributeOn(A_REVERSE);
1729
1730 m_delegate.TreeDelegateDrawTreeItem(*this, window);
1731
1732 if (highlight)
1733 window.AttributeOff(A_REVERSE);
1734 ++row_idx;
1735 --num_rows_left;
1736 }
1737
1738 if (num_rows_left <= 0)
1739 return false; // We are done drawing...
1740
1741 if (IsExpanded()) {
1742 for (auto &item : m_children) {
1743 // If we displayed all the rows and item.Draw() returns false we are
1744 // done drawing and can exit this for loop
1745 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
1746 num_rows_left))
1747 break;
1748 }
1749 }
1750 return num_rows_left >= 0; // Return true if not done drawing yet
1751 }
1752
DrawTreeForChild(Window & window,TreeItem * child,uint32_t reverse_depth)1753 void DrawTreeForChild(Window &window, TreeItem *child,
1754 uint32_t reverse_depth) {
1755 if (m_parent)
1756 m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
1757
1758 if (&m_children.back() == child) {
1759 // Last child
1760 if (reverse_depth == 0) {
1761 window.PutChar(ACS_LLCORNER);
1762 window.PutChar(ACS_HLINE);
1763 } else {
1764 window.PutChar(' ');
1765 window.PutChar(' ');
1766 }
1767 } else {
1768 if (reverse_depth == 0) {
1769 window.PutChar(ACS_LTEE);
1770 window.PutChar(ACS_HLINE);
1771 } else {
1772 window.PutChar(ACS_VLINE);
1773 window.PutChar(' ');
1774 }
1775 }
1776 }
1777
GetItemForRowIndex(uint32_t row_idx)1778 TreeItem *GetItemForRowIndex(uint32_t row_idx) {
1779 if (static_cast<uint32_t>(m_row_idx) == row_idx)
1780 return this;
1781 if (m_children.empty())
1782 return nullptr;
1783 if (IsExpanded()) {
1784 for (auto &item : m_children) {
1785 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
1786 if (selected_item_ptr)
1787 return selected_item_ptr;
1788 }
1789 }
1790 return nullptr;
1791 }
1792
GetUserData() const1793 void *GetUserData() const { return m_user_data; }
1794
SetUserData(void * user_data)1795 void SetUserData(void *user_data) { m_user_data = user_data; }
1796
GetIdentifier() const1797 uint64_t GetIdentifier() const { return m_identifier; }
1798
SetIdentifier(uint64_t identifier)1799 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
1800
SetMightHaveChildren(bool b)1801 void SetMightHaveChildren(bool b) { m_might_have_children = b; }
1802
1803 protected:
1804 TreeItem *m_parent;
1805 TreeDelegate &m_delegate;
1806 void *m_user_data;
1807 uint64_t m_identifier;
1808 int m_row_idx; // Zero based visible row index, -1 if not visible or for the
1809 // root item
1810 std::vector<TreeItem> m_children;
1811 bool m_might_have_children;
1812 bool m_is_expanded;
1813 };
1814
1815 class TreeWindowDelegate : public WindowDelegate {
1816 public:
TreeWindowDelegate(Debugger & debugger,const TreeDelegateSP & delegate_sp)1817 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
1818 : m_debugger(debugger), m_delegate_sp(delegate_sp),
1819 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
1820 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
1821 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
1822
NumVisibleRows() const1823 int NumVisibleRows() const { return m_max_y - m_min_y; }
1824
WindowDelegateDraw(Window & window,bool force)1825 bool WindowDelegateDraw(Window &window, bool force) override {
1826 ExecutionContext exe_ctx(
1827 m_debugger.GetCommandInterpreter().GetExecutionContext());
1828 Process *process = exe_ctx.GetProcessPtr();
1829
1830 bool display_content = false;
1831 if (process) {
1832 StateType state = process->GetState();
1833 if (StateIsStoppedState(state, true)) {
1834 // We are stopped, so it is ok to
1835 display_content = true;
1836 } else if (StateIsRunningState(state)) {
1837 return true; // Don't do any updating when we are running
1838 }
1839 }
1840
1841 m_min_x = 2;
1842 m_min_y = 1;
1843 m_max_x = window.GetWidth() - 1;
1844 m_max_y = window.GetHeight() - 1;
1845
1846 window.Erase();
1847 window.DrawTitleBox(window.GetName());
1848
1849 if (display_content) {
1850 const int num_visible_rows = NumVisibleRows();
1851 m_num_rows = 0;
1852 m_root.CalculateRowIndexes(m_num_rows);
1853
1854 // If we unexpanded while having something selected our total number of
1855 // rows is less than the num visible rows, then make sure we show all the
1856 // rows by setting the first visible row accordingly.
1857 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
1858 m_first_visible_row = 0;
1859
1860 // Make sure the selected row is always visible
1861 if (m_selected_row_idx < m_first_visible_row)
1862 m_first_visible_row = m_selected_row_idx;
1863 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
1864 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
1865
1866 int row_idx = 0;
1867 int num_rows_left = num_visible_rows;
1868 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
1869 num_rows_left);
1870 // Get the selected row
1871 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1872 } else {
1873 m_selected_item = nullptr;
1874 }
1875
1876 return true; // Drawing handled
1877 }
1878
WindowDelegateGetHelpText()1879 const char *WindowDelegateGetHelpText() override {
1880 return "Thread window keyboard shortcuts:";
1881 }
1882
WindowDelegateGetKeyHelp()1883 KeyHelp *WindowDelegateGetKeyHelp() override {
1884 static curses::KeyHelp g_source_view_key_help[] = {
1885 {KEY_UP, "Select previous item"},
1886 {KEY_DOWN, "Select next item"},
1887 {KEY_RIGHT, "Expand the selected item"},
1888 {KEY_LEFT,
1889 "Unexpand the selected item or select parent if not expanded"},
1890 {KEY_PPAGE, "Page up"},
1891 {KEY_NPAGE, "Page down"},
1892 {'h', "Show help dialog"},
1893 {' ', "Toggle item expansion"},
1894 {',', "Page up"},
1895 {'.', "Page down"},
1896 {'\0', nullptr}};
1897 return g_source_view_key_help;
1898 }
1899
WindowDelegateHandleChar(Window & window,int c)1900 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
1901 switch (c) {
1902 case ',':
1903 case KEY_PPAGE:
1904 // Page up key
1905 if (m_first_visible_row > 0) {
1906 if (m_first_visible_row > m_max_y)
1907 m_first_visible_row -= m_max_y;
1908 else
1909 m_first_visible_row = 0;
1910 m_selected_row_idx = m_first_visible_row;
1911 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1912 if (m_selected_item)
1913 m_selected_item->ItemWasSelected();
1914 }
1915 return eKeyHandled;
1916
1917 case '.':
1918 case KEY_NPAGE:
1919 // Page down key
1920 if (m_num_rows > m_max_y) {
1921 if (m_first_visible_row + m_max_y < m_num_rows) {
1922 m_first_visible_row += m_max_y;
1923 m_selected_row_idx = m_first_visible_row;
1924 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1925 if (m_selected_item)
1926 m_selected_item->ItemWasSelected();
1927 }
1928 }
1929 return eKeyHandled;
1930
1931 case KEY_UP:
1932 if (m_selected_row_idx > 0) {
1933 --m_selected_row_idx;
1934 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1935 if (m_selected_item)
1936 m_selected_item->ItemWasSelected();
1937 }
1938 return eKeyHandled;
1939
1940 case KEY_DOWN:
1941 if (m_selected_row_idx + 1 < m_num_rows) {
1942 ++m_selected_row_idx;
1943 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1944 if (m_selected_item)
1945 m_selected_item->ItemWasSelected();
1946 }
1947 return eKeyHandled;
1948
1949 case KEY_RIGHT:
1950 if (m_selected_item) {
1951 if (!m_selected_item->IsExpanded())
1952 m_selected_item->Expand();
1953 }
1954 return eKeyHandled;
1955
1956 case KEY_LEFT:
1957 if (m_selected_item) {
1958 if (m_selected_item->IsExpanded())
1959 m_selected_item->Unexpand();
1960 else if (m_selected_item->GetParent()) {
1961 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
1962 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1963 if (m_selected_item)
1964 m_selected_item->ItemWasSelected();
1965 }
1966 }
1967 return eKeyHandled;
1968
1969 case ' ':
1970 // Toggle expansion state when SPACE is pressed
1971 if (m_selected_item) {
1972 if (m_selected_item->IsExpanded())
1973 m_selected_item->Unexpand();
1974 else
1975 m_selected_item->Expand();
1976 }
1977 return eKeyHandled;
1978
1979 case 'h':
1980 window.CreateHelpSubwindow();
1981 return eKeyHandled;
1982
1983 default:
1984 break;
1985 }
1986 return eKeyNotHandled;
1987 }
1988
1989 protected:
1990 Debugger &m_debugger;
1991 TreeDelegateSP m_delegate_sp;
1992 TreeItem m_root;
1993 TreeItem *m_selected_item;
1994 int m_num_rows;
1995 int m_selected_row_idx;
1996 int m_first_visible_row;
1997 int m_min_x;
1998 int m_min_y;
1999 int m_max_x;
2000 int m_max_y;
2001 };
2002
2003 class FrameTreeDelegate : public TreeDelegate {
2004 public:
FrameTreeDelegate()2005 FrameTreeDelegate() : TreeDelegate() {
2006 FormatEntity::Parse(
2007 "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
2008 m_format);
2009 }
2010
2011 ~FrameTreeDelegate() override = default;
2012
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)2013 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2014 Thread *thread = (Thread *)item.GetUserData();
2015 if (thread) {
2016 const uint64_t frame_idx = item.GetIdentifier();
2017 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
2018 if (frame_sp) {
2019 StreamString strm;
2020 const SymbolContext &sc =
2021 frame_sp->GetSymbolContext(eSymbolContextEverything);
2022 ExecutionContext exe_ctx(frame_sp);
2023 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
2024 nullptr, false, false)) {
2025 int right_pad = 1;
2026 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2027 }
2028 }
2029 }
2030 }
2031
TreeDelegateGenerateChildren(TreeItem & item)2032 void TreeDelegateGenerateChildren(TreeItem &item) override {
2033 // No children for frames yet...
2034 }
2035
TreeDelegateItemSelected(TreeItem & item)2036 bool TreeDelegateItemSelected(TreeItem &item) override {
2037 Thread *thread = (Thread *)item.GetUserData();
2038 if (thread) {
2039 thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
2040 thread->GetID());
2041 const uint64_t frame_idx = item.GetIdentifier();
2042 thread->SetSelectedFrameByIndex(frame_idx);
2043 return true;
2044 }
2045 return false;
2046 }
2047
2048 protected:
2049 FormatEntity::Entry m_format;
2050 };
2051
2052 class ThreadTreeDelegate : public TreeDelegate {
2053 public:
ThreadTreeDelegate(Debugger & debugger)2054 ThreadTreeDelegate(Debugger &debugger)
2055 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
2056 m_stop_id(UINT32_MAX) {
2057 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
2058 "reason = ${thread.stop-reason}}",
2059 m_format);
2060 }
2061
2062 ~ThreadTreeDelegate() override = default;
2063
GetProcess()2064 ProcessSP GetProcess() {
2065 return m_debugger.GetCommandInterpreter()
2066 .GetExecutionContext()
2067 .GetProcessSP();
2068 }
2069
GetThread(const TreeItem & item)2070 ThreadSP GetThread(const TreeItem &item) {
2071 ProcessSP process_sp = GetProcess();
2072 if (process_sp)
2073 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
2074 return ThreadSP();
2075 }
2076
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)2077 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2078 ThreadSP thread_sp = GetThread(item);
2079 if (thread_sp) {
2080 StreamString strm;
2081 ExecutionContext exe_ctx(thread_sp);
2082 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
2083 nullptr, false, false)) {
2084 int right_pad = 1;
2085 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2086 }
2087 }
2088 }
2089
TreeDelegateGenerateChildren(TreeItem & item)2090 void TreeDelegateGenerateChildren(TreeItem &item) override {
2091 ProcessSP process_sp = GetProcess();
2092 if (process_sp && process_sp->IsAlive()) {
2093 StateType state = process_sp->GetState();
2094 if (StateIsStoppedState(state, true)) {
2095 ThreadSP thread_sp = GetThread(item);
2096 if (thread_sp) {
2097 if (m_stop_id == process_sp->GetStopID() &&
2098 thread_sp->GetID() == m_tid)
2099 return; // Children are already up to date
2100 if (!m_frame_delegate_sp) {
2101 // Always expand the thread item the first time we show it
2102 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
2103 }
2104
2105 m_stop_id = process_sp->GetStopID();
2106 m_tid = thread_sp->GetID();
2107
2108 TreeItem t(&item, *m_frame_delegate_sp, false);
2109 size_t num_frames = thread_sp->GetStackFrameCount();
2110 item.Resize(num_frames, t);
2111 for (size_t i = 0; i < num_frames; ++i) {
2112 item[i].SetUserData(thread_sp.get());
2113 item[i].SetIdentifier(i);
2114 }
2115 }
2116 return;
2117 }
2118 }
2119 item.ClearChildren();
2120 }
2121
TreeDelegateItemSelected(TreeItem & item)2122 bool TreeDelegateItemSelected(TreeItem &item) override {
2123 ProcessSP process_sp = GetProcess();
2124 if (process_sp && process_sp->IsAlive()) {
2125 StateType state = process_sp->GetState();
2126 if (StateIsStoppedState(state, true)) {
2127 ThreadSP thread_sp = GetThread(item);
2128 if (thread_sp) {
2129 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
2130 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
2131 ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
2132 if (selected_thread_sp->GetID() != thread_sp->GetID()) {
2133 thread_list.SetSelectedThreadByID(thread_sp->GetID());
2134 return true;
2135 }
2136 }
2137 }
2138 }
2139 return false;
2140 }
2141
2142 protected:
2143 Debugger &m_debugger;
2144 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
2145 lldb::user_id_t m_tid;
2146 uint32_t m_stop_id;
2147 FormatEntity::Entry m_format;
2148 };
2149
2150 class ThreadsTreeDelegate : public TreeDelegate {
2151 public:
ThreadsTreeDelegate(Debugger & debugger)2152 ThreadsTreeDelegate(Debugger &debugger)
2153 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
2154 m_stop_id(UINT32_MAX) {
2155 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
2156 m_format);
2157 }
2158
2159 ~ThreadsTreeDelegate() override = default;
2160
GetProcess()2161 ProcessSP GetProcess() {
2162 return m_debugger.GetCommandInterpreter()
2163 .GetExecutionContext()
2164 .GetProcessSP();
2165 }
2166
TreeDelegateDrawTreeItem(TreeItem & item,Window & window)2167 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2168 ProcessSP process_sp = GetProcess();
2169 if (process_sp && process_sp->IsAlive()) {
2170 StreamString strm;
2171 ExecutionContext exe_ctx(process_sp);
2172 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
2173 nullptr, false, false)) {
2174 int right_pad = 1;
2175 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2176 }
2177 }
2178 }
2179
TreeDelegateGenerateChildren(TreeItem & item)2180 void TreeDelegateGenerateChildren(TreeItem &item) override {
2181 ProcessSP process_sp = GetProcess();
2182 if (process_sp && process_sp->IsAlive()) {
2183 StateType state = process_sp->GetState();
2184 if (StateIsStoppedState(state, true)) {
2185 const uint32_t stop_id = process_sp->GetStopID();
2186 if (m_stop_id == stop_id)
2187 return; // Children are already up to date
2188
2189 m_stop_id = stop_id;
2190
2191 if (!m_thread_delegate_sp) {
2192 // Always expand the thread item the first time we show it
2193 // item.Expand();
2194 m_thread_delegate_sp =
2195 std::make_shared<ThreadTreeDelegate>(m_debugger);
2196 }
2197
2198 TreeItem t(&item, *m_thread_delegate_sp, false);
2199 ThreadList &threads = process_sp->GetThreadList();
2200 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
2201 size_t num_threads = threads.GetSize();
2202 item.Resize(num_threads, t);
2203 for (size_t i = 0; i < num_threads; ++i) {
2204 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID());
2205 item[i].SetMightHaveChildren(true);
2206 }
2207 return;
2208 }
2209 }
2210 item.ClearChildren();
2211 }
2212
TreeDelegateItemSelected(TreeItem & item)2213 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
2214
2215 protected:
2216 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
2217 Debugger &m_debugger;
2218 uint32_t m_stop_id;
2219 FormatEntity::Entry m_format;
2220 };
2221
2222 class ValueObjectListDelegate : public WindowDelegate {
2223 public:
ValueObjectListDelegate()2224 ValueObjectListDelegate()
2225 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2226 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {}
2227
ValueObjectListDelegate(ValueObjectList & valobj_list)2228 ValueObjectListDelegate(ValueObjectList &valobj_list)
2229 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2230 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
2231 SetValues(valobj_list);
2232 }
2233
2234 ~ValueObjectListDelegate() override = default;
2235
SetValues(ValueObjectList & valobj_list)2236 void SetValues(ValueObjectList &valobj_list) {
2237 m_selected_row = nullptr;
2238 m_selected_row_idx = 0;
2239 m_first_visible_row = 0;
2240 m_num_rows = 0;
2241 m_rows.clear();
2242 for (auto &valobj_sp : valobj_list.GetObjects())
2243 m_rows.push_back(Row(valobj_sp, nullptr));
2244 }
2245
WindowDelegateDraw(Window & window,bool force)2246 bool WindowDelegateDraw(Window &window, bool force) override {
2247 m_num_rows = 0;
2248 m_min_x = 2;
2249 m_min_y = 1;
2250 m_max_x = window.GetWidth() - 1;
2251 m_max_y = window.GetHeight() - 1;
2252
2253 window.Erase();
2254 window.DrawTitleBox(window.GetName());
2255
2256 const int num_visible_rows = NumVisibleRows();
2257 const int num_rows = CalculateTotalNumberRows(m_rows);
2258
2259 // If we unexpanded while having something selected our total number of
2260 // rows is less than the num visible rows, then make sure we show all the
2261 // rows by setting the first visible row accordingly.
2262 if (m_first_visible_row > 0 && num_rows < num_visible_rows)
2263 m_first_visible_row = 0;
2264
2265 // Make sure the selected row is always visible
2266 if (m_selected_row_idx < m_first_visible_row)
2267 m_first_visible_row = m_selected_row_idx;
2268 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
2269 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
2270
2271 DisplayRows(window, m_rows, g_options);
2272
2273 // Get the selected row
2274 m_selected_row = GetRowForRowIndex(m_selected_row_idx);
2275 // Keep the cursor on the selected row so the highlight and the cursor are
2276 // always on the same line
2277 if (m_selected_row)
2278 window.MoveCursor(m_selected_row->x, m_selected_row->y);
2279
2280 return true; // Drawing handled
2281 }
2282
WindowDelegateGetKeyHelp()2283 KeyHelp *WindowDelegateGetKeyHelp() override {
2284 static curses::KeyHelp g_source_view_key_help[] = {
2285 {KEY_UP, "Select previous item"},
2286 {KEY_DOWN, "Select next item"},
2287 {KEY_RIGHT, "Expand selected item"},
2288 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
2289 {KEY_PPAGE, "Page up"},
2290 {KEY_NPAGE, "Page down"},
2291 {'A', "Format as annotated address"},
2292 {'b', "Format as binary"},
2293 {'B', "Format as hex bytes with ASCII"},
2294 {'c', "Format as character"},
2295 {'d', "Format as a signed integer"},
2296 {'D', "Format selected value using the default format for the type"},
2297 {'f', "Format as float"},
2298 {'h', "Show help dialog"},
2299 {'i', "Format as instructions"},
2300 {'o', "Format as octal"},
2301 {'p', "Format as pointer"},
2302 {'s', "Format as C string"},
2303 {'t', "Toggle showing/hiding type names"},
2304 {'u', "Format as an unsigned integer"},
2305 {'x', "Format as hex"},
2306 {'X', "Format as uppercase hex"},
2307 {' ', "Toggle item expansion"},
2308 {',', "Page up"},
2309 {'.', "Page down"},
2310 {'\0', nullptr}};
2311 return g_source_view_key_help;
2312 }
2313
WindowDelegateHandleChar(Window & window,int c)2314 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
2315 switch (c) {
2316 case 'x':
2317 case 'X':
2318 case 'o':
2319 case 's':
2320 case 'u':
2321 case 'd':
2322 case 'D':
2323 case 'i':
2324 case 'A':
2325 case 'p':
2326 case 'c':
2327 case 'b':
2328 case 'B':
2329 case 'f':
2330 // Change the format for the currently selected item
2331 if (m_selected_row) {
2332 auto valobj_sp = m_selected_row->value.GetSP();
2333 if (valobj_sp)
2334 valobj_sp->SetFormat(FormatForChar(c));
2335 }
2336 return eKeyHandled;
2337
2338 case 't':
2339 // Toggle showing type names
2340 g_options.show_types = !g_options.show_types;
2341 return eKeyHandled;
2342
2343 case ',':
2344 case KEY_PPAGE:
2345 // Page up key
2346 if (m_first_visible_row > 0) {
2347 if (static_cast<int>(m_first_visible_row) > m_max_y)
2348 m_first_visible_row -= m_max_y;
2349 else
2350 m_first_visible_row = 0;
2351 m_selected_row_idx = m_first_visible_row;
2352 }
2353 return eKeyHandled;
2354
2355 case '.':
2356 case KEY_NPAGE:
2357 // Page down key
2358 if (m_num_rows > static_cast<size_t>(m_max_y)) {
2359 if (m_first_visible_row + m_max_y < m_num_rows) {
2360 m_first_visible_row += m_max_y;
2361 m_selected_row_idx = m_first_visible_row;
2362 }
2363 }
2364 return eKeyHandled;
2365
2366 case KEY_UP:
2367 if (m_selected_row_idx > 0)
2368 --m_selected_row_idx;
2369 return eKeyHandled;
2370
2371 case KEY_DOWN:
2372 if (m_selected_row_idx + 1 < m_num_rows)
2373 ++m_selected_row_idx;
2374 return eKeyHandled;
2375
2376 case KEY_RIGHT:
2377 if (m_selected_row) {
2378 if (!m_selected_row->expanded)
2379 m_selected_row->Expand();
2380 }
2381 return eKeyHandled;
2382
2383 case KEY_LEFT:
2384 if (m_selected_row) {
2385 if (m_selected_row->expanded)
2386 m_selected_row->Unexpand();
2387 else if (m_selected_row->parent)
2388 m_selected_row_idx = m_selected_row->parent->row_idx;
2389 }
2390 return eKeyHandled;
2391
2392 case ' ':
2393 // Toggle expansion state when SPACE is pressed
2394 if (m_selected_row) {
2395 if (m_selected_row->expanded)
2396 m_selected_row->Unexpand();
2397 else
2398 m_selected_row->Expand();
2399 }
2400 return eKeyHandled;
2401
2402 case 'h':
2403 window.CreateHelpSubwindow();
2404 return eKeyHandled;
2405
2406 default:
2407 break;
2408 }
2409 return eKeyNotHandled;
2410 }
2411
2412 protected:
2413 std::vector<Row> m_rows;
2414 Row *m_selected_row;
2415 uint32_t m_selected_row_idx;
2416 uint32_t m_first_visible_row;
2417 uint32_t m_num_rows;
2418 int m_min_x;
2419 int m_min_y;
2420 int m_max_x;
2421 int m_max_y;
2422
FormatForChar(int c)2423 static Format FormatForChar(int c) {
2424 switch (c) {
2425 case 'x':
2426 return eFormatHex;
2427 case 'X':
2428 return eFormatHexUppercase;
2429 case 'o':
2430 return eFormatOctal;
2431 case 's':
2432 return eFormatCString;
2433 case 'u':
2434 return eFormatUnsigned;
2435 case 'd':
2436 return eFormatDecimal;
2437 case 'D':
2438 return eFormatDefault;
2439 case 'i':
2440 return eFormatInstruction;
2441 case 'A':
2442 return eFormatAddressInfo;
2443 case 'p':
2444 return eFormatPointer;
2445 case 'c':
2446 return eFormatChar;
2447 case 'b':
2448 return eFormatBinary;
2449 case 'B':
2450 return eFormatBytesWithASCII;
2451 case 'f':
2452 return eFormatFloat;
2453 }
2454 return eFormatDefault;
2455 }
2456
DisplayRowObject(Window & window,Row & row,DisplayOptions & options,bool highlight,bool last_child)2457 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
2458 bool highlight, bool last_child) {
2459 ValueObject *valobj = row.value.GetSP().get();
2460
2461 if (valobj == nullptr)
2462 return false;
2463
2464 const char *type_name =
2465 options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
2466 const char *name = valobj->GetName().GetCString();
2467 const char *value = valobj->GetValueAsCString();
2468 const char *summary = valobj->GetSummaryAsCString();
2469
2470 window.MoveCursor(row.x, row.y);
2471
2472 row.DrawTree(window);
2473
2474 if (highlight)
2475 window.AttributeOn(A_REVERSE);
2476
2477 if (type_name && type_name[0])
2478 window.PrintfTruncated(1, "(%s) ", type_name);
2479
2480 if (name && name[0])
2481 window.PutCStringTruncated(1, name);
2482
2483 attr_t changd_attr = 0;
2484 if (valobj->GetValueDidChange())
2485 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
2486
2487 if (value && value[0]) {
2488 window.PutCStringTruncated(1, " = ");
2489 if (changd_attr)
2490 window.AttributeOn(changd_attr);
2491 window.PutCStringTruncated(1, value);
2492 if (changd_attr)
2493 window.AttributeOff(changd_attr);
2494 }
2495
2496 if (summary && summary[0]) {
2497 window.PutCStringTruncated(1, " ");
2498 if (changd_attr)
2499 window.AttributeOn(changd_attr);
2500 window.PutCStringTruncated(1, summary);
2501 if (changd_attr)
2502 window.AttributeOff(changd_attr);
2503 }
2504
2505 if (highlight)
2506 window.AttributeOff(A_REVERSE);
2507
2508 return true;
2509 }
2510
DisplayRows(Window & window,std::vector<Row> & rows,DisplayOptions & options)2511 void DisplayRows(Window &window, std::vector<Row> &rows,
2512 DisplayOptions &options) {
2513 // > 0x25B7
2514 // \/ 0x25BD
2515
2516 bool window_is_active = window.IsActive();
2517 for (auto &row : rows) {
2518 const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
2519 // Save the row index in each Row structure
2520 row.row_idx = m_num_rows;
2521 if ((m_num_rows >= m_first_visible_row) &&
2522 ((m_num_rows - m_first_visible_row) <
2523 static_cast<size_t>(NumVisibleRows()))) {
2524 row.x = m_min_x;
2525 row.y = m_num_rows - m_first_visible_row + 1;
2526 if (DisplayRowObject(window, row, options,
2527 window_is_active &&
2528 m_num_rows == m_selected_row_idx,
2529 last_child)) {
2530 ++m_num_rows;
2531 } else {
2532 row.x = 0;
2533 row.y = 0;
2534 }
2535 } else {
2536 row.x = 0;
2537 row.y = 0;
2538 ++m_num_rows;
2539 }
2540
2541 auto &children = row.GetChildren();
2542 if (row.expanded && !children.empty()) {
2543 DisplayRows(window, children, options);
2544 }
2545 }
2546 }
2547
CalculateTotalNumberRows(std::vector<Row> & rows)2548 int CalculateTotalNumberRows(std::vector<Row> &rows) {
2549 int row_count = 0;
2550 for (auto &row : rows) {
2551 ++row_count;
2552 if (row.expanded)
2553 row_count += CalculateTotalNumberRows(row.GetChildren());
2554 }
2555 return row_count;
2556 }
2557
GetRowForRowIndexImpl(std::vector<Row> & rows,size_t & row_index)2558 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
2559 for (auto &row : rows) {
2560 if (row_index == 0)
2561 return &row;
2562 else {
2563 --row_index;
2564 auto &children = row.GetChildren();
2565 if (row.expanded && !children.empty()) {
2566 Row *result = GetRowForRowIndexImpl(children, row_index);
2567 if (result)
2568 return result;
2569 }
2570 }
2571 }
2572 return nullptr;
2573 }
2574
GetRowForRowIndex(size_t row_index)2575 Row *GetRowForRowIndex(size_t row_index) {
2576 return GetRowForRowIndexImpl(m_rows, row_index);
2577 }
2578
NumVisibleRows() const2579 int NumVisibleRows() const { return m_max_y - m_min_y; }
2580
2581 static DisplayOptions g_options;
2582 };
2583
2584 class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
2585 public:
FrameVariablesWindowDelegate(Debugger & debugger)2586 FrameVariablesWindowDelegate(Debugger &debugger)
2587 : ValueObjectListDelegate(), m_debugger(debugger),
2588 m_frame_block(nullptr) {}
2589
2590 ~FrameVariablesWindowDelegate() override = default;
2591
WindowDelegateGetHelpText()2592 const char *WindowDelegateGetHelpText() override {
2593 return "Frame variable window keyboard shortcuts:";
2594 }
2595
WindowDelegateDraw(Window & window,bool force)2596 bool WindowDelegateDraw(Window &window, bool force) override {
2597 ExecutionContext exe_ctx(
2598 m_debugger.GetCommandInterpreter().GetExecutionContext());
2599 Process *process = exe_ctx.GetProcessPtr();
2600 Block *frame_block = nullptr;
2601 StackFrame *frame = nullptr;
2602
2603 if (process) {
2604 StateType state = process->GetState();
2605 if (StateIsStoppedState(state, true)) {
2606 frame = exe_ctx.GetFramePtr();
2607 if (frame)
2608 frame_block = frame->GetFrameBlock();
2609 } else if (StateIsRunningState(state)) {
2610 return true; // Don't do any updating when we are running
2611 }
2612 }
2613
2614 ValueObjectList local_values;
2615 if (frame_block) {
2616 // Only update the variables if they have changed
2617 if (m_frame_block != frame_block) {
2618 m_frame_block = frame_block;
2619
2620 VariableList *locals = frame->GetVariableList(true);
2621 if (locals) {
2622 const DynamicValueType use_dynamic = eDynamicDontRunTarget;
2623 for (const VariableSP &local_sp : *locals) {
2624 ValueObjectSP value_sp =
2625 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
2626 if (value_sp) {
2627 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
2628 if (synthetic_value_sp)
2629 local_values.Append(synthetic_value_sp);
2630 else
2631 local_values.Append(value_sp);
2632 }
2633 }
2634 // Update the values
2635 SetValues(local_values);
2636 }
2637 }
2638 } else {
2639 m_frame_block = nullptr;
2640 // Update the values with an empty list if there is no frame
2641 SetValues(local_values);
2642 }
2643
2644 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2645 }
2646
2647 protected:
2648 Debugger &m_debugger;
2649 Block *m_frame_block;
2650 };
2651
2652 class RegistersWindowDelegate : public ValueObjectListDelegate {
2653 public:
RegistersWindowDelegate(Debugger & debugger)2654 RegistersWindowDelegate(Debugger &debugger)
2655 : ValueObjectListDelegate(), m_debugger(debugger) {}
2656
2657 ~RegistersWindowDelegate() override = default;
2658
WindowDelegateGetHelpText()2659 const char *WindowDelegateGetHelpText() override {
2660 return "Register window keyboard shortcuts:";
2661 }
2662
WindowDelegateDraw(Window & window,bool force)2663 bool WindowDelegateDraw(Window &window, bool force) override {
2664 ExecutionContext exe_ctx(
2665 m_debugger.GetCommandInterpreter().GetExecutionContext());
2666 StackFrame *frame = exe_ctx.GetFramePtr();
2667
2668 ValueObjectList value_list;
2669 if (frame) {
2670 if (frame->GetStackID() != m_stack_id) {
2671 m_stack_id = frame->GetStackID();
2672 RegisterContextSP reg_ctx(frame->GetRegisterContext());
2673 if (reg_ctx) {
2674 const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
2675 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
2676 value_list.Append(
2677 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
2678 }
2679 }
2680 SetValues(value_list);
2681 }
2682 } else {
2683 Process *process = exe_ctx.GetProcessPtr();
2684 if (process && process->IsAlive())
2685 return true; // Don't do any updating if we are running
2686 else {
2687 // Update the values with an empty list if there is no process or the
2688 // process isn't alive anymore
2689 SetValues(value_list);
2690 }
2691 }
2692 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2693 }
2694
2695 protected:
2696 Debugger &m_debugger;
2697 StackID m_stack_id;
2698 };
2699
CursesKeyToCString(int ch)2700 static const char *CursesKeyToCString(int ch) {
2701 static char g_desc[32];
2702 if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
2703 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
2704 return g_desc;
2705 }
2706 switch (ch) {
2707 case KEY_DOWN:
2708 return "down";
2709 case KEY_UP:
2710 return "up";
2711 case KEY_LEFT:
2712 return "left";
2713 case KEY_RIGHT:
2714 return "right";
2715 case KEY_HOME:
2716 return "home";
2717 case KEY_BACKSPACE:
2718 return "backspace";
2719 case KEY_DL:
2720 return "delete-line";
2721 case KEY_IL:
2722 return "insert-line";
2723 case KEY_DC:
2724 return "delete-char";
2725 case KEY_IC:
2726 return "insert-char";
2727 case KEY_CLEAR:
2728 return "clear";
2729 case KEY_EOS:
2730 return "clear-to-eos";
2731 case KEY_EOL:
2732 return "clear-to-eol";
2733 case KEY_SF:
2734 return "scroll-forward";
2735 case KEY_SR:
2736 return "scroll-backward";
2737 case KEY_NPAGE:
2738 return "page-down";
2739 case KEY_PPAGE:
2740 return "page-up";
2741 case KEY_STAB:
2742 return "set-tab";
2743 case KEY_CTAB:
2744 return "clear-tab";
2745 case KEY_CATAB:
2746 return "clear-all-tabs";
2747 case KEY_ENTER:
2748 return "enter";
2749 case KEY_PRINT:
2750 return "print";
2751 case KEY_LL:
2752 return "lower-left key";
2753 case KEY_A1:
2754 return "upper left of keypad";
2755 case KEY_A3:
2756 return "upper right of keypad";
2757 case KEY_B2:
2758 return "center of keypad";
2759 case KEY_C1:
2760 return "lower left of keypad";
2761 case KEY_C3:
2762 return "lower right of keypad";
2763 case KEY_BTAB:
2764 return "back-tab key";
2765 case KEY_BEG:
2766 return "begin key";
2767 case KEY_CANCEL:
2768 return "cancel key";
2769 case KEY_CLOSE:
2770 return "close key";
2771 case KEY_COMMAND:
2772 return "command key";
2773 case KEY_COPY:
2774 return "copy key";
2775 case KEY_CREATE:
2776 return "create key";
2777 case KEY_END:
2778 return "end key";
2779 case KEY_EXIT:
2780 return "exit key";
2781 case KEY_FIND:
2782 return "find key";
2783 case KEY_HELP:
2784 return "help key";
2785 case KEY_MARK:
2786 return "mark key";
2787 case KEY_MESSAGE:
2788 return "message key";
2789 case KEY_MOVE:
2790 return "move key";
2791 case KEY_NEXT:
2792 return "next key";
2793 case KEY_OPEN:
2794 return "open key";
2795 case KEY_OPTIONS:
2796 return "options key";
2797 case KEY_PREVIOUS:
2798 return "previous key";
2799 case KEY_REDO:
2800 return "redo key";
2801 case KEY_REFERENCE:
2802 return "reference key";
2803 case KEY_REFRESH:
2804 return "refresh key";
2805 case KEY_REPLACE:
2806 return "replace key";
2807 case KEY_RESTART:
2808 return "restart key";
2809 case KEY_RESUME:
2810 return "resume key";
2811 case KEY_SAVE:
2812 return "save key";
2813 case KEY_SBEG:
2814 return "shifted begin key";
2815 case KEY_SCANCEL:
2816 return "shifted cancel key";
2817 case KEY_SCOMMAND:
2818 return "shifted command key";
2819 case KEY_SCOPY:
2820 return "shifted copy key";
2821 case KEY_SCREATE:
2822 return "shifted create key";
2823 case KEY_SDC:
2824 return "shifted delete-character key";
2825 case KEY_SDL:
2826 return "shifted delete-line key";
2827 case KEY_SELECT:
2828 return "select key";
2829 case KEY_SEND:
2830 return "shifted end key";
2831 case KEY_SEOL:
2832 return "shifted clear-to-end-of-line key";
2833 case KEY_SEXIT:
2834 return "shifted exit key";
2835 case KEY_SFIND:
2836 return "shifted find key";
2837 case KEY_SHELP:
2838 return "shifted help key";
2839 case KEY_SHOME:
2840 return "shifted home key";
2841 case KEY_SIC:
2842 return "shifted insert-character key";
2843 case KEY_SLEFT:
2844 return "shifted left-arrow key";
2845 case KEY_SMESSAGE:
2846 return "shifted message key";
2847 case KEY_SMOVE:
2848 return "shifted move key";
2849 case KEY_SNEXT:
2850 return "shifted next key";
2851 case KEY_SOPTIONS:
2852 return "shifted options key";
2853 case KEY_SPREVIOUS:
2854 return "shifted previous key";
2855 case KEY_SPRINT:
2856 return "shifted print key";
2857 case KEY_SREDO:
2858 return "shifted redo key";
2859 case KEY_SREPLACE:
2860 return "shifted replace key";
2861 case KEY_SRIGHT:
2862 return "shifted right-arrow key";
2863 case KEY_SRSUME:
2864 return "shifted resume key";
2865 case KEY_SSAVE:
2866 return "shifted save key";
2867 case KEY_SSUSPEND:
2868 return "shifted suspend key";
2869 case KEY_SUNDO:
2870 return "shifted undo key";
2871 case KEY_SUSPEND:
2872 return "suspend key";
2873 case KEY_UNDO:
2874 return "undo key";
2875 case KEY_MOUSE:
2876 return "Mouse event has occurred";
2877 case KEY_RESIZE:
2878 return "Terminal resize event";
2879 #ifdef KEY_EVENT
2880 case KEY_EVENT:
2881 return "We were interrupted by an event";
2882 #endif
2883 case KEY_RETURN:
2884 return "return";
2885 case ' ':
2886 return "space";
2887 case '\t':
2888 return "tab";
2889 case KEY_ESCAPE:
2890 return "escape";
2891 default:
2892 if (llvm::isPrint(ch))
2893 snprintf(g_desc, sizeof(g_desc), "%c", ch);
2894 else
2895 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
2896 return g_desc;
2897 }
2898 return nullptr;
2899 }
2900
HelpDialogDelegate(const char * text,KeyHelp * key_help_array)2901 HelpDialogDelegate::HelpDialogDelegate(const char *text,
2902 KeyHelp *key_help_array)
2903 : m_text(), m_first_visible_line(0) {
2904 if (text && text[0]) {
2905 m_text.SplitIntoLines(text);
2906 m_text.AppendString("");
2907 }
2908 if (key_help_array) {
2909 for (KeyHelp *key = key_help_array; key->ch; ++key) {
2910 StreamString key_description;
2911 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
2912 key->description);
2913 m_text.AppendString(key_description.GetString());
2914 }
2915 }
2916 }
2917
2918 HelpDialogDelegate::~HelpDialogDelegate() = default;
2919
WindowDelegateDraw(Window & window,bool force)2920 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
2921 window.Erase();
2922 const int window_height = window.GetHeight();
2923 int x = 2;
2924 int y = 1;
2925 const int min_y = y;
2926 const int max_y = window_height - 1 - y;
2927 const size_t num_visible_lines = max_y - min_y + 1;
2928 const size_t num_lines = m_text.GetSize();
2929 const char *bottom_message;
2930 if (num_lines <= num_visible_lines)
2931 bottom_message = "Press any key to exit";
2932 else
2933 bottom_message = "Use arrows to scroll, any other key to exit";
2934 window.DrawTitleBox(window.GetName(), bottom_message);
2935 while (y <= max_y) {
2936 window.MoveCursor(x, y);
2937 window.PutCStringTruncated(
2938 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
2939 ++y;
2940 }
2941 return true;
2942 }
2943
WindowDelegateHandleChar(Window & window,int key)2944 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
2945 int key) {
2946 bool done = false;
2947 const size_t num_lines = m_text.GetSize();
2948 const size_t num_visible_lines = window.GetHeight() - 2;
2949
2950 if (num_lines <= num_visible_lines) {
2951 done = true;
2952 // If we have all lines visible and don't need scrolling, then any key
2953 // press will cause us to exit
2954 } else {
2955 switch (key) {
2956 case KEY_UP:
2957 if (m_first_visible_line > 0)
2958 --m_first_visible_line;
2959 break;
2960
2961 case KEY_DOWN:
2962 if (m_first_visible_line + num_visible_lines < num_lines)
2963 ++m_first_visible_line;
2964 break;
2965
2966 case KEY_PPAGE:
2967 case ',':
2968 if (m_first_visible_line > 0) {
2969 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
2970 m_first_visible_line -= num_visible_lines;
2971 else
2972 m_first_visible_line = 0;
2973 }
2974 break;
2975
2976 case KEY_NPAGE:
2977 case '.':
2978 if (m_first_visible_line + num_visible_lines < num_lines) {
2979 m_first_visible_line += num_visible_lines;
2980 if (static_cast<size_t>(m_first_visible_line) > num_lines)
2981 m_first_visible_line = num_lines - num_visible_lines;
2982 }
2983 break;
2984
2985 default:
2986 done = true;
2987 break;
2988 }
2989 }
2990 if (done)
2991 window.GetParent()->RemoveSubWindow(&window);
2992 return eKeyHandled;
2993 }
2994
2995 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
2996 public:
2997 enum {
2998 eMenuID_LLDB = 1,
2999 eMenuID_LLDBAbout,
3000 eMenuID_LLDBExit,
3001
3002 eMenuID_Target,
3003 eMenuID_TargetCreate,
3004 eMenuID_TargetDelete,
3005
3006 eMenuID_Process,
3007 eMenuID_ProcessAttach,
3008 eMenuID_ProcessDetachResume,
3009 eMenuID_ProcessDetachSuspended,
3010 eMenuID_ProcessLaunch,
3011 eMenuID_ProcessContinue,
3012 eMenuID_ProcessHalt,
3013 eMenuID_ProcessKill,
3014
3015 eMenuID_Thread,
3016 eMenuID_ThreadStepIn,
3017 eMenuID_ThreadStepOver,
3018 eMenuID_ThreadStepOut,
3019
3020 eMenuID_View,
3021 eMenuID_ViewBacktrace,
3022 eMenuID_ViewRegisters,
3023 eMenuID_ViewSource,
3024 eMenuID_ViewVariables,
3025
3026 eMenuID_Help,
3027 eMenuID_HelpGUIHelp
3028 };
3029
ApplicationDelegate(Application & app,Debugger & debugger)3030 ApplicationDelegate(Application &app, Debugger &debugger)
3031 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
3032
3033 ~ApplicationDelegate() override = default;
3034
WindowDelegateDraw(Window & window,bool force)3035 bool WindowDelegateDraw(Window &window, bool force) override {
3036 return false; // Drawing not handled, let standard window drawing happen
3037 }
3038
WindowDelegateHandleChar(Window & window,int key)3039 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3040 switch (key) {
3041 case '\t':
3042 window.SelectNextWindowAsActive();
3043 return eKeyHandled;
3044
3045 case KEY_BTAB:
3046 window.SelectPreviousWindowAsActive();
3047 return eKeyHandled;
3048
3049 case 'h':
3050 window.CreateHelpSubwindow();
3051 return eKeyHandled;
3052
3053 case KEY_ESCAPE:
3054 return eQuitApplication;
3055
3056 default:
3057 break;
3058 }
3059 return eKeyNotHandled;
3060 }
3061
WindowDelegateGetHelpText()3062 const char *WindowDelegateGetHelpText() override {
3063 return "Welcome to the LLDB curses GUI.\n\n"
3064 "Press the TAB key to change the selected view.\n"
3065 "Each view has its own keyboard shortcuts, press 'h' to open a "
3066 "dialog to display them.\n\n"
3067 "Common key bindings for all views:";
3068 }
3069
WindowDelegateGetKeyHelp()3070 KeyHelp *WindowDelegateGetKeyHelp() override {
3071 static curses::KeyHelp g_source_view_key_help[] = {
3072 {'\t', "Select next view"},
3073 {KEY_BTAB, "Select previous view"},
3074 {'h', "Show help dialog with view specific key bindings"},
3075 {',', "Page up"},
3076 {'.', "Page down"},
3077 {KEY_UP, "Select previous"},
3078 {KEY_DOWN, "Select next"},
3079 {KEY_LEFT, "Unexpand or select parent"},
3080 {KEY_RIGHT, "Expand"},
3081 {KEY_PPAGE, "Page up"},
3082 {KEY_NPAGE, "Page down"},
3083 {'\0', nullptr}};
3084 return g_source_view_key_help;
3085 }
3086
MenuDelegateAction(Menu & menu)3087 MenuActionResult MenuDelegateAction(Menu &menu) override {
3088 switch (menu.GetIdentifier()) {
3089 case eMenuID_ThreadStepIn: {
3090 ExecutionContext exe_ctx =
3091 m_debugger.GetCommandInterpreter().GetExecutionContext();
3092 if (exe_ctx.HasThreadScope()) {
3093 Process *process = exe_ctx.GetProcessPtr();
3094 if (process && process->IsAlive() &&
3095 StateIsStoppedState(process->GetState(), true))
3096 exe_ctx.GetThreadRef().StepIn(true);
3097 }
3098 }
3099 return MenuActionResult::Handled;
3100
3101 case eMenuID_ThreadStepOut: {
3102 ExecutionContext exe_ctx =
3103 m_debugger.GetCommandInterpreter().GetExecutionContext();
3104 if (exe_ctx.HasThreadScope()) {
3105 Process *process = exe_ctx.GetProcessPtr();
3106 if (process && process->IsAlive() &&
3107 StateIsStoppedState(process->GetState(), true))
3108 exe_ctx.GetThreadRef().StepOut();
3109 }
3110 }
3111 return MenuActionResult::Handled;
3112
3113 case eMenuID_ThreadStepOver: {
3114 ExecutionContext exe_ctx =
3115 m_debugger.GetCommandInterpreter().GetExecutionContext();
3116 if (exe_ctx.HasThreadScope()) {
3117 Process *process = exe_ctx.GetProcessPtr();
3118 if (process && process->IsAlive() &&
3119 StateIsStoppedState(process->GetState(), true))
3120 exe_ctx.GetThreadRef().StepOver(true);
3121 }
3122 }
3123 return MenuActionResult::Handled;
3124
3125 case eMenuID_ProcessContinue: {
3126 ExecutionContext exe_ctx =
3127 m_debugger.GetCommandInterpreter().GetExecutionContext();
3128 if (exe_ctx.HasProcessScope()) {
3129 Process *process = exe_ctx.GetProcessPtr();
3130 if (process && process->IsAlive() &&
3131 StateIsStoppedState(process->GetState(), true))
3132 process->Resume();
3133 }
3134 }
3135 return MenuActionResult::Handled;
3136
3137 case eMenuID_ProcessKill: {
3138 ExecutionContext exe_ctx =
3139 m_debugger.GetCommandInterpreter().GetExecutionContext();
3140 if (exe_ctx.HasProcessScope()) {
3141 Process *process = exe_ctx.GetProcessPtr();
3142 if (process && process->IsAlive())
3143 process->Destroy(false);
3144 }
3145 }
3146 return MenuActionResult::Handled;
3147
3148 case eMenuID_ProcessHalt: {
3149 ExecutionContext exe_ctx =
3150 m_debugger.GetCommandInterpreter().GetExecutionContext();
3151 if (exe_ctx.HasProcessScope()) {
3152 Process *process = exe_ctx.GetProcessPtr();
3153 if (process && process->IsAlive())
3154 process->Halt();
3155 }
3156 }
3157 return MenuActionResult::Handled;
3158
3159 case eMenuID_ProcessDetachResume:
3160 case eMenuID_ProcessDetachSuspended: {
3161 ExecutionContext exe_ctx =
3162 m_debugger.GetCommandInterpreter().GetExecutionContext();
3163 if (exe_ctx.HasProcessScope()) {
3164 Process *process = exe_ctx.GetProcessPtr();
3165 if (process && process->IsAlive())
3166 process->Detach(menu.GetIdentifier() ==
3167 eMenuID_ProcessDetachSuspended);
3168 }
3169 }
3170 return MenuActionResult::Handled;
3171
3172 case eMenuID_Process: {
3173 // Populate the menu with all of the threads if the process is stopped
3174 // when the Process menu gets selected and is about to display its
3175 // submenu.
3176 Menus &submenus = menu.GetSubmenus();
3177 ExecutionContext exe_ctx =
3178 m_debugger.GetCommandInterpreter().GetExecutionContext();
3179 Process *process = exe_ctx.GetProcessPtr();
3180 if (process && process->IsAlive() &&
3181 StateIsStoppedState(process->GetState(), true)) {
3182 if (submenus.size() == 7)
3183 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
3184 else if (submenus.size() > 8)
3185 submenus.erase(submenus.begin() + 8, submenus.end());
3186
3187 ThreadList &threads = process->GetThreadList();
3188 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3189 size_t num_threads = threads.GetSize();
3190 for (size_t i = 0; i < num_threads; ++i) {
3191 ThreadSP thread_sp = threads.GetThreadAtIndex(i);
3192 char menu_char = '\0';
3193 if (i < 9)
3194 menu_char = '1' + i;
3195 StreamString thread_menu_title;
3196 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
3197 const char *thread_name = thread_sp->GetName();
3198 if (thread_name && thread_name[0])
3199 thread_menu_title.Printf(" %s", thread_name);
3200 else {
3201 const char *queue_name = thread_sp->GetQueueName();
3202 if (queue_name && queue_name[0])
3203 thread_menu_title.Printf(" %s", queue_name);
3204 }
3205 menu.AddSubmenu(
3206 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
3207 nullptr, menu_char, thread_sp->GetID())));
3208 }
3209 } else if (submenus.size() > 7) {
3210 // Remove the separator and any other thread submenu items that were
3211 // previously added
3212 submenus.erase(submenus.begin() + 7, submenus.end());
3213 }
3214 // Since we are adding and removing items we need to recalculate the name
3215 // lengths
3216 menu.RecalculateNameLengths();
3217 }
3218 return MenuActionResult::Handled;
3219
3220 case eMenuID_ViewVariables: {
3221 WindowSP main_window_sp = m_app.GetMainWindow();
3222 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3223 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3224 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3225 const Rect source_bounds = source_window_sp->GetBounds();
3226
3227 if (variables_window_sp) {
3228 const Rect variables_bounds = variables_window_sp->GetBounds();
3229
3230 main_window_sp->RemoveSubWindow(variables_window_sp.get());
3231
3232 if (registers_window_sp) {
3233 // We have a registers window, so give all the area back to the
3234 // registers window
3235 Rect registers_bounds = variables_bounds;
3236 registers_bounds.size.width = source_bounds.size.width;
3237 registers_window_sp->SetBounds(registers_bounds);
3238 } else {
3239 // We have no registers window showing so give the bottom area back
3240 // to the source view
3241 source_window_sp->Resize(source_bounds.size.width,
3242 source_bounds.size.height +
3243 variables_bounds.size.height);
3244 }
3245 } else {
3246 Rect new_variables_rect;
3247 if (registers_window_sp) {
3248 // We have a registers window so split the area of the registers
3249 // window into two columns where the left hand side will be the
3250 // variables and the right hand side will be the registers
3251 const Rect variables_bounds = registers_window_sp->GetBounds();
3252 Rect new_registers_rect;
3253 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
3254 new_registers_rect);
3255 registers_window_sp->SetBounds(new_registers_rect);
3256 } else {
3257 // No registers window, grab the bottom part of the source window
3258 Rect new_source_rect;
3259 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3260 new_variables_rect);
3261 source_window_sp->SetBounds(new_source_rect);
3262 }
3263 WindowSP new_window_sp = main_window_sp->CreateSubWindow(
3264 "Variables", new_variables_rect, false);
3265 new_window_sp->SetDelegate(
3266 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
3267 }
3268 touchwin(stdscr);
3269 }
3270 return MenuActionResult::Handled;
3271
3272 case eMenuID_ViewRegisters: {
3273 WindowSP main_window_sp = m_app.GetMainWindow();
3274 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3275 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3276 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3277 const Rect source_bounds = source_window_sp->GetBounds();
3278
3279 if (registers_window_sp) {
3280 if (variables_window_sp) {
3281 const Rect variables_bounds = variables_window_sp->GetBounds();
3282
3283 // We have a variables window, so give all the area back to the
3284 // variables window
3285 variables_window_sp->Resize(variables_bounds.size.width +
3286 registers_window_sp->GetWidth(),
3287 variables_bounds.size.height);
3288 } else {
3289 // We have no variables window showing so give the bottom area back
3290 // to the source view
3291 source_window_sp->Resize(source_bounds.size.width,
3292 source_bounds.size.height +
3293 registers_window_sp->GetHeight());
3294 }
3295 main_window_sp->RemoveSubWindow(registers_window_sp.get());
3296 } else {
3297 Rect new_regs_rect;
3298 if (variables_window_sp) {
3299 // We have a variables window, split it into two columns where the
3300 // left hand side will be the variables and the right hand side will
3301 // be the registers
3302 const Rect variables_bounds = variables_window_sp->GetBounds();
3303 Rect new_vars_rect;
3304 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
3305 new_regs_rect);
3306 variables_window_sp->SetBounds(new_vars_rect);
3307 } else {
3308 // No variables window, grab the bottom part of the source window
3309 Rect new_source_rect;
3310 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3311 new_regs_rect);
3312 source_window_sp->SetBounds(new_source_rect);
3313 }
3314 WindowSP new_window_sp =
3315 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
3316 new_window_sp->SetDelegate(
3317 WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
3318 }
3319 touchwin(stdscr);
3320 }
3321 return MenuActionResult::Handled;
3322
3323 case eMenuID_HelpGUIHelp:
3324 m_app.GetMainWindow()->CreateHelpSubwindow();
3325 return MenuActionResult::Handled;
3326
3327 default:
3328 break;
3329 }
3330
3331 return MenuActionResult::NotHandled;
3332 }
3333
3334 protected:
3335 Application &m_app;
3336 Debugger &m_debugger;
3337 };
3338
3339 class StatusBarWindowDelegate : public WindowDelegate {
3340 public:
StatusBarWindowDelegate(Debugger & debugger)3341 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
3342 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
3343 }
3344
3345 ~StatusBarWindowDelegate() override = default;
3346
WindowDelegateDraw(Window & window,bool force)3347 bool WindowDelegateDraw(Window &window, bool force) override {
3348 ExecutionContext exe_ctx =
3349 m_debugger.GetCommandInterpreter().GetExecutionContext();
3350 Process *process = exe_ctx.GetProcessPtr();
3351 Thread *thread = exe_ctx.GetThreadPtr();
3352 StackFrame *frame = exe_ctx.GetFramePtr();
3353 window.Erase();
3354 window.SetBackground(BlackOnWhite);
3355 window.MoveCursor(0, 0);
3356 if (process) {
3357 const StateType state = process->GetState();
3358 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
3359 StateAsCString(state));
3360
3361 if (StateIsStoppedState(state, true)) {
3362 StreamString strm;
3363 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
3364 nullptr, nullptr, false, false)) {
3365 window.MoveCursor(40, 0);
3366 window.PutCStringTruncated(1, strm.GetString().str().c_str());
3367 }
3368
3369 window.MoveCursor(60, 0);
3370 if (frame)
3371 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,
3372 frame->GetFrameIndex(),
3373 frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
3374 exe_ctx.GetTargetPtr()));
3375 } else if (state == eStateExited) {
3376 const char *exit_desc = process->GetExitDescription();
3377 const int exit_status = process->GetExitStatus();
3378 if (exit_desc && exit_desc[0])
3379 window.Printf(" with status = %i (%s)", exit_status, exit_desc);
3380 else
3381 window.Printf(" with status = %i", exit_status);
3382 }
3383 }
3384 return true;
3385 }
3386
3387 protected:
3388 Debugger &m_debugger;
3389 FormatEntity::Entry m_format;
3390 };
3391
3392 class SourceFileWindowDelegate : public WindowDelegate {
3393 public:
SourceFileWindowDelegate(Debugger & debugger)3394 SourceFileWindowDelegate(Debugger &debugger)
3395 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
3396 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
3397 m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0),
3398 m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0),
3399 m_first_visible_column(0), m_min_x(0), m_min_y(0), m_max_x(0),
3400 m_max_y(0) {}
3401
3402 ~SourceFileWindowDelegate() override = default;
3403
Update(const SymbolContext & sc)3404 void Update(const SymbolContext &sc) { m_sc = sc; }
3405
NumVisibleLines() const3406 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
3407
WindowDelegateGetHelpText()3408 const char *WindowDelegateGetHelpText() override {
3409 return "Source/Disassembly window keyboard shortcuts:";
3410 }
3411
WindowDelegateGetKeyHelp()3412 KeyHelp *WindowDelegateGetKeyHelp() override {
3413 static curses::KeyHelp g_source_view_key_help[] = {
3414 {KEY_RETURN, "Run to selected line with one shot breakpoint"},
3415 {KEY_UP, "Select previous source line"},
3416 {KEY_DOWN, "Select next source line"},
3417 {KEY_LEFT, "Scroll to the left"},
3418 {KEY_RIGHT, "Scroll to the right"},
3419 {KEY_PPAGE, "Page up"},
3420 {KEY_NPAGE, "Page down"},
3421 {'b', "Set breakpoint on selected source/disassembly line"},
3422 {'c', "Continue process"},
3423 {'D', "Detach with process suspended"},
3424 {'h', "Show help dialog"},
3425 {'n', "Step over (source line)"},
3426 {'N', "Step over (single instruction)"},
3427 {'f', "Step out (finish)"},
3428 {'s', "Step in (source line)"},
3429 {'S', "Step in (single instruction)"},
3430 {'u', "Frame up"},
3431 {'d', "Frame down"},
3432 {',', "Page up"},
3433 {'.', "Page down"},
3434 {'\0', nullptr}};
3435 return g_source_view_key_help;
3436 }
3437
WindowDelegateDraw(Window & window,bool force)3438 bool WindowDelegateDraw(Window &window, bool force) override {
3439 ExecutionContext exe_ctx =
3440 m_debugger.GetCommandInterpreter().GetExecutionContext();
3441 Process *process = exe_ctx.GetProcessPtr();
3442 Thread *thread = nullptr;
3443
3444 bool update_location = false;
3445 if (process) {
3446 StateType state = process->GetState();
3447 if (StateIsStoppedState(state, true)) {
3448 // We are stopped, so it is ok to
3449 update_location = true;
3450 }
3451 }
3452
3453 m_min_x = 1;
3454 m_min_y = 2;
3455 m_max_x = window.GetMaxX() - 1;
3456 m_max_y = window.GetMaxY() - 1;
3457
3458 const uint32_t num_visible_lines = NumVisibleLines();
3459 StackFrameSP frame_sp;
3460 bool set_selected_line_to_pc = false;
3461
3462 if (update_location) {
3463 const bool process_alive = process ? process->IsAlive() : false;
3464 bool thread_changed = false;
3465 if (process_alive) {
3466 thread = exe_ctx.GetThreadPtr();
3467 if (thread) {
3468 frame_sp = thread->GetSelectedFrame();
3469 auto tid = thread->GetID();
3470 thread_changed = tid != m_tid;
3471 m_tid = tid;
3472 } else {
3473 if (m_tid != LLDB_INVALID_THREAD_ID) {
3474 thread_changed = true;
3475 m_tid = LLDB_INVALID_THREAD_ID;
3476 }
3477 }
3478 }
3479 const uint32_t stop_id = process ? process->GetStopID() : 0;
3480 const bool stop_id_changed = stop_id != m_stop_id;
3481 bool frame_changed = false;
3482 m_stop_id = stop_id;
3483 m_title.Clear();
3484 if (frame_sp) {
3485 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
3486 if (m_sc.module_sp) {
3487 m_title.Printf(
3488 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
3489 ConstString func_name = m_sc.GetFunctionName();
3490 if (func_name)
3491 m_title.Printf("`%s", func_name.GetCString());
3492 }
3493 const uint32_t frame_idx = frame_sp->GetFrameIndex();
3494 frame_changed = frame_idx != m_frame_idx;
3495 m_frame_idx = frame_idx;
3496 } else {
3497 m_sc.Clear(true);
3498 frame_changed = m_frame_idx != UINT32_MAX;
3499 m_frame_idx = UINT32_MAX;
3500 }
3501
3502 const bool context_changed =
3503 thread_changed || frame_changed || stop_id_changed;
3504
3505 if (process_alive) {
3506 if (m_sc.line_entry.IsValid()) {
3507 m_pc_line = m_sc.line_entry.line;
3508 if (m_pc_line != UINT32_MAX)
3509 --m_pc_line; // Convert to zero based line number...
3510 // Update the selected line if the stop ID changed...
3511 if (context_changed)
3512 m_selected_line = m_pc_line;
3513
3514 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) {
3515 // Same file, nothing to do, we should either have the lines or not
3516 // (source file missing)
3517 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
3518 if (m_selected_line >= m_first_visible_line + num_visible_lines)
3519 m_first_visible_line = m_selected_line - 10;
3520 } else {
3521 if (m_selected_line > 10)
3522 m_first_visible_line = m_selected_line - 10;
3523 else
3524 m_first_visible_line = 0;
3525 }
3526 } else {
3527 // File changed, set selected line to the line with the PC
3528 m_selected_line = m_pc_line;
3529 m_file_sp =
3530 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
3531 if (m_file_sp) {
3532 const size_t num_lines = m_file_sp->GetNumLines();
3533 m_line_width = 1;
3534 for (size_t n = num_lines; n >= 10; n = n / 10)
3535 ++m_line_width;
3536
3537 if (num_lines < num_visible_lines ||
3538 m_selected_line < num_visible_lines)
3539 m_first_visible_line = 0;
3540 else
3541 m_first_visible_line = m_selected_line - 10;
3542 }
3543 }
3544 } else {
3545 m_file_sp.reset();
3546 }
3547
3548 if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
3549 // Show disassembly
3550 bool prefer_file_cache = false;
3551 if (m_sc.function) {
3552 if (m_disassembly_scope != m_sc.function) {
3553 m_disassembly_scope = m_sc.function;
3554 m_disassembly_sp = m_sc.function->GetInstructions(
3555 exe_ctx, nullptr, prefer_file_cache);
3556 if (m_disassembly_sp) {
3557 set_selected_line_to_pc = true;
3558 m_disassembly_range = m_sc.function->GetAddressRange();
3559 } else {
3560 m_disassembly_range.Clear();
3561 }
3562 } else {
3563 set_selected_line_to_pc = context_changed;
3564 }
3565 } else if (m_sc.symbol) {
3566 if (m_disassembly_scope != m_sc.symbol) {
3567 m_disassembly_scope = m_sc.symbol;
3568 m_disassembly_sp = m_sc.symbol->GetInstructions(
3569 exe_ctx, nullptr, prefer_file_cache);
3570 if (m_disassembly_sp) {
3571 set_selected_line_to_pc = true;
3572 m_disassembly_range.GetBaseAddress() =
3573 m_sc.symbol->GetAddress();
3574 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
3575 } else {
3576 m_disassembly_range.Clear();
3577 }
3578 } else {
3579 set_selected_line_to_pc = context_changed;
3580 }
3581 }
3582 }
3583 } else {
3584 m_pc_line = UINT32_MAX;
3585 }
3586 }
3587
3588 const int window_width = window.GetWidth();
3589 window.Erase();
3590 window.DrawTitleBox("Sources");
3591 if (!m_title.GetString().empty()) {
3592 window.AttributeOn(A_REVERSE);
3593 window.MoveCursor(1, 1);
3594 window.PutChar(' ');
3595 window.PutCStringTruncated(1, m_title.GetString().str().c_str());
3596 int x = window.GetCursorX();
3597 if (x < window_width - 1) {
3598 window.Printf("%*s", window_width - x - 1, "");
3599 }
3600 window.AttributeOff(A_REVERSE);
3601 }
3602
3603 Target *target = exe_ctx.GetTargetPtr();
3604 const size_t num_source_lines = GetNumSourceLines();
3605 if (num_source_lines > 0) {
3606 // Display source
3607 BreakpointLines bp_lines;
3608 if (target) {
3609 BreakpointList &bp_list = target->GetBreakpointList();
3610 const size_t num_bps = bp_list.GetSize();
3611 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3612 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3613 const size_t num_bps_locs = bp_sp->GetNumLocations();
3614 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
3615 BreakpointLocationSP bp_loc_sp =
3616 bp_sp->GetLocationAtIndex(bp_loc_idx);
3617 LineEntry bp_loc_line_entry;
3618 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
3619 bp_loc_line_entry)) {
3620 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
3621 bp_lines.insert(bp_loc_line_entry.line);
3622 }
3623 }
3624 }
3625 }
3626 }
3627
3628 const attr_t selected_highlight_attr = A_REVERSE;
3629 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue);
3630
3631 for (size_t i = 0; i < num_visible_lines; ++i) {
3632 const uint32_t curr_line = m_first_visible_line + i;
3633 if (curr_line < num_source_lines) {
3634 const int line_y = m_min_y + i;
3635 window.MoveCursor(1, line_y);
3636 const bool is_pc_line = curr_line == m_pc_line;
3637 const bool line_is_selected = m_selected_line == curr_line;
3638 // Highlight the line as the PC line first, then if the selected line
3639 // isn't the same as the PC line, highlight it differently
3640 attr_t highlight_attr = 0;
3641 attr_t bp_attr = 0;
3642 if (is_pc_line)
3643 highlight_attr = pc_highlight_attr;
3644 else if (line_is_selected)
3645 highlight_attr = selected_highlight_attr;
3646
3647 if (bp_lines.find(curr_line + 1) != bp_lines.end())
3648 bp_attr = COLOR_PAIR(BlackOnWhite);
3649
3650 if (bp_attr)
3651 window.AttributeOn(bp_attr);
3652
3653 window.Printf(" %*u ", m_line_width, curr_line + 1);
3654
3655 if (bp_attr)
3656 window.AttributeOff(bp_attr);
3657
3658 window.PutChar(ACS_VLINE);
3659 // Mark the line with the PC with a diamond
3660 if (is_pc_line)
3661 window.PutChar(ACS_DIAMOND);
3662 else
3663 window.PutChar(' ');
3664
3665 if (highlight_attr)
3666 window.AttributeOn(highlight_attr);
3667
3668 StreamString lineStream;
3669 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream);
3670 StringRef line = lineStream.GetString();
3671 if (line.endswith("\n"))
3672 line = line.drop_back();
3673 bool wasWritten = window.OutputColoredStringTruncated(
3674 1, line, m_first_visible_column, line_is_selected);
3675 if (line_is_selected && !wasWritten) {
3676 // Draw an empty space to show the selected line if empty,
3677 // or draw '<' if nothing is visible because of scrolling too much
3678 // to the right.
3679 window.PutCStringTruncated(
3680 1, line.empty() && m_first_visible_column == 0 ? " " : "<");
3681 }
3682
3683 if (is_pc_line && frame_sp &&
3684 frame_sp->GetConcreteFrameIndex() == 0) {
3685 StopInfoSP stop_info_sp;
3686 if (thread)
3687 stop_info_sp = thread->GetStopInfo();
3688 if (stop_info_sp) {
3689 const char *stop_description = stop_info_sp->GetDescription();
3690 if (stop_description && stop_description[0]) {
3691 size_t stop_description_len = strlen(stop_description);
3692 int desc_x = window_width - stop_description_len - 16;
3693 if (desc_x - window.GetCursorX() > 0)
3694 window.Printf("%*s", desc_x - window.GetCursorX(), "");
3695 window.MoveCursor(window_width - stop_description_len - 16,
3696 line_y);
3697 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
3698 window.AttributeOn(stop_reason_attr);
3699 window.PrintfTruncated(1, " <<< Thread %u: %s ",
3700 thread->GetIndexID(), stop_description);
3701 window.AttributeOff(stop_reason_attr);
3702 }
3703 } else {
3704 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
3705 }
3706 }
3707 if (highlight_attr)
3708 window.AttributeOff(highlight_attr);
3709 } else {
3710 break;
3711 }
3712 }
3713 } else {
3714 size_t num_disassembly_lines = GetNumDisassemblyLines();
3715 if (num_disassembly_lines > 0) {
3716 // Display disassembly
3717 BreakpointAddrs bp_file_addrs;
3718 Target *target = exe_ctx.GetTargetPtr();
3719 if (target) {
3720 BreakpointList &bp_list = target->GetBreakpointList();
3721 const size_t num_bps = bp_list.GetSize();
3722 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3723 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3724 const size_t num_bps_locs = bp_sp->GetNumLocations();
3725 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
3726 ++bp_loc_idx) {
3727 BreakpointLocationSP bp_loc_sp =
3728 bp_sp->GetLocationAtIndex(bp_loc_idx);
3729 LineEntry bp_loc_line_entry;
3730 const lldb::addr_t file_addr =
3731 bp_loc_sp->GetAddress().GetFileAddress();
3732 if (file_addr != LLDB_INVALID_ADDRESS) {
3733 if (m_disassembly_range.ContainsFileAddress(file_addr))
3734 bp_file_addrs.insert(file_addr);
3735 }
3736 }
3737 }
3738 }
3739
3740 const attr_t selected_highlight_attr = A_REVERSE;
3741 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
3742
3743 StreamString strm;
3744
3745 InstructionList &insts = m_disassembly_sp->GetInstructionList();
3746 Address pc_address;
3747
3748 if (frame_sp)
3749 pc_address = frame_sp->GetFrameCodeAddress();
3750 const uint32_t pc_idx =
3751 pc_address.IsValid()
3752 ? insts.GetIndexOfInstructionAtAddress(pc_address)
3753 : UINT32_MAX;
3754 if (set_selected_line_to_pc) {
3755 m_selected_line = pc_idx;
3756 }
3757
3758 const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
3759 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
3760 m_first_visible_line = 0;
3761
3762 if (pc_idx < num_disassembly_lines) {
3763 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
3764 pc_idx >= m_first_visible_line + num_visible_lines)
3765 m_first_visible_line = pc_idx - non_visible_pc_offset;
3766 }
3767
3768 for (size_t i = 0; i < num_visible_lines; ++i) {
3769 const uint32_t inst_idx = m_first_visible_line + i;
3770 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
3771 if (!inst)
3772 break;
3773
3774 const int line_y = m_min_y + i;
3775 window.MoveCursor(1, line_y);
3776 const bool is_pc_line = frame_sp && inst_idx == pc_idx;
3777 const bool line_is_selected = m_selected_line == inst_idx;
3778 // Highlight the line as the PC line first, then if the selected line
3779 // isn't the same as the PC line, highlight it differently
3780 attr_t highlight_attr = 0;
3781 attr_t bp_attr = 0;
3782 if (is_pc_line)
3783 highlight_attr = pc_highlight_attr;
3784 else if (line_is_selected)
3785 highlight_attr = selected_highlight_attr;
3786
3787 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
3788 bp_file_addrs.end())
3789 bp_attr = COLOR_PAIR(BlackOnWhite);
3790
3791 if (bp_attr)
3792 window.AttributeOn(bp_attr);
3793
3794 window.Printf(" 0x%16.16llx ",
3795 static_cast<unsigned long long>(
3796 inst->GetAddress().GetLoadAddress(target)));
3797
3798 if (bp_attr)
3799 window.AttributeOff(bp_attr);
3800
3801 window.PutChar(ACS_VLINE);
3802 // Mark the line with the PC with a diamond
3803 if (is_pc_line)
3804 window.PutChar(ACS_DIAMOND);
3805 else
3806 window.PutChar(' ');
3807
3808 if (highlight_attr)
3809 window.AttributeOn(highlight_attr);
3810
3811 const char *mnemonic = inst->GetMnemonic(&exe_ctx);
3812 const char *operands = inst->GetOperands(&exe_ctx);
3813 const char *comment = inst->GetComment(&exe_ctx);
3814
3815 if (mnemonic != nullptr && mnemonic[0] == '\0')
3816 mnemonic = nullptr;
3817 if (operands != nullptr && operands[0] == '\0')
3818 operands = nullptr;
3819 if (comment != nullptr && comment[0] == '\0')
3820 comment = nullptr;
3821
3822 strm.Clear();
3823
3824 if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
3825 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
3826 else if (mnemonic != nullptr && operands != nullptr)
3827 strm.Printf("%-8s %s", mnemonic, operands);
3828 else if (mnemonic != nullptr)
3829 strm.Printf("%s", mnemonic);
3830
3831 int right_pad = 1;
3832 window.PutCStringTruncated(
3833 right_pad,
3834 strm.GetString().substr(m_first_visible_column).data());
3835
3836 if (is_pc_line && frame_sp &&
3837 frame_sp->GetConcreteFrameIndex() == 0) {
3838 StopInfoSP stop_info_sp;
3839 if (thread)
3840 stop_info_sp = thread->GetStopInfo();
3841 if (stop_info_sp) {
3842 const char *stop_description = stop_info_sp->GetDescription();
3843 if (stop_description && stop_description[0]) {
3844 size_t stop_description_len = strlen(stop_description);
3845 int desc_x = window_width - stop_description_len - 16;
3846 if (desc_x - window.GetCursorX() > 0)
3847 window.Printf("%*s", desc_x - window.GetCursorX(), "");
3848 window.MoveCursor(window_width - stop_description_len - 15,
3849 line_y);
3850 window.PrintfTruncated(1, "<<< Thread %u: %s ",
3851 thread->GetIndexID(), stop_description);
3852 }
3853 } else {
3854 window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
3855 }
3856 }
3857 if (highlight_attr)
3858 window.AttributeOff(highlight_attr);
3859 }
3860 }
3861 }
3862 return true; // Drawing handled
3863 }
3864
GetNumLines()3865 size_t GetNumLines() {
3866 size_t num_lines = GetNumSourceLines();
3867 if (num_lines == 0)
3868 num_lines = GetNumDisassemblyLines();
3869 return num_lines;
3870 }
3871
GetNumSourceLines() const3872 size_t GetNumSourceLines() const {
3873 if (m_file_sp)
3874 return m_file_sp->GetNumLines();
3875 return 0;
3876 }
3877
GetNumDisassemblyLines() const3878 size_t GetNumDisassemblyLines() const {
3879 if (m_disassembly_sp)
3880 return m_disassembly_sp->GetInstructionList().GetSize();
3881 return 0;
3882 }
3883
WindowDelegateHandleChar(Window & window,int c)3884 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
3885 const uint32_t num_visible_lines = NumVisibleLines();
3886 const size_t num_lines = GetNumLines();
3887
3888 switch (c) {
3889 case ',':
3890 case KEY_PPAGE:
3891 // Page up key
3892 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
3893 m_first_visible_line -= num_visible_lines;
3894 else
3895 m_first_visible_line = 0;
3896 m_selected_line = m_first_visible_line;
3897 return eKeyHandled;
3898
3899 case '.':
3900 case KEY_NPAGE:
3901 // Page down key
3902 {
3903 if (m_first_visible_line + num_visible_lines < num_lines)
3904 m_first_visible_line += num_visible_lines;
3905 else if (num_lines < num_visible_lines)
3906 m_first_visible_line = 0;
3907 else
3908 m_first_visible_line = num_lines - num_visible_lines;
3909 m_selected_line = m_first_visible_line;
3910 }
3911 return eKeyHandled;
3912
3913 case KEY_UP:
3914 if (m_selected_line > 0) {
3915 m_selected_line--;
3916 if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
3917 m_first_visible_line = m_selected_line;
3918 }
3919 return eKeyHandled;
3920
3921 case KEY_DOWN:
3922 if (m_selected_line + 1 < num_lines) {
3923 m_selected_line++;
3924 if (m_first_visible_line + num_visible_lines < m_selected_line)
3925 m_first_visible_line++;
3926 }
3927 return eKeyHandled;
3928
3929 case KEY_LEFT:
3930 if (m_first_visible_column > 0)
3931 --m_first_visible_column;
3932 return eKeyHandled;
3933
3934 case KEY_RIGHT:
3935 ++m_first_visible_column;
3936 return eKeyHandled;
3937
3938 case '\r':
3939 case '\n':
3940 case KEY_ENTER:
3941 // Set a breakpoint and run to the line using a one shot breakpoint
3942 if (GetNumSourceLines() > 0) {
3943 ExecutionContext exe_ctx =
3944 m_debugger.GetCommandInterpreter().GetExecutionContext();
3945 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
3946 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
3947 nullptr, // Don't limit the breakpoint to certain modules
3948 m_file_sp->GetFileSpec(), // Source file
3949 m_selected_line +
3950 1, // Source line number (m_selected_line is zero based)
3951 0, // Unspecified column.
3952 0, // No offset
3953 eLazyBoolCalculate, // Check inlines using global setting
3954 eLazyBoolCalculate, // Skip prologue using global setting,
3955 false, // internal
3956 false, // request_hardware
3957 eLazyBoolCalculate); // move_to_nearest_code
3958 // Make breakpoint one shot
3959 bp_sp->GetOptions()->SetOneShot(true);
3960 exe_ctx.GetProcessRef().Resume();
3961 }
3962 } else if (m_selected_line < GetNumDisassemblyLines()) {
3963 const Instruction *inst = m_disassembly_sp->GetInstructionList()
3964 .GetInstructionAtIndex(m_selected_line)
3965 .get();
3966 ExecutionContext exe_ctx =
3967 m_debugger.GetCommandInterpreter().GetExecutionContext();
3968 if (exe_ctx.HasTargetScope()) {
3969 Address addr = inst->GetAddress();
3970 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
3971 addr, // lldb_private::Address
3972 false, // internal
3973 false); // request_hardware
3974 // Make breakpoint one shot
3975 bp_sp->GetOptions()->SetOneShot(true);
3976 exe_ctx.GetProcessRef().Resume();
3977 }
3978 }
3979 return eKeyHandled;
3980
3981 case 'b': // 'b' == toggle breakpoint on currently selected line
3982 ToggleBreakpointOnSelectedLine();
3983 return eKeyHandled;
3984
3985 case 'D': // 'D' == detach and keep stopped
3986 {
3987 ExecutionContext exe_ctx =
3988 m_debugger.GetCommandInterpreter().GetExecutionContext();
3989 if (exe_ctx.HasProcessScope())
3990 exe_ctx.GetProcessRef().Detach(true);
3991 }
3992 return eKeyHandled;
3993
3994 case 'c':
3995 // 'c' == continue
3996 {
3997 ExecutionContext exe_ctx =
3998 m_debugger.GetCommandInterpreter().GetExecutionContext();
3999 if (exe_ctx.HasProcessScope())
4000 exe_ctx.GetProcessRef().Resume();
4001 }
4002 return eKeyHandled;
4003
4004 case 'f':
4005 // 'f' == step out (finish)
4006 {
4007 ExecutionContext exe_ctx =
4008 m_debugger.GetCommandInterpreter().GetExecutionContext();
4009 if (exe_ctx.HasThreadScope() &&
4010 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4011 exe_ctx.GetThreadRef().StepOut();
4012 }
4013 }
4014 return eKeyHandled;
4015
4016 case 'n': // 'n' == step over
4017 case 'N': // 'N' == step over instruction
4018 {
4019 ExecutionContext exe_ctx =
4020 m_debugger.GetCommandInterpreter().GetExecutionContext();
4021 if (exe_ctx.HasThreadScope() &&
4022 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4023 bool source_step = (c == 'n');
4024 exe_ctx.GetThreadRef().StepOver(source_step);
4025 }
4026 }
4027 return eKeyHandled;
4028
4029 case 's': // 's' == step into
4030 case 'S': // 'S' == step into instruction
4031 {
4032 ExecutionContext exe_ctx =
4033 m_debugger.GetCommandInterpreter().GetExecutionContext();
4034 if (exe_ctx.HasThreadScope() &&
4035 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4036 bool source_step = (c == 's');
4037 exe_ctx.GetThreadRef().StepIn(source_step);
4038 }
4039 }
4040 return eKeyHandled;
4041
4042 case 'u': // 'u' == frame up
4043 case 'd': // 'd' == frame down
4044 {
4045 ExecutionContext exe_ctx =
4046 m_debugger.GetCommandInterpreter().GetExecutionContext();
4047 if (exe_ctx.HasThreadScope()) {
4048 Thread *thread = exe_ctx.GetThreadPtr();
4049 uint32_t frame_idx = thread->GetSelectedFrameIndex();
4050 if (frame_idx == UINT32_MAX)
4051 frame_idx = 0;
4052 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
4053 ++frame_idx;
4054 else if (c == 'd' && frame_idx > 0)
4055 --frame_idx;
4056 if (thread->SetSelectedFrameByIndex(frame_idx, true))
4057 exe_ctx.SetFrameSP(thread->GetSelectedFrame());
4058 }
4059 }
4060 return eKeyHandled;
4061
4062 case 'h':
4063 window.CreateHelpSubwindow();
4064 return eKeyHandled;
4065
4066 default:
4067 break;
4068 }
4069 return eKeyNotHandled;
4070 }
4071
ToggleBreakpointOnSelectedLine()4072 void ToggleBreakpointOnSelectedLine() {
4073 ExecutionContext exe_ctx =
4074 m_debugger.GetCommandInterpreter().GetExecutionContext();
4075 if (!exe_ctx.HasTargetScope())
4076 return;
4077 if (GetNumSourceLines() > 0) {
4078 // Source file breakpoint.
4079 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
4080 const size_t num_bps = bp_list.GetSize();
4081 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
4082 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
4083 const size_t num_bps_locs = bp_sp->GetNumLocations();
4084 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
4085 BreakpointLocationSP bp_loc_sp =
4086 bp_sp->GetLocationAtIndex(bp_loc_idx);
4087 LineEntry bp_loc_line_entry;
4088 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
4089 bp_loc_line_entry)) {
4090 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file &&
4091 m_selected_line + 1 == bp_loc_line_entry.line) {
4092 bool removed =
4093 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
4094 assert(removed);
4095 UNUSED_IF_ASSERT_DISABLED(removed);
4096 return; // Existing breakpoint removed.
4097 }
4098 }
4099 }
4100 }
4101 // No breakpoint found on the location, add it.
4102 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
4103 nullptr, // Don't limit the breakpoint to certain modules
4104 m_file_sp->GetFileSpec(), // Source file
4105 m_selected_line +
4106 1, // Source line number (m_selected_line is zero based)
4107 0, // No column specified.
4108 0, // No offset
4109 eLazyBoolCalculate, // Check inlines using global setting
4110 eLazyBoolCalculate, // Skip prologue using global setting,
4111 false, // internal
4112 false, // request_hardware
4113 eLazyBoolCalculate); // move_to_nearest_code
4114 } else {
4115 // Disassembly breakpoint.
4116 assert(GetNumDisassemblyLines() > 0);
4117 assert(m_selected_line < GetNumDisassemblyLines());
4118 const Instruction *inst = m_disassembly_sp->GetInstructionList()
4119 .GetInstructionAtIndex(m_selected_line)
4120 .get();
4121 Address addr = inst->GetAddress();
4122 // Try to find it.
4123 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
4124 const size_t num_bps = bp_list.GetSize();
4125 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
4126 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
4127 const size_t num_bps_locs = bp_sp->GetNumLocations();
4128 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
4129 BreakpointLocationSP bp_loc_sp =
4130 bp_sp->GetLocationAtIndex(bp_loc_idx);
4131 LineEntry bp_loc_line_entry;
4132 const lldb::addr_t file_addr =
4133 bp_loc_sp->GetAddress().GetFileAddress();
4134 if (file_addr == addr.GetFileAddress()) {
4135 bool removed =
4136 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
4137 assert(removed);
4138 UNUSED_IF_ASSERT_DISABLED(removed);
4139 return; // Existing breakpoint removed.
4140 }
4141 }
4142 }
4143 // No breakpoint found on the address, add it.
4144 BreakpointSP bp_sp =
4145 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
4146 false, // internal
4147 false); // request_hardware
4148 }
4149 }
4150
4151 protected:
4152 typedef std::set<uint32_t> BreakpointLines;
4153 typedef std::set<lldb::addr_t> BreakpointAddrs;
4154
4155 Debugger &m_debugger;
4156 SymbolContext m_sc;
4157 SourceManager::FileSP m_file_sp;
4158 SymbolContextScope *m_disassembly_scope;
4159 lldb::DisassemblerSP m_disassembly_sp;
4160 AddressRange m_disassembly_range;
4161 StreamString m_title;
4162 lldb::user_id_t m_tid;
4163 int m_line_width;
4164 uint32_t m_selected_line; // The selected line
4165 uint32_t m_pc_line; // The line with the PC
4166 uint32_t m_stop_id;
4167 uint32_t m_frame_idx;
4168 int m_first_visible_line;
4169 int m_first_visible_column;
4170 int m_min_x;
4171 int m_min_y;
4172 int m_max_x;
4173 int m_max_y;
4174 };
4175
4176 DisplayOptions ValueObjectListDelegate::g_options = {true};
4177
IOHandlerCursesGUI(Debugger & debugger)4178 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
4179 : IOHandler(debugger, IOHandler::Type::Curses) {}
4180
Activate()4181 void IOHandlerCursesGUI::Activate() {
4182 IOHandler::Activate();
4183 if (!m_app_ap) {
4184 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
4185
4186 // This is both a window and a menu delegate
4187 std::shared_ptr<ApplicationDelegate> app_delegate_sp(
4188 new ApplicationDelegate(*m_app_ap, m_debugger));
4189
4190 MenuDelegateSP app_menu_delegate_sp =
4191 std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
4192 MenuSP lldb_menu_sp(
4193 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
4194 MenuSP exit_menuitem_sp(
4195 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
4196 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
4197 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
4198 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
4199 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4200 lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
4201
4202 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
4203 ApplicationDelegate::eMenuID_Target));
4204 target_menu_sp->AddSubmenu(MenuSP(new Menu(
4205 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
4206 target_menu_sp->AddSubmenu(MenuSP(new Menu(
4207 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
4208
4209 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
4210 ApplicationDelegate::eMenuID_Process));
4211 process_menu_sp->AddSubmenu(MenuSP(new Menu(
4212 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
4213 process_menu_sp->AddSubmenu(
4214 MenuSP(new Menu("Detach and resume", nullptr, 'd',
4215 ApplicationDelegate::eMenuID_ProcessDetachResume)));
4216 process_menu_sp->AddSubmenu(
4217 MenuSP(new Menu("Detach suspended", nullptr, 's',
4218 ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
4219 process_menu_sp->AddSubmenu(MenuSP(new Menu(
4220 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
4221 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4222 process_menu_sp->AddSubmenu(
4223 MenuSP(new Menu("Continue", nullptr, 'c',
4224 ApplicationDelegate::eMenuID_ProcessContinue)));
4225 process_menu_sp->AddSubmenu(MenuSP(new Menu(
4226 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
4227 process_menu_sp->AddSubmenu(MenuSP(new Menu(
4228 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
4229
4230 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
4231 ApplicationDelegate::eMenuID_Thread));
4232 thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4233 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
4234 thread_menu_sp->AddSubmenu(
4235 MenuSP(new Menu("Step Over", nullptr, 'v',
4236 ApplicationDelegate::eMenuID_ThreadStepOver)));
4237 thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4238 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
4239
4240 MenuSP view_menu_sp(
4241 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
4242 view_menu_sp->AddSubmenu(
4243 MenuSP(new Menu("Backtrace", nullptr, 'b',
4244 ApplicationDelegate::eMenuID_ViewBacktrace)));
4245 view_menu_sp->AddSubmenu(
4246 MenuSP(new Menu("Registers", nullptr, 'r',
4247 ApplicationDelegate::eMenuID_ViewRegisters)));
4248 view_menu_sp->AddSubmenu(MenuSP(new Menu(
4249 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
4250 view_menu_sp->AddSubmenu(
4251 MenuSP(new Menu("Variables", nullptr, 'v',
4252 ApplicationDelegate::eMenuID_ViewVariables)));
4253
4254 MenuSP help_menu_sp(
4255 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
4256 help_menu_sp->AddSubmenu(MenuSP(new Menu(
4257 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
4258
4259 m_app_ap->Initialize();
4260 WindowSP &main_window_sp = m_app_ap->GetMainWindow();
4261
4262 MenuSP menubar_sp(new Menu(Menu::Type::Bar));
4263 menubar_sp->AddSubmenu(lldb_menu_sp);
4264 menubar_sp->AddSubmenu(target_menu_sp);
4265 menubar_sp->AddSubmenu(process_menu_sp);
4266 menubar_sp->AddSubmenu(thread_menu_sp);
4267 menubar_sp->AddSubmenu(view_menu_sp);
4268 menubar_sp->AddSubmenu(help_menu_sp);
4269 menubar_sp->SetDelegate(app_menu_delegate_sp);
4270
4271 Rect content_bounds = main_window_sp->GetFrame();
4272 Rect menubar_bounds = content_bounds.MakeMenuBar();
4273 Rect status_bounds = content_bounds.MakeStatusBar();
4274 Rect source_bounds;
4275 Rect variables_bounds;
4276 Rect threads_bounds;
4277 Rect source_variables_bounds;
4278 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4279 threads_bounds);
4280 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
4281 variables_bounds);
4282
4283 WindowSP menubar_window_sp =
4284 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
4285 // Let the menubar get keys if the active window doesn't handle the keys
4286 // that are typed so it can respond to menubar key presses.
4287 menubar_window_sp->SetCanBeActive(
4288 false); // Don't let the menubar become the active window
4289 menubar_window_sp->SetDelegate(menubar_sp);
4290
4291 WindowSP source_window_sp(
4292 main_window_sp->CreateSubWindow("Source", source_bounds, true));
4293 WindowSP variables_window_sp(
4294 main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
4295 WindowSP threads_window_sp(
4296 main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
4297 WindowSP status_window_sp(
4298 main_window_sp->CreateSubWindow("Status", status_bounds, false));
4299 status_window_sp->SetCanBeActive(
4300 false); // Don't let the status bar become the active window
4301 main_window_sp->SetDelegate(
4302 std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
4303 source_window_sp->SetDelegate(
4304 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
4305 variables_window_sp->SetDelegate(
4306 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
4307 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
4308 threads_window_sp->SetDelegate(WindowDelegateSP(
4309 new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
4310 status_window_sp->SetDelegate(
4311 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
4312
4313 // Show the main help window once the first time the curses GUI is launched
4314 static bool g_showed_help = false;
4315 if (!g_showed_help) {
4316 g_showed_help = true;
4317 main_window_sp->CreateHelpSubwindow();
4318 }
4319
4320 // All colors with black background.
4321 init_pair(1, COLOR_BLACK, COLOR_BLACK);
4322 init_pair(2, COLOR_RED, COLOR_BLACK);
4323 init_pair(3, COLOR_GREEN, COLOR_BLACK);
4324 init_pair(4, COLOR_YELLOW, COLOR_BLACK);
4325 init_pair(5, COLOR_BLUE, COLOR_BLACK);
4326 init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
4327 init_pair(7, COLOR_CYAN, COLOR_BLACK);
4328 init_pair(8, COLOR_WHITE, COLOR_BLACK);
4329 // All colors with blue background.
4330 init_pair(9, COLOR_BLACK, COLOR_BLUE);
4331 init_pair(10, COLOR_RED, COLOR_BLUE);
4332 init_pair(11, COLOR_GREEN, COLOR_BLUE);
4333 init_pair(12, COLOR_YELLOW, COLOR_BLUE);
4334 init_pair(13, COLOR_BLUE, COLOR_BLUE);
4335 init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
4336 init_pair(15, COLOR_CYAN, COLOR_BLUE);
4337 init_pair(16, COLOR_WHITE, COLOR_BLUE);
4338 // These must match the order in the color indexes enum.
4339 init_pair(17, COLOR_BLACK, COLOR_WHITE);
4340 init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
4341 static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
4342 }
4343 }
4344
Deactivate()4345 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
4346
Run()4347 void IOHandlerCursesGUI::Run() {
4348 m_app_ap->Run(m_debugger);
4349 SetIsDone(true);
4350 }
4351
4352 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
4353
Cancel()4354 void IOHandlerCursesGUI::Cancel() {}
4355
Interrupt()4356 bool IOHandlerCursesGUI::Interrupt() { return false; }
4357
GotEOF()4358 void IOHandlerCursesGUI::GotEOF() {}
4359
TerminalSizeChanged()4360 void IOHandlerCursesGUI::TerminalSizeChanged() {
4361 m_app_ap->TerminalSizeChanged();
4362 }
4363
4364 #endif // LLDB_ENABLE_CURSES
4365