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