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