File Coverage

blib/lib/Term/ReadLine/Zoid.pm
Criterion Covered Total %
statement 191 418 45.6
branch 65 214 30.3
condition 23 84 27.3
subroutine 33 61 54.1
pod 25 49 51.0
total 337 826 40.8


line stmt bran cond sub pod time code
1             package Term::ReadLine::Zoid;
2              
3 3     3   23624 use strict;
  3         4  
  3         115  
4 3     3   16 use vars '$AUTOLOAD';
  3         7  
  3         141  
5 3     3   1246 use Term::ReadLine::Zoid::Base;
  3         9  
  3         144  
6             #use encoding 'utf8';
7 3     3   25 no warnings; # undef == '' down here
  3         4  
  3         16341  
8              
9             our @ISA = qw/Term::ReadLine::Zoid::Base Term::ReadLine::Stub/; # explicitly not use'ing T:RL::Stub
10             our $VERSION = '0.07';
11              
12             sub import { # terrible hack - Term::ReadLine in perl 5.6.x is defective
13 10 50 66 10   7306 return unless (caller())[0] eq 'Term::ReadLine' and $] < 5.008 ;
14             *Term::ReadLine::Stub::new = sub {
15 0     0   0 shift;
16 0         0 my $self = bless {}, 'Term::ReadLine::Zoid';
17 0         0 return $self->_init(@_);
18 0         0 };
19             }
20              
21             sub new {
22 3     3 1 489 my $self = bless {}, shift(@_);
23 3         22 return $self->_init(@_);
24             }
25              
26             our $_current;
27             our %_config = (
28             minline => 0,
29             autohistory => 1,
30             autoenv => 1,
31             autolist => 1,
32             automultiline => 1,
33             PS2 => '> ',
34             comment_begin => '#',
35             maxcomplete => 'pager',
36             default_mode => 'insert',
37             );
38             our %_keymaps = (
39             insert => {
40             return => 'accept_line',
41             ctrl_O => 'operate_and_get_next',
42             ctrl_D => 'delete_char_or_eof',
43             ctrl_C => 'return_empty_string',
44             escape => 'switch_mode_command',
45             ctrl_R => 'switch_mode_isearch',
46             ctrl__ => 'switch_mode_fbrowse',
47             right => 'forward_char',
48             ctrl_F => 'forward_char',
49             left => 'backward_char',
50             ctrl_B => 'backward_char',
51             home => 'beginning_of_line',
52             ctrl_A => 'beginning_of_line',
53             end => 'end_of_line',
54             ctrl_E => 'end_of_line',
55             up => 'previous_history',
56             page_up => 'previous_history',
57             ctrl_P => 'previous_history',
58             down => 'next_history',
59             page_down => 'next_history',
60             ctrl_N => 'next_history',
61             delete => 'delete_char',
62             backspace => 'backward_delete_char',
63             ctrl_U => 'unix_line_discard',
64             ctrl_K => 'kill_line',
65             ctrl_W => 'unix_word_rubout',
66             tab => 'complete',
67             ctrl_V => 'quoted_insert',
68             insert => 'overwrite_mode',
69             ctrl_L => 'clear_screen',
70             _default => 'self_insert',
71             },
72             multiline => {
73             return => 'insert_line',
74             up => 'backward_line',
75             down => 'forward_line',
76             page_up => 'page_up',
77             page_down => 'page_down',
78             _isa => 'insert',
79             },
80             command => { _use => 'Term::ReadLine::Zoid::ViCommand' },
81             emacs => { _use => 'Term::ReadLine::Zoid::Emacs' },
82             emacs_multiline => { _use => 'Term::ReadLine::Zoid::Emacs' },
83             fbrowse => { _use => 'Term::ReadLine::Zoid::FileBrowse' },
84             isearch => { _use => 'Term::ReadLine::Zoid::ISearch' },
85             );
86              
87             sub _init {
88 3     3   7 my ($self, $name, $in, $out) = @_;
89              
90 3   50     108 %$self = (
      50        
91             appname => $name,
92             IN => $in || *STDIN{IO},
93             OUT => $out || *STDOUT{IO},
94             history => [],
95             hist_cnt => 1,
96             class => ref($self), # we might be overloaded
97             undostack => [],
98             %$self );
99              
100 3   66     122 $$self{config}{$_} ||= $_config{$_} for keys %_config ;
101 3   33     85 $$self{keymaps}{$_} ||= $_keymaps{$_} for keys %_keymaps;
102 3         9 eval "sub switch_mode_$_;" for keys %{$$self{keymaps}}; # if we declare, we can()
  3         647  
103              
104             # rcfiles
105 3 50       23 my ($rcfile) = grep {-e $_ && -r _}
  9         162  
106             "$ENV{HOME}/.perl_rl_zoid_rc",
107             "$ENV{HOME}/.zoid/perl_rl_zoid_rc",
108             "/etc/perl_rl_zoid_rc";
109 3 50       13 if ($rcfile) {
110 0         0 local $_current = $self;
111 0         0 do $rcfile;
112             }
113              
114             # PERL_RL
115 3 100       17 if (exists $ENV{PERL_RL}) {
116 2         8 my ($which, @config) = split /\s+/, $ENV{PERL_RL};
117 2 50       14 if (UNIVERSAL::isa($self, "Term::ReadLine::$which")) {
118 2         28 for (@config) {
119 0 0       0 /(\w+)=(.*)/ or next;
120 0         0 $$self{config}{$1} = $2;
121             }
122             }
123             }
124              
125 3         20 $self->switch_mode();
126 3         11 return $self;
127             }
128              
129             sub AUTOLOAD {
130 7     7   36 $AUTOLOAD =~ s/.*:://;
131 7 50       21 return if $AUTOLOAD eq 'DESTROY';
132 7         11 my $self = shift;
133 7 50       27 if ($AUTOLOAD =~ /^switch_mode_(.*)/) {
    0          
134 7         20 $self->switch_mode($1, @_);
135             }
136             elsif ($$self{class} ne __PACKAGE__) {
137 0         0 my $sub = $$self{class}.'::'.$AUTOLOAD;
138 0         0 $self->$sub(@_);
139             }
140             else {
141 0         0 my (undef, $f, $l) = caller;
142 0         0 die "$AUTOLOAD: no such method at $f line $l\n"
143             }
144             }
145              
146             # ############ #
147             # ReadLine api #
148             # ############ #
149              
150 1     1 1 17 sub ReadLine { return 'Term::ReadLine::Zoid' }
151              
152             sub readline {
153 0     0 1 0 my ($self, $prompt, $preput) = @_;
154 0         0 $self->reset();
155 0         0 $self->switch_mode();
156 0 0       0 $$self{prompt} = defined($prompt) ? $prompt : $$self{appname}.' !> ';
157 0 0       0 $$self{lines} = [ split /\n/, $preput ] if defined $preput;
158 0   0     0 my $title = $$self{config}{title} || $$self{appname};
159 0         0 $self->title($title);
160 0         0 $self->new_line();
161 0 0       0 if ($$self{prev_hist_p}) {
162 0         0 $self->set_history( delete $$self{prev_hist_p} );
163             }
164 0         0 $self->loop();
165 0         0 return $self->_return();
166             }
167              
168             sub _return { # also used by continue
169 0     0   0 my $self = shift;
170 0         0 bless $self, $$self{class}; # rebless default class
171 0         0 print { $$self{OUT} } "\n";
  0         0  
172 0 0       0 return undef unless defined $$self{_loop}; # exit application
173 0 0       0 return '' unless length $$self{_loop}; # return empty string
174 0   0     0 my $string = join("\n", @{$$self{lines}}) || '';
175 0 0       0 $self->AddHistory($string) if $$self{config}{autohistory};
176 0         0 return '' if $$self{config}{comment_begin}
177 0 0 0     0 and ! grep {$_ !~ /^\s*\Q$$self{config}{comment_begin}\E/} @{$$self{lines}};
  0         0  
178 0 0       0 $string =~ s/\\\n//ge if $$self{config}{automultiline};
179             #print STDERR "string: $string\n";
180 0         0 return $string;
181             }
182              
183             sub addhistory {
184 0     0 1 0 my ($self, $line) = @_;
185 0 0       0 return unless defined $$self{config}{minline};
186 0 0 0     0 return unless length $line and length($line) > $$self{config}{minline};
187 0         0 unshift @{$$self{history}}, $line;
  0         0  
188 0         0 $$self{hist_cnt}++;
189             }
190             *AddHistory = \&addhistory; # T:RL:Gnu compat
191              
192 0     0 1 0 sub IN { $_[0]{IN} }
193              
194 0     0 1 0 sub OUT { $_[0]{OUT} }
195              
196             sub MinLine {
197 0     0 1 0 my ($self, $minl) = @_;
198 0         0 my $old_minl = $$self{config}{minline};
199 0         0 $$self{config}{minline} = $minl;
200 0         0 return $old_minl;
201             }
202              
203 0     0 1 0 sub Attribs { $_[0]{config} }
204              
205             sub Features { {
206 0     0 1 0 ( map {($_ => 1)} qw/appname minline attribs
  0         0  
  0         0  
207             addhistory addHistory getHistory getHistory TermSize/ ),
208 0         0 ( map {($_ => $_[0]{config}{$_})}
209             qw/autohistory autoenv automultiline/ ),
210             } }
211              
212             # ############ #
213             # Extended api #
214             # ############ #
215              
216             sub GetHistory {
217             return wantarray
218 0         0 ? ( reverse @{$_[0]{history}} )
  0         0  
219 0 0   0 1 0 : [ reverse @{$_[0]{history}} ] ;
220             }
221              
222             sub SetHistory {
223 0     0 1 0 my $self = shift;
224 0         0 $self->{history} = ref($_[0])
225 0 0       0 ? [ reverse @{$_[0]} ]
226             : [ reverse @_ ] ;
227             }
228              
229             # TermSize in Base
230              
231             sub continue { # user typed \n but app says we ain't done
232 0     0 1 0 my $self = shift;
233 0 0       0 shift @{$$self{history}} if $$self{history}[0] eq join "\n", @{$$self{lines}};
  0         0  
  0         0  
234 0         0 $$self{_buffer}++; # previous _return printed a \n
235 0         0 $self->switch_mode( $$self{mode} ); # switch into last mode
236 0         0 $self->insert_line();
237 0         0 $self->loop();
238 0         0 return $self->_return();
239             }
240              
241             sub current {
242 0 0   0 1 0 return $_current if $_current;
243 0         0 my (undef, $f, $l) = caller;
244 0         0 die "No current Term::ReadLine::Zoid object at $f line $l";
245             }
246              
247             sub bindkey {
248 3     3 1 11 my ($self, $key, $sub, $mode) = @_;
249 3   33     26 $mode ||= $$self{config}{default_mode};
250 3   50     11 $$self{keymaps}{$mode} ||= {};
251 3 50       15 $key = 'meta_'.uc($1) if $key =~ /^[mM]-(.)$/;
252 3 100       24 $key = 'ctrl_'.uc($1) if $key =~ /^(?:\^|[cC]-)(.)$/;
253 3 100       21 $sub =~ tr/-/_/ unless ref $sub;
254 3         30 $$self{keymaps}{$mode}{$key} = $sub;
255             }
256              
257             # ######### #
258             # Render Fu #
259             # ######### #
260              
261             sub draw {
262 0     0 0 0 my $self = shift;
263 0         0 my @pos = @{$$self{pos}}; # force copy
  0         0  
264 0         0 my @lines = @{$$self{lines}}; # idem
  0         0  
265             # use Data::Dumper; print STDERR Dumper \@lines, \@pos;
266              
267 0 0       0 $pos[0] = length $lines[ $pos[1] ]
268             if $pos[0] > length $lines[ $pos[1] ];
269              
270             # replace the non printables
271 0         0 for (0 .. $#lines) {
272 0 0       0 if ($_ == $pos[1]) {
273 0         0 my $start = substr $lines[$_], 0, $pos[0], '';
274 0         0 my $n = ( $start =~ s{([^[:print:]])}{
275 0         0 my $ord = ord $1;
276 0 0       0 ($ord < 32) ? '^'.(chr $ord + 64) : '^?'
277             }ge );
278 0         0 $pos[0] += $n;
279 0         0 $lines[$_] = $start . $lines[$_];
280             }
281 0         0 $lines[$_] =~ s{([^[:print:]\e])}{
282 0         0 my $ord = ord $1;
283 0 0       0 ($ord < 32) ? '^'.(chr $ord + 64) : '^?'
284             }ge;
285             }
286              
287             # format PS1
288 0 0       0 my $prompt = ref($$self{prompt}) ? ${$$self{prompt}} : $$self{prompt};
  0         0  
289 0 0       0 $prompt =~ s/(!!)|!/$1?'!':$$self{hist_cnt}/eg;
  0         0  
290              
291             # format PS2 ... thank carl0s if you like to set nu
292 0         0 my $len = length scalar @lines;
293 0 0 0     0 my $nu_form = (defined $ENV{CLICOLOR} and ! $ENV{CLICOLOR})
294             ? " %${len}u " : " \e[33m%${len}u\e[0m " ;
295 0 0       0 if (@lines > 1) {
296 0 0       0 my $ps2 = ref($$self{config}{PS2}) ? ${$$self{config}{PS2}} : $$self{config}{PS2};
  0         0  
297 0 0       0 if ($$self{config}{nu}) { # line numbering
298             $lines[$_] = sprintf($nu_form, $_ + 1) . $ps2 . $lines[$_]
299 0         0 for 1 .. $#lines;
300 0 0       0 $pos[0] += $self->print_length($ps2) + $len + 3 if $pos[1];
301             }
302             else {
303 0         0 $lines[$_] = $ps2 . $lines[$_] for 1 .. $#lines;
304 0 0       0 $pos[0] += $self->print_length($ps2) if $pos[1];
305             }
306             }
307              
308             # include PS1
309 0         0 my @prompt = split /\n/, $prompt, -1;
310 0 0       0 if (@prompt) {
311 0 0       0 $prompt[-1] = sprintf($nu_form, 1) . $prompt[-1] if $$self{config}{nu};
312 0 0       0 $pos[0] += $self->print_length($prompt[-1]) unless $pos[1];
313 0         0 $pos[1] += $#prompt;
314 0         0 $lines[0] = pop(@prompt) . $lines[0];
315 0 0       0 unshift @lines, @prompt if @prompt;
316             }
317              
318             # format RPS1
319 0 0       0 if (my $rprompt = $$self{config}{RPS1}) {
320 0 0       0 $rprompt = $$rprompt if ref $rprompt;
321 0         0 my $l = $self->print_length($lines[0]);
322 0 0 0     0 if ($rprompt and $l < $$self{term_size}[0]) {
323 0         0 $rprompt = substr $rprompt, - $$self{term_size}[0] + $l - 1;
324 0         0 my $w = $$self{term_size}[0] - $l - $self->print_length($rprompt) - 1;
325 0         0 $lines[0] .= (' 'x$w) . $rprompt;
326             }
327             }
328              
329 0         0 $self->print(\@lines, \@pos);
330             }
331              
332             *redraw_current_line = \&draw;
333              
334             # ############ #
335             # Internal api #
336             # ############ #
337              
338             sub switch_mode {
339 42     42 1 27355 my ($self, $mode, @args) = @_;
340 42   66     154 $mode ||= $$self{config}{default_mode};
341 42 50       130 unless ($$self{keymaps}{$mode}) {
342 0         0 warn "$mode: no such keymap\n\n";
343 0         0 $mode = 'insert'; # hardcoded fallback
344             }
345 42         81 $$self{mode} = $mode;
346 42 100       136 if (my $class = delete $$self{keymaps}{$mode}{_use}) { # bootstrap
347 2     2   240 eval "use $class";
  2         1082  
  2         6  
  2         22  
348 2 50       12 if ($@) {
349 0         0 $$self{keymaps}{$mode}{_use} = $class; # put it back
350 0         0 die $@;
351             }
352 2         9 bless $self, $class;
353 2         38 $$self{keymaps}{$mode} = {
354 2         12 %{ $$self{keymaps}{$mode} },
355 2 50       23 %{ $self->keymap($mode) }
356             } if UNIVERSAL::can($class, 'keymap');
357 2   33     22 $$self{keymaps}{$mode}{_class} ||= $class;
358             }
359             else {
360 40   66     157 my $class = $$self{keymaps}{$mode}{_class} || $$self{class};
361             #print STDERR "class: $class\n";
362 40         135 bless $self, $class;
363             }
364              
365 42 100       993 if (exists $$self{keymaps}{$mode}{_on_switch}) {
366 31         69 my $sub = $$self{keymaps}{$mode}{_on_switch};
367 31 50       151 return ref($sub) ? $sub->($self, @args) : $self->$sub(@args) ;
368             }
369             }
370              
371             sub reset { # should this go in Base ?
372 39     39 1 20684 my $self = shift;
373 39         129 $$self{lines} = [''];
374 39         127 $$self{pos} = [0, 0];
375 39         92 $$self{_buffer} = 0;
376 39         51 $$self{replace} = 0;
377 39         60 $$self{hist_p} = undef;
378 39         73 $$self{undostack} = [];
379 39         152 $$self{scroll_pos} = 0;
380             }
381              
382             sub save {
383 83     83 1 131 my $self = shift;
384 83         218 my %save = (
385 83         401 pos => [ @{$$self{pos}} ],
386 83         94 lines => [ @{$$self{lines}} ],
387             prompt => $$self{prompt},
388             );
389 83         277 return \%save;
390             }
391              
392             sub restore {
393 2     2 1 11 my ($self, $save) = @_;
394 2         4 $$self{pos} = [ @{$$save{pos}} ];
  2         6  
395 2         46 $$self{lines} = [ @{$$save{lines}} ];
  2         8  
396 2         7 $$self{prompt} = $$save{prompt};
397             }
398              
399             sub substring { # buffer is undef is copy, end is undef is insert
400 33     33 0 1791 my ($self, $buffer, $start, $end) = @_;
401              
402 33 50       163 ($start, $end) = sort {$$a[1] <=> $$b[1] or $$a[0] <=> $$b[0]} ($start, $end) if $end;
  26 100       129  
403 33   66     100 my ($pre, $post) = _split($start || $$self{pos}, [ @{$$self{lines}} ]); # force copy of lines
  33         124  
404 33         73 my $re = [''];
405 33 100       108 if ($end) {
406 26 100       86 $$end[0] = $$end[0] - $$start[0] if $$end[1] == $$start[1];
407 26         40 $$end[1] = $$end[1] - $$start[1];
408 26         45 ($re, $post) = _split($end, $post);
409             }
410 33 100       211 return join "\n", @$re unless defined $buffer;
411              
412 32 50       113 $buffer = [split /\n/, $buffer, -1] if ! ref $buffer;
413 32 100       91 $buffer = [''] unless @$buffer;
414 32         67 $$pre[-1] .= shift @$buffer;
415 32         48 push @$pre, @$buffer;
416 32         91 $$self{pos} = [ length($$pre[-1]), $#$pre ];
417 32         61 $$pre[-1] .= shift @$post;
418 32         168 $$self{lines} = [ @$pre, @$post ];
419              
420 32         274 return join "\n", @$re;
421             }
422              
423             sub _split {
424 59     59   105 my ($pos, $buf, $nbuf) = (@_, []);
425 59 100       309 push @$nbuf, splice @$buf, 0, $$pos[1] if $$pos[1];
426 59   100     332 push @$nbuf, substr($$buf[0], 0, $$pos[0], '') || '';
427 59         137 return ($nbuf, $buf);
428             }
429              
430             # ############ #
431             # Key routines #
432             # ############ #
433              
434             sub previous_history {
435 4     4 0 8 my $self = shift;
436 4 100       13 if (not defined $$self{hist_p}) {
  3 100       10  
437 1 50       2 return $self->bell unless scalar @{$$self{history}};
  1         6  
438 1         4 $$self{_hist_save} = $self->save();
439 1         5 $self->set_history(0);
440             }
441             elsif ($$self{hist_p} < $#{$$self{history}}) {
442 2         5 $self->set_history( ++$$self{hist_p} );
443             }
444 1         5 else { return $self->bell }
445 3         7 return 1;
446             }
447              
448             sub next_history {
449 4     4 0 9 my $self = shift;
450 4 100       16 return $self->bell unless defined $$self{hist_p};
451 3 100       9 if ($$self{hist_p} == 0) {
452 1         2 $$self{hist_p} = undef;
453 1         15 $self->restore($$self{_hist_save});
454             }
455 2         5 else { $self->set_history( --$$self{hist_p} ) }
456 3         8 return 1;
457             }
458              
459             sub set_history {
460 5     5 1 7 my $self = shift;
461 5         8 my $hist_p = shift;
462 5 50 33     16 return $self->bell if $hist_p < 0 or $$self{hist_p} > $#{$$self{history}};
  5         22  
463 5         8 $$self{hist_p} = $hist_p;
464 5         21 $$self{lines} = [ split /\n/, $$self{history}[$hist_p] ];
465 5         12 $$self{pos} = [ length($$self{lines}[-1]), $#{$$self{lines}} ];
  5         16  
466             # posix says {pos} should be [0, 0], i disagree
467             }
468              
469             sub self_insert {
470 7     7 0 13 my ($self, $chr) = (@_);
471              
472             # force pos on end of line
473 7 50       29 $$self{pos}[0] = length $$self{lines}[ $$self{pos}[1] ]
474             if $$self{pos}[0] > length $$self{lines}[ $$self{pos}[1] ];
475              
476 7         22 substr $$self{lines}[ $$self{pos}[1] ], $$self{pos}[0], $$self{replace}, $chr;
477 7         25 $$self{pos}[0] += length $chr;
478             }
479              
480             sub accept_line {
481 0     0 0 0 my $self = shift;
482 0 0 0     0 if (
      0        
483 0         0 $$self{config}{automultiline} and scalar @{$$self{lines}}
  0         0  
484             and ! grep /\\\\$|(?
485             ) { #print STDERR "funky auto multiline :)\n";
486 0         0 push @{$$self{lines}}, '';
  0         0  
487 0         0 $$self{pos} = [0, $#{$$self{lines}}];
  0         0  
488             }
489 0         0 else { $$self{_loop} = 0 }
490             }
491              
492             *return = \&accept_line;
493              
494             sub operate_and_get_next {
495 0     0 0 0 my $self = shift;
496 0         0 $$self{prev_hist_p} = $$self{hist_p};
497 0         0 $$self{_loop} = 0;
498             }
499              
500             sub return_eof_maybe {
501 0 0   0 1 0 length( join "\n", @{$_[0]{lines}} )
  0         0  
502             ? ( $_[0]->bell )
503             : ( $_[0]{_loop} = undef ) ;
504             }
505              
506 0     0 1 0 sub return_eof { $_[0]{_loop} = undef }
507              
508 0     0 0 0 sub return_empty_string { $_[0]{_loop} = '' }
509              
510             sub delete_char {
511 11     11 0 30 my $self = shift;
512              
513 11 100       34 if ($$self{pos}[0] >= length $$self{lines}[ $$self{pos}[1] ]) {
514 1         4 $$self{pos}[0] = length $$self{lines}[ $$self{pos}[1] ]; # force pos on end of line
515 1 50       2 return $self->bell unless $$self{pos}[1] < @{$$self{lines}};
  1         6  
516 1         5 $$self{lines}[ $$self{pos}[1] ] .= $$self{lines}[ $$self{pos}[1] + 1 ]; # append next line
517 1         2 splice @{$$self{lines}}, $$self{pos}[1] + 1, 1; # kill next line
  1         5  
518             }
519 10         31 else { substr $$self{lines}[ $$self{pos}[1] ], $$self{pos}[0], 1, '' }
520 11         41 return 1;
521             }
522              
523             sub delete_char_or_eof {
524 0     0 0 0 my $self = shift;
525 0 0 0     0 if (
526 0         0 $$self{pos}[1] == $#{$$self{lines}}
527             and ! length $$self{lines}[-1]
528 0 0       0 ) { $$self{_loop} = $$self{pos}[1] ? 0 : undef }
529 0         0 else { $self->delete_char() }
530             }
531              
532             sub backward_delete_char {
533 9     9 0 32 $_[0]->backward_char();
534 9 50       47 $_[0]->delete_char() unless $_[0]{replace};
535             }
536              
537             sub unix_line_discard {
538 1     1 0 2 $_[0]{killbuf} = join "\n", @{$_[0]{lines}};
  1         6  
539 1         5 @{$_[0]}{'lines', 'pos'} = ([''], [0, 0])
  1         4  
540             }
541              
542             sub possible_completions {
543 0     0 1 0 my $self = shift;
544 0         0 $self->complete(undef, 'PREVIEW');
545             }
546              
547             sub complete {
548 0     0 0 0 my ($self, undef, $preview) = @_;
549              
550             # check !autolist stuff
551 0 0 0     0 if ($$self{completions} && @{$$self{completions}}) {
  0         0  
552 0         0 $self->output( @{$$self{completions}} );
  0         0  
553 0         0 delete $$self{completions};
554 0         0 return;
555             }
556              
557             # get the right function
558 0 0       0 my $func = exists($$self{config}{completion_function})
559             ? $$self{config}{completion_function}
560             : $readline::rl_completion_function ;
561 0 0       0 return unless $func;
562 0 0       0 unless (ref $func) {
563 3     3   36 no strict;
  3         8  
  3         5677  
564 0         0 $func = *{$func}{CODE};
  0         0  
565 0 0       0 return unless ref $func; # how does this work ?
566             }
567              
568             # generate the arguments
569 0         0 my $buffer = join "\n", @{$$self{lines}};
  0         0  
570 0         0 my $end = $self->pos2off($$self{pos});
571 0         0 my $word = substr $buffer, 0, $end;
572 0         0 $word =~ s/^.*\s//s; # only leave /\S*$/
573 0         0 my $lw = length $word;
574              
575             # get the completions and output
576 0         0 my @compl = $func->($word, $buffer, $end - $lw); # word, line, start
577 0 0       0 my $meta = ref($compl[0]) ? shift(@compl) : {} ; # hash constitutes an undocumented feature
578 0 0       0 $self->output( $$meta{message} ) if $$meta{message};
579              
580 0 0       0 return $self->bell unless @compl;
581 0 0       0 if ($compl[0] eq $compl[-1]) { @compl = ($compl[0]) } # 1 item or list with only duplicates
  0         0  
582 0         0 else { @compl = $self->longest_match(@compl) } # returns $compl, @compl
583              
584             # format completion
585 0         0 my $compl = shift @compl;
586 0         0 $compl = $$meta{prefix} . $compl;
587 0 0       0 $compl .= $$meta{postfix} unless @compl;
588 0 0       0 unless ($$meta{quoted}) {
589 0 0       0 if ($$meta{quote}) {
590 0 0       0 if (ref $$meta{quote}) { $compl = $$meta{quote}->($compl) } # should be code ref
  0         0  
591             else { # plain quote
592 0 0       0 $compl =~ s#\\\\|(?<=[^\\])($$meta{quote})#$1?"\\$1":'\\\\'#ge if $$meta{quote};
  0 0       0  
593 0 0 0     0 $compl .= $$meta{quote} if !@compl and $compl =~ /\w$/; # arbitrary cruft
594             }
595             }
596 0 0       0 else { $compl =~ s#\\\\|(?
  0         0  
597 0 0 0     0 $compl .= ' ' if !@compl and $compl =~ /\w$/; # arbitrary cruft
598             }
599              
600             # display completions
601 0 0       0 if (@compl) {
602 0 0 0     0 if ($$self{config}{autolist} || $preview) {
603 0         0 $self->output( @compl );
604 0 0       0 return if $preview;
605             }
606 0         0 else { $$self{completions} = \@compl }
607             }
608              
609             # update buffer
610 0 0       0 push @{$$self{undostack}}, $self->save() if length $compl;
  0         0  
611             # print STDERR ">>$buffer<< end $end off: ".($end - $lw)." l: $lw c: $compl\n";
612 0   0     0 my $start = $$meta{start} || $end - $lw;
613 0         0 substr $buffer, $start, $end - $start, $compl;
614 0         0 $$self{lines} = [ split /\n/, $buffer ];
615 0         0 $$self{pos}[0] -= $lw - length($compl); # for the moment completions can't contains \n
616             }
617              
618             sub longest_match { # cut doubles and find longest match
619 0     0 1 0 my ($self, @compl) = @_;
620              
621 0         0 @compl = sort @compl;
622 0         0 my $match = $compl[0];
623 0   0     0 while (length $match and $compl[-1] !~ /^\Q$match\E/) { chop $match } # due to sort only one diff
  0         0  
624              
625 0         0 my $prev = '';
626             return ($match, grep {
627 0 0       0 if ($_ eq $prev) { 0 }
  0         0  
  0         0  
628 0         0 else { $prev = $_; 1 }
  0         0  
629             } @compl);
630             }
631              
632             sub overwrite_mode {
633 0     0 0 0 my $b = $_[0]{replace};
634 0         0 $_[0]->switch_mode(); # for command mode
635 0 0       0 $_[0]{replace} = $b ? 0 : 1;
636             }
637              
638             sub forward_char { # including cnt for vi mode
639 27     27 0 56 my ($self, undef, $cnt) = @_;
640 27   100     95 for (1 .. $cnt||1) {
641 39 100       109 if ($$self{pos}[0] >= length $$self{lines}[ $$self{pos}[1] ]) {
642 4 100       13 return $self->bell unless $$self{pos}[1] < $#{$$self{lines}};
  4         27  
643 2         8 $$self{pos} = [0, ++$$self{pos}[1]];
644             }
645 35         72 else { $$self{pos}[0]++ }
646             }
647 25         79 return 1;
648             }
649              
650             sub backward_char { # including cnt for vi mode
651 40     40 0 573 my ($self, undef, $cnt) = @_;
652             # print STDERR "going $cnt left, pos $$self{pos}[0]\n";
653 40   100     170 for (1 .. $cnt||1) {
654 49 100       197 if ($$self{pos}[0] == 0) {
    100          
655 4 100       32 return $self->bell if $$self{pos}[1] == 0;
656 2         3 $$self{pos}[1]--;
657 2         6 $$self{pos}[0] = length $$self{lines}[ $$self{pos}[1] ];
658             }
659             elsif ($$self{pos}[0] >= length $$self{lines}[ $$self{pos}[1] ]) {
660 4         14 $$self{pos}[0] = length($$self{lines}[ $$self{pos}[1] ]) - 1;
661             }
662 41         156 else { $$self{pos}[0]-- }
663             }
664 38         115 return 1;
665             }
666              
667 2     2 0 6 sub beginning_of_line { $_[0]{pos}[0] = 0; return 1 }
  2         7  
668              
669 4     4 0 19 sub end_of_line { $_[0]{pos}[0] = length $_[0]{lines}[ $_[0]{pos}[1] ]; return 1 }
  4         100  
670              
671             sub quoted_insert {
672 1     1 0 2 my $self = shift;
673 1         6 $self->self_insert($self->read_key);
674             }
675              
676             sub unix_word_rubout {
677 2     2 0 4 my $self = shift;
678 2 50       11 $$self{pos}[0] = length $$self{lines}[ $$self{pos}[1] ]
679             if $$self{pos}[0] > length $$self{lines}[ $$self{pos}[1] ];
680 2         8 my $pre = substr $$self{lines}[ $$self{pos}[1] ], 0, $$self{pos}[0], '';
681 2         22 $pre =~ s/\S*\s*$//;
682 2         5 $$self{pos}[0] = length $pre;
683 2         13 $$self{lines}[ $$self{pos}[1] ] = $pre . $$self{lines}[ $$self{pos}[1] ];
684             }
685              
686             sub kill_line {
687 0     0 0 0 my $self = shift;
688 0         0 $$self{lines}[ $$self{pos}[1] ] = substr $$self{lines}[ $$self{pos}[1] ], 0, $$self{pos}[0];
689             }
690              
691             sub insert_line {
692 0     0 0 0 my $self = shift;
693 0         0 my $l = length $$self{lines}[ $$self{pos}[1] ];
694 0         0 my $end = substr $$self{lines}[ $$self{pos}[1] ], $$self{pos}[0], $l, '';
695 0         0 $$self{pos} = [0, $$self{pos}[1] + 1];
696 0   0     0 splice @{$$self{lines}}, $$self{pos}[1], 0, $end || '';
  0         0  
697             }
698              
699             sub backward_line {
700 4     4 0 744 my $self = shift;
701 4 100       22 return 0 unless $$self{pos}[1] > 0;
702 2         4 $$self{pos}[1]--;
703 2         9 return 1;
704             }
705              
706             sub forward_line {
707 4     4 0 7 my $self = shift;
708 4 100       9 return 0 unless $$self{pos}[1] < $#{$$self{lines}};
  4         21  
709 2         5 $$self{pos}[1]++;
710 2         11 return 1;
711             }
712              
713             sub page_up {
714 0     0 1 0 my $self = shift;
715 0         0 my (undef, $higth) = $self->TermSize();
716 0         0 $$self{pos}[1] -= $higth;
717 0 0       0 $$self{pos}[1] = 0 if $$self{pos}[1] < 0;
718             }
719              
720              
721             sub page_down {
722 0     0 1 0 my $self = shift;
723 0         0 my (undef, $higth) = $self->TermSize();
724 0         0 $$self{pos}[1] += $higth;
725 0 0       0 $$self{pos}[1] = $#{$$self{lines}} if $$self{pos}[1] > $#{$$self{lines}};
  0         0  
  0         0  
726             }
727              
728             1;
729              
730             __END__