Coverage Report

Created: 2023-09-21 18:56

/Users/buildslave/jenkins/workspace/coverage/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp
Line
Count
Source (jump to first uncovered line)
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
0
#define KEY_CTRL_A 1
88
0
#define KEY_CTRL_E 5
89
0
#define KEY_CTRL_K 11
90
0
#define KEY_RETURN 10
91
0
#define KEY_ESCAPE 27
92
0
#define KEY_DELETE 127
93
94
0
#define KEY_SHIFT_TAB (KEY_MAX + 1)
95
0
#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
0
  Point(int _x = 0, int _y = 0) : x(_x), y(_y) {}
121
122
0
  void Clear() {
123
0
    x = 0;
124
0
    y = 0;
125
0
  }
126
127
0
  Point &operator+=(const Point &rhs) {
128
0
    x += rhs.x;
129
0
    y += rhs.y;
130
0
    return *this;
131
0
  }
132
133
0
  void Dump() { printf("(x=%i, y=%i)\n", x, y); }
134
};
135
136
0
bool operator==(const Point &lhs, const Point &rhs) {
137
0
  return lhs.x == rhs.x && lhs.y == rhs.y;
138
0
}
139
140
0
bool operator!=(const Point &lhs, const Point &rhs) {
141
0
  return lhs.x != rhs.x || lhs.y != rhs.y;
142
0
}
143
144
struct Size {
145
  int width;
146
  int height;
147
0
  Size(int w = 0, int h = 0) : width(w), height(h) {}
148
149
0
  void Clear() {
150
0
    width = 0;
151
0
    height = 0;
152
0
  }
153
154
0
  void Dump() { printf("(w=%i, h=%i)\n", width, height); }
155
};
156
157
0
bool operator==(const Size &lhs, const Size &rhs) {
158
0
  return lhs.width == rhs.width && lhs.height == rhs.height;
159
0
}
160
161
0
bool operator!=(const Size &lhs, const Size &rhs) {
162
0
  return lhs.width != rhs.width || lhs.height != rhs.height;
163
0
}
164
165
struct Rect {
166
  Point origin;
167
  Size size;
168
169
0
  Rect() : origin(), size() {}
170
171
0
  Rect(const Point &p, const Size &s) : origin(p), size(s) {}
172
173
0
  void Clear() {
174
0
    origin.Clear();
175
0
    size.Clear();
176
0
  }
177
178
0
  void Dump() {
179
0
    printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width,
180
0
           size.height);
181
0
  }
182
183
0
  void Inset(int w, int h) {
184
0
    if (size.width > w * 2)
185
0
      size.width -= w * 2;
186
0
    origin.x += w;
187
188
0
    if (size.height > h * 2)
189
0
      size.height -= h * 2;
190
0
    origin.y += h;
191
0
  }
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
0
  Rect MakeStatusBar() {
196
0
    Rect status_bar;
197
0
    if (size.height > 1) {
198
0
      status_bar.origin.x = origin.x;
199
0
      status_bar.origin.y = size.height;
200
0
      status_bar.size.width = size.width;
201
0
      status_bar.size.height = 1;
202
0
      --size.height;
203
0
    }
204
0
    return status_bar;
205
0
  }
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
0
  Rect MakeMenuBar() {
210
0
    Rect menubar;
211
0
    if (size.height > 1) {
212
0
      menubar.origin.x = origin.x;
213
0
      menubar.origin.y = origin.y;
214
0
      menubar.size.width = size.width;
215
0
      menubar.size.height = 1;
216
0
      ++origin.y;
217
0
      --size.height;
218
0
    }
219
0
    return menubar;
220
0
  }
221
222
  void HorizontalSplitPercentage(float top_percentage, Rect &top,
223
0
                                 Rect &bottom) const {
224
0
    float top_height = top_percentage * size.height;
225
0
    HorizontalSplit(top_height, top, bottom);
226
0
  }
227
228
0
  void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const {
229
0
    top = *this;
230
0
    if (top_height < size.height) {
231
0
      top.size.height = top_height;
232
0
      bottom.origin.x = origin.x;
233
0
      bottom.origin.y = origin.y + top.size.height;
234
0
      bottom.size.width = size.width;
235
0
      bottom.size.height = size.height - top.size.height;
236
0
    } else {
237
0
      bottom.Clear();
238
0
    }
239
0
  }
240
241
  void VerticalSplitPercentage(float left_percentage, Rect &left,
242
0
                               Rect &right) const {
243
0
    float left_width = left_percentage * size.width;
244
0
    VerticalSplit(left_width, left, right);
245
0
  }
246
247
0
  void VerticalSplit(int left_width, Rect &left, Rect &right) const {
248
0
    left = *this;
249
0
    if (left_width < size.width) {
250
0
      left.size.width = left_width;
251
0
      right.origin.x = origin.x + left.size.width;
252
0
      right.origin.y = origin.y;
253
0
      right.size.width = size.width - left.size.width;
254
0
      right.size.height = size.height;
255
0
    } else {
256
0
      right.Clear();
257
0
    }
258
0
  }
259
};
260
261
0
bool operator==(const Rect &lhs, const Rect &rhs) {
262
0
  return lhs.origin == rhs.origin && lhs.size == rhs.size;
263
0
}
264
265
0
bool operator!=(const Rect &lhs, const Rect &rhs) {
266
0
  return lhs.origin != rhs.origin || lhs.size != rhs.size;
267
0
}
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
0
  virtual ~WindowDelegate() = default;
315
316
0
  virtual bool WindowDelegateDraw(Window &window, bool force) {
317
0
    return false; // Drawing not handled
318
0
  }
319
320
0
  virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) {
321
0
    return eKeyNotHandled;
322
0
  }
323
324
0
  virtual const char *WindowDelegateGetHelpText() { return nullptr; }
325
326
0
  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
0
  size_t GetNumLines() const { return m_text.GetSize(); }
340
341
0
  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
0
  Surface(Surface::Type type) : m_type(type) {}
357
358
0
  WINDOW *get() { return m_window; }
359
360
0
  operator WINDOW *() { return m_window; }
361
362
0
  Surface SubSurface(Rect bounds) {
363
0
    Surface subSurface(m_type);
364
0
    if (m_type == Type::Pad)
365
0
      subSurface.m_window =
366
0
          ::subpad(m_window, bounds.size.height, bounds.size.width,
367
0
                   bounds.origin.y, bounds.origin.x);
368
0
    else
369
0
      subSurface.m_window =
370
0
          ::derwin(m_window, bounds.size.height, bounds.size.width,
371
0
                   bounds.origin.y, bounds.origin.x);
372
0
    return subSurface;
373
0
  }
374
375
  // Copy a region of the surface to another surface.
376
  void CopyToSurface(Surface &target, Point source_origin, Point target_origin,
377
0
                     Size size) {
378
0
    ::copywin(m_window, target.get(), source_origin.y, source_origin.x,
379
0
              target_origin.y, target_origin.x,
380
0
              target_origin.y + size.height - 1,
381
0
              target_origin.x + size.width - 1, false);
382
0
  }
383
384
0
  int GetCursorX() const { return getcurx(m_window); }
385
0
  int GetCursorY() const { return getcury(m_window); }
386
0
  void MoveCursor(int x, int y) { ::wmove(m_window, y, x); }
387
388
0
  void AttributeOn(attr_t attr) { ::wattron(m_window, attr); }
389
0
  void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); }
390
391
0
  int GetMaxX() const { return getmaxx(m_window); }
392
0
  int GetMaxY() const { return getmaxy(m_window); }
393
0
  int GetWidth() const { return GetMaxX(); }
394
0
  int GetHeight() const { return GetMaxY(); }
395
0
  Size GetSize() const { return Size(GetWidth(), GetHeight()); }
396
  // Get a zero origin rectangle width the surface size.
397
0
  Rect GetFrame() const { return Rect(Point(), GetSize()); }
398
399
0
  void Clear() { ::wclear(m_window); }
400
0
  void Erase() { ::werase(m_window); }
401
402
0
  void SetBackground(int color_pair_idx) {
403
0
    ::wbkgd(m_window, COLOR_PAIR(color_pair_idx));
404
0
  }
405
406
0
  void PutChar(int ch) { ::waddch(m_window, ch); }
407
0
  void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); }
408
409
0
  void PutCStringTruncated(int right_pad, const char *s, int len = -1) {
410
0
    int bytes_left = GetWidth() - GetCursorX();
411
0
    if (bytes_left > right_pad) {
412
0
      bytes_left -= right_pad;
413
0
      ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len));
414
0
    }
415
0
  }
416
417
0
  void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) {
418
0
    va_list args;
419
0
    va_start(args, format);
420
0
    vw_printw(m_window, format, args);
421
0
    va_end(args);
422
0
  }
423
424
  void PrintfTruncated(int right_pad, const char *format, ...)
425
0
      __attribute__((format(printf, 3, 4))) {
426
0
    va_list args;
427
0
    va_start(args, format);
428
0
    StreamString strm;
429
0
    strm.PrintfVarArg(format, args);
430
0
    va_end(args);
431
0
    PutCStringTruncated(right_pad, strm.GetData());
432
0
  }
433
434
0
  void VerticalLine(int n, chtype v_char = ACS_VLINE) {
435
0
    ::wvline(m_window, v_char, n);
436
0
  }
437
0
  void HorizontalLine(int n, chtype h_char = ACS_HLINE) {
438
0
    ::whline(m_window, h_char, n);
439
0
  }
440
0
  void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
441
0
    ::box(m_window, v_char, h_char);
442
0
  }
443
444
  void TitledBox(const char *title, chtype v_char = ACS_VLINE,
445
0
                 chtype h_char = ACS_HLINE) {
446
0
    Box(v_char, h_char);
447
0
    int title_offset = 2;
448
0
    MoveCursor(title_offset, 0);
449
0
    PutChar('[');
450
0
    PutCString(title, GetWidth() - title_offset);
451
0
    PutChar(']');
452
0
  }
453
454
  void Box(const Rect &bounds, chtype v_char = ACS_VLINE,
455
0
           chtype h_char = ACS_HLINE) {
456
0
    MoveCursor(bounds.origin.x, bounds.origin.y);
457
0
    VerticalLine(bounds.size.height);
458
0
    HorizontalLine(bounds.size.width);
459
0
    PutChar(ACS_ULCORNER);
460
0
461
0
    MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y);
462
0
    VerticalLine(bounds.size.height);
463
0
    PutChar(ACS_URCORNER);
464
0
465
0
    MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1);
466
0
    HorizontalLine(bounds.size.width);
467
0
    PutChar(ACS_LLCORNER);
468
0
469
0
    MoveCursor(bounds.origin.x + bounds.size.width - 1,
470
0
               bounds.origin.y + bounds.size.height - 1);
471
0
    PutChar(ACS_LRCORNER);
472
0
  }
473
474
  void TitledBox(const Rect &bounds, const char *title,
475
0
                 chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) {
476
0
    Box(bounds, v_char, h_char);
477
0
    int title_offset = 2;
478
0
    MoveCursor(bounds.origin.x + title_offset, bounds.origin.y);
479
0
    PutChar('[');
480
0
    PutCString(title, bounds.size.width - title_offset);
481
0
    PutChar(']');
482
0
  }
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
0
                                    bool use_blue_background) {
492
0
    attr_t saved_attr;
493
0
    short saved_pair;
494
0
    bool result = false;
495
0
    wattr_get(m_window, &saved_attr, &saved_pair, nullptr);
496
0
    if (use_blue_background)
497
0
      ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
498
0
    while (!string.empty()) {
499
0
      size_t esc_pos = string.find(ANSI_ESC_START);
500
0
      if (esc_pos == StringRef::npos) {
501
0
        string = string.substr(skip_first_count);
502
0
        if (!string.empty()) {
503
0
          PutCStringTruncated(right_pad, string.data(), string.size());
504
0
          result = true;
505
0
        }
506
0
        break;
507
0
      }
508
0
      if (esc_pos > 0) {
509
0
        if (skip_first_count > 0) {
510
0
          int skip = std::min(esc_pos, skip_first_count);
511
0
          string = string.substr(skip);
512
0
          skip_first_count -= skip;
513
0
          esc_pos -= skip;
514
0
        }
515
0
        if (esc_pos > 0) {
516
0
          PutCStringTruncated(right_pad, string.data(), esc_pos);
517
0
          result = true;
518
0
          string = string.drop_front(esc_pos);
519
0
        }
520
0
      }
521
0
      bool consumed = string.consume_front(ANSI_ESC_START);
522
0
      assert(consumed);
523
0
      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
0
      int value;
530
0
      if (!!string.consumeInteger(10, value) || // Returns false on success.
531
0
          !(value == 0 || value == ANSI_CTRL_UNDERLINE ||
532
0
            (value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) {
533
0
        llvm::errs() << "No valid color code in color escape sequence.\n";
534
0
        continue;
535
0
      }
536
0
      if (!string.consume_front(ANSI_ESC_END)) {
537
0
        llvm::errs() << "Missing '" << ANSI_ESC_END
538
0
                     << "' in color escape sequence.\n";
539
0
        continue;
540
0
      }
541
0
      if (value == 0) { // Reset.
542
0
        wattr_set(m_window, saved_attr, saved_pair, nullptr);
543
0
        if (use_blue_background)
544
0
          ::wattron(m_window, COLOR_PAIR(WhiteOnBlue));
545
0
      } else if (value == ANSI_CTRL_UNDERLINE) {
546
0
        ::wattron(m_window, A_UNDERLINE);
547
0
      } else {
548
        // Mapped directly to first 16 color pairs (black/blue background).
549
0
        ::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 +
550
0
                                       (use_blue_background ? 8 : 0)));
551
0
      }
552
0
    }
553
0
    wattr_set(m_window, saved_attr, saved_pair, nullptr);
554
0
    return result;
555
0
  }
556
557
protected:
558
  Type m_type;
559
  WINDOW *m_window = nullptr;
560
};
561
562
class Pad : public Surface {
563
public:
564
0
  Pad(Size size) : Surface(Surface::Type::Pad) {
565
0
    m_window = ::newpad(size.height, size.width);
566
0
  }
567
568
0
  ~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
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {}
579
580
  Window(const char *name, WINDOW *w, bool del = true)
581
0
      : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr),
582
0
        m_parent(nullptr), m_subwindows(), m_delegate_sp(),
583
        m_curr_active_window_idx(UINT32_MAX),
584
0
        m_prev_active_window_idx(UINT32_MAX), m_delete(del),
585
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
586
0
    if (w)
587
0
      Reset(w);
588
0
  }
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
0
        m_needs_update(true), m_can_activate(true), m_is_subwin(false) {
596
0
    Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y,
597
0
                   bounds.origin.y));
598
0
  }
599
600
0
  virtual ~Window() {
601
0
    RemoveSubWindows();
602
0
    Reset();
603
0
  }
604
605
0
  void Reset(WINDOW *w = nullptr, bool del = true) {
606
0
    if (m_window == w)
607
0
      return;
608
609
0
    if (m_panel) {
610
0
      ::del_panel(m_panel);
611
0
      m_panel = nullptr;
612
0
    }
613
0
    if (m_window && m_delete) {
614
0
      ::delwin(m_window);
615
0
      m_window = nullptr;
616
0
      m_delete = false;
617
0
    }
618
0
    if (w) {
619
0
      m_window = w;
620
0
      m_panel = ::new_panel(m_window);
621
0
      m_delete = del;
622
0
    }
623
0
  }
624
625
  // Get the rectangle in our parent window
626
0
  Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
627
628
0
  Rect GetCenteredRect(int width, int height) {
629
0
    Size size = GetSize();
630
0
    width = std::min(size.width, width);
631
0
    height = std::min(size.height, height);
632
0
    int x = (size.width - width) / 2;
633
0
    int y = (size.height - height) / 2;
634
0
    return Rect(Point(x, y), Size(width, height));
635
0
  }
636
637
0
  int GetChar() { return ::wgetch(m_window); }
638
0
  Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
639
0
  int GetParentX() const { return getparx(m_window); }
640
0
  int GetParentY() const { return getpary(m_window); }
641
0
  void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); }
642
0
  void Resize(int w, int h) { ::wresize(m_window, h, w); }
643
0
  void Resize(const Size &size) {
644
0
    ::wresize(m_window, size.height, size.width);
645
0
  }
646
0
  void MoveWindow(const Point &origin) {
647
0
    const bool moving_window = origin != GetParentOrigin();
648
0
    if (m_is_subwin && moving_window) {
649
      // Can't move subwindows, must delete and re-create
650
0
      Size size = GetSize();
651
0
      Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y,
652
0
                     origin.x),
653
0
            true);
654
0
    } else {
655
0
      ::mvwin(m_window, origin.y, origin.x);
656
0
    }
657
0
  }
658
659
0
  void SetBounds(const Rect &bounds) {
660
0
    const bool moving_window = bounds.origin != GetParentOrigin();
661
0
    if (m_is_subwin && moving_window) {
662
      // Can't move subwindows, must delete and re-create
663
0
      Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width,
664
0
                     bounds.origin.y, bounds.origin.x),
665
0
            true);
666
0
    } else {
667
0
      if (moving_window)
668
0
        MoveWindow(bounds.origin);
669
0
      Resize(bounds.size);
670
0
    }
671
0
  }
672
673
0
  void Touch() {
674
0
    ::touchwin(m_window);
675
0
    if (m_parent)
676
0
      m_parent->Touch();
677
0
  }
678
679
  WindowSP CreateSubWindow(const char *name, const Rect &bounds,
680
0
                           bool make_active) {
681
0
    auto get_window = [this, &bounds]() {
682
0
      return m_window
683
0
                 ? ::subwin(m_window, bounds.size.height, bounds.size.width,
684
0
                            bounds.origin.y, bounds.origin.x)
685
0
                 : ::newwin(bounds.size.height, bounds.size.width,
686
0
                            bounds.origin.y, bounds.origin.x);
687
0
    };
688
0
    WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true);
689
0
    subwindow_sp->m_is_subwin = subwindow_sp.operator bool();
690
0
    subwindow_sp->m_parent = this;
691
0
    if (make_active) {
692
0
      m_prev_active_window_idx = m_curr_active_window_idx;
693
0
      m_curr_active_window_idx = m_subwindows.size();
694
0
    }
695
0
    m_subwindows.push_back(subwindow_sp);
696
0
    ::top_panel(subwindow_sp->m_panel);
697
0
    m_needs_update = true;
698
0
    return subwindow_sp;
699
0
  }
700
701
0
  bool RemoveSubWindow(Window *window) {
702
0
    Windows::iterator pos, end = m_subwindows.end();
703
0
    size_t i = 0;
704
0
    for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
705
0
      if ((*pos).get() == window) {
706
0
        if (m_prev_active_window_idx == i)
707
0
          m_prev_active_window_idx = UINT32_MAX;
708
0
        else if (m_prev_active_window_idx != UINT32_MAX &&
709
0
                 m_prev_active_window_idx > i)
710
0
          --m_prev_active_window_idx;
711
712
0
        if (m_curr_active_window_idx == i)
713
0
          m_curr_active_window_idx = UINT32_MAX;
714
0
        else if (m_curr_active_window_idx != UINT32_MAX &&
715
0
                 m_curr_active_window_idx > i)
716
0
          --m_curr_active_window_idx;
717
0
        window->Erase();
718
0
        m_subwindows.erase(pos);
719
0
        m_needs_update = true;
720
0
        if (m_parent)
721
0
          m_parent->Touch();
722
0
        else
723
0
          ::touchwin(stdscr);
724
0
        return true;
725
0
      }
726
0
    }
