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