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');