727
0
    return false;
728
0
  }
729
730
0
  WindowSP FindSubWindow(const char *name) {
731
0
    Windows::iterator pos, end = m_subwindows.end();
732
0
    size_t i = 0;
733
0
    for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) {
734
0
      if ((*pos)->m_name == name)
735
0
        return *pos;
736
0
    }
737
0
    return WindowSP();
738
0
  }
739
740
0
  void RemoveSubWindows() {
741
0
    m_curr_active_window_idx = UINT32_MAX;
742
0
    m_prev_active_window_idx = UINT32_MAX;
743
0
    for (Windows::iterator pos = m_subwindows.begin();
744
0
         pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) {
745
0
      (*pos)->Erase();
746
0
    }
747
0
    if (m_parent)
748
0
      m_parent->Touch();
749
0
    else
750
0
      ::touchwin(stdscr);
751
0
  }
752
753
  // Window drawing utilities
754
0
  void DrawTitleBox(const char *title, const char *bottom_message = nullptr) {
755
0
    attr_t attr = 0;
756
0
    if (IsActive())
757
0
      attr = A_BOLD | COLOR_PAIR(BlackOnWhite);
758
0
    else
759
0
      attr = 0;
760
0
    if (attr)
761
0
      AttributeOn(attr);
762
763
0
    Box();
764
0
    MoveCursor(3, 0);
765
766
0
    if (title && title[0]) {
767
0
      PutChar('<');
768
0
      PutCString(title);
769
0
      PutChar('>');
770
0
    }
771
772
0
    if (bottom_message && bottom_message[0]) {
773
0
      int bottom_message_length = strlen(bottom_message);
774
0
      int x = GetWidth() - 3 - (bottom_message_length + 2);
775
776
0
      if (x > 0) {
777
0
        MoveCursor(x, GetHeight() - 1);
778
0
        PutChar('[');
779
0
        PutCString(bottom_message);
780
0
        PutChar(']');
781
0
      } else {
782
0
        MoveCursor(1, GetHeight() - 1);
783
0
        PutChar('[');
784
0
        PutCStringTruncated(1, bottom_message);
785
0
      }
786
0
    }
787
0
    if (attr)
788
0
      AttributeOff(attr);
789
0
  }
790
791
0
  virtual void Draw(bool force) {
792
0
    if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force))
793
0
      return;
794
795
0
    for (auto &subwindow_sp : m_subwindows)
796
0
      subwindow_sp->Draw(force);
797
0
  }
798
799
0
  bool CreateHelpSubwindow() {
800
0
    if (m_delegate_sp) {
801
0
      const char *text = m_delegate_sp->WindowDelegateGetHelpText();
802
0
      KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp();
803
0
      if ((text && text[0]) || key_help) {
804
0
        std::unique_ptr<HelpDialogDelegate> help_delegate_up(
805
0
            new HelpDialogDelegate(text, key_help));
806
0
        const size_t num_lines = help_delegate_up->GetNumLines();
807
0
        const size_t max_length = help_delegate_up->GetMaxLineLength();
808
0
        Rect bounds = GetBounds();
809
0
        bounds.Inset(1, 1);
810
0
        if (max_length + 4 < static_cast<size_t>(bounds.size.width)) {
811
0
          bounds.origin.x += (bounds.size.width - max_length + 4) / 2;
812
0
          bounds.size.width = max_length + 4;
813
0
        } else {
814
0
          if (bounds.size.width > 100) {
815
0
            const int inset_w = bounds.size.width / 4;
816
0
            bounds.origin.x += inset_w;
817
0
            bounds.size.width -= 2 * inset_w;
818
0
          }
819
0
        }
820
821
0
        if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) {
822
0
          bounds.origin.y += (bounds.size.height - num_lines + 2) / 2;
823
0
          bounds.size.height = num_lines + 2;
824
0
        } else {
825
0
          if (bounds.size.height > 100) {
826
0
            const int inset_h = bounds.size.height / 4;
827
0
            bounds.origin.y += inset_h;
828
0
            bounds.size.height -= 2 * inset_h;
829
0
          }
830
0
        }
831
0
        WindowSP help_window_sp;
832
0
        Window *parent_window = GetParent();
833
0
        if (parent_window)
834
0
          help_window_sp = parent_window->CreateSubWindow("Help", bounds, true);
835
0
        else
836
0
          help_window_sp = CreateSubWindow("Help", bounds, true);
837
0
        help_window_sp->SetDelegate(
838
0
            WindowDelegateSP(help_delegate_up.release()));
839
0
        return true;
840
0
      }
841
0
    }
842
0
    return false;
843
0
  }
844
845
0
  virtual HandleCharResult HandleChar(int key) {
846
    // Always check the active window first
847
0
    HandleCharResult result = eKeyNotHandled;
848
0
    WindowSP active_window_sp = GetActiveWindow();
849
0
    if (active_window_sp) {
850
0
      result = active_window_sp->HandleChar(key);
851
0
      if (result != eKeyNotHandled)
852
0
        return result;
853
0
    }
854
855
0
    if (m_delegate_sp) {
856
0
      result = m_delegate_sp->WindowDelegateHandleChar(*this, key);
857
0
      if (result != eKeyNotHandled)
858
0
        return result;
859
0
    }
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
0
    Windows subwindows(m_subwindows);
866
0
    for (auto subwindow_sp : subwindows) {
867
0
      if (!subwindow_sp->m_can_activate) {
868
0
        HandleCharResult result = subwindow_sp->HandleChar(key);
869
0
        if (result != eKeyNotHandled)
870
0
          return result;
871
0
      }
872
0
    }
873
874
0
    return eKeyNotHandled;
875
0
  }
876
877
0
  WindowSP GetActiveWindow() {
878
0
    if (!m_subwindows.empty()) {
879
0
      if (m_curr_active_window_idx >= m_subwindows.size()) {
880
0
        if (m_prev_active_window_idx < m_subwindows.size()) {
881
0
          m_curr_active_window_idx = m_prev_active_window_idx;
882
0
          m_prev_active_window_idx = UINT32_MAX;
883
0
        } else if (IsActive()) {
884
0
          m_prev_active_window_idx = UINT32_MAX;
885
0
          m_curr_active_window_idx = UINT32_MAX;
886
887
          // Find first window that wants to be active if this window is active
888
0
          const size_t num_subwindows = m_subwindows.size();
889
0
          for (size_t i = 0; i < num_subwindows; ++i) {
890
0
            if (m_subwindows[i]->GetCanBeActive()) {
891
0
              m_curr_active_window_idx = i;
892
0
              break;
893
0
            }
894
0
          }
895
0
        }
896
0
      }
897
898
0
      if (m_curr_active_window_idx < m_subwindows.size())
899
0
        return m_subwindows[m_curr_active_window_idx];
900
0
    }
901
0
    return WindowSP();
902
0
  }
903
904
0
  bool GetCanBeActive() const { return m_can_activate; }
905
906
0
  void SetCanBeActive(bool b) { m_can_activate = b; }
907
908
0
  void SetDelegate(const WindowDelegateSP &delegate_sp) {
909
0
    m_delegate_sp = delegate_sp;
910
0
  }
911
912
0
  Window *GetParent() const { return m_parent; }
913
914
0
  bool IsActive() const {
915
0
    if (m_parent)
916
0
      return m_parent->GetActiveWindow().get() == this;
917
0
    else
918
0
      return true; // Top level window is always active
919
0
  }
920
921
0
  void SelectNextWindowAsActive() {
922
    // Move active focus to next window
923
0
    const int num_subwindows = m_subwindows.size();
924
0
    int start_idx = 0;
925
0
    if (m_curr_active_window_idx != UINT32_MAX) {
926
0
      m_prev_active_window_idx = m_curr_active_window_idx;
927
0
      start_idx = m_curr_active_window_idx + 1;
928
0
    }
929
0
    for (int idx = start_idx; idx < num_subwindows; ++idx) {
930
0
      if (m_subwindows[idx]->GetCanBeActive()) {
931
0
        m_curr_active_window_idx = idx;
932
0
        return;
933
0
      }
934
0
    }
935
0
    for (int idx = 0; idx < start_idx; ++idx) {
936
0
      if (m_subwindows[idx]->GetCanBeActive()) {
937
0
        m_curr_active_window_idx = idx;
938
0
        break;
939
0
      }
940
0
    }
941
0
  }
942
943
0
  void SelectPreviousWindowAsActive() {
944
    // Move active focus to previous window
945
0
    const int num_subwindows = m_subwindows.size();
946
0
    int start_idx = num_subwindows - 1;
947
0
    if (m_curr_active_window_idx != UINT32_MAX) {
948
0
      m_prev_active_window_idx = m_curr_active_window_idx;
949
0
      start_idx = m_curr_active_window_idx - 1;
950
0
    }
951
0
    for (int idx = start_idx; idx >= 0; --idx) {
952
0
      if (m_subwindows[idx]->GetCanBeActive()) {
953
0
        m_curr_active_window_idx = idx;
954
0
        return;
955
0
      }
956
0
    }
957
0
    for (int idx = num_subwindows - 1; idx > start_idx; --idx) {
958
0
      if (m_subwindows[idx]->GetCanBeActive()) {
959
0
        m_curr_active_window_idx = idx;
960
0
        break;
961
0
      }
962
0
    }
963
0
  }
964
965
0
  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
0
  ScrollContext(int line) : start(line), end(line) {}
998
0
  ScrollContext(int _start, int _end) : start(_start), end(_end) {}
999
1000
0
  void Offset(int offset) {
1001
0
    start += offset;
1002
0
    end += offset;
1003
0
  }
1004
};
1005
1006
class FieldDelegate {
1007
public:
1008
0
  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
0
  virtual ScrollContext FieldDelegateGetScrollContext() {
1020
0
    return ScrollContext(0, FieldDelegateGetHeight() - 1);
1021
0
  }
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
0
  virtual HandleCharResult FieldDelegateHandleChar(int key) {
1030
0
    return eKeyNotHandled;
1031
0
  }
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
0
  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
0
  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
0
  virtual bool FieldDelegateOnLastOrOnlyElement() { return true; }
1056
1057
  // Select the first element in the field if multiple elements exists.
1058
0
  virtual void FieldDelegateSelectFirstElement() {}
1059
1060
  // Select the last element in the field if multiple elements exists.
1061
0
  virtual void FieldDelegateSelectLastElement() {}
1062
1063
  // Returns true if the field has an error, false otherwise.
1064
0
  virtual bool FieldDelegateHasError() { return false; }
1065
1066
0
  bool FieldDelegateIsVisible() { return m_is_visible; }
1067
1068
0
  void FieldDelegateHide() { m_is_visible = false; }
1069
1070
0
  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
0
      : m_label(label), m_required(required) {
1082
0
    if (content)
1083
0
      m_content = content;
1084
0
  }
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
0
  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
0
  int FieldDelegateGetHeight() override {
1101
0
    int height = GetFieldHeight();
1102
0
    if (FieldDelegateHasError())
1103
0
      height++;
1104
0
    return height;
1105
0
  }
1106
1107
  // Get the cursor X position in the surface coordinate.
1108
0
  int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; }
1109
1110
0
  int GetContentLength() { return m_content.length(); }
1111
1112
0
  void DrawContent(Surface &surface, bool is_selected) {
1113
0
    UpdateScrolling(surface.GetWidth());
1114
1115
0
    surface.MoveCursor(0, 0);
1116
0
    const char *text = m_content.c_str() + m_first_visibile_char;
1117
0
    surface.PutCString(text, surface.GetWidth());
1118
1119
    // Highlight the cursor.
1120
0
    surface.MoveCursor(GetCursorXPosition(), 0);
1121
0
    if (is_selected)
1122
0
      surface.AttributeOn(A_REVERSE);
1123
0
    if (m_cursor_position == GetContentLength())
1124
      // Cursor is past the last character. Highlight an empty space.
1125
0
      surface.PutChar(' ');
1126
0
    else
1127
0
      surface.PutChar(m_content[m_cursor_position]);
1128
0
    if (is_selected)
1129
0
      surface.AttributeOff(A_REVERSE);
1130
0
  }
1131
1132
0
  void DrawField(Surface &surface, bool is_selected) {
1133
0
    surface.TitledBox(m_label.c_str());
1134
1135
0
    Rect content_bounds = surface.GetFrame();
1136
0
    content_bounds.Inset(1, 1);
1137
0
    Surface content_surface = surface.SubSurface(content_bounds);
1138
1139
0
    DrawContent(content_surface, is_selected);
1140
0
  }
1141
1142
0
  void DrawError(Surface &surface) {
1143
0
    if (!FieldDelegateHasError())
1144
0
      return;
1145
0
    surface.MoveCursor(0, 0);
1146
0
    surface.AttributeOn(COLOR_PAIR(RedOnBlack));
1147
0
    surface.PutChar(ACS_DIAMOND);
1148
0
    surface.PutChar(' ');
1149
0
    surface.PutCStringTruncated(1, GetError().c_str());
1150
0
    surface.AttributeOff(COLOR_PAIR(RedOnBlack));
1151
0
  }
1152
1153
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1154
0
    Rect frame = surface.GetFrame();
1155
0
    Rect field_bounds, error_bounds;
1156
0
    frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds);
1157
0
    Surface field_surface = surface.SubSurface(field_bounds);
1158
0
    Surface error_surface = surface.SubSurface(error_bounds);
1159
1160
0
    DrawField(field_surface, is_selected);
1161
0
    DrawError(error_surface);
1162
0
  }
1163
1164
  // Get the position of the last visible character.
1165
0
  int GetLastVisibleCharPosition(int width) {
1166
0
    int position = m_first_visibile_char + width - 1;
1167
0
    return std::min(position, GetContentLength());
1168
0
  }
1169
1170
0
  void UpdateScrolling(int width) {
1171
0
    if (m_cursor_position < m_first_visibile_char) {
1172
0
      m_first_visibile_char = m_cursor_position;
1173
0
      return;
1174
0
    }
1175
1176
0
    if (m_cursor_position > GetLastVisibleCharPosition(width))
1177
0
      m_first_visibile_char = m_cursor_position - (width - 1);
1178
0
  }
1179
1180
  // The cursor is allowed to move one character past the string.
1181
  // m_cursor_position is in range [0, GetContentLength()].
1182
0
  void MoveCursorRight() {
1183
0
    if (m_cursor_position < GetContentLength())
1184
0
      m_cursor_position++;
1185
0
  }
1186
1187
0
  void MoveCursorLeft() {
1188
0
    if (m_cursor_position > 0)
1189
0
      m_cursor_position--;
1190
0
  }
1191
1192
0
  void MoveCursorToStart() { m_cursor_position = 0; }
1193
1194
0
  void MoveCursorToEnd() { m_cursor_position = GetContentLength(); }
1195
1196
0
  void ScrollLeft() {
1197
0
    if (m_first_visibile_char > 0)
1198
0
      m_first_visibile_char--;
1199
0
  }
1200
1201
  // Insert a character at the current cursor position and advance the cursor
1202
  // position.
1203
0
  void InsertChar(char character) {
1204
0
    m_content.insert(m_cursor_position, 1, character);
1205
0
    m_cursor_position++;
1206
0
    ClearError();
1207
0
  }
1208
1209
  // Remove the character before the cursor position, retreat the cursor
1210
  // position, and scroll left.
1211
0
  void RemovePreviousChar() {
1212
0
    if (m_cursor_position == 0)
1213
0
      return;
1214
1215
0
    m_content.erase(m_cursor_position - 1, 1);
1216
0
    m_cursor_position--;
1217
0
    ScrollLeft();
1218
0
    ClearError();
1219
0
  }
1220
1221
  // Remove the character after the cursor position.
1222
0
  void RemoveNextChar() {
1223
0
    if (m_cursor_position == GetContentLength())
1224
0
      return;
1225
1226
0
    m_content.erase(m_cursor_position, 1);
1227
0
    ClearError();
1228
0
  }
1229
1230
  // Clear characters from the current cursor position to the end.
1231
0
  void ClearToEnd() {
1232
0
    m_content.erase(m_cursor_position);
1233
0
    ClearError();
1234
0
  }
1235
1236
0
  void Clear() {
1237
0
    m_content.clear();
1238
0
    m_cursor_position = 0;
1239
0
    ClearError();
1240
0
  }
1241
1242
  // True if the key represents a char that can be inserted in the field
1243
  // content, false otherwise.
1244
0
  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
0
    if (key > 127)
1248
0
      return false;
1249
0
    return isprint(key);
1250
0
  }
1251
1252
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1253
0
    if (IsAcceptableChar(key)) {
1254
0
      ClearError();
1255
0
      InsertChar((char)key);
1256
0
      return eKeyHandled;
1257
0
    }
1258
1259
0
    switch (key) {
1260
0
    case KEY_HOME:
1261
0
    case KEY_CTRL_A:
1262
0
      MoveCursorToStart();
1263
0
      return eKeyHandled;
1264
0
    case KEY_END:
1265
0
    case KEY_CTRL_E:
1266
0
      MoveCursorToEnd();
1267
0
      return eKeyHandled;
1268
0
    case KEY_RIGHT:
1269
0
    case KEY_SF:
1270
0
      MoveCursorRight();
1271
0
      return eKeyHandled;
1272
0
    case KEY_LEFT:
1273
0
    case KEY_SR:
1274
0
      MoveCursorLeft();
1275
0
      return eKeyHandled;
1276
0
    case KEY_BACKSPACE:
1277
0
    case KEY_DELETE:
1278
0
      RemovePreviousChar();
1279
0
      return eKeyHandled;
1280
0
    case KEY_DC:
1281
0
      RemoveNextChar();
1282
0
      return eKeyHandled;
1283
0
    case KEY_EOL:
1284
0
    case KEY_CTRL_K:
1285
0
      ClearToEnd();
1286
0
      return eKeyHandled;
1287
0
    case KEY_DL:
1288
0
    case KEY_CLEAR:
1289
0
      Clear();
1290
0
      return eKeyHandled;
1291
0
    default:
1292
0
      break;
1293
0
    }
1294
0
    return eKeyNotHandled;
1295
0
  }
1296
1297
0
  bool FieldDelegateHasError() override { return !m_error.empty(); }
1298
1299
0
  void FieldDelegateExitCallback() override {
1300
0
    if (!IsSpecified() && m_required)
1301
0
      SetError("This field is required!");
1302
0
  }
1303
1304
0
  bool IsSpecified() { return !m_content.empty(); }
1305
1306
0
  void ClearError() { m_error.clear(); }
1307
1308
0
  const std::string &GetError() { return m_error; }
1309
1310
0
  void SetError(const char *error) { m_error = error; }
