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

Last change on this file since 10183 was 10183, checked in by tbretz, 9 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.