File Coverage

lib/Term/ReadLine/Perl5/OO.pm
Criterion Covered Total %
statement 83 481 17.2
branch 4 150 2.6
condition 1 53 1.8
subroutine 27 70 38.5
pod 2 46 4.3
total 117 800 14.6


line stmt bran cond sub pod time code
1             package Term::ReadLine::Perl5::OO;
2 10     10   72282 use 5.008005;
  10         52  
3 10     10   62 use strict; use warnings;
  10     10   23  
  10         315  
  10         52  
  10         18  
  10         353  
4 10     10   3278 use POSIX qw(termios_h);
  10         59615  
  10         68  
5 10     10   21761 use Storable;
  10         28608  
  10         685  
6 10     10   3176 use Text::VisualWidth::PP 0.03 qw(vwidth);
  10         222234  
  10         1026  
7 10     10   3445 use Unicode::EastAsianWidth::Detect qw(is_cjk_lang);
  10         7605  
  10         694  
8 10     10   823 use Term::ReadKey qw(GetTerminalSize ReadLine ReadKey ReadMode);
  10         3189  
  10         669  
9 10     10   3522 use IO::Handle;
  10         51298  
  10         528  
10 10     10   732 use English;
  10         4688  
  10         90  
11              
12 10     10   101 eval "use rlib '.' "; # rlib is now optional
  10         30  
  10         101  
13 10     10   5713 use Term::ReadLine::Perl5::OO::History;
  10         37  
  10         462  
14 10     10   3948 use Term::ReadLine::Perl5::OO::Keymap;
  10         37  
  10         387  
15 10     10   3231 use Term::ReadLine::Perl5::OO::State;
  10         31  
  10         287  
16 10     10   65 use Term::ReadLine::Perl5::Common;
  10         22  
  10         548  
17 10     10   1258 use Term::ReadLine::Perl5::readline;
  10         33  
  10         1980  
18 10     10   101 use Term::ReadLine::Perl5::TermCap;
  10         32  
  10         543  
19              
20             our $VERSION = "0.43";
21              
22 10     10   84 use constant HISTORY_NEXT => +1;
  10         28  
  10         1103  
23 10     10   80 use constant HISTORY_PREV => -1;
  10         26  
  10         937  
24              
25             my $IS_WIN32 = $^O eq 'MSWin32';
26             require Win32::Console::ANSI if $IS_WIN32;
27              
28             use Class::Accessor::Lite 0.05 (
29 10         90 rw => [qw(completion_callback rl_MaxHistorySize)],
30 10     10   77 );
  10         222  
31              
32             use constant {
33 10         1550 CTRL_B => 2,
34             CTRL_C => 3,
35             CTRL_F => 6,
36             CTRL_H => 8,
37             CTRL_Z => 26,
38             BACKSPACE => 127,
39             ENTER => 13,
40             TAB => 9,
41             ESC => 27,
42 10     10   1286 };
  10         28  
43              
44 10     10   75 no warnings 'once';
  10         29  
  10         1115  
45             *add_history = \&Term::ReadLine::Perl5::OO::History::add_history;
46             *read_history = \&Term::ReadLine::Perl5::OO::History::read_history;
47             *write_history = \&Term::ReadLine::Perl5::OO::History::write_history;
48 10     10   70 use warnings 'once';
  10         73  
  10         48093  
