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