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