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