1311
1312
0
  const std::string &GetText() { return m_content; }
1313
1314
0
  void SetText(const char *text) {
1315
0
    if (text == nullptr) {
1316
0
      m_content.clear();
1317
0
      return;
1318
0
    }
1319
0
    m_content = text;
1320
0
  }
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
0
      : TextFieldDelegate(label, std::to_string(content).c_str(), required) {}
1340
1341
  // Only accept digits.
1342
0
  bool IsAcceptableChar(int key) override { return isdigit(key); }
1343
1344
  // Returns the integer content of the field.
1345
0
  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
0
      : TextFieldDelegate(label, content, required),
1353
0
        m_need_to_exist(need_to_exist) {}
1354
1355
0
  void FieldDelegateExitCallback() override {
1356
0
    TextFieldDelegate::FieldDelegateExitCallback();
1357
0
    if (!IsSpecified())
1358
0
      return;
1359
1360
0
    if (!m_need_to_exist)
1361
0
      return;
1362
1363
0
    FileSpec file = GetResolvedFileSpec();
1364
0
    if (!FileSystem::Instance().Exists(file)) {
1365
0
      SetError("File doesn't exist!");
1366
0
      return;
1367
0
    }
1368
0
    if (FileSystem::Instance().IsDirectory(file)) {
1369
0
      SetError("Not a file!");
1370
0
      return;
1371
0
    }
1372
0
  }
1373
1374
0
  FileSpec GetFileSpec() {
1375
0
    FileSpec file_spec(GetPath());
1376
0
    return file_spec;
1377
0
  }
1378
1379
0
  FileSpec GetResolvedFileSpec() {
1380
0
    FileSpec file_spec(GetPath());
1381
0
    FileSystem::Instance().Resolve(file_spec);
1382
0
    return file_spec;
1383
0
  }
1384
1385
0
  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
0
      : TextFieldDelegate(label, content, required),
1396
0
        m_need_to_exist(need_to_exist) {}
1397
1398
0
  void FieldDelegateExitCallback() override {
1399
0
    TextFieldDelegate::FieldDelegateExitCallback();
1400
0
    if (!IsSpecified())
1401
0
      return;
1402
1403
0
    if (!m_need_to_exist)
1404
0
      return;
1405
1406
0
    FileSpec file = GetResolvedFileSpec();
1407
0
    if (!FileSystem::Instance().Exists(file)) {
1408
0
      SetError("Directory doesn't exist!");
1409
0
      return;
1410
0
    }
1411
0
    if (!FileSystem::Instance().IsDirectory(file)) {
1412
0
      SetError("Not a directory!");
1413
0
      return;
1414
0
    }
1415
0
  }
1416
1417
0
  FileSpec GetFileSpec() {
1418
0
    FileSpec file_spec(GetPath());
1419
0
    return file_spec;
1420
0
  }
1421
1422
0
  FileSpec GetResolvedFileSpec() {
1423
0
    FileSpec file_spec(GetPath());
1424
0
    FileSystem::Instance().Resolve(file_spec);
1425
0
    return file_spec;
1426
0
  }
1427
1428
0
  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
0
      : TextFieldDelegate(label, content, required) {}
1438
1439
0
  void FieldDelegateExitCallback() override {
1440
0
    TextFieldDelegate::FieldDelegateExitCallback();
1441
0
    if (!IsSpecified())
1442
0
      return;
1443
1444
0
    if (!GetArchSpec().IsValid())
1445
0
      SetError("Not a valid arch!");
1446
0
  }
1447
1448
0
  const std::string &GetArchString() { return m_content; }
1449
1450
0
  ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); }
1451
};
1452
1453
class BooleanFieldDelegate : public FieldDelegate {
1454
public:
1455
  BooleanFieldDelegate(const char *label, bool content)
1456
0
      : 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
0
  int FieldDelegateGetHeight() override { return 1; }
1464
1465
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1466
0
    surface.MoveCursor(0, 0);
1467
0
    surface.PutChar('[');
1468
0
    if (is_selected)
1469
0
      surface.AttributeOn(A_REVERSE);
1470
0
    surface.PutChar(m_content ? ACS_DIAMOND : ' ');
1471
0
    if (is_selected)
1472
0
      surface.AttributeOff(A_REVERSE);
1473
0
    surface.PutChar(']');
1474
0
    surface.PutChar(' ');
1475
0
    surface.PutCString(m_label.c_str());
1476
0
  }
1477
1478
0
  void ToggleContent() { m_content = !m_content; }
1479
1480
0
  void SetContentToTrue() { m_content = true; }
1481
1482
0
  void SetContentToFalse() { m_content = false; }
1483
1484
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1485
0
    switch (key) {
1486
0
    case 't':
1487
0
    case '1':
1488
0
      SetContentToTrue();
1489
0
      return eKeyHandled;
1490
0
    case 'f':
1491
0
    case '0':
1492
0
      SetContentToFalse();
1493
0
      return eKeyHandled;
1494
0
    case ' ':
1495
0
    case '\r':
1496
0
    case '\n':
1497
0
    case KEY_ENTER:
1498
0
      ToggleContent();
1499
0
      return eKeyHandled;
1500
0
    default:
1501
0
      break;
1502
0
    }
1503
0
    return eKeyNotHandled;
1504
0
  }
1505
1506
  // Returns the boolean content of the field.
1507
0
  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
0
      : m_label(label), m_number_of_visible_choices(number_of_visible_choices),
1519
0
        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
0
  int FieldDelegateGetHeight() override {
1534
0
    return m_number_of_visible_choices + 2;
1535
0
  }
1536
1537
0
  int GetNumberOfChoices() { return m_choices.size(); }
1538
1539
  // Get the index of the last visible choice.
1540
0
  int GetLastVisibleChoice() {
1541
0
    int index = m_first_visibile_choice + m_number_of_visible_choices;
1542
0
    return std::min(index, GetNumberOfChoices()) - 1;
1543
0
  }
1544
1545
0
  void DrawContent(Surface &surface, bool is_selected) {
1546
0
    int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1;
1547
0
    for (int i = 0; i < choices_to_draw; i++) {
1548
0
      surface.MoveCursor(0, i);
1549
0
      int current_choice = m_first_visibile_choice + i;
1550
0
      const char *text = m_choices[current_choice].c_str();
1551
0
      bool highlight = is_selected && current_choice == m_choice;
1552
0
      if (highlight)
1553
0
        surface.AttributeOn(A_REVERSE);
1554
0
      surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' ');
1555
0
      surface.PutCString(text);
1556
0
      if (highlight)
1557
0
        surface.AttributeOff(A_REVERSE);
1558
0
    }
1559
0
  }
1560
1561
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1562
0
    UpdateScrolling();
1563
1564
0
    surface.TitledBox(m_label.c_str());
1565
1566
0
    Rect content_bounds = surface.GetFrame();
1567
0
    content_bounds.Inset(1, 1);
1568
0
    Surface content_surface = surface.SubSurface(content_bounds);
1569
1570
0
    DrawContent(content_surface, is_selected);
1571
0
  }
1572
1573
0
  void SelectPrevious() {
1574
0
    if (m_choice > 0)
1575
0
      m_choice--;
1576
0
  }
1577
1578
0
  void SelectNext() {
1579
0
    if (m_choice < GetNumberOfChoices() - 1)
1580
0
      m_choice++;
1581
0
  }
1582
1583
0
  void UpdateScrolling() {
1584
0
    if (m_choice > GetLastVisibleChoice()) {
1585
0
      m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1);
1586
0
      return;
1587
0
    }
1588
1589
0
    if (m_choice < m_first_visibile_choice)
1590
0
      m_first_visibile_choice = m_choice;
1591
0
  }
1592
1593
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1594
0
    switch (key) {
1595
0
    case KEY_UP:
1596
0
      SelectPrevious();
1597
0
      return eKeyHandled;
1598
0
    case KEY_DOWN:
1599
0
      SelectNext();
1600
0
      return eKeyHandled;
1601
0
    default:
1602
0
      break;
1603
0
    }
1604
0
    return eKeyNotHandled;
1605
0
  }
1606
1607
  // Returns the content of the choice as a string.
1608
0
  std::string GetChoiceContent() { return m_choices[m_choice]; }
1609
1610
  // Returns the index of the choice.
1611
0
  int GetChoice() { return m_choice; }
1612
1613
0
  void SetChoice(llvm::StringRef choice) {
1614
0
    for (int i = 0; i < GetNumberOfChoices(); i++) {
1615
0
      if (choice == m_choices[i]) {
1616
0
        m_choice = i;
1617
0
        return;
1618
0
      }
1619
0
    }
1620
0
  }
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
0
      : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) {
1636
0
    PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform();
1637
0
    if (platform_sp)
1638
0
      SetChoice(platform_sp->GetPluginName());
1639
0
  }
1640
1641
0
  std::vector<std::string> GetPossiblePluginNames() {
1642
0
    std::vector<std::string> names;
1643
0
    size_t i = 0;
1644
0
    for (llvm::StringRef name =
1645
0
             PluginManager::GetPlatformPluginNameAtIndex(i++);
1646
0
         !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1647
0
      names.push_back(name.str());
1648
0
    return names;
1649
0
  }
1650
1651
0
  std::string GetPluginName() {
1652
0
    std::string plugin_name = GetChoiceContent();
1653
0
    return plugin_name;
1654
0
  }
1655
};
1656
1657
class ProcessPluginFieldDelegate : public ChoicesFieldDelegate {
1658
public:
1659
  ProcessPluginFieldDelegate()
1660
0
      : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {}
1661
1662
0
  std::vector<std::string> GetPossiblePluginNames() {
1663
0
    std::vector<std::string> names;
1664
0
    names.push_back("<default>");
1665
1666
0
    size_t i = 0;
1667
0
    for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++);
1668
0
         !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++))
1669
0
      names.push_back(name.str());
1670
0
    return names;
1671
0
  }
1672
1673
0
  std::string GetPluginName() {
1674
0
    std::string plugin_name = GetChoiceContent();
1675
0
    if (plugin_name == "<default>")
1676
0
      return "";
1677
0
    return plugin_name;
1678
0
  }
