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