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