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