source: trunk/FACT++/src/Shell.cc@ 10252

Last change on this file since 10252 was 10183, checked in by tbretz, 10 years ago
New import.
File size: 19.3 KB
Line 
1// **************************************************************************
2/** @class Shell
3
4@brief Implementation of a console based user shell with an input and output window
5
6Shell is based on the ReadlineWindow class. It creates two windows
7(panels) using ncurses which are used for input and output. The output
8window panel is displayed on top of the input panel, but can be hidden
9for example by pressing F1.
10
11The idea is that continous messages like logging messages do not interfere
12with the input area, although one still has both diplayed in the same
13console window.
14
15To get a list of the command and functions supported by Shell
16type 'h' or 'help' at a command line prompt.
17
18The usage is quite simple. Instantiate an object of Shell with the
19programname as an argument. For its meaning see the base class
20documentation Readline::Readline(). The created input- and output-
21stream can be accessed through GetStreamIn() and GetStreamOut()
22whihc are both ostreams, one redirected to the input window and the
23other one redirected to the output window. Especially, GetStreamIn()
24should not be used while the Readline prompt is in progress, but can for
25example be used to display errors about what was entered.
26
27The recommanded way of usage is:
28
29\code
30
31 static Shell shell("myprog"); // readline will read the myprog.his history file
32
33 while (1)
34 {
35 string txt = shell.Prompt("prompt> ");
36 if (txt=="quit)
37 break;
38
39 // ... do something ...
40
41 shell.AddHistory(txt);
42 }
43
44 // On destruction redline will write the current history to the file
45 // By declaring the Shell static the correct terminal is restored even
46 // the program got killed (not if killed with SIGABRT)
47
48\endcode
49
50If for some reason the terminal is not correctly restored type (maybe blindly)
51<I>reset</I> in your console. This should restor everything back to normal.
52
53@section References
54
55 - <A HREF="http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html">GNU Readline</A>
56 - <A HREF="http://www.gnu.org/software/ncurses">GNU Ncurses</A>
57
58
59@todo
60 - Introduce the possibility to scroll in both windows
61 - Add redisplay functionality for both panels if the console was resized
62
63*/
64// **************************************************************************
65#include "Shell.h"
66
67#include <fstream>
68#include <iostream>
69
70#include <signal.h> // SIGWINCH
71#include <sys/wait.h> // waitpid
72#include <sys/ioctl.h> // ioctl
73
74#include <panel.h>
75
76#define FORK_ALLOWED
77
78using namespace std;
79
80Shell *Shell::This = 0;
81
82// --------------------------------------------------------------------------
83//
84//! This initializes the Ncurses environment. Since the ncurses environment
85//! is global only one instance of this class is allowed.
86//!
87//! The first 8 color pairs (COLOR_PAIR) are set to the first 8 color
88//! with default background.
89//!
90//! The shells windows and panels are created. And their pointers are
91//! propagated to the two associated WindowLog streams.
92//!
93//! Also some key bindings are initialized.
94//
95Shell::Shell(const char *prgname) : ReadlineWindow(prgname),
96 fPanelHeight(13), fIsVisible(1), fLine(0)
97{
98 if (stdscr!=0)
99 {
100 endwin();
101 cout << "ERROR - Only one instance of class Shell is allowed." << endl;
102 exit(-1);
103 }
104
105 This = this;
106
107 // ---------------------- Setup ncurses -------------------------
108
109 initscr(); // Start curses mode
110
111 cbreak(); // Line buffering disabled, Pass on
112 noecho(); // Switch off echo mode
113 nonl(); // Associate return with CR
114
115 intrflush(stdscr, FALSE);
116 keypad(stdscr, FALSE); // Switch off keymapping for function keys
117
118 start_color(); // Initialize ncurses colors
119 use_default_colors(); // Assign terminal default colors to -1
120 //assume_default_colors(-1, -1); // standard terminal colors assigned to pair 0
121
122 // Setup colors
123 for (int i=1; i<8; i++)
124 init_pair(i, i, -1); // -1: def background
125
126 signal(SIGWINCH, HandleResizeImp); // Attach HandleResize to SIGWINCH signal
127
128 // ---------------------- Setup pansl --------------------------
129
130 // Create the necessary windows
131 WINDOW *wins[4];
132 CreateWindows(wins);
133
134 // Initialize the panels
135 fPanelIn = new_panel(wins[0]);
136 fPanelFrame = new_panel(wins[1]);
137 fPanelOut = new_panel(wins[2]);
138
139 win.SetWindow(wins[0]);
140 wout.SetWindow(wins[2]);
141
142 // Get the panels into the right order for startup
143 ShowHide(1);
144
145 // Setup Readline
146 SetWindow(wins[0]);
147 SetColorPrompt(COLOR_PAIR(COLOR_BLUE));
148
149 // ------------------- Setup key bindings -----------------------
150 BindKeySequence("\033OP", rl_proc_F1); // \033OP
151 BindKeySequence("\033[1;5B", rl_scroll_top); // \033OP
152 BindKeySequence("\033[1;5A", rl_scroll_top); // \033OP
153 BindKeySequence("\033[1;3A", rl_scroll_bot); // \033OP
154 BindKeySequence("\033[1;3B", rl_scroll_bot); // \033OP
155 BindKeySequence("\033[5;3~", rl_top_inc); // \033OP
156 BindKeySequence("\033[6;3~", rl_top_dec); // \033OP
157 BindKeySequence("\033+", rl_top_resize); // \033OP
158 BindKeySequence("\033-", rl_top_resize); // \033OP
159
160 /*
161 rl_bind_keyseq("\033\t", rl_complete); // Meta-Tab
162 rl_bind_keyseq("\033[1~", home); // Home (console)
163 rl_bind_keyseq("\033[H", home); // Home (x)
164 rl_bind_keyseq("\033[4~", end); // End (console)
165 rl_bind_keyseq("\033[F", end); // End (x)
166 rl_bind_keyseq("\033[A", up); // Up
167 rl_bind_keyseq("\033[B", down); // Down
168 rl_bind_keyseq("\033[[B", accept); // F2 (console)
169 rl_bind_keyseq("\033OQ", accept); // F2 (x)
170 rl_bind_keyseq("\033[21~", cancel); // F10
171 */
172
173 // Ctrl+dn: \033[1;5B
174 // Ctrl+up: \033[1;5A
175 // Alt+up: \033[1;3A
176 // Alt+dn: \033[1;3B
177 // Alt+pg up: \033[5;3~
178 // Alt+pg dn: \033[6;3~
179}
180
181// --------------------------------------------------------------------------
182//
183//! Ends the ncurses environment by calling endwin().
184//
185Shell::~Shell()
186{
187 // Maybe not needed because the window is more or less valid until the
188 // object is destructed anyway.
189 //win.SetWindow(0);
190 //wout.SetWindow(0);
191 //SetWindow(0);
192
193 endwin();
194 cout << "The end." << endl;
195}
196
197// --------------------------------------------------------------------------
198//
199//! This function gets the windows into the expected order which is:
200//!
201//! @param v
202//! - \b 0: Do not show the output panel
203//! - \b 1: Show the output panel
204//! - \b -1: Toggle the visibility of the output panel
205//! - \b -2: Just update the panels, do not change their visibility
206//
207void Shell::ShowHide(int v)
208{
209 if (v>-2)
210 fIsVisible = v==-1 ? !fIsVisible : v;
211
212 if (fIsVisible)
213 {
214 show_panel(fPanelIn);
215 show_panel(fPanelFrame);
216 show_panel(fPanelOut);
217 }
218 else
219 {
220 show_panel(fPanelIn);
221 hide_panel(fPanelFrame);
222 hide_panel(fPanelOut);
223 }
224
225 update_panels();
226 doupdate();
227}
228
229
230// --------------------------------------------------------------------------
231//
232//! Creates the windows to be used as panels and draws a frame around one
233//!
234//! @param w
235//! pointers to the three windows which have been created are returned
236//!
237//! @param all
238//! If all is false do not (re-)create the bottom or input-window
239//
240void Shell::CreateWindows(WINDOW *w[3], int all)
241{
242 int maxx, maxy;
243 getmaxyx(stdscr, maxy, maxx);
244
245 int separator = maxy-fPanelHeight;
246
247 WINDOW *new_in = all ? newwin(maxy, maxx, 0, 0) : 0;
248 WINDOW *new_frame = newwin(separator-1, maxx, 0, 0);
249 WINDOW *new_out = newwin(separator-1-2, maxx-2, 1, 1);
250
251 box(new_frame, 0,0);
252 wmove(new_frame, 0, 1);
253 waddch(new_frame, ACS_RTEE);
254 wprintw(new_frame, " F1 ");
255 waddch(new_frame, ACS_LTEE);
256
257 scrollok(new_out, true);
258 leaveok (new_out, true);
259
260 if (new_in)
261 {
262 scrollok(new_in, true); // Allow scrolling
263 leaveok (new_in, false); // Move the cursor with the output
264
265 wmove(new_in, maxy-1, 0);
266 }
267
268 w[0] = new_in;
269 w[1] = new_frame;
270 w[2] = new_out;
271}
272
273// --------------------------------------------------------------------------
274//
275//! Key binding for F1. Toggles upper panel by calling ShowHide(-1)
276//
277int Shell::rl_proc_F1(int /*cnt*/, int /*key*/)
278{
279 This->ShowHide(-1); // toggle
280 return 0;
281}
282
283int Shell::rl_scroll_top(int, int key)
284{
285 This->win << "Scroll " << key << endl;
286 return 0;
287}
288
289int Shell::rl_scroll_bot(int, int key)
290{
291 This->win << "Scroll " << key << endl;
292 return 0;
293}
294
295int Shell::rl_top_inc(int, int key)
296{
297 This->win << "Increase " << key << endl;
298 return 0;
299}
300
301int Shell::rl_top_dec(int, int key)
302{
303 This->win << "Increase " << key << endl;
304 return 0;
305}
306
307int Shell::rl_top_resize(int, int key)
308{
309 This->Resize(key=='+' ? This->fPanelHeight-1 : This->fPanelHeight+1);
310 return 0;
311}
312
313
314// --------------------------------------------------------------------------
315//
316//! Signal handler for SIGWINCH, calls HandleResize
317//
318void Shell::HandleResizeImp(int)
319{
320 This->HandleResize();
321}
322
323// --------------------------------------------------------------------------
324//
325//! Signal handler for SIGWINCH. It resizes the terminal and all panels
326//! according to the new terminal size and redisplay the backlog buffer
327//! in both windows
328//!
329//! @todo
330//! Maybe there are more efficient ways than to display the whole buffers
331//
332void Shell::HandleResize()
333{
334 // Get the new terminal size
335 struct winsize w;
336 ioctl(0, TIOCGWINSZ, &w);
337
338 // propagate it to the terminal
339 resize_term(w.ws_row, w.ws_col);
340
341 // Store the pointer to the old windows
342 WINDOW *w_in = panel_window(fPanelIn);
343 WINDOW *w_frame = panel_window(fPanelFrame);
344 WINDOW *w_out = panel_window(fPanelOut);
345
346 // Create new windows
347 WINDOW *wins[3];
348 CreateWindows(wins);
349
350 // Redirect the streams and the readline output to the new windows
351 win.SetWindow(wins[0]);
352 wout.SetWindow(wins[2]);
353
354 SetWindow(wins[0]);
355
356 // Replace windows in the panels
357 replace_panel(fPanelIn, wins[0]);
358 replace_panel(fPanelFrame, wins[1]);
359 replace_panel(fPanelOut, wins[2]);
360
361 // delete the old obsolete windows
362 delwin(w_in);
363 delwin(w_out);
364 delwin(w_frame);
365
366 // FIXME: NEEDED also in Redisplay panel
367 //Redisplay();
368
369 // Redisplay their contents
370 win.Display();
371 wout.Display();
372}
373
374// --------------------------------------------------------------------------
375//
376//! This resized the top panel or output panel as requested by the argument.
377//! The argument is the number of lines which are kept free for the input
378//! panel below the top panel
379//!
380//! @returns
381//! always true
382//
383bool Shell::Resize(int h)
384{
385 // Get curretn terminal size
386 int lines, cols;
387 getmaxyx(stdscr, lines, cols);
388
389 // Check if we are in a valid range
390 if (h<1 || h>lines-5)
391 return false;
392
393 // Set new height for panel to be kept free
394 fPanelHeight = h;
395
396 // Store the pointers of the old windows associated with the panels
397 // which should be resized
398 WINDOW *w_frame = panel_window(fPanelFrame);
399 WINDOW *w_out = panel_window(fPanelOut);
400
401 // Create new windows
402 WINDOW *wins[3];
403 CreateWindows(wins, false);
404
405 // Redirect the output stream to the new window
406 wout.SetWindow(wins[2]);
407
408 // Replace the windows associated with the panels
409 replace_panel(fPanelFrame, wins[1]);
410 replace_panel(fPanelOut, wins[2]);
411
412 // delete the ols windows
413 delwin(w_out);
414 delwin(w_frame);
415
416 // FIXME: NEEDED also in Redisplay panel
417 //Redisplay();
418
419 // Redisplay the contents
420 wout.Display();
421
422 return true;
423}
424
425// --------------------------------------------------------------------------
426//
427//! This wrapt the given readline function into a redirection to a file,
428//! which contents is then displayed afterwards in the input panel.
429//!
430//! For convinience the given title is printed in bold before the list.
431//!
432//! This allows to show some readline output in the panel.
433//!
434//! @param function
435//! Takes a function of type bool(*)() as argument
436//!
437//! @param title
438//! A title streamed in bold before the output starts
439//!
440//! @returns
441//! The return value of the function
442//
443bool Shell::RedirectionWrapper(bool (*function)(), const char *title)
444{
445 FILE *save = SetStreamOut(tmpfile());
446 const bool rc = function();
447 FILE *file = SetStreamOut(save);
448
449 const bool empty = ftell(file)==0;
450
451 rewind(file);
452
453 win << endl;
454 win << kBold << title << endl;
455
456 if (empty)
457 {
458 win << " <empty>" << endl;
459 fclose(file);
460 return rc;
461 }
462
463 while (1)
464 {
465 const int c = getc(file);
466 if (feof(file))
467 break;
468 win << (char)c;
469 }
470 win << endl;
471
472 fclose(file);
473
474 return rc;
475}
476
477// --------------------------------------------------------------------------
478//
479//! Can be overwritten to display user defined commands when the command
480//! c is issued.
481//!
482//! @returns
483//! always true
484//
485bool Shell::PrintCommands()
486{
487 win << endl;
488 win << " " << kUnderline << " Commands:" << endl;
489 win << " No application specific commands defined." << endl;
490 win << endl;
491
492 return true;
493}
494
495// --------------------------------------------------------------------------
496//
497//! Displays the available ncurses attributes, like color.
498//!
499//! @returns
500//! always true
501//
502bool Shell::PrintAttributes()
503{
504 //ostream &win = wout;
505 win << endl;
506 win << " Attributes:" << endl;
507 win << " " << kReset << "kReset" << endl;
508 win << " " << kNormal << "kNormal" << endl;
509 win << " " << kHighlight << "kHighlight" << endl;
510 win << " " << kReverse << "kReverse" << endl;
511 win << " " << kUnderline << "kUnderline" << endl;
512 win << " " << kBlink << "kBlink" << endl;
513 win << " " << kDim << "kDim" << endl;
514 win << " " << kBold << "kBold" << endl;
515 win << " " << kProtect << "kProtect" << endl;
516 win << " " << kInvisible << "kInvisible" << endl;
517 win << " " << kAltCharset << "kAltCharset" << kReset << " (kAltCharset)" << endl;
518 win << endl;
519 win << " Colors:" << endl;
520 win << " " << kDefault << "kDefault " << kBold << "+ kBold" << endl;
521 win << " " << kRed << "kRed " << kBold << "+ kBold" << endl;
522 win << " " << kGreen << "kGreen " << kBold << "+ kBold" << endl;
523 win << " " << kYellow << "kYellow " << kBold << "+ kBold" << endl;
524 win << " " << kBlue << "kBlue " << kBold << "+ kBold" << endl;
525 win << " " << kMagenta << "kMagenta " << kBold << "+ kBold" << endl;
526 win << " " << kCyan << "kCyan " << kBold << "+ kBold" << endl;
527 win << " " << kWhite << "kWhite " << kBold << "+ kBold" << endl;
528 win << " " << endl;
529
530 return true;
531}
532
533// --------------------------------------------------------------------------
534//
535//! Displays the keybindings available due to the Shell class
536//!
537//! @returns
538//! always true
539//!
540//! @todo
541//! Update output
542//
543bool Shell::PrintKeyBindings()
544{
545 win << endl;
546 win << " " << kUnderline << "Key bindings:" << endl;
547 win << kBold << " Page-up " << kReset << "Search backward in history" << endl;
548 win << kBold << " Page-dn " << kReset << "Search forward in history" << endl;
549 win << kBold << " Ctrl-left " << kReset << "One word backward" << endl;
550 win << kBold << " Ctrl-right " << kReset << "One word forward" << endl;
551 win << kBold << " Ctrl-y " << kReset << "Delete line" << endl;
552 win << kBold << " Alt-end/Ctrl-k " << kReset << "Delete until the end of the line" << endl;
553 win << kBold << " F1 " << kReset << "Toggle visibility of upper panel" << endl;
554 win << endl;
555 win << " Default key-bindings are identical with your bash." << endl;
556 win << endl;
557
558 return true;
559}
560
561// --------------------------------------------------------------------------
562//
563//! Print a general help text which also includes the commands pre-defined
564//! by the Shell class.
565//!
566//! @returns
567//! always true
568//!
569//! @todo
570//! Get it up-to-date
571//
572bool Shell::PrintGeneralHelp()
573{
574 win << endl;
575 win << " " << kUnderline << "General help:" << endl;
576 win << kBold << " h,help " << kReset << "Print this help message" << endl;
577 win << kBold << " clear " << kReset << "Clear history buffer" << endl;
578 win << kBold << " lh,history " << kReset << "Dump the history buffer to the screen" << endl;
579 win << kBold << " v,variable " << kReset << "Dump readline variables" << endl;
580 win << kBold << " f,function " << kReset << "Dump readline functions" << endl;
581 win << kBold << " m,funmap " << kReset << "Dump readline funmap" << endl;
582 win << kBold << " c,command " << kReset << "Dump available commands" << endl;
583 win << kBold << " k,keylist " << kReset << "Dump key bindings" << endl;
584 win << kBold << " a,attrs " << kReset << "Dump available stream attributes" << endl;
585 win << kBold << " .q,quit " << kReset << "Quit" << endl;
586 win << endl;
587 win << " The command history is automatically loaded and saves to" << endl;
588 win << " and from " << GetName() << endl;
589 win << endl;
590
591 return true;
592}
593
594
595// --------------------------------------------------------------------------
596//
597//! Processes the command provided by the Shell-class.
598//!
599//! @returns
600//! whether a command was successfully processed or could not be found
601//
602bool Shell::Process(const string &str)
603{
604 // Implement readline commands:
605 // rl set (rl_variable_bind(..))
606 // rl_read_init_file(filename)
607 // int rl_add_defun (const char *name, rl_command_func_t *function, int key)
608
609 // ----------- Readline -----------
610
611 if (str=="lh" || str=="history")
612 return RedirectionWrapper(DumpHistory, "History:");
613
614 if (str=="v" || str=="variable")
615 return RedirectionWrapper(DumpVariables, "Variables:");
616
617 if (str=="f" || str=="function")
618 return RedirectionWrapper(DumpFunctions, "Functions:");
619
620 if (str=="m" || str=="funmap")
621 return RedirectionWrapper(DumpFunmap, "Funmap:");
622
623 if (Readline::Process(str))
624 return true;
625
626 // ------------ ReadlineWindow -------------
627
628 if (str=="a" || str=="attrs")
629 return PrintAttributes();
630
631 // ----------- ReadlineNcurses -----------
632
633 if (string(str)=="hide")
634 {
635 ShowHide(0);
636 return true;
637 }
638 if (string(str)=="show")
639 {
640 ShowHide(1);
641 return true;
642 }
643
644 if (str.substr(0, 7)=="height ")
645 {
646 int h;
647 sscanf(str.c_str()+7, "%d", &h);
648 return Resize(h);
649 }
650
651 if (str=="d")
652 {
653 wout.Display();
654 return true;
655 }
656
657 return false;
658}
659
660// --------------------------------------------------------------------------
661//
662//! Overwrites Shutdown. It's main purpose is to re-output
663//! the prompt and the buffer using the WindowLog stream so that it is
664//! buffered in its backlog.
665//!
666//! @param buf
667//! A pointer to the buffer returned by readline
668//!
669void Shell::Shutdown(const char *buf)
670{
671 ReadlineWindow::Shutdown(buf);
672
673 // Now move the cursor to the start of the prompt
674 RewindCursor();
675
676 // Output the text ourself to get it into the backlog
677 // buffer of win. We cannot use GetBuffer() because rl_end
678 // is not updated finally.
679 win << kBlue << GetPrompt() << kReset << buf << endl;
680}
Note: See TracBrowser for help on using the repository browser.