49              
50             my %attribs = (
51             stiflehistory => 1,
52             getHistory => 1,
53             addHistory => 1,
54             attribs => 1,
55             appname => 1,
56             autohistory => 1,
57             readHistory => 1,
58             setHistory => 1
59             );
60              
61             my %features = (
62             appname => 1, # "new" is recognized
63             minline => 1, # we have a working MinLine()
64             autohistory => 1, # lines are put into history automatically,
65             # subject to MinLine()
66             getHistory => 1, # we have a working getHistory()
67             setHistory => 1, # we have a working setHistory()
68             addHistory => 1, # we have a working add_history(), addhistory(),
69             # or addHistory()
70             attribs => 1,
71             stiflehistory => 1, # we have stifle_history()
72             );
73              
74              
75 0     0 0 0 sub Attribs { \%attribs; }
76 0     0 0 0 sub Features { \%features; }
77              
78             our @EmacsKeymap = ();
79              
80             =head3 new
81              
82             B([I<%options>])
83              
84             returns the handle for subsequent calls to following functions.
85             Argument is the name of the application.
86              
87             =cut
88              
89             sub new {
90 1     1 1 128 my $class = shift;
91 1 50       12 my %args = @_==1? %{$_[0]} : @_;
  0         0  
92 1         3 my $keymap;
93 1   50     11 my $editMode = $args{'editMode'} || 'emacs';
94 1 50       7 if ($editMode eq 'vicmd') {
95 0         0 $keymap = Term::ReadLine::Perl5::OO::Keymap::ViKeymap();
96             } else {
97 1         13 $keymap = Term::ReadLine::Perl5::OO::Keymap::EmacsKeymap();
98             }
99              
100             my $self = bless {
101             char => undef, # last character
102             current_keymap => $keymap,
103             toplevel_keymap => $keymap,
104             debug => !!$ENV{CAROLINE_DEBUG},
105 1         18 history_base => 0,
106             history_stifled => 0,
107             minlength => 1,
108             multi_line => 1,
109             rl_HistoryIndex => 0, # Is set on use
110             rl_MaxHistorySize => 100,
111             rl_history_length => 0, # is set on use
112             rl_max_input_history => 0,
113             state => undef, # line buffer and its state
114             rl_History => [],
115             rl_term_set => \@Term::ReadLine::TermCap::rl_term_set,
116             editMode => $editMode,
117             %args
118             }, $class;
119 1         4 return $self;
120             }
121              
122             sub debug {
123 0     0 0 0 my ($self, $stuff) = @_;
124 0 0       0 return unless $self->{debug};
125              
126             # require JSON::PP;
127 0         0 open my $fh, '>>:utf8', 'readline-oo.debug.log';
128 0         0 print $fh $stuff;
129             # print $fh JSON::PP->new->allow_nonref(1)->encode($stuff) . "\n";
130 0         0 close $fh;
131             }
132              
133             sub is_supported($) {
134 0     0 0 0 my $self = shift;
135 0 0       0 return 1 if $IS_WIN32;
136 0         0 my $term = $ENV{'TERM'};
137 0 0       0 return 0 unless defined $term;
138 0 0       0 return 0 if $term eq 'dumb';
139 0 0       0 return 0 if $term eq 'cons25';
140 0         0 return 1;
141             }
142              
143             #### FIXME redo this history stuff:
144 1     1 1 11 sub history($) { shift->{rl_History} }
145              
146             sub history_len($) {
147 0     0 0 0 shift->{rl_history_length};
148             }
149              
150             sub refresh_line {
151 0     0 0 0 my ($self, $state) = @_;
152 0 0       0 if ($self->{multi_line}) {
153 0         0 $self->refresh_multi_line($state);
154             } else {
155 0         0 $self->refresh_single_line($state);
156             }
157             }
158              
159             sub refresh_multi_line {
160 0     0 0 0 my ($self, $state) = @_;
161              
162 0         0 my $plen = vwidth($state->prompt);
163 0         0 $self->debug($state->buf . "\n");
164              
165             # rows used by current buf
166 0         0 my $rows = int(($plen + vwidth($state->buf) + $state->cols -1) / $state->cols);
167 0 0       0 if (defined $state->query) {
168 0         0 $rows++;
169             }
170              
171             # cursor relative row
172 0         0 my $rpos = int(($plen + $state->oldpos + $state->cols) / $state->cols);
173              
174 0         0 my $old_rows = $state->maxrows;
175              
176             # update maxrows if needed.
177 0 0       0 if ($rows > $state->maxrows) {
178 0         0 $state->maxrows($rows);
179             }
180              
181 0         0 $self->debug(sprintf "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d",
182             $state->len, $state->pos, $state->oldpos, $plen, $rows, $rpos, $state->maxrows, $old_rows);
183              
184             # First step: clear all the lines used before. To do start by going to the last row.
185 0 0       0 if ($old_rows - $rpos > 0) {
186 0         0 $self->debug(sprintf ", go down %d", $old_rows-$rpos);
187 0         0 printf STDOUT "\x1b[%dB", $old_rows-$rpos;
188             }
189              
190             # Now for every row clear it, go up.
191 0         0 my $j;
192 0         0 for ($j=0; $j < ($old_rows-1); ++$j) {
193 0         0 $self->debug(sprintf ", clear+up %d %d", $old_rows-1, $j);
194 0         0 print("\x1b[0G\x1b[0K\x1b[1A");
195             }
196              
197             # Clean the top line
198 0         0 $self->debug(", clear");
199 0         0 print("\x1b[0G\x1b[0K");
200              
201             # Write the prompt and the current buffer content
202 0         0 print $state->prompt;
203 0         0 print $state->buf;
204 0 0       0 if (defined $state->query) {
205 0         0 print "\015\nSearch: " . $state->query;
206             }
207              
208             # If we are at the very end of the screen with our prompt, we need to
209             # emit a newline and move the prompt to the first column
210 0 0 0     0 if ($state->pos && $state->pos == $state->len && ($state->pos + $plen) % $state->cols == 0) {
      0        
211 0         0 $self->debug("");
212 0         0 print "\n";
213 0         0 print "\x1b[0G";
214 0         0 $rows++;
215 0 0       0 if ($rows > $state->maxrows) {
216 0         0 $state->maxrows(int $rows);
217             }
218             }
219              
220             # Move cursor to right position
221 0         0 my $rpos2 = int(($plen + $state->vpos + $state->cols) / $state->cols); # current cursor relative row
222 0         0 $self->debug(sprintf ", rpos2 %d", $rpos2);
223             # Go up till we reach the expected position
224 0 0       0 if ($rows - $rpos2 > 0) {
225             # cursor up
226 0         0 printf "\x1b[%dA", $rows-$rpos2;
227             }
228              
229             # Set column
230 0         0 my $col;
231             {
232 0         0 $col = 1;
  0         0  
233 0         0 my $buf = $state->prompt . substr($state->buf, 0, $state->pos);
234 0         0 for (split //, $buf) {
235 0         0 $col += vwidth($_);
236 0 0       0 if ($col > $state->cols) {
237 0         0 $col -= $state->cols;
238             }
239             }
240             }
241 0         0 $self->debug(sprintf ", set col %d", $col);
242 0         0 printf "\x1b[%dG", $col;
243              
244 0         0 $state->oldpos($state->pos);
245              
246 0         0 $self->debug("\n");
247             }
248              
249              
250             # Show the line, default inverted.
251             sub redisplay_inverted($$) {
252 0     0 0 0 my ($self, $line) = @_;
253              
254             # FIXME: fixup from readline::redisplay_high
255             # get_ornaments_selected();
256             # @$rl_term_set[2,3,4,5] = @$rl_term_set[4,5,2,3];
257              
258             # Show the line, default inverted.
259 0         0 print STDOUT $line;
260              
261             # FIXME: fixup from readline::redisplay_high
262             # @$rl_term_set[2,3,4,5] = @$rl_term_set[4,5,2,3];
263             }
264              
265             sub refresh_single_line($$) {
266 0     0 0 0 my ($self, $state) = @_;
267              
268 0         0 my $buf = $state->buf;
269 0         0 my $len = $state->len;
270 0         0 my $pos = $state->pos;
271 0         0 while ((vwidth($state->prompt)+$pos) >= $state->cols) {
272 0         0 substr($buf, 0, 1) = '';
273 0         0 $len--;
274 0         0 $pos--;
275             }
276 0         0 while (vwidth($state->prompt) + vwidth($buf) > $state->cols) {
277 0         0 $len--;
278             }
279              
280 0         0 print STDOUT "\x1b[0G"; # cursor to left edge
281 0         0 $self->redisplay_high($self->{prompt});
282 0         0 print STDOUT $buf;
283 0         0 print STDOUT "\x1b[0K"; # erase to right
284              
285             # Move cursor to original position
286             printf "\x1b[0G\x1b[%dC", (
287             length($state->{prompt})
288 0         0 + vwidth(substr($buf, 0, $pos))
289             );
290             }
291              
292             sub edit_insert {
293 0     0 0 0 my ($self, $state, $c) = @_;
294 0 0       0 if (length($state->buf) == $state->pos) {
295 0         0 $state->{buf} .= $c;
296             } else {
297 0         0 substr($state->{buf}, $state->{pos}, 0) = $c;
298             }
299 0         0 $state->{pos}++;
300 0         0 $self->refresh_line($state);
301             }
302              
303             sub edit_delete_prev_word {
304 0     0 0 0 my ($self, $state) = @_;
305              
306 0         0 my $old_pos = $state->pos;
307 0   0     0 while ($state->pos > 0 && substr($state->buf, $state->pos-1, 1) eq ' ') {
308 0         0 $state->{pos}--;
309             }
310 0   0     0 while ($state->pos > 0 && substr($state->buf, $state->pos-1, 1) ne ' ') {
311 0         0 $state->{pos}--;
312             }
313 0         0 my $diff = $old_pos - $state->pos;
314 0         0 substr($state->{buf}, $state->pos, $diff) = '';
315 0         0 $self->refresh_line($state);
316             }
317              
318             sub edit_history($$$) {
319 0     0 0 0 my ($self, $state, $dir) = @_;
320 0         0 my $hist_len = $self->{rl_history_length};
321 0 0       0 if ($hist_len > 0) {
322 0         0 $self->{rl_HistoryIndex} += $dir ;
323 0 0       0 if ($self->{rl_HistoryIndex} <= 0) {
    0          
324 0         0 $self->F_Ding();
325 0         0 $self->{rl_HistoryIndex} = 1;
326 0         0 return;
327             } elsif ($self->{rl_HistoryIndex} > $hist_len) {
328 0         0 $self->F_Ding();
329 0         0 $self->{rl_HistoryIndex} = $hist_len;
330 0         0 return;
331             }
332 0         0 $state->{buf} = $self->{rl_History}->[$self->{rl_HistoryIndex}-1];
333 0         0 $state->{pos} = $state->len;
334 0         0 $self->refresh_line($state);
335             }
336             }
337              
338             ########################################
339              
340             sub F_AcceptLine($) {
341 0     0 0 0 my $self = shift;
342 0         0 my $buf = $self->{state}->buf;
343 0         0 Term::ReadLine::Perl5::OO::History::add_history($self, $buf);
344 0         0 return (1, $buf);
345             }
346              
347             sub F_BackwardChar($) {
348 0     0 0 0 my $self = shift;
349 0         0 my $state = $self->{state};
350 0 0       0 if ($state->pos > 0) {
351 0         0 $state->{pos}--;
352 0         0 $self->refresh_line($state);
353             }
354 0         0 return undef, undef;
355             }
356              
357             sub F_BackwardDeleteChar($) {
358 0     0 0 0 my $self = shift;
359 0         0 my $state = $self->{state};
360 0 0 0     0 if ($state->pos > 0 && length($state->buf) > 0) {
361 0         0 substr($state->{buf}, $state->pos-1, 1) = '';
362 0         0 $state->{pos}--;
363 0         0 $self->refresh_line($state);
364             }
365 0         0 return undef, undef;
366             }
367              
368             sub F_BeginningOfLine($)
369             {
370 0     0 0 0 my $self = shift;
371 0         0 my $state = $self->{state};
372 0         0 $state->{pos} = 0;
373 0         0 $self->refresh_line($state);
374 0         0 return undef, undef;
375             }
376              
377             sub F_ClearScreen($) {
378 0     0 0 0 my $self = shift;
379 0         0 my $state = $self->{state};
380 0         0 print STDOUT "\x1b[H\x1b[2J";
381 0         0 return undef, undef;
382 0         0 $self->refresh_line($state);
383             }
384              
385             sub F_DeleteChar($) {
386 0     0 0 0 my $self = shift;
387 0         0 my $state = $self->{state};
388 0 0       0 if (length($state->buf) > 0) {
389 0         0 $self->edit_delete($state);
390             }
391 0         0 return undef, undef;
392             }
393              
394             sub F_EndOfLine($)
395             {
396 0     0 0 0 my $self = shift;
397 0         0 my $state = $self->{state};
398 0         0 $state->{pos} = length($state->buf);
399 0         0 $self->refresh_line($state);
400 0         0 return undef, undef;
401             }
402              
403             sub F_Ding($) {
404 0     0 0 0 my $self = shift;
405 0         0 Term::ReadLine::Perl5::Common::F_Ding(*STDERR);
406 0         0 return undef, undef;
407             }
408              
409             sub F_ForwardChar($) {
410 0     0 0 0 my $self = shift;
411 0         0 my $state = $self->{state};
412 0 0       0 if ($state->pos != length($state->buf)) {
413 0         0 $state->{pos}++;
414 0         0 $self->refresh_line($state);
415             }
416 0         0 return undef, undef;
417             }
418              
419             sub F_Interrupt() {
420 0     0 0 0 my $self = shift;
421 0         0 $self->{sigint}++;
422 0         0 return undef, undef;
423             }
424              
425             sub F_KillLine($)
426             {
427 0     0 0 0 my $self = shift;
428 0         0 my $state = $self->{state};
429 0         0 substr($state->{buf}, $state->{pos}) = '';
430 0         0 $self->refresh_line($state);
431 0         0 return undef, undef;
432             }
433              
434             sub F_NextHistory($) {
435 0     0 0 0 my $self = shift;
436 0         0 my $state = $self->{state};
437 0         0 $self->edit_history($state, HISTORY_NEXT);
438 0         0 return undef, undef;
439             }
440              
441             ##
442             ## Execute the next character input as a command in a meta keymap.
443             ##
444             sub F_PrefixMeta
445             {
446 0     0 0 0 my $self = shift;
447 0         0 my $cc = ord($self->{char});
448 0         0 $self->{current_keymap} = $self->{function}[$cc]->[1];
449 0         0 return undef, undef;
450             }
451              
452             sub F_PreviousHistory($) {
453 0     0 0 0 my $self = shift;
454 0         0 my $state = $self->{state};
455 0         0 $self->edit_history($state, HISTORY_PREV);
456 0         0 return undef, undef;
457             }
458              
459             sub F_ReverseSearchHistory($) {
460 0     0 0 0 my $self = shift;
461 0         0 my $state = $self->{state};
462 0         0 $self->search($state);
463 0         0 return undef, undef;
464             }
465              
466             sub F_SelfInsert($)
467             {
468 0     0 0 0 my $self = shift;
469 0         0 my $state = $self->{state};
470 0         0 my $c = $self->{char};
471 0         0 $self->debug("inserting ord($c)\n");
472 0         0 $self->edit_insert($state, $c);
473             # tuple[0] == '' signals not to eval function again
474 0         0 return '', undef;
475             }
476              
477             sub F_Suspend($)
478             {
479 0     0 0 0 my $self = shift;
480 0         0 my $state = $self->{state};
481 0         0 $self->{sigtstp}++;
482 0         0 return 1, $state->buf;
483             }
484              
485             # swaps current character with previous
486             sub F_TransposeChars($) {
487 0     0 0 0 my $self = shift;
488 0         0 my $state = $self->{state};
489 0 0 0     0 if ($state->pos > 0 && $state->pos < $state->len) {
490 0         0 my $aux = substr($state->buf, $state->pos-1, 1);
491 0         0 substr($state->{buf}, $state->pos-1, 1) = substr($state->{buf}, $state->pos, 1);
492 0         0 substr($state->{buf}, $state->pos, 1) = $aux;
493 0 0       0 if ($state->pos != $state->len -1) {
494 0         0 $state->{pos}++;
495             }
496             }
497 0         0 $self->refresh_line($state);
498 0         0 return undef, undef;
499             }
500              
501             sub F_UnixLineDiscard($)
502             {
503 0     0 0 0 my $self = shift;
504 0         0 my $state = $self->{state};
505 0         0 $state->{buf} = '';
506 0         0 $state->{pos} = 0;
507 0         0 $self->refresh_line($state);
508 0         0 return undef, undef;
509             }
510              
511             sub F_UnixRubout($)
512             {
513 0     0 0 0 my $self = shift;
514 0         0 my $state = $self->{state};
515 0         0 $self->edit_delete_prev_word($state);
516 0         0 return undef, undef;
517             }
518              
519             ########################################
520              
521             sub DESTROY {
522 1     1   911 shift->disable_raw_mode();
523 1         7 Term::ReadLine::Perl5::readline::ResetTTY;
524             }
525              
526             sub readline {
527 0     0 0 0 my ($self, $prompt) = @_;
528 0 0       0 $prompt = '> ' unless defined $prompt;
529 0         0 STDOUT->autoflush(1);
530              
531 0         0 local $Text::VisualWidth::PP::EastAsian = is_cjk_lang;
532              
533 0 0 0     0 if ($self->is_supported && -t STDIN) {
534 0         0 return $self->read_raw($prompt);
535             } else {
536 0         0 print STDOUT $prompt;
537 0         0 STDOUT->flush;
538             # I need to use ReadLine() to support Win32.
539 0         0 my $line = ReadLine(0);
540 0 0       0 $line =~ s/\n$// if defined $line;
541 0         0 return $line;
542             }
543             }
544              
545             sub get_columns {
546 0     0 0 0 my $self = shift;
547 0         0 my ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();
548 0         0 return $wchar;
549             }
550              
551             sub readkey {
552 0     0 0 0 my $self = shift;
553 0         0 my $c = ReadKey(0);
554 0 0       0 return undef unless defined $c;
555 0 0       0 return $c unless $IS_WIN32;
556              
557             # Win32 API always return the bytes encoded with ACP. So it must be
558             # decoded from double byte sequence. To detect double byte sequence, it
559             # use Win32 API IsDBCSLeadByte.
560 0         0 require Win32::API;
561 0         0 require Encode;
562 0         0 require Term::Encoding;
563 0   0     0 $self->{isleadbyte} ||= Win32::API->new(
564             'kernel32', 'int IsDBCSLeadByte(char a)',
565             );
566 0   0     0 $self->{encoding} ||= Term::Encoding::get_encoding();
567 0 0       0 if ($self->{isleadbyte}->Call($c)) {
568 0         0 $c .= ReadKey(0);
569 0         0 $c = Encode::decode($self->{encoding}, $c);
570             }
571 0         0 $c;
572             }
573              
574             # linenoiseRaw
575             sub read_raw {
576 0     0 0 0 my ($self, $prompt) = @_;
577              
578 0         0 local $self->{sigint};
579 0         0 local $self->{sigtstp};
580 0         0 my $ret;
581             {
582 0         0 $self->enable_raw_mode();
  0         0  
583 0         0 $ret = $self->edit($prompt);
584 0         0 $self->disable_raw_mode();
585             }
586 0         0 print STDOUT "\n";
587 0         0 STDOUT->flush;
588 0 0       0 if ($self->{sigint}) {
    0          
589 0         0 kill 'INT', $$;
590             } elsif ($self->{sigtstp}) {
591 0 0       0 kill $IS_WIN32 ? 'INT' : 'TSTP', $$;
592             }
593 0         0 return $ret;
594             }
595              
596             sub enable_raw_mode {
597 0     0 0 0 my $self = shift;
598              
599 0 0       0 if ($IS_WIN32) {
600 0         0 ReadMode(5);
601 0         0 return undef;
602             }
603 0         0 my $termios = POSIX::Termios->new;
604 0         0 $termios->getattr(0);
605 0         0 $self->{rawmode} = [$termios->getiflag, $termios->getoflag, $termios->getcflag, $termios->getlflag, $termios->getcc(VMIN), $termios->getcc(VTIME)];
606 0         0 $termios->setiflag($termios->getiflag & ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON));
607 0         0 $termios->setoflag($termios->getoflag & ~(OPOST));
608 0         0 $termios->setcflag($termios->getcflag | ~(CS8));
609 0         0 $termios->setlflag($termios->getlflag & ~(ECHO|ICANON|IEXTEN | ISIG));
610 0         0 $termios->setcc(VMIN, 1);
611 0         0 $termios->setcc(VTIME, 0);
612 0         0 $termios->setattr(0, TCSAFLUSH);
613 0         0 return undef;
614             }
615              
616             sub disable_raw_mode {
617 1     1 0 2 my $self = shift;
618              
619 1 50       4 if ($IS_WIN32) {
620 0         0 ReadMode(0);
621 0         0 return undef;
622             }
623 1 50       3 if (my $r = delete $self->{rawmode}) {
624 0         0 my $termios = POSIX::Termios->new;
625 0         0 $termios->getattr(0);
626 0         0 $termios->setiflag($r->[0]);
627 0         0 $termios->setoflag($r->[1]);
628 0         0 $termios->setcflag($r->[2]);
629 0         0 $termios->setlflag($r->[3]);
630 0         0 $termios->setcc(VMIN, $r->[4]);
631 0         0 $termios->setcc(VTIME, $r->[5]);
632 0         0 $termios->setattr(0, TCSAFLUSH);
633             }
634 1         2 return undef;
635             }
636              
637             sub lookup_key($)
638             {
639 0     0 0   my ($self, $cc) = @_;
640 0           my $tuple = $self->{current_keymap}{function}->[$cc];
641 0 0 0       return $tuple if $tuple && defined($tuple->[0]);
642 0           my $fn_raw = $self->{current_keymap}{default};
643 0           my $fn = "F_${fn_raw}()";
644 0           print "+++", $fn, "\n";
645 0           my($done, $retval);
646 0           my $cmd = "(\$done, \$retval) = \$self->$fn";
647             # print $cmd, "\n";
648 0           eval($cmd);
649 0           return [$done, $retval];
650             }
651              
652             sub edit {
653 0     0 0   my ($self, $prompt) = @_;
654 0           print STDOUT $prompt;
655 0           STDOUT->flush;
656              
657 0           my $state = Term::ReadLine::Perl5::OO::State->new;
658 0           $state->{prompt} = $prompt;
659 0           $state->cols($self->get_columns);
660 0           $self->debug("Columns: $state->{cols}\n");
661 0           $self->{state} = $state;
662              
663 0           while (1) {
664 0           my $c = $self->readkey;
665 0 0         unless (defined $c) {
666 0           return $state->buf;
667             }
668 0 0         my $cc = ord($c) or next;
669              
670 0 0 0       if ($cc == TAB && defined $self->{completion_callback}) {
671 0           $c = $self->complete_line($state);
672 0 0         return undef unless defined $c;
673 0           $cc = ord($c);
674 0 0         next if $cc == 0;
675             }
676              
677 0           $self->{char} = $c;
678 0           my $tuple = $self->lookup_key($cc);
679 0 0 0       if ($tuple && $tuple->[0] ) {
680 0           my $fn = sprintf "F_%s()", $tuple->[0];
681 0           my($done, $retval);
682             ###### DEBUG ######
683             # if ($fn eq 'F_PrefixMeta()') {
684             # print "+++ Switching keymap\n";
685             # $self->{current_keymap} = $tuple->[1];
686             # next;
687             # }
688 0           my $cmd = "(\$done, \$retval) = \$self->$fn";
689             # use Data::Printer;
690             # print "+++ cmd:\n$cmd\n";
691             # p $tuple;
692 0           eval($cmd);
693 0 0         return $retval if $done;
694             } else {
695             # print "+++ Back to top\n";
696             $self->{current_keymap} = $self->{toplevel_keymap}
697 0           }
698              
699             # FIXME: When doing keymap lookup, I need a way to note that
700             # we want a return rather than to continue editing.
701 0 0         if ($cc == ESC) { # escape sequence
702             # Read the next two bytes representing the escape sequence
703 0 0         my $buf = $self->readkey or return undef;
704 0 0         $buf .= $self->readkey or return undef;
705              
706 0 0         if ($buf eq "[D") { # left arrow
    0          
    0          
    0          
    0          
    0          
707 0           $self->F_BackwardChar($state);
708             } elsif ($buf eq "[C") { # right arrow
709 0           $self->F_ForwardChar($state);
710             } elsif ($buf eq "[A") { # up arrow
711 0           $self->F_PreviousHistory($state);
712             } elsif ($buf eq "[B") { # down arrow
713 0           $self->F_NextHistory($state);
714             } elsif ($buf eq "[1") { # home
715 0 0         $buf = $self->readkey or return undef;
716 0 0         if ($buf eq '~') {
717 0           $state->{pos} = 0;
718 0           $self->refresh_line($state);
719             }
720             } elsif ($buf eq "[4") { # end
721 0 0         $buf = $self->readkey or return undef;
722 0 0         if ($buf eq '~') {
723 0           $state->{pos} = length($state->buf);
724 0           $self->refresh_line($state);
725             }
726             }
727             # TODO:
728             # else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
729             # /* extended escape, read additional two bytes. */
730             # if (read(fd,seq2,2) == -1) break;
731             # if (seq[1] == 51 && seq2[0] == 126) {
732             # /* Delete key. */
733             # linenoiseEditDelete(&l);
734             # }
735             # }
736             }
737             }
738 0           return $state->buf;
739             }
740              
741             sub edit_delete {
742 0     0 0   my ($self, $status) = @_;
743 0 0 0       if ($status->len > 0 && $status->pos < $status->len) {
744 0           substr($status->{buf}, $status->pos, 1) = '';
745 0           $self->refresh_line($status);
746             }
747             }
748              
749             sub search {
750 0     0 0   my ($self, $state) = @_;
751              
752 0           my $query = '';
753 0           local $state->{query} = '';
754             LOOP:
755 0           while (1) {
756 0           my $c = $self->readkey;
757 0 0         unless (defined $c) {
758 0           return $state->buf;
759             }
760 0 0         my $cc = ord($c) or next;
761              
762 0 0 0       if (
      0        
      0        
763             $cc == CTRL_B
764             || $cc == CTRL_C
765             || $cc == CTRL_F
766             || $cc == ENTER
767             ) {
768 0           return;
769             }
770 0 0 0       if ($cc == BACKSPACE || $cc == CTRL_H) {
771 0           $self->debug("ctrl-h in searching\n");
772 0           $query =~ s/.\z//;
773             } else {
774 0           $query .= $c;
775             }
776 0           $self->debug("C: $cc\n");
777              
778 0           $state->query($query);
779 0           $self->debug("Searching '$query'\n");
780             SEARCH:
781 0           for my $hist (@{$self->history}) {
  0            
782 0 0         if ((my $idx = index($hist, $query)) >= 0) {
783 0           $state->buf($hist);
784 0           $state->pos($idx);
785 0           $self->refresh_line($state);
786 0           next LOOP;
787             }
788             }
789 0           $self->F_Ding();
790 0           $self->refresh_line($state);
791             }
792             }
793              
794             sub complete_line {
795 0     0 0   my ($self, $state) = @_;
796              
797 0           my @ret = grep { defined $_ } $self->{completion_callback}->($state->buf);
  0            
798 0 0         unless (@ret) {
799 0           $self->F_Ding();
800 0           return "\0";
801             }
802              
803 0           my $i = 0;
804 0           while (1) {
805             # Show completion or original buffer
806 0 0         if ($i < @ret) {
807 0           my $cloned = Storable::dclone($state);
808 0           $cloned->{buf} = $ret[$i];
809 0           $cloned->{pos} = length($cloned->{buf});
810 0           $self->refresh_line($cloned);
811             } else {
812 0           $self->refresh_line($state);
813             }
814              
815 0           my $c = $self->readkey;
816 0 0         unless (defined $c) {
817 0           return undef;
818             }
819 0 0         my $cc = ord($c) or next;
820              
821 0 0         if ($cc == TAB) { # tab
    0          
822 0           $i = ($i+1) % (1+@ret);
823 0 0         if ($i==@ret) {
824 0           $self->F_Ding();
825             }
826             } elsif ($cc == ESC) { # escape
827             # Re-show original buffer
828 0 0         if ($i<@ret) {
829 0           $self->refresh_line($state);
830             }
831 0           return $c;
832             } else {
833             # Update buffer and return
834 0 0         if ($i<@ret) {
835 0           $state->{buf} = $ret[$i];
836 0           $state->{pos} = length($state->{buf});
837             }
838 0           return $c;
839             }
840             }
841             }
842              
843             unless (caller()) {
844             my $c = __PACKAGE__->new;
845             if (@ARGV) {
846             while (defined(my $line = $c->readline($ARGV[0] .'> '))) {
847             if ($line =~ /\S/) {
848             print eval $line, "\n";
849             }
850             }
851             }
852             }
853              
854             sub MinLine($;$) {
855 0     0 0   my $self = $_[0];
856 0           my $old = $self->{minlength};
857 0 0         $self->{minlength} = $_[1] if @_ == 2;
858 0           return $old;
859             }
860              
861             1;
862             __END__