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