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