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