# dummy items to play with
my @animals = (qw( cow camel dog cat ));
my @foods = (qw( fish pasture meat ));
my @places = (qw( wood sea desert ));
# commands
my $commands = {
select => {
completion_list => \@animals,
commands => {
give => {
completion_list => \@foods,
},
take_to => {
completion_list => \@places,
},
},
},
kill => {
completion_list => \@animals,
}
};
####
Press TAB for autocompletion or available options
autocompletion nested>("kill", "select") # 'TAB' on empty input shows available commands
autocompletion nested>select c("camel", "cat", "cow") # 'cTAB' shows animals but not 'dog'
autocompletion nested>select ca("camel", "cat") # 'caTAB' shows two animals starting with 'ca'
autocompletion nested>select camel ("give", "take_to") # I press 'mTAB' thae autocomplete in 'camel'
# 'TAB' again for next level commands: give and take_to
autocompletion nested>select camel take_to ("desert", "sea", "wood") # 'tTAB' and 'TAB' again
autocompletion nested>select camel take_to desert # 'desTAB' leads to 'desert'
# ENTER 1
autocompletion nested> # ENTER 2 exits the user input cycle
CHOOSEN: select camel take_to desert # finally we print the resulting path
####
#!perl
use strict;
use warnings;
use Data::Diver qw( Dive );
use Data::Dump;
# https://www.perlmonks.org/?node_id=1108329
BEGIN {
# you can force Term::ReadLine to load the ::Perl
# by setting the ENV PERL_RL variable
# but you have to do it in a begin block
# before the use statement
# try to comment next line: probably ::Perl will be loaded
# anyway if it is installed
# try to set to a non available sub module
# see details: https://metacpan.org/pod/Term::ReadLine#ENVIRONMENT
$ENV{PERL_RL}="Perl";
# on win32 systems it ENV TERM is 'dumb'
# autocompletion is not possible.
# try to comment next line on win32 and watch how does not work
# see also http://bvr.github.io/2010/11/term-readline/
$ENV{TERM} = 'not dumb' if $^O eq 'MSWin32';
}
use Term::ReadLine;
my $term=Term::ReadLine->new("test");
my $debug = 0; # 0 1 2
# Term::ReadLine is a wrapper module: it initilizes an Attribs hasref
# internally used to fill in the right configuration.
# You can use this hashref to set autompletion manually. As i did here.
#
# YOU MUST NOT USE readline::rl_basic_commands DIRECTLY!
#
# you cannot print anything from hashref returned by $term->Attribs
#foreach my $k (keys $term->Attribs){print "$k ${$term->Attribs}{$k}\n"}
# dummy items to play with
my @animals = (qw( cow camel dog cat ));
my @foods = (qw( fish pasture meat ));
my @places = (qw( wood sea desert));
# commands
my $commands = {
select => {
completion_list => \@animals,
commands => {
give => {
completion_list => \@foods,
},
take_to => {
completion_list => \@places,
},
},
},
kill => {
completion_list => \@animals,
}
};
# starting from top level of $commands
my $hr = $commands;
# first time initialization of autocompletion
define_completion_func($hr);
print "The Term::ReadLine effective module loaded is: ",$term->ReadLine,"\n" if $debug;
print "Press TAB for autocompletion or available options\n";
my @choosen;
my $prompt = "autocompletion nested>";
while ( defined ( $_ = $term->readline( $prompt ) ) ) {
s/\s+//g;
if (/^$/){
print "CHOOSEN: ", (join ' ', @choosen),"\n";
last;
}
}
sub define_completion_func {
my $cur_hr = shift;
# completion_list OR keys of the current HASH
my @cur_list = $cur_hr->{completion_list} ? @{$cur_hr->{completion_list}} : keys %$cur_hr;
$term->Attribs->{completion_function} = sub {
my ($text, $line, $start) = @_;
if ( $debug ){ print "\n\tDEBUG: text, line, start = [$text] [$line] [$start]\n";}
# populate results (0 1 or more)
my @res = grep { /^$text/i } sort @cur_list ;
#####################################
# one result AND defined in the %HASH
if ( 1 == scalar @res and defined Dive($cur_hr, $res[0]) ){
# change the current pointer to the HASH to what entered
$hr = $cur_hr->{ $res[0] };
if ( $debug > 1 ){ print "CHANGING \$hr to: "; dd $hr; }
# save the result
push @choosen, $res[0];
# redefine autocmpletion
define_completion_func($hr);
}
########################################################
# one result ( but NOT defined this result in the HASH )
elsif( 1 == scalar @res ){
# change the current pointer to the HASH to one level above..
$hr = Dive( $cur_hr, @choosen[0..$#choosen-1] );
# ..to commands
$hr = $hr->{commands};
if ( $debug > 1 ){ print "BACK to commands. \$hr: "; dd $hr; }
# save the result
push @choosen, $res[0];
# redefine autocmpletion
define_completion_func($hr);
}
#####################################################
# more then one result: print options still available
elsif ( scalar @res > 1){
# show available options
dd @res;
# mimicry of the prompt and the line at this moment
print $prompt,(join ' ',@choosen), (@choosen ? " " : ""), $text;
# nothing to return
return;
}
#############
# TAB pressed
elsif ( $text eq '' ){
# show available options
dd @res;
# mimicry of the prompt and the line at this moment
print $prompt,(join ' ',@choosen), @choosen ? " " : "";
# nothing to return
return;
}
# elsif ( 0 == @cur_list ){
# how to tell the while loop to terminate??
# }
# RELAXED ??
# extra stuff can be provided ? filling @choosen_relaxed?
else{ die "Unknown happened.." }
# BACKSPACE ?? modify the line? no...
# my $check_line = join ' ',@choosen;
# unless ( $check_line =~ /$line/ ){
# print "BACKSPACE!\n";
# dd @choosen;
# }
if ( $debug > 1 ){ print "RETURNING "; dd @res; }
# return results
return @res;
};
}