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