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