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