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