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