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