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