File Coverage

blib/lib/Caroline.pm
Criterion Covered Total %
statement 75 409 18.3
branch 10 186 5.3
condition 0 48 0.0
subroutine 21 49 42.8
pod 5 29 17.2
total 111 721 15.4


line stmt bran cond sub pod time code
1             package Caroline;
2 3     3   26610 use 5.008005;
  3         9  
3 3     3   9 use strict;
  3         4  
  3         51  
4 3     3   24 use warnings;
  3         2  
  3         84  
5 3     3   1260 use POSIX qw(termios_h);
  3         13697  
  3         11  
6 3     3   4655 use Storable;
  3         6641  
  3         158  
7 3     3   1142 use Text::VisualWidth::PP 0.03 qw(vwidth);
  3         47268  
  3         214  
8 3     3   1171 use Unicode::EastAsianWidth::Detect qw(is_cjk_lang);
  3         1548  
  3         144  
9 3     3   1302 use Term::ReadKey qw(GetTerminalSize ReadLine ReadKey ReadMode);
  3         8081  
  3         169  
10 3     3   1286 use IO::Handle;
  3         12468  
  3         251  
11              
12             our $VERSION = "0.22";
13              
14             our @EXPORT = qw( caroline );
15              
16             my $HISTORY_NEXT = 0;
17             my $HISTORY_PREV = 1;
18              
19             my $IS_WIN32 = $^O eq 'MSWin32';
20             require Win32::Console::ANSI if $IS_WIN32;
21              
22             use Class::Accessor::Lite 0.05 (
23 3         14 rw => [qw(completion_callback history_max_len)],
24 3     3   1330 );
  3         2479  
25              
26             use constant {
27 3         9177 CTRL_A => 1,
28             CTRL_B => 2,
29             CTRL_C => 3,
30             CTRL_D => 4,
31             CTRL_E => 5,
32             CTRL_F => 6,
33             CTRL_H => 8,
34             CTRL_I => 9,
35             CTRL_K => 11,
36             CTRL_L => 12,
37             CTRL_M => 13,
38             CTRL_N => 14,
39             CTRL_P => 16,
40             CTRL_R => 18,
41             CTRL_T => 20,
42             CTRL_U => 21,
43             CTRL_W => 23,
44             CTRL_Z => 26,
45             BACKSPACE => 127,
46             ENTER => 13,
47             TAB => 9,
48             ESC => 27,
49 3     3   244 };
  3         4  
