1 | use warnings; |
---|
2 | use strict; |
---|
3 | |
---|
4 | =head1 NAME |
---|
5 | |
---|
6 | BarnOwl::Completion::Context |
---|
7 | |
---|
8 | =head1 DESCRIPTION |
---|
9 | |
---|
10 | BarnOwl::Completion::Context is the context that is passed to a |
---|
11 | completion function by BarnOwl. It contains information on the text |
---|
12 | being completed. |
---|
13 | |
---|
14 | =head1 METHODS |
---|
15 | |
---|
16 | =head2 line |
---|
17 | |
---|
18 | The entire command-line currently being completed. |
---|
19 | |
---|
20 | =head2 point |
---|
21 | |
---|
22 | The index of the cursor in C<line>, in the range C<[0,len($line)]>. |
---|
23 | |
---|
24 | =head2 words |
---|
25 | |
---|
26 | The current command-line, tokenized according to BarnOwl's |
---|
27 | tokenization rules, as an array reference. |
---|
28 | |
---|
29 | =head2 word |
---|
30 | |
---|
31 | The current word the point is sitting in, as an index into C<words>. |
---|
32 | |
---|
33 | =head2 word_point |
---|
34 | |
---|
35 | The index of the point within C<$words->[$word]>. |
---|
36 | |
---|
37 | =cut |
---|
38 | |
---|
39 | package BarnOwl::Completion::Context; |
---|
40 | |
---|
41 | use base qw(Class::Accessor::Fast); |
---|
42 | use Carp qw(croak); |
---|
43 | |
---|
44 | __PACKAGE__->mk_ro_accessors(qw(line point words word word_point |
---|
45 | word_start word_end)); |
---|
46 | |
---|
47 | sub new { |
---|
48 | my $class = shift; |
---|
49 | my $before_point = shift; |
---|
50 | my $after_point = shift; |
---|
51 | |
---|
52 | my $line = $before_point . $after_point; |
---|
53 | my $point = length ($before_point); |
---|
54 | my ($words, $word, $word_point, |
---|
55 | $word_start, $word_end) = tokenize($line, $point); |
---|
56 | |
---|
57 | my $self = { |
---|
58 | line => $line, |
---|
59 | point => $point, |
---|
60 | words => $words, |
---|
61 | word => $word, |
---|
62 | word_point => $word_point, |
---|
63 | word_start => $word_start, |
---|
64 | word_end => $word_end |
---|
65 | }; |
---|
66 | return bless($self, $class); |
---|
67 | } |
---|
68 | |
---|
69 | =head2 shift_words N |
---|
70 | |
---|
71 | Returns a new C<Context> object, with the leading C<N> words |
---|
72 | stripped. All fields are updated as appopriate. If C<N> > C<< |
---|
73 | $self->word >>, C<croak>s with an error message. |
---|
74 | |
---|
75 | =cut |
---|
76 | |
---|
77 | sub shift_words { |
---|
78 | my $self = shift; |
---|
79 | my $n = shift; |
---|
80 | |
---|
81 | if($n > $self->word) { |
---|
82 | croak "Context::shift: Unable to shift $n words"; |
---|
83 | } |
---|
84 | |
---|
85 | my $before = substr($self->line, 0, $self->point); |
---|
86 | my $after = substr($self->line, $self->point); |
---|
87 | |
---|
88 | return BarnOwl::Completion::Context->new(BarnOwl::skiptokens($before, $n), |
---|
89 | $after); |
---|
90 | } |
---|
91 | |
---|
92 | =for doc |
---|
93 | |
---|
94 | Ideally, this should use the same codepath we use to /actually/ |
---|
95 | tokenize commands, but for now, make sure this is kept in sync with |
---|
96 | owl_parseline in util.c |
---|
97 | |
---|
98 | Unlike owl_parseline, we always return a result, even in the presence |
---|
99 | of parse errors, since we may be called on incomplete command-lines. |
---|
100 | |
---|
101 | The owl_parseline rules are: |
---|
102 | |
---|
103 | * Tokenize on ' ' and '\t' |
---|
104 | * ' and " are quote characters |
---|
105 | * \ has no effect |
---|
106 | |
---|
107 | =cut |
---|
108 | |
---|
109 | my $boring = qr{[^'" \t]}; |
---|
110 | my $quote = qr{['"]}; |
---|
111 | my $space = qr{[ \t]}; |
---|
112 | |
---|
113 | sub tokenize { |
---|
114 | my $line = shift; |
---|
115 | my $point = shift; |
---|
116 | |
---|
117 | my @words = (); |
---|
118 | my $cword = 0; |
---|
119 | my $cword_start; |
---|
120 | my $cword_end; |
---|
121 | my $word_point; |
---|
122 | |
---|
123 | my $word = ''; |
---|
124 | my $wstart = 0; |
---|
125 | my $skipped = 0; |
---|
126 | |
---|
127 | pos($line) = 0; |
---|
128 | while(pos($line) < length($line)) { |
---|
129 | if($line =~ m{\G ($boring+) }gcx) { |
---|
130 | $word .= $1; |
---|
131 | } elsif ($line =~ m{\G ($quote)}gcx) { |
---|
132 | my $chr = $1; |
---|
133 | $skipped++ if pos($line) > $point; |
---|
134 | if($line =~ m{\G ([^$chr]*) $chr}gcx) { |
---|
135 | $word .= $1; |
---|
136 | $skipped++ if pos($line) > $point; |
---|
137 | } else { |
---|
138 | $word .= substr($line, pos($line)); |
---|
139 | pos($line) = length($line); |
---|
140 | } |
---|
141 | } |
---|
142 | |
---|
143 | if ($line =~ m{\G ($space+|$)}gcx) { |
---|
144 | my $wend = pos($line) - length($1); |
---|
145 | push @words, $word; |
---|
146 | $cword++ unless $wend >= $point; |
---|
147 | if(($wend >= $point) && !defined($word_point)) { |
---|
148 | $word_point = length($word) - ($wend - $point) + $skipped; |
---|
149 | $cword_start = $wstart; |
---|
150 | $cword_end = $wend; |
---|
151 | } |
---|
152 | $word = ''; |
---|
153 | $wstart = pos($line); |
---|
154 | $skipped = 0; |
---|
155 | } |
---|
156 | } |
---|
157 | |
---|
158 | if(length($word)) { die("Internal error, leftover=$word"); } |
---|
159 | |
---|
160 | unless(defined($word_point)) { |
---|
161 | $word_point = 0; |
---|
162 | $cword_start = $cword_end = $point; |
---|
163 | push @words, '' if $point > 0; |
---|
164 | } |
---|
165 | |
---|
166 | return (\@words, $cword, $word_point, $cword_start, $cword_end); |
---|
167 | } |
---|
168 | |
---|
169 | 1; |
---|