Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | package CommandTerm::Rl; |
2 | ||
3 | require 5.004; | |
4 | use strict; | |
5 | use Term::ReadLine; | |
6 | use Term::ANSIColor; | |
7 | use FileHandle; | |
8 | use POSIX; | |
9 | use CommandTerm; | |
10 | ||
11 | use vars qw( | |
12 | @ISA | |
13 | %DerivedConfig | |
14 | $TABDLY | |
15 | ); | |
16 | ||
17 | @ISA = qw(CommandTerm); | |
18 | ||
19 | BEGIN { | |
20 | %DerivedConfig = ( | |
21 | 'dataFace' => 'clear', | |
22 | 'statusFace' => 'green', | |
23 | 'errorFace' => 'red', | |
24 | ); | |
25 | $TABDLY = 0x00001800; | |
26 | } | |
27 | ||
28 | sub New { | |
29 | my ($class, $name, $cfgfile, $app_config) = @_; | |
30 | my $self = {}; | |
31 | ||
32 | bless $self, $class; | |
33 | ||
34 | $self->InitSignalHandlers; | |
35 | ||
36 | $self->Init($name, $cfgfile, $app_config, \%DerivedConfig); | |
37 | ||
38 | $self->{'Termios'} = undef; | |
39 | $self->{'OrigTermios'} = undef; | |
40 | $self->{'ReadFD'} = ''; | |
41 | $self->{'WriteFD'} = ''; | |
42 | $self->{'ReadCallbacks'} = {}; | |
43 | $self->{'WriteCallbacks'} = {}; | |
44 | $self->{'GotInterrupt'} = 0; | |
45 | ||
46 | $self->InitTerminal; | |
47 | $self->InitReadLine; | |
48 | ||
49 | return $self; | |
50 | } | |
51 | ||
52 | sub MainLoop { | |
53 | my ($self) = @_; | |
54 | ||
55 | my ($fileno, $fd_read, $fd_write); | |
56 | ||
57 | while( 1 ) { | |
58 | ||
59 | if( $self->GotInterrupt ) { | |
60 | $self->HandleInterrupt; | |
61 | $self->GotInterrupt(0); | |
62 | } | |
63 | ||
64 | if( $self->UpdatePrompt ) { | |
65 | $self->CloseRedirection; | |
66 | $self->fileevent( | |
67 | *STDIN, | |
68 | 'readable' => sub{ $self->ReadLine->callback_read_char; } | |
69 | ); | |
70 | $self->ReadLine->CallbackHandlerInstall( | |
71 | $self->GetPrompt, | |
72 | sub { $self->ProcessLine(@_); } | |
73 | ); | |
74 | $self->UpdatePrompt(0); | |
75 | } | |
76 | ||
77 | next if select($fd_read = $self->ReadFD, $fd_write = $self->WriteFD, undef, 1.0) < 0; | |
78 | ||
79 | foreach $fileno (keys %{ $self->ReadCallbacks }) { | |
80 | &{ $self->{'ReadCallbacks'}{$fileno} } if vec($fd_read, $fileno, 1); | |
81 | } | |
82 | ||
83 | foreach $fileno (keys %{ $self->WriteCallbacks }) { | |
84 | &{ $self->{'WriteCallbacks'}{$fileno} } if vec($fd_write, $fileno, 1); | |
85 | } | |
86 | ||
87 | } | |
88 | } | |
89 | ||
90 | sub Print { | |
91 | my ($self, $how, @what) = @_; | |
92 | my $what = join '', @what; | |
93 | ||
94 | my $LOGFILE = $self->LogFH; | |
95 | my $ReDirFH = $self->ReDirFH; | |
96 | ||
97 | print $LOGFILE $what if defined($LOGFILE); | |
98 | ||
99 | if( $ReDirFH ) { | |
100 | print { $ReDirFH } $what; | |
101 | } elsif( $ENV{'TERM'} and $ENV{'TERM'} ne 'vt100' ) { | |
102 | print colored($what, $how); | |
103 | } else { | |
104 | print $what; | |
105 | } | |
106 | } | |
107 | ||
108 | sub Title { | |
109 | my ($self, $titlestr) = @_; | |
110 | ||
111 | print STDOUT "\e]2;$titlestr\cg"; | |
112 | } | |
113 | ||
114 | sub Icon { | |
115 | my ($self, $iconstr) = @_; | |
116 | ||
117 | print STDOUT "\e]1;$iconstr\cg"; | |
118 | } | |
119 | ||
120 | sub clear { | |
121 | my ($self) = @_; | |
122 | ||
123 | $self->ReadLine->CallbackHandlerInstall('', sub {}); | |
124 | $self->ReadLine->call_function('clear-screen'); | |
125 | $self->Attribs->{'erase_empty_line'} = 1; | |
126 | $self->ReadLine->stuff_char(ord("\n")); | |
127 | $self->ReadLine->callback_read_char; | |
128 | $self->Attribs->{'erase_empty_line'} = 0; | |
129 | } | |
130 | ||
131 | sub InitSignalHandlers { | |
132 | my ($self) = @_; | |
133 | ||
134 | $self->SUPER::InitSignalHandlers; | |
135 | $SIG{'INT'} = sub { $self->GotInterrupt(1); }; | |
136 | } | |
137 | ||
138 | sub InitTerminal { | |
139 | my ($self) = @_; | |
140 | ||
141 | STDOUT->autoflush(1); | |
142 | ||
143 | my $Termios = POSIX::Termios->new; | |
144 | my $OrigTermios = POSIX::Termios->new; | |
145 | ||
146 | if( not $Termios->getattr(STDIN_FILENO) ) { | |
147 | $self->PrintError("Error in getattr for STDIN(", STDIN_FILENO, "): $!\n "); | |
148 | $self->PrintError("Weird terminal behavior may occur.\n"); | |
149 | return; | |
150 | } | |
151 | ||
152 | $OrigTermios->getattr(STDIN_FILENO); | |
153 | ||
154 | $Termios->setlflag(($Termios->getlflag | ISIG) & ~ICANON); | |
155 | $Termios->setoflag($Termios->getoflag & ~$TABDLY); | |
156 | $Termios->setcc(VTIME, 1); | |
157 | $Termios->setcc(VINTR, 3); | |
158 | if( not $Termios->setattr(STDIN_FILENO, TCSANOW) ) { | |
159 | $self->PrintError("Error in setattr for STDIN(", STDIN_FILENO, "): $!\n "); | |
160 | $self->PrintError("Weird terminal behavior may occur.\n"); | |
161 | $Termios = undef; | |
162 | } | |
163 | ||
164 | $self->Termios($Termios); | |
165 | $self->OrigTermios($OrigTermios); | |
166 | } | |
167 | ||
168 | sub InitReadLine { | |
169 | my ($self) = @_; | |
170 | ||
171 | $self->SUPER::InitReadLine; | |
172 | ||
173 | if( $ENV{'TERM'} and $ENV{'TERM'} eq 'vt100' ) { | |
174 | # turn off all ornaments if the terminal can't handle it | |
175 | $self->ReadLine->ornaments(0); | |
176 | } else { | |
177 | # else set prompt to bold | |
178 | $self->ReadLine->ornaments('md,me,,'); | |
179 | } | |
180 | } | |
181 | ||
182 | sub TogglePrompt { | |
183 | my ($self) = @_; | |
184 | ||
185 | &{ $self->TogglePromptCallback }; | |
186 | ||
187 | my $line = $self->Attribs->{'line_buffer'}; | |
188 | ||
189 | # This bit of magic just erases the current line, prompt included. | |
190 | $self->ReadLine->CallbackHandlerInstall('', sub {}); | |
191 | $self->Attribs->{'erase_empty_line'} = 1; | |
192 | $self->ReadLine->stuff_char(ord("\n")); | |
193 | $self->ReadLine->callback_read_char; | |
194 | $self->Attribs->{'erase_empty_line'} = 0; | |
195 | ||
196 | # Install a new prompt and restore the old line | |
197 | $self->ReadLine->CallbackHandlerInstall($self->GetPrompt, sub { $self->ProcessLine(@_); }); | |
198 | $self->ReadLine->insert_text($line); | |
199 | } | |
200 | ||
201 | sub HandleInterrupt { | |
202 | my ($self) = @_; | |
203 | my $ReadLine = $self->ReadLine; | |
204 | my $Attribs = $self->Attribs; | |
205 | ||
206 | if( $Attribs and $Attribs->{'end'} != 0 ) { | |
207 | $ReadLine->modifying; | |
208 | $ReadLine->delete_text; | |
209 | $Attribs->{'point'} = $Attribs->{'end'} = 0; | |
210 | $ReadLine->redisplay; | |
211 | } else { | |
212 | &{ $self->HandleInterruptCallback }; | |
213 | } | |
214 | } | |
215 | ||
216 | sub DefaultInterruptCallback { | |
217 | my ($self) = @_; | |
218 | ||
219 | print STDOUT "\n"; | |
220 | $self->UpdatePrompt(1); | |
221 | } | |
222 | ||
223 | sub fileevent { | |
224 | my ($self, $fh, $readwrite, $func) = @_; | |
225 | ||
226 | return unless defined($fh) and defined($readwrite); | |
227 | ||
228 | my $fileno = fileno($fh); | |
229 | ||
230 | if( $readwrite eq 'readable' ) { | |
231 | if( defined($func) and ref($func) eq 'CODE' ) { | |
232 | vec($self->{'ReadFD'}, $fileno, 1) = 1; | |
233 | $self->{'ReadCallbacks'}{$fileno} = $func; | |
234 | } else { | |
235 | vec($self->{'ReadFD'}, $fileno, 1) = 0; | |
236 | delete $self->{'ReadCallbacks'}{$fileno} | |
237 | } | |
238 | } elsif( $readwrite eq 'writable' ) { | |
239 | if( defined($func) and ref($func) eq 'CODE' ) { | |
240 | vec($self->{'WriteFD'}, $fileno, 1) = 1; | |
241 | $self->{'WriteCallbacks'}{$fileno} = $func; | |
242 | } else { | |
243 | vec($self->{'WriteFD'}, $fileno, 1) = 0; | |
244 | delete $self->{'WriteCallbacks'}{$fileno}; | |
245 | } | |
246 | } else { | |
247 | $self->PrintError("Bad mode to fileevent: '$readwrite'\n"); | |
248 | } | |
249 | } | |
250 | ||
251 | sub Shutdown { | |
252 | my ($self) = @_; | |
253 | ||
254 | $self->OrigTermios->setattr(STDIN_FILENO, TCSANOW) if $self->OrigTermios; | |
255 | } | |
256 | ||
257 | sub ProcessLine { | |
258 | my ($self, $line) = @_; | |
259 | my $ReadLine = $self->ReadLine; | |
260 | my $Attribs = $self->Attribs; | |
261 | ||
262 | # User typed Ctrl-D | |
263 | unless( defined($line) ) { | |
264 | print STDOUT "\n"; | |
265 | $self->quit; | |
266 | } | |
267 | ||
268 | $ReadLine->delete_text; | |
269 | $Attribs->{'point'} = $Attribs->{'end'} = 0; | |
270 | ||
271 | $self->UpdatePrompt(1); | |
272 | $self->fileevent(*STDIN, 'readable' => ''); | |
273 | $ReadLine->callback_handler_remove; | |
274 | ||
275 | $self->SUPER::ProcessLine($line); | |
276 | } | |
277 | ||
278 | 1; |