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