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