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