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