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           const size_t num_locals = locals->GetSize();
3006           for (size_t i = 0; i < num_locals; ++i) {
3007             ValueObjectSP value_sp = frame->GetValueObjectForFrameVariable(
3008                 locals->GetVariableAtIndex(i), use_dynamic);
3009             if (value_sp) {
3010               ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue();
3011               if (synthetic_value_sp)
3012                 local_values.Append(synthetic_value_sp);
3013               else
3014                 local_values.Append(value_sp);
3015             }
3016           }
3017           // Update the values
3018           SetValues(local_values);
3019         }
3020       }
3021     } else {
3022       m_frame_block = nullptr;
3023       // Update the values with an empty list if there is no frame
3024       SetValues(local_values);
3025     }
3026 
3027     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
3028   }
3029 
3030 protected:
3031   Debugger &m_debugger;
3032   Block *m_frame_block;
3033 };
3034 
3035 class RegistersWindowDelegate : public ValueObjectListDelegate {
3036 public:
3037   RegistersWindowDelegate(Debugger &debugger)
3038       : ValueObjectListDelegate(), m_debugger(debugger) {}
3039 
3040   ~RegistersWindowDelegate() override = default;
3041 
3042   const char *WindowDelegateGetHelpText() override {
3043     return "Register window keyboard shortcuts:";
3044   }
3045 
3046   bool WindowDelegateDraw(Window &window, bool force) override {
3047     ExecutionContext exe_ctx(
3048         m_debugger.GetCommandInterpreter().GetExecutionContext());
3049     StackFrame *frame = exe_ctx.GetFramePtr();
3050 
3051     ValueObjectList value_list;
3052     if (frame) {
3053       if (frame->GetStackID() != m_stack_id) {
3054         m_stack_id = frame->GetStackID();
3055         RegisterContextSP reg_ctx(frame->GetRegisterContext());
3056         if (reg_ctx) {
3057           const uint32_t num_sets = reg_ctx->GetRegisterSetCount();
3058           for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) {
3059             value_list.Append(
3060                 ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx));
3061           }
3062         }
3063         SetValues(value_list);
3064       }
3065     } else {
3066       Process *process = exe_ctx.GetProcessPtr();
3067       if (process && process->IsAlive())
3068         return true; // Don't do any updating if we are running
3069       else {
3070         // Update the values with an empty list if there is no process or the
3071         // process isn't alive anymore
3072         SetValues(value_list);
3073       }
3074     }
3075     return ValueObjectListDelegate::WindowDelegateDraw(window, force);
3076   }
3077 
3078 protected:
3079   Debugger &m_debugger;
3080   StackID m_stack_id;
3081 };
3082 
3083 static const char *CursesKeyToCString(int ch) {
3084   static char g_desc[32];
3085   if (ch >= KEY_F0 && ch < KEY_F0 + 64) {
3086     snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0);
3087     return g_desc;
3088   }
3089   switch (ch) {
3090   case KEY_DOWN:
3091     return "down";
3092   case KEY_UP:
3093     return "up";
3094   case KEY_LEFT:
3095     return "left";
3096   case KEY_RIGHT:
3097     return "right";
3098   case KEY_HOME:
3099     return "home";
3100   case KEY_BACKSPACE:
3101     return "backspace";
3102   case KEY_DL:
3103     return "delete-line";
3104   case KEY_IL:
3105     return "insert-line";
3106   case KEY_DC:
3107     return "delete-char";
3108   case KEY_IC:
3109     return "insert-char";
3110   case KEY_CLEAR:
3111     return "clear";
3112   case KEY_EOS:
3113     return "clear-to-eos";
3114   case KEY_EOL:
3115     return "clear-to-eol";
3116   case KEY_SF:
3117     return "scroll-forward";
3118   case KEY_SR:
3119     return "scroll-backward";
3120   case KEY_NPAGE:
3121     return "page-down";
3122   case KEY_PPAGE:
3123     return "page-up";
3124   case KEY_STAB:
3125     return "set-tab";
3126   case KEY_CTAB:
3127     return "clear-tab";
3128   case KEY_CATAB:
3129     return "clear-all-tabs";
3130   case KEY_ENTER:
3131     return "enter";
3132   case KEY_PRINT:
3133     return "print";
3134   case KEY_LL:
3135     return "lower-left key";
3136   case KEY_A1:
3137     return "upper left of keypad";
3138   case KEY_A3:
3139     return "upper right of keypad";
3140   case KEY_B2:
3141     return "center of keypad";
3142   case KEY_C1:
3143     return "lower left of keypad";
3144   case KEY_C3:
3145     return "lower right of keypad";
3146   case KEY_BTAB:
3147     return "back-tab key";
3148   case KEY_BEG:
3149     return "begin key";
3150   case KEY_CANCEL:
3151     return "cancel key";
3152   case KEY_CLOSE:
3153     return "close key";
3154   case KEY_COMMAND:
3155     return "command key";
3156   case KEY_COPY:
3157     return "copy key";
3158   case KEY_CREATE:
3159     return "create key";
3160   case KEY_END:
3161     return "end key";
3162   case KEY_EXIT:
3163     return "exit key";
3164   case KEY_FIND:
3165     return "find key";
3166   case KEY_HELP:
3167     return "help key";
3168   case KEY_MARK:
3169     return "mark key";
3170   case KEY_MESSAGE:
3171     return "message key";
3172   case KEY_MOVE:
3173     return "move key";
3174   case KEY_NEXT:
3175     return "next key";
3176   case KEY_OPEN:
3177     return "open key";
3178   case KEY_OPTIONS:
3179     return "options key";
3180   case KEY_PREVIOUS:
3181     return "previous key";
3182   case KEY_REDO:
3183     return "redo key";
3184   case KEY_REFERENCE:
3185     return "reference key";
3186   case KEY_REFRESH:
3187     return "refresh key";
3188   case KEY_REPLACE:
3189     return "replace key";
3190   case KEY_RESTART:
3191     return "restart key";
3192   case KEY_RESUME:
3193     return "resume key";
3194   case KEY_SAVE:
3195     return "save key";
3196   case KEY_SBEG:
3197     return "shifted begin key";
3198   case KEY_SCANCEL:
3199     return "shifted cancel key";
3200   case KEY_SCOMMAND:
3201     return "shifted command key";
3202   case KEY_SCOPY:
3203     return "shifted copy key";
3204   case KEY_SCREATE:
3205     return "shifted create key";
3206   case KEY_SDC:
3207     return "shifted delete-character key";
3208   case KEY_SDL:
3209     return "shifted delete-line key";
3210   case KEY_SELECT:
3211     return "select key";
3212   case KEY_SEND:
3213     return "shifted end key";
3214   case KEY_SEOL:
3215     return "shifted clear-to-end-of-line key";
3216   case KEY_SEXIT:
3217     return "shifted exit key";
3218   case KEY_SFIND:
3219     return "shifted find key";
3220   case KEY_SHELP:
3221     return "shifted help key";
3222   case KEY_SHOME:
3223     return "shifted home key";
3224   case KEY_SIC:
3225     return "shifted insert-character key";
3226   case KEY_SLEFT:
3227     return "shifted left-arrow key";
3228   case KEY_SMESSAGE:
3229     return "shifted message key";
3230   case KEY_SMOVE:
3231     return "shifted move key";
3232   case KEY_SNEXT:
3233     return "shifted next key";
3234   case KEY_SOPTIONS:
3235     return "shifted options key";
3236   case KEY_SPREVIOUS:
3237     return "shifted previous key";
3238   case KEY_SPRINT:
3239     return "shifted print key";
3240   case KEY_SREDO:
3241     return "shifted redo key";
3242   case KEY_SREPLACE:
3243     return "shifted replace key";
3244   case KEY_SRIGHT:
3245     return "shifted right-arrow key";
3246   case KEY_SRSUME:
3247     return "shifted resume key";
3248   case KEY_SSAVE:
3249     return "shifted save key";
3250   case KEY_SSUSPEND:
3251     return "shifted suspend key";
3252   case KEY_SUNDO:
3253     return "shifted undo key";
3254   case KEY_SUSPEND:
3255     return "suspend key";
3256   case KEY_UNDO:
3257     return "undo key";
3258   case KEY_MOUSE:
3259     return "Mouse event has occurred";
3260   case KEY_RESIZE:
3261     return "Terminal resize event";
3262 #ifdef KEY_EVENT
3263   case KEY_EVENT:
3264     return "We were interrupted by an event";
3265 #endif
3266   case KEY_RETURN:
3267     return "return";
3268   case ' ':
3269     return "space";
3270   case '\t':
3271     return "tab";
3272   case KEY_ESCAPE:
3273     return "escape";
3274   default:
3275     if (isprint(ch))
3276       snprintf(g_desc, sizeof(g_desc), "%c", ch);
3277     else
3278       snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch);
3279     return g_desc;
3280   }
3281   return nullptr;
3282 }
3283 
3284 HelpDialogDelegate::HelpDialogDelegate(const char *text,
3285                                        KeyHelp *key_help_array)
3286     : m_text(), m_first_visible_line(0) {
3287   if (text && text[0]) {
3288     m_text.SplitIntoLines(text);
3289     m_text.AppendString("");
3290   }
3291   if (key_help_array) {
3292     for (KeyHelp *key = key_help_array; key->ch; ++key) {
3293       StreamString key_description;
3294       key_description.Printf("%10s - %s", CursesKeyToCString(key->ch),
3295                              key->description);
3296       m_text.AppendString(key_description.GetString());
3297     }
3298   }
3299 }
3300 
3301 HelpDialogDelegate::~HelpDialogDelegate() = default;
3302 
3303 bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) {
3304   window.Erase();
3305   const int window_height = window.GetHeight();
3306   int x = 2;
3307   int y = 1;
3308   const int min_y = y;
3309   const int max_y = window_height - 1 - y;
3310   const size_t num_visible_lines = max_y - min_y + 1;
3311   const size_t num_lines = m_text.GetSize();
3312   const char *bottom_message;
3313   if (num_lines <= num_visible_lines)
3314     bottom_message = "Press any key to exit";
3315   else
3316     bottom_message = "Use arrows to scroll, any other key to exit";
3317   window.DrawTitleBox(window.GetName(), bottom_message);
3318   while (y <= max_y) {
3319     window.MoveCursor(x, y);
3320     window.PutCStringTruncated(
3321         m_text.GetStringAtIndex(m_first_visible_line + y - min_y), 1);
3322     ++y;
3323   }
3324   return true;
3325 }
3326 
3327 HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window,
3328                                                               int key) {
3329   bool done = false;
3330   const size_t num_lines = m_text.GetSize();
3331   const size_t num_visible_lines = window.GetHeight() - 2;
3332 
3333   if (num_lines <= num_visible_lines) {
3334     done = true;
3335     // If we have all lines visible and don't need scrolling, then any key
3336     // press will cause us to exit
3337   } else {
3338     switch (key) {
3339     case KEY_UP:
3340       if (m_first_visible_line > 0)
3341         --m_first_visible_line;
3342       break;
3343 
3344     case KEY_DOWN:
3345       if (m_first_visible_line + num_visible_lines < num_lines)
3346         ++m_first_visible_line;
3347       break;
3348 
3349     case KEY_PPAGE:
3350     case ',':
3351       if (m_first_visible_line > 0) {
3352         if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines)
3353           m_first_visible_line -= num_visible_lines;
3354         else
3355           m_first_visible_line = 0;
3356       }
3357       break;
3358 
3359     case KEY_NPAGE:
3360     case '.':
3361       if (m_first_visible_line + num_visible_lines < num_lines) {
3362         m_first_visible_line += num_visible_lines;
3363         if (static_cast<size_t>(m_first_visible_line) > num_lines)
3364           m_first_visible_line = num_lines - num_visible_lines;
3365       }
3366       break;
3367 
3368     default:
3369       done = true;
3370       break;
3371     }
3372   }
3373   if (done)
3374     window.GetParent()->RemoveSubWindow(&window);
3375   return eKeyHandled;
3376 }
3377 
3378 class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
3379 public:
3380   enum {
3381     eMenuID_LLDB = 1,
3382     eMenuID_LLDBAbout,
3383     eMenuID_LLDBExit,
3384 
3385     eMenuID_Target,
3386     eMenuID_TargetCreate,
3387     eMenuID_TargetDelete,
3388 
3389     eMenuID_Process,
3390     eMenuID_ProcessAttach,
3391     eMenuID_ProcessDetach,
3392     eMenuID_ProcessLaunch,
3393     eMenuID_ProcessContinue,
3394     eMenuID_ProcessHalt,
3395     eMenuID_ProcessKill,
3396 
3397     eMenuID_Thread,
3398     eMenuID_ThreadStepIn,
3399     eMenuID_ThreadStepOver,
3400     eMenuID_ThreadStepOut,
3401 
3402     eMenuID_View,
3403     eMenuID_ViewBacktrace,
3404     eMenuID_ViewRegisters,
3405     eMenuID_ViewSource,
3406     eMenuID_ViewVariables,
3407 
3408     eMenuID_Help,
3409     eMenuID_HelpGUIHelp
3410   };
3411 
3412   ApplicationDelegate(Application &app, Debugger &debugger)
3413       : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {}
3414 
3415   ~ApplicationDelegate() override = default;
3416 
3417   bool WindowDelegateDraw(Window &window, bool force) override {
3418     return false; // Drawing not handled, let standard window drawing happen
3419   }
3420 
3421   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
3422     switch (key) {
3423     case '\t':
3424       window.SelectNextWindowAsActive();
3425       return eKeyHandled;
3426 
3427     case 'h':
3428       window.CreateHelpSubwindow();
3429       return eKeyHandled;
3430 
3431     case KEY_ESCAPE:
3432       return eQuitApplication;
3433 
3434     default:
3435       break;
3436     }
3437     return eKeyNotHandled;
3438   }
3439 
3440   const char *WindowDelegateGetHelpText() override {
3441     return "Welcome to the LLDB curses GUI.\n\n"
3442            "Press the TAB key to change the selected view.\n"
3443            "Each view has its own keyboard shortcuts, press 'h' to open a "
3444            "dialog to display them.\n\n"
3445            "Common key bindings for all views:";
3446   }
3447 
3448   KeyHelp *WindowDelegateGetKeyHelp() override {
3449     static curses::KeyHelp g_source_view_key_help[] = {
3450         {'\t', "Select next view"},
3451         {'h', "Show help dialog with view specific key bindings"},
3452         {',', "Page up"},
3453         {'.', "Page down"},
3454         {KEY_UP, "Select previous"},
3455         {KEY_DOWN, "Select next"},
3456         {KEY_LEFT, "Unexpand or select parent"},
3457         {KEY_RIGHT, "Expand"},
3458         {KEY_PPAGE, "Page up"},
3459         {KEY_NPAGE, "Page down"},
3460         {'\0', nullptr}};
3461     return g_source_view_key_help;
3462   }
3463 
3464   MenuActionResult MenuDelegateAction(Menu &menu) override {
3465     switch (menu.GetIdentifier()) {
3466     case eMenuID_ThreadStepIn: {
3467       ExecutionContext exe_ctx =
3468           m_debugger.GetCommandInterpreter().GetExecutionContext();
3469       if (exe_ctx.HasThreadScope()) {
3470         Process *process = exe_ctx.GetProcessPtr();
3471         if (process && process->IsAlive() &&
3472             StateIsStoppedState(process->GetState(), true))
3473           exe_ctx.GetThreadRef().StepIn(true);
3474       }
3475     }
3476       return MenuActionResult::Handled;
3477 
3478     case eMenuID_ThreadStepOut: {
3479       ExecutionContext exe_ctx =
3480           m_debugger.GetCommandInterpreter().GetExecutionContext();
3481       if (exe_ctx.HasThreadScope()) {
3482         Process *process = exe_ctx.GetProcessPtr();
3483         if (process && process->IsAlive() &&
3484             StateIsStoppedState(process->GetState(), true))
3485           exe_ctx.GetThreadRef().StepOut();
3486       }
3487     }
3488       return MenuActionResult::Handled;
3489 
3490     case eMenuID_ThreadStepOver: {
3491       ExecutionContext exe_ctx =
3492           m_debugger.GetCommandInterpreter().GetExecutionContext();
3493       if (exe_ctx.HasThreadScope()) {
3494         Process *process = exe_ctx.GetProcessPtr();
3495         if (process && process->IsAlive() &&
3496             StateIsStoppedState(process->GetState(), true))
3497           exe_ctx.GetThreadRef().StepOver(true);
3498       }
3499     }
3500       return MenuActionResult::Handled;
3501 
3502     case eMenuID_ProcessContinue: {
3503       ExecutionContext exe_ctx =
3504           m_debugger.GetCommandInterpreter().GetExecutionContext();
3505       if (exe_ctx.HasProcessScope()) {
3506         Process *process = exe_ctx.GetProcessPtr();
3507         if (process && process->IsAlive() &&
3508             StateIsStoppedState(process->GetState(), true))
3509           process->Resume();
3510       }
3511     }
3512       return MenuActionResult::Handled;
3513 
3514     case eMenuID_ProcessKill: {
3515       ExecutionContext exe_ctx =
3516           m_debugger.GetCommandInterpreter().GetExecutionContext();
3517       if (exe_ctx.HasProcessScope()) {
3518         Process *process = exe_ctx.GetProcessPtr();
3519         if (process && process->IsAlive())
3520           process->Destroy(false);
3521       }
3522     }
3523       return MenuActionResult::Handled;
3524 
3525     case eMenuID_ProcessHalt: {
3526       ExecutionContext exe_ctx =
3527           m_debugger.GetCommandInterpreter().GetExecutionContext();
3528       if (exe_ctx.HasProcessScope()) {
3529         Process *process = exe_ctx.GetProcessPtr();
3530         if (process && process->IsAlive())
3531           process->Halt();
3532       }
3533     }
3534       return MenuActionResult::Handled;
3535 
3536     case eMenuID_ProcessDetach: {
3537       ExecutionContext exe_ctx =
3538           m_debugger.GetCommandInterpreter().GetExecutionContext();
3539       if (exe_ctx.HasProcessScope()) {
3540         Process *process = exe_ctx.GetProcessPtr();
3541         if (process && process->IsAlive())
3542           process->Detach(false);
3543       }
3544     }
3545       return MenuActionResult::Handled;
3546 
3547     case eMenuID_Process: {
3548       // Populate the menu with all of the threads if the process is stopped
3549       // when the Process menu gets selected and is about to display its
3550       // submenu.
3551       Menus &submenus = menu.GetSubmenus();
3552       ExecutionContext exe_ctx =
3553           m_debugger.GetCommandInterpreter().GetExecutionContext();
3554       Process *process = exe_ctx.GetProcessPtr();
3555       if (process && process->IsAlive() &&
3556           StateIsStoppedState(process->GetState(), true)) {
3557         if (submenus.size() == 7)
3558           menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
3559         else if (submenus.size() > 8)
3560           submenus.erase(submenus.begin() + 8, submenus.end());
3561 
3562         ThreadList &threads = process->GetThreadList();
3563         std::lock_guard<std::recursive_mutex> guard(threads.GetMutex());
3564         size_t num_threads = threads.GetSize();
3565         for (size_t i = 0; i < num_threads; ++i) {
3566           ThreadSP thread_sp = threads.GetThreadAtIndex(i);
3567           char menu_char = '\0';
3568           if (i < 9)
3569             menu_char = '1' + i;
3570           StreamString thread_menu_title;
3571           thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID());
3572           const char *thread_name = thread_sp->GetName();
3573           if (thread_name && thread_name[0])
3574             thread_menu_title.Printf(" %s", thread_name);
3575           else {
3576             const char *queue_name = thread_sp->GetQueueName();
3577             if (queue_name && queue_name[0])
3578               thread_menu_title.Printf(" %s", queue_name);
3579           }
3580           menu.AddSubmenu(
3581               MenuSP(new Menu(thread_menu_title.GetString().str().c_str(),
3582                               nullptr, menu_char, thread_sp->GetID())));
3583         }
3584       } else if (submenus.size() > 7) {
3585         // Remove the separator and any other thread submenu items that were
3586         // previously added
3587         submenus.erase(submenus.begin() + 7, submenus.end());
3588       }
3589       // Since we are adding and removing items we need to recalculate the name
3590       // lengths
3591       menu.RecalculateNameLengths();
3592     }
3593       return MenuActionResult::Handled;
3594 
3595     case eMenuID_ViewVariables: {
3596       WindowSP main_window_sp = m_app.GetMainWindow();
3597       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3598       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3599       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3600       const Rect source_bounds = source_window_sp->GetBounds();
3601 
3602       if (variables_window_sp) {
3603         const Rect variables_bounds = variables_window_sp->GetBounds();
3604 
3605         main_window_sp->RemoveSubWindow(variables_window_sp.get());
3606 
3607         if (registers_window_sp) {
3608           // We have a registers window, so give all the area back to the
3609           // registers window
3610           Rect registers_bounds = variables_bounds;
3611           registers_bounds.size.width = source_bounds.size.width;
3612           registers_window_sp->SetBounds(registers_bounds);
3613         } else {
3614           // We have no registers window showing so give the bottom area back
3615           // to the source view
3616           source_window_sp->Resize(source_bounds.size.width,
3617                                    source_bounds.size.height +
3618                                        variables_bounds.size.height);
3619         }
3620       } else {
3621         Rect new_variables_rect;
3622         if (registers_window_sp) {
3623           // We have a registers window so split the area of the registers
3624           // window into two columns where the left hand side will be the
3625           // variables and the right hand side will be the registers
3626           const Rect variables_bounds = registers_window_sp->GetBounds();
3627           Rect new_registers_rect;
3628           variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect,
3629                                                    new_registers_rect);
3630           registers_window_sp->SetBounds(new_registers_rect);
3631         } else {
3632           // No variables window, grab the bottom part of the source window
3633           Rect new_source_rect;
3634           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3635                                                   new_variables_rect);
3636           source_window_sp->SetBounds(new_source_rect);
3637         }
3638         WindowSP new_window_sp = main_window_sp->CreateSubWindow(
3639             "Variables", new_variables_rect, false);
3640         new_window_sp->SetDelegate(
3641             WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
3642       }
3643       touchwin(stdscr);
3644     }
3645       return MenuActionResult::Handled;
3646 
3647     case eMenuID_ViewRegisters: {
3648       WindowSP main_window_sp = m_app.GetMainWindow();
3649       WindowSP source_window_sp = main_window_sp->FindSubWindow("Source");
3650       WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables");
3651       WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers");
3652       const Rect source_bounds = source_window_sp->GetBounds();
3653 
3654       if (registers_window_sp) {
3655         if (variables_window_sp) {
3656           const Rect variables_bounds = variables_window_sp->GetBounds();
3657 
3658           // We have a variables window, so give all the area back to the
3659           // variables window
3660           variables_window_sp->Resize(variables_bounds.size.width +
3661                                           registers_window_sp->GetWidth(),
3662                                       variables_bounds.size.height);
3663         } else {
3664           // We have no variables window showing so give the bottom area back
3665           // to the source view
3666           source_window_sp->Resize(source_bounds.size.width,
3667                                    source_bounds.size.height +
3668                                        registers_window_sp->GetHeight());
3669         }
3670         main_window_sp->RemoveSubWindow(registers_window_sp.get());
3671       } else {
3672         Rect new_regs_rect;
3673         if (variables_window_sp) {
3674           // We have a variables window, split it into two columns where the
3675           // left hand side will be the variables and the right hand side will
3676           // be the registers
3677           const Rect variables_bounds = variables_window_sp->GetBounds();
3678           Rect new_vars_rect;
3679           variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect,
3680                                                    new_regs_rect);
3681           variables_window_sp->SetBounds(new_vars_rect);
3682         } else {
3683           // No registers window, grab the bottom part of the source window
3684           Rect new_source_rect;
3685           source_bounds.HorizontalSplitPercentage(0.70, new_source_rect,
3686                                                   new_regs_rect);
3687           source_window_sp->SetBounds(new_source_rect);
3688         }
3689         WindowSP new_window_sp =
3690             main_window_sp->CreateSubWindow("Registers", new_regs_rect, false);
3691         new_window_sp->SetDelegate(
3692             WindowDelegateSP(new RegistersWindowDelegate(m_debugger)));
3693       }
3694       touchwin(stdscr);
3695     }
3696       return MenuActionResult::Handled;
3697 
3698     case eMenuID_HelpGUIHelp:
3699       m_app.GetMainWindow()->CreateHelpSubwindow();
3700       return MenuActionResult::Handled;
3701 
3702     default:
3703       break;
3704     }
3705 
3706     return MenuActionResult::NotHandled;
3707   }
3708 
3709 protected:
3710   Application &m_app;
3711   Debugger &m_debugger;
3712 };
3713 
3714 class StatusBarWindowDelegate : public WindowDelegate {
3715 public:
3716   StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) {
3717     FormatEntity::Parse("Thread: ${thread.id%tid}", m_format);
3718   }
3719 
3720   ~StatusBarWindowDelegate() override = default;
3721 
3722   bool WindowDelegateDraw(Window &window, bool force) override {
3723     ExecutionContext exe_ctx =
3724         m_debugger.GetCommandInterpreter().GetExecutionContext();
3725     Process *process = exe_ctx.GetProcessPtr();
3726     Thread *thread = exe_ctx.GetThreadPtr();
3727     StackFrame *frame = exe_ctx.GetFramePtr();
3728     window.Erase();
3729     window.SetBackground(2);
3730     window.MoveCursor(0, 0);
3731     if (process) {
3732       const StateType state = process->GetState();
3733       window.Printf("Process: %5" PRIu64 " %10s", process->GetID(),
3734                     StateAsCString(state));
3735 
3736       if (StateIsStoppedState(state, true)) {
3737         StreamString strm;
3738         if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
3739                                            nullptr, nullptr, false, false)) {
3740           window.MoveCursor(40, 0);
3741           window.PutCStringTruncated(strm.GetString().str().c_str(), 1);
3742         }
3743 
3744         window.MoveCursor(60, 0);
3745         if (frame)
3746           window.Printf("Frame: %3u  PC = 0x%16.16" PRIx64,
3747                         frame->GetFrameIndex(),
3748                         frame->GetFrameCodeAddress().GetOpcodeLoadAddress(
3749                             exe_ctx.GetTargetPtr()));
3750       } else if (state == eStateExited) {
3751         const char *exit_desc = process->GetExitDescription();
3752         const int exit_status = process->GetExitStatus();
3753         if (exit_desc && exit_desc[0])
3754           window.Printf(" with status = %i (%s)", exit_status, exit_desc);
3755         else
3756           window.Printf(" with status = %i", exit_status);
3757       }
3758     }
3759     return true;
3760   }
3761 
3762 protected:
3763   Debugger &m_debugger;
3764   FormatEntity::Entry m_format;
3765 };
3766 
3767 class SourceFileWindowDelegate : public WindowDelegate {
3768 public:
3769   SourceFileWindowDelegate(Debugger &debugger)
3770       : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(),
3771         m_disassembly_scope(nullptr), m_disassembly_sp(), m_disassembly_range(),
3772         m_title(), m_line_width(4), m_selected_line(0), m_pc_line(0),
3773         m_stop_id(0), m_frame_idx(UINT32_MAX), m_first_visible_line(0),
3774         m_min_x(0), m_min_y(0), m_max_x(0), m_max_y(0) {}
3775 
3776   ~SourceFileWindowDelegate() override = default;
3777 
3778   void Update(const SymbolContext &sc) { m_sc = sc; }
3779 
3780   uint32_t NumVisibleLines() const { return m_max_y - m_min_y; }
3781 
3782   const char *WindowDelegateGetHelpText() override {
3783     return "Source/Disassembly window keyboard shortcuts:";
3784   }
3785 
3786   KeyHelp *WindowDelegateGetKeyHelp() override {
3787     static curses::KeyHelp g_source_view_key_help[] = {
3788         {KEY_RETURN, "Run to selected line with one shot breakpoint"},
3789         {KEY_UP, "Select previous source line"},
3790         {KEY_DOWN, "Select next source line"},
3791         {KEY_PPAGE, "Page up"},
3792         {KEY_NPAGE, "Page down"},
3793         {'b', "Set breakpoint on selected source/disassembly line"},
3794         {'c', "Continue process"},
3795         {'d', "Detach and resume process"},
3796         {'D', "Detach with process suspended"},
3797         {'h', "Show help dialog"},
3798         {'k', "Kill process"},
3799         {'n', "Step over (source line)"},
3800         {'N', "Step over (single instruction)"},
3801         {'o', "Step out"},
3802         {'s', "Step in (source line)"},
3803         {'S', "Step in (single instruction)"},
3804         {',', "Page up"},
3805         {'.', "Page down"},
3806         {'\0', nullptr}};
3807     return g_source_view_key_help;
3808   }
3809 
3810   bool WindowDelegateDraw(Window &window, bool force) override {
3811     ExecutionContext exe_ctx =
3812         m_debugger.GetCommandInterpreter().GetExecutionContext();
3813     Process *process = exe_ctx.GetProcessPtr();
3814     Thread *thread = nullptr;
3815 
3816     bool update_location = false;
3817     if (process) {
3818       StateType state = process->GetState();
3819       if (StateIsStoppedState(state, true)) {
3820         // We are stopped, so it is ok to
3821         update_location = true;
3822       }
3823     }
3824 
3825     m_min_x = 1;
3826     m_min_y = 2;
3827     m_max_x = window.GetMaxX() - 1;
3828     m_max_y = window.GetMaxY() - 1;
3829 
3830     const uint32_t num_visible_lines = NumVisibleLines();
3831     StackFrameSP frame_sp;
3832     bool set_selected_line_to_pc = false;
3833 
3834     if (update_location) {
3835       const bool process_alive = process ? process->IsAlive() : false;
3836       bool thread_changed = false;
3837       if (process_alive) {
3838         thread = exe_ctx.GetThreadPtr();
3839         if (thread) {
3840           frame_sp = thread->GetSelectedFrame();
3841           auto tid = thread->GetID();
3842           thread_changed = tid != m_tid;
3843           m_tid = tid;
3844         } else {
3845           if (m_tid != LLDB_INVALID_THREAD_ID) {
3846             thread_changed = true;
3847             m_tid = LLDB_INVALID_THREAD_ID;
3848           }
3849         }
3850       }
3851       const uint32_t stop_id = process ? process->GetStopID() : 0;
3852       const bool stop_id_changed = stop_id != m_stop_id;
3853       bool frame_changed = false;
3854       m_stop_id = stop_id;
3855       m_title.Clear();
3856       if (frame_sp) {
3857         m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything);
3858         if (m_sc.module_sp) {
3859           m_title.Printf(
3860               "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString());
3861           ConstString func_name = m_sc.GetFunctionName();
3862           if (func_name)
3863             m_title.Printf("`%s", func_name.GetCString());
3864         }
3865         const uint32_t frame_idx = frame_sp->GetFrameIndex();
3866         frame_changed = frame_idx != m_frame_idx;
3867         m_frame_idx = frame_idx;
3868       } else {
3869         m_sc.Clear(true);
3870         frame_changed = m_frame_idx != UINT32_MAX;
3871         m_frame_idx = UINT32_MAX;
3872       }
3873 
3874       const bool context_changed =
3875           thread_changed || frame_changed || stop_id_changed;
3876 
3877       if (process_alive) {
3878         if (m_sc.line_entry.IsValid()) {
3879           m_pc_line = m_sc.line_entry.line;
3880           if (m_pc_line != UINT32_MAX)
3881             --m_pc_line; // Convert to zero based line number...
3882           // Update the selected line if the stop ID changed...
3883           if (context_changed)
3884             m_selected_line = m_pc_line;
3885 
3886           if (m_file_sp && m_file_sp->FileSpecMatches(m_sc.line_entry.file)) {
3887             // Same file, nothing to do, we should either have the lines or not
3888             // (source file missing)
3889             if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) {
3890               if (m_selected_line >= m_first_visible_line + num_visible_lines)
3891                 m_first_visible_line = m_selected_line - 10;
3892             } else {
3893               if (m_selected_line > 10)
3894                 m_first_visible_line = m_selected_line - 10;
3895               else
3896                 m_first_visible_line = 0;
3897             }
3898           } else {
3899             // File changed, set selected line to the line with the PC
3900             m_selected_line = m_pc_line;
3901             m_file_sp =
3902                 m_debugger.GetSourceManager().GetFile(m_sc.line_entry.file);
3903             if (m_file_sp) {
3904               const size_t num_lines = m_file_sp->GetNumLines();
3905               m_line_width = 1;
3906               for (size_t n = num_lines; n >= 10; n = n / 10)
3907                 ++m_line_width;
3908 
3909               if (num_lines < num_visible_lines ||
3910                   m_selected_line < num_visible_lines)
3911                 m_first_visible_line = 0;
3912               else
3913                 m_first_visible_line = m_selected_line - 10;
3914             }
3915           }
3916         } else {
3917           m_file_sp.reset();
3918         }
3919 
3920         if (!m_file_sp || m_file_sp->GetNumLines() == 0) {
3921           // Show disassembly
3922           bool prefer_file_cache = false;
3923           if (m_sc.function) {
3924             if (m_disassembly_scope != m_sc.function) {
3925               m_disassembly_scope = m_sc.function;
3926               m_disassembly_sp = m_sc.function->GetInstructions(
3927                   exe_ctx, nullptr, prefer_file_cache);
3928               if (m_disassembly_sp) {
3929                 set_selected_line_to_pc = true;
3930                 m_disassembly_range = m_sc.function->GetAddressRange();
3931               } else {
3932                 m_disassembly_range.Clear();
3933               }
3934             } else {
3935               set_selected_line_to_pc = context_changed;
3936             }
3937           } else if (m_sc.symbol) {
3938             if (m_disassembly_scope != m_sc.symbol) {
3939               m_disassembly_scope = m_sc.symbol;
3940               m_disassembly_sp = m_sc.symbol->GetInstructions(
3941                   exe_ctx, nullptr, prefer_file_cache);
3942               if (m_disassembly_sp) {
3943                 set_selected_line_to_pc = true;
3944                 m_disassembly_range.GetBaseAddress() =
3945                     m_sc.symbol->GetAddress();
3946                 m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize());
3947               } else {
3948                 m_disassembly_range.Clear();
3949               }
3950             } else {
3951               set_selected_line_to_pc = context_changed;
3952             }
3953           }
3954         }
3955       } else {
3956         m_pc_line = UINT32_MAX;
3957       }
3958     }
3959 
3960     const int window_width = window.GetWidth();
3961     window.Erase();
3962     window.DrawTitleBox("Sources");
3963     if (!m_title.GetString().empty()) {
3964       window.AttributeOn(A_REVERSE);
3965       window.MoveCursor(1, 1);
3966       window.PutChar(' ');
3967       window.PutCStringTruncated(m_title.GetString().str().c_str(), 1);
3968       int x = window.GetCursorX();
3969       if (x < window_width - 1) {
3970         window.Printf("%*s", window_width - x - 1, "");
3971       }
3972       window.AttributeOff(A_REVERSE);
3973     }
3974 
3975     Target *target = exe_ctx.GetTargetPtr();
3976     const size_t num_source_lines = GetNumSourceLines();
3977     if (num_source_lines > 0) {
3978       // Display source
3979       BreakpointLines bp_lines;
3980       if (target) {
3981         BreakpointList &bp_list = target->GetBreakpointList();
3982         const size_t num_bps = bp_list.GetSize();
3983         for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
3984           BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
3985           const size_t num_bps_locs = bp_sp->GetNumLocations();
3986           for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) {
3987             BreakpointLocationSP bp_loc_sp =
3988                 bp_sp->GetLocationAtIndex(bp_loc_idx);
3989             LineEntry bp_loc_line_entry;
3990             if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry(
3991                     bp_loc_line_entry)) {
3992               if (m_file_sp->GetFileSpec() == bp_loc_line_entry.file) {
3993                 bp_lines.insert(bp_loc_line_entry.line);
3994               }
3995             }
3996           }
3997         }
3998       }
3999 
4000       const attr_t selected_highlight_attr = A_REVERSE;
4001       const attr_t pc_highlight_attr = COLOR_PAIR(1);
4002 
4003       for (size_t i = 0; i < num_visible_lines; ++i) {
4004         const uint32_t curr_line = m_first_visible_line + i;
4005         if (curr_line < num_source_lines) {
4006           const int line_y = m_min_y + i;
4007           window.MoveCursor(1, line_y);
4008           const bool is_pc_line = curr_line == m_pc_line;
4009           const bool line_is_selected = m_selected_line == curr_line;
4010           // Highlight the line as the PC line first, then if the selected line
4011           // isn't the same as the PC line, highlight it differently
4012           attr_t highlight_attr = 0;
4013           attr_t bp_attr = 0;
4014           if (is_pc_line)
4015             highlight_attr = pc_highlight_attr;
4016           else if (line_is_selected)
4017             highlight_attr = selected_highlight_attr;
4018 
4019           if (bp_lines.find(curr_line + 1) != bp_lines.end())
4020             bp_attr = COLOR_PAIR(2);
4021 
4022           if (bp_attr)
4023             window.AttributeOn(bp_attr);
4024 
4025           window.Printf(" %*u ", m_line_width, curr_line + 1);
4026 
4027           if (bp_attr)
4028             window.AttributeOff(bp_attr);
4029 
4030           window.PutChar(ACS_VLINE);
4031           // Mark the line with the PC with a diamond
4032           if (is_pc_line)
4033             window.PutChar(ACS_DIAMOND);
4034           else
4035             window.PutChar(' ');
4036 
4037           if (highlight_attr)
4038             window.AttributeOn(highlight_attr);
4039           const uint32_t line_len =
4040               m_file_sp->GetLineLength(curr_line + 1, false);
4041           if (line_len > 0)
4042             window.PutCString(m_file_sp->PeekLineData(curr_line + 1), line_len);
4043 
4044           if (is_pc_line && frame_sp &&
4045               frame_sp->GetConcreteFrameIndex() == 0) {
4046             StopInfoSP stop_info_sp;
4047             if (thread)
4048               stop_info_sp = thread->GetStopInfo();
4049             if (stop_info_sp) {
4050               const char *stop_description = stop_info_sp->GetDescription();
4051               if (stop_description && stop_description[0]) {
4052                 size_t stop_description_len = strlen(stop_description);
4053                 int desc_x = window_width - stop_description_len - 16;
4054                 window.Printf("%*s", desc_x - window.GetCursorX(), "");
4055                 // window.MoveCursor(window_width - stop_description_len - 15,
4056                 // line_y);
4057                 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(),
4058                               stop_description);
4059               }
4060             } else {
4061               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
4062             }
4063           }
4064           if (highlight_attr)
4065             window.AttributeOff(highlight_attr);
4066         } else {
4067           break;
4068         }
4069       }
4070     } else {
4071       size_t num_disassembly_lines = GetNumDisassemblyLines();
4072       if (num_disassembly_lines > 0) {
4073         // Display disassembly
4074         BreakpointAddrs bp_file_addrs;
4075         Target *target = exe_ctx.GetTargetPtr();
4076         if (target) {
4077           BreakpointList &bp_list = target->GetBreakpointList();
4078           const size_t num_bps = bp_list.GetSize();
4079           for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) {
4080             BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx);
4081             const size_t num_bps_locs = bp_sp->GetNumLocations();
4082             for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs;
4083                  ++bp_loc_idx) {
4084               BreakpointLocationSP bp_loc_sp =
4085                   bp_sp->GetLocationAtIndex(bp_loc_idx);
4086               LineEntry bp_loc_line_entry;
4087               const lldb::addr_t file_addr =
4088                   bp_loc_sp->GetAddress().GetFileAddress();
4089               if (file_addr != LLDB_INVALID_ADDRESS) {
4090                 if (m_disassembly_range.ContainsFileAddress(file_addr))
4091                   bp_file_addrs.insert(file_addr);
4092               }
4093             }
4094           }
4095         }
4096 
4097         const attr_t selected_highlight_attr = A_REVERSE;
4098         const attr_t pc_highlight_attr = COLOR_PAIR(1);
4099 
4100         StreamString strm;
4101 
4102         InstructionList &insts = m_disassembly_sp->GetInstructionList();
4103         Address pc_address;
4104 
4105         if (frame_sp)
4106           pc_address = frame_sp->GetFrameCodeAddress();
4107         const uint32_t pc_idx =
4108             pc_address.IsValid()
4109                 ? insts.GetIndexOfInstructionAtAddress(pc_address)
4110                 : UINT32_MAX;
4111         if (set_selected_line_to_pc) {
4112           m_selected_line = pc_idx;
4113         }
4114 
4115         const uint32_t non_visible_pc_offset = (num_visible_lines / 5);
4116         if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines)
4117           m_first_visible_line = 0;
4118 
4119         if (pc_idx < num_disassembly_lines) {
4120           if (pc_idx < static_cast<uint32_t>(m_first_visible_line) ||
4121               pc_idx >= m_first_visible_line + num_visible_lines)
4122             m_first_visible_line = pc_idx - non_visible_pc_offset;
4123         }
4124 
4125         for (size_t i = 0; i < num_visible_lines; ++i) {
4126           const uint32_t inst_idx = m_first_visible_line + i;
4127           Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get();
4128           if (!inst)
4129             break;
4130 
4131           const int line_y = m_min_y + i;
4132           window.MoveCursor(1, line_y);
4133           const bool is_pc_line = frame_sp && inst_idx == pc_idx;
4134           const bool line_is_selected = m_selected_line == inst_idx;
4135           // Highlight the line as the PC line first, then if the selected line
4136           // isn't the same as the PC line, highlight it differently
4137           attr_t highlight_attr = 0;
4138           attr_t bp_attr = 0;
4139           if (is_pc_line)
4140             highlight_attr = pc_highlight_attr;
4141           else if (line_is_selected)
4142             highlight_attr = selected_highlight_attr;
4143 
4144           if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) !=
4145               bp_file_addrs.end())
4146             bp_attr = COLOR_PAIR(2);
4147 
4148           if (bp_attr)
4149             window.AttributeOn(bp_attr);
4150 
4151           window.Printf(" 0x%16.16llx ",
4152                         static_cast<unsigned long long>(
4153                             inst->GetAddress().GetLoadAddress(target)));
4154 
4155           if (bp_attr)
4156             window.AttributeOff(bp_attr);
4157 
4158           window.PutChar(ACS_VLINE);
4159           // Mark the line with the PC with a diamond
4160           if (is_pc_line)
4161             window.PutChar(ACS_DIAMOND);
4162           else
4163             window.PutChar(' ');
4164 
4165           if (highlight_attr)
4166             window.AttributeOn(highlight_attr);
4167 
4168           const char *mnemonic = inst->GetMnemonic(&exe_ctx);
4169           const char *operands = inst->GetOperands(&exe_ctx);
4170           const char *comment = inst->GetComment(&exe_ctx);
4171 
4172           if (mnemonic != nullptr && mnemonic[0] == '\0')
4173             mnemonic = nullptr;
4174           if (operands != nullptr && operands[0] == '\0')
4175             operands = nullptr;
4176           if (comment != nullptr && comment[0] == '\0')
4177             comment = nullptr;
4178 
4179           strm.Clear();
4180 
4181           if (mnemonic != nullptr && operands != nullptr && comment != nullptr)
4182             strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment);
4183           else if (mnemonic != nullptr && operands != nullptr)
4184             strm.Printf("%-8s %s", mnemonic, operands);
4185           else if (mnemonic != nullptr)
4186             strm.Printf("%s", mnemonic);
4187 
4188           int right_pad = 1;
4189           window.PutCStringTruncated(strm.GetData(), right_pad);
4190 
4191           if (is_pc_line && frame_sp &&
4192               frame_sp->GetConcreteFrameIndex() == 0) {
4193             StopInfoSP stop_info_sp;
4194             if (thread)
4195               stop_info_sp = thread->GetStopInfo();
4196             if (stop_info_sp) {
4197               const char *stop_description = stop_info_sp->GetDescription();
4198               if (stop_description && stop_description[0]) {
4199                 size_t stop_description_len = strlen(stop_description);
4200                 int desc_x = window_width - stop_description_len - 16;
4201                 window.Printf("%*s", desc_x - window.GetCursorX(), "");
4202                 // window.MoveCursor(window_width - stop_description_len - 15,
4203                 // line_y);
4204                 window.Printf("<<< Thread %u: %s ", thread->GetIndexID(),
4205                               stop_description);
4206               }
4207             } else {
4208               window.Printf("%*s", window_width - window.GetCursorX() - 1, "");
4209             }
4210           }
4211           if (highlight_attr)
4212             window.AttributeOff(highlight_attr);
4213         }
4214       }
4215     }
4216     return true; // Drawing handled
4217   }
4218 
4219   size_t GetNumLines() {
4220     size_t num_lines = GetNumSourceLines();
4221     if (num_lines == 0)
4222       num_lines = GetNumDisassemblyLines();
4223     return num_lines;
4224   }
4225 
4226   size_t GetNumSourceLines() const {
4227     if (m_file_sp)
4228       return m_file_sp->GetNumLines();
4229     return 0;
4230   }
4231 
4232   size_t GetNumDisassemblyLines() const {
4233     if (m_disassembly_sp)
4234       return m_disassembly_sp->GetInstructionList().GetSize();
4235     return 0;
4236   }
4237 
4238   HandleCharResult WindowDelegateHandleChar(Window &window, int c) override {
4239     const uint32_t num_visible_lines = NumVisibleLines();
4240     const size_t num_lines = GetNumLines();
4241 
4242     switch (c) {
4243     case ',':
4244     case KEY_PPAGE:
4245       // Page up key
4246       if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines)
4247         m_first_visible_line -= num_visible_lines;
4248       else
4249         m_first_visible_line = 0;
4250       m_selected_line = m_first_visible_line;
4251       return eKeyHandled;
4252 
4253     case '.':
4254     case KEY_NPAGE:
4255       // Page down key
4256       {
4257         if (m_first_visible_line + num_visible_lines < num_lines)
4258           m_first_visible_line += num_visible_lines;
4259         else if (num_lines < num_visible_lines)
4260           m_first_visible_line = 0;
4261         else
4262           m_first_visible_line = num_lines - num_visible_lines;
4263         m_selected_line = m_first_visible_line;
4264       }
4265       return eKeyHandled;
4266 
4267     case KEY_UP:
4268       if (m_selected_line > 0) {
4269         m_selected_line--;
4270         if (static_cast<size_t>(m_first_visible_line) > m_selected_line)
4271           m_first_visible_line = m_selected_line;
4272       }
4273       return eKeyHandled;
4274 
4275     case KEY_DOWN:
4276       if (m_selected_line + 1 < num_lines) {
4277         m_selected_line++;
4278         if (m_first_visible_line + num_visible_lines < m_selected_line)
4279           m_first_visible_line++;
4280       }
4281       return eKeyHandled;
4282 
4283     case '\r':
4284     case '\n':
4285     case KEY_ENTER:
4286       // Set a breakpoint and run to the line using a one shot breakpoint
4287       if (GetNumSourceLines() > 0) {
4288         ExecutionContext exe_ctx =
4289             m_debugger.GetCommandInterpreter().GetExecutionContext();
4290         if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) {
4291           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
4292               nullptr, // Don't limit the breakpoint to certain modules
4293               m_file_sp->GetFileSpec(), // Source file
4294               m_selected_line +
4295                   1, // Source line number (m_selected_line is zero based)
4296               0,     // Unspecified column.
4297               0,     // No offset
4298               eLazyBoolCalculate,  // Check inlines using global setting
4299               eLazyBoolCalculate,  // Skip prologue using global setting,
4300               false,               // internal
4301               false,               // request_hardware
4302               eLazyBoolCalculate); // move_to_nearest_code
4303           // Make breakpoint one shot
4304           bp_sp->GetOptions()->SetOneShot(true);
4305           exe_ctx.GetProcessRef().Resume();
4306         }
4307       } else if (m_selected_line < GetNumDisassemblyLines()) {
4308         const Instruction *inst = m_disassembly_sp->GetInstructionList()
4309                                       .GetInstructionAtIndex(m_selected_line)
4310                                       .get();
4311         ExecutionContext exe_ctx =
4312             m_debugger.GetCommandInterpreter().GetExecutionContext();
4313         if (exe_ctx.HasTargetScope()) {
4314           Address addr = inst->GetAddress();
4315           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
4316               addr,   // lldb_private::Address
4317               false,  // internal
4318               false); // request_hardware
4319           // Make breakpoint one shot
4320           bp_sp->GetOptions()->SetOneShot(true);
4321           exe_ctx.GetProcessRef().Resume();
4322         }
4323       }
4324       return eKeyHandled;
4325 
4326     case 'b': // 'b' == toggle breakpoint on currently selected line
4327       if (m_selected_line < GetNumSourceLines()) {
4328         ExecutionContext exe_ctx =
4329             m_debugger.GetCommandInterpreter().GetExecutionContext();
4330         if (exe_ctx.HasTargetScope()) {
4331           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
4332               nullptr, // Don't limit the breakpoint to certain modules
4333               m_file_sp->GetFileSpec(), // Source file
4334               m_selected_line +
4335                   1, // Source line number (m_selected_line is zero based)
4336               0,     // No column specified.
4337               0,     // No offset
4338               eLazyBoolCalculate,  // Check inlines using global setting
4339               eLazyBoolCalculate,  // Skip prologue using global setting,
4340               false,               // internal
4341               false,               // request_hardware
4342               eLazyBoolCalculate); // move_to_nearest_code
4343         }
4344       } else if (m_selected_line < GetNumDisassemblyLines()) {
4345         const Instruction *inst = m_disassembly_sp->GetInstructionList()
4346                                       .GetInstructionAtIndex(m_selected_line)
4347                                       .get();
4348         ExecutionContext exe_ctx =
4349             m_debugger.GetCommandInterpreter().GetExecutionContext();
4350         if (exe_ctx.HasTargetScope()) {
4351           Address addr = inst->GetAddress();
4352           BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint(
4353               addr,   // lldb_private::Address
4354               false,  // internal
4355               false); // request_hardware
4356         }
4357       }
4358       return eKeyHandled;
4359 
4360     case 'd': // 'd' == detach and let run
4361     case 'D': // 'D' == detach and keep stopped
4362     {
4363       ExecutionContext exe_ctx =
4364           m_debugger.GetCommandInterpreter().GetExecutionContext();
4365       if (exe_ctx.HasProcessScope())
4366         exe_ctx.GetProcessRef().Detach(c == 'D');
4367     }
4368       return eKeyHandled;
4369 
4370     case 'k':
4371       // 'k' == kill
4372       {
4373         ExecutionContext exe_ctx =
4374             m_debugger.GetCommandInterpreter().GetExecutionContext();
4375         if (exe_ctx.HasProcessScope())
4376           exe_ctx.GetProcessRef().Destroy(false);
4377       }
4378       return eKeyHandled;
4379 
4380     case 'c':
4381       // 'c' == continue
4382       {
4383         ExecutionContext exe_ctx =
4384             m_debugger.GetCommandInterpreter().GetExecutionContext();
4385         if (exe_ctx.HasProcessScope())
4386           exe_ctx.GetProcessRef().Resume();
4387       }
4388       return eKeyHandled;
4389 
4390     case 'o':
4391       // 'o' == step out
4392       {
4393         ExecutionContext exe_ctx =
4394             m_debugger.GetCommandInterpreter().GetExecutionContext();
4395         if (exe_ctx.HasThreadScope() &&
4396             StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4397           exe_ctx.GetThreadRef().StepOut();
4398         }
4399       }
4400       return eKeyHandled;
4401 
4402     case 'n': // 'n' == step over
4403     case 'N': // 'N' == step over instruction
4404     {
4405       ExecutionContext exe_ctx =
4406           m_debugger.GetCommandInterpreter().GetExecutionContext();
4407       if (exe_ctx.HasThreadScope() &&
4408           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4409         bool source_step = (c == 'n');
4410         exe_ctx.GetThreadRef().StepOver(source_step);
4411       }
4412     }
4413       return eKeyHandled;
4414 
4415     case 's': // 's' == step into
4416     case 'S': // 'S' == step into instruction
4417     {
4418       ExecutionContext exe_ctx =
4419           m_debugger.GetCommandInterpreter().GetExecutionContext();
4420       if (exe_ctx.HasThreadScope() &&
4421           StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) {
4422         bool source_step = (c == 's');
4423         exe_ctx.GetThreadRef().StepIn(source_step);
4424       }
4425     }
4426       return eKeyHandled;
4427 
4428     case 'h':
4429       window.CreateHelpSubwindow();
4430       return eKeyHandled;
4431 
4432     default:
4433       break;
4434     }
4435     return eKeyNotHandled;
4436   }
4437 
4438 protected:
4439   typedef std::set<uint32_t> BreakpointLines;
4440   typedef std::set<lldb::addr_t> BreakpointAddrs;
4441 
4442   Debugger &m_debugger;
4443   SymbolContext m_sc;
4444   SourceManager::FileSP m_file_sp;
4445   SymbolContextScope *m_disassembly_scope;
4446   lldb::DisassemblerSP m_disassembly_sp;
4447   AddressRange m_disassembly_range;
4448   StreamString m_title;
4449   lldb::user_id_t m_tid;
4450   int m_line_width;
4451   uint32_t m_selected_line; // The selected line
4452   uint32_t m_pc_line;       // The line with the PC
4453   uint32_t m_stop_id;
4454   uint32_t m_frame_idx;
4455   int m_first_visible_line;
4456   int m_min_x;
4457   int m_min_y;
4458   int m_max_x;
4459   int m_max_y;
4460 };
4461 
4462 DisplayOptions ValueObjectListDelegate::g_options = {true};
4463 
4464 IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger)
4465     : IOHandler(debugger, IOHandler::Type::Curses) {}
4466 
4467 void IOHandlerCursesGUI::Activate() {
4468   IOHandler::Activate();
4469   if (!m_app_ap) {
4470     m_app_ap.reset(new Application(GetInputFILE(), GetOutputFILE()));
4471 
4472     // This is both a window and a menu delegate
4473     std::shared_ptr<ApplicationDelegate> app_delegate_sp(
4474         new ApplicationDelegate(*m_app_ap, m_debugger));
4475 
4476     MenuDelegateSP app_menu_delegate_sp =
4477         std::static_pointer_cast<MenuDelegate>(app_delegate_sp);
4478     MenuSP lldb_menu_sp(
4479         new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB));
4480     MenuSP exit_menuitem_sp(
4481         new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit));
4482     exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit);
4483     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(
4484         "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout)));
4485     lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4486     lldb_menu_sp->AddSubmenu(exit_menuitem_sp);
4487 
4488     MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2),
4489                                    ApplicationDelegate::eMenuID_Target));
4490     target_menu_sp->AddSubmenu(MenuSP(new Menu(
4491         "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate)));
4492     target_menu_sp->AddSubmenu(MenuSP(new Menu(
4493         "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete)));
4494 
4495     MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3),
4496                                     ApplicationDelegate::eMenuID_Process));
4497     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4498         "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach)));
4499     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4500         "Detach", nullptr, 'd', ApplicationDelegate::eMenuID_ProcessDetach)));
4501     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4502         "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch)));
4503     process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator)));
4504     process_menu_sp->AddSubmenu(
4505         MenuSP(new Menu("Continue", nullptr, 'c',
4506                         ApplicationDelegate::eMenuID_ProcessContinue)));
4507     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4508         "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt)));
4509     process_menu_sp->AddSubmenu(MenuSP(new Menu(
4510         "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill)));
4511 
4512     MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4),
4513                                    ApplicationDelegate::eMenuID_Thread));
4514     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4515         "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn)));
4516     thread_menu_sp->AddSubmenu(
4517         MenuSP(new Menu("Step Over", nullptr, 'v',
4518                         ApplicationDelegate::eMenuID_ThreadStepOver)));
4519     thread_menu_sp->AddSubmenu(MenuSP(new Menu(
4520         "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut)));
4521 
4522     MenuSP view_menu_sp(
4523         new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View));
4524     view_menu_sp->AddSubmenu(
4525         MenuSP(new Menu("Backtrace", nullptr, 'b',
4526                         ApplicationDelegate::eMenuID_ViewBacktrace)));
4527     view_menu_sp->AddSubmenu(
4528         MenuSP(new Menu("Registers", nullptr, 'r',
4529                         ApplicationDelegate::eMenuID_ViewRegisters)));
4530     view_menu_sp->AddSubmenu(MenuSP(new Menu(
4531         "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource)));
4532     view_menu_sp->AddSubmenu(
4533         MenuSP(new Menu("Variables", nullptr, 'v',
4534                         ApplicationDelegate::eMenuID_ViewVariables)));
4535 
4536     MenuSP help_menu_sp(
4537         new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help));
4538     help_menu_sp->AddSubmenu(MenuSP(new Menu(
4539         "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp)));
4540 
4541     m_app_ap->Initialize();
4542     WindowSP &main_window_sp = m_app_ap->GetMainWindow();
4543 
4544     MenuSP menubar_sp(new Menu(Menu::Type::Bar));
4545     menubar_sp->AddSubmenu(lldb_menu_sp);
4546     menubar_sp->AddSubmenu(target_menu_sp);
4547     menubar_sp->AddSubmenu(process_menu_sp);
4548     menubar_sp->AddSubmenu(thread_menu_sp);
4549     menubar_sp->AddSubmenu(view_menu_sp);
4550     menubar_sp->AddSubmenu(help_menu_sp);
4551     menubar_sp->SetDelegate(app_menu_delegate_sp);
4552 
4553     Rect content_bounds = main_window_sp->GetFrame();
4554     Rect menubar_bounds = content_bounds.MakeMenuBar();
4555     Rect status_bounds = content_bounds.MakeStatusBar();
4556     Rect source_bounds;
4557     Rect variables_bounds;
4558     Rect threads_bounds;
4559     Rect source_variables_bounds;
4560     content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds,
4561                                            threads_bounds);
4562     source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds,
4563                                                       variables_bounds);
4564 
4565     WindowSP menubar_window_sp =
4566         main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false);
4567     // Let the menubar get keys if the active window doesn't handle the keys
4568     // that are typed so it can respond to menubar key presses.
4569     menubar_window_sp->SetCanBeActive(
4570         false); // Don't let the menubar become the active window
4571     menubar_window_sp->SetDelegate(menubar_sp);
4572 
4573     WindowSP source_window_sp(
4574         main_window_sp->CreateSubWindow("Source", source_bounds, true));
4575     WindowSP variables_window_sp(
4576         main_window_sp->CreateSubWindow("Variables", variables_bounds, false));
4577     WindowSP threads_window_sp(
4578         main_window_sp->CreateSubWindow("Threads", threads_bounds, false));
4579     WindowSP status_window_sp(
4580         main_window_sp->CreateSubWindow("Status", status_bounds, false));
4581     status_window_sp->SetCanBeActive(
4582         false); // Don't let the status bar become the active window
4583     main_window_sp->SetDelegate(
4584         std::static_pointer_cast<WindowDelegate>(app_delegate_sp));
4585     source_window_sp->SetDelegate(
4586         WindowDelegateSP(new SourceFileWindowDelegate(m_debugger)));
4587     variables_window_sp->SetDelegate(
4588         WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger)));
4589     TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger));
4590     threads_window_sp->SetDelegate(WindowDelegateSP(
4591         new TreeWindowDelegate(m_debugger, thread_delegate_sp)));
4592     status_window_sp->SetDelegate(
4593         WindowDelegateSP(new StatusBarWindowDelegate(m_debugger)));
4594 
4595     // Show the main help window once the first time the curses GUI is launched
4596     static bool g_showed_help = false;
4597     if (!g_showed_help) {
4598       g_showed_help = true;
4599       main_window_sp->CreateHelpSubwindow();
4600     }
4601 
4602     init_pair(1, COLOR_WHITE, COLOR_BLUE);
4603     init_pair(2, COLOR_BLACK, COLOR_WHITE);
4604     init_pair(3, COLOR_MAGENTA, COLOR_WHITE);
4605     init_pair(4, COLOR_MAGENTA, COLOR_BLACK);
4606     init_pair(5, COLOR_RED, COLOR_BLACK);
4607   }
4608 }
4609 
4610 void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); }
4611 
4612 void IOHandlerCursesGUI::Run() {
4613   m_app_ap->Run(m_debugger);
4614   SetIsDone(true);
4615 }
4616 
4617 IOHandlerCursesGUI::~IOHandlerCursesGUI() = default;
4618 
4619 void IOHandlerCursesGUI::Cancel() {}
4620 
4621 bool IOHandlerCursesGUI::Interrupt() { return false; }
4622 
4623 void IOHandlerCursesGUI::GotEOF() {}
4624 
4625 #endif // LLDB_DISABLE_CURSES
4626