html/spellcheck.pl


   1 #!/usr/bin/perl -w
   2 
   3 # ** This script is a 10-minutes-hack, so it's EXPERIMENTAL. **
   4 #
   5 # Requires:
   6 #  - Irssi 0.8.12 or newer (http://irssi.org/).
   7 #  - GNU Aspell with appropriate dictionaries (http://aspell.net/).
   8 #  - Perl module Text::Aspell (available from CPAN).
   9 #
  10 #
  11 # Description:
  12 #  Works as you type, printing suggestions when Aspell thinks
  13 #  your last word was misspelled. 
  14 #  It also adds suggestions to the list of tabcompletions,
  15 #  so once you know last word is wrong, you can go back 
  16 #  and tabcomplete through what Aspell suggests.
  17 #
  18 #
  19 # Settings:
  20 #
  21 #  spellcheck_languages  -- a list of space and/or comma
  22 #    separated languages to use on certain networks/channels.
  23 #    Example: 
  24 #    /set spellcheck_languages netA/#chan1/en_US, #chan2/fi_FI, netB/!chan3/pl_PL
  25 #    will use en_US for #chan1 on network netA, fi_FI for #chan2
  26 #    on every network, and pl_PL for !chan3 on network netB.
  27 #    By default this setting is empty.
  28 #
  29 #  spellcheck_default_language  -- language to use in empty
  30 #    windows, or when nothing from spellcheck_languages matches.
  31 #    Defaults to 'en_US'.
  32 #
  33 #  spellcheck_enabled [ON/OFF]  -- self explaining. Sometimes
  34 #    (like when pasting foreign-language text) you don't want
  35 #    the script to spit out lots of suggestions, and turning it
  36 #    off for a while is the easiest way. By default it's ON.
  37 #
  38 #
  39 # BUGS:
  40 #  - won't catch all mistakes
  41 #  - picking actual words from what you type is very kludgy,
  42 #    you may occasionally see some leftovers like digits or punctuation
  43 #  - works every time you press space or a dot (so won't work for
  44 #    the last word before pressing enter, unless you're using dot
  45 #    to finish your sentences)
  46 #  - when you press space and realize that the word is wrong,
  47 #    you can't tabcomplete to the suggestions right away - you need
  48 #    to use backspace and then tabcomplete. With dot you get an extra
  49 #    space after tabcompletion.
  50 #  - all words will be marked and no suggestions given if 
  51 #    dictionary is missing (ie. wrong spellcheck_default_language)
  52 #  - probably more, please report to $IRSSI{'contact'}
  53 #
  54 #
  55 # $Id: spellcheck.pl 5 2008-05-28 22:31:06Z shasta $
  56 #
  57 
  58 use strict;
  59 use vars qw($VERSION %IRSSI);
  60 use Irssi 20070804;
  61 use Text::Aspell;
  62 
  63 $VERSION = '0.3';
  64 %IRSSI = (
  65     authors     => 'Jakub Jankowski',
  66     contact     => 'shasta@toxcorp.com',
  67     name        => 'Spellcheck',
  68     description => 'Checks for spelling errors using Aspell.',
  69     license     => 'GPLv2',
  70     url         => 'http://toxcorp.com/irc/irssi/spellcheck/',
  71 );
  72 
  73 my %speller;
  74 
  75 sub spellcheck_setup
  76 {
  77     return if (exists $speller{$_[0]} && defined $speller{$_[0]});
  78     $speller{$_[0]} = Text::Aspell->new or return undef;
  79     $speller{$_[0]}->set_option('lang', $_[0]) or return undef;
  80     $speller{$_[0]}->set_option('sug-mode', 'fast') or return undef;
  81     return 1;
  82 }
  83 
  84 # add_rest means "add (whatever you chopped from the word before
  85 # spellchecking it) to the suggestions returned"
  86 sub spellcheck_check_word
  87 {
  88     my ($lang, $word, $add_rest) = @_;
  89     my $win = Irssi::active_win();
  90     my @suggestions = ();
  91 
  92     # setup Text::Aspell for that lang if needed
  93     if (!exists $speller{$lang} || !defined $speller{$lang})
  94     {
  95 	if (!spellcheck_setup($lang))
  96 	{
  97 	    $win->print("Error while setting up spellchecker for $lang");
  98 	    # don't change the message
  99 	    return @suggestions;
 100 	}
 101     }
 102 
 103     # do the spellchecking
 104     my ($stripped, $rest) = $word =~ /([^[:punct:][:digit:]]{2,})(.*)/; # HAX
 105     # Irssi::print("Debug: stripped $word is '$stripped', rest is '$rest'");
 106     if (defined $stripped && !$speller{$lang}->check($stripped))
 107     {
 108 	push(@suggestions, $add_rest ? $_ . $rest : $_) for ($speller{$lang}->suggest($stripped));
 109     }
 110     return @suggestions;
 111 }
 112 
 113 sub spellcheck_find_language
 114 {
 115     my ($network, $target) = @_;
 116     return Irssi::settings_get_str('spellcheck_default_language') unless (defined $network && defined $target);
 117 
 118     # support !channels correctly
 119     $target = '!' . substr($target, 6) if ($target =~ /^\!/);
 120 
 121     # lowercase net/chan
 122     $network = lc($network);
 123     $target  = lc($target);
 124 
 125     # possible settings: network/channel/lang  or  channel/lang
 126     my @languages = split(/[ ,]/, Irssi::settings_get_str('spellcheck_languages'));
 127     for my $langstr (@languages)
 128     {
 129 	# strip trailing slashes
 130 	$langstr =~ s=/+$==;
 131 	# Irssi::print("Debug: checking network $network target $target against langstr $langstr");
 132 	my ($s1, $s2, $s3) = split(/\//, $langstr, 3);
 133 	my ($t, $c, $l);
 134 	if (defined $s3 && $s3 ne '')
 135 	{
 136 	    # network/channel/lang
 137 	    $t = lc($s1); $c = lc($s2); $l = $s3;
 138 	}
 139 	else
 140 	{
 141 	    # channel/lang
 142 	    $c = lc($s1); $l = $s2;
 143 	}
 144 
 145 	if ($c eq $target && (!defined $t || $t eq $network))
 146 	{
 147 	    # Irssi::print("Debug: language found: $l");
 148 	    return $l;
 149 	}
 150     }
 151 
 152     # Irssi::print("Debug: language not found, using default");
 153     # no match, use defaults
 154     return Irssi::settings_get_str('spellcheck_default_language');
 155 }
 156 
 157 sub spellcheck_key_pressed
 158 {
 159     my ($key) = @_;
 160     my $win = Irssi::active_win();
 161 
 162     # I know no way to *mark* misspelled words in the input line,
 163     # that's why there's no spellcheck_print_suggestions -
 164     # because printing suggestions is our only choice.
 165     return unless Irssi::settings_get_bool('spellcheck_enabled');
 166 
 167     # don't bother unless pressed key is space or dot
 168     return unless (chr $key eq ' ' or chr $key eq '.');
 169 
 170     # get current inputline
 171     my $inputline = Irssi::parse_special('$L');
 172 
 173     # check if inputline starts with any of cmdchars
 174     # we shouldn't spellcheck commands
 175     my $cmdchars = Irssi::settings_get_str('cmdchars');
 176     my $re = qr/^[$cmdchars]/;
 177     return if ($inputline =~ $re);
 178 
 179     # get last bit from the inputline
 180     my ($word) = $inputline =~ /\s*([^\s]+)$/;
 181 
 182     # find appropriate language for current window item
 183     my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});
 184 
 185     my @suggestions = spellcheck_check_word($lang, $word, 0);
 186     # Irssi::print("Debug: spellcheck_check_word($word) returned array of " . scalar @suggestions);
 187     return if (scalar @suggestions == 0);
 188 
 189     # we found a mistake, print suggestions
 190     $win->print("Suggestions for $word - " . join(", ", @suggestions));
 191 }
 192 
 193 
 194 sub spellcheck_complete_word
 195 {
 196     my ($complist, $win, $word, $lstart, $wantspace) = @_;
 197 
 198     return unless Irssi::settings_get_bool('spellcheck_enabled');
 199 
 200     # find appropriate language for the current window item
 201     my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});
 202 
 203     # add suggestions to the completion list
 204     push(@$complist, spellcheck_check_word($lang, $word, 1));
 205 }
 206 
 207 
 208 Irssi::settings_add_bool('spellcheck', 'spellcheck_enabled', 1);
 209 Irssi::settings_add_str( 'spellcheck', 'spellcheck_default_language', 'en_US');
 210 Irssi::settings_add_str( 'spellcheck', 'spellcheck_languages', '');
 211 
 212 Irssi::signal_add_first('gui key pressed', 'spellcheck_key_pressed');
 213 Irssi::signal_add_last('complete word', 'spellcheck_complete_word');