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