Audio-Nama
view release on metacpan or search on metacpan
lib/Audio/Nama/Completion.pm view on Meta::CPAN
=head1 DESCRIPTION
This package applies code to a L<Tickit::Widget::Entry> instance to implement
word-completion logic while editing. This logic is activated by pressing the
C<< <Tab> >> key.
If the word currently being edited has a unique match in the list of words,
then the word is completed entirely, followed by a space.
If there are multiple words that could complete from the word at the cursor,
then a popup menu is presented showing the next available characters or
matches. The user can continue typing more characters to narrow down the
choice until a unique match is found.
=cut
=head1 METHODS
=cut
=head2 apply
lib/Audio/Nama/Completion.pm view on Meta::CPAN
The underlying entry widget instance.
=back
=item words => ARRAY
A shortcut to providing C<gen_words>; a reference to an array containing all
the possible words, in no particular order, that are offered for completion.
=item use_popup => BOOL
Optional. If false, do not display a popup menu. Defaults to true.
When this is disabled, the completion logic will apply longest-prefix matching
on the set of available words, but will not otherwise display or offer any
interactive UI on the list of matches.
=item ignore_case => BOOL
Optional. If true, word matching will be performed ignoring case, by using the
C</i> regexp flag. Defaults to false. When the completion logic has selected a
word to insert, it may change the case of the text already in the buffer to
lib/Audio/Nama/Completion.pm view on Meta::CPAN
$entry->bind_keys(
Tab => sub { $plugin->key_complete },
);
# Need to disable style-applied keypress binding so this takes effect
$entry->set_style( '<Tab>' => "" );
}
field $_ignore_case :param //= 0;
field $_use_popup :param //= 1;
field $_append_after_word :param //= " ";
field $_gen_words :param = undef;
ADJUST :params (
:$words = undef,
) {
if( $words and !$_gen_words ) {
$_gen_words = sub { return $words->@* };
}
$_gen_words or
croak "Require either 'gen_words' or 'words'";
}
field $_entry :param;
field $_popup_window;
method key_complete
{
my ( $partial ) = substr( $_entry->text, 0, $_entry->position ) =~ m/(\S*)$/;
my $plen = length $partial or return 1;
my $match = $_ignore_case ? qr/^\Q$partial\E/i : qr/^\Q$partial\E/;
my @completions = grep { $_ =~ $match } $_gen_words->(
word => $partial,
wordpos => $_entry->position - $plen,
lib/Audio/Nama/Completion.pm view on Meta::CPAN
}
if( @completions == 1 ) {
# No other completions, so we have a complete match
$add .= $_append_after_word unless $add =~ m{ / $ }x;
}
$_entry->text_splice( $_entry->position - $plen, $plen, $add );
return 1 if @completions < 2;
return 1 unless $_use_popup;
# Split matches on next letter
my %next;
foreach ( @completions ) {
my $l = substr( $_, $plen, 1 );
push @{ $next{$l} }, $_;
}
my @possibles = map {
@{ $next{$_} } == 1 ? $next{$_}[0]
: substr( $next{$_}[0], 0, $plen + 1 ) . "..."
} sort keys %next;
# Popup above, unless there's no room at which point, go below
my $popup_line = ( $_entry->window->abs_top >= @possibles ) ? -(scalar @possibles) : +1;
my $popup = $_entry->make_popup_at_cursor(
$popup_line, -$plen,
scalar @possibles, max( map { textwidth($_) } @possibles ),
);
# TODO: Some style stuff here
$popup->pen->chattrs({ bg => 'green', fg => 'black' });
$popup->bind_event( expose => sub ( $win, $, $info, @ ) {
my $rb = $info->rb;
foreach my $line ( 0 .. $#possibles ) {
my $str = $possibles[$line];
$rb->goto( $line, 0 );
$rb->text( substr( $str, 0, $plen + 1 ), PEN_UNDER );
$rb->text( substr( $str, $plen + 1 ) ) if length $str > $plen + 1;
$rb->erase_to( $win->cols );
}
} );
$popup->bind_event( key => sub ( $win, $, $info, @ ) {
my $redo_complete;
my $str = $info->str;
if( $info->type eq "text" ) {
$_entry->text_splice( $_entry->position, 0, $str );
$redo_complete++;
}
elsif( $str eq "Backspace" ) {
$_entry->text_splice( $_entry->position - 1, 1, "" );
$redo_complete++;
}
elsif( $str eq "Escape" ) {
# OK, just dismiss
}
else {
# TODO: Handle at least Enter, maybe arrows to select?
print STDERR "TODO: Unsure how to handle key $str in popup menu\n";
}
$popup->hide;
undef $_popup_window;
$_entry->take_focus;
$self->key_complete if $redo_complete;
return 1;
} );
$popup->cursor_at( 0, $plen );
$popup->take_focus;
$popup->show;
$_popup_window = $popup;
return 1;
}
=head1 AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
=cut
lib/Audio/Nama/Entry.pm view on Meta::CPAN
if( defined $value ) {
$_keybindings{$str} = $value;
}
else {
delete $_keybindings{$str};
}
}
}
=head2 make_popup_at_cursor
$win = $entry->make_popup_at_cursor( $top_offset, $left_offset, $lines, $cols );
I<Since version 0.33.>
Creates a new popup window, as if calling L<Tickit::Window/make_popup> on the
widget's main window, but with an offset relative to the current cursor
position.
An offet of (0, 0) will position the popup window's top left corner exactly over
the cursor; this is likely not what you want.
To position the popup just below the widget, use a top offset of +1:
$win = $entry->make_popup_at_cursor( +1, 0, $lines, $cols );
To position the popup just above the widget, use a top offset of negative the
number of lines:
$win = $entry->make_popup_at_cursor( -$lines, 0, $lines, $cols );
=cut
method make_popup_at_cursor
{
my ( $topoff, $leftoff, $lines, $cols ) = @_;
$self->window or
croak "Cannot ->make_popup_at_cursor on an Entry widget with no window";
my $pos_x = $self->char2col( $_pos_ch ) - $_scrolloffs_co;
return $self->window->make_popup( $topoff, $pos_x + $leftoff, $lines, $cols );
}
=head1 TEXT MODEL METHODS
These methods operate on the text input buffer directly, updating the stored
text and changing the rendered display to reflect the changes. They can be
used by a program to directly manipulate the text.
=cut
lib/Audio/Nama/Terminal.pm view on Meta::CPAN
text => prompt(),
on_enter => $do_command,
);
show_prompt();
}
sub setup_key_bindings {
Tickit::Widget::Entry::Plugin::Completion->apply($entry,
gen_words => \&gen_words,
use_popup => 0,
ignore_case => 1);
my $backspace = sub {
my $stop_pos = length prompt();
$entry->text_delete( $entry->position - 1, 1 )
unless $entry->position <= $stop_pos
};
my $left = sub {
my $stop_pos = length prompt();
$entry->set_position( $entry->position - 1 )
( run in 0.873 second using v1.01-cache-2.11-cpan-364913b4093 )