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
|
# try to init Term::ReadLine if it was requested
|
||||||
# (shakes fist at @br3nda, it's all her fault)
|
# (shakes fist at @br3nda, it's all her fault)
|
||||||
%readline_completion = ();
|
our %readline_completion = ();
|
||||||
$readline = 1 if (!defined $readline); # Enable readline by default
|
$readline = 1 if (!defined $readline); # Enable readline by default
|
||||||
print STDOUT "-- DEBUG: readline=$readline, silent=$silent, script=$script\n" if ($verbose);
|
print STDOUT "-- DEBUG: readline=$readline, silent=$silent, script=$script\n" if ($verbose);
|
||||||
if ($readline && !$silent && !$script) {
|
if ($readline && !$silent && !$script) {
|
||||||
@@ -522,8 +522,14 @@ EOF
|
|||||||
$readline =~ s/^"//; # for optimizer
|
$readline =~ s/^"//; # for optimizer
|
||||||
$readline =~ s/"$//;
|
$readline =~ s/"$//;
|
||||||
#$termrl->Attribs()->{'autohistory'} = undef; # not yet
|
#$termrl->Attribs()->{'autohistory'} = undef; # not yet
|
||||||
(%readline_completion) = map {$_ => 1} split(/\s+/, $readline);
|
# Merge readline config completions with existing completions (don't overwrite)
|
||||||
%original_readline = %readline_completion;
|
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
|
# readline repaint can't be tested here. we cache our
|
||||||
# result later.
|
# result later.
|
||||||
}
|
}
|
||||||
@@ -633,6 +639,295 @@ print $stdout "-- termios test: $termios\n" if ($verbose);
|
|||||||
# Term::ReadLine::Gnu is well-maintained and compatible
|
# Term::ReadLine::Gnu is well-maintained and compatible
|
||||||
if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
||||||
print $stdout "-- using Term::ReadLine::Gnu for enhanced readline support\n" if ($verbose);
|
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.
|
# try to get signal numbers for SIG* from POSIX. use internals if failed.
|
||||||
@@ -2947,7 +3242,10 @@ EOF
|
|||||||
$countmaybe += 0;
|
$countmaybe += 0;
|
||||||
|
|
||||||
$uname =~ s/^\@//;
|
$uname =~ s/^\@//;
|
||||||
$readline_completion{'@'.$uname}++ if ($termrl);
|
if ($termrl) {
|
||||||
|
$readline_completion{'@'.$uname}++;
|
||||||
|
&save_completion_cache;
|
||||||
|
}
|
||||||
print $stdout
|
print $stdout
|
||||||
"-- synchronous /again command for $uname ($countmaybe)\n"
|
"-- synchronous /again command for $uname ($countmaybe)\n"
|
||||||
if ($verbose);
|
if ($verbose);
|
||||||
@@ -2971,7 +3269,10 @@ EOF
|
|||||||
if ($_ =~ m#^/w(hois|a|again)?\s+(\+\d+\s+)?\@?([^\s]+)#) {
|
if ($_ =~ m#^/w(hois|a|again)?\s+(\+\d+\s+)?\@?([^\s]+)#) {
|
||||||
my $uname = lc($3);
|
my $uname = lc($3);
|
||||||
$uname =~ s/^\@//;
|
$uname =~ s/^\@//;
|
||||||
$readline_completion{'@'.$uname}++ if ($termrl);
|
if ($termrl) {
|
||||||
|
$readline_completion{'@'.$uname}++;
|
||||||
|
&save_completion_cache;
|
||||||
|
}
|
||||||
print $stdout "-- synchronous /whois command for $uname\n"
|
print $stdout "-- synchronous /whois command for $uname\n"
|
||||||
if ($verbose);
|
if ($verbose);
|
||||||
|
|
||||||
@@ -3751,7 +4052,10 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
|||||||
} else {
|
} else {
|
||||||
$_ = ".$_";
|
$_ = ".$_";
|
||||||
}
|
}
|
||||||
$readline_completion{'@'.lc($target)}++ if ($termrl);
|
if ($termrl) {
|
||||||
|
$readline_completion{'@'.lc($target)}++;
|
||||||
|
&save_completion_cache;
|
||||||
|
}
|
||||||
print $stdout &wwrap("(expanded to \"$_\")");
|
print $stdout &wwrap("(expanded to \"$_\")");
|
||||||
print $stdout "\n";
|
print $stdout "\n";
|
||||||
goto POSTPRINT; # fugly! FUGLY!
|
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
|
# in the future, add DM in_reply_to here
|
||||||
my $target = &descape($dm->{'last_status'}->{'account'}->{'acct'} || $dm->{'last_status'}->{'account'}->{'username'});
|
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 $_";
|
$_ = "/dm $target $_";
|
||||||
print $stdout &wwrap("(expanded to \"$_\")");
|
print $stdout &wwrap("(expanded to \"$_\")");
|
||||||
print $stdout "\n";
|
print $stdout "\n";
|
||||||
@@ -3822,8 +4129,10 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
|||||||
$_ .= " $text";
|
$_ .= " $text";
|
||||||
|
|
||||||
# add everyone in did_mentions to readline_completion
|
# 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
|
# and fall through to post
|
||||||
print $stdout &wwrap("(expanded to \"$_\")");
|
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: 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);
|
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);
|
return ($new_max_id, $j);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6171,6 +6491,14 @@ sub dmrefresh {
|
|||||||
}
|
}
|
||||||
$dm_first_time = 0 if ($last_dm || !scalar(@{ $my_json_ref }));
|
$dm_first_time = 0 if ($last_dm || !scalar(@{ $my_json_ref }));
|
||||||
print $stdout "-- dm bookmark is $last_dm.\n" if ($verbose);
|
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;
|
&$dmconclude;
|
||||||
&send_repaint if ($termrl);
|
&send_repaint if ($termrl);
|
||||||
}
|
}
|
||||||
@@ -6547,9 +6875,28 @@ sub standardpost {
|
|||||||
my $sn = &descape($ref->{'user'}->{'acct'} || $ref->{'user'}->{'username'});
|
my $sn = &descape($ref->{'user'}->{'acct'} || $ref->{'user'}->{'username'});
|
||||||
my $post = &descape($ref->{'text'});
|
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
|
# Debug boost display
|
||||||
if (exists($ref->{'boost_attribution'}) && $ref->{'boost_attribution'}) {
|
if (exists($ref->{'boost_attribution'}) && $ref->{'boost_attribution'}) {
|
||||||
print $stdout "-- DEBUG: standardpost - user: '$sn', text: '$post', boost_attribution: '" . $ref->{'boost_attribution'} . "'\n" if ($verbose);
|
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;
|
my $colour;
|
||||||
@@ -7330,7 +7677,22 @@ sub defaultconclude {
|
|||||||
sub defaultdmhandle {
|
sub defaultdmhandle {
|
||||||
(&flag_default_call, return) if ($multi_module_context);
|
(&flag_default_call, return) if ($multi_module_context);
|
||||||
my $dm_ref = shift;
|
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);
|
my $dm_content = &standarddm($dm_ref);
|
||||||
print $streamout $dm_content;
|
print $streamout $dm_content;
|
||||||
@@ -7502,10 +7864,16 @@ sub defaultpostpost {
|
|||||||
|
|
||||||
# populate %readline_completion if readline is on
|
# populate %readline_completion if readline is on
|
||||||
while($line =~ s/^\@(\w+)\s+//) {
|
while($line =~ s/^\@(\w+)\s+//) {
|
||||||
$readline_completion{'@'.lc($1)}++;
|
if ($termrl) {
|
||||||
|
$readline_completion{'@'.lc($1)}++;
|
||||||
|
&save_completion_cache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($line =~ /^[dD]\s+(\w+)\s+/) {
|
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 dmthump_no_skip { print C "dmthump_no_skip----\n"; &sync_semaphore; }
|
||||||
|
|
||||||
sub sync_n_quit {
|
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) {
|
if ($child) {
|
||||||
print $stdout "waiting for child ...\n" unless ($silent);
|
print $stdout "waiting for child ...\n" unless ($silent);
|
||||||
print C "sync---------------\n";
|
print C "sync---------------\n";
|
||||||
|
|||||||
Reference in New Issue
Block a user