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