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