/[cwmp]/google/lib/Term/Shelly.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /google/lib/Term/Shelly.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 88 - (hide annotations)
Fri Jun 22 20:06:22 2007 UTC (16 years, 11 months ago) by dpavlin
Original Path: google/lib/Shelly.pm
File size: 26273 byte(s)
and updated to current upstream:
http://www.semicomplete.com/svnweb/Term-Shelly/Shelly.pm?revision=1302&view=markup
1 dpavlin 87 =pod
2    
3     =head1 NAME
4    
5     Term::Shelly - Yet Another Shell Kit for Perl
6    
7     =head1 VERSION
8    
9 dpavlin 88 $Id$
10 dpavlin 87
11     =head1 GOAL
12    
13     I needed a shell kit for an aim client I was writing. All of the Term::ReadLine modules are do blocking reads in doing their readline() functions, and as such are entirely unacceptable. This module is an effort on my part to provide the advanced functionality of great ReadLine modules like Zoid into a package that's more flexible, extendable, and most importantly, allows nonblocking reads to allow other things to happen at the same time.
14    
15     =head1 NEEDS
16    
17 dpavlin 88 - Settable key bindings
18     - history
19     - vi mode (Yeah, I lub vi)
20 dpavlin 87
21 dpavlin 88 =head1 DONE
22 dpavlin 87
23 dpavlin 88 - Callback for 'anykey'
24     - Tab completion
25     - Support for window size changes (sigwinch)
26     - movement/in-line editing.
27     - Completion function calls
28     - Settable callbacks for when we have an end-of-line (EOL binding?)
29    
30 dpavlin 87 =cut
31    
32     package Term::Shelly;
33    
34     use strict;
35     use warnings;
36    
37     use vars qw($VERSION);
38 dpavlin 88 $VERSION = '0.2';
39 dpavlin 87
40     # Default perl modules...
41 dpavlin 88 use IO::Select;
42 dpavlin 87 use IO::Handle; # I need flush()... or do i?;
43    
44     # Get these from CPAN
45     use Term::ReadKey;
46    
47     # Useful constants we need...
48    
49     # for find_word_bound()
50 dpavlin 88 use constant WORD_BEGINNING => 1; # look for the beginning of this word.
51     use constant WORD_END => 2; # look for end of the word.
52     use constant WORD_NEXT => 4; # look for beginning of next word
53     use constant WORD_ONLY => 8; # Trailing spaces are important.
54     use constant WORD_REGEX => 16; # I want to specify my own regexp
55 dpavlin 87
56 dpavlin 88 # for vi_jumpchar()
57     use constant JUMP_BACKCHARTO => 000; # 'T' in vi (backwards)
58     use constant JUMP_BACKCHAR => 001; # 'F' in vi (backwards)
59     use constant JUMP_CHARTO => 010; # 't' in vi (forwards)
60     use constant JUMP_CHAR => 011; # 'f' in vi (forwards)
61    
62 dpavlin 87 # Some key constant name mappings.
63 dpavlin 88 # I definately need a function to do this and some sort of hash returned which
64     # specifies the key pressed and any modifiers too.
65     # Like... ctrl+f5 is \e[15;5~ ... and not on all systems.
66 dpavlin 87 my %KEY_CONSTANTS = (
67     "\e[A" => "UP",
68     "\e[B" => "DOWN",
69     "\e[C" => "RIGHT",
70     "\e[D" => "LEFT",
71     );
72    
73     # stty raw, basically
74     ReadMode 3;
75    
76     # I need to know how big the terminal is (columns, anyway)
77    
78     =pod
79    
80     =head1 DESCRIPTION
81    
82     =over 4
83    
84     =cut
85    
86     sub new ($) {
87     my $class = shift;
88    
89     my $self = {
90     "input_line" => "",
91     "input_position" => 0,
92 dpavlin 88 "input_prompt" => "",
93 dpavlin 87 "leftcol" => 0,
94 dpavlin 88 "echo" => 1,
95     "vi_mode" => 0,
96     "mode" => "insert",
97 dpavlin 87 };
98    
99     bless $self, $class;
100    
101     ($self->{"termcols"}) = GetTerminalSize();
102     $SIG{WINCH} = sub { ($self->{"termcols"}) = GetTerminalSize(); $self->fix_inputline() };
103 dpavlin 88 $SIG{CONT} = sub { ReadMode 3; $self->fix_inputline; };
104    
105     $self->{"select"} = new IO::Select(\*STDIN);
106    
107 dpavlin 87 my $bindings = {
108 dpavlin 88 "ANYKEY" => "anykey",
109 dpavlin 87 "LEFT" => "backward-char",
110     "RIGHT" => "forward-char",
111     "UP" => "up-history",
112     "DOWN" => "down-history",
113    
114     "BACKSPACE" => "delete-char-backward",
115     "^H" => "delete-char-backward",
116     "^?" => "delete-char-backward",
117     "^W" => "delete-word-backward",
118    
119     "^U" => "kill-line",
120    
121     "^J" => "newline",
122     "^M" => "newline",
123    
124     "^A" => "beginning-of-line",
125     "^E" => "end-of-line",
126     "^K" => "kill-to-eol",
127     "^L" => "redraw",
128    
129     "^I" => "complete-word",
130     "TAB" => "complete-word",
131    
132     #"^T" => "expand-line",
133 dpavlin 88
134     #--------------------------------
135     # vi bindings
136     #
137    
138     # -------- DIRECTIONS
139    
140     "vi_h" => "vi-backward-char", # DONE
141     "vi_l" => "vi-forward-char", # DONE
142     "vi_k" => "vi-up-history",
143     "vi_j" => "vi-down-history",
144    
145     "vi_w" => "vi-forward-word", # DONE
146     "vi_W" => "vi-forward-whole-word", # DONE
147     "vi_e" => "vi-end-word", # DONE
148     "vi_E" => "vi-end-whole-word", # DONE
149     "vi_t" => "vi-forward-charto",
150     "vi_T" => "vi-backward-charto",
151     "vi_f" => "vi-forward-charat",
152     "vi_F" => "vi-backward-charat",
153     "vi_G" => "vi-history-goto",
154     "vi_b" => "vi-beginning-word",
155     "vi_B" => "vi-beginning-whole-word",,
156     "vi_n" => "vi-search-next",
157     "vi_N" => "vi-search-prev",
158     "vi_'" => "vi-mark-goto",
159     'vi_$' => "vi-end-of-line",
160     "vi_^" => "vi-beginning-of-line",
161    
162     # -------- INSERTION
163    
164     "vi_i" => "vi-insert",
165     "vi_I" => "vi-insert-at-bol",
166     "vi_a" => "vi-add",
167     "vi_A" => "vi-add-at-eol",
168     "vi_r" => "vi-replace-char",
169     "vi_R" => "vi-replace-mode",
170     "vi_s" => "vi-substitute-char",
171     "vi_S" => "vi-substitute-line",
172     #"vi_o"
173     #"vi_O"
174     "vi_c" => "vi-change",
175     "vi_C" => "vi-change-to-eol",
176    
177     #"vi_y" => "vi-yank-direction",
178     #"vi_Y" => "vi-yank-to-eol",
179     #"vi_u" => "vi-undo",
180     #"vi_p" => "vi-paste-at",
181     #"vi_P" => "vi-paste-before",
182     "vi_x" => "vi-delete-char-backward",
183     "vi_X" => "vi-delete-char-forward",
184     "vi_d" => "vi-delete",
185    
186     # -------- OTHER COMMANDS
187    
188     "vi_m" => "vi-mark",
189    
190 dpavlin 87 };
191    
192     my $mappings = {
193 dpavlin 88 "anykey" => [ \&anykey ],
194     "backward-char" => [ \&backward_char ],
195     "forward-char" => [ \&forward_char ],
196     "delete-char-backward" => [ \&delete_char_backward ],
197     "kill-line" => [ \&kill_line ],
198     "newline" => [ \&newline ],
199     "redraw" => [ \&fix_inputline ],
200     "beginning-of-line" => [ \&beginning_of_line ],
201     "end-of-line" => [ \&end_of_line ],
202     "delete-word-backward" => [ \&delete_word_backward ],
203 dpavlin 87
204 dpavlin 88 "complete-word" => [ \&complete_word ],
205     #"expand-line" => [ \&expand_line ],
206    
207     # ----------------------------------------------------------- vi mappings
208     "vi-backward-char" => [ \&vi_backward_char ],
209     "vi-forward-char" => [ \&vi_forward_char ],
210     "vi-forward-word" => [ \&vi_forward_word ],
211     "vi-forward-whole-word" => [ \&vi_forward_whole_word ],
212     "vi-beginning-word" => [ \&vi_beginning_word ],
213     "vi-beginning-whole-word" => [ \&vi_beginning_whole_word ],
214     "vi-end-of-line" => [ \&vi_eol ],
215     "vi-beginning-of-line" => [ \&vi_bol ],
216     "vi-forward-charto" => [ \&vi_forward_charto ],
217     "vi-forward-charat" => [ \&vi_forward_charat ],
218     "vi-backward-charto" => [ \&vi_backward_charto ],
219     "vi-backward-charat" => [ \&vi_backward_charat ],
220    
221     "vi-end-word" => [ \&vi_end_word ],
222     "vi-end-whole-word" => [ \&vi_end_whole_word ],
223     "vi-insert" => [ \&vi_insert ],
224     "vi-insert-at-bol" => [ \&vi_insert_at_bol ],,
225     "vi-add" => [ \&vi_add ],
226     "vi-add-at-eol" => [ \&vi_add_at_eol ],
227    
228     "vi-delete-char-backward" => [ \&vi_delete_char_backward ],
229     "vi-delete-char-forward" => [ \&vi_delete_char_forward ],
230     "vi-delete" => [ \&vi_delete ],
231    
232 dpavlin 87 };
233    
234     $self->{"bindings"} = $bindings;
235     $self->{"mappings"} = $mappings;
236     return $self;
237     }
238    
239 dpavlin 88 sub DESTROY {
240     my $self = shift;
241     $self->real_out("\n");
242     ReadMode 0;
243     }
244    
245 dpavlin 87 =pod
246    
247     =item $sh->do_one_loop()
248    
249     Does... one... loop. Makes a pass at grabbing input and processing it. For
250     speedy pasts, this loops until there are no characters left to read.
251     It will handle event processing, etc.
252    
253     =cut
254    
255     # Nonblocking readline
256     sub do_one_loop ($) {
257     my $self = shift;
258 dpavlin 88 my $text;
259 dpavlin 87 my $char;
260    
261 dpavlin 88 # Select for .01
262     #
263     if ($self->{"select"}->can_read(.01)) {
264     my $bytes = sysread(STDIN, $text, 4096);
265     for (my $i = 0; $i < length($text); $i++) {
266     $char = substr($text,$i,1);
267     $self->handle_key($char);
268     }
269 dpavlin 87 }
270    
271     }
272    
273     =pod
274    
275     =item handle_key($key)
276    
277     Handle a single character input. This is not a "key press" so much as doing all
278     the necessary things to handle key presses.
279    
280     =cut
281    
282     sub handle_key($$) {
283     my $self = shift;
284     my $char = shift;
285    
286     my $line = $self->{"input_line"} || "";
287     my $pos = $self->{"input_position"} || 0;
288    
289 dpavlin 88 if (defined($self->{"input_slurper"})) {
290     &{$self->{"input_slurper"}}($self, $char);
291     return;
292     }
293    
294 dpavlin 87 if ($self->{"escape"}) {
295     $self->{"escape_string"} .= $char;
296     if ($self->{"escape_expect_ansi"}) {
297 dpavlin 88 $self->{"escape_expect_ansi"} = 0 if ($char =~ m/[a-zA-Z~]/);
298 dpavlin 87 }
299    
300     $self->{"escape_expect_ansi"} = 1 if ($char eq '[');
301     $self->{"escape"} = 0 unless ($self->{"escape_expect_ansi"});
302    
303     unless ($self->{"escape_expect_ansi"}) {
304     my $estring = $self->{"escape_string"};
305    
306     $self->{"escape_string"} = undef;
307 dpavlin 88 $self->execute_binding("\e".$estring);
308     } else {
309     return;
310 dpavlin 87 }
311 dpavlin 88 } elsif ($char eq "\e") { # Trap escapes, they're speshul.
312     if ($self->{"vi_mode"}) {
313     if ($self->{"mode"} eq 'insert') {
314     $self->{"input_position"}-- if ($self->{"input_position"} > 1);
315     $self->{"mode"} = "command";
316     }
317     } else {
318     $self->{"escape"} = 1;
319     $self->{"escape_string"} = undef;
320     return;
321     }
322     } elsif ((ord($char) < 32) || (ord($char) > 126)) { # Control character
323 dpavlin 87 $self->execute_binding($char);
324 dpavlin 88 } elsif ((defined($char)) && (ord($char) >= 32)) {
325     if (defined($self->{"mode"}) && $self->{"mode"} eq "command") {
326     if ($char =~ m/[0-9]/) {
327     $self->{"vi_count"} .= $char;
328     } else {
329     my $cmdwait = defined($self->{"vi_command_waiting"});
330 dpavlin 87
331 dpavlin 88 $self->{"vi_count"} ||= 1;
332     while ($self->{"vi_count"} > 0) {
333     $self->execute_binding("vi_$char");
334     $self->{"vi_count"}--;
335     }
336     if ($cmdwait) {
337     &{$self->{"vi_command_waiting"}}($self, 1);
338     $self->{"input_position"} = $self->{"vi_input_position"};
339     delete $self->{"vi_command_waiting"};
340     }
341     }
342     } else {
343     if (defined($self->{"bindings"}->{"$char"})) {
344     $self->execute_binding($char);
345     } else {
346     # Insert the character in our string, wherever we are.
347     #substr($line, $pos, 0) = $char;
348     #$self->{"input_position"}++;
349     $self->insert_at_cursor($char);
350     }
351     }
352 dpavlin 87
353     # If we just did a tab completion, kill the state.
354     delete($self->{"completion"}) if (defined($self->{"completion"}));
355 dpavlin 88 $self->fix_inputline();
356 dpavlin 87 }
357    
358 dpavlin 88 # This is sometimes a nice feature to have...
359     # Press the any key!!!
360     $self->{"lastchar"} = $char;
361     $self->execute_binding("ANYKEY");
362    
363     #$self->fix_inputline();
364 dpavlin 87 }
365    
366     =pod
367    
368     =item execute_binding(raw_key)
369    
370     Guess what this does? Ok I'll explain anyway... It takes a key and prettifies
371     it, then checks the known key bindings for a mapping and checks if that mapping
372     is a coderef (a function reference). If it is, it'll call that function. If
373     not, it'll do nothing. If it finds a binding for which there is no mapped
374     function, it'll tell you that it is an unimplemented function.
375    
376     =cut
377    
378     sub execute_binding ($$) {
379     my $self = shift;
380     my $str = shift;
381     my $key = $self->prettify_key($str);
382    
383 dpavlin 88 #$self->out("Key: $key");
384    
385 dpavlin 87 my $bindings = $self->{"bindings"};
386     my $mappings = $self->{"mappings"};
387    
388     if (defined($bindings->{$key})) {
389    
390     # Check if we have stored completion state and the next binding is
391     # not complete-word. If it isn't, then kill the completion state.
392 dpavlin 88 if (defined($self->{"completion"}) && $key ne 'ANYKEY' &&
393 dpavlin 87 $bindings->{$key} ne 'complete-word') {
394     delete($self->{"completion"});
395     }
396    
397 dpavlin 88 if (ref($mappings->{$bindings->{$key}}) =~ m/(CODE|ARRAY)/) {
398 dpavlin 87
399     # This is a hack, passing $self instead of doing:
400     # $self->function, becuase I don't want to do an eval.
401    
402 dpavlin 88 if ($1 eq 'ARRAY') {
403     map { &{$_}($self) } @{$mappings->{$bindings->{$key}}};
404     } else {
405     &{$mappings->{$bindings->{$key}}}($self);
406     }
407 dpavlin 87
408     } else {
409 dpavlin 88 $self->error("Unimplemented function, " . $bindings->{$key});
410 dpavlin 87 }
411     }
412     }
413    
414     =pod
415    
416     =item prettify_key(raw_key)
417    
418     This happy function lets me turn raw input into something less ugly. It turns
419     control keys into their equivalent ^X form. It does some other things to turn
420     the key into something more readable
421    
422     =cut
423    
424     sub prettify_key ($$) {
425     my $self = shift;
426     my $key = shift;
427    
428     # Return ^X for control characters, like CTRL+A...
429     if (length($key) == 1) { # One-character keycombos should only be ctrl keys
430     if (ord($key) <= 26) { # Control codes, another check anyway...
431     return "^" . chr(65 + ord($key) - 1);
432     }
433     if (ord($key) == 127) { # Speshul backspace key
434     return "^?";
435     }
436     if (ord($key) < 32) {
437     return "^" . (split("", "\]_^"))[ord($key) - 28];
438     }
439     }
440    
441     # Return ESC-X for escape shenanigans, like ESC-W
442     if (length($key) == 2) {
443     my ($p, $k) = split("", $key);
444     if ($p eq "\e") { # This should always be an escape, but.. check anyway
445     return "ESC-" . $k;
446     }
447     }
448    
449 dpavlin 88 # Ok, so it's not ^X or ESC-X, it's gotta be some ansi funk or a normal char.
450     return $KEY_CONSTANTS{$key} || $key;
451 dpavlin 87 }
452    
453     =pod
454    
455     =item real_out($string)
456    
457     This function allows you to bypass any sort of evil shenanigans regarding output fudging. All this does is 'print @_;'
458    
459     Don't use this unless you know what you're doing.
460    
461     =cut
462    
463     sub real_out {
464     my $self = shift;
465     print @_;
466     }
467    
468     sub out ($;$) {
469     my $self = shift;
470     $self->real_out("\r\e[2K", @_, "\n");
471     $self->fix_inputline();
472     }
473    
474     sub error ($$) {
475     my $self = shift;
476 dpavlin 88 $self->real_out("\r\e[2K");
477 dpavlin 87 print STDERR "*> ", @_, "\n";
478     $self->fix_inputline();
479     }
480    
481     =pod
482    
483     =item fix_inputline
484    
485     This super-happy function redraws the input line. If input_position is beyond the bounds of the terminal, it'll shuffle around so that it can display it. This function is called just about any time any key is hit.
486    
487     =cut
488    
489 dpavlin 88 sub fix_inputline ($) {
490 dpavlin 87 my $self = shift;
491    
492     print "\r\e[2K";
493    
494 dpavlin 88 if ($self->{"echo"} == 0) {
495     #print "Echo is off...\n";
496     print $self->{"input_prompt"};
497     return;
498     }
499    
500     # If we're before the beginning of the terminal line, shuffle over!
501 dpavlin 87 if ($self->{"input_position"} - $self->{"leftcol"} <= 0) {
502     $self->{"leftcol"} -= 30;
503     $self->{"leftcol"} = 0 if ($self->{"leftcol"} < 0);
504     }
505    
506 dpavlin 88 # If we're past the end of the terminal line, shuffle back!
507     # length = input_position - leftcol + input_prompt - leftcol
508     my $pl = length($self->{"input_prompt"}) - $self->{"leftcol"};
509     $pl = 0 if ($pl < 0);
510     if ($self->{"input_position"} - $self->{"leftcol"} + $pl > $self->{"termcols"}) {
511 dpavlin 87 $self->{"leftcol"} += 30;
512     }
513    
514     # Can se show the whole line? If so, do it!
515 dpavlin 88 if (length($self->{"input_line"}) + length($self->{"input_prompt"}) < $self->{"termcols"}) {
516 dpavlin 87 $self->{"leftcol"} = 0;
517     }
518    
519     # only print as much as we can in this one line.
520 dpavlin 88 my $prompt = $self->{"input_prompt"};
521     my $offset = 0;
522     if ($self->{"leftcol"} <= length($self->{"input_prompt"})) {
523     print substr($prompt,$self->{"leftcol"});
524     $offset = length(substr($prompt,$self->{"leftcol"}));
525     }
526    
527     print substr($self->{"input_line"}, $self->{"leftcol"}, $self->{"termcols"} - $offset);
528 dpavlin 87 print "\r";
529 dpavlin 88 print "\e[" . ($self->{"input_position"} - $self->{"leftcol"} + $offset) .
530     "C" if (($self->{"input_position"} + $offset) > 0);
531 dpavlin 87 STDOUT->flush();
532     }
533    
534     sub newline {
535     my $self = shift;
536     # Process the input line.
537    
538 dpavlin 88 if ($self->{"supress_newline_echo"}) {
539     # Clear the line
540     $self->real_out("\e[2K");
541     } else {
542     $self->real_out("\n");
543     }
544 dpavlin 87
545 dpavlin 88 my $line = $self->{"input_line"};
546    
547 dpavlin 87 $self->{"input_line"} = "";
548     $self->{"input_position"} = 0;
549 dpavlin 88 $self->{"leftcol"} = 0;
550    
551     $self->callback("readline", $line);
552     #if (ref($self->{"readline_callback"}) eq 'CODE') {
553     #&{$self->{"readline_callback"}}($line);
554     #}
555    
556     $self->fix_inputline();
557 dpavlin 87 }
558    
559     sub kill_line {
560     my $self = shift;
561 dpavlin 88
562     # Ask for more data perhaps...
563     $self->callback("fardelete");# if (length($self->{"input_line"}) == 0);
564    
565 dpavlin 87 $self->{"input_line"} = "";
566     $self->{"input_position"} = 0;
567     $self->{"leftcol"} = 0;
568    
569     #real_out("\r\e[2K");
570    
571     $self->fix_inputline();
572    
573     return 0;
574     }
575    
576     sub forward_char {
577     my $self = shift;
578     if ($self->{"input_position"} < length($self->{"input_line"})) {
579     $self->{"input_position"}++;
580     $self->real_out("\e[C");
581     }
582     }
583    
584     sub backward_char {
585     my $self = shift;
586     if ($self->{"input_position"} > 0) {
587     $self->{"input_position"}--;
588     $self->real_out("\e[D");
589     }
590     }
591    
592     sub delete_char_backward {
593     my $self = shift;
594 dpavlin 88
595     $self->callback("fardelete") if (length($self->{"input_line"}) == 0);
596    
597 dpavlin 87 if ($self->{"input_position"} > 0) {
598     substr($self->{"input_line"}, $self->{"input_position"} - 1, 1) = '';
599     $self->{"input_position"}--;
600     $self->fix_inputline();
601     }
602     }
603    
604     sub beginning_of_line {
605     my $self = shift;
606     $self->{"input_position"} = 0;
607     $self->{"leftcol"} = 0;
608     $self->fix_inputline();
609     }
610    
611     sub end_of_line {
612     my $self = shift;
613     $self->{"input_position"} = length($self->{"input_line"});
614     $self->fix_inputline();
615     }
616    
617     sub delete_word_backward {
618     my $self = shift;
619     my $pos = $self->{"input_position"};
620     my $line = $self->{"input_line"};
621 dpavlin 88 #my $regex = '[A-Za-z0-9]';
622     my $regex = '\S';
623 dpavlin 87 my $bword;
624    
625 dpavlin 88 $self->callback("fardelete") if (length($self->{"input_line"}) == 0);
626     $bword = $self->find_word_bound($line, $pos, WORD_BEGINNING | WORD_REGEX, $regex);
627 dpavlin 87
628 dpavlin 88 #$self->error("Testing $bword $pos");
629 dpavlin 87 # Delete whatever word we just found.
630     substr($line, $bword, $pos - $bword) = '';
631    
632     # Update stuff...
633     $self->{"input_line"} = $line;
634     $self->{"input_position"} -= ($pos - $bword);
635    
636     $self->fix_inputline();
637     }
638    
639 dpavlin 88 sub vi_backward_char {
640     my $self = shift;
641    
642     $self->backward_char();
643     $self->{"vi_done"} = 1;
644     }
645    
646     sub vi_forward_char {
647     my $self = shift;
648    
649     $self->forward_char();
650     $self->{"vi_done"} = 1;
651     }
652    
653     sub vi_forward_word {
654     my $self = shift;
655     my $pos = $self->{"input_position"};
656     my $line = $self->{"input_line"};
657     my $bword = $pos;
658     my $BITS = WORD_NEXT;
659     my $regex = shift;
660    
661     $BITS |= WORD_REGEX if (defined($regex));
662     $bword = $self->find_word_bound($line, $pos, $BITS, $regex);
663    
664     $self->{"input_position"} = $bword;
665    
666     $self->{"vi_done"};
667     }
668    
669     sub vi_forward_whole_word {
670     my $self = shift;
671     $self->vi_forward_word('\S');
672     $self->{"vi_done"} = 1;
673     }
674    
675     sub vi_beginning_word {
676     my $self = shift;
677     my $pos = $self->{"input_position"};
678     my $line = $self->{"input_line"};
679     my $bword = $pos;
680     my $BITS = WORD_BEGINNING;
681     my $regex = shift;
682    
683     $BITS |= WORD_REGEX if (defined($regex));
684     $bword = $self->find_word_bound($line, $pos, $BITS, $regex);
685    
686     $self->{"input_position"} = $bword;
687    
688     $self->{"vi_done"};
689     }
690    
691     sub vi_beginning_whole_word {
692     my $self = shift;
693     $self->vi_beginning_word('\S');
694     $self->{"vi_done"} = 1;
695     }
696    
697     sub vi_end_word {
698     my $self = shift;
699     my $pos = $self->{"input_position"};
700     my $line = $self->{"input_line"};
701     my $bword = $pos;
702     my $BITS = WORD_END;
703     my $regex = shift;
704    
705     $BITS |= WORD_REGEX if (defined($regex));
706     $bword = $self->find_word_bound($line, $pos, $BITS, $regex);
707    
708     $self->{"input_position"} = $bword;
709    
710     $self->{"vi_done"};
711     }
712    
713     sub vi_end_whole_word {
714     my $self = shift;
715     $self->vi_end_word('\S');
716     $self->{"vi_done"} = 1;
717     }
718    
719     sub vi_forward_charto {
720     my $self = shift;
721    
722     # We need to wait for another character input...
723     $self->{"jumpchardir"} = JUMP_CHARTO;
724     $self->{"input_slurper"} = \&vi_jumpchar;
725     }
726    
727     sub vi_forward_charat {
728     my $self = shift;
729    
730     # We need to wait for another character input...
731     $self->{"jumpchardir"} = JUMP_CHAR;
732     $self->{"input_slurper"} = \&vi_jumpchar;
733     }
734    
735     sub vi_backward_charto {
736     my $self = shift;
737    
738     $self->{"jumpchardir"} = JUMP_BACKCHARTO;
739     $self->{"input_slurper"} = \&vi_jumpchar;
740     }
741    
742     sub vi_backward_charat {
743     my $self = shift;
744    
745     $self->{"jumpchardir"} = JUMP_BACKCHAR;
746     $self->{"input_slurper"} = \&vi_jumpchar;
747     }
748    
749     sub vi_jumpchar {
750     my $self = shift;
751     my $char = shift;
752     my $pos = $self->{"input_position"};
753     my $line = $self->{"input_line"};
754     my $newpos;
755     my $mod = 0;
756    
757     delete $self->{"input_slurper"};
758    
759     $mod = ($self->{"jumpchardir"} & JUMP_CHARTO ? 1 : -1);
760    
761     if ($mod == 1) {
762     #$self->out("F: $line / $pos / " . $line =~ m/^(.{$pos}[^$char]*)$char/);
763     #$self->out(" " . " " x ($pos) . "^ / $1");
764     $pos = length($1) if (defined($1));
765     } else {
766     #$self->out("B: $line / $pos / " . $line =~ m/$char([^$char]*.{$pos})$/);
767     #$self->out(" " . " " x ($pos - 1) . "^ / $1");
768     $pos = length($line) - length($1) if (defined($1));
769     }
770     $self->{"input_position"} = $pos;
771    
772     $self->fix_inputline();
773     }
774    
775     sub vi_bol {
776     my $self = shift;
777     $self->{"input_position"} = 0;
778     $self->{"vi_done"} = 1;
779     }
780    
781     sub vi_eol {
782     my $self = shift;
783     $self->{"input_position"} = length($self->{"input_line"});
784     $self->{"vi_done"} = 1;
785     }
786     sub vi_insert {
787     my $self = shift;
788    
789     $self->{"mode"} = "insert";
790     $self->{"vi_done"} = 1;
791     }
792    
793     sub vi_insert_at_bol {
794     my $self = shift;
795    
796     $self->vi_bol();
797     $self->vi_insert();
798     $self->{"vi_done"} = 1;
799     }
800    
801     sub vi_add {
802     my $self = shift;
803    
804     $self->{"input_position"}++ if ($self->{"input_position"} < length($self->{"input_line"}));
805    
806     $self->vi_insert();
807     $self->{"vi_done"} = 1;
808     }
809    
810     sub vi_add_at_eol {
811     my $self = shift;
812    
813     $self->vi_eol();
814     $self->vi_add();
815     $self->{"vi_done"} = 1;
816     }
817    
818     sub vi_delete_char_forward {
819     my $self = shift;
820     unless ($self->{"input_position"} == 0) {
821     substr($self->{"input_line"}, $self->{"input_position"} - 1, 1) = '';
822     $self->{"input_position"}--;
823     }
824     }
825    
826     sub vi_delete_char_backward {
827     my $self = shift;
828    
829     $self->callback("fardelete") if (length($self->{"input_line"}) == 0);
830    
831     substr($self->{"input_line"}, $self->{"input_position"}, 1) = '';
832     $self->{"input_position"}-- if ($self->{"input_position"} == length($self->{"input_line"}) && $self->{"input_position"} > 0);
833     }
834    
835     sub vi_delete {
836     my $self = shift;
837     my $exec = shift || 0;
838    
839     if ($exec == 1) {
840     my ($start, $end);
841    
842     $self->callback("fardelete") if (length($self->{"input_line"}) == 0);
843    
844     if ($self->{"input_position"} < $self->{"vi_input_position"}) {
845     $start = $self->{"input_position"};
846     $end = $self->{"vi_input_position"};
847     } else {
848     $start = $self->{"vi_input_position"};
849     $end = $self->{"input_position"};
850     }
851     substr($self->{"input_line"}, $start, ($end - $start)) = '';
852     } else {
853     # Mark such that we remember what command we're doing at the time
854     # and set ourselves as the call back for the end of the next valid
855     # command. soo.... something like:
856     $self->{"vi_command_waiting"} = \&vi_delete;
857     $self->{"vi_input_position"} = $self->{"input_position"};
858     }
859    
860     }
861    
862 dpavlin 87 =pod
863    
864     =item $sh->complete_word
865    
866     This is called whenever the complete-word binding is triggered. See the
867     COMPLETION section below for how to write your own completion function.
868    
869     =cut
870    
871     sub complete_word {
872     my $self = shift;
873     my $pos = $self->{"input_position"};
874     my $line = $self->{"input_line"};
875     my $regex = "[A-Za-z0-9]";
876     my $bword;
877     my $complete;
878    
879     if (ref($self->{"completion_function"}) eq 'CODE') {
880     my @matches;
881    
882     # Maintain some sort of state here if this is the first time we've
883     # hit complete_word() for this "scenario." What I mean is, we need to track
884     # whether or not this user is hitting tab once or twice (or more) in the
885     # same position.
886     RECHECK:
887     if (!defined($self->{"completion"})) {
888     $bword = $self->find_word_bound($line, $pos, WORD_BEGINNING | WORD_REGEX, '\S');
889     $complete = substr($line,$bword,$pos - $bword);
890     #$self->out("Complete: $complete");
891 dpavlin 88 #$self->out(length($line) . " / $bword / $pos");
892 dpavlin 87
893 dpavlin 88 # Make sure we can actually do this ?
894    
895 dpavlin 87 #$self->out("First time completing $complete");
896     $self->{"completion"} = {
897     "index" => 0,
898     "original" => $complete,
899     "pos" => $pos,
900     "bword" => $bword,
901     "line" => $line,
902     "endpos" => $pos,
903     };
904     } else {
905     $bword = $self->{"completion"}->{"bword"};
906 dpavlin 88 #$self->out(length($line) . " / $bword / $pos");
907 dpavlin 87 $complete = substr($line,$bword,$pos - $bword);
908     }
909    
910     # If we don't have any matches to check against...
911     unless (defined($self->{"completion"}->{"matches"})) {
912     @matches =
913     &{$self->{"completion_function"}}($line, $bword, $pos, $complete);
914     @{$self->{"completion"}->{"matches"}} = @matches;
915     } else {
916     @matches = @{$self->{"completion"}->{"matches"}};
917     }
918    
919     my $match = $matches[$self->{"completion"}->{"index"}];
920    
921     return unless (defined($match));
922    
923     $self->{"completion"}->{"index"}++;
924     $self->{"completion"}->{"index"} = 0 if ($self->{"completion"}->{"index"} == scalar(@matches));
925    
926 dpavlin 88 #$self->out(length($line) . " / $bword / $pos");
927     substr($line, $bword, $pos - $bword) = $match . " ";
928 dpavlin 87
929     $self->{"completion"}->{"endpos"} = $pos;
930    
931 dpavlin 88 $pos = $bword + length($match) + 1;
932 dpavlin 87 $self->{"input_position"} = $pos;
933     $self->{"input_line"} = $line;
934    
935     $self->fix_inputline();
936 dpavlin 88 }
937     }
938 dpavlin 87
939 dpavlin 88 sub anykey {
940     my $self = shift;
941    
942     $self->callback("anykey");
943     #if (ref($self->{"anykey_callback"}) eq 'CODE') {
944     #&{$self->{"anykey_callback"}};
945     #}
946     }
947    
948    
949    
950     #------------------------------------------------------------------------------
951     # Useful functions to set prompt and other things.
952    
953     =pod
954    
955     =item $sh->prompt([$prompt])
956    
957     Get or set the prompt
958    
959     =cut
960    
961     sub prompt ($;$) {
962     my $self = shift;
963    
964     if (@_) {
965     $self->{"input_prompt"} = shift;
966     $self->fix_inputline();
967 dpavlin 87 }
968 dpavlin 88 return $self->{"input_prompt"};
969 dpavlin 87 }
970    
971 dpavlin 88 sub echo ($;$) {
972     my $self = shift;
973 dpavlin 87
974 dpavlin 88 if (@_) {
975     $self->{"echo"} = shift;
976     }
977     return $self->{"echo"};
978     }
979    
980 dpavlin 87 # --------------------------------------------------------------------
981     # Helper functions
982 dpavlin 88 #
983 dpavlin 87
984 dpavlin 88 sub callback($$;$) {
985 dpavlin 87 my $self = shift;
986 dpavlin 88 my $callback = shift() . "_callback";
987     if (ref($self->{$callback}) eq 'CODE') {
988     $self->{$callback}->(@_);
989     }
990     }
991 dpavlin 87
992 dpavlin 88 # Go from a position and find the beginning of the word we're on.
993     sub find_word_bound ($$$$;$) {
994     my ($self, $line, $pos, $opts, $rx) = @_;
995     my $nrx;
996     $rx = '\\w' if (!($opts & WORD_REGEX));
997 dpavlin 87
998     # Mod? This is either -1 or +1 depending on if we're looking behind or
999     # if we're looking ahead.
1000 dpavlin 88 my $mod = ($opts & WORD_BEGINNING) ? -1 : 1;
1001     $nrx = qr/[^$rx]/;
1002     $rx = qr/[$rx]/;
1003 dpavlin 87
1004 dpavlin 88 if ($opts & WORD_NEXT) {
1005     #$regex = qr/^.{$pos}(.+?)(?<!$regex)$regex/;
1006     $rx = qr/^.{$pos}(.+?)(?<!$rx)$rx/;
1007     } elsif ($opts & WORD_BEGINNING) {
1008     #$regex = qr/($regex+[^$regex]*)(?<=^.{$pos})/;
1009     $rx = qr/($rx+$nrx*)(?<=^.{$pos})/;
1010     } elsif ($opts & WORD_END) {
1011     #$regex = qr/^.{$pos}(.+?)$regex(?:[^$regex]|$)/;
1012     $rx = qr/^.{$pos}(.+?)$rx(?:$nrx|$)/;
1013     }
1014 dpavlin 87
1015 dpavlin 88 #$self->out("$rx");
1016    
1017     if ($line =~ $rx) {
1018     $pos += length($1) * $mod;
1019     } else {
1020     $pos = ($mod == 1 ? length($line) : 0);
1021 dpavlin 87 }
1022    
1023 dpavlin 88 return $pos;
1024     }
1025 dpavlin 87
1026 dpavlin 88 # -----------------------------------------------------------------------------
1027     # Functions people might call on us...
1028     #
1029 dpavlin 87
1030 dpavlin 88 sub insert_at_cursor($$) {
1031     my $self = shift;
1032     my $string = shift;
1033 dpavlin 87
1034 dpavlin 88 substr($self->{"input_line"}, $self->{"input_position"}, 0) = $string;
1035     $self->{"input_position"} += length($string)
1036 dpavlin 87 }
1037    
1038     =pod
1039    
1040     =back
1041    
1042     =cut
1043    
1044     1;

  ViewVC Help
Powered by ViewVC 1.1.26