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