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