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