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