# typofix.pl - when someone uses s/foo/bar typofixing, this script really # goes and modifies the original text on screen. use strict; use warnings; use Irssi qw( settings_get_str settings_get_bool settings_add_str settings_add_bool signal_add signal_stop ); use Irssi 20140701; use Irssi::TextUI; use Algorithm::Diff 'sdiff'; our $VERSION = '1.12'; # 4be3787e5717715 our %IRSSI = ( authors => 'Juerd (first version: Timo Sirainen, additions by: Qrczak)', contact => '[email protected], [email protected], [email protected]', name => 'Typofix', description => 'When someone uses s/foo/bar/, this really modifies the text', license => 'Same as Irssi', url => 'http://juerd.nl/irssi/', changed => 'Sat Jun 28 16:24:26 CEST 2014', upgrade_info => '/set typofix_modify_string %wold%gnew%n', NOTE1 => 'you need irssi 0.8.17' ); # /SET typofix_modify_string [fixed] - append string after replaced text # /SET typofix_hide_replace NO - hide the s/foo/bar/ line # (J) /SET typofix_format - format with "old" and "new" in it my $chars = '/|;:\'"_=+*&^%$#@!~,.?-'; my $regex = qq{(?x-sm: # "s/foo/bar/i # oops" \\s* # Optional whitespace s # Substitution operator s ([$chars]) # Delimiter / ( (?: \\\\. | (?!\\1). )* ) # Pattern foo # Backslash plus any char, or a single non-delimiter char \\1 # Delimiter / ( (?: \\\\. | (?!\\1). )* ) # Replacementstring bar \\1? # Optional delimiter / ([a-z]*) # Modifiers i \\s* # Optional whitespace (.?) # Don't hide if there's more # oops )}; my $irssi_mumbo = qr/\cD[`-i]|\cD[&-@\xff]./; my $irssi_mumbo_no_partial = qr/(?<!\cD)(?<!\cD[&-@\xff])/; sub replace { my ($window, $nick, $from, $to, $opt, $screen) = @_; my $view = $window->view(); my $line = $screen ? $view->{bottom_startline} : $view->{startline}; my $last_line; (my $copy = $from) =~ s/\^|^/^.*\\b$nick\\b.*?\\s.*?/; while ($line) { my $text = $line->get_text(0); eval { $last_line = $line if ($line->{info}{level} & (MSGLEVEL_PUBLIC | MSGLEVEL_MSGS)) && $text !~ /$regex/o && $text =~ /$copy/; 1 } or return; $line = $line->next(); } return 0 if (!$last_line); my $text = $last_line->get_text(1); # variables and case insensitivity $from = "(?i:$from)" if $opt =~ /i/; $to = quotemeta $to; $to =~ s{\\\\\\(.)|\\(.)([1-9])?}{ if (defined $1) { "\\$1" } elsif (defined $3 && ($2 eq "\\" || $2 eq "\$")) { "\$$3" } else { "\\$2".($3//"") } }ge; # text replacing $text =~ s/(.*(?:\b|$irssi_mumbo)$irssi_mumbo_no_partial$nick(?:\b|$irssi_mumbo).*?\s)//; my $pre = $1; $text =~ s/$irssi_mumbo//g; my $format = settings_get_str('typofix_format'); $format =~ s/old/\0\cA/; $format =~ s/new/\0\cB/; $format =~ s/%/\0\cC/g; my $old = $text; eval " \$text =~ s/\$from/$to/".($opt =~ /g/ ? "g" : "")." ; 1 " or Irssi::print "Typofix warning: $@", return 0; my $new = ''; my $diff = Algorithm::Diff->new([split//,$old],[split//,$text]); while ($diff->Next()) { local $" = ''; if (my @it = $diff->Same()) { $new .= "@it"; } else { my %r = ("\cA" => [ $diff->Items(1) ], "\cB" => [ $diff->Items(2) ]); my $format_st = $format; $format_st =~ s/\0([\cA\cB])/@{$r{$1}}/g; $new .= $format_st; } } s/%/%%/g for $pre, $new; s/\0\cC/%/g for $new; $text = $pre . $new . settings_get_str('typofix_modify_string'); my $bottom = $view->{bottom}; my $info = $last_line->{info}; $window->print_after($last_line, $info->{level}, $text, $info->{time}); $view->remove_line($last_line); $window->command('^scrollback end') if $bottom && !$window->view->{bottom}; $view->redraw(); return 1; } sub event_privmsg { my ($server, $data, $nick, $address) = @_; my ($target, $text) = $data =~ /^(\S*)\s:(.*)/ or return; return unless $text =~ /^$regex/o; my ($from, $to, $opt, $extra) = ($2, $3, $4, $5); my $hide = settings_get_bool('typofix_hide_replace') && !$extra; my $ischannel = $server->ischannel($target); my $level = $ischannel ? MSGLEVEL_PUBLIC : MSGLEVEL_MSGS; $target = $nick unless $ischannel; my $window = $server->window_find_closest($target, $level); signal_stop() if (replace($window, $nick, $from, $to, $opt, 0) && $hide); } sub event_own_public { my ($server, $text, $target) = @_; return unless $text =~ /^$regex/o; my ($from, $to, $opt, $extra) = ($2, $3, $4, $5); my $hide = settings_get_bool('typofix_hide_replace') && !$extra; $hide = 0 if settings_get_bool('typofix_own_no_hide'); my $level = $server->ischannel($target) ? MSGLEVEL_MSGS : MSGLEVEL_PUBLIC; my $window = $server->window_find_closest($target, $level); signal_stop() if (replace($window, $server->{nick}, $from, $to, $opt, 0) && $hide); } settings_add_str ('typofix', 'typofix_modify_string', ' [fixed]'); settings_add_str ('typofix', 'typofix_format', '%rold%gnew%n'); settings_add_bool('typofix', 'typofix_hide_replace', 0); settings_add_bool('typofix', 'typofix_own_no_hide', 0); signal_add { 'event privmsg' => \&event_privmsg, 'message own_public' => \&event_own_public };