1#!/usr/local/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
58use strict;
59use vars qw($VERSION %IRSSI);
60use Irssi 20070804;
61use Text::Aspell;
62
63$VERSION = '0.4';
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
73my %speller;
74
75sub 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"
86sub 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
113sub 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
157sub 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 $cmdre = qr/^[$cmdchars]/;
177    return if ($inputline =~ $cmdre);
178
179    # get last bit from the inputline
180    my ($word) = $inputline =~ /\s*([^\s]+)$/;
181
182    # do not spellcheck urls
183    my $urlre = qr/(^[a-zA-Z]+:\/\/\S+)|(^www)/;
184    return if ($word =~ $urlre);
185
186    # find appropriate language for current window item
187    my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});
188
189    my @suggestions = spellcheck_check_word($lang, $word, 0);
190    # Irssi::print("Debug: spellcheck_check_word($word) returned array of " . scalar @suggestions);
191    return if (scalar @suggestions == 0);
192
193    # we found a mistake, print suggestions
194    $win->print("Suggestions for $word - " . join(", ", @suggestions));
195}
196
197
198sub spellcheck_complete_word
199{
200    my ($complist, $win, $word, $lstart, $wantspace) = @_;
201
202    return unless Irssi::settings_get_bool('spellcheck_enabled');
203
204    # find appropriate language for the current window item
205    my $lang = spellcheck_find_language($win->{active_server}->{tag}, $win->{active}->{name});
206
207    # add suggestions to the completion list
208    push(@$complist, spellcheck_check_word($lang, $word, 1));
209}
210
211
212Irssi::settings_add_bool('spellcheck', 'spellcheck_enabled', 1);
213Irssi::settings_add_str( 'spellcheck', 'spellcheck_default_language', 'en_US');
214Irssi::settings_add_str( 'spellcheck', 'spellcheck_languages', '');
215
216Irssi::signal_add_first('gui key pressed', 'spellcheck_key_pressed');
217Irssi::signal_add_last('complete word', 'spellcheck_complete_word');
218