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