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