Tab completion added for usernames and /commands. This was hard, there are likely bugs.
This commit is contained in:
+386
-12
@@ -494,7 +494,7 @@ EOF
|
||||
|
||||
# try to init Term::ReadLine if it was requested
|
||||
# (shakes fist at @br3nda, it's all her fault)
|
||||
%readline_completion = ();
|
||||
our %readline_completion = ();
|
||||
$readline = 1 if (!defined $readline); # Enable readline by default
|
||||
print STDOUT "-- DEBUG: readline=$readline, silent=$silent, script=$script\n" if ($verbose);
|
||||
if ($readline && !$silent && !$script) {
|
||||
@@ -522,8 +522,14 @@ EOF
|
||||
$readline =~ s/^"//; # for optimizer
|
||||
$readline =~ s/"$//;
|
||||
#$termrl->Attribs()->{'autohistory'} = undef; # not yet
|
||||
(%readline_completion) = map {$_ => 1} split(/\s+/, $readline);
|
||||
%original_readline = %readline_completion;
|
||||
# Merge readline config completions with existing completions (don't overwrite)
|
||||
my %config_completions = map {$_ => 1} split(/\s+/, $readline);
|
||||
%original_readline = %config_completions;
|
||||
# Add config completions to our existing hash instead of replacing it
|
||||
my $before_count = scalar(keys %readline_completion);
|
||||
%readline_completion = (%readline_completion, %config_completions);
|
||||
my $after_count = scalar(keys %readline_completion);
|
||||
print $stdout "-- merged " . scalar(keys %config_completions) . " config completions (before: $before_count, after: $after_count)\n" if ($verbose);
|
||||
# readline repaint can't be tested here. we cache our
|
||||
# result later.
|
||||
}
|
||||
@@ -633,6 +639,295 @@ print $stdout "-- termios test: $termios\n" if ($verbose);
|
||||
# Term::ReadLine::Gnu is well-maintained and compatible
|
||||
if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
||||
print $stdout "-- using Term::ReadLine::Gnu for enhanced readline support\n" if ($verbose);
|
||||
|
||||
# Set up comprehensive tab completion
|
||||
my $attribs = $termrl->Attribs;
|
||||
|
||||
# Dynamically extract all commands from the source code
|
||||
my @commands = ();
|
||||
{
|
||||
# Read our own source file to extract commands
|
||||
# Try multiple possible filenames
|
||||
my $source_file = $0;
|
||||
$source_file = 'ttyverse.pl' if (!-f $source_file && -f 'ttyverse.pl');
|
||||
$source_file = './ttyverse.pl' if (!-f $source_file && -f './ttyverse.pl');
|
||||
|
||||
my $source = '';
|
||||
if (-f $source_file) {
|
||||
open(my $fh, '<', $source_file) or warn "Cannot read source file ($source_file): $!";
|
||||
if ($fh) {
|
||||
$source = do { local $/; <$fh> };
|
||||
close($fh);
|
||||
}
|
||||
}
|
||||
|
||||
# If we couldn't read the source file (packaged installation), fall back to manual list
|
||||
my %seen_commands = ();
|
||||
if (!$source || length($source) < 1000) {
|
||||
print $stdout "-- using fallback command list (source file not accessible)\n" if ($verbose);
|
||||
%seen_commands = map { $_ => 1 } qw(
|
||||
/help /? /quit /q /bye /end /e /exit
|
||||
/refresh /r /thump /again /a
|
||||
/dm /dmr /dmrefresh /dms /dmsent /dmagain
|
||||
/replies /re /reply /timeline /timelines
|
||||
/media /visibility /search /se
|
||||
/history /h /print /p /verbose /ve
|
||||
/ruler /ru /cls /clear /url /open
|
||||
/short /sh /rate /ratelimit
|
||||
/track /tron /troff /trends /woeids
|
||||
/notrack /set /unset /add /del
|
||||
/push /pop /list /lists /listfollowers
|
||||
/listfriends /dump /du /eval /ev
|
||||
/version /update /versioncheck /updatecheck
|
||||
/thread /th /entities /ent /delete
|
||||
/deletelast /rtsof /vote /whois /w /me
|
||||
);
|
||||
} else {
|
||||
# Dynamic extraction from source code
|
||||
# Pattern 1: if ($_ eq '/command' || $_ eq '/alias')
|
||||
while ($source =~ /if\s*\(\s*\$_\s+eq\s+['"]([\/][a-z?]+)['"](?:\s*\|\|\s*\$_\s+eq\s+['"]([\/][a-z?]+)['"])?/gi) {
|
||||
$seen_commands{$1}++ if defined $1;
|
||||
$seen_commands{$2}++ if defined $2;
|
||||
}
|
||||
|
||||
# Pattern 2: if (m#^/command# or similar regex patterns
|
||||
while ($source =~ /if\s*\(\s*[^)]*m#?\^([\/][a-z?]+)/gi) {
|
||||
$seen_commands{$1}++;
|
||||
}
|
||||
|
||||
# Pattern 3: return -1 if ($_ eq '/quit' patterns
|
||||
while ($source =~ /return\s+-1\s+if\s*\(\s*\$_\s+eq\s+['"]([\/][a-z?]+)['"](?:\s*\|\|\s*\$_\s+eq\s+['"]([\/][a-z?]+)['"])?/gi) {
|
||||
$seen_commands{$1}++ if defined $1;
|
||||
$seen_commands{$2}++ if defined $2;
|
||||
}
|
||||
|
||||
# Some manual additions for complex patterns we might miss
|
||||
$seen_commands{'/reply'}++; # Often has complex parsing
|
||||
$seen_commands{'/me'}++;
|
||||
$seen_commands{'/url'}++;
|
||||
$seen_commands{'/open'}++;
|
||||
}
|
||||
|
||||
@commands = sort keys %seen_commands;
|
||||
print $stdout "-- extracted " . scalar(@commands) . " commands for tab completion\n" if ($verbose);
|
||||
print $stdout "-- commands: " . join(" ", @commands) . "\n" if ($verbose);
|
||||
}
|
||||
|
||||
# Custom completion function
|
||||
$attribs->{attempted_completion_function} = sub {
|
||||
my ($text, $line, $start, $end) = @_;
|
||||
|
||||
# Debug output
|
||||
print STDERR "COMPLETION DEBUG: text='$text' line='$line' start=$start end=$end\n" if ($verbose);
|
||||
my $hash_size = scalar(keys %readline_completion);
|
||||
my @hash_keys = keys %readline_completion;
|
||||
print STDERR "COMPLETION DEBUG: Hash has $hash_size entries: " . join(", ", @hash_keys) . "\n" if ($verbose);
|
||||
|
||||
# Command completion at start of line or after whitespace
|
||||
if ($line =~ /^\s*\/\w*$/ || ($start == 0 && $text =~ /^\//) ||
|
||||
($line =~ /^\s+$/ && $text =~ /^\//)) {
|
||||
|
||||
print STDERR "COMPLETION: Trying command completion\n" if ($verbose);
|
||||
my @matches = grep { index($_, $text) == 0 } @commands;
|
||||
print STDERR "COMPLETION: Found " . scalar(@matches) . " command matches: " . join(", ", @matches) . "\n" if ($verbose);
|
||||
|
||||
if (@matches) {
|
||||
return $termrl->completion_matches($text, sub {
|
||||
my ($text, $state) = @_;
|
||||
return $matches[$state] // undef;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
# @mention completion - check if line has @ and we're completing after it
|
||||
if ($line =~ /\@/ && $start >= 1 && substr($line, $start-1, 1) eq '@') {
|
||||
print STDERR "COMPLETION: Trying \@mention completion for '$text' (line: '$line', start: $start)\n" if ($verbose);
|
||||
print STDERR "COMPLETION: Hash reference check: " . \%readline_completion . "\n" if ($verbose);
|
||||
my @mentions = keys %readline_completion;
|
||||
print STDERR "COMPLETION: Total readline_completion keys: " . scalar(@mentions) . "\n" if ($verbose);
|
||||
print STDERR "COMPLETION: All keys: " . join(", ", @mentions) . "\n" if ($verbose);
|
||||
@mentions = grep { /^@/ } @mentions; # Only @mentions
|
||||
|
||||
# If no @mentions in completion hash, try to extract from timeline cache
|
||||
if (!@mentions && %tl) {
|
||||
print STDERR "COMPLETION: No mentions in cache, extracting from timeline\n" if ($verbose);
|
||||
foreach my $post_id (keys %tl) {
|
||||
my $post = $tl{$post_id};
|
||||
if ($post && $post->{'user'}) {
|
||||
my $user = $post->{'user'};
|
||||
my $username = $user->{'acct'} || $user->{'username'} || $user;
|
||||
if ($username) {
|
||||
my $mention = '@' . $username;
|
||||
push @mentions, $mention;
|
||||
# Also add to completion hash for future use
|
||||
$readline_completion{$mention}++;
|
||||
}
|
||||
# Also check for boost attribution
|
||||
if ($post->{'boost_attribution'}) {
|
||||
my $booster = '@' . $post->{'boost_attribution'};
|
||||
push @mentions, $booster;
|
||||
$readline_completion{$booster}++;
|
||||
}
|
||||
}
|
||||
}
|
||||
# Remove duplicates
|
||||
my %seen = ();
|
||||
@mentions = grep { !$seen{$_}++ } @mentions;
|
||||
print STDERR "COMPLETION: Extracted " . scalar(@mentions) . " mentions from timeline\n" if ($verbose);
|
||||
&save_completion_cache if (@mentions > 0);
|
||||
}
|
||||
|
||||
print STDERR "COMPLETION: Available mentions: " . join(", ", @mentions) . "\n" if ($verbose);
|
||||
|
||||
# Filter matches based on what user has typed (text doesn't include @)
|
||||
# We need to match against the part after @ in the mention
|
||||
my @matches = ();
|
||||
foreach my $mention (@mentions) {
|
||||
my $username = $mention;
|
||||
$username =~ s/^@//; # Remove @ prefix for matching
|
||||
if ($text eq '' || index(lc($username), lc($text)) == 0) {
|
||||
push @matches, $username; # Return without @ since completion will add it
|
||||
}
|
||||
}
|
||||
print STDERR "COMPLETION: Filtered matches: " . join(", ", @matches) . "\n" if ($verbose);
|
||||
|
||||
if (@matches) {
|
||||
return $termrl->completion_matches($text, sub {
|
||||
my ($text, $state) = @_;
|
||||
return $matches[$state] // undef;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
# Try @ completion when user types @ anywhere
|
||||
if ($text eq '@' || ($line =~ /\@$/ && $text eq '')) {
|
||||
print STDERR "COMPLETION: Trying bare \@ completion\n" if ($verbose);
|
||||
my @mentions = keys %readline_completion;
|
||||
@mentions = grep { /^@/ } @mentions;
|
||||
print STDERR "COMPLETION: Available mentions: " . join(", ", @mentions) . "\n" if ($verbose);
|
||||
|
||||
if (@mentions) {
|
||||
return $termrl->completion_matches('@', sub {
|
||||
my ($text, $state) = @_;
|
||||
return $mentions[$state] // undef;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
# File path completion for /media command
|
||||
if ($line =~ /^\/media\s+/ && $start > 7) {
|
||||
print STDERR "COMPLETION: Using file completion for /media\n" if ($verbose);
|
||||
# Let default filename completion handle this
|
||||
return ();
|
||||
}
|
||||
|
||||
# Username completion for commands that take usernames
|
||||
if ($line =~ /^\/(?:whois|w|again|a|list|lists|reply)\s+/ && $text !~ /^[\/@]/) {
|
||||
print STDERR "COMPLETION: Trying username completion\n" if ($verbose);
|
||||
my @mentions = keys %readline_completion;
|
||||
@mentions = grep { /^@/ } @mentions;
|
||||
|
||||
# Remove @ prefix for username-only completion
|
||||
my @usernames = map { substr($_, 1) } @mentions;
|
||||
@usernames = grep { index(lc($_), lc($text)) == 0 } @usernames if $text;
|
||||
print STDERR "COMPLETION: Username matches: " . join(", ", @usernames) . "\n" if ($verbose);
|
||||
|
||||
if (@usernames) {
|
||||
return $termrl->completion_matches($text, sub {
|
||||
my ($text, $state) = @_;
|
||||
return $usernames[$state] // undef;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
print STDERR "COMPLETION: No completion available\n" if ($verbose);
|
||||
# No completion
|
||||
return ();
|
||||
};
|
||||
|
||||
# Load persistent completion cache
|
||||
my $completion_cache_file = "$config/completion_cache";
|
||||
if (-f $completion_cache_file) {
|
||||
if (open(my $cache_fh, '<', $completion_cache_file)) {
|
||||
my $read_count = 0;
|
||||
my $validated_count = 0;
|
||||
while (my $line = <$cache_fh>) {
|
||||
chomp $line;
|
||||
$read_count++;
|
||||
print STDERR "CACHE_LOAD: Read line $read_count: '$line'\n" if ($verbose);
|
||||
next unless $line;
|
||||
# Validate username format before adding
|
||||
if ($line =~ /^@[a-zA-Z0-9_.-]+(?:@[a-zA-Z0-9.-]+)?$/) {
|
||||
print STDERR "CACHE_LOAD: Validated and adding: '$line'\n" if ($verbose);
|
||||
$readline_completion{$line}++;
|
||||
$validated_count++;
|
||||
} else {
|
||||
print STDERR "CACHE_LOAD: Failed validation: '$line'\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
close($cache_fh);
|
||||
my $loaded_count = scalar(grep { /^@/ } keys %readline_completion);
|
||||
print STDERR "CACHE_LOAD: Read $read_count lines, validated $validated_count, hash has $loaded_count entries\n" if ($verbose);
|
||||
my @loaded_keys = sort(grep { /^@/ } keys %readline_completion);
|
||||
print STDERR "CACHE_LOAD: Loaded keys: " . join(", ", @loaded_keys) . "\n" if ($verbose);
|
||||
print $stdout "-- loaded $loaded_count cached usernames for tab completion\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
# Populate completion cache from existing timeline cache
|
||||
if (%tl) {
|
||||
print $stdout "-- populating completion cache from existing timeline data\n" if ($verbose);
|
||||
my $added_count = 0;
|
||||
foreach my $post_id (keys %tl) {
|
||||
my $post = $tl{$post_id};
|
||||
if ($post && $post->{'user'}) {
|
||||
my $user = $post->{'user'};
|
||||
# Extract username from user object
|
||||
my $username = $user->{'acct'} || $user->{'username'} || $user;
|
||||
if ($username) {
|
||||
$readline_completion{'@'.$username}++;
|
||||
$added_count++;
|
||||
}
|
||||
# Also check for boost attribution
|
||||
if ($post->{'boost_attribution'}) {
|
||||
$readline_completion{'@'.$post->{'boost_attribution'}}++;
|
||||
$added_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
print $stdout "-- added $added_count usernames from cached timeline to completion\n" if ($verbose);
|
||||
&save_completion_cache if ($added_count > 0);
|
||||
}
|
||||
|
||||
print $stdout "-- tab completion enabled for commands, @mentions, and file paths\n" if ($verbose);
|
||||
}
|
||||
|
||||
# Save completion cache to disk (with rate limiting)
|
||||
our $last_cache_save = 0;
|
||||
sub save_completion_cache {
|
||||
return unless ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu');
|
||||
|
||||
# Rate limit saves to every 30 seconds to avoid excessive disk I/O
|
||||
my $now = time();
|
||||
return if ($now - $last_cache_save < 30);
|
||||
$last_cache_save = $now;
|
||||
|
||||
my $completion_cache_file = "$config/completion_cache";
|
||||
|
||||
# Create config directory if it doesn't exist
|
||||
unless (-d $config) {
|
||||
mkdir($config, 0700) or return;
|
||||
}
|
||||
|
||||
# Get all @mentions, validate them, and save
|
||||
my @mentions = grep { /^@/ } keys %readline_completion;
|
||||
@mentions = grep { /^@[a-zA-Z0-9_.-]+(?:@[a-zA-Z0-9.-]+)?$/ } @mentions; # Validate format
|
||||
|
||||
if (open(my $cache_fh, '>', $completion_cache_file)) {
|
||||
print $cache_fh "$_\n" for sort @mentions;
|
||||
close($cache_fh);
|
||||
print $stdout "-- saved " . scalar(@mentions) . " usernames to completion cache: " . join(", ", sort @mentions) . "\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
# try to get signal numbers for SIG* from POSIX. use internals if failed.
|
||||
@@ -2947,7 +3242,10 @@ EOF
|
||||
$countmaybe += 0;
|
||||
|
||||
$uname =~ s/^\@//;
|
||||
$readline_completion{'@'.$uname}++ if ($termrl);
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.$uname}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
print $stdout
|
||||
"-- synchronous /again command for $uname ($countmaybe)\n"
|
||||
if ($verbose);
|
||||
@@ -2971,7 +3269,10 @@ EOF
|
||||
if ($_ =~ m#^/w(hois|a|again)?\s+(\+\d+\s+)?\@?([^\s]+)#) {
|
||||
my $uname = lc($3);
|
||||
$uname =~ s/^\@//;
|
||||
$readline_completion{'@'.$uname}++ if ($termrl);
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.$uname}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
print $stdout "-- synchronous /whois command for $uname\n"
|
||||
if ($verbose);
|
||||
|
||||
@@ -3751,7 +4052,10 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
||||
} else {
|
||||
$_ = ".$_";
|
||||
}
|
||||
$readline_completion{'@'.lc($target)}++ if ($termrl);
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.lc($target)}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
print $stdout &wwrap("(expanded to \"$_\")");
|
||||
print $stdout "\n";
|
||||
goto POSTPRINT; # fugly! FUGLY!
|
||||
@@ -3771,7 +4075,10 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
||||
}
|
||||
# in the future, add DM in_reply_to here
|
||||
my $target = &descape($dm->{'last_status'}->{'account'}->{'acct'} || $dm->{'last_status'}->{'account'}->{'username'});
|
||||
$readline_completion{'@'.lc($target)}++ if ($termrl);
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.lc($target)}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
$_ = "/dm $target $_";
|
||||
print $stdout &wwrap("(expanded to \"$_\")");
|
||||
print $stdout "\n";
|
||||
@@ -3822,8 +4129,10 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
||||
$_ .= " $text";
|
||||
|
||||
# add everyone in did_mentions to readline_completion
|
||||
grep { $readline_completion{'@'.$_}++ } (keys %did_mentions)
|
||||
if ($termrl);
|
||||
if ($termrl) {
|
||||
grep { $readline_completion{'@'.$_}++ } (keys %did_mentions);
|
||||
&save_completion_cache;
|
||||
}
|
||||
|
||||
# and fall through to post
|
||||
print $stdout &wwrap("(expanded to \"$_\")");
|
||||
@@ -5958,6 +6267,17 @@ sub tdisplay { # used by both synchronous /again and asynchronous refreshes
|
||||
print $stdout "-- DEBUG: No valid posts for ID calculation, using last_id='$last_id'\n" if ($verbose);
|
||||
}
|
||||
print $stdout "-- DEBUG: tdisplay returning max_id='$new_max_id' (from " . scalar(@{ $my_json_ref }) . " posts)\n" if ($verbose);
|
||||
|
||||
# Save completion cache after timeline processing
|
||||
if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
||||
my $cache_count = scalar(grep { /^@/ } keys %readline_completion);
|
||||
if ($cache_count > 0) {
|
||||
$last_cache_save = 0; # Force save
|
||||
&save_completion_cache;
|
||||
print $stdout "-- saved completion cache after timeline processing ($cache_count entries)\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
return ($new_max_id, $j);
|
||||
}
|
||||
|
||||
@@ -6171,6 +6491,14 @@ sub dmrefresh {
|
||||
}
|
||||
$dm_first_time = 0 if ($last_dm || !scalar(@{ $my_json_ref }));
|
||||
print $stdout "-- dm bookmark is $last_dm.\n" if ($verbose);
|
||||
|
||||
# Save completion cache after processing DMs
|
||||
my $dm_users_added = scalar(grep { /^@/ } keys %readline_completion) - 10; # original timeline users
|
||||
if ($dm_users_added > 0) {
|
||||
print $stdout "-- saving completion cache after DM processing ($dm_users_added DM users added)\n" if ($verbose);
|
||||
&save_completion_cache;
|
||||
}
|
||||
|
||||
&$dmconclude;
|
||||
&send_repaint if ($termrl);
|
||||
}
|
||||
@@ -6547,9 +6875,28 @@ sub standardpost {
|
||||
my $sn = &descape($ref->{'user'}->{'acct'} || $ref->{'user'}->{'username'});
|
||||
my $post = &descape($ref->{'text'});
|
||||
|
||||
# Add usernames to completion cache
|
||||
if ($termrl && $sn) {
|
||||
$readline_completion{'@'.$sn}++;
|
||||
my $total_keys = scalar(keys %readline_completion);
|
||||
print STDERR "COMPLETION: Added \@$sn to completion cache (total: $total_keys)\n" if ($verbose);
|
||||
# Don't save on every add - let the final save handle it
|
||||
}
|
||||
|
||||
# Debug boost display
|
||||
if (exists($ref->{'boost_attribution'}) && $ref->{'boost_attribution'}) {
|
||||
print $stdout "-- DEBUG: standardpost - user: '$sn', text: '$post', boost_attribution: '" . $ref->{'boost_attribution'} . "'\n" if ($verbose);
|
||||
|
||||
# Also add boost attribution to completion cache
|
||||
if ($termrl) {
|
||||
my $booster = &descape($ref->{'boost_attribution'});
|
||||
if ($booster) {
|
||||
$readline_completion{'@'.$booster}++;
|
||||
my $total_keys = scalar(keys %readline_completion);
|
||||
print STDERR "COMPLETION: Added \@$booster (booster) to completion cache (total: $total_keys)\n" if ($verbose);
|
||||
# Don't save on every add - let the final save handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $colour;
|
||||
@@ -7330,7 +7677,22 @@ sub defaultconclude {
|
||||
sub defaultdmhandle {
|
||||
(&flag_default_call, return) if ($multi_module_context);
|
||||
my $dm_ref = shift;
|
||||
my $sns = &descape($dm_ref->{'last_status'}->{'account'}->{'username'} || $dm_ref->{'last_status'}->{'account'}->{'acct'});
|
||||
my $sns = &descape($dm_ref->{'last_status'}->{'account'}->{'acct'} || $dm_ref->{'last_status'}->{'account'}->{'username'});
|
||||
|
||||
# Add DM username to completion cache
|
||||
print STDERR "DM_DEBUG: sns='$sns', whoami='$whoami'\n" if ($verbose && $sns);
|
||||
if ($sns && $sns ne $whoami) {
|
||||
my $username = $sns;
|
||||
$username = '@' . $username unless $username =~ /^@/;
|
||||
print STDERR "DM_DEBUG: Processing username '$username' for completion\n" if ($verbose);
|
||||
if (!exists $readline_completion{$username}) {
|
||||
$readline_completion{$username}++;
|
||||
my $total = scalar(grep { /^@/ } keys %readline_completion);
|
||||
print STDERR "COMPLETION: Added $username (DM user) to completion cache (total: $total)\n" if ($verbose);
|
||||
} else {
|
||||
print STDERR "DM_DEBUG: Username '$username' already in completion cache\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
my $dm_content = &standarddm($dm_ref);
|
||||
print $streamout $dm_content;
|
||||
@@ -7502,10 +7864,16 @@ sub defaultpostpost {
|
||||
|
||||
# populate %readline_completion if readline is on
|
||||
while($line =~ s/^\@(\w+)\s+//) {
|
||||
$readline_completion{'@'.lc($1)}++;
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.lc($1)}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
}
|
||||
if ($line =~ /^[dD]\s+(\w+)\s+/) {
|
||||
$readline_completion{'@'.lc($1)}++;
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.lc($1)}++;
|
||||
&save_completion_cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7823,6 +8191,12 @@ sub dmthump { print C "dmthump------------\n"; &sync_semaphore; }
|
||||
sub dmthump_no_skip { print C "dmthump_no_skip----\n"; &sync_semaphore; }
|
||||
|
||||
sub sync_n_quit {
|
||||
# Save completion cache before exiting
|
||||
if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
||||
$last_cache_save = 0; # Force save on exit
|
||||
&save_completion_cache;
|
||||
}
|
||||
|
||||
if ($child) {
|
||||
print $stdout "waiting for child ...\n" unless ($silent);
|
||||
print C "sync---------------\n";
|
||||
|
||||
Reference in New Issue
Block a user