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