#!/usr/bin/perl # to read the docs outside of irssi: perldoc /path/to/unifmt.pl # in irssi, "/script load unifmt.pl", then "/unifmt_help" =encoding utf8 =pod =head1 NAME unifmt.pl - unicode text formatting for irssi =head1 SYNOPSIS in shell: cp unifmt.pl ~/.irssi/scripts/ in irssi: /script load unifmt.pl =head1 DESCRIPTION unifmt.pl adds keystrokes to irssi that allow you to type double-width ASCII (Unicode FF00 block) characters, and use Unicode combining characters to make your text appear underlined (single or double line), struck out, or "slashed" out. Rather than executing the script as an irssi slash-command, the modes are controlled via keystrokes. This allows the formatted text to be mixed with normal text on the same line of input. Only one of the underline, strikeout, slashout modes can be enabled at the same time, but they can be combined with wide mode. =head1 KEYSTROKES All the formatting controls must be preceded by a prefix character, which defaults B<^F> (control-F). The default keystrokes are shown here; see SETTINGS, below, to change them. =over 4 =item B<^F w> Enables wide formatting. Each character you type is replaced by its double-width equivalent from the Unicode B range, if it has one. Only characters B (AKA the printable ASCII charset) have double-width equivalents. As a special case, the space (B) character is replaced with B, B, which is a double-width space. All other characters are treated normally. Example: This is wide text Wide formatting can be combined with one (at a time) of the other formatting options. =item B<^F _> Enables underlining. Each character you type is followed by the Unicode combining character B, B. Example: U̲n̲d̲e̲r̲l̲i̲n̲e̲d̲ =item B<^F => Enables double underlining. Each character you type is followed by the Unicode combining character B, B. Example: U̳n̳d̳e̳r̳l̳i̳n̳e̳d̳ =item B<^F -> Enables strikethrough. Each character you type is followed by the Unicode combining character B, B. Example: S̶t̶r̶i̶k̶e̶t̶h̶r̶o̶u̶g̶h̶ =item B<^F /> Enables slashthrough. Each character you type is followed by the Unicode combining character B, B. Example: S̸l̸a̸s̸h̸o̸u̸t̸ =item B<^F ^F> Acts like a single B<^F> was pressed. Does not disable formatting. If you have a regular irssi keybinding for B<^F>, it will be acted on. Otherwise, a B<^F> will be inserted into the input buffer. =item B<^F F> Disables all the formatting modes. Actually, B<^F> followed by any character not listed above will do the same thing, but I promise not to change the B<^F F> combo in any future versions of this script. =back =head1 SETTINGS =over 4 =item B String, the 6 keystrokes used to enable the formatting modes. This defaults to B. The order is: Prefix, Wide, Underline, Double-Underline, Strikethrough, Slashthrough. The prefix key is used as a control key, but when you set it, use a regular alphabetic (e.g. don't say B<^X>, just say B or B). =item B Boolean, whether or not to apply formatting to spaces (default: false). Does not affect wide mode (spaces will always be double-wide). =back =head1 NOTES For any of this to work, you'll have to enable UTF-8 in irssi, and use a UTF-8 capable terminal. This applies to everyone else too: if you're sending UTF-8 encoded Unicode to them, their client (and terminal if it's a terminal client) will have to know how to display it. If not, they'll see garbage in place of what you intended. This script was developed with urxvt (AKA rxvt-unicode). It should work with any terminal that fully supports UTF-8 and Unicode... but you want a terminal that supports looking up glyphs from a list of fonts, like urxvt does. Otherwise, you might have a hard time finding a single font that has all the glyphs you'll need. The editing keys (arrows, home/end, pgup/pgdn, and any alt-? combos) might act strangely while the formatting modes are enabled, depending on your terminal and TERM environment variable. Backspace and ^U (kill line) should still work OK. This will probably be fixed in the future. Tab-completing nicks doesn't work either, but fixing that will be a huge PITA (or maybe impossible). None of the formatting modes persist past the end of the current line of input. Pressing Enter always clears all the modes. This is to minimize annoyance, as there's no visual indicator of which mode(s) you're in. Before you use this script on a public channel, you'd better make sure the channel doesn't have rules against using fancy Unicode. You may annoy the other users, and/or find yourself banned. tmux doesn't seem to be capable of actually displaying the wide + combining character combinations. They render as plain wide. If you copy/paste them to another window (a terminal not running tmux for instance), they show up correctly. So tmux "knows" the formatting is there, but doesn't display it. The underline, strike, slashout combinations don't work with screen, and probably never will. I'd love to be proven wrong, so let me know if you get it working there. I at least can see the wide characters with "screen -U". =head1 AUTHOR Urchlay =head1 LICENSE WTFPL: Do WTF you want with this. =head1 SEE ALSO irssi(1), urxvt(1), unicode(7), utf-8(7) =cut our $VERSION = "0.1"; our %IRSSI = ( authors => 'Urchlay', contact => 'Urchlay on FreeNode', name => 'unifmt', description => 'Fancy Unicode text formatting', license => 'WTFPL', url => 'https://slackware.uk/~urchlay/repos/misc-scripts', ); use warnings; use strict; # 20200827 bkw: adding gui_input_get_pos to the list of imports causes # this script to fail to autoload when irssi starts (but it'll load OK # if manually loaded after startup). I only use it for debugging anyway. use Irssi qw{ command command_bind parse_special signal_register signal_add_first signal_add_last signal_continue signal_emit signal_stop settings_set_str settings_get_str settings_add_str settings_get_bool settings_add_bool }; our $SELF = $IRSSI{name}; our $default_keys = "fw_=-/"; # These 3 are controlled by setting unifmt_keys: our $prefix_key; our $wide_key; our %combining_map; # Toggled with ^F w our $widemode = 0; # Which of combining_map is active, or 0 for none our $combining_char = 0; # True if the last keypress was ^F our $was_prefix = 0; # 2 if the last keypress was escape, 1 if the last 2 were escape and [, # 0 otherwise. our $was_escape = 0; # There's no way to enable debugging without editing the script. our $DEBUG = 0; # Only used for debugging. our $count = 0; # Ditto. sub dump_buf { my $buf = parse_special('$L', 0, 0); my $len = length($buf); my $pos = Irssi::gui_input_get_pos(); my $out = "pos==" . $pos . " "; for(my $i = 0; $i < $len; $i++) { my $star = ($i == $len ? "*" : ""); $out .= sprintf("$star%02x ", ord(substr($buf, $i, 1))); } print $out; } sub get_ctrl_key { return ord(uc($_[0])) - 0x40; } sub fmt_key { # \002 is "toggle bold" return "'\002" . $_[0] . "\002'"; } sub init_keys { our $default_keys; my $keys = settings_get_str('unifmt_keys'); if(length $keys != 6) { print "$SELF: Invalid unifmt_keys, should be 6 keystrokes, defaulting to '$default_keys'"; settings_set_str('unifmt_keys', ($keys = $default_keys) ); } my ($p, $w, $u, $d, $s, $l) = split "", $keys; $p = get_ctrl_key($p); if($p < 0 || $p > 0x1f) { my $pd = uc substr($default_keys, 0, 1); print "$SELF: Invalid prefix key, defaulting to " . fmt_key("^" . $pd); $p = get_ctrl_key($pd); } our $prefix_key = $p; our $wide_key = $w; our %combining_map = ( $u => 0x332, # underline $d => 0x333, # double underline $s => 0x336, # strikethrough $l => 0x338, # slash-through ); print "$SELF: " . "prefix " . fmt_key("^" . chr($prefix_key + 0x40)) . ", " . "wide " . fmt_key($wide_key) . ", " . "underline " . fmt_key($u) . ", " . "double " . fmt_key($d) . ", " . "strike " . fmt_key($s) . ", " . "slash " . fmt_key($l); } sub handle_keypress { my $key = shift; if($DEBUG) { printf $count++ . ": got key 0x%x", $key; dump_buf(); } # hackish way to let most escape codes through unmodified. assumes # (incorrectly) that all escape codes are either Esc-[-(something), # 3 bytes... or else Esc-(something that isn't [), 2 bytes. This # happens to let urxvt's arrow keys and alt-numbers through, at least. if($was_escape) { if($was_escape == 2 && $key != ord('[')) { $was_escape = 0; } else { $was_escape--; } signal_continue($key); return; } # don't try to combine with combining chars! for(values our %combining_map) { #warn "$key $_"; if($key == $_) { signal_continue($key); return; } } # ctrl-space is mapped to the null character. make it dump the # current input buffer contents in hex, if debugging is active. if($DEBUG && $key == 0) { dump_buf(); signal_stop(); return; } # ^F pressed once: set flag, but don't insert into buffer. # Pressed twice = unset flag, inset into buffer. if($key == $prefix_key) { if($was_prefix) { $was_prefix = 0; signal_continue($key); } else { $was_prefix = 1; signal_stop(); } return; } # enter/return, turn off formatting if($key == 0x0d || $key == 0x0a) { $widemode = $combining_char = $was_prefix = 0; signal_continue($key); return; } # backspace/delete and control characters are acted on normally, except # that escape has to set a flag if($key == 0x7f || $key < 0x20) { if($key == 0x1b) { $was_escape = 2; } signal_continue($key); return; } # last key pressed was ^F, act on it, but don't insert into the buffer if($was_prefix) { if($key == ord($wide_key)) { $widemode = 1; } else { $combining_char = $combining_map{chr($key)} || 0; # unrecognized keys also turn off wide mode $widemode = 0 unless $combining_char; } $was_prefix = 0; signal_stop(); return; } # unicode 0x0f01 to 0x0fee are wide versions of ASCII if($widemode) { if($key == 0x20) { $key = 0x3000; } elsif($key >= 0x21 && $key <= 0x7e) { $key += 0xfee0; } # else pass it through as-is } signal_continue($key); # if it was a space and we're not formatting spaces, we're done if(($key == 0x20 || $key == 0x3000) && !settings_get_bool('unifmt_spaces')) { return; } if($combining_char) { if($DEBUG) { print "combining($key, $combining_char)"; } signal_emit('gui key pressed', $combining_char); } } sub unifmt_help { command("/exec - pod2text " . __FILE__); } ### main() settings_add_str($SELF, 'unifmt_keys', $default_keys); settings_add_bool($SELF, 'unifmt_spaces', 0); init_keys(); signal_add_last('setup changed', \&init_keys); signal_register({ "gui key pressed", [ "integer" ] }); signal_add_first("gui key pressed", \&handle_keypress); command_bind("unifmt_help", \&unifmt_help); print "$SELF.pl loaded, /unifmt_help for help"