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