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