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