1679
};
1680
1681
class LazyBooleanFieldDelegate : public ChoicesFieldDelegate {
1682
public:
1683
  LazyBooleanFieldDelegate(const char *label, const char *calculate_label)
1684
0
      : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {}
1685
1686
  static constexpr const char *kNo = "No";
1687
  static constexpr const char *kYes = "Yes";
1688
1689
0
  std::vector<std::string> GetPossibleOptions(const char *calculate_label) {
1690
0
    std::vector<std::string> options;
1691
0
    options.push_back(calculate_label);
1692
0
    options.push_back(kYes);
1693
0
    options.push_back(kNo);
1694
0
    return options;
1695
0
  }
1696
1697
0
  LazyBool GetLazyBoolean() {
1698
0
    std::string choice = GetChoiceContent();
1699
0
    if (choice == kNo)
1700
0
      return eLazyBoolNo;
1701
0
    else if (choice == kYes)
1702
0
      return eLazyBoolYes;
1703
0
    else
1704
0
      return eLazyBoolCalculate;
1705
0
  }
1706
};
1707
1708
template <class T> class ListFieldDelegate : public FieldDelegate {
1709
public:
1710
  ListFieldDelegate(const char *label, T default_field)
1711
0
      : m_label(label), m_default_field(default_field),
1712
0
        m_selection_type(SelectionType::NewButton) {}
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::ListFieldDelegate(char const*, curses::TextFieldDelegate)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::ListFieldDelegate(char const*, curses::EnvironmentVariableFieldDelegate)
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
0
  int FieldDelegateGetHeight() override {
1734
    // 2 border characters.
1735
0
    int height = 2;
1736
    // Total height of the fields.
1737
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
1738
0
      height += m_fields[i].FieldDelegateGetHeight();
1739
0
    }
1740
    // A line for the New button.
1741
0
    height++;
1742
0
    return height;
1743
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateGetHeight()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateGetHeight()
1744
1745
0
  ScrollContext FieldDelegateGetScrollContext() override {
1746
0
    int height = FieldDelegateGetHeight();
1747
0
    if (m_selection_type == SelectionType::NewButton)
1748
0
      return ScrollContext(height - 2, height - 1);
1749
1750
0
    FieldDelegate &field = m_fields[m_selection_index];
1751
0
    ScrollContext context = field.FieldDelegateGetScrollContext();
1752
1753
    // Start at 1 because of the top border.
1754
0
    int offset = 1;
1755
0
    for (int i = 0; i < m_selection_index; i++) {
1756
0
      offset += m_fields[i].FieldDelegateGetHeight();
1757
0
    }
1758
0
    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
0
    if (context.start == 1)
1763
0
      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
0
    if (context.end == height - 3)
1768
0
      context.end += 2;
1769
1770
0
    return context;
1771
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateGetScrollContext()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateGetScrollContext()
1772
1773
0
  void DrawRemoveButton(Surface &surface, int highlight) {
1774
0
    surface.MoveCursor(1, surface.GetHeight() / 2);
1775
0
    if (highlight)
1776
0
      surface.AttributeOn(A_REVERSE);
1777
0
    surface.PutCString("[Remove]");
1778
0
    if (highlight)
1779
0
      surface.AttributeOff(A_REVERSE);
1780
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawRemoveButton(curses::Surface&, int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawRemoveButton(curses::Surface&, int)
1781
1782
0
  void DrawFields(Surface &surface, bool is_selected) {
1783
0
    int line = 0;
1784
0
    int width = surface.GetWidth();
1785
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
1786
0
      int height = m_fields[i].FieldDelegateGetHeight();
1787
0
      Rect bounds = Rect(Point(0, line), Size(width, height));
1788
0
      Rect field_bounds, remove_button_bounds;
1789
0
      bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"),
1790
0
                           field_bounds, remove_button_bounds);
1791
0
      Surface field_surface = surface.SubSurface(field_bounds);
1792
0
      Surface remove_button_surface = surface.SubSurface(remove_button_bounds);
1793
1794
0
      bool is_element_selected = m_selection_index == i && is_selected;
1795
0
      bool is_field_selected =
1796
0
          is_element_selected && m_selection_type == SelectionType::Field;
1797
0
      bool is_remove_button_selected =
1798
0
          is_element_selected &&
1799
0
          m_selection_type == SelectionType::RemoveButton;
1800
0
      m_fields[i].FieldDelegateDraw(field_surface, is_field_selected);
1801
0
      DrawRemoveButton(remove_button_surface, is_remove_button_selected);
1802
1803
0
      line += height;
1804
0
    }
1805
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawFields(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawFields(curses::Surface&, bool)
1806
1807
0
  void DrawNewButton(Surface &surface, bool is_selected) {
1808
0
    const char *button_text = "[New]";
1809
0
    int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2;
1810
0
    surface.MoveCursor(x, 0);
1811
0
    bool highlight =
1812
0
        is_selected && m_selection_type == SelectionType::NewButton;
1813
0
    if (highlight)
1814
0
      surface.AttributeOn(A_REVERSE);
1815
0
    surface.PutCString(button_text);
1816
0
    if (highlight)
1817
0
      surface.AttributeOff(A_REVERSE);
1818
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::DrawNewButton(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::DrawNewButton(curses::Surface&, bool)
1819
1820
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
1821
0
    surface.TitledBox(m_label.c_str());
1822
1823
0
    Rect content_bounds = surface.GetFrame();
1824
0
    content_bounds.Inset(1, 1);
1825
0
    Rect fields_bounds, new_button_bounds;
1826
0
    content_bounds.HorizontalSplit(content_bounds.size.height - 1,
1827
0
                                   fields_bounds, new_button_bounds);
1828
0
    Surface fields_surface = surface.SubSurface(fields_bounds);
1829
0
    Surface new_button_surface = surface.SubSurface(new_button_bounds);
1830
1831
0
    DrawFields(fields_surface, is_selected);
1832
0
    DrawNewButton(new_button_surface, is_selected);
1833
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateDraw(curses::Surface&, bool)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateDraw(curses::Surface&, bool)
1834
1835
0
  void AddNewField() {
1836
0
    m_fields.push_back(m_default_field);
1837
0
    m_selection_index = GetNumberOfFields() - 1;
1838
0
    m_selection_type = SelectionType::Field;
1839
0
    FieldDelegate &field = m_fields[m_selection_index];
1840
0
    field.FieldDelegateSelectFirstElement();
1841
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::AddNewField()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::AddNewField()
1842
1843
0
  void RemoveField() {
1844
0
    m_fields.erase(m_fields.begin() + m_selection_index);
1845
0
    if (m_selection_index != 0)
1846
0
      m_selection_index--;
1847
1848
0
    if (GetNumberOfFields() > 0) {
1849
0
      m_selection_type = SelectionType::Field;
1850
0
      FieldDelegate &field = m_fields[m_selection_index];
1851
0
      field.FieldDelegateSelectFirstElement();
1852
0
    } else
1853
0
      m_selection_type = SelectionType::NewButton;
1854
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::RemoveField()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::RemoveField()
1855
1856
0
  HandleCharResult SelectNext(int key) {
1857
0
    if (m_selection_type == SelectionType::NewButton)
1858
0
      return eKeyNotHandled;
1859
1860
0
    if (m_selection_type == SelectionType::RemoveButton) {
1861
0
      if (m_selection_index == GetNumberOfFields() - 1) {
1862
0
        m_selection_type = SelectionType::NewButton;
1863
0
        return eKeyHandled;
1864
0
      }
1865
0
      m_selection_index++;
1866
0
      m_selection_type = SelectionType::Field;
1867
0
      FieldDelegate &next_field = m_fields[m_selection_index];
1868
0
      next_field.FieldDelegateSelectFirstElement();
1869
0
      return eKeyHandled;
1870
0
    }
1871
1872
0
    FieldDelegate &field = m_fields[m_selection_index];
1873
0
    if (!field.FieldDelegateOnLastOrOnlyElement()) {
1874
0
      return field.FieldDelegateHandleChar(key);
1875
0
    }
1876
1877
0
    field.FieldDelegateExitCallback();
1878
1879
0
    m_selection_type = SelectionType::RemoveButton;
1880
0
    return eKeyHandled;
1881
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectNext(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectNext(int)
1882
1883
0
  HandleCharResult SelectPrevious(int key) {
1884
0
    if (FieldDelegateOnFirstOrOnlyElement())
1885
0
      return eKeyNotHandled;
1886
1887
0
    if (m_selection_type == SelectionType::RemoveButton) {
1888
0
      m_selection_type = SelectionType::Field;
1889
0
      FieldDelegate &field = m_fields[m_selection_index];
1890
0
      field.FieldDelegateSelectLastElement();
1891
0
      return eKeyHandled;
1892
0
    }
1893
1894
0
    if (m_selection_type == SelectionType::NewButton) {
1895
0
      m_selection_type = SelectionType::RemoveButton;
1896
0
      m_selection_index = GetNumberOfFields() - 1;
1897
0
      return eKeyHandled;
1898
0
    }
1899
1900
0
    FieldDelegate &field = m_fields[m_selection_index];
1901
0
    if (!field.FieldDelegateOnFirstOrOnlyElement()) {
1902
0
      return field.FieldDelegateHandleChar(key);
1903
0
    }
1904
1905
0
    field.FieldDelegateExitCallback();
1906
1907
0
    m_selection_type = SelectionType::RemoveButton;
1908
0
    m_selection_index--;
1909
0
    return eKeyHandled;
1910
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectPrevious(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectPrevious(int)
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
0
  HandleCharResult SelectNextInList(int key) {
1915
0
    assert(m_selection_type == SelectionType::Field);
1916
1917
0
    FieldDelegate &field = m_fields[m_selection_index];
1918
0
    if (field.FieldDelegateHandleChar(key) == eKeyHandled)
1919
0
      return eKeyHandled;
1920
1921
0
    if (!field.FieldDelegateOnLastOrOnlyElement())
1922
0
      return eKeyNotHandled;
1923
1924
0
    field.FieldDelegateExitCallback();
1925
1926
0
    if (m_selection_index == GetNumberOfFields() - 1) {
1927
0
      m_selection_type = SelectionType::NewButton;
1928
0
      return eKeyHandled;
1929
0
    }
1930
1931
0
    m_selection_index++;
1932
0
    FieldDelegate &next_field = m_fields[m_selection_index];
1933
0
    next_field.FieldDelegateSelectFirstElement();
1934
0
    return eKeyHandled;
1935
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::SelectNextInList(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::SelectNextInList(int)
1936
1937
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
1938
0
    switch (key) {
1939
0
    case '\r':
1940
0
    case '\n':
1941
0
    case KEY_ENTER:
1942
0
      switch (m_selection_type) {
1943
0
      case SelectionType::NewButton:
1944
0
        AddNewField();
1945
0
        return eKeyHandled;
1946
0
      case SelectionType::RemoveButton:
1947
0
        RemoveField();
1948
0
        return eKeyHandled;
1949
0
      case SelectionType::Field:
1950
0
        return SelectNextInList(key);
1951
0
      }
1952
0
      break;
1953
0
    case '\t':
1954
0
      return SelectNext(key);
1955
0
    case KEY_SHIFT_TAB:
1956
0
      return SelectPrevious(key);
1957
0
    default:
1958
0
      break;
1959
0
    }
1960
1961
    // If the key wasn't handled and one of the fields is selected, pass the key
1962
    // to that field.
1963
0
    if (m_selection_type == SelectionType::Field) {
1964
0
      return m_fields[m_selection_index].FieldDelegateHandleChar(key);
1965
0
    }
1966
1967
0
    return eKeyNotHandled;
1968
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateHandleChar(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateHandleChar(int)
1969
1970
0
  bool FieldDelegateOnLastOrOnlyElement() override {
1971
0
    if (m_selection_type == SelectionType::NewButton) {
1972
0
      return true;
1973
0
    }
1974
0
    return false;
1975
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateOnLastOrOnlyElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateOnLastOrOnlyElement()
1976
1977
0
  bool FieldDelegateOnFirstOrOnlyElement() override {
1978
0
    if (m_selection_type == SelectionType::NewButton &&
1979
0
        GetNumberOfFields() == 0)
1980
0
      return true;
1981
1982
0
    if (m_selection_type == SelectionType::Field && m_selection_index == 0) {
1983
0
      FieldDelegate &field = m_fields[m_selection_index];
1984
0
      return field.FieldDelegateOnFirstOrOnlyElement();
1985
0
    }
1986
1987
0
    return false;
1988
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateOnFirstOrOnlyElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateOnFirstOrOnlyElement()
1989
1990
0
  void FieldDelegateSelectFirstElement() override {
1991
0
    if (GetNumberOfFields() == 0) {
1992
0
      m_selection_type = SelectionType::NewButton;
1993
0
      return;
1994
0
    }
1995
1996
0
    m_selection_type = SelectionType::Field;
1997
0
    m_selection_index = 0;
1998
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateSelectFirstElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateSelectFirstElement()
1999
2000
0
  void FieldDelegateSelectLastElement() override {
2001
0
    m_selection_type = SelectionType::NewButton;
2002
0
  }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::FieldDelegateSelectLastElement()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::FieldDelegateSelectLastElement()
2003
2004
0
  int GetNumberOfFields() { return m_fields.size(); }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::GetNumberOfFields()
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::GetNumberOfFields()
2005
2006
  // Returns the form delegate at the current index.
2007
0
  T &GetField(int index) { return m_fields[index]; }
Unexecuted instantiation: curses::ListFieldDelegate<curses::TextFieldDelegate>::GetField(int)
Unexecuted instantiation: curses::ListFieldDelegate<curses::EnvironmentVariableFieldDelegate>::GetField(int)
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
0
      : ListFieldDelegate("Arguments",
2024
0
                          TextFieldDelegate("Argument", "", false)) {}
2025
2026
0
  Args GetArguments() {
2027
0
    Args arguments;
2028
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2029
0
      arguments.AppendArgument(GetField(i).GetText());
2030
0
    }
2031
0
    return arguments;
2032
0
  }
2033
2034
0
  void AddArguments(const Args &arguments) {
2035
0
    for (size_t i = 0; i < arguments.GetArgumentCount(); i++) {
2036
0
      AddNewField();
2037
0
      TextFieldDelegate &field = GetField(GetNumberOfFields() - 1);
2038
0
      field.SetText(arguments.GetArgumentAtIndex(i));
2039
0
    }
2040
0
  }
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
0
      : m_key_field(key_field), m_value_field(value_field),
2049
0
        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
0
  int FieldDelegateGetHeight() override {
2066
0
    return std::max(m_key_field.FieldDelegateGetHeight(),
2067
0
                    m_value_field.FieldDelegateGetHeight());
2068
0
  }
2069
2070
0
  void DrawArrow(Surface &surface) {
2071
0
    surface.MoveCursor(0, 1);
2072
0
    surface.PutChar(ACS_RARROW);
2073
0
  }
2074
2075
0
  void FieldDelegateDraw(Surface &surface, bool is_selected) override {
2076
0
    Rect bounds = surface.GetFrame();
2077
0
    Rect key_field_bounds, arrow_and_value_field_bounds;
2078
0
    bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds,
2079
0
                         arrow_and_value_field_bounds);
2080
0
    Rect arrow_bounds, value_field_bounds;
2081
0
    arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds,
2082
0
                                               value_field_bounds);
2083
2084
0
    Surface key_field_surface = surface.SubSurface(key_field_bounds);
2085
0
    Surface arrow_surface = surface.SubSurface(arrow_bounds);
2086
0
    Surface value_field_surface = surface.SubSurface(value_field_bounds);
2087
2088
0
    bool key_is_selected =
2089
0
        m_selection_type == SelectionType::Key && is_selected;
2090
0
    m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected);
2091
0
    DrawArrow(arrow_surface);
2092
0
    bool value_is_selected =
2093
0
        m_selection_type == SelectionType::Value && is_selected;
2094
0
    m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected);
2095
0
  }
2096
2097
0
  HandleCharResult SelectNext(int key) {
2098
0
    if (FieldDelegateOnLastOrOnlyElement())
2099
0
      return eKeyNotHandled;
2100
2101
0
    if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) {
2102
0
      return m_key_field.FieldDelegateHandleChar(key);
2103
0
    }
2104
2105
0
    m_key_field.FieldDelegateExitCallback();
2106
0
    m_selection_type = SelectionType::Value;
2107
0
    m_value_field.FieldDelegateSelectFirstElement();
2108
0
    return eKeyHandled;
2109
0
  }
2110
2111
0
  HandleCharResult SelectPrevious(int key) {
2112
0
    if (FieldDelegateOnFirstOrOnlyElement())
2113
0
      return eKeyNotHandled;
2114
2115
0
    if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) {
2116
0
      return m_value_field.FieldDelegateHandleChar(key);
2117
0
    }
2118
2119
0
    m_value_field.FieldDelegateExitCallback();
2120
0
    m_selection_type = SelectionType::Key;
2121
0
    m_key_field.FieldDelegateSelectLastElement();
2122
0
    return eKeyHandled;
2123
0
  }
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
0
  HandleCharResult SelectNextField(int key) {
2129
0
    if (m_selection_type == SelectionType::Value) {
2130
0
      return m_value_field.FieldDelegateHandleChar(key);
2131
0
    }
2132
2133
0
    if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled)
2134
0
      return eKeyHandled;
2135
2136
0
    if (!m_key_field.FieldDelegateOnLastOrOnlyElement())
2137
0
      return eKeyNotHandled;
2138
2139
0
    m_key_field.FieldDelegateExitCallback();
2140
0
    m_selection_type = SelectionType::Value;
2141
0
    m_value_field.FieldDelegateSelectFirstElement();
2142
0
    return eKeyHandled;
2143
0
  }
2144
2145
0
  HandleCharResult FieldDelegateHandleChar(int key) override {
2146
0
    switch (key) {
2147
0
    case KEY_RETURN:
2148
0
      return SelectNextField(key);
2149
0
    case '\t':
2150
0
      return SelectNext(key);
2151
0
    case KEY_SHIFT_TAB:
2152
0
      return SelectPrevious(key);
2153
0
    default:
2154
0
      break;
2155
0
    }
2156
2157
    // If the key wasn't handled, pass the key to the selected field.
2158
0
    if (m_selection_type == SelectionType::Key)
2159
0
      return m_key_field.FieldDelegateHandleChar(key);
2160
0
    else
2161
0
      return m_value_field.FieldDelegateHandleChar(key);
2162
2163
0
    return eKeyNotHandled;
2164
0
  }
2165
2166
0
  bool FieldDelegateOnFirstOrOnlyElement() override {
2167
0
    return m_selection_type == SelectionType::Key;
2168
0
  }
2169
2170
0
  bool FieldDelegateOnLastOrOnlyElement() override {
2171
0
    return m_selection_type == SelectionType::Value;
2172
0
  }
2173
2174
0
  void FieldDelegateSelectFirstElement() override {
2175
0
    m_selection_type = SelectionType::Key;
2176
0
  }
2177
2178
0
  void FieldDelegateSelectLastElement() override {
2179
0
    m_selection_type = SelectionType::Value;
2180
0
  }
2181
2182
0
  bool FieldDelegateHasError() override {
2183
0
    return m_key_field.FieldDelegateHasError() ||
2184
0
           m_value_field.FieldDelegateHasError();
2185
0
  }
2186
2187
0
  KeyFieldDelegateType &GetKeyField() { return m_key_field; }
2188
2189
0
  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
0
      : TextFieldDelegate("Name", content, true) {}
2202
2203
  // Environment variable names can't contain an equal sign.
2204
0
  bool IsAcceptableChar(int key) override {
2205
0
    return TextFieldDelegate::IsAcceptableChar(key) && key != '=';
2206
0
  }
2207
2208
0
  const std::string &GetName() { return m_content; }
2209
};
2210
2211
class EnvironmentVariableFieldDelegate
2212
    : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate,
2213
                                  TextFieldDelegate> {
2214
public:
2215
  EnvironmentVariableFieldDelegate()
2216
0
      : MappingFieldDelegate(
2217
0
            EnvironmentVariableNameFieldDelegate(""),
2218
0
            TextFieldDelegate("Value", "", /*required=*/false)) {}
2219
2220
0
  const std::string &GetName() { return GetKeyField().GetName(); }
2221
2222
0
  const std::string &GetValue() { return GetValueField().GetText(); }
2223
2224
0
  void SetName(const char *name) { return GetKeyField().SetText(name); }
2225
2226
0
  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
0
      : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {}
2234
2235
0
  Environment GetEnvironment() {
2236
0
    Environment environment;
2237
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2238
0
      environment.insert(
2239
0
          std::make_pair(GetField(i).GetName(), GetField(i).GetValue()));
2240
0
    }
2241
0
    return environment;
2242
0
  }
2243
2244
0
  void AddEnvironmentVariables(const Environment &environment) {
2245
0
    for (auto &variable : environment) {
2246
0
      AddNewField();
2247
0
      EnvironmentVariableFieldDelegate &field =
2248
0
          GetField(GetNumberOfFields() - 1);
2249
0
      field.SetName(variable.getKey().str().c_str());
2250
0
      field.SetValue(variable.getValue().c_str());
2251
0
    }
2252
0
  }
2253
};
2254
2255
class FormAction {
2256
public:
2257
  FormAction(const char *label, std::function<void(Window &)> action)
2258
0
      : m_action(action) {
2259
0
    if (label)
2260
0
      m_label = label;
2261
0
  }
2262
2263
  // Draw a centered [Label].
2264
0
  void Draw(Surface &surface, bool is_selected) {
2265
0
    int x = (surface.GetWidth() - m_label.length()) / 2;
2266
0
    surface.MoveCursor(x, 0);
2267
0
    if (is_selected)
2268
0
      surface.AttributeOn(A_REVERSE);
2269
0
    surface.PutChar('[');
2270
0
    surface.PutCString(m_label.c_str());
2271
0
    surface.PutChar(']');
2272
0
    if (is_selected)
2273
0
      surface.AttributeOff(A_REVERSE);
2274
0
  }
2275
2276
0
  void Execute(Window &window) { m_action(window); }
2277
2278
0
  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
0
  FormDelegate() = default;
2288
2289
0
  virtual ~FormDelegate() = default;
2290
2291
  virtual std::string GetName() = 0;
2292
2293
0
  virtual void UpdateFieldsVisibility() {}
2294
2295
0
  FieldDelegate *GetField(uint32_t field_index) {
2296
0
    if (field_index < m_fields.size())
2297
0
      return m_fields[field_index].get();
2298
0
    return nullptr;
2299
0
  }
2300
2301
0
  FormAction &GetAction(int action_index) { return m_actions[action_index]; }
2302
2303
0
  int GetNumberOfFields() { return m_fields.size(); }
2304
2305
0
  int GetNumberOfActions() { return m_actions.size(); }
2306
2307
0
  bool HasError() { return !m_error.empty(); }
2308
2309
0
  void ClearError() { m_error.clear(); }
2310
2311
0
  const std::string &GetError() { return m_error; }
2312
2313
0
  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
0
  bool CheckFieldsValidity() {
2319
0
    for (int i = 0; i < GetNumberOfFields(); i++) {
2320
0
      GetField(i)->FieldDelegateExitCallback();
2321
0
      if (GetField(i)->FieldDelegateHasError()) {
2322
0
        SetError("Some fields are invalid!");
2323
0
        return false;
2324
0
      }
2325
0
    }
2326
0
    return true;
2327
0
  }
2328
2329
  // Factory methods to create and add fields of specific types.
2330
2331
  TextFieldDelegate *AddTextField(const char *label, const char *content,
2332
0
                                  bool required) {
2333
0
    TextFieldDelegate *delegate =
2334
0
        new TextFieldDelegate(label, content, required);
2335
0
    m_fields.push_back(FieldDelegateUP(delegate));
2336
0
    return delegate;
2337
0
  }
2338
2339
  FileFieldDelegate *AddFileField(const char *label, const char *content,
2340
0
                                  bool need_to_exist, bool required) {
2341
0
    FileFieldDelegate *delegate =
2342
0
        new FileFieldDelegate(label, content, need_to_exist, required);
2343
0
    m_fields.push_back(FieldDelegateUP(delegate));
2344
0
    return delegate;
2345
0
  }
2346
2347
  DirectoryFieldDelegate *AddDirectoryField(const char *label,
2348
                                            const char *content,
2349
0
                                            bool need_to_exist, bool required) {
2350
0
    DirectoryFieldDelegate *delegate =
2351
0
        new DirectoryFieldDelegate(label, content, need_to_exist, required);
2352
0
    m_fields.push_back(FieldDelegateUP(delegate));
2353
0
    return delegate;
2354
0
  }
2355
2356
  ArchFieldDelegate *AddArchField(const char *label, const char *content,
2357
0
                                  bool required) {
2358
0
    ArchFieldDelegate *delegate =
2359
0
        new ArchFieldDelegate(label, content, required);
2360
0
    m_fields.push_back(FieldDelegateUP(delegate));
2361
0
    return delegate;
2362
0
  }
2363
2364
  IntegerFieldDelegate *AddIntegerField(const char *label, int content,
2365
0
                                        bool required) {
2366
0
    IntegerFieldDelegate *delegate =
2367
0
        new IntegerFieldDelegate(label, content, required);
2368
0
    m_fields.push_back(FieldDelegateUP(delegate));
2369
0
    return delegate;
2370
0
  }
2371
2372
0
  BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
2373
0
    BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
2374
0
    m_fields.push_back(FieldDelegateUP(delegate));
2375
0
    return delegate;
2376
0
  }
2377
2378
  LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label,
2379
0
                                                const char *calculate_label) {
2380
0
    LazyBooleanFieldDelegate *delegate =
2381
0
        new LazyBooleanFieldDelegate(label, calculate_label);
2382
0
    m_fields.push_back(FieldDelegateUP(delegate));
2383
0
    return delegate;
2384
0
  }
2385
2386
  ChoicesFieldDelegate *AddChoicesField(const char *label, int height,
2387
0
                                        std::vector<std::string> choices) {
2388
0
    ChoicesFieldDelegate *delegate =
2389
0
        new ChoicesFieldDelegate(label, height, choices);
2390
0
    m_fields.push_back(FieldDelegateUP(delegate));
2391
0
    return delegate;
2392
0
  }
2393
2394
0
  PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) {
2395
0
    PlatformPluginFieldDelegate *delegate =
2396
0
        new PlatformPluginFieldDelegate(debugger);
2397
0
    m_fields.push_back(FieldDelegateUP(delegate));
2398
0
    return delegate;
2399
0
  }
2400
2401
0
  ProcessPluginFieldDelegate *AddProcessPluginField() {
2402
0
    ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate();
2403
0
    m_fields.push_back(FieldDelegateUP(delegate));
2404
0
    return delegate;
2405
0
  }
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
0
  ArgumentsFieldDelegate *AddArgumentsField() {
2416
0
    ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate();
2417
0
    m_fields.push_back(FieldDelegateUP(delegate));
2418
0
    return delegate;
2419
0
  }
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
0
  AddEnvironmentVariableNameField(const char *content) {
2431
0
    EnvironmentVariableNameFieldDelegate *delegate =
2432
0
        new EnvironmentVariableNameFieldDelegate(content);
2433
0
    m_fields.push_back(FieldDelegateUP(delegate));
2434
0
    return delegate;
2435
0
  }
2436
2437
0
  EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() {
2438
0
    EnvironmentVariableFieldDelegate *delegate =
2439
0
        new EnvironmentVariableFieldDelegate();
2440
0
    m_fields.push_back(FieldDelegateUP(delegate));
2441
0
    return delegate;
2442
0
  }
2443
2444
  EnvironmentVariableListFieldDelegate *
2445
0
  AddEnvironmentVariableListField(const char *label) {
2446
0
    EnvironmentVariableListFieldDelegate *delegate =
2447
0
        new EnvironmentVariableListFieldDelegate(label);
2448
0
    m_fields.push_back(FieldDelegateUP(delegate));
2449
0
    return delegate;
2450
0
  }
2451
2452
  // Factory methods for adding actions.
2453
2454
0
  void AddAction(const char *label, std::function<void(Window &)> action) {
2455
0
    m_actions.push_back(FormAction(label, action));
2456
0
  }
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
0
  FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) {
2470
0
    assert(m_delegate_sp->GetNumberOfActions() > 0);
2471
0
    if (m_delegate_sp->GetNumberOfFields() > 0)
2472
0
      m_selection_type = SelectionType::Field;
2473
0
    else
2474
0
      m_selection_type = SelectionType::Action;
2475
0
  }
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
0
  int GetErrorHeight() {
2499
0
    if (m_delegate_sp->HasError())
2500
0
      return 2;
2501
0
    return 0;
2502
0
  }
2503
2504
  // Actions span a single line.
2505
0
  int GetActionsHeight() {
2506
0
    if (m_delegate_sp->GetNumberOfActions() > 0)
2507
0
      return 1;
2508
0
    return 0;
2509
0
  }
2510
2511
  // Get the total number of needed lines to draw the contents.
2512
0
  int GetContentHeight() {
2513
0
    int height = 0;
2514
0
    height += GetErrorHeight();
2515
0
    for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2516
0
      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2517
0
        continue;
2518
0
      height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2519
0
    }
2520
0
    height += GetActionsHeight();
2521
0
    return height;
2522
0
  }
2523
2524
0
  ScrollContext GetScrollContext() {
2525
0
    if (m_selection_type == SelectionType::Action)
2526
0
      return ScrollContext(GetContentHeight() - 1);
2527
2528
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2529
0
    ScrollContext context = field->FieldDelegateGetScrollContext();
2530
2531
0
    int offset = GetErrorHeight();
2532
0
    for (int i = 0; i < m_selection_index; i++) {
2533
0
      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
2534
0
        continue;
2535
0
      offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
2536
0
    }
2537
0
    context.Offset(offset);
2538
2539
    // If the context is touching the error, include the error in the context as
2540
    // well.
2541
0
    if (context.start == GetErrorHeight())
2542
0
      context.start = 0;
2543
2544
0
    return context;
2545
0
  }
2546
2547
0
  void UpdateScrolling(Surface &surface) {
2548
0
    ScrollContext context = GetScrollContext();
2549
0
    int content_height = GetContentHeight();
2550
0
    int surface_height = surface.GetHeight();
2551
0
    int visible_height = std::min(content_height, surface_height);
2552
0
    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
0
    if (last_visible_line > content_height - 1) {
2558
0
      m_first_visible_line = content_height - visible_height;
2559
0
    }
2560
2561
0
    if (context.start < m_first_visible_line) {
2562
0
      m_first_visible_line = context.start;
2563
0
      return;
2564
0
    }
2565
2566
0
    if (context.end > last_visible_line) {
2567
0
      m_first_visible_line = context.end - visible_height + 1;
2568
0
    }
2569
0
  }
2570
2571
0
  void DrawError(Surface &surface) {
2572
0
    if (!m_delegate_sp->HasError())
2573
0
      return;
2574
0
    surface.MoveCursor(0, 0);
2575
0
    surface.AttributeOn(COLOR_PAIR(RedOnBlack));
2576
0
    surface.PutChar(ACS_DIAMOND);
2577
0
    surface.PutChar(' ');
2578
0
    surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str());
2579
0
    surface.AttributeOff(COLOR_PAIR(RedOnBlack));
2580
2581
0
    surface.MoveCursor(0, 1);
2582
0
    surface.HorizontalLine(surface.GetWidth());
2583
0
  }
2584
2585
0
  void DrawFields(Surface &surface) {
2586
0
    int line = 0;
2587
0
    int width = surface.GetWidth();
2588
0
    bool a_field_is_selected = m_selection_type == SelectionType::Field;
2589
0
    for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
2590
0
      FieldDelegate *field = m_delegate_sp->GetField(i);
2591
0
      if (!field->FieldDelegateIsVisible())
2592
0
        continue;
2593
0
      bool is_field_selected = a_field_is_selected && m_selection_index == i;
2594
0
      int height = field->FieldDelegateGetHeight();
2595
0
      Rect bounds = Rect(Point(0, line), Size(width, height));
2596
0
      Surface field_surface = surface.SubSurface(bounds);
2597
0
      field->FieldDelegateDraw(field_surface, is_field_selected);
2598
0
      line += height;
2599
0
    }
2600
0
  }
2601
2602
0
  void DrawActions(Surface &surface) {
2603
0
    int number_of_actions = m_delegate_sp->GetNumberOfActions();
2604
0
    int width = surface.GetWidth() / number_of_actions;
2605
0
    bool an_action_is_selected = m_selection_type == SelectionType::Action;
2606
0
    int x = 0;
2607
0
    for (int i = 0; i < number_of_actions; i++) {
2608
0
      bool is_action_selected = an_action_is_selected && m_selection_index == i;
2609
0
      FormAction &action = m_delegate_sp->GetAction(i);
2610
0
      Rect bounds = Rect(Point(x, 0), Size(width, 1));
2611
0
      Surface action_surface = surface.SubSurface(bounds);
2612
0
      action.Draw(action_surface, is_action_selected);
2613
0
      x += width;
2614
0
    }
2615
0
  }
2616
2617
0
  void DrawElements(Surface &surface) {
2618
0
    Rect frame = surface.GetFrame();
2619
0
    Rect fields_bounds, actions_bounds;
2620
0
    frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(),
2621
0
                          fields_bounds, actions_bounds);
2622
0
    Surface fields_surface = surface.SubSurface(fields_bounds);
2623
0
    Surface actions_surface = surface.SubSurface(actions_bounds);
2624
2625
0
    DrawFields(fields_surface);
2626
0
    DrawActions(actions_surface);
2627
0
  }
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
0
  void DrawContent(Surface &surface) {
2633
0
    UpdateScrolling(surface);
2634
2635
0
    int width = surface.GetWidth();
2636
0
    int height = GetContentHeight();
2637
0
    Pad pad = Pad(Size(width, height));
2638
2639
0
    Rect frame = pad.GetFrame();
2640
0
    Rect error_bounds, elements_bounds;
2641
0
    frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds);
2642
0
    Surface error_surface = pad.SubSurface(error_bounds);
2643
0
    Surface elements_surface = pad.SubSurface(elements_bounds);
2644
2645
0
    DrawError(error_surface);
2646
0
    DrawElements(elements_surface);
2647
2648
0
    int copy_height = std::min(surface.GetHeight(), pad.GetHeight());
2649
0
    pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(),
2650
0
                      Size(width, copy_height));
