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