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