Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp
39587 views
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/ValueObjectUpdater.h"
29
#include "lldb/Host/File.h"
30
#include "lldb/Utility/AnsiTerminal.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
#include "lldb/Interpreter/OptionGroupPlatform.h"
40
41
#if LLDB_ENABLE_CURSES
42
#include "lldb/Breakpoint/BreakpointLocation.h"
43
#include "lldb/Core/Module.h"
44
#include "lldb/Core/PluginManager.h"
45
#include "lldb/Core/ValueObject.h"
46
#include "lldb/Core/ValueObjectRegister.h"
47
#include "lldb/Symbol/Block.h"
48
#include "lldb/Symbol/CompileUnit.h"
49
#include "lldb/Symbol/Function.h"
50
#include "lldb/Symbol/Symbol.h"
51
#include "lldb/Symbol/VariableList.h"
52
#include "lldb/Target/Process.h"
53
#include "lldb/Target/RegisterContext.h"
54
#include "lldb/Target/StackFrame.h"
55
#include "lldb/Target/StopInfo.h"
56
#include "lldb/Target/Target.h"
57
#include "lldb/Target/Thread.h"
58
#include "lldb/Utility/State.h"
59
#endif
60
61
#include "llvm/ADT/StringRef.h"
62
63
#ifdef _WIN32
64
#include "lldb/Host/windows/windows.h"
65
#endif
66
67
#include <memory>
68
#include <mutex>
69
70
#include <cassert>
71
#include <cctype>
72
#include <cerrno>
73
#include <cstdint>
74
#include <cstdio>
75
#include <cstring>
76
#include <functional>
77
#include <optional>
78
#include <type_traits>
79
80
using namespace lldb;
81
using namespace lldb_private;
82
using llvm::StringRef;
83
84
// we may want curses to be disabled for some builds for instance, windows
85
#if LLDB_ENABLE_CURSES
86
87
#define KEY_CTRL_A 1
88
#define KEY_CTRL_E 5
89
#define KEY_CTRL_K 11
90
#define KEY_RETURN 10
91
#define KEY_ESCAPE 27
92
#define KEY_DELETE 127
93
94
#define KEY_SHIFT_TAB (KEY_MAX + 1)
95
#define KEY_ALT_ENTER (KEY_MAX + 2)
96
97
namespace curses {
98
class Menu;
99
class MenuDelegate;
100
class Window;
101
class WindowDelegate;
102
typedef std::shared_ptr<Menu> MenuSP;
103
typedef std::shared_ptr<MenuDelegate> MenuDelegateSP;
104
typedef std::shared_ptr<Window> WindowSP;
105
typedef std::shared_ptr<WindowDelegate> WindowDelegateSP;
106
typedef std::vector<MenuSP> Menus;
107
typedef std::vector<WindowSP> Windows;
108
typedef std::vector<WindowDelegateSP> WindowDelegates;
109
110
#if 0
111
type summary add -s "x=${var.x}, y=${var.y}" curses::Point
112
type summary add -s "w=${var.width}, h=${var.height}" curses::Size
113
type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect
114
#endif
115
116
struct Point {
117
int x;
118
int y;
119
120
Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
121
122
void Clear() {
123
x = 0;
124
y = 0;
125
}
126
127
Point &operator+=(const Point &rhs) {
128
x += rhs.x;
129
y += rhs.y;
130
return *this;
131
}
132
133
void Dump() { printf("(x=%i, y=%i)\n", x, y); }
134
};
135
136
bool operator==(const Point &lhs, const Point &rhs) {
137
return lhs.x == rhs.x && lhs.y == rhs.y;
138
}
139
140
bool operator!=(const Point &lhs, const Point &rhs) {
141
return lhs.x != rhs.x || lhs.y != rhs.y;
142
}
143
144
struct Size {
145
int width;
146
int height;
147
Size(int w = 0, int h = 0) : width(w), height(h) {}
148
149
void Clear() {
150
width = 0;
151
height = 0;
152
}
153
154
void Dump() { printf("(w=%i, h=%i)\n", width, height); }
155
};
156
157
bool operator==(const Size &lhs, const Size &rhs) {
158
return lhs.width == rhs.width && lhs.height == rhs.height;
159
}
160
161
bool operator!=(const Size &lhs, const Size &rhs) {
162
return lhs.width != rhs.width || lhs.height != rhs.height;
163
}
164
165
struct Rect {
166
Point origin;
167
Size size;
168
169
Rect() : origin(), size() {}
170
171
Rect(const Point &p, const Size &s) : origin(p), size(s) {}
172
173
void Clear() {
174
origin.Clear();
175
size.Clear();
176
}
177
178
void Dump() {
179
printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
180
size.height);
181
}
182
183
void Inset(int w, int h) {
184
if (size.width > w * 2)
185
size.width -= w * 2;
186
origin.x += w;
187
188
if (size.height > h * 2)
189
size.height -= h * 2;
190
origin.y += h;
191
}
192
193
// Return a status bar rectangle which is the last line of this rectangle.
194
// This rectangle will be modified to not include the status bar area.
195
Rect MakeStatusBar() {
196
Rect status_bar;
197
if (size.height > 1) {
198
status_bar.origin.x = origin.x;
199
status_bar.origin.y = size.height;
200
status_bar.size.width = size.width;
201
status_bar.size.height = 1;
202
--size.height;
203
}
204
return status_bar;
205
}
206
207
// Return a menubar rectangle which is the first line of this rectangle. This
208
// rectangle will be modified to not include the menubar area.
209
Rect MakeMenuBar() {
210
Rect menubar;
211
if (size.height > 1) {
212
menubar.origin.x = origin.x;
213
menubar.origin.y = origin.y;
214
menubar.size.width = size.width;
215
menubar.size.height = 1;
216
++origin.y;
217
--size.height;
218
}
219
return menubar;
220
}
221
222
void HorizontalSplitPercentage(float top_percentage, Rect &top,
223
Rect &bottom) const {
224
float top_height = top_percentage * size.height;
225
HorizontalSplit(top_height, top, bottom);
226
}
227
228
void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
229
top = *this;
230
if (top_height < size.height) {
231
top.size.height = top_height;
232
bottom.origin.x = origin.x;
233
bottom.origin.y = origin.y + top.size.height;
234
bottom.size.width = size.width;
235
bottom.size.height = size.height - top.size.height;
236
} else {
237
bottom.Clear();
238
}
239
}
240
241
void VerticalSplitPercentage(float left_percentage, Rect &left,
242
Rect &right) const {
243
float left_width = left_percentage * size.width;
244
VerticalSplit(left_width, left, right);
245
}
246
247
void VerticalSplit(int left_width, Rect &left, Rect &right) const {
248
left = *this;
249
if (left_width < size.width) {
250
left.size.width = left_width;
251
right.origin.x = origin.x + left.size.width;
252
right.origin.y = origin.y;
253
right.size.width = size.width - left.size.width;
254
right.size.height = size.height;
255
} else {
256
right.Clear();
257
}
258
}
259
};
260
261
bool operator==(const Rect &lhs, const Rect &rhs) {
262
return lhs.origin == rhs.origin && lhs.size == rhs.size;
263
}
264
265
bool operator!=(const Rect &lhs, const Rect &rhs) {
266
return lhs.origin != rhs.origin || lhs.size != rhs.size;
267
}
268
269
enum HandleCharResult {
270
eKeyNotHandled = 0,
271
eKeyHandled = 1,
272
eQuitApplication = 2
273
};
274
275
enum class MenuActionResult {
276
Handled,
277
NotHandled,
278
Quit // Exit all menus and quit
279
};
280
281
struct KeyHelp {
282
int ch;
283
const char *description;
284
};
285
286
// COLOR_PAIR index names
287
enum {
288
// First 16 colors are 8 black background and 8 blue background colors,
289
// needed by OutputColoredStringTruncated().
290
BlackOnBlack = 1,
291
RedOnBlack,
292
GreenOnBlack,
293
YellowOnBlack,
294
BlueOnBlack,
295
MagentaOnBlack,
296
CyanOnBlack,
297
WhiteOnBlack,
298
BlackOnBlue,
299
RedOnBlue,
300
GreenOnBlue,
301
YellowOnBlue,
302
BlueOnBlue,
303
MagentaOnBlue,
304
CyanOnBlue,
305
WhiteOnBlue,
306
// Other colors, as needed.
307
BlackOnWhite,
308
MagentaOnWhite,
309
LastColorPairIndex = MagentaOnWhite
310
};
311
312
class WindowDelegate {
313
public:
314
virtual ~WindowDelegate() = default;
315
316
virtual bool WindowDelegateDraw(Window &window, bool force) {
317
return false; // Drawing not handled
318
}
319
320
virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
321
return eKeyNotHandled;
322
}
323
324
virtual const char *WindowDelegateGetHelpText() { return nullptr; }
325
326
virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; }
327
};
328
329
class HelpDialogDelegate : public WindowDelegate {
330
public:
331
HelpDialogDelegate(const char *text, KeyHelp *key_help_array);
332
333
~HelpDialogDelegate() override;
334
335
bool WindowDelegateDraw(Window &window, bool force) override;
336
337
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
338
339
size_t GetNumLines() const { return m_text.GetSize(); }
340
341
size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); }
342
343
protected:
344
StringList m_text;
345
int m_first_visible_line = 0;
346
};
347
348
// A surface is an abstraction for something than can be drawn on. The surface
349
// have a width, a height, a cursor position, and a multitude of drawing
350
// operations. This type should be sub-classed to get an actually useful ncurses
351
// object, such as a Window or a Pad.
352
class Surface {
353
public:
354
enum class Type { Window, Pad };
355
356
Surface(Surface::Type type) : m_type(type) {}
357
358
WINDOW *get() { return m_window; }
359
360
operator WINDOW *() { return m_window; }
361
362
Surface SubSurface(Rect bounds) {
363
Surface subSurface(m_type);
364
if (m_type == Type::Pad)
365
subSurface.m_window =
366
::subpad(m_window, bounds.size.height, bounds.size.width,
367
bounds.origin.y, bounds.origin.x);
368
else
369
subSurface.m_window =
370
::derwin(m_window, bounds.size.height, bounds.size.width,
371
bounds.origin.y, bounds.origin.x);
372
return subSurface;
373
}
374
375
// Copy a region of the surface to another surface.
376
void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
377
Size size) {
378
::copywin(m_window, target.get(), source_origin.y, source_origin.x,
379
target_origin.y, target_origin.x,
380
target_origin.y + size.height - 1,
381
target_origin.x + size.width - 1, false);
382
}
383
384
int GetCursorX() const { return getcurx(m_window); }
385
int GetCursorY() const { return getcury(m_window); }
386
void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
387
388
void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
389
void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
390
391
int GetMaxX() const { return getmaxx(m_window); }
392
int GetMaxY() const { return getmaxy(m_window); }
393
int GetWidth() const { return GetMaxX(); }
394
int GetHeight() const { return GetMaxY(); }
395
Size GetSize() const { return Size(GetWidth(), GetHeight()); }
396
// Get a zero origin rectangle width the surface size.
397
Rect GetFrame() const { return Rect(Point(), GetSize()); }
398
399
void Clear() { ::wclear(m_window); }
400
void Erase() { ::werase(m_window); }
401
402
void SetBackground(int color_pair_idx) {
403
::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
404
}
405
406
void PutChar(int ch) { ::waddch(m_window, ch); }
407
void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
408
409
void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
410
int bytes_left = GetWidth() - GetCursorX();
411
if (bytes_left > right_pad) {
412
bytes_left -= right_pad;
413
::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
414
}
415
}
416
417
void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
418
va_list args;
419
va_start(args, format);
420
vw_printw(m_window, format, args);
421
va_end(args);
422
}
423
424
void PrintfTruncated(int right_pad, const char *format, ...)
425
__attribute__((format(printf, 3, 4))) {
426
va_list args;
427
va_start(args, format);
428
StreamString strm;
429
strm.PrintfVarArg(format, args);
430
va_end(args);
431
PutCStringTruncated(right_pad, strm.GetData());
432
}
433
434
void VerticalLine(int n, chtype v_char = ACS_VLINE) {
435
::wvline(m_window, v_char, n);
436
}
437
void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
438
::whline(m_window, h_char, n);
439
}
440
void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
441
::box(m_window, v_char, h_char);
442
}
443
444
void TitledBox(const char *title, chtype v_char = ACS_VLINE,
445
chtype h_char = ACS_HLINE) {
446
Box(v_char, h_char);
447
int title_offset = 2;
448
MoveCursor(title_offset, 0);
449
PutChar('[');
450
PutCString(title, GetWidth() - title_offset);
451
PutChar(']');
452
}
453
454
void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
455
chtype h_char = ACS_HLINE) {
456
MoveCursor(bounds.origin.x, bounds.origin.y);
457
VerticalLine(bounds.size.height);
458
HorizontalLine(bounds.size.width);
459
PutChar(ACS_ULCORNER);
460
461
MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
462
VerticalLine(bounds.size.height);
463
PutChar(ACS_URCORNER);
464
465
MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
466
HorizontalLine(bounds.size.width);
467
PutChar(ACS_LLCORNER);
468
469
MoveCursor(bounds.origin.x + bounds.size.width - 1,
470
bounds.origin.y + bounds.size.height - 1);
471
PutChar(ACS_LRCORNER);
472
}
473
474
void TitledBox(const Rect &bounds, const char *title,
475
chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
476
Box(bounds, v_char, h_char);
477
int title_offset = 2;
478
MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
479
PutChar('[');
480
PutCString(title, bounds.size.width - title_offset);
481
PutChar(']');
482
}
483
484
// Curses doesn't allow direct output of color escape sequences, but that's
485
// how we get source lines from the Highligher class. Read the line and
486
// convert color escape sequences to curses color attributes. Use
487
// first_skip_count to skip leading visible characters. Returns false if all
488
// visible characters were skipped due to first_skip_count.
489
bool OutputColoredStringTruncated(int right_pad, StringRef string,
490
size_t skip_first_count,
491
bool use_blue_background) {
492
attr_t saved_attr;
493
short saved_pair;
494
bool result = false;
495
wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
496
if (use_blue_background)
497
::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
498
while (!string.empty()) {
499
size_t esc_pos = string.find(ANSI_ESC_START);
500
if (esc_pos == StringRef::npos) {
501
string = string.substr(skip_first_count);
502
if (!string.empty()) {
503
PutCStringTruncated(right_pad, string.data(), string.size());
504
result = true;
505
}
506
break;
507
}
508
if (esc_pos > 0) {
509
if (skip_first_count > 0) {
510
int skip = std::min(esc_pos, skip_first_count);
511
string = string.substr(skip);
512
skip_first_count -= skip;
513
esc_pos -= skip;
514
}
515
if (esc_pos > 0) {
516
PutCStringTruncated(right_pad, string.data(), esc_pos);
517
result = true;
518
string = string.drop_front(esc_pos);
519
}
520
}
521
bool consumed = string.consume_front(ANSI_ESC_START);
522
assert(consumed);
523
UNUSED_IF_ASSERT_DISABLED(consumed);
524
// This is written to match our Highlighter classes, which seem to
525
// generate only foreground color escape sequences. If necessary, this
526
// will need to be extended.
527
// Only 8 basic foreground colors, underline and reset, our Highlighter
528
// doesn't use anything else.
529
int value;
530
if (!!string.consumeInteger(10, value) || // Returns false on success.
531
!(value == 0 || value == ANSI_CTRL_UNDERLINE ||
532
(value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) {
533
llvm::errs() << "No valid color code in color escape sequence.\n";
534
continue;
535
}
536
if (!string.consume_front(ANSI_ESC_END)) {
537
llvm::errs() << "Missing '" << ANSI_ESC_END
538
<< "' 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 if (value == ANSI_CTRL_UNDERLINE) {
546
::wattron(m_window, A_UNDERLINE);
547
} else {
548
// Mapped directly to first 16 color pairs (black/blue background).
549
::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 +
550
(use_blue_background ? 8 : 0)));
551
}
552
}
553
wattr_set(m_window, saved_attr, saved_pair, nullptr);
554
return result;
555
}
556
557
protected:
558
Type m_type;
559
WINDOW *m_window = nullptr;
560
};
561
562
class Pad : public Surface {
563
public:
564
Pad(Size size) : Surface(Surface::Type::Pad) {
565
m_window = ::newpad(size.height, size.width);
566
}
567
568
~Pad() { ::delwin(m_window); }
569
};
570
571
class Window : public Surface {
572
public:
573
Window(const char *name)
574
: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
575
m_parent(nullptr), m_subwindows(), m_delegate_sp(),
576
m_curr_active_window_idx(UINT32_MAX),
577
m_prev_active_window_idx(UINT32_MAX), m_delete(false),
578
m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
579
580
Window(const char *name, WINDOW *w, bool del = true)
581
: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
582
m_parent(nullptr), m_subwindows(), m_delegate_sp(),
583
m_curr_active_window_idx(UINT32_MAX),
584
m_prev_active_window_idx(UINT32_MAX), m_delete(del),
585
m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
586
if (w)
587
Reset(w);
588
}
589
590
Window(const char *name, const Rect &bounds)
591
: Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
592
m_parent(nullptr), m_subwindows(), m_delegate_sp(),
593
m_curr_active_window_idx(UINT32_MAX),
594
m_prev_active_window_idx(UINT32_MAX), m_delete(false),
595
m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
596
Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
597
bounds.origin.y));
598
}
599
600
virtual ~Window() {
601
RemoveSubWindows();
602
Reset();
603
}
604
605
void Reset(WINDOW *w = nullptr, bool del = true) {
606
if (m_window == w)
607
return;
608
609
if (m_panel) {
610
::del_panel(m_panel);
611
m_panel = nullptr;
612
}
613
if (m_window && m_delete) {
614
::delwin(m_window);
615
m_window = nullptr;
616
m_delete = false;
617
}
618
if (w) {
619
m_window = w;
620
m_panel = ::new_panel(m_window);
621
m_delete = del;
622
}
623
}
624
625
// Get the rectangle in our parent window
626
Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
627
628
Rect GetCenteredRect(int width, int height) {
629
Size size = GetSize();
630
width = std::min(size.width, width);
631
height = std::min(size.height, height);
632
int x = (size.width - width) / 2;
633
int y = (size.height - height) / 2;
634
return Rect(Point(x, y), Size(width, height));
635
}
636
637
int GetChar() { return ::wgetch(m_window); }
638
Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
639
int GetParentX() const { return getparx(m_window); }
640
int GetParentY() const { return getpary(m_window); }
641
void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
642
void Resize(int w, int h) { ::wresize(m_window, h, w); }
643
void Resize(const Size &size) {
644
::wresize(m_window, size.height, size.width);
645
}
646
void MoveWindow(const Point &origin) {
647
const bool moving_window = origin != GetParentOrigin();
648
if (m_is_subwin && moving_window) {
649
// Can't move subwindows, must delete and re-create
650
Size size = GetSize();
651
Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
652
origin.x),
653
true);
654
} else {
655
::mvwin(m_window, origin.y, origin.x);
656
}
657
}
658
659
void SetBounds(const Rect &bounds) {
660
const bool moving_window = bounds.origin != GetParentOrigin();
661
if (m_is_subwin && moving_window) {
662
// Can't move subwindows, must delete and re-create
663
Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
664
bounds.origin.y, bounds.origin.x),
665
true);
666
} else {
667
if (moving_window)
668
MoveWindow(bounds.origin);
669
Resize(bounds.size);
670
}
671
}
672
673
void Touch() {
674
::touchwin(m_window);
675
if (m_parent)
676
m_parent->Touch();
677
}
678
679
WindowSP CreateSubWindow(const char *name, const Rect &bounds,
680
bool make_active) {
681
auto get_window = [this, &bounds]() {
682
return m_window
683
? ::subwin(m_window, bounds.size.height, bounds.size.width,
684
bounds.origin.y, bounds.origin.x)
685
: ::newwin(bounds.size.height, bounds.size.width,
686
bounds.origin.y, bounds.origin.x);
687
};
688
WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
689
subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
690
subwindow_sp->m_parent = this;
691
if (make_active) {
692
m_prev_active_window_idx = m_curr_active_window_idx;
693
m_curr_active_window_idx = m_subwindows.size();
694
}
695
m_subwindows.push_back(subwindow_sp);
696
::top_panel(subwindow_sp->m_panel);
697
m_needs_update = true;
698
return subwindow_sp;
699
}
700
701
bool RemoveSubWindow(Window *window) {
702
Windows::iterator pos, end = m_subwindows.end();
703
size_t i = 0;
704
for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
705
if ((*pos).get() == window) {
706
if (m_prev_active_window_idx == i)
707
m_prev_active_window_idx = UINT32_MAX;
708
else if (m_prev_active_window_idx != UINT32_MAX &&
709
m_prev_active_window_idx > i)
710
--m_prev_active_window_idx;
711
712
if (m_curr_active_window_idx == i)
713
m_curr_active_window_idx = UINT32_MAX;
714
else if (m_curr_active_window_idx != UINT32_MAX &&
715
m_curr_active_window_idx > i)
716
--m_curr_active_window_idx;
717
window->Erase();
718
m_subwindows.erase(pos);
719
m_needs_update = true;
720
if (m_parent)
721
m_parent->Touch();
722
else
723
::touchwin(stdscr);
724
return true;
725
}
726
}
727
return false;
728
}
729
730
WindowSP FindSubWindow(const char *name) {
731
Windows::iterator pos, end = m_subwindows.end();
732
size_t i = 0;
733
for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
734
if ((*pos)->m_name == name)
735
return *pos;
736
}
737
return WindowSP();
738
}
739
740
void RemoveSubWindows() {
741
m_curr_active_window_idx = UINT32_MAX;
742
m_prev_active_window_idx = UINT32_MAX;
743
for (Windows::iterator pos = m_subwindows.begin();
744
pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
745
(*pos)->Erase();
746
}
747
if (m_parent)
748
m_parent->Touch();
749
else
750
::touchwin(stdscr);
751
}
752
753
// Window drawing utilities
754
void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
755
attr_t attr = 0;
756
if (IsActive())
757
attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
758
else
759
attr = 0;
760
if (attr)
761
AttributeOn(attr);
762
763
Box();
764
MoveCursor(3, 0);
765
766
if (title && title[0]) {
767
PutChar('<');
768
PutCString(title);
769
PutChar('>');
770
}
771
772
if (bottom_message && bottom_message[0]) {
773
int bottom_message_length = strlen(bottom_message);
774
int x = GetWidth() - 3 - (bottom_message_length + 2);
775
776
if (x > 0) {
777
MoveCursor(x, GetHeight() - 1);
778
PutChar('[');
779
PutCString(bottom_message);
780
PutChar(']');
781
} else {
782
MoveCursor(1, GetHeight() - 1);
783
PutChar('[');
784
PutCStringTruncated(1, bottom_message);
785
}
786
}
787
if (attr)
788
AttributeOff(attr);
789
}
790
791
virtual void Draw(bool force) {
792
if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
793
return;
794
795
for (auto &subwindow_sp : m_subwindows)
796
subwindow_sp->Draw(force);
797
}
798
799
bool CreateHelpSubwindow() {
800
if (m_delegate_sp) {
801
const char *text = m_delegate_sp->WindowDelegateGetHelpText();
802
KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
803
if ((text && text[0]) || key_help) {
804
std::unique_ptr<HelpDialogDelegate> help_delegate_up(
805
new HelpDialogDelegate(text, key_help));
806
const size_t num_lines = help_delegate_up->GetNumLines();
807
const size_t max_length = help_delegate_up->GetMaxLineLength();
808
Rect bounds = GetBounds();
809
bounds.Inset(1, 1);
810
if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
811
bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
812
bounds.size.width = max_length + 4;
813
} else {
814
if (bounds.size.width > 100) {
815
const int inset_w = bounds.size.width / 4;
816
bounds.origin.x += inset_w;
817
bounds.size.width -= 2 * inset_w;
818
}
819
}
820
821
if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
822
bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
823
bounds.size.height = num_lines + 2;
824
} else {
825
if (bounds.size.height > 100) {
826
const int inset_h = bounds.size.height / 4;
827
bounds.origin.y += inset_h;
828
bounds.size.height -= 2 * inset_h;
829
}
830
}
831
WindowSP help_window_sp;
832
Window *parent_window = GetParent();
833
if (parent_window)
834
help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
835
else
836
help_window_sp = CreateSubWindow("Help", bounds, true);
837
help_window_sp->SetDelegate(
838
WindowDelegateSP(help_delegate_up.release()));
839
return true;
840
}
841
}
842
return false;
843
}
844
845
virtual HandleCharResult HandleChar(int key) {
846
// Always check the active window first
847
HandleCharResult result = eKeyNotHandled;
848
WindowSP active_window_sp = GetActiveWindow();
849
if (active_window_sp) {
850
result = active_window_sp->HandleChar(key);
851
if (result != eKeyNotHandled)
852
return result;
853
}
854
855
if (m_delegate_sp) {
856
result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
857
if (result != eKeyNotHandled)
858
return result;
859
}
860
861
// Then check for any windows that want any keys that weren't handled. This
862
// is typically only for a menubar. Make a copy of the subwindows in case
863
// any HandleChar() functions muck with the subwindows. If we don't do
864
// this, we can crash when iterating over the subwindows.
865
Windows subwindows(m_subwindows);
866
for (auto subwindow_sp : subwindows) {
867
if (!subwindow_sp->m_can_activate) {
868
HandleCharResult result = subwindow_sp->HandleChar(key);
869
if (result != eKeyNotHandled)
870
return result;
871
}
872
}
873
874
return eKeyNotHandled;
875
}
876
877
WindowSP GetActiveWindow() {
878
if (!m_subwindows.empty()) {
879
if (m_curr_active_window_idx >= m_subwindows.size()) {
880
if (m_prev_active_window_idx < m_subwindows.size()) {
881
m_curr_active_window_idx = m_prev_active_window_idx;
882
m_prev_active_window_idx = UINT32_MAX;
883
} else if (IsActive()) {
884
m_prev_active_window_idx = UINT32_MAX;
885
m_curr_active_window_idx = UINT32_MAX;
886
887
// Find first window that wants to be active if this window is active
888
const size_t num_subwindows = m_subwindows.size();
889
for (size_t i = 0; i < num_subwindows; ++i) {
890
if (m_subwindows[i]->GetCanBeActive()) {
891
m_curr_active_window_idx = i;
892
break;
893
}
894
}
895
}
896
}
897
898
if (m_curr_active_window_idx < m_subwindows.size())
899
return m_subwindows[m_curr_active_window_idx];
900
}
901
return WindowSP();
902
}
903
904
bool GetCanBeActive() const { return m_can_activate; }
905
906
void SetCanBeActive(bool b) { m_can_activate = b; }
907
908
void SetDelegate(const WindowDelegateSP &delegate_sp) {
909
m_delegate_sp = delegate_sp;
910
}
911
912
Window *GetParent() const { return m_parent; }
913
914
bool IsActive() const {
915
if (m_parent)
916
return m_parent->GetActiveWindow().get() == this;
917
else
918
return true; // Top level window is always active
919
}
920
921
void SelectNextWindowAsActive() {
922
// Move active focus to next window
923
const int num_subwindows = m_subwindows.size();
924
int start_idx = 0;
925
if (m_curr_active_window_idx != UINT32_MAX) {
926
m_prev_active_window_idx = m_curr_active_window_idx;
927
start_idx = m_curr_active_window_idx + 1;
928
}
929
for (int idx = start_idx; idx < num_subwindows; ++idx) {
930
if (m_subwindows[idx]->GetCanBeActive()) {
931
m_curr_active_window_idx = idx;
932
return;
933
}
934
}
935
for (int idx = 0; idx < start_idx; ++idx) {
936
if (m_subwindows[idx]->GetCanBeActive()) {
937
m_curr_active_window_idx = idx;
938
break;
939
}
940
}
941
}
942
943
void SelectPreviousWindowAsActive() {
944
// Move active focus to previous window
945
const int num_subwindows = m_subwindows.size();
946
int start_idx = num_subwindows - 1;
947
if (m_curr_active_window_idx != UINT32_MAX) {
948
m_prev_active_window_idx = m_curr_active_window_idx;
949
start_idx = m_curr_active_window_idx - 1;
950
}
951
for (int idx = start_idx; idx >= 0; --idx) {
952
if (m_subwindows[idx]->GetCanBeActive()) {
953
m_curr_active_window_idx = idx;
954
return;
955
}
956
}
957
for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
958
if (m_subwindows[idx]->GetCanBeActive()) {
959
m_curr_active_window_idx = idx;
960
break;
961
}
962
}
963
}
964
965
const char *GetName() const { return m_name.c_str(); }
966
967
protected:
968
std::string m_name;
969
PANEL *m_panel;
970
Window *m_parent;
971
Windows m_subwindows;
972
WindowDelegateSP m_delegate_sp;
973
uint32_t m_curr_active_window_idx;
974
uint32_t m_prev_active_window_idx;
975
bool m_delete;
976
bool m_needs_update;
977
bool m_can_activate;
978
bool m_is_subwin;
979
980
private:
981
Window(const Window &) = delete;
982
const Window &operator=(const Window &) = delete;
983
};
984
985
/////////
986
// Forms
987
/////////
988
989
// A scroll context defines a vertical region that needs to be visible in a
990
// scrolling area. The region is defined by the index of the start and end lines
991
// of the region. The start and end lines may be equal, in which case, the
992
// region is a single line.
993
struct ScrollContext {
994
int start;
995
int end;
996
997
ScrollContext(int line) : start(line), end(line) {}
998
ScrollContext(int _start, int _end) : start(_start), end(_end) {}
999
1000
void Offset(int offset) {
1001
start += offset;
1002
end += offset;
1003
}
1004
};
1005
1006
class FieldDelegate {
1007
public:
1008
virtual ~FieldDelegate() = default;
1009
1010
// Returns the number of lines needed to draw the field. The draw method will
1011
// be given a surface that have exactly this number of lines.
1012
virtual int FieldDelegateGetHeight() = 0;
1013
1014
// Returns the scroll context in the local coordinates of the field. By
1015
// default, the scroll context spans the whole field. Bigger fields with
1016
// internal navigation should override this method to provide a finer context.
1017
// Typical override methods would first get the scroll context of the internal
1018
// element then add the offset of the element in the field.
1019
virtual ScrollContext FieldDelegateGetScrollContext() {
1020
return ScrollContext(0, FieldDelegateGetHeight() - 1);
1021
}
1022
1023
// Draw the field in the given subpad surface. The surface have a height that
1024
// is equal to the height returned by FieldDelegateGetHeight(). If the field
1025
// is selected in the form window, then is_selected will be true.
1026
virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0;
1027
1028
// Handle the key that wasn't handled by the form window or a container field.
1029
virtual HandleCharResult FieldDelegateHandleChar(int key) {
1030
return eKeyNotHandled;
1031
}
1032
1033
// This is executed once the user exists the field, that is, once the user
1034
// navigates to the next or the previous field. This is particularly useful to
1035
// do in-field validation and error setting. Fields with internal navigation
1036
// should call this method on their fields.
1037
virtual void FieldDelegateExitCallback() {}
1038
1039
// Fields may have internal navigation, for instance, a List Field have
1040
// multiple internal elements, which needs to be navigated. To allow for this
1041
// mechanism, the window shouldn't handle the navigation keys all the time,
1042
// and instead call the key handing method of the selected field. It should
1043
// only handle the navigation keys when the field contains a single element or
1044
// have the last or first element selected depending on if the user is
1045
// navigating forward or backward. Additionally, once a field is selected in
1046
// the forward or backward direction, its first or last internal element
1047
// should be selected. The following methods implements those mechanisms.
1048
1049
// Returns true if the first element in the field is selected or if the field
1050
// contains a single element.
1051
virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; }
1052
1053
// Returns true if the last element in the field is selected or if the field
1054
// contains a single element.
1055
virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1056
1057
// Select the first element in the field if multiple elements exists.
1058
virtual void FieldDelegateSelectFirstElement() {}
1059
1060
// Select the last element in the field if multiple elements exists.
1061
virtual void FieldDelegateSelectLastElement() {}
1062
1063
// Returns true if the field has an error, false otherwise.
1064
virtual bool FieldDelegateHasError() { return false; }
1065
1066
bool FieldDelegateIsVisible() { return m_is_visible; }
1067
1068
void FieldDelegateHide() { m_is_visible = false; }
1069
1070
void FieldDelegateShow() { m_is_visible = true; }
1071
1072
protected:
1073
bool m_is_visible = true;
1074
};
1075
1076
typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
1077
1078
class TextFieldDelegate : public FieldDelegate {
1079
public:
1080
TextFieldDelegate(const char *label, const char *content, bool required)
1081
: m_label(label), m_required(required) {
1082
if (content)
1083
m_content = content;
1084
}
1085
1086
// Text fields are drawn as titled boxes of a single line, with a possible
1087
// error messages at the end.
1088
//
1089
// __[Label]___________
1090
// | |
1091
// |__________________|
1092
// - Error message if it exists.
1093
1094
// The text field has a height of 3 lines. 2 lines for borders and 1 line for
1095
// the content.
1096
int GetFieldHeight() { return 3; }
1097
1098
// The text field has a full height of 3 or 4 lines. 3 lines for the actual
1099
// field and an optional line for an error if it exists.
1100
int FieldDelegateGetHeight() override {
1101
int height = GetFieldHeight();
1102
if (FieldDelegateHasError())
1103
height++;
1104
return height;
1105
}
1106
1107
// Get the cursor X position in the surface coordinate.
1108
int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1109
1110
int GetContentLength() { return m_content.length(); }
1111
1112
void DrawContent(Surface &surface, bool is_selected) {
1113
UpdateScrolling(surface.GetWidth());
1114
1115
surface.MoveCursor(0, 0);
1116
const char *text = m_content.c_str() + m_first_visibile_char;
1117
surface.PutCString(text, surface.GetWidth());
1118
1119
// Highlight the cursor.
1120
surface.MoveCursor(GetCursorXPosition(), 0);
1121
if (is_selected)
1122
surface.AttributeOn(A_REVERSE);
1123
if (m_cursor_position == GetContentLength())
1124
// Cursor is past the last character. Highlight an empty space.
1125
surface.PutChar(' ');
1126
else
1127
surface.PutChar(m_content[m_cursor_position]);
1128
if (is_selected)
1129
surface.AttributeOff(A_REVERSE);
1130
}
1131
1132
void DrawField(Surface &surface, bool is_selected) {
1133
surface.TitledBox(m_label.c_str());
1134
1135
Rect content_bounds = surface.GetFrame();
1136
content_bounds.Inset(1, 1);
1137
Surface content_surface = surface.SubSurface(content_bounds);
1138
1139
DrawContent(content_surface, is_selected);
1140
}
1141
1142
void DrawError(Surface &surface) {
1143
if (!FieldDelegateHasError())
1144
return;
1145
surface.MoveCursor(0, 0);
1146
surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1147
surface.PutChar(ACS_DIAMOND);
1148
surface.PutChar(' ');
1149
surface.PutCStringTruncated(1, GetError().c_str());
1150
surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1151
}
1152
1153
void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1154
Rect frame = surface.GetFrame();
1155
Rect field_bounds, error_bounds;
1156
frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1157
Surface field_surface = surface.SubSurface(field_bounds);
1158
Surface error_surface = surface.SubSurface(error_bounds);
1159
1160
DrawField(field_surface, is_selected);
1161
DrawError(error_surface);
1162
}
1163
1164
// Get the position of the last visible character.
1165
int GetLastVisibleCharPosition(int width) {
1166
int position = m_first_visibile_char + width - 1;
1167
return std::min(position, GetContentLength());
1168
}
1169
1170
void UpdateScrolling(int width) {
1171
if (m_cursor_position < m_first_visibile_char) {
1172
m_first_visibile_char = m_cursor_position;
1173
return;
1174
}
1175
1176
if (m_cursor_position > GetLastVisibleCharPosition(width))
1177
m_first_visibile_char = m_cursor_position - (width - 1);
1178
}
1179
1180
// The cursor is allowed to move one character past the string.
1181
// m_cursor_position is in range [0, GetContentLength()].
1182
void MoveCursorRight() {
1183
if (m_cursor_position < GetContentLength())
1184
m_cursor_position++;
1185
}
1186
1187
void MoveCursorLeft() {
1188
if (m_cursor_position > 0)
1189
m_cursor_position--;
1190
}
1191
1192
void MoveCursorToStart() { m_cursor_position = 0; }
1193
1194
void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1195
1196
void ScrollLeft() {
1197
if (m_first_visibile_char > 0)
1198
m_first_visibile_char--;
1199
}
1200
1201
// Insert a character at the current cursor position and advance the cursor
1202
// position.
1203
void InsertChar(char character) {
1204
m_content.insert(m_cursor_position, 1, character);
1205
m_cursor_position++;
1206
ClearError();
1207
}
1208
1209
// Remove the character before the cursor position, retreat the cursor
1210
// position, and scroll left.
1211
void RemovePreviousChar() {
1212
if (m_cursor_position == 0)
1213
return;
1214
1215
m_content.erase(m_cursor_position - 1, 1);
1216
m_cursor_position--;
1217
ScrollLeft();
1218
ClearError();
1219
}
1220
1221
// Remove the character after the cursor position.
1222
void RemoveNextChar() {
1223
if (m_cursor_position == GetContentLength())
1224
return;
1225
1226
m_content.erase(m_cursor_position, 1);
1227
ClearError();
1228
}
1229
1230
// Clear characters from the current cursor position to the end.
1231
void ClearToEnd() {
1232
m_content.erase(m_cursor_position);
1233
ClearError();
1234
}
1235
1236
void Clear() {
1237
m_content.clear();
1238
m_cursor_position = 0;
1239
ClearError();
1240
}
1241
1242
// True if the key represents a char that can be inserted in the field
1243
// content, false otherwise.
1244
virtual bool IsAcceptableChar(int key) {
1245
// The behavior of isprint is undefined when the value is not representable
1246
// as an unsigned char. So explicitly check for non-ascii key codes.
1247
if (key > 127)
1248
return false;
1249
return isprint(key);
1250
}
1251
1252
HandleCharResult FieldDelegateHandleChar(int key) override {
1253
if (IsAcceptableChar(key)) {
1254
ClearError();
1255
InsertChar((char)key);
1256
return eKeyHandled;
1257
}
1258
1259
switch (key) {
1260
case KEY_HOME:
1261
case KEY_CTRL_A:
1262
MoveCursorToStart();
1263
return eKeyHandled;
1264
case KEY_END:
1265
case KEY_CTRL_E:
1266
MoveCursorToEnd();
1267
return eKeyHandled;
1268
case KEY_RIGHT:
1269
case KEY_SF:
1270
MoveCursorRight();
1271
return eKeyHandled;
1272
case KEY_LEFT:
1273
case KEY_SR:
1274
MoveCursorLeft();
1275
return eKeyHandled;
1276
case KEY_BACKSPACE:
1277
case KEY_DELETE:
1278
RemovePreviousChar();
1279
return eKeyHandled;
1280
case KEY_DC:
1281
RemoveNextChar();
1282
return eKeyHandled;
1283
case KEY_EOL:
1284
case KEY_CTRL_K:
1285
ClearToEnd();
1286
return eKeyHandled;
1287
case KEY_DL:
1288
case KEY_CLEAR:
1289
Clear();
1290
return eKeyHandled;
1291
default:
1292
break;
1293
}
1294
return eKeyNotHandled;
1295
}
1296
1297
bool FieldDelegateHasError() override { return !m_error.empty(); }
1298
1299
void FieldDelegateExitCallback() override {
1300
if (!IsSpecified() && m_required)
1301
SetError("This field is required!");
1302
}
1303
1304
bool IsSpecified() { return !m_content.empty(); }
1305
1306
void ClearError() { m_error.clear(); }
1307
1308
const std::string &GetError() { return m_error; }
1309
1310
void SetError(const char *error) { m_error = error; }
1311
1312
const std::string &GetText() { return m_content; }
1313
1314
void SetText(const char *text) {
1315
if (text == nullptr) {
1316
m_content.clear();
1317
return;
1318
}
1319
m_content = text;
1320
}
1321
1322
protected:
1323
std::string m_label;
1324
bool m_required;
1325
// The position of the top left corner character of the border.
1326
std::string m_content;
1327
// The cursor position in the content string itself. Can be in the range
1328
// [0, GetContentLength()].
1329
int m_cursor_position = 0;
1330
// The index of the first visible character in the content.
1331
int m_first_visibile_char = 0;
1332
// Optional error message. If empty, field is considered to have no error.
1333
std::string m_error;
1334
};
1335
1336
class IntegerFieldDelegate : public TextFieldDelegate {
1337
public:
1338
IntegerFieldDelegate(const char *label, int content, bool required)
1339
: TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1340
1341
// Only accept digits.
1342
bool IsAcceptableChar(int key) override { return isdigit(key); }
1343
1344
// Returns the integer content of the field.
1345
int GetInteger() { return std::stoi(m_content); }
1346
};
1347
1348
class FileFieldDelegate : public TextFieldDelegate {
1349
public:
1350
FileFieldDelegate(const char *label, const char *content, bool need_to_exist,
1351
bool required)
1352
: TextFieldDelegate(label, content, required),
1353
m_need_to_exist(need_to_exist) {}
1354
1355
void FieldDelegateExitCallback() override {
1356
TextFieldDelegate::FieldDelegateExitCallback();
1357
if (!IsSpecified())
1358
return;
1359
1360
if (!m_need_to_exist)
1361
return;
1362
1363
FileSpec file = GetResolvedFileSpec();
1364
if (!FileSystem::Instance().Exists(file)) {
1365
SetError("File doesn't exist!");
1366
return;
1367
}
1368
if (FileSystem::Instance().IsDirectory(file)) {
1369
SetError("Not a file!");
1370
return;
1371
}
1372
}
1373
1374
FileSpec GetFileSpec() {
1375
FileSpec file_spec(GetPath());
1376
return file_spec;
1377
}
1378
1379
FileSpec GetResolvedFileSpec() {
1380
FileSpec file_spec(GetPath());
1381
FileSystem::Instance().Resolve(file_spec);
1382
return file_spec;
1383
}
1384
1385
const std::string &GetPath() { return m_content; }
1386
1387
protected:
1388
bool m_need_to_exist;
1389
};
1390
1391
class DirectoryFieldDelegate : public TextFieldDelegate {
1392
public:
1393
DirectoryFieldDelegate(const char *label, const char *content,
1394
bool need_to_exist, bool required)
1395
: TextFieldDelegate(label, content, required),
1396
m_need_to_exist(need_to_exist) {}
1397
1398
void FieldDelegateExitCallback() override {
1399
TextFieldDelegate::FieldDelegateExitCallback();
1400
if (!IsSpecified())
1401
return;
1402
1403
if (!m_need_to_exist)
1404
return;
1405
1406
FileSpec file = GetResolvedFileSpec();
1407
if (!FileSystem::Instance().Exists(file)) {
1408
SetError("Directory doesn't exist!");
1409
return;
1410
}
1411
if (!FileSystem::Instance().IsDirectory(file)) {
1412
SetError("Not a directory!");
1413
return;
1414
}
1415
}
1416
1417
FileSpec GetFileSpec() {
1418
FileSpec file_spec(GetPath());
1419
return file_spec;
1420
}
1421
1422
FileSpec GetResolvedFileSpec() {
1423
FileSpec file_spec(GetPath());
1424
FileSystem::Instance().Resolve(file_spec);
1425
return file_spec;
1426
}
1427
1428
const std::string &GetPath() { return m_content; }
1429
1430
protected:
1431
bool m_need_to_exist;
1432
};
1433
1434
class ArchFieldDelegate : public TextFieldDelegate {
1435
public:
1436
ArchFieldDelegate(const char *label, const char *content, bool required)
1437
: TextFieldDelegate(label, content, required) {}
1438
1439
void FieldDelegateExitCallback() override {
1440
TextFieldDelegate::FieldDelegateExitCallback();
1441
if (!IsSpecified())
1442
return;
1443
1444
if (!GetArchSpec().IsValid())
1445
SetError("Not a valid arch!");
1446
}
1447
1448
const std::string &GetArchString() { return m_content; }
1449
1450
ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1451
};
1452
1453
class BooleanFieldDelegate : public FieldDelegate {
1454
public:
1455
BooleanFieldDelegate(const char *label, bool content)
1456
: m_label(label), m_content(content) {}
1457
1458
// Boolean fields are drawn as checkboxes.
1459
//
1460
// [X] Label or [ ] Label
1461
1462
// Boolean fields are have a single line.
1463
int FieldDelegateGetHeight() override { return 1; }
1464
1465
void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1466
surface.MoveCursor(0, 0);
1467
surface.PutChar('[');
1468
if (is_selected)
1469
surface.AttributeOn(A_REVERSE);
1470
surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1471
if (is_selected)
1472
surface.AttributeOff(A_REVERSE);
1473
surface.PutChar(']');
1474
surface.PutChar(' ');
1475
surface.PutCString(m_label.c_str());
1476
}
1477
1478
void ToggleContent() { m_content = !m_content; }
1479
1480
void SetContentToTrue() { m_content = true; }
1481
1482
void SetContentToFalse() { m_content = false; }
1483
1484
HandleCharResult FieldDelegateHandleChar(int key) override {
1485
switch (key) {
1486
case 't':
1487
case '1':
1488
SetContentToTrue();
1489
return eKeyHandled;
1490
case 'f':
1491
case '0':
1492
SetContentToFalse();
1493
return eKeyHandled;
1494
case ' ':
1495
case '\r':
1496
case '\n':
1497
case KEY_ENTER:
1498
ToggleContent();
1499
return eKeyHandled;
1500
default:
1501
break;
1502
}
1503
return eKeyNotHandled;
1504
}
1505
1506
// Returns the boolean content of the field.
1507
bool GetBoolean() { return m_content; }
1508
1509
protected:
1510
std::string m_label;
1511
bool m_content;
1512
};
1513
1514
class ChoicesFieldDelegate : public FieldDelegate {
1515
public:
1516
ChoicesFieldDelegate(const char *label, int number_of_visible_choices,
1517
std::vector<std::string> choices)
1518
: m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1519
m_choices(choices) {}
1520
1521
// Choices fields are drawn as titles boxses of a number of visible choices.
1522
// The rest of the choices become visible as the user scroll. The selected
1523
// choice is denoted by a diamond as the first character.
1524
//
1525
// __[Label]___________
1526
// |-Choice 1 |
1527
// | Choice 2 |
1528
// | Choice 3 |
1529
// |__________________|
1530
1531
// Choices field have two border characters plus the number of visible
1532
// choices.
1533
int FieldDelegateGetHeight() override {
1534
return m_number_of_visible_choices + 2;
1535
}
1536
1537
int GetNumberOfChoices() { return m_choices.size(); }
1538
1539
// Get the index of the last visible choice.
1540
int GetLastVisibleChoice() {
1541
int index = m_first_visibile_choice + m_number_of_visible_choices;
1542
return std::min(index, GetNumberOfChoices()) - 1;
1543
}
1544
1545
void DrawContent(Surface &surface, bool is_selected) {
1546
int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1547
for (int i = 0; i < choices_to_draw; i++) {
1548
surface.MoveCursor(0, i);
1549
int current_choice = m_first_visibile_choice + i;
1550
const char *text = m_choices[current_choice].c_str();
1551
bool highlight = is_selected && current_choice == m_choice;
1552
if (highlight)
1553
surface.AttributeOn(A_REVERSE);
1554
surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1555
surface.PutCString(text);
1556
if (highlight)
1557
surface.AttributeOff(A_REVERSE);
1558
}
1559
}
1560
1561
void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1562
UpdateScrolling();
1563
1564
surface.TitledBox(m_label.c_str());
1565
1566
Rect content_bounds = surface.GetFrame();
1567
content_bounds.Inset(1, 1);
1568
Surface content_surface = surface.SubSurface(content_bounds);
1569
1570
DrawContent(content_surface, is_selected);
1571
}
1572
1573
void SelectPrevious() {
1574
if (m_choice > 0)
1575
m_choice--;
1576
}
1577
1578
void SelectNext() {
1579
if (m_choice < GetNumberOfChoices() - 1)
1580
m_choice++;
1581
}
1582
1583
void UpdateScrolling() {
1584
if (m_choice > GetLastVisibleChoice()) {
1585
m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1586
return;
1587
}
1588
1589
if (m_choice < m_first_visibile_choice)
1590
m_first_visibile_choice = m_choice;
1591
}
1592
1593
HandleCharResult FieldDelegateHandleChar(int key) override {
1594
switch (key) {
1595
case KEY_UP:
1596
SelectPrevious();
1597
return eKeyHandled;
1598
case KEY_DOWN:
1599
SelectNext();
1600
return eKeyHandled;
1601
default:
1602
break;
1603
}
1604
return eKeyNotHandled;
1605
}
1606
1607
// Returns the content of the choice as a string.
1608
std::string GetChoiceContent() { return m_choices[m_choice]; }
1609
1610
// Returns the index of the choice.
1611
int GetChoice() { return m_choice; }
1612
1613
void SetChoice(llvm::StringRef choice) {
1614
for (int i = 0; i < GetNumberOfChoices(); i++) {
1615
if (choice == m_choices[i]) {
1616
m_choice = i;
1617
return;
1618
}
1619
}
1620
}
1621
1622
protected:
1623
std::string m_label;
1624
int m_number_of_visible_choices;
1625
std::vector<std::string> m_choices;
1626
// The index of the selected choice.
1627
int m_choice = 0;
1628
// The index of the first visible choice in the field.
1629
int m_first_visibile_choice = 0;
1630
};
1631
1632
class PlatformPluginFieldDelegate : public ChoicesFieldDelegate {
1633
public:
1634
PlatformPluginFieldDelegate(Debugger &debugger)
1635
: ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1636
PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1637
if (platform_sp)
1638
SetChoice(platform_sp->GetPluginName());
1639
}
1640
1641
std::vector<std::string> GetPossiblePluginNames() {
1642
std::vector<std::string> names;
1643
size_t i = 0;
1644
for (llvm::StringRef name =
1645
PluginManager::GetPlatformPluginNameAtIndex(i++);
1646
!name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1647
names.push_back(name.str());
1648
return names;
1649
}
1650
1651
std::string GetPluginName() {
1652
std::string plugin_name = GetChoiceContent();
1653
return plugin_name;
1654
}
1655
};
1656
1657
class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1658
public:
1659
ProcessPluginFieldDelegate()
1660
: ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1661
1662
std::vector<std::string> GetPossiblePluginNames() {
1663
std::vector<std::string> names;
1664
names.push_back("<default>");
1665
1666
size_t i = 0;
1667
for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);
1668
!name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1669
names.push_back(name.str());
1670
return names;
1671
}
1672
1673
std::string GetPluginName() {
1674
std::string plugin_name = GetChoiceContent();
1675
if (plugin_name == "<default>")
1676
return "";
1677
return plugin_name;
1678
}
1679
};
1680
1681
class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1682
public:
1683
LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1684
: ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1685
1686
static constexpr const char *kNo = "No";
1687
static constexpr const char *kYes = "Yes";
1688
1689
std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1690
std::vector<std::string> options;
1691
options.push_back(calculate_label);
1692
options.push_back(kYes);
1693
options.push_back(kNo);
1694
return options;
1695
}
1696
1697
LazyBool GetLazyBoolean() {
1698
std::string choice = GetChoiceContent();
1699
if (choice == kNo)
1700
return eLazyBoolNo;
1701
else if (choice == kYes)
1702
return eLazyBoolYes;
1703
else
1704
return eLazyBoolCalculate;
1705
}
1706
};
1707
1708
template <class T> class ListFieldDelegate : public FieldDelegate {
1709
public:
1710
ListFieldDelegate(const char *label, T default_field)
1711
: m_label(label), m_default_field(default_field),
1712
m_selection_type(SelectionType::NewButton) {}
1713
1714
// Signify which element is selected. If a field or a remove button is
1715
// selected, then m_selection_index signifies the particular field that
1716
// is selected or the field that the remove button belongs to.
1717
enum class SelectionType { Field, RemoveButton, NewButton };
1718
1719
// A List field is drawn as a titled box of a number of other fields of the
1720
// same type. Each field has a Remove button next to it that removes the
1721
// corresponding field. Finally, the last line contains a New button to add a
1722
// new field.
1723
//
1724
// __[Label]___________
1725
// | Field 0 [Remove] |
1726
// | Field 1 [Remove] |
1727
// | Field 2 [Remove] |
1728
// | [New] |
1729
// |__________________|
1730
1731
// List fields have two lines for border characters, 1 line for the New
1732
// button, and the total height of the available fields.
1733
int FieldDelegateGetHeight() override {
1734
// 2 border characters.
1735
int height = 2;
1736
// Total height of the fields.
1737
for (int i = 0; i < GetNumberOfFields(); i++) {
1738
height += m_fields[i].FieldDelegateGetHeight();
1739
}
1740
// A line for the New button.
1741
height++;
1742
return height;
1743
}
1744
1745
ScrollContext FieldDelegateGetScrollContext() override {
1746
int height = FieldDelegateGetHeight();
1747
if (m_selection_type == SelectionType::NewButton)
1748
return ScrollContext(height - 2, height - 1);
1749
1750
FieldDelegate &field = m_fields[m_selection_index];
1751
ScrollContext context = field.FieldDelegateGetScrollContext();
1752
1753
// Start at 1 because of the top border.
1754
int offset = 1;
1755
for (int i = 0; i < m_selection_index; i++) {
1756
offset += m_fields[i].FieldDelegateGetHeight();
1757
}
1758
context.Offset(offset);
1759
1760
// If the scroll context is touching the top border, include it in the
1761
// context to show the label.
1762
if (context.start == 1)
1763
context.start--;
1764
1765
// If the scroll context is touching the new button, include it as well as
1766
// the bottom border in the context.
1767
if (context.end == height - 3)
1768
context.end += 2;
1769
1770
return context;
1771
}
1772
1773
void DrawRemoveButton(Surface &surface, int highlight) {
1774
surface.MoveCursor(1, surface.GetHeight() / 2);
1775
if (highlight)
1776
surface.AttributeOn(A_REVERSE);
1777
surface.PutCString("[Remove]");
1778
if (highlight)
1779
surface.AttributeOff(A_REVERSE);
1780
}
1781
1782
void DrawFields(Surface &surface, bool is_selected) {
1783
int line = 0;
1784
int width = surface.GetWidth();
1785
for (int i = 0; i < GetNumberOfFields(); i++) {
1786
int height = m_fields[i].FieldDelegateGetHeight();
1787
Rect bounds = Rect(Point(0, line), Size(width, height));
1788
Rect field_bounds, remove_button_bounds;
1789
bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1790
field_bounds, remove_button_bounds);
1791
Surface field_surface = surface.SubSurface(field_bounds);
1792
Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1793
1794
bool is_element_selected = m_selection_index == i && is_selected;
1795
bool is_field_selected =
1796
is_element_selected && m_selection_type == SelectionType::Field;
1797
bool is_remove_button_selected =
1798
is_element_selected &&
1799
m_selection_type == SelectionType::RemoveButton;
1800
m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1801
DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1802
1803
line += height;
1804
}
1805
}
1806
1807
void DrawNewButton(Surface &surface, bool is_selected) {
1808
const char *button_text = "[New]";
1809
int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1810
surface.MoveCursor(x, 0);
1811
bool highlight =
1812
is_selected && m_selection_type == SelectionType::NewButton;
1813
if (highlight)
1814
surface.AttributeOn(A_REVERSE);
1815
surface.PutCString(button_text);
1816
if (highlight)
1817
surface.AttributeOff(A_REVERSE);
1818
}
1819
1820
void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1821
surface.TitledBox(m_label.c_str());
1822
1823
Rect content_bounds = surface.GetFrame();
1824
content_bounds.Inset(1, 1);
1825
Rect fields_bounds, new_button_bounds;
1826
content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1827
fields_bounds, new_button_bounds);
1828
Surface fields_surface = surface.SubSurface(fields_bounds);
1829
Surface new_button_surface = surface.SubSurface(new_button_bounds);
1830
1831
DrawFields(fields_surface, is_selected);
1832
DrawNewButton(new_button_surface, is_selected);
1833
}
1834
1835
void AddNewField() {
1836
m_fields.push_back(m_default_field);
1837
m_selection_index = GetNumberOfFields() - 1;
1838
m_selection_type = SelectionType::Field;
1839
FieldDelegate &field = m_fields[m_selection_index];
1840
field.FieldDelegateSelectFirstElement();
1841
}
1842
1843
void RemoveField() {
1844
m_fields.erase(m_fields.begin() + m_selection_index);
1845
if (m_selection_index != 0)
1846
m_selection_index--;
1847
1848
if (GetNumberOfFields() > 0) {
1849
m_selection_type = SelectionType::Field;
1850
FieldDelegate &field = m_fields[m_selection_index];
1851
field.FieldDelegateSelectFirstElement();
1852
} else
1853
m_selection_type = SelectionType::NewButton;
1854
}
1855
1856
HandleCharResult SelectNext(int key) {
1857
if (m_selection_type == SelectionType::NewButton)
1858
return eKeyNotHandled;
1859
1860
if (m_selection_type == SelectionType::RemoveButton) {
1861
if (m_selection_index == GetNumberOfFields() - 1) {
1862
m_selection_type = SelectionType::NewButton;
1863
return eKeyHandled;
1864
}
1865
m_selection_index++;
1866
m_selection_type = SelectionType::Field;
1867
FieldDelegate &next_field = m_fields[m_selection_index];
1868
next_field.FieldDelegateSelectFirstElement();
1869
return eKeyHandled;
1870
}
1871
1872
FieldDelegate &field = m_fields[m_selection_index];
1873
if (!field.FieldDelegateOnLastOrOnlyElement()) {
1874
return field.FieldDelegateHandleChar(key);
1875
}
1876
1877
field.FieldDelegateExitCallback();
1878
1879
m_selection_type = SelectionType::RemoveButton;
1880
return eKeyHandled;
1881
}
1882
1883
HandleCharResult SelectPrevious(int key) {
1884
if (FieldDelegateOnFirstOrOnlyElement())
1885
return eKeyNotHandled;
1886
1887
if (m_selection_type == SelectionType::RemoveButton) {
1888
m_selection_type = SelectionType::Field;
1889
FieldDelegate &field = m_fields[m_selection_index];
1890
field.FieldDelegateSelectLastElement();
1891
return eKeyHandled;
1892
}
1893
1894
if (m_selection_type == SelectionType::NewButton) {
1895
m_selection_type = SelectionType::RemoveButton;
1896
m_selection_index = GetNumberOfFields() - 1;
1897
return eKeyHandled;
1898
}
1899
1900
FieldDelegate &field = m_fields[m_selection_index];
1901
if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1902
return field.FieldDelegateHandleChar(key);
1903
}
1904
1905
field.FieldDelegateExitCallback();
1906
1907
m_selection_type = SelectionType::RemoveButton;
1908
m_selection_index--;
1909
return eKeyHandled;
1910
}
1911
1912
// If the last element of the field is selected and it didn't handle the key.
1913
// Select the next field or new button if the selected field is the last one.
1914
HandleCharResult SelectNextInList(int key) {
1915
assert(m_selection_type == SelectionType::Field);
1916
1917
FieldDelegate &field = m_fields[m_selection_index];
1918
if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1919
return eKeyHandled;
1920
1921
if (!field.FieldDelegateOnLastOrOnlyElement())
1922
return eKeyNotHandled;
1923
1924
field.FieldDelegateExitCallback();
1925
1926
if (m_selection_index == GetNumberOfFields() - 1) {
1927
m_selection_type = SelectionType::NewButton;
1928
return eKeyHandled;
1929
}
1930
1931
m_selection_index++;
1932
FieldDelegate &next_field = m_fields[m_selection_index];
1933
next_field.FieldDelegateSelectFirstElement();
1934
return eKeyHandled;
1935
}
1936
1937
HandleCharResult FieldDelegateHandleChar(int key) override {
1938
switch (key) {
1939
case '\r':
1940
case '\n':
1941
case KEY_ENTER:
1942
switch (m_selection_type) {
1943
case SelectionType::NewButton:
1944
AddNewField();
1945
return eKeyHandled;
1946
case SelectionType::RemoveButton:
1947
RemoveField();
1948
return eKeyHandled;
1949
case SelectionType::Field:
1950
return SelectNextInList(key);
1951
}
1952
break;
1953
case '\t':
1954
return SelectNext(key);
1955
case KEY_SHIFT_TAB:
1956
return SelectPrevious(key);
1957
default:
1958
break;
1959
}
1960
1961
// If the key wasn't handled and one of the fields is selected, pass the key
1962
// to that field.
1963
if (m_selection_type == SelectionType::Field) {
1964
return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1965
}
1966
1967
return eKeyNotHandled;
1968
}
1969
1970
bool FieldDelegateOnLastOrOnlyElement() override {
1971
if (m_selection_type == SelectionType::NewButton) {
1972
return true;
1973
}
1974
return false;
1975
}
1976
1977
bool FieldDelegateOnFirstOrOnlyElement() override {
1978
if (m_selection_type == SelectionType::NewButton &&
1979
GetNumberOfFields() == 0)
1980
return true;
1981
1982
if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1983
FieldDelegate &field = m_fields[m_selection_index];
1984
return field.FieldDelegateOnFirstOrOnlyElement();
1985
}
1986
1987
return false;
1988
}
1989
1990
void FieldDelegateSelectFirstElement() override {
1991
if (GetNumberOfFields() == 0) {
1992
m_selection_type = SelectionType::NewButton;
1993
return;
1994
}
1995
1996
m_selection_type = SelectionType::Field;
1997
m_selection_index = 0;
1998
}
1999
2000
void FieldDelegateSelectLastElement() override {
2001
m_selection_type = SelectionType::NewButton;
2002
}
2003
2004
int GetNumberOfFields() { return m_fields.size(); }
2005
2006
// Returns the form delegate at the current index.
2007
T &GetField(int index) { return m_fields[index]; }
2008
2009
protected:
2010
std::string m_label;
2011
// The default field delegate instance from which new field delegates will be
2012
// created though a copy.
2013
T m_default_field;
2014
std::vector<T> m_fields;
2015
int m_selection_index = 0;
2016
// See SelectionType class enum.
2017
SelectionType m_selection_type;
2018
};
2019
2020
class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> {
2021
public:
2022
ArgumentsFieldDelegate()
2023
: ListFieldDelegate("Arguments",
2024
TextFieldDelegate("Argument", "", false)) {}
2025
2026
Args GetArguments() {
2027
Args arguments;
2028
for (int i = 0; i < GetNumberOfFields(); i++) {
2029
arguments.AppendArgument(GetField(i).GetText());
2030
}
2031
return arguments;
2032
}
2033
2034
void AddArguments(const Args &arguments) {
2035
for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2036
AddNewField();
2037
TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2038
field.SetText(arguments.GetArgumentAtIndex(i));
2039
}
2040
}
2041
};
2042
2043
template <class KeyFieldDelegateType, class ValueFieldDelegateType>
2044
class MappingFieldDelegate : public FieldDelegate {
2045
public:
2046
MappingFieldDelegate(KeyFieldDelegateType key_field,
2047
ValueFieldDelegateType value_field)
2048
: m_key_field(key_field), m_value_field(value_field),
2049
m_selection_type(SelectionType::Key) {}
2050
2051
// Signify which element is selected. The key field or its value field.
2052
enum class SelectionType { Key, Value };
2053
2054
// A mapping field is drawn as two text fields with a right arrow in between.
2055
// The first field stores the key of the mapping and the second stores the
2056
// value if the mapping.
2057
//
2058
// __[Key]_____________ __[Value]___________
2059
// | | > | |
2060
// |__________________| |__________________|
2061
// - Error message if it exists.
2062
2063
// The mapping field has a height that is equal to the maximum height between
2064
// the key and value fields.
2065
int FieldDelegateGetHeight() override {
2066
return std::max(m_key_field.FieldDelegateGetHeight(),
2067
m_value_field.FieldDelegateGetHeight());
2068
}
2069
2070
void DrawArrow(Surface &surface) {
2071
surface.MoveCursor(0, 1);
2072
surface.PutChar(ACS_RARROW);
2073
}
2074
2075
void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2076
Rect bounds = surface.GetFrame();
2077
Rect key_field_bounds, arrow_and_value_field_bounds;
2078
bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2079
arrow_and_value_field_bounds);
2080
Rect arrow_bounds, value_field_bounds;
2081
arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2082
value_field_bounds);
2083
2084
Surface key_field_surface = surface.SubSurface(key_field_bounds);
2085
Surface arrow_surface = surface.SubSurface(arrow_bounds);
2086
Surface value_field_surface = surface.SubSurface(value_field_bounds);
2087
2088
bool key_is_selected =
2089
m_selection_type == SelectionType::Key && is_selected;
2090
m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2091
DrawArrow(arrow_surface);
2092
bool value_is_selected =
2093
m_selection_type == SelectionType::Value && is_selected;
2094
m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2095
}
2096
2097
HandleCharResult SelectNext(int key) {
2098
if (FieldDelegateOnLastOrOnlyElement())
2099
return eKeyNotHandled;
2100
2101
if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2102
return m_key_field.FieldDelegateHandleChar(key);
2103
}
2104
2105
m_key_field.FieldDelegateExitCallback();
2106
m_selection_type = SelectionType::Value;
2107
m_value_field.FieldDelegateSelectFirstElement();
2108
return eKeyHandled;
2109
}
2110
2111
HandleCharResult SelectPrevious(int key) {
2112
if (FieldDelegateOnFirstOrOnlyElement())
2113
return eKeyNotHandled;
2114
2115
if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2116
return m_value_field.FieldDelegateHandleChar(key);
2117
}
2118
2119
m_value_field.FieldDelegateExitCallback();
2120
m_selection_type = SelectionType::Key;
2121
m_key_field.FieldDelegateSelectLastElement();
2122
return eKeyHandled;
2123
}
2124
2125
// If the value field is selected, pass the key to it. If the key field is
2126
// selected, its last element is selected, and it didn't handle the key, then
2127
// select its corresponding value field.
2128
HandleCharResult SelectNextField(int key) {
2129
if (m_selection_type == SelectionType::Value) {
2130
return m_value_field.FieldDelegateHandleChar(key);
2131
}
2132
2133
if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2134
return eKeyHandled;
2135
2136
if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2137
return eKeyNotHandled;
2138
2139
m_key_field.FieldDelegateExitCallback();
2140
m_selection_type = SelectionType::Value;
2141
m_value_field.FieldDelegateSelectFirstElement();
2142
return eKeyHandled;
2143
}
2144
2145
HandleCharResult FieldDelegateHandleChar(int key) override {
2146
switch (key) {
2147
case KEY_RETURN:
2148
return SelectNextField(key);
2149
case '\t':
2150
return SelectNext(key);
2151
case KEY_SHIFT_TAB:
2152
return SelectPrevious(key);
2153
default:
2154
break;
2155
}
2156
2157
// If the key wasn't handled, pass the key to the selected field.
2158
if (m_selection_type == SelectionType::Key)
2159
return m_key_field.FieldDelegateHandleChar(key);
2160
else
2161
return m_value_field.FieldDelegateHandleChar(key);
2162
2163
return eKeyNotHandled;
2164
}
2165
2166
bool FieldDelegateOnFirstOrOnlyElement() override {
2167
return m_selection_type == SelectionType::Key;
2168
}
2169
2170
bool FieldDelegateOnLastOrOnlyElement() override {
2171
return m_selection_type == SelectionType::Value;
2172
}
2173
2174
void FieldDelegateSelectFirstElement() override {
2175
m_selection_type = SelectionType::Key;
2176
}
2177
2178
void FieldDelegateSelectLastElement() override {
2179
m_selection_type = SelectionType::Value;
2180
}
2181
2182
bool FieldDelegateHasError() override {
2183
return m_key_field.FieldDelegateHasError() ||
2184
m_value_field.FieldDelegateHasError();
2185
}
2186
2187
KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2188
2189
ValueFieldDelegateType &GetValueField() { return m_value_field; }
2190
2191
protected:
2192
KeyFieldDelegateType m_key_field;
2193
ValueFieldDelegateType m_value_field;
2194
// See SelectionType class enum.
2195
SelectionType m_selection_type;
2196
};
2197
2198
class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate {
2199
public:
2200
EnvironmentVariableNameFieldDelegate(const char *content)
2201
: TextFieldDelegate("Name", content, true) {}
2202
2203
// Environment variable names can't contain an equal sign.
2204
bool IsAcceptableChar(int key) override {
2205
return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2206
}
2207
2208
const std::string &GetName() { return m_content; }
2209
};
2210
2211
class EnvironmentVariableFieldDelegate
2212
: public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2213
TextFieldDelegate> {
2214
public:
2215
EnvironmentVariableFieldDelegate()
2216
: MappingFieldDelegate(
2217
EnvironmentVariableNameFieldDelegate(""),
2218
TextFieldDelegate("Value", "", /*required=*/false)) {}
2219
2220
const std::string &GetName() { return GetKeyField().GetName(); }
2221
2222
const std::string &GetValue() { return GetValueField().GetText(); }
2223
2224
void SetName(const char *name) { return GetKeyField().SetText(name); }
2225
2226
void SetValue(const char *value) { return GetValueField().SetText(value); }
2227
};
2228
2229
class EnvironmentVariableListFieldDelegate
2230
: public ListFieldDelegate<EnvironmentVariableFieldDelegate> {
2231
public:
2232
EnvironmentVariableListFieldDelegate(const char *label)
2233
: ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2234
2235
Environment GetEnvironment() {
2236
Environment environment;
2237
for (int i = 0; i < GetNumberOfFields(); i++) {
2238
environment.insert(
2239
std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2240
}
2241
return environment;
2242
}
2243
2244
void AddEnvironmentVariables(const Environment &environment) {
2245
for (auto &variable : environment) {
2246
AddNewField();
2247
EnvironmentVariableFieldDelegate &field =
2248
GetField(GetNumberOfFields() - 1);
2249
field.SetName(variable.getKey().str().c_str());
2250
field.SetValue(variable.getValue().c_str());
2251
}
2252
}
2253
};
2254
2255
class FormAction {
2256
public:
2257
FormAction(const char *label, std::function<void(Window &)> action)
2258
: m_action(action) {
2259
if (label)
2260
m_label = label;
2261
}
2262
2263
// Draw a centered [Label].
2264
void Draw(Surface &surface, bool is_selected) {
2265
int x = (surface.GetWidth() - m_label.length()) / 2;
2266
surface.MoveCursor(x, 0);
2267
if (is_selected)
2268
surface.AttributeOn(A_REVERSE);
2269
surface.PutChar('[');
2270
surface.PutCString(m_label.c_str());
2271
surface.PutChar(']');
2272
if (is_selected)
2273
surface.AttributeOff(A_REVERSE);
2274
}
2275
2276
void Execute(Window &window) { m_action(window); }
2277
2278
const std::string &GetLabel() { return m_label; }
2279
2280
protected:
2281
std::string m_label;
2282
std::function<void(Window &)> m_action;
2283
};
2284
2285
class FormDelegate {
2286
public:
2287
FormDelegate() = default;
2288
2289
virtual ~FormDelegate() = default;
2290
2291
virtual std::string GetName() = 0;
2292
2293
virtual void UpdateFieldsVisibility() {}
2294
2295
FieldDelegate *GetField(uint32_t field_index) {
2296
if (field_index < m_fields.size())
2297
return m_fields[field_index].get();
2298
return nullptr;
2299
}
2300
2301
FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2302
2303
int GetNumberOfFields() { return m_fields.size(); }
2304
2305
int GetNumberOfActions() { return m_actions.size(); }
2306
2307
bool HasError() { return !m_error.empty(); }
2308
2309
void ClearError() { m_error.clear(); }
2310
2311
const std::string &GetError() { return m_error; }
2312
2313
void SetError(const char *error) { m_error = error; }
2314
2315
// If all fields are valid, true is returned. Otherwise, an error message is
2316
// set and false is returned. This method is usually called at the start of an
2317
// action that requires valid fields.
2318
bool CheckFieldsValidity() {
2319
for (int i = 0; i < GetNumberOfFields(); i++) {
2320
GetField(i)->FieldDelegateExitCallback();
2321
if (GetField(i)->FieldDelegateHasError()) {
2322
SetError("Some fields are invalid!");
2323
return false;
2324
}
2325
}
2326
return true;
2327
}
2328
2329
// Factory methods to create and add fields of specific types.
2330
2331
TextFieldDelegate *AddTextField(const char *label, const char *content,
2332
bool required) {
2333
TextFieldDelegate *delegate =
2334
new TextFieldDelegate(label, content, required);
2335
m_fields.push_back(FieldDelegateUP(delegate));
2336
return delegate;
2337
}
2338
2339
FileFieldDelegate *AddFileField(const char *label, const char *content,
2340
bool need_to_exist, bool required) {
2341
FileFieldDelegate *delegate =
2342
new FileFieldDelegate(label, content, need_to_exist, required);
2343
m_fields.push_back(FieldDelegateUP(delegate));
2344
return delegate;
2345
}
2346
2347
DirectoryFieldDelegate *AddDirectoryField(const char *label,
2348
const char *content,
2349
bool need_to_exist, bool required) {
2350
DirectoryFieldDelegate *delegate =
2351
new DirectoryFieldDelegate(label, content, need_to_exist, required);
2352
m_fields.push_back(FieldDelegateUP(delegate));
2353
return delegate;
2354
}
2355
2356
ArchFieldDelegate *AddArchField(const char *label, const char *content,
2357
bool required) {
2358
ArchFieldDelegate *delegate =
2359
new ArchFieldDelegate(label, content, required);
2360
m_fields.push_back(FieldDelegateUP(delegate));
2361
return delegate;
2362
}
2363
2364
IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2365
bool required) {
2366
IntegerFieldDelegate *delegate =
2367
new IntegerFieldDelegate(label, content, required);
2368
m_fields.push_back(FieldDelegateUP(delegate));
2369
return delegate;
2370
}
2371
2372
BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2373
BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2374
m_fields.push_back(FieldDelegateUP(delegate));
2375
return delegate;
2376
}
2377
2378
LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2379
const char *calculate_label) {
2380
LazyBooleanFieldDelegate *delegate =
2381
new LazyBooleanFieldDelegate(label, calculate_label);
2382
m_fields.push_back(FieldDelegateUP(delegate));
2383
return delegate;
2384
}
2385
2386
ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2387
std::vector<std::string> choices) {
2388
ChoicesFieldDelegate *delegate =
2389
new ChoicesFieldDelegate(label, height, choices);
2390
m_fields.push_back(FieldDelegateUP(delegate));
2391
return delegate;
2392
}
2393
2394
PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2395
PlatformPluginFieldDelegate *delegate =
2396
new PlatformPluginFieldDelegate(debugger);
2397
m_fields.push_back(FieldDelegateUP(delegate));
2398
return delegate;
2399
}
2400
2401
ProcessPluginFieldDelegate *AddProcessPluginField() {
2402
ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2403
m_fields.push_back(FieldDelegateUP(delegate));
2404
return delegate;
2405
}
2406
2407
template <class T>
2408
ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
2409
ListFieldDelegate<T> *delegate =
2410
new ListFieldDelegate<T>(label, default_field);
2411
m_fields.push_back(FieldDelegateUP(delegate));
2412
return delegate;
2413
}
2414
2415
ArgumentsFieldDelegate *AddArgumentsField() {
2416
ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2417
m_fields.push_back(FieldDelegateUP(delegate));
2418
return delegate;
2419
}
2420
2421
template <class K, class V>
2422
MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) {
2423
MappingFieldDelegate<K, V> *delegate =
2424
new MappingFieldDelegate<K, V>(key_field, value_field);
2425
m_fields.push_back(FieldDelegateUP(delegate));
2426
return delegate;
2427
}
2428
2429
EnvironmentVariableNameFieldDelegate *
2430
AddEnvironmentVariableNameField(const char *content) {
2431
EnvironmentVariableNameFieldDelegate *delegate =
2432
new EnvironmentVariableNameFieldDelegate(content);
2433
m_fields.push_back(FieldDelegateUP(delegate));
2434
return delegate;
2435
}
2436
2437
EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2438
EnvironmentVariableFieldDelegate *delegate =
2439
new EnvironmentVariableFieldDelegate();
2440
m_fields.push_back(FieldDelegateUP(delegate));
2441
return delegate;
2442
}
2443
2444
EnvironmentVariableListFieldDelegate *
2445
AddEnvironmentVariableListField(const char *label) {
2446
EnvironmentVariableListFieldDelegate *delegate =
2447
new EnvironmentVariableListFieldDelegate(label);
2448
m_fields.push_back(FieldDelegateUP(delegate));
2449
return delegate;
2450
}
2451
2452
// Factory methods for adding actions.
2453
2454
void AddAction(const char *label, std::function<void(Window &)> action) {
2455
m_actions.push_back(FormAction(label, action));
2456
}
2457
2458
protected:
2459
std::vector<FieldDelegateUP> m_fields;
2460
std::vector<FormAction> m_actions;
2461
// Optional error message. If empty, form is considered to have no error.
2462
std::string m_error;
2463
};
2464
2465
typedef std::shared_ptr<FormDelegate> FormDelegateSP;
2466
2467
class FormWindowDelegate : public WindowDelegate {
2468
public:
2469
FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {
2470
assert(m_delegate_sp->GetNumberOfActions() > 0);
2471
if (m_delegate_sp->GetNumberOfFields() > 0)
2472
m_selection_type = SelectionType::Field;
2473
else
2474
m_selection_type = SelectionType::Action;
2475
}
2476
2477
// Signify which element is selected. If a field or an action is selected,
2478
// then m_selection_index signifies the particular field or action that is
2479
// selected.
2480
enum class SelectionType { Field, Action };
2481
2482
// A form window is padded by one character from all sides. First, if an error
2483
// message exists, it is drawn followed by a separator. Then one or more
2484
// fields are drawn. Finally, all available actions are drawn on a single
2485
// line.
2486
//
2487
// ___<Form Name>_________________________________________________
2488
// | |
2489
// | - Error message if it exists. |
2490
// |-------------------------------------------------------------|
2491
// | Form elements here. |
2492
// | Form actions here. |
2493
// | |
2494
// |______________________________________[Press Esc to cancel]__|
2495
//
2496
2497
// One line for the error and another for the horizontal line.
2498
int GetErrorHeight() {
2499
if (m_delegate_sp->HasError())
2500
return 2;
2501
return 0;
2502
}
2503
2504
// Actions span a single line.
2505
int GetActionsHeight() {
2506
if (m_delegate_sp->GetNumberOfActions() > 0)
2507
return 1;
2508
return 0;
2509
}
2510
2511
// Get the total number of needed lines to draw the contents.
2512
int GetContentHeight() {
2513
int height = 0;
2514
height += GetErrorHeight();
2515
for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2516
if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2517
continue;
2518
height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2519
}
2520
height += GetActionsHeight();
2521
return height;
2522
}
2523
2524
ScrollContext GetScrollContext() {
2525
if (m_selection_type == SelectionType::Action)
2526
return ScrollContext(GetContentHeight() - 1);
2527
2528
FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2529
ScrollContext context = field->FieldDelegateGetScrollContext();
2530
2531
int offset = GetErrorHeight();
2532
for (int i = 0; i < m_selection_index; i++) {
2533
if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2534
continue;
2535
offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2536
}
2537
context.Offset(offset);
2538
2539
// If the context is touching the error, include the error in the context as
2540
// well.
2541
if (context.start == GetErrorHeight())
2542
context.start = 0;
2543
2544
return context;
2545
}
2546
2547
void UpdateScrolling(Surface &surface) {
2548
ScrollContext context = GetScrollContext();
2549
int content_height = GetContentHeight();
2550
int surface_height = surface.GetHeight();
2551
int visible_height = std::min(content_height, surface_height);
2552
int last_visible_line = m_first_visible_line + visible_height - 1;
2553
2554
// If the last visible line is bigger than the content, then it is invalid
2555
// and needs to be set to the last line in the content. This can happen when
2556
// a field has shrunk in height.
2557
if (last_visible_line > content_height - 1) {
2558
m_first_visible_line = content_height - visible_height;
2559
}
2560
2561
if (context.start < m_first_visible_line) {
2562
m_first_visible_line = context.start;
2563
return;
2564
}
2565
2566
if (context.end > last_visible_line) {
2567
m_first_visible_line = context.end - visible_height + 1;
2568
}
2569
}
2570
2571
void DrawError(Surface &surface) {
2572
if (!m_delegate_sp->HasError())
2573
return;
2574
surface.MoveCursor(0, 0);
2575
surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2576
surface.PutChar(ACS_DIAMOND);
2577
surface.PutChar(' ');
2578
surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2579
surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2580
2581
surface.MoveCursor(0, 1);
2582
surface.HorizontalLine(surface.GetWidth());
2583
}
2584
2585
void DrawFields(Surface &surface) {
2586
int line = 0;
2587
int width = surface.GetWidth();
2588
bool a_field_is_selected = m_selection_type == SelectionType::Field;
2589
for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2590
FieldDelegate *field = m_delegate_sp->GetField(i);
2591
if (!field->FieldDelegateIsVisible())
2592
continue;
2593
bool is_field_selected = a_field_is_selected && m_selection_index == i;
2594
int height = field->FieldDelegateGetHeight();
2595
Rect bounds = Rect(Point(0, line), Size(width, height));
2596
Surface field_surface = surface.SubSurface(bounds);
2597
field->FieldDelegateDraw(field_surface, is_field_selected);
2598
line += height;
2599
}
2600
}
2601
2602
void DrawActions(Surface &surface) {
2603
int number_of_actions = m_delegate_sp->GetNumberOfActions();
2604
int width = surface.GetWidth() / number_of_actions;
2605
bool an_action_is_selected = m_selection_type == SelectionType::Action;
2606
int x = 0;
2607
for (int i = 0; i < number_of_actions; i++) {
2608
bool is_action_selected = an_action_is_selected && m_selection_index == i;
2609
FormAction &action = m_delegate_sp->GetAction(i);
2610
Rect bounds = Rect(Point(x, 0), Size(width, 1));
2611
Surface action_surface = surface.SubSurface(bounds);
2612
action.Draw(action_surface, is_action_selected);
2613
x += width;
2614
}
2615
}
2616
2617
void DrawElements(Surface &surface) {
2618
Rect frame = surface.GetFrame();
2619
Rect fields_bounds, actions_bounds;
2620
frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2621
fields_bounds, actions_bounds);
2622
Surface fields_surface = surface.SubSurface(fields_bounds);
2623
Surface actions_surface = surface.SubSurface(actions_bounds);
2624
2625
DrawFields(fields_surface);
2626
DrawActions(actions_surface);
2627
}
2628
2629
// Contents are first drawn on a pad. Then a subset of that pad is copied to
2630
// the derived window starting at the first visible line. This essentially
2631
// provides scrolling functionality.
2632
void DrawContent(Surface &surface) {
2633
UpdateScrolling(surface);
2634
2635
int width = surface.GetWidth();
2636
int height = GetContentHeight();
2637
Pad pad = Pad(Size(width, height));
2638
2639
Rect frame = pad.GetFrame();
2640
Rect error_bounds, elements_bounds;
2641
frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2642
Surface error_surface = pad.SubSurface(error_bounds);
2643
Surface elements_surface = pad.SubSurface(elements_bounds);
2644
2645
DrawError(error_surface);
2646
DrawElements(elements_surface);
2647
2648
int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2649
pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2650
Size(width, copy_height));
2651
}
2652
2653
void DrawSubmitHint(Surface &surface, bool is_active) {
2654
surface.MoveCursor(2, surface.GetHeight() - 1);
2655
if (is_active)
2656
surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2657
surface.Printf("[Press Alt+Enter to %s]",
2658
m_delegate_sp->GetAction(0).GetLabel().c_str());
2659
if (is_active)
2660
surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2661
}
2662
2663
bool WindowDelegateDraw(Window &window, bool force) override {
2664
m_delegate_sp->UpdateFieldsVisibility();
2665
2666
window.Erase();
2667
2668
window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2669
"Press Esc to Cancel");
2670
DrawSubmitHint(window, window.IsActive());
2671
2672
Rect content_bounds = window.GetFrame();
2673
content_bounds.Inset(2, 2);
2674
Surface content_surface = window.SubSurface(content_bounds);
2675
2676
DrawContent(content_surface);
2677
return true;
2678
}
2679
2680
void SkipNextHiddenFields() {
2681
while (true) {
2682
if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2683
return;
2684
2685
if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2686
m_selection_type = SelectionType::Action;
2687
m_selection_index = 0;
2688
return;
2689
}
2690
2691
m_selection_index++;
2692
}
2693
}
2694
2695
HandleCharResult SelectNext(int key) {
2696
if (m_selection_type == SelectionType::Action) {
2697
if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2698
m_selection_index++;
2699
return eKeyHandled;
2700
}
2701
2702
m_selection_index = 0;
2703
m_selection_type = SelectionType::Field;
2704
SkipNextHiddenFields();
2705
if (m_selection_type == SelectionType::Field) {
2706
FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2707
next_field->FieldDelegateSelectFirstElement();
2708
}
2709
return eKeyHandled;
2710
}
2711
2712
FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2713
if (!field->FieldDelegateOnLastOrOnlyElement()) {
2714
return field->FieldDelegateHandleChar(key);
2715
}
2716
2717
field->FieldDelegateExitCallback();
2718
2719
if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2720
m_selection_type = SelectionType::Action;
2721
m_selection_index = 0;
2722
return eKeyHandled;
2723
}
2724
2725
m_selection_index++;
2726
SkipNextHiddenFields();
2727
2728
if (m_selection_type == SelectionType::Field) {
2729
FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2730
next_field->FieldDelegateSelectFirstElement();
2731
}
2732
2733
return eKeyHandled;
2734
}
2735
2736
void SkipPreviousHiddenFields() {
2737
while (true) {
2738
if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2739
return;
2740
2741
if (m_selection_index == 0) {
2742
m_selection_type = SelectionType::Action;
2743
m_selection_index = 0;
2744
return;
2745
}
2746
2747
m_selection_index--;
2748
}
2749
}
2750
2751
HandleCharResult SelectPrevious(int key) {
2752
if (m_selection_type == SelectionType::Action) {
2753
if (m_selection_index > 0) {
2754
m_selection_index--;
2755
return eKeyHandled;
2756
}
2757
m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2758
m_selection_type = SelectionType::Field;
2759
SkipPreviousHiddenFields();
2760
if (m_selection_type == SelectionType::Field) {
2761
FieldDelegate *previous_field =
2762
m_delegate_sp->GetField(m_selection_index);
2763
previous_field->FieldDelegateSelectLastElement();
2764
}
2765
return eKeyHandled;
2766
}
2767
2768
FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2769
if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2770
return field->FieldDelegateHandleChar(key);
2771
}
2772
2773
field->FieldDelegateExitCallback();
2774
2775
if (m_selection_index == 0) {
2776
m_selection_type = SelectionType::Action;
2777
m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2778
return eKeyHandled;
2779
}
2780
2781
m_selection_index--;
2782
SkipPreviousHiddenFields();
2783
2784
if (m_selection_type == SelectionType::Field) {
2785
FieldDelegate *previous_field =
2786
m_delegate_sp->GetField(m_selection_index);
2787
previous_field->FieldDelegateSelectLastElement();
2788
}
2789
2790
return eKeyHandled;
2791
}
2792
2793
void ExecuteAction(Window &window, int index) {
2794
FormAction &action = m_delegate_sp->GetAction(index);
2795
action.Execute(window);
2796
if (m_delegate_sp->HasError()) {
2797
m_first_visible_line = 0;
2798
m_selection_index = 0;
2799
m_selection_type = SelectionType::Field;
2800
}
2801
}
2802
2803
// Always return eKeyHandled to absorb all events since forms are always
2804
// added as pop-ups that should take full control until canceled or submitted.
2805
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2806
switch (key) {
2807
case '\r':
2808
case '\n':
2809
case KEY_ENTER:
2810
if (m_selection_type == SelectionType::Action) {
2811
ExecuteAction(window, m_selection_index);
2812
return eKeyHandled;
2813
}
2814
break;
2815
case KEY_ALT_ENTER:
2816
ExecuteAction(window, 0);
2817
return eKeyHandled;
2818
case '\t':
2819
SelectNext(key);
2820
return eKeyHandled;
2821
case KEY_SHIFT_TAB:
2822
SelectPrevious(key);
2823
return eKeyHandled;
2824
case KEY_ESCAPE:
2825
window.GetParent()->RemoveSubWindow(&window);
2826
return eKeyHandled;
2827
default:
2828
break;
2829
}
2830
2831
// If the key wasn't handled and one of the fields is selected, pass the key
2832
// to that field.
2833
if (m_selection_type == SelectionType::Field) {
2834
FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2835
if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2836
return eKeyHandled;
2837
}
2838
2839
// If the key wasn't handled by the possibly selected field, handle some
2840
// extra keys for navigation.
2841
switch (key) {
2842
case KEY_DOWN:
2843
SelectNext(key);
2844
return eKeyHandled;
2845
case KEY_UP:
2846
SelectPrevious(key);
2847
return eKeyHandled;
2848
default:
2849
break;
2850
}
2851
2852
return eKeyHandled;
2853
}
2854
2855
protected:
2856
FormDelegateSP m_delegate_sp;
2857
// The index of the currently selected SelectionType.
2858
int m_selection_index = 0;
2859
// See SelectionType class enum.
2860
SelectionType m_selection_type;
2861
// The first visible line from the pad.
2862
int m_first_visible_line = 0;
2863
};
2864
2865
///////////////////////////
2866
// Form Delegate Instances
2867
///////////////////////////
2868
2869
class DetachOrKillProcessFormDelegate : public FormDelegate {
2870
public:
2871
DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2872
SetError("There is a running process, either detach or kill it.");
2873
2874
m_keep_stopped_field =
2875
AddBooleanField("Keep process stopped when detaching.", false);
2876
2877
AddAction("Detach", [this](Window &window) { Detach(window); });
2878
AddAction("Kill", [this](Window &window) { Kill(window); });
2879
}
2880
2881
std::string GetName() override { return "Detach/Kill Process"; }
2882
2883
void Kill(Window &window) {
2884
Status destroy_status(m_process->Destroy(false));
2885
if (destroy_status.Fail()) {
2886
SetError("Failed to kill process.");
2887
return;
2888
}
2889
window.GetParent()->RemoveSubWindow(&window);
2890
}
2891
2892
void Detach(Window &window) {
2893
Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2894
if (detach_status.Fail()) {
2895
SetError("Failed to detach from process.");
2896
return;
2897
}
2898
window.GetParent()->RemoveSubWindow(&window);
2899
}
2900
2901
protected:
2902
Process *m_process;
2903
BooleanFieldDelegate *m_keep_stopped_field;
2904
};
2905
2906
class ProcessAttachFormDelegate : public FormDelegate {
2907
public:
2908
ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
2909
: m_debugger(debugger), m_main_window_sp(main_window_sp) {
2910
std::vector<std::string> types;
2911
types.push_back(std::string("Name"));
2912
types.push_back(std::string("PID"));
2913
m_type_field = AddChoicesField("Attach By", 2, types);
2914
m_pid_field = AddIntegerField("PID", 0, true);
2915
m_name_field =
2916
AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2917
m_continue_field = AddBooleanField("Continue once attached.", false);
2918
m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2919
m_include_existing_field =
2920
AddBooleanField("Include existing processes.", false);
2921
m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2922
m_plugin_field = AddProcessPluginField();
2923
2924
AddAction("Attach", [this](Window &window) { Attach(window); });
2925
}
2926
2927
std::string GetName() override { return "Attach Process"; }
2928
2929
void UpdateFieldsVisibility() override {
2930
if (m_type_field->GetChoiceContent() == "Name") {
2931
m_pid_field->FieldDelegateHide();
2932
m_name_field->FieldDelegateShow();
2933
m_wait_for_field->FieldDelegateShow();
2934
if (m_wait_for_field->GetBoolean())
2935
m_include_existing_field->FieldDelegateShow();
2936
else
2937
m_include_existing_field->FieldDelegateHide();
2938
} else {
2939
m_pid_field->FieldDelegateShow();
2940
m_name_field->FieldDelegateHide();
2941
m_wait_for_field->FieldDelegateHide();
2942
m_include_existing_field->FieldDelegateHide();
2943
}
2944
if (m_show_advanced_field->GetBoolean())
2945
m_plugin_field->FieldDelegateShow();
2946
else
2947
m_plugin_field->FieldDelegateHide();
2948
}
2949
2950
// Get the basename of the target's main executable if available, empty string
2951
// otherwise.
2952
std::string GetDefaultProcessName() {
2953
Target *target = m_debugger.GetSelectedTarget().get();
2954
if (target == nullptr)
2955
return "";
2956
2957
ModuleSP module_sp = target->GetExecutableModule();
2958
if (!module_sp->IsExecutable())
2959
return "";
2960
2961
return module_sp->GetFileSpec().GetFilename().AsCString();
2962
}
2963
2964
bool StopRunningProcess() {
2965
ExecutionContext exe_ctx =
2966
m_debugger.GetCommandInterpreter().GetExecutionContext();
2967
2968
if (!exe_ctx.HasProcessScope())
2969
return false;
2970
2971
Process *process = exe_ctx.GetProcessPtr();
2972
if (!(process && process->IsAlive()))
2973
return false;
2974
2975
FormDelegateSP form_delegate_sp =
2976
FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2977
Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2978
WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2979
form_delegate_sp->GetName().c_str(), bounds, true);
2980
WindowDelegateSP window_delegate_sp =
2981
WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2982
form_window_sp->SetDelegate(window_delegate_sp);
2983
2984
return true;
2985
}
2986
2987
Target *GetTarget() {
2988
Target *target = m_debugger.GetSelectedTarget().get();
2989
2990
if (target != nullptr)
2991
return target;
2992
2993
TargetSP new_target_sp;
2994
m_debugger.GetTargetList().CreateTarget(
2995
m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2996
2997
target = new_target_sp.get();
2998
2999
if (target == nullptr)
3000
SetError("Failed to create target.");
3001
3002
m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3003
3004
return target;
3005
}
3006
3007
ProcessAttachInfo GetAttachInfo() {
3008
ProcessAttachInfo attach_info;
3009
attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3010
if (m_type_field->GetChoiceContent() == "Name") {
3011
attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3012
FileSpec::Style::native);
3013
attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3014
if (m_wait_for_field->GetBoolean())
3015
attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3016
} else {
3017
attach_info.SetProcessID(m_pid_field->GetInteger());
3018
}
3019
attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3020
3021
return attach_info;
3022
}
3023
3024
void Attach(Window &window) {
3025
ClearError();
3026
3027
bool all_fields_are_valid = CheckFieldsValidity();
3028
if (!all_fields_are_valid)
3029
return;
3030
3031
bool process_is_running = StopRunningProcess();
3032
if (process_is_running)
3033
return;
3034
3035
Target *target = GetTarget();
3036
if (HasError())
3037
return;
3038
3039
StreamString stream;
3040
ProcessAttachInfo attach_info = GetAttachInfo();
3041
Status status = target->Attach(attach_info, &stream);
3042
3043
if (status.Fail()) {
3044
SetError(status.AsCString());
3045
return;
3046
}
3047
3048
ProcessSP process_sp(target->GetProcessSP());
3049
if (!process_sp) {
3050
SetError("Attached sucessfully but target has no process.");
3051
return;
3052
}
3053
3054
if (attach_info.GetContinueOnceAttached())
3055
process_sp->Resume();
3056
3057
window.GetParent()->RemoveSubWindow(&window);
3058
}
3059
3060
protected:
3061
Debugger &m_debugger;
3062
WindowSP m_main_window_sp;
3063
3064
ChoicesFieldDelegate *m_type_field;
3065
IntegerFieldDelegate *m_pid_field;
3066
TextFieldDelegate *m_name_field;
3067
BooleanFieldDelegate *m_continue_field;
3068
BooleanFieldDelegate *m_wait_for_field;
3069
BooleanFieldDelegate *m_include_existing_field;
3070
BooleanFieldDelegate *m_show_advanced_field;
3071
ProcessPluginFieldDelegate *m_plugin_field;
3072
};
3073
3074
class TargetCreateFormDelegate : public FormDelegate {
3075
public:
3076
TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3077
m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3078
/*required=*/true);
3079
m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3080
/*required=*/false);
3081
m_symbol_file_field = AddFileField(
3082
"Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3083
m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3084
m_remote_file_field = AddFileField(
3085
"Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3086
m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3087
m_platform_field = AddPlatformPluginField(debugger);
3088
m_load_dependent_files_field =
3089
AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3090
3091
AddAction("Create", [this](Window &window) { CreateTarget(window); });
3092
}
3093
3094
std::string GetName() override { return "Create Target"; }
3095
3096
void UpdateFieldsVisibility() override {
3097
if (m_show_advanced_field->GetBoolean()) {
3098
m_remote_file_field->FieldDelegateShow();
3099
m_arch_field->FieldDelegateShow();
3100
m_platform_field->FieldDelegateShow();
3101
m_load_dependent_files_field->FieldDelegateShow();
3102
} else {
3103
m_remote_file_field->FieldDelegateHide();
3104
m_arch_field->FieldDelegateHide();
3105
m_platform_field->FieldDelegateHide();
3106
m_load_dependent_files_field->FieldDelegateHide();
3107
}
3108
}
3109
3110
static constexpr const char *kLoadDependentFilesNo = "No";
3111
static constexpr const char *kLoadDependentFilesYes = "Yes";
3112
static constexpr const char *kLoadDependentFilesExecOnly = "Executable only";
3113
3114
std::vector<std::string> GetLoadDependentFilesChoices() {
3115
std::vector<std::string> load_dependents_options;
3116
load_dependents_options.push_back(kLoadDependentFilesExecOnly);
3117
load_dependents_options.push_back(kLoadDependentFilesYes);
3118
load_dependents_options.push_back(kLoadDependentFilesNo);
3119
return load_dependents_options;
3120
}
3121
3122
LoadDependentFiles GetLoadDependentFiles() {
3123
std::string choice = m_load_dependent_files_field->GetChoiceContent();
3124
if (choice == kLoadDependentFilesNo)
3125
return eLoadDependentsNo;
3126
if (choice == kLoadDependentFilesYes)
3127
return eLoadDependentsYes;
3128
return eLoadDependentsDefault;
3129
}
3130
3131
OptionGroupPlatform GetPlatformOptions() {
3132
OptionGroupPlatform platform_options(false);
3133
platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3134
return platform_options;
3135
}
3136
3137
TargetSP GetTarget() {
3138
OptionGroupPlatform platform_options = GetPlatformOptions();
3139
TargetSP target_sp;
3140
Status status = m_debugger.GetTargetList().CreateTarget(
3141
m_debugger, m_executable_field->GetPath(),
3142
m_arch_field->GetArchString(), GetLoadDependentFiles(),
3143
&platform_options, target_sp);
3144
3145
if (status.Fail()) {
3146
SetError(status.AsCString());
3147
return nullptr;
3148
}
3149
3150
m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3151
3152
return target_sp;
3153
}
3154
3155
void SetSymbolFile(TargetSP target_sp) {
3156
if (!m_symbol_file_field->IsSpecified())
3157
return;
3158
3159
ModuleSP module_sp(target_sp->GetExecutableModule());
3160
if (!module_sp)
3161
return;
3162
3163
module_sp->SetSymbolFileFileSpec(
3164
m_symbol_file_field->GetResolvedFileSpec());
3165
}
3166
3167
void SetCoreFile(TargetSP target_sp) {
3168
if (!m_core_file_field->IsSpecified())
3169
return;
3170
3171
FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3172
3173
FileSpec core_file_directory_spec;
3174
core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory());
3175
target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3176
3177
ProcessSP process_sp(target_sp->CreateProcess(
3178
m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3179
3180
if (!process_sp) {
3181
SetError("Unknown core file format!");
3182
return;
3183
}
3184
3185
Status status = process_sp->LoadCore();
3186
if (status.Fail()) {
3187
SetError("Unknown core file format!");
3188
return;
3189
}
3190
}
3191
3192
void SetRemoteFile(TargetSP target_sp) {
3193
if (!m_remote_file_field->IsSpecified())
3194
return;
3195
3196
ModuleSP module_sp(target_sp->GetExecutableModule());
3197
if (!module_sp)
3198
return;
3199
3200
FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3201
module_sp->SetPlatformFileSpec(remote_file_spec);
3202
}
3203
3204
void RemoveTarget(TargetSP target_sp) {
3205
m_debugger.GetTargetList().DeleteTarget(target_sp);
3206
}
3207
3208
void CreateTarget(Window &window) {
3209
ClearError();
3210
3211
bool all_fields_are_valid = CheckFieldsValidity();
3212
if (!all_fields_are_valid)
3213
return;
3214
3215
TargetSP target_sp = GetTarget();
3216
if (HasError())
3217
return;
3218
3219
SetSymbolFile(target_sp);
3220
if (HasError()) {
3221
RemoveTarget(target_sp);
3222
return;
3223
}
3224
3225
SetCoreFile(target_sp);
3226
if (HasError()) {
3227
RemoveTarget(target_sp);
3228
return;
3229
}
3230
3231
SetRemoteFile(target_sp);
3232
if (HasError()) {
3233
RemoveTarget(target_sp);
3234
return;
3235
}
3236
3237
window.GetParent()->RemoveSubWindow(&window);
3238
}
3239
3240
protected:
3241
Debugger &m_debugger;
3242
3243
FileFieldDelegate *m_executable_field;
3244
FileFieldDelegate *m_core_file_field;
3245
FileFieldDelegate *m_symbol_file_field;
3246
BooleanFieldDelegate *m_show_advanced_field;
3247
FileFieldDelegate *m_remote_file_field;
3248
ArchFieldDelegate *m_arch_field;
3249
PlatformPluginFieldDelegate *m_platform_field;
3250
ChoicesFieldDelegate *m_load_dependent_files_field;
3251
};
3252
3253
class ProcessLaunchFormDelegate : public FormDelegate {
3254
public:
3255
ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp)
3256
: m_debugger(debugger), m_main_window_sp(main_window_sp) {
3257
3258
m_arguments_field = AddArgumentsField();
3259
SetArgumentsFieldDefaultValue();
3260
m_target_environment_field =
3261
AddEnvironmentVariableListField("Target Environment Variables");
3262
SetTargetEnvironmentFieldDefaultValue();
3263
m_working_directory_field = AddDirectoryField(
3264
"Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3265
3266
m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3267
3268
m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3269
m_detach_on_error_field =
3270
AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3271
m_disable_aslr_field =
3272
AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3273
m_plugin_field = AddProcessPluginField();
3274
m_arch_field = AddArchField("Architecture", "", false);
3275
m_shell_field = AddFileField("Shell", "", true, false);
3276
m_expand_shell_arguments_field =
3277
AddBooleanField("Expand shell arguments.", false);
3278
3279
m_disable_standard_io_field =
3280
AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3281
m_standard_output_field =
3282
AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3283
/*required=*/false);
3284
m_standard_error_field =
3285
AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3286
/*required=*/false);
3287
m_standard_input_field =
3288
AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3289
/*required=*/false);
3290
3291
m_show_inherited_environment_field =
3292
AddBooleanField("Show inherited environment variables.", false);
3293
m_inherited_environment_field =
3294
AddEnvironmentVariableListField("Inherited Environment Variables");
3295
SetInheritedEnvironmentFieldDefaultValue();
3296
3297
AddAction("Launch", [this](Window &window) { Launch(window); });
3298
}
3299
3300
std::string GetName() override { return "Launch Process"; }
3301
3302
void UpdateFieldsVisibility() override {
3303
if (m_show_advanced_field->GetBoolean()) {
3304
m_stop_at_entry_field->FieldDelegateShow();
3305
m_detach_on_error_field->FieldDelegateShow();
3306
m_disable_aslr_field->FieldDelegateShow();
3307
m_plugin_field->FieldDelegateShow();
3308
m_arch_field->FieldDelegateShow();
3309
m_shell_field->FieldDelegateShow();
3310
m_expand_shell_arguments_field->FieldDelegateShow();
3311
m_disable_standard_io_field->FieldDelegateShow();
3312
if (m_disable_standard_io_field->GetBoolean()) {
3313
m_standard_input_field->FieldDelegateHide();
3314
m_standard_output_field->FieldDelegateHide();
3315
m_standard_error_field->FieldDelegateHide();
3316
} else {
3317
m_standard_input_field->FieldDelegateShow();
3318
m_standard_output_field->FieldDelegateShow();
3319
m_standard_error_field->FieldDelegateShow();
3320
}
3321
m_show_inherited_environment_field->FieldDelegateShow();
3322
if (m_show_inherited_environment_field->GetBoolean())
3323
m_inherited_environment_field->FieldDelegateShow();
3324
else
3325
m_inherited_environment_field->FieldDelegateHide();
3326
} else {
3327
m_stop_at_entry_field->FieldDelegateHide();
3328
m_detach_on_error_field->FieldDelegateHide();
3329
m_disable_aslr_field->FieldDelegateHide();
3330
m_plugin_field->FieldDelegateHide();
3331
m_arch_field->FieldDelegateHide();
3332
m_shell_field->FieldDelegateHide();
3333
m_expand_shell_arguments_field->FieldDelegateHide();
3334
m_disable_standard_io_field->FieldDelegateHide();
3335
m_standard_input_field->FieldDelegateHide();
3336
m_standard_output_field->FieldDelegateHide();
3337
m_standard_error_field->FieldDelegateHide();
3338
m_show_inherited_environment_field->FieldDelegateHide();
3339
m_inherited_environment_field->FieldDelegateHide();
3340
}
3341
}
3342
3343
// Methods for setting the default value of the fields.
3344
3345
void SetArgumentsFieldDefaultValue() {
3346
TargetSP target = m_debugger.GetSelectedTarget();
3347
if (target == nullptr)
3348
return;
3349
3350
const Args &target_arguments =
3351
target->GetProcessLaunchInfo().GetArguments();
3352
m_arguments_field->AddArguments(target_arguments);
3353
}
3354
3355
void SetTargetEnvironmentFieldDefaultValue() {
3356
TargetSP target = m_debugger.GetSelectedTarget();
3357
if (target == nullptr)
3358
return;
3359
3360
const Environment &target_environment = target->GetTargetEnvironment();
3361
m_target_environment_field->AddEnvironmentVariables(target_environment);
3362
}
3363
3364
void SetInheritedEnvironmentFieldDefaultValue() {
3365
TargetSP target = m_debugger.GetSelectedTarget();
3366
if (target == nullptr)
3367
return;
3368
3369
const Environment &inherited_environment =
3370
target->GetInheritedEnvironment();
3371
m_inherited_environment_field->AddEnvironmentVariables(
3372
inherited_environment);
3373
}
3374
3375
std::string GetDefaultWorkingDirectory() {
3376
TargetSP target = m_debugger.GetSelectedTarget();
3377
if (target == nullptr)
3378
return "";
3379
3380
PlatformSP platform = target->GetPlatform();
3381
return platform->GetWorkingDirectory().GetPath();
3382
}
3383
3384
bool GetDefaultDisableASLR() {
3385
TargetSP target = m_debugger.GetSelectedTarget();
3386
if (target == nullptr)
3387
return false;
3388
3389
return target->GetDisableASLR();
3390
}
3391
3392
bool GetDefaultDisableStandardIO() {
3393
TargetSP target = m_debugger.GetSelectedTarget();
3394
if (target == nullptr)
3395
return true;
3396
3397
return target->GetDisableSTDIO();
3398
}
3399
3400
bool GetDefaultDetachOnError() {
3401
TargetSP target = m_debugger.GetSelectedTarget();
3402
if (target == nullptr)
3403
return true;
3404
3405
return target->GetDetachOnError();
3406
}
3407
3408
// Methods for getting the necessary information and setting them to the
3409
// ProcessLaunchInfo.
3410
3411
void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3412
TargetSP target = m_debugger.GetSelectedTarget();
3413
ModuleSP executable_module = target->GetExecutableModule();
3414
llvm::StringRef target_settings_argv0 = target->GetArg0();
3415
3416
if (!target_settings_argv0.empty()) {
3417
launch_info.GetArguments().AppendArgument(target_settings_argv0);
3418
launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3419
false);
3420
return;
3421
}
3422
3423
launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3424
true);
3425
}
3426
3427
void GetArguments(ProcessLaunchInfo &launch_info) {
3428
TargetSP target = m_debugger.GetSelectedTarget();
3429
Args arguments = m_arguments_field->GetArguments();
3430
launch_info.GetArguments().AppendArguments(arguments);
3431
}
3432
3433
void GetEnvironment(ProcessLaunchInfo &launch_info) {
3434
Environment target_environment =
3435
m_target_environment_field->GetEnvironment();
3436
Environment inherited_environment =
3437
m_inherited_environment_field->GetEnvironment();
3438
launch_info.GetEnvironment().insert(target_environment.begin(),
3439
target_environment.end());
3440
launch_info.GetEnvironment().insert(inherited_environment.begin(),
3441
inherited_environment.end());
3442
}
3443
3444
void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3445
if (m_working_directory_field->IsSpecified())
3446
launch_info.SetWorkingDirectory(
3447
m_working_directory_field->GetResolvedFileSpec());
3448
}
3449
3450
void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3451
if (m_stop_at_entry_field->GetBoolean())
3452
launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3453
else
3454
launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3455
}
3456
3457
void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3458
if (m_detach_on_error_field->GetBoolean())
3459
launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3460
else
3461
launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3462
}
3463
3464
void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3465
if (m_disable_aslr_field->GetBoolean())
3466
launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3467
else
3468
launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3469
}
3470
3471
void GetPlugin(ProcessLaunchInfo &launch_info) {
3472
launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3473
}
3474
3475
void GetArch(ProcessLaunchInfo &launch_info) {
3476
if (!m_arch_field->IsSpecified())
3477
return;
3478
3479
TargetSP target_sp = m_debugger.GetSelectedTarget();
3480
PlatformSP platform_sp =
3481
target_sp ? target_sp->GetPlatform() : PlatformSP();
3482
launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3483
platform_sp.get(), m_arch_field->GetArchString());
3484
}
3485
3486
void GetShell(ProcessLaunchInfo &launch_info) {
3487
if (!m_shell_field->IsSpecified())
3488
return;
3489
3490
launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3491
launch_info.SetShellExpandArguments(
3492
m_expand_shell_arguments_field->GetBoolean());
3493
}
3494
3495
void GetStandardIO(ProcessLaunchInfo &launch_info) {
3496
if (m_disable_standard_io_field->GetBoolean()) {
3497
launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3498
return;
3499
}
3500
3501
FileAction action;
3502
if (m_standard_input_field->IsSpecified()) {
3503
if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3504
false))
3505
launch_info.AppendFileAction(action);
3506
}
3507
if (m_standard_output_field->IsSpecified()) {
3508
if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),
3509
false, true))
3510
launch_info.AppendFileAction(action);
3511
}
3512
if (m_standard_error_field->IsSpecified()) {
3513
if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),
3514
false, true))
3515
launch_info.AppendFileAction(action);
3516
}
3517
}
3518
3519
void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3520
if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3521
launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3522
}
3523
3524
ProcessLaunchInfo GetLaunchInfo() {
3525
ProcessLaunchInfo launch_info;
3526
3527
GetExecutableSettings(launch_info);
3528
GetArguments(launch_info);
3529
GetEnvironment(launch_info);
3530
GetWorkingDirectory(launch_info);
3531
GetStopAtEntry(launch_info);
3532
GetDetachOnError(launch_info);
3533
GetDisableASLR(launch_info);
3534
GetPlugin(launch_info);
3535
GetArch(launch_info);
3536
GetShell(launch_info);
3537
GetStandardIO(launch_info);
3538
GetInheritTCC(launch_info);
3539
3540
return launch_info;
3541
}
3542
3543
bool StopRunningProcess() {
3544
ExecutionContext exe_ctx =
3545
m_debugger.GetCommandInterpreter().GetExecutionContext();
3546
3547
if (!exe_ctx.HasProcessScope())
3548
return false;
3549
3550
Process *process = exe_ctx.GetProcessPtr();
3551
if (!(process && process->IsAlive()))
3552
return false;
3553
3554
FormDelegateSP form_delegate_sp =
3555
FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3556
Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3557
WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3558
form_delegate_sp->GetName().c_str(), bounds, true);
3559
WindowDelegateSP window_delegate_sp =
3560
WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3561
form_window_sp->SetDelegate(window_delegate_sp);
3562
3563
return true;
3564
}
3565
3566
Target *GetTarget() {
3567
Target *target = m_debugger.GetSelectedTarget().get();
3568
3569
if (target == nullptr) {
3570
SetError("No target exists!");
3571
return nullptr;
3572
}
3573
3574
ModuleSP exe_module_sp = target->GetExecutableModule();
3575
3576
if (exe_module_sp == nullptr) {
3577
SetError("No executable in target!");
3578
return nullptr;
3579
}
3580
3581
return target;
3582
}
3583
3584
void Launch(Window &window) {
3585
ClearError();
3586
3587
bool all_fields_are_valid = CheckFieldsValidity();
3588
if (!all_fields_are_valid)
3589
return;
3590
3591
bool process_is_running = StopRunningProcess();
3592
if (process_is_running)
3593
return;
3594
3595
Target *target = GetTarget();
3596
if (HasError())
3597
return;
3598
3599
StreamString stream;
3600
ProcessLaunchInfo launch_info = GetLaunchInfo();
3601
Status status = target->Launch(launch_info, &stream);
3602
3603
if (status.Fail()) {
3604
SetError(status.AsCString());
3605
return;
3606
}
3607
3608
ProcessSP process_sp(target->GetProcessSP());
3609
if (!process_sp) {
3610
SetError("Launched successfully but target has no process!");
3611
return;
3612
}
3613
3614
window.GetParent()->RemoveSubWindow(&window);
3615
}
3616
3617
protected:
3618
Debugger &m_debugger;
3619
WindowSP m_main_window_sp;
3620
3621
ArgumentsFieldDelegate *m_arguments_field;
3622
EnvironmentVariableListFieldDelegate *m_target_environment_field;
3623
DirectoryFieldDelegate *m_working_directory_field;
3624
3625
BooleanFieldDelegate *m_show_advanced_field;
3626
3627
BooleanFieldDelegate *m_stop_at_entry_field;
3628
BooleanFieldDelegate *m_detach_on_error_field;
3629
BooleanFieldDelegate *m_disable_aslr_field;
3630
ProcessPluginFieldDelegate *m_plugin_field;
3631
ArchFieldDelegate *m_arch_field;
3632
FileFieldDelegate *m_shell_field;
3633
BooleanFieldDelegate *m_expand_shell_arguments_field;
3634
BooleanFieldDelegate *m_disable_standard_io_field;
3635
FileFieldDelegate *m_standard_input_field;
3636
FileFieldDelegate *m_standard_output_field;
3637
FileFieldDelegate *m_standard_error_field;
3638
3639
BooleanFieldDelegate *m_show_inherited_environment_field;
3640
EnvironmentVariableListFieldDelegate *m_inherited_environment_field;
3641
};
3642
3643
////////////
3644
// Searchers
3645
////////////
3646
3647
class SearcherDelegate {
3648
public:
3649
SearcherDelegate() = default;
3650
3651
virtual ~SearcherDelegate() = default;
3652
3653
virtual int GetNumberOfMatches() = 0;
3654
3655
// Get the string that will be displayed for the match at the input index.
3656
virtual const std::string &GetMatchTextAtIndex(int index) = 0;
3657
3658
// Update the matches of the search. This is executed every time the text
3659
// field handles an event.
3660
virtual void UpdateMatches(const std::string &text) = 0;
3661
3662
// Execute the user callback given the index of some match. This is executed
3663
// once the user selects a match.
3664
virtual void ExecuteCallback(int match_index) = 0;
3665
};
3666
3667
typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP;
3668
3669
class SearcherWindowDelegate : public WindowDelegate {
3670
public:
3671
SearcherWindowDelegate(SearcherDelegateSP &delegate_sp)
3672
: m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {
3673
;
3674
}
3675
3676
// A completion window is padded by one character from all sides. A text field
3677
// is first drawn for inputting the searcher request, then a list of matches
3678
// are displayed in a scrollable list.
3679
//
3680
// ___<Searcher Window Name>____________________________
3681
// | |
3682
// | __[Search]_______________________________________ |
3683
// | | | |
3684
// | |_______________________________________________| |
3685
// | - Match 1. |
3686
// | - Match 2. |
3687
// | - ... |
3688
// | |
3689
// |____________________________[Press Esc to Cancel]__|
3690
//
3691
3692
// Get the index of the last visible match. Assuming at least one match
3693
// exists.
3694
int GetLastVisibleMatch(int height) {
3695
int index = m_first_visible_match + height;
3696
return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3697
}
3698
3699
int GetNumberOfVisibleMatches(int height) {
3700
return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3701
}
3702
3703
void UpdateScrolling(Surface &surface) {
3704
if (m_selected_match < m_first_visible_match) {
3705
m_first_visible_match = m_selected_match;
3706
return;
3707
}
3708
3709
int height = surface.GetHeight();
3710
int last_visible_match = GetLastVisibleMatch(height);
3711
if (m_selected_match > last_visible_match) {
3712
m_first_visible_match = m_selected_match - height + 1;
3713
}
3714
}
3715
3716
void DrawMatches(Surface &surface) {
3717
if (m_delegate_sp->GetNumberOfMatches() == 0)
3718
return;
3719
3720
UpdateScrolling(surface);
3721
3722
int count = GetNumberOfVisibleMatches(surface.GetHeight());
3723
for (int i = 0; i < count; i++) {
3724
surface.MoveCursor(1, i);
3725
int current_match = m_first_visible_match + i;
3726
if (current_match == m_selected_match)
3727
surface.AttributeOn(A_REVERSE);
3728
surface.PutCString(
3729
m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3730
if (current_match == m_selected_match)
3731
surface.AttributeOff(A_REVERSE);
3732
}
3733
}
3734
3735
void DrawContent(Surface &surface) {
3736
Rect content_bounds = surface.GetFrame();
3737
Rect text_field_bounds, matchs_bounds;
3738
content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3739
text_field_bounds, matchs_bounds);
3740
Surface text_field_surface = surface.SubSurface(text_field_bounds);
3741
Surface matches_surface = surface.SubSurface(matchs_bounds);
3742
3743
m_text_field.FieldDelegateDraw(text_field_surface, true);
3744
DrawMatches(matches_surface);
3745
}
3746
3747
bool WindowDelegateDraw(Window &window, bool force) override {
3748
window.Erase();
3749
3750
window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3751
3752
Rect content_bounds = window.GetFrame();
3753
content_bounds.Inset(2, 2);
3754
Surface content_surface = window.SubSurface(content_bounds);
3755
3756
DrawContent(content_surface);
3757
return true;
3758
}
3759
3760
void SelectNext() {
3761
if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3762
m_selected_match++;
3763
}
3764
3765
void SelectPrevious() {
3766
if (m_selected_match != 0)
3767
m_selected_match--;
3768
}
3769
3770
void ExecuteCallback(Window &window) {
3771
m_delegate_sp->ExecuteCallback(m_selected_match);
3772
window.GetParent()->RemoveSubWindow(&window);
3773
}
3774
3775
void UpdateMatches() {
3776
m_delegate_sp->UpdateMatches(m_text_field.GetText());
3777
m_selected_match = 0;
3778
}
3779
3780
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3781
switch (key) {
3782
case '\r':
3783
case '\n':
3784
case KEY_ENTER:
3785
ExecuteCallback(window);
3786
return eKeyHandled;
3787
case '\t':
3788
case KEY_DOWN:
3789
SelectNext();
3790
return eKeyHandled;
3791
case KEY_SHIFT_TAB:
3792
case KEY_UP:
3793
SelectPrevious();
3794
return eKeyHandled;
3795
case KEY_ESCAPE:
3796
window.GetParent()->RemoveSubWindow(&window);
3797
return eKeyHandled;
3798
default:
3799
break;
3800
}
3801
3802
if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3803
UpdateMatches();
3804
3805
return eKeyHandled;
3806
}
3807
3808
protected:
3809
SearcherDelegateSP m_delegate_sp;
3810
TextFieldDelegate m_text_field;
3811
// The index of the currently selected match.
3812
int m_selected_match = 0;
3813
// The index of the first visible match.
3814
int m_first_visible_match = 0;
3815
};
3816
3817
//////////////////////////////
3818
// Searcher Delegate Instances
3819
//////////////////////////////
3820
3821
// This is a searcher delegate wrapper around CommandCompletions common
3822
// callbacks. The callbacks are only given the match string. The completion_mask
3823
// can be a combination of lldb::CompletionType.
3824
class CommonCompletionSearcherDelegate : public SearcherDelegate {
3825
public:
3826
typedef std::function<void(const std::string &)> CallbackType;
3827
3828
CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask,
3829
CallbackType callback)
3830
: m_debugger(debugger), m_completion_mask(completion_mask),
3831
m_callback(callback) {}
3832
3833
int GetNumberOfMatches() override { return m_matches.GetSize(); }
3834
3835
const std::string &GetMatchTextAtIndex(int index) override {
3836
return m_matches[index];
3837
}
3838
3839
void UpdateMatches(const std::string &text) override {
3840
CompletionResult result;
3841
CompletionRequest request(text.c_str(), text.size(), result);
3842
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
3843
m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3844
nullptr);
3845
result.GetMatches(m_matches);
3846
}
3847
3848
void ExecuteCallback(int match_index) override {
3849
m_callback(m_matches[match_index]);
3850
}
3851
3852
protected:
3853
Debugger &m_debugger;
3854
// A compound mask from lldb::CompletionType.
3855
uint32_t m_completion_mask;
3856
// A callback to execute once the user selects a match. The match is passed to
3857
// the callback as a string.
3858
CallbackType m_callback;
3859
StringList m_matches;
3860
};
3861
3862
////////
3863
// Menus
3864
////////
3865
3866
class MenuDelegate {
3867
public:
3868
virtual ~MenuDelegate() = default;
3869
3870
virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0;
3871
};
3872
3873
class Menu : public WindowDelegate {
3874
public:
3875
enum class Type { Invalid, Bar, Item, Separator };
3876
3877
// Menubar or separator constructor
3878
Menu(Type type);
3879
3880
// Menuitem constructor
3881
Menu(const char *name, const char *key_name, int key_value,
3882
uint64_t identifier);
3883
3884
~Menu() override = default;
3885
3886
const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3887
3888
void SetDelegate(const MenuDelegateSP &delegate_sp) {
3889
m_delegate_sp = delegate_sp;
3890
}
3891
3892
void RecalculateNameLengths();
3893
3894
void AddSubmenu(const MenuSP &menu_sp);
3895
3896
int DrawAndRunMenu(Window &window);
3897
3898
void DrawMenuTitle(Window &window, bool highlight);
3899
3900
bool WindowDelegateDraw(Window &window, bool force) override;
3901
3902
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override;
3903
3904
MenuActionResult ActionPrivate(Menu &menu) {
3905
MenuActionResult result = MenuActionResult::NotHandled;
3906
if (m_delegate_sp) {
3907
result = m_delegate_sp->MenuDelegateAction(menu);
3908
if (result != MenuActionResult::NotHandled)
3909
return result;
3910
} else if (m_parent) {
3911
result = m_parent->ActionPrivate(menu);
3912
if (result != MenuActionResult::NotHandled)
3913
return result;
3914
}
3915
return m_canned_result;
3916
}
3917
3918
MenuActionResult Action() {
3919
// Call the recursive action so it can try to handle it with the menu
3920
// delegate, and if not, try our parent menu
3921
return ActionPrivate(*this);
3922
}
3923
3924
void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3925
3926
Menus &GetSubmenus() { return m_submenus; }
3927
3928
const Menus &GetSubmenus() const { return m_submenus; }
3929
3930
int GetSelectedSubmenuIndex() const { return m_selected; }
3931
3932
void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3933
3934
Type GetType() const { return m_type; }
3935
3936
int GetStartingColumn() const { return m_start_col; }
3937
3938
void SetStartingColumn(int col) { m_start_col = col; }
3939
3940
int GetKeyValue() const { return m_key_value; }
3941
3942
std::string &GetName() { return m_name; }
3943
3944
int GetDrawWidth() const {
3945
return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3946
}
3947
3948
uint64_t GetIdentifier() const { return m_identifier; }
3949
3950
void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
3951
3952
protected:
3953
std::string m_name;
3954
std::string m_key_name;
3955
uint64_t m_identifier;
3956
Type m_type;
3957
int m_key_value;
3958
int m_start_col;
3959
int m_max_submenu_name_length;
3960
int m_max_submenu_key_name_length;
3961
int m_selected;
3962
Menu *m_parent;
3963
Menus m_submenus;
3964
WindowSP m_menu_window_sp;
3965
MenuActionResult m_canned_result;
3966
MenuDelegateSP m_delegate_sp;
3967
};
3968
3969
// Menubar or separator constructor
3970
Menu::Menu(Type type)
3971
: m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3972
m_start_col(0), m_max_submenu_name_length(0),
3973
m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3974
m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3975
m_delegate_sp() {}
3976
3977
// Menuitem constructor
3978
Menu::Menu(const char *name, const char *key_name, int key_value,
3979
uint64_t identifier)
3980
: m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3981
m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3982
m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3983
m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3984
m_delegate_sp() {
3985
if (name && name[0]) {
3986
m_name = name;
3987
m_type = Type::Item;
3988
if (key_name && key_name[0])
3989
m_key_name = key_name;
3990
} else {
3991
m_type = Type::Separator;
3992
}
3993
}
3994
3995
void Menu::RecalculateNameLengths() {
3996
m_max_submenu_name_length = 0;
3997
m_max_submenu_key_name_length = 0;
3998
Menus &submenus = GetSubmenus();
3999
const size_t num_submenus = submenus.size();
4000
for (size_t i = 0; i < num_submenus; ++i) {
4001
Menu *submenu = submenus[i].get();
4002
if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4003
m_max_submenu_name_length = submenu->m_name.size();
4004
if (static_cast<size_t>(m_max_submenu_key_name_length) <
4005
submenu->m_key_name.size())
4006
m_max_submenu_key_name_length = submenu->m_key_name.size();
4007
}
4008
}
4009
4010
void Menu::AddSubmenu(const MenuSP &menu_sp) {
4011
menu_sp->m_parent = this;
4012
if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4013
m_max_submenu_name_length = menu_sp->m_name.size();
4014
if (static_cast<size_t>(m_max_submenu_key_name_length) <
4015
menu_sp->m_key_name.size())
4016
m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4017
m_submenus.push_back(menu_sp);
4018
}
4019
4020
void Menu::DrawMenuTitle(Window &window, bool highlight) {
4021
if (m_type == Type::Separator) {
4022
window.MoveCursor(0, window.GetCursorY());
4023
window.PutChar(ACS_LTEE);
4024
int width = window.GetWidth();
4025
if (width > 2) {
4026
width -= 2;
4027
for (int i = 0; i < width; ++i)
4028
window.PutChar(ACS_HLINE);
4029
}
4030
window.PutChar(ACS_RTEE);
4031
} else {
4032
const int shortcut_key = m_key_value;
4033
bool underlined_shortcut = false;
4034
const attr_t highlight_attr = A_REVERSE;
4035
if (highlight)
4036
window.AttributeOn(highlight_attr);
4037
if (llvm::isPrint(shortcut_key)) {
4038
size_t lower_pos = m_name.find(tolower(shortcut_key));
4039
size_t upper_pos = m_name.find(toupper(shortcut_key));
4040
const char *name = m_name.c_str();
4041
size_t pos = std::min<size_t>(lower_pos, upper_pos);
4042
if (pos != std::string::npos) {
4043
underlined_shortcut = true;
4044
if (pos > 0) {
4045
window.PutCString(name, pos);
4046
name += pos;
4047
}
4048
const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4049
window.AttributeOn(shortcut_attr);
4050
window.PutChar(name[0]);
4051
window.AttributeOff(shortcut_attr);
4052
name++;
4053
if (name[0])
4054
window.PutCString(name);
4055
}
4056
}
4057
4058
if (!underlined_shortcut) {
4059
window.PutCString(m_name.c_str());
4060
}
4061
4062
if (highlight)
4063
window.AttributeOff(highlight_attr);
4064
4065
if (m_key_name.empty()) {
4066
if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4067
window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4068
window.Printf(" (%c)", m_key_value);
4069
window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4070
}
4071
} else {
4072
window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4073
window.Printf(" (%s)", m_key_name.c_str());
4074
window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4075
}
4076
}
4077
}
4078
4079
bool Menu::WindowDelegateDraw(Window &window, bool force) {
4080
Menus &submenus = GetSubmenus();
4081
const size_t num_submenus = submenus.size();
4082
const int selected_idx = GetSelectedSubmenuIndex();
4083
Menu::Type menu_type = GetType();
4084
switch (menu_type) {
4085
case Menu::Type::Bar: {
4086
window.SetBackground(BlackOnWhite);
4087
window.MoveCursor(0, 0);
4088
for (size_t i = 0; i < num_submenus; ++i) {
4089
Menu *menu = submenus[i].get();
4090
if (i > 0)
4091
window.PutChar(' ');
4092
menu->SetStartingColumn(window.GetCursorX());
4093
window.PutCString("| ");
4094
menu->DrawMenuTitle(window, false);
4095
}
4096
window.PutCString(" |");
4097
} break;
4098
4099
case Menu::Type::Item: {
4100
int y = 1;
4101
int x = 3;
4102
// Draw the menu
4103
int cursor_x = 0;
4104
int cursor_y = 0;
4105
window.Erase();
4106
window.SetBackground(BlackOnWhite);
4107
window.Box();
4108
for (size_t i = 0; i < num_submenus; ++i) {
4109
const bool is_selected = (i == static_cast<size_t>(selected_idx));
4110
window.MoveCursor(x, y + i);
4111
if (is_selected) {
4112
// Remember where we want the cursor to be
4113
cursor_x = x - 1;
4114
cursor_y = y + i;
4115
}
4116
submenus[i]->DrawMenuTitle(window, is_selected);
4117
}
4118
window.MoveCursor(cursor_x, cursor_y);
4119
} break;
4120
4121
default:
4122
case Menu::Type::Separator:
4123
break;
4124
}
4125
return true; // Drawing handled...
4126
}
4127
4128
HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4129
HandleCharResult result = eKeyNotHandled;
4130
4131
Menus &submenus = GetSubmenus();
4132
const size_t num_submenus = submenus.size();
4133
const int selected_idx = GetSelectedSubmenuIndex();
4134
Menu::Type menu_type = GetType();
4135
if (menu_type == Menu::Type::Bar) {
4136
MenuSP run_menu_sp;
4137
switch (key) {
4138
case KEY_DOWN:
4139
case KEY_UP:
4140
// Show last menu or first menu
4141
if (selected_idx < static_cast<int>(num_submenus))
4142
run_menu_sp = submenus[selected_idx];
4143
else if (!submenus.empty())
4144
run_menu_sp = submenus.front();
4145
result = eKeyHandled;
4146
break;
4147
4148
case KEY_RIGHT:
4149
++m_selected;
4150
if (m_selected >= static_cast<int>(num_submenus))
4151
m_selected = 0;
4152
if (m_selected < static_cast<int>(num_submenus))
4153
run_menu_sp = submenus[m_selected];
4154
else if (!submenus.empty())
4155
run_menu_sp = submenus.front();
4156
result = eKeyHandled;
4157
break;
4158
4159
case KEY_LEFT:
4160
--m_selected;
4161
if (m_selected < 0)
4162
m_selected = num_submenus - 1;
4163
if (m_selected < static_cast<int>(num_submenus))
4164
run_menu_sp = submenus[m_selected];
4165
else if (!submenus.empty())
4166
run_menu_sp = submenus.front();
4167
result = eKeyHandled;
4168
break;
4169
4170
default:
4171
for (size_t i = 0; i < num_submenus; ++i) {
4172
if (submenus[i]->GetKeyValue() == key) {
4173
SetSelectedSubmenuIndex(i);
4174
run_menu_sp = submenus[i];
4175
result = eKeyHandled;
4176
break;
4177
}
4178
}
4179
break;
4180
}
4181
4182
if (run_menu_sp) {
4183
// Run the action on this menu in case we need to populate the menu with
4184
// dynamic content and also in case check marks, and any other menu
4185
// decorations need to be calculated
4186
if (run_menu_sp->Action() == MenuActionResult::Quit)
4187
return eQuitApplication;
4188
4189
Rect menu_bounds;
4190
menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4191
menu_bounds.origin.y = 1;
4192
menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4193
menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4194
if (m_menu_window_sp)
4195
window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4196
4197
m_menu_window_sp = window.GetParent()->CreateSubWindow(
4198
run_menu_sp->GetName().c_str(), menu_bounds, true);
4199
m_menu_window_sp->SetDelegate(run_menu_sp);
4200
}
4201
} else if (menu_type == Menu::Type::Item) {
4202
switch (key) {
4203
case KEY_DOWN:
4204
if (m_submenus.size() > 1) {
4205
const int start_select = m_selected;
4206
while (++m_selected != start_select) {
4207
if (static_cast<size_t>(m_selected) >= num_submenus)
4208
m_selected = 0;
4209
if (m_submenus[m_selected]->GetType() == Type::Separator)
4210
continue;
4211
else
4212
break;
4213
}
4214
return eKeyHandled;
4215
}
4216
break;
4217
4218
case KEY_UP:
4219
if (m_submenus.size() > 1) {
4220
const int start_select = m_selected;
4221
while (--m_selected != start_select) {
4222
if (m_selected < static_cast<int>(0))
4223
m_selected = num_submenus - 1;
4224
if (m_submenus[m_selected]->GetType() == Type::Separator)
4225
continue;
4226
else
4227
break;
4228
}
4229
return eKeyHandled;
4230
}
4231
break;
4232
4233
case KEY_RETURN:
4234
if (static_cast<size_t>(selected_idx) < num_submenus) {
4235
if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4236
return eQuitApplication;
4237
window.GetParent()->RemoveSubWindow(&window);
4238
return eKeyHandled;
4239
}
4240
break;
4241
4242
case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4243
// case other chars are entered for escaped sequences
4244
window.GetParent()->RemoveSubWindow(&window);
4245
return eKeyHandled;
4246
4247
default:
4248
for (size_t i = 0; i < num_submenus; ++i) {
4249
Menu *menu = submenus[i].get();
4250
if (menu->GetKeyValue() == key) {
4251
SetSelectedSubmenuIndex(i);
4252
window.GetParent()->RemoveSubWindow(&window);
4253
if (menu->Action() == MenuActionResult::Quit)
4254
return eQuitApplication;
4255
return eKeyHandled;
4256
}
4257
}
4258
break;
4259
}
4260
} else if (menu_type == Menu::Type::Separator) {
4261
}
4262
return result;
4263
}
4264
4265
class Application {
4266
public:
4267
Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}
4268
4269
~Application() {
4270
m_window_delegates.clear();
4271
m_window_sp.reset();
4272
if (m_screen) {
4273
::delscreen(m_screen);
4274
m_screen = nullptr;
4275
}
4276
}
4277
4278
void Initialize() {
4279
m_screen = ::newterm(nullptr, m_out, m_in);
4280
::start_color();
4281
::curs_set(0);
4282
::noecho();
4283
::keypad(stdscr, TRUE);
4284
}
4285
4286
void Terminate() { ::endwin(); }
4287
4288
void Run(Debugger &debugger) {
4289
bool done = false;
4290
int delay_in_tenths_of_a_second = 1;
4291
4292
// Alas the threading model in curses is a bit lame so we need to resort
4293
// to polling every 0.5 seconds. We could poll for stdin ourselves and
4294
// then pass the keys down but then we need to translate all of the escape
4295
// sequences ourselves. So we resort to polling for input because we need
4296
// to receive async process events while in this loop.
4297
4298
halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4299
// tenths of seconds seconds when
4300
// calling Window::GetChar()
4301
4302
ListenerSP listener_sp(
4303
Listener::MakeListener("lldb.IOHandler.curses.Application"));
4304
ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4305
debugger.EnableForwardEvents(listener_sp);
4306
4307
m_update_screen = true;
4308
#if defined(__APPLE__)
4309
std::deque<int> escape_chars;
4310
#endif
4311
4312
while (!done) {
4313
if (m_update_screen) {
4314
m_window_sp->Draw(false);
4315
// All windows should be calling Window::DeferredRefresh() instead of
4316
// Window::Refresh() so we can do a single update and avoid any screen
4317
// blinking
4318
update_panels();
4319
4320
// Cursor hiding isn't working on MacOSX, so hide it in the top left
4321
// corner
4322
m_window_sp->MoveCursor(0, 0);
4323
4324
doupdate();
4325
m_update_screen = false;
4326
}
4327
4328
#if defined(__APPLE__)
4329
// Terminal.app doesn't map its function keys correctly, F1-F4 default
4330
// to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if
4331
// possible
4332
int ch;
4333
if (escape_chars.empty())
4334
ch = m_window_sp->GetChar();
4335
else {
4336
ch = escape_chars.front();
4337
escape_chars.pop_front();
4338
}
4339
if (ch == KEY_ESCAPE) {
4340
int ch2 = m_window_sp->GetChar();
4341
if (ch2 == 'O') {
4342
int ch3 = m_window_sp->GetChar();
4343
switch (ch3) {
4344
case 'P':
4345
ch = KEY_F(1);
4346
break;
4347
case 'Q':
4348
ch = KEY_F(2);
4349
break;
4350
case 'R':
4351
ch = KEY_F(3);
4352
break;
4353
case 'S':
4354
ch = KEY_F(4);
4355
break;
4356
default:
4357
escape_chars.push_back(ch2);
4358
if (ch3 != -1)
4359
escape_chars.push_back(ch3);
4360
break;
4361
}
4362
} else if (ch2 != -1)
4363
escape_chars.push_back(ch2);
4364
}
4365
#else
4366
int ch = m_window_sp->GetChar();
4367
4368
#endif
4369
if (ch == -1) {
4370
if (feof(m_in) || ferror(m_in)) {
4371
done = true;
4372
} else {
4373
// Just a timeout from using halfdelay(), check for events
4374
EventSP event_sp;
4375
while (listener_sp->PeekAtNextEvent()) {
4376
listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4377
4378
if (event_sp) {
4379
Broadcaster *broadcaster = event_sp->GetBroadcaster();
4380
if (broadcaster) {
4381
// uint32_t event_type = event_sp->GetType();
4382
ConstString broadcaster_class(
4383
broadcaster->GetBroadcasterClass());
4384
if (broadcaster_class == broadcaster_class_process) {
4385
m_update_screen = true;
4386
continue; // Don't get any key, just update our view
4387
}
4388
}
4389
}
4390
}
4391
}
4392
} else {
4393
HandleCharResult key_result = m_window_sp->HandleChar(ch);
4394
switch (key_result) {
4395
case eKeyHandled:
4396
m_update_screen = true;
4397
break;
4398
case eKeyNotHandled:
4399
if (ch == 12) { // Ctrl+L, force full redraw
4400
redrawwin(m_window_sp->get());
4401
m_update_screen = true;
4402
}
4403
break;
4404
case eQuitApplication:
4405
done = true;
4406
break;
4407
}
4408
}
4409
}
4410
4411
debugger.CancelForwardEvents(listener_sp);
4412
}
4413
4414
WindowSP &GetMainWindow() {
4415
if (!m_window_sp)
4416
m_window_sp = std::make_shared<Window>("main", stdscr, false);
4417
return m_window_sp;
4418
}
4419
4420
void TerminalSizeChanged() {
4421
::endwin();
4422
::refresh();
4423
Rect content_bounds = m_window_sp->GetFrame();
4424
m_window_sp->SetBounds(content_bounds);
4425
if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4426
menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4427
if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4428
status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4429
4430
WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4431
WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4432
WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4433
WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4434
4435
Rect threads_bounds;
4436
Rect source_variables_bounds;
4437
content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4438
threads_bounds);
4439
if (threads_window_sp)
4440
threads_window_sp->SetBounds(threads_bounds);
4441
else
4442
source_variables_bounds = content_bounds;
4443
4444
Rect source_bounds;
4445
Rect variables_registers_bounds;
4446
source_variables_bounds.HorizontalSplitPercentage(
4447
0.70, source_bounds, variables_registers_bounds);
4448
if (variables_window_sp || registers_window_sp) {
4449
if (variables_window_sp && registers_window_sp) {
4450
Rect variables_bounds;
4451
Rect registers_bounds;
4452
variables_registers_bounds.VerticalSplitPercentage(
4453
0.50, variables_bounds, registers_bounds);
4454
variables_window_sp->SetBounds(variables_bounds);
4455
registers_window_sp->SetBounds(registers_bounds);
4456
} else if (variables_window_sp) {
4457
variables_window_sp->SetBounds(variables_registers_bounds);
4458
} else {
4459
registers_window_sp->SetBounds(variables_registers_bounds);
4460
}
4461
} else {
4462
source_bounds = source_variables_bounds;
4463
}
4464
4465
source_window_sp->SetBounds(source_bounds);
4466
4467
touchwin(stdscr);
4468
redrawwin(m_window_sp->get());
4469
m_update_screen = true;
4470
}
4471
4472
protected:
4473
WindowSP m_window_sp;
4474
WindowDelegates m_window_delegates;
4475
SCREEN *m_screen = nullptr;
4476
FILE *m_in;
4477
FILE *m_out;
4478
bool m_update_screen = false;
4479
};
4480
4481
} // namespace curses
4482
4483
using namespace curses;
4484
4485
struct Row {
4486
ValueObjectUpdater value;
4487
Row *parent;
4488
// The process stop ID when the children were calculated.
4489
uint32_t children_stop_id = 0;
4490
int row_idx = 0;
4491
int x = 1;
4492
int y = 1;
4493
bool might_have_children;
4494
bool expanded = false;
4495
bool calculated_children = false;
4496
std::vector<Row> children;
4497
4498
Row(const ValueObjectSP &v, Row *p)
4499
: value(v), parent(p),
4500
might_have_children(v ? v->MightHaveChildren() : false) {}
4501
4502
size_t GetDepth() const {
4503
if (parent)
4504
return 1 + parent->GetDepth();
4505
return 0;
4506
}
4507
4508
void Expand() { expanded = true; }
4509
4510
std::vector<Row> &GetChildren() {
4511
ProcessSP process_sp = value.GetProcessSP();
4512
auto stop_id = process_sp->GetStopID();
4513
if (process_sp && stop_id != children_stop_id) {
4514
children_stop_id = stop_id;
4515
calculated_children = false;
4516
}
4517
if (!calculated_children) {
4518
children.clear();
4519
calculated_children = true;
4520
ValueObjectSP valobj = value.GetSP();
4521
if (valobj) {
4522
const uint32_t num_children = valobj->GetNumChildrenIgnoringErrors();
4523
for (size_t i = 0; i < num_children; ++i) {
4524
children.push_back(Row(valobj->GetChildAtIndex(i), this));
4525
}
4526
}
4527
}
4528
return children;
4529
}
4530
4531
void Unexpand() {
4532
expanded = false;
4533
calculated_children = false;
4534
children.clear();
4535
}
4536
4537
void DrawTree(Window &window) {
4538
if (parent)
4539
parent->DrawTreeForChild(window, this, 0);
4540
4541
if (might_have_children &&
4542
(!calculated_children || !GetChildren().empty())) {
4543
// It we can get UTF8 characters to work we should try to use the
4544
// "symbol" UTF8 string below
4545
// const char *symbol = "";
4546
// if (row.expanded)
4547
// symbol = "\xe2\x96\xbd ";
4548
// else
4549
// symbol = "\xe2\x96\xb7 ";
4550
// window.PutCString (symbol);
4551
4552
// The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v'
4553
// or '>' character...
4554
// if (expanded)
4555
// window.PutChar (ACS_DARROW);
4556
// else
4557
// window.PutChar (ACS_RARROW);
4558
// Since we can't find any good looking right arrow/down arrow symbols,
4559
// just use a diamond...
4560
window.PutChar(ACS_DIAMOND);
4561
window.PutChar(ACS_HLINE);
4562
}
4563
}
4564
4565
void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4566
if (parent)
4567
parent->DrawTreeForChild(window, this, reverse_depth + 1);
4568
4569
if (&GetChildren().back() == child) {
4570
// Last child
4571
if (reverse_depth == 0) {
4572
window.PutChar(ACS_LLCORNER);
4573
window.PutChar(ACS_HLINE);
4574
} else {
4575
window.PutChar(' ');
4576
window.PutChar(' ');
4577
}
4578
} else {
4579
if (reverse_depth == 0) {
4580
window.PutChar(ACS_LTEE);
4581
window.PutChar(ACS_HLINE);
4582
} else {
4583
window.PutChar(ACS_VLINE);
4584
window.PutChar(' ');
4585
}
4586
}
4587
}
4588
};
4589
4590
struct DisplayOptions {
4591
bool show_types;
4592
};
4593
4594
class TreeItem;
4595
4596
class TreeDelegate {
4597
public:
4598
TreeDelegate() = default;
4599
virtual ~TreeDelegate() = default;
4600
4601
virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0;
4602
virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0;
4603
virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
4604
TreeItem *&selected_item) {}
4605
// This is invoked when a tree item is selected. If true is returned, the
4606
// views are updated.
4607
virtual bool TreeDelegateItemSelected(TreeItem &item) = 0;
4608
virtual bool TreeDelegateExpandRootByDefault() { return false; }
4609
// This is mostly useful for root tree delegates. If false is returned,
4610
// drawing will be skipped completely. This is needed, for instance, in
4611
// skipping drawing of the threads tree if there is no running process.
4612
virtual bool TreeDelegateShouldDraw() { return true; }
4613
};
4614
4615
typedef std::shared_ptr<TreeDelegate> TreeDelegateSP;
4616
4617
struct TreeItemData {
4618
TreeItemData(TreeItem *parent, TreeDelegate &delegate,
4619
bool might_have_children, bool is_expanded)
4620
: m_parent(parent), m_delegate(&delegate),
4621
m_might_have_children(might_have_children), m_is_expanded(is_expanded) {
4622
}
4623
4624
protected:
4625
TreeItem *m_parent;
4626
TreeDelegate *m_delegate;
4627
void *m_user_data = nullptr;
4628
uint64_t m_identifier = 0;
4629
std::string m_text;
4630
int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for
4631
// the root item
4632
bool m_might_have_children;
4633
bool m_is_expanded = false;
4634
};
4635
4636
class TreeItem : public TreeItemData {
4637
public:
4638
TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children)
4639
: TreeItemData(parent, delegate, might_have_children,
4640
parent == nullptr
4641
? delegate.TreeDelegateExpandRootByDefault()
4642
: false),
4643
m_children() {}
4644
4645
TreeItem(const TreeItem &) = delete;
4646
TreeItem &operator=(const TreeItem &rhs) = delete;
4647
4648
TreeItem &operator=(TreeItem &&rhs) {
4649
if (this != &rhs) {
4650
TreeItemData::operator=(std::move(rhs));
4651
AdoptChildren(rhs.m_children);
4652
}
4653
return *this;
4654
}
4655
4656
TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) {
4657
AdoptChildren(rhs.m_children);
4658
}
4659
4660
size_t GetDepth() const {
4661
if (m_parent)
4662
return 1 + m_parent->GetDepth();
4663
return 0;
4664
}
4665
4666
int GetRowIndex() const { return m_row_idx; }
4667
4668
void ClearChildren() { m_children.clear(); }
4669
4670
void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) {
4671
if (m_children.size() >= n) {
4672
m_children.erase(m_children.begin() + n, m_children.end());
4673
return;
4674
}
4675
m_children.reserve(n);
4676
std::generate_n(std::back_inserter(m_children), n - m_children.size(),
4677
[&, parent = this]() {
4678
return TreeItem(parent, delegate, might_have_children);
4679
});
4680
}
4681
4682
TreeItem &operator[](size_t i) { return m_children[i]; }
4683
4684
void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4685
4686
size_t GetNumChildren() {
4687
m_delegate->TreeDelegateGenerateChildren(*this);
4688
return m_children.size();
4689
}
4690
4691
void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); }
4692
4693
void CalculateRowIndexes(int &row_idx) {
4694
SetRowIndex(row_idx);
4695
++row_idx;
4696
4697
const bool expanded = IsExpanded();
4698
4699
// The root item must calculate its children, or we must calculate the
4700
// number of children if the item is expanded
4701
if (m_parent == nullptr || expanded)
4702
GetNumChildren();
4703
4704
for (auto &item : m_children) {
4705
if (expanded)
4706
item.CalculateRowIndexes(row_idx);
4707
else
4708
item.SetRowIndex(-1);
4709
}
4710
}
4711
4712
TreeItem *GetParent() { return m_parent; }
4713
4714
bool IsExpanded() const { return m_is_expanded; }
4715
4716
void Expand() { m_is_expanded = true; }
4717
4718
void Unexpand() { m_is_expanded = false; }
4719
4720
bool Draw(Window &window, const int first_visible_row,
4721
const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4722
if (num_rows_left <= 0)
4723
return false;
4724
4725
if (m_row_idx >= first_visible_row) {
4726
window.MoveCursor(2, row_idx + 1);
4727
4728
if (m_parent)
4729
m_parent->DrawTreeForChild(window, this, 0);
4730
4731
if (m_might_have_children) {
4732
// It we can get UTF8 characters to work we should try to use the
4733
// "symbol" UTF8 string below
4734
// const char *symbol = "";
4735
// if (row.expanded)
4736
// symbol = "\xe2\x96\xbd ";
4737
// else
4738
// symbol = "\xe2\x96\xb7 ";
4739
// window.PutCString (symbol);
4740
4741
// The ACS_DARROW and ACS_RARROW don't look very nice they are just a
4742
// 'v' or '>' character...
4743
// if (expanded)
4744
// window.PutChar (ACS_DARROW);
4745
// else
4746
// window.PutChar (ACS_RARROW);
4747
// Since we can't find any good looking right arrow/down arrow symbols,
4748
// just use a diamond...
4749
window.PutChar(ACS_DIAMOND);
4750
window.PutChar(ACS_HLINE);
4751
}
4752
bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4753
window.IsActive();
4754
4755
if (highlight)
4756
window.AttributeOn(A_REVERSE);
4757
4758
m_delegate->TreeDelegateDrawTreeItem(*this, window);
4759
4760
if (highlight)
4761
window.AttributeOff(A_REVERSE);
4762
++row_idx;
4763
--num_rows_left;
4764
}
4765
4766
if (num_rows_left <= 0)
4767
return false; // We are done drawing...
4768
4769
if (IsExpanded()) {
4770
for (auto &item : m_children) {
4771
// If we displayed all the rows and item.Draw() returns false we are
4772
// done drawing and can exit this for loop
4773
if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx,
4774
num_rows_left))
4775
break;
4776
}
4777
}
4778
return num_rows_left >= 0; // Return true if not done drawing yet
4779
}
4780
4781
void DrawTreeForChild(Window &window, TreeItem *child,
4782
uint32_t reverse_depth) {
4783
if (m_parent)
4784
m_parent->DrawTreeForChild(window, this, reverse_depth + 1);
4785
4786
if (&m_children.back() == child) {
4787
// Last child
4788
if (reverse_depth == 0) {
4789
window.PutChar(ACS_LLCORNER);
4790
window.PutChar(ACS_HLINE);
4791
} else {
4792
window.PutChar(' ');
4793
window.PutChar(' ');
4794
}
4795
} else {
4796
if (reverse_depth == 0) {
4797
window.PutChar(ACS_LTEE);
4798
window.PutChar(ACS_HLINE);
4799
} else {
4800
window.PutChar(ACS_VLINE);
4801
window.PutChar(' ');
4802
}
4803
}
4804
}
4805
4806
TreeItem *GetItemForRowIndex(uint32_t row_idx) {
4807
if (static_cast<uint32_t>(m_row_idx) == row_idx)
4808
return this;
4809
if (m_children.empty())
4810
return nullptr;
4811
if (IsExpanded()) {
4812
for (auto &item : m_children) {
4813
TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx);
4814
if (selected_item_ptr)
4815
return selected_item_ptr;
4816
}
4817
}
4818
return nullptr;
4819
}
4820
4821
void *GetUserData() const { return m_user_data; }
4822
4823
void SetUserData(void *user_data) { m_user_data = user_data; }
4824
4825
uint64_t GetIdentifier() const { return m_identifier; }
4826
4827
void SetIdentifier(uint64_t identifier) { m_identifier = identifier; }
4828
4829
const std::string &GetText() const { return m_text; }
4830
4831
void SetText(const char *text) {
4832
if (text == nullptr) {
4833
m_text.clear();
4834
return;
4835
}
4836
m_text = text;
4837
}
4838
4839
void SetMightHaveChildren(bool b) { m_might_have_children = b; }
4840
4841
protected:
4842
void AdoptChildren(std::vector<TreeItem> &children) {
4843
m_children = std::move(children);
4844
for (auto &child : m_children)
4845
child.m_parent = this;
4846
}
4847
4848
std::vector<TreeItem> m_children;
4849
};
4850
4851
class TreeWindowDelegate : public WindowDelegate {
4852
public:
4853
TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp)
4854
: m_debugger(debugger), m_delegate_sp(delegate_sp),
4855
m_root(nullptr, *delegate_sp, true) {}
4856
4857
int NumVisibleRows() const { return m_max_y - m_min_y; }
4858
4859
bool WindowDelegateDraw(Window &window, bool force) override {
4860
m_min_x = 2;
4861
m_min_y = 1;
4862
m_max_x = window.GetWidth() - 1;
4863
m_max_y = window.GetHeight() - 1;
4864
4865
window.Erase();
4866
window.DrawTitleBox(window.GetName());
4867
4868
if (!m_delegate_sp->TreeDelegateShouldDraw()) {
4869
m_selected_item = nullptr;
4870
return true;
4871
}
4872
4873
const int num_visible_rows = NumVisibleRows();
4874
m_num_rows = 0;
4875
m_root.CalculateRowIndexes(m_num_rows);
4876
m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx,
4877
m_selected_item);
4878
4879
// If we unexpanded while having something selected our total number of
4880
// rows is less than the num visible rows, then make sure we show all the
4881
// rows by setting the first visible row accordingly.
4882
if (m_first_visible_row > 0 && m_num_rows < num_visible_rows)
4883
m_first_visible_row = 0;
4884
4885
// Make sure the selected row is always visible
4886
if (m_selected_row_idx < m_first_visible_row)
4887
m_first_visible_row = m_selected_row_idx;
4888
else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
4889
m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
4890
4891
int row_idx = 0;
4892
int num_rows_left = num_visible_rows;
4893
m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx,
4894
num_rows_left);
4895
// Get the selected row
4896
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4897
4898
return true; // Drawing handled
4899
}
4900
4901
const char *WindowDelegateGetHelpText() override {
4902
return "Thread window keyboard shortcuts:";
4903
}
4904
4905
KeyHelp *WindowDelegateGetKeyHelp() override {
4906
static curses::KeyHelp g_source_view_key_help[] = {
4907
{KEY_UP, "Select previous item"},
4908
{KEY_DOWN, "Select next item"},
4909
{KEY_RIGHT, "Expand the selected item"},
4910
{KEY_LEFT,
4911
"Unexpand the selected item or select parent if not expanded"},
4912
{KEY_PPAGE, "Page up"},
4913
{KEY_NPAGE, "Page down"},
4914
{'h', "Show help dialog"},
4915
{' ', "Toggle item expansion"},
4916
{',', "Page up"},
4917
{'.', "Page down"},
4918
{'\0', nullptr}};
4919
return g_source_view_key_help;
4920
}
4921
4922
HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4923
switch (c) {
4924
case ',':
4925
case KEY_PPAGE:
4926
// Page up key
4927
if (m_first_visible_row > 0) {
4928
if (m_first_visible_row > m_max_y)
4929
m_first_visible_row -= m_max_y;
4930
else
4931
m_first_visible_row = 0;
4932
m_selected_row_idx = m_first_visible_row;
4933
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4934
if (m_selected_item)
4935
m_selected_item->ItemWasSelected();
4936
}
4937
return eKeyHandled;
4938
4939
case '.':
4940
case KEY_NPAGE:
4941
// Page down key
4942
if (m_num_rows > m_max_y) {
4943
if (m_first_visible_row + m_max_y < m_num_rows) {
4944
m_first_visible_row += m_max_y;
4945
m_selected_row_idx = m_first_visible_row;
4946
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4947
if (m_selected_item)
4948
m_selected_item->ItemWasSelected();
4949
}
4950
}
4951
return eKeyHandled;
4952
4953
case KEY_UP:
4954
if (m_selected_row_idx > 0) {
4955
--m_selected_row_idx;
4956
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4957
if (m_selected_item)
4958
m_selected_item->ItemWasSelected();
4959
}
4960
return eKeyHandled;
4961
4962
case KEY_DOWN:
4963
if (m_selected_row_idx + 1 < m_num_rows) {
4964
++m_selected_row_idx;
4965
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4966
if (m_selected_item)
4967
m_selected_item->ItemWasSelected();
4968
}
4969
return eKeyHandled;
4970
4971
case KEY_RIGHT:
4972
if (m_selected_item) {
4973
if (!m_selected_item->IsExpanded())
4974
m_selected_item->Expand();
4975
}
4976
return eKeyHandled;
4977
4978
case KEY_LEFT:
4979
if (m_selected_item) {
4980
if (m_selected_item->IsExpanded())
4981
m_selected_item->Unexpand();
4982
else if (m_selected_item->GetParent()) {
4983
m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex();
4984
m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx);
4985
if (m_selected_item)
4986
m_selected_item->ItemWasSelected();
4987
}
4988
}
4989
return eKeyHandled;
4990
4991
case ' ':
4992
// Toggle expansion state when SPACE is pressed
4993
if (m_selected_item) {
4994
if (m_selected_item->IsExpanded())
4995
m_selected_item->Unexpand();
4996
else
4997
m_selected_item->Expand();
4998
}
4999
return eKeyHandled;
5000
5001
case 'h':
5002
window.CreateHelpSubwindow();
5003
return eKeyHandled;
5004
5005
default:
5006
break;
5007
}
5008
return eKeyNotHandled;
5009
}
5010
5011
protected:
5012
Debugger &m_debugger;
5013
TreeDelegateSP m_delegate_sp;
5014
TreeItem m_root;
5015
TreeItem *m_selected_item = nullptr;
5016
int m_num_rows = 0;
5017
int m_selected_row_idx = 0;
5018
int m_first_visible_row = 0;
5019
int m_min_x = 0;
5020
int m_min_y = 0;
5021
int m_max_x = 0;
5022
int m_max_y = 0;
5023
};
5024
5025
// A tree delegate that just draws the text member of the tree item, it doesn't
5026
// have any children or actions.
5027
class TextTreeDelegate : public TreeDelegate {
5028
public:
5029
TextTreeDelegate() : TreeDelegate() {}
5030
5031
~TextTreeDelegate() override = default;
5032
5033
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5034
window.PutCStringTruncated(1, item.GetText().c_str());
5035
}
5036
5037
void TreeDelegateGenerateChildren(TreeItem &item) override {}
5038
5039
bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5040
};
5041
5042
class FrameTreeDelegate : public TreeDelegate {
5043
public:
5044
FrameTreeDelegate() : TreeDelegate() {
5045
FormatEntity::Parse(
5046
"#${frame.index}: {${function.name}${function.pc-offset}}}", m_format);
5047
}
5048
5049
~FrameTreeDelegate() override = default;
5050
5051
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5052
Thread *thread = (Thread *)item.GetUserData();
5053
if (thread) {
5054
const uint64_t frame_idx = item.GetIdentifier();
5055
StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx);
5056
if (frame_sp) {
5057
StreamString strm;
5058
const SymbolContext &sc =
5059
frame_sp->GetSymbolContext(eSymbolContextEverything);
5060
ExecutionContext exe_ctx(frame_sp);
5061
if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
5062
nullptr, false, false)) {
5063
int right_pad = 1;
5064
window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5065
}
5066
}
5067
}
5068
}
5069
5070
void TreeDelegateGenerateChildren(TreeItem &item) override {
5071
// No children for frames yet...
5072
}
5073
5074
bool TreeDelegateItemSelected(TreeItem &item) override {
5075
Thread *thread = (Thread *)item.GetUserData();
5076
if (thread) {
5077
thread->GetProcess()->GetThreadList().SetSelectedThreadByID(
5078
thread->GetID());
5079
const uint64_t frame_idx = item.GetIdentifier();
5080
thread->SetSelectedFrameByIndex(frame_idx);
5081
return true;
5082
}
5083
return false;
5084
}
5085
5086
protected:
5087
FormatEntity::Entry m_format;
5088
};
5089
5090
class ThreadTreeDelegate : public TreeDelegate {
5091
public:
5092
ThreadTreeDelegate(Debugger &debugger)
5093
: TreeDelegate(), m_debugger(debugger) {
5094
FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop "
5095
"reason = ${thread.stop-reason}}",
5096
m_format);
5097
}
5098
5099
~ThreadTreeDelegate() override = default;
5100
5101
ProcessSP GetProcess() {
5102
return m_debugger.GetCommandInterpreter()
5103
.GetExecutionContext()
5104
.GetProcessSP();
5105
}
5106
5107
ThreadSP GetThread(const TreeItem &item) {
5108
ProcessSP process_sp = GetProcess();
5109
if (process_sp)
5110
return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier());
5111
return ThreadSP();
5112
}
5113
5114
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5115
ThreadSP thread_sp = GetThread(item);
5116
if (thread_sp) {
5117
StreamString strm;
5118
ExecutionContext exe_ctx(thread_sp);
5119
if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5120
nullptr, false, false)) {
5121
int right_pad = 1;
5122
window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5123
}
5124
}
5125
}
5126
5127
void TreeDelegateGenerateChildren(TreeItem &item) override {
5128
ProcessSP process_sp = GetProcess();
5129
if (process_sp && process_sp->IsAlive()) {
5130
StateType state = process_sp->GetState();
5131
if (StateIsStoppedState(state, true)) {
5132
ThreadSP thread_sp = GetThread(item);
5133
if (thread_sp) {
5134
if (m_stop_id == process_sp->GetStopID() &&
5135
thread_sp->GetID() == m_tid)
5136
return; // Children are already up to date
5137
if (!m_frame_delegate_sp) {
5138
// Always expand the thread item the first time we show it
5139
m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>();
5140
}
5141
5142
m_stop_id = process_sp->GetStopID();
5143
m_tid = thread_sp->GetID();
5144
5145
size_t num_frames = thread_sp->GetStackFrameCount();
5146
item.Resize(num_frames, *m_frame_delegate_sp, false);
5147
for (size_t i = 0; i < num_frames; ++i) {
5148
item[i].SetUserData(thread_sp.get());
5149
item[i].SetIdentifier(i);
5150
}
5151
}
5152
return;
5153
}
5154
}
5155
item.ClearChildren();
5156
}
5157
5158
bool TreeDelegateItemSelected(TreeItem &item) override {
5159
ProcessSP process_sp = GetProcess();
5160
if (process_sp && process_sp->IsAlive()) {
5161
StateType state = process_sp->GetState();
5162
if (StateIsStoppedState(state, true)) {
5163
ThreadSP thread_sp = GetThread(item);
5164
if (thread_sp) {
5165
ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList();
5166
std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex());
5167
ThreadSP selected_thread_sp = thread_list.GetSelectedThread();
5168
if (selected_thread_sp->GetID() != thread_sp->GetID()) {
5169
thread_list.SetSelectedThreadByID(thread_sp->GetID());
5170
return true;
5171
}
5172
}
5173
}
5174
}
5175
return false;
5176
}
5177
5178
protected:
5179
Debugger &m_debugger;
5180
std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp;
5181
lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
5182
uint32_t m_stop_id = UINT32_MAX;
5183
FormatEntity::Entry m_format;
5184
};
5185
5186
class ThreadsTreeDelegate : public TreeDelegate {
5187
public:
5188
ThreadsTreeDelegate(Debugger &debugger)
5189
: TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) {
5190
FormatEntity::Parse("process ${process.id}{, name = ${process.name}}",
5191
m_format);
5192
}
5193
5194
~ThreadsTreeDelegate() override = default;
5195
5196
ProcessSP GetProcess() {
5197
return m_debugger.GetCommandInterpreter()
5198
.GetExecutionContext()
5199
.GetProcessSP();
5200
}
5201
5202
bool TreeDelegateShouldDraw() override {
5203
ProcessSP process = GetProcess();
5204
if (!process)
5205
return false;
5206
5207
if (StateIsRunningState(process->GetState()))
5208
return false;
5209
5210
return true;
5211
}
5212
5213
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5214
ProcessSP process_sp = GetProcess();
5215
if (process_sp && process_sp->IsAlive()) {
5216
StreamString strm;
5217
ExecutionContext exe_ctx(process_sp);
5218
if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
5219
nullptr, false, false)) {
5220
int right_pad = 1;
5221
window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
5222
}
5223
}
5224
}
5225
5226
void TreeDelegateGenerateChildren(TreeItem &item) override {
5227
ProcessSP process_sp = GetProcess();
5228
m_update_selection = false;
5229
if (process_sp && process_sp->IsAlive()) {
5230
StateType state = process_sp->GetState();
5231
if (StateIsStoppedState(state, true)) {
5232
const uint32_t stop_id = process_sp->GetStopID();
5233
if (m_stop_id == stop_id)
5234
return; // Children are already up to date
5235
5236
m_stop_id = stop_id;
5237
m_update_selection = true;
5238
5239
if (!m_thread_delegate_sp) {
5240
// Always expand the thread item the first time we show it
5241
// item.Expand();
5242
m_thread_delegate_sp =
5243
std::make_shared<ThreadTreeDelegate>(m_debugger);
5244
}
5245
5246
ThreadList &threads = process_sp->GetThreadList();
5247
std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5248
ThreadSP selected_thread = threads.GetSelectedThread();
5249
size_t num_threads = threads.GetSize();
5250
item.Resize(num_threads, *m_thread_delegate_sp, false);
5251
for (size_t i = 0; i < num_threads; ++i) {
5252
ThreadSP thread = threads.GetThreadAtIndex(i);
5253
item[i].SetIdentifier(thread->GetID());
5254
item[i].SetMightHaveChildren(true);
5255
if (selected_thread->GetID() == thread->GetID())
5256
item[i].Expand();
5257
}
5258
return;
5259
}
5260
}
5261
item.ClearChildren();
5262
}
5263
5264
void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index,
5265
TreeItem *&selected_item) override {
5266
if (!m_update_selection)
5267
return;
5268
5269
ProcessSP process_sp = GetProcess();
5270
if (!(process_sp && process_sp->IsAlive()))
5271
return;
5272
5273
StateType state = process_sp->GetState();
5274
if (!StateIsStoppedState(state, true))
5275
return;
5276
5277
ThreadList &threads = process_sp->GetThreadList();
5278
std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
5279
ThreadSP selected_thread = threads.GetSelectedThread();
5280
size_t num_threads = threads.GetSize();
5281
for (size_t i = 0; i < num_threads; ++i) {
5282
ThreadSP thread = threads.GetThreadAtIndex(i);
5283
if (selected_thread->GetID() == thread->GetID()) {
5284
selected_item =
5285
&root[i][thread->GetSelectedFrameIndex(SelectMostRelevantFrame)];
5286
selection_index = selected_item->GetRowIndex();
5287
return;
5288
}
5289
}
5290
}
5291
5292
bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5293
5294
bool TreeDelegateExpandRootByDefault() override { return true; }
5295
5296
protected:
5297
std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp;
5298
Debugger &m_debugger;
5299
uint32_t m_stop_id = UINT32_MAX;
5300
bool m_update_selection = false;
5301
FormatEntity::Entry m_format;
5302
};
5303
5304
class BreakpointLocationTreeDelegate : public TreeDelegate {
5305
public:
5306
BreakpointLocationTreeDelegate(Debugger &debugger)
5307
: TreeDelegate(), m_debugger(debugger) {}
5308
5309
~BreakpointLocationTreeDelegate() override = default;
5310
5311
Process *GetProcess() {
5312
ExecutionContext exe_ctx(
5313
m_debugger.GetCommandInterpreter().GetExecutionContext());
5314
return exe_ctx.GetProcessPtr();
5315
}
5316
5317
BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) {
5318
Breakpoint *breakpoint = (Breakpoint *)item.GetUserData();
5319
return breakpoint->GetLocationAtIndex(item.GetIdentifier());
5320
}
5321
5322
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5323
BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5324
Process *process = GetProcess();
5325
StreamString stream;
5326
stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(),
5327
breakpoint_location->GetID());
5328
Address address = breakpoint_location->GetAddress();
5329
address.Dump(&stream, process, Address::DumpStyleResolvedDescription,
5330
Address::DumpStyleInvalid);
5331
window.PutCStringTruncated(1, stream.GetString().str().c_str());
5332
}
5333
5334
StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) {
5335
StringList details;
5336
5337
Address address = breakpoint_location->GetAddress();
5338
SymbolContext symbol_context;
5339
address.CalculateSymbolContext(&symbol_context);
5340
5341
if (symbol_context.module_sp) {
5342
StreamString module_stream;
5343
module_stream.PutCString("module = ");
5344
symbol_context.module_sp->GetFileSpec().Dump(
5345
module_stream.AsRawOstream());
5346
details.AppendString(module_stream.GetString());
5347
}
5348
5349
if (symbol_context.comp_unit != nullptr) {
5350
StreamString compile_unit_stream;
5351
compile_unit_stream.PutCString("compile unit = ");
5352
symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump(
5353
&compile_unit_stream);
5354
details.AppendString(compile_unit_stream.GetString());
5355
5356
if (symbol_context.function != nullptr) {
5357
StreamString function_stream;
5358
function_stream.PutCString("function = ");
5359
function_stream.PutCString(
5360
symbol_context.function->GetName().AsCString("<unknown>"));
5361
details.AppendString(function_stream.GetString());
5362
}
5363
5364
if (symbol_context.line_entry.line > 0) {
5365
StreamString location_stream;
5366
location_stream.PutCString("location = ");
5367
symbol_context.line_entry.DumpStopContext(&location_stream, true);
5368
details.AppendString(location_stream.GetString());
5369
}
5370
5371
} else {
5372
if (symbol_context.symbol) {
5373
StreamString symbol_stream;
5374
if (breakpoint_location->IsReExported())
5375
symbol_stream.PutCString("re-exported target = ");
5376
else
5377
symbol_stream.PutCString("symbol = ");
5378
symbol_stream.PutCString(
5379
symbol_context.symbol->GetName().AsCString("<unknown>"));
5380
details.AppendString(symbol_stream.GetString());
5381
}
5382
}
5383
5384
Process *process = GetProcess();
5385
5386
StreamString address_stream;
5387
address.Dump(&address_stream, process, Address::DumpStyleLoadAddress,
5388
Address::DumpStyleModuleWithFileAddress);
5389
details.AppendString(address_stream.GetString());
5390
5391
BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite();
5392
if (breakpoint_location->IsIndirect() && breakpoint_site) {
5393
Address resolved_address;
5394
resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(),
5395
&breakpoint_location->GetTarget());
5396
Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol();
5397
if (resolved_symbol) {
5398
StreamString indirect_target_stream;
5399
indirect_target_stream.PutCString("indirect target = ");
5400
indirect_target_stream.PutCString(
5401
resolved_symbol->GetName().GetCString());
5402
details.AppendString(indirect_target_stream.GetString());
5403
}
5404
}
5405
5406
bool is_resolved = breakpoint_location->IsResolved();
5407
StreamString resolved_stream;
5408
resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false");
5409
details.AppendString(resolved_stream.GetString());
5410
5411
bool is_hardware = is_resolved && breakpoint_site->IsHardware();
5412
StreamString hardware_stream;
5413
hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false");
5414
details.AppendString(hardware_stream.GetString());
5415
5416
StreamString hit_count_stream;
5417
hit_count_stream.Printf("hit count = %-4u",
5418
breakpoint_location->GetHitCount());
5419
details.AppendString(hit_count_stream.GetString());
5420
5421
return details;
5422
}
5423
5424
void TreeDelegateGenerateChildren(TreeItem &item) override {
5425
BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item);
5426
StringList details = ComputeDetailsList(breakpoint_location);
5427
5428
if (!m_string_delegate_sp)
5429
m_string_delegate_sp = std::make_shared<TextTreeDelegate>();
5430
5431
item.Resize(details.GetSize(), *m_string_delegate_sp, false);
5432
for (size_t i = 0; i < details.GetSize(); i++) {
5433
item[i].SetText(details.GetStringAtIndex(i));
5434
}
5435
}
5436
5437
bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5438
5439
protected:
5440
Debugger &m_debugger;
5441
std::shared_ptr<TextTreeDelegate> m_string_delegate_sp;
5442
};
5443
5444
class BreakpointTreeDelegate : public TreeDelegate {
5445
public:
5446
BreakpointTreeDelegate(Debugger &debugger)
5447
: TreeDelegate(), m_debugger(debugger),
5448
m_breakpoint_location_delegate_sp() {}
5449
5450
~BreakpointTreeDelegate() override = default;
5451
5452
BreakpointSP GetBreakpoint(const TreeItem &item) {
5453
TargetSP target = m_debugger.GetSelectedTarget();
5454
BreakpointList &breakpoints = target->GetBreakpointList(false);
5455
return breakpoints.GetBreakpointAtIndex(item.GetIdentifier());
5456
}
5457
5458
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5459
BreakpointSP breakpoint = GetBreakpoint(item);
5460
StreamString stream;
5461
stream.Format("{0}: ", breakpoint->GetID());
5462
breakpoint->GetResolverDescription(&stream);
5463
breakpoint->GetFilterDescription(&stream);
5464
window.PutCStringTruncated(1, stream.GetString().str().c_str());
5465
}
5466
5467
void TreeDelegateGenerateChildren(TreeItem &item) override {
5468
BreakpointSP breakpoint = GetBreakpoint(item);
5469
5470
if (!m_breakpoint_location_delegate_sp)
5471
m_breakpoint_location_delegate_sp =
5472
std::make_shared<BreakpointLocationTreeDelegate>(m_debugger);
5473
5474
item.Resize(breakpoint->GetNumLocations(),
5475
*m_breakpoint_location_delegate_sp, true);
5476
for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) {
5477
item[i].SetIdentifier(i);
5478
item[i].SetUserData(breakpoint.get());
5479
}
5480
}
5481
5482
bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5483
5484
protected:
5485
Debugger &m_debugger;
5486
std::shared_ptr<BreakpointLocationTreeDelegate>
5487
m_breakpoint_location_delegate_sp;
5488
};
5489
5490
class BreakpointsTreeDelegate : public TreeDelegate {
5491
public:
5492
BreakpointsTreeDelegate(Debugger &debugger)
5493
: TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {}
5494
5495
~BreakpointsTreeDelegate() override = default;
5496
5497
bool TreeDelegateShouldDraw() override {
5498
TargetSP target = m_debugger.GetSelectedTarget();
5499
if (!target)
5500
return false;
5501
5502
return true;
5503
}
5504
5505
void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override {
5506
window.PutCString("Breakpoints");
5507
}
5508
5509
void TreeDelegateGenerateChildren(TreeItem &item) override {
5510
TargetSP target = m_debugger.GetSelectedTarget();
5511
5512
BreakpointList &breakpoints = target->GetBreakpointList(false);
5513
std::unique_lock<std::recursive_mutex> lock;
5514
breakpoints.GetListMutex(lock);
5515
5516
if (!m_breakpoint_delegate_sp)
5517
m_breakpoint_delegate_sp =
5518
std::make_shared<BreakpointTreeDelegate>(m_debugger);
5519
5520
item.Resize(breakpoints.GetSize(), *m_breakpoint_delegate_sp, true);
5521
for (size_t i = 0; i < breakpoints.GetSize(); i++) {
5522
item[i].SetIdentifier(i);
5523
}
5524
}
5525
5526
bool TreeDelegateItemSelected(TreeItem &item) override { return false; }
5527
5528
bool TreeDelegateExpandRootByDefault() override { return true; }
5529
5530
protected:
5531
Debugger &m_debugger;
5532
std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp;
5533
};
5534
5535
class ValueObjectListDelegate : public WindowDelegate {
5536
public:
5537
ValueObjectListDelegate() : m_rows() {}
5538
5539
ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() {
5540
SetValues(valobj_list);
5541
}
5542
5543
~ValueObjectListDelegate() override = default;
5544
5545
void SetValues(ValueObjectList &valobj_list) {
5546
m_selected_row = nullptr;
5547
m_selected_row_idx = 0;
5548
m_first_visible_row = 0;
5549
m_num_rows = 0;
5550
m_rows.clear();
5551
for (auto &valobj_sp : valobj_list.GetObjects())
5552
m_rows.push_back(Row(valobj_sp, nullptr));
5553
}
5554
5555
bool WindowDelegateDraw(Window &window, bool force) override {
5556
m_num_rows = 0;
5557
m_min_x = 2;
5558
m_min_y = 1;
5559
m_max_x = window.GetWidth() - 1;
5560
m_max_y = window.GetHeight() - 1;
5561
5562
window.Erase();
5563
window.DrawTitleBox(window.GetName());
5564
5565
const int num_visible_rows = NumVisibleRows();
5566
const int num_rows = CalculateTotalNumberRows(m_rows);
5567
5568
// If we unexpanded while having something selected our total number of
5569
// rows is less than the num visible rows, then make sure we show all the
5570
// rows by setting the first visible row accordingly.
5571
if (m_first_visible_row > 0 && num_rows < num_visible_rows)
5572
m_first_visible_row = 0;
5573
5574
// Make sure the selected row is always visible
5575
if (m_selected_row_idx < m_first_visible_row)
5576
m_first_visible_row = m_selected_row_idx;
5577
else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx)
5578
m_first_visible_row = m_selected_row_idx - num_visible_rows + 1;
5579
5580
DisplayRows(window, m_rows, g_options);
5581
5582
// Get the selected row
5583
m_selected_row = GetRowForRowIndex(m_selected_row_idx);
5584
// Keep the cursor on the selected row so the highlight and the cursor are
5585
// always on the same line
5586
if (m_selected_row)
5587
window.MoveCursor(m_selected_row->x, m_selected_row->y);
5588
5589
return true; // Drawing handled
5590
}
5591
5592
KeyHelp *WindowDelegateGetKeyHelp() override {
5593
static curses::KeyHelp g_source_view_key_help[] = {
5594
{KEY_UP, "Select previous item"},
5595
{KEY_DOWN, "Select next item"},
5596
{KEY_RIGHT, "Expand selected item"},
5597
{KEY_LEFT, "Unexpand selected item or select parent if not expanded"},
5598
{KEY_PPAGE, "Page up"},
5599
{KEY_NPAGE, "Page down"},
5600
{'A', "Format as annotated address"},
5601
{'b', "Format as binary"},
5602
{'B', "Format as hex bytes with ASCII"},
5603
{'c', "Format as character"},
5604
{'d', "Format as a signed integer"},
5605
{'D', "Format selected value using the default format for the type"},
5606
{'f', "Format as float"},
5607
{'h', "Show help dialog"},
5608
{'i', "Format as instructions"},
5609
{'o', "Format as octal"},
5610
{'p', "Format as pointer"},
5611
{'s', "Format as C string"},
5612
{'t', "Toggle showing/hiding type names"},
5613
{'u', "Format as an unsigned integer"},
5614
{'x', "Format as hex"},
5615
{'X', "Format as uppercase hex"},
5616
{' ', "Toggle item expansion"},
5617
{',', "Page up"},
5618
{'.', "Page down"},
5619
{'\0', nullptr}};
5620
return g_source_view_key_help;
5621
}
5622
5623
HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
5624
switch (c) {
5625
case 'x':
5626
case 'X':
5627
case 'o':
5628
case 's':
5629
case 'u':
5630
case 'd':
5631
case 'D':
5632
case 'i':
5633
case 'A':
5634
case 'p':
5635
case 'c':
5636
case 'b':
5637
case 'B':
5638
case 'f':
5639
// Change the format for the currently selected item
5640
if (m_selected_row) {
5641
auto valobj_sp = m_selected_row->value.GetSP();
5642
if (valobj_sp)
5643
valobj_sp->SetFormat(FormatForChar(c));
5644
}
5645
return eKeyHandled;
5646
5647
case 't':
5648
// Toggle showing type names
5649
g_options.show_types = !g_options.show_types;
5650
return eKeyHandled;
5651
5652
case ',':
5653
case KEY_PPAGE:
5654
// Page up key
5655
if (m_first_visible_row > 0) {
5656
if (static_cast<int>(m_first_visible_row) > m_max_y)
5657
m_first_visible_row -= m_max_y;
5658
else
5659
m_first_visible_row = 0;
5660
m_selected_row_idx = m_first_visible_row;
5661
}
5662
return eKeyHandled;
5663
5664
case '.':
5665
case KEY_NPAGE:
5666
// Page down key
5667
if (m_num_rows > static_cast<size_t>(m_max_y)) {
5668
if (m_first_visible_row + m_max_y < m_num_rows) {
5669
m_first_visible_row += m_max_y;
5670
m_selected_row_idx = m_first_visible_row;
5671
}
5672
}
5673
return eKeyHandled;
5674
5675
case KEY_UP:
5676
if (m_selected_row_idx > 0)
5677
--m_selected_row_idx;
5678
return eKeyHandled;
5679
5680
case KEY_DOWN:
5681
if (m_selected_row_idx + 1 < m_num_rows)
5682
++m_selected_row_idx;
5683
return eKeyHandled;
5684
5685
case KEY_RIGHT:
5686
if (m_selected_row) {
5687
if (!m_selected_row->expanded)
5688
m_selected_row->Expand();
5689
}
5690
return eKeyHandled;
5691
5692
case KEY_LEFT:
5693
if (m_selected_row) {
5694
if (m_selected_row->expanded)
5695
m_selected_row->Unexpand();
5696
else if (m_selected_row->parent)
5697
m_selected_row_idx = m_selected_row->parent->row_idx;
5698
}
5699
return eKeyHandled;
5700
5701
case ' ':
5702
// Toggle expansion state when SPACE is pressed
5703
if (m_selected_row) {
5704
if (m_selected_row->expanded)
5705
m_selected_row->Unexpand();
5706
else
5707
m_selected_row->Expand();
5708
}
5709
return eKeyHandled;
5710
5711
case 'h':
5712
window.CreateHelpSubwindow();
5713
return eKeyHandled;
5714
5715
default:
5716
break;
5717
}
5718
return eKeyNotHandled;
5719
}
5720
5721
protected:
5722
std::vector<Row> m_rows;
5723
Row *m_selected_row = nullptr;
5724
uint32_t m_selected_row_idx = 0;
5725
uint32_t m_first_visible_row = 0;
5726
uint32_t m_num_rows = 0;
5727
int m_min_x = 0;
5728
int m_min_y = 0;
5729
int m_max_x = 0;
5730
int m_max_y = 0;
5731
5732
static Format FormatForChar(int c) {
5733
switch (c) {
5734
case 'x':
5735
return eFormatHex;
5736
case 'X':
5737
return eFormatHexUppercase;
5738
case 'o':
5739
return eFormatOctal;
5740
case 's':
5741
return eFormatCString;
5742
case 'u':
5743
return eFormatUnsigned;
5744
case 'd':
5745
return eFormatDecimal;
5746
case 'D':
5747
return eFormatDefault;
5748
case 'i':
5749
return eFormatInstruction;
5750
case 'A':
5751
return eFormatAddressInfo;
5752
case 'p':
5753
return eFormatPointer;
5754
case 'c':
5755
return eFormatChar;
5756
case 'b':
5757
return eFormatBinary;
5758
case 'B':
5759
return eFormatBytesWithASCII;
5760
case 'f':
5761
return eFormatFloat;
5762
}
5763
return eFormatDefault;
5764
}
5765
5766
bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options,
5767
bool highlight, bool last_child) {
5768
ValueObject *valobj = row.value.GetSP().get();
5769
5770
if (valobj == nullptr)
5771
return false;
5772
5773
const char *type_name =
5774
options.show_types ? valobj->GetTypeName().GetCString() : nullptr;
5775
const char *name = valobj->GetName().GetCString();
5776
const char *value = valobj->GetValueAsCString();
5777
const char *summary = valobj->GetSummaryAsCString();
5778
5779
window.MoveCursor(row.x, row.y);
5780
5781
row.DrawTree(window);
5782
5783
if (highlight)
5784
window.AttributeOn(A_REVERSE);
5785
5786
if (type_name && type_name[0])
5787
window.PrintfTruncated(1, "(%s) ", type_name);
5788
5789
if (name && name[0])
5790
window.PutCStringTruncated(1, name);
5791
5792
attr_t changd_attr = 0;
5793
if (valobj->GetValueDidChange())
5794
changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD;
5795
5796
if (value && value[0]) {
5797
window.PutCStringTruncated(1, " = ");
5798
if (changd_attr)
5799
window.AttributeOn(changd_attr);
5800
window.PutCStringTruncated(1, value);
5801
if (changd_attr)
5802
window.AttributeOff(changd_attr);
5803
}
5804
5805
if (summary && summary[0]) {
5806
window.PutCStringTruncated(1, " ");
5807
if (changd_attr)
5808
window.AttributeOn(changd_attr);
5809
window.PutCStringTruncated(1, summary);
5810
if (changd_attr)
5811
window.AttributeOff(changd_attr);
5812
}
5813
5814
if (highlight)
5815
window.AttributeOff(A_REVERSE);
5816
5817
return true;
5818
}
5819
5820
void DisplayRows(Window &window, std::vector<Row> &rows,
5821
DisplayOptions &options) {
5822
// > 0x25B7
5823
// \/ 0x25BD
5824
5825
bool window_is_active = window.IsActive();
5826
for (auto &row : rows) {
5827
const bool last_child = row.parent && &rows[rows.size() - 1] == &row;
5828
// Save the row index in each Row structure
5829
row.row_idx = m_num_rows;
5830
if ((m_num_rows >= m_first_visible_row) &&
5831
((m_num_rows - m_first_visible_row) <
5832
static_cast<size_t>(NumVisibleRows()))) {
5833
row.x = m_min_x;
5834
row.y = m_num_rows - m_first_visible_row + 1;
5835
if (DisplayRowObject(window, row, options,
5836
window_is_active &&
5837
m_num_rows == m_selected_row_idx,
5838
last_child)) {
5839
++m_num_rows;
5840
} else {
5841
row.x = 0;
5842
row.y = 0;
5843
}
5844
} else {
5845
row.x = 0;
5846
row.y = 0;
5847
++m_num_rows;
5848
}
5849
5850
if (row.expanded) {
5851
auto &children = row.GetChildren();
5852
if (!children.empty()) {
5853
DisplayRows(window, children, options);
5854
}
5855
}
5856
}
5857
}
5858
5859
int CalculateTotalNumberRows(std::vector<Row> &rows) {
5860
int row_count = 0;
5861
for (auto &row : rows) {
5862
++row_count;
5863
if (row.expanded)
5864
row_count += CalculateTotalNumberRows(row.GetChildren());
5865
}
5866
return row_count;
5867
}
5868
5869
static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) {
5870
for (auto &row : rows) {
5871
if (row_index == 0)
5872
return &row;
5873
else {
5874
--row_index;
5875
if (row.expanded) {
5876
auto &children = row.GetChildren();
5877
if (!children.empty()) {
5878
Row *result = GetRowForRowIndexImpl(children, row_index);
5879
if (result)
5880
return result;
5881
}
5882
}
5883
}
5884
}
5885
return nullptr;
5886
}
5887
5888
Row *GetRowForRowIndex(size_t row_index) {
5889
return GetRowForRowIndexImpl(m_rows, row_index);
5890
}
5891
5892
int NumVisibleRows() const { return m_max_y - m_min_y; }
5893
5894
static DisplayOptions g_options;
5895
};
5896
5897
class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
5898
public:
5899
FrameVariablesWindowDelegate(Debugger &debugger)
5900
: ValueObjectListDelegate(), m_debugger(debugger) {}
5901
5902
~FrameVariablesWindowDelegate() override = default;
5903
5904
const char *WindowDelegateGetHelpText() override {
5905
return "Frame variable window keyboard shortcuts:";
5906
}
5907
5908
bool WindowDelegateDraw(Window &window, bool force) override {
5909
ExecutionContext exe_ctx(
5910
m_debugger.GetCommandInterpreter().GetExecutionContext());
5911
Process *process = exe_ctx.GetProcessPtr();
5912
Block *frame_block = nullptr;
5913
StackFrame *frame = nullptr;
5914
5915
if (process) {
5916
StateType state = process->GetState();
5917
if (StateIsStoppedState(state, true)) {
5918
frame = exe_ctx.GetFramePtr();
5919
if (frame)
5920
frame_block = frame->GetFrameBlock();
5921
} else if (StateIsRunningState(state)) {
5922
return true; // Don't do any updating when we are running
5923
}
5924
}
5925
5926
ValueObjectList local_values;
5927
if (frame_block) {
5928
// Only update the variables if they have changed
5929
if (m_frame_block != frame_block) {
5930
m_frame_block = frame_block;
5931
5932
VariableList *locals = frame->GetVariableList(true, nullptr);
5933
if (locals) {
5934
const DynamicValueType use_dynamic = eDynamicDontRunTarget;
5935
for (const VariableSP &local_sp : *locals) {
5936
ValueObjectSP value_sp =
5937
frame->GetValueObjectForFrameVariable(local_sp, use_dynamic);
5938
if (value_sp) {
5939
ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
5940
if (synthetic_value_sp)
5941
local_values.Append(synthetic_value_sp);
5942
else
5943
local_values.Append(value_sp);
5944
}
5945
}
5946
// Update the values
5947
SetValues(local_values);
5948
}
5949
}
5950
} else {
5951
m_frame_block = nullptr;
5952
// Update the values with an empty list if there is no frame
5953
SetValues(local_values);
5954
}
5955
5956
return ValueObjectListDelegate::WindowDelegateDraw(window, force);
5957
}
5958
5959
protected:
5960
Debugger &m_debugger;
5961
Block *m_frame_block = nullptr;
5962
};
5963
5964
class RegistersWindowDelegate : public ValueObjectListDelegate {
5965
public:
5966
RegistersWindowDelegate(Debugger &debugger)
5967
: ValueObjectListDelegate(), m_debugger(debugger) {}
5968
5969
~RegistersWindowDelegate() override = default;
5970
5971
const char *WindowDelegateGetHelpText() override {
5972
return "Register window keyboard shortcuts:";
5973
}
5974
5975
bool WindowDelegateDraw(Window &window, bool force) override {
5976
ExecutionContext exe_ctx(
5977
m_debugger.GetCommandInterpreter().GetExecutionContext());
5978
StackFrame *frame = exe_ctx.GetFramePtr();
5979
5980
ValueObjectList value_list;
5981
if (frame) {
5982
if (frame->GetStackID() != m_stack_id) {
5983
m_stack_id = frame->GetStackID();
5984
RegisterContextSP reg_ctx(frame->GetRegisterContext());
5985
if (reg_ctx) {
5986
const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
5987
for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
5988
value_list.Append(
5989
ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
5990
}
5991
}
5992
SetValues(value_list);
5993
}
5994
} else {
5995
Process *process = exe_ctx.GetProcessPtr();
5996
if (process && process->IsAlive())
5997
return true; // Don't do any updating if we are running
5998
else {
5999
// Update the values with an empty list if there is no process or the
6000
// process isn't alive anymore
6001
SetValues(value_list);
6002
}
6003
}
6004
return ValueObjectListDelegate::WindowDelegateDraw(window, force);
6005
}
6006
6007
protected:
6008
Debugger &m_debugger;
6009
StackID m_stack_id;
6010
};
6011
6012
static const char *CursesKeyToCString(int ch) {
6013
static char g_desc[32];
6014
if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
6015
snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
6016
return g_desc;
6017
}
6018
switch (ch) {
6019
case KEY_DOWN:
6020
return "down";
6021
case KEY_UP:
6022
return "up";
6023
case KEY_LEFT:
6024
return "left";
6025
case KEY_RIGHT:
6026
return "right";
6027
case KEY_HOME:
6028
return "home";
6029
case KEY_BACKSPACE:
6030
return "backspace";
6031
case KEY_DL:
6032
return "delete-line";
6033
case KEY_IL:
6034
return "insert-line";
6035
case KEY_DC:
6036
return "delete-char";
6037
case KEY_IC:
6038
return "insert-char";
6039
case KEY_CLEAR:
6040
return "clear";
6041
case KEY_EOS:
6042
return "clear-to-eos";
6043
case KEY_EOL:
6044
return "clear-to-eol";
6045
case KEY_SF:
6046
return "scroll-forward";
6047
case KEY_SR:
6048
return "scroll-backward";
6049
case KEY_NPAGE:
6050
return "page-down";
6051
case KEY_PPAGE:
6052
return "page-up";
6053
case KEY_STAB:
6054
return "set-tab";
6055
case KEY_CTAB:
6056
return "clear-tab";
6057
case KEY_CATAB:
6058
return "clear-all-tabs";
6059
case KEY_ENTER:
6060
return "enter";
6061
case KEY_PRINT:
6062
return "print";
6063
case KEY_LL:
6064
return "lower-left key";
6065
case KEY_A1:
6066
return "upper left of keypad";
6067
case KEY_A3:
6068
return "upper right of keypad";
6069
case KEY_B2:
6070
return "center of keypad";
6071
case KEY_C1:
6072
return "lower left of keypad";
6073
case KEY_C3:
6074
return "lower right of keypad";
6075
case KEY_BTAB:
6076
return "back-tab key";
6077
case KEY_BEG:
6078
return "begin key";
6079
case KEY_CANCEL:
6080
return "cancel key";
6081
case KEY_CLOSE:
6082
return "close key";
6083
case KEY_COMMAND:
6084
return "command key";
6085
case KEY_COPY:
6086
return "copy key";
6087
case KEY_CREATE:
6088
return "create key";
6089
case KEY_END:
6090
return "end key";
6091
case KEY_EXIT:
6092
return "exit key";
6093
case KEY_FIND:
6094
return "find key";
6095
case KEY_HELP:
6096
return "help key";
6097
case KEY_MARK:
6098
return "mark key";
6099
case KEY_MESSAGE:
6100
return "message key";
6101
case KEY_MOVE:
6102
return "move key";
6103
case KEY_NEXT:
6104
return "next key";
6105
case KEY_OPEN:
6106
return "open key";
6107
case KEY_OPTIONS:
6108
return "options key";
6109
case KEY_PREVIOUS:
6110
return "previous key";
6111
case KEY_REDO:
6112
return "redo key";
6113
case KEY_REFERENCE:
6114
return "reference key";
6115
case KEY_REFRESH:
6116
return "refresh key";
6117
case KEY_REPLACE:
6118
return "replace key";
6119
case KEY_RESTART:
6120
return "restart key";
6121
case KEY_RESUME:
6122
return "resume key";
6123
case KEY_SAVE:
6124
return "save key";
6125
case KEY_SBEG:
6126
return "shifted begin key";
6127
case KEY_SCANCEL:
6128
return "shifted cancel key";
6129
case KEY_SCOMMAND:
6130
return "shifted command key";
6131
case KEY_SCOPY:
6132
return "shifted copy key";
6133
case KEY_SCREATE:
6134
return "shifted create key";
6135
case KEY_SDC:
6136
return "shifted delete-character key";
6137
case KEY_SDL:
6138
return "shifted delete-line key";
6139
case KEY_SELECT:
6140
return "select key";
6141
case KEY_SEND:
6142
return "shifted end key";
6143
case KEY_SEOL:
6144
return "shifted clear-to-end-of-line key";
6145
case KEY_SEXIT:
6146
return "shifted exit key";
6147
case KEY_SFIND:
6148
return "shifted find key";
6149
case KEY_SHELP:
6150
return "shifted help key";
6151
case KEY_SHOME:
6152
return "shifted home key";
6153
case KEY_SIC:
6154
return "shifted insert-character key";
6155
case KEY_SLEFT:
6156
return "shifted left-arrow key";
6157
case KEY_SMESSAGE:
6158
return "shifted message key";
6159
case KEY_SMOVE:
6160
return "shifted move key";
6161
case KEY_SNEXT:
6162
return "shifted next key";
6163
case KEY_SOPTIONS:
6164
return "shifted options key";
6165
case KEY_SPREVIOUS:
6166
return "shifted previous key";
6167
case KEY_SPRINT:
6168
return "shifted print key";
6169
case KEY_SREDO:
6170
return "shifted redo key";
6171
case KEY_SREPLACE:
6172
return "shifted replace key";
6173
case KEY_SRIGHT:
6174
return "shifted right-arrow key";
6175
case KEY_SRSUME:
6176
return "shifted resume key";
6177
case KEY_SSAVE:
6178
return "shifted save key";
6179
case KEY_SSUSPEND:
6180
return "shifted suspend key";
6181
case KEY_SUNDO:
6182
return "shifted undo key";
6183
case KEY_SUSPEND:
6184
return "suspend key";
6185
case KEY_UNDO:
6186
return "undo key";
6187
case KEY_MOUSE:
6188
return "Mouse event has occurred";
6189
case KEY_RESIZE:
6190
return "Terminal resize event";
6191
#ifdef KEY_EVENT
6192
case KEY_EVENT:
6193
return "We were interrupted by an event";
6194
#endif
6195
case KEY_RETURN:
6196
return "return";
6197
case ' ':
6198
return "space";
6199
case '\t':
6200
return "tab";
6201
case KEY_ESCAPE:
6202
return "escape";
6203
default:
6204
if (llvm::isPrint(ch))
6205
snprintf(g_desc, sizeof(g_desc), "%c", ch);
6206
else
6207
snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
6208
return g_desc;
6209
}
6210
return nullptr;
6211
}
6212
6213
HelpDialogDelegate::HelpDialogDelegate(const char *text,
6214
KeyHelp *key_help_array)
6215
: m_text() {
6216
if (text && text[0]) {
6217
m_text.SplitIntoLines(text);
6218
m_text.AppendString("");
6219
}
6220
if (key_help_array) {
6221
for (KeyHelp *key = key_help_array; key->ch; ++key) {
6222
StreamString key_description;
6223
key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
6224
key->description);
6225
m_text.AppendString(key_description.GetString());
6226
}
6227
}
6228
}
6229
6230
HelpDialogDelegate::~HelpDialogDelegate() = default;
6231
6232
bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
6233
window.Erase();
6234
const int window_height = window.GetHeight();
6235
int x = 2;
6236
int y = 1;
6237
const int min_y = y;
6238
const int max_y = window_height - 1 - y;
6239
const size_t num_visible_lines = max_y - min_y + 1;
6240
const size_t num_lines = m_text.GetSize();
6241
const char *bottom_message;
6242
if (num_lines <= num_visible_lines)
6243
bottom_message = "Press any key to exit";
6244
else
6245
bottom_message = "Use arrows to scroll, any other key to exit";
6246
window.DrawTitleBox(window.GetName(), bottom_message);
6247
while (y <= max_y) {
6248
window.MoveCursor(x, y);
6249
window.PutCStringTruncated(
6250
1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y));
6251
++y;
6252
}
6253
return true;
6254
}
6255
6256
HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
6257
int key) {
6258
bool done = false;
6259
const size_t num_lines = m_text.GetSize();
6260
const size_t num_visible_lines = window.GetHeight() - 2;
6261
6262
if (num_lines <= num_visible_lines) {
6263
done = true;
6264
// If we have all lines visible and don't need scrolling, then any key
6265
// press will cause us to exit
6266
} else {
6267
switch (key) {
6268
case KEY_UP:
6269
if (m_first_visible_line > 0)
6270
--m_first_visible_line;
6271
break;
6272
6273
case KEY_DOWN:
6274
if (m_first_visible_line + num_visible_lines < num_lines)
6275
++m_first_visible_line;
6276
break;
6277
6278
case KEY_PPAGE:
6279
case ',':
6280
if (m_first_visible_line > 0) {
6281
if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
6282
m_first_visible_line -= num_visible_lines;
6283
else
6284
m_first_visible_line = 0;
6285
}
6286
break;
6287
6288
case KEY_NPAGE:
6289
case '.':
6290
if (m_first_visible_line + num_visible_lines < num_lines) {
6291
m_first_visible_line += num_visible_lines;
6292
if (static_cast<size_t>(m_first_visible_line) > num_lines)
6293
m_first_visible_line = num_lines - num_visible_lines;
6294
}
6295
break;
6296
6297
default:
6298
done = true;
6299
break;
6300
}
6301
}
6302
if (done)
6303
window.GetParent()->RemoveSubWindow(&window);
6304
return eKeyHandled;
6305
}
6306
6307
class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
6308
public:
6309
enum {
6310
eMenuID_LLDB = 1,
6311
eMenuID_LLDBAbout,
6312
eMenuID_LLDBExit,
6313
6314
eMenuID_Target,
6315
eMenuID_TargetCreate,
6316
eMenuID_TargetDelete,
6317
6318
eMenuID_Process,
6319
eMenuID_ProcessAttach,
6320
eMenuID_ProcessDetachResume,
6321
eMenuID_ProcessDetachSuspended,
6322
eMenuID_ProcessLaunch,
6323
eMenuID_ProcessContinue,
6324
eMenuID_ProcessHalt,
6325
eMenuID_ProcessKill,
6326
6327
eMenuID_Thread,
6328
eMenuID_ThreadStepIn,
6329
eMenuID_ThreadStepOver,
6330
eMenuID_ThreadStepOut,
6331
6332
eMenuID_View,
6333
eMenuID_ViewBacktrace,
6334
eMenuID_ViewRegisters,
6335
eMenuID_ViewSource,
6336
eMenuID_ViewVariables,
6337
eMenuID_ViewBreakpoints,
6338
6339
eMenuID_Help,
6340
eMenuID_HelpGUIHelp
6341
};
6342
6343
ApplicationDelegate(Application &app, Debugger &debugger)
6344
: WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
6345
6346
~ApplicationDelegate() override = default;
6347
6348
bool WindowDelegateDraw(Window &window, bool force) override {
6349
return false; // Drawing not handled, let standard window drawing happen
6350
}
6351
6352
HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
6353
switch (key) {
6354
case '\t':
6355
window.SelectNextWindowAsActive();
6356
return eKeyHandled;
6357
6358
case KEY_SHIFT_TAB:
6359
window.SelectPreviousWindowAsActive();
6360
return eKeyHandled;
6361
6362
case 'h':
6363
window.CreateHelpSubwindow();
6364
return eKeyHandled;
6365
6366
case KEY_ESCAPE:
6367
return eQuitApplication;
6368
6369
default:
6370
break;
6371
}
6372
return eKeyNotHandled;
6373
}
6374
6375
const char *WindowDelegateGetHelpText() override {
6376
return "Welcome to the LLDB curses GUI.\n\n"
6377
"Press the TAB key to change the selected view.\n"
6378
"Each view has its own keyboard shortcuts, press 'h' to open a "
6379
"dialog to display them.\n\n"
6380
"Common key bindings for all views:";
6381
}
6382
6383
KeyHelp *WindowDelegateGetKeyHelp() override {
6384
static curses::KeyHelp g_source_view_key_help[] = {
6385
{'\t', "Select next view"},
6386
{KEY_BTAB, "Select previous view"},
6387
{'h', "Show help dialog with view specific key bindings"},
6388
{',', "Page up"},
6389
{'.', "Page down"},
6390
{KEY_UP, "Select previous"},
6391
{KEY_DOWN, "Select next"},
6392
{KEY_LEFT, "Unexpand or select parent"},
6393
{KEY_RIGHT, "Expand"},
6394
{KEY_PPAGE, "Page up"},
6395
{KEY_NPAGE, "Page down"},
6396
{'\0', nullptr}};
6397
return g_source_view_key_help;
6398
}
6399
6400
MenuActionResult MenuDelegateAction(Menu &menu) override {
6401
switch (menu.GetIdentifier()) {
6402
case eMenuID_TargetCreate: {
6403
WindowSP main_window_sp = m_app.GetMainWindow();
6404
FormDelegateSP form_delegate_sp =
6405
FormDelegateSP(new TargetCreateFormDelegate(m_debugger));
6406
Rect bounds = main_window_sp->GetCenteredRect(80, 19);
6407
WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6408
form_delegate_sp->GetName().c_str(), bounds, true);
6409
WindowDelegateSP window_delegate_sp =
6410
WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6411
form_window_sp->SetDelegate(window_delegate_sp);
6412
return MenuActionResult::Handled;
6413
}
6414
case eMenuID_ThreadStepIn: {
6415
ExecutionContext exe_ctx =
6416
m_debugger.GetCommandInterpreter().GetExecutionContext();
6417
if (exe_ctx.HasThreadScope()) {
6418
Process *process = exe_ctx.GetProcessPtr();
6419
if (process && process->IsAlive() &&
6420
StateIsStoppedState(process->GetState(), true))
6421
exe_ctx.GetThreadRef().StepIn(true);
6422
}
6423
}
6424
return MenuActionResult::Handled;
6425
6426
case eMenuID_ThreadStepOut: {
6427
ExecutionContext exe_ctx =
6428
m_debugger.GetCommandInterpreter().GetExecutionContext();
6429
if (exe_ctx.HasThreadScope()) {
6430
Process *process = exe_ctx.GetProcessPtr();
6431
if (process && process->IsAlive() &&
6432
StateIsStoppedState(process->GetState(), true)) {
6433
Thread *thread = exe_ctx.GetThreadPtr();
6434
uint32_t frame_idx =
6435
thread->GetSelectedFrameIndex(SelectMostRelevantFrame);
6436
exe_ctx.GetThreadRef().StepOut(frame_idx);
6437
}
6438
}
6439
}
6440
return MenuActionResult::Handled;
6441
6442
case eMenuID_ThreadStepOver: {
6443
ExecutionContext exe_ctx =
6444
m_debugger.GetCommandInterpreter().GetExecutionContext();
6445
if (exe_ctx.HasThreadScope()) {
6446
Process *process = exe_ctx.GetProcessPtr();
6447
if (process && process->IsAlive() &&
6448
StateIsStoppedState(process->GetState(), true))
6449
exe_ctx.GetThreadRef().StepOver(true);
6450
}
6451
}
6452
return MenuActionResult::Handled;
6453
6454
case eMenuID_ProcessAttach: {
6455
WindowSP main_window_sp = m_app.GetMainWindow();
6456
FormDelegateSP form_delegate_sp = FormDelegateSP(
6457
new ProcessAttachFormDelegate(m_debugger, main_window_sp));
6458
Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6459
WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6460
form_delegate_sp->GetName().c_str(), bounds, true);
6461
WindowDelegateSP window_delegate_sp =
6462
WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6463
form_window_sp->SetDelegate(window_delegate_sp);
6464
return MenuActionResult::Handled;
6465
}
6466
case eMenuID_ProcessLaunch: {
6467
WindowSP main_window_sp = m_app.GetMainWindow();
6468
FormDelegateSP form_delegate_sp = FormDelegateSP(
6469
new ProcessLaunchFormDelegate(m_debugger, main_window_sp));
6470
Rect bounds = main_window_sp->GetCenteredRect(80, 22);
6471
WindowSP form_window_sp = main_window_sp->CreateSubWindow(
6472
form_delegate_sp->GetName().c_str(), bounds, true);
6473
WindowDelegateSP window_delegate_sp =
6474
WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
6475
form_window_sp->SetDelegate(window_delegate_sp);
6476
return MenuActionResult::Handled;
6477
}
6478
6479
case eMenuID_ProcessContinue: {
6480
ExecutionContext exe_ctx =
6481
m_debugger.GetCommandInterpreter().GetExecutionContext();
6482
if (exe_ctx.HasProcessScope()) {
6483
Process *process = exe_ctx.GetProcessPtr();
6484
if (process && process->IsAlive() &&
6485
StateIsStoppedState(process->GetState(), true))
6486
process->Resume();
6487
}
6488
}
6489
return MenuActionResult::Handled;
6490
6491
case eMenuID_ProcessKill: {
6492
ExecutionContext exe_ctx =
6493
m_debugger.GetCommandInterpreter().GetExecutionContext();
6494
if (exe_ctx.HasProcessScope()) {
6495
Process *process = exe_ctx.GetProcessPtr();
6496
if (process && process->IsAlive())
6497
process->Destroy(false);
6498
}
6499
}
6500
return MenuActionResult::Handled;
6501
6502
case eMenuID_ProcessHalt: {
6503
ExecutionContext exe_ctx =
6504
m_debugger.GetCommandInterpreter().GetExecutionContext();
6505
if (exe_ctx.HasProcessScope()) {
6506
Process *process = exe_ctx.GetProcessPtr();
6507
if (process && process->IsAlive())
6508
process->Halt();
6509
}
6510
}
6511
return MenuActionResult::Handled;
6512
6513
case eMenuID_ProcessDetachResume:
6514
case eMenuID_ProcessDetachSuspended: {
6515
ExecutionContext exe_ctx =
6516
m_debugger.GetCommandInterpreter().GetExecutionContext();
6517
if (exe_ctx.HasProcessScope()) {
6518
Process *process = exe_ctx.GetProcessPtr();
6519
if (process && process->IsAlive())
6520
process->Detach(menu.GetIdentifier() ==
6521
eMenuID_ProcessDetachSuspended);
6522
}
6523
}
6524
return MenuActionResult::Handled;
6525
6526
case eMenuID_Process: {
6527
// Populate the menu with all of the threads if the process is stopped
6528
// when the Process menu gets selected and is about to display its
6529
// submenu.
6530
Menus &submenus = menu.GetSubmenus();
6531
ExecutionContext exe_ctx =
6532
m_debugger.GetCommandInterpreter().GetExecutionContext();
6533
Process *process = exe_ctx.GetProcessPtr();
6534
if (process && process->IsAlive() &&
6535
StateIsStoppedState(process->GetState(), true)) {
6536
if (submenus.size() == 7)
6537
menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
6538
else if (submenus.size() > 8)
6539
submenus.erase(submenus.begin() + 8, submenus.end());
6540
6541
ThreadList &threads = process->GetThreadList();
6542
std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
6543
size_t num_threads = threads.GetSize();
6544
for (size_t i = 0; i < num_threads; ++i) {
6545
ThreadSP thread_sp = threads.GetThreadAtIndex(i);
6546
char menu_char = '\0';
6547
if (i < 9)
6548
menu_char = '1' + i;
6549
StreamString thread_menu_title;
6550
thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
6551
const char *thread_name = thread_sp->GetName();
6552
if (thread_name && thread_name[0])
6553
thread_menu_title.Printf(" %s", thread_name);
6554
else {
6555
const char *queue_name = thread_sp->GetQueueName();
6556
if (queue_name && queue_name[0])
6557
thread_menu_title.Printf(" %s", queue_name);
6558
}
6559
menu.AddSubmenu(
6560
MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
6561
nullptr, menu_char, thread_sp->GetID())));
6562
}
6563
} else if (submenus.size() > 7) {
6564
// Remove the separator and any other thread submenu items that were
6565
// previously added
6566
submenus.erase(submenus.begin() + 7, submenus.end());
6567
}
6568
// Since we are adding and removing items we need to recalculate the
6569
// name lengths
6570
menu.RecalculateNameLengths();
6571
}
6572
return MenuActionResult::Handled;
6573
6574
case eMenuID_ViewVariables: {
6575
WindowSP main_window_sp = m_app.GetMainWindow();
6576
WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6577
WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6578
WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6579
const Rect source_bounds = source_window_sp->GetBounds();
6580
6581
if (variables_window_sp) {
6582
const Rect variables_bounds = variables_window_sp->GetBounds();
6583
6584
main_window_sp->RemoveSubWindow(variables_window_sp.get());
6585
6586
if (registers_window_sp) {
6587
// We have a registers window, so give all the area back to the
6588
// registers window
6589
Rect registers_bounds = variables_bounds;
6590
registers_bounds.size.width = source_bounds.size.width;
6591
registers_window_sp->SetBounds(registers_bounds);
6592
} else {
6593
// We have no registers window showing so give the bottom area back
6594
// to the source view
6595
source_window_sp->Resize(source_bounds.size.width,
6596
source_bounds.size.height +
6597
variables_bounds.size.height);
6598
}
6599
} else {
6600
Rect new_variables_rect;
6601
if (registers_window_sp) {
6602
// We have a registers window so split the area of the registers
6603
// window into two columns where the left hand side will be the
6604
// variables and the right hand side will be the registers
6605
const Rect variables_bounds = registers_window_sp->GetBounds();
6606
Rect new_registers_rect;
6607
variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
6608
new_registers_rect);
6609
registers_window_sp->SetBounds(new_registers_rect);
6610
} else {
6611
// No registers window, grab the bottom part of the source window
6612
Rect new_source_rect;
6613
source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6614
new_variables_rect);
6615
source_window_sp->SetBounds(new_source_rect);
6616
}
6617
WindowSP new_window_sp = main_window_sp->CreateSubWindow(
6618
"Variables", new_variables_rect, false);
6619
new_window_sp->SetDelegate(
6620
WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
6621
}
6622
touchwin(stdscr);
6623
}
6624
return MenuActionResult::Handled;
6625
6626
case eMenuID_ViewRegisters: {
6627
WindowSP main_window_sp = m_app.GetMainWindow();
6628
WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
6629
WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
6630
WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
6631
const Rect source_bounds = source_window_sp->GetBounds();
6632
6633
if (registers_window_sp) {
6634
if (variables_window_sp) {
6635
const Rect variables_bounds = variables_window_sp->GetBounds();
6636
6637
// We have a variables window, so give all the area back to the
6638
// variables window
6639
variables_window_sp->Resize(variables_bounds.size.width +
6640
registers_window_sp->GetWidth(),
6641
variables_bounds.size.height);
6642
} else {
6643
// We have no variables window showing so give the bottom area back
6644
// to the source view
6645
source_window_sp->Resize(source_bounds.size.width,
6646
source_bounds.size.height +
6647
registers_window_sp->GetHeight());
6648
}
6649
main_window_sp->RemoveSubWindow(registers_window_sp.get());
6650
} else {
6651
Rect new_regs_rect;
6652
if (variables_window_sp) {
6653
// We have a variables window, split it into two columns where the
6654
// left hand side will be the variables and the right hand side will
6655
// be the registers
6656
const Rect variables_bounds = variables_window_sp->GetBounds();
6657
Rect new_vars_rect;
6658
variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
6659
new_regs_rect);
6660
variables_window_sp->SetBounds(new_vars_rect);
6661
} else {
6662
// No variables window, grab the bottom part of the source window
6663
Rect new_source_rect;
6664
source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
6665
new_regs_rect);
6666
source_window_sp->SetBounds(new_source_rect);
6667
}
6668
WindowSP new_window_sp =
6669
main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
6670
new_window_sp->SetDelegate(
6671
WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
6672
}
6673
touchwin(stdscr);
6674
}
6675
return MenuActionResult::Handled;
6676
6677
case eMenuID_ViewBreakpoints: {
6678
WindowSP main_window_sp = m_app.GetMainWindow();
6679
WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads");
6680
WindowSP breakpoints_window_sp =
6681
main_window_sp->FindSubWindow("Breakpoints");
6682
const Rect threads_bounds = threads_window_sp->GetBounds();
6683
6684
// If a breakpoints window already exists, remove it and give the area
6685
// it used to occupy to the threads window. If it doesn't exist, split
6686
// the threads window horizontally into two windows where the top window
6687
// is the threads window and the bottom window is a newly added
6688
// breakpoints window.
6689
if (breakpoints_window_sp) {
6690
threads_window_sp->Resize(threads_bounds.size.width,
6691
threads_bounds.size.height +
6692
breakpoints_window_sp->GetHeight());
6693
main_window_sp->RemoveSubWindow(breakpoints_window_sp.get());
6694
} else {
6695
Rect new_threads_bounds, breakpoints_bounds;
6696
threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds,
6697
breakpoints_bounds);
6698
threads_window_sp->SetBounds(new_threads_bounds);
6699
breakpoints_window_sp = main_window_sp->CreateSubWindow(
6700
"Breakpoints", breakpoints_bounds, false);
6701
TreeDelegateSP breakpoints_delegate_sp(
6702
new BreakpointsTreeDelegate(m_debugger));
6703
breakpoints_window_sp->SetDelegate(WindowDelegateSP(
6704
new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp)));
6705
}
6706
touchwin(stdscr);
6707
return MenuActionResult::Handled;
6708
}
6709
6710
case eMenuID_HelpGUIHelp:
6711
m_app.GetMainWindow()->CreateHelpSubwindow();
6712
return MenuActionResult::Handled;
6713
6714
default:
6715
break;
6716
}
6717
6718
return MenuActionResult::NotHandled;
6719
}
6720
6721
protected:
6722
Application &m_app;
6723
Debugger &m_debugger;
6724
};
6725
6726
class StatusBarWindowDelegate : public WindowDelegate {
6727
public:
6728
StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
6729
FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
6730
}
6731
6732
~StatusBarWindowDelegate() override = default;
6733
6734
bool WindowDelegateDraw(Window &window, bool force) override {
6735
ExecutionContext exe_ctx =
6736
m_debugger.GetCommandInterpreter().GetExecutionContext();
6737
Process *process = exe_ctx.GetProcessPtr();
6738
Thread *thread = exe_ctx.GetThreadPtr();
6739
StackFrame *frame = exe_ctx.GetFramePtr();
6740
window.Erase();
6741
window.SetBackground(BlackOnWhite);
6742
window.MoveCursor(0, 0);
6743
if (process) {
6744
const StateType state = process->GetState();
6745
window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
6746
StateAsCString(state));
6747
6748
if (StateIsStoppedState(state, true)) {
6749
StreamString strm;
6750
if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
6751
nullptr, nullptr, false, false)) {
6752
window.MoveCursor(40, 0);
6753
window.PutCStringTruncated(1, strm.GetString().str().c_str());
6754
}
6755
6756
window.MoveCursor(60, 0);
6757
if (frame)
6758
window.Printf("Frame: %3u PC = 0x%16.16" PRIx64,
6759
frame->GetFrameIndex(),
6760
frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
6761
exe_ctx.GetTargetPtr()));
6762
} else if (state == eStateExited) {
6763
const char *exit_desc = process->GetExitDescription();
6764
const int exit_status = process->GetExitStatus();
6765
if (exit_desc && exit_desc[0])
6766
window.Printf(" with status = %i (%s)", exit_status, exit_desc);
6767
else
6768
window.Printf(" with status = %i", exit_status);
6769
}
6770
}
6771
return true;
6772
}
6773
6774
protected:
6775
Debugger &m_debugger;
6776
FormatEntity::Entry m_format;
6777
};
6778
6779
class SourceFileWindowDelegate : public WindowDelegate {
6780
public:
6781
SourceFileWindowDelegate(Debugger &debugger)
6782
: WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
6783
m_disassembly_sp(), m_disassembly_range(), m_title() {}
6784
6785
~SourceFileWindowDelegate() override = default;
6786
6787
void Update(const SymbolContext &sc) { m_sc = sc; }
6788
6789
uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
6790
6791
const char *WindowDelegateGetHelpText() override {
6792
return "Source/Disassembly window keyboard shortcuts:";
6793
}
6794
6795
KeyHelp *WindowDelegateGetKeyHelp() override {
6796
static curses::KeyHelp g_source_view_key_help[] = {
6797
{KEY_RETURN, "Run to selected line with one shot breakpoint"},
6798
{KEY_UP, "Select previous source line"},
6799
{KEY_DOWN, "Select next source line"},
6800
{KEY_LEFT, "Scroll to the left"},
6801
{KEY_RIGHT, "Scroll to the right"},
6802
{KEY_PPAGE, "Page up"},
6803
{KEY_NPAGE, "Page down"},
6804
{'b', "Set breakpoint on selected source/disassembly line"},
6805
{'c', "Continue process"},
6806
{'D', "Detach with process suspended"},
6807
{'h', "Show help dialog"},
6808
{'n', "Step over (source line)"},
6809
{'N', "Step over (single instruction)"},
6810
{'f', "Step out (finish)"},
6811
{'s', "Step in (source line)"},
6812
{'S', "Step in (single instruction)"},
6813
{'u', "Frame up"},
6814
{'d', "Frame down"},
6815
{',', "Page up"},
6816
{'.', "Page down"},
6817
{'\0', nullptr}};
6818
return g_source_view_key_help;
6819
}
6820
6821
bool WindowDelegateDraw(Window &window, bool force) override {
6822
ExecutionContext exe_ctx =
6823
m_debugger.GetCommandInterpreter().GetExecutionContext();
6824
Process *process = exe_ctx.GetProcessPtr();
6825
Thread *thread = nullptr;
6826
6827
bool update_location = false;
6828
if (process) {
6829
StateType state = process->GetState();
6830
if (StateIsStoppedState(state, true)) {
6831
// We are stopped, so it is ok to
6832
update_location = true;
6833
}
6834
}
6835
6836
m_min_x = 1;
6837
m_min_y = 2;
6838
m_max_x = window.GetMaxX() - 1;
6839
m_max_y = window.GetMaxY() - 1;
6840
6841
const uint32_t num_visible_lines = NumVisibleLines();
6842
StackFrameSP frame_sp;
6843
bool set_selected_line_to_pc = false;
6844
6845
if (update_location) {
6846
const bool process_alive = process->IsAlive();
6847
bool thread_changed = false;
6848
if (process_alive) {
6849
thread = exe_ctx.GetThreadPtr();
6850
if (thread) {
6851
frame_sp = thread->GetSelectedFrame(SelectMostRelevantFrame);
6852
auto tid = thread->GetID();
6853
thread_changed = tid != m_tid;
6854
m_tid = tid;
6855
} else {
6856
if (m_tid != LLDB_INVALID_THREAD_ID) {
6857
thread_changed = true;
6858
m_tid = LLDB_INVALID_THREAD_ID;
6859
}
6860
}
6861
}
6862
const uint32_t stop_id = process ? process->GetStopID() : 0;
6863
const bool stop_id_changed = stop_id != m_stop_id;
6864
bool frame_changed = false;
6865
m_stop_id = stop_id;
6866
m_title.Clear();
6867
if (frame_sp) {
6868
m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
6869
if (m_sc.module_sp) {
6870
m_title.Printf(
6871
"%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
6872
ConstString func_name = m_sc.GetFunctionName();
6873
if (func_name)
6874
m_title.Printf("`%s", func_name.GetCString());
6875
}
6876
const uint32_t frame_idx = frame_sp->GetFrameIndex();
6877
frame_changed = frame_idx != m_frame_idx;
6878
m_frame_idx = frame_idx;
6879
} else {
6880
m_sc.Clear(true);
6881
frame_changed = m_frame_idx != UINT32_MAX;
6882
m_frame_idx = UINT32_MAX;
6883
}
6884
6885
const bool context_changed =
6886
thread_changed || frame_changed || stop_id_changed;
6887
6888
if (process_alive) {
6889
if (m_sc.line_entry.IsValid()) {
6890
m_pc_line = m_sc.line_entry.line;
6891
if (m_pc_line != UINT32_MAX)
6892
--m_pc_line; // Convert to zero based line number...
6893
// Update the selected line if the stop ID changed...
6894
if (context_changed)
6895
m_selected_line = m_pc_line;
6896
6897
if (m_file_sp &&
6898
m_file_sp->GetFileSpec() == m_sc.line_entry.GetFile()) {
6899
// Same file, nothing to do, we should either have the lines or
6900
// not (source file missing)
6901
if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
6902
if (m_selected_line >= m_first_visible_line + num_visible_lines)
6903
m_first_visible_line = m_selected_line - 10;
6904
} else {
6905
if (m_selected_line > 10)
6906
m_first_visible_line = m_selected_line - 10;
6907
else
6908
m_first_visible_line = 0;
6909
}
6910
} else {
6911
// File changed, set selected line to the line with the PC
6912
m_selected_line = m_pc_line;
6913
m_file_sp = m_debugger.GetSourceManager().GetFile(
6914
m_sc.line_entry.GetFile());
6915
if (m_file_sp) {
6916
const size_t num_lines = m_file_sp->GetNumLines();
6917
m_line_width = 1;
6918
for (size_t n = num_lines; n >= 10; n = n / 10)
6919
++m_line_width;
6920
6921
if (num_lines < num_visible_lines ||
6922
m_selected_line < num_visible_lines)
6923
m_first_visible_line = 0;
6924
else
6925
m_first_visible_line = m_selected_line - 10;
6926
}
6927
}
6928
} else {
6929
m_file_sp.reset();
6930
}
6931
6932
if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
6933
// Show disassembly
6934
bool prefer_file_cache = false;
6935
if (m_sc.function) {
6936
if (m_disassembly_scope != m_sc.function) {
6937
m_disassembly_scope = m_sc.function;
6938
m_disassembly_sp = m_sc.function->GetInstructions(
6939
exe_ctx, nullptr, !prefer_file_cache);
6940
if (m_disassembly_sp) {
6941
set_selected_line_to_pc = true;
6942
m_disassembly_range = m_sc.function->GetAddressRange();
6943
} else {
6944
m_disassembly_range.Clear();
6945
}
6946
} else {
6947
set_selected_line_to_pc = context_changed;
6948
}
6949
} else if (m_sc.symbol) {
6950
if (m_disassembly_scope != m_sc.symbol) {
6951
m_disassembly_scope = m_sc.symbol;
6952
m_disassembly_sp = m_sc.symbol->GetInstructions(
6953
exe_ctx, nullptr, prefer_file_cache);
6954
if (m_disassembly_sp) {
6955
set_selected_line_to_pc = true;
6956
m_disassembly_range.GetBaseAddress() =
6957
m_sc.symbol->GetAddress();
6958
m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
6959
} else {
6960
m_disassembly_range.Clear();
6961
}
6962
} else {
6963
set_selected_line_to_pc = context_changed;
6964
}
6965
}
6966
}
6967
} else {
6968
m_pc_line = UINT32_MAX;
6969
}
6970
}
6971
6972
const int window_width = window.GetWidth();
6973
window.Erase();
6974
window.DrawTitleBox("Sources");
6975
if (!m_title.GetString().empty()) {
6976
window.AttributeOn(A_REVERSE);
6977
window.MoveCursor(1, 1);
6978
window.PutChar(' ');
6979
window.PutCStringTruncated(1, m_title.GetString().str().c_str());
6980
int x = window.GetCursorX();
6981
if (x < window_width - 1) {
6982
window.Printf("%*s", window_width - x - 1, "");
6983
}
6984
window.AttributeOff(A_REVERSE);
6985
}
6986
6987
Target *target = exe_ctx.GetTargetPtr();
6988
const size_t num_source_lines = GetNumSourceLines();
6989
if (num_source_lines > 0) {
6990
// Display source
6991
BreakpointLines bp_lines;
6992
if (target) {
6993
BreakpointList &bp_list = target->GetBreakpointList();
6994
const size_t num_bps = bp_list.GetSize();
6995
for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
6996
BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
6997
const size_t num_bps_locs = bp_sp->GetNumLocations();
6998
for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
6999
BreakpointLocationSP bp_loc_sp =
7000
bp_sp->GetLocationAtIndex(bp_loc_idx);
7001
LineEntry bp_loc_line_entry;
7002
if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7003
bp_loc_line_entry)) {
7004
if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile()) {
7005
bp_lines.insert(bp_loc_line_entry.line);
7006
}
7007
}
7008
}
7009
}
7010
}
7011
7012
for (size_t i = 0; i < num_visible_lines; ++i) {
7013
const uint32_t curr_line = m_first_visible_line + i;
7014
if (curr_line < num_source_lines) {
7015
const int line_y = m_min_y + i;
7016
window.MoveCursor(1, line_y);
7017
const bool is_pc_line = curr_line == m_pc_line;
7018
const bool line_is_selected = m_selected_line == curr_line;
7019
// Highlight the line as the PC line first (done by passing
7020
// argument to OutputColoredStringTruncated()), then if the selected
7021
// line isn't the same as the PC line, highlight it differently.
7022
attr_t highlight_attr = 0;
7023
attr_t bp_attr = 0;
7024
if (line_is_selected && !is_pc_line)
7025
highlight_attr = A_REVERSE;
7026
7027
if (bp_lines.find(curr_line + 1) != bp_lines.end())
7028
bp_attr = COLOR_PAIR(BlackOnWhite);
7029
7030
if (bp_attr)
7031
window.AttributeOn(bp_attr);
7032
7033
window.Printf(" %*u ", m_line_width, curr_line + 1);
7034
7035
if (bp_attr)
7036
window.AttributeOff(bp_attr);
7037
7038
window.PutChar(ACS_VLINE);
7039
// Mark the line with the PC with a diamond
7040
if (is_pc_line)
7041
window.PutChar(ACS_DIAMOND);
7042
else
7043
window.PutChar(' ');
7044
7045
if (highlight_attr)
7046
window.AttributeOn(highlight_attr);
7047
7048
StreamString lineStream;
7049
7050
std::optional<size_t> column;
7051
if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column)
7052
column = m_sc.line_entry.column - 1;
7053
m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0,
7054
&lineStream);
7055
StringRef line = lineStream.GetString();
7056
if (line.ends_with("\n"))
7057
line = line.drop_back();
7058
bool wasWritten = window.OutputColoredStringTruncated(
7059
1, line, m_first_visible_column, is_pc_line);
7060
if (!wasWritten && (line_is_selected || is_pc_line)) {
7061
// Draw an empty space to show the selected/PC line if empty,
7062
// or draw '<' if nothing is visible because of scrolling too much
7063
// to the right.
7064
window.PutCStringTruncated(
7065
1, line.empty() && m_first_visible_column == 0 ? " " : "<");
7066
}
7067
7068
if (is_pc_line && frame_sp &&
7069
frame_sp->GetConcreteFrameIndex() == 0) {
7070
StopInfoSP stop_info_sp;
7071
if (thread)
7072
stop_info_sp = thread->GetStopInfo();
7073
if (stop_info_sp) {
7074
const char *stop_description = stop_info_sp->GetDescription();
7075
if (stop_description && stop_description[0]) {
7076
size_t stop_description_len = strlen(stop_description);
7077
int desc_x = window_width - stop_description_len - 16;
7078
if (desc_x - window.GetCursorX() > 0)
7079
window.Printf("%*s", desc_x - window.GetCursorX(), "");
7080
window.MoveCursor(window_width - stop_description_len - 16,
7081
line_y);
7082
const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue);
7083
window.AttributeOn(stop_reason_attr);
7084
window.PrintfTruncated(1, " <<< Thread %u: %s ",
7085
thread->GetIndexID(), stop_description);
7086
window.AttributeOff(stop_reason_attr);
7087
}
7088
} else {
7089
window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7090
}
7091
}
7092
if (highlight_attr)
7093
window.AttributeOff(highlight_attr);
7094
} else {
7095
break;
7096
}
7097
}
7098
} else {
7099
size_t num_disassembly_lines = GetNumDisassemblyLines();
7100
if (num_disassembly_lines > 0) {
7101
// Display disassembly
7102
BreakpointAddrs bp_file_addrs;
7103
Target *target = exe_ctx.GetTargetPtr();
7104
if (target) {
7105
BreakpointList &bp_list = target->GetBreakpointList();
7106
const size_t num_bps = bp_list.GetSize();
7107
for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7108
BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7109
const size_t num_bps_locs = bp_sp->GetNumLocations();
7110
for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
7111
++bp_loc_idx) {
7112
BreakpointLocationSP bp_loc_sp =
7113
bp_sp->GetLocationAtIndex(bp_loc_idx);
7114
LineEntry bp_loc_line_entry;
7115
const lldb::addr_t file_addr =
7116
bp_loc_sp->GetAddress().GetFileAddress();
7117
if (file_addr != LLDB_INVALID_ADDRESS) {
7118
if (m_disassembly_range.ContainsFileAddress(file_addr))
7119
bp_file_addrs.insert(file_addr);
7120
}
7121
}
7122
}
7123
}
7124
7125
const attr_t selected_highlight_attr = A_REVERSE;
7126
const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue);
7127
7128
StreamString strm;
7129
7130
InstructionList &insts = m_disassembly_sp->GetInstructionList();
7131
Address pc_address;
7132
7133
if (frame_sp)
7134
pc_address = frame_sp->GetFrameCodeAddress();
7135
const uint32_t pc_idx =
7136
pc_address.IsValid()
7137
? insts.GetIndexOfInstructionAtAddress(pc_address)
7138
: UINT32_MAX;
7139
if (set_selected_line_to_pc) {
7140
m_selected_line = pc_idx;
7141
}
7142
7143
const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
7144
if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
7145
m_first_visible_line = 0;
7146
7147
if (pc_idx < num_disassembly_lines) {
7148
if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
7149
pc_idx >= m_first_visible_line + num_visible_lines)
7150
m_first_visible_line = pc_idx - non_visible_pc_offset;
7151
}
7152
7153
for (size_t i = 0; i < num_visible_lines; ++i) {
7154
const uint32_t inst_idx = m_first_visible_line + i;
7155
Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
7156
if (!inst)
7157
break;
7158
7159
const int line_y = m_min_y + i;
7160
window.MoveCursor(1, line_y);
7161
const bool is_pc_line = frame_sp && inst_idx == pc_idx;
7162
const bool line_is_selected = m_selected_line == inst_idx;
7163
// Highlight the line as the PC line first, then if the selected
7164
// line isn't the same as the PC line, highlight it differently
7165
attr_t highlight_attr = 0;
7166
attr_t bp_attr = 0;
7167
if (is_pc_line)
7168
highlight_attr = pc_highlight_attr;
7169
else if (line_is_selected)
7170
highlight_attr = selected_highlight_attr;
7171
7172
if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
7173
bp_file_addrs.end())
7174
bp_attr = COLOR_PAIR(BlackOnWhite);
7175
7176
if (bp_attr)
7177
window.AttributeOn(bp_attr);
7178
7179
window.Printf(" 0x%16.16llx ",
7180
static_cast<unsigned long long>(
7181
inst->GetAddress().GetLoadAddress(target)));
7182
7183
if (bp_attr)
7184
window.AttributeOff(bp_attr);
7185
7186
window.PutChar(ACS_VLINE);
7187
// Mark the line with the PC with a diamond
7188
if (is_pc_line)
7189
window.PutChar(ACS_DIAMOND);
7190
else
7191
window.PutChar(' ');
7192
7193
if (highlight_attr)
7194
window.AttributeOn(highlight_attr);
7195
7196
const char *mnemonic = inst->GetMnemonic(&exe_ctx);
7197
const char *operands = inst->GetOperands(&exe_ctx);
7198
const char *comment = inst->GetComment(&exe_ctx);
7199
7200
if (mnemonic != nullptr && mnemonic[0] == '\0')
7201
mnemonic = nullptr;
7202
if (operands != nullptr && operands[0] == '\0')
7203
operands = nullptr;
7204
if (comment != nullptr && comment[0] == '\0')
7205
comment = nullptr;
7206
7207
strm.Clear();
7208
7209
if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
7210
strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
7211
else if (mnemonic != nullptr && operands != nullptr)
7212
strm.Printf("%-8s %s", mnemonic, operands);
7213
else if (mnemonic != nullptr)
7214
strm.Printf("%s", mnemonic);
7215
7216
int right_pad = 1;
7217
window.PutCStringTruncated(
7218
right_pad,
7219
strm.GetString().substr(m_first_visible_column).data());
7220
7221
if (is_pc_line && frame_sp &&
7222
frame_sp->GetConcreteFrameIndex() == 0) {
7223
StopInfoSP stop_info_sp;
7224
if (thread)
7225
stop_info_sp = thread->GetStopInfo();
7226
if (stop_info_sp) {
7227
const char *stop_description = stop_info_sp->GetDescription();
7228
if (stop_description && stop_description[0]) {
7229
size_t stop_description_len = strlen(stop_description);
7230
int desc_x = window_width - stop_description_len - 16;
7231
if (desc_x - window.GetCursorX() > 0)
7232
window.Printf("%*s", desc_x - window.GetCursorX(), "");
7233
window.MoveCursor(window_width - stop_description_len - 15,
7234
line_y);
7235
if (thread)
7236
window.PrintfTruncated(1, "<<< Thread %u: %s ",
7237
thread->GetIndexID(),
7238
stop_description);
7239
}
7240
} else {
7241
window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
7242
}
7243
}
7244
if (highlight_attr)
7245
window.AttributeOff(highlight_attr);
7246
}
7247
}
7248
}
7249
return true; // Drawing handled
7250
}
7251
7252
size_t GetNumLines() {
7253
size_t num_lines = GetNumSourceLines();
7254
if (num_lines == 0)
7255
num_lines = GetNumDisassemblyLines();
7256
return num_lines;
7257
}
7258
7259
size_t GetNumSourceLines() const {
7260
if (m_file_sp)
7261
return m_file_sp->GetNumLines();
7262
return 0;
7263
}
7264
7265
size_t GetNumDisassemblyLines() const {
7266
if (m_disassembly_sp)
7267
return m_disassembly_sp->GetInstructionList().GetSize();
7268
return 0;
7269
}
7270
7271
HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
7272
const uint32_t num_visible_lines = NumVisibleLines();
7273
const size_t num_lines = GetNumLines();
7274
7275
switch (c) {
7276
case ',':
7277
case KEY_PPAGE:
7278
// Page up key
7279
if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
7280
m_first_visible_line -= num_visible_lines;
7281
else
7282
m_first_visible_line = 0;
7283
m_selected_line = m_first_visible_line;
7284
return eKeyHandled;
7285
7286
case '.':
7287
case KEY_NPAGE:
7288
// Page down key
7289
{
7290
if (m_first_visible_line + num_visible_lines < num_lines)
7291
m_first_visible_line += num_visible_lines;
7292
else if (num_lines < num_visible_lines)
7293
m_first_visible_line = 0;
7294
else
7295
m_first_visible_line = num_lines - num_visible_lines;
7296
m_selected_line = m_first_visible_line;
7297
}
7298
return eKeyHandled;
7299
7300
case KEY_UP:
7301
if (m_selected_line > 0) {
7302
m_selected_line--;
7303
if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
7304
m_first_visible_line = m_selected_line;
7305
}
7306
return eKeyHandled;
7307
7308
case KEY_DOWN:
7309
if (m_selected_line + 1 < num_lines) {
7310
m_selected_line++;
7311
if (m_first_visible_line + num_visible_lines < m_selected_line)
7312
m_first_visible_line++;
7313
}
7314
return eKeyHandled;
7315
7316
case KEY_LEFT:
7317
if (m_first_visible_column > 0)
7318
--m_first_visible_column;
7319
return eKeyHandled;
7320
7321
case KEY_RIGHT:
7322
++m_first_visible_column;
7323
return eKeyHandled;
7324
7325
case '\r':
7326
case '\n':
7327
case KEY_ENTER:
7328
// Set a breakpoint and run to the line using a one shot breakpoint
7329
if (GetNumSourceLines() > 0) {
7330
ExecutionContext exe_ctx =
7331
m_debugger.GetCommandInterpreter().GetExecutionContext();
7332
if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
7333
BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7334
nullptr, // Don't limit the breakpoint to certain modules
7335
m_file_sp->GetFileSpec(), // Source file
7336
m_selected_line +
7337
1, // Source line number (m_selected_line is zero based)
7338
0, // Unspecified column.
7339
0, // No offset
7340
eLazyBoolCalculate, // Check inlines using global setting
7341
eLazyBoolCalculate, // Skip prologue using global setting,
7342
false, // internal
7343
false, // request_hardware
7344
eLazyBoolCalculate); // move_to_nearest_code
7345
// Make breakpoint one shot
7346
bp_sp->GetOptions().SetOneShot(true);
7347
exe_ctx.GetProcessRef().Resume();
7348
}
7349
} else if (m_selected_line < GetNumDisassemblyLines()) {
7350
const Instruction *inst = m_disassembly_sp->GetInstructionList()
7351
.GetInstructionAtIndex(m_selected_line)
7352
.get();
7353
ExecutionContext exe_ctx =
7354
m_debugger.GetCommandInterpreter().GetExecutionContext();
7355
if (exe_ctx.HasTargetScope()) {
7356
Address addr = inst->GetAddress();
7357
BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7358
addr, // lldb_private::Address
7359
false, // internal
7360
false); // request_hardware
7361
// Make breakpoint one shot
7362
bp_sp->GetOptions().SetOneShot(true);
7363
exe_ctx.GetProcessRef().Resume();
7364
}
7365
}
7366
return eKeyHandled;
7367
7368
case 'b': // 'b' == toggle breakpoint on currently selected line
7369
ToggleBreakpointOnSelectedLine();
7370
return eKeyHandled;
7371
7372
case 'D': // 'D' == detach and keep stopped
7373
{
7374
ExecutionContext exe_ctx =
7375
m_debugger.GetCommandInterpreter().GetExecutionContext();
7376
if (exe_ctx.HasProcessScope())
7377
exe_ctx.GetProcessRef().Detach(true);
7378
}
7379
return eKeyHandled;
7380
7381
case 'c':
7382
// 'c' == continue
7383
{
7384
ExecutionContext exe_ctx =
7385
m_debugger.GetCommandInterpreter().GetExecutionContext();
7386
if (exe_ctx.HasProcessScope())
7387
exe_ctx.GetProcessRef().Resume();
7388
}
7389
return eKeyHandled;
7390
7391
case 'f':
7392
// 'f' == step out (finish)
7393
{
7394
ExecutionContext exe_ctx =
7395
m_debugger.GetCommandInterpreter().GetExecutionContext();
7396
if (exe_ctx.HasThreadScope() &&
7397
StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7398
Thread *thread = exe_ctx.GetThreadPtr();
7399
uint32_t frame_idx =
7400
thread->GetSelectedFrameIndex(SelectMostRelevantFrame);
7401
exe_ctx.GetThreadRef().StepOut(frame_idx);
7402
}
7403
}
7404
return eKeyHandled;
7405
7406
case 'n': // 'n' == step over
7407
case 'N': // 'N' == step over instruction
7408
{
7409
ExecutionContext exe_ctx =
7410
m_debugger.GetCommandInterpreter().GetExecutionContext();
7411
if (exe_ctx.HasThreadScope() &&
7412
StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7413
bool source_step = (c == 'n');
7414
exe_ctx.GetThreadRef().StepOver(source_step);
7415
}
7416
}
7417
return eKeyHandled;
7418
7419
case 's': // 's' == step into
7420
case 'S': // 'S' == step into instruction
7421
{
7422
ExecutionContext exe_ctx =
7423
m_debugger.GetCommandInterpreter().GetExecutionContext();
7424
if (exe_ctx.HasThreadScope() &&
7425
StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
7426
bool source_step = (c == 's');
7427
exe_ctx.GetThreadRef().StepIn(source_step);
7428
}
7429
}
7430
return eKeyHandled;
7431
7432
case 'u': // 'u' == frame up
7433
case 'd': // 'd' == frame down
7434
{
7435
ExecutionContext exe_ctx =
7436
m_debugger.GetCommandInterpreter().GetExecutionContext();
7437
if (exe_ctx.HasThreadScope()) {
7438
Thread *thread = exe_ctx.GetThreadPtr();
7439
uint32_t frame_idx =
7440
thread->GetSelectedFrameIndex(SelectMostRelevantFrame);
7441
if (frame_idx == UINT32_MAX)
7442
frame_idx = 0;
7443
if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount())
7444
++frame_idx;
7445
else if (c == 'd' && frame_idx > 0)
7446
--frame_idx;
7447
if (thread->SetSelectedFrameByIndex(frame_idx, true))
7448
exe_ctx.SetFrameSP(thread->GetSelectedFrame(SelectMostRelevantFrame));
7449
}
7450
}
7451
return eKeyHandled;
7452
7453
case 'h':
7454
window.CreateHelpSubwindow();
7455
return eKeyHandled;
7456
7457
default:
7458
break;
7459
}
7460
return eKeyNotHandled;
7461
}
7462
7463
void ToggleBreakpointOnSelectedLine() {
7464
ExecutionContext exe_ctx =
7465
m_debugger.GetCommandInterpreter().GetExecutionContext();
7466
if (!exe_ctx.HasTargetScope())
7467
return;
7468
if (GetNumSourceLines() > 0) {
7469
// Source file breakpoint.
7470
BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7471
const size_t num_bps = bp_list.GetSize();
7472
for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7473
BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7474
const size_t num_bps_locs = bp_sp->GetNumLocations();
7475
for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7476
BreakpointLocationSP bp_loc_sp =
7477
bp_sp->GetLocationAtIndex(bp_loc_idx);
7478
LineEntry bp_loc_line_entry;
7479
if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
7480
bp_loc_line_entry)) {
7481
if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile() &&
7482
m_selected_line + 1 == bp_loc_line_entry.line) {
7483
bool removed =
7484
exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7485
assert(removed);
7486
UNUSED_IF_ASSERT_DISABLED(removed);
7487
return; // Existing breakpoint removed.
7488
}
7489
}
7490
}
7491
}
7492
// No breakpoint found on the location, add it.
7493
BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
7494
nullptr, // Don't limit the breakpoint to certain modules
7495
m_file_sp->GetFileSpec(), // Source file
7496
m_selected_line +
7497
1, // Source line number (m_selected_line is zero based)
7498
0, // No column specified.
7499
0, // No offset
7500
eLazyBoolCalculate, // Check inlines using global setting
7501
eLazyBoolCalculate, // Skip prologue using global setting,
7502
false, // internal
7503
false, // request_hardware
7504
eLazyBoolCalculate); // move_to_nearest_code
7505
} else {
7506
// Disassembly breakpoint.
7507
assert(GetNumDisassemblyLines() > 0);
7508
assert(m_selected_line < GetNumDisassemblyLines());
7509
const Instruction *inst = m_disassembly_sp->GetInstructionList()
7510
.GetInstructionAtIndex(m_selected_line)
7511
.get();
7512
Address addr = inst->GetAddress();
7513
// Try to find it.
7514
BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList();
7515
const size_t num_bps = bp_list.GetSize();
7516
for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
7517
BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
7518
const size_t num_bps_locs = bp_sp->GetNumLocations();
7519
for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
7520
BreakpointLocationSP bp_loc_sp =
7521
bp_sp->GetLocationAtIndex(bp_loc_idx);
7522
LineEntry bp_loc_line_entry;
7523
const lldb::addr_t file_addr =
7524
bp_loc_sp->GetAddress().GetFileAddress();
7525
if (file_addr == addr.GetFileAddress()) {
7526
bool removed =
7527
exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID());
7528
assert(removed);
7529
UNUSED_IF_ASSERT_DISABLED(removed);
7530
return; // Existing breakpoint removed.
7531
}
7532
}
7533
}
7534
// No breakpoint found on the address, add it.
7535
BreakpointSP bp_sp =
7536
exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address
7537
false, // internal
7538
false); // request_hardware
7539
}
7540
}
7541
7542
protected:
7543
typedef std::set<uint32_t> BreakpointLines;
7544
typedef std::set<lldb::addr_t> BreakpointAddrs;
7545
7546
Debugger &m_debugger;
7547
SymbolContext m_sc;
7548
SourceManager::FileSP m_file_sp;
7549
SymbolContextScope *m_disassembly_scope = nullptr;
7550
lldb::DisassemblerSP m_disassembly_sp;
7551
AddressRange m_disassembly_range;
7552
StreamString m_title;
7553
lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID;
7554
int m_line_width = 4;
7555
uint32_t m_selected_line = 0; // The selected line
7556
uint32_t m_pc_line = 0; // The line with the PC
7557
uint32_t m_stop_id = 0;
7558
uint32_t m_frame_idx = UINT32_MAX;
7559
int m_first_visible_line = 0;
7560
int m_first_visible_column = 0;
7561
int m_min_x = 0;
7562
int m_min_y = 0;
7563
int m_max_x = 0;
7564
int m_max_y = 0;
7565
};
7566
7567
DisplayOptions ValueObjectListDelegate::g_options = {true};
7568
7569
IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
7570
: IOHandler(debugger, IOHandler::Type::Curses) {}
7571
7572
void IOHandlerCursesGUI::Activate() {
7573
IOHandler::Activate();
7574
if (!m_app_ap) {
7575
m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE());
7576
7577
// This is both a window and a menu delegate
7578
std::shared_ptr<ApplicationDelegate> app_delegate_sp(
7579
new ApplicationDelegate(*m_app_ap, m_debugger));
7580
7581
MenuDelegateSP app_menu_delegate_sp =
7582
std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
7583
MenuSP lldb_menu_sp(
7584
new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
7585
MenuSP exit_menuitem_sp(
7586
new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
7587
exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
7588
lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
7589
"About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
7590
lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7591
lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
7592
7593
MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
7594
ApplicationDelegate::eMenuID_Target));
7595
target_menu_sp->AddSubmenu(MenuSP(new Menu(
7596
"Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
7597
target_menu_sp->AddSubmenu(MenuSP(new Menu(
7598
"Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
7599
7600
MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
7601
ApplicationDelegate::eMenuID_Process));
7602
process_menu_sp->AddSubmenu(MenuSP(new Menu(
7603
"Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
7604
process_menu_sp->AddSubmenu(
7605
MenuSP(new Menu("Detach and resume", nullptr, 'd',
7606
ApplicationDelegate::eMenuID_ProcessDetachResume)));
7607
process_menu_sp->AddSubmenu(
7608
MenuSP(new Menu("Detach suspended", nullptr, 's',
7609
ApplicationDelegate::eMenuID_ProcessDetachSuspended)));
7610
process_menu_sp->AddSubmenu(MenuSP(new Menu(
7611
"Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
7612
process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
7613
process_menu_sp->AddSubmenu(
7614
MenuSP(new Menu("Continue", nullptr, 'c',
7615
ApplicationDelegate::eMenuID_ProcessContinue)));
7616
process_menu_sp->AddSubmenu(MenuSP(new Menu(
7617
"Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
7618
process_menu_sp->AddSubmenu(MenuSP(new Menu(
7619
"Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
7620
7621
MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
7622
ApplicationDelegate::eMenuID_Thread));
7623
thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7624
"Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
7625
thread_menu_sp->AddSubmenu(
7626
MenuSP(new Menu("Step Over", nullptr, 'v',
7627
ApplicationDelegate::eMenuID_ThreadStepOver)));
7628
thread_menu_sp->AddSubmenu(MenuSP(new Menu(
7629
"Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
7630
7631
MenuSP view_menu_sp(
7632
new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
7633
view_menu_sp->AddSubmenu(
7634
MenuSP(new Menu("Backtrace", nullptr, 't',
7635
ApplicationDelegate::eMenuID_ViewBacktrace)));
7636
view_menu_sp->AddSubmenu(
7637
MenuSP(new Menu("Registers", nullptr, 'r',
7638
ApplicationDelegate::eMenuID_ViewRegisters)));
7639
view_menu_sp->AddSubmenu(MenuSP(new Menu(
7640
"Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
7641
view_menu_sp->AddSubmenu(
7642
MenuSP(new Menu("Variables", nullptr, 'v',
7643
ApplicationDelegate::eMenuID_ViewVariables)));
7644
view_menu_sp->AddSubmenu(
7645
MenuSP(new Menu("Breakpoints", nullptr, 'b',
7646
ApplicationDelegate::eMenuID_ViewBreakpoints)));
7647
7648
MenuSP help_menu_sp(
7649
new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
7650
help_menu_sp->AddSubmenu(MenuSP(new Menu(
7651
"GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
7652
7653
m_app_ap->Initialize();
7654
WindowSP &main_window_sp = m_app_ap->GetMainWindow();
7655
7656
MenuSP menubar_sp(new Menu(Menu::Type::Bar));
7657
menubar_sp->AddSubmenu(lldb_menu_sp);
7658
menubar_sp->AddSubmenu(target_menu_sp);
7659
menubar_sp->AddSubmenu(process_menu_sp);
7660
menubar_sp->AddSubmenu(thread_menu_sp);
7661
menubar_sp->AddSubmenu(view_menu_sp);
7662
menubar_sp->AddSubmenu(help_menu_sp);
7663
menubar_sp->SetDelegate(app_menu_delegate_sp);
7664
7665
Rect content_bounds = main_window_sp->GetFrame();
7666
Rect menubar_bounds = content_bounds.MakeMenuBar();
7667
Rect status_bounds = content_bounds.MakeStatusBar();
7668
Rect source_bounds;
7669
Rect variables_bounds;
7670
Rect threads_bounds;
7671
Rect source_variables_bounds;
7672
content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
7673
threads_bounds);
7674
source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
7675
variables_bounds);
7676
7677
WindowSP menubar_window_sp =
7678
main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
7679
// Let the menubar get keys if the active window doesn't handle the keys
7680
// that are typed so it can respond to menubar key presses.
7681
menubar_window_sp->SetCanBeActive(
7682
false); // Don't let the menubar become the active window
7683
menubar_window_sp->SetDelegate(menubar_sp);
7684
7685
WindowSP source_window_sp(
7686
main_window_sp->CreateSubWindow("Source", source_bounds, true));
7687
WindowSP variables_window_sp(
7688
main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
7689
WindowSP threads_window_sp(
7690
main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
7691
WindowSP status_window_sp(
7692
main_window_sp->CreateSubWindow("Status", status_bounds, false));
7693
status_window_sp->SetCanBeActive(
7694
false); // Don't let the status bar become the active window
7695
main_window_sp->SetDelegate(
7696
std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
7697
source_window_sp->SetDelegate(
7698
WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
7699
variables_window_sp->SetDelegate(
7700
WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
7701
TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
7702
threads_window_sp->SetDelegate(WindowDelegateSP(
7703
new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
7704
status_window_sp->SetDelegate(
7705
WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
7706
7707
// All colors with black background.
7708
init_pair(1, COLOR_BLACK, COLOR_BLACK);
7709
init_pair(2, COLOR_RED, COLOR_BLACK);
7710
init_pair(3, COLOR_GREEN, COLOR_BLACK);
7711
init_pair(4, COLOR_YELLOW, COLOR_BLACK);
7712
init_pair(5, COLOR_BLUE, COLOR_BLACK);
7713
init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
7714
init_pair(7, COLOR_CYAN, COLOR_BLACK);
7715
init_pair(8, COLOR_WHITE, COLOR_BLACK);
7716
// All colors with blue background.
7717
init_pair(9, COLOR_BLACK, COLOR_BLUE);
7718
init_pair(10, COLOR_RED, COLOR_BLUE);
7719
init_pair(11, COLOR_GREEN, COLOR_BLUE);
7720
init_pair(12, COLOR_YELLOW, COLOR_BLUE);
7721
init_pair(13, COLOR_BLUE, COLOR_BLUE);
7722
init_pair(14, COLOR_MAGENTA, COLOR_BLUE);
7723
init_pair(15, COLOR_CYAN, COLOR_BLUE);
7724
init_pair(16, COLOR_WHITE, COLOR_BLUE);
7725
// These must match the order in the color indexes enum.
7726
init_pair(17, COLOR_BLACK, COLOR_WHITE);
7727
init_pair(18, COLOR_MAGENTA, COLOR_WHITE);
7728
static_assert(LastColorPairIndex == 18, "Color indexes do not match.");
7729
7730
define_key("\033[Z", KEY_SHIFT_TAB);
7731
define_key("\033\015", KEY_ALT_ENTER);
7732
}
7733
}
7734
7735
void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
7736
7737
void IOHandlerCursesGUI::Run() {
7738
m_app_ap->Run(m_debugger);
7739
SetIsDone(true);
7740
}
7741
7742
IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
7743
7744
void IOHandlerCursesGUI::Cancel() {}
7745
7746
bool IOHandlerCursesGUI::Interrupt() {
7747
return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this);
7748
}
7749
7750
void IOHandlerCursesGUI::GotEOF() {}
7751
7752
void IOHandlerCursesGUI::TerminalSizeChanged() {
7753
m_app_ap->TerminalSizeChanged();
7754
}
7755
7756
#endif // LLDB_ENABLE_CURSES
7757
7758