/[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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26