2651
0
  }
2652
2653
0
  void DrawSubmitHint(Surface &surface, bool is_active) {
2654
0
    surface.MoveCursor(2, surface.GetHeight() - 1);
2655
0
    if (is_active)
2656
0
      surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite));
2657
0
    surface.Printf("[Press Alt+Enter to %s]",
2658
0
                   m_delegate_sp->GetAction(0).GetLabel().c_str());
2659
0
    if (is_active)
2660
0
      surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite));
2661
0
  }
2662
2663
0
  bool WindowDelegateDraw(Window &window, bool force) override {
2664
0
    m_delegate_sp->UpdateFieldsVisibility();
2665
2666
0
    window.Erase();
2667
2668
0
    window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
2669
0
                        "Press Esc to Cancel");
2670
0
    DrawSubmitHint(window, window.IsActive());
2671
2672
0
    Rect content_bounds = window.GetFrame();
2673
0
    content_bounds.Inset(2, 2);
2674
0
    Surface content_surface = window.SubSurface(content_bounds);
2675
2676
0
    DrawContent(content_surface);
2677
0
    return true;
2678
0
  }
2679
2680
0
  void SkipNextHiddenFields() {
2681
0
    while (true) {
2682
0
      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2683
0
        return;
2684
2685
0
      if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2686
0
        m_selection_type = SelectionType::Action;
2687
0
        m_selection_index = 0;
2688
0
        return;
2689
0
      }
2690
2691
0
      m_selection_index++;
2692
0
    }
2693
0
  }
2694
2695
0
  HandleCharResult SelectNext(int key) {
2696
0
    if (m_selection_type == SelectionType::Action) {
2697
0
      if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
2698
0
        m_selection_index++;
2699
0
        return eKeyHandled;
2700
0
      }
2701
2702
0
      m_selection_index = 0;
2703
0
      m_selection_type = SelectionType::Field;
2704
0
      SkipNextHiddenFields();
2705
0
      if (m_selection_type == SelectionType::Field) {
2706
0
        FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2707
0
        next_field->FieldDelegateSelectFirstElement();
2708
0
      }
2709
0
      return eKeyHandled;
2710
0
    }
2711
2712
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2713
0
    if (!field->FieldDelegateOnLastOrOnlyElement()) {
2714
0
      return field->FieldDelegateHandleChar(key);
2715
0
    }
2716
2717
0
    field->FieldDelegateExitCallback();
2718
2719
0
    if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
2720
0
      m_selection_type = SelectionType::Action;
2721
0
      m_selection_index = 0;
2722
0
      return eKeyHandled;
2723
0
    }
2724
2725
0
    m_selection_index++;
2726
0
    SkipNextHiddenFields();
2727
2728
0
    if (m_selection_type == SelectionType::Field) {
2729
0
      FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
2730
0
      next_field->FieldDelegateSelectFirstElement();
2731
0
    }
2732
2733
0
    return eKeyHandled;
2734
0
  }
2735
2736
0
  void SkipPreviousHiddenFields() {
2737
0
    while (true) {
2738
0
      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
2739
0
        return;
2740
2741
0
      if (m_selection_index == 0) {
2742
0
        m_selection_type = SelectionType::Action;
2743
0
        m_selection_index = 0;
2744
0
        return;
2745
0
      }
2746
2747
0
      m_selection_index--;
2748
0
    }
2749
0
  }
2750
2751
0
  HandleCharResult SelectPrevious(int key) {
2752
0
    if (m_selection_type == SelectionType::Action) {
2753
0
      if (m_selection_index > 0) {
2754
0
        m_selection_index--;
2755
0
        return eKeyHandled;
2756
0
      }
2757
0
      m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
2758
0
      m_selection_type = SelectionType::Field;
2759
0
      SkipPreviousHiddenFields();
2760
0
      if (m_selection_type == SelectionType::Field) {
2761
0
        FieldDelegate *previous_field =
2762
0
            m_delegate_sp->GetField(m_selection_index);
2763
0
        previous_field->FieldDelegateSelectLastElement();
2764
0
      }
2765
0
      return eKeyHandled;
2766
0
    }
2767
2768
0
    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2769
0
    if (!field->FieldDelegateOnFirstOrOnlyElement()) {
2770
0
      return field->FieldDelegateHandleChar(key);
2771
0
    }
2772
2773
0
    field->FieldDelegateExitCallback();
2774
2775
0
    if (m_selection_index == 0) {
2776
0
      m_selection_type = SelectionType::Action;
2777
0
      m_selection_index = m_delegate_sp->GetNumberOfActions() - 1;
2778
0
      return eKeyHandled;
2779
0
    }
2780
2781
0
    m_selection_index--;
2782
0
    SkipPreviousHiddenFields();
2783
2784
0
    if (m_selection_type == SelectionType::Field) {
2785
0
      FieldDelegate *previous_field =
2786
0
          m_delegate_sp->GetField(m_selection_index);
2787
0
      previous_field->FieldDelegateSelectLastElement();
2788
0
    }
2789
2790
0
    return eKeyHandled;
2791
0
  }
2792
2793
0
  void ExecuteAction(Window &window, int index) {
2794
0
    FormAction &action = m_delegate_sp->GetAction(index);
2795
0
    action.Execute(window);
2796
0
    if (m_delegate_sp->HasError()) {
2797
0
      m_first_visible_line = 0;
2798
0
      m_selection_index = 0;
2799
0
      m_selection_type = SelectionType::Field;
2800
0
    }
2801
0
  }
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
0
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
2806
0
    switch (key) {
2807
0
    case '\r':
2808
0
    case '\n':
2809
0
    case KEY_ENTER:
2810
0
      if (m_selection_type == SelectionType::Action) {
2811
0
        ExecuteAction(window, m_selection_index);
2812
0
        return eKeyHandled;
2813
0
      }
2814
0
      break;
2815
0
    case KEY_ALT_ENTER:
2816
0
      ExecuteAction(window, 0);
2817
0
      return eKeyHandled;
2818
0
    case '\t':
2819
0
      SelectNext(key);
2820
0
      return eKeyHandled;
2821
0
    case KEY_SHIFT_TAB:
2822
0
      SelectPrevious(key);
2823
0
      return eKeyHandled;
2824
0
    case KEY_ESCAPE:
2825
0
      window.GetParent()->RemoveSubWindow(&window);
2826
0
      return eKeyHandled;
2827
0
    default:
2828
0
      break;
2829
0
    }
2830
2831
    // If the key wasn't handled and one of the fields is selected, pass the key
2832
    // to that field.
2833
0
    if (m_selection_type == SelectionType::Field) {
2834
0
      FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
2835
0
      if (field->FieldDelegateHandleChar(key) == eKeyHandled)
2836
0
        return eKeyHandled;
2837
0
    }
2838
2839
    // If the key wasn't handled by the possibly selected field, handle some
2840
    // extra keys for navigation.
2841
0
    switch (key) {
2842
0
    case KEY_DOWN:
2843
0
      SelectNext(key);
2844
0
      return eKeyHandled;
2845
0
    case KEY_UP:
2846
0
      SelectPrevious(key);
2847
0
      return eKeyHandled;
2848
0
    default:
2849
0
      break;
2850
0
    }
2851
2852
0
    return eKeyHandled;
2853
0
  }
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
0
  DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
2872
0
    SetError("There is a running process, either detach or kill it.");
2873
2874
0
    m_keep_stopped_field =
2875
0
        AddBooleanField("Keep process stopped when detaching.", false);
2876
2877
0
    AddAction("Detach", [this](Window &window) { Detach(window); });
2878
0
    AddAction("Kill", [this](Window &window) { Kill(window); });
2879
0
  }
2880
2881
0
  std::string GetName() override { return "Detach/Kill Process"; }
2882
2883
0
  void Kill(Window &window) {
2884
0
    Status destroy_status(m_process->Destroy(false));
2885
0
    if (destroy_status.Fail()) {
2886
0
      SetError("Failed to kill process.");
2887
0
      return;
2888
0
    }
2889
0
    window.GetParent()->RemoveSubWindow(&window);
2890
0
  }
2891
2892
0
  void Detach(Window &window) {
2893
0
    Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
2894
0
    if (detach_status.Fail()) {
2895
0
      SetError("Failed to detach from process.");
2896
0
      return;
2897
0
    }
2898
0
    window.GetParent()->RemoveSubWindow(&window);
2899
0
  }
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
0
      : m_debugger(debugger), m_main_window_sp(main_window_sp) {
2910
0
    std::vector<std::string> types;
2911
0
    types.push_back(std::string("Name"));
2912
0
    types.push_back(std::string("PID"));
2913
0
    m_type_field = AddChoicesField("Attach By", 2, types);
2914
0
    m_pid_field = AddIntegerField("PID", 0, true);
2915
0
    m_name_field =
2916
0
        AddTextField("Process Name", GetDefaultProcessName().c_str(), true);
2917
0
    m_continue_field = AddBooleanField("Continue once attached.", false);
2918
0
    m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
2919
0
    m_include_existing_field =
2920
0
        AddBooleanField("Include existing processes.", false);
2921
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
2922
0
    m_plugin_field = AddProcessPluginField();
2923
2924
0
    AddAction("Attach", [this](Window &window) { Attach(window); });
2925
0
  }
2926
2927
0
  std::string GetName() override { return "Attach Process"; }
2928
2929
0
  void UpdateFieldsVisibility() override {
2930
0
    if (m_type_field->GetChoiceContent() == "Name") {
2931
0
      m_pid_field->FieldDelegateHide();
2932
0
      m_name_field->FieldDelegateShow();
2933
0
      m_wait_for_field->FieldDelegateShow();
2934
0
      if (m_wait_for_field->GetBoolean())
2935
0
        m_include_existing_field->FieldDelegateShow();
2936
0
      else
2937
0
        m_include_existing_field->FieldDelegateHide();
2938
0
    } else {
2939
0
      m_pid_field->FieldDelegateShow();
2940
0
      m_name_field->FieldDelegateHide();
2941
0
      m_wait_for_field->FieldDelegateHide();
2942
0
      m_include_existing_field->FieldDelegateHide();
2943
0
    }
2944
0
    if (m_show_advanced_field->GetBoolean())
2945
0
      m_plugin_field->FieldDelegateShow();
2946
0
    else
2947
0
      m_plugin_field->FieldDelegateHide();
2948
0
  }
2949
2950
  // Get the basename of the target's main executable if available, empty string
2951
  // otherwise.
2952
0
  std::string GetDefaultProcessName() {
2953
0
    Target *target = m_debugger.GetSelectedTarget().get();
2954
0
    if (target == nullptr)
2955
0
      return "";
2956
2957
0
    ModuleSP module_sp = target->GetExecutableModule();
2958
0
    if (!module_sp->IsExecutable())
2959
0
      return "";
2960
2961
0
    return module_sp->GetFileSpec().GetFilename().AsCString();
2962
0
  }
2963
2964
0
  bool StopRunningProcess() {
2965
0
    ExecutionContext exe_ctx =
2966
0
        m_debugger.GetCommandInterpreter().GetExecutionContext();
2967
2968
0
    if (!exe_ctx.HasProcessScope())
2969
0
      return false;
2970
2971
0
    Process *process = exe_ctx.GetProcessPtr();
2972
0
    if (!(process && process->IsAlive()))
2973
0
      return false;
2974
2975
0
    FormDelegateSP form_delegate_sp =
2976
0
        FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
2977
0
    Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
2978
0
    WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
2979
0
        form_delegate_sp->GetName().c_str(), bounds, true);
2980
0
    WindowDelegateSP window_delegate_sp =
2981
0
        WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
2982
0
    form_window_sp->SetDelegate(window_delegate_sp);
2983
2984
0
    return true;
2985
0
  }
2986
2987
0
  Target *GetTarget() {
2988
0
    Target *target = m_debugger.GetSelectedTarget().get();
2989
2990
0
    if (target != nullptr)
2991
0
      return target;
2992
2993
0
    TargetSP new_target_sp;
2994
0
    m_debugger.GetTargetList().CreateTarget(
2995
0
        m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
2996
2997
0
    target = new_target_sp.get();
2998
2999
0
    if (target == nullptr)
3000
0
      SetError("Failed to create target.");
3001
3002
0
    m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
3003
3004
0
    return target;
3005
0
  }
3006
3007
0
  ProcessAttachInfo GetAttachInfo() {
3008
0
    ProcessAttachInfo attach_info;
3009
0
    attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
3010
0
    if (m_type_field->GetChoiceContent() == "Name") {
3011
0
      attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
3012
0
                                              FileSpec::Style::native);
3013
0
      attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
3014
0
      if (m_wait_for_field->GetBoolean())
3015
0
        attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
3016
0
    } else {
3017
0
      attach_info.SetProcessID(m_pid_field->GetInteger());
3018
0
    }
3019
0
    attach_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3020
3021
0
    return attach_info;
3022
0
  }
