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 FileSpec file(GetPath()); 1304 if (m_need_to_exist && !FileSystem::Instance().Exists(file)) { 1305 SetError("File doesn't exist!"); 1306 return; 1307 } 1308 if (FileSystem::Instance().IsDirectory(file)) { 1309 SetError("Not a file!"); 1310 return; 1311 } 1312 } 1313 1314 // Returns the path of the file. 1315 const std::string &GetPath() { return m_content; } 1316 1317 protected: 1318 bool m_need_to_exist; 1319 }; 1320 1321 class DirectoryFieldDelegate : public TextFieldDelegate { 1322 public: 1323 DirectoryFieldDelegate(const char *label, const char *content, 1324 bool need_to_exist, bool required) 1325 : TextFieldDelegate(label, content, required), 1326 m_need_to_exist(need_to_exist) {} 1327 1328 void FieldDelegateExitCallback() override { 1329 TextFieldDelegate::FieldDelegateExitCallback(); 1330 if (!IsSpecified()) 1331 return; 1332 1333 FileSpec file(GetPath()); 1334 if (m_need_to_exist && !FileSystem::Instance().Exists(file)) { 1335 SetError("Directory doesn't exist!"); 1336 return; 1337 } 1338 if (!FileSystem::Instance().IsDirectory(file)) { 1339 SetError("Not a directory!"); 1340 return; 1341 } 1342 } 1343 1344 // Returns the path of the file. 1345 const std::string &GetPath() { return m_content; } 1346 1347 protected: 1348 bool m_need_to_exist; 1349 }; 1350 1351 class BooleanFieldDelegate : public FieldDelegate { 1352 public: 1353 BooleanFieldDelegate(const char *label, bool content) 1354 : m_label(label), m_content(content) {} 1355 1356 // Boolean fields are drawn as checkboxes. 1357 // 1358 // [X] Label or [ ] Label 1359 1360 // Boolean fields are have a single line. 1361 int FieldDelegateGetHeight() override { return 1; } 1362 1363 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1364 surface.MoveCursor(0, 0); 1365 surface.PutChar('['); 1366 if (is_selected) 1367 surface.AttributeOn(A_REVERSE); 1368 surface.PutChar(m_content ? ACS_DIAMOND : ' '); 1369 if (is_selected) 1370 surface.AttributeOff(A_REVERSE); 1371 surface.PutChar(']'); 1372 surface.PutChar(' '); 1373 surface.PutCString(m_label.c_str()); 1374 } 1375 1376 void ToggleContent() { m_content = !m_content; } 1377 1378 void SetContentToTrue() { m_content = true; } 1379 1380 void SetContentToFalse() { m_content = false; } 1381 1382 HandleCharResult FieldDelegateHandleChar(int key) override { 1383 switch (key) { 1384 case 't': 1385 case '1': 1386 SetContentToTrue(); 1387 return eKeyHandled; 1388 case 'f': 1389 case '0': 1390 SetContentToFalse(); 1391 return eKeyHandled; 1392 case ' ': 1393 case '\r': 1394 case '\n': 1395 case KEY_ENTER: 1396 ToggleContent(); 1397 return eKeyHandled; 1398 default: 1399 break; 1400 } 1401 return eKeyNotHandled; 1402 } 1403 1404 // Returns the boolean content of the field. 1405 bool GetBoolean() { return m_content; } 1406 1407 protected: 1408 std::string m_label; 1409 bool m_content; 1410 }; 1411 1412 class ChoicesFieldDelegate : public FieldDelegate { 1413 public: 1414 ChoicesFieldDelegate(const char *label, int number_of_visible_choices, 1415 std::vector<std::string> choices) 1416 : m_label(label), m_number_of_visible_choices(number_of_visible_choices), 1417 m_choices(choices), m_choice(0), m_first_visibile_choice(0) {} 1418 1419 // Choices fields are drawn as titles boxses of a number of visible choices. 1420 // The rest of the choices become visible as the user scroll. The selected 1421 // choice is denoted by a diamond as the first character. 1422 // 1423 // __[Label]___________ 1424 // |-Choice 1 | 1425 // | Choice 2 | 1426 // | Choice 3 | 1427 // |__________________| 1428 1429 // Choices field have two border characters plus the number of visible 1430 // choices. 1431 int FieldDelegateGetHeight() override { 1432 return m_number_of_visible_choices + 2; 1433 } 1434 1435 int GetNumberOfChoices() { return m_choices.size(); } 1436 1437 // Get the index of the last visible choice. 1438 int GetLastVisibleChoice() { 1439 int index = m_first_visibile_choice + m_number_of_visible_choices; 1440 return std::min(index, GetNumberOfChoices()) - 1; 1441 } 1442 1443 void DrawContent(SubPad &surface, bool is_selected) { 1444 int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1; 1445 for (int i = 0; i < choices_to_draw; i++) { 1446 surface.MoveCursor(0, i); 1447 int current_choice = m_first_visibile_choice + i; 1448 const char *text = m_choices[current_choice].c_str(); 1449 bool highlight = is_selected && current_choice == m_choice; 1450 if (highlight) 1451 surface.AttributeOn(A_REVERSE); 1452 surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' '); 1453 surface.PutCString(text); 1454 if (highlight) 1455 surface.AttributeOff(A_REVERSE); 1456 } 1457 } 1458 1459 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1460 UpdateScrolling(); 1461 1462 surface.TitledBox(m_label.c_str()); 1463 1464 Rect content_bounds = surface.GetFrame(); 1465 content_bounds.Inset(1, 1); 1466 SubPad content_surface = SubPad(surface, content_bounds); 1467 1468 DrawContent(content_surface, is_selected); 1469 } 1470 1471 void SelectPrevious() { 1472 if (m_choice > 0) 1473 m_choice--; 1474 } 1475 1476 void SelectNext() { 1477 if (m_choice < GetNumberOfChoices() - 1) 1478 m_choice++; 1479 } 1480 1481 void UpdateScrolling() { 1482 if (m_choice > GetLastVisibleChoice()) { 1483 m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1); 1484 return; 1485 } 1486 1487 if (m_choice < m_first_visibile_choice) 1488 m_first_visibile_choice = m_choice; 1489 } 1490 1491 HandleCharResult FieldDelegateHandleChar(int key) override { 1492 switch (key) { 1493 case KEY_UP: 1494 SelectPrevious(); 1495 return eKeyHandled; 1496 case KEY_DOWN: 1497 SelectNext(); 1498 return eKeyHandled; 1499 default: 1500 break; 1501 } 1502 return eKeyNotHandled; 1503 } 1504 1505 // Returns the content of the choice as a string. 1506 std::string GetChoiceContent() { return m_choices[m_choice]; } 1507 1508 // Returns the index of the choice. 1509 int GetChoice() { return m_choice; } 1510 1511 void SetChoice(const std::string &choice) { 1512 for (int i = 0; i < GetNumberOfChoices(); i++) { 1513 if (choice == m_choices[i]) { 1514 m_choice = i; 1515 return; 1516 } 1517 } 1518 } 1519 1520 protected: 1521 std::string m_label; 1522 int m_number_of_visible_choices; 1523 std::vector<std::string> m_choices; 1524 // The index of the selected choice. 1525 int m_choice; 1526 // The index of the first visible choice in the field. 1527 int m_first_visibile_choice; 1528 }; 1529 1530 class PlatformPluginFieldDelegate : public ChoicesFieldDelegate { 1531 public: 1532 PlatformPluginFieldDelegate(Debugger &debugger) 1533 : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) { 1534 PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); 1535 if (platform_sp) 1536 SetChoice(platform_sp->GetName().AsCString()); 1537 } 1538 1539 std::vector<std::string> GetPossiblePluginNames() { 1540 std::vector<std::string> names; 1541 size_t i = 0; 1542 while (auto name = PluginManager::GetPlatformPluginNameAtIndex(i++)) 1543 names.push_back(name); 1544 return names; 1545 } 1546 1547 std::string GetPluginName() { 1548 std::string plugin_name = GetChoiceContent(); 1549 return plugin_name; 1550 } 1551 }; 1552 1553 class ProcessPluginFieldDelegate : public ChoicesFieldDelegate { 1554 public: 1555 ProcessPluginFieldDelegate() 1556 : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {} 1557 1558 std::vector<std::string> GetPossiblePluginNames() { 1559 std::vector<std::string> names; 1560 names.push_back("<default>"); 1561 1562 size_t i = 0; 1563 while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++)) 1564 names.push_back(name); 1565 return names; 1566 } 1567 1568 std::string GetPluginName() { 1569 std::string plugin_name = GetChoiceContent(); 1570 if (plugin_name == "<default>") 1571 return ""; 1572 return plugin_name; 1573 } 1574 }; 1575 1576 template <class T> class ListFieldDelegate : public FieldDelegate { 1577 public: 1578 ListFieldDelegate(const char *label, T default_field) 1579 : m_label(label), m_default_field(default_field), m_selection_index(0), 1580 m_selection_type(SelectionType::NewButton) {} 1581 1582 // Signify which element is selected. If a field or a remove button is 1583 // selected, then m_selection_index signifies the particular field that 1584 // is selected or the field that the remove button belongs to. 1585 enum class SelectionType { Field, RemoveButton, NewButton }; 1586 1587 // A List field is drawn as a titled box of a number of other fields of the 1588 // same type. Each field has a Remove button next to it that removes the 1589 // corresponding field. Finally, the last line contains a New button to add a 1590 // new field. 1591 // 1592 // __[Label]___________ 1593 // | Field 0 [Remove] | 1594 // | Field 1 [Remove] | 1595 // | Field 2 [Remove] | 1596 // | [New] | 1597 // |__________________| 1598 1599 // List fields have two lines for border characters, 1 line for the New 1600 // button, and the total height of the available fields. 1601 int FieldDelegateGetHeight() override { 1602 // 2 border characters. 1603 int height = 2; 1604 // Total height of the fields. 1605 for (int i = 0; i < GetNumberOfFields(); i++) { 1606 height += m_fields[i].FieldDelegateGetHeight(); 1607 } 1608 // A line for the New button. 1609 height++; 1610 return height; 1611 } 1612 1613 ScrollContext FieldDelegateGetScrollContext() override { 1614 int height = FieldDelegateGetHeight(); 1615 if (m_selection_type == SelectionType::NewButton) 1616 return ScrollContext(height - 2, height - 1); 1617 1618 FieldDelegate &field = m_fields[m_selection_index]; 1619 ScrollContext context = field.FieldDelegateGetScrollContext(); 1620 1621 // Start at 1 because of the top border. 1622 int offset = 1; 1623 for (int i = 0; i < m_selection_index; i++) { 1624 offset += m_fields[i].FieldDelegateGetHeight(); 1625 } 1626 context.Offset(offset); 1627 1628 // If the scroll context is touching the top border, include it in the 1629 // context to show the label. 1630 if (context.start == 1) 1631 context.start--; 1632 1633 // If the scroll context is touching the new button, include it as well as 1634 // the bottom border in the context. 1635 if (context.end == height - 3) 1636 context.end += 2; 1637 1638 return context; 1639 } 1640 1641 void DrawRemoveButton(SubPad &surface, int highlight) { 1642 surface.MoveCursor(1, surface.GetHeight() / 2); 1643 if (highlight) 1644 surface.AttributeOn(A_REVERSE); 1645 surface.PutCString("[Remove]"); 1646 if (highlight) 1647 surface.AttributeOff(A_REVERSE); 1648 } 1649 1650 void DrawFields(SubPad &surface, bool is_selected) { 1651 int line = 0; 1652 int width = surface.GetWidth(); 1653 for (int i = 0; i < GetNumberOfFields(); i++) { 1654 int height = m_fields[i].FieldDelegateGetHeight(); 1655 Rect bounds = Rect(Point(0, line), Size(width, height)); 1656 Rect field_bounds, remove_button_bounds; 1657 bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"), 1658 field_bounds, remove_button_bounds); 1659 SubPad field_surface = SubPad(surface, field_bounds); 1660 SubPad remove_button_surface = SubPad(surface, remove_button_bounds); 1661 1662 bool is_element_selected = m_selection_index == i && is_selected; 1663 bool is_field_selected = 1664 is_element_selected && m_selection_type == SelectionType::Field; 1665 bool is_remove_button_selected = 1666 is_element_selected && 1667 m_selection_type == SelectionType::RemoveButton; 1668 m_fields[i].FieldDelegateDraw(field_surface, is_field_selected); 1669 DrawRemoveButton(remove_button_surface, is_remove_button_selected); 1670 1671 line += height; 1672 } 1673 } 1674 1675 void DrawNewButton(SubPad &surface, bool is_selected) { 1676 const char *button_text = "[New]"; 1677 int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2; 1678 surface.MoveCursor(x, 0); 1679 bool highlight = 1680 is_selected && m_selection_type == SelectionType::NewButton; 1681 if (highlight) 1682 surface.AttributeOn(A_REVERSE); 1683 surface.PutCString(button_text); 1684 if (highlight) 1685 surface.AttributeOff(A_REVERSE); 1686 } 1687 1688 void FieldDelegateDraw(SubPad &surface, bool is_selected) override { 1689 surface.TitledBox(m_label.c_str()); 1690 1691 Rect content_bounds = surface.GetFrame(); 1692 content_bounds.Inset(1, 1); 1693 Rect fields_bounds, new_button_bounds; 1694 content_bounds.HorizontalSplit(content_bounds.size.height - 1, 1695 fields_bounds, new_button_bounds); 1696 SubPad fields_surface = SubPad(surface, fields_bounds); 1697 SubPad new_button_surface = SubPad(surface, new_button_bounds); 1698 1699 DrawFields(fields_surface, is_selected); 1700 DrawNewButton(new_button_surface, is_selected); 1701 } 1702 1703 void AddNewField() { 1704 m_fields.push_back(m_default_field); 1705 m_selection_index = GetNumberOfFields() - 1; 1706 m_selection_type = SelectionType::Field; 1707 FieldDelegate &field = m_fields[m_selection_index]; 1708 field.FieldDelegateSelectFirstElement(); 1709 } 1710 1711 void RemoveField() { 1712 m_fields.erase(m_fields.begin() + m_selection_index); 1713 if (m_selection_index != 0) 1714 m_selection_index--; 1715 1716 if (GetNumberOfFields() > 0) { 1717 m_selection_type = SelectionType::Field; 1718 FieldDelegate &field = m_fields[m_selection_index]; 1719 field.FieldDelegateSelectFirstElement(); 1720 } else 1721 m_selection_type = SelectionType::NewButton; 1722 } 1723 1724 HandleCharResult SelectNext(int key) { 1725 if (m_selection_type == SelectionType::NewButton) 1726 return eKeyNotHandled; 1727 1728 if (m_selection_type == SelectionType::RemoveButton) { 1729 if (m_selection_index == GetNumberOfFields() - 1) { 1730 m_selection_type = SelectionType::NewButton; 1731 return eKeyHandled; 1732 } 1733 m_selection_index++; 1734 m_selection_type = SelectionType::Field; 1735 FieldDelegate &next_field = m_fields[m_selection_index]; 1736 next_field.FieldDelegateSelectFirstElement(); 1737 return eKeyHandled; 1738 } 1739 1740 FieldDelegate &field = m_fields[m_selection_index]; 1741 if (!field.FieldDelegateOnLastOrOnlyElement()) { 1742 return field.FieldDelegateHandleChar(key); 1743 } 1744 1745 field.FieldDelegateExitCallback(); 1746 1747 m_selection_type = SelectionType::RemoveButton; 1748 return eKeyHandled; 1749 } 1750 1751 HandleCharResult SelectPrevious(int key) { 1752 if (FieldDelegateOnFirstOrOnlyElement()) 1753 return eKeyNotHandled; 1754 1755 if (m_selection_type == SelectionType::RemoveButton) { 1756 m_selection_type = SelectionType::Field; 1757 FieldDelegate &field = m_fields[m_selection_index]; 1758 field.FieldDelegateSelectLastElement(); 1759 return eKeyHandled; 1760 } 1761 1762 if (m_selection_type == SelectionType::NewButton) { 1763 m_selection_type = SelectionType::RemoveButton; 1764 m_selection_index = GetNumberOfFields() - 1; 1765 return eKeyHandled; 1766 } 1767 1768 FieldDelegate &field = m_fields[m_selection_index]; 1769 if (!field.FieldDelegateOnFirstOrOnlyElement()) { 1770 return field.FieldDelegateHandleChar(key); 1771 } 1772 1773 field.FieldDelegateExitCallback(); 1774 1775 m_selection_type = SelectionType::RemoveButton; 1776 m_selection_index--; 1777 return eKeyHandled; 1778 } 1779 1780 HandleCharResult FieldDelegateHandleChar(int key) override { 1781 switch (key) { 1782 case '\r': 1783 case '\n': 1784 case KEY_ENTER: 1785 switch (m_selection_type) { 1786 case SelectionType::NewButton: 1787 AddNewField(); 1788 return eKeyHandled; 1789 case SelectionType::RemoveButton: 1790 RemoveField(); 1791 return eKeyHandled; 1792 default: 1793 break; 1794 } 1795 break; 1796 case '\t': 1797 SelectNext(key); 1798 return eKeyHandled; 1799 case KEY_SHIFT_TAB: 1800 SelectPrevious(key); 1801 return eKeyHandled; 1802 default: 1803 break; 1804 } 1805 1806 // If the key wasn't handled and one of the fields is selected, pass the key 1807 // to that field. 1808 if (m_selection_type == SelectionType::Field) { 1809 return m_fields[m_selection_index].FieldDelegateHandleChar(key); 1810 } 1811 1812 return eKeyNotHandled; 1813 } 1814 1815 bool FieldDelegateOnLastOrOnlyElement() override { 1816 if (m_selection_type == SelectionType::NewButton) { 1817 return true; 1818 } 1819 return false; 1820 } 1821 1822 bool FieldDelegateOnFirstOrOnlyElement() override { 1823 if (m_selection_type == SelectionType::NewButton && 1824 GetNumberOfFields() == 0) 1825 return true; 1826 1827 if (m_selection_type == SelectionType::Field && m_selection_index == 0) { 1828 FieldDelegate &field = m_fields[m_selection_index]; 1829 return field.FieldDelegateOnFirstOrOnlyElement(); 1830 } 1831 1832 return false; 1833 } 1834 1835 void FieldDelegateSelectFirstElement() override { 1836 if (GetNumberOfFields() == 0) { 1837 m_selection_type = SelectionType::NewButton; 1838 return; 1839 } 1840 1841 m_selection_type = SelectionType::Field; 1842 m_selection_index = 0; 1843 } 1844 1845 void FieldDelegateSelectLastElement() override { 1846 m_selection_type = SelectionType::NewButton; 1847 return; 1848 } 1849 1850 int GetNumberOfFields() { return m_fields.size(); } 1851 1852 // Returns the form delegate at the current index. 1853 T &GetField(int index) { return m_fields[index]; } 1854 1855 protected: 1856 std::string m_label; 1857 // The default field delegate instance from which new field delegates will be 1858 // created though a copy. 1859 T m_default_field; 1860 std::vector<T> m_fields; 1861 int m_selection_index; 1862 // See SelectionType class enum. 1863 SelectionType m_selection_type; 1864 }; 1865 1866 class FormAction { 1867 public: 1868 FormAction(const char *label, std::function<void(Window &)> action) 1869 : m_action(action) { 1870 if (label) 1871 m_label = label; 1872 } 1873 1874 // Draw a centered [Label]. 1875 void Draw(SubPad &surface, bool is_selected) { 1876 int x = (surface.GetWidth() - m_label.length()) / 2; 1877 surface.MoveCursor(x, 0); 1878 if (is_selected) 1879 surface.AttributeOn(A_REVERSE); 1880 surface.PutChar('['); 1881 surface.PutCString(m_label.c_str()); 1882 surface.PutChar(']'); 1883 if (is_selected) 1884 surface.AttributeOff(A_REVERSE); 1885 } 1886 1887 void Execute(Window &window) { m_action(window); } 1888 1889 const std::string &GetLabel() { return m_label; } 1890 1891 protected: 1892 std::string m_label; 1893 std::function<void(Window &)> m_action; 1894 }; 1895 1896 class FormDelegate { 1897 public: 1898 FormDelegate() {} 1899 1900 virtual ~FormDelegate() = default; 1901 1902 virtual std::string GetName() = 0; 1903 1904 virtual void UpdateFieldsVisibility() { return; } 1905 1906 FieldDelegate *GetField(uint32_t field_index) { 1907 if (field_index < m_fields.size()) 1908 return m_fields[field_index].get(); 1909 return nullptr; 1910 } 1911 1912 FormAction &GetAction(int action_index) { return m_actions[action_index]; } 1913 1914 int GetNumberOfFields() { return m_fields.size(); } 1915 1916 int GetNumberOfActions() { return m_actions.size(); } 1917 1918 bool HasError() { return !m_error.empty(); } 1919 1920 void ClearError() { m_error.clear(); } 1921 1922 const std::string &GetError() { return m_error; } 1923 1924 void SetError(const char *error) { m_error = error; } 1925 1926 // If all fields are valid, true is returned. Otherwise, an error message is 1927 // set and false is returned. This method is usually called at the start of an 1928 // action that requires valid fields. 1929 bool CheckFieldsValidity() { 1930 for (int i = 0; i < GetNumberOfFields(); i++) { 1931 if (GetField(i)->FieldDelegateHasError()) { 1932 SetError("Some fields are invalid!"); 1933 return false; 1934 } 1935 } 1936 return true; 1937 } 1938 1939 // Factory methods to create and add fields of specific types. 1940 1941 TextFieldDelegate *AddTextField(const char *label, const char *content, 1942 bool required) { 1943 TextFieldDelegate *delegate = 1944 new TextFieldDelegate(label, content, required); 1945 m_fields.push_back(FieldDelegateUP(delegate)); 1946 return delegate; 1947 } 1948 1949 FileFieldDelegate *AddFileField(const char *label, const char *content, 1950 bool need_to_exist, bool required) { 1951 FileFieldDelegate *delegate = 1952 new FileFieldDelegate(label, content, need_to_exist, required); 1953 m_fields.push_back(FieldDelegateUP(delegate)); 1954 return delegate; 1955 } 1956 1957 DirectoryFieldDelegate *AddDirectoryField(const char *label, 1958 const char *content, 1959 bool need_to_exist, bool required) { 1960 DirectoryFieldDelegate *delegate = 1961 new DirectoryFieldDelegate(label, content, need_to_exist, required); 1962 m_fields.push_back(FieldDelegateUP(delegate)); 1963 return delegate; 1964 } 1965 1966 IntegerFieldDelegate *AddIntegerField(const char *label, int content, 1967 bool required) { 1968 IntegerFieldDelegate *delegate = 1969 new IntegerFieldDelegate(label, content, required); 1970 m_fields.push_back(FieldDelegateUP(delegate)); 1971 return delegate; 1972 } 1973 1974 BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { 1975 BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); 1976 m_fields.push_back(FieldDelegateUP(delegate)); 1977 return delegate; 1978 } 1979 1980 ChoicesFieldDelegate *AddChoicesField(const char *label, int height, 1981 std::vector<std::string> choices) { 1982 ChoicesFieldDelegate *delegate = 1983 new ChoicesFieldDelegate(label, height, choices); 1984 m_fields.push_back(FieldDelegateUP(delegate)); 1985 return delegate; 1986 } 1987 1988 PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { 1989 PlatformPluginFieldDelegate *delegate = 1990 new PlatformPluginFieldDelegate(debugger); 1991 m_fields.push_back(FieldDelegateUP(delegate)); 1992 return delegate; 1993 } 1994 1995 ProcessPluginFieldDelegate *AddProcessPluginField() { 1996 ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); 1997 m_fields.push_back(FieldDelegateUP(delegate)); 1998 return delegate; 1999 } 2000 2001 template <class T> 2002 ListFieldDelegate<T> *AddListField(const char *label, T default_field) { 2003 ListFieldDelegate<T> *delegate = 2004 new ListFieldDelegate<T>(label, default_field); 2005 m_fields.push_back(FieldDelegateUP(delegate)); 2006 return delegate; 2007 } 2008 2009 // Factory methods for adding actions. 2010 2011 void AddAction(const char *label, std::function<void(Window &)> action) { 2012 m_actions.push_back(FormAction(label, action)); 2013 } 2014 2015 protected: 2016 std::vector<FieldDelegateUP> m_fields; 2017 std::vector<FormAction> m_actions; 2018 // Optional error message. If empty, form is considered to have no error. 2019 std::string m_error; 2020 }; 2021 2022 typedef std::shared_ptr<FormDelegate> FormDelegateSP; 2023 2024 class FormWindowDelegate : public WindowDelegate { 2025 public: 2026 FormWindowDelegate(FormDelegateSP &delegate_sp) 2027 : m_delegate_sp(delegate_sp), m_selection_index(0), 2028 m_first_visible_line(0) { 2029 assert(m_delegate_sp->GetNumberOfActions() > 0); 2030 if (m_delegate_sp->GetNumberOfFields() > 0) 2031 m_selection_type = SelectionType::Field; 2032 else 2033 m_selection_type = SelectionType::Action; 2034 } 2035 2036 // Signify which element is selected. If a field or an action is selected, 2037 // then m_selection_index signifies the particular field or action that is 2038 // selected. 2039 enum class SelectionType { Field, Action }; 2040 2041 // A form window is padded by one character from all sides. First, if an error 2042 // message exists, it is drawn followed by a separator. Then one or more 2043 // fields are drawn. Finally, all available actions are drawn on a single 2044 // line. 2045 // 2046 // ___<Form Name>_________________________________________________ 2047 // | | 2048 // | - Error message if it exists. | 2049 // |-------------------------------------------------------------| 2050 // | Form elements here. | 2051 // | Form actions here. | 2052 // | | 2053 // |______________________________________[Press Esc to cancel]__| 2054 // 2055 2056 // One line for the error and another for the horizontal line. 2057 int GetErrorHeight() { 2058 if (m_delegate_sp->HasError()) 2059 return 2; 2060 return 0; 2061 } 2062 2063 // Actions span a single line. 2064 int GetActionsHeight() { 2065 if (m_delegate_sp->GetNumberOfActions() > 0) 2066 return 1; 2067 return 0; 2068 } 2069 2070 // Get the total number of needed lines to draw the contents. 2071 int GetContentHeight() { 2072 int height = 0; 2073 height += GetErrorHeight(); 2074 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2075 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2076 continue; 2077 height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2078 } 2079 height += GetActionsHeight(); 2080 return height; 2081 } 2082 2083 ScrollContext GetScrollContext() { 2084 if (m_selection_type == SelectionType::Action) 2085 return ScrollContext(GetContentHeight() - 1); 2086 2087 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2088 ScrollContext context = field->FieldDelegateGetScrollContext(); 2089 2090 int offset = GetErrorHeight(); 2091 for (int i = 0; i < m_selection_index; i++) { 2092 if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) 2093 continue; 2094 offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); 2095 } 2096 context.Offset(offset); 2097 2098 // If the context is touching the error, include the error in the context as 2099 // well. 2100 if (context.start == GetErrorHeight()) 2101 context.start = 0; 2102 2103 return context; 2104 } 2105 2106 void UpdateScrolling(DerivedWindow &surface) { 2107 ScrollContext context = GetScrollContext(); 2108 int content_height = GetContentHeight(); 2109 int surface_height = surface.GetHeight(); 2110 int visible_height = std::min(content_height, surface_height); 2111 int last_visible_line = m_first_visible_line + visible_height - 1; 2112 2113 // If the last visible line is bigger than the content, then it is invalid 2114 // and needs to be set to the last line in the content. This can happen when 2115 // a field has shrunk in height. 2116 if (last_visible_line > content_height - 1) { 2117 m_first_visible_line = content_height - visible_height; 2118 } 2119 2120 if (context.start < m_first_visible_line) { 2121 m_first_visible_line = context.start; 2122 return; 2123 } 2124 2125 if (context.end > last_visible_line) { 2126 m_first_visible_line = context.end - visible_height + 1; 2127 } 2128 } 2129 2130 void DrawError(SubPad &surface) { 2131 if (!m_delegate_sp->HasError()) 2132 return; 2133 surface.MoveCursor(0, 0); 2134 surface.AttributeOn(COLOR_PAIR(RedOnBlack)); 2135 surface.PutChar(ACS_DIAMOND); 2136 surface.PutChar(' '); 2137 surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); 2138 surface.AttributeOff(COLOR_PAIR(RedOnBlack)); 2139 2140 surface.MoveCursor(0, 1); 2141 surface.HorizontalLine(surface.GetWidth()); 2142 } 2143 2144 void DrawFields(SubPad &surface) { 2145 int line = 0; 2146 int width = surface.GetWidth(); 2147 bool a_field_is_selected = m_selection_type == SelectionType::Field; 2148 for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { 2149 FieldDelegate *field = m_delegate_sp->GetField(i); 2150 if (!field->FieldDelegateIsVisible()) 2151 continue; 2152 bool is_field_selected = a_field_is_selected && m_selection_index == i; 2153 int height = field->FieldDelegateGetHeight(); 2154 Rect bounds = Rect(Point(0, line), Size(width, height)); 2155 SubPad field_surface = SubPad(surface, bounds); 2156 field->FieldDelegateDraw(field_surface, is_field_selected); 2157 line += height; 2158 } 2159 } 2160 2161 void DrawActions(SubPad &surface) { 2162 int number_of_actions = m_delegate_sp->GetNumberOfActions(); 2163 int width = surface.GetWidth() / number_of_actions; 2164 bool an_action_is_selected = m_selection_type == SelectionType::Action; 2165 int x = 0; 2166 for (int i = 0; i < number_of_actions; i++) { 2167 bool is_action_selected = an_action_is_selected && m_selection_index == i; 2168 FormAction &action = m_delegate_sp->GetAction(i); 2169 Rect bounds = Rect(Point(x, 0), Size(width, 1)); 2170 SubPad action_surface = SubPad(surface, bounds); 2171 action.Draw(action_surface, is_action_selected); 2172 x += width; 2173 } 2174 } 2175 2176 void DrawElements(SubPad &surface) { 2177 Rect frame = surface.GetFrame(); 2178 Rect fields_bounds, actions_bounds; 2179 frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), 2180 fields_bounds, actions_bounds); 2181 SubPad fields_surface = SubPad(surface, fields_bounds); 2182 SubPad actions_surface = SubPad(surface, actions_bounds); 2183 2184 DrawFields(fields_surface); 2185 DrawActions(actions_surface); 2186 } 2187 2188 // Contents are first drawn on a pad. Then a subset of that pad is copied to 2189 // the derived window starting at the first visible line. This essentially 2190 // provides scrolling functionality. 2191 void DrawContent(DerivedWindow &surface) { 2192 UpdateScrolling(surface); 2193 2194 int width = surface.GetWidth(); 2195 int height = GetContentHeight(); 2196 Pad pad = Pad(Size(width, height)); 2197 2198 Rect frame = pad.GetFrame(); 2199 Rect error_bounds, elements_bounds; 2200 frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); 2201 SubPad error_surface = SubPad(pad, error_bounds); 2202 SubPad elements_surface = SubPad(pad, elements_bounds); 2203 2204 DrawError(error_surface); 2205 DrawElements(elements_surface); 2206 2207 int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); 2208 pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), 2209 Size(width, copy_height)); 2210 } 2211 2212 bool WindowDelegateDraw(Window &window, bool force) override { 2213 m_delegate_sp->UpdateFieldsVisibility(); 2214 2215 window.Erase(); 2216 2217 window.DrawTitleBox(m_delegate_sp->GetName().c_str(), 2218 "Press Esc to cancel"); 2219 2220 Rect content_bounds = window.GetFrame(); 2221 content_bounds.Inset(2, 2); 2222 DerivedWindow content_surface = DerivedWindow(window, content_bounds); 2223 2224 DrawContent(content_surface); 2225 return true; 2226 } 2227 2228 void SkipNextHiddenFields() { 2229 while (true) { 2230 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2231 return; 2232 2233 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2234 m_selection_type = SelectionType::Action; 2235 m_selection_index = 0; 2236 return; 2237 } 2238 2239 m_selection_index++; 2240 } 2241 } 2242 2243 HandleCharResult SelectNext(int key) { 2244 if (m_selection_type == SelectionType::Action) { 2245 if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { 2246 m_selection_index++; 2247 return eKeyHandled; 2248 } 2249 2250 m_selection_index = 0; 2251 m_selection_type = SelectionType::Field; 2252 SkipNextHiddenFields(); 2253 if (m_selection_type == SelectionType::Field) { 2254 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2255 next_field->FieldDelegateSelectFirstElement(); 2256 } 2257 return eKeyHandled; 2258 } 2259 2260 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2261 if (!field->FieldDelegateOnLastOrOnlyElement()) { 2262 return field->FieldDelegateHandleChar(key); 2263 } 2264 2265 field->FieldDelegateExitCallback(); 2266 2267 if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { 2268 m_selection_type = SelectionType::Action; 2269 m_selection_index = 0; 2270 return eKeyHandled; 2271 } 2272 2273 m_selection_index++; 2274 SkipNextHiddenFields(); 2275 2276 if (m_selection_type == SelectionType::Field) { 2277 FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); 2278 next_field->FieldDelegateSelectFirstElement(); 2279 } 2280 2281 return eKeyHandled; 2282 } 2283 2284 void SkipPreviousHiddenFields() { 2285 while (true) { 2286 if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) 2287 return; 2288 2289 if (m_selection_index == 0) { 2290 m_selection_type = SelectionType::Action; 2291 m_selection_index = 0; 2292 return; 2293 } 2294 2295 m_selection_index--; 2296 } 2297 } 2298 2299 HandleCharResult SelectPrevious(int key) { 2300 if (m_selection_type == SelectionType::Action) { 2301 if (m_selection_index > 0) { 2302 m_selection_index--; 2303 return eKeyHandled; 2304 } 2305 m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; 2306 m_selection_type = SelectionType::Field; 2307 SkipPreviousHiddenFields(); 2308 if (m_selection_type == SelectionType::Field) { 2309 FieldDelegate *previous_field = 2310 m_delegate_sp->GetField(m_selection_index); 2311 previous_field->FieldDelegateSelectLastElement(); 2312 } 2313 return eKeyHandled; 2314 } 2315 2316 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2317 if (!field->FieldDelegateOnFirstOrOnlyElement()) { 2318 return field->FieldDelegateHandleChar(key); 2319 } 2320 2321 field->FieldDelegateExitCallback(); 2322 2323 if (m_selection_index == 0) { 2324 m_selection_type = SelectionType::Action; 2325 m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; 2326 return eKeyHandled; 2327 } 2328 2329 m_selection_index--; 2330 SkipPreviousHiddenFields(); 2331 2332 if (m_selection_type == SelectionType::Field) { 2333 FieldDelegate *previous_field = 2334 m_delegate_sp->GetField(m_selection_index); 2335 previous_field->FieldDelegateSelectLastElement(); 2336 } 2337 2338 return eKeyHandled; 2339 } 2340 2341 void ExecuteAction(Window &window) { 2342 FormAction &action = m_delegate_sp->GetAction(m_selection_index); 2343 action.Execute(window); 2344 if (m_delegate_sp->HasError()) { 2345 m_first_visible_line = 0; 2346 m_selection_index = 0; 2347 m_selection_type = SelectionType::Field; 2348 } 2349 } 2350 2351 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 2352 switch (key) { 2353 case '\r': 2354 case '\n': 2355 case KEY_ENTER: 2356 if (m_selection_type == SelectionType::Action) { 2357 ExecuteAction(window); 2358 return eKeyHandled; 2359 } 2360 break; 2361 case '\t': 2362 return SelectNext(key); 2363 case KEY_SHIFT_TAB: 2364 return SelectPrevious(key); 2365 case KEY_ESCAPE: 2366 window.GetParent()->RemoveSubWindow(&window); 2367 return eKeyHandled; 2368 default: 2369 break; 2370 } 2371 2372 // If the key wasn't handled and one of the fields is selected, pass the key 2373 // to that field. 2374 if (m_selection_type == SelectionType::Field) { 2375 FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); 2376 return field->FieldDelegateHandleChar(key); 2377 } 2378 2379 return eKeyNotHandled; 2380 } 2381 2382 protected: 2383 FormDelegateSP m_delegate_sp; 2384 // The index of the currently selected SelectionType. 2385 int m_selection_index; 2386 // See SelectionType class enum. 2387 SelectionType m_selection_type; 2388 // The first visible line from the pad. 2389 int m_first_visible_line; 2390 }; 2391 2392 /////////////////////////// 2393 // Form Delegate Instances 2394 /////////////////////////// 2395 2396 class DetachOrKillProcessFormDelegate : public FormDelegate { 2397 public: 2398 DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { 2399 SetError("There is a running process, either detach or kill it."); 2400 2401 m_keep_stopped_field = 2402 AddBooleanField("Keep process stopped when detaching.", false); 2403 2404 AddAction("Detach", [this](Window &window) { Detach(window); }); 2405 AddAction("Kill", [this](Window &window) { Kill(window); }); 2406 } 2407 2408 std::string GetName() override { return "Detach/Kill Process"; } 2409 2410 void Kill(Window &window) { 2411 Status destroy_status(m_process->Destroy(false)); 2412 if (destroy_status.Fail()) { 2413 SetError("Failed to kill process."); 2414 return; 2415 } 2416 window.GetParent()->RemoveSubWindow(&window); 2417 } 2418 2419 void Detach(Window &window) { 2420 Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); 2421 if (detach_status.Fail()) { 2422 SetError("Failed to detach from process."); 2423 return; 2424 } 2425 window.GetParent()->RemoveSubWindow(&window); 2426 } 2427 2428 protected: 2429 Process *m_process; 2430 BooleanFieldDelegate *m_keep_stopped_field; 2431 }; 2432 2433 class ProcessAttachFormDelegate : public FormDelegate { 2434 public: 2435 ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) 2436 : m_debugger(debugger), m_main_window_sp(main_window_sp) { 2437 std::vector<std::string> types; 2438 types.push_back(std::string("Name")); 2439 types.push_back(std::string("PID")); 2440 m_type_field = AddChoicesField("Attach By", 2, types); 2441 m_pid_field = AddIntegerField("PID", 0, true); 2442 m_name_field = 2443 AddTextField("Process Name", GetDefaultProcessName().c_str(), true); 2444 m_continue_field = AddBooleanField("Continue once attached.", false); 2445 m_wait_for_field = AddBooleanField("Wait for process to launch.", false); 2446 m_include_existing_field = 2447 AddBooleanField("Include existing processes.", false); 2448 m_show_advanced_field = AddBooleanField("Show advanced settings.", false); 2449 m_plugin_field = AddProcessPluginField(); 2450 2451 AddAction("Attach", [this](Window &window) { Attach(window); }); 2452 } 2453 2454 std::string GetName() override { return "Attach Process"; } 2455 2456 void UpdateFieldsVisibility() override { 2457 if (m_type_field->GetChoiceContent() == "Name") { 2458 m_pid_field->FieldDelegateHide(); 2459 m_name_field->FieldDelegateShow(); 2460 m_wait_for_field->FieldDelegateShow(); 2461 if (m_wait_for_field->GetBoolean()) 2462 m_include_existing_field->FieldDelegateShow(); 2463 else 2464 m_include_existing_field->FieldDelegateHide(); 2465 } else { 2466 m_pid_field->FieldDelegateShow(); 2467 m_name_field->FieldDelegateHide(); 2468 m_wait_for_field->FieldDelegateHide(); 2469 m_include_existing_field->FieldDelegateHide(); 2470 } 2471 if (m_show_advanced_field->GetBoolean()) 2472 m_plugin_field->FieldDelegateShow(); 2473 else 2474 m_plugin_field->FieldDelegateHide(); 2475 } 2476 2477 // Get the basename of the target's main executable if available, empty string 2478 // otherwise. 2479 std::string GetDefaultProcessName() { 2480 Target *target = m_debugger.GetSelectedTarget().get(); 2481 if (target == nullptr) 2482 return ""; 2483 2484 ModuleSP module_sp = target->GetExecutableModule(); 2485 if (!module_sp->IsExecutable()) 2486 return ""; 2487 2488 return module_sp->GetFileSpec().GetFilename().AsCString(); 2489 } 2490 2491 bool StopRunningProcess() { 2492 ExecutionContext exe_ctx = 2493 m_debugger.GetCommandInterpreter().GetExecutionContext(); 2494 2495 if (!exe_ctx.HasProcessScope()) 2496 return false; 2497 2498 Process *process = exe_ctx.GetProcessPtr(); 2499 if (!(process && process->IsAlive())) 2500 return false; 2501 2502 FormDelegateSP form_delegate_sp = 2503 FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); 2504 Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); 2505 WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( 2506 form_delegate_sp->GetName().c_str(), bounds, true); 2507 WindowDelegateSP window_delegate_sp = 2508 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 2509 form_window_sp->SetDelegate(window_delegate_sp); 2510 2511 return true; 2512 } 2513 2514 Target *GetTarget() { 2515 Target *target = m_debugger.GetSelectedTarget().get(); 2516 2517 if (target != nullptr) 2518 return target; 2519 2520 TargetSP new_target_sp; 2521 m_debugger.GetTargetList().CreateTarget( 2522 m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); 2523 2524 target = new_target_sp.get(); 2525 2526 if (target == nullptr) 2527 SetError("Failed to create target."); 2528 2529 m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); 2530 2531 return target; 2532 } 2533 2534 ProcessAttachInfo GetAttachInfo() { 2535 ProcessAttachInfo attach_info; 2536 attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); 2537 if (m_type_field->GetChoiceContent() == "Name") { 2538 attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), 2539 FileSpec::Style::native); 2540 attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); 2541 if (m_wait_for_field->GetBoolean()) 2542 attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); 2543 } else { 2544 attach_info.SetProcessID(m_pid_field->GetInteger()); 2545 } 2546 attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); 2547 2548 return attach_info; 2549 } 2550 2551 void Attach(Window &window) { 2552 ClearError(); 2553 2554 bool all_fields_are_valid = CheckFieldsValidity(); 2555 if (!all_fields_are_valid) 2556 return; 2557 2558 bool process_is_running = StopRunningProcess(); 2559 if (process_is_running) 2560 return; 2561 2562 Target *target = GetTarget(); 2563 if (HasError()) 2564 return; 2565 2566 StreamString stream; 2567 ProcessAttachInfo attach_info = GetAttachInfo(); 2568 Status status = target->Attach(attach_info, &stream); 2569 2570 if (status.Fail()) { 2571 SetError(status.AsCString()); 2572 return; 2573 } 2574 2575 ProcessSP process_sp(target->GetProcessSP()); 2576 if (!process_sp) { 2577 SetError("Attached sucessfully but target has no process."); 2578 return; 2579 } 2580 2581 if (attach_info.GetContinueOnceAttached()) 2582 process_sp->Resume(); 2583 2584 window.GetParent()->RemoveSubWindow(&window); 2585 } 2586 2587 protected: 2588 Debugger &m_debugger; 2589 WindowSP m_main_window_sp; 2590 2591 ChoicesFieldDelegate *m_type_field; 2592 IntegerFieldDelegate *m_pid_field; 2593 TextFieldDelegate *m_name_field; 2594 BooleanFieldDelegate *m_continue_field; 2595 BooleanFieldDelegate *m_wait_for_field; 2596 BooleanFieldDelegate *m_include_existing_field; 2597 BooleanFieldDelegate *m_show_advanced_field; 2598 ProcessPluginFieldDelegate *m_plugin_field; 2599 }; 2600 2601 class MenuDelegate { 2602 public: 2603 virtual ~MenuDelegate() = default; 2604 2605 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 2606 }; 2607 2608 class Menu : public WindowDelegate { 2609 public: 2610 enum class Type { Invalid, Bar, Item, Separator }; 2611 2612 // Menubar or separator constructor 2613 Menu(Type type); 2614 2615 // Menuitem constructor 2616 Menu(const char *name, const char *key_name, int key_value, 2617 uint64_t identifier); 2618 2619 ~Menu() override = default; 2620 2621 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 2622 2623 void SetDelegate(const MenuDelegateSP &delegate_sp) { 2624 m_delegate_sp = delegate_sp; 2625 } 2626 2627 void RecalculateNameLengths(); 2628 2629 void AddSubmenu(const MenuSP &menu_sp); 2630 2631 int DrawAndRunMenu(Window &window); 2632 2633 void DrawMenuTitle(Window &window, bool highlight); 2634 2635 bool WindowDelegateDraw(Window &window, bool force) override; 2636 2637 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 2638 2639 MenuActionResult ActionPrivate(Menu &menu) { 2640 MenuActionResult result = MenuActionResult::NotHandled; 2641 if (m_delegate_sp) { 2642 result = m_delegate_sp->MenuDelegateAction(menu); 2643 if (result != MenuActionResult::NotHandled) 2644 return result; 2645 } else if (m_parent) { 2646 result = m_parent->ActionPrivate(menu); 2647 if (result != MenuActionResult::NotHandled) 2648 return result; 2649 } 2650 return m_canned_result; 2651 } 2652 2653 MenuActionResult Action() { 2654 // Call the recursive action so it can try to handle it with the menu 2655 // delegate, and if not, try our parent menu 2656 return ActionPrivate(*this); 2657 } 2658 2659 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 2660 2661 Menus &GetSubmenus() { return m_submenus; } 2662 2663 const Menus &GetSubmenus() const { return m_submenus; } 2664 2665 int GetSelectedSubmenuIndex() const { return m_selected; } 2666 2667 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 2668 2669 Type GetType() const { return m_type; } 2670 2671 int GetStartingColumn() const { return m_start_col; } 2672 2673 void SetStartingColumn(int col) { m_start_col = col; } 2674 2675 int GetKeyValue() const { return m_key_value; } 2676 2677 std::string &GetName() { return m_name; } 2678 2679 int GetDrawWidth() const { 2680 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 2681 } 2682 2683 uint64_t GetIdentifier() const { return m_identifier; } 2684 2685 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 2686 2687 protected: 2688 std::string m_name; 2689 std::string m_key_name; 2690 uint64_t m_identifier; 2691 Type m_type; 2692 int m_key_value; 2693 int m_start_col; 2694 int m_max_submenu_name_length; 2695 int m_max_submenu_key_name_length; 2696 int m_selected; 2697 Menu *m_parent; 2698 Menus m_submenus; 2699 WindowSP m_menu_window_sp; 2700 MenuActionResult m_canned_result; 2701 MenuDelegateSP m_delegate_sp; 2702 }; 2703 2704 // Menubar or separator constructor 2705 Menu::Menu(Type type) 2706 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 2707 m_start_col(0), m_max_submenu_name_length(0), 2708 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2709 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2710 m_delegate_sp() {} 2711 2712 // Menuitem constructor 2713 Menu::Menu(const char *name, const char *key_name, int key_value, 2714 uint64_t identifier) 2715 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 2716 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 2717 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 2718 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 2719 m_delegate_sp() { 2720 if (name && name[0]) { 2721 m_name = name; 2722 m_type = Type::Item; 2723 if (key_name && key_name[0]) 2724 m_key_name = key_name; 2725 } else { 2726 m_type = Type::Separator; 2727 } 2728 } 2729 2730 void Menu::RecalculateNameLengths() { 2731 m_max_submenu_name_length = 0; 2732 m_max_submenu_key_name_length = 0; 2733 Menus &submenus = GetSubmenus(); 2734 const size_t num_submenus = submenus.size(); 2735 for (size_t i = 0; i < num_submenus; ++i) { 2736 Menu *submenu = submenus[i].get(); 2737 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 2738 m_max_submenu_name_length = submenu->m_name.size(); 2739 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2740 submenu->m_key_name.size()) 2741 m_max_submenu_key_name_length = submenu->m_key_name.size(); 2742 } 2743 } 2744 2745 void Menu::AddSubmenu(const MenuSP &menu_sp) { 2746 menu_sp->m_parent = this; 2747 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 2748 m_max_submenu_name_length = menu_sp->m_name.size(); 2749 if (static_cast<size_t>(m_max_submenu_key_name_length) < 2750 menu_sp->m_key_name.size()) 2751 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 2752 m_submenus.push_back(menu_sp); 2753 } 2754 2755 void Menu::DrawMenuTitle(Window &window, bool highlight) { 2756 if (m_type == Type::Separator) { 2757 window.MoveCursor(0, window.GetCursorY()); 2758 window.PutChar(ACS_LTEE); 2759 int width = window.GetWidth(); 2760 if (width > 2) { 2761 width -= 2; 2762 for (int i = 0; i < width; ++i) 2763 window.PutChar(ACS_HLINE); 2764 } 2765 window.PutChar(ACS_RTEE); 2766 } else { 2767 const int shortcut_key = m_key_value; 2768 bool underlined_shortcut = false; 2769 const attr_t highlight_attr = A_REVERSE; 2770 if (highlight) 2771 window.AttributeOn(highlight_attr); 2772 if (llvm::isPrint(shortcut_key)) { 2773 size_t lower_pos = m_name.find(tolower(shortcut_key)); 2774 size_t upper_pos = m_name.find(toupper(shortcut_key)); 2775 const char *name = m_name.c_str(); 2776 size_t pos = std::min<size_t>(lower_pos, upper_pos); 2777 if (pos != std::string::npos) { 2778 underlined_shortcut = true; 2779 if (pos > 0) { 2780 window.PutCString(name, pos); 2781 name += pos; 2782 } 2783 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 2784 window.AttributeOn(shortcut_attr); 2785 window.PutChar(name[0]); 2786 window.AttributeOff(shortcut_attr); 2787 name++; 2788 if (name[0]) 2789 window.PutCString(name); 2790 } 2791 } 2792 2793 if (!underlined_shortcut) { 2794 window.PutCString(m_name.c_str()); 2795 } 2796 2797 if (highlight) 2798 window.AttributeOff(highlight_attr); 2799 2800 if (m_key_name.empty()) { 2801 if (!underlined_shortcut && llvm::isPrint(m_key_value)) { 2802 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2803 window.Printf(" (%c)", m_key_value); 2804 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2805 } 2806 } else { 2807 window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); 2808 window.Printf(" (%s)", m_key_name.c_str()); 2809 window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); 2810 } 2811 } 2812 } 2813 2814 bool Menu::WindowDelegateDraw(Window &window, bool force) { 2815 Menus &submenus = GetSubmenus(); 2816 const size_t num_submenus = submenus.size(); 2817 const int selected_idx = GetSelectedSubmenuIndex(); 2818 Menu::Type menu_type = GetType(); 2819 switch (menu_type) { 2820 case Menu::Type::Bar: { 2821 window.SetBackground(BlackOnWhite); 2822 window.MoveCursor(0, 0); 2823 for (size_t i = 0; i < num_submenus; ++i) { 2824 Menu *menu = submenus[i].get(); 2825 if (i > 0) 2826 window.PutChar(' '); 2827 menu->SetStartingColumn(window.GetCursorX()); 2828 window.PutCString("| "); 2829 menu->DrawMenuTitle(window, false); 2830 } 2831 window.PutCString(" |"); 2832 } break; 2833 2834 case Menu::Type::Item: { 2835 int y = 1; 2836 int x = 3; 2837 // Draw the menu 2838 int cursor_x = 0; 2839 int cursor_y = 0; 2840 window.Erase(); 2841 window.SetBackground(BlackOnWhite); 2842 window.Box(); 2843 for (size_t i = 0; i < num_submenus; ++i) { 2844 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 2845 window.MoveCursor(x, y + i); 2846 if (is_selected) { 2847 // Remember where we want the cursor to be 2848 cursor_x = x - 1; 2849 cursor_y = y + i; 2850 } 2851 submenus[i]->DrawMenuTitle(window, is_selected); 2852 } 2853 window.MoveCursor(cursor_x, cursor_y); 2854 } break; 2855 2856 default: 2857 case Menu::Type::Separator: 2858 break; 2859 } 2860 return true; // Drawing handled... 2861 } 2862 2863 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 2864 HandleCharResult result = eKeyNotHandled; 2865 2866 Menus &submenus = GetSubmenus(); 2867 const size_t num_submenus = submenus.size(); 2868 const int selected_idx = GetSelectedSubmenuIndex(); 2869 Menu::Type menu_type = GetType(); 2870 if (menu_type == Menu::Type::Bar) { 2871 MenuSP run_menu_sp; 2872 switch (key) { 2873 case KEY_DOWN: 2874 case KEY_UP: 2875 // Show last menu or first menu 2876 if (selected_idx < static_cast<int>(num_submenus)) 2877 run_menu_sp = submenus[selected_idx]; 2878 else if (!submenus.empty()) 2879 run_menu_sp = submenus.front(); 2880 result = eKeyHandled; 2881 break; 2882 2883 case KEY_RIGHT: 2884 ++m_selected; 2885 if (m_selected >= static_cast<int>(num_submenus)) 2886 m_selected = 0; 2887 if (m_selected < static_cast<int>(num_submenus)) 2888 run_menu_sp = submenus[m_selected]; 2889 else if (!submenus.empty()) 2890 run_menu_sp = submenus.front(); 2891 result = eKeyHandled; 2892 break; 2893 2894 case KEY_LEFT: 2895 --m_selected; 2896 if (m_selected < 0) 2897 m_selected = num_submenus - 1; 2898 if (m_selected < static_cast<int>(num_submenus)) 2899 run_menu_sp = submenus[m_selected]; 2900 else if (!submenus.empty()) 2901 run_menu_sp = submenus.front(); 2902 result = eKeyHandled; 2903 break; 2904 2905 default: 2906 for (size_t i = 0; i < num_submenus; ++i) { 2907 if (submenus[i]->GetKeyValue() == key) { 2908 SetSelectedSubmenuIndex(i); 2909 run_menu_sp = submenus[i]; 2910 result = eKeyHandled; 2911 break; 2912 } 2913 } 2914 break; 2915 } 2916 2917 if (run_menu_sp) { 2918 // Run the action on this menu in case we need to populate the menu with 2919 // dynamic content and also in case check marks, and any other menu 2920 // decorations need to be calculated 2921 if (run_menu_sp->Action() == MenuActionResult::Quit) 2922 return eQuitApplication; 2923 2924 Rect menu_bounds; 2925 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 2926 menu_bounds.origin.y = 1; 2927 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 2928 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 2929 if (m_menu_window_sp) 2930 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 2931 2932 m_menu_window_sp = window.GetParent()->CreateSubWindow( 2933 run_menu_sp->GetName().c_str(), menu_bounds, true); 2934 m_menu_window_sp->SetDelegate(run_menu_sp); 2935 } 2936 } else if (menu_type == Menu::Type::Item) { 2937 switch (key) { 2938 case KEY_DOWN: 2939 if (m_submenus.size() > 1) { 2940 const int start_select = m_selected; 2941 while (++m_selected != start_select) { 2942 if (static_cast<size_t>(m_selected) >= num_submenus) 2943 m_selected = 0; 2944 if (m_submenus[m_selected]->GetType() == Type::Separator) 2945 continue; 2946 else 2947 break; 2948 } 2949 return eKeyHandled; 2950 } 2951 break; 2952 2953 case KEY_UP: 2954 if (m_submenus.size() > 1) { 2955 const int start_select = m_selected; 2956 while (--m_selected != start_select) { 2957 if (m_selected < static_cast<int>(0)) 2958 m_selected = num_submenus - 1; 2959 if (m_submenus[m_selected]->GetType() == Type::Separator) 2960 continue; 2961 else 2962 break; 2963 } 2964 return eKeyHandled; 2965 } 2966 break; 2967 2968 case KEY_RETURN: 2969 if (static_cast<size_t>(selected_idx) < num_submenus) { 2970 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 2971 return eQuitApplication; 2972 window.GetParent()->RemoveSubWindow(&window); 2973 return eKeyHandled; 2974 } 2975 break; 2976 2977 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 2978 // case other chars are entered for escaped sequences 2979 window.GetParent()->RemoveSubWindow(&window); 2980 return eKeyHandled; 2981 2982 default: 2983 for (size_t i = 0; i < num_submenus; ++i) { 2984 Menu *menu = submenus[i].get(); 2985 if (menu->GetKeyValue() == key) { 2986 SetSelectedSubmenuIndex(i); 2987 window.GetParent()->RemoveSubWindow(&window); 2988 if (menu->Action() == MenuActionResult::Quit) 2989 return eQuitApplication; 2990 return eKeyHandled; 2991 } 2992 } 2993 break; 2994 } 2995 } else if (menu_type == Menu::Type::Separator) { 2996 } 2997 return result; 2998 } 2999 3000 class Application { 3001 public: 3002 Application(FILE *in, FILE *out) 3003 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 3004 3005 ~Application() { 3006 m_window_delegates.clear(); 3007 m_window_sp.reset(); 3008 if (m_screen) { 3009 ::delscreen(m_screen); 3010 m_screen = nullptr; 3011 } 3012 } 3013 3014 void Initialize() { 3015 m_screen = ::newterm(nullptr, m_out, m_in); 3016 ::start_color(); 3017 ::curs_set(0); 3018 ::noecho(); 3019 ::keypad(stdscr, TRUE); 3020 } 3021 3022 void Terminate() { ::endwin(); } 3023 3024 void Run(Debugger &debugger) { 3025 bool done = false; 3026 int delay_in_tenths_of_a_second = 1; 3027 3028 // Alas the threading model in curses is a bit lame so we need to resort to 3029 // polling every 0.5 seconds. We could poll for stdin ourselves and then 3030 // pass the keys down but then we need to translate all of the escape 3031 // sequences ourselves. So we resort to polling for input because we need 3032 // to receive async process events while in this loop. 3033 3034 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 3035 // of seconds seconds when calling 3036 // Window::GetChar() 3037 3038 ListenerSP listener_sp( 3039 Listener::MakeListener("lldb.IOHandler.curses.Application")); 3040 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 3041 debugger.EnableForwardEvents(listener_sp); 3042 3043 m_update_screen = true; 3044 #if defined(__APPLE__) 3045 std::deque<int> escape_chars; 3046 #endif 3047 3048 while (!done) { 3049 if (m_update_screen) { 3050 m_window_sp->Draw(false); 3051 // All windows should be calling Window::DeferredRefresh() instead of 3052 // Window::Refresh() so we can do a single update and avoid any screen 3053 // blinking 3054 update_panels(); 3055 3056 // Cursor hiding isn't working on MacOSX, so hide it in the top left 3057 // corner 3058 m_window_sp->MoveCursor(0, 0); 3059 3060 doupdate(); 3061 m_update_screen = false; 3062 } 3063 3064 #if defined(__APPLE__) 3065 // Terminal.app doesn't map its function keys correctly, F1-F4 default 3066 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 3067 // possible 3068 int ch; 3069 if (escape_chars.empty()) 3070 ch = m_window_sp->GetChar(); 3071 else { 3072 ch = escape_chars.front(); 3073 escape_chars.pop_front(); 3074 } 3075 if (ch == KEY_ESCAPE) { 3076 int ch2 = m_window_sp->GetChar(); 3077 if (ch2 == 'O') { 3078 int ch3 = m_window_sp->GetChar(); 3079 switch (ch3) { 3080 case 'P': 3081 ch = KEY_F(1); 3082 break; 3083 case 'Q': 3084 ch = KEY_F(2); 3085 break; 3086 case 'R': 3087 ch = KEY_F(3); 3088 break; 3089 case 'S': 3090 ch = KEY_F(4); 3091 break; 3092 default: 3093 escape_chars.push_back(ch2); 3094 if (ch3 != -1) 3095 escape_chars.push_back(ch3); 3096 break; 3097 } 3098 } else if (ch2 != -1) 3099 escape_chars.push_back(ch2); 3100 } 3101 #else 3102 int ch = m_window_sp->GetChar(); 3103 3104 #endif 3105 if (ch == -1) { 3106 if (feof(m_in) || ferror(m_in)) { 3107 done = true; 3108 } else { 3109 // Just a timeout from using halfdelay(), check for events 3110 EventSP event_sp; 3111 while (listener_sp->PeekAtNextEvent()) { 3112 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 3113 3114 if (event_sp) { 3115 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 3116 if (broadcaster) { 3117 // uint32_t event_type = event_sp->GetType(); 3118 ConstString broadcaster_class( 3119 broadcaster->GetBroadcasterClass()); 3120 if (broadcaster_class == broadcaster_class_process) { 3121 m_update_screen = true; 3122 continue; // Don't get any key, just update our view 3123 } 3124 } 3125 } 3126 } 3127 } 3128 } else { 3129 HandleCharResult key_result = m_window_sp->HandleChar(ch); 3130 switch (key_result) { 3131 case eKeyHandled: 3132 m_update_screen = true; 3133 break; 3134 case eKeyNotHandled: 3135 if (ch == 12) { // Ctrl+L, force full redraw 3136 redrawwin(m_window_sp->get()); 3137 m_update_screen = true; 3138 } 3139 break; 3140 case eQuitApplication: 3141 done = true; 3142 break; 3143 } 3144 } 3145 } 3146 3147 debugger.CancelForwardEvents(listener_sp); 3148 } 3149 3150 WindowSP &GetMainWindow() { 3151 if (!m_window_sp) 3152 m_window_sp = std::make_shared<Window>("main", stdscr, false); 3153 return m_window_sp; 3154 } 3155 3156 void TerminalSizeChanged() { 3157 ::endwin(); 3158 ::refresh(); 3159 Rect content_bounds = m_window_sp->GetFrame(); 3160 m_window_sp->SetBounds(content_bounds); 3161 if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) 3162 menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); 3163 if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) 3164 status_window_sp->SetBounds(content_bounds.MakeStatusBar()); 3165 3166 WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); 3167 WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); 3168 WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); 3169 WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); 3170 3171 Rect threads_bounds; 3172 Rect source_variables_bounds; 3173 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 3174 threads_bounds); 3175 if (threads_window_sp) 3176 threads_window_sp->SetBounds(threads_bounds); 3177 else 3178 source_variables_bounds = content_bounds; 3179 3180 Rect source_bounds; 3181 Rect variables_registers_bounds; 3182 source_variables_bounds.HorizontalSplitPercentage( 3183 0.70, source_bounds, variables_registers_bounds); 3184 if (variables_window_sp || registers_window_sp) { 3185 if (variables_window_sp && registers_window_sp) { 3186 Rect variables_bounds; 3187 Rect registers_bounds; 3188 variables_registers_bounds.VerticalSplitPercentage( 3189 0.50, variables_bounds, registers_bounds); 3190 variables_window_sp->SetBounds(variables_bounds); 3191 registers_window_sp->SetBounds(registers_bounds); 3192 } else if (variables_window_sp) { 3193 variables_window_sp->SetBounds(variables_registers_bounds); 3194 } else { 3195 registers_window_sp->SetBounds(variables_registers_bounds); 3196 } 3197 } else { 3198 source_bounds = source_variables_bounds; 3199 } 3200 3201 source_window_sp->SetBounds(source_bounds); 3202 3203 touchwin(stdscr); 3204 redrawwin(m_window_sp->get()); 3205 m_update_screen = true; 3206 } 3207 3208 protected: 3209 WindowSP m_window_sp; 3210 WindowDelegates m_window_delegates; 3211 SCREEN *m_screen; 3212 FILE *m_in; 3213 FILE *m_out; 3214 bool m_update_screen = false; 3215 }; 3216 3217 } // namespace curses 3218 3219 using namespace curses; 3220 3221 struct Row { 3222 ValueObjectUpdater value; 3223 Row *parent; 3224 // The process stop ID when the children were calculated. 3225 uint32_t children_stop_id = 0; 3226 int row_idx = 0; 3227 int x = 1; 3228 int y = 1; 3229 bool might_have_children; 3230 bool expanded = false; 3231 bool calculated_children = false; 3232 std::vector<Row> children; 3233 3234 Row(const ValueObjectSP &v, Row *p) 3235 : value(v), parent(p), 3236 might_have_children(v ? v->MightHaveChildren() : false) {} 3237 3238 size_t GetDepth() const { 3239 if (parent) 3240 return 1 + parent->GetDepth(); 3241 return 0; 3242 } 3243 3244 void Expand() { expanded = true; } 3245 3246 std::vector<Row> &GetChildren() { 3247 ProcessSP process_sp = value.GetProcessSP(); 3248 auto stop_id = process_sp->GetStopID(); 3249 if (process_sp && stop_id != children_stop_id) { 3250 children_stop_id = stop_id; 3251 calculated_children = false; 3252 } 3253 if (!calculated_children) { 3254 children.clear(); 3255 calculated_children = true; 3256 ValueObjectSP valobj = value.GetSP(); 3257 if (valobj) { 3258 const size_t num_children = valobj->GetNumChildren(); 3259 for (size_t i = 0; i < num_children; ++i) { 3260 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 3261 } 3262 } 3263 } 3264 return children; 3265 } 3266 3267 void Unexpand() { 3268 expanded = false; 3269 calculated_children = false; 3270 children.clear(); 3271 } 3272 3273 void DrawTree(Window &window) { 3274 if (parent) 3275 parent->DrawTreeForChild(window, this, 0); 3276 3277 if (might_have_children) { 3278 // It we can get UTF8 characters to work we should try to use the 3279 // "symbol" UTF8 string below 3280 // const char *symbol = ""; 3281 // if (row.expanded) 3282 // symbol = "\xe2\x96\xbd "; 3283 // else 3284 // symbol = "\xe2\x96\xb7 "; 3285 // window.PutCString (symbol); 3286 3287 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 3288 // or '>' character... 3289 // if (expanded) 3290 // window.PutChar (ACS_DARROW); 3291 // else 3292 // window.PutChar (ACS_RARROW); 3293 // Since we can't find any good looking right arrow/down arrow symbols, 3294 // just use a diamond... 3295 window.PutChar(ACS_DIAMOND); 3296 window.PutChar(ACS_HLINE); 3297 } 3298 } 3299 3300 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 3301 if (parent) 3302 parent->DrawTreeForChild(window, this, reverse_depth + 1); 3303 3304 if (&GetChildren().back() == child) { 3305 // Last child 3306 if (reverse_depth == 0) { 3307 window.PutChar(ACS_LLCORNER); 3308 window.PutChar(ACS_HLINE); 3309 } else { 3310 window.PutChar(' '); 3311 window.PutChar(' '); 3312 } 3313 } else { 3314 if (reverse_depth == 0) { 3315 window.PutChar(ACS_LTEE); 3316 window.PutChar(ACS_HLINE); 3317 } else { 3318 window.PutChar(ACS_VLINE); 3319 window.PutChar(' '); 3320 } 3321 } 3322 } 3323 }; 3324 3325 struct DisplayOptions { 3326 bool show_types; 3327 }; 3328 3329 class TreeItem; 3330 3331 class TreeDelegate { 3332 public: 3333 TreeDelegate() = default; 3334 virtual ~TreeDelegate() = default; 3335 3336 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 3337 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 3338 virtual bool TreeDelegateItemSelected( 3339 TreeItem &item) = 0; // Return true if we need to update views 3340 }; 3341 3342 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 3343 3344 class TreeItem { 3345 public: 3346 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 3347 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 3348 m_identifier(0), m_row_idx(-1), m_children(), 3349 m_might_have_children(might_have_children), m_is_expanded(false) {} 3350 3351 TreeItem &operator=(const TreeItem &rhs) { 3352 if (this != &rhs) { 3353 m_parent = rhs.m_parent; 3354 m_delegate = rhs.m_delegate; 3355 m_user_data = rhs.m_user_data; 3356 m_identifier = rhs.m_identifier; 3357 m_row_idx = rhs.m_row_idx; 3358 m_children = rhs.m_children; 3359 m_might_have_children = rhs.m_might_have_children; 3360 m_is_expanded = rhs.m_is_expanded; 3361 } 3362 return *this; 3363 } 3364 3365 TreeItem(const TreeItem &) = default; 3366 3367 size_t GetDepth() const { 3368 if (m_parent) 3369 return 1 + m_parent->GetDepth(); 3370 return 0; 3371 } 3372 3373 int GetRowIndex() const { return m_row_idx; } 3374 3375 void ClearChildren() { m_children.clear(); } 3376 3377 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 3378 3379 TreeItem &operator[](size_t i) { return m_children[i]; } 3380 3381 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 3382 3383 size_t GetNumChildren() { 3384 m_delegate.TreeDelegateGenerateChildren(*this); 3385 return m_children.size(); 3386 } 3387 3388 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 3389 3390 void CalculateRowIndexes(int &row_idx) { 3391 SetRowIndex(row_idx); 3392 ++row_idx; 3393 3394 const bool expanded = IsExpanded(); 3395 3396 // The root item must calculate its children, or we must calculate the 3397 // number of children if the item is expanded 3398 if (m_parent == nullptr || expanded) 3399 GetNumChildren(); 3400 3401 for (auto &item : m_children) { 3402 if (expanded) 3403 item.CalculateRowIndexes(row_idx); 3404 else 3405 item.SetRowIndex(-1); 3406 } 3407 } 3408 3409 TreeItem *GetParent() { return m_parent; } 3410 3411 bool IsExpanded() const { return m_is_expanded; } 3412 3413 void Expand() { m_is_expanded = true; } 3414 3415 void Unexpand() { m_is_expanded = false; } 3416 3417 bool Draw(Window &window, const int first_visible_row, 3418 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 3419 if (num_rows_left <= 0) 3420 return false; 3421 3422 if (m_row_idx >= first_visible_row) { 3423 window.MoveCursor(2, row_idx + 1); 3424 3425 if (m_parent) 3426 m_parent->DrawTreeForChild(window, this, 0); 3427 3428 if (m_might_have_children) { 3429 // It we can get UTF8 characters to work we should try to use the 3430 // "symbol" UTF8 string below 3431 // const char *symbol = ""; 3432 // if (row.expanded) 3433 // symbol = "\xe2\x96\xbd "; 3434 // else 3435 // symbol = "\xe2\x96\xb7 "; 3436 // window.PutCString (symbol); 3437 3438 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 3439 // 'v' or '>' character... 3440 // if (expanded) 3441 // window.PutChar (ACS_DARROW); 3442 // else 3443 // window.PutChar (ACS_RARROW); 3444 // Since we can't find any good looking right arrow/down arrow symbols, 3445 // just use a diamond... 3446 window.PutChar(ACS_DIAMOND); 3447 window.PutChar(ACS_HLINE); 3448 } 3449 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 3450 window.IsActive(); 3451 3452 if (highlight) 3453 window.AttributeOn(A_REVERSE); 3454 3455 m_delegate.TreeDelegateDrawTreeItem(*this, window); 3456 3457 if (highlight) 3458 window.AttributeOff(A_REVERSE); 3459 ++row_idx; 3460 --num_rows_left; 3461 } 3462 3463 if (num_rows_left <= 0) 3464 return false; // We are done drawing... 3465 3466 if (IsExpanded()) { 3467 for (auto &item : m_children) { 3468 // If we displayed all the rows and item.Draw() returns false we are 3469 // done drawing and can exit this for loop 3470 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 3471 num_rows_left)) 3472 break; 3473 } 3474 } 3475 return num_rows_left >= 0; // Return true if not done drawing yet 3476 } 3477 3478 void DrawTreeForChild(Window &window, TreeItem *child, 3479 uint32_t reverse_depth) { 3480 if (m_parent) 3481 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 3482 3483 if (&m_children.back() == child) { 3484 // Last child 3485 if (reverse_depth == 0) { 3486 window.PutChar(ACS_LLCORNER); 3487 window.PutChar(ACS_HLINE); 3488 } else { 3489 window.PutChar(' '); 3490 window.PutChar(' '); 3491 } 3492 } else { 3493 if (reverse_depth == 0) { 3494 window.PutChar(ACS_LTEE); 3495 window.PutChar(ACS_HLINE); 3496 } else { 3497 window.PutChar(ACS_VLINE); 3498 window.PutChar(' '); 3499 } 3500 } 3501 } 3502 3503 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 3504 if (static_cast<uint32_t>(m_row_idx) == row_idx) 3505 return this; 3506 if (m_children.empty()) 3507 return nullptr; 3508 if (IsExpanded()) { 3509 for (auto &item : m_children) { 3510 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 3511 if (selected_item_ptr) 3512 return selected_item_ptr; 3513 } 3514 } 3515 return nullptr; 3516 } 3517 3518 void *GetUserData() const { return m_user_data; } 3519 3520 void SetUserData(void *user_data) { m_user_data = user_data; } 3521 3522 uint64_t GetIdentifier() const { return m_identifier; } 3523 3524 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 3525 3526 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 3527 3528 protected: 3529 TreeItem *m_parent; 3530 TreeDelegate &m_delegate; 3531 void *m_user_data; 3532 uint64_t m_identifier; 3533 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 3534 // root item 3535 std::vector<TreeItem> m_children; 3536 bool m_might_have_children; 3537 bool m_is_expanded; 3538 }; 3539 3540 class TreeWindowDelegate : public WindowDelegate { 3541 public: 3542 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 3543 : m_debugger(debugger), m_delegate_sp(delegate_sp), 3544 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 3545 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 3546 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 3547 3548 int NumVisibleRows() const { return m_max_y - m_min_y; } 3549 3550 bool WindowDelegateDraw(Window &window, bool force) override { 3551 ExecutionContext exe_ctx( 3552 m_debugger.GetCommandInterpreter().GetExecutionContext()); 3553 Process *process = exe_ctx.GetProcessPtr(); 3554 3555 bool display_content = false; 3556 if (process) { 3557 StateType state = process->GetState(); 3558 if (StateIsStoppedState(state, true)) { 3559 // We are stopped, so it is ok to 3560 display_content = true; 3561 } else if (StateIsRunningState(state)) { 3562 return true; // Don't do any updating when we are running 3563 } 3564 } 3565 3566 m_min_x = 2; 3567 m_min_y = 1; 3568 m_max_x = window.GetWidth() - 1; 3569 m_max_y = window.GetHeight() - 1; 3570 3571 window.Erase(); 3572 window.DrawTitleBox(window.GetName()); 3573 3574 if (display_content) { 3575 const int num_visible_rows = NumVisibleRows(); 3576 m_num_rows = 0; 3577 m_root.CalculateRowIndexes(m_num_rows); 3578 3579 // If we unexpanded while having something selected our total number of 3580 // rows is less than the num visible rows, then make sure we show all the 3581 // rows by setting the first visible row accordingly. 3582 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 3583 m_first_visible_row = 0; 3584 3585 // Make sure the selected row is always visible 3586 if (m_selected_row_idx < m_first_visible_row) 3587 m_first_visible_row = m_selected_row_idx; 3588 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 3589 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 3590 3591 int row_idx = 0; 3592 int num_rows_left = num_visible_rows; 3593 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 3594 num_rows_left); 3595 // Get the selected row 3596 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3597 } else { 3598 m_selected_item = nullptr; 3599 } 3600 3601 return true; // Drawing handled 3602 } 3603 3604 const char *WindowDelegateGetHelpText() override { 3605 return "Thread window keyboard shortcuts:"; 3606 } 3607 3608 KeyHelp *WindowDelegateGetKeyHelp() override { 3609 static curses::KeyHelp g_source_view_key_help[] = { 3610 {KEY_UP, "Select previous item"}, 3611 {KEY_DOWN, "Select next item"}, 3612 {KEY_RIGHT, "Expand the selected item"}, 3613 {KEY_LEFT, 3614 "Unexpand the selected item or select parent if not expanded"}, 3615 {KEY_PPAGE, "Page up"}, 3616 {KEY_NPAGE, "Page down"}, 3617 {'h', "Show help dialog"}, 3618 {' ', "Toggle item expansion"}, 3619 {',', "Page up"}, 3620 {'.', "Page down"}, 3621 {'\0', nullptr}}; 3622 return g_source_view_key_help; 3623 } 3624 3625 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 3626 switch (c) { 3627 case ',': 3628 case KEY_PPAGE: 3629 // Page up key 3630 if (m_first_visible_row > 0) { 3631 if (m_first_visible_row > m_max_y) 3632 m_first_visible_row -= m_max_y; 3633 else 3634 m_first_visible_row = 0; 3635 m_selected_row_idx = m_first_visible_row; 3636 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3637 if (m_selected_item) 3638 m_selected_item->ItemWasSelected(); 3639 } 3640 return eKeyHandled; 3641 3642 case '.': 3643 case KEY_NPAGE: 3644 // Page down key 3645 if (m_num_rows > m_max_y) { 3646 if (m_first_visible_row + m_max_y < m_num_rows) { 3647 m_first_visible_row += m_max_y; 3648 m_selected_row_idx = m_first_visible_row; 3649 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3650 if (m_selected_item) 3651 m_selected_item->ItemWasSelected(); 3652 } 3653 } 3654 return eKeyHandled; 3655 3656 case KEY_UP: 3657 if (m_selected_row_idx > 0) { 3658 --m_selected_row_idx; 3659 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3660 if (m_selected_item) 3661 m_selected_item->ItemWasSelected(); 3662 } 3663 return eKeyHandled; 3664 3665 case KEY_DOWN: 3666 if (m_selected_row_idx + 1 < m_num_rows) { 3667 ++m_selected_row_idx; 3668 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3669 if (m_selected_item) 3670 m_selected_item->ItemWasSelected(); 3671 } 3672 return eKeyHandled; 3673 3674 case KEY_RIGHT: 3675 if (m_selected_item) { 3676 if (!m_selected_item->IsExpanded()) 3677 m_selected_item->Expand(); 3678 } 3679 return eKeyHandled; 3680 3681 case KEY_LEFT: 3682 if (m_selected_item) { 3683 if (m_selected_item->IsExpanded()) 3684 m_selected_item->Unexpand(); 3685 else if (m_selected_item->GetParent()) { 3686 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 3687 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 3688 if (m_selected_item) 3689 m_selected_item->ItemWasSelected(); 3690 } 3691 } 3692 return eKeyHandled; 3693 3694 case ' ': 3695 // Toggle expansion state when SPACE is pressed 3696 if (m_selected_item) { 3697 if (m_selected_item->IsExpanded()) 3698 m_selected_item->Unexpand(); 3699 else 3700 m_selected_item->Expand(); 3701 } 3702 return eKeyHandled; 3703 3704 case 'h': 3705 window.CreateHelpSubwindow(); 3706 return eKeyHandled; 3707 3708 default: 3709 break; 3710 } 3711 return eKeyNotHandled; 3712 } 3713 3714 protected: 3715 Debugger &m_debugger; 3716 TreeDelegateSP m_delegate_sp; 3717 TreeItem m_root; 3718 TreeItem *m_selected_item; 3719 int m_num_rows; 3720 int m_selected_row_idx; 3721 int m_first_visible_row; 3722 int m_min_x; 3723 int m_min_y; 3724 int m_max_x; 3725 int m_max_y; 3726 }; 3727 3728 class FrameTreeDelegate : public TreeDelegate { 3729 public: 3730 FrameTreeDelegate() : TreeDelegate() { 3731 FormatEntity::Parse( 3732 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 3733 m_format); 3734 } 3735 3736 ~FrameTreeDelegate() override = default; 3737 3738 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3739 Thread *thread = (Thread *)item.GetUserData(); 3740 if (thread) { 3741 const uint64_t frame_idx = item.GetIdentifier(); 3742 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 3743 if (frame_sp) { 3744 StreamString strm; 3745 const SymbolContext &sc = 3746 frame_sp->GetSymbolContext(eSymbolContextEverything); 3747 ExecutionContext exe_ctx(frame_sp); 3748 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 3749 nullptr, false, false)) { 3750 int right_pad = 1; 3751 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3752 } 3753 } 3754 } 3755 } 3756 3757 void TreeDelegateGenerateChildren(TreeItem &item) override { 3758 // No children for frames yet... 3759 } 3760 3761 bool TreeDelegateItemSelected(TreeItem &item) override { 3762 Thread *thread = (Thread *)item.GetUserData(); 3763 if (thread) { 3764 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 3765 thread->GetID()); 3766 const uint64_t frame_idx = item.GetIdentifier(); 3767 thread->SetSelectedFrameByIndex(frame_idx); 3768 return true; 3769 } 3770 return false; 3771 } 3772 3773 protected: 3774 FormatEntity::Entry m_format; 3775 }; 3776 3777 class ThreadTreeDelegate : public TreeDelegate { 3778 public: 3779 ThreadTreeDelegate(Debugger &debugger) 3780 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 3781 m_stop_id(UINT32_MAX) { 3782 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 3783 "reason = ${thread.stop-reason}}", 3784 m_format); 3785 } 3786 3787 ~ThreadTreeDelegate() override = default; 3788 3789 ProcessSP GetProcess() { 3790 return m_debugger.GetCommandInterpreter() 3791 .GetExecutionContext() 3792 .GetProcessSP(); 3793 } 3794 3795 ThreadSP GetThread(const TreeItem &item) { 3796 ProcessSP process_sp = GetProcess(); 3797 if (process_sp) 3798 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 3799 return ThreadSP(); 3800 } 3801 3802 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3803 ThreadSP thread_sp = GetThread(item); 3804 if (thread_sp) { 3805 StreamString strm; 3806 ExecutionContext exe_ctx(thread_sp); 3807 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3808 nullptr, false, false)) { 3809 int right_pad = 1; 3810 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3811 } 3812 } 3813 } 3814 3815 void TreeDelegateGenerateChildren(TreeItem &item) override { 3816 ProcessSP process_sp = GetProcess(); 3817 if (process_sp && process_sp->IsAlive()) { 3818 StateType state = process_sp->GetState(); 3819 if (StateIsStoppedState(state, true)) { 3820 ThreadSP thread_sp = GetThread(item); 3821 if (thread_sp) { 3822 if (m_stop_id == process_sp->GetStopID() && 3823 thread_sp->GetID() == m_tid) 3824 return; // Children are already up to date 3825 if (!m_frame_delegate_sp) { 3826 // Always expand the thread item the first time we show it 3827 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 3828 } 3829 3830 m_stop_id = process_sp->GetStopID(); 3831 m_tid = thread_sp->GetID(); 3832 3833 TreeItem t(&item, *m_frame_delegate_sp, false); 3834 size_t num_frames = thread_sp->GetStackFrameCount(); 3835 item.Resize(num_frames, t); 3836 for (size_t i = 0; i < num_frames; ++i) { 3837 item[i].SetUserData(thread_sp.get()); 3838 item[i].SetIdentifier(i); 3839 } 3840 } 3841 return; 3842 } 3843 } 3844 item.ClearChildren(); 3845 } 3846 3847 bool TreeDelegateItemSelected(TreeItem &item) override { 3848 ProcessSP process_sp = GetProcess(); 3849 if (process_sp && process_sp->IsAlive()) { 3850 StateType state = process_sp->GetState(); 3851 if (StateIsStoppedState(state, true)) { 3852 ThreadSP thread_sp = GetThread(item); 3853 if (thread_sp) { 3854 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 3855 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 3856 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 3857 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 3858 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 3859 return true; 3860 } 3861 } 3862 } 3863 } 3864 return false; 3865 } 3866 3867 protected: 3868 Debugger &m_debugger; 3869 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 3870 lldb::user_id_t m_tid; 3871 uint32_t m_stop_id; 3872 FormatEntity::Entry m_format; 3873 }; 3874 3875 class ThreadsTreeDelegate : public TreeDelegate { 3876 public: 3877 ThreadsTreeDelegate(Debugger &debugger) 3878 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 3879 m_stop_id(UINT32_MAX) { 3880 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 3881 m_format); 3882 } 3883 3884 ~ThreadsTreeDelegate() override = default; 3885 3886 ProcessSP GetProcess() { 3887 return m_debugger.GetCommandInterpreter() 3888 .GetExecutionContext() 3889 .GetProcessSP(); 3890 } 3891 3892 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 3893 ProcessSP process_sp = GetProcess(); 3894 if (process_sp && process_sp->IsAlive()) { 3895 StreamString strm; 3896 ExecutionContext exe_ctx(process_sp); 3897 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 3898 nullptr, false, false)) { 3899 int right_pad = 1; 3900 window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); 3901 } 3902 } 3903 } 3904 3905 void TreeDelegateGenerateChildren(TreeItem &item) override { 3906 ProcessSP process_sp = GetProcess(); 3907 if (process_sp && process_sp->IsAlive()) { 3908 StateType state = process_sp->GetState(); 3909 if (StateIsStoppedState(state, true)) { 3910 const uint32_t stop_id = process_sp->GetStopID(); 3911 if (m_stop_id == stop_id) 3912 return; // Children are already up to date 3913 3914 m_stop_id = stop_id; 3915 3916 if (!m_thread_delegate_sp) { 3917 // Always expand the thread item the first time we show it 3918 // item.Expand(); 3919 m_thread_delegate_sp = 3920 std::make_shared<ThreadTreeDelegate>(m_debugger); 3921 } 3922 3923 TreeItem t(&item, *m_thread_delegate_sp, false); 3924 ThreadList &threads = process_sp->GetThreadList(); 3925 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3926 size_t num_threads = threads.GetSize(); 3927 item.Resize(num_threads, t); 3928 for (size_t i = 0; i < num_threads; ++i) { 3929 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); 3930 item[i].SetMightHaveChildren(true); 3931 } 3932 return; 3933 } 3934 } 3935 item.ClearChildren(); 3936 } 3937 3938 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 3939 3940 protected: 3941 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 3942 Debugger &m_debugger; 3943 uint32_t m_stop_id; 3944 FormatEntity::Entry m_format; 3945 }; 3946 3947 class ValueObjectListDelegate : public WindowDelegate { 3948 public: 3949 ValueObjectListDelegate() : m_rows() {} 3950 3951 ValueObjectListDelegate(ValueObjectList &valobj_list) 3952 : m_rows(), m_selected_row(nullptr), m_selected_row_idx(0), 3953 m_first_visible_row(0), m_num_rows(0), m_max_x(0), m_max_y(0) { 3954 SetValues(valobj_list); 3955 } 3956 3957 ~ValueObjectListDelegate() override = default; 3958 3959 void SetValues(ValueObjectList &valobj_list) { 3960 m_selected_row = nullptr; 3961 m_selected_row_idx = 0; 3962 m_first_visible_row = 0; 3963 m_num_rows = 0; 3964 m_rows.clear(); 3965 for (auto &valobj_sp : valobj_list.GetObjects()) 3966 m_rows.push_back(Row(valobj_sp, nullptr)); 3967 } 3968 3969 bool WindowDelegateDraw(Window &window, bool force) override { 3970 m_num_rows = 0; 3971 m_min_x = 2; 3972 m_min_y = 1; 3973 m_max_x = window.GetWidth() - 1; 3974 m_max_y = window.GetHeight() - 1; 3975 3976 window.Erase(); 3977 window.DrawTitleBox(window.GetName()); 3978 3979 const int num_visible_rows = NumVisibleRows(); 3980 const int num_rows = CalculateTotalNumberRows(m_rows); 3981 3982 // If we unexpanded while having something selected our total number of 3983 // rows is less than the num visible rows, then make sure we show all the 3984 // rows by setting the first visible row accordingly. 3985 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 3986 m_first_visible_row = 0; 3987 3988 // Make sure the selected row is always visible 3989 if (m_selected_row_idx < m_first_visible_row) 3990 m_first_visible_row = m_selected_row_idx; 3991 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 3992 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 3993 3994 DisplayRows(window, m_rows, g_options); 3995 3996 // Get the selected row 3997 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 3998 // Keep the cursor on the selected row so the highlight and the cursor are 3999 // always on the same line 4000 if (m_selected_row) 4001 window.MoveCursor(m_selected_row->x, m_selected_row->y); 4002 4003 return true; // Drawing handled 4004 } 4005 4006 KeyHelp *WindowDelegateGetKeyHelp() override { 4007 static curses::KeyHelp g_source_view_key_help[] = { 4008 {KEY_UP, "Select previous item"}, 4009 {KEY_DOWN, "Select next item"}, 4010 {KEY_RIGHT, "Expand selected item"}, 4011 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 4012 {KEY_PPAGE, "Page up"}, 4013 {KEY_NPAGE, "Page down"}, 4014 {'A', "Format as annotated address"}, 4015 {'b', "Format as binary"}, 4016 {'B', "Format as hex bytes with ASCII"}, 4017 {'c', "Format as character"}, 4018 {'d', "Format as a signed integer"}, 4019 {'D', "Format selected value using the default format for the type"}, 4020 {'f', "Format as float"}, 4021 {'h', "Show help dialog"}, 4022 {'i', "Format as instructions"}, 4023 {'o', "Format as octal"}, 4024 {'p', "Format as pointer"}, 4025 {'s', "Format as C string"}, 4026 {'t', "Toggle showing/hiding type names"}, 4027 {'u', "Format as an unsigned integer"}, 4028 {'x', "Format as hex"}, 4029 {'X', "Format as uppercase hex"}, 4030 {' ', "Toggle item expansion"}, 4031 {',', "Page up"}, 4032 {'.', "Page down"}, 4033 {'\0', nullptr}}; 4034 return g_source_view_key_help; 4035 } 4036 4037 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 4038 switch (c) { 4039 case 'x': 4040 case 'X': 4041 case 'o': 4042 case 's': 4043 case 'u': 4044 case 'd': 4045 case 'D': 4046 case 'i': 4047 case 'A': 4048 case 'p': 4049 case 'c': 4050 case 'b': 4051 case 'B': 4052 case 'f': 4053 // Change the format for the currently selected item 4054 if (m_selected_row) { 4055 auto valobj_sp = m_selected_row->value.GetSP(); 4056 if (valobj_sp) 4057 valobj_sp->SetFormat(FormatForChar(c)); 4058 } 4059 return eKeyHandled; 4060 4061 case 't': 4062 // Toggle showing type names 4063 g_options.show_types = !g_options.show_types; 4064 return eKeyHandled; 4065 4066 case ',': 4067 case KEY_PPAGE: 4068 // Page up key 4069 if (m_first_visible_row > 0) { 4070 if (static_cast<int>(m_first_visible_row) > m_max_y) 4071 m_first_visible_row -= m_max_y; 4072 else 4073 m_first_visible_row = 0; 4074 m_selected_row_idx = m_first_visible_row; 4075 } 4076 return eKeyHandled; 4077 4078 case '.': 4079 case KEY_NPAGE: 4080 // Page down key 4081 if (m_num_rows > static_cast<size_t>(m_max_y)) { 4082 if (m_first_visible_row + m_max_y < m_num_rows) { 4083 m_first_visible_row += m_max_y; 4084 m_selected_row_idx = m_first_visible_row; 4085 } 4086 } 4087 return eKeyHandled; 4088 4089 case KEY_UP: 4090 if (m_selected_row_idx > 0) 4091 --m_selected_row_idx; 4092 return eKeyHandled; 4093 4094 case KEY_DOWN: 4095 if (m_selected_row_idx + 1 < m_num_rows) 4096 ++m_selected_row_idx; 4097 return eKeyHandled; 4098 4099 case KEY_RIGHT: 4100 if (m_selected_row) { 4101 if (!m_selected_row->expanded) 4102 m_selected_row->Expand(); 4103 } 4104 return eKeyHandled; 4105 4106 case KEY_LEFT: 4107 if (m_selected_row) { 4108 if (m_selected_row->expanded) 4109 m_selected_row->Unexpand(); 4110 else if (m_selected_row->parent) 4111 m_selected_row_idx = m_selected_row->parent->row_idx; 4112 } 4113 return eKeyHandled; 4114 4115 case ' ': 4116 // Toggle expansion state when SPACE is pressed 4117 if (m_selected_row) { 4118 if (m_selected_row->expanded) 4119 m_selected_row->Unexpand(); 4120 else 4121 m_selected_row->Expand(); 4122 } 4123 return eKeyHandled; 4124 4125 case 'h': 4126 window.CreateHelpSubwindow(); 4127 return eKeyHandled; 4128 4129 default: 4130 break; 4131 } 4132 return eKeyNotHandled; 4133 } 4134 4135 protected: 4136 std::vector<Row> m_rows; 4137 Row *m_selected_row = nullptr; 4138 uint32_t m_selected_row_idx = 0; 4139 uint32_t m_first_visible_row = 0; 4140 uint32_t m_num_rows = 0; 4141 int m_min_x; 4142 int m_min_y; 4143 int m_max_x = 0; 4144 int m_max_y = 0; 4145 4146 static Format FormatForChar(int c) { 4147 switch (c) { 4148 case 'x': 4149 return eFormatHex; 4150 case 'X': 4151 return eFormatHexUppercase; 4152 case 'o': 4153 return eFormatOctal; 4154 case 's': 4155 return eFormatCString; 4156 case 'u': 4157 return eFormatUnsigned; 4158 case 'd': 4159 return eFormatDecimal; 4160 case 'D': 4161 return eFormatDefault; 4162 case 'i': 4163 return eFormatInstruction; 4164 case 'A': 4165 return eFormatAddressInfo; 4166 case 'p': 4167 return eFormatPointer; 4168 case 'c': 4169 return eFormatChar; 4170 case 'b': 4171 return eFormatBinary; 4172 case 'B': 4173 return eFormatBytesWithASCII; 4174 case 'f': 4175 return eFormatFloat; 4176 } 4177 return eFormatDefault; 4178 } 4179 4180 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 4181 bool highlight, bool last_child) { 4182 ValueObject *valobj = row.value.GetSP().get(); 4183 4184 if (valobj == nullptr) 4185 return false; 4186 4187 const char *type_name = 4188 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 4189 const char *name = valobj->GetName().GetCString(); 4190 const char *value = valobj->GetValueAsCString(); 4191 const char *summary = valobj->GetSummaryAsCString(); 4192 4193 window.MoveCursor(row.x, row.y); 4194 4195 row.DrawTree(window); 4196 4197 if (highlight) 4198 window.AttributeOn(A_REVERSE); 4199 4200 if (type_name && type_name[0]) 4201 window.PrintfTruncated(1, "(%s) ", type_name); 4202 4203 if (name && name[0]) 4204 window.PutCStringTruncated(1, name); 4205 4206 attr_t changd_attr = 0; 4207 if (valobj->GetValueDidChange()) 4208 changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; 4209 4210 if (value && value[0]) { 4211 window.PutCStringTruncated(1, " = "); 4212 if (changd_attr) 4213 window.AttributeOn(changd_attr); 4214 window.PutCStringTruncated(1, value); 4215 if (changd_attr) 4216 window.AttributeOff(changd_attr); 4217 } 4218 4219 if (summary && summary[0]) { 4220 window.PutCStringTruncated(1, " "); 4221 if (changd_attr) 4222 window.AttributeOn(changd_attr); 4223 window.PutCStringTruncated(1, summary); 4224 if (changd_attr) 4225 window.AttributeOff(changd_attr); 4226 } 4227 4228 if (highlight) 4229 window.AttributeOff(A_REVERSE); 4230 4231 return true; 4232 } 4233 4234 void DisplayRows(Window &window, std::vector<Row> &rows, 4235 DisplayOptions &options) { 4236 // > 0x25B7 4237 // \/ 0x25BD 4238 4239 bool window_is_active = window.IsActive(); 4240 for (auto &row : rows) { 4241 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 4242 // Save the row index in each Row structure 4243 row.row_idx = m_num_rows; 4244 if ((m_num_rows >= m_first_visible_row) && 4245 ((m_num_rows - m_first_visible_row) < 4246 static_cast<size_t>(NumVisibleRows()))) { 4247 row.x = m_min_x; 4248 row.y = m_num_rows - m_first_visible_row + 1; 4249 if (DisplayRowObject(window, row, options, 4250 window_is_active && 4251 m_num_rows == m_selected_row_idx, 4252 last_child)) { 4253 ++m_num_rows; 4254 } else { 4255 row.x = 0; 4256 row.y = 0; 4257 } 4258 } else { 4259 row.x = 0; 4260 row.y = 0; 4261 ++m_num_rows; 4262 } 4263 4264 auto &children = row.GetChildren(); 4265 if (row.expanded && !children.empty()) { 4266 DisplayRows(window, children, options); 4267 } 4268 } 4269 } 4270 4271 int CalculateTotalNumberRows(std::vector<Row> &rows) { 4272 int row_count = 0; 4273 for (auto &row : rows) { 4274 ++row_count; 4275 if (row.expanded) 4276 row_count += CalculateTotalNumberRows(row.GetChildren()); 4277 } 4278 return row_count; 4279 } 4280 4281 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 4282 for (auto &row : rows) { 4283 if (row_index == 0) 4284 return &row; 4285 else { 4286 --row_index; 4287 auto &children = row.GetChildren(); 4288 if (row.expanded && !children.empty()) { 4289 Row *result = GetRowForRowIndexImpl(children, row_index); 4290 if (result) 4291 return result; 4292 } 4293 } 4294 } 4295 return nullptr; 4296 } 4297 4298 Row *GetRowForRowIndex(size_t row_index) { 4299 return GetRowForRowIndexImpl(m_rows, row_index); 4300 } 4301 4302 int NumVisibleRows() const { return m_max_y - m_min_y; } 4303 4304 static DisplayOptions g_options; 4305 }; 4306 4307 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 4308 public: 4309 FrameVariablesWindowDelegate(Debugger &debugger) 4310 : ValueObjectListDelegate(), m_debugger(debugger), 4311 m_frame_block(nullptr) {} 4312 4313 ~FrameVariablesWindowDelegate() override = default; 4314 4315 const char *WindowDelegateGetHelpText() override { 4316 return "Frame variable window keyboard shortcuts:"; 4317 } 4318 4319 bool WindowDelegateDraw(Window &window, bool force) override { 4320 ExecutionContext exe_ctx( 4321 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4322 Process *process = exe_ctx.GetProcessPtr(); 4323 Block *frame_block = nullptr; 4324 StackFrame *frame = nullptr; 4325 4326 if (process) { 4327 StateType state = process->GetState(); 4328 if (StateIsStoppedState(state, true)) { 4329 frame = exe_ctx.GetFramePtr(); 4330 if (frame) 4331 frame_block = frame->GetFrameBlock(); 4332 } else if (StateIsRunningState(state)) { 4333 return true; // Don't do any updating when we are running 4334 } 4335 } 4336 4337 ValueObjectList local_values; 4338 if (frame_block) { 4339 // Only update the variables if they have changed 4340 if (m_frame_block != frame_block) { 4341 m_frame_block = frame_block; 4342 4343 VariableList *locals = frame->GetVariableList(true); 4344 if (locals) { 4345 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 4346 for (const VariableSP &local_sp : *locals) { 4347 ValueObjectSP value_sp = 4348 frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); 4349 if (value_sp) { 4350 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 4351 if (synthetic_value_sp) 4352 local_values.Append(synthetic_value_sp); 4353 else 4354 local_values.Append(value_sp); 4355 } 4356 } 4357 // Update the values 4358 SetValues(local_values); 4359 } 4360 } 4361 } else { 4362 m_frame_block = nullptr; 4363 // Update the values with an empty list if there is no frame 4364 SetValues(local_values); 4365 } 4366 4367 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4368 } 4369 4370 protected: 4371 Debugger &m_debugger; 4372 Block *m_frame_block; 4373 }; 4374 4375 class RegistersWindowDelegate : public ValueObjectListDelegate { 4376 public: 4377 RegistersWindowDelegate(Debugger &debugger) 4378 : ValueObjectListDelegate(), m_debugger(debugger) {} 4379 4380 ~RegistersWindowDelegate() override = default; 4381 4382 const char *WindowDelegateGetHelpText() override { 4383 return "Register window keyboard shortcuts:"; 4384 } 4385 4386 bool WindowDelegateDraw(Window &window, bool force) override { 4387 ExecutionContext exe_ctx( 4388 m_debugger.GetCommandInterpreter().GetExecutionContext()); 4389 StackFrame *frame = exe_ctx.GetFramePtr(); 4390 4391 ValueObjectList value_list; 4392 if (frame) { 4393 if (frame->GetStackID() != m_stack_id) { 4394 m_stack_id = frame->GetStackID(); 4395 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 4396 if (reg_ctx) { 4397 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 4398 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 4399 value_list.Append( 4400 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 4401 } 4402 } 4403 SetValues(value_list); 4404 } 4405 } else { 4406 Process *process = exe_ctx.GetProcessPtr(); 4407 if (process && process->IsAlive()) 4408 return true; // Don't do any updating if we are running 4409 else { 4410 // Update the values with an empty list if there is no process or the 4411 // process isn't alive anymore 4412 SetValues(value_list); 4413 } 4414 } 4415 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 4416 } 4417 4418 protected: 4419 Debugger &m_debugger; 4420 StackID m_stack_id; 4421 }; 4422 4423 static const char *CursesKeyToCString(int ch) { 4424 static char g_desc[32]; 4425 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 4426 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 4427 return g_desc; 4428 } 4429 switch (ch) { 4430 case KEY_DOWN: 4431 return "down"; 4432 case KEY_UP: 4433 return "up"; 4434 case KEY_LEFT: 4435 return "left"; 4436 case KEY_RIGHT: 4437 return "right"; 4438 case KEY_HOME: 4439 return "home"; 4440 case KEY_BACKSPACE: 4441 return "backspace"; 4442 case KEY_DL: 4443 return "delete-line"; 4444 case KEY_IL: 4445 return "insert-line"; 4446 case KEY_DC: 4447 return "delete-char"; 4448 case KEY_IC: 4449 return "insert-char"; 4450 case KEY_CLEAR: 4451 return "clear"; 4452 case KEY_EOS: 4453 return "clear-to-eos"; 4454 case KEY_EOL: 4455 return "clear-to-eol"; 4456 case KEY_SF: 4457 return "scroll-forward"; 4458 case KEY_SR: 4459 return "scroll-backward"; 4460 case KEY_NPAGE: 4461 return "page-down"; 4462 case KEY_PPAGE: 4463 return "page-up"; 4464 case KEY_STAB: 4465 return "set-tab"; 4466 case KEY_CTAB: 4467 return "clear-tab"; 4468 case KEY_CATAB: 4469 return "clear-all-tabs"; 4470 case KEY_ENTER: 4471 return "enter"; 4472 case KEY_PRINT: 4473 return "print"; 4474 case KEY_LL: 4475 return "lower-left key"; 4476 case KEY_A1: 4477 return "upper left of keypad"; 4478 case KEY_A3: 4479 return "upper right of keypad"; 4480 case KEY_B2: 4481 return "center of keypad"; 4482 case KEY_C1: 4483 return "lower left of keypad"; 4484 case KEY_C3: 4485 return "lower right of keypad"; 4486 case KEY_BTAB: 4487 return "back-tab key"; 4488 case KEY_BEG: 4489 return "begin key"; 4490 case KEY_CANCEL: 4491 return "cancel key"; 4492 case KEY_CLOSE: 4493 return "close key"; 4494 case KEY_COMMAND: 4495 return "command key"; 4496 case KEY_COPY: 4497 return "copy key"; 4498 case KEY_CREATE: 4499 return "create key"; 4500 case KEY_END: 4501 return "end key"; 4502 case KEY_EXIT: 4503 return "exit key"; 4504 case KEY_FIND: 4505 return "find key"; 4506 case KEY_HELP: 4507 return "help key"; 4508 case KEY_MARK: 4509 return "mark key"; 4510 case KEY_MESSAGE: 4511 return "message key"; 4512 case KEY_MOVE: 4513 return "move key"; 4514 case KEY_NEXT: 4515 return "next key"; 4516 case KEY_OPEN: 4517 return "open key"; 4518 case KEY_OPTIONS: 4519 return "options key"; 4520 case KEY_PREVIOUS: 4521 return "previous key"; 4522 case KEY_REDO: 4523 return "redo key"; 4524 case KEY_REFERENCE: 4525 return "reference key"; 4526 case KEY_REFRESH: 4527 return "refresh key"; 4528 case KEY_REPLACE: 4529 return "replace key"; 4530 case KEY_RESTART: 4531 return "restart key"; 4532 case KEY_RESUME: 4533 return "resume key"; 4534 case KEY_SAVE: 4535 return "save key"; 4536 case KEY_SBEG: 4537 return "shifted begin key"; 4538 case KEY_SCANCEL: 4539 return "shifted cancel key"; 4540 case KEY_SCOMMAND: 4541 return "shifted command key"; 4542 case KEY_SCOPY: 4543 return "shifted copy key"; 4544 case KEY_SCREATE: 4545 return "shifted create key"; 4546 case KEY_SDC: 4547 return "shifted delete-character key"; 4548 case KEY_SDL: 4549 return "shifted delete-line key"; 4550 case KEY_SELECT: 4551 return "select key"; 4552 case KEY_SEND: 4553 return "shifted end key"; 4554 case KEY_SEOL: 4555 return "shifted clear-to-end-of-line key"; 4556 case KEY_SEXIT: 4557 return "shifted exit key"; 4558 case KEY_SFIND: 4559 return "shifted find key"; 4560 case KEY_SHELP: 4561 return "shifted help key"; 4562 case KEY_SHOME: 4563 return "shifted home key"; 4564 case KEY_SIC: 4565 return "shifted insert-character key"; 4566 case KEY_SLEFT: 4567 return "shifted left-arrow key"; 4568 case KEY_SMESSAGE: 4569 return "shifted message key"; 4570 case KEY_SMOVE: 4571 return "shifted move key"; 4572 case KEY_SNEXT: 4573 return "shifted next key"; 4574 case KEY_SOPTIONS: 4575 return "shifted options key"; 4576 case KEY_SPREVIOUS: 4577 return "shifted previous key"; 4578 case KEY_SPRINT: 4579 return "shifted print key"; 4580 case KEY_SREDO: 4581 return "shifted redo key"; 4582 case KEY_SREPLACE: 4583 return "shifted replace key"; 4584 case KEY_SRIGHT: 4585 return "shifted right-arrow key"; 4586 case KEY_SRSUME: 4587 return "shifted resume key"; 4588 case KEY_SSAVE: 4589 return "shifted save key"; 4590 case KEY_SSUSPEND: 4591 return "shifted suspend key"; 4592 case KEY_SUNDO: 4593 return "shifted undo key"; 4594 case KEY_SUSPEND: 4595 return "suspend key"; 4596 case KEY_UNDO: 4597 return "undo key"; 4598 case KEY_MOUSE: 4599 return "Mouse event has occurred"; 4600 case KEY_RESIZE: 4601 return "Terminal resize event"; 4602 #ifdef KEY_EVENT 4603 case KEY_EVENT: 4604 return "We were interrupted by an event"; 4605 #endif 4606 case KEY_RETURN: 4607 return "return"; 4608 case ' ': 4609 return "space"; 4610 case '\t': 4611 return "tab"; 4612 case KEY_ESCAPE: 4613 return "escape"; 4614 default: 4615 if (llvm::isPrint(ch)) 4616 snprintf(g_desc, sizeof(g_desc), "%c", ch); 4617 else 4618 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 4619 return g_desc; 4620 } 4621 return nullptr; 4622 } 4623 4624 HelpDialogDelegate::HelpDialogDelegate(const char *text, 4625 KeyHelp *key_help_array) 4626 : m_text(), m_first_visible_line(0) { 4627 if (text && text[0]) { 4628 m_text.SplitIntoLines(text); 4629 m_text.AppendString(""); 4630 } 4631 if (key_help_array) { 4632 for (KeyHelp *key = key_help_array; key->ch; ++key) { 4633 StreamString key_description; 4634 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 4635 key->description); 4636 m_text.AppendString(key_description.GetString()); 4637 } 4638 } 4639 } 4640 4641 HelpDialogDelegate::~HelpDialogDelegate() = default; 4642 4643 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 4644 window.Erase(); 4645 const int window_height = window.GetHeight(); 4646 int x = 2; 4647 int y = 1; 4648 const int min_y = y; 4649 const int max_y = window_height - 1 - y; 4650 const size_t num_visible_lines = max_y - min_y + 1; 4651 const size_t num_lines = m_text.GetSize(); 4652 const char *bottom_message; 4653 if (num_lines <= num_visible_lines) 4654 bottom_message = "Press any key to exit"; 4655 else 4656 bottom_message = "Use arrows to scroll, any other key to exit"; 4657 window.DrawTitleBox(window.GetName(), bottom_message); 4658 while (y <= max_y) { 4659 window.MoveCursor(x, y); 4660 window.PutCStringTruncated( 4661 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); 4662 ++y; 4663 } 4664 return true; 4665 } 4666 4667 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 4668 int key) { 4669 bool done = false; 4670 const size_t num_lines = m_text.GetSize(); 4671 const size_t num_visible_lines = window.GetHeight() - 2; 4672 4673 if (num_lines <= num_visible_lines) { 4674 done = true; 4675 // If we have all lines visible and don't need scrolling, then any key 4676 // press will cause us to exit 4677 } else { 4678 switch (key) { 4679 case KEY_UP: 4680 if (m_first_visible_line > 0) 4681 --m_first_visible_line; 4682 break; 4683 4684 case KEY_DOWN: 4685 if (m_first_visible_line + num_visible_lines < num_lines) 4686 ++m_first_visible_line; 4687 break; 4688 4689 case KEY_PPAGE: 4690 case ',': 4691 if (m_first_visible_line > 0) { 4692 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 4693 m_first_visible_line -= num_visible_lines; 4694 else 4695 m_first_visible_line = 0; 4696 } 4697 break; 4698 4699 case KEY_NPAGE: 4700 case '.': 4701 if (m_first_visible_line + num_visible_lines < num_lines) { 4702 m_first_visible_line += num_visible_lines; 4703 if (static_cast<size_t>(m_first_visible_line) > num_lines) 4704 m_first_visible_line = num_lines - num_visible_lines; 4705 } 4706 break; 4707 4708 default: 4709 done = true; 4710 break; 4711 } 4712 } 4713 if (done) 4714 window.GetParent()->RemoveSubWindow(&window); 4715 return eKeyHandled; 4716 } 4717 4718 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 4719 public: 4720 enum { 4721 eMenuID_LLDB = 1, 4722 eMenuID_LLDBAbout, 4723 eMenuID_LLDBExit, 4724 4725 eMenuID_Target, 4726 eMenuID_TargetCreate, 4727 eMenuID_TargetDelete, 4728 4729 eMenuID_Process, 4730 eMenuID_ProcessAttach, 4731 eMenuID_ProcessDetachResume, 4732 eMenuID_ProcessDetachSuspended, 4733 eMenuID_ProcessLaunch, 4734 eMenuID_ProcessContinue, 4735 eMenuID_ProcessHalt, 4736 eMenuID_ProcessKill, 4737 4738 eMenuID_Thread, 4739 eMenuID_ThreadStepIn, 4740 eMenuID_ThreadStepOver, 4741 eMenuID_ThreadStepOut, 4742 4743 eMenuID_View, 4744 eMenuID_ViewBacktrace, 4745 eMenuID_ViewRegisters, 4746 eMenuID_ViewSource, 4747 eMenuID_ViewVariables, 4748 4749 eMenuID_Help, 4750 eMenuID_HelpGUIHelp 4751 }; 4752 4753 ApplicationDelegate(Application &app, Debugger &debugger) 4754 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 4755 4756 ~ApplicationDelegate() override = default; 4757 4758 bool WindowDelegateDraw(Window &window, bool force) override { 4759 return false; // Drawing not handled, let standard window drawing happen 4760 } 4761 4762 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 4763 switch (key) { 4764 case '\t': 4765 window.SelectNextWindowAsActive(); 4766 return eKeyHandled; 4767 4768 case KEY_SHIFT_TAB: 4769 window.SelectPreviousWindowAsActive(); 4770 return eKeyHandled; 4771 4772 case 'h': 4773 window.CreateHelpSubwindow(); 4774 return eKeyHandled; 4775 4776 case KEY_ESCAPE: 4777 return eQuitApplication; 4778 4779 default: 4780 break; 4781 } 4782 return eKeyNotHandled; 4783 } 4784 4785 const char *WindowDelegateGetHelpText() override { 4786 return "Welcome to the LLDB curses GUI.\n\n" 4787 "Press the TAB key to change the selected view.\n" 4788 "Each view has its own keyboard shortcuts, press 'h' to open a " 4789 "dialog to display them.\n\n" 4790 "Common key bindings for all views:"; 4791 } 4792 4793 KeyHelp *WindowDelegateGetKeyHelp() override { 4794 static curses::KeyHelp g_source_view_key_help[] = { 4795 {'\t', "Select next view"}, 4796 {KEY_BTAB, "Select previous view"}, 4797 {'h', "Show help dialog with view specific key bindings"}, 4798 {',', "Page up"}, 4799 {'.', "Page down"}, 4800 {KEY_UP, "Select previous"}, 4801 {KEY_DOWN, "Select next"}, 4802 {KEY_LEFT, "Unexpand or select parent"}, 4803 {KEY_RIGHT, "Expand"}, 4804 {KEY_PPAGE, "Page up"}, 4805 {KEY_NPAGE, "Page down"}, 4806 {'\0', nullptr}}; 4807 return g_source_view_key_help; 4808 } 4809 4810 MenuActionResult MenuDelegateAction(Menu &menu) override { 4811 switch (menu.GetIdentifier()) { 4812 case eMenuID_ThreadStepIn: { 4813 ExecutionContext exe_ctx = 4814 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4815 if (exe_ctx.HasThreadScope()) { 4816 Process *process = exe_ctx.GetProcessPtr(); 4817 if (process && process->IsAlive() && 4818 StateIsStoppedState(process->GetState(), true)) 4819 exe_ctx.GetThreadRef().StepIn(true); 4820 } 4821 } 4822 return MenuActionResult::Handled; 4823 4824 case eMenuID_ThreadStepOut: { 4825 ExecutionContext exe_ctx = 4826 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4827 if (exe_ctx.HasThreadScope()) { 4828 Process *process = exe_ctx.GetProcessPtr(); 4829 if (process && process->IsAlive() && 4830 StateIsStoppedState(process->GetState(), true)) 4831 exe_ctx.GetThreadRef().StepOut(); 4832 } 4833 } 4834 return MenuActionResult::Handled; 4835 4836 case eMenuID_ThreadStepOver: { 4837 ExecutionContext exe_ctx = 4838 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4839 if (exe_ctx.HasThreadScope()) { 4840 Process *process = exe_ctx.GetProcessPtr(); 4841 if (process && process->IsAlive() && 4842 StateIsStoppedState(process->GetState(), true)) 4843 exe_ctx.GetThreadRef().StepOver(true); 4844 } 4845 } 4846 return MenuActionResult::Handled; 4847 4848 case eMenuID_ProcessAttach: { 4849 WindowSP main_window_sp = m_app.GetMainWindow(); 4850 FormDelegateSP form_delegate_sp = FormDelegateSP( 4851 new ProcessAttachFormDelegate(m_debugger, main_window_sp)); 4852 Rect bounds = main_window_sp->GetCenteredRect(80, 22); 4853 WindowSP form_window_sp = main_window_sp->CreateSubWindow( 4854 form_delegate_sp->GetName().c_str(), bounds, true); 4855 WindowDelegateSP window_delegate_sp = 4856 WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); 4857 form_window_sp->SetDelegate(window_delegate_sp); 4858 return MenuActionResult::Handled; 4859 } 4860 4861 case eMenuID_ProcessContinue: { 4862 ExecutionContext exe_ctx = 4863 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4864 if (exe_ctx.HasProcessScope()) { 4865 Process *process = exe_ctx.GetProcessPtr(); 4866 if (process && process->IsAlive() && 4867 StateIsStoppedState(process->GetState(), true)) 4868 process->Resume(); 4869 } 4870 } 4871 return MenuActionResult::Handled; 4872 4873 case eMenuID_ProcessKill: { 4874 ExecutionContext exe_ctx = 4875 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4876 if (exe_ctx.HasProcessScope()) { 4877 Process *process = exe_ctx.GetProcessPtr(); 4878 if (process && process->IsAlive()) 4879 process->Destroy(false); 4880 } 4881 } 4882 return MenuActionResult::Handled; 4883 4884 case eMenuID_ProcessHalt: { 4885 ExecutionContext exe_ctx = 4886 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4887 if (exe_ctx.HasProcessScope()) { 4888 Process *process = exe_ctx.GetProcessPtr(); 4889 if (process && process->IsAlive()) 4890 process->Halt(); 4891 } 4892 } 4893 return MenuActionResult::Handled; 4894 4895 case eMenuID_ProcessDetachResume: 4896 case eMenuID_ProcessDetachSuspended: { 4897 ExecutionContext exe_ctx = 4898 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4899 if (exe_ctx.HasProcessScope()) { 4900 Process *process = exe_ctx.GetProcessPtr(); 4901 if (process && process->IsAlive()) 4902 process->Detach(menu.GetIdentifier() == 4903 eMenuID_ProcessDetachSuspended); 4904 } 4905 } 4906 return MenuActionResult::Handled; 4907 4908 case eMenuID_Process: { 4909 // Populate the menu with all of the threads if the process is stopped 4910 // when the Process menu gets selected and is about to display its 4911 // submenu. 4912 Menus &submenus = menu.GetSubmenus(); 4913 ExecutionContext exe_ctx = 4914 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4915 Process *process = exe_ctx.GetProcessPtr(); 4916 if (process && process->IsAlive() && 4917 StateIsStoppedState(process->GetState(), true)) { 4918 if (submenus.size() == 7) 4919 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4920 else if (submenus.size() > 8) 4921 submenus.erase(submenus.begin() + 8, submenus.end()); 4922 4923 ThreadList &threads = process->GetThreadList(); 4924 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 4925 size_t num_threads = threads.GetSize(); 4926 for (size_t i = 0; i < num_threads; ++i) { 4927 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 4928 char menu_char = '\0'; 4929 if (i < 9) 4930 menu_char = '1' + i; 4931 StreamString thread_menu_title; 4932 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 4933 const char *thread_name = thread_sp->GetName(); 4934 if (thread_name && thread_name[0]) 4935 thread_menu_title.Printf(" %s", thread_name); 4936 else { 4937 const char *queue_name = thread_sp->GetQueueName(); 4938 if (queue_name && queue_name[0]) 4939 thread_menu_title.Printf(" %s", queue_name); 4940 } 4941 menu.AddSubmenu( 4942 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 4943 nullptr, menu_char, thread_sp->GetID()))); 4944 } 4945 } else if (submenus.size() > 7) { 4946 // Remove the separator and any other thread submenu items that were 4947 // previously added 4948 submenus.erase(submenus.begin() + 7, submenus.end()); 4949 } 4950 // Since we are adding and removing items we need to recalculate the name 4951 // lengths 4952 menu.RecalculateNameLengths(); 4953 } 4954 return MenuActionResult::Handled; 4955 4956 case eMenuID_ViewVariables: { 4957 WindowSP main_window_sp = m_app.GetMainWindow(); 4958 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 4959 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 4960 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 4961 const Rect source_bounds = source_window_sp->GetBounds(); 4962 4963 if (variables_window_sp) { 4964 const Rect variables_bounds = variables_window_sp->GetBounds(); 4965 4966 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 4967 4968 if (registers_window_sp) { 4969 // We have a registers window, so give all the area back to the 4970 // registers window 4971 Rect registers_bounds = variables_bounds; 4972 registers_bounds.size.width = source_bounds.size.width; 4973 registers_window_sp->SetBounds(registers_bounds); 4974 } else { 4975 // We have no registers window showing so give the bottom area back 4976 // to the source view 4977 source_window_sp->Resize(source_bounds.size.width, 4978 source_bounds.size.height + 4979 variables_bounds.size.height); 4980 } 4981 } else { 4982 Rect new_variables_rect; 4983 if (registers_window_sp) { 4984 // We have a registers window so split the area of the registers 4985 // window into two columns where the left hand side will be the 4986 // variables and the right hand side will be the registers 4987 const Rect variables_bounds = registers_window_sp->GetBounds(); 4988 Rect new_registers_rect; 4989 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 4990 new_registers_rect); 4991 registers_window_sp->SetBounds(new_registers_rect); 4992 } else { 4993 // No registers window, grab the bottom part of the source window 4994 Rect new_source_rect; 4995 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 4996 new_variables_rect); 4997 source_window_sp->SetBounds(new_source_rect); 4998 } 4999 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 5000 "Variables", new_variables_rect, false); 5001 new_window_sp->SetDelegate( 5002 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 5003 } 5004 touchwin(stdscr); 5005 } 5006 return MenuActionResult::Handled; 5007 5008 case eMenuID_ViewRegisters: { 5009 WindowSP main_window_sp = m_app.GetMainWindow(); 5010 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 5011 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 5012 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 5013 const Rect source_bounds = source_window_sp->GetBounds(); 5014 5015 if (registers_window_sp) { 5016 if (variables_window_sp) { 5017 const Rect variables_bounds = variables_window_sp->GetBounds(); 5018 5019 // We have a variables window, so give all the area back to the 5020 // variables window 5021 variables_window_sp->Resize(variables_bounds.size.width + 5022 registers_window_sp->GetWidth(), 5023 variables_bounds.size.height); 5024 } else { 5025 // We have no variables window showing so give the bottom area back 5026 // to the source view 5027 source_window_sp->Resize(source_bounds.size.width, 5028 source_bounds.size.height + 5029 registers_window_sp->GetHeight()); 5030 } 5031 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 5032 } else { 5033 Rect new_regs_rect; 5034 if (variables_window_sp) { 5035 // We have a variables window, split it into two columns where the 5036 // left hand side will be the variables and the right hand side will 5037 // be the registers 5038 const Rect variables_bounds = variables_window_sp->GetBounds(); 5039 Rect new_vars_rect; 5040 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 5041 new_regs_rect); 5042 variables_window_sp->SetBounds(new_vars_rect); 5043 } else { 5044 // No variables window, grab the bottom part of the source window 5045 Rect new_source_rect; 5046 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 5047 new_regs_rect); 5048 source_window_sp->SetBounds(new_source_rect); 5049 } 5050 WindowSP new_window_sp = 5051 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 5052 new_window_sp->SetDelegate( 5053 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 5054 } 5055 touchwin(stdscr); 5056 } 5057 return MenuActionResult::Handled; 5058 5059 case eMenuID_HelpGUIHelp: 5060 m_app.GetMainWindow()->CreateHelpSubwindow(); 5061 return MenuActionResult::Handled; 5062 5063 default: 5064 break; 5065 } 5066 5067 return MenuActionResult::NotHandled; 5068 } 5069 5070 protected: 5071 Application &m_app; 5072 Debugger &m_debugger; 5073 }; 5074 5075 class StatusBarWindowDelegate : public WindowDelegate { 5076 public: 5077 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 5078 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 5079 } 5080 5081 ~StatusBarWindowDelegate() override = default; 5082 5083 bool WindowDelegateDraw(Window &window, bool force) override { 5084 ExecutionContext exe_ctx = 5085 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5086 Process *process = exe_ctx.GetProcessPtr(); 5087 Thread *thread = exe_ctx.GetThreadPtr(); 5088 StackFrame *frame = exe_ctx.GetFramePtr(); 5089 window.Erase(); 5090 window.SetBackground(BlackOnWhite); 5091 window.MoveCursor(0, 0); 5092 if (process) { 5093 const StateType state = process->GetState(); 5094 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 5095 StateAsCString(state)); 5096 5097 if (StateIsStoppedState(state, true)) { 5098 StreamString strm; 5099 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 5100 nullptr, nullptr, false, false)) { 5101 window.MoveCursor(40, 0); 5102 window.PutCStringTruncated(1, strm.GetString().str().c_str()); 5103 } 5104 5105 window.MoveCursor(60, 0); 5106 if (frame) 5107 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 5108 frame->GetFrameIndex(), 5109 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 5110 exe_ctx.GetTargetPtr())); 5111 } else if (state == eStateExited) { 5112 const char *exit_desc = process->GetExitDescription(); 5113 const int exit_status = process->GetExitStatus(); 5114 if (exit_desc && exit_desc[0]) 5115 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 5116 else 5117 window.Printf(" with status = %i", exit_status); 5118 } 5119 } 5120 return true; 5121 } 5122 5123 protected: 5124 Debugger &m_debugger; 5125 FormatEntity::Entry m_format; 5126 }; 5127 5128 class SourceFileWindowDelegate : public WindowDelegate { 5129 public: 5130 SourceFileWindowDelegate(Debugger &debugger) 5131 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 5132 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 5133 m_title(), m_tid(LLDB_INVALID_THREAD_ID), m_line_width(4), 5134 m_selected_line(0), m_pc_line(0), m_stop_id(0), m_frame_idx(UINT32_MAX), 5135 m_first_visible_line(0), m_first_visible_column(0), m_min_x(0), 5136 m_min_y(0), m_max_x(0), m_max_y(0) {} 5137 5138 ~SourceFileWindowDelegate() override = default; 5139 5140 void Update(const SymbolContext &sc) { m_sc = sc; } 5141 5142 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 5143 5144 const char *WindowDelegateGetHelpText() override { 5145 return "Source/Disassembly window keyboard shortcuts:"; 5146 } 5147 5148 KeyHelp *WindowDelegateGetKeyHelp() override { 5149 static curses::KeyHelp g_source_view_key_help[] = { 5150 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 5151 {KEY_UP, "Select previous source line"}, 5152 {KEY_DOWN, "Select next source line"}, 5153 {KEY_LEFT, "Scroll to the left"}, 5154 {KEY_RIGHT, "Scroll to the right"}, 5155 {KEY_PPAGE, "Page up"}, 5156 {KEY_NPAGE, "Page down"}, 5157 {'b', "Set breakpoint on selected source/disassembly line"}, 5158 {'c', "Continue process"}, 5159 {'D', "Detach with process suspended"}, 5160 {'h', "Show help dialog"}, 5161 {'n', "Step over (source line)"}, 5162 {'N', "Step over (single instruction)"}, 5163 {'f', "Step out (finish)"}, 5164 {'s', "Step in (source line)"}, 5165 {'S', "Step in (single instruction)"}, 5166 {'u', "Frame up"}, 5167 {'d', "Frame down"}, 5168 {',', "Page up"}, 5169 {'.', "Page down"}, 5170 {'\0', nullptr}}; 5171 return g_source_view_key_help; 5172 } 5173 5174 bool WindowDelegateDraw(Window &window, bool force) override { 5175 ExecutionContext exe_ctx = 5176 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5177 Process *process = exe_ctx.GetProcessPtr(); 5178 Thread *thread = nullptr; 5179 5180 bool update_location = false; 5181 if (process) { 5182 StateType state = process->GetState(); 5183 if (StateIsStoppedState(state, true)) { 5184 // We are stopped, so it is ok to 5185 update_location = true; 5186 } 5187 } 5188 5189 m_min_x = 1; 5190 m_min_y = 2; 5191 m_max_x = window.GetMaxX() - 1; 5192 m_max_y = window.GetMaxY() - 1; 5193 5194 const uint32_t num_visible_lines = NumVisibleLines(); 5195 StackFrameSP frame_sp; 5196 bool set_selected_line_to_pc = false; 5197 5198 if (update_location) { 5199 const bool process_alive = process ? process->IsAlive() : false; 5200 bool thread_changed = false; 5201 if (process_alive) { 5202 thread = exe_ctx.GetThreadPtr(); 5203 if (thread) { 5204 frame_sp = thread->GetSelectedFrame(); 5205 auto tid = thread->GetID(); 5206 thread_changed = tid != m_tid; 5207 m_tid = tid; 5208 } else { 5209 if (m_tid != LLDB_INVALID_THREAD_ID) { 5210 thread_changed = true; 5211 m_tid = LLDB_INVALID_THREAD_ID; 5212 } 5213 } 5214 } 5215 const uint32_t stop_id = process ? process->GetStopID() : 0; 5216 const bool stop_id_changed = stop_id != m_stop_id; 5217 bool frame_changed = false; 5218 m_stop_id = stop_id; 5219 m_title.Clear(); 5220 if (frame_sp) { 5221 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 5222 if (m_sc.module_sp) { 5223 m_title.Printf( 5224 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 5225 ConstString func_name = m_sc.GetFunctionName(); 5226 if (func_name) 5227 m_title.Printf("`%s", func_name.GetCString()); 5228 } 5229 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 5230 frame_changed = frame_idx != m_frame_idx; 5231 m_frame_idx = frame_idx; 5232 } else { 5233 m_sc.Clear(true); 5234 frame_changed = m_frame_idx != UINT32_MAX; 5235 m_frame_idx = UINT32_MAX; 5236 } 5237 5238 const bool context_changed = 5239 thread_changed || frame_changed || stop_id_changed; 5240 5241 if (process_alive) { 5242 if (m_sc.line_entry.IsValid()) { 5243 m_pc_line = m_sc.line_entry.line; 5244 if (m_pc_line != UINT32_MAX) 5245 --m_pc_line; // Convert to zero based line number... 5246 // Update the selected line if the stop ID changed... 5247 if (context_changed) 5248 m_selected_line = m_pc_line; 5249 5250 if (m_file_sp && m_file_sp->GetFileSpec() == m_sc.line_entry.file) { 5251 // Same file, nothing to do, we should either have the lines or not 5252 // (source file missing) 5253 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 5254 if (m_selected_line >= m_first_visible_line + num_visible_lines) 5255 m_first_visible_line = m_selected_line - 10; 5256 } else { 5257 if (m_selected_line > 10) 5258 m_first_visible_line = m_selected_line - 10; 5259 else 5260 m_first_visible_line = 0; 5261 } 5262 } else { 5263 // File changed, set selected line to the line with the PC 5264 m_selected_line = m_pc_line; 5265 m_file_sp = 5266 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 5267 if (m_file_sp) { 5268 const size_t num_lines = m_file_sp->GetNumLines(); 5269 m_line_width = 1; 5270 for (size_t n = num_lines; n >= 10; n = n / 10) 5271 ++m_line_width; 5272 5273 if (num_lines < num_visible_lines || 5274 m_selected_line < num_visible_lines) 5275 m_first_visible_line = 0; 5276 else 5277 m_first_visible_line = m_selected_line - 10; 5278 } 5279 } 5280 } else { 5281 m_file_sp.reset(); 5282 } 5283 5284 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 5285 // Show disassembly 5286 bool prefer_file_cache = false; 5287 if (m_sc.function) { 5288 if (m_disassembly_scope != m_sc.function) { 5289 m_disassembly_scope = m_sc.function; 5290 m_disassembly_sp = m_sc.function->GetInstructions( 5291 exe_ctx, nullptr, !prefer_file_cache); 5292 if (m_disassembly_sp) { 5293 set_selected_line_to_pc = true; 5294 m_disassembly_range = m_sc.function->GetAddressRange(); 5295 } else { 5296 m_disassembly_range.Clear(); 5297 } 5298 } else { 5299 set_selected_line_to_pc = context_changed; 5300 } 5301 } else if (m_sc.symbol) { 5302 if (m_disassembly_scope != m_sc.symbol) { 5303 m_disassembly_scope = m_sc.symbol; 5304 m_disassembly_sp = m_sc.symbol->GetInstructions( 5305 exe_ctx, nullptr, prefer_file_cache); 5306 if (m_disassembly_sp) { 5307 set_selected_line_to_pc = true; 5308 m_disassembly_range.GetBaseAddress() = 5309 m_sc.symbol->GetAddress(); 5310 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 5311 } else { 5312 m_disassembly_range.Clear(); 5313 } 5314 } else { 5315 set_selected_line_to_pc = context_changed; 5316 } 5317 } 5318 } 5319 } else { 5320 m_pc_line = UINT32_MAX; 5321 } 5322 } 5323 5324 const int window_width = window.GetWidth(); 5325 window.Erase(); 5326 window.DrawTitleBox("Sources"); 5327 if (!m_title.GetString().empty()) { 5328 window.AttributeOn(A_REVERSE); 5329 window.MoveCursor(1, 1); 5330 window.PutChar(' '); 5331 window.PutCStringTruncated(1, m_title.GetString().str().c_str()); 5332 int x = window.GetCursorX(); 5333 if (x < window_width - 1) { 5334 window.Printf("%*s", window_width - x - 1, ""); 5335 } 5336 window.AttributeOff(A_REVERSE); 5337 } 5338 5339 Target *target = exe_ctx.GetTargetPtr(); 5340 const size_t num_source_lines = GetNumSourceLines(); 5341 if (num_source_lines > 0) { 5342 // Display source 5343 BreakpointLines bp_lines; 5344 if (target) { 5345 BreakpointList &bp_list = target->GetBreakpointList(); 5346 const size_t num_bps = bp_list.GetSize(); 5347 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5348 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5349 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5350 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5351 BreakpointLocationSP bp_loc_sp = 5352 bp_sp->GetLocationAtIndex(bp_loc_idx); 5353 LineEntry bp_loc_line_entry; 5354 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5355 bp_loc_line_entry)) { 5356 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 5357 bp_lines.insert(bp_loc_line_entry.line); 5358 } 5359 } 5360 } 5361 } 5362 } 5363 5364 const attr_t selected_highlight_attr = A_REVERSE; 5365 const attr_t pc_highlight_attr = COLOR_PAIR(BlackOnBlue); 5366 5367 for (size_t i = 0; i < num_visible_lines; ++i) { 5368 const uint32_t curr_line = m_first_visible_line + i; 5369 if (curr_line < num_source_lines) { 5370 const int line_y = m_min_y + i; 5371 window.MoveCursor(1, line_y); 5372 const bool is_pc_line = curr_line == m_pc_line; 5373 const bool line_is_selected = m_selected_line == curr_line; 5374 // Highlight the line as the PC line first, then if the selected line 5375 // isn't the same as the PC line, highlight it differently 5376 attr_t highlight_attr = 0; 5377 attr_t bp_attr = 0; 5378 if (is_pc_line) 5379 highlight_attr = pc_highlight_attr; 5380 else if (line_is_selected) 5381 highlight_attr = selected_highlight_attr; 5382 5383 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 5384 bp_attr = COLOR_PAIR(BlackOnWhite); 5385 5386 if (bp_attr) 5387 window.AttributeOn(bp_attr); 5388 5389 window.Printf(" %*u ", m_line_width, curr_line + 1); 5390 5391 if (bp_attr) 5392 window.AttributeOff(bp_attr); 5393 5394 window.PutChar(ACS_VLINE); 5395 // Mark the line with the PC with a diamond 5396 if (is_pc_line) 5397 window.PutChar(ACS_DIAMOND); 5398 else 5399 window.PutChar(' '); 5400 5401 if (highlight_attr) 5402 window.AttributeOn(highlight_attr); 5403 5404 StreamString lineStream; 5405 m_file_sp->DisplaySourceLines(curr_line + 1, {}, 0, 0, &lineStream); 5406 StringRef line = lineStream.GetString(); 5407 if (line.endswith("\n")) 5408 line = line.drop_back(); 5409 bool wasWritten = window.OutputColoredStringTruncated( 5410 1, line, m_first_visible_column, line_is_selected); 5411 if (line_is_selected && !wasWritten) { 5412 // Draw an empty space to show the selected line if empty, 5413 // or draw '<' if nothing is visible because of scrolling too much 5414 // to the right. 5415 window.PutCStringTruncated( 5416 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); 5417 } 5418 5419 if (is_pc_line && frame_sp && 5420 frame_sp->GetConcreteFrameIndex() == 0) { 5421 StopInfoSP stop_info_sp; 5422 if (thread) 5423 stop_info_sp = thread->GetStopInfo(); 5424 if (stop_info_sp) { 5425 const char *stop_description = stop_info_sp->GetDescription(); 5426 if (stop_description && stop_description[0]) { 5427 size_t stop_description_len = strlen(stop_description); 5428 int desc_x = window_width - stop_description_len - 16; 5429 if (desc_x - window.GetCursorX() > 0) 5430 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5431 window.MoveCursor(window_width - stop_description_len - 16, 5432 line_y); 5433 const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); 5434 window.AttributeOn(stop_reason_attr); 5435 window.PrintfTruncated(1, " <<< Thread %u: %s ", 5436 thread->GetIndexID(), stop_description); 5437 window.AttributeOff(stop_reason_attr); 5438 } 5439 } else { 5440 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5441 } 5442 } 5443 if (highlight_attr) 5444 window.AttributeOff(highlight_attr); 5445 } else { 5446 break; 5447 } 5448 } 5449 } else { 5450 size_t num_disassembly_lines = GetNumDisassemblyLines(); 5451 if (num_disassembly_lines > 0) { 5452 // Display disassembly 5453 BreakpointAddrs bp_file_addrs; 5454 Target *target = exe_ctx.GetTargetPtr(); 5455 if (target) { 5456 BreakpointList &bp_list = target->GetBreakpointList(); 5457 const size_t num_bps = bp_list.GetSize(); 5458 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5459 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5460 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5461 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 5462 ++bp_loc_idx) { 5463 BreakpointLocationSP bp_loc_sp = 5464 bp_sp->GetLocationAtIndex(bp_loc_idx); 5465 LineEntry bp_loc_line_entry; 5466 const lldb::addr_t file_addr = 5467 bp_loc_sp->GetAddress().GetFileAddress(); 5468 if (file_addr != LLDB_INVALID_ADDRESS) { 5469 if (m_disassembly_range.ContainsFileAddress(file_addr)) 5470 bp_file_addrs.insert(file_addr); 5471 } 5472 } 5473 } 5474 } 5475 5476 const attr_t selected_highlight_attr = A_REVERSE; 5477 const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); 5478 5479 StreamString strm; 5480 5481 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 5482 Address pc_address; 5483 5484 if (frame_sp) 5485 pc_address = frame_sp->GetFrameCodeAddress(); 5486 const uint32_t pc_idx = 5487 pc_address.IsValid() 5488 ? insts.GetIndexOfInstructionAtAddress(pc_address) 5489 : UINT32_MAX; 5490 if (set_selected_line_to_pc) { 5491 m_selected_line = pc_idx; 5492 } 5493 5494 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 5495 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 5496 m_first_visible_line = 0; 5497 5498 if (pc_idx < num_disassembly_lines) { 5499 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 5500 pc_idx >= m_first_visible_line + num_visible_lines) 5501 m_first_visible_line = pc_idx - non_visible_pc_offset; 5502 } 5503 5504 for (size_t i = 0; i < num_visible_lines; ++i) { 5505 const uint32_t inst_idx = m_first_visible_line + i; 5506 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 5507 if (!inst) 5508 break; 5509 5510 const int line_y = m_min_y + i; 5511 window.MoveCursor(1, line_y); 5512 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 5513 const bool line_is_selected = m_selected_line == inst_idx; 5514 // Highlight the line as the PC line first, then if the selected line 5515 // isn't the same as the PC line, highlight it differently 5516 attr_t highlight_attr = 0; 5517 attr_t bp_attr = 0; 5518 if (is_pc_line) 5519 highlight_attr = pc_highlight_attr; 5520 else if (line_is_selected) 5521 highlight_attr = selected_highlight_attr; 5522 5523 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 5524 bp_file_addrs.end()) 5525 bp_attr = COLOR_PAIR(BlackOnWhite); 5526 5527 if (bp_attr) 5528 window.AttributeOn(bp_attr); 5529 5530 window.Printf(" 0x%16.16llx ", 5531 static_cast<unsigned long long>( 5532 inst->GetAddress().GetLoadAddress(target))); 5533 5534 if (bp_attr) 5535 window.AttributeOff(bp_attr); 5536 5537 window.PutChar(ACS_VLINE); 5538 // Mark the line with the PC with a diamond 5539 if (is_pc_line) 5540 window.PutChar(ACS_DIAMOND); 5541 else 5542 window.PutChar(' '); 5543 5544 if (highlight_attr) 5545 window.AttributeOn(highlight_attr); 5546 5547 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 5548 const char *operands = inst->GetOperands(&exe_ctx); 5549 const char *comment = inst->GetComment(&exe_ctx); 5550 5551 if (mnemonic != nullptr && mnemonic[0] == '\0') 5552 mnemonic = nullptr; 5553 if (operands != nullptr && operands[0] == '\0') 5554 operands = nullptr; 5555 if (comment != nullptr && comment[0] == '\0') 5556 comment = nullptr; 5557 5558 strm.Clear(); 5559 5560 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 5561 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 5562 else if (mnemonic != nullptr && operands != nullptr) 5563 strm.Printf("%-8s %s", mnemonic, operands); 5564 else if (mnemonic != nullptr) 5565 strm.Printf("%s", mnemonic); 5566 5567 int right_pad = 1; 5568 window.PutCStringTruncated( 5569 right_pad, 5570 strm.GetString().substr(m_first_visible_column).data()); 5571 5572 if (is_pc_line && frame_sp && 5573 frame_sp->GetConcreteFrameIndex() == 0) { 5574 StopInfoSP stop_info_sp; 5575 if (thread) 5576 stop_info_sp = thread->GetStopInfo(); 5577 if (stop_info_sp) { 5578 const char *stop_description = stop_info_sp->GetDescription(); 5579 if (stop_description && stop_description[0]) { 5580 size_t stop_description_len = strlen(stop_description); 5581 int desc_x = window_width - stop_description_len - 16; 5582 if (desc_x - window.GetCursorX() > 0) 5583 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 5584 window.MoveCursor(window_width - stop_description_len - 15, 5585 line_y); 5586 window.PrintfTruncated(1, "<<< Thread %u: %s ", 5587 thread->GetIndexID(), stop_description); 5588 } 5589 } else { 5590 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 5591 } 5592 } 5593 if (highlight_attr) 5594 window.AttributeOff(highlight_attr); 5595 } 5596 } 5597 } 5598 return true; // Drawing handled 5599 } 5600 5601 size_t GetNumLines() { 5602 size_t num_lines = GetNumSourceLines(); 5603 if (num_lines == 0) 5604 num_lines = GetNumDisassemblyLines(); 5605 return num_lines; 5606 } 5607 5608 size_t GetNumSourceLines() const { 5609 if (m_file_sp) 5610 return m_file_sp->GetNumLines(); 5611 return 0; 5612 } 5613 5614 size_t GetNumDisassemblyLines() const { 5615 if (m_disassembly_sp) 5616 return m_disassembly_sp->GetInstructionList().GetSize(); 5617 return 0; 5618 } 5619 5620 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 5621 const uint32_t num_visible_lines = NumVisibleLines(); 5622 const size_t num_lines = GetNumLines(); 5623 5624 switch (c) { 5625 case ',': 5626 case KEY_PPAGE: 5627 // Page up key 5628 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 5629 m_first_visible_line -= num_visible_lines; 5630 else 5631 m_first_visible_line = 0; 5632 m_selected_line = m_first_visible_line; 5633 return eKeyHandled; 5634 5635 case '.': 5636 case KEY_NPAGE: 5637 // Page down key 5638 { 5639 if (m_first_visible_line + num_visible_lines < num_lines) 5640 m_first_visible_line += num_visible_lines; 5641 else if (num_lines < num_visible_lines) 5642 m_first_visible_line = 0; 5643 else 5644 m_first_visible_line = num_lines - num_visible_lines; 5645 m_selected_line = m_first_visible_line; 5646 } 5647 return eKeyHandled; 5648 5649 case KEY_UP: 5650 if (m_selected_line > 0) { 5651 m_selected_line--; 5652 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 5653 m_first_visible_line = m_selected_line; 5654 } 5655 return eKeyHandled; 5656 5657 case KEY_DOWN: 5658 if (m_selected_line + 1 < num_lines) { 5659 m_selected_line++; 5660 if (m_first_visible_line + num_visible_lines < m_selected_line) 5661 m_first_visible_line++; 5662 } 5663 return eKeyHandled; 5664 5665 case KEY_LEFT: 5666 if (m_first_visible_column > 0) 5667 --m_first_visible_column; 5668 return eKeyHandled; 5669 5670 case KEY_RIGHT: 5671 ++m_first_visible_column; 5672 return eKeyHandled; 5673 5674 case '\r': 5675 case '\n': 5676 case KEY_ENTER: 5677 // Set a breakpoint and run to the line using a one shot breakpoint 5678 if (GetNumSourceLines() > 0) { 5679 ExecutionContext exe_ctx = 5680 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5681 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 5682 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5683 nullptr, // Don't limit the breakpoint to certain modules 5684 m_file_sp->GetFileSpec(), // Source file 5685 m_selected_line + 5686 1, // Source line number (m_selected_line is zero based) 5687 0, // Unspecified column. 5688 0, // No offset 5689 eLazyBoolCalculate, // Check inlines using global setting 5690 eLazyBoolCalculate, // Skip prologue using global setting, 5691 false, // internal 5692 false, // request_hardware 5693 eLazyBoolCalculate); // move_to_nearest_code 5694 // Make breakpoint one shot 5695 bp_sp->GetOptions().SetOneShot(true); 5696 exe_ctx.GetProcessRef().Resume(); 5697 } 5698 } else if (m_selected_line < GetNumDisassemblyLines()) { 5699 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5700 .GetInstructionAtIndex(m_selected_line) 5701 .get(); 5702 ExecutionContext exe_ctx = 5703 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5704 if (exe_ctx.HasTargetScope()) { 5705 Address addr = inst->GetAddress(); 5706 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5707 addr, // lldb_private::Address 5708 false, // internal 5709 false); // request_hardware 5710 // Make breakpoint one shot 5711 bp_sp->GetOptions().SetOneShot(true); 5712 exe_ctx.GetProcessRef().Resume(); 5713 } 5714 } 5715 return eKeyHandled; 5716 5717 case 'b': // 'b' == toggle breakpoint on currently selected line 5718 ToggleBreakpointOnSelectedLine(); 5719 return eKeyHandled; 5720 5721 case 'D': // 'D' == detach and keep stopped 5722 { 5723 ExecutionContext exe_ctx = 5724 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5725 if (exe_ctx.HasProcessScope()) 5726 exe_ctx.GetProcessRef().Detach(true); 5727 } 5728 return eKeyHandled; 5729 5730 case 'c': 5731 // 'c' == continue 5732 { 5733 ExecutionContext exe_ctx = 5734 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5735 if (exe_ctx.HasProcessScope()) 5736 exe_ctx.GetProcessRef().Resume(); 5737 } 5738 return eKeyHandled; 5739 5740 case 'f': 5741 // 'f' == step out (finish) 5742 { 5743 ExecutionContext exe_ctx = 5744 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5745 if (exe_ctx.HasThreadScope() && 5746 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5747 exe_ctx.GetThreadRef().StepOut(); 5748 } 5749 } 5750 return eKeyHandled; 5751 5752 case 'n': // 'n' == step over 5753 case 'N': // 'N' == step over instruction 5754 { 5755 ExecutionContext exe_ctx = 5756 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5757 if (exe_ctx.HasThreadScope() && 5758 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5759 bool source_step = (c == 'n'); 5760 exe_ctx.GetThreadRef().StepOver(source_step); 5761 } 5762 } 5763 return eKeyHandled; 5764 5765 case 's': // 's' == step into 5766 case 'S': // 'S' == step into instruction 5767 { 5768 ExecutionContext exe_ctx = 5769 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5770 if (exe_ctx.HasThreadScope() && 5771 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 5772 bool source_step = (c == 's'); 5773 exe_ctx.GetThreadRef().StepIn(source_step); 5774 } 5775 } 5776 return eKeyHandled; 5777 5778 case 'u': // 'u' == frame up 5779 case 'd': // 'd' == frame down 5780 { 5781 ExecutionContext exe_ctx = 5782 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5783 if (exe_ctx.HasThreadScope()) { 5784 Thread *thread = exe_ctx.GetThreadPtr(); 5785 uint32_t frame_idx = thread->GetSelectedFrameIndex(); 5786 if (frame_idx == UINT32_MAX) 5787 frame_idx = 0; 5788 if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) 5789 ++frame_idx; 5790 else if (c == 'd' && frame_idx > 0) 5791 --frame_idx; 5792 if (thread->SetSelectedFrameByIndex(frame_idx, true)) 5793 exe_ctx.SetFrameSP(thread->GetSelectedFrame()); 5794 } 5795 } 5796 return eKeyHandled; 5797 5798 case 'h': 5799 window.CreateHelpSubwindow(); 5800 return eKeyHandled; 5801 5802 default: 5803 break; 5804 } 5805 return eKeyNotHandled; 5806 } 5807 5808 void ToggleBreakpointOnSelectedLine() { 5809 ExecutionContext exe_ctx = 5810 m_debugger.GetCommandInterpreter().GetExecutionContext(); 5811 if (!exe_ctx.HasTargetScope()) 5812 return; 5813 if (GetNumSourceLines() > 0) { 5814 // Source file breakpoint. 5815 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5816 const size_t num_bps = bp_list.GetSize(); 5817 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5818 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5819 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5820 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5821 BreakpointLocationSP bp_loc_sp = 5822 bp_sp->GetLocationAtIndex(bp_loc_idx); 5823 LineEntry bp_loc_line_entry; 5824 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 5825 bp_loc_line_entry)) { 5826 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file && 5827 m_selected_line + 1 == bp_loc_line_entry.line) { 5828 bool removed = 5829 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5830 assert(removed); 5831 UNUSED_IF_ASSERT_DISABLED(removed); 5832 return; // Existing breakpoint removed. 5833 } 5834 } 5835 } 5836 } 5837 // No breakpoint found on the location, add it. 5838 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 5839 nullptr, // Don't limit the breakpoint to certain modules 5840 m_file_sp->GetFileSpec(), // Source file 5841 m_selected_line + 5842 1, // Source line number (m_selected_line is zero based) 5843 0, // No column specified. 5844 0, // No offset 5845 eLazyBoolCalculate, // Check inlines using global setting 5846 eLazyBoolCalculate, // Skip prologue using global setting, 5847 false, // internal 5848 false, // request_hardware 5849 eLazyBoolCalculate); // move_to_nearest_code 5850 } else { 5851 // Disassembly breakpoint. 5852 assert(GetNumDisassemblyLines() > 0); 5853 assert(m_selected_line < GetNumDisassemblyLines()); 5854 const Instruction *inst = m_disassembly_sp->GetInstructionList() 5855 .GetInstructionAtIndex(m_selected_line) 5856 .get(); 5857 Address addr = inst->GetAddress(); 5858 // Try to find it. 5859 BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); 5860 const size_t num_bps = bp_list.GetSize(); 5861 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 5862 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5863 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5864 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 5865 BreakpointLocationSP bp_loc_sp = 5866 bp_sp->GetLocationAtIndex(bp_loc_idx); 5867 LineEntry bp_loc_line_entry; 5868 const lldb::addr_t file_addr = 5869 bp_loc_sp->GetAddress().GetFileAddress(); 5870 if (file_addr == addr.GetFileAddress()) { 5871 bool removed = 5872 exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); 5873 assert(removed); 5874 UNUSED_IF_ASSERT_DISABLED(removed); 5875 return; // Existing breakpoint removed. 5876 } 5877 } 5878 } 5879 // No breakpoint found on the address, add it. 5880 BreakpointSP bp_sp = 5881 exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address 5882 false, // internal 5883 false); // request_hardware 5884 } 5885 } 5886 5887 protected: 5888 typedef std::set<uint32_t> BreakpointLines; 5889 typedef std::set<lldb::addr_t> BreakpointAddrs; 5890 5891 Debugger &m_debugger; 5892 SymbolContext m_sc; 5893 SourceManager::FileSP m_file_sp; 5894 SymbolContextScope *m_disassembly_scope; 5895 lldb::DisassemblerSP m_disassembly_sp; 5896 AddressRange m_disassembly_range; 5897 StreamString m_title; 5898 lldb::user_id_t m_tid; 5899 int m_line_width; 5900 uint32_t m_selected_line; // The selected line 5901 uint32_t m_pc_line; // The line with the PC 5902 uint32_t m_stop_id; 5903 uint32_t m_frame_idx; 5904 int m_first_visible_line; 5905 int m_first_visible_column; 5906 int m_min_x; 5907 int m_min_y; 5908 int m_max_x; 5909 int m_max_y; 5910 }; 5911 5912 DisplayOptions ValueObjectListDelegate::g_options = {true}; 5913 5914 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 5915 : IOHandler(debugger, IOHandler::Type::Curses) {} 5916 5917 void IOHandlerCursesGUI::Activate() { 5918 IOHandler::Activate(); 5919 if (!m_app_ap) { 5920 m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); 5921 5922 // This is both a window and a menu delegate 5923 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 5924 new ApplicationDelegate(*m_app_ap, m_debugger)); 5925 5926 MenuDelegateSP app_menu_delegate_sp = 5927 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 5928 MenuSP lldb_menu_sp( 5929 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 5930 MenuSP exit_menuitem_sp( 5931 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 5932 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 5933 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 5934 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 5935 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 5936 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 5937 5938 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 5939 ApplicationDelegate::eMenuID_Target)); 5940 target_menu_sp->AddSubmenu(MenuSP(new Menu( 5941 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 5942 target_menu_sp->AddSubmenu(MenuSP(new Menu( 5943 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 5944 5945 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 5946 ApplicationDelegate::eMenuID_Process)); 5947 process_menu_sp->AddSubmenu(MenuSP(new Menu( 5948 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 5949 process_menu_sp->AddSubmenu( 5950 MenuSP(new Menu("Detach and resume", nullptr, 'd', 5951 ApplicationDelegate::eMenuID_ProcessDetachResume))); 5952 process_menu_sp->AddSubmenu( 5953 MenuSP(new Menu("Detach suspended", nullptr, 's', 5954 ApplicationDelegate::eMenuID_ProcessDetachSuspended))); 5955 process_menu_sp->AddSubmenu(MenuSP(new Menu( 5956 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 5957 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 5958 process_menu_sp->AddSubmenu( 5959 MenuSP(new Menu("Continue", nullptr, 'c', 5960 ApplicationDelegate::eMenuID_ProcessContinue))); 5961 process_menu_sp->AddSubmenu(MenuSP(new Menu( 5962 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 5963 process_menu_sp->AddSubmenu(MenuSP(new Menu( 5964 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 5965 5966 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 5967 ApplicationDelegate::eMenuID_Thread)); 5968 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 5969 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 5970 thread_menu_sp->AddSubmenu( 5971 MenuSP(new Menu("Step Over", nullptr, 'v', 5972 ApplicationDelegate::eMenuID_ThreadStepOver))); 5973 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 5974 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 5975 5976 MenuSP view_menu_sp( 5977 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 5978 view_menu_sp->AddSubmenu( 5979 MenuSP(new Menu("Backtrace", nullptr, 'b', 5980 ApplicationDelegate::eMenuID_ViewBacktrace))); 5981 view_menu_sp->AddSubmenu( 5982 MenuSP(new Menu("Registers", nullptr, 'r', 5983 ApplicationDelegate::eMenuID_ViewRegisters))); 5984 view_menu_sp->AddSubmenu(MenuSP(new Menu( 5985 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 5986 view_menu_sp->AddSubmenu( 5987 MenuSP(new Menu("Variables", nullptr, 'v', 5988 ApplicationDelegate::eMenuID_ViewVariables))); 5989 5990 MenuSP help_menu_sp( 5991 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 5992 help_menu_sp->AddSubmenu(MenuSP(new Menu( 5993 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 5994 5995 m_app_ap->Initialize(); 5996 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 5997 5998 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 5999 menubar_sp->AddSubmenu(lldb_menu_sp); 6000 menubar_sp->AddSubmenu(target_menu_sp); 6001 menubar_sp->AddSubmenu(process_menu_sp); 6002 menubar_sp->AddSubmenu(thread_menu_sp); 6003 menubar_sp->AddSubmenu(view_menu_sp); 6004 menubar_sp->AddSubmenu(help_menu_sp); 6005 menubar_sp->SetDelegate(app_menu_delegate_sp); 6006 6007 Rect content_bounds = main_window_sp->GetFrame(); 6008 Rect menubar_bounds = content_bounds.MakeMenuBar(); 6009 Rect status_bounds = content_bounds.MakeStatusBar(); 6010 Rect source_bounds; 6011 Rect variables_bounds; 6012 Rect threads_bounds; 6013 Rect source_variables_bounds; 6014 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 6015 threads_bounds); 6016 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 6017 variables_bounds); 6018 6019 WindowSP menubar_window_sp = 6020 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 6021 // Let the menubar get keys if the active window doesn't handle the keys 6022 // that are typed so it can respond to menubar key presses. 6023 menubar_window_sp->SetCanBeActive( 6024 false); // Don't let the menubar become the active window 6025 menubar_window_sp->SetDelegate(menubar_sp); 6026 6027 WindowSP source_window_sp( 6028 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 6029 WindowSP variables_window_sp( 6030 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 6031 WindowSP threads_window_sp( 6032 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 6033 WindowSP status_window_sp( 6034 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 6035 status_window_sp->SetCanBeActive( 6036 false); // Don't let the status bar become the active window 6037 main_window_sp->SetDelegate( 6038 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 6039 source_window_sp->SetDelegate( 6040 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 6041 variables_window_sp->SetDelegate( 6042 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 6043 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 6044 threads_window_sp->SetDelegate(WindowDelegateSP( 6045 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 6046 status_window_sp->SetDelegate( 6047 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 6048 6049 // Show the main help window once the first time the curses GUI is launched 6050 static bool g_showed_help = false; 6051 if (!g_showed_help) { 6052 g_showed_help = true; 6053 main_window_sp->CreateHelpSubwindow(); 6054 } 6055 6056 // All colors with black background. 6057 init_pair(1, COLOR_BLACK, COLOR_BLACK); 6058 init_pair(2, COLOR_RED, COLOR_BLACK); 6059 init_pair(3, COLOR_GREEN, COLOR_BLACK); 6060 init_pair(4, COLOR_YELLOW, COLOR_BLACK); 6061 init_pair(5, COLOR_BLUE, COLOR_BLACK); 6062 init_pair(6, COLOR_MAGENTA, COLOR_BLACK); 6063 init_pair(7, COLOR_CYAN, COLOR_BLACK); 6064 init_pair(8, COLOR_WHITE, COLOR_BLACK); 6065 // All colors with blue background. 6066 init_pair(9, COLOR_BLACK, COLOR_BLUE); 6067 init_pair(10, COLOR_RED, COLOR_BLUE); 6068 init_pair(11, COLOR_GREEN, COLOR_BLUE); 6069 init_pair(12, COLOR_YELLOW, COLOR_BLUE); 6070 init_pair(13, COLOR_BLUE, COLOR_BLUE); 6071 init_pair(14, COLOR_MAGENTA, COLOR_BLUE); 6072 init_pair(15, COLOR_CYAN, COLOR_BLUE); 6073 init_pair(16, COLOR_WHITE, COLOR_BLUE); 6074 // These must match the order in the color indexes enum. 6075 init_pair(17, COLOR_BLACK, COLOR_WHITE); 6076 init_pair(18, COLOR_MAGENTA, COLOR_WHITE); 6077 static_assert(LastColorPairIndex == 18, "Color indexes do not match."); 6078 6079 define_key("\033[Z", KEY_SHIFT_TAB); 6080 } 6081 } 6082 6083 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 6084 6085 void IOHandlerCursesGUI::Run() { 6086 m_app_ap->Run(m_debugger); 6087 SetIsDone(true); 6088 } 6089 6090 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 6091 6092 void IOHandlerCursesGUI::Cancel() {} 6093 6094 bool IOHandlerCursesGUI::Interrupt() { return false; } 6095 6096 void IOHandlerCursesGUI::GotEOF() {} 6097 6098 void IOHandlerCursesGUI::TerminalSizeChanged() { 6099 m_app_ap->TerminalSizeChanged(); 6100 } 6101 6102 #endif // LLDB_ENABLE_CURSES 6103