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