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