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