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