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