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