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