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