3023
3024
0
  void Attach(Window &window) {
3025
0
    ClearError();
3026
3027
0
    bool all_fields_are_valid = CheckFieldsValidity();
3028
0
    if (!all_fields_are_valid)
3029
0
      return;
3030
3031
0
    bool process_is_running = StopRunningProcess();
3032
0
    if (process_is_running)
3033
0
      return;
3034
3035
0
    Target *target = GetTarget();
3036
0
    if (HasError())
3037
0
      return;
3038
3039
0
    StreamString stream;
3040
0
    ProcessAttachInfo attach_info = GetAttachInfo();
3041
0
    Status status = target->Attach(attach_info, &stream);
3042
3043
0
    if (status.Fail()) {
3044
0
      SetError(status.AsCString());
3045
0
      return;
3046
0
    }
3047
3048
0
    ProcessSP process_sp(target->GetProcessSP());
3049
0
    if (!process_sp) {
3050
0
      SetError("Attached sucessfully but target has no process.");
3051
0
      return;
3052
0
    }
3053
3054
0
    if (attach_info.GetContinueOnceAttached())
3055
0
      process_sp->Resume();
3056
3057
0
    window.GetParent()->RemoveSubWindow(&window);
3058
0
  }
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
0
  TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) {
3077
0
    m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true,
3078
0
                                      /*required=*/true);
3079
0
    m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true,
3080
0
                                     /*required=*/false);
3081
0
    m_symbol_file_field = AddFileField(
3082
0
        "Symbol File", "", /*need_to_exist=*/true, /*required=*/false);
3083
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3084
0
    m_remote_file_field = AddFileField(
3085
0
        "Remote File", "", /*need_to_exist=*/false, /*required=*/false);
3086
0
    m_arch_field = AddArchField("Architecture", "", /*required=*/false);
3087
0
    m_platform_field = AddPlatformPluginField(debugger);
3088
0
    m_load_dependent_files_field =
3089
0
        AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices());
3090
3091
0
    AddAction("Create", [this](Window &window) { CreateTarget(window); });
3092
0
  }
3093
3094
0
  std::string GetName() override { return "Create Target"; }
3095
3096
0
  void UpdateFieldsVisibility() override {
3097
0
    if (m_show_advanced_field->GetBoolean()) {
3098
0
      m_remote_file_field->FieldDelegateShow();
3099
0
      m_arch_field->FieldDelegateShow();
3100
0
      m_platform_field->FieldDelegateShow();
3101
0
      m_load_dependent_files_field->FieldDelegateShow();
3102
0
    } else {
3103
0
      m_remote_file_field->FieldDelegateHide();
3104
0
      m_arch_field->FieldDelegateHide();
3105
0
      m_platform_field->FieldDelegateHide();
3106
0
      m_load_dependent_files_field->FieldDelegateHide();
3107
0
    }
3108
0
  }
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
0
  std::vector<std::string> GetLoadDependentFilesChoices() {
3115
0
    std::vector<std::string> load_dependents_options;
3116
0
    load_dependents_options.push_back(kLoadDependentFilesExecOnly);
3117
0
    load_dependents_options.push_back(kLoadDependentFilesYes);
3118
0
    load_dependents_options.push_back(kLoadDependentFilesNo);
3119
0
    return load_dependents_options;
3120
0
  }
3121
3122
0
  LoadDependentFiles GetLoadDependentFiles() {
3123
0
    std::string choice = m_load_dependent_files_field->GetChoiceContent();
3124
0
    if (choice == kLoadDependentFilesNo)
3125
0
      return eLoadDependentsNo;
3126
0
    if (choice == kLoadDependentFilesYes)
3127
0
      return eLoadDependentsYes;
3128
0
    return eLoadDependentsDefault;
3129
0
  }
3130
3131
0
  OptionGroupPlatform GetPlatformOptions() {
3132
0
    OptionGroupPlatform platform_options(false);
3133
0
    platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str());
3134
0
    return platform_options;
3135
0
  }
3136
3137
0
  TargetSP GetTarget() {
3138
0
    OptionGroupPlatform platform_options = GetPlatformOptions();
3139
0
    TargetSP target_sp;
3140
0
    Status status = m_debugger.GetTargetList().CreateTarget(
3141
0
        m_debugger, m_executable_field->GetPath(),
3142
0
        m_arch_field->GetArchString(), GetLoadDependentFiles(),
3143
0
        &platform_options, target_sp);
3144
3145
0
    if (status.Fail()) {
3146
0
      SetError(status.AsCString());
3147
0
      return nullptr;
3148
0
    }
3149
3150
0
    m_debugger.GetTargetList().SetSelectedTarget(target_sp);
3151
3152
0
    return target_sp;
3153
0
  }
3154
3155
0
  void SetSymbolFile(TargetSP target_sp) {
3156
0
    if (!m_symbol_file_field->IsSpecified())
3157
0
      return;
3158
3159
0
    ModuleSP module_sp(target_sp->GetExecutableModule());
3160
0
    if (!module_sp)
3161
0
      return;
3162
3163
0
    module_sp->SetSymbolFileFileSpec(
3164
0
        m_symbol_file_field->GetResolvedFileSpec());
3165
0
  }
3166
3167
0
  void SetCoreFile(TargetSP target_sp) {
3168
0
    if (!m_core_file_field->IsSpecified())
3169
0
      return;
3170
3171
0
    FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec();
3172
3173
0
    FileSpec core_file_directory_spec;
3174
0
    core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory());
3175
0
    target_sp->AppendExecutableSearchPaths(core_file_directory_spec);
3176
3177
0
    ProcessSP process_sp(target_sp->CreateProcess(
3178
0
        m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false));
3179
3180
0
    if (!process_sp) {
3181
0
      SetError("Unable to find process plug-in for core file!");
3182
0
      return;
3183
0
    }
3184
3185
0
    Status status = process_sp->LoadCore();
3186
0
    if (status.Fail()) {
3187
0
      SetError("Can't find plug-in for core file!");
3188
0
      return;
3189
0
    }
3190
0
  }
3191
3192
0
  void SetRemoteFile(TargetSP target_sp) {
3193
0
    if (!m_remote_file_field->IsSpecified())
3194
0
      return;
3195
3196
0
    ModuleSP module_sp(target_sp->GetExecutableModule());
3197
0
    if (!module_sp)
3198
0
      return;
3199
3200
0
    FileSpec remote_file_spec = m_remote_file_field->GetFileSpec();
3201
0
    module_sp->SetPlatformFileSpec(remote_file_spec);
3202
0
  }
3203
3204
0
  void RemoveTarget(TargetSP target_sp) {
3205
0
    m_debugger.GetTargetList().DeleteTarget(target_sp);
3206
0
  }
3207
3208
0
  void CreateTarget(Window &window) {
3209
0
    ClearError();
3210
3211
0
    bool all_fields_are_valid = CheckFieldsValidity();
3212
0
    if (!all_fields_are_valid)
3213
0
      return;
3214
3215
0
    TargetSP target_sp = GetTarget();
3216
0
    if (HasError())
3217
0
      return;
3218
3219
0
    SetSymbolFile(target_sp);
3220
0
    if (HasError()) {
3221
0
      RemoveTarget(target_sp);
3222
0
      return;
3223
0
    }
3224
3225
0
    SetCoreFile(target_sp);
3226
0
    if (HasError()) {
3227
0
      RemoveTarget(target_sp);
3228
0
      return;
3229
0
    }
3230
3231
0
    SetRemoteFile(target_sp);
3232
0
    if (HasError()) {
3233
0
      RemoveTarget(target_sp);
3234
0
      return;
3235
0
    }
3236
3237
0
    window.GetParent()->RemoveSubWindow(&window);
3238
0
  }
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
0
      : m_debugger(debugger), m_main_window_sp(main_window_sp) {
3257
3258
0
    m_arguments_field = AddArgumentsField();
3259
0
    SetArgumentsFieldDefaultValue();
3260
0
    m_target_environment_field =
3261
0
        AddEnvironmentVariableListField("Target Environment Variables");
3262
0
    SetTargetEnvironmentFieldDefaultValue();
3263
0
    m_working_directory_field = AddDirectoryField(
3264
0
        "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false);
3265
3266
0
    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
3267
3268
0
    m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false);
3269
0
    m_detach_on_error_field =
3270
0
        AddBooleanField("Detach on error.", GetDefaultDetachOnError());
3271
0
    m_disable_aslr_field =
3272
0
        AddBooleanField("Disable ASLR", GetDefaultDisableASLR());
3273
0
    m_plugin_field = AddProcessPluginField();
3274
0
    m_arch_field = AddArchField("Architecture", "", false);
3275
0
    m_shell_field = AddFileField("Shell", "", true, false);
3276
0
    m_expand_shell_arguments_field =
3277
0
        AddBooleanField("Expand shell arguments.", false);
3278
3279
0
    m_disable_standard_io_field =
3280
0
        AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO());
3281
0
    m_standard_output_field =
3282
0
        AddFileField("Standard Output File", "", /*need_to_exist=*/false,
3283
0
                     /*required=*/false);
3284
0
    m_standard_error_field =
3285
0
        AddFileField("Standard Error File", "", /*need_to_exist=*/false,
3286
0
                     /*required=*/false);
3287
0
    m_standard_input_field =
3288
0
        AddFileField("Standard Input File", "", /*need_to_exist=*/false,
3289
0
                     /*required=*/false);
3290
3291
0
    m_show_inherited_environment_field =
3292
0
        AddBooleanField("Show inherited environment variables.", false);
3293
0
    m_inherited_environment_field =
3294
0
        AddEnvironmentVariableListField("Inherited Environment Variables");
3295
0
    SetInheritedEnvironmentFieldDefaultValue();
3296
3297
0
    AddAction("Launch", [this](Window &window) { Launch(window); });
3298
0
  }
3299
3300
0
  std::string GetName() override { return "Launch Process"; }
3301
3302
0
  void UpdateFieldsVisibility() override {
3303
0
    if (m_show_advanced_field->GetBoolean()) {
3304
0
      m_stop_at_entry_field->FieldDelegateShow();
3305
0
      m_detach_on_error_field->FieldDelegateShow();
3306
0
      m_disable_aslr_field->FieldDelegateShow();
3307
0
      m_plugin_field->FieldDelegateShow();
3308
0
      m_arch_field->FieldDelegateShow();
3309
0
      m_shell_field->FieldDelegateShow();
3310
0
      m_expand_shell_arguments_field->FieldDelegateShow();
3311
0
      m_disable_standard_io_field->FieldDelegateShow();
3312
0
      if (m_disable_standard_io_field->GetBoolean()) {
3313
0
        m_standard_input_field->FieldDelegateHide();
3314
0
        m_standard_output_field->FieldDelegateHide();
3315
0
        m_standard_error_field->FieldDelegateHide();
3316
0
      } else {
3317
0
        m_standard_input_field->FieldDelegateShow();
3318
0
        m_standard_output_field->FieldDelegateShow();
3319
0
        m_standard_error_field->FieldDelegateShow();
3320
0
      }
3321
0
      m_show_inherited_environment_field->FieldDelegateShow();
3322
0
      if (m_show_inherited_environment_field->GetBoolean())
3323
0
        m_inherited_environment_field->FieldDelegateShow();
3324
0
      else
3325
0
        m_inherited_environment_field->FieldDelegateHide();
3326
0
    } else {
3327
0
      m_stop_at_entry_field->FieldDelegateHide();
3328
0
      m_detach_on_error_field->FieldDelegateHide();
3329
0
      m_disable_aslr_field->FieldDelegateHide();
3330
0
      m_plugin_field->FieldDelegateHide();
3331
0
      m_arch_field->FieldDelegateHide();
3332
0
      m_shell_field->FieldDelegateHide();
3333
0
      m_expand_shell_arguments_field->FieldDelegateHide();
3334
0
      m_disable_standard_io_field->FieldDelegateHide();
3335
0
      m_standard_input_field->FieldDelegateHide();
3336
0
      m_standard_output_field->FieldDelegateHide();
3337
0
      m_standard_error_field->FieldDelegateHide();
3338
0
      m_show_inherited_environment_field->FieldDelegateHide();
3339
0
      m_inherited_environment_field->FieldDelegateHide();
3340
0
    }
3341
0
  }
3342
3343
  // Methods for setting the default value of the fields.
3344
3345
0
  void SetArgumentsFieldDefaultValue() {
3346
0
    TargetSP target = m_debugger.GetSelectedTarget();
3347
0
    if (target == nullptr)
3348
0
      return;
3349
3350
0
    const Args &target_arguments =
3351
0
        target->GetProcessLaunchInfo().GetArguments();
3352
0
    m_arguments_field->AddArguments(target_arguments);
3353
0
  }
3354
3355
0
  void SetTargetEnvironmentFieldDefaultValue() {
3356
0
    TargetSP target = m_debugger.GetSelectedTarget();
3357
0
    if (target == nullptr)
3358
0
      return;
3359
3360
0
    const Environment &target_environment = target->GetTargetEnvironment();
3361
0
    m_target_environment_field->AddEnvironmentVariables(target_environment);
3362
0
  }
3363
3364
0
  void SetInheritedEnvironmentFieldDefaultValue() {
3365
0
    TargetSP target = m_debugger.GetSelectedTarget();
3366
0
    if (target == nullptr)
3367
0
      return;
3368
3369
0
    const Environment &inherited_environment =
3370
0
        target->GetInheritedEnvironment();
3371
0
    m_inherited_environment_field->AddEnvironmentVariables(
3372
0
        inherited_environment);
3373
0
  }
3374
3375
0
  std::string GetDefaultWorkingDirectory() {
3376
0
    TargetSP target = m_debugger.GetSelectedTarget();
3377
0
    if (target == nullptr)
3378
0
      return "";
3379
3380
0
    PlatformSP platform = target->GetPlatform();
3381
0
    return platform->GetWorkingDirectory().GetPath();
3382
0
  }
3383
3384
0
  bool GetDefaultDisableASLR() {
3385
0
    TargetSP target = m_debugger.GetSelectedTarget();
3386
0
    if (target == nullptr)
3387
0
      return false;
3388
3389
0
    return target->GetDisableASLR();
3390
0
  }
3391
3392
0
  bool GetDefaultDisableStandardIO() {
3393
0
    TargetSP target = m_debugger.GetSelectedTarget();
3394
0
    if (target == nullptr)
3395
0
      return true;
3396
3397
0
    return target->GetDisableSTDIO();
3398
0
  }
3399
3400
0
  bool GetDefaultDetachOnError() {
3401
0
    TargetSP target = m_debugger.GetSelectedTarget();
3402
0
    if (target == nullptr)
3403
0
      return true;
3404
3405
0
    return target->GetDetachOnError();
3406
0
  }
3407
3408
  // Methods for getting the necessary information and setting them to the
3409
  // ProcessLaunchInfo.
3410
3411
0
  void GetExecutableSettings(ProcessLaunchInfo &launch_info) {
3412
0
    TargetSP target = m_debugger.GetSelectedTarget();
3413
0
    ModuleSP executable_module = target->GetExecutableModule();
3414
0
    llvm::StringRef target_settings_argv0 = target->GetArg0();
3415
3416
0
    if (!target_settings_argv0.empty()) {
3417
0
      launch_info.GetArguments().AppendArgument(target_settings_argv0);
3418
0
      launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3419
0
                                    false);
3420
0
      return;
3421
0
    }
3422
3423
0
    launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(),
3424
0
                                  true);
3425
0
  }
3426
3427
0
  void GetArguments(ProcessLaunchInfo &launch_info) {
3428
0
    TargetSP target = m_debugger.GetSelectedTarget();
3429
0
    Args arguments = m_arguments_field->GetArguments();
3430
0
    launch_info.GetArguments().AppendArguments(arguments);
3431
0
  }
3432
3433
0
  void GetEnvironment(ProcessLaunchInfo &launch_info) {
3434
0
    Environment target_environment =
3435
0
        m_target_environment_field->GetEnvironment();
3436
0
    Environment inherited_environment =
3437
0
        m_inherited_environment_field->GetEnvironment();
3438
0
    launch_info.GetEnvironment().insert(target_environment.begin(),
3439
0
                                        target_environment.end());
3440
0
    launch_info.GetEnvironment().insert(inherited_environment.begin(),
3441
0
                                        inherited_environment.end());
3442
0
  }
3443
3444
0
  void GetWorkingDirectory(ProcessLaunchInfo &launch_info) {
3445
0
    if (m_working_directory_field->IsSpecified())
3446
0
      launch_info.SetWorkingDirectory(
3447
0
          m_working_directory_field->GetResolvedFileSpec());
3448
0
  }
3449
3450
0
  void GetStopAtEntry(ProcessLaunchInfo &launch_info) {
3451
0
    if (m_stop_at_entry_field->GetBoolean())
3452
0
      launch_info.GetFlags().Set(eLaunchFlagStopAtEntry);
3453
0
    else
3454
0
      launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry);
3455
0
  }
3456
3457
0
  void GetDetachOnError(ProcessLaunchInfo &launch_info) {
3458
0
    if (m_detach_on_error_field->GetBoolean())
3459
0
      launch_info.GetFlags().Set(eLaunchFlagDetachOnError);
3460
0
    else
3461
0
      launch_info.GetFlags().Clear(eLaunchFlagDetachOnError);
3462
0
  }
3463
3464
0
  void GetDisableASLR(ProcessLaunchInfo &launch_info) {
3465
0
    if (m_disable_aslr_field->GetBoolean())
3466
0
      launch_info.GetFlags().Set(eLaunchFlagDisableASLR);
3467
0
    else
3468
0
      launch_info.GetFlags().Clear(eLaunchFlagDisableASLR);
3469
0
  }
3470
3471
0
  void GetPlugin(ProcessLaunchInfo &launch_info) {
3472
0
    launch_info.SetProcessPluginName(m_plugin_field->GetPluginName());
3473
0
  }
3474
3475
0
  void GetArch(ProcessLaunchInfo &launch_info) {
3476
0
    if (!m_arch_field->IsSpecified())
3477
0
      return;
3478
3479
0
    TargetSP target_sp = m_debugger.GetSelectedTarget();
3480
0
    PlatformSP platform_sp =
3481
0
        target_sp ? target_sp->GetPlatform() : PlatformSP();
3482
0
    launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec(
3483
0
        platform_sp.get(), m_arch_field->GetArchString());
3484
0
  }
3485
3486
0
  void GetShell(ProcessLaunchInfo &launch_info) {
3487
0
    if (!m_shell_field->IsSpecified())
3488
0
      return;
3489
3490
0
    launch_info.SetShell(m_shell_field->GetResolvedFileSpec());
3491
0
    launch_info.SetShellExpandArguments(
3492
0
        m_expand_shell_arguments_field->GetBoolean());
3493
0
  }
