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