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