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 <ncurses.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 case KEY_EVENT: return "We were interrupted by an event"; 4111 case KEY_RETURN: return "return"; 4112 case ' ': return "space"; 4113 case '\t': return "tab"; 4114 case KEY_ESCAPE: return "escape"; 4115 default: 4116 if (isprint(ch)) 4117 snprintf(g_desc, sizeof(g_desc), "%c", ch); 4118 else 4119 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 4120 return g_desc; 4121 } 4122 return NULL; 4123 } 4124 4125 HelpDialogDelegate::HelpDialogDelegate (const char *text, KeyHelp *key_help_array) : 4126 m_text (), 4127 m_first_visible_line (0) 4128 { 4129 if (text && text[0]) 4130 { 4131 m_text.SplitIntoLines(text); 4132 m_text.AppendString(""); 4133 } 4134 if (key_help_array) 4135 { 4136 for (KeyHelp *key = key_help_array; key->ch; ++key) 4137 { 4138 StreamString key_description; 4139 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), key->description); 4140 m_text.AppendString(std::move(key_description.GetString())); 4141 } 4142 } 4143 } 4144 4145 HelpDialogDelegate::~HelpDialogDelegate() = default; 4146 4147 bool 4148 HelpDialogDelegate::WindowDelegateDraw (Window &window, bool force) 4149 { 4150 window.Erase(); 4151 const int window_height = window.GetHeight(); 4152 int x = 2; 4153 int y = 1; 4154 const int min_y = y; 4155 const int max_y = window_height - 1 - y; 4156 const size_t num_visible_lines = max_y - min_y + 1; 4157 const size_t num_lines = m_text.GetSize(); 4158 const char *bottom_message; 4159 if (num_lines <= num_visible_lines) 4160 bottom_message = "Press any key to exit"; 4161 else 4162 bottom_message = "Use arrows to scroll, any other key to exit"; 4163 window.DrawTitleBox(window.GetName(), bottom_message); 4164 while (y <= max_y) 4165 { 4166 window.MoveCursor(x, y); 4167 window.PutCStringTruncated(m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1); 4168 ++y; 4169 } 4170 return true; 4171 } 4172 4173 HandleCharResult 4174 HelpDialogDelegate::WindowDelegateHandleChar (Window &window, int key) 4175 { 4176 bool done = false; 4177 const size_t num_lines = m_text.GetSize(); 4178 const size_t num_visible_lines = window.GetHeight() - 2; 4179 4180 if (num_lines <= num_visible_lines) 4181 { 4182 done = true; 4183 // If we have all lines visible and don't need scrolling, then any 4184 // key press will cause us to exit 4185 } 4186 else 4187 { 4188 switch (key) 4189 { 4190 case KEY_UP: 4191 if (m_first_visible_line > 0) 4192 --m_first_visible_line; 4193 break; 4194 4195 case KEY_DOWN: 4196 if (m_first_visible_line + num_visible_lines < num_lines) 4197 ++m_first_visible_line; 4198 break; 4199 4200 case KEY_PPAGE: 4201 case ',': 4202 if (m_first_visible_line > 0) 4203 { 4204 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 4205 m_first_visible_line -= num_visible_lines; 4206 else 4207 m_first_visible_line = 0; 4208 } 4209 break; 4210 4211 case KEY_NPAGE: 4212 case '.': 4213 if (m_first_visible_line + num_visible_lines < num_lines) 4214 { 4215 m_first_visible_line += num_visible_lines; 4216 if (static_cast<size_t>(m_first_visible_line) > num_lines) 4217 m_first_visible_line = num_lines - num_visible_lines; 4218 } 4219 break; 4220 4221 default: 4222 done = true; 4223 break; 4224 } 4225 } 4226 if (done) 4227 window.GetParent()->RemoveSubWindow(&window); 4228 return eKeyHandled; 4229 } 4230 4231 class ApplicationDelegate : 4232 public WindowDelegate, 4233 public MenuDelegate 4234 { 4235 public: 4236 enum { 4237 eMenuID_LLDB = 1, 4238 eMenuID_LLDBAbout, 4239 eMenuID_LLDBExit, 4240 4241 eMenuID_Target, 4242 eMenuID_TargetCreate, 4243 eMenuID_TargetDelete, 4244 4245 eMenuID_Process, 4246 eMenuID_ProcessAttach, 4247 eMenuID_ProcessDetach, 4248 eMenuID_ProcessLaunch, 4249 eMenuID_ProcessContinue, 4250 eMenuID_ProcessHalt, 4251 eMenuID_ProcessKill, 4252 4253 eMenuID_Thread, 4254 eMenuID_ThreadStepIn, 4255 eMenuID_ThreadStepOver, 4256 eMenuID_ThreadStepOut, 4257 4258 eMenuID_View, 4259 eMenuID_ViewBacktrace, 4260 eMenuID_ViewRegisters, 4261 eMenuID_ViewSource, 4262 eMenuID_ViewVariables, 4263 4264 eMenuID_Help, 4265 eMenuID_HelpGUIHelp 4266 }; 4267 4268 ApplicationDelegate (Application &app, Debugger &debugger) : 4269 WindowDelegate (), 4270 MenuDelegate (), 4271 m_app (app), 4272 m_debugger (debugger) 4273 { 4274 } 4275 4276 ~ApplicationDelegate() override = default; 4277 4278 bool 4279 WindowDelegateDraw (Window &window, bool force) override 4280 { 4281 return false; // Drawing not handled, let standard window drawing happen 4282 } 4283 4284 HandleCharResult 4285 WindowDelegateHandleChar (Window &window, int key) override 4286 { 4287 switch (key) 4288 { 4289 case '\t': 4290 window.SelectNextWindowAsActive(); 4291 return eKeyHandled; 4292 4293 case 'h': 4294 window.CreateHelpSubwindow(); 4295 return eKeyHandled; 4296 4297 case KEY_ESCAPE: 4298 return eQuitApplication; 4299 4300 default: 4301 break; 4302 } 4303 return eKeyNotHandled; 4304 } 4305 4306 const char * 4307 WindowDelegateGetHelpText () override 4308 { 4309 return "Welcome to the LLDB curses GUI.\n\n" 4310 "Press the TAB key to change the selected view.\n" 4311 "Each view has its own keyboard shortcuts, press 'h' to open a dialog to display them.\n\n" 4312 "Common key bindings for all views:"; 4313 } 4314 4315 KeyHelp * 4316 WindowDelegateGetKeyHelp () override 4317 { 4318 static curses::KeyHelp g_source_view_key_help[] = { 4319 { '\t', "Select next view" }, 4320 { 'h', "Show help dialog with view specific key bindings" }, 4321 { ',', "Page up" }, 4322 { '.', "Page down" }, 4323 { KEY_UP, "Select previous" }, 4324 { KEY_DOWN, "Select next" }, 4325 { KEY_LEFT, "Unexpand or select parent" }, 4326 { KEY_RIGHT, "Expand" }, 4327 { KEY_PPAGE, "Page up" }, 4328 { KEY_NPAGE, "Page down" }, 4329 { '\0', NULL } 4330 }; 4331 return g_source_view_key_help; 4332 } 4333 4334 MenuActionResult 4335 MenuDelegateAction (Menu &menu) override 4336 { 4337 switch (menu.GetIdentifier()) 4338 { 4339 case eMenuID_ThreadStepIn: 4340 { 4341 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4342 if (exe_ctx.HasThreadScope()) 4343 { 4344 Process *process = exe_ctx.GetProcessPtr(); 4345 if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) 4346 exe_ctx.GetThreadRef().StepIn(true); 4347 } 4348 } 4349 return MenuActionResult::Handled; 4350 4351 case eMenuID_ThreadStepOut: 4352 { 4353 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4354 if (exe_ctx.HasThreadScope()) 4355 { 4356 Process *process = exe_ctx.GetProcessPtr(); 4357 if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) 4358 exe_ctx.GetThreadRef().StepOut(); 4359 } 4360 } 4361 return MenuActionResult::Handled; 4362 4363 case eMenuID_ThreadStepOver: 4364 { 4365 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4366 if (exe_ctx.HasThreadScope()) 4367 { 4368 Process *process = exe_ctx.GetProcessPtr(); 4369 if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) 4370 exe_ctx.GetThreadRef().StepOver(true); 4371 } 4372 } 4373 return MenuActionResult::Handled; 4374 4375 case eMenuID_ProcessContinue: 4376 { 4377 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4378 if (exe_ctx.HasProcessScope()) 4379 { 4380 Process *process = exe_ctx.GetProcessPtr(); 4381 if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) 4382 process->Resume(); 4383 } 4384 } 4385 return MenuActionResult::Handled; 4386 4387 case eMenuID_ProcessKill: 4388 { 4389 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4390 if (exe_ctx.HasProcessScope()) 4391 { 4392 Process *process = exe_ctx.GetProcessPtr(); 4393 if (process && process->IsAlive()) 4394 process->Destroy(false); 4395 } 4396 } 4397 return MenuActionResult::Handled; 4398 4399 case eMenuID_ProcessHalt: 4400 { 4401 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4402 if (exe_ctx.HasProcessScope()) 4403 { 4404 Process *process = exe_ctx.GetProcessPtr(); 4405 if (process && process->IsAlive()) 4406 process->Halt(); 4407 } 4408 } 4409 return MenuActionResult::Handled; 4410 4411 case eMenuID_ProcessDetach: 4412 { 4413 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4414 if (exe_ctx.HasProcessScope()) 4415 { 4416 Process *process = exe_ctx.GetProcessPtr(); 4417 if (process && process->IsAlive()) 4418 process->Detach(false); 4419 } 4420 } 4421 return MenuActionResult::Handled; 4422 4423 case eMenuID_Process: 4424 { 4425 // Populate the menu with all of the threads if the process is stopped when 4426 // the Process menu gets selected and is about to display its submenu. 4427 Menus &submenus = menu.GetSubmenus(); 4428 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4429 Process *process = exe_ctx.GetProcessPtr(); 4430 if (process && process->IsAlive() && StateIsStoppedState (process->GetState(), true)) 4431 { 4432 if (submenus.size() == 7) 4433 menu.AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); 4434 else if (submenus.size() > 8) 4435 submenus.erase (submenus.begin() + 8, submenus.end()); 4436 4437 ThreadList &threads = process->GetThreadList(); 4438 Mutex::Locker locker (threads.GetMutex()); 4439 size_t num_threads = threads.GetSize(); 4440 for (size_t i=0; i<num_threads; ++i) 4441 { 4442 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 4443 char menu_char = '\0'; 4444 if (i < 9) 4445 menu_char = '1' + i; 4446 StreamString thread_menu_title; 4447 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 4448 const char *thread_name = thread_sp->GetName(); 4449 if (thread_name && thread_name[0]) 4450 thread_menu_title.Printf (" %s", thread_name); 4451 else 4452 { 4453 const char *queue_name = thread_sp->GetQueueName(); 4454 if (queue_name && queue_name[0]) 4455 thread_menu_title.Printf (" %s", queue_name); 4456 } 4457 menu.AddSubmenu (MenuSP (new Menu(thread_menu_title.GetString().c_str(), NULL, menu_char, thread_sp->GetID()))); 4458 } 4459 } 4460 else if (submenus.size() > 7) 4461 { 4462 // Remove the separator and any other thread submenu items 4463 // that were previously added 4464 submenus.erase (submenus.begin() + 7, submenus.end()); 4465 } 4466 // Since we are adding and removing items we need to recalculate the name lengths 4467 menu.RecalculateNameLengths(); 4468 } 4469 return MenuActionResult::Handled; 4470 4471 case eMenuID_ViewVariables: 4472 { 4473 WindowSP main_window_sp = m_app.GetMainWindow(); 4474 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 4475 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 4476 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 4477 const Rect source_bounds = source_window_sp->GetBounds(); 4478 4479 if (variables_window_sp) 4480 { 4481 const Rect variables_bounds = variables_window_sp->GetBounds(); 4482 4483 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 4484 4485 if (registers_window_sp) 4486 { 4487 // We have a registers window, so give all the area back to the registers window 4488 Rect registers_bounds = variables_bounds; 4489 registers_bounds.size.width = source_bounds.size.width; 4490 registers_window_sp->SetBounds(registers_bounds); 4491 } 4492 else 4493 { 4494 // We have no registers window showing so give the bottom 4495 // area back to the source view 4496 source_window_sp->Resize (source_bounds.size.width, 4497 source_bounds.size.height + variables_bounds.size.height); 4498 } 4499 } 4500 else 4501 { 4502 Rect new_variables_rect; 4503 if (registers_window_sp) 4504 { 4505 // We have a registers window so split the area of the registers 4506 // window into two columns where the left hand side will be the 4507 // variables and the right hand side will be the registers 4508 const Rect variables_bounds = registers_window_sp->GetBounds(); 4509 Rect new_registers_rect; 4510 variables_bounds.VerticalSplitPercentage (0.50, new_variables_rect, new_registers_rect); 4511 registers_window_sp->SetBounds (new_registers_rect); 4512 } 4513 else 4514 { 4515 // No variables window, grab the bottom part of the source window 4516 Rect new_source_rect; 4517 source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_variables_rect); 4518 source_window_sp->SetBounds (new_source_rect); 4519 } 4520 WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Variables", 4521 new_variables_rect, 4522 false); 4523 new_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 4524 } 4525 touchwin(stdscr); 4526 } 4527 return MenuActionResult::Handled; 4528 4529 case eMenuID_ViewRegisters: 4530 { 4531 WindowSP main_window_sp = m_app.GetMainWindow(); 4532 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 4533 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 4534 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 4535 const Rect source_bounds = source_window_sp->GetBounds(); 4536 4537 if (registers_window_sp) 4538 { 4539 if (variables_window_sp) 4540 { 4541 const Rect variables_bounds = variables_window_sp->GetBounds(); 4542 4543 // We have a variables window, so give all the area back to the variables window 4544 variables_window_sp->Resize (variables_bounds.size.width + registers_window_sp->GetWidth(), 4545 variables_bounds.size.height); 4546 } 4547 else 4548 { 4549 // We have no variables window showing so give the bottom 4550 // area back to the source view 4551 source_window_sp->Resize (source_bounds.size.width, 4552 source_bounds.size.height + registers_window_sp->GetHeight()); 4553 } 4554 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 4555 } 4556 else 4557 { 4558 Rect new_regs_rect; 4559 if (variables_window_sp) 4560 { 4561 // We have a variables window, split it into two columns 4562 // where the left hand side will be the variables and the 4563 // right hand side will be the registers 4564 const Rect variables_bounds = variables_window_sp->GetBounds(); 4565 Rect new_vars_rect; 4566 variables_bounds.VerticalSplitPercentage (0.50, new_vars_rect, new_regs_rect); 4567 variables_window_sp->SetBounds (new_vars_rect); 4568 } 4569 else 4570 { 4571 // No registers window, grab the bottom part of the source window 4572 Rect new_source_rect; 4573 source_bounds.HorizontalSplitPercentage (0.70, new_source_rect, new_regs_rect); 4574 source_window_sp->SetBounds (new_source_rect); 4575 } 4576 WindowSP new_window_sp = main_window_sp->CreateSubWindow ("Registers", 4577 new_regs_rect, 4578 false); 4579 new_window_sp->SetDelegate (WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 4580 } 4581 touchwin(stdscr); 4582 } 4583 return MenuActionResult::Handled; 4584 4585 case eMenuID_HelpGUIHelp: 4586 m_app.GetMainWindow ()->CreateHelpSubwindow(); 4587 return MenuActionResult::Handled; 4588 4589 default: 4590 break; 4591 } 4592 4593 return MenuActionResult::NotHandled; 4594 } 4595 protected: 4596 Application &m_app; 4597 Debugger &m_debugger; 4598 }; 4599 4600 class StatusBarWindowDelegate : public WindowDelegate 4601 { 4602 public: 4603 StatusBarWindowDelegate (Debugger &debugger) : 4604 m_debugger (debugger) 4605 { 4606 FormatEntity::Parse("Thread: ${thread.id%tid}", 4607 m_format); 4608 } 4609 4610 ~StatusBarWindowDelegate() override = default; 4611 4612 bool 4613 WindowDelegateDraw (Window &window, bool force) override 4614 { 4615 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4616 Process *process = exe_ctx.GetProcessPtr(); 4617 Thread *thread = exe_ctx.GetThreadPtr(); 4618 StackFrame *frame = exe_ctx.GetFramePtr(); 4619 window.Erase(); 4620 window.SetBackground(2); 4621 window.MoveCursor (0, 0); 4622 if (process) 4623 { 4624 const StateType state = process->GetState(); 4625 window.Printf ("Process: %5" PRIu64 " %10s", process->GetID(), StateAsCString(state)); 4626 4627 if (StateIsStoppedState(state, true)) 4628 { 4629 StreamString strm; 4630 if (thread && FormatEntity::Format (m_format, strm, NULL, &exe_ctx, NULL, NULL, false, false)) 4631 { 4632 window.MoveCursor (40, 0); 4633 window.PutCStringTruncated(strm.GetString().c_str(), 1); 4634 } 4635 4636 window.MoveCursor (60, 0); 4637 if (frame) 4638 window.Printf ("Frame: %3u PC = 0x%16.16" PRIx64, frame->GetFrameIndex(), frame->GetFrameCodeAddress().GetOpcodeLoadAddress (exe_ctx.GetTargetPtr())); 4639 } 4640 else if (state == eStateExited) 4641 { 4642 const char *exit_desc = process->GetExitDescription(); 4643 const int exit_status = process->GetExitStatus(); 4644 if (exit_desc && exit_desc[0]) 4645 window.Printf (" with status = %i (%s)", exit_status, exit_desc); 4646 else 4647 window.Printf (" with status = %i", exit_status); 4648 } 4649 } 4650 window.DeferredRefresh(); 4651 return true; 4652 } 4653 4654 protected: 4655 Debugger &m_debugger; 4656 FormatEntity::Entry m_format; 4657 }; 4658 4659 class SourceFileWindowDelegate : public WindowDelegate 4660 { 4661 public: 4662 SourceFileWindowDelegate (Debugger &debugger) : 4663 WindowDelegate (), 4664 m_debugger (debugger), 4665 m_sc (), 4666 m_file_sp (), 4667 m_disassembly_scope (NULL), 4668 m_disassembly_sp (), 4669 m_disassembly_range (), 4670 m_title (), 4671 m_line_width (4), 4672 m_selected_line (0), 4673 m_pc_line (0), 4674 m_stop_id (0), 4675 m_frame_idx (UINT32_MAX), 4676 m_first_visible_line (0), 4677 m_min_x (0), 4678 m_min_y (0), 4679 m_max_x (0), 4680 m_max_y (0) 4681 { 4682 } 4683 4684 ~SourceFileWindowDelegate() override = default; 4685 4686 void 4687 Update (const SymbolContext &sc) 4688 { 4689 m_sc = sc; 4690 } 4691 4692 uint32_t 4693 NumVisibleLines () const 4694 { 4695 return m_max_y - m_min_y; 4696 } 4697 4698 const char * 4699 WindowDelegateGetHelpText () override 4700 { 4701 return "Source/Disassembly window keyboard shortcuts:"; 4702 } 4703 4704 KeyHelp * 4705 WindowDelegateGetKeyHelp () override 4706 { 4707 static curses::KeyHelp g_source_view_key_help[] = { 4708 { KEY_RETURN, "Run to selected line with one shot breakpoint" }, 4709 { KEY_UP, "Select previous source line" }, 4710 { KEY_DOWN, "Select next source line" }, 4711 { KEY_PPAGE, "Page up" }, 4712 { KEY_NPAGE, "Page down" }, 4713 { 'b', "Set breakpoint on selected source/disassembly line" }, 4714 { 'c', "Continue process" }, 4715 { 'd', "Detach and resume process" }, 4716 { 'D', "Detach with process suspended" }, 4717 { 'h', "Show help dialog" }, 4718 { 'k', "Kill process" }, 4719 { 'n', "Step over (source line)" }, 4720 { 'N', "Step over (single instruction)" }, 4721 { 'o', "Step out" }, 4722 { 's', "Step in (source line)" }, 4723 { 'S', "Step in (single instruction)" }, 4724 { ',', "Page up" }, 4725 { '.', "Page down" }, 4726 { '\0', NULL } 4727 }; 4728 return g_source_view_key_help; 4729 } 4730 4731 bool 4732 WindowDelegateDraw (Window &window, bool force) override 4733 { 4734 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 4735 Process *process = exe_ctx.GetProcessPtr(); 4736 Thread *thread = NULL; 4737 4738 bool update_location = false; 4739 if (process) 4740 { 4741 StateType state = process->GetState(); 4742 if (StateIsStoppedState(state, true)) 4743 { 4744 // We are stopped, so it is ok to 4745 update_location = true; 4746 } 4747 } 4748 4749 m_min_x = 1; 4750 m_min_y = 2; 4751 m_max_x = window.GetMaxX()-1; 4752 m_max_y = window.GetMaxY()-1; 4753 4754 const uint32_t num_visible_lines = NumVisibleLines(); 4755 StackFrameSP frame_sp; 4756 bool set_selected_line_to_pc = false; 4757 4758 if (update_location) 4759 { 4760 const bool process_alive = process ? process->IsAlive() : false; 4761 bool thread_changed = false; 4762 if (process_alive) 4763 { 4764 thread = exe_ctx.GetThreadPtr(); 4765 if (thread) 4766 { 4767 frame_sp = thread->GetSelectedFrame(); 4768 auto tid = thread->GetID(); 4769 thread_changed = tid != m_tid; 4770 m_tid = tid; 4771 } 4772 else 4773 { 4774 if (m_tid != LLDB_INVALID_THREAD_ID) 4775 { 4776 thread_changed = true; 4777 m_tid = LLDB_INVALID_THREAD_ID; 4778 } 4779 } 4780 } 4781 const uint32_t stop_id = process ? process->GetStopID() : 0; 4782 const bool stop_id_changed = stop_id != m_stop_id; 4783 bool frame_changed = false; 4784 m_stop_id = stop_id; 4785 m_title.Clear(); 4786 if (frame_sp) 4787 { 4788 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 4789 if (m_sc.module_sp) 4790 { 4791 m_title.Printf("%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 4792 ConstString func_name = m_sc.GetFunctionName(); 4793 if (func_name) 4794 m_title.Printf("`%s", func_name.GetCString()); 4795 } 4796 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 4797 frame_changed = frame_idx != m_frame_idx; 4798 m_frame_idx = frame_idx; 4799 } 4800 else 4801 { 4802 m_sc.Clear(true); 4803 frame_changed = m_frame_idx != UINT32_MAX; 4804 m_frame_idx = UINT32_MAX; 4805 } 4806 4807 const bool context_changed = thread_changed || frame_changed || stop_id_changed; 4808 4809 if (process_alive) 4810 { 4811 if (m_sc.line_entry.IsValid()) 4812 { 4813 m_pc_line = m_sc.line_entry.line; 4814 if (m_pc_line != UINT32_MAX) 4815 --m_pc_line; // Convert to zero based line number... 4816 // Update the selected line if the stop ID changed... 4817 if (context_changed) 4818 m_selected_line = m_pc_line; 4819 4820 if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) 4821 { 4822 // Same file, nothing to do, we should either have the 4823 // lines or not (source file missing) 4824 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) 4825 { 4826 if (m_selected_line >= m_first_visible_line + num_visible_lines) 4827 m_first_visible_line = m_selected_line - 10; 4828 } 4829 else 4830 { 4831 if (m_selected_line > 10) 4832 m_first_visible_line = m_selected_line - 10; 4833 else 4834 m_first_visible_line = 0; 4835 } 4836 } 4837 else 4838 { 4839 // File changed, set selected line to the line with the PC 4840 m_selected_line = m_pc_line; 4841 m_file_sp = m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 4842 if (m_file_sp) 4843 { 4844 const size_t num_lines = m_file_sp->GetNumLines(); 4845 int m_line_width = 1; 4846 for (size_t n = num_lines; n >= 10; n = n / 10) 4847 ++m_line_width; 4848 4849 snprintf (m_line_format, sizeof(m_line_format), " %%%iu ", m_line_width); 4850 if (num_lines < num_visible_lines || m_selected_line < num_visible_lines) 4851 m_first_visible_line = 0; 4852 else 4853 m_first_visible_line = m_selected_line - 10; 4854 } 4855 } 4856 } 4857 else 4858 { 4859 m_file_sp.reset(); 4860 } 4861 4862 if (!m_file_sp || m_file_sp->GetNumLines() == 0) 4863 { 4864 // Show disassembly 4865 bool prefer_file_cache = false; 4866 if (m_sc.function) 4867 { 4868 if (m_disassembly_scope != m_sc.function) 4869 { 4870 m_disassembly_scope = m_sc.function; 4871 m_disassembly_sp = m_sc.function->GetInstructions (exe_ctx, NULL, prefer_file_cache); 4872 if (m_disassembly_sp) 4873 { 4874 set_selected_line_to_pc = true; 4875 m_disassembly_range = m_sc.function->GetAddressRange(); 4876 } 4877 else 4878 { 4879 m_disassembly_range.Clear(); 4880 } 4881 } 4882 else 4883 { 4884 set_selected_line_to_pc = context_changed; 4885 } 4886 } 4887 else if (m_sc.symbol) 4888 { 4889 if (m_disassembly_scope != m_sc.symbol) 4890 { 4891 m_disassembly_scope = m_sc.symbol; 4892 m_disassembly_sp = m_sc.symbol->GetInstructions (exe_ctx, NULL, prefer_file_cache); 4893 if (m_disassembly_sp) 4894 { 4895 set_selected_line_to_pc = true; 4896 m_disassembly_range.GetBaseAddress() = m_sc.symbol->GetAddress(); 4897 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 4898 } 4899 else 4900 { 4901 m_disassembly_range.Clear(); 4902 } 4903 } 4904 else 4905 { 4906 set_selected_line_to_pc = context_changed; 4907 } 4908 } 4909 } 4910 } 4911 else 4912 { 4913 m_pc_line = UINT32_MAX; 4914 } 4915 } 4916 4917 const int window_width = window.GetWidth(); 4918 window.Erase(); 4919 window.DrawTitleBox ("Sources"); 4920 if (!m_title.GetString().empty()) 4921 { 4922 window.AttributeOn(A_REVERSE); 4923 window.MoveCursor(1, 1); 4924 window.PutChar(' '); 4925 window.PutCStringTruncated(m_title.GetString().c_str(), 1); 4926 int x = window.GetCursorX(); 4927 if (x < window_width - 1) 4928 { 4929 window.Printf ("%*s", window_width - x - 1, ""); 4930 } 4931 window.AttributeOff(A_REVERSE); 4932 } 4933 4934 Target *target = exe_ctx.GetTargetPtr(); 4935 const size_t num_source_lines = GetNumSourceLines(); 4936 if (num_source_lines > 0) 4937 { 4938 // Display source 4939 BreakpointLines bp_lines; 4940 if (target) 4941 { 4942 BreakpointList &bp_list = target->GetBreakpointList(); 4943 const size_t num_bps = bp_list.GetSize(); 4944 for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) 4945 { 4946 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4947 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4948 for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) 4949 { 4950 BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); 4951 LineEntry bp_loc_line_entry; 4952 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry (bp_loc_line_entry)) 4953 { 4954 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) 4955 { 4956 bp_lines.insert(bp_loc_line_entry.line); 4957 } 4958 } 4959 } 4960 } 4961 } 4962 4963 const attr_t selected_highlight_attr = A_REVERSE; 4964 const attr_t pc_highlight_attr = COLOR_PAIR(1); 4965 4966 for (size_t i=0; i<num_visible_lines; ++i) 4967 { 4968 const uint32_t curr_line = m_first_visible_line + i; 4969 if (curr_line < num_source_lines) 4970 { 4971 const int line_y = m_min_y+i; 4972 window.MoveCursor(1, line_y); 4973 const bool is_pc_line = curr_line == m_pc_line; 4974 const bool line_is_selected = m_selected_line == curr_line; 4975 // Highlight the line as the PC line first, then if the selected line 4976 // isn't the same as the PC line, highlight it differently 4977 attr_t highlight_attr = 0; 4978 attr_t bp_attr = 0; 4979 if (is_pc_line) 4980 highlight_attr = pc_highlight_attr; 4981 else if (line_is_selected) 4982 highlight_attr = selected_highlight_attr; 4983 4984 if (bp_lines.find(curr_line+1) != bp_lines.end()) 4985 bp_attr = COLOR_PAIR(2); 4986 4987 if (bp_attr) 4988 window.AttributeOn(bp_attr); 4989 4990 window.Printf (m_line_format, curr_line + 1); 4991 4992 if (bp_attr) 4993 window.AttributeOff(bp_attr); 4994 4995 window.PutChar(ACS_VLINE); 4996 // Mark the line with the PC with a diamond 4997 if (is_pc_line) 4998 window.PutChar(ACS_DIAMOND); 4999 else 5000 window.PutChar(' '); 5001 5002 if (highlight_attr) 5003 window.AttributeOn(highlight_attr); 5004 const uint32_t line_len = m_file_sp->GetLineLength(curr_line + 1, false); 5005 if (line_len > 0) 5006 window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len); 5007 5008 if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) 5009 { 5010 StopInfoSP stop_info_sp; 5011 if (thread) 5012 stop_info_sp = thread->GetStopInfo(); 5013 if (stop_info_sp) 5014 { 5015 const char *stop_description = stop_info_sp->GetDescription(); 5016 if (stop_description && stop_description[0]) 5017 { 5018 size_t stop_description_len = strlen(stop_description); 5019 int desc_x = window_width - stop_description_len - 16; 5020 window.Printf ("%*s", desc_x - window.GetCursorX(), ""); 5021 //window.MoveCursor(window_width - stop_description_len - 15, line_y); 5022 window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); 5023 } 5024 } 5025 else 5026 { 5027 window.Printf ("%*s", window_width - window.GetCursorX() - 1, ""); 5028 } 5029 } 5030 if (highlight_attr) 5031 window.AttributeOff(highlight_attr); 5032 } 5033 else 5034 { 5035 break; 5036 } 5037 } 5038 } 5039 else 5040 { 5041 size_t num_disassembly_lines = GetNumDisassemblyLines(); 5042 if (num_disassembly_lines > 0) 5043 { 5044 // Display disassembly 5045 BreakpointAddrs bp_file_addrs; 5046 Target *target = exe_ctx.GetTargetPtr(); 5047 if (target) 5048 { 5049 BreakpointList &bp_list = target->GetBreakpointList(); 5050 const size_t num_bps = bp_list.GetSize(); 5051 for (size_t bp_idx=0; bp_idx<num_bps; ++bp_idx) 5052 { 5053 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 5054 const size_t num_bps_locs = bp_sp->GetNumLocations(); 5055 for (size_t bp_loc_idx=0; bp_loc_idx<num_bps_locs; ++bp_loc_idx) 5056 { 5057 BreakpointLocationSP bp_loc_sp = bp_sp->GetLocationAtIndex(bp_loc_idx); 5058 LineEntry bp_loc_line_entry; 5059 const lldb::addr_t file_addr = bp_loc_sp->GetAddress().GetFileAddress(); 5060 if (file_addr != LLDB_INVALID_ADDRESS) 5061 { 5062 if (m_disassembly_range.ContainsFileAddress(file_addr)) 5063 bp_file_addrs.insert(file_addr); 5064 } 5065 } 5066 } 5067 } 5068 5069 const attr_t selected_highlight_attr = A_REVERSE; 5070 const attr_t pc_highlight_attr = COLOR_PAIR(1); 5071 5072 StreamString strm; 5073 5074 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 5075 Address pc_address; 5076 5077 if (frame_sp) 5078 pc_address = frame_sp->GetFrameCodeAddress(); 5079 const uint32_t pc_idx = pc_address.IsValid() ? insts.GetIndexOfInstructionAtAddress (pc_address) : UINT32_MAX; 5080 if (set_selected_line_to_pc) 5081 { 5082 m_selected_line = pc_idx; 5083 } 5084 5085 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 5086 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 5087 m_first_visible_line = 0; 5088 5089 if (pc_idx < num_disassembly_lines) 5090 { 5091 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 5092 pc_idx >= m_first_visible_line + num_visible_lines) 5093 m_first_visible_line = pc_idx - non_visible_pc_offset; 5094 } 5095 5096 for (size_t i=0; i<num_visible_lines; ++i) 5097 { 5098 const uint32_t inst_idx = m_first_visible_line + i; 5099 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 5100 if (!inst) 5101 break; 5102 5103 const int line_y = m_min_y+i; 5104 window.MoveCursor(1, line_y); 5105 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 5106 const bool line_is_selected = m_selected_line == inst_idx; 5107 // Highlight the line as the PC line first, then if the selected line 5108 // isn't the same as the PC line, highlight it differently 5109 attr_t highlight_attr = 0; 5110 attr_t bp_attr = 0; 5111 if (is_pc_line) 5112 highlight_attr = pc_highlight_attr; 5113 else if (line_is_selected) 5114 highlight_attr = selected_highlight_attr; 5115 5116 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != bp_file_addrs.end()) 5117 bp_attr = COLOR_PAIR(2); 5118 5119 if (bp_attr) 5120 window.AttributeOn(bp_attr); 5121 5122 window.Printf (" 0x%16.16llx ", 5123 static_cast<unsigned long long>(inst->GetAddress().GetLoadAddress(target))); 5124 5125 if (bp_attr) 5126 window.AttributeOff(bp_attr); 5127 5128 window.PutChar(ACS_VLINE); 5129 // Mark the line with the PC with a diamond 5130 if (is_pc_line) 5131 window.PutChar(ACS_DIAMOND); 5132 else 5133 window.PutChar(' '); 5134 5135 if (highlight_attr) 5136 window.AttributeOn(highlight_attr); 5137 5138 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 5139 const char *operands = inst->GetOperands(&exe_ctx); 5140 const char *comment = inst->GetComment(&exe_ctx); 5141 5142 if (mnemonic && mnemonic[0] == '\0') 5143 mnemonic = NULL; 5144 if (operands && operands[0] == '\0') 5145 operands = NULL; 5146 if (comment && comment[0] == '\0') 5147 comment = NULL; 5148 5149 strm.Clear(); 5150 5151 if (mnemonic && operands && comment) 5152 strm.Printf ("%-8s %-25s ; %s", mnemonic, operands, comment); 5153 else if (mnemonic && operands) 5154 strm.Printf ("%-8s %s", mnemonic, operands); 5155 else if (mnemonic) 5156 strm.Printf ("%s", mnemonic); 5157 5158 int right_pad = 1; 5159 window.PutCStringTruncated(strm.GetString().c_str(), right_pad); 5160 5161 if (is_pc_line && frame_sp && frame_sp->GetConcreteFrameIndex() == 0) 5162 { 5163 StopInfoSP stop_info_sp; 5164 if (thread) 5165 stop_info_sp = thread->GetStopInfo(); 5166 if (stop_info_sp) 5167 { 5168 const char *stop_description = stop_info_sp->GetDescription(); 5169 if (stop_description && stop_description[0]) 5170 { 5171 size_t stop_description_len = strlen(stop_description); 5172 int desc_x = window_width - stop_description_len - 16; 5173 window.Printf ("%*s", desc_x - window.GetCursorX(), ""); 5174 //window.MoveCursor(window_width - stop_description_len - 15, line_y); 5175 window.Printf ("<<< Thread %u: %s ", thread->GetIndexID(), stop_description); 5176 } 5177 } 5178 else 5179 { 5180 window.Printf ("%*s", window_width - window.GetCursorX() - 1, ""); 5181 } 5182 } 5183 if (highlight_attr) 5184 window.AttributeOff(highlight_attr); 5185 } 5186 } 5187 } 5188 window.DeferredRefresh(); 5189 return true; // Drawing handled 5190 } 5191 5192 size_t 5193 GetNumLines () 5194 { 5195 size_t num_lines = GetNumSourceLines(); 5196 if (num_lines == 0) 5197 num_lines = GetNumDisassemblyLines(); 5198 return num_lines; 5199 } 5200 5201 size_t 5202 GetNumSourceLines () const 5203 { 5204 if (m_file_sp) 5205 return m_file_sp->GetNumLines(); 5206 return 0; 5207 } 5208 5209 size_t 5210 GetNumDisassemblyLines () const 5211 { 5212 if (m_disassembly_sp) 5213 return m_disassembly_sp->GetInstructionList().GetSize(); 5214 return 0; 5215 } 5216 5217 HandleCharResult 5218 WindowDelegateHandleChar (Window &window, int c) override 5219 { 5220 const uint32_t num_visible_lines = NumVisibleLines(); 5221 const size_t num_lines = GetNumLines (); 5222 5223 switch (c) 5224 { 5225 case ',': 5226 case KEY_PPAGE: 5227 // Page up key 5228 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 5229 m_first_visible_line -= num_visible_lines; 5230 else 5231 m_first_visible_line = 0; 5232 m_selected_line = m_first_visible_line; 5233 return eKeyHandled; 5234 5235 case '.': 5236 case KEY_NPAGE: 5237 // Page down key 5238 { 5239 if (m_first_visible_line + num_visible_lines < num_lines) 5240 m_first_visible_line += num_visible_lines; 5241 else if (num_lines < num_visible_lines) 5242 m_first_visible_line = 0; 5243 else 5244 m_first_visible_line = num_lines - num_visible_lines; 5245 m_selected_line = m_first_visible_line; 5246 } 5247 return eKeyHandled; 5248 5249 case KEY_UP: 5250 if (m_selected_line > 0) 5251 { 5252 m_selected_line--; 5253 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 5254 m_first_visible_line = m_selected_line; 5255 } 5256 return eKeyHandled; 5257 5258 case KEY_DOWN: 5259 if (m_selected_line + 1 < num_lines) 5260 { 5261 m_selected_line++; 5262 if (m_first_visible_line + num_visible_lines < m_selected_line) 5263 m_first_visible_line++; 5264 } 5265 return eKeyHandled; 5266 5267 case '\r': 5268 case '\n': 5269 case KEY_ENTER: 5270 // Set a breakpoint and run to the line using a one shot breakpoint 5271 if (GetNumSourceLines() > 0) 5272 { 5273 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5274 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) 5275 { 5276 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules 5277 m_file_sp->GetFileSpec(), // Source file 5278 m_selected_line + 1, // Source line number (m_selected_line is zero based) 5279 eLazyBoolCalculate, // Check inlines using global setting 5280 eLazyBoolCalculate, // Skip prologue using global setting, 5281 false, // internal 5282 false, // request_hardware 5283 eLazyBoolCalculate); // move_to_nearest_code 5284 // Make breakpoint one shot 5285 bp_sp->GetOptions()->SetOneShot(true); 5286 exe_ctx.GetProcessRef().Resume(); 5287 } 5288 } 5289 else if (m_selected_line < GetNumDisassemblyLines()) 5290 { 5291 const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); 5292 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5293 if (exe_ctx.HasTargetScope()) 5294 { 5295 Address addr = inst->GetAddress(); 5296 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address 5297 false, // internal 5298 false); // request_hardware 5299 // Make breakpoint one shot 5300 bp_sp->GetOptions()->SetOneShot(true); 5301 exe_ctx.GetProcessRef().Resume(); 5302 } 5303 } 5304 return eKeyHandled; 5305 5306 case 'b': // 'b' == toggle breakpoint on currently selected line 5307 if (m_selected_line < GetNumSourceLines()) 5308 { 5309 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5310 if (exe_ctx.HasTargetScope()) 5311 { 5312 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (NULL, // Don't limit the breakpoint to certain modules 5313 m_file_sp->GetFileSpec(), // Source file 5314 m_selected_line + 1, // Source line number (m_selected_line is zero based) 5315 eLazyBoolCalculate, // Check inlines using global setting 5316 eLazyBoolCalculate, // Skip prologue using global setting, 5317 false, // internal 5318 false, // request_hardware 5319 eLazyBoolCalculate); // move_to_nearest_code 5320 } 5321 } 5322 else if (m_selected_line < GetNumDisassemblyLines()) 5323 { 5324 const Instruction *inst = m_disassembly_sp->GetInstructionList().GetInstructionAtIndex(m_selected_line).get(); 5325 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5326 if (exe_ctx.HasTargetScope()) 5327 { 5328 Address addr = inst->GetAddress(); 5329 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint (addr, // lldb_private::Address 5330 false, // internal 5331 false); // request_hardware 5332 } 5333 } 5334 return eKeyHandled; 5335 5336 case 'd': // 'd' == detach and let run 5337 case 'D': // 'D' == detach and keep stopped 5338 { 5339 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5340 if (exe_ctx.HasProcessScope()) 5341 exe_ctx.GetProcessRef().Detach(c == 'D'); 5342 } 5343 return eKeyHandled; 5344 5345 case 'k': 5346 // 'k' == kill 5347 { 5348 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5349 if (exe_ctx.HasProcessScope()) 5350 exe_ctx.GetProcessRef().Destroy(false); 5351 } 5352 return eKeyHandled; 5353 5354 case 'c': 5355 // 'c' == continue 5356 { 5357 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5358 if (exe_ctx.HasProcessScope()) 5359 exe_ctx.GetProcessRef().Resume(); 5360 } 5361 return eKeyHandled; 5362 5363 case 'o': 5364 // 'o' == step out 5365 { 5366 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5367 if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) 5368 { 5369 exe_ctx.GetThreadRef().StepOut(); 5370 } 5371 } 5372 return eKeyHandled; 5373 5374 case 'n': // 'n' == step over 5375 case 'N': // 'N' == step over instruction 5376 { 5377 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5378 if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) 5379 { 5380 bool source_step = (c == 'n'); 5381 exe_ctx.GetThreadRef().StepOver(source_step); 5382 } 5383 } 5384 return eKeyHandled; 5385 5386 case 's': // 's' == step into 5387 case 'S': // 'S' == step into instruction 5388 { 5389 ExecutionContext exe_ctx = m_debugger.GetCommandInterpreter().GetExecutionContext(); 5390 if (exe_ctx.HasThreadScope() && StateIsStoppedState (exe_ctx.GetProcessRef().GetState(), true)) 5391 { 5392 bool source_step = (c == 's'); 5393 exe_ctx.GetThreadRef().StepIn(source_step); 5394 } 5395 } 5396 return eKeyHandled; 5397 5398 case 'h': 5399 window.CreateHelpSubwindow (); 5400 return eKeyHandled; 5401 5402 default: 5403 break; 5404 } 5405 return eKeyNotHandled; 5406 } 5407 5408 protected: 5409 typedef std::set<uint32_t> BreakpointLines; 5410 typedef std::set<lldb::addr_t> BreakpointAddrs; 5411 5412 Debugger &m_debugger; 5413 SymbolContext m_sc; 5414 SourceManager::FileSP m_file_sp; 5415 SymbolContextScope *m_disassembly_scope; 5416 lldb::DisassemblerSP m_disassembly_sp; 5417 AddressRange m_disassembly_range; 5418 StreamString m_title; 5419 lldb::user_id_t m_tid; 5420 char m_line_format[8]; 5421 int m_line_width; 5422 uint32_t m_selected_line; // The selected line 5423 uint32_t m_pc_line; // The line with the PC 5424 uint32_t m_stop_id; 5425 uint32_t m_frame_idx; 5426 int m_first_visible_line; 5427 int m_min_x; 5428 int m_min_y; 5429 int m_max_x; 5430 int m_max_y; 5431 }; 5432 5433 DisplayOptions ValueObjectListDelegate::g_options = { true }; 5434 5435 IOHandlerCursesGUI::IOHandlerCursesGUI (Debugger &debugger) : 5436 IOHandler (debugger, IOHandler::Type::Curses) 5437 { 5438 } 5439 5440 void 5441 IOHandlerCursesGUI::Activate () 5442 { 5443 IOHandler::Activate(); 5444 if (!m_app_ap) 5445 { 5446 m_app_ap.reset (new Application (GetInputFILE(), GetOutputFILE())); 5447 5448 // This is both a window and a menu delegate 5449 std::shared_ptr<ApplicationDelegate> app_delegate_sp(new ApplicationDelegate(*m_app_ap, m_debugger)); 5450 5451 MenuDelegateSP app_menu_delegate_sp = std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 5452 MenuSP lldb_menu_sp(new Menu("LLDB" , "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 5453 MenuSP exit_menuitem_sp(new Menu("Exit", NULL, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 5454 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 5455 lldb_menu_sp->AddSubmenu (MenuSP (new Menu("About LLDB", NULL, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 5456 lldb_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); 5457 lldb_menu_sp->AddSubmenu (exit_menuitem_sp); 5458 5459 MenuSP target_menu_sp(new Menu("Target" ,"F2", KEY_F(2), ApplicationDelegate::eMenuID_Target)); 5460 target_menu_sp->AddSubmenu (MenuSP (new Menu("Create", NULL, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 5461 target_menu_sp->AddSubmenu (MenuSP (new Menu("Delete", NULL, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 5462 5463 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), ApplicationDelegate::eMenuID_Process)); 5464 process_menu_sp->AddSubmenu (MenuSP (new Menu("Attach" , NULL, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 5465 process_menu_sp->AddSubmenu (MenuSP (new Menu("Detach" , NULL, 'd', ApplicationDelegate::eMenuID_ProcessDetach))); 5466 process_menu_sp->AddSubmenu (MenuSP (new Menu("Launch" , NULL, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 5467 process_menu_sp->AddSubmenu (MenuSP (new Menu(Menu::Type::Separator))); 5468 process_menu_sp->AddSubmenu (MenuSP (new Menu("Continue", NULL, 'c', ApplicationDelegate::eMenuID_ProcessContinue))); 5469 process_menu_sp->AddSubmenu (MenuSP (new Menu("Halt" , NULL, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 5470 process_menu_sp->AddSubmenu (MenuSP (new Menu("Kill" , NULL, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 5471 5472 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), ApplicationDelegate::eMenuID_Thread)); 5473 thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step In" , NULL, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 5474 thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Over", NULL, 'v', ApplicationDelegate::eMenuID_ThreadStepOver))); 5475 thread_menu_sp->AddSubmenu (MenuSP (new Menu("Step Out" , NULL, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 5476 5477 MenuSP view_menu_sp(new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 5478 view_menu_sp->AddSubmenu (MenuSP (new Menu("Backtrace", NULL, 'b', ApplicationDelegate::eMenuID_ViewBacktrace))); 5479 view_menu_sp->AddSubmenu (MenuSP (new Menu("Registers", NULL, 'r', ApplicationDelegate::eMenuID_ViewRegisters))); 5480 view_menu_sp->AddSubmenu (MenuSP (new Menu("Source" , NULL, 's', ApplicationDelegate::eMenuID_ViewSource))); 5481 view_menu_sp->AddSubmenu (MenuSP (new Menu("Variables", NULL, 'v', ApplicationDelegate::eMenuID_ViewVariables))); 5482 5483 MenuSP help_menu_sp(new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 5484 help_menu_sp->AddSubmenu (MenuSP (new Menu("GUI Help", NULL, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 5485 5486 m_app_ap->Initialize(); 5487 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 5488 5489 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 5490 menubar_sp->AddSubmenu (lldb_menu_sp); 5491 menubar_sp->AddSubmenu (target_menu_sp); 5492 menubar_sp->AddSubmenu (process_menu_sp); 5493 menubar_sp->AddSubmenu (thread_menu_sp); 5494 menubar_sp->AddSubmenu (view_menu_sp); 5495 menubar_sp->AddSubmenu (help_menu_sp); 5496 menubar_sp->SetDelegate(app_menu_delegate_sp); 5497 5498 Rect content_bounds = main_window_sp->GetFrame(); 5499 Rect menubar_bounds = content_bounds.MakeMenuBar(); 5500 Rect status_bounds = content_bounds.MakeStatusBar(); 5501 Rect source_bounds; 5502 Rect variables_bounds; 5503 Rect threads_bounds; 5504 Rect source_variables_bounds; 5505 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, threads_bounds); 5506 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, variables_bounds); 5507 5508 WindowSP menubar_window_sp = main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 5509 // Let the menubar get keys if the active window doesn't handle the 5510 // keys that are typed so it can respond to menubar key presses. 5511 menubar_window_sp->SetCanBeActive(false); // Don't let the menubar become the active window 5512 menubar_window_sp->SetDelegate(menubar_sp); 5513 5514 WindowSP source_window_sp (main_window_sp->CreateSubWindow("Source", 5515 source_bounds, 5516 true)); 5517 WindowSP variables_window_sp (main_window_sp->CreateSubWindow("Variables", 5518 variables_bounds, 5519 false)); 5520 WindowSP threads_window_sp (main_window_sp->CreateSubWindow("Threads", 5521 threads_bounds, 5522 false)); 5523 WindowSP status_window_sp (main_window_sp->CreateSubWindow("Status", 5524 status_bounds, 5525 false)); 5526 status_window_sp->SetCanBeActive(false); // Don't let the status bar become the active window 5527 main_window_sp->SetDelegate (std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 5528 source_window_sp->SetDelegate (WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 5529 variables_window_sp->SetDelegate (WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 5530 TreeDelegateSP thread_delegate_sp (new ThreadsTreeDelegate(m_debugger)); 5531 threads_window_sp->SetDelegate (WindowDelegateSP(new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 5532 status_window_sp->SetDelegate (WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 5533 5534 // Show the main help window once the first time the curses GUI is launched 5535 static bool g_showed_help = false; 5536 if (!g_showed_help) 5537 { 5538 g_showed_help = true; 5539 main_window_sp->CreateHelpSubwindow(); 5540 } 5541 5542 init_pair (1, COLOR_WHITE , COLOR_BLUE ); 5543 init_pair (2, COLOR_BLACK , COLOR_WHITE ); 5544 init_pair (3, COLOR_MAGENTA , COLOR_WHITE ); 5545 init_pair (4, COLOR_MAGENTA , COLOR_BLACK ); 5546 init_pair (5, COLOR_RED , COLOR_BLACK ); 5547 } 5548 } 5549 5550 void 5551 IOHandlerCursesGUI::Deactivate () 5552 { 5553 m_app_ap->Terminate(); 5554 } 5555 5556 void 5557 IOHandlerCursesGUI::Run () 5558 { 5559 m_app_ap->Run(m_debugger); 5560 SetIsDone(true); 5561 } 5562 5563 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 5564 5565 void 5566 IOHandlerCursesGUI::Cancel () 5567 { 5568 } 5569 5570 bool 5571 IOHandlerCursesGUI::Interrupt () 5572 { 5573 return false; 5574 } 5575 5576 void 5577 IOHandlerCursesGUI::GotEOF() 5578 { 5579 } 5580 5581 #endif // LLDB_DISABLE_CURSES 5582