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