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/Core/ValueObjectUpdater.h"
30#include "lldb/Host/File.h"
31#include "lldb/Utility/Predicate.h"
32#include "lldb/Utility/Status.h"
33#include "lldb/Utility/StreamString.h"
34#include "lldb/Utility/StringList.h"
35#include "lldb/lldb-forward.h"
36
37#include "lldb/Interpreter/CommandCompletions.h"
38#include "lldb/Interpreter/CommandInterpreter.h"
39
40#if LLDB_ENABLE_CURSES
41#include "lldb/Breakpoint/BreakpointLocation.h"
42#include "lldb/Core/Module.h"
43#include "lldb/Core/ValueObject.h"
44#include "lldb/Core/ValueObjectRegister.h"
45#include "lldb/Symbol/Block.h"
46#include "lldb/Symbol/Function.h"
47#include "lldb/Symbol/Symbol.h"
48#include "lldb/Symbol/VariableList.h"
49#include "lldb/Target/Process.h"
50#include "lldb/Target/RegisterContext.h"
51#include "lldb/Target/StackFrame.h"
52#include "lldb/Target/StopInfo.h"
53#include "lldb/Target/Target.h"
54#include "lldb/Target/Thread.h"
55#include "lldb/Utility/State.h"
56#endif
57
58#include "llvm/ADT/StringRef.h"
59
60#ifdef _WIN32
61#include "lldb/Host/windows/windows.h"
62#endif
63
64#include <memory>
65#include <mutex>
66
67#include <assert.h>
68#include <ctype.h>
69#include <errno.h>
70#include <locale.h>
71#include <stdint.h>
72#include <stdio.h>
73#include <string.h>
74#include <type_traits>
75
76using namespace lldb;
77using namespace lldb_private;
78using llvm::None;
79using llvm::Optional;
80using llvm::StringRef;
81
82// we may want curses to be disabled for some builds for instance, windows
83#if LLDB_ENABLE_CURSES
84
85#define KEY_RETURN 10
86#define KEY_ESCAPE 27
87
88namespace curses {
89class Menu;
90class MenuDelegate;
91class Window;
92class WindowDelegate;
93typedef std::shared_ptr<Menu> MenuSP;
94typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
95typedef std::shared_ptr<Window> WindowSP;
96typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
97typedef std::vector<MenuSP> Menus;
98typedef std::vector<WindowSP> Windows;
99typedef std::vector<WindowDelegateSP> WindowDelegates;
100
101#if 0
102type summary add -s "x=${var.x}, y=${var.y}" curses::Point
103type summary add -s "w=${var.width}, h=${var.height}" curses::Size
104type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
105#endif
106
107struct Point {
108 int x;
109 int y;
110
111 Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
112
113 void Clear() {
114 x = 0;
115 y = 0;
116 }
117
118 Point &operator+=(const Point &rhs) {
119 x += rhs.x;
120 y += rhs.y;
121 return *this;
122 }
123
124 void Dump() { printf("(x=%i, y=%i)\n", x, y); }
125};
126
127bool operator==(const Point &lhs, const Point &rhs) {
128 return lhs.x == rhs.x && lhs.y == rhs.y;
129}
130
131bool operator!=(const Point &lhs, const Point &rhs) {
132 return lhs.x != rhs.x || lhs.y != rhs.y;
133}
134
135struct Size {
136 int width;
137 int height;
138 Size(int w = 0, int h = 0) : width(w), height(h) {}
139
140 void Clear() {
141 width = 0;
142 height = 0;
143 }
144
145 void Dump() { printf("(w=%i, h=%i)\n", width, height); }
146};
147
148bool operator==(const Size &lhs, const Size &rhs) {
149 return lhs.width == rhs.width && lhs.height == rhs.height;
150}
151
152bool operator!=(const Size &lhs, const Size &rhs) {
153 return lhs.width != rhs.width || lhs.height != rhs.height;
154}
155
156struct Rect {
157 Point origin;
158 Size size;
159
160 Rect() : origin(), size() {}
161
162 Rect(const Point &p, const Size &s) : origin(p), size(s) {}
163
164 void Clear() {
165 origin.Clear();
166 size.Clear();
167 }
168
169 void Dump() {
170 printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
171 size.height);
172 }
173
174 void Inset(int w, int h) {
175 if (size.width > w * 2)
176 size.width -= w * 2;
177 origin.x += w;
178
179 if (size.height > h * 2)
180 size.height -= h * 2;
181 origin.y += h;
182 }
183
184 // Return a status bar rectangle which is the last line of this rectangle.
185 // This rectangle will be modified to not include the status bar area.
186 Rect MakeStatusBar() {
187 Rect status_bar;
188 if (size.height > 1) {
189 status_bar.origin.x = origin.x;
190 status_bar.origin.y = size.height;
191 status_bar.size.width = size.width;
192 status_bar.size.height = 1;
193 --size.height;
194 }
195 return status_bar;
196 }
197
198 // Return a menubar rectangle which is the first line of this rectangle. This
199 // rectangle will be modified to not include the menubar area.
200 Rect MakeMenuBar() {
201 Rect menubar;
202 if (size.height > 1) {
203 menubar.origin.x = origin.x;
204 menubar.origin.y = origin.y;
205 menubar.size.width = size.width;
206 menubar.size.height = 1;
207 ++origin.y;
208 --size.height;
209 }
210 return menubar;
211 }
212
213 void HorizontalSplitPercentage(float top_percentage, Rect &top,
214 Rect &bottom) const {
215 float top_height = top_percentage * size.height;
216 HorizontalSplit(top_height, top, bottom);
217 }
218
219 void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
220 top = *this;
221 if (top_height < size.height) {
222 top.size.height = top_height;
223 bottom.origin.x = origin.x;
224 bottom.origin.y = origin.y + top.size.height;
225 bottom.size.width = size.width;
226 bottom.size.height = size.height - top.size.height;
227 } else {
228 bottom.Clear();
229 }
230 }
231
232 void VerticalSplitPercentage(float left_percentage, Rect &left,
233 Rect &right) const {
234 float left_width = left_percentage * size.width;
235 VerticalSplit(left_width, left, right);
236 }
237
238 void VerticalSplit(int left_width, Rect &left, Rect &right) const {
239 left = *this;
240 if (left_width < size.width) {
241 left.size.width = left_width;
242 right.origin.x = origin.x + left.size.width;
243 right.origin.y = origin.y;
244 right.size.width = size.width - left.size.width;
245 right.size.height = size.height;
246 } else {
247 right.Clear();
248 }
249 }
250};
251
252bool operator==(const Rect &lhs, const Rect &rhs) {
253 return lhs.origin == rhs.origin && lhs.size == rhs.size;
254}
255
256bool operator!=(const Rect &lhs, const Rect &rhs) {
257 return lhs.origin != rhs.origin || lhs.size != rhs.size;
258}
259
260enum HandleCharResult {
261 eKeyNotHandled = 0,
262 eKeyHandled = 1,
263 eQuitApplication = 2
264};
265
266enum class MenuActionResult {
267 Handled,
268 NotHandled,
269 Quit // Exit all menus and quit
270};
271
272struct KeyHelp {
273 int ch;
274 const char *description;
275};
276
277// COLOR_PAIR index names
278enum {
279 // First 16 colors are 8 black background and 8 blue background colors,
280 // needed by OutputColoredStringTruncated().
281 BlackOnBlack = 1,
282 RedOnBlack,
283 GreenOnBlack,
284 YellowOnBlack,
285 BlueOnBlack,
286 MagentaOnBlack,
287 CyanOnBlack,
288 WhiteOnBlack,
289 BlackOnBlue,
290 RedOnBlue,
291 GreenOnBlue,
292 YellowOnBlue,
293 BlueOnBlue,
294 MagentaOnBlue,
295 CyanOnBlue,
296 WhiteOnBlue,
297 // Other colors, as needed.
298 BlackOnWhite,
299 MagentaOnWhite,
300 LastColorPairIndex = MagentaOnWhite
301};
302
303class WindowDelegate {
304public:
305 virtual ~WindowDelegate() = default;
306
307 virtual bool WindowDelegateDraw(Window &window, bool force) {
308 return false; // Drawing not handled
309 }
310
311 virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
312 return eKeyNotHandled;
313 }
314
315 virtual const char *WindowDelegateGetHelpText() { return nullptr; }
316
317 virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
318};
319
320class HelpDialogDelegate : public WindowDelegate {
321public:
322 HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
323
324 ~HelpDialogDelegate() override;
325
326 bool WindowDelegateDraw(Window &window, bool force) override;
327
328 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
329
330 size_t GetNumLines() const { return m_text.GetSize(); }
331
332 size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
333
334protected:
335 StringList m_text;
336 int m_first_visible_line;
337};
338
339class Window {
340public:
341 Window(const char *name)
342 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
343 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
344 m_prev_active_window_idx(UINT32_MAX), m_delete(false),
345 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
346
347 Window(const char *name, WINDOW *w, bool del = true)
348 : m_name(name), m_window(nullptr), m_panel(nullptr), m_parent(nullptr),
349 m_subwindows(), m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
350 m_prev_active_window_idx(UINT32_MAX), m_delete(del),
351 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
352 if (w)
353 Reset(w);
354 }
355
356 Window(const char *name, const Rect &bounds)
357 : m_name(name), m_window(nullptr), m_parent(nullptr), m_subwindows(),
358 m_delegate_sp(), m_curr_active_window_idx(UINT32_MAX),
359 m_prev_active_window_idx(UINT32_MAX), m_delete(true),
360 m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
361 Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
362 bounds.origin.y));
363 }
364
365 virtual ~Window() {
366 RemoveSubWindows();
367 Reset();
368 }
369
370 void Reset(WINDOW *w = nullptr, bool del = true) {
371 if (m_window == w)
372 return;
373
374 if (m_panel) {
375 ::del_panel(m_panel);
376 m_panel = nullptr;
377 }
378 if (m_window && m_delete) {
379 ::delwin(m_window);
380 m_window = nullptr;
381 m_delete = false;
382 }
383 if (w) {
384 m_window = w;
385 m_panel = ::new_panel(m_window);
386 m_delete = del;
387 }
388 }
389
390 void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
391 void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
392 void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
393 ::box(m_window, v_char, h_char);
394 }
395 void Clear() { ::wclear(m_window); }
396 void Erase() { ::werase(m_window); }
397 Rect GetBounds() const {
398 return Rect(GetParentOrigin(), GetSize());
399 } // Get the rectangle in our parent window
400 int GetChar() { return ::wgetch(m_window); }
401 int GetCursorX() const { return getcurx(m_window); }
402 int GetCursorY() const { return getcury(m_window); }
403 Rect GetFrame() const {
404 return Rect(Point(), GetSize());
405 } // Get our rectangle in our own coordinate system
406 Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
407 Size GetSize() const { return Size(GetWidth(), GetHeight()); }
408 int GetParentX() const { return getparx(m_window); }
409 int GetParentY() const { return getpary(m_window); }
410 int GetMaxX() const { return getmaxx(m_window); }
411 int GetMaxY() const { return getmaxy(m_window); }
412 int GetWidth() const { return GetMaxX(); }
413 int GetHeight() const { return GetMaxY(); }
414 void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
415 void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
416 void Resize(int w, int h) { ::wresize(m_window, h, w); }
417 void Resize(const Size &size) {
418 ::wresize(m_window, size.height, size.width);
419 }
420 void PutChar(int ch) { ::waddch(m_window, ch); }
421 void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
422 void SetBackground(int color_pair_idx) {
423 ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
424 }
425
426 void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
427 int bytes_left = GetWidth() - GetCursorX();
428 if (bytes_left > right_pad) {
429 bytes_left -= right_pad;
430 ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
431 }
432 }
433
434 void MoveWindow(const Point &origin) {
435 const bool moving_window = origin != GetParentOrigin();
436 if (m_is_subwin && moving_window) {
437 // Can't move subwindows, must delete and re-create
438 Size size = GetSize();
439 Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
440 origin.x),
441 true);
442 } else {
443 ::mvwin(m_window, origin.y, origin.x);
444 }
445 }
446
447 void SetBounds(const Rect &bounds) {
448 const bool moving_window = bounds.origin != GetParentOrigin();
449 if (m_is_subwin && moving_window) {
450 // Can't move subwindows, must delete and re-create
451 Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
452 bounds.origin.y, bounds.origin.x),
453 true);
454 } else {
455 if (moving_window)
456 MoveWindow(bounds.origin);
457 Resize(bounds.size);
458 }
459 }
460
461 void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
462 va_list args;
463 va_start(args, format);
464 vwprintw(m_window, format, args);
465 va_end(args);
466 }
467
468 void PrintfTruncated(int right_pad, const char *format, ...)
469 __attribute__((format(printf, 3, 4))) {
470 va_list args;
471 va_start(args, format);
472 StreamString strm;
473 strm.PrintfVarArg(format, args);
474 va_end(args);
475 PutCStringTruncated(right_pad, strm.GetData());
476 }
477
478 size_t LimitLengthToRestOfLine(size_t length) const {
479 return std::min<size_t>(length, std::max(0, GetWidth() - GetCursorX() - 1));
480 }
481
482 // Curses doesn't allow direct output of color escape sequences, but that's
483 // how we get source lines from the Highligher class. Read the line and
484 // convert color escape sequences to curses color attributes. Use
485 // first_skip_count to skip leading visible characters. Returns false if all
486 // visible characters were skipped due to first_skip_count.
487 bool OutputColoredStringTruncated(int right_pad, StringRef string,
488 size_t skip_first_count,
489 bool use_blue_background) {
490 attr_t saved_attr;
491 short saved_pair;
492 bool result = false;
493 wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
494 if (use_blue_background)
495 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
496 while (!string.empty()) {
497 size_t esc_pos = string.find('\x1b');
498 if (esc_pos == StringRef::npos) {
499 string = string.substr(skip_first_count);
500 if (!string.empty()) {
501 PutCStringTruncated(right_pad, string.data(), string.size());
502 result = true;
503 }
504 break;
505 }
506 if (esc_pos > 0) {
507 if (skip_first_count > 0) {
508 int skip = std::min(esc_pos, skip_first_count);
509 string = string.substr(skip);
510 skip_first_count -= skip;
511 esc_pos -= skip;
512 }
513 if (esc_pos > 0) {
514 PutCStringTruncated(right_pad, string.data(), esc_pos);
515 result = true;
516 string = string.drop_front(esc_pos);
517 }
518 }
519 bool consumed = string.consume_front("\x1b");
520 assert(consumed);
521 UNUSED_IF_ASSERT_DISABLED(consumed);
522 // This is written to match our Highlighter classes, which seem to
523 // generate only foreground color escape sequences. If necessary, this
524 // will need to be extended.
525 if (!string.consume_front("[")) {
526 llvm::errs() << "Missing '[' in color escape sequence.\n";
527 continue;
528 }
529 // Only 8 basic foreground colors and reset, our Highlighter doesn't use
530 // anything else.
531 int value;
532 if (!!string.consumeInteger(10, value) || // Returns false on success.
533 !(value == 0 || (value >= 30 && value <= 37))) {
534 llvm::errs() << "No valid color code in color escape sequence.\n";
535 continue;
536 }
537 if (!string.consume_front("m")) {
538 llvm::errs() << "Missing 'm' in color escape sequence.\n";
539 continue;
540 }
541 if (value == 0) { // Reset.
542 wattr_set(m_window, saved_attr, saved_pair, nullptr);
543 if (use_blue_background)
544 ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
545 } else {
546 // Mapped directly to first 16 color pairs (black/blue background).
547 ::wattron(m_window,
548 COLOR_PAIR(value - 30 + 1 + (use_blue_background ? 8 : 0)));
549 }
550 }
551 wattr_set(m_window, saved_attr, saved_pair, nullptr);
552 return result;
553 }
554
555 void Touch() {
556 ::touchwin(m_window);
557 if (m_parent)
558 m_parent->Touch();
559 }
560
561 WindowSP CreateSubWindow(const char *name, const Rect &bounds,
562 bool make_active) {
563 auto get_window = [this, &bounds]() {
564 return m_window
565 ? ::subwin(m_window, bounds.size.height, bounds.size.width,
566 bounds.origin.y, bounds.origin.x)
567 : ::newwin(bounds.size.height, bounds.size.width,
568 bounds.origin.y, bounds.origin.x);
569 };
570 WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
571 subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
572 subwindow_sp->m_parent = this;
573 if (make_active) {
574 m_prev_active_window_idx = m_curr_active_window_idx;
575 m_curr_active_window_idx = m_subwindows.size();
576 }
577 m_subwindows.push_back(subwindow_sp);
578 ::top_panel(subwindow_sp->m_panel);
579 m_needs_update = true;
580 return subwindow_sp;
581 }
582
583 bool RemoveSubWindow(Window *window) {
584 Windows::iterator pos, end = m_subwindows.end();
585 size_t i = 0;
586 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
587 if ((*pos).get() == window) {
588 if (m_prev_active_window_idx == i)
589 m_prev_active_window_idx = UINT32_MAX;
590 else if (m_prev_active_window_idx != UINT32_MAX &&
591 m_prev_active_window_idx > i)
592 --m_prev_active_window_idx;
593
594 if (m_curr_active_window_idx == i)
595 m_curr_active_window_idx = UINT32_MAX;
596 else if (m_curr_active_window_idx != UINT32_MAX &&
597 m_curr_active_window_idx > i)
598 --m_curr_active_window_idx;
599 window->Erase();
600 m_subwindows.erase(pos);
601 m_needs_update = true;
602 if (m_parent)
603 m_parent->Touch();
604 else
605 ::touchwin(stdscr);
606 return true;
607 }
608 }
609 return false;
610 }
611
612 WindowSP FindSubWindow(const char *name) {
613 Windows::iterator pos, end = m_subwindows.end();
614 size_t i = 0;
615 for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
616 if ((*pos)->m_name == name)
617 return *pos;
618 }
619 return WindowSP();
620 }
621
622 void RemoveSubWindows() {
623 m_curr_active_window_idx = UINT32_MAX;
624 m_prev_active_window_idx = UINT32_MAX;
625 for (Windows::iterator pos = m_subwindows.begin();
626 pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
627 (*pos)->Erase();
628 }
629 if (m_parent)
630 m_parent->Touch();
631 else
632 ::touchwin(stdscr);
633 }
634
635 WINDOW *get() { return m_window; }
636
637 operator WINDOW *() { return m_window; }
638
639 // Window drawing utilities
640 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
641 attr_t attr = 0;
642 if (IsActive())
643 attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
644 else
645 attr = 0;
646 if (attr)
647 AttributeOn(attr);
648
649 Box();
650 MoveCursor(3, 0);
651
652 if (title && title[0]) {
653 PutChar('<');
654 PutCString(title);
655 PutChar('>');
656 }
657
658 if (bottom_message && bottom_message[0]) {
659 int bottom_message_length = strlen(bottom_message);
660 int x = GetWidth() - 3 - (bottom_message_length + 2);
661
662 if (x > 0) {
663 MoveCursor(x, GetHeight() - 1);
664 PutChar('[');
665 PutCString(bottom_message);
666 PutChar(']');
667 } else {
668 MoveCursor(1, GetHeight() - 1);
669 PutChar('[');
670 PutCStringTruncated(1, bottom_message);
671 }
672 }
673 if (attr)
674 AttributeOff(attr);
675 }
676
677 virtual void Draw(bool force) {
678 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
679 return;
680
681 for (auto &subwindow_sp : m_subwindows)
682 subwindow_sp->Draw(force);
683 }
684
685 bool CreateHelpSubwindow() {
686 if (m_delegate_sp) {
687 const char *text = m_delegate_sp->WindowDelegateGetHelpText();
688 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
689 if ((text && text[0]) || key_help) {
690 std::unique_ptr<HelpDialogDelegate> help_delegate_up(
691 new HelpDialogDelegate(text, key_help));
692 const size_t num_lines = help_delegate_up->GetNumLines();
693 const size_t max_length = help_delegate_up->GetMaxLineLength();
694 Rect bounds = GetBounds();
695 bounds.Inset(1, 1);
696 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
697 bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
698 bounds.size.width = max_length + 4;
699 } else {
700 if (bounds.size.width > 100) {
701 const int inset_w = bounds.size.width / 4;
702 bounds.origin.x += inset_w;
703 bounds.size.width -= 2 * inset_w;
704 }
705 }
706
707 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
708 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
709 bounds.size.height = num_lines + 2;
710 } else {
711 if (bounds.size.height > 100) {
712 const int inset_h = bounds.size.height / 4;
713 bounds.origin.y += inset_h;
714 bounds.size.height -= 2 * inset_h;
715 }
716 }
717 WindowSP help_window_sp;
718 Window *parent_window = GetParent();
719 if (parent_window)
720 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
721 else
722 help_window_sp = CreateSubWindow("Help", bounds, true);
723 help_window_sp->SetDelegate(
724 WindowDelegateSP(help_delegate_up.release()));
725 return true;
726 }
727 }
728 return false;
729 }
730
731 virtual HandleCharResult HandleChar(int key) {
732 // Always check the active window first
733 HandleCharResult result = eKeyNotHandled;
734 WindowSP active_window_sp = GetActiveWindow();
735 if (active_window_sp) {
736 result = active_window_sp->HandleChar(key);
737 if (result != eKeyNotHandled)
738 return result;
739 }
740
741 if (m_delegate_sp) {
742 result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
743 if (result != eKeyNotHandled)
744 return result;
745 }
746
747 // Then check for any windows that want any keys that weren't handled. This
748 // is typically only for a menubar. Make a copy of the subwindows in case
749 // any HandleChar() functions muck with the subwindows. If we don't do
750 // this, we can crash when iterating over the subwindows.
751 Windows subwindows(m_subwindows);
752 for (auto subwindow_sp : subwindows) {
753 if (!subwindow_sp->m_can_activate) {
754 HandleCharResult result = subwindow_sp->HandleChar(key);
755 if (result != eKeyNotHandled)
756 return result;
757 }
758 }
759
760 return eKeyNotHandled;
761 }
762
763 WindowSP GetActiveWindow() {
764 if (!m_subwindows.empty()) {
765 if (m_curr_active_window_idx >= m_subwindows.size()) {
766 if (m_prev_active_window_idx < m_subwindows.size()) {
767 m_curr_active_window_idx = m_prev_active_window_idx;
768 m_prev_active_window_idx = UINT32_MAX;
769 } else if (IsActive()) {
770 m_prev_active_window_idx = UINT32_MAX;
771 m_curr_active_window_idx = UINT32_MAX;
772
773 // Find first window that wants to be active if this window is active
774 const size_t num_subwindows = m_subwindows.size();
775 for (size_t i = 0; i < num_subwindows; ++i) {
776 if (m_subwindows[i]->GetCanBeActive()) {
777 m_curr_active_window_idx = i;
778 break;
779 }
780 }
781 }
782 }
783
784 if (m_curr_active_window_idx < m_subwindows.size())
785 return m_subwindows[m_curr_active_window_idx];
786 }
787 return WindowSP();
788 }
789
790 bool GetCanBeActive() const { return m_can_activate; }
791
792 void SetCanBeActive(bool b) { m_can_activate = b; }
793
794 void SetDelegate(const WindowDelegateSP &delegate_sp) {
795 m_delegate_sp = delegate_sp;
796 }
797
798 Window *GetParent() const { return m_parent; }
799
800 bool IsActive() const {
801 if (m_parent)
802 return m_parent->GetActiveWindow().get() == this;
803 else
804 return true; // Top level window is always active
805 }
806
807 void SelectNextWindowAsActive() {
808 // Move active focus to next window
809 const int num_subwindows = m_subwindows.size();
810 int start_idx = 0;
811 if (m_curr_active_window_idx != UINT32_MAX) {
812 m_prev_active_window_idx = m_curr_active_window_idx;
813 start_idx = m_curr_active_window_idx + 1;
814 }
815 for (int idx = start_idx; idx < num_subwindows; ++idx) {
816 if (m_subwindows[idx]->GetCanBeActive()) {
817 m_curr_active_window_idx = idx;
818 return;
819 }
820 }
821 for (int idx = 0; idx < start_idx; ++idx) {
822 if (m_subwindows[idx]->GetCanBeActive()) {
823 m_curr_active_window_idx = idx;
824 break;
825 }
826 }
827 }
828
829 void SelectPreviousWindowAsActive() {
830 // Move active focus to previous window
831 const int num_subwindows = m_subwindows.size();
832 int start_idx = num_subwindows - 1;
833 if (m_curr_active_window_idx != UINT32_MAX) {
834 m_prev_active_window_idx = m_curr_active_window_idx;
835 start_idx = m_curr_active_window_idx - 1;
836 }
837 for (int idx = start_idx; idx >= 0; --idx) {
838 if (m_subwindows[idx]->GetCanBeActive()) {
839 m_curr_active_window_idx = idx;
840 return;
841 }
842 }
843 for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
844 if (m_subwindows[idx]->GetCanBeActive()) {
845 m_curr_active_window_idx = idx;
846 break;
847 }
848 }
849 }
850
851 const char *GetName() const { return m_name.c_str(); }
852
853protected:
854 std::string m_name;
855 WINDOW *m_window;
856 PANEL *m_panel;
857 Window *m_parent;
858 Windows m_subwindows;
859 WindowDelegateSP m_delegate_sp;
860 uint32_t m_curr_active_window_idx;
861 uint32_t m_prev_active_window_idx;
862 bool m_delete;
863 bool m_needs_update;
864 bool m_can_activate;
865 bool m_is_subwin;
866
867private:
868 Window(const Window &) = delete;
869 const Window &operator=(const Window &) = delete;
870};
871
872class MenuDelegate {
873public:
874 virtual ~MenuDelegate() = default;
875
876 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
877};
878
879class Menu : public WindowDelegate {
880public:
881 enum class Type { Invalid, Bar, Item, Separator };
882
883 // Menubar or separator constructor
884 Menu(Type type);
885
886 // Menuitem constructor
887 Menu(const char *name, const char *key_name, int key_value,
888 uint64_t identifier);
889
890 ~Menu() override = default;
891
892 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
893
894 void SetDelegate(const MenuDelegateSP &delegate_sp) {
895 m_delegate_sp = delegate_sp;
896 }
897
898 void RecalculateNameLengths();
899
900 void AddSubmenu(const MenuSP &menu_sp);
901
902 int DrawAndRunMenu(Window &window);
903
904 void DrawMenuTitle(Window &window, bool highlight);
905
906 bool WindowDelegateDraw(Window &window, bool force) override;
907
908 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
909
910 MenuActionResult ActionPrivate(Menu &menu) {
911 MenuActionResult result = MenuActionResult::NotHandled;
912 if (m_delegate_sp) {
913 result = m_delegate_sp->MenuDelegateAction(menu);
914 if (result != MenuActionResult::NotHandled)
915 return result;
916 } else if (m_parent) {
917 result = m_parent->ActionPrivate(menu);
918 if (result != MenuActionResult::NotHandled)
919 return result;
920 }
921 return m_canned_result;
922 }
923
924 MenuActionResult Action() {
925 // Call the recursive action so it can try to handle it with the menu
926 // delegate, and if not, try our parent menu
927 return ActionPrivate(*this);
928 }
929
930 void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
931
932 Menus &GetSubmenus() { return m_submenus; }
933
934 const Menus &GetSubmenus() const { return m_submenus; }
935
936 int GetSelectedSubmenuIndex() const { return m_selected; }
937
938 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
939
940 Type GetType() const { return m_type; }
941
942 int GetStartingColumn() const { return m_start_col; }
943
944 void SetStartingColumn(int col) { m_start_col = col; }
945
946 int GetKeyValue() const { return m_key_value; }
947
948 std::string &GetName() { return m_name; }
949
950 int GetDrawWidth() const {
951 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
952 }
953
954 uint64_t GetIdentifier() const { return m_identifier; }
955
956 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
957
958protected:
959 std::string m_name;
960 std::string m_key_name;
961 uint64_t m_identifier;
962 Type m_type;
963 int m_key_value;
964 int m_start_col;
965 int m_max_submenu_name_length;
966 int m_max_submenu_key_name_length;
967 int m_selected;
968 Menu *m_parent;
969 Menus m_submenus;
970 WindowSP m_menu_window_sp;
971 MenuActionResult m_canned_result;
972 MenuDelegateSP m_delegate_sp;
973};
974
975// Menubar or separator constructor
976Menu::Menu(Type type)
977 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
978 m_start_col(0), m_max_submenu_name_length(0),
979 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
980 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
981 m_delegate_sp() {}
982
983// Menuitem constructor
984Menu::Menu(const char *name, const char *key_name, int key_value,
985 uint64_t identifier)
986 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
987 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
988 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
989 m_submenus(), m_canned_result(MenuActionResult::NotHandled),
990 m_delegate_sp() {
991 if (name && name[0]) {
992 m_name = name;
993 m_type = Type::Item;
994 if (key_name && key_name[0])
995 m_key_name = key_name;
996 } else {
997 m_type = Type::Separator;
998 }
999}
1000
1001void Menu::RecalculateNameLengths() {
1002 m_max_submenu_name_length = 0;
1003 m_max_submenu_key_name_length = 0;
1004 Menus &submenus = GetSubmenus();
1005 const size_t num_submenus = submenus.size();
1006 for (size_t i = 0; i < num_submenus; ++i) {
1007 Menu *submenu = submenus[i].get();
1008 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
1009 m_max_submenu_name_length = submenu->m_name.size();
1010 if (static_cast<size_t>(m_max_submenu_key_name_length) <
1011 submenu->m_key_name.size())
1012 m_max_submenu_key_name_length = submenu->m_key_name.size();
1013 }
1014}
1015
1016void Menu::AddSubmenu(const MenuSP &menu_sp) {
1017 menu_sp->m_parent = this;
1018 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
1019 m_max_submenu_name_length = menu_sp->m_name.size();
1020 if (static_cast<size_t>(m_max_submenu_key_name_length) <
1021 menu_sp->m_key_name.size())
1022 m_max_submenu_key_name_length = menu_sp->m_key_name.size();
1023 m_submenus.push_back(menu_sp);
1024}
1025
1026void Menu::DrawMenuTitle(Window &window, bool highlight) {
1027 if (m_type == Type::Separator) {
1028 window.MoveCursor(0, window.GetCursorY());
1029 window.PutChar(ACS_LTEE);
1030 int width = window.GetWidth();
1031 if (width > 2) {
1032 width -= 2;
1033 for (int i = 0; i < width; ++i)
1034 window.PutChar(ACS_HLINE);
1035 }
1036 window.PutChar(ACS_RTEE);
1037 } else {
1038 const int shortcut_key = m_key_value;
1039 bool underlined_shortcut = false;
1040 const attr_t highlight_attr = A_REVERSE;
1041 if (highlight)
1042 window.AttributeOn(highlight_attr);
1043 if (llvm::isPrint(shortcut_key)) {
1044 size_t lower_pos = m_name.find(tolower(shortcut_key));
1045 size_t upper_pos = m_name.find(toupper(shortcut_key));
1046 const char *name = m_name.c_str();
1047 size_t pos = std::min<size_t>(lower_pos, upper_pos);
1048 if (pos != std::string::npos) {
1049 underlined_shortcut = true;
1050 if (pos > 0) {
1051 window.PutCString(name, pos);
1052 name += pos;
1053 }
1054 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
1055 window.AttributeOn(shortcut_attr);
1056 window.PutChar(name[0]);
1057 window.AttributeOff(shortcut_attr);
1058 name++;
1059 if (name[0])
1060 window.PutCString(name);
1061 }
1062 }
1063
1064 if (!underlined_shortcut) {
1065 window.PutCString(m_name.c_str());
1066 }
1067
1068 if (highlight)
1069 window.AttributeOff(highlight_attr);
1070
1071 if (m_key_name.empty()) {
1072 if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
1073 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
1074 window.Printf(" (%c)", m_key_value);
1075 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
1076 }
1077 } else {
1078 window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
1079 window.Printf(" (%s)", m_key_name.c_str());
1080 window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
1081 }
1082 }
1083}
1084
1085bool Menu::WindowDelegateDraw(Window &window, bool force) {
1086 Menus &submenus = GetSubmenus();
1087 const size_t num_submenus = submenus.size();
1088 const int selected_idx = GetSelectedSubmenuIndex();
1089 Menu::Type menu_type = GetType();
1090 switch (menu_type) {
1091 case Menu::Type::Bar: {
1092 window.SetBackground(BlackOnWhite);
1093 window.MoveCursor(0, 0);
1094 for (size_t i = 0; i < num_submenus; ++i) {
1095 Menu *menu = submenus[i].get();
1096 if (i > 0)
1097 window.PutChar(' ');
1098 menu->SetStartingColumn(window.GetCursorX());
1099 window.PutCString("| ");
1100 menu->DrawMenuTitle(window, false);
1101 }
1102 window.PutCString(" |");
1103 } break;
1104
1105 case Menu::Type::Item: {
1106 int y = 1;
1107 int x = 3;
1108 // Draw the menu
1109 int cursor_x = 0;
1110 int cursor_y = 0;
1111 window.Erase();
1112 window.SetBackground(BlackOnWhite);
1113 window.Box();
1114 for (size_t i = 0; i < num_submenus; ++i) {
1115 const bool is_selected = (i == static_cast<size_t>(selected_idx));
1116 window.MoveCursor(x, y + i);
1117 if (is_selected) {
1118 // Remember where we want the cursor to be
1119 cursor_x = x - 1;
1120 cursor_y = y + i;
1121 }
1122 submenus[i]->DrawMenuTitle(window, is_selected);
1123 }
1124 window.MoveCursor(cursor_x, cursor_y);
1125 } break;
1126
1127 default:
1128 case Menu::Type::Separator:
1129 break;
1130 }
1131 return true; // Drawing handled...
1132}
1133
1134HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
1135 HandleCharResult result = eKeyNotHandled;
1136
1137 Menus &submenus = GetSubmenus();
1138 const size_t num_submenus = submenus.size();
1139 const int selected_idx = GetSelectedSubmenuIndex();
1140 Menu::Type menu_type = GetType();
1141 if (menu_type == Menu::Type::Bar) {
1142 MenuSP run_menu_sp;
1143 switch (key) {
1144 case KEY_DOWN:
1145 case KEY_UP:
1146 // Show last menu or first menu
1147 if (selected_idx < static_cast<int>(num_submenus))
1148 run_menu_sp = submenus[selected_idx];
1149 else if (!submenus.empty())
1150 run_menu_sp = submenus.front();
1151 result = eKeyHandled;
1152 break;
1153
1154 case KEY_RIGHT:
1155 ++m_selected;
1156 if (m_selected >= static_cast<int>(num_submenus))
1157 m_selected = 0;
1158 if (m_selected < static_cast<int>(num_submenus))
1159 run_menu_sp = submenus[m_selected];
1160 else if (!submenus.empty())
1161 run_menu_sp = submenus.front();
1162 result = eKeyHandled;
1163 break;
1164
1165 case KEY_LEFT:
1166 --m_selected;
1167 if (m_selected < 0)
1168 m_selected = num_submenus - 1;
1169 if (m_selected < static_cast<int>(num_submenus))
1170 run_menu_sp = submenus[m_selected];
1171 else if (!submenus.empty())
1172 run_menu_sp = submenus.front();
1173 result = eKeyHandled;
1174 break;
1175
1176 default:
1177 for (size_t i = 0; i < num_submenus; ++i) {
1178 if (submenus[i]->GetKeyValue() == key) {
1179 SetSelectedSubmenuIndex(i);
1180 run_menu_sp = submenus[i];
1181 result = eKeyHandled;
1182 break;
1183 }
1184 }
1185 break;
1186 }
1187
1188 if (run_menu_sp) {
1189 // Run the action on this menu in case we need to populate the menu with
1190 // dynamic content and also in case check marks, and any other menu
1191 // decorations need to be calculated
1192 if (run_menu_sp->Action() == MenuActionResult::Quit)
1193 return eQuitApplication;
1194
1195 Rect menu_bounds;
1196 menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
1197 menu_bounds.origin.y = 1;
1198 menu_bounds.size.width = run_menu_sp->GetDrawWidth();
1199 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
1200 if (m_menu_window_sp)
1201 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
1202
1203 m_menu_window_sp = window.GetParent()->CreateSubWindow(
1204 run_menu_sp->GetName().c_str(), menu_bounds, true);
1205 m_menu_window_sp->SetDelegate(run_menu_sp);
1206 }
1207 } else if (menu_type == Menu::Type::Item) {
1208 switch (key) {
1209 case KEY_DOWN:
1210 if (m_submenus.size() > 1) {
1211 const int start_select = m_selected;
1212 while (++m_selected != start_select) {
1213 if (static_cast<size_t>(m_selected) >= num_submenus)
1214 m_selected = 0;
1215 if (m_submenus[m_selected]->GetType() == Type::Separator)
1216 continue;
1217 else
1218 break;
1219 }
1220 return eKeyHandled;
1221 }
1222 break;
1223
1224 case KEY_UP:
1225 if (m_submenus.size() > 1) {
1226 const int start_select = m_selected;
1227 while (--m_selected != start_select) {
1228 if (m_selected < static_cast<int>(0))
1229 m_selected = num_submenus - 1;
1230 if (m_submenus[m_selected]->GetType() == Type::Separator)
1231 continue;
1232 else
1233 break;
1234 }
1235 return eKeyHandled;
1236 }
1237 break;
1238
1239 case KEY_RETURN:
1240 if (static_cast<size_t>(selected_idx) < num_submenus) {
1241 if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
1242 return eQuitApplication;
1243 window.GetParent()->RemoveSubWindow(&window);
1244 return eKeyHandled;
1245 }
1246 break;
1247
1248 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
1249 // case other chars are entered for escaped sequences
1250 window.GetParent()->RemoveSubWindow(&window);
1251 return eKeyHandled;
1252
1253 default:
1254 for (size_t i = 0; i < num_submenus; ++i) {
1255 Menu *menu = submenus[i].get();
1256 if (menu->GetKeyValue() == key) {
1257 SetSelectedSubmenuIndex(i);
1258 window.GetParent()->RemoveSubWindow(&window);
1259 if (menu->Action() == MenuActionResult::Quit)
1260 return eQuitApplication;
1261 return eKeyHandled;
1262 }
1263 }
1264 break;
1265 }
1266 } else if (menu_type == Menu::Type::Separator) {
1267 }
1268 return result;
1269}
1270
1271class Application {
1272public:
1273 Application(FILE *in, FILE *out)
1274 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {}
1275
1276 ~Application() {
1277 m_window_delegates.clear();
1278 m_window_sp.reset();
1279 if (m_screen) {
1280 ::delscreen(m_screen);
1281 m_screen = nullptr;
1282 }
1283 }
1284
1285 void Initialize() {
1286 ::setlocale(LC_ALL, "");
1287 ::setlocale(LC_CTYPE, "");
1288 m_screen = ::newterm(nullptr, m_out, m_in);
1289 ::start_color();
1290 ::curs_set(0);
1291 ::noecho();
1292 ::keypad(stdscr, TRUE);
1293 }
1294
1295 void Terminate() { ::endwin(); }
1296
1297 void Run(Debugger &debugger) {
1298 bool done = false;
1299 int delay_in_tenths_of_a_second = 1;
1300
1301 // Alas the threading model in curses is a bit lame so we need to resort to
1302 // polling every 0.5 seconds. We could poll for stdin ourselves and then
1303 // pass the keys down but then we need to translate all of the escape
1304 // sequences ourselves. So we resort to polling for input because we need
1305 // to receive async process events while in this loop.
1306
1307 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths
1308 // of seconds seconds when calling
1309 // Window::GetChar()
1310
1311 ListenerSP listener_sp(
1312 Listener::MakeListener("lldb.IOHandler.curses.Application"));
1313 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
1314 debugger.EnableForwardEvents(listener_sp);
1315
1316 m_update_screen = true;
1317#if defined(__APPLE__)
1318 std::deque<int> escape_chars;
1319#endif
1320
1321 while (!done) {
1322 if (m_update_screen) {
1323 m_window_sp->Draw(false);
1324 // All windows should be calling Window::DeferredRefresh() instead of
1325 // Window::Refresh() so we can do a single update and avoid any screen
1326 // blinking
1327 update_panels();
1328
1329 // Cursor hiding isn't working on MacOSX, so hide it in the top left
1330 // corner
1331 m_window_sp->MoveCursor(0, 0);
1332
1333 doupdate();
1334 m_update_screen = false;
1335 }
1336
1337#if defined(__APPLE__)
1338 // Terminal.app doesn't map its function keys correctly, F1-F4 default
1339 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
1340 // possible
1341 int ch;
1342 if (escape_chars.empty())
1343 ch = m_window_sp->GetChar();
1344 else {
1345 ch = escape_chars.front();
1346 escape_chars.pop_front();
1347 }
1348 if (ch == KEY_ESCAPE) {
1349 int ch2 = m_window_sp->GetChar();
1350 if (ch2 == 'O') {
1351 int ch3 = m_window_sp->GetChar();
1352 switch (ch3) {
1353 case 'P':
1354 ch = KEY_F(1);
1355 break;
1356 case 'Q':
1357 ch = KEY_F(2);
1358 break;
1359 case 'R':
1360 ch = KEY_F(3);
1361 break;
1362 case 'S':
1363 ch = KEY_F(4);
1364 break;
1365 default:
1366 escape_chars.push_back(ch2);
1367 if (ch3 != -1)
1368 escape_chars.push_back(ch3);
1369 break;
1370 }
1371 } else if (ch2 != -1)
1372 escape_chars.push_back(ch2);
1373 }
1374#else
1375 int ch = m_window_sp->GetChar();
1376
1377#endif
1378 if (ch == -1) {
1379 if (feof(m_in) || ferror(m_in)) {
1380 done = true;
1381 } else {
1382 // Just a timeout from using halfdelay(), check for events
1383 EventSP event_sp;
1384 while (listener_sp->PeekAtNextEvent()) {
1385 listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
1386
1387 if (event_sp) {
1388 Broadcaster *broadcaster = event_sp->GetBroadcaster();
1389 if (broadcaster) {
1390 // uint32_t event_type = event_sp->GetType();
1391 ConstString broadcaster_class(
1392 broadcaster->GetBroadcasterClass());
1393 if (broadcaster_class == broadcaster_class_process) {
1394 m_update_screen = true;
1395 continue; // Don't get any key, just update our view
1396 }
1397 }
1398 }
1399 }
1400 }
1401 } else {
1402 HandleCharResult key_result = m_window_sp->HandleChar(ch);
1403 switch (key_result) {
1404 case eKeyHandled:
1405 m_update_screen = true;
1406 break;
1407 case eKeyNotHandled:
1408 if (ch == 12) { // Ctrl+L, force full redraw
1409 redrawwin(m_window_sp->get());
1410 m_update_screen = true;
1411 }
1412 break;
1413 case eQuitApplication:
1414 done = true;
1415 break;
1416 }
1417 }
1418 }
1419
1420 debugger.CancelForwardEvents(listener_sp);
1421 }
1422
1423 WindowSP &GetMainWindow() {
1424 if (!m_window_sp)
1425 m_window_sp = std::make_shared<Window>("main", stdscr, false);
1426 return m_window_sp;
1427 }
1428
1429 void TerminalSizeChanged() {
1430 ::endwin();
1431 ::refresh();
1432 Rect content_bounds = m_window_sp->GetFrame();
1433 m_window_sp->SetBounds(content_bounds);
1434 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
1435 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
1436 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
1437 status_window_sp->SetBounds(content_bounds.MakeStatusBar());
1438
1439 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
1440 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
1441 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
1442 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
1443
1444 Rect threads_bounds;
1445 Rect source_variables_bounds;
1446 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
1447 threads_bounds);
1448 if (threads_window_sp)
1449 threads_window_sp->SetBounds(threads_bounds);
1450 else
1451 source_variables_bounds = content_bounds;
1452
1453 Rect source_bounds;
1454 Rect variables_registers_bounds;
1455 source_variables_bounds.HorizontalSplitPercentage(
1456 0.70, source_bounds, variables_registers_bounds);
1457 if (variables_window_sp || registers_window_sp) {
1458 if (variables_window_sp && registers_window_sp) {
1459 Rect variables_bounds;
1460 Rect registers_bounds;
1461 variables_registers_bounds.VerticalSplitPercentage(
1462 0.50, variables_bounds, registers_bounds);
1463 variables_window_sp->SetBounds(variables_bounds);
1464 registers_window_sp->SetBounds(registers_bounds);
1465 } else if (variables_window_sp) {
1466 variables_window_sp->SetBounds(variables_registers_bounds);
1467 } else {
1468 registers_window_sp->SetBounds(variables_registers_bounds);
1469 }
1470 } else {
1471 source_bounds = source_variables_bounds;
1472 }
1473
1474 source_window_sp->SetBounds(source_bounds);
1475
1476 touchwin(stdscr);
1477 redrawwin(m_window_sp->get());
1478 m_update_screen = true;
1479 }
1480
1481protected:
1482 WindowSP m_window_sp;
1483 WindowDelegates m_window_delegates;
1484 SCREEN *m_screen;
1485 FILE *m_in;
1486 FILE *m_out;
1487 bool m_update_screen = false;
1488};
1489
1490} // namespace curses
1491
1492using namespace curses;
1493
1494struct Row {
1495 ValueObjectUpdater value;
1496 Row *parent;
1497 // The process stop ID when the children were calculated.
1498 uint32_t children_stop_id = 0;
1499 int row_idx = 0;
1500 int x = 1;
1501 int y = 1;
1502 bool might_have_children;
1503 bool expanded = false;
1504 bool calculated_children = false;
1505 std::vector<Row> children;
1506
1507 Row(const ValueObjectSP &v, Row *p)
1508 : value(v), parent(p),
1509 might_have_children(v ? v->MightHaveChildren() : false) {}
1510
1511 size_t GetDepth() const {
1512 if (parent)
1513 return 1 + parent->GetDepth();
1514 return 0;
1515 }
1516
1517 void Expand() { expanded = true; }
1518
1519 std::vector<Row> &GetChildren() {
1520 ProcessSP process_sp = value.GetProcessSP();
1521 auto stop_id = process_sp->GetStopID();
1522 if (process_sp && stop_id != children_stop_id) {
1523 children_stop_id = stop_id;
1524 calculated_children = false;
1525 }
1526 if (!calculated_children) {
1527 children.clear();
1528 calculated_children = true;
1529 ValueObjectSP valobj = value.GetSP();
1530 if (valobj) {
1531 const size_t num_children = valobj->GetNumChildren();
1532 for (size_t i = 0; i < num_children; ++i) {
1533 children.push_back(Row(valobj->GetChildAtIndex(i, true), this));
1534 }
1535 }
1536 }
1537 return children;
1538 }
1539
1540 void Unexpand() {
1541 expanded = false;
1542 calculated_children = false;
1543 children.clear();
1544 }
1545
1546 void DrawTree(Window &window) {
1547 if (parent)
1548 parent->DrawTreeForChild(window, this, 0);
1549
1550 if (might_have_children) {
1551 // It we can get UTF8 characters to work we should try to use the
1552 // "symbol" UTF8 string below
1553 // const char *symbol = "";
1554 // if (row.expanded)
1555 // symbol = "\xe2\x96\xbd ";
1556 // else
1557 // symbol = "\xe2\x96\xb7 ";
1558 // window.PutCString (symbol);
1559
1560 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
1561 // or '>' character...
1562 // if (expanded)
1563 // window.PutChar (ACS_DARROW);
1564 // else
1565 // window.PutChar (ACS_RARROW);
1566 // Since we can't find any good looking right arrow/down arrow symbols,
1567 // just use a diamond...
1568 window.PutChar(ACS_DIAMOND);
1569 window.PutChar(ACS_HLINE);
1570 }
1571 }
1572
1573 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
1574 if (parent)
1575 parent->DrawTreeForChild(window, this, reverse_depth + 1);
1576
1577 if (&GetChildren().back() == child) {
1578 // Last child
1579 if (reverse_depth == 0) {
1580 window.PutChar(ACS_LLCORNER);
1581 window.PutChar(ACS_HLINE);
1582 } else {
1583 window.PutChar(' ');
1584 window.PutChar(' ');
1585 }
1586 } else {
1587 if (reverse_depth == 0) {
1588 window.PutChar(ACS_LTEE);
1589 window.PutChar(ACS_HLINE);
1590 } else {
1591 window.PutChar(ACS_VLINE);
1592 window.PutChar(' ');
1593 }
1594 }
1595 }
1596};
1597
1598struct DisplayOptions {
1599 bool show_types;
1600};
1601
1602class TreeItem;
1603
1604class TreeDelegate {
1605public:
1606 TreeDelegate() = default;
1607 virtual ~TreeDelegate() = default;
1608
1609 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
1610 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
1611 virtual bool TreeDelegateItemSelected(
1612 TreeItem &item) = 0; // Return true if we need to update views
1613};
1614
1615typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
1616
1617class TreeItem {
1618public:
1619 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
1620 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr),
1621 m_identifier(0), m_row_idx(-1), m_children(),
1622 m_might_have_children(might_have_children), m_is_expanded(false) {}
1623
1624 TreeItem &operator=(const TreeItem &rhs) {
1625 if (this != &rhs) {
1626 m_parent = rhs.m_parent;
1627 m_delegate = rhs.m_delegate;
1628 m_user_data = rhs.m_user_data;
1629 m_identifier = rhs.m_identifier;
1630 m_row_idx = rhs.m_row_idx;
1631 m_children = rhs.m_children;
1632 m_might_have_children = rhs.m_might_have_children;
1633 m_is_expanded = rhs.m_is_expanded;
1634 }
1635 return *this;
1636 }
1637
1638 TreeItem(const TreeItem &) = default;
1639
1640 size_t GetDepth() const {
1641 if (m_parent)
1642 return 1 + m_parent->GetDepth();
1643 return 0;
1644 }
1645
1646 int GetRowIndex() const { return m_row_idx; }
1647
1648 void ClearChildren() { m_children.clear(); }
1649
1650 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); }
1651
1652 TreeItem &operator[](size_t i) { return m_children[i]; }
1653
1654 void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
1655
1656 size_t GetNumChildren() {
1657 m_delegate.TreeDelegateGenerateChildren(*this);
1658 return m_children.size();
1659 }
1660
1661 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); }
1662
1663 void CalculateRowIndexes(int &row_idx) {
1664 SetRowIndex(row_idx);
1665 ++row_idx;
1666
1667 const bool expanded = IsExpanded();
1668
1669 // The root item must calculate its children, or we must calculate the
1670 // number of children if the item is expanded
1671 if (m_parent == nullptr || expanded)
1672 GetNumChildren();
1673
1674 for (auto &item : m_children) {
1675 if (expanded)
1676 item.CalculateRowIndexes(row_idx);
1677 else
1678 item.SetRowIndex(-1);
1679 }
1680 }
1681
1682 TreeItem *GetParent() { return m_parent; }
1683
1684 bool IsExpanded() const { return m_is_expanded; }
1685
1686 void Expand() { m_is_expanded = true; }
1687
1688 void Unexpand() { m_is_expanded = false; }
1689
1690 bool Draw(Window &window, const int first_visible_row,
1691 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
1692 if (num_rows_left <= 0)
1693 return false;
1694
1695 if (m_row_idx >= first_visible_row) {
1696 window.MoveCursor(2, row_idx + 1);
1697
1698 if (m_parent)
1699 m_parent->DrawTreeForChild(window, this, 0);
1700
1701 if (m_might_have_children) {
1702 // It we can get UTF8 characters to work we should try to use the
1703 // "symbol" UTF8 string below
1704 // const char *symbol = "";
1705 // if (row.expanded)
1706 // symbol = "\xe2\x96\xbd ";
1707 // else
1708 // symbol = "\xe2\x96\xb7 ";
1709 // window.PutCString (symbol);
1710
1711 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a
1712 // 'v' or '>' character...
1713 // if (expanded)
1714 // window.PutChar (ACS_DARROW);
1715 // else
1716 // window.PutChar (ACS_RARROW);
1717 // Since we can't find any good looking right arrow/down arrow symbols,
1718 // just use a diamond...
1719 window.PutChar(ACS_DIAMOND);
1720 window.PutChar(ACS_HLINE);
1721 }
1722 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
1723 window.IsActive();
1724
1725 if (highlight)
1726 window.AttributeOn(A_REVERSE);
1727
1728 m_delegate.TreeDelegateDrawTreeItem(*this, window);
1729
1730 if (highlight)
1731 window.AttributeOff(A_REVERSE);
1732 ++row_idx;
1733 --num_rows_left;
1734 }
1735
1736 if (num_rows_left <= 0)
1737 return false; // We are done drawing...
1738
1739 if (IsExpanded()) {
1740 for (auto &item : m_children) {
1741 // If we displayed all the rows and item.Draw() returns false we are
1742 // done drawing and can exit this for loop
1743 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
1744 num_rows_left))
1745 break;
1746 }
1747 }
1748 return num_rows_left >= 0; // Return true if not done drawing yet
1749 }
1750
1751 void DrawTreeForChild(Window &window, TreeItem *child,
1752 uint32_t reverse_depth) {
1753 if (m_parent)
1754 m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
1755
1756 if (&m_children.back() == child) {
1757 // Last child
1758 if (reverse_depth == 0) {
1759 window.PutChar(ACS_LLCORNER);
1760 window.PutChar(ACS_HLINE);
1761 } else {
1762 window.PutChar(' ');
1763 window.PutChar(' ');
1764 }
1765 } else {
1766 if (reverse_depth == 0) {
1767 window.PutChar(ACS_LTEE);
1768 window.PutChar(ACS_HLINE);
1769 } else {
1770 window.PutChar(ACS_VLINE);
1771 window.PutChar(' ');
1772 }
1773 }
1774 }
1775
1776 TreeItem *GetItemForRowIndex(uint32_t row_idx) {
1777 if (static_cast<uint32_t>(m_row_idx) == row_idx)
1778 return this;
1779 if (m_children.empty())
1780 return nullptr;
1781 if (IsExpanded()) {
1782 for (auto &item : m_children) {
1783 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
1784 if (selected_item_ptr)
1785 return selected_item_ptr;
1786 }
1787 }
1788 return nullptr;
1789 }
1790
1791 void *GetUserData() const { return m_user_data; }
1792
1793 void SetUserData(void *user_data) { m_user_data = user_data; }
1794
1795 uint64_t GetIdentifier() const { return m_identifier; }
1796
1797 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
1798
1799 void SetMightHaveChildren(bool b) { m_might_have_children = b; }
1800
1801protected:
1802 TreeItem *m_parent;
1803 TreeDelegate &m_delegate;
1804 void *m_user_data;
1805 uint64_t m_identifier;
1806 int m_row_idx; // Zero based visible row index, -1 if not visible or for the
1807 // root item
1808 std::vector<TreeItem> m_children;
1809 bool m_might_have_children;
1810 bool m_is_expanded;
1811};
1812
1813class TreeWindowDelegate : public WindowDelegate {
1814public:
1815 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
1816 : m_debugger(debugger), m_delegate_sp(delegate_sp),
1817 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr),
1818 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0),
1819 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
1820
1821 int NumVisibleRows() const { return m_max_y - m_min_y; }
1822
1823 bool WindowDelegateDraw(Window &window, bool force) override {
1824 ExecutionContext exe_ctx(
1825 m_debugger.GetCommandInterpreter().GetExecutionContext());
1826 Process *process = exe_ctx.GetProcessPtr();
1827
1828 bool display_content = false;
1829 if (process) {
1830 StateType state = process->GetState();
1831 if (StateIsStoppedState(state, true)) {
1832 // We are stopped, so it is ok to
1833 display_content = true;
1834 } else if (StateIsRunningState(state)) {
1835 return true; // Don't do any updating when we are running
1836 }
1837 }
1838
1839 m_min_x = 2;
1840 m_min_y = 1;
1841 m_max_x = window.GetWidth() - 1;
1842 m_max_y = window.GetHeight() - 1;
1843
1844 window.Erase();
1845 window.DrawTitleBox(window.GetName());
1846
1847 if (display_content) {
1848 const int num_visible_rows = NumVisibleRows();
1849 m_num_rows = 0;
1850 m_root.CalculateRowIndexes(m_num_rows);
1851
1852 // If we unexpanded while having something selected our total number of
1853 // rows is less than the num visible rows, then make sure we show all the
1854 // rows by setting the first visible row accordingly.
1855 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
1856 m_first_visible_row = 0;
1857
1858 // Make sure the selected row is always visible
1859 if (m_selected_row_idx < m_first_visible_row)
1860 m_first_visible_row = m_selected_row_idx;
1861 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
1862 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
1863
1864 int row_idx = 0;
1865 int num_rows_left = num_visible_rows;
1866 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
1867 num_rows_left);
1868 // Get the selected row
1869 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1870 } else {
1871 m_selected_item = nullptr;
1872 }
1873
1874 return true; // Drawing handled
1875 }
1876
1877 const char *WindowDelegateGetHelpText() override {
1878 return "Thread window keyboard shortcuts:";
1879 }
1880
1881 KeyHelp *WindowDelegateGetKeyHelp() override {
1882 static curses::KeyHelp g_source_view_key_help[] = {
1883 {KEY_UP, "Select previous item"},
1884 {KEY_DOWN, "Select next item"},
1885 {KEY_RIGHT, "Expand the selected item"},
1886 {KEY_LEFT,
1887 "Unexpand the selected item or select parent if not expanded"},
1888 {KEY_PPAGE, "Page up"},
1889 {KEY_NPAGE, "Page down"},
1890 {'h', "Show help dialog"},
1891 {' ', "Toggle item expansion"},
1892 {',', "Page up"},
1893 {'.', "Page down"},
1894 {'\0', nullptr}};
1895 return g_source_view_key_help;
1896 }
1897
1898 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
1899 switch (c) {
1900 case ',':
1901 case KEY_PPAGE:
1902 // Page up key
1903 if (m_first_visible_row > 0) {
1904 if (m_first_visible_row > m_max_y)
1905 m_first_visible_row -= m_max_y;
1906 else
1907 m_first_visible_row = 0;
1908 m_selected_row_idx = m_first_visible_row;
1909 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1910 if (m_selected_item)
1911 m_selected_item->ItemWasSelected();
1912 }
1913 return eKeyHandled;
1914
1915 case '.':
1916 case KEY_NPAGE:
1917 // Page down key
1918 if (m_num_rows > m_max_y) {
1919 if (m_first_visible_row + m_max_y < m_num_rows) {
1920 m_first_visible_row += m_max_y;
1921 m_selected_row_idx = m_first_visible_row;
1922 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1923 if (m_selected_item)
1924 m_selected_item->ItemWasSelected();
1925 }
1926 }
1927 return eKeyHandled;
1928
1929 case KEY_UP:
1930 if (m_selected_row_idx > 0) {
1931 --m_selected_row_idx;
1932 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1933 if (m_selected_item)
1934 m_selected_item->ItemWasSelected();
1935 }
1936 return eKeyHandled;
1937
1938 case KEY_DOWN:
1939 if (m_selected_row_idx + 1 < m_num_rows) {
1940 ++m_selected_row_idx;
1941 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1942 if (m_selected_item)
1943 m_selected_item->ItemWasSelected();
1944 }
1945 return eKeyHandled;
1946
1947 case KEY_RIGHT:
1948 if (m_selected_item) {
1949 if (!m_selected_item->IsExpanded())
1950 m_selected_item->Expand();
1951 }
1952 return eKeyHandled;
1953
1954 case KEY_LEFT:
1955 if (m_selected_item) {
1956 if (m_selected_item->IsExpanded())
1957 m_selected_item->Unexpand();
1958 else if (m_selected_item->GetParent()) {
1959 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
1960 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
1961 if (m_selected_item)
1962 m_selected_item->ItemWasSelected();
1963 }
1964 }
1965 return eKeyHandled;
1966
1967 case ' ':
1968 // Toggle expansion state when SPACE is pressed
1969 if (m_selected_item) {
1970 if (m_selected_item->IsExpanded())
1971 m_selected_item->Unexpand();
1972 else
1973 m_selected_item->Expand();
1974 }
1975 return eKeyHandled;
1976
1977 case 'h':
1978 window.CreateHelpSubwindow();
1979 return eKeyHandled;
1980
1981 default:
1982 break;
1983 }
1984 return eKeyNotHandled;
1985 }
1986
1987protected:
1988 Debugger &m_debugger;
1989 TreeDelegateSP m_delegate_sp;
1990 TreeItem m_root;
1991 TreeItem *m_selected_item;
1992 int m_num_rows;
1993 int m_selected_row_idx;
1994 int m_first_visible_row;
1995 int m_min_x;
1996 int m_min_y;
1997 int m_max_x;
1998 int m_max_y;
1999};
2000
2001class FrameTreeDelegate : public TreeDelegate {
2002public:
2003 FrameTreeDelegate() : TreeDelegate() {
2004 FormatEntity::Parse(
2005 "frame #${frame.index}: {${function.name}${function.pc-offset}}}",
2006 m_format);
2007 }
2008
2009 ~FrameTreeDelegate() override = default;
2010
2011 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2012 Thread *thread = (Thread *)item.GetUserData();
2013 if (thread) {
2014 const uint64_t frame_idx = item.GetIdentifier();
2015 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
2016 if (frame_sp) {
2017 StreamString strm;
2018 const SymbolContext &sc =
2019 frame_sp->GetSymbolContext(eSymbolContextEverything);
2020 ExecutionContext exe_ctx(frame_sp);
2021 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
2022 nullptr, false, false)) {
2023 int right_pad = 1;
2024 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2025 }
2026 }
2027 }
2028 }
2029
2030 void TreeDelegateGenerateChildren(TreeItem &item) override {
2031 // No children for frames yet...
2032 }
2033
2034 bool TreeDelegateItemSelected(TreeItem &item) override {
2035 Thread *thread = (Thread *)item.GetUserData();
2036 if (thread) {
2037 thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
2038 thread->GetID());
2039 const uint64_t frame_idx = item.GetIdentifier();
2040 thread->SetSelectedFrameByIndex(frame_idx);
2041 return true;
2042 }
2043 return false;
2044 }
2045
2046protected:
2047 FormatEntity::Entry m_format;
2048};
2049
2050class ThreadTreeDelegate : public TreeDelegate {
2051public:
2052 ThreadTreeDelegate(Debugger &debugger)
2053 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID),
2054 m_stop_id(UINT32_MAX) {
2055 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
2056 "reason = ${thread.stop-reason}}",
2057 m_format);
2058 }
2059
2060 ~ThreadTreeDelegate() override = default;
2061
2062 ProcessSP GetProcess() {
2063 return m_debugger.GetCommandInterpreter()
2064 .GetExecutionContext()
2065 .GetProcessSP();
2066 }
2067
2068 ThreadSP GetThread(const TreeItem &item) {
2069 ProcessSP process_sp = GetProcess();
2070 if (process_sp)
2071 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
2072 return ThreadSP();
2073 }
2074
2075 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2076 ThreadSP thread_sp = GetThread(item);
2077 if (thread_sp) {
2078 StreamString strm;
2079 ExecutionContext exe_ctx(thread_sp);
2080 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
2081 nullptr, false, false)) {
2082 int right_pad = 1;
2083 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2084 }
2085 }
2086 }
2087
2088 void TreeDelegateGenerateChildren(TreeItem &item) override {
2089 ProcessSP process_sp = GetProcess();
2090 if (process_sp && process_sp->IsAlive()) {
2091 StateType state = process_sp->GetState();
2092 if (StateIsStoppedState(state, true)) {
2093 ThreadSP thread_sp = GetThread(item);
2094 if (thread_sp) {
2095 if (m_stop_id == process_sp->GetStopID() &&
2096 thread_sp->GetID() == m_tid)
2097 return; // Children are already up to date
2098 if (!m_frame_delegate_sp) {
2099 // Always expand the thread item the first time we show it
2100 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
2101 }
2102
2103 m_stop_id = process_sp->GetStopID();
2104 m_tid = thread_sp->GetID();
2105
2106 TreeItem t(&item, *m_frame_delegate_sp, false);
2107 size_t num_frames = thread_sp->GetStackFrameCount();
2108 item.Resize(num_frames, t);
2109 for (size_t i = 0; i < num_frames; ++i) {
2110 item[i].SetUserData(thread_sp.get());
2111 item[i].SetIdentifier(i);
2112 }
2113 }
2114 return;
2115 }
2116 }
2117 item.ClearChildren();
2118 }
2119
2120 bool TreeDelegateItemSelected(TreeItem &item) override {
2121 ProcessSP process_sp = GetProcess();
2122 if (process_sp && process_sp->IsAlive()) {
2123 StateType state = process_sp->GetState();
2124 if (StateIsStoppedState(state, true)) {
2125 ThreadSP thread_sp = GetThread(item);
2126 if (thread_sp) {
2127 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
2128 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
2129 ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
2130 if (selected_thread_sp->GetID() != thread_sp->GetID()) {
2131 thread_list.SetSelectedThreadByID(thread_sp->GetID());
2132 return true;
2133 }
2134 }
2135 }
2136 }
2137 return false;
2138 }
2139
2140protected:
2141 Debugger &m_debugger;
2142 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
2143 lldb::user_id_t m_tid;
2144 uint32_t m_stop_id;
2145 FormatEntity::Entry m_format;
2146};
2147
2148class ThreadsTreeDelegate : public TreeDelegate {
2149public:
2150 ThreadsTreeDelegate(Debugger &debugger)
2151 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger),
2152 m_stop_id(UINT32_MAX) {
2153 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
2154 m_format);
2155 }
2156
2157 ~ThreadsTreeDelegate() override = default;
2158
2159 ProcessSP GetProcess() {
2160 return m_debugger.GetCommandInterpreter()
2161 .GetExecutionContext()
2162 .GetProcessSP();
2163 }
2164
2165 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
2166 ProcessSP process_sp = GetProcess();
2167 if (process_sp && process_sp->IsAlive()) {
2168 StreamString strm;
2169 ExecutionContext exe_ctx(process_sp);
2170 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
2171 nullptr, false, false)) {
2172 int right_pad = 1;
2173 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
2174 }
2175 }
2176 }
2177
2178 void TreeDelegateGenerateChildren(TreeItem &item) override {
2179 ProcessSP process_sp = GetProcess();
2180 if (process_sp && process_sp->IsAlive()) {
2181 StateType state = process_sp->GetState();
2182 if (StateIsStoppedState(state, true)) {
2183 const uint32_t stop_id = process_sp->GetStopID();
2184 if (m_stop_id == stop_id)
2185 return; // Children are already up to date
2186
2187 m_stop_id = stop_id;
2188
2189 if (!m_thread_delegate_sp) {
2190 // Always expand the thread item the first time we show it
2191 // item.Expand();
2192 m_thread_delegate_sp =
2193 std::make_shared<ThreadTreeDelegate>(m_debugger);
2194 }
2195
2196 TreeItem t(&item, *m_thread_delegate_sp, false);
2197 ThreadList &threads = process_sp->GetThreadList();
2198 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
2199 size_t num_threads = threads.GetSize();
2200 item.Resize(num_threads, t);
2201 for (size_t i = 0; i < num_threads; ++i) {
2202 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID());
2203 item[i].SetMightHaveChildren(true);
2204 }
2205 return;
2206 }
2207 }
2208 item.ClearChildren();
2209 }
2210
2211 bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
2212
2213protected:
2214 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
2215 Debugger &m_debugger;
2216 uint32_t m_stop_id;
2217 FormatEntity::Entry m_format;
2218};
2219
2220class ValueObjectListDelegate : public WindowDelegate {
2221public:
2222 ValueObjectListDelegate()
2223 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2224 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {}
2225
2226 ValueObjectListDelegate(ValueObjectList &valobj_list)
2227 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0),
2228 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) {
2229 SetValues(valobj_list);
2230 }
2231
2232 ~ValueObjectListDelegate() override = default;
2233
2234 void SetValues(ValueObjectList &valobj_list) {
2235 m_selected_row = nullptr;
2236 m_selected_row_idx = 0;
2237 m_first_visible_row = 0;
2238 m_num_rows = 0;
2239 m_rows.clear();
2240 for (auto &valobj_sp : valobj_list.GetObjects())
2241 m_rows.push_back(Row(valobj_sp, nullptr));
2242 }
2243
2244 bool WindowDelegateDraw(Window &window, bool force) override {
2245 m_num_rows = 0;
2246 m_min_x = 2;
2247 m_min_y = 1;
2248 m_max_x = window.GetWidth() - 1;
2249 m_max_y = window.GetHeight() - 1;
2250
2251 window.Erase();
2252 window.DrawTitleBox(window.GetName());
2253
2254 const int num_visible_rows = NumVisibleRows();
2255 const int num_rows = CalculateTotalNumberRows(m_rows);
2256
2257 // If we unexpanded while having something selected our total number of
2258 // rows is less than the num visible rows, then make sure we show all the
2259 // rows by setting the first visible row accordingly.
2260 if (m_first_visible_row > 0 && num_rows < num_visible_rows)
2261 m_first_visible_row = 0;
2262
2263 // Make sure the selected row is always visible
2264 if (m_selected_row_idx < m_first_visible_row)
2265 m_first_visible_row = m_selected_row_idx;
2266 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
2267 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
2268
2269 DisplayRows(window, m_rows, g_options);
2270
2271 // Get the selected row
2272 m_selected_row = GetRowForRowIndex(m_selected_row_idx);
2273 // Keep the cursor on the selected row so the highlight and the cursor are
2274 // always on the same line
2275 if (m_selected_row)
2276 window.MoveCursor(m_selected_row->x, m_selected_row->y);
2277
2278 return true; // Drawing handled
2279 }
2280
2281 KeyHelp *WindowDelegateGetKeyHelp() override {
2282 static curses::KeyHelp g_source_view_key_help[] = {
2283 {KEY_UP, "Select previous item"},
2284 {KEY_DOWN, "Select next item"},
2285 {KEY_RIGHT, "Expand selected item"},
2286 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
2287 {KEY_PPAGE, "Page up"},
2288 {KEY_NPAGE, "Page down"},
2289 {'A', "Format as annotated address"},
2290 {'b', "Format as binary"},
2291 {'B', "Format as hex bytes with ASCII"},
2292 {'c', "Format as character"},
2293 {'d', "Format as a signed integer"},
2294 {'D', "Format selected value using the default format for the type"},
2295 {'f', "Format as float"},
2296 {'h', "Show help dialog"},
2297 {'i', "Format as instructions"},
2298 {'o', "Format as octal"},
2299 {'p', "Format as pointer"},
2300 {'s', "Format as C string"},
2301 {'t', "Toggle showing/hiding type names"},
2302 {'u', "Format as an unsigned integer"},
2303 {'x', "Format as hex"},
2304 {'X', "Format as uppercase hex"},
2305 {' ', "Toggle item expansion"},
2306 {',', "Page up"},
2307 {'.', "Page down"},
2308 {'\0', nullptr}};
2309 return g_source_view_key_help;
2310 }
2311
2312 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
2313 switch (c) {
2314 case 'x':
2315 case 'X':
2316 case 'o':
2317 case 's':
2318 case 'u':
2319 case 'd':
2320 case 'D':
2321 case 'i':
2322 case 'A':
2323 case 'p':
2324 case 'c':
2325 case 'b':
2326 case 'B':
2327 case 'f':
2328 // Change the format for the currently selected item
2329 if (m_selected_row) {
2330 auto valobj_sp = m_selected_row->value.GetSP();
2331 if (valobj_sp)
2332 valobj_sp->SetFormat(FormatForChar(c));
2333 }
2334 return eKeyHandled;
2335
2336 case 't':
2337 // Toggle showing type names
2338 g_options.show_types = !g_options.show_types;
2339 return eKeyHandled;
2340
2341 case ',':
2342 case KEY_PPAGE:
2343 // Page up key
2344 if (m_first_visible_row > 0) {
2345 if (static_cast<int>(m_first_visible_row) > m_max_y)
2346 m_first_visible_row -= m_max_y;
2347 else
2348 m_first_visible_row = 0;
2349 m_selected_row_idx = m_first_visible_row;
2350 }
2351 return eKeyHandled;
2352
2353 case '.':
2354 case KEY_NPAGE:
2355 // Page down key
2356 if (m_num_rows > static_cast<size_t>(m_max_y)) {
2357 if (m_first_visible_row + m_max_y < m_num_rows) {
2358 m_first_visible_row += m_max_y;
2359 m_selected_row_idx = m_first_visible_row;
2360 }
2361 }
2362 return eKeyHandled;
2363
2364 case KEY_UP:
2365 if (m_selected_row_idx > 0)
2366 --m_selected_row_idx;
2367 return eKeyHandled;
2368
2369 case KEY_DOWN:
2370 if (m_selected_row_idx + 1 < m_num_rows)
2371 ++m_selected_row_idx;
2372 return eKeyHandled;
2373
2374 case KEY_RIGHT:
2375 if (m_selected_row) {
2376 if (!m_selected_row->expanded)
2377 m_selected_row->Expand();
2378 }
2379 return eKeyHandled;
2380
2381 case KEY_LEFT:
2382 if (m_selected_row) {
2383 if (m_selected_row->expanded)
2384 m_selected_row->Unexpand();
2385 else if (m_selected_row->parent)
2386 m_selected_row_idx = m_selected_row->parent->row_idx;
2387 }
2388 return eKeyHandled;
2389
2390 case ' ':
2391 // Toggle expansion state when SPACE is pressed
2392 if (m_selected_row) {
2393 if (m_selected_row->expanded)
2394 m_selected_row->Unexpand();
2395 else
2396 m_selected_row->Expand();
2397 }
2398 return eKeyHandled;
2399
2400 case 'h':
2401 window.CreateHelpSubwindow();
2402 return eKeyHandled;
2403
2404 default:
2405 break;
2406 }
2407 return eKeyNotHandled;
2408 }
2409
2410protected:
2411 std::vector<Row> m_rows;
2412 Row *m_selected_row;
2413 uint32_t m_selected_row_idx;
2414 uint32_t m_first_visible_row;
2415 uint32_t m_num_rows;
2416 int m_min_x;
2417 int m_min_y;
2418 int m_max_x;
2419 int m_max_y;
2420
2421 static Format FormatForChar(int c) {
2422 switch (c) {
2423 case 'x':
2424 return eFormatHex;
2425 case 'X':
2426 return eFormatHexUppercase;
2427 case 'o':
2428 return eFormatOctal;
2429 case 's':
2430 return eFormatCString;
2431 case 'u':
2432 return eFormatUnsigned;
2433 case 'd':
2434 return eFormatDecimal;
2435 case 'D':
2436 return eFormatDefault;
2437 case 'i':
2438 return eFormatInstruction;
2439 case 'A':
2440 return eFormatAddressInfo;
2441 case 'p':
2442 return eFormatPointer;
2443 case 'c':
2444 return eFormatChar;
2445 case 'b':
2446 return eFormatBinary;
2447 case 'B':
2448 return eFormatBytesWithASCII;
2449 case 'f':
2450 return eFormatFloat;
2451 }
2452 return eFormatDefault;
2453 }
2454
2455 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
2456 bool highlight, bool last_child) {
2457 ValueObject *valobj = row.value.GetSP().get();
2458
2459 if (valobj == nullptr)
2460 return false;
2461
2462 const char *type_name =
2463 options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
2464 const char *name = valobj->GetName().GetCString();
2465 const char *value = valobj->GetValueAsCString();
2466 const char *summary = valobj->GetSummaryAsCString();
2467
2468 window.MoveCursor(row.x, row.y);
2469
2470 row.DrawTree(window);
2471
2472 if (highlight)
2473 window.AttributeOn(A_REVERSE);
2474
2475 if (type_name && type_name[0])
2476 window.PrintfTruncated(1, "(%s) ", type_name);
2477
2478 if (name && name[0])
2479 window.PutCStringTruncated(1, name);
2480
2481 attr_t changd_attr = 0;
2482 if (valobj->GetValueDidChange())
2483 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
2484
2485 if (value && value[0]) {
2486 window.PutCStringTruncated(1, " = ");
2487 if (changd_attr)
2488 window.AttributeOn(changd_attr);
2489 window.PutCStringTruncated(1, value);
2490 if (changd_attr)
2491 window.AttributeOff(changd_attr);
2492 }
2493
2494 if (summary && summary[0]) {
2495 window.PutCStringTruncated(1, " ");
2496 if (changd_attr)
2497 window.AttributeOn(changd_attr);
2498 window.PutCStringTruncated(1, summary);
2499 if (changd_attr)
2500 window.AttributeOff(changd_attr);
2501 }
2502
2503 if (highlight)
2504 window.AttributeOff(A_REVERSE);
2505
2506 return true;
2507 }
2508
2509 void DisplayRows(Window &window, std::vector<Row> &rows,
2510 DisplayOptions &options) {
2511 // > 0x25B7
2512 // \/ 0x25BD
2513
2514 bool window_is_active = window.IsActive();
2515 for (auto &row : rows) {
2516 const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
2517 // Save the row index in each Row structure
2518 row.row_idx = m_num_rows;
2519 if ((m_num_rows >= m_first_visible_row) &&
2520 ((m_num_rows - m_first_visible_row) <
2521 static_cast<size_t>(NumVisibleRows()))) {
2522 row.x = m_min_x;
2523 row.y = m_num_rows - m_first_visible_row + 1;
2524 if (DisplayRowObject(window, row, options,
2525 window_is_active &&
2526 m_num_rows == m_selected_row_idx,
2527 last_child)) {
2528 ++m_num_rows;
2529 } else {
2530 row.x = 0;
2531 row.y = 0;
2532 }
2533 } else {
2534 row.x = 0;
2535 row.y = 0;
2536 ++m_num_rows;
2537 }
2538
2539 auto &children = row.GetChildren();
2540 if (row.expanded && !children.empty()) {
2541 DisplayRows(window, children, options);
2542 }
2543 }
2544 }
2545
2546 int CalculateTotalNumberRows(std::vector<Row> &rows) {
2547 int row_count = 0;
2548 for (auto &row : rows) {
2549 ++row_count;
2550 if (row.expanded)
2551 row_count += CalculateTotalNumberRows(row.GetChildren());
2552 }
2553 return row_count;
2554 }
2555
2556 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
2557 for (auto &row : rows) {
2558 if (row_index == 0)
2559 return &row;
2560 else {
2561 --row_index;
2562 auto &children = row.GetChildren();
2563 if (row.expanded && !children.empty()) {
2564 Row *result = GetRowForRowIndexImpl(children, row_index);
2565 if (result)
2566 return result;
2567 }
2568 }
2569 }
2570 return nullptr;
2571 }
2572
2573 Row *GetRowForRowIndex(size_t row_index) {
2574 return GetRowForRowIndexImpl(m_rows, row_index);
2575 }
2576
2577 int NumVisibleRows() const { return m_max_y - m_min_y; }
2578
2579 static DisplayOptions g_options;
2580};
2581
2582class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
2583public:
2584 FrameVariablesWindowDelegate(Debugger &debugger)
2585 : ValueObjectListDelegate(), m_debugger(debugger),
2586 m_frame_block(nullptr) {}
2587
2588 ~FrameVariablesWindowDelegate() override = default;
2589
2590 const char *WindowDelegateGetHelpText() override {
2591 return "Frame variable window keyboard shortcuts:";
2592 }
2593
2594 bool WindowDelegateDraw(Window &window, bool force) override {
2595 ExecutionContext exe_ctx(
2596 m_debugger.GetCommandInterpreter().GetExecutionContext());
2597 Process *process = exe_ctx.GetProcessPtr();
2598 Block *frame_block = nullptr;
2599 StackFrame *frame = nullptr;
2600
2601 if (process) {
2602 StateType state = process->GetState();
2603 if (StateIsStoppedState(state, true)) {
2604 frame = exe_ctx.GetFramePtr();
2605 if (frame)
2606 frame_block = frame->GetFrameBlock();
2607 } else if (StateIsRunningState(state)) {
2608 return true; // Don't do any updating when we are running
2609 }
2610 }
2611
2612 ValueObjectList local_values;
2613 if (frame_block) {
2614 // Only update the variables if they have changed
2615 if (m_frame_block != frame_block) {
2616 m_frame_block = frame_block;
2617
2618 VariableList *locals = frame->GetVariableList(true);
2619 if (locals) {
2620 const DynamicValueType use_dynamic = eDynamicDontRunTarget;
2621 for (const VariableSP &local_sp : *locals) {
2622 ValueObjectSP value_sp =
2623 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
2624 if (value_sp) {
2625 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
2626 if (synthetic_value_sp)
2627 local_values.Append(synthetic_value_sp);
2628 else
2629 local_values.Append(value_sp);
2630 }
2631 }
2632 // Update the values
2633 SetValues(local_values);
2634 }
2635 }
2636 } else {
2637 m_frame_block = nullptr;
2638 // Update the values with an empty list if there is no frame
2639 SetValues(local_values);
2640 }
2641
2642 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2643 }
2644
2645protected:
2646 Debugger &m_debugger;
2647 Block *m_frame_block;
2648};
2649
2650class RegistersWindowDelegate : public ValueObjectListDelegate {
2651public:
2652 RegistersWindowDelegate(Debugger &debugger)
2653 : ValueObjectListDelegate(), m_debugger(debugger) {}
2654
2655 ~RegistersWindowDelegate() override = default;
2656
2657 const char *WindowDelegateGetHelpText() override {
2658 return "Register window keyboard shortcuts:";
2659 }
2660
2661 bool WindowDelegateDraw(Window &window, bool force) override {
2662 ExecutionContext exe_ctx(
2663 m_debugger.GetCommandInterpreter().GetExecutionContext());
2664 StackFrame *frame = exe_ctx.GetFramePtr();
2665
2666 ValueObjectList value_list;
2667 if (frame) {
2668 if (frame->GetStackID() != m_stack_id) {
2669 m_stack_id = frame->GetStackID();
2670 RegisterContextSP reg_ctx(frame->GetRegisterContext());
2671 if (reg_ctx) {
2672 const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
2673 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
2674 value_list.Append(
2675 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
2676 }
2677 }
2678 SetValues(value_list);
2679 }
2680 } else {
2681 Process *process = exe_ctx.GetProcessPtr();
2682 if (process && process->IsAlive())
2683 return true; // Don't do any updating if we are running
2684 else {
2685 // Update the values with an empty list if there is no process or the
2686 // process isn't alive anymore
2687 SetValues(value_list);
2688 }
2689 }
2690 return ValueObjectListDelegate::WindowDelegateDraw(window, force);
2691 }
2692
2693protected:
2694 Debugger &m_debugger;
2695 StackID m_stack_id;
2696};
2697
2698static const char *CursesKeyToCString(int ch) {
2699 static char g_desc[32];
2700 if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
2701 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
2702 return g_desc;
2703 }
2704 switch (ch) {
2705 case KEY_DOWN:
2706 return "down";
2707 case KEY_UP:
2708 return "up";
2709 case KEY_LEFT:
2710 return "left";
2711 case KEY_RIGHT:
2712 return "right";
2713 case KEY_HOME:
2714 return "home";
2715 case KEY_BACKSPACE:
2716 return "backspace";
2717 case KEY_DL:
2718 return "delete-line";
2719 case KEY_IL:
2720 return "insert-line";
2721 case KEY_DC:
2722 return "delete-char";
2723 case KEY_IC:
2724 return "insert-char";
2725 case KEY_CLEAR:
2726 return "clear";
2727 case KEY_EOS:
2728 return "clear-to-eos";
2729 case KEY_EOL:
2730 return "clear-to-eol";
2731 case KEY_SF:
2732 return "scroll-forward";
2733 case KEY_SR:
2734 return "scroll-backward";
2735 case KEY_NPAGE:
2736 return "page-down";
2737 case KEY_PPAGE:
2738 return "page-up";
2739 case KEY_STAB:
2740 return "set-tab";
2741 case KEY_CTAB:
2742 return "clear-tab";
2743 case KEY_CATAB:
2744 return "clear-all-tabs";
2745 case KEY_ENTER:
2746 return "enter";
2747 case KEY_PRINT:
2748 return "print";
2749 case KEY_LL:
2750 return "lower-left key";
2751 case KEY_A1:
2752 return "upper left of keypad";
2753 case KEY_A3:
2754 return "upper right of keypad";
2755 case KEY_B2:
2756 return "center of keypad";
2757 case KEY_C1:
2758 return "lower left of keypad";
2759 case KEY_C3:
2760 return "lower right of keypad";
2761 case KEY_BTAB:
2762 return "back-tab key";
2763 case KEY_BEG:
2764 return "begin key";
2765 case KEY_CANCEL:
2766 return "cancel key";
2767 case KEY_CLOSE:
2768 return "close key";
2769 case KEY_COMMAND:
2770 return "command key";
2771 case KEY_COPY:
2772 return "copy key";
2773 case KEY_CREATE:
2774 return "create key";
2775 case KEY_END:
2776 return "end key";
2777 case KEY_EXIT:
2778 return "exit key";
2779 case KEY_FIND:
2780 return "find key";
2781 case KEY_HELP:
2782 return "help key";
2783 case KEY_MARK:
2784 return "mark key";
2785 case KEY_MESSAGE:
2786 return "message key";
2787 case KEY_MOVE:
2788 return "move key";
2789 case KEY_NEXT:
2790 return "next key";
2791 case KEY_OPEN:
2792 return "open key";
2793 case KEY_OPTIONS:
2794 return "options key";
2795 case KEY_PREVIOUS:
2796 return "previous key";
2797 case KEY_REDO:
2798 return "redo key";
2799 case KEY_REFERENCE:
2800 return "reference key";
2801 case KEY_REFRESH:
2802 return "refresh key";
2803 case KEY_REPLACE:
2804 return "replace key";
2805 case KEY_RESTART:
2806 return "restart key";
2807 case KEY_RESUME:
2808 return "resume key";
2809 case KEY_SAVE:
2810 return "save key";
2811 case KEY_SBEG:
2812 return "shifted begin key";
2813 case KEY_SCANCEL:
2814 return "shifted cancel key";
2815 case KEY_SCOMMAND:
2816 return "shifted command key";
2817 case KEY_SCOPY:
2818 return "shifted copy key";
2819 case KEY_SCREATE:
2820 return "shifted create key";
2821 case KEY_SDC:
2822 return "shifted delete-character key";
2823 case KEY_SDL:
2824 return "shifted delete-line key";
2825 case KEY_SELECT:
2826 return "select key";
2827 case KEY_SEND:
2828 return "shifted end key";
2829 case KEY_SEOL:
2830 return "shifted clear-to-end-of-line key";
2831 case KEY_SEXIT:
2832 return "shifted exit key";
2833 case KEY_SFIND:
2834 return "shifted find key";
2835 case KEY_SHELP:
2836 return "shifted help key";
2837 case KEY_SHOME:
2838 return "shifted home key";
2839 case KEY_SIC:
2840 return "shifted insert-character key";
2841 case KEY_SLEFT:
2842 return "shifted left-arrow key";
2843 case KEY_SMESSAGE:
2844 return "shifted message key";
2845 case KEY_SMOVE:
2846 return "shifted move key";
2847 case KEY_SNEXT:
2848 return "shifted next key";
2849 case KEY_SOPTIONS:
2850 return "shifted options key";
2851 case KEY_SPREVIOUS:
2852 return "shifted previous key";
2853 case KEY_SPRINT:
2854 return "shifted print key";
2855 case KEY_SREDO:
2856 return "shifted redo key";
2857 case KEY_SREPLACE:
2858 return "shifted replace key";
2859 case KEY_SRIGHT:
2860 return "shifted right-arrow key";
2861 case KEY_SRSUME:
2862 return "shifted resume key";
2863 case KEY_SSAVE:
2864 return "shifted save key";
2865 case KEY_SSUSPEND:
2866 return "shifted suspend key";
2867 case KEY_SUNDO:
2868 return "shifted undo key";
2869 case KEY_SUSPEND:
2870 return "suspend key";
2871 case KEY_UNDO:
2872 return "undo key";
2873 case KEY_MOUSE:
2874 return "Mouse event has occurred";
2875 case KEY_RESIZE:
2876 return "Terminal resize event";
2877#ifdef KEY_EVENT
2878 case KEY_EVENT:
2879 return "We were interrupted by an event";
2880#endif
2881 case KEY_RETURN:
2882 return "return";
2883 case ' ':
2884 return "space";
2885 case '\t':
2886 return "tab";
2887 case KEY_ESCAPE:
2888 return "escape";
2889 default:
2890 if (llvm::isPrint(ch))
2891 snprintf(g_desc, sizeof(g_desc), "%c", ch);
2892 else
2893 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
2894 return g_desc;
2895 }
2896 return nullptr;
2897}
2898
2899HelpDialogDelegate::HelpDialogDelegate(const char *text,
2900 KeyHelp *key_help_array)
2901 : m_text(), m_first_visible_line(0) {
2902 if (text && text[0]) {
2903 m_text.SplitIntoLines(text);
2904 m_text.AppendString("");
2905 }
2906 if (key_help_array) {
2907 for (KeyHelp *key = key_help_array; key->ch; ++key) {
2908 StreamString key_description;
2909 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
2910 key->description);
2911 m_text.AppendString(key_description.GetString());
2912 }
2913 }
2914}
2915
2916HelpDialogDelegate::~HelpDialogDelegate() = default;
2917
2918bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
2919 window.Erase();
2920 const int window_height = window.GetHeight();
2921 int x = 2;
2922 int y = 1;
2923 const int min_y = y;
2924 const int max_y = window_height - 1 - y;
2925 const size_t num_visible_lines = max_y - min_y + 1;
2926 const size_t num_lines = m_text.GetSize();
2927 const char *bottom_message;
2928 if (num_lines <= num_visible_lines)
2929 bottom_message = "Press any key to exit";
2930 else
2931 bottom_message = "Use arrows to scroll, any other key to exit";
2932 window.DrawTitleBox(window.GetName(), bottom_message);
2933 while (y <= max_y) {
2934 window.MoveCursor(x, y);
2935 window.PutCStringTruncated(
2936 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
2937 ++y;
2938 }
2939 return true;
2940}
2941
2942HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
2943 int key) {
2944 bool done = false;
2945 const size_t num_lines = m_text.GetSize();
2946 const size_t num_visible_lines = window.GetHeight() - 2;
2947
2948 if (num_lines <= num_visible_lines) {
2949 done = true;
2950 // If we have all lines visible and don't need scrolling, then any key
2951 // press will cause us to exit
2952 } else {
2953 switch (key) {
2954 case KEY_UP:
2955 if (m_first_visible_line > 0)
2956 --m_first_visible_line;
2957 break;
2958
2959 case KEY_DOWN:
2960 if (m_first_visible_line + num_visible_lines < num_lines)
2961 ++m_first_visible_line;
2962 break;
2963