ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-ReadLine/ReadLine.pm
Revision: 1.1
Committed: Sat May 26 10:47:26 2012 UTC (11 years, 11 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::ReadLine - simple but versatile readline implementation
4
5 =head1 SYNOPSIS
6
7 use AnyEvent::ReadLine;
8
9 =head1 DESCRIPTION
10
11 This module implements some kind of readline interface - something that
12 allows you to read a (command-) line entered by the user, and allows the
13 user to comfortably (hopefully) edit it.
14
15 Basically, this module implements something like perl's C<readline>
16 function, except potentially with user editing.
17
18 There are a multitude of readline modules and -implementations available
19 for Perl. The difference of this module to others is that it takes pipes,
20 network sockets and other generic file handles into account, in addition
21 to traditional tty interfaces.
22
23 =over 4
24
25 =cut
26
27 package AnyEvent::ReadLine;
28
29 use common::sense;
30 use Scalar::Util ();
31 use Errno ();
32 use Term::Terminfo ();
33 use AnyEvent;
34
35 our $VERSION = '0.0';
36
37 =item $rl = new AnyEvent::ReadLine key => value...
38
39 Creates a new AnyEvent::ReadLine object.
40
41 The key value pairs that are understood are the following:
42
43 =over 4
44
45 =item fh => $fh
46
47 The file handle used to read user input from.
48
49 =item ofh => $fh
50
51 The file handle used to write feedback to - defaults to C<fh>.
52
53 =item mode => "none"|"termios"|"telnet"
54
55 Governs how the terminal is driven. Defaults to C<termios> when
56 C<fh> is connected to a tty, C<none> otherwise.
57
58 =item term => $terminfo
59
60 The terminfo name of the attached terminal - in termios mode, defaults
61 to C<$ENV{TERM}>, otherwise the default is C<ansi> which works in most
62 situations.
63
64 =item prompt => $prompt
65
66 The prompt string to use (default: C<< > >>). C<$prompt> can also be
67 a code reference that returns the prompt, and will be called on every
68 redisplay.
69
70 =item timeout => $seconds
71
72 After how many seconds a key sequence is considered complete. To
73 differentiate between, say, the escape key and typical arrow key sequences
74 a timeout needs to be used. The default is 0.02 in termios mode, and 0.1
75 in all others.
76
77 =item encoding => $encoding
78
79 The encoding of the terminal. Only "utf-8" and "iso-8859-1" are supported
80 at the moment, because Perl apparently has no incremental decoding
81 facilities (Encode can't do it, perlio can't do it).
82
83 The default is utf-8, until I can find out how to detect if a filehandle
84 is in utf-8 mode without resorting to undocumented functionality.
85
86 =item width => $width
87
88 =item height => $height
89
90 The width and height of the screen - these will be updated to the true
91 widht and height if possible.
92
93 =item on_read => $cb->($rl, $string)
94
95 Called each time the user is finished entering a line. The string is in
96 unicode format.
97
98 =item on_eof => $cb->($rl)
99
100 Called when an EOF is detected on the input file handle. Must eventually
101 destroy the object.
102
103 =item on_error => $cb->($rl, "$!")
104
105 Called on any unhandled error that occured. Second argument is a string
106 describing the saituation, C<$!> contains the actual error.
107
108 =item on_change => $cb->($rl, $string, $pos)
109
110 When specified, C<ofh> is ignored - each time the presentation changes,
111 this callback is called with the string to display (including any prompt)
112 and the position of the cursor inside the string.
113
114 The string is in unicode (and thus C<$pos> is a character position) - to
115 output it, you normally must encode it first.
116
117 =item on_key_XXX => $cb->($rl, $key)
118
119 Each time a key XXX in the keymap is entered, this callback will be
120 invoked if it exists. It may want to invoke the original C<key_XXX>
121 method.
122
123 =back
124
125 =cut
126
127 sub new {
128 my $class = shift;
129
130 my $self = bless {
131 prompt => "> ",
132 @_,
133 raw => 0,
134 }, $class;
135
136 $self->{ofh} ||= $self->{fh};
137 $self->{mode} ||= -t $self->{fh} ? "termios" : "none";
138
139 $self->{timeout} ||= $self->{mode} eq "termios" ? 0.02 : 0.1;
140 $self->{encoding} ||= "utf-8";
141
142 $self->set_term ((delete $self->{term}) || ($self->{mode} eq "termios" && $ENV{TERM}) || "ansi");
143
144 {
145 Scalar::Util::weaken (my $self = $self);
146
147 $self->{tcb} = sub {
148 delete $self->{tw};
149 $self->feed;
150 };
151
152 $self->{rcb} = sub {
153 my $len = sysread $self->{fh}, $self->{rbuf}, 1, length $self->{rbuf};
154 if ($len) {
155 $self->{tw} = AE::timer $self->{timeout}, 0, $self->{tcb};
156 } elsif (defined $len) {
157 $self->{on_eof}
158 ? $self->{on_eof}($self)
159 : $self->{on_error}($self, 0, "unexpected end of file");
160 } elsif ($! != Errno::EAGAIN && $! != Errno::EINTR) {
161 $self->{on_error}($self, $!, "$!");
162 }
163 };
164
165 $self->{wcb} = sub {
166 my $len = syswrite $self->{ofh}, $self->{wbuf};
167 substr $self->{wbuf}, 0, $len, "";
168 delete $self->{ww} unless length $self->{wbuf};
169 # todo error handling
170 };
171 }
172
173 $self->enable;
174
175 $self
176 }
177
178 sub DESTROY {
179 my ($self) = @_;
180
181 $self->disable;
182 }
183
184 sub default_keymap {
185 my ($self) = @_;
186
187 my %keymap;
188
189 for (0..31) {
190 $keymap{chr $_ } = "ctrl$_";
191 $keymap{chr $_ + 128} = "hctrl$_";
192 }
193
194 $keymap{"\x03"} = "intr";
195 $keymap{"\x04"} = "eof";
196 $keymap{"\x08"} = "backspace"; # common
197 $keymap{"\x0a"} = "enter";
198 $keymap{"\x0c"} = "redraw";
199 $keymap{"\x0d"} = "enter";
200 $keymap{"\x15"} = "kill";
201 $keymap{"\x17"} = "werase";
202 $keymap{"\x1a"} = "susp";
203 $keymap{"\x1b"} = "esc";
204 $keymap{"\x1c"} = "quit";
205 $keymap{"\x7f"} = "backspace"; # correct
206
207 if (my $ti = $self->{ti}) {
208 # for my $func (qw(up down left right enter backspace dc)) {
209 # $keymap{$ti->str_by_varname ("key_$func")} = $func;
210 # }
211 # we bind all keys that have known sequences, to avoid
212 # inserting their raw codes when we don't understand them
213 for my $key (grep s/^key_//, $ti->str_varnames) {
214 my $str = $ti->str_by_varname ("key_$key");
215 $key =~ y/a-z0-9_//cd; # increase security - these are used for method calls
216 $keymap{$str} = $key;
217 }
218 }
219
220 if ($self->{mode} eq "termios") {
221 require POSIX;
222
223 my $tio = POSIX::Termios->new;
224 if ($tio->getattr (fileno $self->{fh})) {
225 $keymap{chr $tio->getcc (&POSIX::VINTR )} = "intr";
226 $keymap{chr $tio->getcc (&POSIX::VSUSP )} = "susp";
227 $keymap{chr $tio->getcc (&POSIX::VEOF )} = "eof";
228 $keymap{chr $tio->getcc (&POSIX::VEOL )} = "enter";
229 $keymap{chr $tio->getcc (&POSIX::VKILL )} = "kill";
230 $keymap{chr $tio->getcc (&POSIX::VERASE)} = "backspace";
231 $keymap{chr $tio->getcc (&POSIX::VQUIT )} = "quit";
232 }
233 }
234
235 # delete some garbage the above might create
236 delete $keymap{""};
237 delete $keymap{"\0"};
238
239 # keymap MUST be nonempty (which must be the case by now)
240
241 $self->{keymap} = \%keymap;
242 delete $self->{ckm};
243 }
244
245 sub set_term {
246 my ($self, $term) = @_;
247
248 return if $self->{term} eq $term;
249
250 $self->{term} = $term;
251 $self->{ti} = new Term::Terminfo $term;
252
253 $self->default_keymap;
254 }
255
256 sub enable {
257 my ($self) = @_;
258
259 return if $self->{raw};
260
261 if ($self->{mode} eq "termios") {
262 require POSIX;
263
264 $self->{tio} = POSIX::Termios->new;
265 $self->{tio}->getattr (fileno $self->{fh})
266 or return;
267
268 my $tio = POSIX::Termios->new;
269 $tio->getattr (fileno $self->{fh})
270 or return;
271
272 $tio->setiflag ($tio->getiflag
273 & ~&POSIX::BRKINT
274 & ~&POSIX::IGNBRK
275 & ~&POSIX::ISTRIP
276 );
277 $tio->setlflag ($tio->getlflag
278 & ~&POSIX::ECHO
279 & ~&POSIX::ECHONL
280 & ~&POSIX::ICANON
281 # & ~&POSIX::ISIG #d#
282 );
283
284 $tio->setattr (fileno $self->{fh});
285 }
286
287 $self->line_draw unless $self->{on_change};
288
289 $self->{rw} = AE::io $self->{fh}, 0, $self->{rcb};
290
291 $self->{raw} = 1;
292 }
293
294 sub disable {
295 my ($self) = @_;
296
297 return unless $self->{raw};
298
299 $self->line_erase unless $self->{on_change};
300
301 delete $self->{rw};
302
303 if ($self->{mode} eq "termios") {
304 (delete $self->{tio})->setattr (fileno $self->{fh});
305 }
306
307 $self->{raw} = 0;
308 }
309
310 sub strwidth {
311 scalar (my @dummy = $_[0] =~ /\p{Print}/g)
312 - scalar (my @dummy = $_[0] =~ /\p{Mark}/g)
313 + scalar (my @dummy = $_[0] =~ /\p{East_Asian_Width=Wide}/g)
314 }
315
316 sub write {
317 my ($self, $data) = @_;
318
319 $self->{wbuf} .= $data;
320
321 $self->{ww} ||= AE::io $self->{ofh}, 1, $self->{wcb};
322 }
323
324 sub line_erase {
325 my ($self) = @_;
326
327 $self->write (
328 ("\x08" x $self->{bpos})
329 . (" " x $self->{blen})
330 . ("\x08" x $self->{blen})
331 );
332
333 $self->{bpos} = 0;
334 $self->{blen} = 0;
335 }
336
337 sub line_draw {
338 my ($self) = @_;
339
340 my $prompt = $self->{prompt};
341 $prompt = $prompt->($self) if ref $prompt;
342
343 my $l = $prompt . substr $self->{lbuf}, 0, $self->{pos};
344 my $r = substr $self->{lbuf}, $self->{pos};
345
346 for ($l, $r) {
347 s/(\P{Print})/sprintf "\\x{%x}", ord $1/ge;
348 }
349
350 my $wl = strwidth $l;
351 my $wr = strwidth $r;
352
353 $self->{blen} = $wl + $wr + 6; # erase a bit more, to feel better
354 $self->{bpos} = $wl;
355
356 my $buf = $prompt . $self->{lbuf};
357
358 utf8::encode $buf if $self->{encoding} eq "utf-8";
359
360 $self->write ($buf . ("\x08" x $wr));
361 }
362
363 sub refresh {
364 my ($self) = @_;
365
366 if ($self->{on_change}) {
367 $self->{on_change}($self, $self->{lbuf}, $self->{pos});
368 } else {
369 $self->line_erase;
370 $self->line_draw;
371 }
372 }
373
374 sub msg {
375 my ($self, $msg) = @_;
376
377 return if $self->{on_change};
378
379 $self->disable;
380 $self->write ("$msg\x0d\x0a");
381 $self->enable;
382 }
383
384 sub feed {
385 my ($self) = @_;
386
387 # TODO: telnet
388
389 # decode the input bytes into unicode
390
391 if ($self->{encoding} eq "utf-8") {
392 while ($self->{rbuf} =~ s/^([\x00-\x7f]|[\x80-\xff]+)//) {
393 my $c = $1;
394 utf8::decode $c; # if fails, we use $c as-is
395 $self->{input} .= $c;
396 }
397 } else {
398 $self->{input} .= delete $self->{rbuf};
399 }
400
401 # compile keymap, if not done yet
402
403 $self->{ckm} ||= do {
404 # compile keymap
405 my $re = join "|", map quotemeta, reverse sort keys %{ $self->{keymap} };
406 qr<^($re)>
407 };
408
409 # now try to match input sequences
410
411 for ($self->{input}) {
412 while () {
413 if (s/$self->{ckm}//) {
414 if (my $key = $self->{keymap}{$1}) {
415 ($self->{"on_$key"}
416 || (UNIVERSAL::can $self, "key_$key")
417 || sub { $self->msg ("$_[1]: unsupported key") }
418 )->($self, $key);
419 }
420 } elsif (s/^(.)//) {
421 substr $self->{lbuf}, $self->{pos}, 0, $1;
422 $self->{pos} += length $1; # always 1
423
424 $self->refresh;
425 } else {
426 last;
427 }
428 }
429 }
430 }
431
432 #############################################################################
433
434 my $rl = new AnyEvent::ReadLine
435 fh => \*STDIN,
436 ofh => \*STDOUT,
437 term => "ansi",
438 prompt => sub { AE::time . "> " },
439 on_eof => sub { die "eof" },
440 on_error => sub { die "<@_>" },
441 ;
442
443 my $t = AE::timer 5, 0, sub { $rl->disable; exit };
444 $rl->enable;
445
446 AE::cv->recv;
447
448 1;
449
450 =back
451
452 =head1 AUTHOR AND CONTACT
453
454 Marc Lehmann <schmorp@schmorp.de>
455 http://software.schmorp.de/pkg/AnyEvent-Readline.html
456
457 =cut
458