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