3494
3495
0
  void GetStandardIO(ProcessLaunchInfo &launch_info) {
3496
0
    if (m_disable_standard_io_field->GetBoolean()) {
3497
0
      launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO);
3498
0
      return;
3499
0
    }
3500
3501
0
    FileAction action;
3502
0
    if (m_standard_input_field->IsSpecified()) {
3503
0
      if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true,
3504
0
                      false))
3505
0
        launch_info.AppendFileAction(action);
3506
0
    }
3507
0
    if (m_standard_output_field->IsSpecified()) {
3508
0
      if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(),
3509
0
                      false, true))
3510
0
        launch_info.AppendFileAction(action);
3511
0
    }
3512
0
    if (m_standard_error_field->IsSpecified()) {
3513
0
      if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(),
3514
0
                      false, true))
3515
0
        launch_info.AppendFileAction(action);
3516
0
    }
3517
0
  }
3518
3519
0
  void GetInheritTCC(ProcessLaunchInfo &launch_info) {
3520
0
    if (m_debugger.GetSelectedTarget()->GetInheritTCC())
3521
0
      launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent);
3522
0
  }
3523
3524
0
  ProcessLaunchInfo GetLaunchInfo() {
3525
0
    ProcessLaunchInfo launch_info;
3526
3527
0
    GetExecutableSettings(launch_info);
3528
0
    GetArguments(launch_info);
3529
0
    GetEnvironment(launch_info);
3530
0
    GetWorkingDirectory(launch_info);
3531
0
    GetStopAtEntry(launch_info);
3532
0
    GetDetachOnError(launch_info);
3533
0
    GetDisableASLR(launch_info);
3534
0
    GetPlugin(launch_info);
3535
0
    GetArch(launch_info);
3536
0
    GetShell(launch_info);
3537
0
    GetStandardIO(launch_info);
3538
0
    GetInheritTCC(launch_info);
3539
3540
0
    return launch_info;
3541
0
  }
3542
3543
0
  bool StopRunningProcess() {
3544
0
    ExecutionContext exe_ctx =
3545
0
        m_debugger.GetCommandInterpreter().GetExecutionContext();
3546
3547
0
    if (!exe_ctx.HasProcessScope())
3548
0
      return false;
3549
3550
0
    Process *process = exe_ctx.GetProcessPtr();
3551
0
    if (!(process && process->IsAlive()))
3552
0
      return false;
3553
3554
0
    FormDelegateSP form_delegate_sp =
3555
0
        FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
3556
0
    Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
3557
0
    WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
3558
0
        form_delegate_sp->GetName().c_str(), bounds, true);
3559
0
    WindowDelegateSP window_delegate_sp =
3560
0
        WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
3561
0
    form_window_sp->SetDelegate(window_delegate_sp);
3562
3563
0
    return true;
3564
0
  }
3565
3566
0
  Target *GetTarget() {
3567
0
    Target *target = m_debugger.GetSelectedTarget().get();
3568
3569
0
    if (target == nullptr) {
3570
0
      SetError("No target exists!");
3571
0
      return nullptr;
3572
0
    }
3573
3574
0
    ModuleSP exe_module_sp = target->GetExecutableModule();
3575
3576
0
    if (exe_module_sp == nullptr) {
3577
0
      SetError("No executable in target!");
3578
0
      return nullptr;
3579
0
    }
3580
3581
0
    return target;
3582
0
  }
3583
3584
0
  void Launch(Window &window) {
3585
0
    ClearError();
3586
3587
0
    bool all_fields_are_valid = CheckFieldsValidity();
3588
0
    if (!all_fields_are_valid)
3589
0
      return;
3590
3591
0
    bool process_is_running = StopRunningProcess();
3592
0
    if (process_is_running)
3593
0
      return;
3594
3595
0
    Target *target = GetTarget();
3596
0
    if (HasError())
3597
0
      return;
3598
3599
0
    StreamString stream;
3600
0
    ProcessLaunchInfo launch_info = GetLaunchInfo();
3601
0
    Status status = target->Launch(launch_info, &stream);
3602
3603
0
    if (status.Fail()) {
3604
0
      SetError(status.AsCString());
3605
0
      return;
3606
0
    }
3607
3608
0
    ProcessSP process_sp(target->GetProcessSP());
3609
0
    if (!process_sp) {
3610
0
      SetError("Launched successfully but target has no process!");
3611
0
      return;
3612
0
    }
3613
3614
0
    window.GetParent()->RemoveSubWindow(&window);
3615
0
  }
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
0
      : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) {
3673
0
    ;
3674
0
  }
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
0
  int GetLastVisibleMatch(int height) {
3695
0
    int index = m_first_visible_match + height;
3696
0
    return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1;
3697
0
  }
3698
3699
0
  int GetNumberOfVisibleMatches(int height) {
3700
0
    return GetLastVisibleMatch(height) - m_first_visible_match + 1;
3701
0
  }
3702
3703
0
  void UpdateScrolling(Surface &surface) {
3704
0
    if (m_selected_match < m_first_visible_match) {
3705
0
      m_first_visible_match = m_selected_match;
3706
0
      return;
3707
0
    }
3708
0
3709
0
    int height = surface.GetHeight();
3710
0
    int last_visible_match = GetLastVisibleMatch(height);
3711
0
    if (m_selected_match > last_visible_match) {
3712
0
      m_first_visible_match = m_selected_match - height + 1;
3713
0
    }
3714
0
  }
3715
3716
0
  void DrawMatches(Surface &surface) {
3717
0
    if (m_delegate_sp->GetNumberOfMatches() == 0)
3718
0
      return;
3719
0
3720
0
    UpdateScrolling(surface);
3721
0
3722
0
    int count = GetNumberOfVisibleMatches(surface.GetHeight());
3723
0
    for (int i = 0; i < count; i++) {
3724
0
      surface.MoveCursor(1, i);
3725
0
      int current_match = m_first_visible_match + i;
3726
0
      if (current_match == m_selected_match)
3727
0
        surface.AttributeOn(A_REVERSE);
3728
0
      surface.PutCString(
3729
0
          m_delegate_sp->GetMatchTextAtIndex(current_match).c_str());
3730
0
      if (current_match == m_selected_match)
3731
0
        surface.AttributeOff(A_REVERSE);
3732
0
    }
3733
0
  }
3734
3735
0
  void DrawContent(Surface &surface) {
3736
0
    Rect content_bounds = surface.GetFrame();
3737
0
    Rect text_field_bounds, matchs_bounds;
3738
0
    content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(),
3739
0
                                   text_field_bounds, matchs_bounds);
3740
0
    Surface text_field_surface = surface.SubSurface(text_field_bounds);
3741
0
    Surface matches_surface = surface.SubSurface(matchs_bounds);
3742
0
3743
0
    m_text_field.FieldDelegateDraw(text_field_surface, true);
3744
0
    DrawMatches(matches_surface);
3745
0
  }
3746
3747
0
  bool WindowDelegateDraw(Window &window, bool force) override {
3748
0
    window.Erase();
3749
0
3750
0
    window.DrawTitleBox(window.GetName(), "Press Esc to Cancel");
3751
0
3752
0
    Rect content_bounds = window.GetFrame();
3753
0
    content_bounds.Inset(2, 2);
3754
0
    Surface content_surface = window.SubSurface(content_bounds);
3755
0
3756
0
    DrawContent(content_surface);
3757
0
    return true;
3758
0
  }
3759
3760
0
  void SelectNext() {
3761
0
    if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1)
3762
0
      m_selected_match++;
3763
0
  }
3764
3765
0
  void SelectPrevious() {
3766
0
    if (m_selected_match != 0)
3767
0
      m_selected_match--;
3768
0
  }
3769
3770
0
  void ExecuteCallback(Window &window) {
3771
0
    m_delegate_sp->ExecuteCallback(m_selected_match);
3772
0
    window.GetParent()->RemoveSubWindow(&window);
3773
0
  }
3774
3775
0
  void UpdateMatches() {
3776
0
    m_delegate_sp->UpdateMatches(m_text_field.GetText());
3777
0
    m_selected_match = 0;
3778
0
  }
3779
3780
0
  HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3781
0
    switch (key) {
3782
0
    case '\r':
3783
0
    case '\n':
3784
0
    case KEY_ENTER:
3785
0
      ExecuteCallback(window);
3786
0
      return eKeyHandled;
3787
0
    case '\t':
3788
0
    case KEY_DOWN:
3789
0
      SelectNext();
3790
0
      return eKeyHandled;
3791
0
    case KEY_SHIFT_TAB:
3792
0
    case KEY_UP:
3793
0
      SelectPrevious();
3794
0
      return eKeyHandled;
3795
0
    case KEY_ESCAPE:
3796
0
      window.GetParent()->RemoveSubWindow(&window);
3797
0
      return eKeyHandled;
3798
0
    default:
3799
0
      break;
3800
0
    }
3801
0
3802
0
    if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled)
3803
0
      UpdateMatches();
3804
0
3805
0
    return eKeyHandled;
3806
0
  }
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
0
        m_callback(callback) {}
3832
3833
0
  int GetNumberOfMatches() override { return m_matches.GetSize(); }
3834
3835
0
  const std::string &GetMatchTextAtIndex(int index) override {
3836
0
    return m_matches[index];
3837
0
  }
3838
3839
0
  void UpdateMatches(const std::string &text) override {
3840
0
    CompletionResult result;
3841
0
    CompletionRequest request(text.c_str(), text.size(), result);
3842
0
    lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
3843
0
        m_debugger.GetCommandInterpreter(), m_completion_mask, request,
3844
0
        nullptr);
3845
0
    result.GetMatches(m_matches);
3846
0
  }
3847
3848
0
  void ExecuteCallback(int match_index) override {
3849
0
    m_callback(m_matches[match_index]);
3850
0
  }
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
0
  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
0
  ~Menu() override = default;
3885
3886
0
  const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; }
3887
3888
0
  void SetDelegate(const MenuDelegateSP &delegate_sp) {
3889
0
    m_delegate_sp = delegate_sp;
3890
0
  }
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
0
  MenuActionResult ActionPrivate(Menu &menu) {
3905
0
    MenuActionResult result = MenuActionResult::NotHandled;
3906
0
    if (m_delegate_sp) {
3907
0
      result = m_delegate_sp->MenuDelegateAction(menu);
3908
0
      if (result != MenuActionResult::NotHandled)
3909
0
        return result;
3910
0
    } else if (m_parent) {
3911
0
      result = m_parent->ActionPrivate(menu);
3912
0
      if (result != MenuActionResult::NotHandled)
3913
0
        return result;
3914
0
    }
3915
0
    return m_canned_result;
3916
0
  }
3917
3918
0
  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
0
    return ActionPrivate(*this);
3922
0
  }
3923
3924
0
  void SetCannedResult(MenuActionResult result) { m_canned_result = result; }
3925
3926
0
  Menus &GetSubmenus() { return m_submenus; }
3927
3928
0
  const Menus &GetSubmenus() const { return m_submenus; }
3929
3930
0
  int GetSelectedSubmenuIndex() const { return m_selected; }
3931
3932
0
  void SetSelectedSubmenuIndex(int idx) { m_selected = idx; }
3933
3934
0
  Type GetType() const { return m_type; }
3935
3936
0
  int GetStartingColumn() const { return m_start_col; }
3937
3938
0
  void SetStartingColumn(int col) { m_start_col = col; }
3939
3940
0
  int GetKeyValue() const { return m_key_value; }
3941
3942
0
  std::string &GetName() { return m_name; }
3943
3944
0
  int GetDrawWidth() const {
3945
0
    return m_max_submenu_name_length + m_max_submenu_key_name_length + 8;
3946
0
  }
3947
3948
0
  uint64_t GetIdentifier() const { return m_identifier; }
3949
3950
0
  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
0
    : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0),
3972
0
      m_start_col(0), m_max_submenu_name_length(0),
3973
0
      m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3974
0
      m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3975
0
      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
0
    : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid),
3981
0
      m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0),
3982
0
      m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr),
3983
0
      m_submenus(), m_canned_result(MenuActionResult::NotHandled),
3984
0
      m_delegate_sp() {
3985
0
  if (name && name[0]) {
3986
0
    m_name = name;
3987
0
    m_type = Type::Item;
3988
0
    if (key_name && key_name[0])
3989
0
      m_key_name = key_name;
3990
0
  } else {
3991
0
    m_type = Type::Separator;
3992
0
  }
3993
0
}
3994
3995
0
void Menu::RecalculateNameLengths() {
3996
0
  m_max_submenu_name_length = 0;
3997
0
  m_max_submenu_key_name_length = 0;
3998
0
  Menus &submenus = GetSubmenus();
3999
0
  const size_t num_submenus = submenus.size();
4000
0
  for (size_t i = 0; i < num_submenus; ++i) {
4001
0
    Menu *submenu = submenus[i].get();
4002
0
    if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size())
4003
0
      m_max_submenu_name_length = submenu->m_name.size();
4004
0
    if (static_cast<size_t>(m_max_submenu_key_name_length) <
4005
0
        submenu->m_key_name.size())
4006
0
      m_max_submenu_key_name_length = submenu->m_key_name.size();
4007
0
  }
4008
0
}
4009
4010
0
void Menu::AddSubmenu(const MenuSP &menu_sp) {
4011
0
  menu_sp->m_parent = this;
4012
0
  if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size())
4013
0
    m_max_submenu_name_length = menu_sp->m_name.size();
4014
0
  if (static_cast<size_t>(m_max_submenu_key_name_length) <
4015
0
      menu_sp->m_key_name.size())
4016
0
    m_max_submenu_key_name_length = menu_sp->m_key_name.size();
4017
0
  m_submenus.push_back(menu_sp);
4018
0
}
4019
4020
0
void Menu::DrawMenuTitle(Window &window, bool highlight) {
4021
0
  if (m_type == Type::Separator) {
4022
0
    window.MoveCursor(0, window.GetCursorY());
4023
0
    window.PutChar(ACS_LTEE);
4024
0
    int width = window.GetWidth();
4025
0
    if (width > 2) {
4026
0
      width -= 2;
4027
0
      for (int i = 0; i < width; ++i)
4028
0
        window.PutChar(ACS_HLINE);
4029
0
    }
4030
0
    window.PutChar(ACS_RTEE);
4031
0
  } else {
4032
0
    const int shortcut_key = m_key_value;
4033
0
    bool underlined_shortcut = false;
4034
0
    const attr_t highlight_attr = A_REVERSE;
4035
0
    if (highlight)
4036
0
      window.AttributeOn(highlight_attr);
4037
0
    if (llvm::isPrint(shortcut_key)) {
4038
0
      size_t lower_pos = m_name.find(tolower(shortcut_key));
4039
0
      size_t upper_pos = m_name.find(toupper(shortcut_key));
4040
0
      const char *name = m_name.c_str();
4041
0
      size_t pos = std::min<size_t>(lower_pos, upper_pos);
4042
0
      if (pos != std::string::npos) {
4043
0
        underlined_shortcut = true;
4044
0
        if (pos > 0) {
4045
0
          window.PutCString(name, pos);
4046
0
          name += pos;
4047
0
        }
4048
0
        const attr_t shortcut_attr = A_UNDERLINE | A_BOLD;
4049
0
        window.AttributeOn(shortcut_attr);
4050
0
        window.PutChar(name[0]);
4051
0
        window.AttributeOff(shortcut_attr);
4052
0
        name++;
4053
0
        if (name[0])
4054
0
          window.PutCString(name);
4055
0
      }
4056
0
    }
4057
4058
0
    if (!underlined_shortcut) {
4059
0
      window.PutCString(m_name.c_str());
4060
0
    }
4061
4062
0
    if (highlight)
4063
0
      window.AttributeOff(highlight_attr);
4064
4065
0
    if (m_key_name.empty()) {
4066
0
      if (!underlined_shortcut && llvm::isPrint(m_key_value)) {
4067
0
        window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4068
0
        window.Printf(" (%c)", m_key_value);
4069
0
        window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4070
0
      }
4071
0
    } else {
4072
0
      window.AttributeOn(COLOR_PAIR(MagentaOnWhite));
4073
0
      window.Printf(" (%s)", m_key_name.c_str());
4074
0
      window.AttributeOff(COLOR_PAIR(MagentaOnWhite));
4075
0
    }
4076
0
  }
4077
0
}
4078
4079
0
bool Menu::WindowDelegateDraw(Window &window, bool force) {
4080
0
  Menus &submenus = GetSubmenus();
4081
0
  const size_t num_submenus = submenus.size();
4082
0
  const int selected_idx = GetSelectedSubmenuIndex();
4083
0
  Menu::Type menu_type = GetType();
4084
0
  switch (menu_type) {
4085
0
  case Menu::Type::Bar: {
4086
0
    window.SetBackground(BlackOnWhite);
4087
0
    window.MoveCursor(0, 0);
4088
0
    for (size_t i = 0; i < num_submenus; ++i) {
4089
0
      Menu *menu = submenus[i].get();
4090
0
      if (i > 0)
4091
0
        window.PutChar(' ');
4092
0
      menu->SetStartingColumn(window.GetCursorX());
4093
0
      window.PutCString("| ");
4094
0
      menu->DrawMenuTitle(window, false);
4095
0
    }
4096
0
    window.PutCString(" |");
4097
0
  } break;
4098
4099
0
  case Menu::Type::Item: {
4100
0
    int y = 1;
4101
0
    int x = 3;
4102
    // Draw the menu
4103
0
    int cursor_x = 0;
4104
0
    int cursor_y = 0;
4105
0
    window.Erase();
4106
0
    window.SetBackground(BlackOnWhite);
4107
0
    window.Box();
4108
0
    for (size_t i = 0; i < num_submenus; ++i) {
4109
0
      const bool is_selected = (i == static_cast<size_t>(selected_idx));
4110
0
      window.MoveCursor(x, y + i);
4111
0
      if (is_selected) {
4112
        // Remember where we want the cursor to be
4113
0
        cursor_x = x - 1;
4114
0
        cursor_y = y + i;
4115
0
      }
4116
0
      submenus[i]->DrawMenuTitle(window, is_selected);
4117
0
    }
4118
0
    window.MoveCursor(cursor_x, cursor_y);
4119
0
  } break;
4120
4121
0
  default:
4122
0
  case Menu::Type::Separator:
4123
0
    break;
4124
0
  }
4125
0
  return true; // Drawing handled...
4126
0
}
4127
4128
0
HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) {
4129
0
  HandleCharResult result = eKeyNotHandled;
4130
4131
0
  Menus &submenus = GetSubmenus();
4132
0
  const size_t num_submenus = submenus.size();
4133
0
  const int selected_idx = GetSelectedSubmenuIndex();
4134
0
  Menu::Type menu_type = GetType();
4135
0
  if (menu_type == Menu::Type::Bar) {
4136
0
    MenuSP run_menu_sp;
4137
0
    switch (key) {
4138
0
    case KEY_DOWN:
4139
0
    case KEY_UP:
4140
      // Show last menu or first menu
4141
0
      if (selected_idx < static_cast<int>(num_submenus))
4142
0
        run_menu_sp = submenus[selected_idx];
4143
0
      else if (!submenus.empty())
4144
0
        run_menu_sp = submenus.front();
4145
0
      result = eKeyHandled;
4146
0
      break;
4147
4148
0
    case KEY_RIGHT:
4149
0
      ++m_selected;
4150
0
      if (m_selected >= static_cast<int>(num_submenus))
4151
0
        m_selected = 0;
4152
0
      if (m_selected < static_cast<int>(num_submenus))
4153
0
        run_menu_sp = submenus[m_selected];
4154
0
      else if (!submenus.empty())
4155
0
        run_menu_sp = submenus.front();
4156
0
      result = eKeyHandled;
4157
0
      break;
4158
4159
0
    case KEY_LEFT:
4160
0
      --m_selected;
4161
0
      if (m_selected < 0)
4162
0
        m_selected = num_submenus - 1;
4163
0
      if (m_selected < static_cast<int>(num_submenus))
4164
0
        run_menu_sp = submenus[m_selected];
4165
0
      else if (!submenus.empty())
4166
0
        run_menu_sp = submenus.front();
4167
0
      result = eKeyHandled;
4168
0
      break;
4169
4170
0
    default:
4171
0
      for (size_t i = 0; i < num_submenus; ++i) {
4172
0
        if (submenus[i]->GetKeyValue() == key) {
4173
0
          SetSelectedSubmenuIndex(i);
4174
0
          run_menu_sp = submenus[i];
4175
0
          result = eKeyHandled;
4176
0
          break;
4177
0
        }
4178
0
      }
4179
0
      break;
4180
0
    }
4181
4182
0
    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
0
      if (run_menu_sp->Action() == MenuActionResult::Quit)
4187
0
        return eQuitApplication;
4188
4189
0
      Rect menu_bounds;
4190
0
      menu_bounds.origin.x = run_menu_sp->GetStartingColumn();
4191
0
      menu_bounds.origin.y = 1;
4192
0
      menu_bounds.size.width = run_menu_sp->GetDrawWidth();
4193
0
      menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2;
4194
0
      if (m_menu_window_sp)
4195
0
        window.GetParent()->RemoveSubWindow(m_menu_window_sp.get());
4196
4197
0
      m_menu_window_sp = window.GetParent()->CreateSubWindow(
4198
0
          run_menu_sp->GetName().c_str(), menu_bounds, true);
4199
0
      m_menu_window_sp->SetDelegate(run_menu_sp);
4200
0
    }
4201
0
  } else if (menu_type == Menu::Type::Item) {
4202
0
    switch (key) {
4203
0
    case KEY_DOWN:
4204
0
      if (m_submenus.size() > 1) {
4205
0
        const int start_select = m_selected;
4206
0
        while (++m_selected != start_select) {
4207
0
          if (static_cast<size_t>(m_selected) >= num_submenus)
4208
0
            m_selected = 0;
4209
0
          if (m_submenus[m_selected]->GetType() == Type::Separator)
4210
0
            continue;
4211
0
          else
4212
0
            break;
4213
0
        }
4214
0
        return eKeyHandled;
4215
0
      }
4216
0
      break;
4217
4218
0
    case KEY_UP:
4219
0
      if (m_submenus.size() > 1) {
4220
0
        const int start_select = m_selected;
4221
0
        while (--m_selected != start_select) {
4222
0
          if (m_selected < static_cast<int>(0))
4223
0
            m_selected = num_submenus - 1;
4224
0
          if (m_submenus[m_selected]->GetType() == Type::Separator)
4225
0
            continue;
4226
0
          else
4227
0
            break;
4228
0
        }
4229
0
        return eKeyHandled;
4230
0
      }
4231
0
      break;
4232
4233
0
    case KEY_RETURN:
4234
0
      if (static_cast<size_t>(selected_idx) < num_submenus) {
4235
0
        if (submenus[selected_idx]->Action() == MenuActionResult::Quit)
4236
0
          return eQuitApplication;
4237
0
        window.GetParent()->RemoveSubWindow(&window);
4238
0
        return eKeyHandled;
4239
0
      }
4240
0
      break;
4241
4242
0
    case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in
4243
                     // case other chars are entered for escaped sequences
4244
0
      window.GetParent()->RemoveSubWindow(&window);
4245
0
      return eKeyHandled;
4246
4247
0
    default:
4248
0
      for (size_t i = 0; i < num_submenus; ++i) {
4249
0
        Menu *menu = submenus[i].get();
4250
0
        if (menu->GetKeyValue() == key) {
4251
0
          SetSelectedSubmenuIndex(i);
4252
0
          window.GetParent()->RemoveSubWindow(&window);
4253
0
          if (menu->Action() == MenuActionResult::Quit)
4254
0
            return eQuitApplication;
4255
0
          return eKeyHandled;
4256
0
        }
4257
0
      }
4258
0
      break;
4259
0
    }
4260
0
  } else if (menu_type == Menu::Type::Separator) {
4261
0
  }
