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 // Window drawing utilities 1082 void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { 1083 attr_t attr = 0; 1084 if (IsActive()) 1085 attr = A_BOLD | COLOR_PAIR(2); 1086 else 1087 attr = 0; 1088 if (attr) 1089 AttributeOn(attr); 1090 1091 Box(); 1092 MoveCursor(3, 0); 1093 1094 if (title && title[0]) { 1095 PutChar('<'); 1096 PutCString(title); 1097 PutChar('>'); 1098 } 1099 1100 if (bottom_message && bottom_message[0]) { 1101 int bottom_message_length = strlen(bottom_message); 1102 int x = GetWidth() - 3 - (bottom_message_length + 2); 1103 1104 if (x > 0) { 1105 MoveCursor(x, GetHeight() - 1); 1106 PutChar('['); 1107 PutCString(bottom_message); 1108 PutChar(']'); 1109 } else { 1110 MoveCursor(1, GetHeight() - 1); 1111 PutChar('['); 1112 PutCStringTruncated(bottom_message, 1); 1113 } 1114 } 1115 if (attr) 1116 AttributeOff(attr); 1117 } 1118 1119 virtual void Draw(bool force) { 1120 if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) 1121 return; 1122 1123 for (auto &subwindow_sp : m_subwindows) 1124 subwindow_sp->Draw(force); 1125 } 1126 1127 bool CreateHelpSubwindow() { 1128 if (m_delegate_sp) { 1129 const char *text = m_delegate_sp->WindowDelegateGetHelpText(); 1130 KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); 1131 if ((text && text[0]) || key_help) { 1132 std::unique_ptr<HelpDialogDelegate> help_delegate_up( 1133 new HelpDialogDelegate(text, key_help)); 1134 const size_t num_lines = help_delegate_up->GetNumLines(); 1135 const size_t max_length = help_delegate_up->GetMaxLineLength(); 1136 Rect bounds = GetBounds(); 1137 bounds.Inset(1, 1); 1138 if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { 1139 bounds.origin.x += (bounds.size.width - max_length + 4) / 2; 1140 bounds.size.width = max_length + 4; 1141 } else { 1142 if (bounds.size.width > 100) { 1143 const int inset_w = bounds.size.width / 4; 1144 bounds.origin.x += inset_w; 1145 bounds.size.width -= 2 * inset_w; 1146 } 1147 } 1148 1149 if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { 1150 bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; 1151 bounds.size.height = num_lines + 2; 1152 } else { 1153 if (bounds.size.height > 100) { 1154 const int inset_h = bounds.size.height / 4; 1155 bounds.origin.y += inset_h; 1156 bounds.size.height -= 2 * inset_h; 1157 } 1158 } 1159 WindowSP help_window_sp; 1160 Window *parent_window = GetParent(); 1161 if (parent_window) 1162 help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); 1163 else 1164 help_window_sp = CreateSubWindow("Help", bounds, true); 1165 help_window_sp->SetDelegate( 1166 WindowDelegateSP(help_delegate_up.release())); 1167 return true; 1168 } 1169 } 1170 return false; 1171 } 1172 1173 virtual HandleCharResult HandleChar(int key) { 1174 // Always check the active window first 1175 HandleCharResult result = eKeyNotHandled; 1176 WindowSP active_window_sp = GetActiveWindow(); 1177 if (active_window_sp) { 1178 result = active_window_sp->HandleChar(key); 1179 if (result != eKeyNotHandled) 1180 return result; 1181 } 1182 1183 if (m_delegate_sp) { 1184 result = m_delegate_sp->WindowDelegateHandleChar(*this, key); 1185 if (result != eKeyNotHandled) 1186 return result; 1187 } 1188 1189 // Then check for any windows that want any keys that weren't handled. This 1190 // is typically only for a menubar. Make a copy of the subwindows in case 1191 // any HandleChar() functions muck with the subwindows. If we don't do 1192 // this, we can crash when iterating over the subwindows. 1193 Windows subwindows(m_subwindows); 1194 for (auto subwindow_sp : subwindows) { 1195 if (!subwindow_sp->m_can_activate) { 1196 HandleCharResult result = subwindow_sp->HandleChar(key); 1197 if (result != eKeyNotHandled) 1198 return result; 1199 } 1200 } 1201 1202 return eKeyNotHandled; 1203 } 1204 1205 bool SetActiveWindow(Window *window) { 1206 const size_t num_subwindows = m_subwindows.size(); 1207 for (size_t i = 0; i < num_subwindows; ++i) { 1208 if (m_subwindows[i].get() == window) { 1209 m_prev_active_window_idx = m_curr_active_window_idx; 1210 ::top_panel(window->m_panel); 1211 m_curr_active_window_idx = i; 1212 return true; 1213 } 1214 } 1215 return false; 1216 } 1217 1218 WindowSP GetActiveWindow() { 1219 if (!m_subwindows.empty()) { 1220 if (m_curr_active_window_idx >= m_subwindows.size()) { 1221 if (m_prev_active_window_idx < m_subwindows.size()) { 1222 m_curr_active_window_idx = m_prev_active_window_idx; 1223 m_prev_active_window_idx = UINT32_MAX; 1224 } else if (IsActive()) { 1225 m_prev_active_window_idx = UINT32_MAX; 1226 m_curr_active_window_idx = UINT32_MAX; 1227 1228 // Find first window that wants to be active if this window is active 1229 const size_t num_subwindows = m_subwindows.size(); 1230 for (size_t i = 0; i < num_subwindows; ++i) { 1231 if (m_subwindows[i]->GetCanBeActive()) { 1232 m_curr_active_window_idx = i; 1233 break; 1234 } 1235 } 1236 } 1237 } 1238 1239 if (m_curr_active_window_idx < m_subwindows.size()) 1240 return m_subwindows[m_curr_active_window_idx]; 1241 } 1242 return WindowSP(); 1243 } 1244 1245 bool GetCanBeActive() const { return m_can_activate; } 1246 1247 void SetCanBeActive(bool b) { m_can_activate = b; } 1248 1249 const WindowDelegateSP &GetDelegate() const { return m_delegate_sp; } 1250 1251 void SetDelegate(const WindowDelegateSP &delegate_sp) { 1252 m_delegate_sp = delegate_sp; 1253 } 1254 1255 Window *GetParent() const { return m_parent; } 1256 1257 bool IsActive() const { 1258 if (m_parent) 1259 return m_parent->GetActiveWindow().get() == this; 1260 else 1261 return true; // Top level window is always active 1262 } 1263 1264 void SelectNextWindowAsActive() { 1265 // Move active focus to next window 1266 const size_t num_subwindows = m_subwindows.size(); 1267 if (m_curr_active_window_idx == UINT32_MAX) { 1268 uint32_t idx = 0; 1269 for (auto subwindow_sp : m_subwindows) { 1270 if (subwindow_sp->GetCanBeActive()) { 1271 m_curr_active_window_idx = idx; 1272 break; 1273 } 1274 ++idx; 1275 } 1276 } else if (m_curr_active_window_idx + 1 < num_subwindows) { 1277 bool handled = false; 1278 m_prev_active_window_idx = m_curr_active_window_idx; 1279 for (size_t idx = m_curr_active_window_idx + 1; idx < num_subwindows; 1280 ++idx) { 1281 if (m_subwindows[idx]->GetCanBeActive()) { 1282 m_curr_active_window_idx = idx; 1283 handled = true; 1284 break; 1285 } 1286 } 1287 if (!handled) { 1288 for (size_t idx = 0; idx <= m_prev_active_window_idx; ++idx) { 1289 if (m_subwindows[idx]->GetCanBeActive()) { 1290 m_curr_active_window_idx = idx; 1291 break; 1292 } 1293 } 1294 } 1295 } else { 1296 m_prev_active_window_idx = m_curr_active_window_idx; 1297 for (size_t idx = 0; idx < num_subwindows; ++idx) { 1298 if (m_subwindows[idx]->GetCanBeActive()) { 1299 m_curr_active_window_idx = idx; 1300 break; 1301 } 1302 } 1303 } 1304 } 1305 1306 const char *GetName() const { return m_name.c_str(); } 1307 1308 protected: 1309 std::string m_name; 1310 WINDOW *m_window; 1311 PANEL *m_panel; 1312 Window *m_parent; 1313 Windows m_subwindows; 1314 WindowDelegateSP m_delegate_sp; 1315 uint32_t m_curr_active_window_idx; 1316 uint32_t m_prev_active_window_idx; 1317 bool m_delete; 1318 bool m_needs_update; 1319 bool m_can_activate; 1320 bool m_is_subwin; 1321 1322 private: 1323 DISALLOW_COPY_AND_ASSIGN(Window); 1324 }; 1325 1326 class MenuDelegate { 1327 public: 1328 virtual ~MenuDelegate() = default; 1329 1330 virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; 1331 }; 1332 1333 class Menu : public WindowDelegate { 1334 public: 1335 enum class Type { Invalid, Bar, Item, Separator }; 1336 1337 // Menubar or separator constructor 1338 Menu(Type type); 1339 1340 // Menuitem constructor 1341 Menu(const char *name, const char *key_name, int key_value, 1342 uint64_t identifier); 1343 1344 ~Menu() override = default; 1345 1346 const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } 1347 1348 void SetDelegate(const MenuDelegateSP &delegate_sp) { 1349 m_delegate_sp = delegate_sp; 1350 } 1351 1352 void RecalculateNameLengths(); 1353 1354 void AddSubmenu(const MenuSP &menu_sp); 1355 1356 int DrawAndRunMenu(Window &window); 1357 1358 void DrawMenuTitle(Window &window, bool highlight); 1359 1360 bool WindowDelegateDraw(Window &window, bool force) override; 1361 1362 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; 1363 1364 MenuActionResult ActionPrivate(Menu &menu) { 1365 MenuActionResult result = MenuActionResult::NotHandled; 1366 if (m_delegate_sp) { 1367 result = m_delegate_sp->MenuDelegateAction(menu); 1368 if (result != MenuActionResult::NotHandled) 1369 return result; 1370 } else if (m_parent) { 1371 result = m_parent->ActionPrivate(menu); 1372 if (result != MenuActionResult::NotHandled) 1373 return result; 1374 } 1375 return m_canned_result; 1376 } 1377 1378 MenuActionResult Action() { 1379 // Call the recursive action so it can try to handle it with the menu 1380 // delegate, and if not, try our parent menu 1381 return ActionPrivate(*this); 1382 } 1383 1384 void SetCannedResult(MenuActionResult result) { m_canned_result = result; } 1385 1386 Menus &GetSubmenus() { return m_submenus; } 1387 1388 const Menus &GetSubmenus() const { return m_submenus; } 1389 1390 int GetSelectedSubmenuIndex() const { return m_selected; } 1391 1392 void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } 1393 1394 Type GetType() const { return m_type; } 1395 1396 int GetStartingColumn() const { return m_start_col; } 1397 1398 void SetStartingColumn(int col) { m_start_col = col; } 1399 1400 int GetKeyValue() const { return m_key_value; } 1401 1402 void SetKeyValue(int key_value) { m_key_value = key_value; } 1403 1404 std::string &GetName() { return m_name; } 1405 1406 std::string &GetKeyName() { return m_key_name; } 1407 1408 int GetDrawWidth() const { 1409 return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; 1410 } 1411 1412 uint64_t GetIdentifier() const { return m_identifier; } 1413 1414 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 1415 1416 protected: 1417 std::string m_name; 1418 std::string m_key_name; 1419 uint64_t m_identifier; 1420 Type m_type; 1421 int m_key_value; 1422 int m_start_col; 1423 int m_max_submenu_name_length; 1424 int m_max_submenu_key_name_length; 1425 int m_selected; 1426 Menu *m_parent; 1427 Menus m_submenus; 1428 WindowSP m_menu_window_sp; 1429 MenuActionResult m_canned_result; 1430 MenuDelegateSP m_delegate_sp; 1431 }; 1432 1433 // Menubar or separator constructor 1434 Menu::Menu(Type type) 1435 : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), 1436 m_start_col(0), m_max_submenu_name_length(0), 1437 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 1438 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 1439 m_delegate_sp() {} 1440 1441 // Menuitem constructor 1442 Menu::Menu(const char *name, const char *key_name, int key_value, 1443 uint64_t identifier) 1444 : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), 1445 m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), 1446 m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), 1447 m_submenus(), m_canned_result(MenuActionResult::NotHandled), 1448 m_delegate_sp() { 1449 if (name && name[0]) { 1450 m_name = name; 1451 m_type = Type::Item; 1452 if (key_name && key_name[0]) 1453 m_key_name = key_name; 1454 } else { 1455 m_type = Type::Separator; 1456 } 1457 } 1458 1459 void Menu::RecalculateNameLengths() { 1460 m_max_submenu_name_length = 0; 1461 m_max_submenu_key_name_length = 0; 1462 Menus &submenus = GetSubmenus(); 1463 const size_t num_submenus = submenus.size(); 1464 for (size_t i = 0; i < num_submenus; ++i) { 1465 Menu *submenu = submenus[i].get(); 1466 if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) 1467 m_max_submenu_name_length = submenu->m_name.size(); 1468 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1469 submenu->m_key_name.size()) 1470 m_max_submenu_key_name_length = submenu->m_key_name.size(); 1471 } 1472 } 1473 1474 void Menu::AddSubmenu(const MenuSP &menu_sp) { 1475 menu_sp->m_parent = this; 1476 if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) 1477 m_max_submenu_name_length = menu_sp->m_name.size(); 1478 if (static_cast<size_t>(m_max_submenu_key_name_length) < 1479 menu_sp->m_key_name.size()) 1480 m_max_submenu_key_name_length = menu_sp->m_key_name.size(); 1481 m_submenus.push_back(menu_sp); 1482 } 1483 1484 void Menu::DrawMenuTitle(Window &window, bool highlight) { 1485 if (m_type == Type::Separator) { 1486 window.MoveCursor(0, window.GetCursorY()); 1487 window.PutChar(ACS_LTEE); 1488 int width = window.GetWidth(); 1489 if (width > 2) { 1490 width -= 2; 1491 for (int i = 0; i < width; ++i) 1492 window.PutChar(ACS_HLINE); 1493 } 1494 window.PutChar(ACS_RTEE); 1495 } else { 1496 const int shortcut_key = m_key_value; 1497 bool underlined_shortcut = false; 1498 const attr_t hilgight_attr = A_REVERSE; 1499 if (highlight) 1500 window.AttributeOn(hilgight_attr); 1501 if (isprint(shortcut_key)) { 1502 size_t lower_pos = m_name.find(tolower(shortcut_key)); 1503 size_t upper_pos = m_name.find(toupper(shortcut_key)); 1504 const char *name = m_name.c_str(); 1505 size_t pos = std::min<size_t>(lower_pos, upper_pos); 1506 if (pos != std::string::npos) { 1507 underlined_shortcut = true; 1508 if (pos > 0) { 1509 window.PutCString(name, pos); 1510 name += pos; 1511 } 1512 const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; 1513 window.AttributeOn(shortcut_attr); 1514 window.PutChar(name[0]); 1515 window.AttributeOff(shortcut_attr); 1516 name++; 1517 if (name[0]) 1518 window.PutCString(name); 1519 } 1520 } 1521 1522 if (!underlined_shortcut) { 1523 window.PutCString(m_name.c_str()); 1524 } 1525 1526 if (highlight) 1527 window.AttributeOff(hilgight_attr); 1528 1529 if (m_key_name.empty()) { 1530 if (!underlined_shortcut && isprint(m_key_value)) { 1531 window.AttributeOn(COLOR_PAIR(3)); 1532 window.Printf(" (%c)", m_key_value); 1533 window.AttributeOff(COLOR_PAIR(3)); 1534 } 1535 } else { 1536 window.AttributeOn(COLOR_PAIR(3)); 1537 window.Printf(" (%s)", m_key_name.c_str()); 1538 window.AttributeOff(COLOR_PAIR(3)); 1539 } 1540 } 1541 } 1542 1543 bool Menu::WindowDelegateDraw(Window &window, bool force) { 1544 Menus &submenus = GetSubmenus(); 1545 const size_t num_submenus = submenus.size(); 1546 const int selected_idx = GetSelectedSubmenuIndex(); 1547 Menu::Type menu_type = GetType(); 1548 switch (menu_type) { 1549 case Menu::Type::Bar: { 1550 window.SetBackground(2); 1551 window.MoveCursor(0, 0); 1552 for (size_t i = 0; i < num_submenus; ++i) { 1553 Menu *menu = submenus[i].get(); 1554 if (i > 0) 1555 window.PutChar(' '); 1556 menu->SetStartingColumn(window.GetCursorX()); 1557 window.PutCString("| "); 1558 menu->DrawMenuTitle(window, false); 1559 } 1560 window.PutCString(" |"); 1561 window.DeferredRefresh(); 1562 } break; 1563 1564 case Menu::Type::Item: { 1565 int y = 1; 1566 int x = 3; 1567 // Draw the menu 1568 int cursor_x = 0; 1569 int cursor_y = 0; 1570 window.Erase(); 1571 window.SetBackground(2); 1572 window.Box(); 1573 for (size_t i = 0; i < num_submenus; ++i) { 1574 const bool is_selected = (i == static_cast<size_t>(selected_idx)); 1575 window.MoveCursor(x, y + i); 1576 if (is_selected) { 1577 // Remember where we want the cursor to be 1578 cursor_x = x - 1; 1579 cursor_y = y + i; 1580 } 1581 submenus[i]->DrawMenuTitle(window, is_selected); 1582 } 1583 window.MoveCursor(cursor_x, cursor_y); 1584 window.DeferredRefresh(); 1585 } break; 1586 1587 default: 1588 case Menu::Type::Separator: 1589 break; 1590 } 1591 return true; // Drawing handled... 1592 } 1593 1594 HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { 1595 HandleCharResult result = eKeyNotHandled; 1596 1597 Menus &submenus = GetSubmenus(); 1598 const size_t num_submenus = submenus.size(); 1599 const int selected_idx = GetSelectedSubmenuIndex(); 1600 Menu::Type menu_type = GetType(); 1601 if (menu_type == Menu::Type::Bar) { 1602 MenuSP run_menu_sp; 1603 switch (key) { 1604 case KEY_DOWN: 1605 case KEY_UP: 1606 // Show last menu or first menu 1607 if (selected_idx < static_cast<int>(num_submenus)) 1608 run_menu_sp = submenus[selected_idx]; 1609 else if (!submenus.empty()) 1610 run_menu_sp = submenus.front(); 1611 result = eKeyHandled; 1612 break; 1613 1614 case KEY_RIGHT: 1615 ++m_selected; 1616 if (m_selected >= static_cast<int>(num_submenus)) 1617 m_selected = 0; 1618 if (m_selected < static_cast<int>(num_submenus)) 1619 run_menu_sp = submenus[m_selected]; 1620 else if (!submenus.empty()) 1621 run_menu_sp = submenus.front(); 1622 result = eKeyHandled; 1623 break; 1624 1625 case KEY_LEFT: 1626 --m_selected; 1627 if (m_selected < 0) 1628 m_selected = num_submenus - 1; 1629 if (m_selected < static_cast<int>(num_submenus)) 1630 run_menu_sp = submenus[m_selected]; 1631 else if (!submenus.empty()) 1632 run_menu_sp = submenus.front(); 1633 result = eKeyHandled; 1634 break; 1635 1636 default: 1637 for (size_t i = 0; i < num_submenus; ++i) { 1638 if (submenus[i]->GetKeyValue() == key) { 1639 SetSelectedSubmenuIndex(i); 1640 run_menu_sp = submenus[i]; 1641 result = eKeyHandled; 1642 break; 1643 } 1644 } 1645 break; 1646 } 1647 1648 if (run_menu_sp) { 1649 // Run the action on this menu in case we need to populate the menu with 1650 // dynamic content and also in case check marks, and any other menu 1651 // decorations need to be calculated 1652 if (run_menu_sp->Action() == MenuActionResult::Quit) 1653 return eQuitApplication; 1654 1655 Rect menu_bounds; 1656 menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); 1657 menu_bounds.origin.y = 1; 1658 menu_bounds.size.width = run_menu_sp->GetDrawWidth(); 1659 menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; 1660 if (m_menu_window_sp) 1661 window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); 1662 1663 m_menu_window_sp = window.GetParent()->CreateSubWindow( 1664 run_menu_sp->GetName().c_str(), menu_bounds, true); 1665 m_menu_window_sp->SetDelegate(run_menu_sp); 1666 } 1667 } else if (menu_type == Menu::Type::Item) { 1668 switch (key) { 1669 case KEY_DOWN: 1670 if (m_submenus.size() > 1) { 1671 const int start_select = m_selected; 1672 while (++m_selected != start_select) { 1673 if (static_cast<size_t>(m_selected) >= num_submenus) 1674 m_selected = 0; 1675 if (m_submenus[m_selected]->GetType() == Type::Separator) 1676 continue; 1677 else 1678 break; 1679 } 1680 return eKeyHandled; 1681 } 1682 break; 1683 1684 case KEY_UP: 1685 if (m_submenus.size() > 1) { 1686 const int start_select = m_selected; 1687 while (--m_selected != start_select) { 1688 if (m_selected < static_cast<int>(0)) 1689 m_selected = num_submenus - 1; 1690 if (m_submenus[m_selected]->GetType() == Type::Separator) 1691 continue; 1692 else 1693 break; 1694 } 1695 return eKeyHandled; 1696 } 1697 break; 1698 1699 case KEY_RETURN: 1700 if (static_cast<size_t>(selected_idx) < num_submenus) { 1701 if (submenus[selected_idx]->Action() == MenuActionResult::Quit) 1702 return eQuitApplication; 1703 window.GetParent()->RemoveSubWindow(&window); 1704 return eKeyHandled; 1705 } 1706 break; 1707 1708 case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in 1709 // case other chars are entered for escaped sequences 1710 window.GetParent()->RemoveSubWindow(&window); 1711 return eKeyHandled; 1712 1713 default: 1714 for (size_t i = 0; i < num_submenus; ++i) { 1715 Menu *menu = submenus[i].get(); 1716 if (menu->GetKeyValue() == key) { 1717 SetSelectedSubmenuIndex(i); 1718 window.GetParent()->RemoveSubWindow(&window); 1719 if (menu->Action() == MenuActionResult::Quit) 1720 return eQuitApplication; 1721 return eKeyHandled; 1722 } 1723 } 1724 break; 1725 } 1726 } else if (menu_type == Menu::Type::Separator) { 1727 } 1728 return result; 1729 } 1730 1731 class Application { 1732 public: 1733 Application(FILE *in, FILE *out) 1734 : m_window_sp(), m_screen(nullptr), m_in(in), m_out(out) {} 1735 1736 ~Application() { 1737 m_window_delegates.clear(); 1738 m_window_sp.reset(); 1739 if (m_screen) { 1740 ::delscreen(m_screen); 1741 m_screen = nullptr; 1742 } 1743 } 1744 1745 void Initialize() { 1746 ::setlocale(LC_ALL, ""); 1747 ::setlocale(LC_CTYPE, ""); 1748 m_screen = ::newterm(nullptr, m_out, m_in); 1749 ::start_color(); 1750 ::curs_set(0); 1751 ::noecho(); 1752 ::keypad(stdscr, TRUE); 1753 } 1754 1755 void Terminate() { ::endwin(); } 1756 1757 void Run(Debugger &debugger) { 1758 bool done = false; 1759 int delay_in_tenths_of_a_second = 1; 1760 1761 // Alas the threading model in curses is a bit lame so we need to resort to 1762 // polling every 0.5 seconds. We could poll for stdin ourselves and then 1763 // pass the keys down but then we need to translate all of the escape 1764 // sequences ourselves. So we resort to polling for input because we need 1765 // to receive async process events while in this loop. 1766 1767 halfdelay(delay_in_tenths_of_a_second); // Poll using some number of tenths 1768 // of seconds seconds when calling 1769 // Window::GetChar() 1770 1771 ListenerSP listener_sp( 1772 Listener::MakeListener("lldb.IOHandler.curses.Application")); 1773 ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); 1774 ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); 1775 ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); 1776 debugger.EnableForwardEvents(listener_sp); 1777 1778 bool update = true; 1779 #if defined(__APPLE__) 1780 std::deque<int> escape_chars; 1781 #endif 1782 1783 while (!done) { 1784 if (update) { 1785 m_window_sp->Draw(false); 1786 // All windows should be calling Window::DeferredRefresh() instead of 1787 // Window::Refresh() so we can do a single update and avoid any screen 1788 // blinking 1789 update_panels(); 1790 1791 // Cursor hiding isn't working on MacOSX, so hide it in the top left 1792 // corner 1793 m_window_sp->MoveCursor(0, 0); 1794 1795 doupdate(); 1796 update = false; 1797 } 1798 1799 #if defined(__APPLE__) 1800 // Terminal.app doesn't map its function keys correctly, F1-F4 default 1801 // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if 1802 // possible 1803 int ch; 1804 if (escape_chars.empty()) 1805 ch = m_window_sp->GetChar(); 1806 else { 1807 ch = escape_chars.front(); 1808 escape_chars.pop_front(); 1809 } 1810 if (ch == KEY_ESCAPE) { 1811 int ch2 = m_window_sp->GetChar(); 1812 if (ch2 == 'O') { 1813 int ch3 = m_window_sp->GetChar(); 1814 switch (ch3) { 1815 case 'P': 1816 ch = KEY_F(1); 1817 break; 1818 case 'Q': 1819 ch = KEY_F(2); 1820 break; 1821 case 'R': 1822 ch = KEY_F(3); 1823 break; 1824 case 'S': 1825 ch = KEY_F(4); 1826 break; 1827 default: 1828 escape_chars.push_back(ch2); 1829 if (ch3 != -1) 1830 escape_chars.push_back(ch3); 1831 break; 1832 } 1833 } else if (ch2 != -1) 1834 escape_chars.push_back(ch2); 1835 } 1836 #else 1837 int ch = m_window_sp->GetChar(); 1838 1839 #endif 1840 if (ch == -1) { 1841 if (feof(m_in) || ferror(m_in)) { 1842 done = true; 1843 } else { 1844 // Just a timeout from using halfdelay(), check for events 1845 EventSP event_sp; 1846 while (listener_sp->PeekAtNextEvent()) { 1847 listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); 1848 1849 if (event_sp) { 1850 Broadcaster *broadcaster = event_sp->GetBroadcaster(); 1851 if (broadcaster) { 1852 // uint32_t event_type = event_sp->GetType(); 1853 ConstString broadcaster_class( 1854 broadcaster->GetBroadcasterClass()); 1855 if (broadcaster_class == broadcaster_class_process) { 1856 debugger.GetCommandInterpreter().UpdateExecutionContext( 1857 nullptr); 1858 update = true; 1859 continue; // Don't get any key, just update our view 1860 } 1861 } 1862 } 1863 } 1864 } 1865 } else { 1866 HandleCharResult key_result = m_window_sp->HandleChar(ch); 1867 switch (key_result) { 1868 case eKeyHandled: 1869 debugger.GetCommandInterpreter().UpdateExecutionContext(nullptr); 1870 update = true; 1871 break; 1872 case eKeyNotHandled: 1873 break; 1874 case eQuitApplication: 1875 done = true; 1876 break; 1877 } 1878 } 1879 } 1880 1881 debugger.CancelForwardEvents(listener_sp); 1882 } 1883 1884 WindowSP &GetMainWindow() { 1885 if (!m_window_sp) 1886 m_window_sp = std::make_shared<Window>("main", stdscr, false); 1887 return m_window_sp; 1888 } 1889 1890 WindowDelegates &GetWindowDelegates() { return m_window_delegates; } 1891 1892 protected: 1893 WindowSP m_window_sp; 1894 WindowDelegates m_window_delegates; 1895 SCREEN *m_screen; 1896 FILE *m_in; 1897 FILE *m_out; 1898 }; 1899 1900 } // namespace curses 1901 1902 using namespace curses; 1903 1904 struct Row { 1905 ValueObjectManager value; 1906 Row *parent; 1907 // The process stop ID when the children were calculated. 1908 uint32_t children_stop_id; 1909 int row_idx; 1910 int x; 1911 int y; 1912 bool might_have_children; 1913 bool expanded; 1914 bool calculated_children; 1915 std::vector<Row> children; 1916 1917 Row(const ValueObjectSP &v, Row *p) 1918 : value(v, lldb::eDynamicDontRunTarget, true), parent(p), row_idx(0), 1919 x(1), y(1), might_have_children(v ? v->MightHaveChildren() : false), 1920 expanded(false), calculated_children(false), children() {} 1921 1922 size_t GetDepth() const { 1923 if (parent) 1924 return 1 + parent->GetDepth(); 1925 return 0; 1926 } 1927 1928 void Expand() { 1929 expanded = true; 1930 } 1931 1932 std::vector<Row> &GetChildren() { 1933 ProcessSP process_sp = value.GetProcessSP(); 1934 auto stop_id = process_sp->GetStopID(); 1935 if (process_sp && stop_id != children_stop_id) { 1936 children_stop_id = stop_id; 1937 calculated_children = false; 1938 } 1939 if (!calculated_children) { 1940 children.clear(); 1941 calculated_children = true; 1942 ValueObjectSP valobj = value.GetSP(); 1943 if (valobj) { 1944 const size_t num_children = valobj->GetNumChildren(); 1945 for (size_t i = 0; i < num_children; ++i) { 1946 children.push_back(Row(valobj->GetChildAtIndex(i, true), this)); 1947 } 1948 } 1949 } 1950 return children; 1951 } 1952 1953 void Unexpand() { 1954 expanded = false; 1955 calculated_children = false; 1956 children.clear(); 1957 } 1958 1959 void DrawTree(Window &window) { 1960 if (parent) 1961 parent->DrawTreeForChild(window, this, 0); 1962 1963 if (might_have_children) { 1964 // It we can get UTF8 characters to work we should try to use the 1965 // "symbol" UTF8 string below 1966 // const char *symbol = ""; 1967 // if (row.expanded) 1968 // symbol = "\xe2\x96\xbd "; 1969 // else 1970 // symbol = "\xe2\x96\xb7 "; 1971 // window.PutCString (symbol); 1972 1973 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' 1974 // or '>' character... 1975 // if (expanded) 1976 // window.PutChar (ACS_DARROW); 1977 // else 1978 // window.PutChar (ACS_RARROW); 1979 // Since we can't find any good looking right arrow/down arrow symbols, 1980 // just use a diamond... 1981 window.PutChar(ACS_DIAMOND); 1982 window.PutChar(ACS_HLINE); 1983 } 1984 } 1985 1986 void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { 1987 if (parent) 1988 parent->DrawTreeForChild(window, this, reverse_depth + 1); 1989 1990 if (&GetChildren().back() == child) { 1991 // Last child 1992 if (reverse_depth == 0) { 1993 window.PutChar(ACS_LLCORNER); 1994 window.PutChar(ACS_HLINE); 1995 } else { 1996 window.PutChar(' '); 1997 window.PutChar(' '); 1998 } 1999 } else { 2000 if (reverse_depth == 0) { 2001 window.PutChar(ACS_LTEE); 2002 window.PutChar(ACS_HLINE); 2003 } else { 2004 window.PutChar(ACS_VLINE); 2005 window.PutChar(' '); 2006 } 2007 } 2008 } 2009 }; 2010 2011 struct DisplayOptions { 2012 bool show_types; 2013 }; 2014 2015 class TreeItem; 2016 2017 class TreeDelegate { 2018 public: 2019 TreeDelegate() = default; 2020 virtual ~TreeDelegate() = default; 2021 2022 virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; 2023 virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; 2024 virtual bool TreeDelegateItemSelected( 2025 TreeItem &item) = 0; // Return true if we need to update views 2026 }; 2027 2028 typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; 2029 2030 class TreeItem { 2031 public: 2032 TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) 2033 : m_parent(parent), m_delegate(delegate), m_user_data(nullptr), 2034 m_identifier(0), m_row_idx(-1), m_children(), 2035 m_might_have_children(might_have_children), m_is_expanded(false) {} 2036 2037 TreeItem &operator=(const TreeItem &rhs) { 2038 if (this != &rhs) { 2039 m_parent = rhs.m_parent; 2040 m_delegate = rhs.m_delegate; 2041 m_user_data = rhs.m_user_data; 2042 m_identifier = rhs.m_identifier; 2043 m_row_idx = rhs.m_row_idx; 2044 m_children = rhs.m_children; 2045 m_might_have_children = rhs.m_might_have_children; 2046 m_is_expanded = rhs.m_is_expanded; 2047 } 2048 return *this; 2049 } 2050 2051 size_t GetDepth() const { 2052 if (m_parent) 2053 return 1 + m_parent->GetDepth(); 2054 return 0; 2055 } 2056 2057 int GetRowIndex() const { return m_row_idx; } 2058 2059 void ClearChildren() { m_children.clear(); } 2060 2061 void Resize(size_t n, const TreeItem &t) { m_children.resize(n, t); } 2062 2063 TreeItem &operator[](size_t i) { return m_children[i]; } 2064 2065 void SetRowIndex(int row_idx) { m_row_idx = row_idx; } 2066 2067 size_t GetNumChildren() { 2068 m_delegate.TreeDelegateGenerateChildren(*this); 2069 return m_children.size(); 2070 } 2071 2072 void ItemWasSelected() { m_delegate.TreeDelegateItemSelected(*this); } 2073 2074 void CalculateRowIndexes(int &row_idx) { 2075 SetRowIndex(row_idx); 2076 ++row_idx; 2077 2078 const bool expanded = IsExpanded(); 2079 2080 // The root item must calculate its children, or we must calculate the 2081 // number of children if the item is expanded 2082 if (m_parent == nullptr || expanded) 2083 GetNumChildren(); 2084 2085 for (auto &item : m_children) { 2086 if (expanded) 2087 item.CalculateRowIndexes(row_idx); 2088 else 2089 item.SetRowIndex(-1); 2090 } 2091 } 2092 2093 TreeItem *GetParent() { return m_parent; } 2094 2095 bool IsExpanded() const { return m_is_expanded; } 2096 2097 void Expand() { m_is_expanded = true; } 2098 2099 void Unexpand() { m_is_expanded = false; } 2100 2101 bool Draw(Window &window, const int first_visible_row, 2102 const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { 2103 if (num_rows_left <= 0) 2104 return false; 2105 2106 if (m_row_idx >= first_visible_row) { 2107 window.MoveCursor(2, row_idx + 1); 2108 2109 if (m_parent) 2110 m_parent->DrawTreeForChild(window, this, 0); 2111 2112 if (m_might_have_children) { 2113 // It we can get UTF8 characters to work we should try to use the 2114 // "symbol" UTF8 string below 2115 // const char *symbol = ""; 2116 // if (row.expanded) 2117 // symbol = "\xe2\x96\xbd "; 2118 // else 2119 // symbol = "\xe2\x96\xb7 "; 2120 // window.PutCString (symbol); 2121 2122 // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 2123 // 'v' or '>' character... 2124 // if (expanded) 2125 // window.PutChar (ACS_DARROW); 2126 // else 2127 // window.PutChar (ACS_RARROW); 2128 // Since we can't find any good looking right arrow/down arrow symbols, 2129 // just use a diamond... 2130 window.PutChar(ACS_DIAMOND); 2131 window.PutChar(ACS_HLINE); 2132 } 2133 bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && 2134 window.IsActive(); 2135 2136 if (highlight) 2137 window.AttributeOn(A_REVERSE); 2138 2139 m_delegate.TreeDelegateDrawTreeItem(*this, window); 2140 2141 if (highlight) 2142 window.AttributeOff(A_REVERSE); 2143 ++row_idx; 2144 --num_rows_left; 2145 } 2146 2147 if (num_rows_left <= 0) 2148 return false; // We are done drawing... 2149 2150 if (IsExpanded()) { 2151 for (auto &item : m_children) { 2152 // If we displayed all the rows and item.Draw() returns false we are 2153 // done drawing and can exit this for loop 2154 if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, 2155 num_rows_left)) 2156 break; 2157 } 2158 } 2159 return num_rows_left >= 0; // Return true if not done drawing yet 2160 } 2161 2162 void DrawTreeForChild(Window &window, TreeItem *child, 2163 uint32_t reverse_depth) { 2164 if (m_parent) 2165 m_parent->DrawTreeForChild(window, this, reverse_depth + 1); 2166 2167 if (&m_children.back() == child) { 2168 // Last child 2169 if (reverse_depth == 0) { 2170 window.PutChar(ACS_LLCORNER); 2171 window.PutChar(ACS_HLINE); 2172 } else { 2173 window.PutChar(' '); 2174 window.PutChar(' '); 2175 } 2176 } else { 2177 if (reverse_depth == 0) { 2178 window.PutChar(ACS_LTEE); 2179 window.PutChar(ACS_HLINE); 2180 } else { 2181 window.PutChar(ACS_VLINE); 2182 window.PutChar(' '); 2183 } 2184 } 2185 } 2186 2187 TreeItem *GetItemForRowIndex(uint32_t row_idx) { 2188 if (static_cast<uint32_t>(m_row_idx) == row_idx) 2189 return this; 2190 if (m_children.empty()) 2191 return nullptr; 2192 if (IsExpanded()) { 2193 for (auto &item : m_children) { 2194 TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); 2195 if (selected_item_ptr) 2196 return selected_item_ptr; 2197 } 2198 } 2199 return nullptr; 2200 } 2201 2202 void *GetUserData() const { return m_user_data; } 2203 2204 void SetUserData(void *user_data) { m_user_data = user_data; } 2205 2206 uint64_t GetIdentifier() const { return m_identifier; } 2207 2208 void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } 2209 2210 void SetMightHaveChildren(bool b) { m_might_have_children = b; } 2211 2212 protected: 2213 TreeItem *m_parent; 2214 TreeDelegate &m_delegate; 2215 void *m_user_data; 2216 uint64_t m_identifier; 2217 int m_row_idx; // Zero based visible row index, -1 if not visible or for the 2218 // root item 2219 std::vector<TreeItem> m_children; 2220 bool m_might_have_children; 2221 bool m_is_expanded; 2222 }; 2223 2224 class TreeWindowDelegate : public WindowDelegate { 2225 public: 2226 TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) 2227 : m_debugger(debugger), m_delegate_sp(delegate_sp), 2228 m_root(nullptr, *delegate_sp, true), m_selected_item(nullptr), 2229 m_num_rows(0), m_selected_row_idx(0), m_first_visible_row(0), 2230 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 2231 2232 int NumVisibleRows() const { return m_max_y - m_min_y; } 2233 2234 bool WindowDelegateDraw(Window &window, bool force) override { 2235 ExecutionContext exe_ctx( 2236 m_debugger.GetCommandInterpreter().GetExecutionContext()); 2237 Process *process = exe_ctx.GetProcessPtr(); 2238 2239 bool display_content = false; 2240 if (process) { 2241 StateType state = process->GetState(); 2242 if (StateIsStoppedState(state, true)) { 2243 // We are stopped, so it is ok to 2244 display_content = true; 2245 } else if (StateIsRunningState(state)) { 2246 return true; // Don't do any updating when we are running 2247 } 2248 } 2249 2250 m_min_x = 2; 2251 m_min_y = 1; 2252 m_max_x = window.GetWidth() - 1; 2253 m_max_y = window.GetHeight() - 1; 2254 2255 window.Erase(); 2256 window.DrawTitleBox(window.GetName()); 2257 2258 if (display_content) { 2259 const int num_visible_rows = NumVisibleRows(); 2260 m_num_rows = 0; 2261 m_root.CalculateRowIndexes(m_num_rows); 2262 2263 // If we unexpanded while having something selected our total number of 2264 // rows is less than the num visible rows, then make sure we show all the 2265 // rows by setting the first visible row accordingly. 2266 if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) 2267 m_first_visible_row = 0; 2268 2269 // Make sure the selected row is always visible 2270 if (m_selected_row_idx < m_first_visible_row) 2271 m_first_visible_row = m_selected_row_idx; 2272 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 2273 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 2274 2275 int row_idx = 0; 2276 int num_rows_left = num_visible_rows; 2277 m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, 2278 num_rows_left); 2279 // Get the selected row 2280 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2281 } else { 2282 m_selected_item = nullptr; 2283 } 2284 2285 window.DeferredRefresh(); 2286 2287 return true; // Drawing handled 2288 } 2289 2290 const char *WindowDelegateGetHelpText() override { 2291 return "Thread window keyboard shortcuts:"; 2292 } 2293 2294 KeyHelp *WindowDelegateGetKeyHelp() override { 2295 static curses::KeyHelp g_source_view_key_help[] = { 2296 {KEY_UP, "Select previous item"}, 2297 {KEY_DOWN, "Select next item"}, 2298 {KEY_RIGHT, "Expand the selected item"}, 2299 {KEY_LEFT, 2300 "Unexpand the selected item or select parent if not expanded"}, 2301 {KEY_PPAGE, "Page up"}, 2302 {KEY_NPAGE, "Page down"}, 2303 {'h', "Show help dialog"}, 2304 {' ', "Toggle item expansion"}, 2305 {',', "Page up"}, 2306 {'.', "Page down"}, 2307 {'\0', nullptr}}; 2308 return g_source_view_key_help; 2309 } 2310 2311 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 2312 switch (c) { 2313 case ',': 2314 case KEY_PPAGE: 2315 // Page up key 2316 if (m_first_visible_row > 0) { 2317 if (m_first_visible_row > m_max_y) 2318 m_first_visible_row -= m_max_y; 2319 else 2320 m_first_visible_row = 0; 2321 m_selected_row_idx = m_first_visible_row; 2322 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2323 if (m_selected_item) 2324 m_selected_item->ItemWasSelected(); 2325 } 2326 return eKeyHandled; 2327 2328 case '.': 2329 case KEY_NPAGE: 2330 // Page down key 2331 if (m_num_rows > m_max_y) { 2332 if (m_first_visible_row + m_max_y < m_num_rows) { 2333 m_first_visible_row += m_max_y; 2334 m_selected_row_idx = m_first_visible_row; 2335 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2336 if (m_selected_item) 2337 m_selected_item->ItemWasSelected(); 2338 } 2339 } 2340 return eKeyHandled; 2341 2342 case KEY_UP: 2343 if (m_selected_row_idx > 0) { 2344 --m_selected_row_idx; 2345 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2346 if (m_selected_item) 2347 m_selected_item->ItemWasSelected(); 2348 } 2349 return eKeyHandled; 2350 2351 case KEY_DOWN: 2352 if (m_selected_row_idx + 1 < m_num_rows) { 2353 ++m_selected_row_idx; 2354 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2355 if (m_selected_item) 2356 m_selected_item->ItemWasSelected(); 2357 } 2358 return eKeyHandled; 2359 2360 case KEY_RIGHT: 2361 if (m_selected_item) { 2362 if (!m_selected_item->IsExpanded()) 2363 m_selected_item->Expand(); 2364 } 2365 return eKeyHandled; 2366 2367 case KEY_LEFT: 2368 if (m_selected_item) { 2369 if (m_selected_item->IsExpanded()) 2370 m_selected_item->Unexpand(); 2371 else if (m_selected_item->GetParent()) { 2372 m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); 2373 m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); 2374 if (m_selected_item) 2375 m_selected_item->ItemWasSelected(); 2376 } 2377 } 2378 return eKeyHandled; 2379 2380 case ' ': 2381 // Toggle expansion state when SPACE is pressed 2382 if (m_selected_item) { 2383 if (m_selected_item->IsExpanded()) 2384 m_selected_item->Unexpand(); 2385 else 2386 m_selected_item->Expand(); 2387 } 2388 return eKeyHandled; 2389 2390 case 'h': 2391 window.CreateHelpSubwindow(); 2392 return eKeyHandled; 2393 2394 default: 2395 break; 2396 } 2397 return eKeyNotHandled; 2398 } 2399 2400 protected: 2401 Debugger &m_debugger; 2402 TreeDelegateSP m_delegate_sp; 2403 TreeItem m_root; 2404 TreeItem *m_selected_item; 2405 int m_num_rows; 2406 int m_selected_row_idx; 2407 int m_first_visible_row; 2408 int m_min_x; 2409 int m_min_y; 2410 int m_max_x; 2411 int m_max_y; 2412 }; 2413 2414 class FrameTreeDelegate : public TreeDelegate { 2415 public: 2416 FrameTreeDelegate() : TreeDelegate() { 2417 FormatEntity::Parse( 2418 "frame #${frame.index}: {${function.name}${function.pc-offset}}}", 2419 m_format); 2420 } 2421 2422 ~FrameTreeDelegate() override = default; 2423 2424 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2425 Thread *thread = (Thread *)item.GetUserData(); 2426 if (thread) { 2427 const uint64_t frame_idx = item.GetIdentifier(); 2428 StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); 2429 if (frame_sp) { 2430 StreamString strm; 2431 const SymbolContext &sc = 2432 frame_sp->GetSymbolContext(eSymbolContextEverything); 2433 ExecutionContext exe_ctx(frame_sp); 2434 if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, 2435 nullptr, false, false)) { 2436 int right_pad = 1; 2437 window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad); 2438 } 2439 } 2440 } 2441 } 2442 2443 void TreeDelegateGenerateChildren(TreeItem &item) override { 2444 // No children for frames yet... 2445 } 2446 2447 bool TreeDelegateItemSelected(TreeItem &item) override { 2448 Thread *thread = (Thread *)item.GetUserData(); 2449 if (thread) { 2450 thread->GetProcess()->GetThreadList().SetSelectedThreadByID( 2451 thread->GetID()); 2452 const uint64_t frame_idx = item.GetIdentifier(); 2453 thread->SetSelectedFrameByIndex(frame_idx); 2454 return true; 2455 } 2456 return false; 2457 } 2458 2459 protected: 2460 FormatEntity::Entry m_format; 2461 }; 2462 2463 class ThreadTreeDelegate : public TreeDelegate { 2464 public: 2465 ThreadTreeDelegate(Debugger &debugger) 2466 : TreeDelegate(), m_debugger(debugger), m_tid(LLDB_INVALID_THREAD_ID), 2467 m_stop_id(UINT32_MAX) { 2468 FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " 2469 "reason = ${thread.stop-reason}}", 2470 m_format); 2471 } 2472 2473 ~ThreadTreeDelegate() override = default; 2474 2475 ProcessSP GetProcess() { 2476 return m_debugger.GetCommandInterpreter() 2477 .GetExecutionContext() 2478 .GetProcessSP(); 2479 } 2480 2481 ThreadSP GetThread(const TreeItem &item) { 2482 ProcessSP process_sp = GetProcess(); 2483 if (process_sp) 2484 return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); 2485 return ThreadSP(); 2486 } 2487 2488 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2489 ThreadSP thread_sp = GetThread(item); 2490 if (thread_sp) { 2491 StreamString strm; 2492 ExecutionContext exe_ctx(thread_sp); 2493 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2494 nullptr, false, false)) { 2495 int right_pad = 1; 2496 window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad); 2497 } 2498 } 2499 } 2500 2501 void TreeDelegateGenerateChildren(TreeItem &item) override { 2502 ProcessSP process_sp = GetProcess(); 2503 if (process_sp && process_sp->IsAlive()) { 2504 StateType state = process_sp->GetState(); 2505 if (StateIsStoppedState(state, true)) { 2506 ThreadSP thread_sp = GetThread(item); 2507 if (thread_sp) { 2508 if (m_stop_id == process_sp->GetStopID() && 2509 thread_sp->GetID() == m_tid) 2510 return; // Children are already up to date 2511 if (!m_frame_delegate_sp) { 2512 // Always expand the thread item the first time we show it 2513 m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); 2514 } 2515 2516 m_stop_id = process_sp->GetStopID(); 2517 m_tid = thread_sp->GetID(); 2518 2519 TreeItem t(&item, *m_frame_delegate_sp, false); 2520 size_t num_frames = thread_sp->GetStackFrameCount(); 2521 item.Resize(num_frames, t); 2522 for (size_t i = 0; i < num_frames; ++i) { 2523 item[i].SetUserData(thread_sp.get()); 2524 item[i].SetIdentifier(i); 2525 } 2526 } 2527 return; 2528 } 2529 } 2530 item.ClearChildren(); 2531 } 2532 2533 bool TreeDelegateItemSelected(TreeItem &item) override { 2534 ProcessSP process_sp = GetProcess(); 2535 if (process_sp && process_sp->IsAlive()) { 2536 StateType state = process_sp->GetState(); 2537 if (StateIsStoppedState(state, true)) { 2538 ThreadSP thread_sp = GetThread(item); 2539 if (thread_sp) { 2540 ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); 2541 std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); 2542 ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); 2543 if (selected_thread_sp->GetID() != thread_sp->GetID()) { 2544 thread_list.SetSelectedThreadByID(thread_sp->GetID()); 2545 return true; 2546 } 2547 } 2548 } 2549 } 2550 return false; 2551 } 2552 2553 protected: 2554 Debugger &m_debugger; 2555 std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; 2556 lldb::user_id_t m_tid; 2557 uint32_t m_stop_id; 2558 FormatEntity::Entry m_format; 2559 }; 2560 2561 class ThreadsTreeDelegate : public TreeDelegate { 2562 public: 2563 ThreadsTreeDelegate(Debugger &debugger) 2564 : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger), 2565 m_stop_id(UINT32_MAX) { 2566 FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", 2567 m_format); 2568 } 2569 2570 ~ThreadsTreeDelegate() override = default; 2571 2572 ProcessSP GetProcess() { 2573 return m_debugger.GetCommandInterpreter() 2574 .GetExecutionContext() 2575 .GetProcessSP(); 2576 } 2577 2578 void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { 2579 ProcessSP process_sp = GetProcess(); 2580 if (process_sp && process_sp->IsAlive()) { 2581 StreamString strm; 2582 ExecutionContext exe_ctx(process_sp); 2583 if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, 2584 nullptr, false, false)) { 2585 int right_pad = 1; 2586 window.PutCStringTruncated(strm.GetString().str().c_str(), right_pad); 2587 } 2588 } 2589 } 2590 2591 void TreeDelegateGenerateChildren(TreeItem &item) override { 2592 ProcessSP process_sp = GetProcess(); 2593 if (process_sp && process_sp->IsAlive()) { 2594 StateType state = process_sp->GetState(); 2595 if (StateIsStoppedState(state, true)) { 2596 const uint32_t stop_id = process_sp->GetStopID(); 2597 if (m_stop_id == stop_id) 2598 return; // Children are already up to date 2599 2600 m_stop_id = stop_id; 2601 2602 if (!m_thread_delegate_sp) { 2603 // Always expand the thread item the first time we show it 2604 // item.Expand(); 2605 m_thread_delegate_sp = 2606 std::make_shared<ThreadTreeDelegate>(m_debugger); 2607 } 2608 2609 TreeItem t(&item, *m_thread_delegate_sp, false); 2610 ThreadList &threads = process_sp->GetThreadList(); 2611 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 2612 size_t num_threads = threads.GetSize(); 2613 item.Resize(num_threads, t); 2614 for (size_t i = 0; i < num_threads; ++i) { 2615 item[i].SetIdentifier(threads.GetThreadAtIndex(i)->GetID()); 2616 item[i].SetMightHaveChildren(true); 2617 } 2618 return; 2619 } 2620 } 2621 item.ClearChildren(); 2622 } 2623 2624 bool TreeDelegateItemSelected(TreeItem &item) override { return false; } 2625 2626 protected: 2627 std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; 2628 Debugger &m_debugger; 2629 uint32_t m_stop_id; 2630 FormatEntity::Entry m_format; 2631 }; 2632 2633 class ValueObjectListDelegate : public WindowDelegate { 2634 public: 2635 ValueObjectListDelegate() 2636 : m_rows(), m_selected_row(nullptr), 2637 m_selected_row_idx(0), m_first_visible_row(0), m_num_rows(0), 2638 m_max_x(0), m_max_y(0) {} 2639 2640 ValueObjectListDelegate(ValueObjectList &valobj_list) 2641 : m_rows(), m_selected_row(nullptr), 2642 m_selected_row_idx(0), m_first_visible_row(0), m_num_rows(0), 2643 m_max_x(0), m_max_y(0) { 2644 SetValues(valobj_list); 2645 } 2646 2647 ~ValueObjectListDelegate() override = default; 2648 2649 void SetValues(ValueObjectList &valobj_list) { 2650 m_selected_row = nullptr; 2651 m_selected_row_idx = 0; 2652 m_first_visible_row = 0; 2653 m_num_rows = 0; 2654 m_rows.clear(); 2655 for (auto &valobj_sp : valobj_list.GetObjects()) 2656 m_rows.push_back(Row(valobj_sp, nullptr)); 2657 } 2658 2659 bool WindowDelegateDraw(Window &window, bool force) override { 2660 m_num_rows = 0; 2661 m_min_x = 2; 2662 m_min_y = 1; 2663 m_max_x = window.GetWidth() - 1; 2664 m_max_y = window.GetHeight() - 1; 2665 2666 window.Erase(); 2667 window.DrawTitleBox(window.GetName()); 2668 2669 const int num_visible_rows = NumVisibleRows(); 2670 const int num_rows = CalculateTotalNumberRows(m_rows); 2671 2672 // If we unexpanded while having something selected our total number of 2673 // rows is less than the num visible rows, then make sure we show all the 2674 // rows by setting the first visible row accordingly. 2675 if (m_first_visible_row > 0 && num_rows < num_visible_rows) 2676 m_first_visible_row = 0; 2677 2678 // Make sure the selected row is always visible 2679 if (m_selected_row_idx < m_first_visible_row) 2680 m_first_visible_row = m_selected_row_idx; 2681 else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) 2682 m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; 2683 2684 DisplayRows(window, m_rows, g_options); 2685 2686 window.DeferredRefresh(); 2687 2688 // Get the selected row 2689 m_selected_row = GetRowForRowIndex(m_selected_row_idx); 2690 // Keep the cursor on the selected row so the highlight and the cursor are 2691 // always on the same line 2692 if (m_selected_row) 2693 window.MoveCursor(m_selected_row->x, m_selected_row->y); 2694 2695 return true; // Drawing handled 2696 } 2697 2698 KeyHelp *WindowDelegateGetKeyHelp() override { 2699 static curses::KeyHelp g_source_view_key_help[] = { 2700 {KEY_UP, "Select previous item"}, 2701 {KEY_DOWN, "Select next item"}, 2702 {KEY_RIGHT, "Expand selected item"}, 2703 {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, 2704 {KEY_PPAGE, "Page up"}, 2705 {KEY_NPAGE, "Page down"}, 2706 {'A', "Format as annotated address"}, 2707 {'b', "Format as binary"}, 2708 {'B', "Format as hex bytes with ASCII"}, 2709 {'c', "Format as character"}, 2710 {'d', "Format as a signed integer"}, 2711 {'D', "Format selected value using the default format for the type"}, 2712 {'f', "Format as float"}, 2713 {'h', "Show help dialog"}, 2714 {'i', "Format as instructions"}, 2715 {'o', "Format as octal"}, 2716 {'p', "Format as pointer"}, 2717 {'s', "Format as C string"}, 2718 {'t', "Toggle showing/hiding type names"}, 2719 {'u', "Format as an unsigned integer"}, 2720 {'x', "Format as hex"}, 2721 {'X', "Format as uppercase hex"}, 2722 {' ', "Toggle item expansion"}, 2723 {',', "Page up"}, 2724 {'.', "Page down"}, 2725 {'\0', nullptr}}; 2726 return g_source_view_key_help; 2727 } 2728 2729 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 2730 switch (c) { 2731 case 'x': 2732 case 'X': 2733 case 'o': 2734 case 's': 2735 case 'u': 2736 case 'd': 2737 case 'D': 2738 case 'i': 2739 case 'A': 2740 case 'p': 2741 case 'c': 2742 case 'b': 2743 case 'B': 2744 case 'f': 2745 // Change the format for the currently selected item 2746 if (m_selected_row) { 2747 auto valobj_sp = m_selected_row->value.GetSP(); 2748 if (valobj_sp) 2749 valobj_sp->SetFormat(FormatForChar(c)); 2750 } 2751 return eKeyHandled; 2752 2753 case 't': 2754 // Toggle showing type names 2755 g_options.show_types = !g_options.show_types; 2756 return eKeyHandled; 2757 2758 case ',': 2759 case KEY_PPAGE: 2760 // Page up key 2761 if (m_first_visible_row > 0) { 2762 if (static_cast<int>(m_first_visible_row) > m_max_y) 2763 m_first_visible_row -= m_max_y; 2764 else 2765 m_first_visible_row = 0; 2766 m_selected_row_idx = m_first_visible_row; 2767 } 2768 return eKeyHandled; 2769 2770 case '.': 2771 case KEY_NPAGE: 2772 // Page down key 2773 if (m_num_rows > static_cast<size_t>(m_max_y)) { 2774 if (m_first_visible_row + m_max_y < m_num_rows) { 2775 m_first_visible_row += m_max_y; 2776 m_selected_row_idx = m_first_visible_row; 2777 } 2778 } 2779 return eKeyHandled; 2780 2781 case KEY_UP: 2782 if (m_selected_row_idx > 0) 2783 --m_selected_row_idx; 2784 return eKeyHandled; 2785 2786 case KEY_DOWN: 2787 if (m_selected_row_idx + 1 < m_num_rows) 2788 ++m_selected_row_idx; 2789 return eKeyHandled; 2790 2791 case KEY_RIGHT: 2792 if (m_selected_row) { 2793 if (!m_selected_row->expanded) 2794 m_selected_row->Expand(); 2795 } 2796 return eKeyHandled; 2797 2798 case KEY_LEFT: 2799 if (m_selected_row) { 2800 if (m_selected_row->expanded) 2801 m_selected_row->Unexpand(); 2802 else if (m_selected_row->parent) 2803 m_selected_row_idx = m_selected_row->parent->row_idx; 2804 } 2805 return eKeyHandled; 2806 2807 case ' ': 2808 // Toggle expansion state when SPACE is pressed 2809 if (m_selected_row) { 2810 if (m_selected_row->expanded) 2811 m_selected_row->Unexpand(); 2812 else 2813 m_selected_row->Expand(); 2814 } 2815 return eKeyHandled; 2816 2817 case 'h': 2818 window.CreateHelpSubwindow(); 2819 return eKeyHandled; 2820 2821 default: 2822 break; 2823 } 2824 return eKeyNotHandled; 2825 } 2826 2827 protected: 2828 std::vector<Row> m_rows; 2829 Row *m_selected_row; 2830 uint32_t m_selected_row_idx; 2831 uint32_t m_first_visible_row; 2832 uint32_t m_num_rows; 2833 int m_min_x; 2834 int m_min_y; 2835 int m_max_x; 2836 int m_max_y; 2837 2838 static Format FormatForChar(int c) { 2839 switch (c) { 2840 case 'x': 2841 return eFormatHex; 2842 case 'X': 2843 return eFormatHexUppercase; 2844 case 'o': 2845 return eFormatOctal; 2846 case 's': 2847 return eFormatCString; 2848 case 'u': 2849 return eFormatUnsigned; 2850 case 'd': 2851 return eFormatDecimal; 2852 case 'D': 2853 return eFormatDefault; 2854 case 'i': 2855 return eFormatInstruction; 2856 case 'A': 2857 return eFormatAddressInfo; 2858 case 'p': 2859 return eFormatPointer; 2860 case 'c': 2861 return eFormatChar; 2862 case 'b': 2863 return eFormatBinary; 2864 case 'B': 2865 return eFormatBytesWithASCII; 2866 case 'f': 2867 return eFormatFloat; 2868 } 2869 return eFormatDefault; 2870 } 2871 2872 bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, 2873 bool highlight, bool last_child) { 2874 ValueObject *valobj = row.value.GetSP().get(); 2875 2876 if (valobj == nullptr) 2877 return false; 2878 2879 const char *type_name = 2880 options.show_types ? valobj->GetTypeName().GetCString() : nullptr; 2881 const char *name = valobj->GetName().GetCString(); 2882 const char *value = valobj->GetValueAsCString(); 2883 const char *summary = valobj->GetSummaryAsCString(); 2884 2885 window.MoveCursor(row.x, row.y); 2886 2887 row.DrawTree(window); 2888 2889 if (highlight) 2890 window.AttributeOn(A_REVERSE); 2891 2892 if (type_name && type_name[0]) 2893 window.Printf("(%s) ", type_name); 2894 2895 if (name && name[0]) 2896 window.PutCString(name); 2897 2898 attr_t changd_attr = 0; 2899 if (valobj->GetValueDidChange()) 2900 changd_attr = COLOR_PAIR(5) | A_BOLD; 2901 2902 if (value && value[0]) { 2903 window.PutCString(" = "); 2904 if (changd_attr) 2905 window.AttributeOn(changd_attr); 2906 window.PutCString(value); 2907 if (changd_attr) 2908 window.AttributeOff(changd_attr); 2909 } 2910 2911 if (summary && summary[0]) { 2912 window.PutChar(' '); 2913 if (changd_attr) 2914 window.AttributeOn(changd_attr); 2915 window.PutCString(summary); 2916 if (changd_attr) 2917 window.AttributeOff(changd_attr); 2918 } 2919 2920 if (highlight) 2921 window.AttributeOff(A_REVERSE); 2922 2923 return true; 2924 } 2925 2926 void DisplayRows(Window &window, std::vector<Row> &rows, 2927 DisplayOptions &options) { 2928 // > 0x25B7 2929 // \/ 0x25BD 2930 2931 bool window_is_active = window.IsActive(); 2932 for (auto &row : rows) { 2933 const bool last_child = row.parent && &rows[rows.size() - 1] == &row; 2934 // Save the row index in each Row structure 2935 row.row_idx = m_num_rows; 2936 if ((m_num_rows >= m_first_visible_row) && 2937 ((m_num_rows - m_first_visible_row) < 2938 static_cast<size_t>(NumVisibleRows()))) { 2939 row.x = m_min_x; 2940 row.y = m_num_rows - m_first_visible_row + 1; 2941 if (DisplayRowObject(window, row, options, 2942 window_is_active && 2943 m_num_rows == m_selected_row_idx, 2944 last_child)) { 2945 ++m_num_rows; 2946 } else { 2947 row.x = 0; 2948 row.y = 0; 2949 } 2950 } else { 2951 row.x = 0; 2952 row.y = 0; 2953 ++m_num_rows; 2954 } 2955 2956 auto &children = row.GetChildren(); 2957 if (row.expanded && !children.empty()) { 2958 DisplayRows(window, children, options); 2959 } 2960 } 2961 } 2962 2963 int CalculateTotalNumberRows(std::vector<Row> &rows) { 2964 int row_count = 0; 2965 for (auto &row : rows) { 2966 ++row_count; 2967 if (row.expanded) 2968 row_count += CalculateTotalNumberRows(row.GetChildren()); 2969 } 2970 return row_count; 2971 } 2972 2973 static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { 2974 for (auto &row : rows) { 2975 if (row_index == 0) 2976 return &row; 2977 else { 2978 --row_index; 2979 auto &children = row.GetChildren(); 2980 if (row.expanded && !children.empty()) { 2981 Row *result = GetRowForRowIndexImpl(children, row_index); 2982 if (result) 2983 return result; 2984 } 2985 } 2986 } 2987 return nullptr; 2988 } 2989 2990 Row *GetRowForRowIndex(size_t row_index) { 2991 return GetRowForRowIndexImpl(m_rows, row_index); 2992 } 2993 2994 int NumVisibleRows() const { return m_max_y - m_min_y; } 2995 2996 static DisplayOptions g_options; 2997 }; 2998 2999 class FrameVariablesWindowDelegate : public ValueObjectListDelegate { 3000 public: 3001 FrameVariablesWindowDelegate(Debugger &debugger) 3002 : ValueObjectListDelegate(), m_debugger(debugger), 3003 m_frame_block(nullptr) {} 3004 3005 ~FrameVariablesWindowDelegate() override = default; 3006 3007 const char *WindowDelegateGetHelpText() override { 3008 return "Frame variable window keyboard shortcuts:"; 3009 } 3010 3011 bool WindowDelegateDraw(Window &window, bool force) override { 3012 ExecutionContext exe_ctx( 3013 m_debugger.GetCommandInterpreter().GetExecutionContext()); 3014 Process *process = exe_ctx.GetProcessPtr(); 3015 Block *frame_block = nullptr; 3016 StackFrame *frame = nullptr; 3017 3018 if (process) { 3019 StateType state = process->GetState(); 3020 if (StateIsStoppedState(state, true)) { 3021 frame = exe_ctx.GetFramePtr(); 3022 if (frame) 3023 frame_block = frame->GetFrameBlock(); 3024 } else if (StateIsRunningState(state)) { 3025 return true; // Don't do any updating when we are running 3026 } 3027 } 3028 3029 ValueObjectList local_values; 3030 if (frame_block) { 3031 // Only update the variables if they have changed 3032 if (m_frame_block != frame_block) { 3033 m_frame_block = frame_block; 3034 3035 VariableList *locals = frame->GetVariableList(true); 3036 if (locals) { 3037 const DynamicValueType use_dynamic = eDynamicDontRunTarget; 3038 const size_t num_locals = locals->GetSize(); 3039 for (size_t i = 0; i < num_locals; ++i) { 3040 ValueObjectSP value_sp = frame->GetValueObjectForFrameVariable( 3041 locals->GetVariableAtIndex(i), use_dynamic); 3042 if (value_sp) { 3043 ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); 3044 if (synthetic_value_sp) 3045 local_values.Append(synthetic_value_sp); 3046 else 3047 local_values.Append(value_sp); 3048 } 3049 } 3050 // Update the values 3051 SetValues(local_values); 3052 } 3053 } 3054 } else { 3055 m_frame_block = nullptr; 3056 // Update the values with an empty list if there is no frame 3057 SetValues(local_values); 3058 } 3059 3060 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 3061 } 3062 3063 protected: 3064 Debugger &m_debugger; 3065 Block *m_frame_block; 3066 }; 3067 3068 class RegistersWindowDelegate : public ValueObjectListDelegate { 3069 public: 3070 RegistersWindowDelegate(Debugger &debugger) 3071 : ValueObjectListDelegate(), m_debugger(debugger) {} 3072 3073 ~RegistersWindowDelegate() override = default; 3074 3075 const char *WindowDelegateGetHelpText() override { 3076 return "Register window keyboard shortcuts:"; 3077 } 3078 3079 bool WindowDelegateDraw(Window &window, bool force) override { 3080 ExecutionContext exe_ctx( 3081 m_debugger.GetCommandInterpreter().GetExecutionContext()); 3082 StackFrame *frame = exe_ctx.GetFramePtr(); 3083 3084 ValueObjectList value_list; 3085 if (frame) { 3086 if (frame->GetStackID() != m_stack_id) { 3087 m_stack_id = frame->GetStackID(); 3088 RegisterContextSP reg_ctx(frame->GetRegisterContext()); 3089 if (reg_ctx) { 3090 const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); 3091 for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { 3092 value_list.Append( 3093 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); 3094 } 3095 } 3096 SetValues(value_list); 3097 } 3098 } else { 3099 Process *process = exe_ctx.GetProcessPtr(); 3100 if (process && process->IsAlive()) 3101 return true; // Don't do any updating if we are running 3102 else { 3103 // Update the values with an empty list if there is no process or the 3104 // process isn't alive anymore 3105 SetValues(value_list); 3106 } 3107 } 3108 return ValueObjectListDelegate::WindowDelegateDraw(window, force); 3109 } 3110 3111 protected: 3112 Debugger &m_debugger; 3113 StackID m_stack_id; 3114 }; 3115 3116 static const char *CursesKeyToCString(int ch) { 3117 static char g_desc[32]; 3118 if (ch >= KEY_F0 && ch < KEY_F0 + 64) { 3119 snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); 3120 return g_desc; 3121 } 3122 switch (ch) { 3123 case KEY_DOWN: 3124 return "down"; 3125 case KEY_UP: 3126 return "up"; 3127 case KEY_LEFT: 3128 return "left"; 3129 case KEY_RIGHT: 3130 return "right"; 3131 case KEY_HOME: 3132 return "home"; 3133 case KEY_BACKSPACE: 3134 return "backspace"; 3135 case KEY_DL: 3136 return "delete-line"; 3137 case KEY_IL: 3138 return "insert-line"; 3139 case KEY_DC: 3140 return "delete-char"; 3141 case KEY_IC: 3142 return "insert-char"; 3143 case KEY_CLEAR: 3144 return "clear"; 3145 case KEY_EOS: 3146 return "clear-to-eos"; 3147 case KEY_EOL: 3148 return "clear-to-eol"; 3149 case KEY_SF: 3150 return "scroll-forward"; 3151 case KEY_SR: 3152 return "scroll-backward"; 3153 case KEY_NPAGE: 3154 return "page-down"; 3155 case KEY_PPAGE: 3156 return "page-up"; 3157 case KEY_STAB: 3158 return "set-tab"; 3159 case KEY_CTAB: 3160 return "clear-tab"; 3161 case KEY_CATAB: 3162 return "clear-all-tabs"; 3163 case KEY_ENTER: 3164 return "enter"; 3165 case KEY_PRINT: 3166 return "print"; 3167 case KEY_LL: 3168 return "lower-left key"; 3169 case KEY_A1: 3170 return "upper left of keypad"; 3171 case KEY_A3: 3172 return "upper right of keypad"; 3173 case KEY_B2: 3174 return "center of keypad"; 3175 case KEY_C1: 3176 return "lower left of keypad"; 3177 case KEY_C3: 3178 return "lower right of keypad"; 3179 case KEY_BTAB: 3180 return "back-tab key"; 3181 case KEY_BEG: 3182 return "begin key"; 3183 case KEY_CANCEL: 3184 return "cancel key"; 3185 case KEY_CLOSE: 3186 return "close key"; 3187 case KEY_COMMAND: 3188 return "command key"; 3189 case KEY_COPY: 3190 return "copy key"; 3191 case KEY_CREATE: 3192 return "create key"; 3193 case KEY_END: 3194 return "end key"; 3195 case KEY_EXIT: 3196 return "exit key"; 3197 case KEY_FIND: 3198 return "find key"; 3199 case KEY_HELP: 3200 return "help key"; 3201 case KEY_MARK: 3202 return "mark key"; 3203 case KEY_MESSAGE: 3204 return "message key"; 3205 case KEY_MOVE: 3206 return "move key"; 3207 case KEY_NEXT: 3208 return "next key"; 3209 case KEY_OPEN: 3210 return "open key"; 3211 case KEY_OPTIONS: 3212 return "options key"; 3213 case KEY_PREVIOUS: 3214 return "previous key"; 3215 case KEY_REDO: 3216 return "redo key"; 3217 case KEY_REFERENCE: 3218 return "reference key"; 3219 case KEY_REFRESH: 3220 return "refresh key"; 3221 case KEY_REPLACE: 3222 return "replace key"; 3223 case KEY_RESTART: 3224 return "restart key"; 3225 case KEY_RESUME: 3226 return "resume key"; 3227 case KEY_SAVE: 3228 return "save key"; 3229 case KEY_SBEG: 3230 return "shifted begin key"; 3231 case KEY_SCANCEL: 3232 return "shifted cancel key"; 3233 case KEY_SCOMMAND: 3234 return "shifted command key"; 3235 case KEY_SCOPY: 3236 return "shifted copy key"; 3237 case KEY_SCREATE: 3238 return "shifted create key"; 3239 case KEY_SDC: 3240 return "shifted delete-character key"; 3241 case KEY_SDL: 3242 return "shifted delete-line key"; 3243 case KEY_SELECT: 3244 return "select key"; 3245 case KEY_SEND: 3246 return "shifted end key"; 3247 case KEY_SEOL: 3248 return "shifted clear-to-end-of-line key"; 3249 case KEY_SEXIT: 3250 return "shifted exit key"; 3251 case KEY_SFIND: 3252 return "shifted find key"; 3253 case KEY_SHELP: 3254 return "shifted help key"; 3255 case KEY_SHOME: 3256 return "shifted home key"; 3257 case KEY_SIC: 3258 return "shifted insert-character key"; 3259 case KEY_SLEFT: 3260 return "shifted left-arrow key"; 3261 case KEY_SMESSAGE: 3262 return "shifted message key"; 3263 case KEY_SMOVE: 3264 return "shifted move key"; 3265 case KEY_SNEXT: 3266 return "shifted next key"; 3267 case KEY_SOPTIONS: 3268 return "shifted options key"; 3269 case KEY_SPREVIOUS: 3270 return "shifted previous key"; 3271 case KEY_SPRINT: 3272 return "shifted print key"; 3273 case KEY_SREDO: 3274 return "shifted redo key"; 3275 case KEY_SREPLACE: 3276 return "shifted replace key"; 3277 case KEY_SRIGHT: 3278 return "shifted right-arrow key"; 3279 case KEY_SRSUME: 3280 return "shifted resume key"; 3281 case KEY_SSAVE: 3282 return "shifted save key"; 3283 case KEY_SSUSPEND: 3284 return "shifted suspend key"; 3285 case KEY_SUNDO: 3286 return "shifted undo key"; 3287 case KEY_SUSPEND: 3288 return "suspend key"; 3289 case KEY_UNDO: 3290 return "undo key"; 3291 case KEY_MOUSE: 3292 return "Mouse event has occurred"; 3293 case KEY_RESIZE: 3294 return "Terminal resize event"; 3295 #ifdef KEY_EVENT 3296 case KEY_EVENT: 3297 return "We were interrupted by an event"; 3298 #endif 3299 case KEY_RETURN: 3300 return "return"; 3301 case ' ': 3302 return "space"; 3303 case '\t': 3304 return "tab"; 3305 case KEY_ESCAPE: 3306 return "escape"; 3307 default: 3308 if (isprint(ch)) 3309 snprintf(g_desc, sizeof(g_desc), "%c", ch); 3310 else 3311 snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); 3312 return g_desc; 3313 } 3314 return nullptr; 3315 } 3316 3317 HelpDialogDelegate::HelpDialogDelegate(const char *text, 3318 KeyHelp *key_help_array) 3319 : m_text(), m_first_visible_line(0) { 3320 if (text && text[0]) { 3321 m_text.SplitIntoLines(text); 3322 m_text.AppendString(""); 3323 } 3324 if (key_help_array) { 3325 for (KeyHelp *key = key_help_array; key->ch; ++key) { 3326 StreamString key_description; 3327 key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), 3328 key->description); 3329 m_text.AppendString(key_description.GetString()); 3330 } 3331 } 3332 } 3333 3334 HelpDialogDelegate::~HelpDialogDelegate() = default; 3335 3336 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { 3337 window.Erase(); 3338 const int window_height = window.GetHeight(); 3339 int x = 2; 3340 int y = 1; 3341 const int min_y = y; 3342 const int max_y = window_height - 1 - y; 3343 const size_t num_visible_lines = max_y - min_y + 1; 3344 const size_t num_lines = m_text.GetSize(); 3345 const char *bottom_message; 3346 if (num_lines <= num_visible_lines) 3347 bottom_message = "Press any key to exit"; 3348 else 3349 bottom_message = "Use arrows to scroll, any other key to exit"; 3350 window.DrawTitleBox(window.GetName(), bottom_message); 3351 while (y <= max_y) { 3352 window.MoveCursor(x, y); 3353 window.PutCStringTruncated( 3354 m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1); 3355 ++y; 3356 } 3357 return true; 3358 } 3359 3360 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, 3361 int key) { 3362 bool done = false; 3363 const size_t num_lines = m_text.GetSize(); 3364 const size_t num_visible_lines = window.GetHeight() - 2; 3365 3366 if (num_lines <= num_visible_lines) { 3367 done = true; 3368 // If we have all lines visible and don't need scrolling, then any key 3369 // press will cause us to exit 3370 } else { 3371 switch (key) { 3372 case KEY_UP: 3373 if (m_first_visible_line > 0) 3374 --m_first_visible_line; 3375 break; 3376 3377 case KEY_DOWN: 3378 if (m_first_visible_line + num_visible_lines < num_lines) 3379 ++m_first_visible_line; 3380 break; 3381 3382 case KEY_PPAGE: 3383 case ',': 3384 if (m_first_visible_line > 0) { 3385 if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) 3386 m_first_visible_line -= num_visible_lines; 3387 else 3388 m_first_visible_line = 0; 3389 } 3390 break; 3391 3392 case KEY_NPAGE: 3393 case '.': 3394 if (m_first_visible_line + num_visible_lines < num_lines) { 3395 m_first_visible_line += num_visible_lines; 3396 if (static_cast<size_t>(m_first_visible_line) > num_lines) 3397 m_first_visible_line = num_lines - num_visible_lines; 3398 } 3399 break; 3400 3401 default: 3402 done = true; 3403 break; 3404 } 3405 } 3406 if (done) 3407 window.GetParent()->RemoveSubWindow(&window); 3408 return eKeyHandled; 3409 } 3410 3411 class ApplicationDelegate : public WindowDelegate, public MenuDelegate { 3412 public: 3413 enum { 3414 eMenuID_LLDB = 1, 3415 eMenuID_LLDBAbout, 3416 eMenuID_LLDBExit, 3417 3418 eMenuID_Target, 3419 eMenuID_TargetCreate, 3420 eMenuID_TargetDelete, 3421 3422 eMenuID_Process, 3423 eMenuID_ProcessAttach, 3424 eMenuID_ProcessDetach, 3425 eMenuID_ProcessLaunch, 3426 eMenuID_ProcessContinue, 3427 eMenuID_ProcessHalt, 3428 eMenuID_ProcessKill, 3429 3430 eMenuID_Thread, 3431 eMenuID_ThreadStepIn, 3432 eMenuID_ThreadStepOver, 3433 eMenuID_ThreadStepOut, 3434 3435 eMenuID_View, 3436 eMenuID_ViewBacktrace, 3437 eMenuID_ViewRegisters, 3438 eMenuID_ViewSource, 3439 eMenuID_ViewVariables, 3440 3441 eMenuID_Help, 3442 eMenuID_HelpGUIHelp 3443 }; 3444 3445 ApplicationDelegate(Application &app, Debugger &debugger) 3446 : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} 3447 3448 ~ApplicationDelegate() override = default; 3449 3450 bool WindowDelegateDraw(Window &window, bool force) override { 3451 return false; // Drawing not handled, let standard window drawing happen 3452 } 3453 3454 HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { 3455 switch (key) { 3456 case '\t': 3457 window.SelectNextWindowAsActive(); 3458 return eKeyHandled; 3459 3460 case 'h': 3461 window.CreateHelpSubwindow(); 3462 return eKeyHandled; 3463 3464 case KEY_ESCAPE: 3465 return eQuitApplication; 3466 3467 default: 3468 break; 3469 } 3470 return eKeyNotHandled; 3471 } 3472 3473 const char *WindowDelegateGetHelpText() override { 3474 return "Welcome to the LLDB curses GUI.\n\n" 3475 "Press the TAB key to change the selected view.\n" 3476 "Each view has its own keyboard shortcuts, press 'h' to open a " 3477 "dialog to display them.\n\n" 3478 "Common key bindings for all views:"; 3479 } 3480 3481 KeyHelp *WindowDelegateGetKeyHelp() override { 3482 static curses::KeyHelp g_source_view_key_help[] = { 3483 {'\t', "Select next view"}, 3484 {'h', "Show help dialog with view specific key bindings"}, 3485 {',', "Page up"}, 3486 {'.', "Page down"}, 3487 {KEY_UP, "Select previous"}, 3488 {KEY_DOWN, "Select next"}, 3489 {KEY_LEFT, "Unexpand or select parent"}, 3490 {KEY_RIGHT, "Expand"}, 3491 {KEY_PPAGE, "Page up"}, 3492 {KEY_NPAGE, "Page down"}, 3493 {'\0', nullptr}}; 3494 return g_source_view_key_help; 3495 } 3496 3497 MenuActionResult MenuDelegateAction(Menu &menu) override { 3498 switch (menu.GetIdentifier()) { 3499 case eMenuID_ThreadStepIn: { 3500 ExecutionContext exe_ctx = 3501 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3502 if (exe_ctx.HasThreadScope()) { 3503 Process *process = exe_ctx.GetProcessPtr(); 3504 if (process && process->IsAlive() && 3505 StateIsStoppedState(process->GetState(), true)) 3506 exe_ctx.GetThreadRef().StepIn(true); 3507 } 3508 } 3509 return MenuActionResult::Handled; 3510 3511 case eMenuID_ThreadStepOut: { 3512 ExecutionContext exe_ctx = 3513 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3514 if (exe_ctx.HasThreadScope()) { 3515 Process *process = exe_ctx.GetProcessPtr(); 3516 if (process && process->IsAlive() && 3517 StateIsStoppedState(process->GetState(), true)) 3518 exe_ctx.GetThreadRef().StepOut(); 3519 } 3520 } 3521 return MenuActionResult::Handled; 3522 3523 case eMenuID_ThreadStepOver: { 3524 ExecutionContext exe_ctx = 3525 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3526 if (exe_ctx.HasThreadScope()) { 3527 Process *process = exe_ctx.GetProcessPtr(); 3528 if (process && process->IsAlive() && 3529 StateIsStoppedState(process->GetState(), true)) 3530 exe_ctx.GetThreadRef().StepOver(true); 3531 } 3532 } 3533 return MenuActionResult::Handled; 3534 3535 case eMenuID_ProcessContinue: { 3536 ExecutionContext exe_ctx = 3537 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3538 if (exe_ctx.HasProcessScope()) { 3539 Process *process = exe_ctx.GetProcessPtr(); 3540 if (process && process->IsAlive() && 3541 StateIsStoppedState(process->GetState(), true)) 3542 process->Resume(); 3543 } 3544 } 3545 return MenuActionResult::Handled; 3546 3547 case eMenuID_ProcessKill: { 3548 ExecutionContext exe_ctx = 3549 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3550 if (exe_ctx.HasProcessScope()) { 3551 Process *process = exe_ctx.GetProcessPtr(); 3552 if (process && process->IsAlive()) 3553 process->Destroy(false); 3554 } 3555 } 3556 return MenuActionResult::Handled; 3557 3558 case eMenuID_ProcessHalt: { 3559 ExecutionContext exe_ctx = 3560 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3561 if (exe_ctx.HasProcessScope()) { 3562 Process *process = exe_ctx.GetProcessPtr(); 3563 if (process && process->IsAlive()) 3564 process->Halt(); 3565 } 3566 } 3567 return MenuActionResult::Handled; 3568 3569 case eMenuID_ProcessDetach: { 3570 ExecutionContext exe_ctx = 3571 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3572 if (exe_ctx.HasProcessScope()) { 3573 Process *process = exe_ctx.GetProcessPtr(); 3574 if (process && process->IsAlive()) 3575 process->Detach(false); 3576 } 3577 } 3578 return MenuActionResult::Handled; 3579 3580 case eMenuID_Process: { 3581 // Populate the menu with all of the threads if the process is stopped 3582 // when the Process menu gets selected and is about to display its 3583 // submenu. 3584 Menus &submenus = menu.GetSubmenus(); 3585 ExecutionContext exe_ctx = 3586 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3587 Process *process = exe_ctx.GetProcessPtr(); 3588 if (process && process->IsAlive() && 3589 StateIsStoppedState(process->GetState(), true)) { 3590 if (submenus.size() == 7) 3591 menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 3592 else if (submenus.size() > 8) 3593 submenus.erase(submenus.begin() + 8, submenus.end()); 3594 3595 ThreadList &threads = process->GetThreadList(); 3596 std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); 3597 size_t num_threads = threads.GetSize(); 3598 for (size_t i = 0; i < num_threads; ++i) { 3599 ThreadSP thread_sp = threads.GetThreadAtIndex(i); 3600 char menu_char = '\0'; 3601 if (i < 9) 3602 menu_char = '1' + i; 3603 StreamString thread_menu_title; 3604 thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); 3605 const char *thread_name = thread_sp->GetName(); 3606 if (thread_name && thread_name[0]) 3607 thread_menu_title.Printf(" %s", thread_name); 3608 else { 3609 const char *queue_name = thread_sp->GetQueueName(); 3610 if (queue_name && queue_name[0]) 3611 thread_menu_title.Printf(" %s", queue_name); 3612 } 3613 menu.AddSubmenu( 3614 MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), 3615 nullptr, menu_char, thread_sp->GetID()))); 3616 } 3617 } else if (submenus.size() > 7) { 3618 // Remove the separator and any other thread submenu items that were 3619 // previously added 3620 submenus.erase(submenus.begin() + 7, submenus.end()); 3621 } 3622 // Since we are adding and removing items we need to recalculate the name 3623 // lengths 3624 menu.RecalculateNameLengths(); 3625 } 3626 return MenuActionResult::Handled; 3627 3628 case eMenuID_ViewVariables: { 3629 WindowSP main_window_sp = m_app.GetMainWindow(); 3630 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3631 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3632 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3633 const Rect source_bounds = source_window_sp->GetBounds(); 3634 3635 if (variables_window_sp) { 3636 const Rect variables_bounds = variables_window_sp->GetBounds(); 3637 3638 main_window_sp->RemoveSubWindow(variables_window_sp.get()); 3639 3640 if (registers_window_sp) { 3641 // We have a registers window, so give all the area back to the 3642 // registers window 3643 Rect registers_bounds = variables_bounds; 3644 registers_bounds.size.width = source_bounds.size.width; 3645 registers_window_sp->SetBounds(registers_bounds); 3646 } else { 3647 // We have no registers window showing so give the bottom area back 3648 // to the source view 3649 source_window_sp->Resize(source_bounds.size.width, 3650 source_bounds.size.height + 3651 variables_bounds.size.height); 3652 } 3653 } else { 3654 Rect new_variables_rect; 3655 if (registers_window_sp) { 3656 // We have a registers window so split the area of the registers 3657 // window into two columns where the left hand side will be the 3658 // variables and the right hand side will be the registers 3659 const Rect variables_bounds = registers_window_sp->GetBounds(); 3660 Rect new_registers_rect; 3661 variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, 3662 new_registers_rect); 3663 registers_window_sp->SetBounds(new_registers_rect); 3664 } else { 3665 // No variables window, grab the bottom part of the source window 3666 Rect new_source_rect; 3667 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3668 new_variables_rect); 3669 source_window_sp->SetBounds(new_source_rect); 3670 } 3671 WindowSP new_window_sp = main_window_sp->CreateSubWindow( 3672 "Variables", new_variables_rect, false); 3673 new_window_sp->SetDelegate( 3674 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 3675 } 3676 touchwin(stdscr); 3677 } 3678 return MenuActionResult::Handled; 3679 3680 case eMenuID_ViewRegisters: { 3681 WindowSP main_window_sp = m_app.GetMainWindow(); 3682 WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); 3683 WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); 3684 WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); 3685 const Rect source_bounds = source_window_sp->GetBounds(); 3686 3687 if (registers_window_sp) { 3688 if (variables_window_sp) { 3689 const Rect variables_bounds = variables_window_sp->GetBounds(); 3690 3691 // We have a variables window, so give all the area back to the 3692 // variables window 3693 variables_window_sp->Resize(variables_bounds.size.width + 3694 registers_window_sp->GetWidth(), 3695 variables_bounds.size.height); 3696 } else { 3697 // We have no variables window showing so give the bottom area back 3698 // to the source view 3699 source_window_sp->Resize(source_bounds.size.width, 3700 source_bounds.size.height + 3701 registers_window_sp->GetHeight()); 3702 } 3703 main_window_sp->RemoveSubWindow(registers_window_sp.get()); 3704 } else { 3705 Rect new_regs_rect; 3706 if (variables_window_sp) { 3707 // We have a variables window, split it into two columns where the 3708 // left hand side will be the variables and the right hand side will 3709 // be the registers 3710 const Rect variables_bounds = variables_window_sp->GetBounds(); 3711 Rect new_vars_rect; 3712 variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, 3713 new_regs_rect); 3714 variables_window_sp->SetBounds(new_vars_rect); 3715 } else { 3716 // No registers window, grab the bottom part of the source window 3717 Rect new_source_rect; 3718 source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, 3719 new_regs_rect); 3720 source_window_sp->SetBounds(new_source_rect); 3721 } 3722 WindowSP new_window_sp = 3723 main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); 3724 new_window_sp->SetDelegate( 3725 WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); 3726 } 3727 touchwin(stdscr); 3728 } 3729 return MenuActionResult::Handled; 3730 3731 case eMenuID_HelpGUIHelp: 3732 m_app.GetMainWindow()->CreateHelpSubwindow(); 3733 return MenuActionResult::Handled; 3734 3735 default: 3736 break; 3737 } 3738 3739 return MenuActionResult::NotHandled; 3740 } 3741 3742 protected: 3743 Application &m_app; 3744 Debugger &m_debugger; 3745 }; 3746 3747 class StatusBarWindowDelegate : public WindowDelegate { 3748 public: 3749 StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { 3750 FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); 3751 } 3752 3753 ~StatusBarWindowDelegate() override = default; 3754 3755 bool WindowDelegateDraw(Window &window, bool force) override { 3756 ExecutionContext exe_ctx = 3757 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3758 Process *process = exe_ctx.GetProcessPtr(); 3759 Thread *thread = exe_ctx.GetThreadPtr(); 3760 StackFrame *frame = exe_ctx.GetFramePtr(); 3761 window.Erase(); 3762 window.SetBackground(2); 3763 window.MoveCursor(0, 0); 3764 if (process) { 3765 const StateType state = process->GetState(); 3766 window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), 3767 StateAsCString(state)); 3768 3769 if (StateIsStoppedState(state, true)) { 3770 StreamString strm; 3771 if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, 3772 nullptr, nullptr, false, false)) { 3773 window.MoveCursor(40, 0); 3774 window.PutCStringTruncated(strm.GetString().str().c_str(), 1); 3775 } 3776 3777 window.MoveCursor(60, 0); 3778 if (frame) 3779 window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, 3780 frame->GetFrameIndex(), 3781 frame->GetFrameCodeAddress().GetOpcodeLoadAddress( 3782 exe_ctx.GetTargetPtr())); 3783 } else if (state == eStateExited) { 3784 const char *exit_desc = process->GetExitDescription(); 3785 const int exit_status = process->GetExitStatus(); 3786 if (exit_desc && exit_desc[0]) 3787 window.Printf(" with status = %i (%s)", exit_status, exit_desc); 3788 else 3789 window.Printf(" with status = %i", exit_status); 3790 } 3791 } 3792 window.DeferredRefresh(); 3793 return true; 3794 } 3795 3796 protected: 3797 Debugger &m_debugger; 3798 FormatEntity::Entry m_format; 3799 }; 3800 3801 class SourceFileWindowDelegate : public WindowDelegate { 3802 public: 3803 SourceFileWindowDelegate(Debugger &debugger) 3804 : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), 3805 m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(), 3806 m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0), 3807 m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0), 3808 m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {} 3809 3810 ~SourceFileWindowDelegate() override = default; 3811 3812 void Update(const SymbolContext &sc) { m_sc = sc; } 3813 3814 uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } 3815 3816 const char *WindowDelegateGetHelpText() override { 3817 return "Source/Disassembly window keyboard shortcuts:"; 3818 } 3819 3820 KeyHelp *WindowDelegateGetKeyHelp() override { 3821 static curses::KeyHelp g_source_view_key_help[] = { 3822 {KEY_RETURN, "Run to selected line with one shot breakpoint"}, 3823 {KEY_UP, "Select previous source line"}, 3824 {KEY_DOWN, "Select next source line"}, 3825 {KEY_PPAGE, "Page up"}, 3826 {KEY_NPAGE, "Page down"}, 3827 {'b', "Set breakpoint on selected source/disassembly line"}, 3828 {'c', "Continue process"}, 3829 {'d', "Detach and resume process"}, 3830 {'D', "Detach with process suspended"}, 3831 {'h', "Show help dialog"}, 3832 {'k', "Kill process"}, 3833 {'n', "Step over (source line)"}, 3834 {'N', "Step over (single instruction)"}, 3835 {'o', "Step out"}, 3836 {'s', "Step in (source line)"}, 3837 {'S', "Step in (single instruction)"}, 3838 {',', "Page up"}, 3839 {'.', "Page down"}, 3840 {'\0', nullptr}}; 3841 return g_source_view_key_help; 3842 } 3843 3844 bool WindowDelegateDraw(Window &window, bool force) override { 3845 ExecutionContext exe_ctx = 3846 m_debugger.GetCommandInterpreter().GetExecutionContext(); 3847 Process *process = exe_ctx.GetProcessPtr(); 3848 Thread *thread = nullptr; 3849 3850 bool update_location = false; 3851 if (process) { 3852 StateType state = process->GetState(); 3853 if (StateIsStoppedState(state, true)) { 3854 // We are stopped, so it is ok to 3855 update_location = true; 3856 } 3857 } 3858 3859 m_min_x = 1; 3860 m_min_y = 2; 3861 m_max_x = window.GetMaxX() - 1; 3862 m_max_y = window.GetMaxY() - 1; 3863 3864 const uint32_t num_visible_lines = NumVisibleLines(); 3865 StackFrameSP frame_sp; 3866 bool set_selected_line_to_pc = false; 3867 3868 if (update_location) { 3869 const bool process_alive = process ? process->IsAlive() : false; 3870 bool thread_changed = false; 3871 if (process_alive) { 3872 thread = exe_ctx.GetThreadPtr(); 3873 if (thread) { 3874 frame_sp = thread->GetSelectedFrame(); 3875 auto tid = thread->GetID(); 3876 thread_changed = tid != m_tid; 3877 m_tid = tid; 3878 } else { 3879 if (m_tid != LLDB_INVALID_THREAD_ID) { 3880 thread_changed = true; 3881 m_tid = LLDB_INVALID_THREAD_ID; 3882 } 3883 } 3884 } 3885 const uint32_t stop_id = process ? process->GetStopID() : 0; 3886 const bool stop_id_changed = stop_id != m_stop_id; 3887 bool frame_changed = false; 3888 m_stop_id = stop_id; 3889 m_title.Clear(); 3890 if (frame_sp) { 3891 m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); 3892 if (m_sc.module_sp) { 3893 m_title.Printf( 3894 "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); 3895 ConstString func_name = m_sc.GetFunctionName(); 3896 if (func_name) 3897 m_title.Printf("`%s", func_name.GetCString()); 3898 } 3899 const uint32_t frame_idx = frame_sp->GetFrameIndex(); 3900 frame_changed = frame_idx != m_frame_idx; 3901 m_frame_idx = frame_idx; 3902 } else { 3903 m_sc.Clear(true); 3904 frame_changed = m_frame_idx != UINT32_MAX; 3905 m_frame_idx = UINT32_MAX; 3906 } 3907 3908 const bool context_changed = 3909 thread_changed || frame_changed || stop_id_changed; 3910 3911 if (process_alive) { 3912 if (m_sc.line_entry.IsValid()) { 3913 m_pc_line = m_sc.line_entry.line; 3914 if (m_pc_line != UINT32_MAX) 3915 --m_pc_line; // Convert to zero based line number... 3916 // Update the selected line if the stop ID changed... 3917 if (context_changed) 3918 m_selected_line = m_pc_line; 3919 3920 if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) { 3921 // Same file, nothing to do, we should either have the lines or not 3922 // (source file missing) 3923 if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { 3924 if (m_selected_line >= m_first_visible_line + num_visible_lines) 3925 m_first_visible_line = m_selected_line - 10; 3926 } else { 3927 if (m_selected_line > 10) 3928 m_first_visible_line = m_selected_line - 10; 3929 else 3930 m_first_visible_line = 0; 3931 } 3932 } else { 3933 // File changed, set selected line to the line with the PC 3934 m_selected_line = m_pc_line; 3935 m_file_sp = 3936 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file); 3937 if (m_file_sp) { 3938 const size_t num_lines = m_file_sp->GetNumLines(); 3939 m_line_width = 1; 3940 for (size_t n = num_lines; n >= 10; n = n / 10) 3941 ++m_line_width; 3942 3943 if (num_lines < num_visible_lines || 3944 m_selected_line < num_visible_lines) 3945 m_first_visible_line = 0; 3946 else 3947 m_first_visible_line = m_selected_line - 10; 3948 } 3949 } 3950 } else { 3951 m_file_sp.reset(); 3952 } 3953 3954 if (!m_file_sp || m_file_sp->GetNumLines() == 0) { 3955 // Show disassembly 3956 bool prefer_file_cache = false; 3957 if (m_sc.function) { 3958 if (m_disassembly_scope != m_sc.function) { 3959 m_disassembly_scope = m_sc.function; 3960 m_disassembly_sp = m_sc.function->GetInstructions( 3961 exe_ctx, nullptr, prefer_file_cache); 3962 if (m_disassembly_sp) { 3963 set_selected_line_to_pc = true; 3964 m_disassembly_range = m_sc.function->GetAddressRange(); 3965 } else { 3966 m_disassembly_range.Clear(); 3967 } 3968 } else { 3969 set_selected_line_to_pc = context_changed; 3970 } 3971 } else if (m_sc.symbol) { 3972 if (m_disassembly_scope != m_sc.symbol) { 3973 m_disassembly_scope = m_sc.symbol; 3974 m_disassembly_sp = m_sc.symbol->GetInstructions( 3975 exe_ctx, nullptr, prefer_file_cache); 3976 if (m_disassembly_sp) { 3977 set_selected_line_to_pc = true; 3978 m_disassembly_range.GetBaseAddress() = 3979 m_sc.symbol->GetAddress(); 3980 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); 3981 } else { 3982 m_disassembly_range.Clear(); 3983 } 3984 } else { 3985 set_selected_line_to_pc = context_changed; 3986 } 3987 } 3988 } 3989 } else { 3990 m_pc_line = UINT32_MAX; 3991 } 3992 } 3993 3994 const int window_width = window.GetWidth(); 3995 window.Erase(); 3996 window.DrawTitleBox("Sources"); 3997 if (!m_title.GetString().empty()) { 3998 window.AttributeOn(A_REVERSE); 3999 window.MoveCursor(1, 1); 4000 window.PutChar(' '); 4001 window.PutCStringTruncated(m_title.GetString().str().c_str(), 1); 4002 int x = window.GetCursorX(); 4003 if (x < window_width - 1) { 4004 window.Printf("%*s", window_width - x - 1, ""); 4005 } 4006 window.AttributeOff(A_REVERSE); 4007 } 4008 4009 Target *target = exe_ctx.GetTargetPtr(); 4010 const size_t num_source_lines = GetNumSourceLines(); 4011 if (num_source_lines > 0) { 4012 // Display source 4013 BreakpointLines bp_lines; 4014 if (target) { 4015 BreakpointList &bp_list = target->GetBreakpointList(); 4016 const size_t num_bps = bp_list.GetSize(); 4017 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4018 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4019 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4020 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { 4021 BreakpointLocationSP bp_loc_sp = 4022 bp_sp->GetLocationAtIndex(bp_loc_idx); 4023 LineEntry bp_loc_line_entry; 4024 if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( 4025 bp_loc_line_entry)) { 4026 if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) { 4027 bp_lines.insert(bp_loc_line_entry.line); 4028 } 4029 } 4030 } 4031 } 4032 } 4033 4034 const attr_t selected_highlight_attr = A_REVERSE; 4035 const attr_t pc_highlight_attr = COLOR_PAIR(1); 4036 4037 for (size_t i = 0; i < num_visible_lines; ++i) { 4038 const uint32_t curr_line = m_first_visible_line + i; 4039 if (curr_line < num_source_lines) { 4040 const int line_y = m_min_y + i; 4041 window.MoveCursor(1, line_y); 4042 const bool is_pc_line = curr_line == m_pc_line; 4043 const bool line_is_selected = m_selected_line == curr_line; 4044 // Highlight the line as the PC line first, then if the selected line 4045 // isn't the same as the PC line, highlight it differently 4046 attr_t highlight_attr = 0; 4047 attr_t bp_attr = 0; 4048 if (is_pc_line) 4049 highlight_attr = pc_highlight_attr; 4050 else if (line_is_selected) 4051 highlight_attr = selected_highlight_attr; 4052 4053 if (bp_lines.find(curr_line + 1) != bp_lines.end()) 4054 bp_attr = COLOR_PAIR(2); 4055 4056 if (bp_attr) 4057 window.AttributeOn(bp_attr); 4058 4059 window.Printf(" %*u ", m_line_width, curr_line + 1); 4060 4061 if (bp_attr) 4062 window.AttributeOff(bp_attr); 4063 4064 window.PutChar(ACS_VLINE); 4065 // Mark the line with the PC with a diamond 4066 if (is_pc_line) 4067 window.PutChar(ACS_DIAMOND); 4068 else 4069 window.PutChar(' '); 4070 4071 if (highlight_attr) 4072 window.AttributeOn(highlight_attr); 4073 const uint32_t line_len = 4074 m_file_sp->GetLineLength(curr_line + 1, false); 4075 if (line_len > 0) 4076 window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len); 4077 4078 if (is_pc_line && frame_sp && 4079 frame_sp->GetConcreteFrameIndex() == 0) { 4080 StopInfoSP stop_info_sp; 4081 if (thread) 4082 stop_info_sp = thread->GetStopInfo(); 4083 if (stop_info_sp) { 4084 const char *stop_description = stop_info_sp->GetDescription(); 4085 if (stop_description && stop_description[0]) { 4086 size_t stop_description_len = strlen(stop_description); 4087 int desc_x = window_width - stop_description_len - 16; 4088 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 4089 // window.MoveCursor(window_width - stop_description_len - 15, 4090 // line_y); 4091 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(), 4092 stop_description); 4093 } 4094 } else { 4095 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 4096 } 4097 } 4098 if (highlight_attr) 4099 window.AttributeOff(highlight_attr); 4100 } else { 4101 break; 4102 } 4103 } 4104 } else { 4105 size_t num_disassembly_lines = GetNumDisassemblyLines(); 4106 if (num_disassembly_lines > 0) { 4107 // Display disassembly 4108 BreakpointAddrs bp_file_addrs; 4109 Target *target = exe_ctx.GetTargetPtr(); 4110 if (target) { 4111 BreakpointList &bp_list = target->GetBreakpointList(); 4112 const size_t num_bps = bp_list.GetSize(); 4113 for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { 4114 BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); 4115 const size_t num_bps_locs = bp_sp->GetNumLocations(); 4116 for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; 4117 ++bp_loc_idx) { 4118 BreakpointLocationSP bp_loc_sp = 4119 bp_sp->GetLocationAtIndex(bp_loc_idx); 4120 LineEntry bp_loc_line_entry; 4121 const lldb::addr_t file_addr = 4122 bp_loc_sp->GetAddress().GetFileAddress(); 4123 if (file_addr != LLDB_INVALID_ADDRESS) { 4124 if (m_disassembly_range.ContainsFileAddress(file_addr)) 4125 bp_file_addrs.insert(file_addr); 4126 } 4127 } 4128 } 4129 } 4130 4131 const attr_t selected_highlight_attr = A_REVERSE; 4132 const attr_t pc_highlight_attr = COLOR_PAIR(1); 4133 4134 StreamString strm; 4135 4136 InstructionList &insts = m_disassembly_sp->GetInstructionList(); 4137 Address pc_address; 4138 4139 if (frame_sp) 4140 pc_address = frame_sp->GetFrameCodeAddress(); 4141 const uint32_t pc_idx = 4142 pc_address.IsValid() 4143 ? insts.GetIndexOfInstructionAtAddress(pc_address) 4144 : UINT32_MAX; 4145 if (set_selected_line_to_pc) { 4146 m_selected_line = pc_idx; 4147 } 4148 4149 const uint32_t non_visible_pc_offset = (num_visible_lines / 5); 4150 if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) 4151 m_first_visible_line = 0; 4152 4153 if (pc_idx < num_disassembly_lines) { 4154 if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || 4155 pc_idx >= m_first_visible_line + num_visible_lines) 4156 m_first_visible_line = pc_idx - non_visible_pc_offset; 4157 } 4158 4159 for (size_t i = 0; i < num_visible_lines; ++i) { 4160 const uint32_t inst_idx = m_first_visible_line + i; 4161 Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); 4162 if (!inst) 4163 break; 4164 4165 const int line_y = m_min_y + i; 4166 window.MoveCursor(1, line_y); 4167 const bool is_pc_line = frame_sp && inst_idx == pc_idx; 4168 const bool line_is_selected = m_selected_line == inst_idx; 4169 // Highlight the line as the PC line first, then if the selected line 4170 // isn't the same as the PC line, highlight it differently 4171 attr_t highlight_attr = 0; 4172 attr_t bp_attr = 0; 4173 if (is_pc_line) 4174 highlight_attr = pc_highlight_attr; 4175 else if (line_is_selected) 4176 highlight_attr = selected_highlight_attr; 4177 4178 if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != 4179 bp_file_addrs.end()) 4180 bp_attr = COLOR_PAIR(2); 4181 4182 if (bp_attr) 4183 window.AttributeOn(bp_attr); 4184 4185 window.Printf(" 0x%16.16llx ", 4186 static_cast<unsigned long long>( 4187 inst->GetAddress().GetLoadAddress(target))); 4188 4189 if (bp_attr) 4190 window.AttributeOff(bp_attr); 4191 4192 window.PutChar(ACS_VLINE); 4193 // Mark the line with the PC with a diamond 4194 if (is_pc_line) 4195 window.PutChar(ACS_DIAMOND); 4196 else 4197 window.PutChar(' '); 4198 4199 if (highlight_attr) 4200 window.AttributeOn(highlight_attr); 4201 4202 const char *mnemonic = inst->GetMnemonic(&exe_ctx); 4203 const char *operands = inst->GetOperands(&exe_ctx); 4204 const char *comment = inst->GetComment(&exe_ctx); 4205 4206 if (mnemonic != nullptr && mnemonic[0] == '\0') 4207 mnemonic = nullptr; 4208 if (operands != nullptr && operands[0] == '\0') 4209 operands = nullptr; 4210 if (comment != nullptr && comment[0] == '\0') 4211 comment = nullptr; 4212 4213 strm.Clear(); 4214 4215 if (mnemonic != nullptr && operands != nullptr && comment != nullptr) 4216 strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); 4217 else if (mnemonic != nullptr && operands != nullptr) 4218 strm.Printf("%-8s %s", mnemonic, operands); 4219 else if (mnemonic != nullptr) 4220 strm.Printf("%s", mnemonic); 4221 4222 int right_pad = 1; 4223 window.PutCStringTruncated(strm.GetData(), right_pad); 4224 4225 if (is_pc_line && frame_sp && 4226 frame_sp->GetConcreteFrameIndex() == 0) { 4227 StopInfoSP stop_info_sp; 4228 if (thread) 4229 stop_info_sp = thread->GetStopInfo(); 4230 if (stop_info_sp) { 4231 const char *stop_description = stop_info_sp->GetDescription(); 4232 if (stop_description && stop_description[0]) { 4233 size_t stop_description_len = strlen(stop_description); 4234 int desc_x = window_width - stop_description_len - 16; 4235 window.Printf("%*s", desc_x - window.GetCursorX(), ""); 4236 // window.MoveCursor(window_width - stop_description_len - 15, 4237 // line_y); 4238 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(), 4239 stop_description); 4240 } 4241 } else { 4242 window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); 4243 } 4244 } 4245 if (highlight_attr) 4246 window.AttributeOff(highlight_attr); 4247 } 4248 } 4249 } 4250 window.DeferredRefresh(); 4251 return true; // Drawing handled 4252 } 4253 4254 size_t GetNumLines() { 4255 size_t num_lines = GetNumSourceLines(); 4256 if (num_lines == 0) 4257 num_lines = GetNumDisassemblyLines(); 4258 return num_lines; 4259 } 4260 4261 size_t GetNumSourceLines() const { 4262 if (m_file_sp) 4263 return m_file_sp->GetNumLines(); 4264 return 0; 4265 } 4266 4267 size_t GetNumDisassemblyLines() const { 4268 if (m_disassembly_sp) 4269 return m_disassembly_sp->GetInstructionList().GetSize(); 4270 return 0; 4271 } 4272 4273 HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { 4274 const uint32_t num_visible_lines = NumVisibleLines(); 4275 const size_t num_lines = GetNumLines(); 4276 4277 switch (c) { 4278 case ',': 4279 case KEY_PPAGE: 4280 // Page up key 4281 if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) 4282 m_first_visible_line -= num_visible_lines; 4283 else 4284 m_first_visible_line = 0; 4285 m_selected_line = m_first_visible_line; 4286 return eKeyHandled; 4287 4288 case '.': 4289 case KEY_NPAGE: 4290 // Page down key 4291 { 4292 if (m_first_visible_line + num_visible_lines < num_lines) 4293 m_first_visible_line += num_visible_lines; 4294 else if (num_lines < num_visible_lines) 4295 m_first_visible_line = 0; 4296 else 4297 m_first_visible_line = num_lines - num_visible_lines; 4298 m_selected_line = m_first_visible_line; 4299 } 4300 return eKeyHandled; 4301 4302 case KEY_UP: 4303 if (m_selected_line > 0) { 4304 m_selected_line--; 4305 if (static_cast<size_t>(m_first_visible_line) > m_selected_line) 4306 m_first_visible_line = m_selected_line; 4307 } 4308 return eKeyHandled; 4309 4310 case KEY_DOWN: 4311 if (m_selected_line + 1 < num_lines) { 4312 m_selected_line++; 4313 if (m_first_visible_line + num_visible_lines < m_selected_line) 4314 m_first_visible_line++; 4315 } 4316 return eKeyHandled; 4317 4318 case '\r': 4319 case '\n': 4320 case KEY_ENTER: 4321 // Set a breakpoint and run to the line using a one shot breakpoint 4322 if (GetNumSourceLines() > 0) { 4323 ExecutionContext exe_ctx = 4324 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4325 if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { 4326 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4327 nullptr, // Don't limit the breakpoint to certain modules 4328 m_file_sp->GetFileSpec(), // Source file 4329 m_selected_line + 4330 1, // Source line number (m_selected_line is zero based) 4331 0, // Unspecified column. 4332 0, // No offset 4333 eLazyBoolCalculate, // Check inlines using global setting 4334 eLazyBoolCalculate, // Skip prologue using global setting, 4335 false, // internal 4336 false, // request_hardware 4337 eLazyBoolCalculate); // move_to_nearest_code 4338 // Make breakpoint one shot 4339 bp_sp->GetOptions()->SetOneShot(true); 4340 exe_ctx.GetProcessRef().Resume(); 4341 } 4342 } else if (m_selected_line < GetNumDisassemblyLines()) { 4343 const Instruction *inst = m_disassembly_sp->GetInstructionList() 4344 .GetInstructionAtIndex(m_selected_line) 4345 .get(); 4346 ExecutionContext exe_ctx = 4347 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4348 if (exe_ctx.HasTargetScope()) { 4349 Address addr = inst->GetAddress(); 4350 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4351 addr, // lldb_private::Address 4352 false, // internal 4353 false); // request_hardware 4354 // Make breakpoint one shot 4355 bp_sp->GetOptions()->SetOneShot(true); 4356 exe_ctx.GetProcessRef().Resume(); 4357 } 4358 } 4359 return eKeyHandled; 4360 4361 case 'b': // 'b' == toggle breakpoint on currently selected line 4362 if (m_selected_line < GetNumSourceLines()) { 4363 ExecutionContext exe_ctx = 4364 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4365 if (exe_ctx.HasTargetScope()) { 4366 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4367 nullptr, // Don't limit the breakpoint to certain modules 4368 m_file_sp->GetFileSpec(), // Source file 4369 m_selected_line + 4370 1, // Source line number (m_selected_line is zero based) 4371 0, // No column specified. 4372 0, // No offset 4373 eLazyBoolCalculate, // Check inlines using global setting 4374 eLazyBoolCalculate, // Skip prologue using global setting, 4375 false, // internal 4376 false, // request_hardware 4377 eLazyBoolCalculate); // move_to_nearest_code 4378 } 4379 } else if (m_selected_line < GetNumDisassemblyLines()) { 4380 const Instruction *inst = m_disassembly_sp->GetInstructionList() 4381 .GetInstructionAtIndex(m_selected_line) 4382 .get(); 4383 ExecutionContext exe_ctx = 4384 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4385 if (exe_ctx.HasTargetScope()) { 4386 Address addr = inst->GetAddress(); 4387 BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( 4388 addr, // lldb_private::Address 4389 false, // internal 4390 false); // request_hardware 4391 } 4392 } 4393 return eKeyHandled; 4394 4395 case 'd': // 'd' == detach and let run 4396 case 'D': // 'D' == detach and keep stopped 4397 { 4398 ExecutionContext exe_ctx = 4399 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4400 if (exe_ctx.HasProcessScope()) 4401 exe_ctx.GetProcessRef().Detach(c == 'D'); 4402 } 4403 return eKeyHandled; 4404 4405 case 'k': 4406 // 'k' == kill 4407 { 4408 ExecutionContext exe_ctx = 4409 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4410 if (exe_ctx.HasProcessScope()) 4411 exe_ctx.GetProcessRef().Destroy(false); 4412 } 4413 return eKeyHandled; 4414 4415 case 'c': 4416 // 'c' == continue 4417 { 4418 ExecutionContext exe_ctx = 4419 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4420 if (exe_ctx.HasProcessScope()) 4421 exe_ctx.GetProcessRef().Resume(); 4422 } 4423 return eKeyHandled; 4424 4425 case 'o': 4426 // 'o' == step out 4427 { 4428 ExecutionContext exe_ctx = 4429 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4430 if (exe_ctx.HasThreadScope() && 4431 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4432 exe_ctx.GetThreadRef().StepOut(); 4433 } 4434 } 4435 return eKeyHandled; 4436 4437 case 'n': // 'n' == step over 4438 case 'N': // 'N' == step over instruction 4439 { 4440 ExecutionContext exe_ctx = 4441 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4442 if (exe_ctx.HasThreadScope() && 4443 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4444 bool source_step = (c == 'n'); 4445 exe_ctx.GetThreadRef().StepOver(source_step); 4446 } 4447 } 4448 return eKeyHandled; 4449 4450 case 's': // 's' == step into 4451 case 'S': // 'S' == step into instruction 4452 { 4453 ExecutionContext exe_ctx = 4454 m_debugger.GetCommandInterpreter().GetExecutionContext(); 4455 if (exe_ctx.HasThreadScope() && 4456 StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { 4457 bool source_step = (c == 's'); 4458 exe_ctx.GetThreadRef().StepIn(source_step); 4459 } 4460 } 4461 return eKeyHandled; 4462 4463 case 'h': 4464 window.CreateHelpSubwindow(); 4465 return eKeyHandled; 4466 4467 default: 4468 break; 4469 } 4470 return eKeyNotHandled; 4471 } 4472 4473 protected: 4474 typedef std::set<uint32_t> BreakpointLines; 4475 typedef std::set<lldb::addr_t> BreakpointAddrs; 4476 4477 Debugger &m_debugger; 4478 SymbolContext m_sc; 4479 SourceManager::FileSP m_file_sp; 4480 SymbolContextScope *m_disassembly_scope; 4481 lldb::DisassemblerSP m_disassembly_sp; 4482 AddressRange m_disassembly_range; 4483 StreamString m_title; 4484 lldb::user_id_t m_tid; 4485 int m_line_width; 4486 uint32_t m_selected_line; // The selected line 4487 uint32_t m_pc_line; // The line with the PC 4488 uint32_t m_stop_id; 4489 uint32_t m_frame_idx; 4490 int m_first_visible_line; 4491 int m_min_x; 4492 int m_min_y; 4493 int m_max_x; 4494 int m_max_y; 4495 }; 4496 4497 DisplayOptions ValueObjectListDelegate::g_options = {true}; 4498 4499 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) 4500 : IOHandler(debugger, IOHandler::Type::Curses) {} 4501 4502 void IOHandlerCursesGUI::Activate() { 4503 IOHandler::Activate(); 4504 if (!m_app_ap) { 4505 m_app_ap.reset(new Application(GetInputFILE(), GetOutputFILE())); 4506 4507 // This is both a window and a menu delegate 4508 std::shared_ptr<ApplicationDelegate> app_delegate_sp( 4509 new ApplicationDelegate(*m_app_ap, m_debugger)); 4510 4511 MenuDelegateSP app_menu_delegate_sp = 4512 std::static_pointer_cast<MenuDelegate>(app_delegate_sp); 4513 MenuSP lldb_menu_sp( 4514 new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); 4515 MenuSP exit_menuitem_sp( 4516 new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); 4517 exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); 4518 lldb_menu_sp->AddSubmenu(MenuSP(new Menu( 4519 "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); 4520 lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4521 lldb_menu_sp->AddSubmenu(exit_menuitem_sp); 4522 4523 MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), 4524 ApplicationDelegate::eMenuID_Target)); 4525 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4526 "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); 4527 target_menu_sp->AddSubmenu(MenuSP(new Menu( 4528 "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); 4529 4530 MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), 4531 ApplicationDelegate::eMenuID_Process)); 4532 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4533 "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); 4534 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4535 "Detach", nullptr, 'd', ApplicationDelegate::eMenuID_ProcessDetach))); 4536 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4537 "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); 4538 process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); 4539 process_menu_sp->AddSubmenu( 4540 MenuSP(new Menu("Continue", nullptr, 'c', 4541 ApplicationDelegate::eMenuID_ProcessContinue))); 4542 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4543 "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); 4544 process_menu_sp->AddSubmenu(MenuSP(new Menu( 4545 "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); 4546 4547 MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), 4548 ApplicationDelegate::eMenuID_Thread)); 4549 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4550 "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); 4551 thread_menu_sp->AddSubmenu( 4552 MenuSP(new Menu("Step Over", nullptr, 'v', 4553 ApplicationDelegate::eMenuID_ThreadStepOver))); 4554 thread_menu_sp->AddSubmenu(MenuSP(new Menu( 4555 "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); 4556 4557 MenuSP view_menu_sp( 4558 new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); 4559 view_menu_sp->AddSubmenu( 4560 MenuSP(new Menu("Backtrace", nullptr, 'b', 4561 ApplicationDelegate::eMenuID_ViewBacktrace))); 4562 view_menu_sp->AddSubmenu( 4563 MenuSP(new Menu("Registers", nullptr, 'r', 4564 ApplicationDelegate::eMenuID_ViewRegisters))); 4565 view_menu_sp->AddSubmenu(MenuSP(new Menu( 4566 "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); 4567 view_menu_sp->AddSubmenu( 4568 MenuSP(new Menu("Variables", nullptr, 'v', 4569 ApplicationDelegate::eMenuID_ViewVariables))); 4570 4571 MenuSP help_menu_sp( 4572 new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); 4573 help_menu_sp->AddSubmenu(MenuSP(new Menu( 4574 "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); 4575 4576 m_app_ap->Initialize(); 4577 WindowSP &main_window_sp = m_app_ap->GetMainWindow(); 4578 4579 MenuSP menubar_sp(new Menu(Menu::Type::Bar)); 4580 menubar_sp->AddSubmenu(lldb_menu_sp); 4581 menubar_sp->AddSubmenu(target_menu_sp); 4582 menubar_sp->AddSubmenu(process_menu_sp); 4583 menubar_sp->AddSubmenu(thread_menu_sp); 4584 menubar_sp->AddSubmenu(view_menu_sp); 4585 menubar_sp->AddSubmenu(help_menu_sp); 4586 menubar_sp->SetDelegate(app_menu_delegate_sp); 4587 4588 Rect content_bounds = main_window_sp->GetFrame(); 4589 Rect menubar_bounds = content_bounds.MakeMenuBar(); 4590 Rect status_bounds = content_bounds.MakeStatusBar(); 4591 Rect source_bounds; 4592 Rect variables_bounds; 4593 Rect threads_bounds; 4594 Rect source_variables_bounds; 4595 content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, 4596 threads_bounds); 4597 source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, 4598 variables_bounds); 4599 4600 WindowSP menubar_window_sp = 4601 main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); 4602 // Let the menubar get keys if the active window doesn't handle the keys 4603 // that are typed so it can respond to menubar key presses. 4604 menubar_window_sp->SetCanBeActive( 4605 false); // Don't let the menubar become the active window 4606 menubar_window_sp->SetDelegate(menubar_sp); 4607 4608 WindowSP source_window_sp( 4609 main_window_sp->CreateSubWindow("Source", source_bounds, true)); 4610 WindowSP variables_window_sp( 4611 main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); 4612 WindowSP threads_window_sp( 4613 main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); 4614 WindowSP status_window_sp( 4615 main_window_sp->CreateSubWindow("Status", status_bounds, false)); 4616 status_window_sp->SetCanBeActive( 4617 false); // Don't let the status bar become the active window 4618 main_window_sp->SetDelegate( 4619 std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); 4620 source_window_sp->SetDelegate( 4621 WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); 4622 variables_window_sp->SetDelegate( 4623 WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); 4624 TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); 4625 threads_window_sp->SetDelegate(WindowDelegateSP( 4626 new TreeWindowDelegate(m_debugger, thread_delegate_sp))); 4627 status_window_sp->SetDelegate( 4628 WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); 4629 4630 // Show the main help window once the first time the curses GUI is launched 4631 static bool g_showed_help = false; 4632 if (!g_showed_help) { 4633 g_showed_help = true; 4634 main_window_sp->CreateHelpSubwindow(); 4635 } 4636 4637 init_pair(1, COLOR_WHITE, COLOR_BLUE); 4638 init_pair(2, COLOR_BLACK, COLOR_WHITE); 4639 init_pair(3, COLOR_MAGENTA, COLOR_WHITE); 4640 init_pair(4, COLOR_MAGENTA, COLOR_BLACK); 4641 init_pair(5, COLOR_RED, COLOR_BLACK); 4642 } 4643 } 4644 4645 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } 4646 4647 void IOHandlerCursesGUI::Run() { 4648 m_app_ap->Run(m_debugger); 4649 SetIsDone(true); 4650 } 4651 4652 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; 4653 4654 void IOHandlerCursesGUI::Cancel() {} 4655 4656 bool IOHandlerCursesGUI::Interrupt() { return false; } 4657 4658 void IOHandlerCursesGUI::GotEOF() {} 4659 4660 #endif // LLDB_DISABLE_CURSES 4661