50              
51             sub new {
52 4     4 1 831 my $class = shift;
53 4 50       15 my %args = @_==1? %{$_[0]} : @_;
  0         0  
54             my $self = bless {
55             history => [],
56             debug => !!$ENV{CAROLINE_DEBUG},
57 4         19 multi_line => 1,
58             history_max_len => 100,
59             %args
60             }, $class;
61 4         9 return $self;
62             }
63              
64             sub debug {
65 0     0 0 0 my ($self, $stuff) = @_;
66 0 0       0 return unless $self->{debug};
67              
68             # require JSON::PP;
69 0         0 open my $fh, '>>:utf8', 'caroline.debug.log';
70 0         0 print $fh $stuff;
71             # print $fh JSON::PP->new->allow_nonref(1)->encode($stuff) . "\n";
72 0         0 close $fh;
73             }
74              
75 4     4 1 25 sub history { shift->{history} }
76              
77             sub history_len {
78 0     0 0 0 my $self = shift;
79 0         0 0+@{$self->{history}};
  0         0  
80             }
81              
82             sub DESTROY {
83 4     4   706 my $self = shift;
84 4         9 $self->disable_raw_mode();
85             }
86              
87             sub readline {
88 0     0 0 0 my ($self, $prompt) = @_;
89 0 0       0 $prompt = '> ' unless defined $prompt;
90 0         0 STDOUT->autoflush(1);
91              
92 0         0 local $Text::VisualWidth::PP::EastAsian = is_cjk_lang;
93              
94 0 0 0     0 if ($self->is_supported && -t STDIN) {
95 0         0 return $self->read_raw($prompt);
96             } else {
97 0         0 print STDOUT $prompt;
98 0         0 STDOUT->flush;
99             # I need to use ReadLine() to support Win32.
100 0         0 my $line = ReadLine(0);
101 0 0       0 $line =~ s/\n$// if defined $line;
102 0         0 return $line;
103             }
104             }
105              
106             sub get_columns {
107 0     0 0 0 my $self = shift;
108 0         0 my ($wchar, $hchar, $wpixels, $hpixels) = GetTerminalSize();
109 0         0 return $wchar;
110             }
111              
112             sub readkey {
113 0     0 0 0 my $self = shift;
114 0         0 my $c = ReadKey(0);
115 0 0       0 return undef unless defined $c;
116 0 0       0 return $c unless $IS_WIN32;
117              
118             # Win32 API always return the bytes encoded with ACP. So it must be
119             # decoded from double byte sequence. To detect double byte sequence, it
120             # use Win32 API IsDBCSLeadByte.
121 0         0 require Win32::API;
122 0         0 require Encode;
123 0         0 require Term::Encoding;
124 0   0     0 $self->{isleadbyte} ||= Win32::API->new(
125             'kernel32', 'int IsDBCSLeadByte(char a)',
126             );
127 0   0     0 $self->{encoding} ||= Term::Encoding::get_encoding();
128 0 0       0 if ($self->{isleadbyte}->Call($c)) {
129 0         0 $c .= ReadKey(0);
130 0         0 $c = Encode::decode($self->{encoding}, $c);
131             }
132 0         0 $c;
133             }
134              
135             # linenoiseRaw
136             sub read_raw {
137 0     0 0 0 my ($self, $prompt) = @_;
138              
139 0         0 local $self->{sigint};
140 0         0 local $self->{sigtstp};
141 0         0 my $ret;
142             {
143 0         0 $self->enable_raw_mode();
  0         0  
144 0         0 $ret = $self->edit($prompt);
145 0         0 $self->disable_raw_mode();
146             }
147 0         0 print STDOUT "\n";
148 0         0 STDOUT->flush;
149 0 0       0 if ($self->{sigint}) {
    0          
150 0         0 kill 'INT', $$;
151             } elsif ($self->{sigtstp}) {
152 0 0       0 kill $IS_WIN32 ? 'INT' : 'TSTP', $$;
153             }
154 0         0 return $ret;
155             }
156              
157             sub enable_raw_mode {
158 0     0 0 0 my $self = shift;
159              
160 0 0       0 if ($IS_WIN32) {
161 0         0 ReadMode(5);
162 0         0 return undef;
163             }
164 0         0 my $termios = POSIX::Termios->new;
165 0         0 $termios->getattr(0);
166 0         0 $self->{rawmode} = [$termios->getiflag, $termios->getoflag, $termios->getcflag, $termios->getlflag, $termios->getcc(VMIN), $termios->getcc(VTIME)];
167 0         0 $termios->setiflag($termios->getiflag & ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON));
168 0         0 $termios->setoflag($termios->getoflag & ~(OPOST));
169 0         0 $termios->setcflag($termios->getcflag | ~(CS8));
170 0         0 $termios->setlflag($termios->getlflag & ~(ECHO|ICANON|IEXTEN | ISIG));
171 0         0 $termios->setcc(VMIN, 1);
172 0         0 $termios->setcc(VTIME, 0);
173 0         0 $termios->setattr(0, TCSAFLUSH);
174 0         0 return undef;
175             }
176              
177             sub disable_raw_mode {
178 4     4 0 5 my $self = shift;
179              
180 4 50       10 if ($IS_WIN32) {
181 0         0 ReadMode(0);
182 0         0 return undef;
183             }
184 4 50       10 if (my $r = delete $self->{rawmode}) {
185 0         0 my $termios = POSIX::Termios->new;
186 0         0 $termios->getattr(0);
187 0         0 $termios->setiflag($r->[0]);
188 0         0 $termios->setoflag($r->[1]);
189 0         0 $termios->setcflag($r->[2]);
190 0         0 $termios->setlflag($r->[3]);
191 0         0 $termios->setcc(VMIN, $r->[4]);
192 0         0 $termios->setcc(VTIME, $r->[5]);
193 0         0 $termios->setattr(0, TCSAFLUSH);
194             }
195 4         32 return undef;
196             }
197              
198             sub history_add {
199 17     17 1 37 my ($self, $line) = @_;
200 17 100       10 if (@{$self->{history}}+1 > $self->history_max_len) {
  17         39  
201 6         16 shift @{$self->{history}};
  6         8  
202             }
203 17         38 push @{$self->{history}}, $line;
  17         31  
204             }
205              
206             sub edit {
207 0     0 0 0 my ($self, $prompt) = @_;
208 0         0 print STDOUT $prompt;
209 0         0 STDOUT->flush;
210              
211 0         0 $self->history_add('');
212              
213 0         0 my $state = Caroline::State->new;
214 0         0 $state->{prompt} = $prompt;
215 0         0 $state->cols($self->get_columns);
216 0         0 $self->debug("Columns: $state->{cols}\n");
217              
218 0         0 while (1) {
219 0         0 my $c = $self->readkey;
220 0 0       0 unless (defined $c) {
221 0         0 return $state->buf;
222             }
223 0 0       0 my $cc = ord($c) or next;
224              
225 0 0 0     0 if ($cc == TAB && defined $self->{completion_callback}) {
226 0         0 $c = $self->complete_line($state);
227 0 0       0 return undef unless defined $c;
228 0         0 $cc = ord($c);
229 0 0       0 next if $cc == 0;
230             }
231              
232 0 0 0     0 if ($cc == ENTER) { # enter
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
233 0         0 pop @{$self->{history}};
  0         0  
234 0         0 return $state->buf;
235             } elsif ($cc==CTRL_C) { # ctrl-c
236 0         0 $self->{sigint}++;
237 0         0 return undef;
238             } elsif ($cc==CTRL_Z) { # ctrl-z
239 0         0 $self->{sigtstp}++;
240 0         0 return $state->buf;
241             } elsif ($cc == BACKSPACE || $cc == CTRL_H) { # backspace or ctrl-h
242 0         0 $self->edit_backspace($state);
243             } elsif ($cc == CTRL_D) { # ctrl-d
244 0 0       0 if (length($state->buf) > 0) {
245 0         0 $self->edit_delete($state);
246             } else {
247 0         0 return undef;
248             }
249             } elsif ($cc == CTRL_T) { # ctrl-t
250             # swaps current character with prvious
251 0 0 0     0 if ($state->pos > 0 && $state->pos < $state->len) {
252 0         0 my $aux = substr($state->buf, $state->pos-1, 1);
253 0         0 substr($state->{buf}, $state->pos-1, 1) = substr($state->{buf}, $state->pos, 1);
254 0         0 substr($state->{buf}, $state->pos, 1) = $aux;
255 0 0       0 if ($state->pos != $state->len -1) {
256 0         0 $state->{pos}++;
257             }
258             }
259 0         0 $self->refresh_line($state);
260             } elsif ($cc == CTRL_B) { # ctrl-b
261 0         0 $self->edit_move_left($state);
262             } elsif ($cc == CTRL_F) { # ctrl-f
263 0         0 $self->edit_move_right($state);
264             } elsif ($cc == CTRL_P) { # ctrl-p
265 0         0 $self->edit_history_next($state, $HISTORY_PREV);
266             } elsif ($cc == CTRL_N) { # ctrl-n
267 0         0 $self->edit_history_next($state, $HISTORY_NEXT);
268             } elsif ($cc == 27) { # escape sequence
269             # Read the next two bytes representing the escape sequence
270 0 0       0 my $buf = $self->readkey or return undef;
271 0 0       0 $buf .= $self->readkey or return undef;
272              
273 0 0       0 if ($buf eq "[D") { # left arrow
    0          
    0          
    0          
    0          
    0          
274 0         0 $self->edit_move_left($state);
275             } elsif ($buf eq "[C") { # right arrow
276 0         0 $self->edit_move_right($state);
277             } elsif ($buf eq "[A") { # up arrow
278 0         0 $self->edit_history_next($state, $HISTORY_PREV);
279             } elsif ($buf eq "[B") { # down arrow
280 0         0 $self->edit_history_next($state, $HISTORY_NEXT);
281             } elsif ($buf eq "[1") { # home
282 0 0       0 $buf = $self->readkey or return undef;
283 0 0       0 if ($buf eq '~') {
284 0         0 $state->{pos} = 0;
285 0         0 $self->refresh_line($state);
286             }
287             } elsif ($buf eq "[4") { # end
288 0 0       0 $buf = $self->readkey or return undef;
289 0 0       0 if ($buf eq '~') {
290 0         0 $state->{pos} = length($state->buf);
291 0         0 $self->refresh_line($state);
292             }
293             }
294             # TODO:
295             # else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
296             # /* extended escape, read additional two bytes. */
297             # if (read(fd,seq2,2) == -1) break;
298             # if (seq[1] == 51 && seq2[0] == 126) {
299             # /* Delete key. */
300             # linenoiseEditDelete(&l);
301             # }
302             # }
303             } elsif ($cc == CTRL_U) { # ctrl-u
304             # delete the whole line.
305 0         0 $state->{buf} = '';
306 0         0 $state->{pos} = 0;
307 0         0 $self->refresh_line($state);
308             } elsif ($cc == CTRL_K) { # ctrl-k
309 0         0 substr($state->{buf}, $state->{pos}) = '';
310 0         0 $self->refresh_line($state);
311             } elsif ($cc == CTRL_A) { # ctrl-a
312 0         0 $state->{pos} = 0;
313 0         0 $self->refresh_line($state);
314             } elsif ($cc == CTRL_E) { # ctrl-e
315 0         0 $state->{pos} = length($state->buf);
316 0         0 $self->refresh_line($state);
317             } elsif ($cc == CTRL_L) { # ctrl-l
318 0         0 $self->clear_screen();
319 0         0 $self->refresh_line($state);
320             } elsif ($cc == CTRL_R) { # ctrl-r
321 0         0 $self->search($state);
322             } elsif ($cc == CTRL_W) { # ctrl-w
323 0         0 $self->edit_delete_prev_word($state);
324             } else {
325 0         0 $self->debug("inserting $cc\n");
326 0         0 $self->edit_insert($state, $c);
327             }
328             }
329 0         0 return $state->buf;
330             }
331              
332             sub read_history_file {
333 1     1 1 5 my ($self, $filename) = @_;
334 1 50       7 open my $fh, '<:encoding(utf-8)', $filename
335             or return undef;
336 1         73 while (my $hist = <$fh>) {
337 3         12 chomp($hist);
338 3         4 $self->history_add($hist);
339             }
340 1         7 close $fh;
341 1         2 return 1;
342             }
343              
344             sub write_history_file {
345 2     2 1 12 my ($self, $filename) = @_;
346              
347 2 100       4 return undef if $self->history_max_len == 0;
348              
349 1 50   1   11 open my $fh, '>:encoding(utf-8)', $filename
  1         48  
  1         1  
  1         7  
350             or return undef;
351 1         7521 for my $hist (@{$self->history}) {
  1         24  
352 3 50       10 next unless $hist =~ /\S/;
353 3         9 print $fh $hist . "\n";
354             }
355 1         54 close $fh;
356 1         5 return 1;
357             }
358              
359             sub edit_delete {
360 0     0 0 0 my ($self, $status) = @_;
361 0 0 0     0 if ($status->len > 0 && $status->pos < $status->len) {
362 0         0 substr($status->{buf}, $status->pos, 1) = '';
363 0         0 $self->refresh_line($status);
364             }
365             }
366              
367             sub search {
368 0     0 0 0 my ($self, $state) = @_;
369              
370 0         0 my $query = '';
371 0         0 local $state->{query} = '';
372             LOOP:
373 0         0 while (1) {
374 0         0 my $c = $self->readkey;
375 0 0       0 unless (defined $c) {
376 0         0 return $state->buf;
377             }
378 0 0       0 my $cc = ord($c) or next;
379              
380 0 0 0     0 if (
      0        
      0        
381             $cc == CTRL_B
382             || $cc == CTRL_C
383             || $cc == CTRL_F
384             || $cc == ENTER
385             ) {
386 0         0 return;
387             }
388 0 0 0     0 if ($cc == BACKSPACE || $cc == CTRL_H) {
389 0         0 $self->debug("ctrl-h in searching\n");
390 0         0 $query =~ s/.\z//;
391             } else {
392 0         0 $query .= $c;
393             }
394 0         0 $self->debug("C: $cc\n");
395              
396 0         0 $state->query($query);
397 0         0 $self->debug("Searching '$query'\n");
398             SEARCH:
399 0         0 for my $hist (@{$self->history}) {
  0         0  
400 0 0       0 if ((my $idx = index($hist, $query)) >= 0) {
401 0         0 $state->buf($hist);
402 0         0 $state->pos($idx);
403 0         0 $self->refresh_line($state);
404 0         0 next LOOP;
405             }
406             }
407 0         0 $self->beep();
408 0         0 $self->refresh_line($state);
409             }
410             }
411              
412             sub complete_line {
413 0     0 0 0 my ($self, $state) = @_;
414              
415 0         0 my @ret = grep { defined $_ } $self->{completion_callback}->($state->buf, $state->pos);
  0         0  
416 0 0       0 unless (@ret) {
417 0         0 $self->beep;
418 0         0 return "\0";
419             }
420              
421 0         0 my $i = 0;
422 0         0 while (1) {
423             # Show completion or original buffer
424 0 0       0 if ($i < @ret) {
425 0         0 my $cloned = Storable::dclone($state);
426 0         0 $cloned->{buf} = $ret[$i];
427 0         0 $cloned->{pos} = $state->pos + length($cloned->{buf}) - length($state->buf);
428 0         0 $self->refresh_line($cloned);
429             } else {
430 0         0 $self->refresh_line($state);
431             }
432              
433 0         0 my $c = $self->readkey;
434 0 0       0 unless (defined $c) {
435 0         0 return undef;
436             }
437 0 0       0 my $cc = ord($c) or next;
438              
439 0 0       0 if ($cc == TAB) { # tab
    0          
440 0         0 $i = ($i+1) % (1+@ret);
441 0 0       0 if ($i==@ret) {
442 0         0 $self->beep();
443             }
444             } elsif ($cc == ESC) { # escape
445             # Re-show original buffer
446 0 0       0 if ($i<@ret) {
447 0         0 $self->refresh_line($state);
448             }
449 0         0 return $c;
450             } else {
451             # Update buffer and return
452 0 0       0 if ($i<@ret) {
453 0         0 $state->{pos} = $state->{pos} + length($ret[$i]) - length($state->{buf});
454 0         0 $state->{buf} = $ret[$i];
455             }
456 0         0 return $c;
457             }
458             }
459             }
460              
461             sub beep {
462 0     0 0 0 my $self = shift;
463 0         0 $self->debug("Beep!\n");
464 0         0 print STDERR "\x7";
465 0         0 STDERR->flush;
466             }
467              
468             sub edit_delete_prev_word {
469 0     0 0 0 my ($self, $state) = @_;
470              
471 0         0 my $old_pos = $state->pos;
472 0   0     0 while ($state->pos > 0 && substr($state->buf, $state->pos-1, 1) eq ' ') {
473 0         0 $state->{pos}--;
474             }
475 0   0     0 while ($state->pos > 0 && substr($state->buf, $state->pos-1, 1) ne ' ') {
476 0         0 $state->{pos}--;
477             }
478 0         0 my $diff = $old_pos - $state->pos;
479 0         0 substr($state->{buf}, $state->pos, $diff) = '';
480 0         0 $self->refresh_line($state);
481             }
482              
483             sub edit_history_next {
484 0     0 0 0 my ($self, $state, $dir) = @_;
485 0 0       0 if ($self->history_len > 1) {
486 0         0 $self->history->[$self->history_len-1-$state->{history_index}] = $state->buf;
487 0 0       0 $state->{history_index} += ( ($dir == $HISTORY_PREV) ? 1 : -1 );
488 0 0       0 if ($state->{history_index} < 0) {
    0          
489 0         0 $state->{history_index} = 0;
490 0         0 return;
491             } elsif ($state->{history_index} >= $self->history_len) {
492 0         0 $state->{history_index} = $self->history_len-1;
493 0         0 return;
494             }
495 0         0 $state->{buf} = $self->history->[$self->history_len - 1 - $state->{history_index}];
496 0         0 $state->{pos} = $state->len;
497 0         0 $self->refresh_line($state);
498             }
499             }
500              
501             sub edit_backspace {
502 0     0 0 0 my ($self, $state) = @_;
503 0 0 0     0 if ($state->pos > 0 && length($state->buf) > 0) {
504 0         0 substr($state->{buf}, $state->pos-1, 1) = '';
505 0         0 $state->{pos}--;
506 0         0 $self->refresh_line($state);
507             }
508             }
509              
510             sub clear_screen {
511 0     0 0 0 my ($self) = @_;
512 0         0 print STDOUT "\x1b[H\x1b[2J";
513             }
514              
515             sub refresh_line {
516 0     0 0 0 my ($self, $state) = @_;
517 0 0       0 if ($self->{multi_line}) {
518 0         0 $self->refresh_multi_line($state);
519             } else {
520 0         0 $self->refresh_single_line($state);
521             }
522             }
523              
524             sub refresh_multi_line {
525 0     0 0 0 my ($self, $state) = @_;
526              
527 0         0 my $plen = vwidth($state->prompt);
528 0         0 $self->debug($state->buf. "\n");
529              
530             # rows used by current buf
531 0         0 my $rows = int(($plen + vwidth($state->buf) + $state->cols -1) / $state->cols);
532 0 0       0 if (defined $state->query) {
533 0         0 $rows++;
534             }
535              
536             # cursor relative row
537 0         0 my $rpos = int(($plen + $state->oldpos + $state->cols) / $state->cols);
538              
539 0         0 my $old_rows = $state->maxrows;
540              
541             # update maxrows if needed.
542 0 0       0 if ($rows > $state->maxrows) {
543 0         0 $state->maxrows($rows);
544             }
545              
546 0         0 $self->debug(sprintf "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d",
547             $state->len, $state->pos, $state->oldpos, $plen, $rows, $rpos, $state->maxrows, $old_rows);
548              
549             # First step: clear all the lines used before. To do start by going to the last row.
550 0 0       0 if ($old_rows - $rpos > 0) {
551 0         0 $self->debug(sprintf ", go down %d", $old_rows-$rpos);
552 0         0 printf STDOUT "\x1b[%dB", $old_rows-$rpos;
553             }
554              
555             # Now for every row clear it, go up.
556 0         0 my $j;
557 0         0 for ($j=0; $j < ($old_rows-1); ++$j) {
558 0         0 $self->debug(sprintf ", clear+up %d %d", $old_rows-1, $j);
559 0         0 print("\x1b[0G\x1b[0K\x1b[1A");
560             }
561              
562             # Clean the top line
563 0         0 $self->debug(", clear");
564 0         0 print("\x1b[0G\x1b[0K");
565              
566             # Write the prompt and the current buffer content
567 0         0 print $state->prompt;
568 0         0 print $state->buf;
569 0 0       0 if (defined $state->query) {
570 0         0 print "\015\nSearch: " . $state->query;
571             }
572              
573             # If we are at the very end of the screen with our prompt, we need to
574             # emit a newline and move the prompt to the first column
575 0 0 0     0 if ($state->pos && $state->pos == $state->len && ($state->pos + $plen) % $state->cols == 0) {
      0        
576 0         0 $self->debug("");
577 0         0 print "\n";
578 0         0 print "\x1b[0G";
579 0         0 $rows++;
580 0 0       0 if ($rows > $state->maxrows) {
581 0         0 $state->maxrows(int $rows);
582             }
583             }
584              
585             # Move cursor to right position
586 0         0 my $rpos2 = int(($plen + $state->vpos + $state->cols) / $state->cols); # current cursor relative row
587 0         0 $self->debug(sprintf ", rpos2 %d", $rpos2);
588             # Go up till we reach the expected position
589 0 0       0 if ($rows - $rpos2 > 0) {
590             # cursor up
591 0         0 printf "\x1b[%dA", $rows-$rpos2;
592             }
593              
594             # Set column
595 0         0 my $col;
596             {
597 0         0 $col = 1;
  0         0  
598 0         0 my $buf = $state->prompt . substr($state->buf, 0, $state->pos);
599 0         0 for (split //, $buf) {
600 0         0 $col += vwidth($_);
601 0 0       0 if ($col > $state->cols) {
602 0         0 $col -= $state->cols;
603             }
604             }
605             }
606 0         0 $self->debug(sprintf ", set col %d", $col);
607 0         0 printf "\x1b[%dG", $col;
608              
609 0         0 $state->oldpos($state->pos);
610              
611 0         0 $self->debug("\n");
612             }
613              
614             sub refresh_single_line {
615 0     0 0 0 my ($self, $state) = @_;
616              
617 0         0 my $buf = $state->buf;
618 0         0 my $len = $state->len;
619 0         0 my $pos = $state->pos;
620 0         0 while ((vwidth($state->prompt)+$pos) >= $state->cols) {
621 0         0 substr($buf, 0, 1) = '';
622 0         0 $len--;
623 0         0 $pos--;
624             }
625 0         0 while (vwidth($state->prompt) + vwidth($buf) > $state->cols) {
626 0         0 $len--;
627             }
628              
629 0         0 print STDOUT "\x1b[0G"; # cursor to left edge
630 0         0 print STDOUT $state->{prompt};
631 0         0 print STDOUT $buf;
632 0         0 print STDOUT "\x1b[0K"; # erase to right
633              
634             # Move cursor to original position
635             printf "\x1b[0G\x1b[%dC", (
636             length($state->{prompt})
637 0         0 + vwidth(substr($buf, 0, $pos))
638             );
639             }
640              
641             sub edit_move_right {
642 0     0 0 0 my ($self, $state) = @_;
643 0 0       0 if ($state->pos != length($state->buf)) {
644 0         0 $state->{pos}++;
645 0         0 $self->refresh_line($state);
646             }
647             }
648              
649             sub edit_move_left {
650 0     0 0 0 my ($self, $state) = @_;
651 0 0       0 if ($state->pos > 0) {
652 0         0 $state->{pos}--;
653 0         0 $self->refresh_line($state);
654             }
655             }
656              
657              
658             sub edit_insert {
659 0     0 0 0 my ($self, $state, $c) = @_;
660 0 0       0 if (length($state->buf) == $state->pos) {
661 0         0 $state->{buf} .= $c;
662             } else {
663 0         0 substr($state->{buf}, $state->{pos}, 0) = $c;
664             }
665 0         0 $state->{pos}++;
666 0         0 $self->refresh_line($state);
667             }
668              
669             sub is_supported {
670 0     0 0 0 my ($self) = @_;
671 0 0       0 return 1 if $IS_WIN32;
672 0         0 my $term = $ENV{'TERM'};
673 0 0       0 return 0 unless defined $term;
674 0 0       0 return 0 if $term eq 'dumb';
675 0 0       0 return 0 if $term eq 'cons25';
676 0         0 return 1;
677             }
678              
679             package Caroline::State;
680              
681             use Class::Accessor::Lite 0.05 (
682 3         23 rw => [qw(buf pos cols prompt oldpos maxrows query)],
683 3     3   24 );
  3         59  
684              
685             sub new {
686 0     0   0 my $class = shift;
687 0         0 bless {
688             buf => '',
689             pos => 0,
690             history_index => 0,
691             oldpos => 0,
692             maxrows => 0,
693             }, $class;
694             }
695 3     3   369 use Text::VisualWidth::PP 0.03 qw(vwidth);
  3         47  
  3         391  
696              
697 0     0   0 sub len { length(shift->buf) }
698 0     0   0 sub plen { length(shift->prompt) }
699              
700             sub vpos {
701 0     0   0 my $self = shift;
702 0         0 vwidth(substr($self->buf, 0, $self->pos));
703             }
704              
705             sub width {
706 0     0   0 my $self = shift;
707 0         0 vwidth($self->prompt . $self->buf);
708             }
709              
710             1;
711             __END__