4262
0
  return result;
4263
0
}
4264
4265
class Application {
4266
public:
4267
0
  Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {}
4268
4269
0
  ~Application() {
4270
0
    m_window_delegates.clear();
4271
0
    m_window_sp.reset();
4272
0
    if (m_screen) {
4273
0
      ::delscreen(m_screen);
4274
0
      m_screen = nullptr;
4275
0
    }
4276
0
  }
4277
4278
0
  void Initialize() {
4279
0
    m_screen = ::newterm(nullptr, m_out, m_in);
4280
0
    ::start_color();
4281
0
    ::curs_set(0);
4282
0
    ::noecho();
4283
0
    ::keypad(stdscr, TRUE);
4284
0
  }
4285
4286
0
  void Terminate() { ::endwin(); }
4287
4288
0
  void Run(Debugger &debugger) {
4289
0
    bool done = false;
4290
0
    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
0
    halfdelay(delay_in_tenths_of_a_second); // Poll using some number of
4299
                                            // tenths of seconds seconds when
4300
                                            // calling Window::GetChar()
4301
4302
0
    ListenerSP listener_sp(
4303
0
        Listener::MakeListener("lldb.IOHandler.curses.Application"));
4304
0
    ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass());
4305
0
    debugger.EnableForwardEvents(listener_sp);
4306
4307
0
    m_update_screen = true;
4308
0
#if defined(__APPLE__)
4309
0
    std::deque<int> escape_chars;
4310
0
#endif
4311
4312
0
    while (!done) {
4313
0
      if (m_update_screen) {
4314
0
        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
0
        update_panels();
4319
4320
        // Cursor hiding isn't working on MacOSX, so hide it in the top left
4321
        // corner
4322
0
        m_window_sp->MoveCursor(0, 0);
4323
4324
0
        doupdate();
4325
0
        m_update_screen = false;
4326
0
      }
4327
4328
0
#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
0
      int ch;
4333
0
      if (escape_chars.empty())
4334
0
        ch = m_window_sp->GetChar();
4335
0
      else {
4336
0
        ch = escape_chars.front();
4337
0
        escape_chars.pop_front();
4338
0
      }
4339
0
      if (ch == KEY_ESCAPE) {
4340
0
        int ch2 = m_window_sp->GetChar();
4341
0
        if (ch2 == 'O') {
4342
0
          int ch3 = m_window_sp->GetChar();
4343
0
          switch (ch3) {
4344
0
          case 'P':
4345
0
            ch = KEY_F(1);
4346
0
            break;
4347
0
          case 'Q':
4348
0
            ch = KEY_F(2);
4349
0
            break;
4350
0
          case 'R':
4351
0
            ch = KEY_F(3);
4352
0
            break;
4353
0
          case 'S':
4354
0
            ch = KEY_F(4);
4355
0
            break;
4356
0
          default:
4357
0
            escape_chars.push_back(ch2);
4358
0
            if (ch3 != -1)
4359
0
              escape_chars.push_back(ch3);
4360
0
            break;
4361
0
          }
4362
0
        } else if (ch2 != -1)
4363
0
          escape_chars.push_back(ch2);
4364
0
      }
4365
#else
4366
      int ch = m_window_sp->GetChar();
4367
4368
#endif
4369
0
      if (ch == -1) {
4370
0
        if (feof(m_in) || ferror(m_in)) {
4371
0
          done = true;
4372
0
        } else {
4373
          // Just a timeout from using halfdelay(), check for events
4374
0
          EventSP event_sp;
4375
0
          while (listener_sp->PeekAtNextEvent()) {
4376
0
            listener_sp->GetEvent(event_sp, std::chrono::seconds(0));
4377
4378
0
            if (event_sp) {
4379
0
              Broadcaster *broadcaster = event_sp->GetBroadcaster();
4380
0
              if (broadcaster) {
4381
                // uint32_t event_type = event_sp->GetType();
4382
0
                ConstString broadcaster_class(
4383
0
                    broadcaster->GetBroadcasterClass());
4384
0
                if (broadcaster_class == broadcaster_class_process) {
4385
0
                  m_update_screen = true;
4386
0
                  continue; // Don't get any key, just update our view
4387
0
                }
4388
0
              }
4389
0
            }
4390
0
          }
4391
0
        }
4392
0
      } else {
4393
0
        HandleCharResult key_result = m_window_sp->HandleChar(ch);
4394
0
        switch (key_result) {
4395
0
        case eKeyHandled:
4396
0
          m_update_screen = true;
4397
0
          break;
4398
0
        case eKeyNotHandled:
4399
0
          if (ch == 12) { // Ctrl+L, force full redraw
4400
0
            redrawwin(m_window_sp->get());
4401
0
            m_update_screen = true;
4402
0
          }
4403
0
          break;
4404
0
        case eQuitApplication:
4405
0
          done = true;
4406
0
          break;
4407
0
        }
4408
0
      }
4409
0
    }
4410
4411
0
    debugger.CancelForwardEvents(listener_sp);
4412
0
  }
4413
4414
0
  WindowSP &GetMainWindow() {
4415
0
    if (!m_window_sp)
4416
0
      m_window_sp = std::make_shared<Window>("main", stdscr, false);
4417
0
    return m_window_sp;
4418
0
  }
4419
4420
0
  void TerminalSizeChanged() {
4421
0
    ::endwin();
4422
0
    ::refresh();
4423
0
    Rect content_bounds = m_window_sp->GetFrame();
4424
0
    m_window_sp->SetBounds(content_bounds);
4425
0
    if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar"))
4426
0
      menubar_window_sp->SetBounds(content_bounds.MakeMenuBar());
4427
0
    if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status"))
4428
0
      status_window_sp->SetBounds(content_bounds.MakeStatusBar());
4429
4430
0
    WindowSP source_window_sp = m_window_sp->FindSubWindow("Source");
4431
0
    WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables");
4432
0
    WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers");
4433
0
    WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads");
4434
4435
0
    Rect threads_bounds;
4436
0
    Rect source_variables_bounds;
4437
0
    content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4438
0
                                           threads_bounds);
4439
0
    if (threads_window_sp)
4440
0
      threads_window_sp->SetBounds(threads_bounds);
4441
0
    else
4442
0
      source_variables_bounds = content_bounds;
4443
4444
0
    Rect source_bounds;
4445
0
    Rect variables_registers_bounds;
4446
0
    source_variables_bounds.HorizontalSplitPercentage(
4447
0
        0.70, source_bounds, variables_registers_bounds);
4448
0
    if (variables_window_sp || registers_window_sp) {
4449
0
      if (variables_window_sp && registers_window_sp) {
4450
0
        Rect variables_bounds;
4451
0
        Rect registers_bounds;
4452
0
        variables_registers_bounds.VerticalSplitPercentage(
4453
0
            0.50, variables_bounds, registers_bounds);
4454
0
        variables_window_sp->SetBounds(variables_bounds);
4455
0
        registers_window_sp->SetBounds(registers_bounds);
4456
0
      } else if (variables_window_sp) {
4457
0
        variables_window_sp->SetBounds(variables_registers_bounds);
4458
0
      } else {
4459
0
        registers_window_sp->SetBounds(variables_registers_bounds);
4460
0
      }
4461
0
    } else {
4462
0
      source_bounds = source_variables_bounds;
4463
0
    }
4464
4465
0
    source_window_sp->SetBounds(source_bounds);
4466
4467
0
    touchwin(stdscr);
4468
0
    redrawwin(m_window_sp->get());
4469
0
    m_update_screen = true;
4470
0
  }
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
0
      : value(v), parent(p),
4500
0
        might_have_children(v ? v->MightHaveChildren() : false) {}
4501
4502
0
  size_t GetDepth() const {
4503
0
    if (parent)
4504
0
      return 1 + parent->GetDepth();
4505
0
    return 0;
4506
0
  }
4507
4508
0
  void Expand() { expanded = true; }
4509
4510
0
  std::vector<Row> &GetChildren() {
4511
0
    ProcessSP process_sp = value.GetProcessSP();
4512
0
    auto stop_id = process_sp->GetStopID();
4513
0
    if (process_sp && stop_id != children_stop_id) {
4514
0
      children_stop_id = stop_id;
4515
0
      calculated_children = false;
4516
0
    }
4517
0
    if (!calculated_children) {
4518
0
      children.clear();
4519
0
      calculated_children = true;
4520
0
      ValueObjectSP valobj = value.GetSP();
4521
0
      if (valobj) {
4522
0
        const size_t num_children = valobj->GetNumChildren();
4523
0
        for (size_t i = 0; i < num_children; ++i) {
4524
0
          children.push_back(Row(valobj->GetChildAtIndex(i), this));
4525
0
        }
4526
0
      }
4527
0
    }
4528
0
    return children;
4529
0
  }
4530
4531
0
  void Unexpand() {
4532
0
    expanded = false;
4533
0
    calculated_children = false;
4534
0
    children.clear();
4535
0
  }
4536
4537
0
  void DrawTree(Window &window) {
4538
0
    if (parent)
4539
0
      parent->DrawTreeForChild(window, this, 0);
4540
4541
0
    if (might_have_children &&
4542
0
        (!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
0
      window.PutChar(ACS_DIAMOND);
4561
0
      window.PutChar(ACS_HLINE);
4562
0
    }
4563
0
  }
4564
4565
0
  void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) {
4566
0
    if (parent)
4567
0
      parent->DrawTreeForChild(window, this, reverse_depth + 1);
4568
4569
0
    if (&GetChildren().back() == child) {
4570
      // Last child
4571
0
      if (reverse_depth == 0) {
4572
0
        window.PutChar(ACS_LLCORNER);
4573
0
        window.PutChar(ACS_HLINE);
4574
0
      } else {
4575
0
        window.PutChar(' ');
4576
0
        window.PutChar(' ');
4577
0
      }
4578
0
    } else {
4579
0
      if (reverse_depth == 0) {
4580
0
        window.PutChar(ACS_LTEE);
4581
0
        window.PutChar(ACS_HLINE);
4582
0
      } else {
4583
0
        window.PutChar(ACS_VLINE);
4584
0
        window.PutChar(' ');
4585
0
      }
4586
0
    }
4587
0
  }
4588
};
4589
4590
struct DisplayOptions {
4591
  bool show_types;
4592
};
4593
4594
class TreeItem;
4595
4596
class TreeDelegate {
4597
public:
4598
0
  TreeDelegate() = default;
4599
0
  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
0
                                           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
0
  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
0
  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
0
      : m_parent(parent), m_delegate(&delegate),
4621
0
        m_might_have_children(might_have_children), m_is_expanded(is_expanded) {
4622
0
  }
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
0
      : TreeItemData(parent, delegate, might_have_children,
4640
0
                     parent == nullptr
4641
0
                         ? delegate.TreeDelegateExpandRootByDefault()
4642
0
                         : false),
4643
0
        m_children() {}
4644
4645
  TreeItem(const TreeItem &) = delete;
4646
  TreeItem &operator=(const TreeItem &rhs) = delete;
4647
4648
0
  TreeItem &operator=(TreeItem &&rhs) {
4649
0
    if (this != &rhs) {
4650
0
      TreeItemData::operator=(std::move(rhs));
4651
0
      AdoptChildren(rhs.m_children);
4652
0
    }
4653
0
    return *this;
4654
0
  }
4655
4656
0
  TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) {
4657
0
    AdoptChildren(rhs.m_children);
4658
0
  }
4659
4660
0
  size_t GetDepth() const {
4661
0
    if (m_parent)
4662
0
      return 1 + m_parent->GetDepth();
4663
0
    return 0;
4664
0
  }
4665
4666
0
  int GetRowIndex() const { return m_row_idx; }
4667
4668
0
  void ClearChildren() { m_children.clear(); }
4669
4670
0
  void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) {
4671
0
    if (m_children.size() >= n) {
4672
0
      m_children.erase(m_children.begin() + n, m_children.end());
4673
0
      return;
4674
0
    }
4675
0
    m_children.reserve(n);
4676
0
    std::generate_n(std::back_inserter(m_children), n - m_children.size(),
4677
0
                    [&, parent = this]() {
4678
0
                      return TreeItem(parent, delegate, might_have_children);
4679
0
                    });
4680
0
  }
4681
4682
0
  TreeItem &operator[](size_t i) { return m_children[i]; }
4683
4684
0
  void SetRowIndex(int row_idx) { m_row_idx = row_idx; }
4685
4686
0
  size_t GetNumChildren() {
4687
0
    m_delegate->TreeDelegateGenerateChildren(*this);
4688
0
    return m_children.size();
4689
0
  }
4690
4691
0
  void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); }
4692
4693
0
  void CalculateRowIndexes(int &row_idx) {
4694
0
    SetRowIndex(row_idx);
4695
0
    ++row_idx;
4696
4697
0
    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
0
    if (m_parent == nullptr || expanded)
4702
0
      GetNumChildren();
4703
4704
0
    for (auto &item : m_children) {
4705
0
      if (expanded)
4706
0
        item.CalculateRowIndexes(row_idx);
4707
0
      else
4708
0
        item.SetRowIndex(-1);
4709
0
    }
4710
0
  }
4711
4712
0
  TreeItem *GetParent() { return m_parent; }
4713
4714
0
  bool IsExpanded() const { return m_is_expanded; }
4715
4716
0
  void Expand() { m_is_expanded = true; }
4717
4718
0
  void Unexpand() { m_is_expanded = false; }
4719
4720
  bool Draw(Window &window, const int first_visible_row,
4721
0
            const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) {
4722
0
    if (num_rows_left <= 0)
4723
0
      return false;
4724
4725
0
    if (m_row_idx >= first_visible_row) {
4726
0
      window.MoveCursor(2, row_idx + 1);
4727
4728
0
      if (m_parent)
4729
0
        m_parent->DrawTreeForChild(window, this, 0);
4730
4731
0
      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
0
        window.PutChar(ACS_DIAMOND);
4750
0
        window.PutChar(ACS_HLINE);
4751
0
      }
4752
0
      bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) &&
4753
0
                       window.IsActive();
4754
4755
0
      if (highlight)
4756
0
        window.AttributeOn(A_REVERSE);
4757
4758
0
      m_delegate->TreeDelegateDrawTreeItem(*this, window);
4759
4760
0
      if (highlight)
4761
0
        window.AttributeOff(A_REVERSE);
4762
0
      ++row_idx;
4763
0
      --num_rows_left;
4764
0
    }
4765
4766
</