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