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