use strict; use warnings; use Xchat (); use File::Spec (); use File::Basename qw(fileparse); # if the last time you addressed someone was greater than this many minutes # ago, ignore it # this avoids having people you have talked to a long time ago coming up too # early in the completion list # Setting this to 0 will disable the check which is effectively the same as # setting it to infinity my $last_use_threshold = 10; # 10 minutes # added to the front of a completion the same way as a suffix, only if # the word is at the beginning of the line my $prefix = ''; # ignore leading non-alphanumeric characters: -[\]^_`{|} # Assuming you have the following nicks in a channel: # [SomeNick] _SomeNick_ `SomeNick SomeNick SomeOtherNick # when $ignore_leading_non_alnum is set to 0 # s will cycle through SomeNick and SomeOtherNick # when $ignore_leading_non_alnum is set to 1 # s will cycle through [SomeNick] _SomeNick_ `SomeNick SomeNick # SomeOtherNick my $ignore_leading_non_alnum = 0; # enable path completion my $path_completion = 1; my $base_path = ''; # ignore the completion_amount setting and always cycle through nicks with tab my $always_cycle = 0; Xchat::register( "Tab Completion", "1.0500", "Alternative tab completion behavior" ); Xchat::hook_print( "Key Press", \&complete ); Xchat::hook_print( "Close Context", \&close_context ); Xchat::hook_print( "Focus Tab", \&focus_tab ); Xchat::hook_print( "Part", \&clean_selected ); Xchat::hook_print( "Part with Reason", \&clean_selected ); Xchat::hook_command( "", \&track_selected ); sub SHIFT() { 1 } sub CTRL() { 4 } sub ALT() { 8 } sub TAB() { 0xFF09 } sub LEFT_TAB() { 0xFE20 } my %completions; my %last_visit; my %selected; my %escape_map = ( '[' => qr![\[{]!, '{' => qr![\[{]!, '}' => qr![\]}]!, ']' => qr![\]}]!, '\\' => qr![\\\|]!, '|' => qr![\\\|]!, '.' => qr!\.!, '^' => qr!\^!, '$' => qr!\$!, '*' => qr!\*!, '+' => qr!\+!, '?' => qr!\?!, '(' => qr!\(!, ')' => qr!\)!, '-' => qr!\-!, ); my $escapes = join "", keys %escape_map; $escapes = qr/[\Q$escapes\E]/; # used to determine if a word is the start of a path my $path_pattern = qr{^(?:~|/|[[:alpha:]]:\\)}; sub complete { my ($key, $modifiers) = @{$_[0]}; # if $_[0][0] contains the value of the key pressed # $_[0][1] contains modifiers # the value for tab is 0xFF09 # the value for shift-tab(Left Tab) is 0xFE20 # we don't care about other keys # the key must be a tab and left tab return Xchat::EAT_NONE unless $key == TAB || $key == LEFT_TAB; # if it is a tab then it must not have any modifiers return Xchat::EAT_NONE if $key == TAB && $modifiers & (CTRL|ALT|SHIFT); # loop backwards for shift+tab/left tab my $delta = $modifiers & SHIFT ? -1 : 1; my $context = Xchat::get_context; $completions{$context} ||= {}; my $completions = $completions{$context}; $completions->{pos} ||= -1; my $suffix = Xchat::get_prefs( "completion_suffix" ); $suffix =~ s/^\s+//; my $input = Xchat::get_info( "inputbox" ); my $cursor_pos = Xchat::get_info( "state_cursor" ); my $left = substr( $input, 0, $cursor_pos ); my $right = substr( $input, $cursor_pos ); my $length = length $left; # trim spaces from the end of $left to avoid grabbing the wrong word # this is mainly needed for completion at the very beginning where a space # is added after the completion $left =~ s/\s+$//; # always add one to the index because # 1) if a space is found we want the position after it # 2) if a space isn't found then we get back -1 my $word_start = rindex( $left, " " ) + 1; my $word = substr( $left, $word_start ); $left = substr( $left, 0, -length $word ); if( $cursor_pos == $completions->{pos} ) { my $previous_word = $completions->{completed}; my $new_left = $input; substr( $new_left, $cursor_pos ) = ""; if( $previous_word and $new_left =~ s/(\Q$previous_word\E)$// ) { $word = $1; $word_start = length( $new_left ); $left = $new_left; } } my $command_char = Xchat::get_prefs( "input_command_char" ); # ignore commands if( ($word !~ m{^[${command_char}]}) or ( $word =~ m{^[${command_char}]} and $word_start != 0 ) ) { if( $cursor_pos == length $input # end of input box # not a valid nick char && $input =~ /(?{pos} # not continuing a completion && $word !~ m{^(?:[&#/~]|[[:alpha:]]:\\)} # not a channel or path ) { # check for path completion unless( $path_completion and $word =~ $path_pattern ) { $word_start = $cursor_pos; $left = $input; $length = length $length; $right = ""; $word = ""; } } if( $word_start == 0 && $prefix && $word =~ /^\Q$prefix/ ) { $word =~ s/^\Q$prefix//; } my $completed; # this is going to be the "completed" word # for parital completions and channel names so a : isn't added #$completions->{skip_suffix} = ($word =~ /^[&#]/) ? 1 : 0; # continuing from a previous completion if( exists $completions->{matches} && @{$completions->{matches}} && $cursor_pos == $completions->{pos} && $word =~ /^\Q$completions->{matches}[$completions->{index}]/ ) { $completions->{index} += $delta; if( $completions->{index} < 0 ) { $completions->{index} += @{$completions->{matches}}; } else { $completions->{index} %= @{$completions->{matches}}; } } else { if( $word =~ /^[&#]/ ) { # channel name completion $completions->{matches} = [ matching_channels( $word ) ]; $completions->{skip_suffix} = 0; } elsif( $path_completion and $word =~ $path_pattern ) { # file name completion $completions->{matches} = [ matching_files( $word ) ]; $completions->{skip_suffix} = 1; } else { # nick completion # fix $word so { equals [, ] equals }, \ equals | # and escape regex metacharacters $word =~ s/($escapes)/$escape_map{$1}/g; $completions->{matches} = [ matching_nicks( $word ) ]; $completions->{skip_suffix} = 0; } $completions->{index} = 0; } $completed = $completions->{matches}[ $completions->{index} ]; $completions->{completed} = $completed; my $completion_amount = Xchat::get_prefs( "completion_amount" ); # don't cycle if the number of possible completions is greater than # completion_amount if( !$always_cycle && ( @{$completions->{matches}} > $completion_amount && @{$completions->{matches}} != 1 ) ) { # don't print if we tabbed in the beginning and the list of possible # completions includes all nicks in the channel my $context_type = Xchat::context_info->{type}; if( $context_type != 2 # not a channel or @{$completions->{matches}} < Xchat::get_list("users") ) { Xchat::print( join " ", @{$completions->{matches}}, "\n" ); } $completed = lcs( $completions->{matches} ); $completions->{skip_suffix} = 1; } if( $completed ) { if( $word_start == 0 && !$completions->{skip_suffix} ) { # at the start of the line append completion suffix Xchat::command( "settext $prefix$completed$suffix$right"); $completions->{pos} = length( "$prefix$completed$suffix" ); } else { Xchat::command( "settext $left$completed
AppName=HexChat Spelling Dictionaries
AppVerName=HexChat Spelling Dictionaries r1
AppVersion=1.0
VersionInfoVersion=1.0
OutputBaseFilename=HexChat Spelling Dictionaries r1
AppPublisher=HexChat
AppPublisherURL=http://www.hexchat.org/
AppCopyright=Copyright (C) 1998-2010 Peter Zelezny
AppSupportURL=https://github.com/hexchat/hexchat/issues
AppUpdatesURL=http://www.hexchat.org/home/downloads
DefaultDirName={localappdata}\enchant
DefaultGroupName=HexChat
DisableProgramGroupPage=yes
DisableDirPage=yes
SolidCompression=yes
Compression=lzma2/ultra64
SourceDir=.
OutputDir=.
FlatComponentsList=no
PrivilegesRequired=lowest
ShowComponentSizes=no
CreateUninstallRegKey=no
Uninstallable=no
DirExistsWarning=no
ArchitecturesAllowed=x86 x64

[Files]
Source: "myspell\*"; DestDir: "