dotfiles

<-- duh.
Log | Files | Refs | LICENSE

commit 242d9b59642300a687ec1669c672dc1d0f0a1e56
parent 9392242453fb039edf5843c90c78afa1553aac77
Author: Hayden Hamilton <haydenh@sdf.org>
Date:   Tue,  7 Jul 2020 17:56:37 +0100

irssi

Diffstat:
M.config/alias | 3+--
M.config/irssi/config | 450++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
A.config/irssi/config.autosave | 590+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M.config/irssi/default.theme | 498+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/fctcplist | 7+++++++
A.config/irssi/pipe.theme | 32++++++++++++++++++++++++++++++++
M.config/irssi/pipeline.theme | 522+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
A.config/irssi/saved_nick_colors | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/adv_windowlist.pl | 2954+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/autorun/README | 21+++++++++++++++++++++
A.config/irssi/scripts/autorun/autorun/ascii.pl | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/autorun/auto_whois.pl | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/autorun/irssi-alert.pl | 35+++++++++++++++++++++++++++++++++++
C.config/irssi/scripts/autorun/nickcolor.pl -> .config/irssi/scripts/autorun/autorun/nickcolor.pl | 0
R.config/irssi/scripts/autorun/usercount.pl -> .config/irssi/scripts/autorun/autorun/usercount.pl | 0
A.config/irssi/scripts/autorun/cmdind.pl | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/ctcpspoof.pl | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/dim_nicks.pl | 431+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/dispatch.pl | 26++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/go.pl | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/ls.pl | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
M.config/irssi/scripts/autorun/nickcolor.pl | 1285++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
A.config/irssi/scripts/autorun/nojointext.pl | 35+++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/tmux-nicklist-portable.pl | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/uberprompt.pl | 774+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/scripts/autorun/vim_mode.pl | 3783+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.config/irssi/startup | 1+
A.config/irssi/triggers | 2++
A.config/nvim/ftdetect/irssi.vim | 4++++
M.config/nvim/modules/filetype.vim | 1-
M.config/nvim/modules/term.vim | 2+-
A.config/nvim/syntax/irssi.vim | 47+++++++++++++++++++++++++++++++++++++++++++++++
M.config/zsh/alias.zsh | 3+--
M.scripts/bin/dmenu/dpass | 56++++++++++++++++++++++----------------------------------
M.scripts/bin/misc/dotadd | 6++----
M.scripts/bin/misc/rmpv | 6+-----
M.scripts/custom/dock | 5++---
37 files changed, 12819 insertions(+), 437 deletions(-)

diff --git a/.config/alias b/.config/alias @@ -10,7 +10,7 @@ g \git c \cp f \find xi sudo xbps-install -xiu sudo xbps-install -S; sudo xbps-install -yu xbps; sudo xbps-install -yu +xiu sudo xbps-install -S; sudo xbps-install -yu xbps; sudo xbps-install -yu; sudo xbps-remove -Ooy; rm -rf ~/.cache ~/.mozilla ~/.local/share/webkitgtk ~/.viminfo ~/.wget-hsts ~/.lesshst ~/.sh_history ~/.python_history ~/.*history ~/.*hst* ~/.dbus ~/.w3m ~/.config/vimb/cookies.db; sudo vkpurge rm all xq sudo xbps-query xr sudo xbps-remove wget \wget --hsts-file="/dev/null" @@ -26,7 +26,6 @@ zsleep sudo zzz hibernate sudo ZZZ rmst bash ~/.scripts/random/gnulinux.sh vimb \vimb --no-maximize -cleancache sudo xbps-remove -Ooy; rm -rf ~/.cache ~/.mozilla ~/.local/share/webkitgtk ~/.viminfo ~/.wget-hsts ~/.lesshst ~/.sh_history ~/.python_history ~/.*history ~/.*hst* ~/.dbus ~/.sciminfo ~/.viminfo ~/.w3m ~/.config/vimb/cookies.db; sudo vkpurge rm all; mkconfall mkmailpass; mkalias tmux tmux -f ~/.config/tmux/config nw pkill newsboat; newsboat diff --git a/.config/irssi/config b/.config/irssi/config @@ -1,46 +1,31 @@ servers = ( - { - address = "chat.freenode.net"; - chatnet = "Freenode"; - port = "6667"; - }, - { address = "irc.rizon.net"; chatnet = "Rizon"; port = "6667"; } -); -chatnets = { - Freenode = { - type = "IRC"; - max_kicks = "1"; - max_msgs = "4"; - max_whois = "1"; - }; - Rizon = { - type = "IRC"; - max_kicks = "1"; - max_msgs = "1"; - max_whois = "1"; - }; -}; -channels = ( - { name = "#voidlinux"; chatnet = "Freenode"; autojoin = "Yes"; }, - { name = "#GNU/matrix"; chatnet = "Freenode"; autojoin = "Yes"; }, - { name = "#GNU/matrix"; chatnet = "Rizon"; autojoin = "Yes"; }, - { name = "#staw"; chatnet = "Freenode"; autojoin = "Yes"; }, - { name = "#neomutt"; chatnet = "Freenode"; autojoin = "Yes"; }, - { name = "#cat-v"; chatnet = "Freenode"; autojoin = "Yes"; }, + { address = "irc.rizon.net"; chatnet = "rizon"; port = "6667"; }, { - name = "#archlinux-offtopic"; - chatnet = "Freenode"; - autojoin = "Yes"; + address = "irc.haydenvh.com"; + chatnet = "hlircnet"; + port = "6697"; + use_tls = "yes"; + tls_verify = "yes"; + tls_capath = "/etc/ssl/certs"; }, - { name = "#vim"; chatnet = "Freenode"; autojoin = "Yes"; }, - { name = "#neovim"; chatnet = "Freenode"; autojoin = "Yes"; }, + { address = "irc.efnet.org"; chatnet = "efnet"; port = "6667"; }, + { address = "irc.sdf.org"; chatnet = "sdf"; port = "6667"; }, + { address = "irc.unix.chat"; chatnet = "unix"; port = "6667"; }, { - name = "#voidlinux-offtopic"; - chatnet = "Freenode"; - autojoin = "Yes"; - }, - { name = "#i3"; chatnet = "Freenode"; autojoin = "Yes"; } + address = "irc.darkscience.net"; + chatnet = "darkscience"; + use_tls = "yes"; + port = "6697"; + } ); +chatnets = { + Rizon = { type = "IRC"; }; + hlircnet = { type = "IRC"; }; + sdf = { type = "IRC"; }; + efnet = { type = "IRC"; }; + unix = { type = "IRC"; }; + darkscience = { type = "IRC"; }; +}; aliases = { ATAG = "WINDOW SERVER"; ADDALLCHANS = "SCRIPT EXEC foreach my \\$channel (Irssi::channels()) { Irssi::command(\"CHANNEL ADD -auto \\$channel->{name} \\$channel->{server}->{tag} \\$channel->{key}\")\\;}"; @@ -57,7 +42,7 @@ aliases = { DESCRIBE = "ACTION"; DHL = "DEHILIGHT"; EXEMPTLIST = "MODE $C +e"; - EXIT = "QUIT"; + EXIT = "try //exit"; GOTO = "SCROLLBACK GOTO"; HIGHLIGHT = "HILIGHT"; HL = "HILIGHT"; @@ -82,7 +67,6 @@ aliases = { SB = "SCROLLBACK"; SBAR = "STATUSBAR"; SIGNOFF = "QUIT"; - SV = "MSG * Irssi $J ($V) - http://www.irssi.org"; T = "TOPIC"; UB = "UNBAN"; UMODE = "MODE $N"; @@ -196,21 +180,50 @@ aliases = { 97 = "WINDOW GOTO 97"; 98 = "WINDOW GOTO 98"; 99 = "WINDOW GOTO 99"; + quit = "echo try //quit"; + ADDALLCHANNELS = "script exec foreach my $$channel (Irssi::channels()) { Irssi::command(\"channel add -auto $$channel->{name} $$channel->{server}->{tag} $$channel->{key}\") }"; + night = "/away -all sleeping"; + day = "back"; + gone = "away -all somewhere"; + wave = "SAY\0110/;SAY /|;SAY / \\\\"; + admin = "mode $0 +a $1"; + super = "mode $0 +q $1"; + desuper = "mode $0 -q $1"; + deadmin = "mode $0 -a $1"; + save = "/layout save; /ADDALLCHANS; /save"; + halfop = "mode $0 +h $1"; + dehalfop = "mode $0 -h $1"; + vm_add = " /^statusbar prompt add -after input -alignment right more; /^statusbar prompt add -after input -alignment right vim_cmd_mode"; + vm_del = "/^statusbar prompt remove vim_cmb_mode; /^statusbar prompt remove more"; + haydenh = "me"; + chanhold = "^msg chanhold"; + whois = "whois $0 $0"; + boxx = "say \\ _\\ \\ \\ _\\ \\ \\ _\\ \\ \\ _\\ \\ \\ _; say |_| |_| |_| |_| |_|"; + kline = "quote KLINE"; + gline = "quote GLINE"; + zline = "quote ZLINE"; + gzline = "quote GZLINE"; + bet = "/say I bet $0 imaginary moneys $1 $2 $3 $4 $5 $6 $7 $8 $9"; + disgust = "/exec - -out head -n 100 < /dev/urandom | tr -d '\\n' | fold -w 20 2>/dev/null | head -n 1"; + time = "/exec - -out date +%H:%M:%S"; }; statusbar = { items = { barstart = "{sbstart}"; ibarstart = "{isbstart}"; barend = "{sbend}"; - user = "{sb {sbnickmode $cumode}$N{sbmode $usermode}{sbaway $A}}"; - window = "{sb $winref:$tag/$itemname{sbmode $M}}"; - window_empty = "{sb $winref{sbservertag $tag}}"; + user = "{sb $N +$usermode{sbaway $A}}"; + winref = "{sb $winname$itemname{sbmode $M}}{sb3 $winref}"; + server = "{sb {sbservertag $tag}}"; prompt = "{prompt $[.15]itemname}"; prompt_empty = "{prompt $winname}"; - topic = " {sb $winref/$itemname} TOPIC: $topic"; + topic = "{sb1 $topic}"; + vim_cmd_mode = "{sb3 $vim_cmd_mode}"; lag = "{sb Lag: $0-}"; act = "{sb Act: $0-}"; - more = "-- more --"; + more = "{sb3 -- more --}"; + end = "%N%M%_────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"; + none = " "; }; default = { window_inact = { @@ -218,44 +231,361 @@ statusbar = { placement = "bottom"; position = "1"; visible = "inactive"; + items = { none = { priority = "1"; }; }; + }; + window = { items = { - ibarstart = { priority = "100"; }; - window = { }; - more = { priority = "-1"; alignment = "right"; }; - barend = { priority = "100"; alignment = "right"; }; + end = { alignment = "left"; priority = "0"; }; + time = { priority = "10"; }; + user = { priority = "20"; }; + winref = { priority = "35"; }; + lag = { priority = "40"; }; + server = { priority = "50"; }; }; }; prompt = { - type = "root"; - placement = "bottom"; - position = "100"; - visible = "always"; items = { - prompt = { priority = "-1"; }; - prompt_empty = { priority = "-1"; }; + uberprompt = { priority = "-1"; }; input = { priority = "10"; }; + vim_cmd_mode = { alignment = "right"; }; }; + position = "100"; }; }; }; settings = { core = { - real_name = "haydenvh.com"; + real_name = "haydenh"; user_name = "haydenh"; - nick = "testing__"; + nick = "haydenh"; recode_transliterate = "no"; timestamp_format = "%H:%M:%S"; + hostname = "vps1.haydenvh.com"; + }; + "fe-text" = { + actlist_sort = "refnum"; + colors_ansi_24bit = "yes"; + scrollback_lines = "2500"; + scrollback_time = "5days"; }; - "fe-text" = { actlist_sort = "refnum"; }; "fe-common/core" = { theme = "pipeline"; autolog = "yes"; - beep_msg_level = "MSGS NOTICES HILIGHT"; + completion_char = ","; + emphasis_italics = "yes"; + beep_msg_level = "msgs hilight dccmsgs"; + show_names_on_join = "no"; + window_check_level_first = "no"; + autocreate_own_query = "no"; + autocreate_windows = "yes"; + use_msgs_window = "no"; + autocreate_query_level = "NONE"; }; "perl/core/scripts" = { ascii_figlet_path = "/usr/bin/figlet"; nickcolor_colors = "4 8 9 10 11 12 13 14 15"; + nicklist_width = "21"; + nicklist_height = "46"; + dim_nicks_color = "r"; + awl_shared_sbar = "OFF"; + awl_block = "25"; + awl_sort = "active/server/tag"; + trackbar_string = "%_─%_"; + trackbar_style = "%M%_"; + trackbar_print_timestamp = "no"; + uberprompt_load_hook = "/^vm_add"; + uberprompt_unload_hook = "/^vm_del"; + cmdind_warn_text = "%G%_MSG?"; + cmdind_text = "%g%_CMD: "; + }; + "irc/core" = { + alternate_nick = "haydenh_"; + ctcp_version_reply = "UNIVERSE v42"; + ctcp_userinfo_reply = "gopher://haydenvh.com:73"; }; - "irc/core" = { alternate_nick = "haydenh_"; }; }; logs = { }; +keyboard = ( + { key = "meta-u"; id = "change_window"; data = "17"; }, + { key = "meta-i"; id = "change_window"; data = "18"; }, + { key = "^u"; id = "command"; data = "nicklist scroll +10"; }, + { key = "^i"; id = "command"; data = "nicklist scroll -10"; }, + { key = "meta-j"; id = "command"; data = "nicklist scroll +10"; }, + { key = "^k"; id = "command"; data = "nicklist scroll -10"; }, + { key = "meta-k"; id = "command"; data = "nicklist scroll -10"; }, + { key = "^s"; id = "command"; data = "away -all away"; }, + { key = "meta-s"; id = "change_window"; data = "22"; }, + { key = "meta-b"; id = "command"; data = "back"; }, + { key = "meta-n"; id = "command"; data = "night"; }, + { key = "^Z"; id = "nothing"; data = ""; }, + { key = "meta-o"; id = "change_window"; data = "19"; }, + { key = "meta-p"; id = "change_window"; data = "20"; }, + { key = "meta-a"; id = "change_window"; data = "21"; }, + { key = "meta-z"; id = "command"; data = "away -all away"; }, + { key = "meta-d"; id = "change_window"; data = "23"; }, + { key = "meta-f"; id = "change_window"; data = "24"; }, + { key = "meta-g"; id = "change_window"; data = "25"; } +); +hilights = ( + { text = "haydenh"; nick = "yes"; word = "yes"; }, + { text = "hayden"; nick = "yes"; word = "yes"; } +); +channels = ( + { name = "#hlircnet"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#GNU/matrix"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#help"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#haydenvh.com"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#gopher"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#vhosts"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#bots"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#voidlinux"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#gopher"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#sdf"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#helpdesk"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#darkscience"; chatnet = "darkscience"; autojoin = "yes"; }, + { name = "#unix"; chatnet = "unix"; autojoin = "yes"; }, + { name = "#efnet"; chatnet = "efnet"; autojoin = "yes"; }, + { name = "#asciiart"; chatnet = "efnet"; autojoin = "yes"; }, + { name = "#opers"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#users"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#service"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#test"; chatnet = "hlircnet"; autojoin = "yes"; } +); +windows = { + 1 = { + immortal = "yes"; + name = "[control-panel]"; + level = "CRAP PUBLICS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS CLIENTNOTICES CLIENTCRAP CLIENTERRORS HILIGHTS"; + sticky = "yes"; + }; + 2 = { + immortal = "yes"; + name = "[notices]"; + level = "SNOTES CTCPS WALLOPS INVITES"; + sticky = "yes"; + parent = "1"; + }; + 3 = { + immortal = "yes"; + name = "[msgs]"; + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS DCC DCCMSGS"; + sticky = "yes"; + parent = "1"; + }; + 4 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#darkscience"; + tag = "darkscience"; + } + ); + }; + 5 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#efnet"; + tag = "efnet"; + } + ); + }; + 6 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#asciiart"; + tag = "efnet"; + } + ); + }; + 7 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#voidlinux"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 8 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#gopher"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 9 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#bots"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 10 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#vhosts"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 11 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#hlircnet"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 12 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#help"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 13 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#haydenvh.com"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 14 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#GNU/matrix"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 15 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#opers"; + tag = "hlircnet"; + } + ); + }; + 16 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#users"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 17 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#service"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "1"; + }; + 18 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#sdf"; + tag = "sdf"; + } + ); + }; + 19 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#gopher"; + tag = "sdf"; + } + ); + }; + 20 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#helpdesk"; + tag = "sdf"; + } + ); + }; + 21 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#unix"; + tag = "unix"; + } + ); + }; +}; +mainwindows = { + 1 = { + first_line = "1"; + lines = "44"; + first_column = "0"; + columns = "146"; + }; +}; diff --git a/.config/irssi/config.autosave b/.config/irssi/config.autosave @@ -0,0 +1,590 @@ +servers = ( + { address = "irc.rizon.net"; chatnet = "rizon"; port = "6667"; }, + { + address = "irc.haydenvh.com"; + chatnet = "hlircnet"; + port = "6697"; + use_tls = "yes"; + tls_verify = "yes"; + tls_capath = "/etc/ssl/certs"; + }, + { address = "irc.efnet.org"; chatnet = "efnet"; port = "6667"; }, + { address = "irc.sdf.org"; chatnet = "sdf"; port = "6667"; }, + { address = "irc.unix.chat"; chatnet = "unix"; port = "6667"; }, + { + address = "irc.darkscience.net"; + chatnet = "darkscience"; + use_tls = "yes"; + port = "6697"; + } +); +chatnets = { + Rizon = { type = "IRC"; }; + hlircnet = { type = "IRC"; }; + sdf = { type = "IRC"; }; + efnet = { type = "IRC"; }; + unix = { type = "IRC"; }; + darkscience = { type = "IRC"; }; +}; +aliases = { + ATAG = "WINDOW SERVER"; + ADDALLCHANS = "SCRIPT EXEC foreach my \\$channel (Irssi::channels()) { Irssi::command(\"CHANNEL ADD -auto \\$channel->{name} \\$channel->{server}->{tag} \\$channel->{key}\")\\;}"; + B = "BAN"; + BACK = "AWAY"; + BANS = "BAN"; + BYE = "QUIT"; + C = "CLEAR"; + CALC = "EXEC - if command -v bc >/dev/null 2>&1\\; then printf '%s=' '$*'\\; echo '$*' | bc -l\\; else echo bc was not found\\; fi"; + CHAT = "DCC CHAT"; + CUBES = "SCRIPT EXEC Irssi::active_win->print(\"%_bases\", MSGLEVEL_CLIENTCRAP) \\; Irssi::active_win->print( do { join '', map { \"%x0\\${_}0\\$_\" } '0'..'9','A'..'F' }, MSGLEVEL_NEVER | MSGLEVEL_CLIENTCRAP) \\; Irssi::active_win->print(\"%_cubes\", MSGLEVEL_CLIENTCRAP) \\; Irssi::active_win->print( do { my \\$y = \\$_*6 \\; join '', map { my \\$x = \\$_ \\; map { \"%x\\$x\\$_\\$x\\$_\" } @{['0'..'9','A'..'Z']}[\\$y .. \\$y+5] } 1..6 }, MSGLEVEL_NEVER | MSGLEVEL_CLIENTCRAP) for 0..5 \\; Irssi::active_win->print(\"%_grays\", MSGLEVEL_CLIENTCRAP) \\; Irssi::active_win->print( do { join '', map { \"%x7\\${_}7\\$_\" } 'A'..'X' }, MSGLEVEL_NEVER | MSGLEVEL_CLIENTCRAP) \\; Irssi::active_win->print(\"%_mIRC extended colours\", MSGLEVEL_CLIENTCRAP) \\; my \\$x \\; \\$x .= sprintf \"\00399,%02d%02d\",\\$_,\\$_ for 0..15 \\; Irssi::active_win->print(\\$x, MSGLEVEL_NEVER | MSGLEVEL_CLIENTCRAP) \\; for my \\$z (0..6) { my \\$x \\; \\$x .= sprintf \"\00399,%02d%02d\",\\$_,\\$_ for 16+(\\$z*12)..16+(\\$z*12)+11 \\; Irssi::active_win->print(\\$x, MSGLEVEL_NEVER | MSGLEVEL_CLIENTCRAP) }"; + DATE = "TIME"; + DEHIGHLIGHT = "DEHILIGHT"; + DESCRIBE = "ACTION"; + DHL = "DEHILIGHT"; + EXEMPTLIST = "MODE $C +e"; + EXIT = "try //exit"; + GOTO = "SCROLLBACK GOTO"; + HIGHLIGHT = "HILIGHT"; + HL = "HILIGHT"; + HOST = "USERHOST"; + IDENTIFY = "msg NickServ identify"; + INVITELIST = "MODE $C +I"; + J = "JOIN"; + K = "KICK"; + KB = "KICKBAN"; + KN = "KNOCKOUT"; + LAST = "LASTLOG"; + LEAVE = "PART"; + M = "MSG"; + MUB = "UNBAN *"; + N = "NAMES"; + NMSG = "^MSG"; + P = "PART"; + Q = "QUERY"; + RESET = "SET -default"; + RUN = "SCRIPT LOAD"; + SAY = "MSG *"; + SB = "SCROLLBACK"; + SBAR = "STATUSBAR"; + SIGNOFF = "QUIT"; + T = "TOPIC"; + UB = "UNBAN"; + UMODE = "MODE $N"; + UNSET = "SET -clear"; + W = "WHO"; + WC = "WINDOW CLOSE"; + WG = "WINDOW GOTO"; + WJOIN = "JOIN -window"; + WI = "WHOIS"; + WII = "WHOIS $0 $0"; + WL = "WINDOW LIST"; + WN = "WINDOW NEW HIDDEN"; + WQUERY = "QUERY -window"; + WW = "WHOWAS"; + 1 = "WINDOW GOTO 1"; + 2 = "WINDOW GOTO 2"; + 3 = "WINDOW GOTO 3"; + 4 = "WINDOW GOTO 4"; + 5 = "WINDOW GOTO 5"; + 6 = "WINDOW GOTO 6"; + 7 = "WINDOW GOTO 7"; + 8 = "WINDOW GOTO 8"; + 9 = "WINDOW GOTO 9"; + 10 = "WINDOW GOTO 10"; + 11 = "WINDOW GOTO 11"; + 12 = "WINDOW GOTO 12"; + 13 = "WINDOW GOTO 13"; + 14 = "WINDOW GOTO 14"; + 15 = "WINDOW GOTO 15"; + 16 = "WINDOW GOTO 16"; + 17 = "WINDOW GOTO 17"; + 18 = "WINDOW GOTO 18"; + 19 = "WINDOW GOTO 19"; + 20 = "WINDOW GOTO 20"; + 21 = "WINDOW GOTO 21"; + 22 = "WINDOW GOTO 22"; + 23 = "WINDOW GOTO 23"; + 24 = "WINDOW GOTO 24"; + 25 = "WINDOW GOTO 25"; + 26 = "WINDOW GOTO 26"; + 27 = "WINDOW GOTO 27"; + 28 = "WINDOW GOTO 28"; + 29 = "WINDOW GOTO 29"; + 30 = "WINDOW GOTO 30"; + 31 = "WINDOW GOTO 31"; + 32 = "WINDOW GOTO 32"; + 33 = "WINDOW GOTO 33"; + 34 = "WINDOW GOTO 34"; + 35 = "WINDOW GOTO 35"; + 36 = "WINDOW GOTO 36"; + 37 = "WINDOW GOTO 37"; + 38 = "WINDOW GOTO 38"; + 39 = "WINDOW GOTO 39"; + 40 = "WINDOW GOTO 40"; + 41 = "WINDOW GOTO 41"; + 42 = "WINDOW GOTO 42"; + 43 = "WINDOW GOTO 43"; + 44 = "WINDOW GOTO 44"; + 45 = "WINDOW GOTO 45"; + 46 = "WINDOW GOTO 46"; + 47 = "WINDOW GOTO 47"; + 48 = "WINDOW GOTO 48"; + 49 = "WINDOW GOTO 49"; + 50 = "WINDOW GOTO 50"; + 51 = "WINDOW GOTO 51"; + 52 = "WINDOW GOTO 52"; + 53 = "WINDOW GOTO 53"; + 54 = "WINDOW GOTO 54"; + 55 = "WINDOW GOTO 55"; + 56 = "WINDOW GOTO 56"; + 57 = "WINDOW GOTO 57"; + 58 = "WINDOW GOTO 58"; + 59 = "WINDOW GOTO 59"; + 60 = "WINDOW GOTO 60"; + 61 = "WINDOW GOTO 61"; + 62 = "WINDOW GOTO 62"; + 63 = "WINDOW GOTO 63"; + 64 = "WINDOW GOTO 64"; + 65 = "WINDOW GOTO 65"; + 66 = "WINDOW GOTO 66"; + 67 = "WINDOW GOTO 67"; + 68 = "WINDOW GOTO 68"; + 69 = "WINDOW GOTO 69"; + 70 = "WINDOW GOTO 70"; + 71 = "WINDOW GOTO 71"; + 72 = "WINDOW GOTO 72"; + 73 = "WINDOW GOTO 73"; + 74 = "WINDOW GOTO 74"; + 75 = "WINDOW GOTO 75"; + 76 = "WINDOW GOTO 76"; + 77 = "WINDOW GOTO 77"; + 78 = "WINDOW GOTO 78"; + 79 = "WINDOW GOTO 79"; + 80 = "WINDOW GOTO 80"; + 81 = "WINDOW GOTO 81"; + 82 = "WINDOW GOTO 82"; + 83 = "WINDOW GOTO 83"; + 84 = "WINDOW GOTO 84"; + 85 = "WINDOW GOTO 85"; + 86 = "WINDOW GOTO 86"; + 87 = "WINDOW GOTO 87"; + 88 = "WINDOW GOTO 88"; + 89 = "WINDOW GOTO 89"; + 90 = "WINDOW GOTO 90"; + 91 = "WINDOW GOTO 91"; + 92 = "WINDOW GOTO 92"; + 93 = "WINDOW GOTO 93"; + 94 = "WINDOW GOTO 94"; + 95 = "WINDOW GOTO 95"; + 96 = "WINDOW GOTO 96"; + 97 = "WINDOW GOTO 97"; + 98 = "WINDOW GOTO 98"; + 99 = "WINDOW GOTO 99"; + quit = "echo try //quit"; + ADDALLCHANNELS = "script exec foreach my $$channel (Irssi::channels()) { Irssi::command(\"channel add -auto $$channel->{name} $$channel->{server}->{tag} $$channel->{key}\") }"; + night = "/away -all sleeping"; + day = "back"; + gone = "away -all somewhere"; + wave = "SAY\0110/;SAY /|;SAY / \\\\"; + admin = "mode $0 +a $1"; + super = "mode $0 +q $1"; + desuper = "mode $0 -q $1"; + deadmin = "mode $0 -a $1"; + save = "/layout save; /ADDALLCHANS; /save; /unexpand"; + halfop = "mode $0 +h $1"; + dehalfop = "mode $0 -h $1"; + vm_add = " /^statusbar prompt add -after input -alignment right more; /^statusbar prompt add -after input -alignment right vim_cmd_mode"; + vm_del = "/^statusbar prompt remove vim_cmb_mode; /^statusbar prompt remove more"; + haydenh = "me"; + chanhold = "^msg chanhold"; + whois = "whois $0 $0"; + boxx = "say \\ _\\ \\ \\ _\\ \\ \\ _\\ \\ \\ _\\ \\ \\ _; say |_| |_| |_| |_| |_|"; + kline = "quote KLINE"; + gline = "quote GLINE"; + zline = "quote ZLINE"; + gzline = "quote GZLINE"; + bet = "/say I bet $0 imaginary moneys $1 $2 $3 $4 $5 $6 $7 $8 $9"; + disgust = "/exec - -out head -n 100 < /dev/urandom | tr -d '\\n' | fold -w 20 2>/dev/null | head -n 1"; + time = "/exec - -out date +%H:%M:%S"; +}; +statusbar = { + items = { + barstart = "{sbstart}"; + ibarstart = "{isbstart}"; + barend = "{sbend}"; + user = "{sb $N +$usermode{sbaway $A}}"; + winref = "{sb $winname$itemname{sbmode $M}}{sb3 $winref}"; + server = "{sb {sbservertag $tag}}"; + prompt = "{prompt $[.15]itemname}"; + prompt_empty = "{prompt $winname}"; + topic = "{sb1 $topic}"; + vim_cmd_mode = "{sb3 $vim_cmd_mode}"; + lag = "{sb Lag: $0-}"; + act = "{sb Act: $0-}"; + more = "{sb3 -- more --}"; + end = "%N%M%_────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"; + none = " "; + }; + default = { + window_inact = { + type = "window"; + placement = "bottom"; + position = "1"; + visible = "inactive"; + items = { none = { priority = "1"; }; }; + }; + window = { + items = { + end = { alignment = "left"; priority = "0"; }; + time = { priority = "10"; }; + user = { priority = "20"; }; + winref = { priority = "35"; }; + lag = { priority = "40"; }; + server = { priority = "50"; }; + }; + }; + prompt = { + items = { + uberprompt = { priority = "-1"; }; + input = { priority = "10"; }; + vim_cmd_mode = { alignment = "right"; }; + }; + position = "100"; + }; + }; +}; +settings = { + core = { + real_name = "haydenh"; + user_name = "haydenh"; + nick = "haydenh"; + recode_transliterate = "no"; + timestamp_format = "%H:%M:%S"; + hostname = "vps1.haydenvh.com"; + }; + "fe-text" = { + actlist_sort = "refnum"; + colors_ansi_24bit = "yes"; + scrollback_lines = "2500"; + scrollback_time = "5days"; + }; + "fe-common/core" = { + theme = "pipeline"; + autolog = "yes"; + completion_char = ","; + emphasis_italics = "yes"; + beep_msg_level = "msgs hilight dccmsgs"; + show_names_on_join = "no"; + window_check_level_first = "no"; + autocreate_own_query = "no"; + autocreate_windows = "yes"; + use_msgs_window = "no"; + autocreate_query_level = "NONE"; + }; + "perl/core/scripts" = { + ascii_figlet_path = "/usr/bin/figlet"; + nickcolor_colors = "4 8 9 10 11 12 13 14 15"; + nicklist_width = "21"; + nicklist_height = "46"; + dim_nicks_color = "r"; + awl_shared_sbar = "OFF"; + awl_block = "25"; + awl_sort = "active/server/tag"; + trackbar_string = "%_─%_"; + trackbar_style = "%M%_"; + trackbar_print_timestamp = "no"; + uberprompt_load_hook = "/^vm_add"; + uberprompt_unload_hook = "/^vm_del"; + cmdind_warn_text = "%G%_MSG?"; + cmdind_text = "%g%_CMD: "; + }; + "irc/core" = { + alternate_nick = "haydenh_"; + ctcp_version_reply = "UNIVERSE v42"; + ctcp_userinfo_reply = "gopher://haydenvh.com:73"; + }; +}; +logs = { }; +keyboard = ( + { key = "meta-u"; id = "change_window"; data = "17"; }, + { key = "meta-i"; id = "change_window"; data = "18"; }, + { key = "^u"; id = "command"; data = "nicklist scroll +10"; }, + { key = "^i"; id = "command"; data = "nicklist scroll -10"; }, + { key = "meta-j"; id = "command"; data = "nicklist scroll +10"; }, + { key = "^k"; id = "command"; data = "nicklist scroll -10"; }, + { key = "meta-k"; id = "command"; data = "nicklist scroll -10"; }, + { key = "^s"; id = "command"; data = "away -all away"; }, + { key = "meta-s"; id = "change_window"; data = "22"; }, + { key = "meta-b"; id = "command"; data = "back"; }, + { key = "meta-n"; id = "command"; data = "night"; }, + { key = "^Z"; id = "nothing"; data = ""; }, + { key = "meta-o"; id = "change_window"; data = "19"; }, + { key = "meta-p"; id = "change_window"; data = "20"; }, + { key = "meta-a"; id = "change_window"; data = "21"; }, + { key = "meta-z"; id = "command"; data = "away -all away"; }, + { key = "meta-d"; id = "change_window"; data = "23"; }, + { key = "meta-f"; id = "change_window"; data = "24"; }, + { key = "meta-g"; id = "change_window"; data = "25"; } +); +hilights = ( + { text = "haydenh"; nick = "yes"; word = "yes"; }, + { text = "hayden"; nick = "yes"; word = "yes"; } +); +channels = ( + { name = "#hlircnet"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#GNU/matrix"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#help"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#haydenvh.com"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#gopher"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#vhosts"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#bots"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#voidlinux"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#gopher"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#sdf"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#helpdesk"; chatnet = "sdf"; autojoin = "yes"; }, + { name = "#darkscience"; chatnet = "darkscience"; autojoin = "yes"; }, + { name = "#unix"; chatnet = "unix"; autojoin = "yes"; }, + { name = "#efnet"; chatnet = "efnet"; autojoin = "yes"; }, + { name = "#asciiart"; chatnet = "efnet"; autojoin = "yes"; }, + { name = "#opers"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#users"; chatnet = "hlircnet"; autojoin = "yes"; }, + { name = "#service"; chatnet = "hlircnet"; autojoin = "yes"; } +); +windows = { + 1 = { + immortal = "yes"; + name = "[control-panel]"; + level = "CRAP PUBLICS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS CLIENTNOTICES CLIENTCRAP HILIGHTS"; + sticky = "yes"; + parent = "11"; + }; + 2 = { + immortal = "yes"; + name = "[notices]"; + level = "SNOTES CTCPS WALLOPS INVITES"; + sticky = "yes"; + parent = "11"; + }; + 3 = { + immortal = "yes"; + name = "[msgs]"; + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS DCC DCCMSGS"; + sticky = "yes"; + parent = "11"; + }; + 4 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#darkscience"; + tag = "darkscience"; + } + ); + }; + 5 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#efnet"; + tag = "efnet"; + } + ); + }; + 6 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#asciiart"; + tag = "efnet"; + } + ); + }; + 7 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#voidlinux"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 8 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#gopher"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 9 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#bots"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 10 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#vhosts"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 11 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#hlircnet"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + }; + 12 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#help"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 13 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#haydenvh.com"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 14 = { + level = "MSGS PUBLICS NOTICES CTCPS ACTIONS JOINS PARTS QUITS KICKS MODES TOPICS NICKS"; + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#GNU/matrix"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 15 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#opers"; + tag = "hlircnet"; + } + ); + }; + 16 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#users"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 17 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#service"; + tag = "hlircnet"; + } + ); + sticky = "yes"; + parent = "11"; + }; + 18 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#sdf"; + tag = "sdf"; + } + ); + }; + 19 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#gopher"; + tag = "sdf"; + } + ); + }; + 20 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#helpdesk"; + tag = "sdf"; + } + ); + }; + 21 = { + items = ( + { + type = "CHANNEL"; + chat_type = "IRC"; + name = "#unix"; + tag = "unix"; + } + ); + }; +}; +mainwindows = { + 11 = { + first_line = "1"; + lines = "44"; + first_column = "0"; + columns = "132"; + }; +}; diff --git a/.config/irssi/default.theme b/.config/irssi/default.theme @@ -293,3 +293,501 @@ abstracts = { # hilight with specified color, $0 = color, $1 = text sb_act_hilight_color = "$0$1-%n"; }; +formats = { + "fe-common/irc/notifylist" = { + notify_join = "{nick $0} [$1@$2] [{hilight $3}] has joined to $4"; + notify_part = "{nick $0} has left $4"; + notify_away = "{nick $0} [$5] [$1@$2] [{hilight $3}] is now away: $4"; + notify_unaway = "{nick $0} [$4] [$1@$2] [{hilight $3}] is now unaway"; + notify_online = "On $0: {hilight $1}"; + notify_offline = "Offline: $0"; + notify_list = "$0: $1 $2"; + notify_list_empty = "The notify list is empty"; + }; + "fe-common/core" = { + line_start = "{line_start}"; + line_start_irssi = "{line_start}{hilight Irssi:} "; + timestamp = "{timestamp $Z} "; + servertag = "[$0] "; + daychange = "Day changed to %%d %%b %%Y"; + talking_with = "You are now talking with {nick $0}"; + refnum_too_low = "Window number must be greater than 1"; + error_server_sticky = "Window's server is sticky and it cannot be changed without -unsticky option"; + set_server_sticky = "Window's server set sticky"; + unset_server_sticky = "Window's server isn't sticky anymore"; + window_name_not_unique = "Window names must be unique"; + window_level = "Window level is now $0"; + window_set_immortal = "Window is now immortal"; + window_unset_immortal = "Window isn't immortal anymore"; + window_immortal_error = "Window is immortal, if you really want to close it, say /WINDOW IMMORTAL OFF"; + windowlist_header = "%#Ref Name Active item Server Level"; + windowlist_line = "%#$[4]0 %|$[20]1 $[15]2 $[15]3 $4"; + windowlist_footer = ""; + windows_layout_saved = "Layout of windows is now remembered"; + windows_layout_reset = "Layout of windows reset to defaults"; + window_info_header = ""; + window_info_footer = ""; + window_info_refnum = "%#Window : {hilight #$0}"; + window_info_refnum_sticky = "%#Window : {hilight #$0 (sticky)}"; + window_info_name = "%#Name : $0"; + window_info_history = "%#History : $0"; + window_info_immortal = "%#Immortal: yes"; + window_info_size = "%#Size : $0x$1"; + window_info_level = "%#Level : $0"; + window_info_server = "%#Server : $0"; + window_info_server_sticky = "%#Server : $0 (sticky)"; + window_info_theme = "%#Theme : $0$1"; + window_info_bound_items_header = "%#Bounds : {hilight Name Server tag}"; + window_info_bound_item = "%# : $[!30]0 $[!15]1 $2"; + window_info_bound_items_footer = ""; + window_info_items_header = "%#Items : {hilight Name Server tag}"; + window_info_item = "%# $[7]0: $[!30]1 $2"; + window_info_items_footer = ""; + looking_up = "Looking up {server $0}"; + connecting = "Connecting to {server $0} [$1] port {hilight $2}"; + reconnecting = "Reconnecting to {server $0} [$1] port {hilight $2} - use /RMRECONNS to abort"; + connection_established = "Connection to {server $0} established"; + cant_connect = "Unable to connect server {server $0} port {hilight $1} {reason $2}"; + connection_lost = "Connection lost to {server $0}"; + lag_disconnected = "No PONG reply from server {server $0} in $1 seconds, disconnecting"; + disconnected = "Disconnected from {server $0} {reason $1}"; + server_quit = "Disconnecting from server {server $0}: {reason $1}"; + server_changed = "Changed to {hilight $2} server {server $1}"; + unknown_server_tag = "Unknown server tag {server $0}"; + no_connected_servers = "Not connected to any servers"; + server_list = "{server $0}: $1:$2 ($3)"; + server_lookup_list = "{server $0}: $1:$2 ($3) (connecting...)"; + server_reconnect_list = "{server $0}: $1:$2 ($3) ($5 left before reconnecting)"; + server_reconnect_removed = "Removed reconnection to server {server $0} port {hilight $1}"; + server_reconnect_not_found = "Reconnection tag {server $0} not found"; + setupserver_added = "Server {server $0} saved"; + setupserver_removed = "Server {server $0} removed"; + setupserver_not_found = "Server {server $0} not found"; + your_nick = "Your nickname is {nick $0}"; + join = "{channick_hilight $0} {chanhost_hilight $1} has joined {channel $2}"; + part = "{channick $0} {chanhost $1} has left {channel $2} {reason $3}"; + kick = "{channick $0} was kicked from {channel $1} by {nick $2} {reason $3}"; + quit = "{channick $0} {chanhost $1} has quit {reason $2}"; + quit_once = "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}"; + invite = "{nick $0} invites you to {channel $1}"; + not_invited = "You have not been invited to a channel!"; + new_topic = "{nick $0} changed the topic of {channel $1} to: $2"; + topic_unset = "Topic unset by {nick $0} on {channel $1}"; + your_nick_changed = "You're now known as {nick $1}"; + nick_changed = "{channick $0} is now known as {channick_hilight $1}"; + talking_in = "You are now talking in {channel $0}"; + not_in_channels = "You are not on any channels"; + current_channel = "Current channel {channel $0}"; + names = "{names_users Users {names_channel $0}}"; + names_prefix = "%#{names_prefix $0}"; + names_nick_op = "{names_nick_op $0 $1}"; + names_nick_halfop = "{names_nick_halfop $0 $1}"; + names_nick_voice = "{names_nick_voice $0 $1}"; + names_nick = "{names_nick $0 $1}"; + endofnames = "{channel $0}: Total of {hilight $1} nicks {comment {hilight $2} ops, {hilight $3} halfops, {hilight $4} voices, {hilight $5} normal}"; + chanlist_header = "%#You are on the following channels:"; + chanlist_line = "%#{channel $[-10]0} %|+$1 ($2): $3"; + chansetup_not_found = "Channel {channel $0} not found"; + chansetup_added = "Channel {channel $0} saved"; + chansetup_removed = "Channel {channel $0} removed"; + chansetup_header = "%#Channel Network Password Settings"; + chansetup_line = "%#{channel $[15]0} %|$[10]1 $[10]2 $3"; + chansetup_footer = ""; + own_msg = "{ownmsgnick $2 {ownnick $0}}$1"; + own_msg_channel = "{ownmsgnick $3 {ownnick $0}{msgchannel $1}}$2"; + own_msg_private = "{ownprivmsg msg $0}$1"; + own_msg_private_query = "{ownprivmsgnick {ownprivnick $2}}$1"; + pubmsg_me = "{pubmsgmenick $2 {menick $0}}$1"; + pubmsg_me_channel = "{pubmsgmenick $3 {menick $0}{msgchannel $1}}$2"; + pubmsg_hilight = "{pubmsghinick $0 $3 $1}$2"; + pubmsg_hilight_channel = "{pubmsghinick $0 $4 $1{msgchannel $2}}$3"; + pubmsg = "{pubmsgnick $2 {pubnick $0}}$1"; + pubmsg_channel = "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2"; + msg_private = "{privmsg $0 $1}$2"; + msg_private_query = "{privmsgnick $0}$2"; + no_msgs_got = "You have not received a message from anyone yet"; + no_msgs_sent = "You have not sent a message to anyone yet"; + query_start = "Starting query in {server $1} with {nick $0}"; + query_stop = "Closing query with {nick $0}"; + no_query = "No query with {nick $0}"; + query_server_changed = "Query with {nick $0} changed to server {server $1}"; + hilight_header = "%#Highlights:"; + hilight_line = "%#$[-4]0 $1 $2 $3$4"; + hilight_footer = ""; + hilight_not_found = "Highlight not found: $0"; + hilight_removed = "Highlight removed: $0"; + alias_added = "Alias $0 added"; + alias_removed = "Alias $0 removed"; + alias_not_found = "No such alias: $0"; + aliaslist_header = "%#Aliases:"; + aliaslist_line = "%#$[10]0 $1"; + aliaslist_footer = ""; + log_opened = "Log file {hilight $0} opened"; + log_closed = "Log file {hilight $0} closed"; + log_create_failed = "Couldn't create log file {hilight $0}: $1"; + log_locked = "Log file {hilight $0} is locked, probably by another running Irssi"; + log_not_open = "Log file {hilight $0} not open"; + log_started = "Started logging to file {hilight $0}"; + log_stopped = "Stopped logging to file {hilight $0}"; + log_list_header = "%#Logs:"; + log_list = "%#$0 $1: $2 $3$4$5"; + log_list_footer = ""; + windowlog_file = "Window LOGFILE set to $0"; + windowlog_file_logging = "Can't change window's logfile while log is on"; + no_away_msgs = "No new messages in awaylog"; + away_msgs = "{hilight $1} new messages in awaylog:"; + module_header = "%#Module Type Submodules"; + module_line = "%#$[!20]0 $[7]1 $2"; + module_footer = ""; + module_already_loaded = "Module {hilight $0/$1} already loaded"; + module_not_loaded = "Module {hilight $0/$1} is not loaded"; + module_load_error = "Error loading module {hilight $0/$1}: $2"; + module_version_mismatch = "{hilight $0/$1} is ABI version $2 but Irssi is version $abiversion, cannot load"; + module_invalid = "{hilight $0/$1} isn't Irssi module"; + module_loaded = "Loaded module {hilight $0/$1}"; + module_unloaded = "Unloaded module {hilight $0/$1}"; + command_unknown = "Unknown command: $0"; + command_ambiguous = "Ambiguous command: $0"; + option_unknown = "Unknown option: $0"; + option_ambiguous = "Ambiguous option: $0"; + option_missing_arg = "Missing required argument for: $0"; + not_enough_params = "Not enough parameters given"; + not_connected = "Not connected to server"; + not_joined = "Not joined to any channel"; + chan_not_found = "Not joined to such channel"; + chan_not_synced = "Channel not fully synchronized yet, try again after a while"; + illegal_proto = "Command isn't designed for the chat protocol of the active server"; + not_good_idea = "Doing this is not a good idea. Add -YES option to command if you really mean it"; + invalid_number = "Invalid number"; + invalid_time = "Invalid timestamp"; + invalid_level = "Invalid message level"; + invalid_size = "Invalid size"; + invalid_charset = "Invalid charset: $0"; + invalid_choice = "Invalid choice, must be one of $0"; + eval_max_recurse = "/eval hit maximum recursion limit"; + program_not_found = "Could not find file or file is not executable"; + no_server_defined = "No servers defined for this network, see /help server for how to add one"; + theme_saved = "Theme saved to $0"; + theme_save_failed = "Error saving theme to $0: $1"; + theme_not_found = "Theme {hilight $0} not found"; + theme_changed = "Now using theme {hilight $0} ($1)"; + window_theme = "Using theme {hilight $0} in this window"; + window_theme_default = "No theme is set for this window"; + window_theme_changed = "Now using theme {hilight $0} ($1) in this window"; + window_theme_removed = "Removed theme from this window"; + format_title = "%:[{hilight $0}] - [{hilight $1}]%:"; + format_subtitle = "[{hilight $0}]"; + format_item = "$0 = $1"; + ignored = "Ignoring {hilight $1} from {nick $0}"; + ignored_options = "Ignoring {hilight $1} from {nick $0} {comment $2}"; + unignored = "Unignored {nick $0}"; + ignore_not_found = "{nick $0} is not being ignored"; + ignore_no_ignores = "There are no ignores"; + ignore_header = "%#Ignore List:"; + ignore_line = "%#$[-4]0 $1: $2 $3 $4"; + ignore_footer = ""; + not_channel_or_query = "The current window is not a channel or query window"; + conversion_added = "Added {hilight $0}/{hilight $1} to conversion database"; + conversion_removed = "Removed {hilight $0} from conversion database"; + conversion_not_found = "{hilight $0} not found in conversion database"; + conversion_no_translits = "Transliterations not supported in this system"; + recode_header = "%#Target Character set"; + recode_line = "%#%|$[!30]0 $1"; + unknown_chat_protocol = "Unknown chat protocol: $0"; + unknown_chatnet = "Unknown chat network: $0 (create it with /NETWORK ADD)"; + not_toggle = "Value must be either ON, OFF or TOGGLE"; + perl_error = "Perl error: $0"; + bind_header = "%#Key Action"; + bind_list = "%#$[!20]0 $1 $2"; + bind_command_list = "$[!30]0 $1"; + bind_footer = ""; + bind_unknown_id = "Unknown bind action: $0"; + config_saved = "Saved configuration to file $0"; + config_reloaded = "Reloaded configuration"; + config_modified = "Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?"; + glib_error = "{error $0} $1"; + overwrite_config = "Overwrite config (y/N)?"; + set_title = "[{hilight $0}]"; + set_item = "$[-!32]0 %_$1"; + set_unknown = "Unknown setting $0"; + set_not_boolean = "Setting {hilight $0} isn't boolean, use /SET"; + no_completions = "There are no completions"; + completion_removed = "Removed completion $0"; + completion_header = "%#Key Value Auto"; + completion_line = "%#$[10]0 $[!40]1 $2"; + completion_footer = ""; + capsicum_enabled = "Capability mode enabled"; + capsicum_disabled = "Capability mode not enabled"; + capsicum_failed = "Capability mode failed: $0"; + tls_ephemeral_key = "EDH Key: {hilight $0} bit {hilight $1}"; + tls_ephemeral_key_unavailable = "EDH Key: {error N/A}"; + tls_pubkey = "Public Key: {hilight $0} bit {hilight $1}, valid from {hilight $2} to {hilight $3}"; + tls_cert_header = "Certificate Chain:"; + tls_cert_subject = " Subject: {hilight $0}"; + tls_cert_issuer = " Issuer: {hilight $0}"; + tls_pubkey_fingerprint = "Public Key Fingerprint: {hilight $0} ({hilight $1})"; + tls_cert_fingerprint = "Certificate Fingerprint: {hilight $0} ({hilight $1})"; + tls_protocol_version = "Protocol: {hilight $0} ({hilight $1} bit, {hilight $2})"; + }; + "fe-common/irc/dcc" = { + own_dcc = "{dccownmsg dcc {dccownnick $1}}$2"; + own_dcc_action = "{dccownaction_target $0 $1}$2"; + own_dcc_action_query = "{dccownaction $0}$2"; + own_dcc_ctcp = "{ownctcp ctcp $0}$1 $2"; + dcc_msg = "{dccmsg dcc $0}$1"; + action_dcc = "{dccaction $0}$1"; + action_dcc_query = "{dccaction $0}$1"; + own_dcc_query = "{ownmsgnick {dccownquerynick $0}}$2"; + dcc_msg_query = "{privmsgnick $0}$1"; + dcc_ctcp = "{dcc >>> DCC CTCP {hilight $1} received from {hilight $0}: $2}"; + dcc_chat = "{dcc DCC CHAT from {nick $0} [$1 port $2]}"; + dcc_chat_channel = "{dcc DCC CHAT from {nick $0} [$1 port $2] requested in channel {channel $3}}"; + dcc_chat_not_found = "{dcc No DCC CHAT connection open to {nick $0}}"; + dcc_chat_connected = "{dcc DCC CHAT connection with {nick $0} [$1 port $2] established}"; + dcc_chat_disconnected = "{dcc DCC lost chat to {nick $0}}"; + dcc_send = "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4]}"; + dcc_send_channel = "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes] requested in channel {channel $5}}"; + dcc_send_exists = "{dcc DCC already sending file {dccfile $0} for {nick $1}}"; + dcc_send_no_route = "{dcc DCC route lost to nick {nick $0} when trying to send file {dccfile $1}}"; + dcc_send_not_found = "{dcc DCC not sending file {dccfile $1} to {nick $0}}"; + dcc_send_file_open_error = "{dcc DCC can't open file {dccfile $0}: $1}"; + dcc_send_connected = "{dcc DCC sending file {dccfile $0} for {nick $1} [$2 port $3]}"; + dcc_send_complete = "{dcc DCC sent file {dccfile $0} [{hilight $1}] for {nick $2} in {hilight $3} [{hilight $4kB/s}]}"; + dcc_send_aborted = "{dcc DCC aborted sending file {dccfile $0} for {nick $1}}"; + dcc_get_not_found = "{dcc DCC no file offered by {nick $0}}"; + dcc_get_connected = "{dcc DCC receiving file {dccfile $0} from {nick $1} [$2 port $3]}"; + dcc_get_complete = "{dcc DCC received file {dccfile $0} [$1] from {nick $2} in {hilight $3} [$4kB/s]}"; + dcc_get_aborted = "{dcc DCC aborted receiving file {dccfile $0} from {nick $1}}"; + dcc_get_write_error = "{dcc DCC error writing to file {dccfile $0}: {comment $1}"; + dcc_unknown_ctcp = "{dcc DCC unknown ctcp {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_reply = "{dcc DCC unknown reply {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_type = "{dcc DCC unknown type {hilight $0}}"; + dcc_invalid_ctcp = "{dcc DCC received CTCP {hilight $0} with invalid parameters from {nick $1}}"; + dcc_connect_error = "{dcc DCC can't connect to {hilight $0} port {hilight $1}}"; + dcc_cant_create = "{dcc DCC can't create file {dccfile $0}: $1}"; + dcc_rejected = "{dcc DCC $0 was rejected by {nick $1} [{hilight $2}]}"; + dcc_request_send = "{dcc DCC $0 request sent to {nick $1}: $2"; + dcc_close = "{dcc DCC $0 close for {nick $1} [{hilight $2}]}"; + dcc_lowport = "{dcc Warning: Port sent with DCC request is a lowport ({hilight $0, $1}) - this isn't normal. It is possible the address/port is faked (or maybe someone is just trying to bypass firewall)}"; + dcc_list_header = "{dcc DCC connections}"; + dcc_list_line_chat = "{dcc $0 $1}"; + dcc_list_line_file = "{dcc $0 $1: %|$2 of $3 ($4%%) - $5kB/s - ETA $7 - $6}"; + dcc_list_line_queued_send = "{dcc - $0 $2 (queued)}"; + dcc_list_footer = ""; + dcc_list_line_server = "{dcc $0: Port($1) - Send($2) - Chat($3) - Fserve($4)}"; + dcc_server_started = "{dcc DCC SERVER started on port {hilight $0}}"; + dcc_server_closed = "{dcc DCC SERVER on port {hilight $0} closed}"; + }; + "fe-common/irc" = { + netsplit = "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2"; + netsplit_more = "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2 (+$3 more, use /NETSPLIT to show all of them)"; + netsplit_join = "{netjoin Netsplit} over, joins: $0"; + netsplit_join_more = "{netjoin Netsplit} over, joins: $0 (+$1 more)"; + no_netsplits = "There are no net splits"; + netsplits_header = "%#Nick Channel Server Split server"; + netsplits_line = "%#$[9]0 $[10]1 $[20]2 $3"; + netsplits_footer = ""; + network_added = "Network $0 saved"; + network_removed = "Network $0 removed"; + network_not_found = "Network $0 not found"; + network_header = "%#Networks:"; + network_line = "%#$0: $1"; + network_footer = ""; + setupserver_header = "%#Server Port Network Settings"; + setupserver_line = "%#%|$[!20]0 $[5]1 $[10]2 $3"; + setupserver_footer = ""; + sasl_success = "SASL authentication succeeded"; + sasl_error = "Cannot authenticate via SASL ($0)"; + cap_req = "Capabilities requested: $0"; + cap_ls = "Capabilities supported: $0"; + cap_ack = "Capabilities acknowledged: $0"; + cap_nak = "Capabilities refused: $0"; + cap_list = "Capabilities currently enabled: $0"; + cap_new = "Capabilities now available: $0"; + cap_del = "Capabilities removed: $0"; + joinerror_toomany = "Cannot join to channel {channel $0} (You have joined to too many channels)"; + joinerror_full = "Cannot join to channel {channel $0} (Channel is full)"; + joinerror_invite = "Cannot join to channel {channel $0} (You must be invited)"; + joinerror_banned = "Cannot join to channel {channel $0} (You are banned)"; + joinerror_bad_key = "Cannot join to channel {channel $0} (Bad channel key)"; + joinerror_bad_mask = "Cannot join to channel {channel $0} (Bad channel mask)"; + joinerror_unavail = "Cannot join to channel {channel $0} (Channel is temporarily unavailable)"; + joinerror_duplicate = "Channel {channel $0} already exists - cannot create it"; + channel_rejoin = "Channel {channel $0} is temporarily unavailable, this is normally because of netsplits. Irssi will now automatically try to rejoin back to this channel until the join is successful. Use /RMREJOINS command if you wish to abort this."; + inviting = "Inviting {nick $0} to {channel $1}"; + channel_created = "Channel {channelhilight $0} created $1"; + url = "Home page for {channelhilight $0}: $1"; + topic = "Topic for {channelhilight $0}: $1"; + no_topic = "No topic set for {channelhilight $0}"; + topic_info = "Topic set by {nick $0} {nickhost $2} {comment $1}"; + chanmode_change = "mode/{channelhilight $0} {mode $1} by {nick $2}"; + server_chanmode_change = "{netsplit ServerMode}/{channelhilight $0} {mode $1} by {nick $2}"; + channel_mode = "mode/{channelhilight $0} {mode $1}"; + bantype = "Ban type changed to {channel $0}"; + no_bans = "No bans in channel {channel $0}"; + banlist = "$0 - {channel $1}: ban {ban $2}"; + banlist_long = "$0 - {channel $1}: ban {ban $2} {comment by {nick $3}, $4 secs ago}"; + ebanlist = "{channel $0}: ban exception {ban $1}"; + ebanlist_long = "{channel $0}: ban exception {ban $1} {comment by {nick $2}, $3 secs ago}"; + no_invitelist = "Invite list is empty in channel {channel $0}"; + invitelist = "{channel $0}: invite {ban $1}"; + invitelist_long = "{channel $0}: invite {ban $1} {comment by {nick $2}, $3 secs ago}"; + no_such_channel = "{channel $0}: No such channel"; + channel_synced = "Join to {channel $0} was synced in {hilight $1} secs"; + usermode_change = "Mode change {mode $0} for user {nick $1}"; + user_mode = "Your user mode is {mode $0}"; + away = "You have been marked as being away"; + unaway = "You are no longer marked as being away"; + nick_away = "{nick $0} is away: $1"; + no_such_nick = "{nick $0}: No such nick/channel"; + nick_in_use = "Nick {nick $0} is already in use"; + nick_unavailable = "Nick {nick $0} is temporarily unavailable"; + your_nick_owned = "Your nick is owned by {nick $3} {comment $1@$2}"; + whois = "{nick $0} {nickhost $1@$2}%:{whois ircname $3}"; + whowas = "{nick $0} {nickhost $1@$2}%:{whois was $3}"; + whois_idle = "{whois idle %|$1 days $2 hours $3 mins $4 secs}"; + whois_idle_signon = "{whois idle %|$1 days $2 hours $3 mins $4 secs {comment signon: $5}}"; + whois_server = "{whois server %|$1 {comment $2}}"; + whois_oper = "{whois {hilight $1}}"; + whois_modes = "{whois modes $1}"; + whois_realhost = "{whois hostname $1-}"; + whois_usermode = "{whois usermode $1}"; + whois_channels = "{whois channels %|$1}"; + whois_away = "{whois away %|$1}"; + whois_special = "{whois %|$1}"; + whois_extra = "{whois account %|$1}"; + end_of_whois = "End of WHOIS"; + end_of_whowas = "End of WHOWAS"; + whois_not_found = "There is no such nick $0"; + who = "%#{channelhilight $[-10]0} %|{nick $[!9]1} $[!3]2 $[!2]3 $4@$5 {comment {hilight $6}}"; + end_of_who = "End of /WHO list"; + own_notice = "{ownnotice notice $0}$1"; + own_action = "{ownaction $0}$1"; + own_action_target = "{ownaction_target $0 $2}$1"; + own_ctcp = "{ownctcp ctcp $0}$1 $2"; + notice_server = "{servernotice $0}$1"; + notice_public = "{notice $0{pubnotice_channel $1}}$2"; + notice_private = "{notice $0{pvtnotice_host $1}}$2"; + action_private = "{pvtaction $0}$2"; + action_private_query = "{pvtaction_query $0}$2"; + action_public = "{pubaction $0}$1"; + action_public_channel = "{pubaction $0{msgchannel $1}}$2"; + ctcp_reply = "CTCP {hilight $0} reply from {nick $1}: $2"; + ctcp_reply_channel = "CTCP {hilight $0} reply from {nick $1} in channel {channel $3}: $2"; + ctcp_ping_reply = "CTCP {hilight PING} reply from {nick $0}: $1.$[-3.0]2 seconds"; + ctcp_requested = "{ctcp {hilight $0} {comment $1} requested CTCP {hilight $2} from {nick $4}}: $3"; + ctcp_requested_unknown = "{ctcp {hilight $0} {comment $1} requested unknown CTCP {hilight $2} from {nick $4}}: $3"; + online = "Users online: {hilight $0}"; + pong = "PONG received from $0: $1"; + wallops = "{wallop WALLOP {wallop_nick $0}} $1"; + action_wallops = "{wallop WALLOP {wallop_action $0}} $1"; + kill = "You were {error killed} by {nick $0} {nickhost $1} {reason $2} {comment Path: $3}"; + kill_server = "You were {error killed} by {server $0} {reason $1} {comment Path: $2}"; + error = "{error ERROR} $0"; + unknown_mode = "Unknown mode character $0"; + default_event = "$1"; + default_event_server = "[$0] $1"; + silenced = "Silenced {nick $0}"; + unsilenced = "Unsilenced {nick $0}"; + silence_line = "{nick $0}: silence {ban $1}"; + ask_oper_pass = "Operator password:"; + accept_list = "Accepted users: {hilight $0}"; + }; + "fe-common/perl" = { + script_not_found = "Script {hilight $0} not found"; + script_not_loaded = "Script {hilight $0} is not loaded"; + script_loaded = "Loaded script {hilight $0}"; + script_unloaded = "Unloaded script {hilight $0}"; + no_scripts_loaded = "No scripts are loaded"; + script_list_header = "%#Loaded scripts:"; + script_list_line = "%#$[!15]0 $1"; + script_list_footer = ""; + script_error = "{error Error in script {hilight $0}:}"; + }; + "Irssi::Script::ascii" = { + ascii_not_connected = "%_$0:%_ You're not connected to server"; + ascii_not_window = "%_$0:%_ Not joined to any channel or query window"; + ascii_not_chanwindow = "%_$0:%_ Not joined to any channel"; + ascii_not_chanop = "%_$0:%_ You're not channel operator in {hilight $1}"; + ascii_figlet_notfound = "%_Ascii:%_ Cannot execute {hilight $0} - file not found or bad permissions"; + ascii_figlet_notset = "%_Ascii:%_ Cannot find external program %_figlet%_, usign /SET ascii_figlet_path [path], to set it"; + ascii_cmd_syntax = "%_$0:%_ $1, usage: $2"; + ascii_figlet_error = "%_Ascii: Figlet returns error:%_ $0-"; + ascii_fontlist = "%_Ascii:%_ Available fonts [in $0]: $1 ($2)"; + ascii_empty_fontlist = "%_Ascii:%_ Cannot find figlet fonts in $0"; + ascii_unknown_fontdir = "%_Ascii:%_ Cannot find figlet fontdir"; + ascii_show_line = "$0-"; + }; + "Irssi::Script::trackbar" = { + trackbar_loaded = "%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2."; + trackbar_wrong_version = "%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.17 or above if you would like to use this feature of trackbar."; + trackbar_all_removed = "%R>>%n %_Trackbar:%_ All the trackbars have been removed."; + trackbar_not_found = "%R>>%n %_Trackbar:%_ No trackbar found in this window."; + }; + "Irssi::Script::adv_windowlist" = { + awl_display_nokey = "$N${cumode_space}$H$C$S"; + awl_display_key = "$Q${cumode_space}$H$C$S"; + awl_display_nokey_visible = "%2$N${cumode_space}$H$C$S"; + awl_display_key_visible = "%2$Q${cumode_space}$H$C$S"; + awl_display_nokey_active = "%1$N${cumode_space}$H$C$S"; + awl_display_key_active = "%1$Q${cumode_space}$H$C$S"; + awl_display_header = "%8$C|${N}"; + awl_name_display = "$0"; + awl_separator = " "; + awl_separator2 = ""; + awl_abbrev_chars = "~〜"; + awl_viewer_item_bg = "%0"; + awl_title = "\\Vawl\\:"; + }; + "Irssi::Script::trigger" = { + trigger_header = "Triggers:"; + trigger_line = "%#$[-4]0 $1"; + trigger_added = "Trigger $0 added: $1"; + trigger_not_found = "Trigger {hilight $0} not found"; + trigger_saved = "Triggers saved to $0"; + trigger_loaded = "Triggers loaded from $0"; + }; + "fe-text" = { + lastlog_too_long = "/LASTLOG would print $0 lines. If you really want to print all these lines use -force option."; + lastlog_count = "{hilight Lastlog}: $0 lines"; + lastlog_start = "{hilight Lastlog}:"; + lastlog_end = "{hilight End of Lastlog}"; + lastlog_separator = "--"; + lastlog_date = "%%F "; + refnum_not_found = "Window number $0 not found"; + window_too_small = "Not enough room to resize this window"; + cant_hide_last = "You can't hide the last window"; + cant_hide_sticky_windows = "You can't hide sticky windows (use /SET autounstick_windows ON)"; + cant_show_sticky_windows = "You can't show sticky windows (use /SET autounstick_windows ON)"; + window_not_sticky = "Window is not sticky"; + window_set_sticky = "Window set sticky"; + window_unset_sticky = "Window is not sticky anymore"; + window_info_sticky = "%#Sticky : $0"; + window_info_scroll = "%#Scroll : $0"; + window_scroll = "Window scroll mode is now $0"; + window_scroll_unknown = "Unknown scroll mode $0, must be ON, OFF or DEFAULT"; + window_hidelevel = "Window hidden level is now $0"; + statusbar_list_header = "%#Name Type Placement Position Visible"; + statusbar_list_footer = ""; + statusbar_list = "%#$[30]0 $[6]1 $[9]2 $[8]3 $4"; + statusbar_info_name = "%#Statusbar: {hilight $0}"; + statusbar_info_type = "%#Type : $0"; + statusbar_info_placement = "%#Placement: $0"; + statusbar_info_position = "%#Position : $0"; + statusbar_info_visible = "%#Visible : $0"; + statusbar_info_item_header = "%#Items : Name Priority Alignment"; + statusbar_info_item_footer = ""; + statusbar_info_item_name = "%# : $[35]0 $[9]1 $2"; + statusbar_not_found = "Statusbar is disabled: $0"; + statusbar_item_not_found = "Statusbar item doesn't exist: $0"; + statusbar_unknown_command = "Unknown statusbar command: $0"; + statusbar_unknown_type = "Statusbar type must be 'window' or 'root'"; + statusbar_unknown_placement = "Statusbar placement must be 'top' or 'bottom'"; + statusbar_unknown_visibility = "Statusbar visibility must be 'always', 'active' or 'inactive'"; + paste_warning = "Pasting $0 lines to $1. Press Ctrl-K if you wish to do this or Ctrl-C to cancel."; + paste_prompt = "Hit Ctrl-K to paste, Ctrl-C to abort?"; + irssi_banner = " ___ _%:|_ _|_ _ _____(_)%: | || '_(_-<_-< |%:|___|_| /__/__/_|%:Irssi v$J - https://irssi.org"; + welcome_firsttime = "- - - - - - - - - - - - - - - - - - - - - - - - - - - -\012Hi there! If this is your first time using Irssi, you%:might want to go to our website and read the startup%:documentation to get you going.%:%:Our community and staff are available to assist you or%:to answer any questions you may have.%:%:Use the /HELP command to get detailed information about%:the available commands.%:%:For Debian specific help type \"/connect OFTC\" and%:\"/join #debian\" (without the quotes) and ask your%:question.%:- - - - - - - - - - - - - - - - - - - - - - - - - - - -"; + welcome_init_settings = "The following settings were initialized"; + }; +}; diff --git a/.config/irssi/fctcplist b/.config/irssi/fctcplist @@ -0,0 +1,7 @@ +time Time to get a watch +advert Server: hlirc.haydenvh.com, port: 6667, 6697 +whoami stupid +hitthisoneifyouareaflooder fuck off +ping pong +version git://haydenvh.com/dotfiles (.config/irssi) +clientinfo ITTHISONEIFYOUAREAFLOODER PING VERSION TIME CLIENTINFO USERINFO WHOAMI ADVERT diff --git a/.config/irssi/pipe.theme b/.config/irssi/pipe.theme @@ -0,0 +1,32 @@ +formats = { + "fe-common/core" = { + join = "%_%B+%_{pubnick \00311$0} %n%w$1"; + part = "%_%G-{pubnick \00311$0} %n%w$1 {reason $3}"; + kick = "%_%G!{pubnick \00311$0} %n%wby {pubnick $2} from ${channel $1} {reason $3}"; + quit = "%_%G<{pubnick \00311$0} %n%w$1 {reason $2}"; + nick_changed = "{nick %w$0%n} %Nis now {nick %W$1%n}"; + endofnames = "{channel $0}: {hilight $1} nicks ({comment @/{hilight $2} +/{hilight $3} -/{hilight $4}})"; + own_msg = "{ownmsgnick $2 {ownnick $[0]0}}$1"; + own_msg_channel = "{ownmsgnick $3 {ownnick $[-16]0}{msgchannel $1}}$2"; + pubmsg_me = "{pubmsgmenick $2 {pubnick $0}}$1"; + pubmsg_me_channel = "{pubmsgmenick $3 {pubnick $[-16]0}{msgchannel $1}}$2"; + pubmsg_hilight = "{pubmsghinick $0 $3 $1}$2"; + pubmsg = "{pubmsgnick $2 {pubnick \00311$0}}$1"; + pubmsg_channel = "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2"; + }; + "fe-common/irc" = { + chanmode_change = "%y{pubnick $2} %nsets mode %b{$mode $1} %non {$channelhilight $0}"; + server_chanmode_change = "{netsplit ServerMode}/{channelhilight $0}: {mode $1} by {nick $2}"; + whois = "{hilight $0} [{nickhost $1@$2}] [$whois_country]%: ircname : $3"; + whois_server = "server : $1 ({comment $2})"; + own_action = "{action_core $0 $1}"; + action_public = "{action_core $0 $1}"; + }; + "Irssi::Script::adv_windowlist" = { + awl_display_nokey = "%N$N $C"; + awl_display_key = "%N$N $C %mM-$Q "; + awl_display_nokey_active = "%N%K$N $C"; + awl_display_key_active = "%N%K$N $C"; + awl_display_header = "%1 %Y$C (${N}) "; + }; +}; diff --git a/.config/irssi/pipeline.theme b/.config/irssi/pipeline.theme @@ -100,9 +100,9 @@ abstracts = { ownnick = "%y$*%n"; # public message in channel, $0 = nick mode, $1 = nick - pubmsgnick = "{msgnick %b$0%n %w$1%n}"; + pubmsgnick = "{msgnick $0%n %w$1%n}"; #pubmsgnick = "{msgnick $0$1-}"; - pubnick = "%w$*%n"; + pubnick = "%w$nickcolor$*%n"; # public message in channel meant for me, $0 = nick mode, $1 = nick #pubmsgmenick = "%Y{msgnick %B$0%n %w$1%n}%w"; @@ -200,10 +200,12 @@ abstracts = { # default statusbar item style - sb = "%m[%G$*%m]%n"; - sbmode = " %b+%n$*"; - sbaway = " (%Gaway%n)"; - sbservertag = ":%W$0"; + sb = "%N%M%_──[%_%G$*%M]"; + sb2 = "%m[%G$*%m]%n"; + sb3 = "%N%M%_[%_%G$*%M]"; + sbmode = " %b+%G$*"; + sbaway = " (away: $*)"; + sbservertag = "$0"; sbmore = "%_-- more --%_"; sblag = "{sb L: %B$*}"; sbmail = "{sb M: $*}"; @@ -220,34 +222,512 @@ abstracts = { sb_uc_normal = "%_.%_%G/%W$*%n"; sb_uc_space = " "; + sb_act_hilight = "%M$*"; + sb_act_hilight_color = "$0$1-%n"; + sb_act_sep = "%c$*"; + sb_act_text = "%c$*"; + sb_act_msg = "%W$*"; }; # %r%n%_$0%_$1%K |%n %| formats = { "fe-common/core" = { - pubmsg = "{pubmsgnick $2 {pubnick \00315$0}}$1"; - join = "%_%B+%_{pubnick \00315$0} %n%w$1"; - part = "%_%G-{pubnick \00315$0} %n%w$1 {reason $3}"; - kick = "%_%G!{pubnick \00315$0} %nb%wy {pubnick $2} from ${channel $1} {reason $3}"; - quit = "%_%G<{pubnick $0} %n$1 {reason $2}"; - nick_changed = "{nick %w$0%n} %Nis now {nick %W$1%n}"; + pubmsg = "{pubmsgnick $2 {pubnick $0}}$1"; + join = "%_%B+%_{pubnick $0} %n%w$1"; + part = "%_%G-{pubnick $0} %n%w$1 {reason $3}"; + kick = "%_%G!{pubnick $0} %n%wby {pubnick $2}, {reason $3}, from $1"; + quit = "%_%G<{pubnick $0} %n%w$1 {reason $2}"; + nick_changed = " {pubnick $0} is now {pubnick $1}"; endofnames = "{channel $0}: {hilight $1} nicks ({comment @/{hilight $2} +/{hilight $3} -/{hilight $4}})"; - own_msg = "{ownmsgnick $2 {ownnick $[-16]0}}$1"; + own_msg = "{ownmsgnick $2 {ownnick $0}}$1"; own_msg_channel = "{ownmsgnick $3 {ownnick $[-16]0}{msgchannel $1}}$2"; pubmsg_me = "{pubmsgmenick $2 {pubnick $0}}$1"; - pubmsg_me_channel = "{pubmsgmenick $3 {pubnick $[-16]0}{msgchannel $1}}$2"; - pubmsg_hilight = "{pubmsghinick $0 $3 $1}$2"; + pubmsg_me_channel = "{pubmsgmenick $3 {pubnick $0}{msgchannel $1}}$2"; + pubmsg_hilight = "{pubmsghinick $4 {pubnick $1}}$2"; pubmsg_channel = "{pubmsgnick $3 {pubnick $0}{msgchannel $1}}$2"; - chanmode_change = " {$channel $0} %W{pubnick $2} %nsets mode %B{$mode $1}"; - channel_mode = " {$channel $0} %W{pubnick $2} %nsets mode %B{$mode $1}"; + chanmode_change = " {$channel $1} {pubnick $2} %nsets mode %B{$mode $1}"; + channel_mode = " {$channel $0} {pubnick $2} %nsets mode %B{$mode $1}"; + timestamp = "%G%%H:%%M%g "; + line_start = "{line_start}"; + line_start_irssi = "{line_start}{hilight Irssi:} "; + servertag = "[$0] "; + daychange = "Day changed to %%d %%b %%Y"; + talking_with = "You are now talking with {nick $0}"; + refnum_too_low = "Window number must be greater than 1"; + error_server_sticky = "Window's server is sticky and it cannot be changed without -unsticky option"; + set_server_sticky = "Window's server set sticky"; + unset_server_sticky = "Window's server isn't sticky anymore"; + window_name_not_unique = "Window names must be unique"; + window_level = "Window level is now $0"; + window_set_immortal = "Window is now immortal"; + window_unset_immortal = "Window isn't immortal anymore"; + window_immortal_error = "Window is immortal, if you really want to close it, say /WINDOW IMMORTAL OFF"; + windowlist_header = "%#Ref Name Active item Server Level"; + windowlist_line = "%#$[4]0 %|$[20]1 $[15]2 $[15]3 $4"; + windowlist_footer = ""; + windows_layout_saved = "Layout of windows is now remembered"; + windows_layout_reset = "Layout of windows reset to defaults"; + window_info_header = ""; + window_info_footer = ""; + window_info_refnum = "%#Window : {hilight #$0}"; + window_info_refnum_sticky = "%#Window : {hilight #$0 (sticky)}"; + window_info_name = "%#Name : $0"; + window_info_history = "%#History : $0"; + window_info_immortal = "%#Immortal: yes"; + window_info_size = "%#Size : $0x$1"; + window_info_level = "%#Level : $0"; + window_info_server = "%#Server : $0"; + window_info_server_sticky = "%#Server : $0 (sticky)"; + window_info_theme = "%#Theme : $0$1"; + window_info_bound_items_header = "%#Bounds : {hilight Name Server tag}"; + window_info_bound_item = "%# : $[!30]0 $[!15]1 $2"; + window_info_bound_items_footer = ""; + window_info_items_header = "%#Items : {hilight Name Server tag}"; + window_info_item = "%# $[7]0: $[!30]1 $2"; + window_info_items_footer = ""; + looking_up = "Looking up {server $0}"; + connecting = "Connecting to {server $0} [$1] port {hilight $2}"; + reconnecting = "Reconnecting to {server $0} [$1] port {hilight $2} - use /RMRECONNS to abort"; + connection_established = "Connection to {server $0} established"; + cant_connect = "Unable to connect server {server $0} port {hilight $1} {reason $2}"; + connection_lost = "Connection lost to {server $0}"; + lag_disconnected = "No PONG reply from server {server $0} in $1 seconds, disconnecting"; + disconnected = "Disconnected from {server $0} {reason $1}"; + server_quit = "Disconnecting from server {server $0}: {reason $1}"; + server_changed = "Changed to {hilight $2} server {server $1}"; + unknown_server_tag = "Unknown server tag {server $0}"; + no_connected_servers = "Not connected to any servers"; + server_list = "{server $0}: $1:$2 ($3)"; + server_lookup_list = "{server $0}: $1:$2 ($3) (connecting...)"; + server_reconnect_list = "{server $0}: $1:$2 ($3) ($5 left before reconnecting)"; + server_reconnect_removed = "Removed reconnection to server {server $0} port {hilight $1}"; + server_reconnect_not_found = "Reconnection tag {server $0} not found"; + setupserver_added = "Server {server $0} saved"; + setupserver_removed = "Server {server $0} removed"; + setupserver_not_found = "Server {server $0} not found"; + your_nick = "Your nickname is {nick $0}"; + quit_once = "{channel $3} {channick $0} {chanhost $1} has quit {reason $2}"; + invite = "{nick $0} invites you to {channel $1}"; + not_invited = "You have not been invited to a channel!"; + new_topic = "{nick $0} changed the topic of {channel $1} to: $2"; + topic_unset = "Topic unset by {nick $0} on {channel $1}"; + your_nick_changed = "You're now known as {nick $1}"; + talking_in = "You are now talking in {channel $0}"; + not_in_channels = "You are not on any channels"; + current_channel = "Current channel {channel $0}"; + names = "{names_users Users {names_channel $0}}"; + names_prefix = "%#{names_prefix $0}"; + names_nick_op = "{names_nick_op $0 $1}"; + names_nick_halfop = "{names_nick_halfop $0 $1}"; + names_nick_voice = "{names_nick_voice $0 $1}"; + names_nick = "{names_nick $0 $1}"; + chanlist_header = "%#You are on the following channels:"; + chanlist_line = "%#{channel $[-10]0} %|+$1 ($2): $3"; + chansetup_not_found = "Channel {channel $0} not found"; + chansetup_added = "Channel {channel $0} saved"; + chansetup_removed = "Channel {channel $0} removed"; + chansetup_header = "%#Channel Network Password Settings"; + chansetup_line = "%#{channel $[15]0} %|$[10]1 $[10]2 $3"; + chansetup_footer = ""; + own_msg_private = "{ownprivmsg {ownnick $N}%K--->{pubnick $0}}%W$1"; + own_msg_private_query = "{ownprivmsgnick {ownprivnick $2}}$1"; + pubmsg_hilight_channel = "{pubmsghinick $0 $4 $1{msgchannel $2}}$3"; + msg_private = "{ownprivmsg %R<---{pubnick $0}}$2"; + msg_private_query = "{privmsgnick $0}$2"; + no_msgs_got = "You have not received a message from anyone yet"; + no_msgs_sent = "You have not sent a message to anyone yet"; + query_start = "Starting query in {server $1} with {nick $0}"; + query_stop = "Closing query with {nick $0}"; + no_query = "No query with {nick $0}"; + query_server_changed = "Query with {nick $0} changed to server {server $1}"; + hilight_header = "%#Highlights:"; + hilight_line = "%#$[-4]0 $1 $2 $3$4"; + hilight_footer = ""; + hilight_not_found = "Highlight not found: $0"; + hilight_removed = "Highlight removed: $0"; + alias_added = "Alias $0 added"; + alias_removed = "Alias $0 removed"; + alias_not_found = "No such alias: $0"; + aliaslist_header = "%#Aliases:"; + aliaslist_line = "%#$[10]0 $1"; + aliaslist_footer = ""; + log_opened = "Log file {hilight $0} opened"; + log_closed = "Log file {hilight $0} closed"; + log_create_failed = "Couldn't create log file {hilight $0}: $1"; + log_locked = "Log file {hilight $0} is locked, probably by another running Irssi"; + log_not_open = "Log file {hilight $0} not open"; + log_started = "Started logging to file {hilight $0}"; + log_stopped = "Stopped logging to file {hilight $0}"; + log_list_header = "%#Logs:"; + log_list = "%#$0 $1: $2 $3$4$5"; + log_list_footer = ""; + windowlog_file = "Window LOGFILE set to $0"; + windowlog_file_logging = "Can't change window's logfile while log is on"; + no_away_msgs = "No new messages in awaylog"; + away_msgs = "{hilight $1} new messages in awaylog:"; + module_header = "%#Module Type Submodules"; + module_line = "%#$[!20]0 $[7]1 $2"; + module_footer = ""; + module_already_loaded = "Module {hilight $0/$1} already loaded"; + module_not_loaded = "Module {hilight $0/$1} is not loaded"; + module_load_error = "Error loading module {hilight $0/$1}: $2"; + module_version_mismatch = "{hilight $0/$1} is ABI version $2 but Irssi is version $abiversion, cannot load"; + module_invalid = "{hilight $0/$1} isn't Irssi module"; + module_loaded = "Loaded module {hilight $0/$1}"; + module_unloaded = "Unloaded module {hilight $0/$1}"; + command_unknown = "Unknown command: $0"; + command_ambiguous = "Ambiguous command: $0"; + option_unknown = "Unknown option: $0"; + option_ambiguous = "Ambiguous option: $0"; + option_missing_arg = "Missing required argument for: $0"; + not_enough_params = "Not enough parameters given"; + not_connected = "Not connected to server"; + not_joined = "Not joined to any channel"; + chan_not_found = "Not joined to such channel"; + chan_not_synced = "Channel not fully synchronized yet, try again after a while"; + illegal_proto = "Command isn't designed for the chat protocol of the active server"; + not_good_idea = "Doing this is not a good idea. Add -YES option to command if you really mean it"; + invalid_number = "Invalid number"; + invalid_time = "Invalid timestamp"; + invalid_level = "Invalid message level"; + invalid_size = "Invalid size"; + invalid_charset = "Invalid charset: $0"; + invalid_choice = "Invalid choice, must be one of $0"; + eval_max_recurse = "/eval hit maximum recursion limit"; + program_not_found = "Could not find file or file is not executable"; + no_server_defined = "No servers defined for this network, see /help server for how to add one"; + theme_saved = "Theme saved to $0"; + theme_save_failed = "Error saving theme to $0: $1"; + theme_not_found = "Theme {hilight $0} not found"; + theme_changed = "Now using theme {hilight $0} ($1)"; + window_theme = "Using theme {hilight $0} in this window"; + window_theme_default = "No theme is set for this window"; + window_theme_changed = "Now using theme {hilight $0} ($1) in this window"; + window_theme_removed = "Removed theme from this window"; + format_title = "%:[{hilight $0}] - [{hilight $1}]%:"; + format_subtitle = "[{hilight $0}]"; + format_item = "$0 = $1"; + ignored = "Ignoring {hilight $1} from {nick $0}"; + ignored_options = "Ignoring {hilight $1} from {nick $0} {comment $2}"; + unignored = "Unignored {nick $0}"; + ignore_not_found = "{nick $0} is not being ignored"; + ignore_no_ignores = "There are no ignores"; + ignore_header = "%#Ignore List:"; + ignore_line = "%#$[-4]0 $1: $2 $3 $4"; + ignore_footer = ""; + not_channel_or_query = "The current window is not a channel or query window"; + conversion_added = "Added {hilight $0}/{hilight $1} to conversion database"; + conversion_removed = "Removed {hilight $0} from conversion database"; + conversion_not_found = "{hilight $0} not found in conversion database"; + conversion_no_translits = "Transliterations not supported in this system"; + recode_header = "%#Target Character set"; + recode_line = "%#%|$[!30]0 $1"; + unknown_chat_protocol = "Unknown chat protocol: $0"; + unknown_chatnet = "Unknown chat network: $0 (create it with /NETWORK ADD)"; + not_toggle = "Value must be either ON, OFF or TOGGLE"; + perl_error = "Perl error: $0"; + bind_header = "%#Key Action"; + bind_list = "%#$[!20]0 $1 $2"; + bind_command_list = "$[!30]0 $1"; + bind_footer = ""; + bind_unknown_id = "Unknown bind action: $0"; + config_saved = "Saved configuration to file $0"; + config_reloaded = "Reloaded configuration"; + config_modified = "Configuration file was modified since irssi was last started - do you want to overwrite the possible changes?"; + glib_error = "{error $0} $1"; + overwrite_config = "Overwrite config (y/N)?"; + set_title = "[{hilight $0}]"; + set_item = "$[-!32]0 %_$1"; + set_unknown = "Unknown setting $0"; + set_not_boolean = "Setting {hilight $0} isn't boolean, use /SET"; + no_completions = "There are no completions"; + completion_removed = "Removed completion $0"; + completion_header = "%#Key Value Auto"; + completion_line = "%#$[10]0 $[!40]1 $2"; + completion_footer = ""; + capsicum_enabled = "Capability mode enabled"; + capsicum_disabled = "Capability mode not enabled"; + capsicum_failed = "Capability mode failed: $0"; + tls_ephemeral_key = "EDH Key: {hilight $0} bit {hilight $1}"; + tls_ephemeral_key_unavailable = "EDH Key: {error N/A}"; + tls_pubkey = "Public Key: {hilight $0} bit {hilight $1}, valid from {hilight $2} to {hilight $3}"; + tls_cert_header = "Certificate Chain:"; + tls_cert_subject = " Subject: {hilight $0}"; + tls_cert_issuer = " Issuer: {hilight $0}"; + tls_pubkey_fingerprint = "Public Key Fingerprint: {hilight $0} ({hilight $1})"; + tls_cert_fingerprint = "Certificate Fingerprint: {hilight $0} ({hilight $1})"; + tls_protocol_version = "Protocol: {hilight $0} ({hilight $1} bit, {hilight $2})"; }; "fe-common/text" = { window_info_sticky = "%# Sticky : $0"; }; "fe-common/irc" = { - chanmode_change = "%y{pubnick $2} %nsets mode %b{$mode $1} %non {$channelhilight $0}"; - whois = "{hilight $0} [{nickhost $1@$2}] [$whois_country]%: ircname : $3"; - server_chanmode_change = " {netsplit ServerMode}/{channelhilight $0}: {mode $1} by {nick $2}"; - whois_server = "server : $1 ({comment $2})"; + chanmode_change = " {pubnick $2} %nsets mode %b{$mode $1} %non {$channelhilight $0}"; + whois = "%:{whois nick %|{hilight $0}}%:{whois host %|$2}%:{whois ident $|$1}%:{whois comment %|$3}"; + server_chanmode_change = "{netsplit ServerMode}/{channelhilight $0}: {mode $1} by {nick $2}"; + whois_server = "{whois server %|$1 ({comment $2})}"; own_action = "{action_core $0 $1}"; action_public = "{action_core $0 $1}"; + away = "%_%G----->"; + unaway = "%_%B<-----"; + end_of_whois = "%|"; + whois_idle_signon = "{whois idle %|$1 days $2 hours $3 mins $4 secs}%:{whois signon %|$5}"; + whois_oper = "{whois oper %|$1}"; + whois_realhost = "{whois realhost %|$1-}"; + whois_special = "{whois info %|$1}"; + whois_extra = "{whois extra %|$1}"; + whois_not_found = "$0 doesn't exist"; + netsplit = "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2"; + netsplit_more = "{netsplit Netsplit} {server $0} <-> {server $1} quits: $2 (+$3 more, use /NETSPLIT to show all of them)"; + netsplit_join = "{netjoin Netsplit} over, joins: $0"; + netsplit_join_more = "{netjoin Netsplit} over, joins: $0 (+$1 more)"; + no_netsplits = "There are no net splits"; + netsplits_header = "%#Nick Channel Server Split server"; + netsplits_line = "%#$[9]0 $[10]1 $[20]2 $3"; + netsplits_footer = ""; + network_added = "Network $0 saved"; + network_removed = "Network $0 removed"; + network_not_found = "Network $0 not found"; + network_header = "%#Networks:"; + network_line = "%#$0: $1"; + network_footer = ""; + setupserver_header = "%#Server Port Network Settings"; + setupserver_line = "%#%|$[!20]0 $[5]1 $[10]2 $3"; + setupserver_footer = ""; + sasl_success = "SASL authentication succeeded"; + sasl_error = "Cannot authenticate via SASL ($0)"; + cap_req = "Capabilities requested: $0"; + cap_ls = "Capabilities supported: $0"; + cap_ack = "Capabilities acknowledged: $0"; + cap_nak = "Capabilities refused: $0"; + cap_list = "Capabilities currently enabled: $0"; + cap_new = "Capabilities now available: $0"; + cap_del = "Capabilities removed: $0"; + joinerror_toomany = "Cannot join to channel {channel $0} (You have joined to too many channels)"; + joinerror_full = "Cannot join to channel {channel $0} (Channel is full)"; + joinerror_invite = "Cannot join to channel {channel $0} (You must be invited)"; + joinerror_banned = "Cannot join to channel {channel $0} (You are banned)"; + joinerror_bad_key = "Cannot join to channel {channel $0} (Bad channel key)"; + joinerror_bad_mask = "Cannot join to channel {channel $0} (Bad channel mask)"; + joinerror_unavail = "Cannot join to channel {channel $0} (Channel is temporarily unavailable)"; + joinerror_duplicate = "Channel {channel $0} already exists - cannot create it"; + channel_rejoin = "Channel {channel $0} is temporarily unavailable, this is normally because of netsplits. Irssi will now automatically try to rejoin back to this channel until the join is successful. Use /RMREJOINS command if you wish to abort this."; + inviting = "Inviting {nick $0} to {channel $1}"; + channel_created = "Channel {channelhilight $0} created $1"; + url = "Home page for {channelhilight $0}: $1"; + topic = "Topic for {channelhilight $0}: $1"; + no_topic = "No topic set for {channelhilight $0}"; + topic_info = "Topic set by {nick $0} {nickhost $2} {comment $1}"; + channel_mode = "mode/{channelhilight $0} {mode $1}"; + bantype = "Ban type changed to {channel $0}"; + no_bans = "No bans in channel {channel $0}"; + banlist = "$0 - {channel $1}: ban {ban $2}"; + banlist_long = "$0 - {channel $1}: ban {ban $2} {comment by {nick $3}, $4 secs ago}"; + ebanlist = "{channel $0}: ban exception {ban $1}"; + ebanlist_long = "{channel $0}: ban exception {ban $1} {comment by {nick $2}, $3 secs ago}"; + no_invitelist = "Invite list is empty in channel {channel $0}"; + invitelist = "{channel $0}: invite {ban $1}"; + invitelist_long = "{channel $0}: invite {ban $1} {comment by {nick $2}, $3 secs ago}"; + no_such_channel = "{channel $0}: No such channel"; + channel_synced = "Join to {channel $0} was synced in {hilight $1} secs"; + usermode_change = "Mode change {mode $0} for user {nick $1}"; + user_mode = "Your user mode is {mode $0}"; + nick_away = "{nick $0} is away: $1"; + no_such_nick = "{nick $0}: No such nick/channel"; + nick_in_use = "Nick {nick $0} is already in use"; + nick_unavailable = "Nick {nick $0} is temporarily unavailable"; + your_nick_owned = "Your nick is owned by {nick $3} {comment $1@$2}"; + whowas = "{nick $0} {nickhost $1@$2}%:{whois was $3}"; + whois_idle = "{whois idle %|$1 days $2 hours $3 mins $4 secs}"; + whois_modes = "{whois modes $1}"; + whois_usermode = "{whois usermode $1}"; + whois_channels = "{whois channels %|$1}"; + whois_away = "{whois away %|$1}"; + end_of_whowas = "End of WHOWAS"; + who = "%#{channelhilight $[-10]0} %|{nick $[!9]1} $[!3]2 $[!2]3 $4@$5 {comment {hilight $6}}"; + end_of_who = "End of /WHO list"; + own_notice = "{ownnotice notice $0}$1"; + own_action_target = "{ownaction_target $0 $2}$1"; + own_ctcp = "{ownctcp ctcp $0}$1 $2"; + notice_server = "{servernotice $0}$1"; + notice_public = "{notice $0{pubnotice_channel $1}}$2"; + notice_private = "{notice $0{pvtnotice_host $1}}$2"; + action_private = "{pvtaction $0}$2"; + action_private_query = "{pvtaction_query $0}$2"; + action_public_channel = "{pubaction $0{msgchannel $1}}$2"; + ctcp_reply = "CTCP {hilight $0} reply from {nick $1}: $2"; + ctcp_reply_channel = "CTCP {hilight $0} reply from {nick $1} in channel {channel $3}: $2"; + ctcp_ping_reply = "CTCP {hilight PING} reply from {nick $0}: $1.$[-3.0]2 seconds"; + ctcp_requested = "{ctcp {hilight $0} {comment $1} requested CTCP {hilight $2} from {nick $4}}: $3"; + ctcp_requested_unknown = "{ctcp {hilight $0} {comment $1} requested unknown CTCP {hilight $2} from {nick $4}}: $3"; + online = "Users online: {hilight $0}"; + pong = "PONG received from $0: $1"; + wallops = "{wallop WALLOP {wallop_nick $0}} $1"; + action_wallops = "{wallop WALLOP {wallop_action $0}} $1"; + kill = "You were {error killed} by {nick $0} {nickhost $1} {reason $2} {comment Path: $3}"; + kill_server = "You were {error killed} by {server $0} {reason $1} {comment Path: $2}"; + error = "{error ERROR} $0"; + unknown_mode = "Unknown mode character $0"; + default_event = "$1"; + default_event_server = "[$0] $1"; + silenced = "Silenced {nick $0}"; + unsilenced = "Unsilenced {nick $0}"; + silence_line = "{nick $0}: silence {ban $1}"; + ask_oper_pass = "Operator password:"; + accept_list = "Accepted users: {hilight $0}"; + }; + "Irssi::Script::adv_windowlist" = { + awl_display_nokey = "%N $H$C$S %N$N"; + awl_display_key = "%N $H$C$S%N %mM-$Q"; + awl_display_nokey_active = "%N%K $C %N$N"; + awl_display_key_active = "%N%K $C %mM-$Q"; + awl_display_header = "%1 %Y$C (${N})"; + awl_display_nokey_visible = "%N%K $C %N$N"; + awl_display_key_visible = "%N%K $C %mM-$Q"; + awl_name_display = "$0"; + awl_separator = " "; + awl_separator2 = ""; + awl_abbrev_chars = "~〜"; + awl_viewer_item_bg = "%0"; + awl_title = "\\Vawl\\:"; + }; + "fe-common/irc/notifylist" = { + notify_join = "{nick $0} [$1@$2] [{hilight $3}] has joined to $4"; + notify_part = "{nick $0} has left $4"; + notify_away = "{nick $0} [$5] [$1@$2] [{hilight $3}] is now away: $4"; + notify_unaway = "{nick $0} [$4] [$1@$2] [{hilight $3}] is now unaway"; + notify_online = "On $0: {hilight $1}"; + notify_offline = "Offline: $0"; + notify_list = "$0: $1 $2"; + notify_list_empty = "The notify list is empty"; + }; + "fe-common/irc/dcc" = { + own_dcc = "{dccownmsg dcc {dccownnick $1}}$2"; + own_dcc_action = "{dccownaction_target $0 $1}$2"; + own_dcc_action_query = "{dccownaction $0}$2"; + own_dcc_ctcp = "{ownctcp ctcp $0}$1 $2"; + dcc_msg = "{dccmsg dcc $0}$1"; + action_dcc = "{dccaction $0}$1"; + action_dcc_query = "{dccaction $0}$1"; + own_dcc_query = "{ownmsgnick {dccownquerynick $0}}$2"; + dcc_msg_query = "{privmsgnick $0}$1"; + dcc_ctcp = "{dcc >>> DCC CTCP {hilight $1} received from {hilight $0}: $2}"; + dcc_chat = "{dcc DCC CHAT from {nick $0} [$1 port $2]}"; + dcc_chat_channel = "{dcc DCC CHAT from {nick $0} [$1 port $2] requested in channel {channel $3}}"; + dcc_chat_not_found = "{dcc No DCC CHAT connection open to {nick $0}}"; + dcc_chat_connected = "{dcc DCC CHAT connection with {nick $0} [$1 port $2] established}"; + dcc_chat_disconnected = "{dcc DCC lost chat to {nick $0}}"; + dcc_send = "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4]}"; + dcc_send_channel = "{dcc DCC SEND from {nick $0} [$1 port $2]: $3 [$4 bytes] requested in channel {channel $5}}"; + dcc_send_exists = "{dcc DCC already sending file {dccfile $0} for {nick $1}}"; + dcc_send_no_route = "{dcc DCC route lost to nick {nick $0} when trying to send file {dccfile $1}}"; + dcc_send_not_found = "{dcc DCC not sending file {dccfile $1} to {nick $0}}"; + dcc_send_file_open_error = "{dcc DCC can't open file {dccfile $0}: $1}"; + dcc_send_connected = "{dcc DCC sending file {dccfile $0} for {nick $1} [$2 port $3]}"; + dcc_send_complete = "{dcc DCC sent file {dccfile $0} [{hilight $1}] for {nick $2} in {hilight $3} [{hilight $4kB/s}]}"; + dcc_send_aborted = "{dcc DCC aborted sending file {dccfile $0} for {nick $1}}"; + dcc_get_not_found = "{dcc DCC no file offered by {nick $0}}"; + dcc_get_connected = "{dcc DCC receiving file {dccfile $0} from {nick $1} [$2 port $3]}"; + dcc_get_complete = "{dcc DCC received file {dccfile $0} [$1] from {nick $2} in {hilight $3} [$4kB/s]}"; + dcc_get_aborted = "{dcc DCC aborted receiving file {dccfile $0} from {nick $1}}"; + dcc_get_write_error = "{dcc DCC error writing to file {dccfile $0}: {comment $1}"; + dcc_unknown_ctcp = "{dcc DCC unknown ctcp {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_reply = "{dcc DCC unknown reply {hilight $0} from {nick $1} [$2]}"; + dcc_unknown_type = "{dcc DCC unknown type {hilight $0}}"; + dcc_invalid_ctcp = "{dcc DCC received CTCP {hilight $0} with invalid parameters from {nick $1}}"; + dcc_connect_error = "{dcc DCC can't connect to {hilight $0} port {hilight $1}}"; + dcc_cant_create = "{dcc DCC can't create file {dccfile $0}: $1}"; + dcc_rejected = "{dcc DCC $0 was rejected by {nick $1} [{hilight $2}]}"; + dcc_request_send = "{dcc DCC $0 request sent to {nick $1}: $2"; + dcc_close = "{dcc DCC $0 close for {nick $1} [{hilight $2}]}"; + dcc_lowport = "{dcc Warning: Port sent with DCC request is a lowport ({hilight $0, $1}) - this isn't normal. It is possible the address/port is faked (or maybe someone is just trying to bypass firewall)}"; + dcc_list_header = "{dcc DCC connections}"; + dcc_list_line_chat = "{dcc $0 $1}"; + dcc_list_line_file = "{dcc $0 $1: %|$2 of $3 ($4%%) - $5kB/s - ETA $7 - $6}"; + dcc_list_line_queued_send = "{dcc - $0 $2 (queued)}"; + dcc_list_footer = ""; + dcc_list_line_server = "{dcc $0: Port($1) - Send($2) - Chat($3) - Fserve($4)}"; + dcc_server_started = "{dcc DCC SERVER started on port {hilight $0}}"; + dcc_server_closed = "{dcc DCC SERVER on port {hilight $0} closed}"; + }; + "fe-common/perl" = { + script_not_found = "Script {hilight $0} not found"; + script_not_loaded = "Script {hilight $0} is not loaded"; + script_loaded = "Loaded script {hilight $0}"; + script_unloaded = "Unloaded script {hilight $0}"; + no_scripts_loaded = "No scripts are loaded"; + script_list_header = "%#Loaded scripts:"; + script_list_line = "%#$[!15]0 $1"; + script_list_footer = ""; + script_error = "{error Error in script {hilight $0}:}"; + }; + "Irssi::Script::ascii" = { + ascii_not_connected = "%_$0:%_ You're not connected to server"; + ascii_not_window = "%_$0:%_ Not joined to any channel or query window"; + ascii_not_chanwindow = "%_$0:%_ Not joined to any channel"; + ascii_not_chanop = "%_$0:%_ You're not channel operator in {hilight $1}"; + ascii_figlet_notfound = "%_Ascii:%_ Cannot execute {hilight $0} - file not found or bad permissions"; + ascii_figlet_notset = "%_Ascii:%_ Cannot find external program %_figlet%_, usign /SET ascii_figlet_path [path], to set it"; + ascii_cmd_syntax = "%_$0:%_ $1, usage: $2"; + ascii_figlet_error = "%_Ascii: Figlet returns error:%_ $0-"; + ascii_fontlist = "%_Ascii:%_ Available fonts [in $0]: $1 ($2)"; + ascii_empty_fontlist = "%_Ascii:%_ Cannot find figlet fonts in $0"; + ascii_unknown_fontdir = "%_Ascii:%_ Cannot find figlet fontdir"; + ascii_show_line = "$0-"; + }; + "Irssi::Script::trackbar" = { + trackbar_loaded = "%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2."; + trackbar_wrong_version = "%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.17 or above if you would like to use this feature of trackbar."; + trackbar_all_removed = "%R>>%n %_Trackbar:%_ All the trackbars have been removed."; + trackbar_not_found = "%R>>%n %_Trackbar:%_ No trackbar found in this window."; + }; + "Irssi::Script::trigger" = { + trigger_header = "Triggers:"; + trigger_line = "%#$[-4]0 $1"; + trigger_added = "Trigger $0 added: $1"; + trigger_not_found = "Trigger {hilight $0} not found"; + trigger_saved = "Triggers saved to $0"; + trigger_loaded = "Triggers loaded from $0"; + }; + "fe-text" = { + lastlog_too_long = "/LASTLOG would print $0 lines. If you really want to print all these lines use -force option."; + lastlog_count = "{hilight Lastlog}: $0 lines"; + lastlog_start = "{hilight Lastlog}:"; + lastlog_end = "{hilight End of Lastlog}"; + lastlog_separator = "--"; + lastlog_date = "%%F "; + refnum_not_found = "Window number $0 not found"; + window_too_small = "Not enough room to resize this window"; + cant_hide_last = "You can't hide the last window"; + cant_hide_sticky_windows = "You can't hide sticky windows (use /SET autounstick_windows ON)"; + cant_show_sticky_windows = "You can't show sticky windows (use /SET autounstick_windows ON)"; + window_not_sticky = "Window is not sticky"; + window_set_sticky = "Window set sticky"; + window_unset_sticky = "Window is not sticky anymore"; + window_info_sticky = "%#Sticky : $0"; + window_info_scroll = "%#Scroll : $0"; + window_scroll = "Window scroll mode is now $0"; + window_scroll_unknown = "Unknown scroll mode $0, must be ON, OFF or DEFAULT"; + window_hidelevel = "Window hidden level is now $0"; + statusbar_list_header = "%#Name Type Placement Position Visible"; + statusbar_list_footer = ""; + statusbar_list = "%#$[30]0 $[6]1 $[9]2 $[8]3 $4"; + statusbar_info_name = "%#Statusbar: {hilight $0}"; + statusbar_info_type = "%#Type : $0"; + statusbar_info_placement = "%#Placement: $0"; + statusbar_info_position = "%#Position : $0"; + statusbar_info_visible = "%#Visible : $0"; + statusbar_info_item_header = "%#Items : Name Priority Alignment"; + statusbar_info_item_footer = ""; + statusbar_info_item_name = "%# : $[35]0 $[9]1 $2"; + statusbar_not_found = "Statusbar is disabled: $0"; + statusbar_item_not_found = "Statusbar item doesn't exist: $0"; + statusbar_unknown_command = "Unknown statusbar command: $0"; + statusbar_unknown_type = "Statusbar type must be 'window' or 'root'"; + statusbar_unknown_placement = "Statusbar placement must be 'top' or 'bottom'"; + statusbar_unknown_visibility = "Statusbar visibility must be 'always', 'active' or 'inactive'"; + paste_warning = "Pasting $0 lines to $1. Press Ctrl-K if you wish to do this or Ctrl-C to cancel."; + paste_prompt = "Hit Ctrl-K to paste, Ctrl-C to abort?"; + irssi_banner = " ___ _%:|_ _|_ _ _____(_)%: | || '_(_-<_-< |%:|___|_| /__/__/_|%:Irssi v$J - https://irssi.org"; + welcome_firsttime = "- - - - - - - - - - - - - - - - - - - - - - - - - - - -\012Hi there! If this is your first time using Irssi, you%:might want to go to our website and read the startup%:documentation to get you going.%:%:Our community and staff are available to assist you or%:to answer any questions you may have.%:%:Use the /HELP command to get detailed information about%:the available commands.%:%:For Debian specific help type \"/connect OFTC\" and%:\"/join #debian\" (without the quotes) and ask your%:question.%:- - - - - - - - - - - - - - - - - - - - - - - - - - - -"; + welcome_init_settings = "The following settings were initialized"; }; }; diff --git a/.config/irssi/saved_nick_colors b/.config/irssi/saved_nick_colors @@ -0,0 +1,256 @@ +[session] +/Absalom:0D +/AlexCato_:3C +/Anzrael:0D +/Aph3x:32 +/Arbiter:05 +/Beta_:0D +/Bethany:42 +/Blanker:32 +/Bollox:0B +/CannaCo:5E +/ChanServ:3C +/Chandra:01 +/Code_Red:06 +/CrashTM:3C +/Cyrus:05 +/DTales:3C +/Derecho:03 +/Diannao:09 +/Digerati:01 +/Dragon:01 +/ElectRo`:06 +/Enui:04 +/EvilB:06 +/Ferris:05 +/Finger:01 +/Fishmongering:05 +/HamAdams:0C +/HashNuke:3C +/Hz0:32 +/Ice_Dragon:5E +/Impulsif:5E +/Jimster480-W10:06 +/Jokkelol:3C +/Jordan:0B +/JoseAlPaca:0D +/Justin_T:0D +/Kemwer:3H +/Ketchup901:3H +/Khaotic:0A +/Latschenharry:04 +/Liothen:32 +/LoKii:3H +/Mantis:0B +/MrRandom:42 +/MuNk:01 +/MusicKing:42 +/NeX-:05 +/Nebula_AI:0D +/Nilgeist:0D +/NiniGeo2:32 +/Oblivion:0A +/Omelette:5E +/OverMind:05 +/OzRat:32 +/Patch:3C +/Pillus:3H +/Pixi:06 +/Randy:3C +/Requit:05 +/Robby:0D +/RobotoneX:32 +/SDr:3A +/Scub:3C +/Seirdy:0B +/Shawn:0C +/SillyGoose:09 +/SpiredMoth:3A +/StormofBytes:42 +/Tempest:3H +/TheSashmo2:3C +/UniquelyGeneric:01 +/Virtual:42 +/Xafloc:0A +/Yeji:3C +/Zed:09 +/Zork:0B +/[R]:04 +/_0x5fc3:5E +/_kylef:04 +/_willo_:0C +/aj:3H +/aldcor:32 +haydenvh/#hlircnet/alice:4N +hlircnet/#GNU/matrix/alice:06 +hlircnet/#haydenvh.com/alice:05 +hlircnet/#hlircnet/alice:06 +hlircnet/#oscss/alice:5E +/allant4:01 +/amoe:0B +/anon__:05 +/ario:04 +/asuka:06 +/aunickuser:5E +/b0x:5E +/bacterio:5E +/bakedpotato:32 +/bamdad:0C +/bendoin:06 +/benharri:32 +/bertiger:3H +/blackest_mamba:05 +/blackntan:3A +/brandfilt:3H +/bryno:04 +/bs719:0C +/caffeinatedcode:3A +/chanhold:0D +/chelmzy:3H +/chik:3H +/chromis:05 +/ciprian_:5E +/cmccabe:3H +/constructed:0C +/crns:42 +/ct16k:3A +/cup:05 +/dab21:42 +/dan:0D +/dayid:09 +/delisa:09 +/dijit:4N +/drelcott:05 +/dv:06 +/elwisp:3C +/exusser:5E +/f3bruary:32 +/fbs:42 +/fckd:09 +/feelingsreal:0A +/fiftysixer:0A +/filostyktos:01 +/flgr:04 +/freebsd.oscss.eu:3A +/garandil:5E +/garret:42 +/goliath:3H +/goog:01 +/gophbot:0A +/greghume:01 +/haiku:3A +darkscience/#darkscience/haydenh:01 +darkscience/#testing/haydenh:01 +efnet/#oscss.eu/haydenh:01 +hlircnet/#GNU/matrix/haydenh:3A +hlircnet/#bots/haydenh:3H +hlircnet/#games/haydenh:0A +hlircnet/#gopher/haydenh:01 +hlircnet/#haydenvh.com/haydenh:5E +hlircnet/#help/haydenh:01 +hlircnet/#hlircnet/haydenh:01 +hlircnet/#marvvin/haydenh:01 +hlircnet/#new/haydenh:02 +hlircnet/#opers/haydenh:01 +hlircnet/#oscss/haydenh:32 +hlircnet/#oscss.eu/haydenh:06 +hlircnet/#test/haydenh:09 +hlircnet/#testing/haydenh:01 +hlircnet/#vhosts/haydenh:3A +hlircnet/#voidlinux/haydenh:3C +/haydenhb1:3C +/heartofrevel:3A +/hello-world:09 +/hiro:5E +/hoek:3A +/hoho:06 +/hugo:09 +/huyens:32 +/hyb:3C +/hyiltiz:06 +/infy:0C +/irc-us.oscss.eu:3C +/irc.haydenvh.com:42 +/irc.oscss.eu:04 +/j0sh:05 +/james:0C +/joes:04 +/johnhamelink:0C +/jrwr:42 +/jvoisin:5E +/kd:3A +/kilroy:05 +/kolobyte:3A +/kylef:0A +/lagzilla:42 +/lawnchair:0A +/lead_pipe23:3A +/lennox:09 +/lowkey:05 +/m1sdirection:5E +/malcom2073:06 +haydenvh/#hlircnet/marvvin:04 +hlircnet/#GNU/matrix/marvvin:05 +hlircnet/#bots/marvvin:05 +hlircnet/#haydenvh.com/marvvin:04 +hlircnet/#hlircnet/marvvin:05 +/mdashx:04 +/mohnish:42 +/mohnish_:5E +/mrnicke:09 +/mruno:3A +haydenvh/#hlircnet/mys:0D +hlircnet/#GNU/matrix/mys:01 +hlircnet/#hlircnet/mys:06 +/naitootat:01 +/neilalexander:5E +/nero:02 +/nesoi:42 +/nin:3C +/nirojan:01 +/njsg:03 +/obviyus:04 +/p80info:0C +/phlud:05 +/pigeons:09 +/plow:42 +/privbot:09 +/psycho:3C +/qy:32 +/r2d2:5E +/robrand:0A +/root:01 +/saba:42 +/shuckks:06 +/slackfam:09 +/smolder:0A +/snazo:04 +/snk:04 +/southey:3A +/ssd532_:42 +/steev:09 +/stid3r:09 +/sun-lightt:0C +/superalpine:3C +/synbiose:0A +/systwi_:3H +/tempette:05 +/test:4N +/themask:32 +/trans:42 +/trip:0A +/trper:0C +/waveframe:05 +/wayne:32 +/weather:0C +/weekend:04 +/wfnintr:0B +/whoami:3C +/xLink:42 +/xadammr:3H +/xeeder:3A +/yakamo:3A +/zelest:0D +/zer0rest:0B +/zeta:0A +/zgrep:3A diff --git a/.config/irssi/scripts/autorun/adv_windowlist.pl b/.config/irssi/scripts/autorun/adv_windowlist.pl @@ -0,0 +1,2954 @@ +use strict; +use warnings; + +our $VERSION = '1.9'; # 32a6d4807a45e71 +our %IRSSI = ( + authors => 'Nei', + contact => 'Nei @ anti@conference.jabber.teamidiot.de', + url => "http://anti.teamidiot.de/", + name => 'adv_windowlist', + description => 'Adds a permanent advanced window list on the right or in a status bar.', + sbitems => 'awl_shared', + license => 'GNU GPLv2 or later', + ); + +# UPGRADE NOTE +# ============ +# for users of 0.7 or earlier series, please note that appearance +# settings have moved to /format, i.e. inside your theme! +# the fifo (screen) has been replaced by an external viewer script + +# Usage +# ===== +# copy the script to ~/.irssi/scripts/ +# +# In irssi: +# +# /run adv_windowlist +# +# In your shell (for example a tmux split): +# +# perl ~/.irssi/scripts/adv_windowlist.pl +# +# To use sbar mode instead: +# +# /toggle awl_viewer +# +# Hint: to get rid of the old [Act:] display +# /statusbar window remove act +# +# to get it back: +# /statusbar window add -after lag -priority 10 act + +# Options +# ======= +# formats can be cleared with /format -delete +# +# /format awl_display_(no)key(_active|_visible) <string> +# * string : Format String for one window. The following $'s are expanded: +# $C : Name +# $N : Number of the Window +# $Q : meta-Keymap +# $H : Start hilighting +# $S : Stop hilighting +# /+++++++++++++++++++++++++++++++++, +# | **** I M P O R T A N T : **** | +# | | +# | don't forget to use $S if you | +# | used $H before! | +# | | +# '+++++++++++++++++++++++++++++++++/ +# key : a key binding that goes to this window could be detected in /bind +# nokey : no such key binding was detected +# active : window would receive the input you are currently typing +# visible : window is also visible on screen but not active (a split window) +# +# /format awl_name_display <string> +# * string : Format String for window names +# $0 : name as formatted by the settings +# +# /format awl_display_header <string> +# * string : Format String for this header line. The following $'s are expanded: +# $C : network tag +# +# /format awl_separator(2) <string> +# * string : Character to use between the channel entries +# variant 2 can be used for alternating separators (only in status bar +# without block display) +# +# /format awl_abbrev_chars <string> +# * string : Character to use when shortening long names. The second character +# will be used if two blocks need to be filled. +# +# /format awl_title <string> +# * string : Text to display in the title string or title bar +# +# /format awl_viewer_item_bg <string> +# * string : Format String specifying the viewer's item background colour +# +# /set awl_prefer_name <ON|OFF> +# * this setting decides whether awl will use the active_name (OFF) or the +# window name as the name/caption in awl_display_*. +# That way you can rename windows using /window name myownname. +# +# /set awl_hide_empty <num> +# * if visible windows without items should be hidden from the window list +# set it to 0 to show all windows +# 1 to hide visible windows without items (negative exempt +# active window) +# +# /set awl_detach <list> +# * list of windows that should be hidden from the window list. you +# can also use /awl detach and /awl attach to manage this +# setting. an optional data_level can be specified with ",num" +# +# /set awl_detach_data <num> +# * num : hide the detached window if its data_level is below num +# +# /set awl_detach_aht <ON|OFF> +# * if enabled, also detach all windows listed in the +# activity_hide_targets setting +# +# /set awl_hide_data <num> +# * num : hide the window if its data_level is below num +# set it to 0 to basically disable this feature, +# 1 if you don't want windows without activity to be shown +# 2 to show only those windows with channel text or hilight +# 3 to show only windows with hilight (negative exempt active window) +# +# /set awl_hide_name_data <num> +# * num : hide the name of the window if its data_level is below num +# (only works in status bar without block display) +# you will want to change your formats to add $H...$S around $Q or $N +# if you plan to use this +# +# /set awl_maxlines <num> +# * num : number of lines to use for the window list (0 to disable, negative +# lock) +# +# /set awl_maxcolumns <num> +# * num : number of columns to use for the window list when using the +# tmux integration (0 to disable) +# +# /set awl_block <num> +# * num : width of a column in viewer mode (negative values = block +# display in status bar mode) +# /+++++++++++++++++++++++++++++++++, +# | ****** W A R N I N G ! ****** | +# | | +# | If your block display looks | +# | DISTORTED, you need to add the | +# | following line to your .theme | +# | file under | +# | abstracts = { : | +# | | +# | sb_act_none = "%K$*"; | +# | | +# '+++++++++++++++++++++++++++++++++/ +# +# /set awl_sbar_maxlength <ON|OFF> +# * if you enable the maxlength setting, the block width will be used as a +# maximum length for the non-block status bar mode too. +# +# /set awl_height_adjust <num> +# * num : how many lines to leave empty in viewer mode +# +# /set awl_sort <-data_level|-last_line|refnum> +# * you can change the window sort order with this variable +# -data_level : sort windows with hilight first +# -last_line : sort windows in order of activity +# refnum : sort windows by window number +# active/server/tag : sort by server name +# lru : sort windows with the last recently used last +# "-" reverses the sort order +# typechecks are supported via ::, e.g. active::Query or active::Irc::Query +# undefinedness can be checked with ~, e.g. ~active +# string comparison can be done with =, e.g. name=(status) +# to make sort case insensitive, use #i, e.g. name#i +# any key in the window hash can be tested, e.g. active/chat_type=XMPP +# multiple criteria can be separated with , or +, e.g. -data_level+-last_line +# +# /set awl_placement <top|bottom> +# /set awl_position <num> +# * these settings correspond to /statusbar because awl will create +# status bars for you +# (see /help statusbar to learn more) +# +# /set awl_all_disable <ON|OFF> +# * if you set awl_all_disable to ON, awl will also remove the +# last status bar it created if it is empty. +# As you might guess, this only makes sense with awl_hide_data > 0 ;) +# +# /set awl_viewer <ON|OFF> +# * enable the external viewer script +# +# /set awl_viewer_launch <ON|OFF> +# * try to auto-launch the viewer under tmux or with a shell command +# /awl restart is required all auto-launch related settings to take +# effect +# +# /set awl_viewer_tmux_position <left|top|right|bottom|custom> +# * try to split in this direction when using tmux for the viewer +# custom : use custom_command setting +# +# /set awl_viewer_xwin_command <shell command> +# * custom command to run in order to start the viewer when irssi is +# running under X +# %A - gets replaced by the command to run the viewer +# %qA - additionally quote the command +# +# /set awl_viewer_custom_command <shell command> +# * custom command to run in order to start the viewer +# +# /set awl_viewer_launch_env <string> +# * specific environment settings for use on viewer auto-launch, +# without the AWL_ prefix +# +# /set awl_shared_sbar <left<right|OFF> +# * share a status bar for the first awl item, you will need to manually +# /statusbar window add -after lag -priority 10 awl_shared +# left : space in cells occupied on the left of status bar +# right : space occupied on the right +# Note: you need to replace "left" AND "right" with the appropriate numbers! +# +# /set awl_path <path> +# * path to the file which the viewer script reads +# +# /set fancy_abbrev <no|head|strict|fancy> +# * how to shorten too long names +# no : shorten in the middle +# head : always cut off the ends +# strict : shorten repeating substrings +# fancy : combination of no+strict +# +# /set awl_custom_xform <perl code> +# * specify a custom routine to transform window names +# example: s/^#// remove the #-mark of IRC channels +# the special flags $CHANNEL / $TAG / $QUERY / $NAME can be +# tested in conditionals +# +# /set awl_last_line_shade <timeout> +# * set timeout to shade activity base colours, to enable +# you also need to add +-last_line to awl_sort +# (requires 256 colour support) +# +# /set awl_no_mode_hint <ON|OFF> +# * whether to show the hint of running the viewer script in the +# status bar +# +# /set awl_mouse <ON|OFF> +# * enable the terminal mouse in irssi +# (use the awl-patched mouse.pl for gestures and commands if you need +# them and disable mouse_escape) +# +# /set awl_mouse_offset <num> +# * specifies where on the screen is the awl status bar +# (0 = on top/bottom, 1 = one additional line in between, +# e.g. prompt) +# you MUST set this correctly otherwise the mouse coordinates will +# be off +# +# /set mouse_scroll <num> +# * how many lines the mouse wheel scrolls +# +# /set mouse_escape <num> +# * seconds to disable the mouse, when not clicked on the windowlist +# + +# Commands +# ======== +# /awl detach <num> +# * hide the current window from the window list. num specifies the +# data_level (optional) +# +# /awl attach +# * unhide the current window from the window list +# +# /awl ack +# * change to the next window with activity, ignoring detached windows +# +# /awl redraw +# * redraws the windowlist. There may be occasions where the +# windowlist can get destroyed so you can use this command to +# force a redraw. +# +# /awl restart +# * restart the connection to the viewer script. + +# Viewer script +# ============= +# When run from the command line, adv_windowlist acts as the viewer +# script to be used together with the irssi script to display the +# window list in a sidebar/terminal of its own. +# +# One optional parameter is accepted, the awl_path +# +# The viewer can be configured by three environment variables: +# +# AWL_HI9=1 +# * interpret %9 as high-intensity toggle instead of bold. This had +# been the default prior to version 0.9b8 +# +# AWL_AUTOFOCUS=0 +# * disable auto-focus behaviour when activating a window +# +# AWL_NOTITLE=1 +# * disable the title bar + +# Nei =^.^= ( anti@conference.jabber.teamidiot.de ) + +no warnings 'redefine'; +use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK}; +use constant SCRIPT_FILE => __FILE__; +no if !IN_IRSSI, strict => (qw(subs refs)); +use if IN_IRSSI, Irssi => (); +use if IN_IRSSI, 'Irssi::TextUI' => (); +use v5.10; +use Encode; +use Storable (); +use IO::Socket::UNIX; +use List::Util qw(min max reduce); +use Hash::Util qw(lock_keys); +use Text::ParseWords qw(shellwords); + +BEGIN { + if ($] < 5.012) { + *CORE::GLOBAL::length = *CORE::GLOBAL::length = sub (_) { + defined $_[0] ? CORE::length($_[0]) : undef + }; + } + *Irssi::active_win = {}; # hide incorrect warning +} + +unless (IN_IRSSI) { + local *_ = \@ARGV; + &AwlViewer::main; + exit; +} + + +use constant GLOB_QUEUE_TIMER => 100; + +our $BLOCK_ALL; # localized blocker +my @actString; # status bar texts +my @win_items; +my $currentLines = 0; +my %awins; +my $globTime; # timer to limit remake calls + +my %CHANGED; +my $VIEWER_MODE; +my $MOUSE_ON; +my %mouse_coords; +my %statusbars; +my %S; # settings +my $settings_str = '1'; +my $window_sort_func; +my $custom_xform; +my ($sb_base_width, $sb_base_width_pre, $sb_base_width_post); +my $print_text_activity; +my $shade_line_timer; +my ($screenHeight, $screenWidth); +my %viewer; + +my (%keymap, %nummap, %wnmap, %specialmap, %wnmap_exp, %custom_key_map); +my %banned_channels; +my %detach_map; +my %abbrev_cache; + +use constant setc => 'awl'; + +sub set ($) { + setc . '_' . $_[0] +} + +sub add_statusbar { + for (@_) { + # add subs + my $l = set $_; + { + my $close = $_; + no strict 'refs'; + *{$l} = sub { awl($close, @_) }; + } + Irssi::command("^statusbar $l reset"); + Irssi::command("statusbar $l enable"); + if (lc $S{placement} eq 'top') { + Irssi::command("statusbar $l placement top"); + } + if (my $x = $S{position}) { + Irssi::command("statusbar $l position $x"); + } + Irssi::command("statusbar $l add -priority 100 -alignment left barstart"); + Irssi::command("statusbar $l add $l"); + Irssi::command("statusbar $l add -priority 100 -alignment right barend"); + Irssi::command("statusbar $l disable"); + Irssi::statusbar_item_register($l, '$0', $l); + $statusbars{$_} = 1; + Irssi::command("statusbar $l enable"); + } +} + +sub remove_statusbar { + for (@_) { + my $l = set $_; + Irssi::command("statusbar $l disable"); + Irssi::command("statusbar $l reset"); + Irssi::statusbar_item_unregister($l); + { + no strict 'refs'; + undef &{$l}; + } + delete $statusbars{$_}; + } +} + +my $awl_shared_empty = sub { + return if $BLOCK_ALL; + my ($item, $get_size_only) = @_; + $item->default_handler($get_size_only, '', '', 0); +}; + +sub syncLines { + my $maxLines = $S{maxlines}; + my $newLines = ($maxLines > 0 and @actString > $maxLines) ? + $maxLines : + ($maxLines < 0) ? + -$maxLines : + @actString; + $currentLines = 1 if !$currentLines && $S{shared_sbar}; + if ($S{shared_sbar} && !$statusbars{shared}) { + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = sub { + return if $BLOCK_ALL; + my ($item, $get_size_only) = @_; + + my $text = $actString[0]; + my $title = _get_format(set 'title'); + if (length $title) { + $title =~ s{\\(.)|(.)}{ + defined $2 ? quotemeta $2 + : $1 eq 'V' ? '\u' + : $1 eq ':' ? quotemeta ':%n' + : $1 =~ /^[uUFQE]$/ ? "\\$1" + : quotemeta "\\$1" + }sge; + $title = eval qq{"$title"}; + $title .= ' '; + } + my $pat = defined $text ? "{sb $title\$*}" : '{sb }'; + $text //= ''; + $item->default_handler($get_size_only, $pat, $text, 0); + }; + } + $statusbars{shared} = 1; + remove_statusbar (0) if $statusbars{0}; + } + elsif ($statusbars{shared} && !$S{shared_sbar}) { + add_statusbar (0) if $currentLines && $newLines; + delete $statusbars{shared}; + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = $awl_shared_empty; + } + } + if ($currentLines == $newLines) { return; } + elsif ($newLines > $currentLines) { + add_statusbar ($currentLines .. ($newLines - 1)); + } + else { + remove_statusbar (reverse ($newLines .. ($currentLines - 1))); + } + $currentLines = $newLines; +} + +sub awl { + return if $BLOCK_ALL; + my ($line, $item, $get_size_only) = @_; + + my $text = $actString[$line]; + my $pat = defined $text ? '{sb $*}' : '{sb }'; + $text //= ''; + $item->default_handler($get_size_only, $pat, $text, 0); +} + +# remove old statusbars +{ my %killBar; + sub get_old_status { + my ($textDest, $cont, $cont_stripped) = @_; + if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) { + my $name = quotemeta(set ''); + if ($cont_stripped =~ m/^$name(\d+)\s/) { $killBar{$1} = 1; } + Irssi::signal_stop; + } + } + sub killOldStatus { + %killBar = (); + Irssi::signal_add_first('print text' => 'get_old_status'); + Irssi::command('statusbar'); + Irssi::signal_remove('print text' => 'get_old_status'); + remove_statusbar(keys %killBar); + } +} + +sub _add_map { + my ($type, $target, $map) = @_; + ($type->{$target}) = sort { length $a <=> length $b || $a cmp $b } + $map, exists $type->{$target} ? $type->{$target} : (); +} + +sub get_keymap { + my ($textDest, undef, $cont_stripped) = @_; + if ($textDest->{level} == 524288 and $textDest->{target} eq '' and !defined $textDest->{server}) { + my $one_meta_or_ctrl_key = qr/((?:meta-)*?)(?:(meta-|\^)(\S)|(\w+))/; + $cont_stripped = as_uni($cont_stripped); + if ($cont_stripped =~ m/((?:$one_meta_or_ctrl_key-)*$one_meta_or_ctrl_key)\s+(.*)$/) { + my ($combo, $command) = ($1, $10); + my $map = ''; + while ($combo =~ s/(?:-|^)$one_meta_or_ctrl_key$//) { + my ($level, $ctl, $key, $nkey) = ($1, $2, $3, $4); + my $numlevel = ($level =~ y/-//); + $ctl = '' if !$ctl || $ctl ne '^'; + $map = ('-' x ($numlevel%2)) . ('+' x ($numlevel/2)) . + $ctl . (defined $key ? $key : "\01$nkey\01") . $map; + } + for ($command) { + last unless length $map; + if (/^change_window (\d+)/i) { + _add_map(\%nummap, $1, $map); + } + elsif (/^(?:command window goto|change_window) (\S+)/i) { + my $window = $1; + if ($window !~ /\D/) { + _add_map(\%nummap, $window, $map); + } + elsif (lc $window eq 'active') { + _add_map(\%specialmap, '_active', $map); + } + else { + _add_map(\%wnmap, $window, $map); + } + } + elsif (/^(?:active_window|command ((awl )?ack))/i) { + _add_map(\%specialmap, '_active', $map); + $viewer{use_ack} = $1; + } + elsif (/^command window last/i) { + _add_map(\%specialmap, '_last', $map); + } + elsif (/^(?:upper_window|command window up)/i) { + _add_map(\%specialmap, '_up', $map); + } + elsif (/^(?:lower_window|command window down)/i) { + _add_map(\%specialmap, '_down', $map); + } + elsif (/^key\s+(\w+)/i) { + $custom_key_map{$1} = $map; + } + } + } + Irssi::signal_stop; + } +} + +sub update_keymap { + %nummap = %wnmap = %specialmap = %custom_key_map = (); + Irssi::signal_remove('command bind' => 'watch_keymap'); + Irssi::signal_add_first('print text' => 'get_keymap'); + Irssi::command('bind'); + Irssi::signal_remove('print text' => 'get_keymap'); + for (keys %custom_key_map) { + if (exists $custom_key_map{$_} && + $custom_key_map{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) { + if ($custom_key_map{$_} =~ /\02/) { + delete $custom_key_map{$_}; + } + else { + redo; + } + } + } + for my $keymap (\(%specialmap, %wnmap, %nummap)) { + for (keys %$keymap) { + if ($keymap->{$_} =~ s/\01(\w+)\01/exists $custom_key_map{$1} ? $custom_key_map{$1} : "\02"/ge) { + if ($keymap->{$_} =~ /\02/) { + delete $keymap->{$_}; + } + } + } + } + Irssi::signal_add('command bind' => 'watch_keymap'); + delete $viewer{client_keymap}; + &wl_changed; +} + +# watch keymap changes +sub watch_keymap { + Irssi::timeout_add_once(1000, 'update_keymap', undef); +} + +{ my %strip_table = ( + # fe-common::core::formats.c:format_expand_styles + # delete format_backs format_fores bold_fores other stuff + (map { $_ => '' } (split //, '04261537' . 'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')), + # escape + (map { $_ => $_ } (split //, '{}%')), + ); + sub ir_strip_codes { # strip %codes + my $o = shift; + $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} : + $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex; + $o + } +} +## ir_parse_special -- wrapper around parse_special +## $i - input format +## $args - array ref of arguments to format +## $win - different target window (default current window) +## $flags - different kind of escape flags (default 4|8) +## returns formatted str +sub ir_parse_special { + my $o; + my $i = shift; + my $args = shift // []; + y/ /\177/ for @$args; # hack to escape spaces + my $win = shift || Irssi::active_win; + my $flags = shift // 0x4|0x8; + my @cmd_args = ($i, (join ' ', @$args), $flags); + my $server = Irssi::active_server(); + if (ref $win and ref $win->{active}) { + $o = $win->{active}->parse_special(@cmd_args); + } + elsif (ref $win and ref $win->{active_server}) { + $o = $win->{active_server}->parse_special(@cmd_args); + } + elsif (ref $server) { + $o = $server->parse_special(@cmd_args); + } + else { + $o = &Irssi::parse_special(@cmd_args); + } + $o =~ y/\177/ /; + $o +} + +sub sb_format_expand { # Irssi::current_theme->format_expand wrapper + Irssi::current_theme->format_expand( + $_[0], + ( + Irssi::EXPAND_FLAG_IGNORE_REPLACES + | + ($_[1] ? 0 : Irssi::EXPAND_FLAG_IGNORE_EMPTY) + ) + ) +} + +{ my $term_type = Irssi::version > 20040819 ? 'term_charset' : 'term_type'; + if (Irssi->can('string_width')) { + *screen_length = sub { Irssi::string_width($_[0]) }; + } + else { + local $@; + eval { require Text::CharWidth; }; + unless ($@) { + *screen_length = sub { Text::CharWidth::mbswidth($_[0]) }; + } + else { + my $err = $@; chomp $err; $err =~ s/\sat .* line \d+\.$//; + #Irssi::print("%_$IRSSI{name}: warning:%_ Text::CharWidth module failed to load. Length calculation may be off! Error was:"); + print "%_$IRSSI{name}:%_ $err"; + *screen_length = sub { + my $temp = shift; + if (lc Irssi::settings_get_str($term_type) eq 'utf-8') { + Encode::_utf8_on($temp); + } + length($temp) + }; + } + } + sub as_uni { + no warnings 'utf8'; + Encode::decode(Irssi::settings_get_str($term_type), $_[0], 0) + } + sub as_tc { + Encode::encode(Irssi::settings_get_str($term_type), $_[0], 0) + } +} + +sub sb_length { + screen_length(ir_strip_codes($_[0])) +} + +sub run_custom_xform { + local $@; + eval { + $custom_xform->() + }; + if ($@) { + $@ =~ /^(.*)/; + print '%_'.(set 'custom_xform').'%_ died (disabling): '.$1; + $custom_xform = undef; + } +} + +sub remove_uniform { + my $o = shift; + $o =~ s/^xmpp:(.*?[%@]).+\.[^.]+$/$1/ or + $o =~ s#^psyc://.+\.[^.]+/([@~].*)$#$1#; + if ($custom_xform) { + run_custom_xform() for $o; + } + $o +} + +sub remove_uniform_vars { + my $win = shift; + my $name = __PACKAGE__ . '::custom_xform::' . $win->{active}{type} + if ref $win->{active} && $win->{active}{type}; + no strict 'refs'; + local ${$name} = 1 if $name; + remove_uniform(+shift); +} + +sub lc1459 { + my $x = shift; + $x =~ y/][\\^/}{|~/; + lc $x +} + +sub window_list { + my $i = 0; + map { $_->[1] } sort $window_sort_func map { [ $i++, $_ ] } Irssi::windows; +} + +sub _calculate_abbrev { + my ($wins, $abbrevList) = @_; + if ($S{fancy_abbrev} !~ /^(no|off|head)/i) { + my @nameList = map { ref $_ ? remove_uniform_vars($_, as_uni($_->get_active_name) // '') : '' } @$wins; + for (my $i = 0; $i < @nameList - 1; ++$i) { + my ($x, $y) = ($nameList[$i], $nameList[$i + 1]); + s/^[+#!=]// for $x, $y; + my $res = exists $abbrev_cache{$x}{$y} ? $abbrev_cache{$x}{$y} + : $abbrev_cache{$x}{$y} = string_LCSS($x, $y); + if (defined $res) { + for ($nameList[$i], $nameList[$i + 1]) { + $abbrevList->{$_} //= int((index $_, $res) + (length $res) / 2); + } + } + } + } +} + +my %act_last_line_shades = ( + r => [qw[ 50 40 30 20 ]], + g => [qw[ 1O 1I 1C 16 ]], + y => [qw[ 5O 4I 3C 26 ]], + b => [qw[ 15 14 13 12 ]], + m => [qw[ 54 43 32 21 ]], + c => [qw[ 1S 1L 1E 17 ]], + w => [qw[ 7W 7T 7Q 3E ]], + K => [qw[ 7M 7K 27 7H ]], + R => [qw[ 60 50 40 30 ]], + G => [qw[ 1U 1O 1I 1C ]], + Y => [qw[ 6U 5O 4I 3C ]], + B => [qw[ 2B 2A 29 28 ]], + M => [qw[ 65 54 43 32 ]], + C => [qw[ 1Z 1S 1L 1E ]], + W => [qw[ 6Z 5S 7R 7O ]], + ); + +sub _format_display { + my (undef, $format, $cformat, $hilight, $name, $number, $key, $win) = @_; + if ($print_text_activity && $S{line_shade}) { + my @hilight_code = split /\177/, sb_format_expand("{$hilight \177}"), 2; + my $max_time = max(1, log($S{line_shade}) - log(1000)); + my $time_delta = min(3, min($max_time, log(max(1, time - $win->{last_line}))) / $max_time * 3); + if ($hilight_code[0] =~ /%(.)/ && exists $act_last_line_shades{$1}) { + $hilight = 'sb_act_hilight_color %X'.$act_last_line_shades{$1}[$time_delta]; + } + } + $cformat = '$0' unless length $cformat; + my %map = ('$C' => $cformat, '$N' => '$1', '$Q' => '$2'); + $format =~ s<(\$.)><$map{$1}//$1>ge; + $format =~ s<\$H((?:\$.|[^\$])*?)\$S><{$hilight $1%n}>g; + my @ret = ir_parse_special(sb_format_expand($format), [$name, $number, $key], $win); + @ret +} + +sub _get_format { + Irssi::current_theme->get_format(__PACKAGE__, @_) +} + +sub _is_detached { + my ($win, $active_number) = @_; + my $level = $win->{data_level} // 0; + my $number = $win->{refnum}; + my $name = lc1459( as_uni($win->{name}) ); + my $active = lc1459( as_uni($win->get_active_name) // '' ); + my $tag = $win->{active} && $win->{active}{server} ? lc1459( as_uni($win->{active}{server}{tag}) // '' ) : ''; + my @cond = ($number); + push @cond, "$name" if length $name; + push @cond, "$tag/$active" if length $tag && length $active; + push @cond, "$active" if length $active; + push @cond, "$tag/*", "$tag/::all" if length $tag; + push @cond, "*", "::all"; + for my $cond (@cond) { + if (exists $detach_map{ $cond }) { + my $dd = $detach_map{ $cond } // $S{detach_data}; + return $win->{data_level} < abs $dd + && ($number != $active_number || 0 <= $dd); + } + } + return; +} + +sub _calculate_items { + my ($wins, $abbrevList) = @_; + + my $display_header = _get_format(set 'display_header'); + my $name_format = _get_format(set 'name_display'); + my $abbrev_chars = as_uni(_get_format(set 'abbrev_chars')); + + my %displays; + + my $active = Irssi::active_win; + @win_items = (); + %keymap = (%nummap, %wnmap_exp); + + my ($numPad, $keyPad) = (0, 0); + if ($VIEWER_MODE or $S{block} < 0) { + $numPad = length((sort { length $b <=> length $a } keys %keymap)[0]) // 0; + $keyPad = length((sort { length $b <=> length $a } values %keymap)[0]) // 0; + } + my $last_net; + my ($abbrev1, $abbrev2) = $abbrev_chars =~ /(\X)(.*)/; + my @abbrev_chars = ('~', "\x{301c}"); + unless (defined $abbrev1 && screen_length(as_tc($abbrev1)) == 1) { $abbrev1 = $abbrev_chars[0] } + unless (length $abbrev2) { + $abbrev2 = $abbrev1; + if ($abbrev1 eq $abbrev_chars[0]) { + $abbrev2 = $abbrev_chars[1]; + } + else { + $abbrev2 = $abbrev1; + } + } + if (screen_length(as_tc($abbrev2)) == 1) { + $abbrev2 x= 2; + } + while (screen_length(as_tc($abbrev2)) > 2) { + chop $abbrev2; + } + unless (screen_length(as_tc($abbrev2)) == 2) { + $abbrev2 = $abbrev_chars[1]; + } + for my $win (@$wins) { + my $global_tag_header_mode; + + next unless ref $win; + + my $backup_win = Storable::dclone($win); + delete $backup_win->{active} unless ref $backup_win->{active}; + + $global_tag_header_mode = + $display_header && ($last_net // '') ne ($backup_win->{active}{server}{tag} // ''); + + if ($win->{data_level} < abs $S{hide_data} + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_data})) { + next; } + elsif (exists $awins{$win->{refnum}} && $S{hide_empty} && !$win->items + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_empty})) { + next; } + elsif (_is_detached($win, $active->{refnum})) { + next; } + + my $colour = $win->{hilight_color} // ''; + my $hilight = do { + if ($win->{data_level} == 0) { 'sb_act_none'; } + elsif ($win->{data_level} == 1) { 'sb_act_text'; } + elsif ($win->{data_level} == 2) { 'sb_act_msg'; } + elsif ($colour ne '') { "sb_act_hilight_color $colour"; } + elsif ($win->{data_level} == 3) { 'sb_act_hilight'; } + else { 'sb_act_special'; } + }; + my $number = $win->{refnum}; + + my ($name, $display, $cdisplay); + if ($global_tag_header_mode) { + $display = $display_header; + $name = as_uni($backup_win->{active}{server}{tag}) // ''; + if ($custom_xform) { + no strict 'refs'; + local ${ __PACKAGE__ . '::custom_xform::TAG' } = 1; + run_custom_xform() for $name; + } + } + else { + my @display = ('display_nokey'); + if (defined $keymap{$number} and $keymap{$number} ne '') { + unshift @display, map { (my $cpy = $_) =~ s/_no/_/; $cpy } @display; + } + if (exists $awins{$number}) { + unshift @display, map { my $cpy = $_; $cpy .= '_visible'; $cpy } @display; + } + if ($active->{refnum} == $number) { + unshift @display, map { my $cpy = $_; $cpy .= '_active'; $cpy } + grep { !/_visible$/ } @display; + } + $display = (grep { length $_ } + map { $displays{$_} //= _get_format(set $_) } + @display)[0]; + $cdisplay = $name_format; + $name = as_uni($win->get_active_name) // ''; + $name = '*' if $S{banned_on} and exists $banned_channels{lc1459($name)}; + $name = remove_uniform_vars($win, $name) if $name ne '*'; + if ($name ne '*' and $win->{name} ne '' and $S{prefer_name}) { + $name = as_uni($win->{name}); + if ($custom_xform) { + no strict 'refs'; + local ${ __PACKAGE__ . '::custom_xform::NAME' } = 1; + run_custom_xform() for $name; + } + } + + if (!$VIEWER_MODE && $S{block} >= 0 && $S{hide_name} + && $win->{data_level} < abs $S{hide_name} + && ($win->{refnum} != $active->{refnum} || 0 <= $S{hide_name})) { + $name = ''; + $cdisplay = ''; + } + } + + $display = "$display%n"; + my $num_ent = (' 'x max(0,$numPad - length $number)) . $number; + my $key_ent = exists $keymap{$number} ? ((' 'x max(0,$keyPad - length $keymap{$number})) . $keymap{$number}) : ' 'x$keyPad; + if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) { + my $baseLength = sb_length(_format_display( + '', $display, $cdisplay, $hilight, + 'x', # placeholder + $num_ent, + $key_ent, + $win)) - 1; + my $diff = (abs $S{block}) - (screen_length(as_tc($name)) + $baseLength); + if ($diff < 0) { # too long + my $screen_length = screen_length(as_tc($name)); + if ((abs $diff) >= $screen_length) { $name = '' } # forget it + elsif ((abs $diff) + screen_length(as_tc(substr($name, 0, 1))) >= $screen_length) { $name = substr($name, 0, 1); } + else { + my $ulen = length $name; + my $middle2 = exists $abbrevList->{$name} ? + ($S{fancy_strict}) ? + 2* $abbrevList->{$name} : + (2*($abbrevList->{$name} + $ulen) / 3) : + ($S{fancy_head}) ? + 2*$ulen : + $ulen; + my $first = 1; + while (length $name > 1) { + my $cp = $middle2 >= 0 ? $middle2/2 : -1; # clearing position + my $rm = 2; + # if character at end is wider than 1 cell -> replace it with ~ + if (screen_length(as_tc(substr $name, $cp, 1)) > 1) { + if ($first || $cp < 0) { + $rm = 1; + $first = undef; + } + } + elsif ($cp < 0) { # elsif at end -> replace last 2 characters + --$cp; + } + (substr $name, $cp, $rm) = $abbrev1; + if ($cp > -1 && $rm > 1) { + --$middle2; + } + my $sl = screen_length(as_tc($name)); + if ($sl + $baseLength < abs $S{block}) { + (substr $name, ($middle2+1)/2, 1) = $abbrev2; + last; + } + elsif ($sl + $baseLength == abs $S{block}) { + last; + } + } + } + } + elsif ($VIEWER_MODE or $S{block} < 0) { + $name .= (' ' x $diff); + } + } + + push @win_items, _format_display( + '', $display, $cdisplay, $hilight, + as_tc($name), + $num_ent, + as_tc($key_ent), + $win); + + if ($global_tag_header_mode) { + $last_net = $backup_win->{active}{server}{tag}; + redo; + } + + $mouse_coords{refnum}{$#win_items} = $number; + } +} + +sub _spread_items { + my $width = $screenWidth - $sb_base_width - 1; + my @separator = _get_format(set 'separator'); + if ($S{block} >= 0) { + my $sep2 = _get_format(set 'separator2'); + push @separator, $sep2 if length $sep2 && $sep2 ne $separator[0]; + } + $separator[0] .= '%n'; + my @sepLen = map { sb_length($_) } @separator; + + @actString = (); + my $curLine; + my $curLen = 0; + if ($S{shared_sbar}) { + $curLen += $S{shared_sbar}[0] + 2; + $width -= $S{shared_sbar}[2]; + } + my $mouse_header_check = 0; + for my $it (@win_items) { + my $itemLen = sb_length($it); + if ($curLen) { + if ($curLen + $itemLen + $sepLen[$mouse_header_check % @sepLen] > $width) { + $width += $S{shared_sbar}[2] + if !@actString && $S{shared_sbar}; + push @actString, $curLine; + $curLine = undef; + $curLen = 0; + } + elsif (defined $curLine) { + $curLine .= $separator[$mouse_header_check % @separator]; + $curLen += $sepLen[$mouse_header_check % @sepLen]; + } + } + $curLine .= $it; + if (exists $mouse_coords{refnum}{$mouse_header_check}) { + $mouse_coords{scalar @actString}{ $_ } = $mouse_coords{refnum}{$mouse_header_check} + for $curLen .. $curLen + $itemLen - 1; + } + $curLen += $itemLen; + } + continue { + ++$mouse_header_check; + } + $curLen -= $S{shared_sbar}[0] + if !@actString && $S{shared_sbar}; + push @actString, $curLine if $curLen; +} + +sub remake { + my %abbrevList; + my @wins = window_list(); + if ($VIEWER_MODE or $S{sbar_maxlen} or $S{block} < 0) { + _calculate_abbrev(\@wins, \%abbrevList); + } + + %mouse_coords = ( refnum => +{} ); + _calculate_items(\@wins, \%abbrevList); + + unless ($VIEWER_MODE) { + _spread_items(); + + push @actString, undef unless @actString || $S{all_disable}; + } +} + +sub update_wl { + return if $BLOCK_ALL; + remake(); + + Irssi::statusbar_items_redraw(set $_) for keys %statusbars; + + unless ($VIEWER_MODE) { + Irssi::timeout_add_once(100, 'syncLines', undef); + } + else { + syncViewer(); + } +} + +sub screenFullRedraw { + my ($window) = @_; + if (!ref $window or $window->{refnum} == Irssi::active_win->{refnum}) { + $viewer{fullRedraw} = 1 if $viewer{client}; + $settings_str = ''; + &setup_changed; + } +} + +sub restartViewerServer { + if ($VIEWER_MODE) { + stop_viewer(); + start_viewer(); + } +} + +sub _simple_quote { + my @r = map { + my $x = $_; + $x =~ s/'/'"'"'/g; + $x = "'$x'"; + } @_; + wantarray ? @r : shift @r +} + +sub _viewer_command_replace_format { + my ($ecmd, @args) = @_; + my $file = _simple_quote(SCRIPT_FILE()); + my $path = _simple_quote($viewer{path}); + my @env; + for my $env (shellwords($S{viewer_launch_env})) { + if ($env =~ /^(\w+)(?:=(.*))$/) { + push @env, "AWL_$1=$2" + } + } + my $cmd = join ' ', + (@env ? ('env', _simple_quote(@env)) : ()), + 'perl', $file, '-1', _simple_quote(@args), $path; + $ecmd =~ s{%(%|\w+)}{ + my $sub = $1; + if ($sub eq '%') { + '%' + } + elsif ($sub =~ /^(q*)A(.*)/) { + my $ret = $cmd; + for (1..length $1) { + $ret = _simple_quote($ret); + } + "$ret$2" + } + else { + "%$sub" + } + }gex; + $ecmd +} + +sub start_viewer { + unlink $viewer{path} if -S $viewer{path} || -p _; + + $viewer{server} = IO::Socket::UNIX->new( + Type => SOCK_STREAM, + Local => $viewer{path}, + Listen => 1 + ); + unless ($viewer{server}) { + $viewer{msg} = "Viewer: $!"; + $viewer{retry} = Irssi::timeout_add_once(5000, 'retry_viewer', 1); + return; + } + $viewer{server}->blocking(0); + set_viewer_mode_hint(); + $viewer{server_tag} = Irssi::input_add($viewer{server}->fileno, INPUT_READ, 'vi_connected', undef); + + if ($S{viewer_launch}) { + if (length $ENV{TMUX_PANE} && length $ENV{TMUX} && lc $S{viewer_tmux_position} ne 'custom') { + my $cmd = _viewer_command_replace_format('%qA', '-p', lc $S{viewer_tmux_position}); + Irssi::command("exec - tmux neww -d $cmd 2>&1 &"); + } + elsif (length $ENV{WINDOWID} && length $ENV{DISPLAY} && length $S{viewer_xwin_command} && $S{viewer_xwin_command} =~ /\S/) { + my $cmd = _viewer_command_replace_format($S{viewer_xwin_command}); + Irssi::command("exec - $cmd 2>&1 &"); + } + elsif (length $S{viewer_custom_command} && $S{viewer_custom_command} =~ /\S/) { + my $cmd = _viewer_command_replace_format($S{viewer_custom_command}); + Irssi::command("exec - $cmd 2>&1 &"); + } + } +} + +sub set_viewer_mode_hint { + return unless $viewer{server}; + if ($S{no_mode_hint}) { + $viewer{msg} = undef; + } + else { + my ($name) = __PACKAGE__ =~ /::([^:]+)$/; + $viewer{msg} = "Run $name from the shell or switch to sbar mode"; + } +} + +sub retry_viewer { + start_viewer(); +} + +sub vi_close_client { + Irssi::input_remove(delete $viewer{client_tag}) if exists $viewer{client_tag}; + $viewer{client}->close if $viewer{client}; + delete $viewer{client}; + delete $viewer{client_keymap}; + delete $viewer{client_settings}; + delete $viewer{client_env}; + delete $viewer{fullRedraw}; +} + +sub vi_connected { + vi_close_client(); + $viewer{client} = $viewer{server}->accept or return; + $viewer{client}->blocking(0); + $viewer{client_tag} = Irssi::input_add($viewer{client}->fileno, INPUT_READ, 'vi_clientinput', undef); + syncViewer(); +} + +use constant VIEWER_BLOCK_SIZE => 1024; +sub vi_clientinput { + if ($viewer{client}->read(my $buf, VIEWER_BLOCK_SIZE)) { + $viewer{rcvbuf} .= $buf; + if ($viewer{rcvbuf} =~ s/^(?:(active|\d+)|(last|up|down))\n//igm) { + if (defined $2) { + Irssi::command("window $2"); + } + elsif (lc $1 eq 'active' && $viewer{use_ack}) { + Irssi::command($viewer{use_ack}); + } + else { + Irssi::command("window goto $1"); + } + } + } + else { + vi_close_client(); + Irssi::timeout_add_once(100, 'syncViewer', undef); + } +} + +sub stop_viewer { + Irssi::timeout_remove(delete $viewer{retry}) if exists $viewer{retry}; + vi_close_client(); + Irssi::input_remove(delete $viewer{server_tag}) if exists $viewer{server_tag}; + return unless $viewer{server}; + $viewer{server}->close; + delete $viewer{server}; +} +sub _encode_var { + my $str; + while (@_) { + my ($name, $var) = splice @_, 0, 2; + my $type = ref $var ? $var =~ /HASH/ ? 'map' : $var =~ /ARRAY/ ? 'list' : '' : ''; + $str .= "\n\U$name$type\_begin\n"; + if ($type eq 'map') { + no warnings 'numeric'; + $str .= " $_\n ${$var}{$_}\n" for sort { $a <=> $b || $a cmp $b } keys %$var; + } + elsif ($type eq 'list') { + $str .= " $_\n" for @$var; + } + else { + $str .= " $var\n"; + } + $str .= "\U$name$type\_end\n"; + } + $str +} +sub syncViewer { + if ($viewer{client}) { + @actString = (); + if ($currentLines) { + killOldStatus(); + $currentLines = 0; + } + my $str; + unless ($viewer{client_keymap}) { + $str .= _encode_var('key', +{ %nummap, %specialmap }); + $viewer{client_keymap} = 1; + } + unless ($viewer{client_settings}) { + $str .= _encode_var( + block => $S{block}, + ha => $S{height_adjust}, + mc => $S{maxcolumns}, + ml => $S{maxlines}, + tc => $S{true_colour}, + ); + $viewer{client_settings} = 1; + } + unless ($viewer{client_env}) { + $str .= _encode_var(irssienv => +{ + length $ENV{TMUX_PANE} && length $ENV{TMUX} ? + (tmux_pane => $ENV{TMUX_PANE}, + tmux_srv => $ENV{TMUX}) : (), + length $ENV{WINDOWID} ? + (xwinid => $ENV{WINDOWID}) : (), + }); + $viewer{client_env} = 1; + } + my $separator = _get_format(set 'separator'); + my $sepLen = sb_length($separator); + my $item_bg = _get_format(set 'viewer_item_bg'); + my $title = _get_format(set 'title'); + if (length $title) { + $title =~ s{\\(.)|(.)}{ + defined $2 ? quotemeta $2 + : $1 eq 'V' ? '\U' + : $1 eq ':' ? quotemeta '%N' + : $1 =~ /^[uUFQE]$/ ? "\\$1" + : quotemeta "\\$1" + }sge; + $title = eval qq{"$title"}; + } + $str .= _encode_var(redraw => 1) if delete $viewer{fullRedraw}; + $str .= _encode_var(separator => $separator, + seplen => $sepLen, + itembg => $item_bg, + title => $title, + mouse => $mouse_coords{refnum}, + key2 => \%wnmap_exp, + win => \@win_items); + + my $was = $viewer{client}->blocking(1); + $viewer{client}->print($str); + $viewer{client}->blocking($was); + } + elsif ($viewer{server}) { + if (defined $viewer{msg}) { + @actString = ((uc setc()).": $viewer{msg}"); + } + else { + @actString = (); + } + } + elsif (defined $viewer{msg}) { + @actString = ((uc setc()).": $viewer{msg}"); + } + if (@actString) { + Irssi::timeout_add_once(100, 'syncLines', undef); + } + elsif ($currentLines) { + killOldStatus(); + $currentLines = 0; + } +} + +sub reset_awl { + Irssi::timeout_remove($shade_line_timer) if $shade_line_timer; $shade_line_timer = undef; + my $was_sort = $S{sort} // ''; + my $was_xform = $S{xform} // ''; + my $was_shared = $S{shared_sbar}; + my $was_no_hint = $S{no_mode_hint}; + %S = ( + sort => Irssi::settings_get_str( set 'sort'), + fancy_abbrev => Irssi::settings_get_str('fancy_abbrev'), + xform => Irssi::settings_get_str( set 'custom_xform'), + block => Irssi::settings_get_int( set 'block'), + banned_on => Irssi::settings_get_bool('banned_channels_on'), + prefer_name => Irssi::settings_get_bool(set 'prefer_name'), + hide_data => Irssi::settings_get_int( set 'hide_data'), + hide_name => Irssi::settings_get_int( set 'hide_name_data'), + hide_empty => Irssi::settings_get_int( set 'hide_empty'), + detach => Irssi::settings_get_str( set 'detach'), + detach_data => Irssi::settings_get_int( set 'detach_data'), + detach_aht => Irssi::settings_get_bool(set 'detach_aht'), + sbar_maxlen => Irssi::settings_get_bool(set 'sbar_maxlength'), + placement => Irssi::settings_get_str( set 'placement'), + position => Irssi::settings_get_int( set 'position'), + maxlines => Irssi::settings_get_int( set 'maxlines'), + maxcolumns => Irssi::settings_get_int( set 'maxcolumns'), + all_disable => Irssi::settings_get_bool(set 'all_disable'), + height_adjust => Irssi::settings_get_int( set 'height_adjust'), + mouse_offset => Irssi::settings_get_int( set 'mouse_offset'), + mouse_scroll => Irssi::settings_get_int( 'mouse_scroll'), + mouse_escape => Irssi::settings_get_int( 'mouse_escape'), + line_shade => Irssi::settings_get_time(set 'last_line_shade'), + no_mode_hint => Irssi::settings_get_bool(set 'no_mode_hint'), + true_colour => Irssi::parse_special('$colors_ansi_24bit'), + viewer_launch => Irssi::settings_get_bool(set 'viewer_launch'), + viewer_launch_env => Irssi::settings_get_str(set 'viewer_launch_env'), + viewer_xwin_command => Irssi::settings_get_str(set 'viewer_xwin_command'), + viewer_custom_command => Irssi::settings_get_str(set 'viewer_custom_command'), + viewer_tmux_position => Irssi::settings_get_str(set 'viewer_tmux_position'), + ); + $S{fancy_strict} = $S{fancy_abbrev} =~ /^strict/i; + $S{fancy_head} = $S{fancy_abbrev} =~ /^head/i; + my $shared = Irssi::settings_get_str(set 'shared_sbar'); + if ($shared =~ /^(\d+)([<])(\d+)$/) { + $S{shared_sbar} = [$1, $2, $3]; + } + else { + Irssi::settings_set_str(set 'shared_sbar', 'OFF'); + $S{shared_sbar} = undef; + } + lock_keys(%S); + if ($was_sort ne $S{sort}) { + $print_text_activity = undef; + my @sort_order = grep { @$_ > 4 } map { + s/^\s*//; + my $reverse = s/^\W*\K[-!]//; + my $undef_check = s/^\W*\K~// ? 1 : undef; + my $equal_check = s/=(.*)\s?$// ? $1 : undef; + s/\s*$//; + my $ignore_case = s/#i$// ? 1 : undef; + + $print_text_activity = 1 if $_ eq 'last_line'; + + my @path = split '/'; + my $class_check = @path && $path[-1] =~ s/(::.*)$// ? $1 : undef; + my $lru = "@path" eq 'lru'; + + [ $reverse ? -1 : 1, $undef_check, $equal_check, $class_check, $ignore_case, $lru, @path ] + } "$S{sort}," =~ /([^+,]*|[^+,]*=[^,]*?\s(?=\+)|[^+,]*=[^,]*)[+,]/g; + $window_sort_func = sub { + no warnings qw(numeric uninitialized); + for my $so (@sort_order) { + my @x = map { + my $ret = 0; + $_ = lc1459($_) if defined $_ && !ref $_ && $so->[4]; + $ret = $_ eq ($so->[4] ? lc1459($so->[2]) : $so->[2]) ? 1 : -1 if defined $so->[2]; + $ret = defined $_ ? ($ret || -3) : 3 if $so->[1]; + $ret = ref $_ && $_->isa('Irssi'.$so->[3]) ? 2 : ($ret || -2) if $so->[3]; + -$ret || $_ + } + map { + $so->[5] ? $_->[0] : reduce { return unless ref $a; $a->{$b} } $_->[1], @{$so}[6..$#$so] + } $a, $b; + return ((($x[0] <=> $x[1] || $x[0] cmp $x[1]) * $so->[0]) || next); + } + return ($a->[1]{refnum} <=> $b->[1]{refnum}); + }; + } + if ($was_xform ne $S{xform}) { + if ($S{xform} !~ /\S/) { + $custom_xform = undef; + } + else { + my $script_pkg = __PACKAGE__ . '::custom_xform'; + local $@; + $custom_xform = eval qq{ +package $script_pkg; +use strict; +no warnings; +our (\$QUERY, \$CHANNEL, \$TAG, \$NAME); +return sub { +# line 1 @{[ set 'custom_xform' ]}\n$S{xform}\n}}; + if ($@) { + $@ =~ /^(.*)/; + print '%_'.(set 'custom_xform').'%_ did not compile: '.$1; + } + } + } + + my $new_settings = join "\n", $VIEWER_MODE + ? ("\\", $S{block}, $S{height_adjust}, $S{maxlines}, $S{maxcolumns}, $S{true_colour}) + : ("!", $S{placement}, $S{position}); + + my $first_viewer = $settings_str eq '1'; + if ($settings_str ne $new_settings) { + @actString = (); + %abbrev_cache = (); + $currentLines = 0; + killOldStatus(); + delete $viewer{client_settings}; + $settings_str = $new_settings; + } + + my $was_mouse_mode = $MOUSE_ON; + if ($MOUSE_ON = Irssi::settings_get_bool(set 'mouse') and !$was_mouse_mode) { + install_mouse(); + } + elsif ($was_mouse_mode and !$MOUSE_ON) { + uninstall_mouse(); + } + + unless ($first_viewer) { + my $path = Irssi::settings_get_str(set 'path'); + my $was_viewer_mode = $VIEWER_MODE; + if ($was_viewer_mode && + defined $viewer{path} && $viewer{path} ne $path) { + stop_viewer(); + $was_viewer_mode = 0; + } + elsif ($was_viewer_mode && $S{no_mode_hint} != $was_no_hint + 0) { + set_viewer_mode_hint(); + } + $viewer{path} = $path; + if ($VIEWER_MODE = Irssi::settings_get_bool(set 'viewer') and !$was_viewer_mode) { + start_viewer(); + } + elsif ($was_viewer_mode and !$VIEWER_MODE) { + stop_viewer(); + } + } + + %banned_channels = map { lc1459(as_uni($_)) => undef } + split ' ', Irssi::settings_get_str('banned_channels'); + + %detach_map = ($S{detach_aht} + ? (map { ( lc1459(as_uni($_)) => undef ) } + split ' ', Irssi::settings_get_str('activity_hide_targets')) : (), + (map { my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + ( lc1459(as_uni($k)) => $v ) } + split ' ', $S{detach})); + + my @sb_base = split /\177/, sb_format_expand("{sbstart}{sb \177}{sbend}"), 2; + $sb_base_width_pre = sb_length($sb_base[0]); + $sb_base_width_post = max 0, sb_length($sb_base[1])-1; + $sb_base_width = $sb_base_width_pre + $sb_base_width_post; + + if ($print_text_activity && $S{line_shade}) { + $shade_line_timer = Irssi::timeout_add(max(10 * GLOB_QUEUE_TIMER, 100*$S{line_shade}**(1/3)), 'wl_changed', undef); + } + + $CHANGED{AWINS} = 1; +} + +sub hide_window { + my ($data) = @_; + my $ent; + + $data =~ s/\s*$//; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + my $name = as_uni($win->{name}); + my $active = as_uni($win->get_active_name) // ''; + my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : ''; + if (length $name) { + $ent = "$name"; + } + elsif (length $tag && length $active) { + $ent = "$tag/$active"; + } + else { + $ent = "$number"; + } + + my $found = 0; + my @setting; + for my $s (split ' ', $S{detach}) { + my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1]; + if (lc1459(as_uni($k)) eq lc1459($ent)) { + unless ($found) { + if ($data =~ /^(-?\d+)$/) { + $ent .= ",$1"; + } + if (defined $v && 0 == abs $v) { + $win->print("Hiding window $ent"); + } + push @setting, as_tc($ent); + $found = 1; + } + } + else { + push @setting, defined $v ? "$k,$v" : $k; + } + } + unless ($found) { + $win->print("Hiding window $ent"); + if ($data =~ /^(-?\d+)$/) { + $ent .= ",$1"; + } + push @setting, as_tc($ent); + } + + if (@setting) { + Irssi::command("^set ".(set 'detach')." @setting"); + } else { + Irssi::command("^set -clear ".(set 'detach')); + } +} + +sub unhide_window { + my ($data, $server, $witem) = @_; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + my $name = as_uni($win->{name}); + my $active = as_uni($win->get_active_name) // ''; + my $tag = $win->{active} && $win->{active}{server} ? as_uni($win->{active}{server}{tag}) // '' : ''; + + my %detach_aht; + if ($S{detach_aht}) { + %detach_aht = (map { ( lc1459(as_uni($_)) => undef ) } + split ' ', Irssi::settings_get_str('activity_hide_targets')); + } + my @setting; + my @kills = (length $name ? $name : undef, + length $tag && length $active ? "$tag/$active" : undef, + length $active ? $active : undef, + $number); + my @was_unhidden = (0) x @kills; + for my $s (split ' ', $S{detach}) { + my ($k, $v) = (split /(?:,(-?\d+))$/, $s)[0, 1]; + my $k2 = lc1459(as_uni($k)); + my $kill; + for my $ki (0..$#kills) { + if (defined $kills[$ki] && $k2 eq lc1459($kills[$ki])) { + $kill = $ki; + } + } + + if (defined $kill) { + if (defined $v && 0 == abs $v) { + $was_unhidden[$kill] = 1; + push @setting, defined $v ? "$k,$v" : $k; + } else { + $win->print("Unhiding window $kills[$kill]"); + } + } + else { + push @setting, defined $v ? "$k,$v" : $k; + } + } + my @is_hidden = (defined $kills[0] && (exists $detach_map{"*"} || exists $detach_map{"::all"}), + defined $kills[1] && (exists $detach_map{lc1459("$tag/*")} || exists $detach_map{lc1459("$tag/::all")} + || exists $detach_map{"*"} || exists $detach_map{"::all"}), + defined $kills[2] && (exists $detach_map{"*"} || exists $detach_map{"::all"}), + (exists $detach_map{"*"} || exists $detach_map{"::all"}) + ); + for my $ki (1, 2, 0, 3) { + if ($is_hidden[$ki]) { + unless ($was_unhidden[$ki]) { + $win->print("Unhiding window $kills[$ki]"); + push @setting, "$kills[$ki],0"; + $was_unhidden[$ki] = 1; + } + last; + } + } + my @is_hidden_aht = (defined $kills[0] && (exists $detach_aht{lc1459($name)} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + defined $kills[1] && (exists $detach_aht{lc1459("$tag/$active")} + || exists $detach_aht{lc1459($active)} + || exists $detach_aht{lc1459("$tag/*")} || exists $detach_aht{lc1459("$tag/::all")} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + defined $kills[2] && (exists $detach_aht{lc1459($active)} + || exists $detach_aht{"*"} || exists $detach_aht{"::all"}), + (exists $detach_aht{$number} || exists $detach_aht{"*"} || exists $detach_aht{"::all"}) + ); + for my $ki (1, 2, 0, 3) { + if ($is_hidden_aht[$ki]) { + unless ($was_unhidden[$ki]) { + $win->print("Unhiding window $kills[$ki], it is hidden because ".(set 'detach_aht')." is ON"); + push @setting, "$kills[$ki],0"; + $was_unhidden[$ki] = 1; + } + last; + } + } + + if (@setting) { + Irssi::command("^set ".(set 'detach')." @setting"); + } else { + Irssi::command("^set -clear ".(set 'detach')); + } +} + +sub ack_window { + my ($data, $server, $witem) = @_; + my $win = Irssi::active_win; + my $number = $win->{refnum}; + if (grep { $_->{cmd} eq 'ack' } Irssi::commands) { + my $Orig_Irssi_windows = \&Irssi::windows; + local *Irssi::windows = sub () { grep { !_is_detached($_, $number) } $Orig_Irssi_windows->() }; + Irssi::command("ack" . (length $data ? " $data" : "")); + } else { + my $ignore_refnum = Irssi::settings_get_bool('active_window_ignore_refnum'); + my $max_win; + my $max_act = 0; + my $max_ref = 0; + for my $rec (Irssi::windows) { + next if _is_detached($rec, $number); + + # ignore refnum + if ($ignore_refnum && + $rec->{data_level} > 0 && $max_act < $rec->{data_level}) { + $max_act = $rec->{data_level}; + $max_win = $rec; + } + + # windows with lower refnums break ties + elsif (!$ignore_refnum && + $rec->{data_level} > 0 && + ($rec->{data_level} > $max_act || + ($rec->{data_level} == $max_act && $rec->{refnum} < $max_ref))) { + $max_act = $rec->{data_level}; + $max_win = $rec; + $max_ref = $rec->{refnum}; + } + } + $max_win->set_active if defined $max_win; + } +} + +sub refnum_changed { + my ($win, $old_refnum) = @_; + my @old_setting = split ' ', $S{detach}; + my @setting = map { + my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + if ($k eq $old_refnum) { + $win->{refnum} . (defined $v ? ",$v" : "") + } + else { + $_ + } + } @old_setting; + if ("@old_setting" ne "@setting") { + $S{detach} = "@setting"; + Irssi::settings_set_str(set 'detach', "@setting"); + &setup_changed; + } + else { + &wl_changed; + } +} + +sub window_destroyed { + my ($win) = @_; + my @old_setting = split ' ', $S{detach}; + my @setting = grep { + my ($k, $v) = (split /(?:,(-?\d+))$/, $_)[0, 1]; + if ($k eq $win->{refnum}) { + 0; + } + else { + 1; + } + } @old_setting; + if ("@old_setting" ne "@setting") { + $S{detach} = "@setting"; + Irssi::settings_set_str(set 'detach', "@setting"); + &setup_changed; + } + else { + &awins_changed; + } +} + +sub stop_mouse_tracking { + print STDERR "\e[?1005l\e[?1000l"; +} +sub start_mouse_tracking { + print STDERR "\e[?1000h\e[?1005h"; +} +sub install_mouse { + Irssi::command_bind('mouse_xterm' => 'mouse_xterm'); + Irssi::command('^bind meta-[M command mouse_xterm'); + Irssi::signal_add_first('gui key pressed' => 'mouse_key_hook'); + start_mouse_tracking(); +} +sub uninstall_mouse { + stop_mouse_tracking(); + Irssi::signal_remove('gui key pressed' => 'mouse_key_hook'); + Irssi::command('^bind -delete meta-[M'); + Irssi::command_unbind('mouse_xterm' => 'mouse_xterm'); +} + +sub awl_mouse_event { + return if $VIEWER_MODE; + if ((($_[0] == 3 and $_[3] == 0) + || $_[0] == 64 || $_[0] == 65) and + $_[1] == $_[4] and $_[2] == $_[5]) { + my $top = lc $S{placement} eq 'top'; + my ($pos, $line) = @_[1 .. 2]; + unless ($top) { + $line -= $screenHeight; + $line += $currentLines; + $line += $S{mouse_offset}; + } + else { + $line -= $S{mouse_offset}; + } + $pos -= $sb_base_width_pre; + return if $line < 0 || $line >= $currentLines; + if ($_[0] == 64) { + Irssi::command('window up'); + } + elsif ($_[0] == 65) { + Irssi::command('window down'); + } + elsif (exists $mouse_coords{$line}{$pos}) { + my $win = $mouse_coords{$line}{$pos}; + Irssi::command('window ' . $win); + } + Irssi::signal_stop; + } +} + +sub mouse_scroll_event { + return unless $S{mouse_scroll}; + if (($_[3] == 64 or $_[3] == 65) and + $_[0] == $_[3] and $_[1] == $_[4] and $_[2] == $_[5]) { + my $cmd = 'scrollback goto ' . ($_[3] == 64 ? '-' : '+') . $S{mouse_scroll}; + Irssi::active_win->command($cmd); + Irssi::signal_stop; + } + elsif ($_[0] == 64 or $_[0] == 65) { + Irssi::signal_stop; + } +} + +sub mouse_escape { + return unless $S{mouse_escape} > 0; + if ($_[0] == 3) { + my $tm = $S{mouse_escape}; + $tm *= 1000 if $tm < 1000; + stop_mouse_tracking(); + Irssi::timeout_add_once($tm, 'start_mouse_tracking', undef); + Irssi::signal_stop; + } +} + +sub UNLOAD { + @actString = (); + killOldStatus(); + stop_viewer() if $VIEWER_MODE; + uninstall_mouse() if $MOUSE_ON; +} + +sub addPrintTextHook { # update on print text + return unless defined $^S; + return if $BLOCK_ALL; + return unless $print_text_activity; + return if $_[0]->{level} == 262144 and $_[0]->{target} eq '' + and !defined($_[0]->{server}); + &wl_changed; +} + +sub block_event_window_change { + Irssi::signal_stop; +} + +sub update_awins { + my @wins = Irssi::windows; + local $BLOCK_ALL = 1; + Irssi::signal_add_first('window changed' => 'block_event_window_change'); + my $bwin = + my $awin = Irssi::active_win; + my $lwin; + my $defer_irssi_broken_last; + unless ($wins[0]{refnum} == $awin->{refnum}) { + # special case: more than 1 last win, so /win last; + # /win last doesn't come back to the current window. eg. after + # connect & autojoin; we can't handle this situation, bail out + $defer_irssi_broken_last = 1; + } + else { + $awin->command('window last'); + $lwin = Irssi::active_win; + $lwin->command('window last'); + $defer_irssi_broken_last = $lwin->{refnum} == $bwin->{refnum}; + } + my $awin_counter = 0; + Irssi::signal_remove('window changed' => 'block_event_window_change'); + unless ($defer_irssi_broken_last) { + # we need to keep the fe-windows code running here + Irssi::signal_add_priority('window changed' => 'block_event_window_change', -99); + %awins = %wnmap_exp = (); + do { + Irssi::active_win->command('window up'); + $awin = Irssi::active_win; + $awins{$awin->{refnum}} = undef; + ++$awin_counter; + } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins); + Irssi::signal_remove('window changed' => 'block_event_window_change'); + + Irssi::signal_add_first('window changed' => 'block_event_window_change'); + for my $key (keys %wnmap) { + next unless Irssi::window_find_name($key) || Irssi::window_find_item($key); + $awin->command("window goto $key"); + my $cwin = Irssi::active_win; + $wnmap_exp{ $cwin->{refnum} } = $wnmap{$key}; + $cwin->command('window last') + if $cwin->{refnum} != $awin->{refnum}; + } + for my $win (reverse @wins) { # restore original window order + Irssi::active_win->command('window '.$win->{refnum}); + } + $awin->command('window '.$lwin->{refnum}); # restore last win + Irssi::active_win->command('window last'); + Irssi::signal_remove('window changed' => 'block_event_window_change'); + } + $CHANGED{WL} = 1; +} + +sub resizeTerm { + if (defined (my $r = `stty size 2>/dev/null`)) { + ($screenHeight, $screenWidth) = split ' ', $r; + $CHANGED{SETUP} = 1; + } + else { + $CHANGED{SIZE} = 1; + } +} + +sub awl_refresh { + $globTime = undef; + resizeTerm() if delete $CHANGED{SIZE}; + reset_awl() if delete $CHANGED{SETUP}; + update_awins() if delete $CHANGED{AWINS}; + update_wl() if delete $CHANGED{WL}; +} + +sub termsize_changed { $CHANGED{SIZE} = 1; &queue_refresh; } +sub setup_changed { $CHANGED{SETUP} = 1; &queue_refresh; } +sub awins_changed { $CHANGED{AWINS} = 1; &queue_refresh; } +sub wl_changed { $CHANGED{WL} = 1; &queue_refresh; } + +sub window_changed { + &awins_changed if $_[1]; +} + +sub queue_refresh { + return if $BLOCK_ALL; + Irssi::timeout_remove($globTime) + if defined $globTime; # delay the update further + $globTime = Irssi::timeout_add_once(GLOB_QUEUE_TIMER, 'awl_refresh', undef); +} + +sub awl_init { + termsize_changed(); + setup_changed(); + update_keymap(); + Irssi::timeout_remove($globTime) + if defined $globTime; + awl_refresh(); + termsize_changed(); +} + +sub runsub { + my $cmd = shift; + sub { + my ($data, $server, $item) = @_; + Irssi::command_runsub($cmd, $data, $server, $item); + }; +} + +Irssi::signal_register({ + 'gui mouse' => [qw/int int int int int int/], + }); +{ my $broken_expandos = (Irssi::version >= 20081128 && Irssi::version < 20110210) + ? sub { my $x = shift; $x =~ s/\$\{cumode_space\}/ /; $x } : undef; + Irssi::theme_register([ + map { $broken_expandos ? $broken_expandos->($_) : $_ } + set 'display_nokey' => '$N${cumode_space}$H$C$S', + set 'display_key' => '$Q${cumode_space}$H$C$S', + set 'display_nokey_visible' => '%2$N${cumode_space}$H$C$S', + set 'display_key_visible' => '%2$Q${cumode_space}$H$C$S', + set 'display_nokey_active' => '%1$N${cumode_space}$H$C$S', + set 'display_key_active' => '%1$Q${cumode_space}$H$C$S', + set 'display_header' => '%8$C|${N}', + set 'name_display' => '$0', + set 'separator' => ' ', + set 'separator2' => '', + set 'abbrev_chars' => "~\x{301c}", + set 'viewer_item_bg' => sb_format_expand('{sb_background}'), + set 'title' => '\V'.setc().'\:', + ]); +} +Irssi::settings_add_bool(setc, set 'prefer_name', 0); # +Irssi::settings_add_int( setc, set 'hide_empty', 0); # +Irssi::settings_add_int( setc, set 'hide_data', 0); # +Irssi::settings_add_str( setc, set 'detach', ''); # +Irssi::settings_add_int( setc, set 'detach_data', -3); # +Irssi::settings_add_bool(setc, set 'detach_aht', 0); # +Irssi::settings_add_int( setc, set 'hide_name_data', 0); # +Irssi::settings_add_int( setc, set 'maxlines', 9); # +Irssi::settings_add_int( setc, set 'maxcolumns', 4); # +Irssi::settings_add_int( setc, set 'block', 15); # +Irssi::settings_add_bool(setc, set 'sbar_maxlength', 1); # +Irssi::settings_add_int( setc, set 'height_adjust', 2); # +Irssi::settings_add_str( setc, set 'sort', 'refnum'); # +Irssi::settings_add_str( setc, set 'placement', 'bottom'); # +Irssi::settings_add_int( setc, set 'position', 0); # +Irssi::settings_add_bool(setc, set 'all_disable', 1); # +Irssi::settings_add_bool(setc, set 'viewer', 1); # +Irssi::settings_add_str( setc, set 'shared_sbar', 'OFF'); # +Irssi::settings_add_bool(setc, set 'mouse', 0); # +Irssi::settings_add_str( setc, set 'path', Irssi::get_irssi_dir . '/_windowlist'); # +Irssi::settings_add_str( setc, set 'custom_xform', ''); # +Irssi::settings_add_time(setc, set 'last_line_shade', '0'); # +Irssi::settings_add_int( setc, set 'mouse_offset', 1); # +Irssi::settings_add_int( setc, 'mouse_scroll', 3); # +Irssi::settings_add_int( setc, 'mouse_escape', 1); # +Irssi::settings_add_str( setc, 'banned_channels', ''); +Irssi::settings_add_bool(setc, 'banned_channels_on', 1); +Irssi::settings_add_str( setc, 'fancy_abbrev', 'fancy'); # +Irssi::settings_add_bool(setc, set 'no_mode_hint', 0); # +Irssi::settings_add_bool(setc, set 'viewer_launch', 1); # +Irssi::settings_add_str( setc, set 'viewer_launch_env', ''); # +Irssi::settings_add_str( setc, set 'viewer_tmux_position', 'left'); # +Irssi::settings_add_str( setc, set 'viewer_xwin_command', 'xterm +sb -e %A'); # +Irssi::settings_add_str( setc, set 'viewer_custom_command', ''); # + +Irssi::signal_add_last({ + 'setup changed' => 'setup_changed', + 'print text' => 'addPrintTextHook', + 'terminal resized' => 'termsize_changed', + 'setup reread' => 'screenFullRedraw', + 'window hilight' => 'wl_changed', + 'command format' => 'wl_changed', +}); +Irssi::signal_add({ + 'window changed' => 'window_changed', + 'window item changed' => 'wl_changed', + 'window changed automatic' => 'window_changed', + 'window created' => 'awins_changed', + 'window destroyed' => 'window_destroyed', + 'window name changed' => 'wl_changed', + 'window refnum changed' => 'refnum_changed', +}); +Irssi::signal_add_last('gui mouse' => 'mouse_escape'); +Irssi::signal_add_last('gui mouse' => 'mouse_scroll_event'); +Irssi::signal_add_last('gui mouse' => 'awl_mouse_event'); +Irssi::command_bind( setc() => runsub(setc()) ); +Irssi::command_bind( setc() . ' redraw' => 'screenFullRedraw' ); +Irssi::command_bind( setc() . ' restart' => 'restartViewerServer' ); +Irssi::command_bind( setc() . ' attach' => 'unhide_window' ); +Irssi::command_bind( setc() . ' detach' => 'hide_window' ); +Irssi::command_bind( setc() . ' ack' => 'ack_window' ); + +{ + my $l = set 'shared'; + { + no strict 'refs'; + *{$l} = $awl_shared_empty; + } + Irssi::statusbar_item_register($l, '$0', $l); +} + +awl_init(); + +# Mouse script based on irssi mouse patch by mirage +{ my $mouse_status = -1; # -1:off 0,1,2:filling mouse_combo + my @mouse_combo; # 0:button 1:x 2:y + my @mouse_previous; # previous contents of mouse_combo + + sub mouse_xterm_off { + $mouse_status = -1; + } + sub mouse_xterm { + $mouse_status = 0; + Irssi::timeout_add_once(10, 'mouse_xterm_off', undef); + } + + sub mouse_key_hook { + my ($key) = @_; + if ($mouse_status != -1) { + if ($mouse_status == 0) { + @mouse_previous = @mouse_combo; + #if @mouse_combo && $mouse_combo[0] < 64; + } + $mouse_combo[$mouse_status] = $key - 32; + $mouse_status++; + if ($mouse_status == 3) { + $mouse_status = -1; + # match screen coordinates + $mouse_combo[1]--; + $mouse_combo[2]--; + Irssi::signal_emit('gui mouse', @mouse_combo[0 .. 2], @mouse_previous[0 .. 2]); + } + Irssi::signal_stop; + } + } +} + +sub string_LCSS { + my $str = join "\0", @_; + (sort { length $b <=> length $a } $str =~ /(?=(.+).*\0.*\1)/g)[0] +} + +# workaround for issue #271 +{ package Irssi::Nick } + +# workaround for issue #572 +@Irssi::UI::Exec::ISA = 'Irssi::Windowitem' + if Irssi::version >= 20140822 && Irssi::version <= 20161101 && !@Irssi::UI::Exec::ISA; + +UNITCHECK +{ package AwlViewer; + use strict; + use warnings; + no warnings 'redefine'; + use Encode; + use IO::Socket::UNIX; + use IO::Select; + use List::Util qw(max); + use constant BLOCK_SIZE => 1024; + use constant RECONNECT_TIME => 5; + + my $sockpath; + + our $VERSION = '0.8'; + + our ($got_int, $resized, $timeout); + + my %vars; + my (%c2w, @seqlist); + my %mouse_coords; + my (@mouse, @last_mouse); + my ($err, $sock, $loop); + my ($keybuf, $rcvbuf); + my @screen; + my ($screenHeight, $screenWidth); + my ($disp_update, $fs_open, $one_shot_integration, $one_shot_resize); + my $integration_position; + my $show_title_bar; + + sub connect_it { + $sock = IO::Socket::UNIX->new( + Type => SOCK_STREAM, + Peer => $sockpath, + ); + unless ($sock) { + $err = $!; + return; + } + $sock->blocking(0); + $loop->add($sock); + } + + sub remove_conn { + my $fh = shift; + $loop->remove($fh); + $fh->close; + $sock = undef; + %vars = (); + @screen = (); + } + + { package Terminfo; # xterm + sub civis { "\e[?25l" } + sub sc { "\e7" } + sub cup { "\e[" . ($_[0] + 1) . ';' . ($_[1] + 1) . 'H' } + sub el { "\e[K" } + sub rc { "\e8" } + sub cnorm { "\e[?25h" } + sub setab { "\e[4" . $_[0] . 'm' } + sub setaf { "\e[3" . $_[0] . 'm' } + sub setaf16 { "\e[9" . $_[0] . 'm' } + sub setab16 { "\e[10" . $_[0] . 'm' } + sub setaf256 { "\e[38;5;" . $_[0] . 'm' } + sub setab256 { "\e[48;5;" . $_[0] . 'm' } + sub setafrgb { "\e[38;2;" . $_[0] . ';' . $_[1] . ';' . $_[2] . 'm' } + sub setabrgb { "\e[48;2;" . $_[0] . ';' . $_[1] . ';' . $_[2] . 'm' } + sub sgr0 { "\e[0m" } + sub bold { "\e[1m" } + sub it { "\e[3m" } + sub ul { "\e[4m" } + sub blink { "\e[5m" } + sub rev { "\e[7m" } + sub op { "\e[39;49m" } + sub exit_bold { "\e[22m" } + sub exit_it { "\e[23m" } + sub exit_ul { "\e[24m" } + sub exit_blink { "\e[25m" } + sub exit_rev { "\e[27m" } + sub smcup { "\e[?1049h" } + sub rmcup { "\e[?1049l" } + sub smmouse { "\e[?1000h\e[?1005h" } + sub rmmouse { "\e[?1005l\e[?1000l" } + } + + sub init { + $sockpath = shift // "$ENV{HOME}/.irssi/_windowlist"; + STDOUT->autoflush(1); + printf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}; + + `stty -icanon -echo`; + + $loop = IO::Select->new; + STDIN->blocking(0); + $loop->add(\*STDIN); + + $SIG{INT} = sub { + $got_int = 1 + }; + $SIG{WINCH} = sub { + $resized = 1 + }; + + $resized = 3; + + $disp_update = 2; + + $show_title_bar = 1; + } + + sub enter_fs { + return if $fs_open; + safe_print(Terminfo::rc, Terminfo::smcup, Terminfo::civis, Terminfo::smmouse); + $fs_open = 1; + } + + sub leave_fs { + return unless $fs_open; + safe_print(Terminfo::rmmouse, Terminfo::cnorm, Terminfo::rmcup); + safe_print(sprintf "\r%swaiting for %s...", Terminfo::sc, $::IRSSI{name}) if $_[0]; + + $fs_open = 0; + } + + sub end_prog { + leave_fs(); + STDIN->blocking(1); + `stty sane`; + printf "\r%s%sthanks for using %s\n", Terminfo::rc, Terminfo::el, $::IRSSI{name}; + } + + sub safe_print { + my $st = STDIN->blocking(1); + print @_; + STDIN->blocking($st); + } + + sub safe_qx { + my $st = STDIN->blocking(1); + my $ret = `$_[0]`; + STDIN->blocking($st); + $ret + } + + sub safe_print_sock { + return unless $sock; + my $was = $sock->blocking(1); + $sock->print(@_); + $sock->blocking($was); + } + + sub process_recv { + my $need = 0; + while ($rcvbuf =~ s/\n(.+)_BEGIN\n((?: .*\n)*)\1_END\n//) { + my $var = lc $1; + my $data = $2; + my @data = split "\n ", "\n$data ", -1; + shift @data; pop @data; + my $itembg = $vars{itembg}; + if ($var =~ s/list$//) { + $vars{$var} = \@data; + } + elsif ($var =~ s/map$//) { + $vars{$var} = +{ @data }; + } + else { + $vars{$var} = join "\n", @data; + } + $need = 1 if $var eq 'win'; + $need = 1 if $var eq 'redraw' && $vars{$var}; + if (($itembg//'') ne ($vars{itembg}//'')) { + $need = $vars{redraw} = 1; + } + _build_keymap() if $var eq 'key2'; + } + $need + } + + { my %ansi_table; + my ($i, $j, $k) = (0, 0, 0); + my %term_state; + sub reset_term_state { my %old_term = %term_state; %term_state = (); %old_term } + sub set_term_state { my %old_term = %term_state; %term_state = @_; %old_term } + %ansi_table = ( + # fe-common::core::formats.c:format_expand_styles + (map { my $t = $i++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setab16 : \&Terminfo::setab; + $n->($t) }) } (split //, '01234567' )), + (map { my $t = $j++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf16 : \&Terminfo::setaf; + $n->($t) }) } (split //, 'krgybmcw' )), + (map { my $t = $k++; ($_ => sub { my $n = $term_state{hicolor} ? \&Terminfo::setaf : \&Terminfo::setaf16; + $n->($t) }) } (split //, 'KRGYBMCW')), + # reset + n => sub { $term_state{hicolor} = 0; my $r = Terminfo::op; + for (qw(blink rev bold)) { + $r .= Terminfo->can("exit_$_")->() if delete $term_state{$_}; + } + { + local $ansi_table{n} = $ansi_table{N}; + $r .= formats_to_ansi_basic($vars{itembg}); + } + $r + }, + N => sub { reset_term_state(); Terminfo::sgr0 }, + # flash/bright + F => sub { my $n = 'blink'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # reverse + 8 => sub { my $n = 'rev'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # bold + "_" => sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # underline + U => sub { my $n = 'ul'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # italic + I => sub { my $n = 'it'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # bold, used as colour modifier if AWL_HI9 is set + 9 => $ENV{AWL_HI9} ? sub { $term_state{hicolor} ^= 1; '' } + : sub { my $n = 'bold'; my $e = ($term_state{$n} ^= 1) ? $n : "exit_$n"; Terminfo->can($e)->() }, + # delete other stuff + (map { $_ => sub { '' } } (split //, ':|>#[')), + # escape + (map { my $close = $_; $_ => sub { $close } } (split //, '{}%')), + ); + for my $base (0 .. 15) { + my $close = $base; + my $idx = ($close&8) | ($close&4)>>2 | ($close&2) | ($close&1)<<2; + $ansi_table{ (sprintf "x0%x", $close) } = + $ansi_table{ (sprintf "x0%X", $close) } = + sub { Terminfo::setab256($idx) }; + $ansi_table{ (sprintf "X0%x", $close) } = + $ansi_table{ (sprintf "X0%X", $close) } = + sub { Terminfo::setaf256($idx) }; + } + for my $plane (1 .. 6) { + for my $coord (0 .. 35) { + my $close = 16 + ($plane-1) * 36 + $coord; + my $ch = $coord < 10 ? $coord : chr( $coord - 10 + ord 'a' ); + $ansi_table{ "x$plane$ch" } = + $ansi_table{ "x$plane\U$ch" } = + sub { Terminfo::setab256($close) }; + $ansi_table{ "X$plane$ch" } = + $ansi_table{ "X$plane\U$ch" } = + sub { Terminfo::setaf256($close) }; + } + } + for my $gray (0 .. 23) { + my $close = 232 + $gray; + my $ch = chr( $gray + ord 'a' ); + $ansi_table{ "x7$ch" } = + $ansi_table{ "x7\U$ch" } = + sub { Terminfo::setab256($close) }; + $ansi_table{ "X7$ch" } = + $ansi_table{ "X7\U$ch" } = + sub { Terminfo::setaf256($close) }; + } + # fe-windows.c:color_24bit_256 + my $cc = sub { + use integer; + + my $cstep_size = 40; + my $cstep_start = 0x5f; + + my $gstep_size = 10; + my $gstep_start = 0x08; + + my @dist = (0) x 3; + my @r; my @gr; + + for (my $i = 0; $i < 3; ++$i) { + my $n = $_[$i]; + $gr[$i] = -1; + if ($n < $cstep_start /2) { + $r[$i] = 0; + $dist[$i] = -$cstep_size/2; + } + else { + $r[$i] = 1+(($n-$cstep_start + $cstep_size /2)/$cstep_size); + $dist[$i] = (($n-$cstep_start + $cstep_size /2)% $cstep_size - $cstep_size/2); + } + if ($n < $gstep_start /2) { + $gr[$i] = -1; + } + else { + $gr[$i] = (($n-$gstep_start + $gstep_size /2)/$gstep_size); + } + } + if ($r[0] == $r[1] && $r[1] == $r[2] && + 4*abs($dist[0]) < $gstep_size && 4*abs($dist[1]) < $gstep_size && 4*abs($dist[2]) < $gstep_size) { + # skip gray detection + } + else { + my $j = $r[1] == $r[2] ? 0 : 1; + if (($r[0] == $r[1] || $r[$j] == $r[2]) && abs($r[$j]-$r[($j+1)% 3]) <= 1) { + my $k = $gr[1] == $gr[2] ? 0 : 1; + if (($gr[0] == $gr[1] || $gr[$k] == $gr[2]) && abs($gr[$k]-$gr[($k+1)% 3]) <= 2) { + if ($gr[$k] < 0) { + $r[0] = $r[1] = $r[2] = 0; + } + elsif ($gr[$k] > 23) { + $r[0] = $r[1] = $r[2] = 5; + } + else { + $r[0] = 6; + $r[1] = ($gr[$k] / 6); + $r[2] = $gr[$k]% 6; + } + } + } + } + return 16 + $r[0]*36 + $r[1] * 6 + $r[2]; + }; + $ansi_table{z} = sub { + my ($r, $g, $b) = map { hex } unpack '(A2)*', $_[0]; + $vars{tc} eq 'ON' ? Terminfo::setabrgb($r, $g, $b) : Terminfo::setab256($cc->($r, $g, $b)); + }; + $ansi_table{Z} = sub { + my ($r, $g, $b) = map { hex } unpack '(A2)*', $_[0]; + $vars{tc} eq 'ON' ? Terminfo::setafrgb($r, $g, $b) : Terminfo::setaf256($cc->($r, $g, $b)); + }; + sub formats_to_ansi_basic { + my $o = shift; + $o =~ s{(%((Z|z)(......)|X..|x..|.))}{ + if ($ansi_table{$2}) { $ansi_table{$2}->() } + elsif ($ansi_table{$3}) { $ansi_table{$3}->($4) } + else { $1 } + }gex; + $o + } + } + + sub _header { + my $str = $vars{title} // uc ::setc(); + my $ccs = qr/%(?:Z(?:[0-9A-F]{6})|X(?:[1-6][0-9A-Z]|7[A-X])|[0-9BCFGIKMNRUWY_])/i; + (my $stripstr = $str) =~ s/($ccs)//g; + my $space = int( ((abs $vars{block}) - length $stripstr) / (1 + length $stripstr)); + if ($space > 0) { + my $ss = ' ' x $space; + my @x = $str =~ /((?:$ccs)*\X(?:(?:$ccs)*$)?)/g; + $str = join $ss, '', @x, ''; + } + ($stripstr = $str) =~ s/($ccs)//g; + my $pad = max 0, (abs $vars{block}) - length $stripstr; + $str = ' ' x ($pad/2) . $str . ' ' x ($pad/2 + $pad%2); + $str + } + + sub _add_item { + my ($i, $j, $c, $wi, $screen, $mouse) = @_; + $screen->[$i][$j] = "%N%n$wi"; + if (exists $vars{mouse}{$c - 1}) { + $mouse->[$i][$j] = $vars{mouse}{$c - 1}; + } + } + sub update_screen { + $disp_update = 0; + unless ($sock && exists $vars{seplen} && exists $vars{block}) { + leave_fs(1); + return; + } + enter_fs(); + @screen = () if delete $vars{redraw}; + %mouse_coords = (); + my $ncols = ($vars{seplen} + abs $vars{block}) ? + int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $xenl = ($vars{seplen} + abs $vars{block}) + && $ncols > int( ($screenWidth + $vars{seplen} - 1) / ($vars{seplen} + abs $vars{block}) ); + my $nrows = $screenHeight - $vars{ha}; + my @wi = @{$vars{win}//[]}; + my $max_items = $ncols * $nrows; + my $c = $show_title_bar ? 1 : 0; + my $items = @wi + $c; + my $titems = $items > $max_items ? $max_items : $items; + my $i = 0; + my $j = 0; + my @new_screen; + my @new_mouse; + $new_screen[0][0] = _header() #. ' ' x $vars{seplen} + if $show_title_bar; + unless ($nrows > $ncols) { # line layout + ++$j if $show_title_bar; + for my $wi (@wi) { + if ($j >= $ncols) { + $j = 0; + ++$i; + } + last if $i >= $nrows; + _add_item($i, $j, $show_title_bar ? $c : $c + 1, + $wi, \@new_screen, \@new_mouse); + if ($c + 1 < $titems && $j + 1 < $ncols) { + $new_screen[$i][$j] .= $vars{separator}; + } + ++$j; + ++$c; + } + } + else { # column layout + ++$i if $show_title_bar; + for my $wi (@wi) { + if ($i >= $nrows) { + $i = 0; + ++$j; + } + last if $j >= $ncols; + _add_item($i, $j, $show_title_bar ? $c : $c + 1, + $wi, \@new_screen, \@new_mouse); + if ($c + $nrows < $titems) { + $new_screen[$i][$j] .= $vars{separator}; + } + ++$i; + ++$c; + } + } + my $step = $vars{seplen} + abs $vars{block}; + $i = 0; + my $str = Terminfo::sc . Terminfo::sgr0; + for (my $i = 0; $i < @new_screen; ++$i) { + for (my $j = 0; $j < @{$new_screen[$i]}; ++$j) { + if (defined $new_mouse[$i] && defined $new_mouse[$i][$j]) { + my $from = $j * $step; + $mouse_coords{$i}{$_} = $new_mouse[$i][$j] + for $from .. $from + abs $vars{block}; + } + next if defined $screen[$i] && defined $screen[$i][$j] + && $screen[$i][$j] eq $new_screen[$i][$j]; + $str .= Terminfo::cup($i, $j * $step) + . formats_to_ansi_basic($new_screen[$i][$j]) + . Terminfo::sgr0; + $str .= Terminfo::el if $j == $#{$new_screen[$i]} && (!$xenl || $j + 1 != $ncols); + } + } + for (@new_screen .. $screenHeight - 1) { + if (!@screen || defined $screen[$_]) { + $str .= Terminfo::cup($_, 0) . Terminfo::sgr0 . Terminfo::el; + } + } + $str .= Terminfo::rc; + safe_print $str; + @screen = @new_screen; + } + + sub handle_resize { + if (defined (my $r = safe_qx('stty size'))) { + ($screenHeight, $screenWidth) = split ' ', $r; + $resized = 0; + @screen = (); + $disp_update = 1; + if ($one_shot_integration == 2) { + $one_shot_resize--; + } + } + else { + } + } + + sub _build_keymap { + %c2w = reverse( %{$vars{key}}, %{$vars{key2}} ); + if (!grep { /^[+-]./ } keys %c2w) { + %c2w = (%c2w, map { ("-$_" => $c2w{$_}) } grep { !/^\^./ } keys %c2w); + } + %c2w = map { + my $key = $_; + s{^(-)?(\+)?(\^)?(.)}{ + join '', ( + ($1 ? "\e" : ''), + ($2 ? "\e\e" : ''), + ($3 ? "$4"^"@" : $4) + ) + }e; + $_ => $c2w{$key} + } keys %c2w; + @seqlist = sort { length $b <=> length $a } keys %c2w; + } + + sub _match_tmux { + length $ENV{TMUX} && exists $vars{irssienv}{tmux_srv} && length $vars{irssienv}{tmux_pane} + && $ENV{TMUX} eq $vars{irssienv}{tmux_srv} + } + + sub process_keys { + Encode::_utf8_on($keybuf); + my $win; + my $use_mouse; + my $maybe; + KEY: while (length $keybuf && !$maybe) { + $maybe = 0; + if ($keybuf =~ s/^\e\[M(.)(.)(.)//) { + @last_mouse = @mouse;# if @mouse && $mouse[0] < 64; + @mouse = map { -32 + ord } ($1, $2, $3); + $use_mouse = 1; + next KEY; + } + for my $s (@seqlist) { + if ($keybuf =~ s/^\Q$s//) { + $win = $c2w{$s}; + $use_mouse = 0; + next KEY; + } + elsif (length $keybuf < length $s && $s =~ /^\Q$keybuf/) { + $maybe = 1; + } + } + unless ($maybe) { + substr $keybuf, 0, 1, ''; + } + } + if ($use_mouse && @mouse && @last_mouse && + $mouse[2] == $last_mouse[2] && + $mouse[1] == $last_mouse[1] && + ($mouse[0] == 3 || $mouse[0] == 64 || $mouse[0] == 65)) { + if ($mouse[0] == 64) { + $win = 'up'; + } + elsif ($mouse[0] == 65) { + $win = 'down'; + } + elsif (exists $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}) { + $win = $mouse_coords{$mouse[2] - 1}{$mouse[1] - 1}; + } + elsif ($mouse[2] == 1 && $mouse[1] <= abs $vars{block}) { + $win = $last_mouse[0] != 0 ? 'last' : 'active'; + } + else { + } + } + if (defined $win) { + $win =~ s/^_//; + safe_print_sock("$win\n"); + if (!exists $ENV{AWL_AUTOFOCUS} || $ENV{AWL_AUTOFOCUS}) { + if (_match_tmux()) { + safe_qx("tmux selectp -t $vars{irssienv}{tmux_pane} 2>&1"); + } + elsif (exists $vars{irssienv}{xwinid}) { + safe_qx("wmctrl -ia $vars{irssienv}{xwinid} 2>/dev/null"); + } + } + } + Encode::_utf8_off($keybuf); + } + + sub check_integration { + return unless $vars{irssienv}; + return unless $sock && exists $vars{seplen} && exists $vars{block}; + if ($one_shot_integration == 1) { + my $nrows = $screenHeight - $vars{ha}; + my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]}; + my $dcols_required = $nrows ? int($items/$nrows) + !!($items%$nrows) : 0; + my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0; + $rows_required = abs $vars{ml} + if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml})); + $dcols_required = abs $vars{mc} + if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc})); + my $rows = $rows_required + $vars{ha}; + my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen}; + if (_match_tmux()) { + # int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ); + my ($pos_flag, $before); + if ($integration_position eq 'left') { + $pos_flag = 'h'; + $before = 1; + } + elsif ($integration_position eq 'top') { + $pos_flag = 'v'; + $before = 1; + } + elsif ($integration_position eq 'right') { + $pos_flag = 'h'; + } + else { + $pos_flag = 'v'; + } + my @cmd = "joinp -d$pos_flag -s $ENV{TMUX_PANE} -t $vars{irssienv}{tmux_pane}"; + push @cmd, "swapp -d -t $ENV{TMUX_PANE} -s $vars{irssienv}{tmux_pane}" + if $before; + $cols = max($cols, 2); + $rows = max($rows, 2); + + safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1"); + } + else { + $resized = 1; + #safe_qx("resize -s $screenHeight $cols 2>&1") + # if $cols > 0; + } + $one_shot_integration++; + if ($resized == 1) { + handle_resize(); + resize_integration(); + } + } + elsif ($one_shot_integration == 2) { + resize_integration(1); + } + } + + sub resize_integration { + return unless $one_shot_integration; + return unless ($one_shot_resize//0) < 0 || shift; + return if ($one_shot_resize//0) > 0; + + my $nrows = $screenHeight - $vars{ha}; + my $ncols = ($vars{seplen} + abs $vars{block}) ? int( ($screenWidth + $vars{seplen}) / ($vars{seplen} + abs $vars{block}) ) : 0; + my $items = ($show_title_bar ? 1 : 0) + @{$vars{win}//[]}; + my $dcols_required = $nrows ? (int($items/$nrows) + !!($items%$nrows)) : 0; + my $rows_required = $ncols ? int($items/$ncols) + !!($items%$ncols) : 0; + $rows_required = abs $vars{ml} + if ($vars{ml} < 0 || ($vars{ml} > 0 && $rows_required > $vars{ml})); + $dcols_required = abs $vars{mc} + if ($vars{mc} < 0 || ($vars{mc} > 0 && $dcols_required > $vars{mc})); + my $rows = $rows_required + $vars{ha}; + my $cols = ($dcols_required * ($vars{seplen} + abs $vars{block})) - $vars{seplen}; + if (_match_tmux()) { + my $pos_flag; + my $before = 0; + if ($integration_position eq 'left') { + $pos_flag = 'h'; + $before = 1; + } + elsif ($integration_position eq 'top') { + $pos_flag = 'v'; + $before = 1; + } + elsif ($integration_position eq 'right') { + $pos_flag = 'h'; + } + else { + $pos_flag = 'v'; + } + my @cmd; + # hard tmux limits + $cols = max($cols, 2); + $rows = max($rows, 2); + if ($pos_flag eq 'h' && $cols != $screenWidth) { + my $change = $screenWidth - $cols; + my $dir = ($before ^ ($change<0)) ? 'L' : 'R'; + push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}"; + #push @cmd, "resizep -x $cols -t $ENV{TMUX_PANE}"; + $one_shot_resize = 1; + } + if ($pos_flag eq 'v' && $rows != $screenHeight) { + #push @cmd, "resizep -y $rows -t $ENV{TMUX_PANE}"; + my $change = $screenHeight - $rows; + my $dir = ($before ^ ($change<0)) ? 'U' : 'D'; + push @cmd, "resizep -$dir -t $ENV{TMUX_PANE} @{[abs $change]}"; + $one_shot_resize = 1; + } + + safe_qx("tmux " . (join " \\\; ", @cmd) . " 2>&1") + if @cmd; + } + else { + $cols = max($cols, 1); + $rows = max($rows, 1); + unless ($nrows > $ncols) { # line layout + if ($rows != $screenHeight) { + safe_qx("resize -s $rows $screenWidth 2>&1"); + $one_shot_resize = 1; + } + } + else { + if ($cols != $screenWidth) { + safe_qx("resize -s $screenHeight $cols 2>&1"); + $one_shot_resize = 1; + } + } + } + if ($resized == 1) { + handle_resize(); + } + } + + sub init_integration { + return unless $one_shot_integration; + if (_match_tmux()) { + } + else { + } + safe_print("\e]2;".(uc ::setc())."\e\\"); + } + + sub main { + require Getopt::Std; + my %opts; + Getopt::Std::getopts('1p:', \%opts); + my $one_shot = $opts{1}; + $integration_position = $opts{p}; + $one_shot_integration = 0+!!$one_shot; + #shift if @_ && $_[0] eq '--'; + &init; + $show_title_bar = 0; + init_integration(); + until ($got_int) { + $timeout = undef; + if ($resized) { + if ($resized == 1) { + $timeout = 1; + $resized++; + } + else { + handle_resize(); + resize_integration(); + } + } + unless ($sock || $timeout) { + connect_it(); + } + $timeout ||= RECONNECT_TIME unless $sock; + update_screen() if $disp_update; + SELECT: while (my @read = $loop->can_read($timeout)) { + for my $fh (@read) { + if ($fh == \*STDIN) { + if (read STDIN, my $buf, BLOCK_SIZE) { + do { + $keybuf .= $buf; + } while read STDIN, $buf, BLOCK_SIZE; + } + else { + $got_int = 1; + last SELECT; + } + } + else { + if ($fh->read(my $buf, BLOCK_SIZE)) { + do { + $rcvbuf .= $buf; + } while $fh->read($buf, BLOCK_SIZE); + } + else { + $disp_update = 1; + remove_conn($fh); + if ($one_shot) { + $got_int = 1; + last SELECT; + } + $timeout ||= RECONNECT_TIME; + } + } + } + $disp_update |= process_recv() if length $rcvbuf; + process_keys() if length $keybuf; + check_integration() if $one_shot; + update_screen() if $disp_update; + } + continue { + } + } + end_prog(); + } +} + +1; + +# Changelog +# ========= +# 1.9 +# - add %Z support to viewer +# +# 1.8 +# - use string_width in Irssi 1.2.0 +# +# 1.7 +# - fix crash on invalid /set awl_sort, introduced in 1.6, reported by +# tpetazzoni +# - delay viewer initialisation +# - improve race condition on tmux resize integration +# +# 1.6 +# - add detach setting to hide windows +# - fix race condition when loading the script, reported by madduck +# - improve compatibility with irssi 1.2 +# - add special value lru to awl_sort to sort windows by usage +# +# 1.5 +# - improve compat. with sideways splits +# +# 1.4 +# - fix line wrapping in some themes, reported by justanotherbody +# - fix named window key detection, reported by madduck +# - make title (in viewer and shared_sbar) configurable +# +# 1.3 +# - workaround for irssi issue #572 +# +# 1.2 +# - new format to choose abbreviation character +# +# 1.1 +# - infinite loop on shortening certain window names reported by Kalan +# +# 1.0 +# - new awl_viewer_launch setting and an array of related settings +# - fixed regression bug /exec -interactive +# - fixed some warnings in perl 5.10 reported by kl3 +# - workaround for crash due to infinite recursion in irssi's Perl +# error handling +# +# 0.9 +# - fix endless loop in awin detection code! +# - correct colour swap in awl_viewer +# - fix passing of alternate socket path to the viewer +# - potential undefinedness in mouse refnum hinted at by Canopus +# - fixed regression bug /exec -interactive +# - add case-insensitive modifier to awl_sort +# - run custom_xform on awl_prefer_name also +# - avoid inconsistent active window state after awin detection +# reported by ss +# - revert %9-hack in the viewer prompted by discussion with pierrot +# - fix new warning in perl 5.22 +# +# 0.8 +# - replace fifo mode with external viewer script +# - remove bundled cpan modules +# - work around bogus irssi warning +# - improve mouse support +# - workaround for broken cumode in irssi 0.8.15 +# - fix handling of non-meta windows (uninitialized warning) +# - add 256 colour support, strip true colour codes +# - fix totally bogus $N padding reported by Ed S. +# - make /window goto #name mappings work but ignore non-existant ones +# - improve incomplete reads reported by bcode +# - fix single % in awl_viewer reported by bcode +# - add support for key bindings by nike and ferret +# - coerce utf8 key binds +# - add settings: custom_xform, last_line_shade, hide_name_data +# - abbreviations were broken in some cases +# - fix some misuse of / as cmdchar in mouse script reported by bcode +# - add shared status bar mode +# - ${type} variables for custom_xform setting +# - crash if custom_xform had runtime error +# - update sorting documentation +# - fix odd case in size calculation noted by lasers +# - add missing font styles to the viewer reported by ishanyx +# - add italic +# +# 0.7g +# - remove screen support and replace it with fifo support +# - add double-width support to the shortener +# - correct documentation regarding $T vs. display_header +# - add missing refresh for window item changed (thanks vague) +# - add visible windows +# - add exemptions for active window +# - workaround for hiding the window changes from trackbar +# - hack to force 16colours in screen mode +# - remember last window (reported by earthnative) +# - wrong window focus on new queries (reported by emsid) +# - dataloss bug on trying to remember last window +# +# 0.6d+ +# - add support for network headers +# - fixed regression bug /exec -interactive +# +# 0.6ca+ +# - add screen support (from nicklist.pl) +# - names can now have a max length and window names can be used +# - fixed a bug with block display in screen mode and status bar mode +# - added space handling to ir_fe and removed it again +# - now handling formats on my own +# - started to work on $tag display +# - added warning about missing sb_act_none abstract leading to +# - display*active settings +# - added warning about the bug in awl_display_(no)key_active settings +# - mouse hack +# +# 0.5d +# - add setting to also hide the last status bar if empty (awl_all_disable) +# - reverted to old utf8 code to also calculate broken utf8 length correctly +# - simplified dealing with status bars in wlreset +# - added a little tweak for the renamed term_type somewhere after Irssi 0.8.9 +# - fixed bug in handling channel #$$ +# - reset background colour at the beginning of an entry +# +# 0.4d +# - fixed order of disabling status bars +# - several attempts at special chars, without any real success +# and much more weird new bugs caused by this +# - setting to specify sort order +# - reduced timeout values +# - added awl_hide_data +# - make it so the dynamic sub is actually deleted +# - fix a bug with removing of the last separator +# - take into consideration parse_special +# +# 0.3b +# - automatically kill old status bars +# - reset on /reload +# - position/placement settings +# +# 0.2 +# - automated retrieval of key bindings (thanks grep.pl authors) +# - improved removing of status bars +# - got rid of status chop +# +# 0.1 +# - Based on chanact.pl which was apparently based on lightbar.c and +# nicklist.pl with various other ideas from random scripts. diff --git a/.config/irssi/scripts/autorun/autorun/README b/.config/irssi/scripts/autorun/autorun/README @@ -0,0 +1,21 @@ + +ascii.pl: + /ASCII [-c1234] [-f <fontname>] [-p <prefix>] [-l|-s|-m <where>] <text> + /COLSAY [-1234] [-m <where>] <text> + /COLME [-1234] <text> + /COLTOPIC [-1234] <text> + /COLKICK [-1234] [nick(,nick_1,...,nick_n)] <reason> + /COLQUIT [-1234] <reason> + /SET ascii_figlet_path [path] + +auto_whois: + all is handled by itself + +nickcolor.pl: + all is handled by itself + +url_hilight.pl: + all is handled by itself + +usercount.pl: + all is handled by itself diff --git a/.config/irssi/scripts/autorun/autorun/ascii.pl b/.config/irssi/scripts/autorun/autorun/ascii.pl @@ -0,0 +1,405 @@ +# +# Commands: /ASCII, /COLSAY, /COLME, /COLTOPIC, /COLKICK, /COLQUIT +# Usage: +# /ASCII [-c1234] [-f <fontname>] [-p <prefix>] [-l|-s|-m <where>] <text> +# /COLSAY [-1234] [-m <where>] <text> +# /COLME [-1234] <text> +# /COLTOPIC [-1234] <text> +# /COLKICK [-1234] [nick(,nick_1,...,nick_n)] <reason> +# /COLQUIT [-1234] <reason> +# Settings: +# /SET ascii_figlet_path [path] +# /SET ascii_default_font [fontname] +# /SET ascii_default_colormode [1-4] +# /SET ascii_default_prefix [prefix] +# /SET ascii_default_kickreason [reason] +# /SET ascii_default_quitreason [reason] +# +# Script is bassed on figlet. +# + +use strict; +use Irssi; +use Irssi::Irc; + +use vars qw($VERSION %IRSSI); + +$VERSION = "1.6.3"; +%IRSSI = ( + "authors" => "Marcin Rozycki", + "contact" => "derwan\@irssi.pl", + "name" => "ascii-art", + "description" => "Ascii-art bassed on figlet. Available commands: /ASCII, /COLSAY, /COLME, /COLTOPIC, /COLKICK, /COLQUIT.", + "url" => "http://derwan.irssi.pl", + "license" => "GNU GPL v2", + "changed" => "Fri Jun 21 17:17:53 CEST 2002" +); + +use IPC::Open3; + +# defaults +my $ascii_default_font = "small.flf"; +my $ascii_default_kickreason = "Irssi BaBy!"; +my $ascii_default_quitreason = "I Quit!"; +my $ascii_last_color = undef; +my @ascii_colors = (12, 12, 12, 9, 5, 4, 13, 8, 7, 3, 11, 10, 2, 6, 6, 6, 6, 10, 8, 7, 4, 3, 9, 11, 2, 12, 13, 5); + +# registering themes +Irssi::theme_register([ + 'ascii_not_connected', '%_$0:%_ You\'re not connected to server', + 'ascii_not_window', '%_$0:%_ Not joined to any channel or query window', + 'ascii_not_chanwindow', '%_$0:%_ Not joined to any channel', + 'ascii_not_chanop', '%_$0:%_ You\'re not channel operator in {hilight $1}', + 'ascii_figlet_notfound', '%_Ascii:%_ Cannot execute {hilight $0} - file not found or bad permissions', + 'ascii_figlet_notset', '%_Ascii:%_ Cannot find external program %_figlet%_, usign /SET ascii_figlet_path [path], to set it', + 'ascii_cmd_syntax', '%_$0:%_ $1, usage: $2', + 'ascii_figlet_error', '%_Ascii: Figlet returns error:%_ $0-', + 'ascii_fontlist', '%_Ascii:%_ Available fonts [in $0]: $1 ($2)', + 'ascii_empty_fontlist', '%_Ascii:%_ Cannot find figlet fonts in $0', + 'ascii_unknown_fontdir', '%_Ascii:%_ Cannot find figlet fontdir', + 'ascii_show_line', '$0-' + +]); + +# str find_figlet_path() +sub find_figlet_path { + foreach my $dir (split(/\:/, $ENV{'PATH'})) + { + return "$dir/figlet" if ($dir and -x "$dir/figlet"); + } +} + +# int randcolor() +sub randcolor { + return $ascii_colors[int(rand(12)+2)]; +} + +# str colorline($colormode, $text) +sub colorline { + my ($colormode, $text) = @_; + my $colortext = undef; + my $last = ($ascii_last_color) ? $ascii_last_color : randcolor(); + my $indx = $last; + + if ($colormode =~ /3/) { + $ascii_last_color = randcolor(); + }elsif ($colormode =~ /4/) { + $ascii_last_color = $ascii_colors[$last]; + }elsif ($colormode !~ /2/) { + $ascii_last_color = $ascii_colors[14+$last]; + } + + while ($text =~ /./g) + { + my $char = "$&"; + + if ($colormode =~ /3/) { + while ($indx == $last) { $indx = randcolor(); }; + $last = $indx; + }elsif ($colormode =~ /4/) { + $indx = $ascii_colors[$indx]; + }elsif ($last) { + $indx = $ascii_colors[$last]; + undef $last; + } else { + $indx = $ascii_colors[$indx]; + $last = $indx + 14; + }; + + $colortext .= $char, next if ($char eq " "); + $colortext .= "\003" . sprintf("%02d", $indx) . $char; + $colortext .= $char if ($char eq ","); + }; + + return $colortext; +}; + +# int colormode() +sub colormode { + my $mode = Irssi::settings_get_int("ascii_default_colormode"); + $mode =~ s/-//g; + return (!$mode or $mode > 4) ? 1 : $mode; +}; + +# bool ascii_test($command, $flags, $server, $window) +sub ascii_test { + my ($cmd, $test, $server, $window) = @_; + + if ($test =~ /s/ and !$server || !$server->{connected}) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_not_connected", $cmd); + return 0; + }; + if ($test =~ /W/ and !$window || $window->{type} !~ /(channel|query)/i) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_not_window", $cmd); + return 0; + }; + if ($test =~ /(w|o)/ and !$window || $window->{type} !~ /channel/i) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_not_chanwindow", $cmd); + return 0; + }; + if ($test =~ /o/ and !$window->{chanop}) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_not_chanop", $cmd, Irssi::active_win()->get_active_name()); + return 0; + }; + + return 1; +} + +# void cmd_ascii() +# handles /ascii +sub cmd_ascii +{ + my $usage = "/ASCII [-c1234] [-f <fontname>] [-p <prefix>] [-l|-s|-m <where>] <text>"; + my $font = Irssi::settings_get_str("ascii_default_font"); + my $prefix = Irssi::settings_get_str("ascii_default_prefix"); + my ($arguments, $server, $witem) = @_; + my ($text, $cmd, $mode); + + $font = $ascii_default_font unless ($font); + $ascii_last_color = randcolor(); + + my $figlet = Irssi::settings_get_str("ascii_figlet_path"); + if (!$figlet or !(-x $figlet)) { + my $theme = ($figlet) ? "ascii_figlet_notfound" : "ascii_figlet_notset"; + Irssi::printformat(MSGLEVEL_CRAP, $theme, $figlet); + return; + }; + + my @foo = split(/ +/, $arguments); + while ($_ = shift(@foo)) + { + /^-l$/ and show_figlet_fonts($figlet), return; + /^-c$/ and $mode = colormode(), next; + /^-(1|2|3|4)$/ and s/-//g, $mode = $_, next; + /^-f$/ and $font = shift(@foo), next; + /^-p$/ and $prefix = shift(@foo), next; + /^-m$/ and $cmd = shift(@foo), next; + /^-s$/ and $cmd = 0, next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Ascii", "Unknown argument: $_", $usage), return; + $text = ($#foo < 0) ? $_ : $_ . " " . join(" ", @foo); + last; + } + + unless (length($text)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Ascii", "Missing arguments", $usage); + return; + }; + + if ($cmd eq "") { + return unless (ascii_test("Ascii", "sW", $server, $witem)); + $cmd = Irssi::active_win()->get_active_name(); + } elsif ($cmd ne "0" and !ascii_test("Ascii", "s", $server, $witem)) { + return; + } + + my $pid = open3(*FIGIN, *FIGOUT, *FIGERR, $figlet, qw(-k -f), $font, $text); + + while (<FIGOUT>) + { + chomp; + next unless (/[^ ]/); + $_ = colorline($mode, $_) if ($mode); + Irssi::printformat(MSGLEVEL_CLIENTCRAP, "ascii_show_line", $prefix.$_), next if ($cmd eq "0"); + $server->command("msg $cmd $prefix$_"); + } + + while (<FIGERR>) + { + chomp; + Irssi::printformat(MSGLEVEL_CRAP, "ascii_figlet_error", $_); + }; + + close FIGIN; + close FIGOUT; + close FIGERR; + + waitpid $pid, 0; +} + +# void show_figlet_fonts(figlet path) +sub show_figlet_fonts { + my @fontlist; + if (my $fontdir = `"$_[0]" -I 2 2>/dev/null`) { + chomp $fontdir; + foreach my $font (glob $fontdir."/*.flf") + { + $font =~ s/^$fontdir\///; + $font =~ s/\.flf$//; + push @fontlist, $font; + } + if ($#fontlist < 0) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_fontlist_empty", $fontdir); + } else { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_fontlist", $fontdir, join(", ", @fontlist), scalar(@fontlist)); + } + } else { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_unknown_fontdir"); + } +} + +# void cmd_colsay() +# handles /colsay +sub cmd_colsay { + my $usage = "/COLSAY [-1234] [-m <where>] <text>"; + my ($arguments, $server, $witem) = @_; + my ($cmd, $text); + my $mode = colormode(); + + $ascii_last_color = randcolor(); + + my @foo = split(/ /, $arguments); + while ($_ = shift(@foo)) + { + /^-(1|2|3|4)$/ and $mode = $_, next; + /^-m$/i and $cmd = shift(@foo), next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colsay", "Unknown argument: $_", $usage), return; + $text = ($#foo < 0) ? $_ : $_ . " " . join(" ", @foo); + last; + }; + + unless (length($text)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colsay", "Missing arguments", $usage); + return; + }; + + if ($cmd) { + return unless (ascii_test("Colsay", "s", $server, $witem)); + } else { + return unless (ascii_test("Colsay", "sW", $server, $witem)); + $cmd = Irssi::active_win()->get_active_name(); + }; + + $server->command("msg $cmd ".colorline($mode, $text)); +} + + +sub cmd_colme { + my $usage = "/COLME [-1234] <text>"; + my ($arguments, $server, $witem) = @_; + my $mode = colormode(); + my $text; + + $ascii_last_color = randcolor(); + + my @foo = split(/ /, $arguments); + while ($_ = shift(@foo)) + { + /^-(1|2|3|4)$/ and $mode = $_, next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colme", "Unknown argument: $_", $usage), return; + $text = ($#foo < 0) ? $_ : $_ . " " . join(" ", @foo); + last; + }; + + unless (length($text)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colme", "Missing arguments", $usage); + return; + }; + + return unless (ascii_test("Colme", "sW", $server, $witem)); + $witem->command("me ".colorline($mode, $text)); +} + +# void cmd_coltopic() +# handles /coltopic +sub cmd_coltopic { + my $usage = "/COLTOPIC [-1234] <text>"; + my ($arguments, $server, $witem) = @_; + my $mode = colormode(); + my $text; + + $ascii_last_color = randcolor(); + + my @foo = split(/ /, $arguments); + while ($_ = shift(@foo)) + { + /^-(1|2|3|4)$/ and $mode = $_, next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Coltopic", "Unknown argument: $_", $usage), return; + $text = ($#foo < 0) ? $_ : $_ . " " . join(" ", @foo); + last; + }; + + unless (length($text)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Coltopic", "Missing arguments", $usage); + return; + }; + + return unless (ascii_test("Coltopic", "sw", $server, $witem)); + + $server->command("topic " . Irssi::active_win()->get_active_name() . " " . colorline($mode, $text)); +}; + +# void cmd_colkick() +# handles /colkick +sub cmd_colkick { + my $usage = "/COLKICK [-1234] [nick(,nick_1,...,nick_n)] <reason>"; + my ($arguments, $server, $witem) = @_; + my $kickreason = Irssi::settings_get_str("ascii_default_kickreason"); + my $mode = colormode(); + my $who = undef; + + $ascii_last_color = randcolor(); + $kickreason = $ascii_default_kickreason unless ($kickreason); + + my @foo = split(/ /, $arguments); + while ($_ = shift(@foo)) + { + /^-(1|2|3|4)$/ and $mode = $_, next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colkick", "Unknown argument: $_", $usage), return; + $kickreason = join(" ", @foo) if ($#foo >= 0); + $who = $_; + last; + }; + + if (!$who or !length($kickreason)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colkick", "Missing arguments", $usage); + return; + }; + + return unless (ascii_test("Colkick", "swo", $server, $witem)); + $witem->command("kick $who ".colorline($mode, $kickreason)); +}; + +# void cmd_colquit() +# handles /colquit +sub cmd_colquit { + my $usage = "/COLQUIT [-1234] <reason>"; + my ($arguments, $server, $witem) = @_; + my $quitreason = Irssi::settings_get_str("ascii_default_quitreason"); + my $mode = colormode(); + + $ascii_last_color = randcolor(); + $quitreason = $ascii_default_quitreason unless ($quitreason); + + my @foo = split(/ /, $arguments); + while ($_ = shift(@foo)) + { + /^-(1|2|3|4)$/ and $mode = $_, next; + /^-/ and Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colquit", "Unknown argument: $_", $usage), return; + $quitreason = ($#foo < 0) ? $_ : $_ . " " . join(" ", @foo); + last; + }; + + unless (length($quitreason)) { + Irssi::printformat(MSGLEVEL_CRAP, "ascii_cmd_syntax", "Colquit", "Missing arguments", $usage); + return; + }; + + return unless (ascii_test("Colquit", "s", $server, $witem)); + $server->command("quit " . colorline($mode, $quitreason)); +} + +# registering settings +Irssi::settings_add_str("misc", "ascii_default_font", $ascii_default_font); +Irssi::settings_add_str("misc", "ascii_default_kickreason", $ascii_default_kickreason); +Irssi::settings_add_str("misc", "ascii_default_quitreason", $ascii_default_quitreason); +Irssi::settings_add_str("misc", "ascii_default_prefix", ""); +Irssi::settings_add_int("misc", "ascii_default_colormode", 1); +Irssi::settings_add_str("misc", "ascii_figlet_path", find_figlet_path); + +# binding commands +Irssi::command_bind("ascii", "cmd_ascii"); +Irssi::command_bind("colsay", "cmd_colsay"); +Irssi::command_bind("colme", "cmd_colme"); +Irssi::command_bind("coltopic", "cmd_coltopic"); +Irssi::command_bind("colkick", "cmd_colkick"); +Irssi::command_bind("colquit", "cmd_colquit"); diff --git a/.config/irssi/scripts/autorun/autorun/auto_whois.pl b/.config/irssi/scripts/autorun/autorun/auto_whois.pl @@ -0,0 +1,80 @@ +# /WHOIS all the users who send you a private message. +# v0.9 for irssi by Andreas 'ads' Scherbaum +# idea and some code taken from autowhois.pl from Timo Sirainen +use strict; +use Irssi; +use vars qw($VERSION %IRSSI); + +$VERSION = "0.9"; +%IRSSI = ( + authors => "Andreas \'ads\' Scherbaum", + contact => "ads\@ufp.de", + name => "auto_whois", + description => "/WHOIS all the users who send you a private message.", + license => "GPL", + url => "http://irssi.org/", + changed => "2004-02-10", + changes => "v0.9: don't /WHOIS if query exists for the nick already" +); + +# History: +# v0.9: don't /WHOIS if query exists for the nick already +# now we store all nicks we have seen in the last 10 minutes + +my @seen = (); + +sub msg_private_first { + my ($server, $msg, $nick, $address) = @_; + + # go through every stored connection and remove, if timed out + my $time = time(); + my ($connection); + my @new = (); + foreach $connection (@seen) { + if ($connection->{lasttime} >= $time - 600) { + # is ok, use it + push(@new, $connection); + # all timed out connections will be dropped + } + } + @seen = @new; +} + +sub msg_private { + my ($server, $msg, $nick, $address) = @_; + + # look, if we already know this connection + my ($connection, $a); + my $known_to_us = 0; + for ($a = 0; $a <= $#seen; $a++) { + $connection = $seen[$a]; + # the lc() works not exact, because irc uses another charset + if ($connection->{server} eq $server->{address} and $connection->{port} eq $server->{port} and lc($connection->{nick}) eq lc($nick)) { + $known_to_us = 1; + # mark as refreshed + $seen[$a]->{lasttime} = time(); + last; + } + } + + if ($known_to_us == 1) { + # all ok, return + return; + } + + # now store the new connection + $connection = {}; + # store our own server data here + $connection->{server} = $server->{address}; + $connection->{port} = $server->{port}; + # and the nick who queried us + $connection->{nick} = $nick; + $connection->{lasttime} = time(); + $connection->{starttime} = time(); + push(@seen, $connection); + + $server->command("whois $nick"); +} + +Irssi::signal_add_first('message private', 'msg_private_first'); +Irssi::signal_add('message private', 'msg_private'); diff --git a/.config/irssi/scripts/autorun/autorun/irssi-alert.pl b/.config/irssi/scripts/autorun/autorun/irssi-alert.pl @@ -0,0 +1,35 @@ +# irssi-alert.pl +use Irssi; + +# config +my $own_nick = 'haydenh'; +my $channel = '#GNU/matrix'; + +$::VERSION='1'; +%::IRSSI = ( + authors => 'haydenh', + contact => 'haydenh@AT@sdf.DOT.org', + name => 'irssi-alert', + description => 'Send the \a escape code on a message containing a certain + string, in a private message, or a specified channel', + license => 'MIT', +); + +sub priv { + system("echo -n '\a'"); +} + +sub pub { + my ($server, $msg, $nick, $address, $target) = @_; + + if ($msg =~ $own_nick) { + system("echo -n '\a'"); + } else { + if ($target =~ $channel) { + system("echo -n '\a'"); + } + } +} + +Irssi::signal_add('message public', 'pub'); +Irssi::signal_add('message private', 'priv'); diff --git a/.config/irssi/scripts/autorun/nickcolor.pl b/.config/irssi/scripts/autorun/autorun/nickcolor.pl diff --git a/.config/irssi/scripts/autorun/usercount.pl b/.config/irssi/scripts/autorun/autorun/usercount.pl diff --git a/.config/irssi/scripts/autorun/cmdind.pl b/.config/irssi/scripts/autorun/cmdind.pl @@ -0,0 +1,61 @@ +use strict; +use warnings; + +our $VERSION = '1.1'; # 67ffc4766319fe4 +our %IRSSI = ( + authors => 'Nei', + contact => 'Nei @ anti@conference.jabber.teamidiot.de', + url => "http://anti.teamidiot.de/", + name => 'cmdind', + description => 'Indicator for input prompt if you are inputting a command or text', + license => 'GNU GPLv2 or later', + ); + +# Usage +# ===== +# This script requires the +# +# uberprompt +# +# script to work. If you don't have it yet, /script install uberprompt + +# Options +# ======= +# /set cmdind_text <string> +# * string : Text to show in prompt when typing a command +# +# /set cmdind_warn_text <string> +# * string : Text to show in prompt when typing a command with spaces in front + +use Irssi; + +my $cmd_state = 0; +my $cmdchars; +my @text; + +sub check_input { + my $inputline = Irssi::parse_special('$L'); + my $c1 = length $inputline > 0 ? substr $inputline, 0, 1 : ''; + my $c2 = length $inputline > 1 ? substr $inputline, 1, 1 : ''; + my $old_state = $cmd_state; + my $x_state = length $c2 && (-1 != index $cmdchars, $c1) && $c2 ne ' '; + my $warn_state = + ($inputline =~ /^\s+(\S)/ && (-1 != index $cmdchars, $1)) + || ($x_state && $inputline =~ /^(.)\1?+\S*[\Q$cmdchars\E]/); + $cmd_state = $warn_state ? 2 : $x_state ? 1 : 3; + if ($cmd_state ne $old_state) { + Irssi::signal_emit('change prompt', $text[ $cmd_state ], 'UP_POST'); + } +} +sub setup_changed { + $cmdchars = Irssi::settings_get_str('cmdchars'); + @text = ('', + Irssi::settings_get_str('cmdind_text'), + Irssi::settings_get_str('cmdind_warn_text'), + ''); +} +Irssi::settings_add_str('cmdind', 'cmdind_text', '%gCmd:'); +Irssi::settings_add_str('cmdind', 'cmdind_warn_text', '%RMsg?'); +setup_changed(); +Irssi::signal_add_last('gui key pressed', 'check_input'); +Irssi::signal_add('setup changed', 'setup_changed'); diff --git a/.config/irssi/scripts/autorun/ctcpspoof.pl b/.config/irssi/scripts/autorun/ctcpspoof.pl @@ -0,0 +1,277 @@ +#!/usr/bin/perl -w + +## Bugreports and Licence disclaimer. +# +# For bugreports and other improvements contact Geert Hauwaerts <geert@irssi.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this script; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +## + +use strict; +use Irssi; +use vars qw($VERSION %IRSSI); + +$VERSION = "1.04"; + +%IRSSI = ( + authors => 'Geert Hauwaerts', + contact => 'geert@irssi.org', + name => 'fakectcp.pl', + description => 'This script sends fake ctcp replies to a client using a fake ctcp list.', + license => 'GNU General Public License', + url => 'http://irssi.hauwaerts.be/default.pl', + changed => '2018-09-17', +); + +my @fakectcp = (); +my $fakectcp_file = "fctcplist"; +my $irssidir = Irssi::get_irssi_dir(); + +my $help = <<EOF; + +Usage: (all on one line) +/FCTCP [-add||-replace <ctcp-item> <ctcp-reply>] [-del <ctcp-item>] [-list] [-help] + +-add: Add a new fake ctcp-reply to the list. +-del: Delete a fake ctcp-reply from the list. +-list: Display the contents of the fake ctcp-reply list. +-help: Display this useful little helpfile. +-replace: Replace an existing fake reply with a new one. If the old one doesn't exist, the new one will be added by default. + +Examples: (all on one line) +/FCTCP -add CHRISTEL We all love christel, don't we! :) +/FCTCP -add LOCATION I'm at home, reading some helpfiles. + +/FCTCP -del CHRISTEL +/FCTCP -del LOCATION + +Note: The caps are not obligated. The default parameter is -list. +EOF + +Irssi::theme_register([ + 'fctcp_info', ' # ctcpitem ctcpreply', + 'fctcp_empty', '%R>>%n %_FCTCP:%_ Your fake ctcp list is empty.', + 'fctcp_added', '%R>>%n %_FCTCP:%_ Added %_$0%_ ($1) to the fake ctcp list.', + 'fctcp_replaced', '%R>>%n %_FCTCP:%_ Replaced the old fake reply %_$0%_ with the new one ($1)', + 'fctcp_delled', '%R>>%n %_FCTCP:%_ Deleted %_$0%_ from the fake ctcp list.', + 'fctcp_nfound', '%R>>%n %_FCTCP:%_ Can\'t find $0 in the fake ctcp list.', + 'fctcp_delusage', '%R>>%n %_FCTCP:%_ Usage: /FCTCP -del <ctcp-item>', + 'fctcp_usage', '%R>>%n %_FCTCP:%_ Usage: /FCTCP -add <ctcp-item> <ctcp-reply>', + 'fctcp_repusage', '%R>>%n %_FCTCP:%_ Usage: /FCTCP -replace <ctcp-item> <ctcp-reply>', + 'fctcp_nload', '%R>>%n %_FCTCP:%_ Could not load the fake ctcp list.', + 'fctcp_request', '%R>>%n %_FCTCP:%_ Used the fake reply %_$1%_ on %_$0%_', + 'fctcp_loaded', '%R>>%n %_FCTCP:%_ The fake reply %_$0%_ already exists, use %_/FCTCP -del $0%_ to remove it from the list.', + 'fctcp_print', '$[!-2]0 $[20]1 $2', + 'fctcp_help', '$0', + 'loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.' +]); + +sub ctcpreply { + + my ($server, $data, $nick, $address, $target) = @_; + my ($findex); + + $data = lc($data); + + return unless (lc($server->{nick}) eq lc($target)); + + if (!already_loaded($data)) { + $findex = check_loaded($data); + $server->command("^NCTCP $nick $data $fakectcp[$findex]->{reply}"); + Irssi::printformat(MSGLEVEL_CTCPS, 'fctcp_request', $nick, $data); + Irssi::signal_stop(); + } +} + +sub new_fctcp { + + my $fctcp = {}; + + $fctcp->{item} = shift; + $fctcp->{reply} = shift; + + return $fctcp; +} + +sub already_loaded { + + my ($item) = @_; + my $loaded = check_loaded($item); + + if ($loaded > -1) { + return 0; + } + + return 1; +} + +sub check_loaded { + + my ($item) = @_; + + $item = lc($item); + + for (my $loaded = 0; $loaded < @fakectcp; ++$loaded) { + return $loaded if (lc($fakectcp[$loaded]->{item}) eq $item); + } + + return -1; +} + +sub load_fakectcplist { + + my ($file) = @_; + + @fakectcp = (); + + if (-e $file) { + local *F; + open(F, "<", $file); + local $/ = "\n"; + + while (<F>) { + chop; + my $new_fctcp = new_fctcp(split("\t")); + + if (($new_fctcp->{item} ne "") && ($new_fctcp->{reply} ne "")) { + push(@fakectcp, $new_fctcp); + } + } + + close(F); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_nload'); + } +} + +sub save_fakectcplist { + + my ($file) = @_; + + local *F; + open(F, ">", $file) or die "Could not load the fake ctcpreply list for writing"; + + for (my $n = 0; $n < @fakectcp; ++$n) { + print(F join("\t", $fakectcp[$n]->{item}, $fakectcp[$n]->{reply}) . "\n"); + } + + close(F); +} + +sub addfakectcp { + + my ($ctcpitem, $ctcpreply) = split (" ", $_[0], 2); + + if (($ctcpitem eq "") || ($ctcpreply eq "")) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_usage'); + return; + } elsif (!already_loaded($ctcpitem)) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_loaded', $ctcpitem); + return; + } + + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_added', $ctcpitem, $ctcpreply); + push(@fakectcp, new_fctcp($ctcpitem, $ctcpreply)); + save_fakectcplist("$irssidir/$fakectcp_file"); +} + +sub delfakectcp { + + my ($fdata) = @_; + my ($fdataindex); + + if ($fdata eq "") { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_delusage'); + return; + } + + for (my $index = 0; $index < @fakectcp; ++$index) { + if (lc($fakectcp[$index]->{item}) eq $fdata) { + $fdataindex = splice(@fakectcp, $index, 1); + } + } + + if ($fdataindex) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_delled', $fdata); + save_fakectcplist("$irssidir/$fakectcp_file"); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_nfound', $fdata); + } +} + +sub replacefakectcp { + + my ($ctcpitem, $ctcpreply) = split (" ", $_[0], 2); + my ($fdataindex); + + if (($ctcpitem eq "") || ($ctcpreply eq "")) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_repusage'); + return; + } + + if (!already_loaded($ctcpitem)) { + for (my $index = 0; $index < @fakectcp; ++$index) { + if (lc($fakectcp[$index]->{item}) eq $ctcpitem) { + $fdataindex = splice(@fakectcp, $index, 1); + } elsif ($fdataindex) { + save_fakectcplist("$irssidir/$fakectcp_file"); + } + } + } + + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_replaced', $ctcpitem, $ctcpreply); + push(@fakectcp, new_fctcp($ctcpitem, $ctcpreply)); + save_fakectcplist("$irssidir/$fakectcp_file"); +} + +sub fakectcp { + + my ($cmdoption, $ctcpitem, $ctcpreply) = split (" ", $_[0], 3); + + $ctcpitem = lc($ctcpitem); + $cmdoption = lc($cmdoption); + + if ($cmdoption eq "-add") { + addfakectcp("$ctcpitem $ctcpreply"); + return; + } elsif ($cmdoption eq "-del") { + delfakectcp("$ctcpitem"); + return; + } elsif ($cmdoption eq "-help") { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_help', $help); + return; + } elsif ($cmdoption eq "-replace") { + replacefakectcp("$ctcpitem $ctcpreply"); + return; + } + + if (@fakectcp == 0) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_empty'); + } else { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_info'); + + for (my $n = 0; $n < @fakectcp ; ++$n) { + Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'fctcp_print', $n, $fakectcp[$n]->{item}, $fakectcp[$n]->{reply}); + } + } +} + +load_fakectcplist("$irssidir/$fakectcp_file"); + +Irssi::signal_add('ctcp msg', 'ctcpreply'); +Irssi::command_bind('fctcp', 'fakectcp'); +Irssi::command_set_options('fctcp','add del list help replace'); +Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'loaded', $IRSSI{name}, $VERSION, $IRSSI{authors}); diff --git a/.config/irssi/scripts/autorun/dim_nicks.pl b/.config/irssi/scripts/autorun/dim_nicks.pl @@ -0,0 +1,431 @@ +use strict; +use warnings; + +our $VERSION = '0.4.9'; +our %IRSSI = ( + authors => 'Nei', + contact => 'Nei @ anti@conference.jabber.teamidiot.de', + url => "http://anti.teamidiot.de/", + name => 'dim_nicks', + description => 'Dims nicks that are not in channel anymore.', + license => 'GNU GPLv2 or later', + ); + +# Usage +# ===== +# Once loaded, this script will record the nicks of each new +# message. If the user leaves the room, the messages will be rewritten +# with the nick in another colour/style. +# +# Depending on your theme, tweaking the forms settings may be +# necessary. With the default irssi theme, this script should just +# work. + +# Options +# ======= +# /set dim_nicks_color <colour> +# * the colour code to use for dimming the nick, or a string of format +# codes with the special token $* in place of the nick (e.g. %I$*%I +# for italic) +# +# /set dim_nicks_history_lines <num> +# * only this many lines of messages are remembered/rewritten (per +# window) +# +# /set dim_nicks_ignore_hilights <ON|OFF> +# * ignore lines with hilight when dimming +# +# /set dim_nicks_forms_skip <num> +# /set dim_nicks_forms_search_max <num> +# * these two settings limit the range where to search for the +# nick. +# It sets how many forms (blocks of irssi format codes or +# non-letters) to skip at the beginning of line before starting to +# search for the nick, and from then on how many forms to search +# before stopping. +# You should set this to the appropriate values to avoid (a) dimming +# your timestamp (b) dimming message content instead of the nick. +# To check your settings, you can use the command +# /script exec Irssi::Script::dim_nicks::debug_forms + + +no warnings 'redefine'; +use constant IN_IRSSI => __PACKAGE__ ne 'main' || $ENV{IRSSI_MOCK}; +use Irssi 20140701; +use Irssi::TextUI; +use Encode; + + +sub setc () { + $IRSSI{name} +} + +sub set ($) { + setc . '_' . $_[0] +} + +my $history_lines = 100; +my $skip_forms = 1; +my $search_forms_max = 5; +my $ignore_hilights = 1; +my $color_letter = 'K'; +my @color_code = ("\cD8/"); # update this when you change $color_letter + +# nick object cache, chan object cache, line id cache, line id -> window map, -> channel, -> nick, -> nickname, channel -> line ids, channel->nickname->departure time, channel->nickname->{parts of line} +my (%nick_reg, %chan_reg, %history_w, %history_c, %history_n, %history_nn, %history_st, %lost_nicks, %lost_nicks_fs, %lost_nicks_fc, %lost_nicks_bc, %lost_nicks_bs); + +our ($dest, $chanref, $nickref); + + +sub msg_line_tag { + my ($srv, $msg, $nick, $addr, $targ) = @_; + local $chanref = $srv->channel_find($targ); + local $nickref = ref $chanref ? $chanref->nick_find($nick) : undef; + &Irssi::signal_continue; +} + +sub color_to_code { + my $win = Irssi::active_win; + my $view = $win->view; + my $cl = $color_letter; + if (-1 == index $cl, '$*') { + $cl = "%$cl\$*"; + } + $win->print_after(undef, MSGLEVEL_NEVER, "$cl "); + my $lp = $win->last_line_insert; + my $color_code = $lp->get_text(1); + $color_code =~ s/ $//; + $view->remove_line($lp); + @color_code = split /\$\*/, $color_code, 2; +} + +sub setup_changed { + $history_lines = Irssi::settings_get_int( set 'history_lines' ); + $skip_forms = Irssi::settings_get_int( set 'forms_skip' ); + $search_forms_max = Irssi::settings_get_int( set 'forms_search_max' ); + $ignore_hilights = Irssi::settings_get_bool( set 'ignore_hilights' ); + my $new_color = Irssi::settings_get_str( set 'color' ); + if ($new_color ne $color_letter) { + $color_letter = $new_color; + color_to_code(); + } +} + +sub init_dim_nicks { + setup_changed(); +} + +sub prt_text_issue { + my ($ld) = @_; + local $dest = $ld; + &Irssi::signal_continue; +} + +sub expire_hist { + for my $ch (keys %history_st) { + if (@{$history_st{$ch}} > 2 * $history_lines) { + my @del = splice @{$history_st{$ch}}, 0, $history_lines; + delete @history_w{ @del }; + delete @history_c{ @del }; + delete @history_n{ @del }; + delete @history_nn{ @del }; + } + } +} + +sub prt_text_ref { + return unless $nickref; + return unless $dest && defined $dest->{target}; + return unless $dest->{level} & MSGLEVEL_PUBLIC; + return if $ignore_hilights && $dest->{level} & MSGLEVEL_HILIGHT; + + my ($win) = @_; + my $view = $win->view; + my $line_id = $view->{buffer}{_irssi} .','. $view->{buffer}{cur_line}{_irssi}; + $chan_reg{ $chanref->{_irssi} } = $chanref; + $nick_reg{ $nickref->{_irssi} } = $nickref; + if (exists $history_w{ $line_id }) { + } + $history_w{ $line_id } = $win->{_irssi}; + $history_c{ $line_id } = $chanref->{_irssi}; + $history_n{ $line_id } = $nickref->{_irssi}; + $history_nn{ $line_id } = $nickref->{nick}; + push @{$history_st{ $chanref->{_irssi} }}, $line_id; + expire_hist(); + my @lost_forever = grep { $view->{buffer}{first_line}{info}{time} > $lost_nicks{ $chanref->{_irssi} }{ $_ } } + keys %{$lost_nicks{ $chanref->{_irssi} }}; + delete @{$lost_nicks{ $chanref->{_irssi} }}{ @lost_forever }; + delete @{$lost_nicks_fs{ $chanref->{_irssi} }}{ @lost_forever }; + delete @{$lost_nicks_fc{ $chanref->{_irssi} }}{ @lost_forever }; + delete @{$lost_nicks_bc{ $chanref->{_irssi} }}{ @lost_forever }; + delete @{$lost_nicks_bs{ $chanref->{_irssi} }}{ @lost_forever }; + return; +} + +sub win_del { + my ($win) = @_; + for my $ch (keys %history_st) { + @{$history_st{$ch}} = grep { exists $history_w{ $_ } && + $history_w{ $_ } != $win->{_irssi} } @{$history_st{$ch}}; + } + my @del = grep { $history_w{ $_ } == $win->{_irssi} } keys %history_w; + delete @history_w{ @del }; + delete @history_c{ @del }; + delete @history_n{ @del }; + delete @history_nn{ @del }; + return; +} + +sub _alter_lines { + my ($chan, $check_lr, $ad) = @_; + my $win = $chan->window; + return unless ref $win; + my $view = $win->view; + my $count = $history_lines; + my $buffer_id = $view->{buffer}{_irssi} .','; + my $lp = $view->{buffer}{cur_line}; + my %check_lr = map { $_ => undef } @$check_lr; + my $redraw; + my $bottom = $view->{bottom}; + while ($lp && $count) { + my $line_id = $buffer_id . $lp->{_irssi}; + if (exists $check_lr{ $line_id }) { + $lp = _alter_line($buffer_id, $line_id, $win, $view, $lp, $chan->{_irssi}, $ad); + unless ($lp) { + last; + } + $redraw = 1; + } + } continue { + --$count; + $lp = $lp->prev; + } + if ($redraw) { + $win->command('^scrollback end') if $bottom && !$win->view->{bottom}; + $view->redraw; + } +} + +my $irssi_mumbo = qr/\cD[`-i]|\cD[&-@\xff]./; +my $irssi_mumbo_no_partial = qr/(?<!\cD)(?<!\cD[&-@\xff])/; +my $irssi_skip_form_re = qr/((?:$irssi_mumbo|[.,*@%+&!#$()=~'";:?\/><]+(?=$irssi_mumbo|\s))+|\s+)/; + +sub debug_forms { + my $win = Irssi::active_win; + my $view = $win->view; + my $lp = $view->{buffer}{cur_line}; + my $count = $history_lines; + my $buffer_id = $view->{buffer}{_irssi} .','; + while ($lp && $count) { + my $line_id = $buffer_id . $lp->{_irssi}; + if (exists $history_w{ $line_id }) { + my $line_nick = $history_nn{ $line_id }; + my $text = $lp->get_text(1); + pos $text = 0; + my $from = 0; + for (my $i = 0; $i < $skip_forms; ++$i) { + last unless + scalar $text =~ /$irssi_skip_form_re/g; + $from = pos $text; + } + my $to = $from; + for (my $i = 0; $i < $search_forms_max; ++$i) { + last unless + scalar $text =~ /$irssi_skip_form_re/g; + $to = pos $text; + } + my $pre = substr $text, 0, $from; + my $search = substr $text, $from, $to-$from; + my $post = substr $text, $to; + unless ($to > $from) { + } else { + my @nick_reg; + unshift @nick_reg, quotemeta substr $line_nick, 0, $_ for 1 .. length $line_nick; + no warnings 'uninitialized'; + for my $nick_reg (@nick_reg) { + last if $search + =~ s/(\Q$color_code[0]\E\s*)?((?:$irssi_mumbo)+)?$irssi_mumbo_no_partial($nick_reg)((?:$irssi_mumbo)+)?(\s*\Q$color_code[0]\E)?/<match>$1$2<nick>$3<\/nick>$4$5<\/match>/; + last if $search + =~ s/(?:\Q$color_code[0]\E)?(?:(?:$irssi_mumbo)+?)?$irssi_mumbo_no_partial($nick_reg)(?:(?:$irssi_mumbo)+?)?(?:\Q$color_code[1]\E)?/<nick>$1<\/nick>/; + } + } + my $msg = "$pre<search>$search</search>$post"; + #$msg =~ s/([^[:print:]])/sprintf '\\x%02x', ord $1/ge; + $msg =~ s/\cDe/%|/g; $msg =~ s/%/%%/g; + $win->print(setc." form debug: [$msg]", MSGLEVEL_CLIENTCRAP); + return; + } + } continue { + --$count; + $lp = $lp->prev; + } + $win->print(setc." form debug: no usable line found", MSGLEVEL_CLIENTCRAP); +} + +sub _alter_line { + my ($buffer_id, $lrp, $win, $view, $lp, $cid, $ad) = @_; + my $line_nick = $history_nn{ $lrp }; + my $text = $lp->get_text(1); + pos $text = 0; + my $from = 0; + for (my $i = 0; $i < $skip_forms; ++$i) { + last unless + scalar $text =~ /$irssi_skip_form_re/g; + $from = pos $text; + } + my $to = $from; + for (my $i = 0; $i < $search_forms_max; ++$i) { + last unless + scalar $text =~ /$irssi_skip_form_re/g; + $to = pos $text; + } + return $lp unless $to > $from; + my @nick_reg; + unshift @nick_reg, quotemeta substr $line_nick, 0, $_ for 1 .. length $line_nick; + { no warnings 'uninitialized'; + if ($ad) { + if (exists $lost_nicks_fs{ $cid }{ $line_nick }) { + my ($fs, $fc, $bc, $bs) = ($lost_nicks_fs{ $cid }{ $line_nick }, $lost_nicks_fc{ $cid }{ $line_nick }, $lost_nicks_bc{ $cid }{ $line_nick }, $lost_nicks_bs{ $cid }{ $line_nick }); + my $sen = length $bs ? $color_code[0] : ''; + for my $nick_reg (@nick_reg) { + last if + (substr $text, $from, $to-$from) + =~ s/(?:\Q$color_code[0]\E)?(?:(?:$irssi_mumbo)+?)?$irssi_mumbo_no_partial($nick_reg)(?:(?:$irssi_mumbo)+?)?(?:\Q$color_code[1]\E)?/$fc$1$bc$sen/; + } + } + } + else { + for my $nick_reg (@nick_reg) { + if ( + (substr $text, $from, $to-$from) + =~ s/(\Q$color_code[0]\E\s*)?((?:$irssi_mumbo)+)?$irssi_mumbo_no_partial($nick_reg)((?:$irssi_mumbo)+)?(\s*\Q$color_code[0]\E)?/$1$2$color_code[0]$3$color_code[1]$4$5/) { + $lost_nicks_fs{ $cid }{ $line_nick } = $1; + $lost_nicks_fc{ $cid }{ $line_nick } = $2; + $lost_nicks_bc{ $cid }{ $line_nick } = $4; + $lost_nicks_bs{ $cid }{ $line_nick } = $5; + last; + } + } + } } + $win->gui_printtext_after($lp->prev, $lp->{info}{level} | MSGLEVEL_NEVER, "$text\n", $lp->{info}{time}); + my $ll = $win->last_line_insert; + my $line_id = $buffer_id . $ll->{_irssi}; + if (exists $history_w{ $line_id }) { + } + grep { $_ eq $lrp and $_ = $line_id } @{$history_st{ $cid }}; + $history_w{ $line_id } = delete $history_w{ $lrp }; + $history_c{ $line_id } = delete $history_c{ $lrp }; + $history_n{ $line_id } = delete $history_n{ $lrp }; + $history_nn{ $line_id } = delete $history_nn{ $lrp }; + $view->remove_line($lp); + $ll; +} + +sub nick_add { + my ($chan, $nick) = @_; + if (delete $lost_nicks{ $chan->{_irssi} }{ $nick->{nick} }) { + my @check_lr = grep { $history_c{ $_ } == $chan->{_irssi} && + $history_n{ $_ } eq $nick->{nick} } keys %history_w; + if (@check_lr) { + $nick_reg{ $nick->{_irssi} } = $nick; + for my $li (@check_lr) { + $history_n{ $li } = $nick->{_irssi}; + } + _alter_lines($chan, \@check_lr, 1); + } + } + delete $lost_nicks_fs{ $chan->{_irssi} }{ $nick->{nick} }; + delete $lost_nicks_fc{ $chan->{_irssi} }{ $nick->{nick} }; + delete $lost_nicks_bc{ $chan->{_irssi} }{ $nick->{nick} }; + delete $lost_nicks_bs{ $chan->{_irssi} }{ $nick->{nick} }; + return; +} + +sub nick_del { + my ($chan, $nick) = @_; + my @check_lr = grep { $history_n{ $_ } eq $nick->{_irssi} } keys %history_w; + for my $li (@check_lr) { + $history_n{ $li } = $nick->{nick}; + } + if (@check_lr) { + $lost_nicks{ $chan->{_irssi} }{ $nick->{nick} } = time; + _alter_lines($chan, \@check_lr, 0); + } + delete $nick_reg{ $nick->{_irssi} }; + return; +} + +sub nick_change { + my ($chan, $nick, $oldnick) = @_; + nick_add($chan, $nick); +} + +sub chan_del { + my ($chan) = @_; + if (my $del = delete $history_st{ $chan->{_irssi} }) { + delete @history_w{ @$del }; + delete @history_c{ @$del }; + delete @history_n{ @$del }; + delete @history_nn{ @$del }; + } + delete $chan_reg{ $chan->{_irssi} }; + delete $lost_nicks{$chan->{_irssi}}; + delete $lost_nicks_fs{$chan->{_irssi}}; + delete $lost_nicks_fc{$chan->{_irssi}}; + delete $lost_nicks_bc{$chan->{_irssi}}; + delete $lost_nicks_bs{$chan->{_irssi}}; + return; +} + +Irssi::settings_add_int( setc, set 'history_lines', $history_lines); +Irssi::settings_add_bool( setc, set 'ignore_hilights', $ignore_hilights); +Irssi::signal_add_last({ + 'setup changed' => 'setup_changed', +}); +Irssi::signal_add({ + 'print text' => 'prt_text_issue', + 'gui print text finished' => 'prt_text_ref', + 'nicklist new' => 'nick_add', + 'nicklist changed' => 'nick_change', + 'nicklist remove' => 'nick_del', + 'window destroyed' => 'win_del', + 'message public' => 'msg_line_tag', + 'channel destroyed' => 'chan_del', +}); + +sub dumphist { + my $win = Irssi::active_win; + my $view = $win->view; + my $buffer_id = $view->{buffer}{_irssi} .','; + for (my $lp = $view->{buffer}{first_line}; $lp; $lp = $lp->next) { + my $line_id = $buffer_id . $lp->{_irssi}; + if (exists $history_w{ $line_id }) { + my $k = $history_c{ $line_id }; + my $kn = $history_n{ $line_id }; + if (exists $chan_reg{ $k }) { + } + if (exists $nick_reg{ $kn }) { + } + if (exists $lost_nicks{ $k } && exists $lost_nicks{ $k }{ $kn }) { + } + } + } +} +Irssi::settings_add_str( setc, set 'color', $color_letter); +Irssi::settings_add_int( setc, set 'forms_skip', $skip_forms); +Irssi::settings_add_int( setc, set 'forms_search_max', $search_forms_max); + +init_dim_nicks(); + +{ package Irssi::Nick } + +# Changelog +# ========= +# 0.4.9 +# - fix default setting not working +# 0.4.8 +# - optionally ignore hilighted lines +# 0.4.7 +# - fix useless re-reading of settings colour +# 0.4.6 +# - fix crash on some lines reported by pierrot diff --git a/.config/irssi/scripts/autorun/dispatch.pl b/.config/irssi/scripts/autorun/dispatch.pl @@ -0,0 +1,26 @@ +use strict; +use warnings; +use Irssi; +use Irssi::Irc; +use vars qw($VERSION %IRSSI); + +$VERSION = "0.0.2"; +%IRSSI = ( + authors => "Sebastian 'yath' Schmidt", + contact => "yathen\@web.de", + name => "Command dispatcher", + description => "This scripts sends unknown commands to the server", + license => "GNU GPLv2", + changed => "Tue Mar 5 14:55:29 CET 2002", +); + +sub event_default_command { + my ($command, $server) = @_; + return if (Irssi::settings_get_bool("dispatch_unknown_commands") == 0 + || !$server); + $server->send_raw($command); + Irssi::signal_stop(); +} + +Irssi::settings_add_bool("misc", "dispatch_unknown_commands", 1); +Irssi::signal_add_first("default command", "event_default_command"); diff --git a/.config/irssi/scripts/autorun/go.pl b/.config/irssi/scripts/autorun/go.pl @@ -0,0 +1,115 @@ +use strict; +use vars qw($VERSION %IRSSI); +use Irssi; +use Irssi::Irc; + +# Usage: +# /script load go.pl +# If you are in #irssi you can type /go #irssi or /go irssi or even /go ir ... +# also try /go ir<tab> and /go <tab> (that's two spaces) +# +# The following settings exist: +# +# /SET go_match_case_sensitive [ON|OFF] +# Match window/item names sensitively (the default). Turning this off +# means e.g. "/go foo" would jump to a window named "Foobar", too. +# +# /SET go_match_anchored [ON|OFF] +# Match window/names only at the start of the word (the default). Turning +# this off will mean that strings can match anywhere in the window/names. +# The leading '#' of channel names is optional either way. +# +# /SET go_complete_case_sensitive [ON|OFF] +# When using tab-completion, match case-insensitively (the default). +# Turning this on means that "/go foo<tab>" will *not* suggest "Foobar". +# +# /SET go_complete_anchored [ON|OFF] +# Match window/names only at the start of the word. The default is 'off', +# which causes completion to match anywhere in the window/names during +# completion. The leading '#' of channel names is optional either way. +# + +$VERSION = '1.1.1'; + +%IRSSI = ( + authors => 'nohar', + contact => 'nohar@freenode', + name => 'go to window', + description => 'Implements /go command that activates a window given a name/partial name. It features a nice completion.', + license => 'GPLv2 or later', + changed => '2019-02-25' +); + +sub _make_regexp { + my ($name, $ci, $aw) = @_; + my $re = "\Q${name}\E"; + $re = "(?i:$re)" unless $ci; + $re = "^#?$re" if $aw; + return $re; +} + +sub signal_complete_go { + my ($complist, $window, $word, $linestart, $want_space) = @_; + my $channel = $window->get_active_name(); + my $k = Irssi::parse_special('$k'); + + return unless ($linestart =~ /^\Q${k}\Ego\b/i); + + my $re = _make_regexp($word, + Irssi::settings_get_bool('go_complete_case_sensitive'), + Irssi::settings_get_bool('go_complete_anchored')); + @$complist = (); + foreach my $w (Irssi::windows) { + my $name = $w->get_active_name(); + if ($word ne "") { + if ($name =~ $re) { + push(@$complist, $name) + } + } else { + push(@$complist, $name); + } + } + Irssi::signal_stop(); +}; + +sub cmd_go +{ + my($chan,$server,$witem) = @_; + + my $case_sensitive = Irssi::settings_get_bool('go_match_case_sensitive'); + my $match_anchored = Irssi::settings_get_bool('go_match_anchored'); + + $chan =~ s/ *//g; + my $re = _make_regexp($chan, $case_sensitive, $match_anchored); + + my @matches; + foreach my $w (Irssi::windows) { + my $name = $w->get_active_name(); + if (($case_sensitive && $name eq $chan) || + (!$case_sensitive && CORE::fc $name eq CORE::fc $chan)) { + $w->set_active(); + return; + } elsif ($name =~ /$re/) { + push(@matches, $w); + } + } + if (@matches) { + $matches[0]->set_active(); + } +} + +Irssi::command_bind("go", "cmd_go"); +Irssi::signal_add_first('complete word', 'signal_complete_go'); +Irssi::settings_add_bool('go', 'go_match_case_sensitive', 1); +Irssi::settings_add_bool('go', 'go_complete_case_sensitive', 0); +Irssi::settings_add_bool('go', 'go_match_anchored', 1); +Irssi::settings_add_bool('go', 'go_complete_anchored', 0); + +# Changelog +# +# 2017-02-02 1.1 martin f. krafft <madduck@madduck.net> +# - made case-sensitivity of match configurable +# - made anchoring of search strings configurable +# +# 2019-02-025 1.1.1 dylan lloyd <dylan@disinclined.org> +# - prefer exact channel matches diff --git a/.config/irssi/scripts/autorun/ls.pl b/.config/irssi/scripts/autorun/ls.pl @@ -0,0 +1,51 @@ +use strict; +use vars qw($VERSION %IRSSI); +use Irssi 20020120; +$VERSION = "0.03"; +%IRSSI = ( + authors => "c0ffee", + contact => "c0ffee\@penguin-breeder.org", + name => "List nicks in channel", + description => "Use /ls <regex> to show all nicks (including ident\@host) matching regex in the current channel", + license => "Public Domain", + url => "http://www.penguin-breeder.org/irssi/", + changed => "Sun Sep 17 06:31 CEST 2017", +); + + +sub cmd_ls { + my ($data, $server, $channel) = @_; + + if ($channel->{type} ne "CHANNEL") { + Irssi::print("You are not on a channel"); + + return; + } + + $channel->print("--- Search results:"); + + my @nicks = $channel->nicks(); + + my $re = eval { qr/$data/i }; + if (not $re) { + chomp $@; + $channel->print("Invalid regex pattern:\n$@"); + return; + } + + my $found; + foreach my $nick (@nicks) { + my $n = $nick->{nick} . "!" . $nick->{host}; + + if ($n =~ $re) { + $channel->print($n); + $found = 1; + } + } + + if (not $found) { + $channel->print("No matches"); + } +} + +Irssi::command_bind('ls','cmd_ls'); diff --git a/.config/irssi/scripts/autorun/nickcolor.pl b/.config/irssi/scripts/autorun/nickcolor.pl @@ -1,388 +1,1065 @@ use strict; -use Irssi 20020101.0250 (); -use vars qw($VERSION %IRSSI); -$VERSION = "2.1"; -%IRSSI = ( - authors => "Timo Sirainen, Ian Peters, David Leadbeater, Bruno Cattáneo", - contact => "tss\@iki.fi", - name => "Nick Color", - description => "assign a different color for each nick", - license => "Public Domain", - url => "http://irssi.org/", - changed => "Mon 08 Jan 21:28:53 BST 2018", -); - -# Settings: -# nickcolor_colors: List of color codes to use. -# e.g. /set nickcolor_colors 2 3 4 5 6 7 9 10 11 12 13 -# (avoid 8, as used for hilights in the default theme). +use warnings; + +our $VERSION = '0.4.0'; # c274f630aff9967 +our %IRSSI = ( + authors => 'Nei', + name => 'nickcolor_expando', + description => 'colourise nicks', + license => 'GPL v2', + ); + +# inspired by bc-bd's nm.pl and mrwright's nickcolor.pl + +# Usage +# ===== +# after loading the script, add the colour expando to the format +# (themes' abstracts are not supported) # -# nickcolor_enable_prefix: Enables prefix for same nick. +# /format pubmsg {pubmsgnick $2 {pubnick $nickcolor$0}}$1 # -# nickcolor_enable_truncate: Enables nick truncation. +# alternatively, use it together with nm2 script + +# Options +# ======= +# /set neat_colors <list of colours> +# * the list of colours for automatic colouring (you can edit it more +# conveniently with /neatcolor colors) # -# nickcolor_prefix_text: Prefix text for succesive messages. -# e.g. /set nickcolor_prefix_text - +# /set neat_ignorechars <regex> +# * regular expression of characters to remove from nick before +# calculating the hash function # -# nickcolor_truncate_value: Truncate nick value. -# e.g. /set nickcolor_truncate_value -7 -# This will truncate nicknames at 7 characters and make them right aligned - -my %saved_colors; -my %session_colors = {}; -my %saved_nicks; # To store each channel's last nickname - -sub load_colors { - open my $color_fh, "<", "$ENV{HOME}/.irssi/saved_colors"; - while (<$color_fh>) { - chomp; - my($nick, $color) = split ":"; - $saved_colors{$nick} = $color; - } +# /set neat_color_reassign_time <time> +# * if the user has not spoken for so long, the assigned colour is +# forgotten and another colour may be picked next time the user +# speaks +# +# /set neat_global_colors <ON|OFF> +# * more strongly prefer one global colour per nickname regardless of +# channel + +# Commands +# ======== +# /neatcolor +# * show the current colour distribution of nicks +# +# /neatcolor set [<network>/<#channel>] <nick> <colour> +# * set a fixed colour for nick +# +# /neatcolor reset [<network>/<#channel>] <nick> +# * remove a set colour of nick +# +# /neatcolor get [<network>/<#channel>] <nick> +# * query the current or set colour of nick +# +# /neatcolor re [<network>/<#channel>] <nick> +# * force change the colour of nick to a random other colour (to +# manually resolve clashes) +# +# /neatcolor save +# * save the colours to ~/.irssi/saved_nick_colors +# +# /neatcolor reset --all +# * re-set all colours +# +# /neatcolor colors +# * show currently configured colours, in colour +# +# /neatcolor colors add <list of colours> +# /neatcolor colors remove <list of colours> +# * add or remove these colours from the neat_colors setting + + +sub cmd_help_neatcolor { + print CLIENTCRAP <<HELP +%9Syntax:%9 + +NEATCOLOR +NEATCOLOR SET [<network>/<#channel>] <nick> <colour> +NEATCOLOR RESET [<network>/<#channel>] <nick> +NEATCOLOR GET [<network>/<#channel>] <nick> +NEATCOLOR RE [<network>/<#channel>] <nick> +NEATCOLOR SAVE +NEATCOLOR RESET --all +NEATCOLOR COLORS +NEATCOLOR COLORS ADD <list of colours> +NEATCOLOR COLORS REMOVE <list of colours> + +%9Parameters:%9 + + SET: set a fixed colour for nick + RESET: remove a set colour of nick + GET: query the current or set colour of nick + RE: force change the colour of nick to a random other + colour (to manually resolve clashes) + SAVE: save the colours to ~/.irssi/saved_nick_colors + RESET --all: re-set all colours + COLORS: show currently configured colours, in colour + COLORS ADD/REMOVE: add or remove these colours from the + neat_colors setting + + If no parameters are given, the current colour distribution of + nicks is shown. + +%9Description:%9 + + Manages nick based colouring + +HELP } -sub save_colors { - open COLORS, ">", "$ENV{HOME}/.irssi/saved_colors"; - - foreach my $nick (keys %saved_colors) { - print COLORS "$nick:$saved_colors{$nick}\n"; +use Hash::Util qw(lock_keys); +use Irssi; + + +{ package Irssi::Nick } + +my @action_protos = qw(irc silc xmpp); +my (%set_colour, %avoid_colour, %has_colour, %last_time, %netchan_hist); +my ($expando, $iexpando, $ignore_re, $ignore_setting, $global_colours, $retain_colour_time, @colours, $exited, $session_load_time); +($expando, $iexpando) = ('', ''); # Initialise to empty + +# the numbers for the scoring system, highest colour value will be chosen +my %scores = ( + set => 200, + keep => 5, + global => 4, + hash => 3, + + avoid => -20, + hist => -10, + used => -2, + ); +lock_keys(%scores); + +my $history_lines = 40; +my $global_mode = 1; # start out with global nick colour + +my @colour_bags = ( + [qw[20 30 40 50 04 66 0C 61 60 67 6L]], # RED + [qw[37 3D 36 4C 46 5C 56 6C 6J 47 5D 6K 6D 57 6E 5E 4E 4K 4J 5J 4D 5K 6R]], # ORANGE + [qw[3C 4I 5I 6O 6I 06 4O 5O 3U 0E 5U 6U 6V 6P 6Q 6W 5P 4P 4V 4W 5W 4Q 5Q 5R 6Y 6X]], # YELLOW + [qw[26 2D 2C 3I 3O 4U 5V 2J 3V 3P 3J 5X]], # YELLOW-GREEN + [qw[16 1C 2I 2U 2O 1I 1O 1V 1P 02 0A 1U 2V 4X]], # GREEN + [qw[1D 1J 1Q 1W 1X 2Y 2S 2R 3Y 3Z 3S 3R 2K 3K 4S 5Z 5Y 4R 3Q 2Q 2X 2W 3X 3W 2P 4Y]], # GREEN-TURQUOIS + [qw[17 1E 1L 1K 1R 1S 03 1M 1N 1T 0B 1Y 1Z 2Z 4Z]], # TURQUOIS + [qw[28 2E 18 1F 19 1G 1A 1B 1H 2N 2H 09 3H 3N 2T 3T 2M 2G 2A 2F 2L 3L 3F 4M 3M 3G 29 4T 5T]], # LIGHT-BLUE + [qw[11 12 23 25 24 13 14 01 15 2B 4N]], # DARK-BLUE + [qw[22 33 44 0D 45 5B 6A 5A 5H 3B 4H 3A 4G 39 4F 6S 6T 5L 5N]], # VIOLET + [qw[21 32 42 53 63 52 43 34 35 55 65 6B 4B 4A 48 5G 6H 5M 6M 6N]], # PINK + [qw[38 31 05 64 54 41 51 62 69 68 59 5F 6F 58 49 6G]], # ROSE + [qw[7A 00 10 7B 7C 7D 7E 7G 7F]], # DARK-GRAY + [qw[7H 7I 27 7K 7J 08 7L 3E 7O 7Q 7N 7M 7P]], # GRAY + [qw[7S 7T 7R 4L 7W 7U 7V 5S 07 7X 6Z 0F]], # LIGHT-GRAY + ); +my %colour_bags; +{ my $idx = 0; + for my $bag (@colour_bags) { + @colour_bags{ @$bag } = ($idx)x@$bag; + } + continue { + ++$idx; } - - close COLORS; } +my @colour_list = map { @$_ } @colour_bags; +my @bases = split //, 'kbgcrmywKBGCRMYW04261537'; +my %base_map = map { $bases[$_] => sprintf '%02X', ($_ % 0x10) } 0..$#bases; +my %ext_to_base_map = map { (sprintf '%02X', $_) => $bases[$_] } 0..15; -# If someone we've colored (either through the saved colors, or the hash -# function) changes their nick, we'd like to keep the same color associated -# with them (but only in the session_colors, ie a temporary mapping). - -sub sig_nick { - my ($server, $newnick, $nick, $address) = @_; - my $color; - - $newnick = substr ($newnick, 1) if ($newnick =~ /^:/); - - if ($color = $saved_colors{$nick}) { - $session_colors{$newnick} = $color; - } elsif ($color = $session_colors{$nick}) { - $session_colors{$newnick} = $color; - } +sub expando_neatcolour { + return $expando; } -# This gave reasonable distribution values when run across -# /usr/share/dict/words +sub expando_neatcolour_inv { + return $iexpando; +} +# one-at-a-time hash sub simple_hash { - my ($string) = @_; - chomp $string; - my @chars = split //, $string; - my $counter; + use integer; + my $hash = 0x5065526c + length $_[0]; + for my $ord (unpack 'U*', $_[0]) { + $hash += $ord; + $hash += $hash << 10; + $hash &= 0xffffffff; + $hash ^= $hash >> 6; + } + $hash += $hash << 3; + $hash &= 0xffffffff; + $hash ^= $hash >> 11; + $hash = $hash + ($hash << 15); + $hash &= 0xffffffff; +} - foreach my $char (@chars) { - $counter += ord $char; +{ my %lut1; + my @z = (0 .. 9, 'A' .. 'Z'); + for my $x (16..255) { + my $idx = $x - 16; + my $col = 1+int($idx / @z); + $lut1{ $col . @z[(($col > 6 ? 10 : 0) + $idx) % @z] } = $x; + } + for my $idx (0..15) { + $lut1{ (sprintf "%02X", $idx) } = ($idx&8) | ($idx&4)>>2 | ($idx&2) | ($idx&1)<<2; } - my @colors = split / /, Irssi::settings_get_str('nickcolor_colors'); - $counter = $colors[$counter % @colors]; - - return $counter; + sub debug_ansicolour { + my ($col, $bg) = @_; + return '' unless defined $col && exists $lut1{$col}; + $bg = $bg ? 48 : 38; + "\e[$bg;5;$lut1{$col}m" + } +} +sub debug_colour { + my ($col, $bg) = @_; + defined $col ? (debug_ansicolour($col, $bg) . $col . "\e[0m") : '(none)' +} +sub debug_score { + my ($score) = @_; + if ($score == 0) { + return $score + } + my @scale = $score > 0 ? (qw(16 1C 1I 1U 2V 4X)) : (qw(20 30 40 60 67 6L));; + my $v = (log 1+ abs $score)*(log 20); + debug_ansicolour($scale[$v >= $#scale ? -1 : $v], 1) . $score . "\e[0m" +} +sub debug_reused { + my ($netchan, $nick, $col) = @_; + my $chc = simple_hash($netchan); + my $hashcolour = @colours ? $colours[ $chc % @colours ] : 0; +} +sub debug_scores { + my ($netchan, $nick, $col, $prios, $colours) = @_; + my $inprogress; + unless (ref $prios) { + $inprogress = $prios; + $prios = [ sort { $colours->{$b} <=> $colours->{$a} } grep { exists $colours->{$_} } @colour_list ]; + } + my $chc = simple_hash($netchan); + my $hashcolour = @colours ? $colours[ $chc % @colours ] : 0; + unless ($inprogress) { + } + else { + } + for my $i (0..$#$prios) { + } } -# process public (others) messages -sub sig_public { - my ($server, $msg, $nick, $address, $target) = @_; +sub colourise_nt { + my ($netchan, $nick, $weak) = @_; + my $time = time; - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); - my $enable_truncate = Irssi::settings_get_bool('nickcolor_enable_truncate'); - my $prefix_text = Irssi::settings_get_str('nickcolor_prefix_text'); - my $truncate_value = Irssi::settings_get_int('nickcolor_truncate_value'); + my $g_or_n = $global_colours ? '' : $netchan; - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; + my $old_colour = $has_colour{$g_or_n}{$nick} // $has_colour{$netchan}{$nick}; + my $last_time = $last_time{$g_or_n}{$nick} // $last_time{$netchan}{$nick}; - # Set default nick truncate value to 0 if option is disabled - $truncate_value = 0 if (!$enable_truncate); + my $keep_score = $weak ? $scores{keep} + $scores{set} : $scores{keep}; - # Has the user assigned this nick a color? - my $color = $saved_colors{$nick}; + unless ($weak) { + $last_time{$netchan}{$nick} + = $last_time{''}{$nick} = $time; + } + else { + $last_time{$netchan}{$nick} ||= 0; + } - # Have -we- already assigned this nick a color? - if (!$color) { - $color = $session_colors{$nick}; - } + my $colour; + if (defined $old_colour && ($weak || (defined $last_time + && ($last_time + $retain_colour_time > $time + || ($last_time > 0 && grep { $_->[0] eq $nick } @{ $netchan_hist{$netchan} // [] }))))) { + $colour = $old_colour; + } + else { + # search for a suitable colour + my %colours = map { $_ => 0 } @colours; + my $hashnick = $nick; + $hashnick =~ s/$ignore_re//g if (defined $ignore_re && length $ignore_re); + my $hash = simple_hash($global_mode ? "/$hashnick" : "$netchan/$hashnick"); + + if (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) { + $colours{ $set_colour{$netchan}{$nick} } += $scores{set}; + } + elsif (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$hashnick}) { + $colours{ $set_colour{$netchan}{$hashnick} } += $scores{set}; + } + elsif (exists $set_colour{''} && exists $set_colour{''}{$nick}) { + $colours{ $set_colour{''}{$nick} } += $scores{set}; + } + elsif (exists $set_colour{''} && exists $set_colour{''}{$hashnick}) { + $colours{ $set_colour{''}{$hashnick} } += $scores{set}; + } + + if (exists $avoid_colour{$netchan} && exists $avoid_colour{$netchan}{$nick}) { + for (@{ $avoid_colour{$netchan}{$nick} }) { + $colours{ $_ } += $scores{avoid} if exists $colours{ $_ }; + } + } + elsif (exists $avoid_colour{$g_or_n} && exists $avoid_colour{$g_or_n}{$nick}) { + for (@{ $avoid_colour{$g_or_n}{$nick} }) { + $colours{ $_ } += $scores{avoid} if exists $colours{ $_ }; + } + } + + if (defined $old_colour) { + $colours{$old_colour} += $keep_score + if exists $colours{$old_colour}; + } + elsif (exists $has_colour{''}{$nick}) { + $colours{ $has_colour{''}{$nick} } += $scores{global} + if exists $colours{ $has_colour{''}{$nick} }; + } + + if (@colours) { + my $hashcolour = $colours[ $hash % @colours ]; + if (!defined $old_colour || $hashcolour ne $old_colour) { + $colours{ $hashcolour } += $scores{hash}; + } + } + + { my @netchans = $global_mode ? keys %has_colour : $netchan; + my $total; + my %colour_pens; + for my $gnc (@netchans) { + for my $onick (keys %{ $has_colour{$gnc} }) { + next if $gnc ne $netchan && exists $has_colour{$netchan}{$onick}; + next unless exists $last_time{$gnc}{$onick}; + if ($last_time{$gnc}{$onick} + $retain_colour_time > $time # XXX + || ($last_time{$gnc}{$onick} == 0 && $session_load_time + $retain_colour_time > $time)) { + if (exists $colours{ $has_colour{$gnc}{$onick} }) { + $colour_pens{ $has_colour{$gnc}{$onick} } += $scores{used}; + ++$total; + } + } + } + } + for (keys %colour_pens) { + $colours{ $_ } += $colour_pens{ $_ } / $total * @colours + if @colours; + } + } + + { my $fac = 1; + for my $gnetchan ($netchan, '') { + my $idx = exp(-log($history_lines)/$scores{hist}); + for my $hent (reverse @{ $netchan_hist{$gnetchan} // [] }) { + next unless defined $hent->[1]; + if ($hent->[0] ne $nick) { + my $pen = 1; + $pen *= 3 if length $nick == length $hent->[0]; + $pen *= 2 if (substr $nick, 0, 1) eq (substr $hent->[0], 0, 1) + || 1 == abs +(length $nick) - (length $hent->[0]); + $colours{ $hent->[1] } -= log($pen*$history_lines)/log($idx) / $fac + if exists $colours{ $hent->[1] }; + } + ++$idx; + last if $idx > $history_lines; + } + ++$fac; + } + } + + { my %bag_pens; + for my $co (keys %colours) { + $bag_pens{ $colour_bags{$co} } -= $colours{$co}/2 if $colours{$co} < 0; + } + for my $bag (keys %bag_pens) { + for my $co (@{ $colour_bags[$bag] }) { + $colours{$co} -= $bag_pens{$bag} / @colours + if @colours && exists $colours{$co}; + } + } + } + + my @prio_colours = sort { $colours{$b} <=> $colours{$a} } grep { exists $colours{$_} } @colour_list; + my $stop_at = 0; + while ($stop_at < $#prio_colours + && $colours{ $prio_colours[$stop_at] } <= $colours{ $prio_colours[$stop_at + 1] }) { + ++$stop_at; + } + $colour = $prio_colours[ $hash % ($stop_at + 1) ] + if @prio_colours; - # Let's assign this nick a color - if (!$color) { - $color = simple_hash $nick; - $session_colors{$nick} = $color; - } + } - $color = sprintf "\003%02d", $color; + unless ($weak) { + expire_hist($netchan, ''); - # Optional: We check if it's the same nickname for current target - if ($saved_nicks{$tagtarget} eq $nick && $enable_prefix) - { - # Grouped message - Irssi::command('/^format pubmsg ' . $prefix_text . '$1'); - } - else - { - # Normal message - Irssi::command('/^format pubmsg {pubmsgnick $2 {pubnick ' . $color . '$0}}$1'); - Irssi::command('/^format part %_%G-{pubnick ' . $color . '$0} %n%w$1 {reason $3}'); - Irssi::command('/^format kick %_%G!{pubnick ' . $color . '$0} %nb%wy {pubnick $2} from ${channel $1} {reason $3}'); - Irssi::command('/^format join %_%B+%_{pubnick ' . $color . '$0} %n%w$1'); - - # Save nickname for next message - $saved_nicks{$tagtarget} = $nick; - } + my $ent = [$nick, $colour]; + push @{ $netchan_hist{$netchan} }, $ent; + push @{ $netchan_hist{''} }, $ent; + } + defined $colour ? ($has_colour{$g_or_n}{$nick} = $has_colour{$netchan}{$nick} = $colour) : $colour } -sub sig_quit { - my ($server, $nick, $address, $reason) = @_; - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); - my $enable_truncate = Irssi::settings_get_bool('nickcolor_enable_truncate'); - my $prefix_text = Irssi::settings_get_str('nickcolor_prefix_text'); - my $truncate_value = Irssi::settings_get_int('nickcolor_truncate_value'); - - # Reference for server/channel - - # Set default nick truncate value to 0 if option is disabled - $truncate_value = 0 if (!$enable_truncate); - - # Has the user assigned this nick a color? - my $color = $saved_colors{$nick}; - - # Have -we- already assigned this nick a color? - if (!$color) { - $color = $session_colors{$nick}; - } +sub expire_hist { + for my $ch (@_) { + if ($netchan_hist{$ch} + && @{$netchan_hist{$ch}} > 2 * $history_lines) { + splice @{$netchan_hist{$ch}}, 0, $history_lines; + } + } +} - # Let's assign this nick a color - if (!$color) { - $color = simple_hash $nick; - $session_colors{$nick} = $color; - } +sub msg_line_tag { + my ($srv, $msg, $nick, $addr, $targ) = @_; + my $obj = $srv->channel_find($targ); + clear_ref(), return unless $obj; + my $nickobj = $obj->nick_find($nick); + $nick = $nickobj->{nick} if $nickobj; + my $colour = colourise_nt($srv->{tag}.'/'.$obj->{name}, $nick); + $expando = $colour ? format_expand('%X'.$colour) : ''; + $iexpando = $colour ? format_expand('%x'.$colour) : ''; +} - $color = sprintf "\003%02d", $color; +sub msg_line_tag_xmppaction { + clear_ref(), return unless @_; + my ($srv, $msg, $nick, $targ) = @_; + msg_line_tag($srv, $msg, $nick, undef, $targ); +} - # Optional: We check if it's the same nickname for current target - Irssi::command('/^format quit %_%G<{pubnick ' . $color . '$0} %n%w$1 {reason $2}'); +sub msg_line_clear { + clear_ref(); +} +sub prnt_clear_public { + my ($dest) = @_; + clear_ref() if $dest->{level} & MSGLEVEL_PUBLIC; } -# process public (me) messages -sub sig_me { - my ($server, $msg, $target) = @_; - my $nick = $server->{nick}; +sub clear_ref { + $expando = ''; + $iexpando = ''; +} - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); - my $enable_truncate = Irssi::settings_get_bool('nickcolor_enable_truncate'); - my $prefix_text = Irssi::settings_get_str('nickcolor_prefix_text'); - my $truncate_value = Irssi::settings_get_int('nickcolor_truncate_value'); +sub nicklist_changed { + my ($chanobj, $nickobj, $old_nick) = @_; - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; + my $netchan = $chanobj->{server}{tag}.'/'.$chanobj->{name}; + my $nickstr = $nickobj->{nick}; - # Set default nick truncate value to 0 if option is disabled - $truncate_value = 0 if (!$enable_truncate); + if (!exists $has_colour{''}{$nickstr} && exists $has_colour{''}{$old_nick}) { + $has_colour{''}{$nickstr} = delete $has_colour{''}{$old_nick}; + } + if (exists $has_colour{$netchan}{$old_nick}) { + $has_colour{$netchan}{$nickstr} = delete $has_colour{$netchan}{$old_nick}; + } - # Optional: We check if it's the same nickname for current target - if ($saved_nicks{$tagtarget} eq $nick && $enable_prefix) - { - # Grouped message - Irssi::command('/^format own_msg ' . $prefix_text . '$1'); - } - else - { - # Normal message - Irssi::command('/^format own_msg {ownmsgnick $2 {ownnick $[' . $truncate_value . ']0}}$1'); + $last_time{$netchan}{$nickstr} + = $last_time{''}{$nickstr} = time; - # Save nickname for next message - $saved_nicks{$tagtarget} = $nick; - } + for my $old_ent (@{ $netchan_hist{$netchan} }) { + $old_ent->[0] = $nickstr if $old_ent->[0] eq $old_nick; + } } -# process public (others) actions -sub sig_action_public { - my ($server, $msg, $nick, $address, $target) = @_; - - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); - - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; +{ + my %format2control = ( + 'F' => "\cDa", '_' => "\cDc", '|' => "\cDe", '#' => "\cDi", "n" => "\cDg", "N" => "\cDg", + 'U' => "\c_", '8' => "\cV", 'I' => "\cDf", + ); + my %bg_base = ( + '0' => '0', '4' => '1', '2' => '2', '6' => '3', '1' => '4', '5' => '5', '3' => '6', '7' => '7', + 'x08' => '8', 'x09' => '9', 'x0a' => ':', 'x0b' => ';', 'x0c' => '<', 'x0d' => '=', 'x0e' => '>', 'x0f' => '?', + ); + my %fg_base = ( + 'k' => '0', 'b' => '1', 'g' => '2', 'c' => '3', 'r' => '4', 'm' => '5', 'p' => '5', 'y' => '6', 'w' => '7', + 'K' => '8', 'B' => '9', 'G' => ':', 'C' => ';', 'R' => '<', 'M' => '=', 'P' => '=', 'Y' => '>', 'W' => '?', + ); + my @ext_colour_off = ( + '.', '-', ',', + '+', "'", '&', + ); + sub format_expand { + my $copy = $_[0]; + $copy =~ s{%(Z.{6}|z.{6}|X..|x..|.)}{ + my $c = $1; + if (exists $format2control{$c}) { + $format2control{$c} + } + elsif (exists $bg_base{$c}) { + "\cD/$bg_base{$c}" + } + elsif (exists $fg_base{$c}) { + "\cD$fg_base{$c}/" + } + elsif ($c =~ /^[{}%]$/) { + $c + } + elsif ($c =~ /^(z|Z)([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})$/) { + my $bg = $1 eq 'z'; + my (@rgb) = map { hex $_ } $2, $3, $4; + my $x = $bg ? 0x1 : 0; + my $out = "\cD" . (chr -13 + ord '0'); + for (my $i = 0; $i < 3; ++$i) { + if ($rgb[$i] > 0x20) { + $out .= chr $rgb[$i]; + } + else { + $x |= 0x10 << $i; $out .= chr 0x20 + $rgb[$i]; + } + } + $out .= chr 0x20 + $x; + $out + } + elsif ($c =~ /^(x)(?:0([[:xdigit:]])|([1-6])(?:([0-9])|([a-z]))|7([a-x]))$/i) { + my $bg = $1 eq 'x'; + my $col = defined $2 ? hex $2 + : defined $6 ? 232 + (ord lc $6) - (ord 'a') + : 16 + 36 * ($3 - 1) + (defined $4 ? $4 : 10 + (ord lc $5) - (ord 'a')); + if ($col < 0x10) { + my $chr = chr $col + ord '0'; + "\cD" . ($bg ? "/$chr" : "$chr/") + } + else { + "\cD" . $ext_colour_off[($col - 0x10) / 0x50 + $bg * 3] . chr (($col - 0x10) % 0x50 - 1 + ord '0') + } + } + else { + "%$c" + } + }ge; + $copy + } +} - # Empty current target nick if prefix option is enabled - $saved_nicks{$tagtarget} = '' if ($enable_prefix); +sub save_colours { + open my $fid, '>', Irssi::get_irssi_dir() . '/saved_nick_colors' + or do { + Irssi::print("Error saving nick colours: $!", MSGLEVEL_CLIENTERROR) + unless $exited; + return; + }; + + local $\ = "\n"; + if (%set_colour) { + print $fid '[set]'; + for my $netch (sort keys %set_colour) { + for my $nick (sort keys %{ $set_colour{$netch} }) { + print $fid "$netch/$nick:".$set_colour{$netch}{$nick}; + } + } + print $fid ''; + } + my $time = time; + print $fid '[session]'; + my %session_colour; + for my $netch (sort keys %last_time) { + for my $nick (sort keys %{ $last_time{$netch} }) { + if (exists $has_colour{$netch} && exists $has_colour{$netch}{$nick} + && ($last_time{$netch}{$nick} + $retain_colour_time > $time + || ($last_time{$netch}{$nick} == 0 && $session_load_time + $retain_colour_time > $time) + || grep { $_->[0] eq $nick } @{ $netchan_hist{$netch} // [] })) { + $session_colour{$netch}{$nick} = $has_colour{$netch}{$nick}; + if (exists $session_colour{''}{$nick}) { + if (defined $session_colour{''}{$nick} + && $session_colour{''}{$nick} ne $session_colour{$netch}{$nick}) { + $session_colour{''}{$nick} = undef; + } + } + else { + $session_colour{''}{$nick} = $session_colour{$netch}{$nick}; + } + } + } + } + for my $nick (sort keys %{ $session_colour{''} }) { + if (defined $session_colour{''}{$nick}) { + print $fid "/$nick:".$session_colour{''}{$nick}; + } + else { + for my $netch (sort keys %session_colour) { + print $fid "$netch/$nick:".$session_colour{$netch}{$nick} + if exists $session_colour{$netch}{$nick} && defined $session_colour{$netch}{$nick}; + } + } + } + close $fid; } -# process public (me) actions -sub sig_action_me { - my ($server, $msg, $target) = @_; - my $nick = $server->{nick}; +sub load_colours { + $session_load_time = time; + + open my $fid, '<', Irssi::get_irssi_dir() . '/saved_nick_colors' + or return; + my $mode; + while (my $line = <$fid>) { + chomp $line; + if ($line =~ /^\[(.*)\]$/) { + $mode = $1; + next; + } + + my $colon = rindex $line, ':'; + next if $colon < 0; + my $slash = rindex $line, '/', $colon; + next if $slash < 0; + my $col = substr $line, $colon +1; + next unless length $col; + my $netch = substr $line, 0, $slash; + my $nick = substr $line, $slash +1, $colon-$slash -1; + if ($mode eq 'set') { + $set_colour{$netch}{$nick} = $col; + } + elsif ($mode eq 'session') { + $has_colour{$netch}{$nick} = $col; + $last_time{$netch}{$nick} = 0; + } + } + close $fid; +} - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); +sub UNLOAD { + return if $exited; + exit_save(); +} - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; +sub exit_save { + $exited = 1; + save_colours() if Irssi::settings_get_bool('settings_autosave'); +} - # Empty current target nick if prefix option is enabled - $saved_nicks{$tagtarget} = '' if ($enable_prefix); +sub get_nick_color2 { + my ($tag, $chan, $nick, $format) = @_; + my $col = colourise_nt($tag.'/'.$chan, $nick, 1); + $col ? $format ? format_expand('%X'.$col) : $col : '' +} +sub _cmd_colours_check { + my ($add, $data) = @_; + my @to_check = grep { defined && length } map { + length == 1 ? $base_map{$_} + : length == 3 ? substr $_, 1 + : $_ } map { /(?|x(..)|([0-7].)|(.))/gi } + split ' ', $data; + my @valid; + my %scolours = map { $_ => undef } @colours; + for my $c (@to_check) { + if ((grep { $_ eq $c } @colour_list)) { + if ($add) { next if exists $scolours{$c} } + else { next if !exists $scolours{$c} } + push @valid, $c; + if ($add) { $scolours{$c} = undef; } + else { delete $scolours{$c}; } + } + } + (\@valid, \%scolours) } -sub cmd_color { - my ($data, $server, $witem) = @_; - my ($op, $nick, $color) = split " ", $data; +sub _cmd_colours_set { + my $scolours = shift; + Irssi::settings_set_str('neat_colors', join '', map { $ext_to_base_map{$_} // "X$_" } grep { exists $scolours->{$_} } @colour_list); +} - $op = lc $op; +sub _cmd_colours_list { + map { "%X$_".($ext_to_base_map{$_} // "X$_").'%n' } @{+shift} +} - if (!$op) { - Irssi::print ("No operation given (save/set/clear/list/preview)"); - } elsif ($op eq "save") { - save_colors; - } elsif ($op eq "set") { - if (!$nick) { - Irssi::print ("Nick not given"); - } elsif (!$color) { - Irssi::print ("Color not given"); - } elsif ($color < 2 || $color > 14) { - Irssi::print ("Color must be between 2 and 14 inclusive"); - } else { - $saved_colors{$nick} = $color; +sub cmd_neatcolor_colors_add { + my ($data, $server, $witem) = @_; + my ($added, $scolours) = _cmd_colours_check(1, $data); + if (@$added) { + _cmd_colours_set($scolours); + Irssi::print("%_nce2%_: added @{[ _cmd_colours_list($added) ]} to neat_colors", MSGLEVEL_CLIENTCRAP); + setup_changed(); } - } elsif ($op eq "clear") { - if (!$nick) { - Irssi::print ("Nick not given"); - } else { - delete ($saved_colors{$nick}); + else { + Irssi::print("%_nce2%_: nothing added", MSGLEVEL_CLIENTCRAP); } - } elsif ($op eq "list") { - Irssi::print ("\nSaved Colors:"); +} +sub cmd_neatcolor_colors_remove { + my ($data, $server, $witem) = @_; + my ($removed, $scolours) = _cmd_colours_check(0, $data); + if (@$removed) { + _cmd_colours_set($scolours); + Irssi::print("%_nce2%_: removed @{[ _cmd_colours_list($removed) ]} from neat_colors", MSGLEVEL_CLIENTCRAP); + setup_changed(); } + else { + Irssi::print("%_nce2%_: nothing removed", MSGLEVEL_CLIENTCRAP); } +} -# process public (me) messages -sub sig_me { - my ($server, $msg, $target) = @_; - my $nick = $server->{nick}; - - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); - my $enable_truncate = Irssi::settings_get_bool('nickcolor_enable_truncate'); - my $prefix_text = Irssi::settings_get_str('nickcolor_prefix_text'); - my $truncate_value = Irssi::settings_get_int('nickcolor_truncate_value'); - - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; +sub cmd_neatcolor_colors { + my ($data, $server, $witem) = @_; + $data =~ s/\s+$//; + unless (length $data) { + Irssi::print("%_nce2%_: current colours: @{[ @colours ? _cmd_colours_list(\@colours) : '(none)' ]}"); + } + Irssi::command_runsub('neatcolor colors', $data, $server, $witem); +} - # Set default nick truncate value to 0 if option is disabled - $truncate_value = 0 if (!$enable_truncate); +sub cmd_neatcolor { + my ($data, $server, $witem) = @_; + $data =~ s/\s+$//; + unless (length $data) { + $witem ||= Irssi::active_win; + my $time = time; + my %distribution = map { $_ => 0 } @colours; + for my $netch (keys %has_colour) { + next unless length $netch; + for my $nick (keys %{ $has_colour{$netch} }) { + if (exists $last_time{$netch}{$nick} + && ($last_time{$netch}{$nick} + $retain_colour_time > $time + || grep { $_->[0] eq $nick } @{ $netchan_hist{$netch} // [] })) { + $distribution{ $has_colour{$netch}{$nick} }++ + } + } + } + $witem->print('%_nce2%_ Colour distribution: '. + (join ', ', + map { "%X$_$_:$distribution{$_}" } + sort { $distribution{$b} <=> $distribution{$a} } + grep { exists $distribution{$_} } @colour_list), MSGLEVEL_CLIENTCRAP); + } + Irssi::command_runsub('neatcolor', $data, $server, $witem); +} - # Optional: We check if it's the same nickname for current target - if ($saved_nicks{$tagtarget} eq $nick && $enable_prefix) - { - # Grouped message - Irssi::command('/^format own_msg ' . $prefix_text . '$1'); - } - else - { - # Normal message - Irssi::command('/^format own_msg {ownmsgnick $2 {ownnick $[' . $truncate_value . ']0}}$1'); +sub _cmd_check_netchan_arg { + my ($cmd, $netchan, $nick) = @_; + my %global = map { $_ => undef } qw(set get reset); + unless (length $netchan) { + Irssi::print('%_nce2%_: no network/channel argument given for neatcolor '.$cmd + .(exists $global{$cmd} ? ', use / to '.$cmd.' global colours' : ''), + MSGLEVEL_CLIENTERROR); + return; + } + elsif (-1 == index $netchan, '/') { + Irssi::print('%_nce2%_: missing network/ in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR); + return; + } + elsif ($netchan =~ m\^[^/]+/$\) { + Irssi::print('%_nce2%_: missing /channel in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR); + return; + } - # Save nickname for next message - $saved_nicks{$tagtarget} = $nick; - } + unless (length $nick) { + Irssi::print('%_nce2%_: no nick argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR); + return; + } + elsif (-1 != index $nick, '/') { + Irssi::print('%_nce2%_: / not supported in nicks in argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR); + return; + } + return 1; } -# process public (others) actions -sub sig_action_public { - my ($server, $msg, $nick, $address, $target) = @_; - - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); +sub _cmd_check_colour { + my ($cmd, $colour) = @_; + $colour = substr $colour, 1 if length $colour == 3; + $colour = $base_map{$colour} if length $colour == 1; + unless (length $colour && grep { $_ eq $colour } @colour_list) { + Irssi::print('%_nce2%_: no colour or invalid colour argument given for neatcolor '.$cmd, MSGLEVEL_CLIENTERROR); + return; + } + return $colour; +} - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; +sub cmd_neatcolor_set { + my ($data, $server, $witem) = @_; + my @args = split ' ', $data; + if (@args < 2) { + Irssi::print('%_nce2%_: not enough arguments for neatcolor set', MSGLEVEL_CLIENTERROR); + return; + } + my $netchan; + if (ref $witem) { + $netchan = $witem->{server}{tag}.'/'.$witem->{name}; + } + my $nick; + my $colour; + if (@args < 3) { + ($nick, $colour) = @args; + } + else { + ($netchan, $nick, $colour) = @args; + } - # Empty current target nick if prefix option is enabled - $saved_nicks{$tagtarget} = '' if ($enable_prefix); + return unless _cmd_check_netchan_arg('set', $netchan, $nick); + return unless defined ($colour = _cmd_check_colour('set', $colour)); + $set_colour{$netchan eq '/' ? '' : $netchan}{$nick} = $colour; + for my $netch ($netchan eq '/' ? keys %has_colour + : $global_colours ? ('', $netchan) + : $netchan) { + delete $has_colour{$netch}{$nick} unless + exists $has_colour{$netch}{$nick} && $has_colour{$netch}{$nick} eq $colour; + } + Irssi::print("%_nce2%_: %X$colour$nick%n colour set to: %X$colour$colour%n ".($netchan eq '/' ? 'globally' : "in $netchan"), MSGLEVEL_CLIENTCRAP); } +sub cmd_neatcolor_get { + my ($data, $server, $witem) = @_; + my @args = split ' ', $data; + if (@args < 1) { + Irssi::print('%_nce2%_: not enough arguments for neatcolor get', MSGLEVEL_CLIENTERROR); + return; + } + my $netchan; + if (ref $witem) { + $netchan = $witem->{server}{tag}.'/'.$witem->{name}; + } + my $nick; + if (@args < 2) { + $nick = $args[0]; + } + else { + ($netchan, $nick) = @args; + } -# process public (me) actions -sub sig_action_me { - my ($server, $msg, $target) = @_; - my $nick = $server->{nick}; - - my $enable_prefix = Irssi::settings_get_bool('nickcolor_enable_prefix'); + return unless _cmd_check_netchan_arg('get', $netchan, $nick); - # Reference for server/channel - my $tagtarget = "$server->{tag}/$target"; + if ($netchan ne '/') { + unless (exists $has_colour{$netchan} && exists $has_colour{$netchan}{$nick}) { + Irssi::print("%_nce2%_: $nick is not coloured (yet) in $netchan", MSGLEVEL_CLIENTCRAP); + } + else { + my $colour = $has_colour{$netchan}{$nick}; + Irssi::print("%_nce2%_: %X$colour$nick%n has colour: %X$colour$colour%n in $netchan", MSGLEVEL_CLIENTCRAP); + } + } + my $hashnick = $nick; + $hashnick =~ s/$ignore_re//g if (defined $ignore_re && length $ignore_re); + if (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) { + my $colour = $set_colour{$netchan}{$nick}; + Irssi::print("%_nce2%_: set colour for %X$colour$nick%n in $netchan: %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP); + } + elsif (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$hashnick}) { + my $colour = $set_colour{$netchan}{$hashnick}; + Irssi::print("%_nce2%_: set colour for %X$colour$hashnick%n in $netchan: %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP); + } + elsif (exists $set_colour{''} && exists $set_colour{''}{$nick}) { + my $colour = $set_colour{''}{$nick}; + Irssi::print("%_nce2%_: set colour for %X$colour$nick%n (global): %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP); + } + elsif (exists $set_colour{''} && exists $set_colour{''}{$hashnick}) { + my $colour = $set_colour{''}{$hashnick}; + Irssi::print("%_nce2%_: set colour for %X$colour$hashnick%n (global): %X$colour$colour%n ", MSGLEVEL_CLIENTCRAP); + } + elsif ($netchan eq '/') { + Irssi::print("%_nce2%_: no global colour set for $nick", MSGLEVEL_CLIENTCRAP); + } +} +sub cmd_neatcolor_reset { + my ($data, $server, $witem) = @_; + my @args = split ' ', $data; + if (@args < 1) { + Irssi::print('%_nce2%_: not enough arguments for neatcolor reset', MSGLEVEL_CLIENTERROR); + return; + } + my $netchan; + if (ref $witem) { + $netchan = $witem->{server}{tag}.'/'.$witem->{name}; + } + my $nick; + if (@args == 1 && $args[0] eq '--all') { + %set_colour = %avoid_colour = %has_colour = (); + Irssi::print("%_nce2%_: re-set all colouring"); + return; + } + if (@args < 2) { + $nick = $args[0]; + } + else { + ($netchan, $nick) = @args; + } - # Empty current target nick if prefix option is enabled - $saved_nicks{$tagtarget} = '' if ($enable_prefix); + return unless _cmd_check_netchan_arg('reset', $netchan, $nick); + $netchan = '' if $netchan eq '/'; + unless (exists $set_colour{$netchan} && exists $set_colour{$netchan}{$nick}) { + Irssi::print("%_nce2%_: $nick has no colour set ". (length $netchan ? "in $netchan" : "globally"), MSGLEVEL_CLIENTERROR); + return; + } + my $colour = delete $set_colour{$netchan}{$nick}; + for my $netch ($netchan eq '' ? keys %has_colour + : $global_colours ? ('', $netchan) + : $netchan) { + delete $has_colour{$netch}{$nick} if exists $has_colour{$netch} && exists $has_colour{$netch}{$nick} + && $has_colour{$netch}{$nick} eq $colour; + } + Irssi::print("%_nce2%_: ".($netchan eq '' ? 'global ' : '')."colouring re-set for $nick".($netchan eq '' ? '' : " in $netchan"), MSGLEVEL_CLIENTERROR); } +sub cmd_neatcolor_re { + my ($data, $server, $witem) = @_; + my @args = split ' ', $data; + if (@args < 1) { + Irssi::print('%_nce2%_: not enough arguments for neatcolor re', MSGLEVEL_CLIENTERROR); + return; + } + my $netchan; + if (ref $witem) { + $netchan = $witem->{server}{tag}.'/'.$witem->{name}; + } + my $nick; + if (@args < 2) { + $nick = $args[0]; + } + else { + ($netchan, $nick) = @args; + } -sub cmd_color { - my ($data, $server, $witem) = @_; - my ($op, $nick, $color) = split " ", $data; - - $op = lc $op; + return unless _cmd_check_netchan_arg('re', $netchan, $nick); - if (!$op) { - Irssi::print ("No operation given (save/set/clear/list/preview)"); - } elsif ($op eq "save") { - save_colors; - } elsif ($op eq "set") { - if (!$nick) { - Irssi::print ("Nick not given"); - } elsif (!$color) { - Irssi::print ("Color not given"); - } elsif ($color < 2 || $color > 14) { - Irssi::print ("Color must be between 2 and 14 inclusive"); - } else { - $saved_colors{$nick} = $color; + unless (exists $has_colour{$netchan} && exists $has_colour{$netchan}{$nick}) { + Irssi::print("%_nce2%_: could not find $nick in $netchan", MSGLEVEL_CLIENTERROR); + return; } - } elsif ($op eq "clear") { - if (!$nick) { - Irssi::print ("Nick not given"); - } else { - delete ($saved_colors{$nick}); + my $colour = delete $has_colour{$netchan}{$nick}; + if (grep { $colour eq $_ } @{ $avoid_colour{$netchan}{$nick} || [] }) { + $avoid_colour{$netchan}{$nick} = [ $colour ] } - } elsif ($op eq "list") { - Irssi::print ("\nSaved Colors:"); - foreach my $nick (keys %saved_colors) { - Irssi::print (chr (3) . sprintf("%02d", $saved_colors{$nick}) . "$nick" . - chr (3) . "1 ($saved_colors{$nick})"); + else { + push @{ $avoid_colour{$netchan}{$nick} }, $colour; } - } elsif ($op eq "preview") { - Irssi::print ("\nAvailable colors:"); - foreach my $i (2..14) { - Irssi::print (chr (3) . "$i" . "Color #$i"); + if ($global_colours) { + delete $has_colour{''}{$nick} if defined $colour; + + if (grep { $colour eq $_ } @{ $avoid_colour{''}{$nick} || [] }) { + $avoid_colour{''}{$nick} = [ $colour ] + } + else { + push @{ $avoid_colour{''}{$nick} }, $colour; + } } - } + Irssi::print("%_nce2%_: re-colouring $nick in $netchan", MSGLEVEL_CLIENTERROR); +} +sub cmd_neatcolor_save { + Irssi::print("%_nce2%_: saving colours to file", MSGLEVEL_CLIENTCRAP); + save_colours(); +} + +sub setup_changed { + $global_colours = Irssi::settings_get_bool('neat_global_colors'); + $retain_colour_time = int( abs( Irssi::settings_get_time('neat_color_reassign_time') ) / 1000 ); + my $old_ignore = $ignore_setting // ''; + $ignore_setting = Irssi::settings_get_str('neat_ignorechars'); + if ($old_ignore ne $ignore_setting) { + local $@; + eval { $ignore_re = qr/$ignore_setting/ }; + if ($@) { + $@ =~ /^(.*)/; + print '%_neat_ignorechars%_ did not compile: '.$1; + } + } + my $old_colours = "@colours"; + my %scolours = map { ($base_map{$_} // $_) => undef } Irssi::settings_get_str('neat_colors') =~ /(?|x(..)|(.))/ig; + @colours = grep { exists $scolours{$_} } @colour_list; + + if ($old_colours ne "@colours") { + my $time = time; + for my $netch (sort keys %last_time) { + for my $nick (sort keys %{ $last_time{$netch} }) { + if (exists $has_colour{$netch} && exists $has_colour{$netch}{$nick}) { + if ($last_time{$netch}{$nick} + $retain_colour_time > $time + || ($last_time{$netch}{$nick} == 0 && $session_load_time + $retain_colour_time > $time)) { + $last_time{$netch}{$nick} = 0; + } + else { + delete $last_time{$netch}{$nick}; + } + } + } + $session_load_time = $time; + } + } +} + +sub internals { + +{ + set => \%set_colour, + avoid => \%avoid_colour, + has => \%has_colour, + time => \%last_time, + hist => \%netchan_hist, + colours => \@colours + } +} + +sub init_nickcolour { + setup_changed(); + load_colours(); } -load_colors; - -Irssi::settings_add_str('misc', 'nickcolor_colors', '2 3 4 5 6 7 9 10 11 12 13'); -Irssi::settings_add_bool('misc', 'nickcolor_enable_prefix', 0); -Irssi::settings_add_bool('misc', 'nickcolor_enable_truncate', 0); -Irssi::settings_add_str('misc', 'nickcolor_prefix_text' => '- '); -Irssi::settings_add_int('misc', 'nickcolor_truncate_value' => 0); -Irssi::command_bind('color', 'cmd_color'); - -Irssi::signal_add('message public', 'sig_public'); -Irssi::signal_add('message join', 'sig_public'); -Irssi::signal_add('message part', 'sig_public'); -Irssi::signal_add('message quit', 'sig_public'); -Irssi::signal_add('message quit', 'sig_quit'); -Irssi::signal_add('message kick', 'sig_public'); -Irssi::signal_add('message nick', 'sig_public'); -Irssi::signal_add('message own_public', 'sig_me'); -Irssi::signal_add('message irc action', 'sig_action_public'); -Irssi::signal_add('message irc own_action', 'sig_action_me'); -Irssi::signal_add('event nick', 'sig_nick'); +Irssi::settings_add_str('misc', 'neat_colors', 'rRgGybBmMcCX42X3AX5EX4NX3HX3CX32'); +Irssi::settings_add_str('misc', 'neat_ignorechars', ''); +Irssi::settings_add_time('misc', 'neat_color_reassign_time', '30min'); +Irssi::settings_add_bool('misc', 'neat_global_colors', 0); +init_nickcolour(); + +Irssi::expando_create('nickcolor', \&expando_neatcolour, { + 'message public' => 'none', + 'message own_public' => 'none', + (map { ("message $_ action" => 'none', + "message $_ own_action" => 'none') + } @action_protos), + }); + +Irssi::expando_create('inickcolor', \&expando_neatcolour_inv, { + 'message public' => 'none', + 'message own_public' => 'none', + (map { ("message $_ action" => 'none', + "message $_ own_action" => 'none') + } @action_protos), + }); + +Irssi::signal_add({ + 'message public' => 'msg_line_tag', + 'message own_public' => 'msg_line_clear', + (map { ("message $_ action" => 'msg_line_tag', + "message $_ own_action" => 'msg_line_clear') + } qw(irc silc)), + "message xmpp action" => 'msg_line_tag_xmppaction', + "message xmpp own_action" => 'msg_line_clear', + 'print text' => 'prnt_clear_public', + 'nicklist changed' => 'nicklist_changed', + 'gui exit' => 'exit_save', +}); +Irssi::command_bind({ + 'help' => sub { &cmd_help_neatcolor if $_[0] =~ /^neatcolor\s*$/i;}, + 'neatcolor' => 'cmd_neatcolor', + 'neatcolor save' => 'cmd_neatcolor_save', + 'neatcolor set' => 'cmd_neatcolor_set', + 'neatcolor get' => 'cmd_neatcolor_get', + 'neatcolor reset' => 'cmd_neatcolor_reset', + 'neatcolor re' => 'cmd_neatcolor_re', + 'neatcolor colors' => 'cmd_neatcolor_colors', + 'neatcolor colors add' => 'cmd_neatcolor_colors_add', + 'neatcolor colors remove' => 'cmd_neatcolor_colors_remove', + }); + +Irssi::signal_add_last('setup changed' => 'setup_changed'); + + +# Changelog +# ========= +# 0.4.0 +# - Allow usage of the colour as a background (using $inickcolor) +# 0.3.7 +# - fix crash if xmpp action signal is not registered (just ignore it) +# 0.3.6 +# - also look up ignorechars in set colours +# 0.3.5 +# - bug fix release +# 0.3.4 +# - re/set/reset-colouring was affected by the global colour +# - set colour score too weak +# 0.3.3 +# - fix error with get / reported by Meicceli +# - now possible to reset global colour +# - check for invalid colours +# 0.3.2 +# - add global colour option +# - respect save settings setting +# - add action handling +# 0.3.1 +# - regression: reset colours after removing colour +# 0.3.0 +# - save some more colours +# 0.2.9 +# - fix incorrect calculation of used colours +# - add some sanity checks to set/get command +# - avoid random colour changes diff --git a/.config/irssi/scripts/autorun/nojointext.pl b/.config/irssi/scripts/autorun/nojointext.pl @@ -0,0 +1,35 @@ +# vim:ft=perl:et:sw=2:ts=2: +use strict; +use Irssi; + +our $VERSION = '0.02'; +our %IRSSI = ( + authors => q{Magnus Woldrich}, + contact => q{m@japh.se}, + name => q{ignore_join_blob}, + description => q{Ignore the blob of text displayed when (re)joining a channel}, + license => q{MIT}, +); + +## ignores this: +# > Topic for #ubuntu: hi +# > Topic set by DalekSec +# > Home page for #ubuntu: https://www.ubuntu.com +# > Channel #ubuntu created Sun Nov 26 07:42:41 2006 +# +# These lines have the CRAP MSGLEVEL (because they are crap) but they don't +# respond to an /ignore * CRAP: +# https://github.com/irssi/irssi/issues/992 +# https://github.com/trapd00r/irssi/commit/87f38a20beda81e409a72efd323f5db45d824927 + +sub sig_print_text { + my ($dest, $string, $stripped) = @_; + + if($dest->{level} & MSGLEVEL_CRAP) { + if($stripped =~ m/Topic (for|set)|Channel [#]\S+ created|Home page for [#]\S+/) { + Irssi::signal_stop(); + } + } +} + +Irssi::signal_add_first('print text', \&sig_print_text); diff --git a/.config/irssi/scripts/autorun/tmux-nicklist-portable.pl b/.config/irssi/scripts/autorun/tmux-nicklist-portable.pl @@ -0,0 +1,432 @@ +# based on the nicklist.pl script +################################################################################ +# tmux_nicklist.pl +# This script integrates tmux and irssi to display a list of nicks in a +# vertical right pane with 20% width. Right now theres no configuration +# or setup, simply initialize the script with irssi and by default you +# will get the nicklist for every channel(customize by altering +# the regex in /set nicklist_channel_re) +# +# /set nicklist_channel_re <regex> +# * only show on channels matching this regular expression +# +# /set nicklist_max_users <num> +# * only show when the channel has so many users or less (0 = always) +# +# /set nicklist_smallest_main <num> +# * only show when main window is larger than this (0 = always) +# +# /set nicklist_pane_width <num> +# * width of the nicklist pane +# +# /set nicklist_color <ON|OFF> +# * colourise the nicks in the nicklist (required nickcolor script +# with get_nick_color2 and debug_ansicolour functions) +# +# /set nicklist_gone_sort <ON|OFF> +# * sort away people below +# +# It supports mouse scrolling and the following keys: +# k/up arrow: up one line +# j/down arrow: down one line +# u/pageup: up 50% lines +# d/pagedown: down 50% lines +# gg: go to top +# G: go to bottom +# +# For better integration, unrecognized sequences will be sent to irssi and +# its pane will be focused. +# +# to toggle the nicklist if it is in the way you can make a key binding: +# /bind meta-Z /script exec Irssi::Script::tmux_nicklist_portable::toggle_nicklist +################################################################################ + +use strict; +use warnings; +use IO::Handle; +use IO::Select; +use POSIX; +use File::Temp qw/ :mktemp /; +use File::Basename; +our $VERSION = '0.1.8'; +our %IRSSI = ( + authors => 'Thiago de Arruda', + contact => 'tpadilha84@gmail.com', + name => 'tmux-nicklist', + description => 'displays a list of nicks in a separate tmux pane', + license => 'WTFPL', +); + +# "other" prefixes by danielg4 <daniel@gimpelevich.san-francisco.ca.us> +# added 'd' and 'u' navigation as in vim, by @gerardbm (github) + +{ package Irssi::Nick } + +if ($#ARGV == -1) { +require Irssi; + +my $enabled = 0; +my $nicklist_toggle = 1; +my $script_path = __FILE__; +my $tmpdir; +my $fifo_path; +my $fifo; +my $just_launched; +my $resize_timer; + +sub enable_nicklist { + return if ($enabled); + $tmpdir = mkdtemp Irssi::get_irssi_dir()."/nicklist-XXXXXXXX"; + $fifo_path = "$tmpdir/fifo"; + POSIX::mkfifo($fifo_path, 0600) or die "can't mkfifo $fifo_path: $!"; + my $cmd = "perl $script_path $fifo_path $ENV{TMUX_PANE}"; + my $width = Irssi::settings_get_int('nicklist_pane_width'); + system('tmux', 'split-window', '-dh', '-l', $width, '-t', $ENV{TMUX_PANE}, $cmd); + open_fifo(); + Irssi::timeout_remove($just_launched) if defined $just_launched; + $just_launched = Irssi::timeout_add_once(300, sub { $just_launched = undef; }, ''); +} + +sub open_fifo { + # The next system call will block until the other pane has opened the pipe + # for reading, so synchronization is not an issue here. + open $fifo, ">", $fifo_path or do { + if ($! == 4) { + Irssi::timeout_add_once(300, \&open_fifo, ''); + $enabled = -1 unless $enabled; + return; + } + die "can't open $fifo_path: $!"; + }; + $fifo->autoflush(1); + if ($enabled < -1) { + $enabled = 1; + disable_nicklist(); + } elsif ($enabled == -1) { + $enabled = 1; + reset_nicklist("enabled"); + } else { + $enabled = 1; + } +} + +sub disable_nicklist { + return unless ($enabled); + if ($enabled > 0) { + print $fifo "EXIT\n"; + close $fifo; + $fifo = undef; + unlink $fifo_path; + rmdir $tmpdir; + } + $enabled--; +} + +sub reset_nicklist { + my $event = shift; + my $active = Irssi::active_win(); + my $channel = $active->{active}; + return disable_nicklist unless $channel && ref $channel; + if ($event =~ /^nick/) { + # check if that nick event is for the current channel/nicklist + my ($event_channel) = @_; + return unless $channel->{_irssi} == $event_channel->{_irssi}; + } + my ($colourer, $ansifier); + if (Irssi::settings_get_bool('nicklist_color')) { + for my $script (sort map { my $z = $_; $z =~ s/::$//; $z } grep { /^nickcolor|nm/ } keys %Irssi::Script::) { + if ($colourer = "Irssi::Script::$script"->can('get_nick_color2')) { + $ansifier = "Irssi::Script::$script"->can('debug_ansicolour'); + last; + } + } + } + my $channel_pattern = Irssi::settings_get_str('nicklist_channel_re'); + { local $@; + $channel_pattern = eval { qr/$channel_pattern/ }; + $channel_pattern = qr/(?!)/ if $@; + } + my $smallest_main = Irssi::settings_get_int('nicklist_smallest_main'); + if (!$nicklist_toggle + || !$channel || !ref($channel) + || !$channel->isa('Irssi::Channel') + || !$channel->{'names_got'} + || $channel->{'name'} !~ $channel_pattern + || ($smallest_main && $channel->window->{width} < $smallest_main)) { + disable_nicklist; + } else { + my %colour; + my @nicks = $channel->nicks(); + my $max_nicks = Irssi::settings_get_int('nicklist_max_users'); + if ($max_nicks && @nicks > $max_nicks) { + disable_nicklist; + } else { + enable_nicklist; + return unless $enabled > 0; + foreach my $nick (sort { $a->{_irssi} <=> $b->{_irssi} } @nicks) { + $colour{$nick->{nick}} = ($ansifier && $colourer) ? $ansifier->($colourer->($active->{active}{server}{tag}, $channel->{name}, $nick->{nick}, 0)) : ''; + } + print($fifo "BEGIN\n"); + my $gone_sort = Irssi::settings_get_bool('nicklist_gone_sort'); + my $prefer_real; + if (exists $Irssi::Script::{'realnames::'}) { + my $code = "Irssi::Script::realnames"->can('use_realnames'); + $prefer_real = $code && $code->($channel); + } + my $_real = sub { + my $nick = shift; + $prefer_real && length $nick->{'realname'} ? $nick->{'realname'} : $nick->{'nick'} + }; + foreach my $nick (sort {($a->{'op'}?'1':$a->{'halfop'}?'2':$a->{'voice'}?'3':$a->{'other'}>32?'0':'4').($gone_sort?($a->{'gone'}?1:0):'').lc($_real->($a)) + cmp ($b->{'op'}?'1':$b->{'halfop'}?'2':$b->{'voice'}?'3':$b->{'other'}>32?'0':'4').($gone_sort?($b->{'gone'}?1:0):'').lc($_real->($b))} @nicks) { + my $colour = $colour{$nick->{nick}} || "\e[39m"; + $colour = "\e[37m" if $nick->{'gone'}; + print($fifo "NICK"); + if ($nick->{'op'}) { + print($fifo "\e[32m\@$colour".$_real->($nick)."\e[39m"); + } elsif ($nick->{'halfop'}) { + print($fifo "\e[34m%$colour".$_real->($nick)."\e[39m"); + } elsif ($nick->{'voice'}) { + print($fifo "\e[33m+$colour".$_real->($nick)."\e[39m"); + } elsif ($nick->{'other'}>32) { + print($fifo "\e[31m".(chr $nick->{'other'})."$colour".$_real->($nick)."\e[39m"); + } else { + print($fifo " $colour".$_real->($nick)."\e[39m"); + } + print($fifo "\n"); + } + print($fifo "END\n"); + } + } +} + +sub toggle_nicklist { + if ($enabled) { + $nicklist_toggle = undef + } else { + $nicklist_toggle = 1; + } + reset_nicklist("toggle"); +} + +sub switch_channel { + print $fifo "SWITCH_CHANNEL\n" if $fifo; + &reset_nicklist; +} + +sub resized_timed { + Irssi::timeout_remove($resize_timer) if defined $resize_timer; + return if defined $just_launched; + $resize_timer = Irssi::timeout_add_once(1100, \&resized, ''); + #resized(); +} +sub resized { + $resize_timer = undef; + return if defined $just_launched; + return unless $enabled >= 0; + disable_nicklist; + Irssi::timeout_add_once(200, sub{reset_nicklist("terminal resized")}, ''); +} +sub UNLOAD { + disable_nicklist; +} + +Irssi::settings_add_str('tmux_nicklist', 'nicklist_channel_re', '.*'); +Irssi::settings_add_int('tmux_nicklist', 'nicklist_max_users', 0); +Irssi::settings_add_int('tmux_nicklist', 'nicklist_smallest_main', 0); +Irssi::settings_add_int('tmux_nicklist', 'nicklist_pane_width', 13); +Irssi::settings_add_bool('tmux_nicklist', 'nicklist_color', 1); +Irssi::settings_add_bool('tmux_nicklist', 'nicklist_gone_sort', 0); +Irssi::signal_add_last('window item changed', sub{switch_channel("window item changed",@_)}); +Irssi::signal_add_last('window changed', sub{switch_channel("window changed",@_)}); +Irssi::signal_add_last('channel joined', sub{switch_channel("channel joined",@_)}); +Irssi::signal_add('nicklist new', sub{reset_nicklist("nicklist new",@_)}); +Irssi::signal_add('nicklist remove', sub{reset_nicklist("nicklist remove",@_)}); +Irssi::signal_add('nicklist changed', sub{reset_nicklist("nicklist changed",@_)}); +Irssi::signal_add_first('nick mode changed', sub{reset_nicklist("nick mode changed",@_)}); +Irssi::signal_add('gui exit', \&disable_nicklist); +Irssi::signal_add_last('terminal resized', \&resized_timed); + +} else { +my $fifo_path = $ARGV[0]; +my $irssi_pane = $ARGV[1]; +# array to store the current channel nicknames +my @nicknames = (); + +# helper functions for manipulating the terminal +# escape sequences taken from +# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html +sub enable_mouse { print "\e[?1000h"; } +# recognized sequences +my $MOUSE_SCROLL_DOWN="\e[Ma"; +my $MOUSE_SCROLL_UP="\e[M`"; +my $ARROW_DOWN="\e[B"; +my $ARROW_UP="\e[A"; +my $DOWN="j"; +my $UP="k"; +my $PAGE_DOWN="\e[6~"; +my $PAGE_UP="\e[5~"; +my $PAGE_DOWN_D="d"; +my $PAGE_UP_U="u"; +my $GO_TOP="gg"; +my $GO_BOTTOM="G"; + +my $current_line = 0; +my $sequence = ''; +my ($rows, $cols); + +sub term_size { + split ' ', `stty size`; +} + +sub redraw { + my $last_nick_idx = @nicknames; + my $last_idx = $current_line + $rows; + # normalize last visible index + if ($last_idx > ($last_nick_idx)) { + $last_idx = $last_nick_idx; + } + # redraw visible nicks + for my $i (reverse 1..$rows) { + print "\e[$i;1H\e[K"; + my $idx = $current_line + $i - 1; + if ($idx < $last_idx) { + my $z = 0; my $col = $cols; + for (split /(\e\[(?:\d|;|:|\?|\s)*.)/, $nicknames[$idx]) { + if ($z ^= 1) { + print +(substr $_, 0, $col) if $col > 0; + $col -= length; + } else { + print + } + } + } + } +} + +sub move_down { + $sequence = ''; + my $count = int $_[0]; + my $nickcount = $#nicknames; + return if ($nickcount <= $rows); + if ($count == -1) { + $current_line = $nickcount - $rows + 1; + redraw; + return; + } + my $visible = $nickcount - $current_line - $count + 1; + if ($visible > $rows) { + $current_line += $count; + redraw; + } elsif (($visible + $count) > $rows) { + # scroll the maximum we can + $current_line = $nickcount - $rows + 1; + redraw; + } +} + +sub move_up { + $sequence = ''; + my $count = int $_[0]; + if ($count == -1) { + $current_line = 0; + redraw; + return; + } + return if ($current_line == 0); + $count = 1 if $count == 0; + $current_line -= $count; + $current_line = 0 if $current_line < 0; + redraw; +} + +$SIG{INT} = 'IGNORE'; + +STDOUT->autoflush(1); +# setup terminal so we can listen for individual key presses without echo +`stty -icanon -echo`; + +# open named pipe and setup the 'select' wrapper object for listening on both +# fds(fifo and sdtin) +open my $fifo, "<", $fifo_path or die "can't open $fifo_path: $!"; +my $select = IO::Select->new(); +my @ready; +$select->add($fifo); +$select->add(\*STDIN); + +enable_mouse; +system('tput', 'smcup'); +print "\e[?7l"; #system('tput', 'rmam'); +system('tput', 'civis'); +MAIN: { + while (@ready = $select->can_read) { + foreach my $fd (@ready) { + ($rows, $cols) = term_size; + if ($fd == $fifo) { + while (<$fifo>) { + my $line = $_; + if ($line =~ /^BEGIN/) { + @nicknames = (); + } elsif ($line =~ /^SWITCH_CHANNEL/) { + $current_line = 0; + } elsif ($line =~ /^NICK(.+)$/) { + push @nicknames, $1; + } elsif ($line =~ /^END$/) { + redraw; + last; + } elsif ($line =~ /^EXIT$/) { + last MAIN; + } + } + } else { + my $key = ''; + sysread(STDIN, $key, 1); + $sequence .= $key; + if ($MOUSE_SCROLL_DOWN =~ /^\Q$sequence\E/) { + if ($MOUSE_SCROLL_DOWN eq $sequence) { + move_down 3; + # mouse scroll has two more bytes that I dont use here + # so consume them now to avoid sending unwanted bytes to + # irssi + sysread(STDIN, $key, 2); + } + } elsif ($MOUSE_SCROLL_UP =~ /^\Q$sequence\E/) { + if ($MOUSE_SCROLL_UP eq $sequence) { + move_up 3; + sysread(STDIN, $key, 2); + } + } elsif ($ARROW_DOWN =~ /^\Q$sequence\E/) { + move_down 1 if ($ARROW_DOWN eq $sequence); + } elsif ($ARROW_UP =~ /^\Q$sequence\E/) { + move_up 1 if ($ARROW_UP eq $sequence); + } elsif ($DOWN =~ /^\Q$sequence\E/) { + move_down 1 if ($DOWN eq $sequence); + } elsif ($UP =~ /^\Q$sequence\E/) { + move_up 1 if ($UP eq $sequence); + } elsif ($PAGE_DOWN =~ /^\Q$sequence\E/) { + move_down $rows/2 if ($PAGE_DOWN eq $sequence); + } elsif ($PAGE_UP =~ /^\Q$sequence\E/) { + move_up $rows/2 if ($PAGE_UP eq $sequence); + } elsif ($PAGE_DOWN_D =~ /^\Q$sequence\E/) { + move_down $rows/2 if ($PAGE_DOWN_D eq $sequence); + } elsif ($PAGE_UP_U =~ /^\Q$sequence\E/) { + move_up $rows/2 if ($PAGE_UP_U eq $sequence); + } elsif ($GO_BOTTOM =~ /^\Q$sequence\E/) { + move_down -1 if ($GO_BOTTOM eq $sequence); + } elsif ($GO_TOP =~ /^\Q$sequence\E/) { + move_up -1 if ($GO_TOP eq $sequence); + } else { + # Unrecognized sequences will be send to irssi and its pane + # will be focused + system('tmux', 'send-keys', '-t', $irssi_pane, $sequence); + system('tmux', 'select-pane', '-t', $irssi_pane); + $sequence = ''; + } + } + } + } +} + +close $fifo; + +} diff --git a/.config/irssi/scripts/autorun/uberprompt.pl b/.config/irssi/scripts/autorun/uberprompt.pl @@ -0,0 +1,774 @@ +=pod + +=head1 NAME + +uberprompt.pl + +=head1 DESCRIPTION + +This script replaces the default prompt status-bar item with one capable of +displaying additional information, under either user control or via scripts. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD F<filename>>. + +It is recommended that you make it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head1 SETUP + +If you have a custom prompt format, you may need to copy it to the +uberprompt_format setting. See below for details. + +=head1 USAGE + +Although the script is designed primarily for other scripts to set +status information into the prompt, the following commands are available: + +=over 4 + +=item * C</prompt set [-inner|-pre|-post|only] E<lt>msgE<gt>> + +Sets the prompt to the given argument. Any use of C<$p> in the argument will +be replaced by the original prompt content. + +A parameter corresponding to the C<UP_*> constants listed below is required, in +the format C</prompt set -inner Hello!> + +=item * C</prompt clear> + +Clears the additional data provided to the prompt. + +=item * C</prompt on> + +Eenables the uberprompt (things may get confused if this is used +whilst the prompt is already enabled) + +=item * C</prompt off> + +Restore the original irssi prompt and prompt_empty statusbars. unloading the +script has the same effect. + +=item * C</help prompt> + +show help for uberprompt commands + +=back + +=head1 SETTINGS + +=head2 UBERPROMPT FORMAT + +C</set uberprompt_format E<lt>formatE<gt>> + +The default is C<[$*$uber]>, which is the same as the default provided in +F<default.theme>. + +Changing this setting will update the prompt immediately, unlike editing your theme. + +An additional variable available within this format is C<$uber>, which expands to +the content of prompt data provided with the C<UP_INNER> or C</prompt set -inner> +placement argument. + +For all other placement arguments, it will expand to the empty string. + +B<Note:> This setting completely overrides the C<prompt="...";> line in your +.theme file, and may cause unexpected behaviour if your theme wishes to set a +different form of prompt. It can be simply copied from the theme file into the +above setting. + +=head2 OTHER SETTINGS + +=over 4 + +=item * C<uberprompt_autostart> + +Boolean value, which determines if uberprompt should enable itself automatically +upon loading. If Off, it must be enabled manually with C</prompt on>. Defaults to On. + +=item * C<uberprompt_debug> + +Boolean value, which determines if uberprompt should print debugging information. +Defaults to Off, and should probably be left that way unless requested for bug-tracing +purposes. + +=item * C<uberprompt_format> + +String value containing the format-string which uberprompt uses to display the +prompt. Defaults to "C<[$*$uber] >", where C<$*> is the content the prompt would +normally display, and C<$uber> is a placeholder variable for dynamic content, as +described in the section above. + +=item * C<uberprompt_load_hook> + +String value which can contain one or more commands to be run whenever the uberprompt +is enabled, either via autostart, or C</prompt on>. Defaults to the empty string, in +which case no commands are run. Some examples include: + +C</set uberprompt_load_hook /echo prompt enabled> or + +C</^sbar prompt add -after input vim_mode> for those using vim_mode.pl who want +the command status indicator on the prompt line. + +=item * C<uberprompt_unload_hook> + +String value, defaulting to the empty string, which can contain commands which +are executed when the uberprompt is disabled, either by unloading the script, +or by the command C</prompt off>. + +=item * C<uberprompt_use_replaces> + +Boolean value, defaults to Off. If enabled, the format string for the prompt +will be subject to the I<replaces> section of the theme. The most obvious +effect of this is that bracket characters C<[ ]> are displayed in a different +colour, typically quite dark. + +=back + +B<Note:> For both C<uberprompt_*_hook> settings above, multiple commands can +be chained together in the form C</eval /^cmd1 ; /^cmd2>. The C<^> prevents +any output from the commands (such as error messages) being displayed. + +=head2 SCRIPTING USAGE + +The primary purpose of uberprompt is to be used by other scripts to +display information in a way that is not possible by printing to the active +window or using statusbar items. + +The content of the prompt can be set from other scripts via the C<"change prompt"> +signal. + +For Example: + + signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER; + +will set the prompt to include that content, by default 'C<[$* some_string]>' + +The possible position arguments are the following strings: + +=over 4 + +=item * C<UP_PRE> - place the provided string before the prompt - C<$string$prompt> + +=item * C<UP_INNER> - place the provided string inside the prompt - C<{prompt $* $string}> + +=item * C<UP_POST> - place the provided string after the prompt - C<$prompt$string> + +=item * C<UP_ONLY> - replace the prompt with the provided string - C<$string> + +=back + +All strings may use the special variable 'C<$prompt>' to include the prompt +verbatim at that position in the string. It is probably only useful for +the C<UP_ONLY> mode however. '$C<prompt_nt>' will include the prompt, minus any +trailing whitespace. + +=head2 CHANGE NOTIFICATIONS + +You can also be notified when the prompt changes in response to the previous +signal or manual C</prompt> commands via: + + signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... }; + +This callback will occur whenever the contents of the prompt is changed. + + +=head2 NOTES FOR SCRIPT WRITERS: + +The following code snippet can be used within your own script as a preamble +to ensure that uberprompt is loaded before your script to avoid +any issues with loading order. It first checks if uberprompt is loaded, and +if not, attempts to load it. If the load fails, the script will die +with an error message, otherwise it will call your app_init() function. + +I<---- start of snippet ----> + + my $DEBUG_ENABLED = 0; + sub DEBUG () { $DEBUG_ENABLED } + + # check we have uberprompt loaded. + + sub script_is_loaded { + return exists($Irssi::Script::{$_[0] . '::'}); + } + + if (not script_is_loaded('uberprompt')) { + + print "This script requires 'uberprompt.pl' in order to work. " + . "Attempting to load it now..."; + + Irssi::signal_add('script error', 'load_uberprompt_failed'); + Irssi::command("script load uberprompt.pl"); + + unless(script_is_loaded('uberprompt')) { + load_uberprompt_failed("File does not exist"); + } + app_init(); + } else { + app_init(); + } + + sub load_uberprompt_failed { + Irssi::signal_remove('script error', 'load_uberprompt_failed'); + + print "Script could not be loaded. Script cannot continue. " + . "Check you have uberprompt.pl installed in your path and " + . "try again."; + + die "Script Load Failed: " . join(" ", @_); + } + +I<---- end of snippet ----> + +=head1 AUTHORS + +Copyright E<copy> 2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item * + +Resizing the terminal rapidly whilst using this script in debug mode may cause +irssi to crash. See bug report at http://bugs.irssi.org/index.php?do=details&task_id=772 for details. + +=back + +=head1 TODO + +=over 4 + +=item * report failure (somehow) to clients if hte prompt is disabled. + +=item * fix issue at autorun startup with sbar item doesn't exist. + +=back + +=cut + +use strict; +use warnings; + +use Irssi; +use Irssi::TextUI; +use Data::Dumper; + +{ package Irssi::Nick } # magic. + +our $VERSION = "0.2"; +our %IRSSI = + ( + authors => "shabble", + contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode', + name => "uberprompt", + description => "Helper script for dynamically adding text " + . "into the input-bar prompt.", + license => "MIT", + changed => "24/7/2010" + ); + + +my $DEBUG_ENABLED = 0; +sub DEBUG { $DEBUG_ENABLED } + +my $prompt_data = ''; +my $prompt_data_pos = 'UP_INNER'; + +my $prompt_last = ''; +my $prompt_format = ''; +my $prompt_format_empty = ''; + +# flag to indicate whether rendering of hte prompt should allow the replaces +# theme formats to be applied to the content. +my $use_replaces = 0; +my $trim_data = 0; + +my $emit_request = 0; + +my $expando_refresh_timer; +my $expando_vars = {}; + +my $init_callbacks = {load => '', unload => ''}; + +pre_init(); + +sub pre_init { + Irssi::command('statusbar prompt reset'); + init(); +} + +sub prompt_subcmd_handler { + my ($data, $server, $item) = @_; + #$data =~ s/\s+$//g; # strip trailing whitespace. + Irssi::command_runsub('prompt', $data, $server, $item); +} + +sub _error($) { + my ($msg) = @_; + Irssi::active_win->print($msg, MSGLEVEL_CLIENTERROR); +} + +sub _debug_print($) { + return unless DEBUG; + my ($msg) = @_; + Irssi::active_win->print($msg, MSGLEVEL_CLIENTCRAP); +} + +sub _print_help { + my ($args) = @_; + if ($args =~ m/^\s*prompt/i) { + my @help_lines = + ( + "", + "PROMPT ON", + "PROMPT OFF", + "PROMPT CLEAR", + "PROMPT SET { -pre | -post | -only | -inner } <content>", + "", + "Commands for manipulating the UberPrompt.", + "", + "/PROMPT ON enables uberprompt, replacing the existing prompt ", + " statusbar-item", + "/PROMPT OFF disables it, and restores the original prompt item", + "/PROMPT CLEAR resets the value of any additional data set by /PROMPT SET", + " or a script", + "/PROMPT SET changes the contents of the prompt, according to the mode", + " and content provided.", + " { -inner sets the value of the \$uber psuedo-variable in the", + " /set uberprompt_format setting.", + " | -pre places the content before the current prompt string", + " | -post places the content after the prompt string", + " | -only replaces the entire prompt contents with the given string }", + "", + "See Also:", + '', + '/SET uberprompt_format -- defaults to "[$*$uber] "', + '/SET uberprompt_format_empty -- defaults to "[$*] "', + "/SET uberprompt_autostart -- determines whether /PROMPT ON is run", + " automatically when the script loads", + "/SET uberprompt_use_replaces -- toggles the use of the current theme", + " \"replaces\" setting. Especially", + " noticeable on brackets \"[ ]\" ", + "/SET uberprompt_trim_data -- defaults to off. Removes whitespace from", + " both sides of the \$uber result.", + "", + ); + + Irssi::print($_, MSGLEVEL_CLIENTCRAP) for @help_lines; + Irssi::signal_stop; + } +} + +sub UNLOAD { + deinit(); +} + +sub exp_lbrace() { '{' } +sub exp_rbrace() { '}' } + +sub deinit { + Irssi::expando_destroy('lbrace'); + Irssi::expando_destroy('rbrace'); + + if (Irssi::settings_get_bool('uberprompt_restore_on_exit')) { + # remove uberprompt and return the original ones. + print "Removing uberprompt and restoring original"; + restore_prompt_items(); + } +} + +sub init { + Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw'); + + # TODO: flags to prevent these from being recomputed? + Irssi::expando_create('lbrace', \&exp_lbrace, {}); + Irssi::expando_create('rbrace', \&exp_rbrace, {}); + + Irssi::settings_add_str ('uberprompt', 'uberprompt_format', '[$*$uber] '); + Irssi::settings_add_str ('uberprompt', 'uberprompt_format_empty', '[$*] '); + + Irssi::settings_add_str ('uberprompt', 'uberprompt_load_hook', ''); + Irssi::settings_add_str ('uberprompt', 'uberprompt_unload_hook', ''); + + Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0); + Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1); + Irssi::settings_add_bool ('uberprompt', 'uberprompt_restore_on_exit', 1); + + Irssi::settings_add_bool('uberprompt', 'uberprompt_use_replaces', 0); + Irssi::settings_add_bool('uberprompt', 'uberprompt_trim_data', 0); + + Irssi::command_bind("prompt", \&prompt_subcmd_handler); + Irssi::command_bind('prompt on', \&replace_prompt_items); + Irssi::command_bind('prompt off', \&restore_prompt_items); + Irssi::command_bind('prompt set', \&cmd_prompt_set); + Irssi::command_bind('prompt clear', + sub { + Irssi::signal_emit 'change prompt', '$p', 'UP_POST'; + }); + + my $prompt_set_args_format = "inner pre post only"; + Irssi::command_set_options('prompt set', $prompt_set_args_format); + + Irssi::command_bind('help', \&_print_help); + + Irssi::signal_add('setup changed', \&reload_settings); + + # intialise the prompt format. + reload_settings(); + + # make sure we redraw when necessary. + Irssi::signal_add('window changed', \&uberprompt_refresh); + Irssi::signal_add('window name changed', \&uberprompt_refresh); + Irssi::signal_add('window changed automatic', \&uberprompt_refresh); + Irssi::signal_add('window item changed', \&uberprompt_refresh); + Irssi::signal_add('window item server changed', \&uberprompt_refresh); + Irssi::signal_add('window server changed', \&uberprompt_refresh); + Irssi::signal_add('server nick changed', \&uberprompt_refresh); + + Irssi::signal_add('nick mode changed', \&refresh_if_me); + + # install our statusbars if required. + if (Irssi::settings_get_bool('uberprompt_autostart')) { + replace_prompt_items(); + } + + # API signals + + Irssi::signal_register({'change prompt' => [qw/string string/]}); + Irssi::signal_add('change prompt' => \&change_prompt_handler); + + # other scripts (specifically overlay/visual) can subscribe to + # this event to be notified when the prompt changes. + # arguments are new contents (string), new length (int) + Irssi::signal_register({'prompt changed' => [qw/string int/]}); + Irssi::signal_register({'prompt length request' => []}); + + Irssi::signal_add('prompt length request', \&length_request_handler); +} + +sub cmd_prompt_set { + my $args = shift; + my @options_list = Irssi::command_parse_options "prompt set", $args; + if (@options_list) { + my ($options, $rest) = @options_list; + + my @opt_modes = keys %$options; + if (@opt_modes != 1) { + _error '%_/prompt set%_ must specify exactly one mode of' + . ' {-inner, -only, -pre, -post}'; + return; + } + + my $mode = 'UP_' . uc($opt_modes[0]); + + Irssi::signal_emit 'change prompt', $rest, $mode; + } else { + _error ('%_/prompt set%_ must specify a mode {-inner, -only, -pre, -post}'); + } +} + +sub refresh_if_me { + my ($channel, $nick) = @_; + + return unless $channel and $nick; + + my $server = Irssi::active_server; + my $window = Irssi::active_win; + + return unless $server and $window; + + my $my_chan = $window->{active}->{name}; + my $my_nick = $server->parse_special('$N'); + + return unless $my_chan and $my_nick; + + _debug_print "Chan: $channel->{name}, " + . "nick: $nick->{nick}, " + . "me: $my_nick, chan: $my_chan"; + + if ($my_chan eq $channel->{name} and $my_nick eq $nick->{nick}) { + uberprompt_refresh(); + } +} + +sub length_request_handler { + $emit_request = 1; + uberprompt_render_prompt(); + $emit_request = 0; +} + +sub reload_settings { + + $use_replaces = Irssi::settings_get_bool('uberprompt_use_replaces'); + $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug'); + + $init_callbacks = { + load => Irssi::settings_get_str('uberprompt_load_hook'), + unload => Irssi::settings_get_str('uberprompt_unload_hook'), + }; + + if (DEBUG) { + Irssi::signal_add 'prompt changed', 'debug_prompt_changed'; + } else { + Irssi::signal_remove 'prompt changed', 'debug_prompt_changed'; + } + + my $new = Irssi::settings_get_str('uberprompt_format'); + + if ($prompt_format ne $new) { + _debug_print("Updated prompt format"); + $prompt_format = $new; + $prompt_format =~ s/\$uber/\$\$uber/; + Irssi::abstracts_register(['uberprompt', $prompt_format]); + + $expando_vars = {}; + + # TODO: something clever here to check if we need to schedule + # an update timer or something, rather than just refreshing on + # every possible activity in init() + while ($prompt_format =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) { + _debug_print("Detected Irssi expando variable $1"); + my $var_name = substr $1, 1; # strip the $ + $expando_vars->{$var_name} = Irssi::parse_special($1); + } + } + + $new = Irssi::settings_get_str('uberprompt_format_empty'); + + if ($prompt_format_empty ne $new) { + _debug_print("Updated prompt format"); + $prompt_format_empty = $new; + $prompt_format_empty =~ s/\$uber/\$\$uber/; + Irssi::abstracts_register(['uberprompt_empty', $prompt_format_empty]); + + $expando_vars = {}; + + # TODO: something clever here to check if we need to schedule + # an update timer or something, rather than just refreshing on + # every possible activity in init() + while ($prompt_format_empty =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) { + _debug_print("Detected Irssi expando variable $1"); + my $var_name = substr $1, 1; # strip the $ + $expando_vars->{$var_name} = Irssi::parse_special($1); + } + } + + $trim_data = Irssi::settings_get_bool('uberprompt_trim_data'); +} + +sub debug_prompt_changed { + my ($text, $len) = @_; + + $text =~ s/%/%%/g; + + print "DEBUG_HANDLER: Prompt Changed to: \"$text\", length: $len"; +} + +sub change_prompt_handler { + my ($text, $pos) = @_; + + # fix for people who used prompt_info and hence the signal won't + # pass the second argument. + $pos = 'UP_INNER' unless defined $pos; + _debug_print("Got prompt change signal with: $text, $pos"); + + my ($changed_text, $changed_pos); + $changed_text = defined $prompt_data ? $prompt_data ne $text : 1; + $changed_pos = defined $prompt_data_pos ? $prompt_data_pos ne $pos : 1; + + $prompt_data = $text; + $prompt_data_pos = $pos; + + if ($changed_text || $changed_pos) { + _debug_print("Redrawing prompt"); + uberprompt_refresh(); + } +} + +sub _escape_prompt_special { + my $str = shift; + $str =~ s/\$/\$\$/g; + $str =~ s/\\/\\\\/g; + #$str =~ s/%/%%/g; + $str =~ s/{/\${lbrace}/g; + $str =~ s/}/\${rbrace}/g; + + return $str; +} + +sub uberprompt_render_prompt { + + my $window = Irssi::active_win; + my $prompt_arg = ''; + + # default prompt sbar arguments (from config) + if (scalar( () = $window->items )) { + $prompt_arg = '$[.15]{itemname}'; + } else { + $prompt_arg = '${winname}'; + } + + my $prompt = ''; # rendered content of the prompt. + my $theme = Irssi::current_theme; + + my $arg = $use_replaces ? 0 : Irssi::EXPAND_FLAG_IGNORE_REPLACES; + + if ($prompt_data && (!$trim_data || trim($prompt_data))) { + $prompt = $theme->format_expand("{uberprompt $prompt_arg}", $arg); + } + else { + $prompt = $theme->format_expand("{uberprompt_empty $prompt_arg}", $arg); + } + + if ($prompt_data_pos eq 'UP_ONLY') { + $prompt =~ s/\$\$uber//; # no need for recursive prompting, I hope. + + # TODO: only compute this if necessary? + my $prompt_nt = $prompt; + $prompt_nt =~ s/\s+$//; + + my $pdata_copy = $prompt_data; + + $pdata_copy =~ s/\$prompt_nt/$prompt_nt/; + $pdata_copy =~ s/\$prompt/$prompt/; + + $prompt = $pdata_copy; + + } elsif ($prompt_data_pos eq 'UP_INNER' && defined $prompt_data) { + + my $esc = _escape_prompt_special($prompt_data); + $esc = $trim_data ? trim($esc) : $esc; + $prompt =~ s/\$\$uber/$esc/; + + } else { + # remove the $uber marker + $prompt =~ s/\$\$uber//; + + # and add the additional text at the appropriate position. + if ($prompt_data_pos eq 'UP_PRE') { + $prompt = $prompt_data . $prompt; + } elsif ($prompt_data_pos eq 'UP_POST') { + $prompt .= $prompt_data; + } + } + + _debug_print("rendering with: $prompt"); + + + if (($prompt ne $prompt_last) or $emit_request) { + + # _debug_print("Emitting prompt changed signal"); + # my $exp = Irssi::current_theme()->format_expand($text, 0); + my $ps = Irssi::parse_special($prompt); + + Irssi::signal_emit('prompt changed', $ps, length($ps)); + $prompt_last = $prompt; + } + return $prompt; +} + +sub uberprompt_draw { + my ($sb_item, $get_size_only) = @_; + + my $prompt = uberprompt_render_prompt(); + + my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0); + _debug_print("redrawing with: $prompt"); + return $ret; +} + +sub uberprompt_refresh { + Irssi::statusbar_items_redraw('uberprompt'); +} + +sub replace_prompt_items { + # remove existing ones. + _debug_print("Removing original prompt"); + + _sbar_command('prompt', 'remove', 'prompt'); + _sbar_command('prompt', 'remove', 'prompt_empty'); + + # add the new one. + + _sbar_command('prompt', 'add', 'uberprompt', + qw/-alignment left -before input -priority '-1'/); + + _sbar_command('prompt', 'position', '100'); + + my $load_hook = $init_callbacks->{load}; + if (defined $load_hook and length $load_hook) { + eval { + Irssi::command($load_hook); + }; + if ($@) { + _error("Uberprompt user load-hook command ($load_hook) failed: $@"); + } + } + +} + +sub restore_prompt_items { + + _sbar_command('prompt', 'remove', 'uberprompt'); + + _debug_print("Restoring original prompt"); + + _sbar_command('prompt', 'reset'); + + my $unload_hook = $init_callbacks->{unload}; + + if (defined $unload_hook and length $unload_hook) { + eval { + Irssi::command($unload_hook); + }; + if ($@) { + _error("Uberprompt user unload-hook command ($unload_hook) failed: $@"); + } + } +} + +sub _sbar_command { + my ($bar, $cmd, $item, @args) = @_; + + my $args_str = join ' ', @args; + + $args_str .= ' ' if length $args_str && defined $item; + + my $command = sprintf 'STATUSBAR %s %s %s%s', + $bar, $cmd, $args_str, defined $item ? $item : ''; + + _debug_print("Running command: $command"); + Irssi::command($command); +} + +sub trim { + my $string = shift; + + $string =~ s/^\s*//; + $string =~ s/\s*$//; + + return $string; +} diff --git a/.config/irssi/scripts/autorun/vim_mode.pl b/.config/irssi/scripts/autorun/vim_mode.pl @@ -0,0 +1,3783 @@ +=pod + +=head1 NAME + +vim_mode.pl + +=head1 DESCRIPTION + +An Irssi script to emulate some of the vi(m) features for the Irssi inputline. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD vim_mode.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head2 DEPENDENCIES + +For proper :ex mode support, vim-mode requires the installation of F<uberprompt.pl> +Uberprompt can be downloaded from: + +L<https://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl> + +and follow the instructions at the top of that file for installation. + +If you don't need Ex-mode, you can run vim_mode.pl without the +uberprompt.pl script, but it is strongly recommended that you use it. + +=head3 Irssi requirements + +0.8.12 and above should work fine. However the following features are +disabled in irssi < 0.8.13: + +=over 4 + +=item * C<j> C<k> (only with count, they work fine without count in older versions) + +=item * C<gg>, C<G> + +=back + +It is intended to work with at Irssi 0.8.12 and later versions. However some +features are disabled in older versions (see below for details). + +Perl >= 5.8.1 is recommended for UTF-8 support (which can be disabled if +necessary). Please report bugs in older versions as well, we'll try to fix +them. Later versions of Perl are also faster, which is probably beneficial +to a script of this size and complexity. + +=head2 SETUP + +Vim Mode provides certain feedback to the user, such as the currently active +mode (command, insert, ex), and if switching buffers, which buffer(s) currently +match the search terms. + +There are two ways to go about displaying this information, as described in +the following sections: + +=head3 Statusbar Items + +Run the following command to add a statusbar item that shows which mode +you're in. + +C</statusbar window add vim_mode> + +And the following to let C<:b [str]> display a list of channels matching your +search string. + +C</statusbar window add vim_windows> + +B<Note:> Remember to C</save> after adding these statusbar items to make them +permanent. + +B<Note:> If you would rather have these statusbar items on your prompt +line rather than thte window statusbar, please follow these steps. + +For technical reasons, I<uberprompt> must occasionally call C</statusbar prompt +reset>, which will remove or deactivate any manually added items on the prompt +statusbar. To get around this, uberprompt provides two command hooks, +C<uberprompt_load_hook> and C<uberprompt_unload_hook>. Both of these settings +can contain one (or more, using C</EVAL>) commands to be executed when the prompt +is enabled and disabled, respectively. + +See the L<uberprompt documentation|https://github.com/shabble/irssi-scripts/blob/master/prompt_info/README.pod> for further details. + +For I<right-aligned> items (that is, after the input field: + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -after input -alignment right vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +For I<left-aligned> items (before the prompt): + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -before prompt -alignment left vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +If you wish to add both C<vim_mode> and C<vim_windows> items, replace steps 1 and 2 +above with the following (right-aligned): + +=over 4 + +=item 1 C</alias vm_add /^eval /^statusbar prompt add -after input -alightment right vim_mode ; /^statusbar prompt add -after input -alignment right vim_windows> + +=item 2 C</alias vm_del /^eval /^statusbar prompt remove vim_mode ; /^statusbar prompt remove vim_windows> + +=back + +and then complete stages 3 and 4 as above. Replace the C<-after> and C<-alignment> +to suit your location preferences. + +B<Note:> It is also possible to place the items between the prompt and input field, +by specifying C<-after prompt> or C<-before input> as appropriate. + +=head3 Expando Variables + +Vim mode augments the existing Irssi expando (automatic variables) with two +additional ones: C<$vim_cmd_mode> and C<$vim_wins>. + +C<$vim_cmd_mode> is the equivalent of the C<vim_mode> statusbar item, and +C<$vim_wins> is the counterpart of C<vim_windows>. + +They can be added to your theme, or inserted into your uberprompt using +a command such as: + +"C</set uberprompt_format [$vim_cmd_mode] $*$uber] >" + +=head3 FILE-BASED CONFIGURATION + +Additionally to the irssi settings described in L<settings|/SETTINGS>, vim_mode +can be configured through an external configuration file named "vim_moderc" +located in F<~/.irssi/vim_moderc>. If available it's loaded on startup and every +supported ex-command is run. Its syntax is similar to "vimrc". To (re)load it +while vim_mode is running use C<:so[urce]>. + +Currently Supported ex-commands: + +=over 4 + +=item * C<:map> + +=back + +=head1 USAGE + +The following section is divided into the different modes as supported by Vim itself: + +=head2 COMMAND MODE + +It supports most commonly used command mode features: + +=over 2 + +=item * Insert/Command mode. + +C<Esc> and C<Ctrl-C> enter command mode. C</set vim_mode_cmd_seq j> allows +to use C<jj> as Escape (any other character can be used as well). + +=item * Cursor motion: + +C<h l 0 ^ $ E<lt>SpaceE<gt> E<lt>BSE<gt> f t F T> + +=item * History motion: + +C<j k gg G> C<gg> moves to the oldest (first) history line. C<G> without a +count moves to the current input line, with a count it goes to the I<count-th> +history line (1 is the oldest). + +=item * Cursor word motion: + +C<w b ge e W gE B E> + +=item * Word objects (only the following work yet): + +C<aw aW> + +=item * Yank and paste: + + C<y p P> + +=item * Change and delete: + +C<c d> + +=item * Delete at cursor: + +C<x X> + +=item * Replace at cursor: + +C<r> + +=item * Insert mode: + +C<i a I A> + +=item * Switch case: + +C<~> + +=item * Repeat change: + +C<.> + +=item * Repeat + +C<ftFT: ; ,> + +=item * Registers: + +C<"a-"z "" "0 "* "+ "_> (black hole) + +=item * Line-wise shortcuts: + +C<dd cc yy> + +=item * Shortcuts: + +C<s S C D> + +=item * Scroll the scrollback buffer: + +C<Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B> + +=item * Switch to last active window: + +C<Ctrl-6/Ctrl-^> + +=item * Switch split windows: + +<Ctrl-W j Ctrl-W k> + +=item * Undo/Redo: + +C<u Ctrl-R> + +=back + +Counts and combinations work as well, e.g. C<d5fx> or C<3iabcE<lt>escE<gt>>. Counts also work with mapped ex-commands (see below), e.g. if you map C<gb> to do C<:bn>, then C<2gb> will switch to the second next buffer. Repeat also supports counts. + +=head3 REGISTERS + +=over 4 + +=item * Appending to register with C<"A-"Z> + +=item * C<""> is the default yank/delete register. + +=item * C<"0> contains the last yank (if no register was specified). + +=item * The special registers C<"* "+> both contain irssi's internal cut-buffer. + +=back + +=head2 INSERT MODE + +The following insert mode mappings are supported: + +=over 4 + +=item * Insert register content: Ctrl-R x (where x is the register to insert) + +=back + +=head2 EX-MODE + +Ex-mode (activated by C<:> in command mode) supports the following commands: + +=over 4 + +=item * Command History: + +C<E<lt>uparrowE<gt>> - cycle backwards through history + +C<E<lt>downarrowE<gt>> - cycle forwards through history + +C<:eh> - show ex history + +=item * Switching buffers: + +C<:[N]b [N]> - switch to channel number + +C<:b#> - switch to last channel + +C<:b> E<lt>partial-channel-nameE<gt> + +C<:b> <partial-server>/<partial-channel> + +C<:buffer {args}> (same as C<:b>) + +C<:[N]bn[ext] [N]> - switch to next window + +C<:[N]bp[rev] [N]> - switch to previous window + +=item * Close window: + +C<:[N]bd[elete] [N]> + +=item * Display windows: + +C<:ls>, C<:buffers> + +=item * Display registers: + +C<:reg[isters] {args}>, C<:di[splay] {args}> + +=item * Display undolist: + +C<:undol[ist]> (mostly used for debugging) + +=item * Source files: + +C<:so[urce]> - only sources vim_moderc at the moment, + F<{file}> not supported + +=item * Mappings: + +C<:map> - display custom mappings + +=item * Saving mappings: + +C<:mkv[imrc][!]> - like in Vim, but [file] not supported + +=item * Substitution: + +C<:s///> - I<i> and I<g> are supported as flags, only C<///> can be used as +eparator, and it uses Perl regex syntax instead of Vim syntax. + +=item * Settings: + +C<:se[t]> - display all options + +C<:se[t] {option}> - display all matching options + +C<:se[t] {option} {value}> - change option to value + +=back + +=head3 MAPPINGS + +=head4 Commands + +=over 4 + +=item * C<:map {lhs}> - display mappings starting with {lhs} + +=item * C<:map {lhs} {rhs}> - add mapping + +=item * C<:unm[ap] {lhs}> - remove custom mapping + +=back + +=head4 Parameters + +I<{lhs}> is the key combination to be mapped, I<{rhs}> the target. The +C<E<lt>E<gt>> notation is used + +(e.g. C<E<lt>C-FE<gt>> is I<Ctrl-F>), case is ignored. + Supported C<E<lt>E<gt>> keys are: + +=over 4 + +=item * C<E<lt>C-AE<gt>> - C<E<lt>C-ZE<gt>>, + +=item * C<E<lt>C-^E<gt>>, C<E<lt>C-6E<gt>> + +=item * C<E<lt>SpaceE<gt>> + +=item * C<E<lt>CRE<gt>> - Enter + +=item * C<E<lt>BSE<gt>> - Backspace + +=item * C<E<lt>NopE<gt>> - No-op (Do Nothing). + +=back + +Mapping ex-mode and irssi commands is supported. When mapping ex-mode commands +the trailing C<E<lt>CrE<gt>> is not necessary. Only default mappings can be used +in I<{rhs}>. + +Examples: + +=over 4 + +=item * C<:map w W> - to remap w to work like W + +=item * C<:map gb :bnext> - to map gb to call :bnext + +=item * C<:map gB :bprev> + +=item * C<:map g1 :b 1> - to map g1 to switch to buffer 1 + +=item * C<:map gb :b> - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 + +=item * C<:map E<lt>C-LE<gt> /clear> - map Ctrl-L to irssi command /clear + +=item * C<:map E<lt>C-GE<gt> /window goto 1> + +=item * C<:map E<lt>C-EE<gt> <Nop>> - disable E<lt>C-EE<gt>, it does nothing now + +=item * C<:unmap E<lt>C-EE<gt>> - restore default behavior of C<E<lt>C-EE<gt>> +after disabling it + +=back + +Note that you must use C</> for irssi commands (like C</clear>), the current value +of Irssi's cmdchars does B<not> work! This is necessary do differentiate between +ex-commands and irssi commands. + +=head2 SETTINGS + +The settings are stored as irssi settings and can be set using C</set> as usual +(prepend C<vim_mode_> to setting name) or using the C<:set> ex-command. The +following settings are available: + +=over 4 + +=item * utf8 - Support UTF-8 characters, boolean, default on + +=item * debug - Enable debug output, boolean, default off + +=item * cmd_seq - Char that when double-pressed simulates C<E<lt>EscE<gt>>, string, default '' (disabled) + +=item * start_cmd - Start every line in command mode, boolean, default off + +=item * max_undo_lines - Sze of the undo buffer. Integer, default 50 items. + +=item * ex_history_size - Number of items stored in the ex-mode history. Integer, default 100. + +=item * prompt_leading_space - Ddetermines whether ex mode prepends a space to the displayed input. Boolean, default on + +=back + +In contrast to irssi's settings, C<:set> accepts 0 and 1 as values for boolean +settings, but only vim_mode's settings can be set/displayed. + +Examples: + + :set cmd_seq=j # set cmd_seq to j + :set cmd_seq= # disable cmd_seq + :set debug=on # enable debug + :set debug=off # disable debug + +=head1 KNOWN ISSUES + +If you use tmux and want to use <esc> to exit insert mode you might want to +reduce the escape-time for a better experience (500 is the default value): + + set -s escape-time 100 + +A similar problem exist in GNU screen, the following settings in ~/.screenrc +fix it (thanks to jsbronder for reporting the screen issue and fix): + + maptimeout 0 + defc1 off + +defc1 might not be necessary. + +=head1 SUPPORT + +Any behavior different from Vim (unless explicitly documented) should be +considered a bug and reported. + +B<NOTE:> This script is still under heavy development, and there may be bugs. +Please submit reproducible sequences to the bug-tracker at: +L<http://github.com/shabble/irssi-scripts/issues/new> + +or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +Copyright E<copy> 2010-2011 Simon Ruderich C<E<lt>simon@ruderich.orgE<gt>> + +=head1 THANKS + +Particular thanks go to + +=over 4 + +=item * estragib: a lot of testing and many bug reports and feature requests + +=item * iaj: testing + +=item * tmr: explaining how various bits of vim work + +=back + +as well as the rest of C<#irssi> and C<#irssi_vim> on Freenode IRC. + +=head1 LICENCE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=head1 BUGS + +=over 4 + +=item * + +count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does + +=item * + +mapping an incomplete ex-command doesn't open the ex-mode with the partial +command (e.g. C<:map gb :b> causes an error instead of opening the ex-mode and +displaying C<:bE<lt>cursorE<gt>>) + +=item * + + undo/redo cursor positions are mostly wrong + +=back + +=head1 TODO + +=over 4 + +=item * + +Make sure the input line is empty when entering ex mode. Stuff hanging around +just looks silly. + +=item * + +History: + +=over 4 + +=item * + + C< * /,?,n,N> to search through history (like rl_history_search.pl) + +=back + +=item * + +Window switching (C<:b>) + +=over 4 + +=item * + +Tab completion of window(-item) names + +=item * + +non-sequential matches(?) + +=back + +=back + +See also the TODO file at +L<github|https://github.com/shabble/irssi-scripts/blob/master/vim-mode/TODO> for +many many more things. + +=head2 WONTFIX + +Things we're not ever likely to do: + +=over 4 + +=item * Macros + +=back + +=cut + +use strict; +use warnings; + +use Encode; +use List::Util; + +use Irssi; +use Irssi::TextUI; # for sbar_items_redraw +use Irssi::Irc; # necessary for 0.8.14 + + + +our $VERSION = "1.1.0"; +our %IRSSI = + ( + authors => "Tom Feist (shabble), Simon Ruderich (rudi_s)", + contact => 'shabble+irssi@metavore.org, ' + . 'shabble@#irssi/Freenode, simon@ruderich.org' + . 'rudi_s@#irssi/Freenode', + name => "vim_mode", + description => "Give Irssi Vim-like commands for editing the inputline", + license => "MIT", + changed => "3/2/2012" + ); + + +# CONSTANTS + +# command mode +sub M_CMD () { 1 } +# insert mode +sub M_INS () { 0 } +# extended mode (after a :?) +sub M_EX () { 2 } + +# operator command +sub C_OPERATOR () { 0 } +# normal command, no special handling necessary +sub C_NORMAL () { 1 } +# command taking another key as argument +sub C_NEEDSKEY () { 2 } +# text-object command (i a) +sub C_TEXTOBJECT () { 3 } +# commands entering insert mode +sub C_INSERT () { 4 } +# ex-mode commands +sub C_EX () { 5 } +# irssi commands +sub C_IRSSI () { 6 } +# does nothing +sub C_NOP () { 7 } + +# setting types, match irssi types as they are stored as irssi settings +sub S_BOOL () { 0 } +sub S_INT () { 1 } +sub S_STR () { 2 } +sub S_TIME () { 3 } + +# word and non-word regex, keep in sync with setup_changed()! +my $word = qr/[\w_]/o; +my $non_word = qr/[^\w_\s]/o; + +# COMMANDS + +# All available commands in command mode, they are stored with a char as key, +# but this is not necessarily the key the command is currently mapped to. +my $commands + = { + # operators + c => { char => 'c', func => \&cmd_operator_c, type => C_OPERATOR, + repeatable => 1 }, + d => { char => 'd', func => \&cmd_operator_d, type => C_OPERATOR, + repeatable => 1 }, + y => { char => 'y', func => \&cmd_operator_y, type => C_OPERATOR, + repeatable => 1 }, + + # arrow like movement + h => { char => 'h', func => \&cmd_h, type => C_NORMAL }, + l => { char => 'l', func => \&cmd_l, type => C_NORMAL }, + "\x08" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL }, + "\x7F" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL }, + ' ' => { char => '<Space>', func => \&cmd_l, type => C_NORMAL }, + # history movement + j => { char => 'j', func => \&cmd_j, type => C_NORMAL, + no_operator => 1 }, + k => { char => 'k', func => \&cmd_k, type => C_NORMAL, + no_operator => 1 }, + gg => { char => 'gg', func => \&cmd_gg, type => C_NORMAL, + no_operator => 1 }, + G => { char => 'G', func => \&cmd_G, type => C_NORMAL, + needs_count => 1, no_operator => 1 }, + # char movement, take an additional parameter and use $movement + f => { char => 'f', func => \&cmd_f, type => C_NEEDSKEY, + selection_needs_move_right => 1 }, + t => { char => 't', func => \&cmd_t, type => C_NEEDSKEY, + selection_needs_move_right => 1 }, + F => { char => 'F', func => \&cmd_F, type => C_NEEDSKEY }, + T => { char => 'T', func => \&cmd_T, type => C_NEEDSKEY }, + ';' => { char => ';', func => \&cmd_semicolon, type => C_NORMAL }, + ',' => { char => ',', func => \&cmd_comma, type => C_NORMAL }, + # word movement + w => { char => 'w', func => \&cmd_w, type => C_NORMAL }, + b => { char => 'b', func => \&cmd_b, type => C_NORMAL }, + e => { char => 'e', func => \&cmd_e, type => C_NORMAL, + selection_needs_move_right => 1 }, + ge => { char => 'ge', func => \&cmd_ge, type => C_NORMAL, + selection_needs_move_right => 1 }, + W => { char => 'W', func => \&cmd_W, type => C_NORMAL }, + B => { char => 'B', func => \&cmd_B, type => C_NORMAL }, + E => { char => 'E', func => \&cmd_E, type => C_NORMAL }, + gE => { char => 'gE', func => \&cmd_gE, type => C_NORMAL, + selection_needs_move_right => 1 }, + # text-objects, leading _ means can't be mapped! + _i => { char => 'i', func => \&cmd__i, type => C_TEXTOBJECT }, + _a => { char => 'a', func => \&cmd__a, type => C_TEXTOBJECT }, + # line movement + '0' => { char => '0', func => \&cmd_0, type => C_NORMAL }, + '^' => { char => '^', func => \&cmd_caret, type => C_NORMAL }, + '$' => { char => '$', func => \&cmd_dollar, type => C_NORMAL }, + # delete chars + x => { char => 'x', func => \&cmd_x, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + X => { char => 'X', func => \&cmd_X, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + # C_NORMAL is correct, operator c takes care of insert mode + s => { char => 's', func => \&cmd_s, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + # C_NORMAL is correct, operator c takes care of insert mode + S => { char => 'S', func => \&cmd_S, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + # insert mode + i => { char => 'i', func => \&cmd_i, type => C_INSERT, + repeatable => 1, no_operator => 1 }, + I => { char => 'I', func => \&cmd_I, type => C_INSERT, + repeatable => 1, no_operator => 1 }, + a => { char => 'a', func => \&cmd_a, type => C_INSERT, + repeatable => 1, no_operator => 1 }, + A => { char => 'A', func => \&cmd_A, type => C_INSERT, + repeatable => 1, no_operator => 1 }, + # replace + r => { char => 'r', func => \&cmd_r, type => C_NEEDSKEY, + repeatable => 1, no_operator => 1 }, + # paste + p => { char => 'p', func => \&cmd_p, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + P => { char => 'P', func => \&cmd_P, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + # to end of line + C => { char => 'C', func => \&cmd_C, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + D => { char => 'D', func => \&cmd_D, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + # scrolling + "\x05" => { char => '<C-E>', func => \&cmd_ctrl_d, type => C_NORMAL, + no_operator => 1 }, + "\x04" => { char => '<C-D>', func => \&cmd_ctrl_d, type => C_NORMAL, + needs_count => 1, no_operator => 1 }, + "\x19" => { char => '<C-Y>', func => \&cmd_ctrl_u, type => C_NORMAL, + no_operator => 1 }, + "\x15" => { char => '<C-U>', func => \&cmd_ctrl_u, type => C_NORMAL, + needs_count => 1, no_operator => 1 }, + "\x06" => { char => '<C-F>', func => \&cmd_ctrl_f, type => C_NORMAL, + no_operator => 1 }, + "\x02" => { char => '<C-B>', func => \&cmd_ctrl_b, type => C_NORMAL, + no_operator => 1 }, + # window switching + "\x17j" => { char => '<C-W>j', func => \&cmd_ctrl_wj, type => C_NORMAL, + no_operator => 1 }, + "\x17k" => { char => '<C-W>k', func => \&cmd_ctrl_wk, type => C_NORMAL, + no_operator => 1 }, + "\x1e" => { char => '<C-^>', func => \&cmd_ctrl_6, type => C_NORMAL, + no_operator => 1 }, + # misc + '~' => { char => '~', func => \&cmd_tilde, type => C_NORMAL, + repeatable => 1, no_operator => 1 }, + '"' => { char => '"', func => \&cmd_register, type => C_NEEDSKEY, + no_operator => 1 }, + '.' => { char => '.', type => C_NORMAL, repeatable => 1, + no_operator => 1 }, + ':' => { char => ':', type => C_NORMAL }, + "\n" => { char => '<CR>', type => C_NORMAL }, # return + # undo + 'u' => { char => 'u', func => \&cmd_undo, type => C_NORMAL, + no_operator => 1 }, + "\x12" => { char => '<C-R>', func => \&cmd_redo, type => C_NORMAL, + no_operator => 1 }, + }; + +# All available commands in Ex-Mode. +my $commands_ex + = { + # arrow keys - not actually used, see handle_input_buffer() + # TODO: make these work. + "\e[A" => { char => ':exprev', func => \&ex_history_back, + type => C_EX }, + "\e[B" => { char => ':exnext', func => \&ex_history_fwd, + type => C_EX }, + + # normal Ex mode commands. + eh => { char => ':exhist', func => \&ex_history_show, + type => C_EX }, + s => { char => ':s', func => \&ex_substitute, + type => C_EX }, + bnext => { char => ':bnext', func => \&ex_bnext, + type => C_EX, uses_count => 1 }, + bn => { char => ':bn', func => \&ex_bnext, + type => C_EX, uses_count => 1 }, + bprev => { char => ':bprev', func => \&ex_bprev, + type => C_EX, uses_count => 1 }, + bp => { char => ':bp', func => \&ex_bprev, + type => C_EX, uses_count => 1 }, + bdelete => { char => ':bdelete', func => \&ex_bdelete, + type => C_EX, uses_count => 1 }, + bd => { char => ':bd', func => \&ex_bdelete, + type => C_EX, uses_count => 1 }, + buffer => { char => ':buffer', func => \&ex_buffer, + type => C_EX, uses_count => 1 }, + b => { char => ':b', func => \&ex_buffer, + type => C_EX, uses_count => 1 }, + registers => { char => ':registers', func => \&ex_registers, + type => C_EX }, + reg => { char => ':reg', func => \&ex_registers, + type => C_EX }, + display => { char => ':display', func => \&ex_registers, + type => C_EX }, + di => { char => ':di', func => \&ex_registers, + type => C_EX }, + buffers => { char => ':buffer', func => \&ex_buffers, + type => C_EX }, + ls => { char => ':ls', func => \&ex_buffers, + type => C_EX }, + undolist => { char => ':undolist', func => \&ex_undolist, + type => C_EX }, + undol => { char => ':undol', func => \&ex_undolist, + type => C_EX }, + map => { char => ':map', func => \&ex_map, + type => C_EX }, + unmap => { char => ':unmap', func => \&ex_unmap, + type => C_EX }, + unm => { char => ':unm', func => \&ex_unmap, + type => C_EX }, + source => { char => ':source', func => \&ex_source, + type => C_EX }, + so => { char => ':so', func => \&ex_source, + type => C_EX }, + mkvimrc => { char => ':mkvimrc', func => \&ex_mkvimrc, + type => C_EX }, + mkv => { char => ':mkv', func => \&ex_mkvimrc, + type => C_EX }, + se => { char => ':se', func => \&ex_set, + type => C_EX }, + set => { char => ':set', func => \&ex_set, + type => C_EX }, + itemnext => { char => ':itemnext', func => \&ex_item_next, + type => C_EX }, + inext => { char => ':itemnext', func => \&ex_item_next, + type => C_EX }, + itemprev => { char => ':itemprev', func => \&ex_item_prev, + type => C_EX }, + iprev => { char => ':itemprev', func => \&ex_item_prev, + type => C_EX }, + servernext => { char => ':servernext', func => \&ex_server_next, + type => C_EX }, + snext => { char => ':servernext', func => \&ex_server_next, + type => C_EX }, + serverprev => { char => ':serverprev', func => \&ex_server_prev, + type => C_EX }, + sprev => { char => ':serverprev', func => \&ex_server_prev, + type => C_EX }, + + }; + +# MAPPINGS + +# default command mode mappings +my $maps = {}; + +# current imap still pending (first character entered) +my $imap = undef; + +# maps for insert mode +my $imaps + = { + # CTRL-R, insert register + "\x12" => { map => undef, func => \&insert_ctrl_r }, + }; + + +# GLOBAL VARIABLES + +# all vim_mode settings, must be enabled in vim_mode_init() before usage +my $settings + = { + # print debug output + debug => { type => S_BOOL, value => 0 }, + # use UTF-8 internally for string calculations/manipulations + utf8 => { type => S_BOOL, value => 1 }, + # esc-shortcut in insert mode + cmd_seq => { type => S_STR, value => '' }, + # start every line in command mode + start_cmd => { type => S_BOOL, value => 0 }, + # not used yet + max_undo_lines => { type => S_INT, value => 50 }, + # size of history buffer for Ex mode. + ex_history_size => { type => S_INT, value => 100 }, + # prompt_leading_space + prompt_leading_space => { type => S_BOOL, value => 1 }, + # <Leader> value for prepending to commands. + map_leader => { type => S_STR, value => '\\' }, + # timeout for keys following esc. In milliseconds. + esc_buf_timeout => { type => S_TIME, value => '10ms' }, + + }; + +sub DEBUG { $settings->{debug}->{value} } + +# buffer to keep track of the last N keystrokes, used for Esc detection and +# insert mode mappings +my @input_buf; +my $input_buf_timer; +my $input_buf_enabled = 0; + +# insert mode repeat buffer, used to repeat (.) last insert +my @insert_buf; + +# flag to allow us to emulate keystrokes without re-intercepting them +my $should_ignore = 0; + +# ex mode buffer +my @ex_buf; + +# ex mode history storage. +my @ex_history; +my $ex_history_index = 0; + +# we are waiting for another mapped key (e.g. g pressed, but there are +# multiple mappings like gg gE etc.) +my $pending_map = undef; + +# for commands like 10x +my $numeric_prefix = undef; +# current operator as $command hash +my $operator = undef; +# vi movements, only used when a movement needs more than one key (like f t). +my $movement = undef; +# last vi command, used by . +my $last + = { + 'cmd' => $commands->{i}, # = i to support . when loading the script + 'numeric_prefix' => undef, + 'operator' => undef, + 'movement' => undef, + 'register' => '"', + }; +# last ftFT movement, used by ; and , +my $last_ftFT + = { + type => undef, # f, t, F or T + char => undef, + }; + +# what Vi mode we're in. We start in insert mode. +my $mode = M_INS; + +# current active register +my $register = '"'; + +# vi registers +my $registers + = { + '"' => '', # default register + '0' => '', # yank register + '+' => '', # contains irssi's cut buffer + '*' => '', # same + '_' => '', # black hole register, always empty + }; + +# index into the history list (for j,k) +my $history_index = undef; +# current line, necessary for j,k or the current input line gets destroyed +my $history_input = undef; +# position in input line +my $history_pos = 0; + +# Undo/redo buffer. + +my @undo_buffer; +my $undo_index = undef; + +# tab completion state vars + +my @tab_candidates; +my $completion_active = 0; +my $completion_string = ''; + +sub script_is_loaded { + return exists($Irssi::Script::{shift(@_) . '::'}); +} + + + + +# INSERT MODE COMMANDS + +sub insert_ctrl_r { + my ($key) = @_; + _debug("ctrl-r called"); + my $char = chr($key); + _debug("ctrl-r called with $char"); + + return if not defined $registers->{$char} or $registers->{$char} eq ''; + + my $pos = _insert_at_position($registers->{$char}, 1, _input_pos()); + _input_pos($pos + 1); +} + + +# COMMAND MODE OPERATORS + +sub cmd_operator_c { + my ($old_pos, $new_pos, $move_cmd, $repeat) = @_; + + # Changing a word or WORD doesn't delete the last space before a word, but + # not if we are on that whitespace before the word. + if ($move_cmd and ($move_cmd == $commands->{w} or + $move_cmd == $commands->{W})) { + my $input = _input(); + if ($new_pos - $old_pos > 1 and + substr($input, $new_pos - 1, 1) =~ /\s/) { + $new_pos--; + } + } + + cmd_operator_d($old_pos, $new_pos, $move_cmd, $repeat, 1); + + if (!$repeat) { + _update_mode(M_INS); + } else { + my $pos = _input_pos(); + $pos = _insert_buffer(1, $pos); + _input_pos($pos); + } +} + +sub cmd_operator_d { + my ($old_pos, $new_pos, $move_cmd, $repeat, $change) = @_; + + my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move_cmd); + + # Remove the selected string from the input. + my $input = _input(); + my $string = substr $input, $pos, $length, ''; + if ($register =~ /[A-Z]/) { + $registers->{lc $register} .= $string; + print "Deleted into $register: ", $registers->{lc $register} if DEBUG; + } else { + $registers->{$register} = $string; + print "Deleted into $register: ", $registers->{$register} if DEBUG; + } + _input($input); + + # Prevent moving after the text when we delete the last character. But not + # when changing (C). + $pos-- if $pos == length($input) and !$change; + + _input_pos($pos); +} + +sub cmd_operator_y { + my ($old_pos, $new_pos, $move_cmd, $repeat) = @_; + + my ($pos, $length) = _get_pos_and_length($old_pos, $new_pos, $move_cmd); + + # Extract the selected string and put it in the " register. + my $input = _input(); + my $string = substr $input, $pos, $length; + if ($register =~ /[A-Z]/) { + $registers->{lc $register} .= $string; + print "Yanked into $register: ", $registers->{lc $register} if DEBUG; + } else { + $registers->{$register} = $string; + print "Yanked into $register: ", $registers->{$register} if DEBUG; + if ($register eq '"') { + $registers->{0} = $string; + print "Yanked into 0: ", $registers->{0} if DEBUG; + } + } + + # Always move to the lower position. + if ($old_pos > $new_pos) { + _input_pos($new_pos); + } else { + _input_pos($old_pos); + } +} + +sub _get_pos_and_length { + my ($old_pos, $new_pos, $move_cmd) = @_; + + my $length = $new_pos - $old_pos; + # We need a positive length and $old_pos must be smaller. + if ($length < 0) { + $old_pos = $new_pos; + $length *= -1; + } + + # Some commands don't move one character after the deletion area which is + # necessary for all commands moving to the right. Fix it. + if ($move_cmd->{selection_needs_move_right}) { + $length += 1; + } + + return ($old_pos, $length); +} + +# COMMAND MODE COMMANDS + +sub cmd_h { + my ($count, $pos, $repeat) = @_; + + $pos -= $count; + $pos = 0 if $pos < 0; + return (undef, $pos); +} + +sub cmd_l { + my ($count, $pos, $repeat) = @_; + + my $length = _input_len(); + $pos += $count; + $pos = _fix_input_pos($pos, $length); + return (undef, $pos); +} + +# later history (down) +sub cmd_j { + my ($count, $pos, $repeat) = @_; + + if (Irssi::version < 20090117) { + # simulate a down-arrow + _emulate_keystrokes(0x1b, 0x5b, 0x42); + return (undef, undef); + } + + my @history = Irssi::active_win->get_history_lines(); + + if (defined $history_index) { + $history_index += $count; + print "History Index: $history_index" if DEBUG; + # Prevent destroying the current input when pressing j after entering + # command mode. Not exactly like in default irssi, but simplest solution + # (and S can be used to clear the input line fast, which is what <down> + # does in plain irssi). + } else { + return (undef, undef); + } + + if ($history_index > $#history) { + # Restore the input line. + _input($history_input); + _input_pos($history_pos); + $history_index = $#history + 1; + } elsif ($history_index >= 0) { + my $history = $history[$history_index]; + # History is not in UTF-8! + if ($settings->{utf8}->{value}) { + $history = decode_utf8($history); + } + _input($history); + _input_pos(0); + } + return (undef, undef); +} + +# earlier history (up) +sub cmd_k { + my ($count, $pos, $repeat) = @_; + + if (Irssi::version < 20090117) { + # simulate an up-arrow + _emulate_keystrokes(0x1b, 0x5b, 0x41); + return (undef, undef); + } + + my @history = Irssi::active_win->get_history_lines(); + + if (defined $history_index) { + $history_index -= $count; + $history_index = 0 if $history_index < 0; + } else { + $history_index = $#history; + $history_input = _input(); + $history_pos = _input_pos(); + } + print "History Index: $history_index" if DEBUG; + if ($history_index >= 0) { + my $history = $history[$history_index]; + # History is not in UTF-8! + if ($settings->{utf8}->{value}) { + $history = decode_utf8($history); + } + _input($history); + _input_pos(0); + } + return (undef, undef); +} + +sub cmd_G { + my ($count, $pos, $repeat) = @_; + + if (Irssi::version < 20090117) { + _warn("G and gg not supported in irssi < 0.8.13"); + return; + } + + my @history = Irssi::active_win->get_history_lines(); + + # Go to the current input line if no count was given or it's too big. + if (not $count or $count - 1 >= scalar @history) { + if (defined $history_input and defined $history_pos) { + _input($history_input); + _input_pos($history_pos); + $history_index = undef; + } + return; + } else { + # Save input line so it doesn't get lost. + if (not defined $history_index) { + $history_input = _input(); + $history_pos = _input_pos(); + } + $history_index = $count - 1; + } + + my $history = $history[$history_index]; + # History is not in UTF-8! + if ($settings->{utf8}->{value}) { + $history = decode_utf8($history); + } + _input($history); + _input_pos(0); + + return (undef, undef); +} + +sub cmd_gg { + my ($count, $pos, $repeat) = @_; + + return cmd_G(1, $pos, $repeat); +} + +sub cmd_f { + my ($count, $pos, $repeat, $char) = @_; + + $pos = _next_occurrence(_input(), $char, $count, $pos); + + $last_ftFT = { type => 'f', char => $char }; + return (undef, $pos); +} + +sub cmd_t { + my ($count, $pos, $repeat, $char) = @_; + + $pos = _next_occurrence(_input(), $char, $count, $pos); + if (defined $pos) { + $pos--; + } + + $last_ftFT = { type => 't', char => $char }; + return (undef, $pos); +} + +sub cmd_F { + my ($count, $pos, $repeat, $char) = @_; + + my $input = reverse _input(); + $pos = _next_occurrence($input, $char, $count, length($input) - $pos - 1); + if (defined $pos) { + $pos = length($input) - $pos - 1; + } + + $last_ftFT = { type => 'F', char => $char }; + return (undef, $pos); +} + +sub cmd_T { + my ($count, $pos, $repeat, $char) = @_; + + my $input = reverse _input(); + $pos = _next_occurrence($input, $char, $count, length($input) - $pos - 1); + if (defined $pos) { + $pos = length($input) - $pos - 1 + 1; + } + + $last_ftFT = { type => 'T', char => $char }; + return (undef, $pos); +} + +# Find $count-th next occurrence of $char. +sub _next_occurrence { + my ($input, $char, $count, $pos) = @_; + + while ($count-- > 0) { + $pos = index $input, $char, $pos + 1; + if ($pos == -1) { + return undef; + } + } + return $pos; +} + +sub cmd_semicolon { + my ($count, $pos, $repeat) = @_; + + return (undef, undef) if not defined $last_ftFT->{type}; + + (undef, $pos) + = $commands->{$last_ftFT->{type}} + ->{func}($count, $pos, $repeat, $last_ftFT->{char}); + return (undef, $pos); +} + +sub cmd_comma { + my ($count, $pos, $repeat) = @_; + + return (undef, undef) if not defined $last_ftFT->{type}; + + # Change direction. + my $save = $last_ftFT->{type}; + my $type = $save; + $type =~ tr/ftFT/FTft/; + + (undef, $pos) + = $commands->{$type} + ->{func}($count, $pos, $repeat, $last_ftFT->{char}); + # Restore type as the move functions overwrites it. + $last_ftFT->{type} = $save; + return (undef, $pos); +} + +sub cmd_w { + my ($count, $pos, $repeat) = @_; + + my $input = _input(); + $pos = _beginning_of_word($input, $count, $pos); + $pos = _fix_input_pos($pos, length $input); + return (undef, $pos); +} + +sub cmd_b { + my ($count, $pos, $repeat) = @_; + + my $input = reverse _input(); + $pos = length($input) - $pos - 1; + $pos = 0 if ($pos < 0); + + $pos = _end_of_word($input, $count, $pos); + $pos = length($input) - $pos - 1; + $pos = 0 if ($pos < 0); + return (undef, $pos); +} + +sub cmd_e { + my ($count, $pos, $repeat) = @_; + + my $input = _input(); + $pos = _end_of_word($input, $count, $pos); + $pos = _fix_input_pos($pos, length $input); + return (undef, $pos); +} + +sub cmd_ge { + my ($count, $pos, $repeat, $char) = @_; + + my $input = reverse _input(); + $pos = length($input) - $pos - 1; + $pos = 0 if ($pos < 0); + + $pos = _beginning_of_word($input, $count, $pos); + $pos = length($input) - $pos - 1; + $pos = 0 if ($pos < 0); + + return (undef, $pos); +} + +# Go to the beginning of $count-th word, like vi's w. +sub _beginning_of_word { + my ($input, $count, $pos) = @_; + + while ($count-- > 0) { + # Go to end of next word/non-word. + if (substr($input, $pos) =~ /^$word+/ or + substr($input, $pos) =~ /^$non_word+/) { + $pos += $+[0]; + } + # And skip over any whitespace if necessary. This also happens when + # we're inside whitespace. + if (substr($input, $pos) =~ /^\s+/) { + $pos += $+[0]; + } + } + + return $pos; +} + +# Go to the end of $count-th word, like vi's e. +sub _end_of_word { + my ($input, $count, $pos) = @_; + + while ($count-- > 0 and length($input) > $pos) { + my $skipped = 0; + # Skip over whitespace if in the middle of it or directly after the + # current word/non-word. + if (substr($input, $pos + 1) =~ /^\s+/) { + $pos += $+[0] + 1; + $skipped = 1; + } + elsif (substr($input, $pos) =~ /^\s+/) { + $pos += $+[0]; + $skipped = 1; + } + # We are inside a word/non-word, skip to the end of it. + if (substr($input, $pos) =~ /^$word{2,}/ or + substr($input, $pos) =~ /^$non_word{2,}/) { + $pos += $+[0] - 1; + # We are the border of word/non-word. Skip to the end of the next one. + # But not if we've already jumped over whitespace because there could + # be only one word/non-word char after the whitespace. + } elsif (!$skipped and (substr($input, $pos) =~ /^$word($non_word+)/ or + substr($input, $pos) =~ /^$non_word($word+)/)) { + $pos += $+[0] - 1; + } + } + + # Necessary for correct deletion at the end of the line. + if (length $input == $pos + 1) { + $pos++; + } + + return $pos; +} + +sub cmd_W { + my ($count, $pos, $repeat) = @_; + + my $input = _input(); + $pos = _beginning_of_WORD($input, $count, $pos); + $pos = _fix_input_pos($pos, length $input); + return (undef, $pos); +} + +sub cmd_B { + my ($count, $pos, $repeat) = @_; + + my $input = reverse _input(); + $pos = _end_of_WORD($input, $count, length($input) - $pos - 1); + if ($pos == -1) { + return cmd_0(); + } else { + return (undef, length($input) - $pos - 1); + } +} + +sub cmd_E { + my ($count, $pos, $repeat) = @_; + + $pos = _end_of_WORD(_input(), $count, $pos); + if ($pos == -1) { + return cmd_dollar(); + } else { + return (undef, $pos); + } +} + +sub cmd_gE { + my ($count, $pos, $repeat, $char) = @_; + + my $input = reverse _input(); + $pos = _beginning_of_WORD($input, $count, length($input) - $pos - 1); + if ($pos == -1 or length($input) - $pos - 1 == -1) { + return cmd_0(); + } else { + $pos = length($input) - $pos - 1; + } + + return (undef, $pos); +} + +# Go to beginning of $count-th WORD, like vi's W. +sub _beginning_of_WORD { + my ($input, $count, $pos) = @_; + + # Necessary for correct movement between two words with only one + # whitespace. + if (substr($input, $pos) =~ /^\s\S/) { + $pos++; + $count--; + } + + while ($count-- > 0 and length($input) > $pos) { + if (substr($input, $pos + 1) !~ /\s+/) { + return length($input); + } + $pos += $+[0] + 1; + } + + return $pos; +} + +# Go to end of $count-th WORD, like vi's E. +sub _end_of_WORD { + my ($input, $count, $pos) = @_; + + return $pos if $pos >= length($input); + + # We are inside a WORD, skip to the end of it. + if (substr($input, $pos + 1) =~ /^\S+(\s)/) { + $pos += $-[1]; + $count--; + } + + while ($count-- > 0) { + if (substr($input, $pos) !~ /\s+\S+(\s+)/) { + return -1; + } + $pos += $-[1] - 1; + } + return $pos; +} + +sub cmd__i { + my ($count, $pos, $repeat, $char) = @_; + + _warn("i_ not implemented yet"); + return (undef, undef); +} + +sub cmd__a { + my ($count, $pos, $repeat, $char) = @_; + + my $cur_pos; + my $input = _input(); + + # aw and aW + if ($char eq 'w' or $char eq 'W') { + while ($count-- > 0 and length($input) > $pos) { + if (substr($input, $pos, 1) =~ /\s/) { + # Any whitespace before the word/WORD must be removed. + if (not defined $cur_pos) { + $cur_pos = _find_regex_before($input, '\S', $pos, 0); + if ($cur_pos < 0) { + $cur_pos = 0; + } else { + $cur_pos++; + } + } + # Move before the word/WORD. + if (substr($input, $pos + 1) =~ /^\s+/) { + $pos += $+[0]; + } + # And delete the word. + if ($char eq 'w') { + if (substr($input, $pos) =~ /^\s($word+|$non_word+)/) { + $pos += $+[0]; + } else { + $pos = length($input); + } + # WORD + } else { + if (substr($input, $pos + 1) =~ /\s/) { + $pos += $-[0] + 1; + } else { + $pos = length($input); + } + } + + # word + } elsif ($char eq 'w') { + # Start at the beginning of this WORD. + if (not defined $cur_pos and $pos > 0 + and substr($input, $pos - 1, 2) + !~ /(\s.|$word$non_word|$non_word$word)/) { + + $cur_pos = _find_regex_before + ( + $input, + "^($word+$non_word|$non_word+$word|$word+\\s|$non_word+\\s)", + $pos, 1 + ); + + if ($cur_pos < 0) { + $cur_pos = 0; + } else { + $cur_pos += 2; + } + } + # Delete to the end of the word. + if (substr($input, $pos) =~ + /^($word+$non_word|$non_word+$word|$word+\s+\S|$non_word+\s+\S)/) { + + $pos += $+[0] - 1; + } else { + $pos = length($input); + # If we are at the end of the line, whitespace before + # the word is also deleted. + my $new_pos = _find_regex_before + ($input, + "^($word+\\s+|$non_word+\\s+)", + $pos, 1); + + if ($new_pos != -1 and + (not defined $cur_pos or + $cur_pos > $new_pos + 1)) { + + $cur_pos = $new_pos + 1; + } + } + + # WORD + } else { + # Start at the beginning of this WORD. + if (not defined $cur_pos and $pos > 0 and + substr($input, $pos - 1, 1) !~ /\s/) { + $cur_pos = _find_regex_before($input, '\s', $pos - 1, 0); + if ($cur_pos < 0) { + $cur_pos = 0; + } else { + $cur_pos++; + } + } + # Delete to the end of the word. + if (substr($input, $pos + 1) =~ /^\S*\s+\S/) { + $pos += $+[0]; + } else { + $pos = length($input); + # If we are at the end of the line, whitespace before + # the WORD is also deleted. + my $new_pos = _find_regex_before($input, '\s+', $pos, 1); + if (not defined $cur_pos or $cur_pos > $new_pos + 1) { + $cur_pos = $new_pos + 1; + } + } + } + } + } + + return ($cur_pos, $pos); +} + +# Find regex as close as possible before the current position. If $end is true +# the end of the match is returned, otherwise the beginning. +sub _find_regex_before { + my ($input, $regex, $pos, $end) = @_; + + $input = reverse $input; + $pos = length($input) - $pos - 1; + $pos = 0 if $pos < 0; + + if (substr($input, $pos) =~ /$regex/) { + if (!$end) { + $pos += $-[0]; + } else { + $pos += $+[0]; + } + return length($input) - $pos - 1; + } else { + return -1; + } +} + +sub cmd_0 { + return (undef, 0); +} + +sub cmd_caret { + my $input = _input(); + my $pos; + # No whitespace at all. + if ($input !~ m/^\s/) { + $pos = 0; + # Some non-whitespace, go to first one. + } elsif ($input =~ m/[^\s]/) { + $pos = $-[0]; + # Only whitespace, go to the end. + } else { + $pos = _fix_input_pos(length $input, length $input); + } + return (undef, $pos); +} + +sub cmd_dollar { + my $length = _input_len(); + return (undef, _fix_input_pos($length, $length)); +} + +sub cmd_x { + my ($count, $pos, $repeat) = @_; + + cmd_operator_d($pos, $pos + $count, $commands->{x}, $repeat); + return (undef, undef); +} + +sub cmd_X { + my ($count, $pos, $repeat) = @_; + + return (undef, undef) if $pos == 0; + + my $new = $pos - $count; + $new = 0 if $new < 0; + cmd_operator_d($pos, $new, $commands->{X}, $repeat); + return (undef, undef); +} + +sub cmd_s { + my ($count, $pos, $repeat) = @_; + + $operator = $commands->{c}; + return (undef, $pos + $count); +} + +sub cmd_S { + my ($count, $pos, $repeat) = @_; + + $operator = $commands->{c}; + return (0, _input_len()); +} + +sub cmd_i { + my ($count, $pos, $repeat) = @_; + + if (!$repeat) { + _update_mode(M_INS); + } else { + $pos = _insert_buffer($count, $pos); + } + return (undef, $pos); +} + +sub cmd_I { + my ($count, $pos, $repeat) = @_; + + $pos = cmd_caret(); + if (!$repeat) { + _update_mode(M_INS); + } else { + $pos = _insert_buffer($count, $pos); + } + return (undef, $pos); +} + +sub cmd_a { + my ($count, $pos, $repeat) = @_; + + # Move after current character. Can't use cmd_l() because we need to mover + # after last character at the end of the line. + my $length = _input_len(); + $pos += 1; + $pos = $length if $pos > $length; + + if (!$repeat) { + _update_mode(M_INS); + } else { + $pos = _insert_buffer($count, $pos); + } + return (undef, $pos); +} + +sub cmd_A { + my ($count, $pos, $repeat) = @_; + + $pos = _input_len(); + + if (!$repeat) { + _update_mode(M_INS); + } else { + $pos = _insert_buffer($count, $pos); + } + return (undef, $pos); +} + +# Add @insert_buf to _input() at the given position. +sub _insert_buffer { + my ($count, $pos) = @_; + return _insert_at_position(join('', @insert_buf), $count, $pos); +} + +sub _insert_at_position { + my ($string, $count, $pos) = @_; + + $string = $string x $count; + + my $input = _input(); + # Check if we are not at the end of the line to prevent substr outside of + # string error. + if (length $input > $pos) { + substr($input, $pos, 0) = $string; + } else { + $input .= $string; + } + _input($input); + + return $pos - 1 + length $string; +} + +sub cmd_r { + my ($count, $pos, $repeat, $char) = @_; + + my $input = _input(); + + # Abort if at end of the line. + return (undef, undef) if length($input) < $pos + $count; + + substr $input, $pos, $count, $char x $count; + _input($input); + return (undef, $pos + $count - 1); +} + +sub cmd_p { + my ($count, $pos, $repeat) = @_; + $pos = _paste_at_position($count, $pos + 1); + return (undef, $pos); +} + +sub cmd_P { + my ($count, $pos, $repeat) = @_; + $pos = _paste_at_position($count, $pos); + return (undef, $pos); +} + +sub _paste_at_position { + my ($count, $pos) = @_; + + return if $registers->{$register} eq ''; + return _insert_at_position($registers->{$register}, $count, $pos); +} + +sub cmd_C { + my ($count, $pos, $repeat) = @_; + + $operator = $commands->{c}; + return (undef, _input_len()); +} + +sub cmd_D { + my ($count, $pos, $repeat) = @_; + + $operator = $commands->{d}; + return (undef, _input_len()); +} + +sub cmd_ctrl_d { + my ($count, $pos, $repeat) = @_; + + my $window = Irssi::active_win(); + # no count = half of screen + if (not defined $count) { + $count = $window->{height} / 2; + } + $window->view()->scroll($count); + + Irssi::statusbar_items_redraw('more'); + return (undef, undef); +} + +sub cmd_ctrl_u { + my ($count, $pos, $repeat) = @_; + + my $window = Irssi::active_win(); + # no count = half of screen + if (not defined $count) { + $count = $window->{height} / 2; + } + $window->view()->scroll($count * -1); + + Irssi::statusbar_items_redraw('more'); + return (undef, undef); +} + +sub cmd_ctrl_f { + my ($count, $pos, $repeat) = @_; + + my $window = Irssi::active_win(); + $window->view()->scroll($count * $window->{height}); + + Irssi::statusbar_items_redraw('more'); + return (undef, undef); +} + +sub cmd_ctrl_b { + my ($count, $pos, $repeat) = @_; + + return cmd_ctrl_f($count * -1, $pos, $repeat); +} + +sub cmd_ctrl_wj { + my ($count, $pos, $repeat) = @_; + + while ($count-- > 0) { + Irssi::command('window down'); + } + + return (undef, undef); +} + +sub cmd_ctrl_wk { + my ($count, $pos, $repeat) = @_; + + while ($count-- > 0) { + Irssi::command('window up'); + } + + return (undef, undef); +} + +sub cmd_ctrl_6 { + # like :b# + Irssi::command('window last'); + return (undef, undef); +} + +sub cmd_tilde { + my ($count, $pos, $repeat) = @_; + + my $input = _input(); + my $string = substr $input, $pos, $count; + $string =~ s/(.)/(uc($1) eq $1) ? lc($1) : uc($1)/ge; + substr $input, $pos, $count, $string; + + _input($input); + return (undef, _fix_input_pos($pos + $count, length $input)); +} + +sub cmd_register { + my ($count, $pos, $repeat, $char) = @_; + + if (not exists $registers->{$char} and not exists $registers->{lc $char}) { + print "Wrong register $char, ignoring." if DEBUG; + return (undef, undef); + } + + # make sure black hole register is always empty + if ($char eq '_') { + $registers->{_} = ''; + } + + # + and * contain both irssi's cut-buffer + if ($char eq '+' or $char eq '*') { + $registers->{'+'} = Irssi::parse_special('$U'); + $registers->{'*'} = $registers->{'+'}; + } + + $register = $char; + print "Changing register to $register" if DEBUG; + return (undef, undef); +} + +sub cmd_undo { + print "Undo!" if DEBUG; + + if ($undo_index != $#undo_buffer) { + $undo_index++; + _restore_undo_entry($undo_index); + print "Undoing entry index: $undo_index of " . scalar(@undo_buffer) + if DEBUG; + } else { + print "No further undo." if DEBUG; + } + return (undef, undef); +} + +sub cmd_redo { + print "Redo!" if DEBUG; + + if ($undo_index != 0) { + $undo_index--; + print "Undoing entry index: $undo_index of " . scalar(@undo_buffer) + if DEBUG; + _restore_undo_entry($undo_index); + } else { + print "No further Redo." if DEBUG; + } + return (undef, undef); +} + +# Adapt the input position depending if an operator is active or not. +sub _fix_input_pos { + my ($pos, $length) = @_; + + # Allow moving past the last character when an operator is active to allow + # correct handling of last character in line. + if ($operator) { + $pos = $length if $pos > $length; + # Otherwise forbid it. + } else { + $pos = $length - 1 if $pos > $length - 1; + } + + return $pos; +} + + +# EX MODE COMMANDS + +sub cmd_ex_command { + my $arg_str = join '', @ex_buf; + + if ($arg_str !~ /^(\d*)?([a-z]+)/) { + return _warn("Invalid Ex-mode command!"); + } + + # Abort if command doesn't exist or used with count for unsupported + # commands. + if (not exists $commands_ex->{$2} or + ($1 ne '' and not $commands_ex->{$2}->{uses_count})) { + return _warn("Ex-mode $1$2 doesn't exist!"); + } + + # add this item to the ex mode history + ex_history_add($arg_str); + $ex_history_index = 0; # and reset the history position. + + my $count = $1; + if ($count eq '') { + $count = undef; + } + $commands_ex->{$2}->{func}($arg_str, $count); +} + +sub ex_substitute { + my ($arg_str, $count) = @_; + + # :s/// + if ($arg_str =~ m|^s/(.+)/(.*)/([ig]*)|) { + my ($search, $replace, $flags) = ($1, $2, $3); + print "Searching for $search, replace: $replace, flags; $flags" + if DEBUG; + + my $rep_fun = sub { $replace }; + + my $line = _input(); + my @re_flags = split '', defined $flags?$flags:''; + + if (scalar grep { $_ eq 'i' } @re_flags) { + $search = '(?i)' . $search; + } + + print "Search is $search" if DEBUG; + + my $re_pattern = qr/$search/; + + if (scalar grep { $_ eq 'g' } @re_flags) { + $line =~ s/$re_pattern/$rep_fun->()/eg; + } else { + print "Single replace: $replace" if DEBUG; + $line =~ s/$re_pattern/$rep_fun->()/e; + } + + print "New line is: $line" if DEBUG; + _input($line); + } else { + _warn_ex('s'); + } +} + +sub ex_bnext { + my ($arg_str, $count) = @_; + + if (not defined $count) { + if ($arg_str =~ /^bn(?:ext)?\s(\d+)$/) { + $count = $1; + } else { + $count = 1; + } + } + + while ($count-- > 0) { + Irssi::command('window next'); + } +} + +sub ex_bprev { + my ($arg_str, $count) = @_; + + if (not defined $count) { + if ($arg_str =~ /^bp(?:rev)?\s(\d+)$/) { + $count = $1; + } else { + $count = 1; + } + } + + while ($count-- > 0) { + Irssi::command('window previous'); + } +} + +sub ex_bdelete { + my ($arg_str, $count) = @_; + + if (not defined $count) { + if ($arg_str =~ /^bd(?:elete)?\s(\d+)$/) { + $count = $1; + } + } + + if (defined $count) { + my $window = Irssi::window_find_refnum($count); + if (not $window) { + return; + } + $window->set_active(); + } + Irssi::command('window close'); +} + +sub ex_buffer { + my ($arg_str, $count) = @_; + + # :b[buffer] {args} + if ($arg_str =~ m|^b(?:uffer)?\s*(.+)$| or defined $count) { + my $window; + my $item; + my $buffer = $1; + + # :[N]:b[uffer] + if (defined $count) { + $window = Irssi::window_find_refnum($count); + # Go to window number. + } elsif ($buffer =~ /^[0-9]+$/) { + $window = Irssi::window_find_refnum($buffer); + # Go to previous window. + } elsif ($buffer eq '#') { + Irssi::command('window last'); + # Go to best regex matching window. + } else { + eval { + my $matches = _matching_windows($buffer); + if (scalar @$matches > 0) { + $window = @$matches[0]->{window}; + $item = @$matches[0]->{item}; + } + }; + # Catch errors in /$buffer/ regex. + if ($@) { + _warn($@); + } + } + + if ($window) { + $window->set_active(); + if ($item) { + $item->set_active(); + } + } + } else { + _warn_ex('buffer'); + } +} + +sub ex_item_next { + my ($arg_str, $count) = @_; + my $win = Irssi::active_win; + $count = 1 unless defined $count; + + $win->item_next for (1..$count); +} + +sub ex_item_prev { + my ($arg_str, $count) = @_; + my $win = Irssi::active_win; + $count = 1 unless defined $count; + + $win->item_prev for (1..$count); +} + +# TODO: factor out the shared search code for server next/prev. +sub ex_server_next { + my ($arg_str, $count) = @_; + + my @server_ids = map { $_->{tag} . "\x1d" . $_->{nick} } Irssi::servers; + my $server = Irssi::active_server; + + return unless $server; + + my $current_id = $server->{tag} . "\x1d" . $server->{nick}; + my $next = 0; + my $server_count = scalar @server_ids; + for my $i (0..$server_count - 1) { + my $s_id = $server_ids[$i]; + if (defined($s_id) and ($s_id eq $current_id)) { + print "Found match at $i" if DEBUG; + $next = ($i + 1) % $server_count; + last; + } + } + + my $next_server = $server_ids[$next]; + $next_server =~ s|^([^\x1d]+)\x1d.*$|$1|; + + print "Changing to server: $next: $next_server" if DEBUG; + Irssi::command("window server $next_server"); +} + +sub ex_server_prev { + my ($arg_str, $count) = @_; + + my @server_ids = map { $_->{tag} . "\x1d" . $_->{nick} } Irssi::servers; + my $server = Irssi::active_server; + + return unless $server; + + my $current_id = $server->{tag} . "\x1d" . $server->{nick}; + my $prev = 0; + my $server_count = scalar @server_ids; + + for my $i (0..$server_count - 1) { + my $s_id = $server_ids[$i]; + if (defined($s_id) and ($s_id eq $current_id)) { + print "Found match at $i" if DEBUG; + $prev = ($i - 1) % $server_count; + last; + } + } + + my $prev_server = $server_ids[$prev]; + $prev_server =~ s|^([^\x1d]+)\x1d.*$|$1|; + + print "Changing to server: $prev: $prev_server" if DEBUG; + Irssi::command("window server $prev_server"); + +} + +sub ex_registers { + my ($arg_str, $count) = @_; + + # :reg[isters] {arg} and :di[splay] {arg} + if ($arg_str =~ /^(?:reg(?:isters)?|di(?:splay)?)(?:\s+(.+)$)?/) { + my @regs; + if ($1) { + my $regs = $1; + $regs =~ s/\s+//g; + @regs = split //, $regs; + } else { + @regs = keys %$registers; + } + + # Update "+ and "* registers so correct values are displayed. + $registers->{'+'} = Irssi::parse_special('$U'); + $registers->{'*'} = $registers->{'+'}; + + my @empty_regs; + my $special_regs = { '+' => 1, '*' => 1, '_' => 1, '"' => 1, '0' => 1 }; + + my $active_window = Irssi::active_win; + foreach my $key (sort @regs) { + next if $key eq '_'; # skip black hole + if (defined $registers->{$key}) { + my $register_val = $registers->{$key}; + if (length $register_val or exists $special_regs->{$key}) { + $register_val =~ s/%/%%/g; + $active_window->print("register $key: $register_val"); + } else { + push @empty_regs, $key; + } + } + } + + # coalesce empty registers into a single line. + if (@empty_regs) { + my @runs; + my $run_start; + foreach my $i (0..$#empty_regs) { + my $cur = $empty_regs[$i]; + my $next = $empty_regs[$i+1]; + + $run_start = $cur unless $run_start; + if (defined $next and ord($cur) + 1 == ord($next)) { + # extend range. + } else { + # terminate range and restart + my $run_str = $run_start; + + if ($cur ne $run_start) { + $run_str .= "-$cur"; + } + push @runs, $run_str; + $run_start = undef; + } + } + $active_window->print("Empty registers: " . join(', ', @runs)); + } + } else { + _warn_ex(':registers'); + } +} + +sub ex_buffers { + my ($arg_str, $count) = @_; + + Irssi::command('window list'); +} + +sub ex_undolist { + my ($arg_str, $count) = @_; + + _print_undo_buffer(); +} + +sub ex_map { + my ($arg_str, $count) = @_; + + # :map {lhs} {rhs} + if ($arg_str =~ /^map (\S+) (\S.*)$/) { + my $lhs = _parse_mapping($1); + my $rhs = $2; + + if (not defined $lhs) { + return _warn_ex('map', 'invalid {lhs}'); + } + + # Add new mapping. + my $command; + # Ex-mode command + if (index($rhs, ':') == 0) { + $rhs =~ /^:(\S+)(\s.+)?$/; + if (not exists $commands_ex->{$1}) { + return _warn_ex('map', "$rhs not found"); + } else { + $command = { char => $rhs, + func => $commands_ex->{$1}->{func}, + type => C_EX, + }; + } + # Irssi command + } elsif (index($rhs, '/') == 0) { + $command = { char => $rhs, + func => substr($rhs, 1), + type => C_IRSSI, + }; + # <Nop> does nothing + } elsif (lc $rhs eq '<nop>') { + $command = { char => '<Nop>', + func => undef, + type => C_NOP, + }; + # command-mode command + } else { + $rhs = _parse_mapping($2); + if (not defined $rhs) { + return _warn_ex('map', 'invalid {rhs}'); + } elsif (not exists $commands->{$rhs}) { + return _warn_ex('map', "$2 not found"); + } else { + $command = $commands->{$rhs}; + } + } + add_map($lhs, $command); + + # :map [lhs] + } elsif ($arg_str =~ m/^map\s*$/ or $arg_str =~ m/^map (\S+)$/) { + # Necessary for case insensitive matchings. lc alone won't work. + my $search = $1; + $search = '' if not defined $search; + $search = _parse_mapping_reverse(_parse_mapping($search)); + + my $active_window = Irssi::active_win(); + foreach my $key (sort keys %$maps) { + my $map = $maps->{$key}; + my $cmd = $map->{cmd}; + if (defined $cmd) { + next if $map->{char} eq $cmd->{char}; # skip default mappings + # FIXME: Hack so <C-H> doesn't show up as mapped to <BS>. + next if $map->{char} eq '<C-H>' and $cmd->{char} eq '<BS>'; + next if $map->{char} !~ /^\Q$search\E/; # skip non-matches + $active_window->print(sprintf "%-15s %s", $map->{char}, + $cmd->{char}); + } + } + } else { + _warn_ex('map'); + } +} +sub ex_unmap { + my ($arg_str, $count) = @_; + + # :unm[ap] {lhs} + if ($arg_str !~ /^unm(?:ap)? (\S+)$/) { + return _warn_ex('unmap'); + } + + my $lhs = _parse_mapping($1); + if (not defined $lhs) { + return _warn_ex('unmap', 'invalid {lhs}'); + # Prevent unmapping of unknown or default mappings. + } elsif (not exists $maps->{$lhs} or not defined $maps->{$lhs}->{cmd} or + ($commands->{$lhs} and $maps->{$lhs}->{cmd} == $commands->{$lhs})) { + return _warn_ex('unmap', "$1 not found"); + } + + delete_map($lhs); +} +sub _parse_mapping { + my ($string) = @_; + + $string =~ s/<([^>]+)>/_parse_mapping_bracket($1)/ge; + _debug("Parse mapping: $string"); + if (index($string, '<invalid>') != -1) { + return undef; + } + return $string; +} +sub _parse_mapping_bracket { + my ($string) = @_; + + $string = lc $string; + + # <C-X>, get corresponding CTRL char. + if ($string =~ /^c-([a-z])$/i) { + $string = chr(ord($1) - 96); + # <C-6> and <C-^> + } elsif ($string =~ /^c-[6^]$/i) { + $string = chr(30); + # <Space> + } elsif ($string eq 'space') { + $string = ' '; + # <CR> + } elsif ($string eq 'cr') { + $string = "\n"; + # <BS> + } elsif ($string eq 'bs') { + $string = chr(127); + } elsif ($string eq 'leader') { + $string = $settings->{map_leader}->{value}; + # Invalid char, return special string to recognize the error. + } else { + $string = '<invalid>'; + } + return $string; +} +sub _parse_mapping_reverse { + my ($string) = @_; + + if (not defined $string) { + _warn("Unable to reverse-map command: " . join('', @ex_buf)); + return; + } + + my $escaped_leader = quotemeta($settings->{map_leader}->{value}); + $string =~ s/$escaped_leader/<Leader>/g; + + # Convert char to <char-name>. + $string =~ s/ /<Space>/g; + $string =~ s/\n/<CR>/g; + $string =~ s/\x7F/<BS>/g; + # Convert Ctrl-X to <C-X>. + $string =~ s/([\x01-\x1A])/"<C-" . chr(ord($1) + 64) . ">"/ge; + # Convert Ctrl-6 and Ctrl-^ to <C-^>. + $string =~ s/\x1E/<C-^>/g; + + return $string; +} +sub _parse_partial_command_reverse { + my ($string) = @_; + + my $escaped_leader = quotemeta($settings->{map_leader}->{value}); + $string =~ s/$escaped_leader/<Leader>/g; + + # Convert Ctrl-X to ^X. + $string =~ s/([\x01-\x1A])/"^" . chr(ord($1) + 64)/ge; + # Convert Ctrl-6 and Ctrl-^ to <C-^>. + $string =~ s/\x1E/^^/g; + + return $string; +} + +sub ex_source { + my ($arg_str, $count) = @_; + + # :so[urce], but only loads the vim_moderc file at the moment + + open my $file, '<', Irssi::get_irssi_dir() . '/vim_moderc' or return; + + while (my $line = <$file>) { + next if $line =~ /^\s*$/ or $line =~ /^\s*"/; + + chomp $line; + # :map {lhs} {rhs}, keep in sync with ex_map() + if ($line =~ /^\s*map (\S+) (\S.*)$/) { + ex_map($line); + } else { + _warn_ex('source', "command not supported: $line"); + } + } +} + +sub ex_mkvimrc { + my ($arg_str, $count) = @_; + + # :mkv[imrc][!], [file] not supported + + my $vim_moderc = Irssi::get_irssi_dir(). '/vim_moderc'; + if (-f $vim_moderc and $arg_str !~ /^mkv(?:imrc)?!$/) { + return _warn_ex('mkvimrc', "$vim_moderc already exists"); + } + + open my $file, '>', $vim_moderc or return; + + # copied from ex_map() + foreach my $key (sort keys %$maps) { + my $map = $maps->{$key}; + my $cmd = $map->{cmd}; + if (defined $cmd) { + next if $map->{char} eq $cmd->{char}; # skip default mappings + print $file "map $map->{char} $cmd->{char}\n"; + } + } + + close $file; +} + +sub ex_set { + my ($arg_str, $count) = @_; + + # :se[t] [option] [value] + if ($arg_str =~ /^se(?:t)?(?:\s([^=]+)(?:=(.*)$)?)?/) { + # :se[t] {option} {value} + if (defined $1 and defined $2) { + if (not exists $settings->{$1}) { + return _warn_ex('map', "setting '$1' not found"); + } + my $name = $1; + my $value = $2; + # Also accept numeric values for boolean options. + if ($settings->{$name}->{type} == S_BOOL) { + if ($value =~ /^(on|off)$/i) { + $value = lc $value eq 'on' ? 1 : 0; + } elsif ($value eq '') { + $value = 0; + } + } + _setting_set($name, $value); + setup_changed(); + + # :se[t] [option] + } else { + my $search = defined $1 ? $1 : ''; + my $active_window = Irssi::active_win(); + foreach my $setting (sort keys %$settings) { + next if $setting !~ /^\Q$search\E/; # skip non-matches + my $value = $settings->{$setting}->{value}; + # Irssi only accepts 'on' and 'off' as values for boolean + # options. + if ($settings->{$setting}->{type} == S_BOOL) { + $value = $value ? 'on' : 'off'; + } + $active_window->print($setting . '=' . $value); + } + } + } else { + _warn_ex('map'); + } +} + +sub _warn_ex { + my ($command, $description) = @_; + my $message = "Error in ex-mode command $command"; + if (defined $description) { + $message .= ": $description"; + } + _warn($message); +} + +sub _matching_windows { + my ($buffer) = @_; + + my $server; + + if ($buffer =~ m{^(.+)/(.+)}) { + $server = $1; + $buffer = $2; + } + + print ":b searching for channel $buffer" if DEBUG; + print ":b on server $server" if $server and DEBUG; + + my @matches; + foreach my $window (Irssi::windows()) { + # Matching window names. + if ($window->{name} =~ /$buffer/i) { + my $win_ratio = ($+[0] - $-[0]) / length($window->{name}); + push @matches, { window => $window, + item => undef, + ratio => $win_ratio, + text => $window->{name} }; + print ":b $window->{name}: $win_ratio" if DEBUG; + } + # Matching Window item names (= channels). + foreach my $item ($window->items()) { + # Wrong server. + if ($server and (!$item->{server} or + $item->{server}->{chatnet} !~ /^$server/i)) { + next; + } + if ($item->{name} =~ /$buffer/i) { + my $length = length($item->{name}); + $length-- if index($item->{name}, '#') == 0; + my $item_ratio = ($+[0] - $-[0]) / $length; + push @matches, { window => $window, + item => $item, + ratio => $item_ratio, + text => $item->{name} }; + print ":b $window->{name} $item->{name}: $item_ratio" if DEBUG; + } + } + } + + @matches = sort {$b->{ratio} <=> $a->{ratio}} @matches; + + return \@matches; +} + + +# STATUS ITEMS + +#TODO: give these things better names. +sub vim_mode_cmd { + + my $mode_str = ''; + if ($mode == M_INS) { + $mode_str = 'Insert'; + } elsif ($mode == M_EX) { + $mode_str = '%_Ex%_'; + } else { + $mode_str = 'Normal'; + if ($register ne '"' or $numeric_prefix or $operator or $movement or + $pending_map) { + my $partial = ''; + if ($register ne '"') { + $partial .= '"' . $register; + } + if ($numeric_prefix) { + $partial .= $numeric_prefix; + } + if ($operator) { + $partial .= $operator->{char}; + } + if ($movement) { + $partial .= $movement->{char}; + } + if (defined $pending_map) { + $partial .= $pending_map; + } + $partial = _parse_partial_command_reverse($partial); + $partial =~ s/\\/\\\\\\\\/g; + $mode_str .= " ($partial)"; + } + } + return $mode_str; +} + +sub vim_wins_data { + my $windows = ''; + + # A little code duplication of cmd_ex_command(), but \s+ instead of \s* so + # :bd doesn't display buffers matching d. + my $arg_str = join '', @ex_buf; + if ($arg_str =~ m|^b(?:uffer)?\s+(.+)$|) { + my $buffer = $1; + if ($buffer !~ /^[0-9]$/ and $buffer ne '#') { + # Display matching windows. + eval { + my $matches = _matching_windows($buffer); + $windows = join ',', map { $_->{text} } @$matches; + }; + # Catch errors in /$buffer/ regex. + if ($@) { + _warn($@); + } + } + } + return $windows; +} + +sub vim_exp_mode { + my ($server, $witem, $arg) = @_; + return vim_mode_cmd(); +} + +sub vim_exp_wins { + my ($server, $witem, $arg) = @_; + return vim_wins_data(); +} + +# vi mode status item. +sub vim_mode_cb { + my ($sb_item, $get_size_only) = @_; + my $mode_str = vim_mode_cmd(); + $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0); +} + +# :b window list item. +sub b_windows_cb { + my ($sb_item, $get_size_only) = @_; + + my $windows = vim_wins_data(); + + $sb_item->default_handler($get_size_only, "{sb $windows}", '', 0); +} + + +# INPUT HANDLING + +sub got_key { + my ($key) = @_; + + return if ($should_ignore); + + # Esc key + if ($key == 27) { + print "Esc seen, starting buffer" if DEBUG; + $input_buf_enabled = 1; + + # NOTE: this timeout might be too low on laggy systems, but + # it comes at the cost of keystroke latency for things that + # contain escape sequences (arrow keys, etc) + my $esc_buf_timeout = $settings->{esc_buf_timeout}->{value}; + + $input_buf_timer + = Irssi::timeout_add_once($esc_buf_timeout, + \&handle_input_buffer, undef); + + print "Buffer Timer tag: $input_buf_timer" if DEBUG; + + } elsif ($mode == M_INS) { + + if ($key == 3) { # Ctrl-C enters command mode + _update_mode(M_CMD); + _stop(); + return; + + } elsif ($key == 10) { # enter. + _commit_line(); + + } elsif ($input_buf_enabled and $imap) { + print "Imap $imap active" if DEBUG; + my $map = $imaps->{$imap}; + if (not defined $map->{map} or chr($key) eq $map->{map}) { + $map->{func}($key); + # Clear the buffer so the imap is not printed. + @input_buf = (); + } else { + push @input_buf, $key; + } + flush_input_buffer(); + _stop(); + $imap = undef; + return; + + } elsif (exists $imaps->{chr($key)}) { + print "Imap " . chr($key) . " seen, starting buffer" if DEBUG; + + # start imap pending mode + $imap = chr($key); + + $input_buf_enabled = 1; + push @input_buf, $key; + $input_buf_timer + = Irssi::timeout_add_once(1000, \&flush_input_buffer, undef); + + _stop(); + return; + + # Pressing delete resets insert mode repetition (8 = BS, 127 = DEL). + # TODO: maybe allow it + } elsif ($key == 8 || $key == 127) { + @insert_buf = (); + # All other entered characters need to be stored to allow repeat of + # insert mode. Ignore delete and control characters. + } elsif ($key > 31) { + push @insert_buf, chr($key); + } + } + + if ($input_buf_enabled) { + push @input_buf, $key; + _stop(); + return; + } + + if ($mode == M_CMD) { + my $should_stop = handle_command_cmd($key); + _stop() if $should_stop; + Irssi::statusbar_items_redraw("vim_mode"); + + } elsif ($mode == M_EX) { + + if ($key == 3) { # C-c cancels Ex mdoe as well. + _update_mode(M_CMD); + _stop(); + return; + } + + handle_command_ex($key); + } +} + +# TODO: merge this with 'flush_input_buffer' below. + +sub handle_input_buffer { + + #Irssi::timeout_remove($input_buf_timer); + $input_buf_timer = undef; + # see what we've collected. + print "Input buffer contains: ", join(", ", @input_buf) if DEBUG; + + if (@input_buf == 1 && $input_buf[0] == 27) { + + print "Enter Normal Mode" if DEBUG; + _update_mode(M_CMD); + + } else { + # we have more than a single esc, implying an escape sequence + # (meta-* or esc-*) + + # currently, we only extract escape sequences if: + # a) we're in ex mode + # b) they're arrow keys (for history control) + + if ($mode == M_EX) { + # ex mode + my $key_str = join '', map { chr } @input_buf; + if ($key_str =~ m/^\e\[([ABCD])/) { + my $arrow = $1; + _debug( "Arrow key: $arrow"); + if ($arrow eq 'A') { # up + ex_history_back(); + } elsif ($arrow eq 'B') { # down + ex_history_fwd(); + } else { + $arrow =~ s/C/right/; + $arrow =~ s/D/left/; + _debug("Arrow key $arrow not supported"); + } + } + } else { + # otherwise, we just forward them to irssi. + _emulate_keystrokes(@input_buf); + } + + # Clear insert buffer, pressing "special" keys (like arrow keys) + # resets it. + @insert_buf = (); + } + + @input_buf = (); + $input_buf_enabled = 0; +} + +sub flush_input_buffer { + Irssi::timeout_remove($input_buf_timer) if defined $input_buf_timer; + $input_buf_timer = undef; + # see what we've collected. + print "Input buffer flushed" if DEBUG; + + # Add the characters to @insert_buf so they can be repeated. + push @insert_buf, map chr, @input_buf; + + _emulate_keystrokes(@input_buf); + + @input_buf = (); + $input_buf_enabled = 0; + + $imap = undef; +} + +sub flush_pending_map { + my ($old_pending_map) = @_; + + print "flush_pending_map(): ", $pending_map, ' ', $old_pending_map + if DEBUG; + + return if not defined $pending_map or + $pending_map ne $old_pending_map; + + handle_command_cmd(undef); + Irssi::statusbar_items_redraw("vim_mode"); +} + +sub handle_numeric_prefix { + my ($char) = @_; + my $num = 0+$char; + + if (defined $numeric_prefix) { + $numeric_prefix *= 10; + $numeric_prefix += $num; + } else { + $numeric_prefix = $num; + } +} + +sub handle_command_cmd { + my ($key) = @_; + + my $pending_map_flushed = 0; + + my $char; + if (defined $key) { + $char = chr($key); + # We were called from flush_pending_map(). + } else { + $char = $pending_map; + $key = 0; + $pending_map_flushed = 1; + } + + # Counts + if (!$movement and !$pending_map and + ($char =~ m/[1-9]/ or ($numeric_prefix && $char =~ m/[0-9]/))) { + print "Processing numeric prefix: $char" if DEBUG; + handle_numeric_prefix($char); + return 1; # call _stop() + } + + if (defined $pending_map and not $pending_map_flushed) { + $pending_map = $pending_map . $char; + $char = $pending_map; + } + + my $map; + if ($movement) { + $map = { char => $movement->{char}, + cmd => $movement, + maps => {}, + }; + + } elsif (exists $maps->{$char}) { + $map = $maps->{$char}; + + # We have multiple mappings starting with this key sequence. + if (!$pending_map_flushed and scalar keys %{$map->{maps}} > 0) { + if (not defined $pending_map) { + $pending_map = $char; + } + + # The current key sequence has a command mapped to it, run if + # after a timeout. + if (defined $map->{cmd}) { + Irssi::timeout_add_once(1000, \&flush_pending_map, + $pending_map); + } + return 1; # call _stop() + } + + } else { + print "No mapping found for $char" if DEBUG; + $pending_map = undef; + $numeric_prefix = undef; + return 1; # call _stop() + } + + $pending_map = undef; + + my $cmd = $map->{cmd}; + + # Make sure we have a valid $cmd. + if (not defined $cmd) { + print "Bug in pending_map_flushed() $map->{char}" if DEBUG; + return 1; # call _stop() + } + + # Ex-mode commands can also be bound in command mode. + if ($cmd->{type} == C_EX) { + print "Processing ex-command: $map->{char} ($cmd->{char})" if DEBUG; + + $cmd->{func}->(substr($cmd->{char}, 1), $numeric_prefix); + $numeric_prefix = undef; + + return 1; # call _stop() + # As can irssi commands. + } elsif ($cmd->{type} == C_IRSSI) { + print "Processing irssi-command: $map->{char} ($cmd->{char})" if DEBUG; + + _command_with_context($cmd->{func}); + + $numeric_prefix = undef; + return 1; # call _stop(); + # <Nop> does nothing. + } elsif ($cmd->{type} == C_NOP) { + print "Processing <Nop>: $map->{char}" if DEBUG; + + $numeric_prefix = undef; + return 1; # call _stop(); + } + + # text-objects (i a) are simulated with $movement + if (!$movement and ($cmd->{type} == C_NEEDSKEY or + ($operator and ($char eq 'i' or $char eq 'a')))) { + print "Processing movement: $map->{char} ($cmd->{char})" if DEBUG; + if ($char eq 'i') { + $movement = $commands->{_i}; + } elsif ($char eq 'a') { + $movement = $commands->{_a}; + } else { + $movement = $cmd; + } + + } elsif (!$movement and $cmd->{type} == C_OPERATOR) { + print "Processing operator: $map->{char} ($cmd->{char})" if DEBUG; + # Abort operator if we already have one pending. + if ($operator) { + # But allow cc/dd/yy. + if ($operator == $cmd) { + print "Processing line operator: ", + $map->{char}, " (", + $cmd->{char} ,")" + if DEBUG; + + my $pos = _input_pos(); + $cmd->{func}->(0, _input_len(), undef, 0); + # Restore position for yy. + if ($cmd == $commands->{y}) { + _input_pos($pos); + # And save undo for other operators. + } else { + _add_undo_entry(_input(), _input_pos()); + } + if ($register ne '"') { + print 'Changing register to "' if DEBUG; + $register = '"'; + } + } + $numeric_prefix = undef; + $operator = undef; + $movement = undef; + # Set new operator. + } else { + $operator = $cmd; + } + + # Start Ex mode. + } elsif ($cmd == $commands->{':'}) { + + if (not script_is_loaded('uberprompt')) { + _warn("Warning: Ex mode requires the 'uberprompt' script. " . + "Please load it and try again."); + } else { + _update_mode(M_EX); + _set_prompt(':'); + } + + # Enter key sends the current input line in command mode as well. + } elsif ($key == 10) { + _commit_line(); + return 0; # don't call _stop() + + } else { + print "Processing command: $map->{char} ($cmd->{char})" if DEBUG; + + my $skip = 0; + my $repeat = 0; + + if (!$movement) { + # . repeats the last command. + if ($cmd == $commands->{'.'} and defined $last->{cmd}) { + $cmd = $last->{cmd}; + $char = $last->{char}; + # If . is given a count then it replaces original count. + if (not defined $numeric_prefix) { + $numeric_prefix = $last->{numeric_prefix}; + } + $operator = $last->{operator}; + $movement = $last->{movement}; + $register = $last->{register}; + $repeat = 1; + } elsif ($cmd == $commands->{'.'}) { + print '. pressed but $last->{char} not set' if DEBUG; + $skip = 1; + } + } + + # Ignore invalid operator/command combinations. + if ($operator and $cmd->{no_operator}) { + print "Invalid operator/command: $operator->{char} $cmd->{char}" + if DEBUG; + $skip = 1; + } + + if ($skip) { + print "Skipping movement and operator." if DEBUG; + } else { + # Make sure count is at least 1 except for functions which need to + # know if no count was used. + if (not $numeric_prefix and not $cmd->{needs_count}) { + $numeric_prefix = 1; + } + + my $cur_pos = _input_pos(); + + # If defined $cur_pos will be changed to this. + my $old_pos; + # Position after the move. + my $new_pos; + # Execute the movement (multiple times). + if (not $movement) { + ($old_pos, $new_pos) + = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat); + } else { + ($old_pos, $new_pos) + = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat, + $char); + } + if (defined $old_pos) { + print "Changing \$cur_pos from $cur_pos to $old_pos" if DEBUG; + $cur_pos = $old_pos; + } + if (defined $new_pos) { + _input_pos($new_pos); + } else { + $new_pos = _input_pos(); + } + + # Update input position of last undo entry so that undo/redo + # restores correct position. + if (@undo_buffer and _input() eq $undo_buffer[0]->[0] and + ((defined $operator and $operator == $commands->{d}) or + $cmd->{repeatable})) { + print "Updating history position: $undo_buffer[0]->[0]" + if DEBUG; + $undo_buffer[0]->[1] = $cur_pos; + } + + # If we have an operator pending then run it on the handled text. + # But only if the movement changed the position (this prevents + # problems with e.g. f when the search string doesn't exist). + if ($operator and $cur_pos != $new_pos) { + print "Processing operator: ", $operator->{char} if DEBUG; + $operator->{func}->($cur_pos, $new_pos, $cmd, $repeat); + } + + # Save an undo checkpoint here for operators, all repeatable + # movements, operators and repetition. + if ((defined $operator and $operator == $commands->{d}) or + $cmd->{repeatable}) { + # TODO: why do history entries still show up in undo + # buffer? Is avoiding the commands here insufficient? + + _add_undo_entry(_input(), _input_pos()); + } + + # Store command, necessary for . + if ($operator or $cmd->{repeatable}) { + $last->{cmd} = $cmd; + $last->{char} = $char; + $last->{numeric_prefix} = $numeric_prefix; + $last->{operator} = $operator; + $last->{movement} = $movement; + $last->{register} = $register; + } + } + + # Reset the count unless we go into insert mode, _update_mode() needs + # to know it when leaving insert mode to support insert with counts + # (like 3i). + if ($repeat or $cmd->{type} != C_INSERT) { + $numeric_prefix = undef; + } + $operator = undef; + $movement = undef; + + if ($cmd != $commands->{'"'} and $register ne '"') { + print 'Changing register to "' if DEBUG; + $register = '"'; + } + + } + + return 1; # call _stop() +} + +sub handle_command_ex { + my ($key) = @_; + + # BS key (8) or DEL key (127) - remove last character. + if ($key == 8 || $key == 127) { + print "Delete" if DEBUG; + if (@ex_buf > 0) { + pop @ex_buf; + _set_prompt(':' . join '', @ex_buf); + # Backspacing over : exits ex-mode. + } else { + _update_mode(M_CMD); + } + + # Return key - execute command + } elsif ($key == 10) { + print "Run ex-mode command" if DEBUG; + cmd_ex_command(); + _update_mode(M_CMD); + + } elsif ($key == 9) { # TAB + print "Tab pressed" if DEBUG; + print "Ex buf contains: " . join('', @ex_buf) if DEBUG; + @tab_candidates = _tab_complete(join('', @ex_buf), [keys %$commands_ex]); + _debug("Candidates: " . join(", ", @tab_candidates)); + if (@tab_candidates == 1) { + @ex_buf = ( split('', $tab_candidates[0]), ' '); + _set_prompt(':' . join '', @ex_buf); + } + # Ignore control characters for now. + } elsif ($key > 0 && $key < 32) { + # TODO: use them later, e.g. completion + + # Append entered key + } else { + if ($key != -1) { + # check we're not called from an ex_history_* function + push @ex_buf, chr $key; + } + _set_prompt(':' . join '', @ex_buf); + } + + Irssi::statusbar_items_redraw("vim_windows"); + + _stop(); +} + +sub _tab_complete { + my ($input, $source) = @_; + my @out; + foreach my $item (@$source) { + if ($item =~ m/^\Q$input\E/) { + push @out, $item; + } + } + + return sort { $a cmp $b } @out; +} + +sub vim_mode_init { + Irssi::signal_add_first 'gui key pressed' => \&got_key; + Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb'); + Irssi::statusbar_item_register ('vim_windows', 0, 'b_windows_cb'); + + Irssi::expando_create('vim_cmd_mode' => \&vim_exp_mode, {}); + Irssi::expando_create('vim_wins' => \&vim_exp_wins, {}); + + + # Register all available settings. + foreach my $name (keys %$settings) { + _setting_register($name); + } + + foreach my $char ('a' .. 'z') { + $registers->{$char} = ''; + } + + setup_changed(); + + Irssi::signal_add 'setup changed' => \&setup_changed; + + # Add all default mappings. + foreach my $char (keys %$commands) { + next if $char =~ /^_/; # skip private commands (text-objects for now) + add_map($char, $commands->{$char}); + } + + # Load the vim_moderc file if it exists. + ex_source('source'); + + setup_changed(); + _reset_undo_buffer(); + + if ($settings->{start_cmd}->{value}) { + _update_mode(M_CMD); + } else { + _update_mode(M_INS); + } +} + +sub setup_changed { + my $value; + + if ($settings->{cmd_seq}->{value} ne '') { + delete $imaps->{$settings->{cmd_seq}->{value}}; + } + $value = _setting_get('cmd_seq'); + if ($value eq '') { + $settings->{cmd_seq}->{value} = $value; + } else { + if (length $value == 1) { + $imaps->{$value} = { 'map' => $value, + 'func' => sub { _update_mode(M_CMD) } + }; + $settings->{cmd_seq}->{value} = $value; + } else { + _warn("Error: vim_mode_cmd_seq must be a single character"); + # Restore the value so $settings and irssi settings are + # consistent. + _setting_set('cmd_seq', $settings->{cmd_seq}->{value}); + } + } + + my $new_utf8 = _setting_get('utf8'); + if ($new_utf8 != $settings->{utf8}->{value}) { + # recompile the patterns when switching to/from utf-8 + $word = qr/[\w_]/o; + $non_word = qr/[^\w_\s]/o; + + $settings->{utf8}->{value} = $new_utf8; + } + if ($new_utf8 and (!$^V or $^V lt v5.8.1)) { + _warn("Warning: UTF-8 isn't supported very well in perl < 5.8.1! " . + "Please disable the vim_mode_utf8 setting."); + } + + # Sync $settings with current irssi values. + foreach my $name (keys %$settings) { + # These were already handled above. + next if $name eq 'cmd_seq' or $name eq 'cmd_seq'; + + $settings->{$name}->{value} = _setting_get($name); + } +} + +sub UNLOAD { + Irssi::signal_remove('gui key pressed' => \&got_key); + Irssi::signal_remove('setup changed' => \&setup_changed); + Irssi::statusbar_item_unregister ('vim_mode'); + Irssi::statusbar_item_unregister ('vim_windows'); +} + +sub _add_undo_entry { + my ($line, $pos) = @_; + + # If we aren't at the top of the history stack, then drop newer entries as + # we can't branch (yet). + while ($undo_index > 0) { + shift @undo_buffer; + $undo_index--; + } + + # check it's not a dupe of the list head + my $current = $undo_buffer[$undo_index]; + if ($line eq $current->[0] && $pos == $current->[1]) { + print "Not adding duplicate to undo list" if DEBUG; + } elsif ($line eq $current->[0]) { + print "Updating position of undo list at $undo_index" if DEBUG; + $undo_buffer[$undo_index]->[1] = $pos; + } else { + print "adding $line ($pos) to undo list" if DEBUG; + # add to the front of the buffer + unshift @undo_buffer, [$line, $pos]; + $undo_index = 0; + } + my $max = $settings->{max_undo_lines}->{value}; +} + +sub _restore_undo_entry { + my $entry = $undo_buffer[$undo_index]; + _input($entry->[0]); + _input_pos($entry->[1]); +} + +sub _print_undo_buffer { + + my $i = 0; + my @buf; + foreach my $entry (@undo_buffer) { + my $str = ''; + if ($i == $undo_index) { + $str .= '* '; + } else { + $str .= ' '; + } + my ($line, $pos) = @$entry; + substr($line, $pos, 0) = '*'; + # substr($line, $pos+3, 0) = '%_'; + + $str .= sprintf('%02d %s [%d]', $i, $line, $pos); + push @buf, $str; + $i++; + } + print "------ undo buffer ------"; + print join("\n", @buf); + print "------------------ ------"; + +} + +sub _reset_undo_buffer { + my ($line, $pos) = @_; + $line = _input() unless defined $line; + $pos = _input_pos() unless defined $pos; + + print "Clearing undo buffer" if DEBUG; + @undo_buffer = ([$line, $pos]); + $undo_index = 0; +} + +sub add_map { + my ($keys, $command) = @_; + + # To allow multiple mappings starting with the same key (like gg, ge, gE) + # also create maps for the keys "leading" to this key (g in this case, but + # can be longer for this like ,ls). When looking for the mapping these + # "leading" maps are followed. + my $tmp = $keys; + while (length $tmp > 1) { + my $map = substr $tmp, -1, 1, ''; + if (not exists $maps->{$tmp}) { + $maps->{$tmp} = { char => _parse_mapping_reverse($tmp), + cmd => undef, + maps => {} + }; + } + if (not exists $maps->{$tmp}->{maps}->{$tmp . $map}) { + $maps->{$tmp}->{maps}->{$tmp . $map} = undef; + } + } + + if (not exists $maps->{$keys}) { + $maps->{$keys} = { char => undef, + cmd => undef, + maps => {} + }; + } + $maps->{$keys}->{char} = _parse_mapping_reverse($keys); + $maps->{$keys}->{cmd} = $command; +} + +sub delete_map { + my ($keys) = @_; + + # Abort for non-existent mappings or placeholder mappings. + return if not exists $maps->{$keys} or not defined $maps->{$keys}->{cmd}; + + my @add = (); + + # If no maps need the current key, then remove it and all other + # unnecessary keys in the "tree". + if (keys %{$maps->{$keys}->{maps}} == 0) { + my $tmp = $keys; + while (length $tmp > 1) { + my $map = substr $tmp, -1, 1, ''; + delete $maps->{$tmp}->{maps}->{$tmp . $map}; + if (not $maps->{$tmp}->{cmd} and keys %{$maps->{$tmp}->{maps}} == 0) { + push @add, $tmp; + delete $maps->{$tmp}; + } else { + last; + } + } + } + + if (keys %{$maps->{$keys}->{maps}} > 0) { + $maps->{$keys}->{cmd} = undef; + } else { + delete $maps->{$keys}; + } + push @add, $keys; + + # Restore default keybindings in case we :unmapped a <Nop> or a remapped + # key. + foreach my $key (@add) { + if (exists $commands->{$key}) { + add_map($key, $commands->{$key}); + } + } +} + + +sub _commit_line { + _update_mode(M_INS); + + # separate from call above as _update_mode() does additional internal work + # and we need to make sure it gets correctly called. + _update_mode(M_CMD) if $settings->{start_cmd}->{value}; + + _reset_undo_buffer('', 0); +} + +sub _input { + my ($data) = @_; + + my $current_data = Irssi::parse_special('$L', 0, 0); + + if ($settings->{utf8}->{value}) { + $current_data = decode_utf8($current_data); + } + + if (defined $data) { + if ($settings->{utf8}->{value}) { + Irssi::gui_input_set(encode_utf8($data)); + } else { + Irssi::gui_input_set($data); + } + } else { + $data = $current_data; + } + + return $data; +} + +sub _input_len { + return length _input(); +} + +sub _input_pos { + my ($pos) = @_; + my $cur_pos = Irssi::gui_input_get_pos(); + # my $dpos = defined $pos?$pos:'undef'; + # my @call = caller(1); + # my $cfunc = $call[3]; + # $cfunc =~ s/^.*?::([^:]+)$/$1/; + # print "pos called from line: $call[2] sub: $cfunc pos: $dpos, cur_pos: $cur_pos" + # if DEBUG; + + if (defined $pos) { + #print "Input pos being set from $cur_pos to $pos" if DEBUG; + Irssi::gui_input_set_pos($pos) if $pos != $cur_pos; + } else { + $pos = $cur_pos; + #print "Input pos retrieved as $pos" if DEBUG; + } + + return $pos; +} + +sub _emulate_keystrokes { + my @keys = @_; + $should_ignore = 1; + for my $key (@keys) { + Irssi::signal_emit('gui key pressed', $key); + } + $should_ignore = 0; +} + +sub _stop() { + Irssi::signal_stop_by_name('gui key pressed'); +} + +sub _update_mode { + my ($new_mode) = @_; + + my $pos; + + if ($mode == M_INS and $new_mode == M_CMD) { + # Support counts with insert modes, like 3i. + if ($numeric_prefix and $numeric_prefix > 1) { + $pos = _insert_buffer($numeric_prefix - 1, _input_pos()); + _input_pos($pos); + $numeric_prefix = undef; + + # In insert mode we are "between" characters, in command mode "on top" + # of keys. When leaving insert mode we have to move on key left to + # accomplish that. + } else { + $pos = _input_pos(); + if ($pos != 0) { + _input_pos($pos - 1); + } + } + # Store current line to allow undo of i/a/I/A. + _add_undo_entry(_input(), _input_pos()); + + # Change mode to i to support insert mode repetition. This doesn't affect + # commands like i/a/I/A because handle_command_cmd() sets $last->{cmd}. + # It's necessary when pressing enter so the next line can be repeated. + } elsif ($mode == M_CMD and $new_mode == M_INS) { + $last->{cmd} = $commands->{i}; + # Make sure prompt is cleared when leaving ex mode. + } elsif ($mode == M_EX and $new_mode != M_EX) { + _set_prompt(''); + } + + $mode = $new_mode; + if ($mode == M_INS) { + $history_index = undef; + $register = '"'; + @insert_buf = (); + # Reset every command mode related status as a fallback in case something + # goes wrong. + } elsif ($mode == M_CMD) { + $numeric_prefix = undef; + $operator = undef; + $movement = undef; + $register = '"'; + + $pending_map = undef; + + # Also clear ex-mode buffer. + @ex_buf = (); + } + + Irssi::statusbar_items_redraw("vim_mode"); + Irssi::statusbar_items_redraw ('uberprompt'); + +} + +sub _set_prompt { + my $msg = shift; + + # add a leading space unless we're trying to clear it entirely. + if (length($msg) and $settings->{prompt_leading_space}->{value}) { + $msg = ' ' . $msg; + } + + # escape % symbols. This prevents any _set_prompt calls from using + # colouring sequences. + $msg =~ s/%/%%/g; + + Irssi::signal_emit('change prompt', $msg, 'UP_INNER'); +} + +sub _setting_get { + my ($name) = @_; + + my $type = $settings->{$name}->{type}; + $name = "vim_mode_$name"; + + my $ret = undef; + + if ($type == S_BOOL) { + $ret = Irssi::settings_get_bool($name); + } elsif ($type == S_INT) { + $ret = Irssi::settings_get_int($name); + } elsif ($type == S_STR) { + $ret = Irssi::settings_get_str($name); + } elsif ($type == S_TIME) { + $ret = Irssi::settings_get_time($name); + } else { + _warn("Unknown setting type '$type', please report."); + } + + return $ret; +} + +sub _setting_set { + my ($name, $value) = @_; + + my $type = $settings->{$name}->{type}; + $name = "vim_mode_$name"; + + if ($type == S_BOOL) { + Irssi::settings_set_bool($name, $value); + } elsif ($type == S_INT) { + Irssi::settings_set_int($name, $value); + } elsif ($type == S_STR) { + Irssi::settings_set_str($name, $value); + } elsif ($type == S_TIME) { + Irssi::settings_set_time($name, $value); + } else { + _warn("Unknown setting type '$type', please report."); + } +} +sub _setting_register { + my ($name) = @_; + + my $value = $settings->{$name}->{value}; + my $type = $settings->{$name}->{type}; + $name = "vim_mode_$name"; + + if ($type == S_BOOL) { + Irssi::settings_add_bool('vim_mode', $name, $value); + } elsif ($type == S_INT) { + Irssi::settings_add_int('vim_mode', $name, $value); + } elsif ($type == S_STR) { + Irssi::settings_add_str('vim_mode', $name, $value); + } elsif ($type == S_TIME) { + Irssi::settings_add_time('vim_mode', $name, $value); + } else { + _warn("Unknown setting type '$type', please report."); + } +} + +sub _warn { + my ($warning) = @_; + + print '%_vim_mode: ', $warning, '%_'; +} + +sub _debug { + return unless DEBUG; + + my ($format, @args) = @_; + my $str = sprintf($format, @args); + print $str; +} + +sub _command_with_context { + my ($command) = @_; + my $context; + my $window = Irssi::active_win; + if (defined $window) { + my $witem = $window->{active}; + if (defined $witem and ref($witem) eq 'Irssi::Windowitem') { + $context = $witem; + } else { + $context = $window; + } + } else { + my $server = Irssi::active_server; + if (defined $server) { + $context = $server; + } + } + if (defined $context) { + print "Command $command Using context: " . ref($context) if DEBUG; + $context->command($command); + } else { + print "Command $command has no context" if DEBUG; + Irssi::command($command); + } +} + +sub ex_history_add { + my ($line) = @_; + + # check it's not an exact dupe of the previous history line + + my $last_hist = $ex_history[$ex_history_index]; + $last_hist = '' unless defined $last_hist; + + return if $last_hist eq $line; + + _debug("Adding $line to ex command history"); + + # add it to the history + unshift @ex_history, $line; + + if ($settings->{ex_history_size}->{value} < @ex_history) { + pop @ex_history; # junk the last entry if we've hit the max. + } +} + +sub ex_history_fwd { + + my $hist_max = $#ex_history; + $ex_history_index++; + if ($ex_history_index > $hist_max) { + $ex_history_index = 0; + _debug("ex history hit top, wrapping to 0"); + } + + my $line = $ex_history[$ex_history_index]; + $line = '' if not defined $line; + + _debug("Ex history line: $line"); + + @ex_buf = split '', $line; + handle_command_ex(-1); +} + +sub ex_history_back { + my $hist_max = $#ex_history; + $ex_history_index--; + if ($ex_history_index == -1) { + $ex_history_index = $hist_max; + _debug("ex history hit bottom, wrapping to $hist_max"); + + } + + my $line = $ex_history[$ex_history_index]; + $line = '' if not defined $line; + + _debug("Ex history line: $line"); + @ex_buf = split '', $line; + handle_command_ex(-1); + +} + +sub ex_history_show { + my $win = Irssi::active_win(); + $win->print("Ex command history:"); + for my $i (0 .. $#ex_history) { + my $flag = $i == $ex_history_index + ? ' <' + : ''; + $win->print("$i " . $ex_history[$i] . $flag); + } +} +vim_mode_init(); diff --git a/.config/irssi/startup b/.config/irssi/startup @@ -0,0 +1 @@ +/load perl diff --git a/.config/irssi/triggers b/.config/irssi/triggers @@ -0,0 +1,2 @@ +#Triggers file version 1.2.4 +-all -regexp '\t' -replace ' ' diff --git a/.config/nvim/ftdetect/irssi.vim b/.config/nvim/ftdetect/irssi.vim @@ -0,0 +1,4 @@ +augroup irssi_ftdetect + au! + au BufRead,BufNewFile *irssi/config set ft=irssi +augroup END diff --git a/.config/nvim/modules/filetype.vim b/.config/nvim/modules/filetype.vim @@ -17,7 +17,6 @@ function Gphconfig() nnoremap <buffer> <localleader>t :%s/\t/ /g<CR> nnoremap <buffer> <localleader>l :call GphZettel("")<left><left> inoremap <buffer> <localleader>l <esc>:call GphZettel("")<left><left> - nnoremap <buffer> <localleader>s :mark `<CR>:%s/\/home\/hayden\/net\//gopher:\/\/haydenh.null/g<CR>:normal ``j$<CR> endfunction function GphZettel(srch) diff --git a/.config/nvim/modules/term.vim b/.config/nvim/modules/term.vim @@ -39,7 +39,7 @@ function! Shwin() call nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts) endfunction -command! -nargs=0 Quickterm call Quickterm() +command! -nargs=0 Qterm call Quickterm() augroup terminal autocmd WinNew,BufNew,BufNewFile,BufEnter,WinEnter * call Termstart() diff --git a/.config/nvim/syntax/irssi.vim b/.config/nvim/syntax/irssi.vim @@ -0,0 +1,47 @@ +" Vim syntax file +" Language: irssi script +" Maintainer: isundill <isundill@gmail.com> +" Created: 2015 Oct 02 +" Last Change: 2015 Oct 05 + +if exists("b:current_syntax") + finish +endif + +syn case ignore +" Comments +syn match m_comment /#.*/ contains=m_todo +syn keyword m_todo TODO NOTE contained +" `)` or `}` not followed by `;` +" -- syn match m_error /[})][^ \t]*[^;])/ +" Keywords & Statements +syn keyword irssi_keywords servers chatnets channels settings aliases statusbar logs hilights ignores +syn region irssi_category start="=[ \t]*(" end=")" fold transparent contains=irssi_servers,irssi_chatnets,irssi_channels,irssi_statusbar,irssi_settings,value_def,m_error,irssi_highlight +syn region irssi_category start="=[ \t]*{" end="}" fold transparent contains=irssi_servers,irssi_chatnets,irssi_channels,irssi_statusbar,irssi_settings,value_def,m_error,irssi_highlight +syn keyword irssi_servers address chatnet port autoconnect use_ssl ssl_verify +syn keyword irssi_chatnets type max_kicks max_msgs max_whois aliases statusbar contained +syn keyword irssi_channels name chatnet autojoin username realname autosendcmd contained +syn keyword irssi_statusbar items barstart barend topicbarstart topicbarend time user window window_empty +syn keyword irssi_statusbar more placement position visible window_inact prompt lag act disabled +syn keyword irssi_statusbar prompt_empty topic topic_empty default input alignment +syn keyword irssi_statusbar barend topicbarstart topicbaren priority contained +syn keyword irssi_settings core real_name user_name nick fe-text actlist_sort recode_autodetect_utf8 recode log_timestamp timestamp_format contained +syn keyword irssi_settings hide_colors theme term_charset window_history autolog_path autolog bell_beeps beep_when_away beep_msg_level screen_away_message colors contained +syn keyword irssi_highlight text nick word contained +syn region value_def start='"' end='"' skip="\\\"" oneline + +" Set colors +hi def link m_comment Comment +hi def link m_todo Todo +hi def link irssi_keywords Keyword +hi def link irssi_servers Statement +hi def link irssi_chatnets Statement +hi def link irssi_channels Statement +hi def link irssi_statusbar Statement +hi def link irssi_settings Statement +hi def link irssi_highlight Statement +hi def link m_error Error +hi def link value_def Constant + +let b:current_syntax = "irssi" + diff --git a/.config/zsh/alias.zsh b/.config/zsh/alias.zsh @@ -23,7 +23,7 @@ alias g=" \git" alias c=" \cp" alias f=" \find" alias xi=" sudo xbps-install" -alias xiu=" sudo xbps-install -S; sudo xbps-install -yu xbps; sudo xbps-install -yu" +alias xiu=" sudo xbps-install -S; sudo xbps-install -yu xbps; sudo xbps-install -yu; sudo xbps-remove -Ooy; rm -rf ~/.cache ~/.mozilla ~/.local/share/webkitgtk ~/.viminfo ~/.wget-hsts ~/.lesshst ~/.sh_history ~/.python_history ~/.*history ~/.*hst* ~/.dbus ~/.w3m ~/.config/vimb/cookies.db; sudo vkpurge rm all" alias xq=" sudo xbps-query" alias xr=" sudo xbps-remove" alias wget=" \wget --hsts-file="/dev/null"" @@ -39,7 +39,6 @@ alias zsleep=" sudo zzz" alias hibernate=" sudo ZZZ" alias rmst=" bash ~/.scripts/random/gnulinux.sh" alias vimb=" \vimb --no-maximize" -alias cleancache=" sudo xbps-remove -Ooy; rm -rf ~/.cache ~/.mozilla ~/.local/share/webkitgtk ~/.viminfo ~/.wget-hsts ~/.lesshst ~/.sh_history ~/.python_history ~/.*history ~/.*hst* ~/.dbus ~/.sciminfo ~/.viminfo ~/.w3m ~/.config/vimb/cookies.db; sudo vkpurge rm all;" alias mkconfall=" mkmailpass; mkalias" alias tmux=" tmux -f ~/.config/tmux/config" alias nw=" pkill newsboat; newsboat" diff --git a/.scripts/bin/dmenu/dpass b/.scripts/bin/dmenu/dpass @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # dmenu/dpass # Created by Hayden Hamilton @@ -7,37 +7,25 @@ # Copyright (c) 2019-2020 Hayden Hamilton. # -if [ "$(echo $1 | awk '/help/ {print $0}')" != "" ] -then - echo "dpass [\"option that your terminal uses to execute the argument\"] - -$(tput bold)ENVIRONMENTAL:$(tput sgr0) (must be set in .profile, .bash_profile .zprofile etc...) -PASS=\"/path/to/password/file\" -TERMINAL=\"name of terminal binary\" - -Written by Hayden Hamilton <haydenvh.com>" - exit 0 -fi -dmenu="dmenu" - +[ -z $PASS ] && PASS=$HOME/.local/pass [ ! -f $PASS ] && touch $PASS -pass=$(printf "GENERATE\nEDIT\n$(cat $PASS | awk '// {print $1}')" | $dmenu -l 50 -i -p "Select a password/generate/edit:" | tr '[:upper:]' '[:lower:]') -getpass=$(cat "$PASS" | awk "/$pass/ "'{print $0}') -if [ "$pass" = "" ] -then - exit 0 -elif [ "$pass" = "generate" ] -then - password=$(head -c 500 /dev/urandom | tr -dc 'a-zA-Z0-9~!@#$%^&*_+=-' | fold -w 35 | head -n 1) - passname=$(echo "" | $dmenu -l 50 -i -p "Name for password (only 1 word):") - [ "$passname" = "" ] && passname="pleaseeditthis" - echo "$passname: $password" >> $PASS - [ "$(printf "No\nYes" | $dmenu -i -p "Edit the pasword file?")" = "Yes" ] && $TERMINAL "$1" vim $PASS -elif [ "$pass" = "edit" ] -then - $TERMINAL "$1" vim $PASS -else - echo "$getpass" | sed "s/$pass.//g" | tr " " "\n" | $dmenu $2 -P -l 20 -p "Password:" | xclip - sleep 5 - printf '' | xclip # self destruct clipboard -fi +chosen=$(printf "GENERATE\nEDIT\n$(cut -d ':' -f 1 < $PASS)" | dmenu -l 20 -i -p "Select a password, or GENERATE:" | tr '[:upper:]' '[:lower:]') +passwd=$(grep "^$chosen: " < $PASS) + +case "$chosen" in + "") exit 1 ;; + generate) + new=$(head -c 2000 /dev/urandom | tr -dc 'a-zA-Z0-9~!@#$%^&*_+=-' | fold -w 35 | shuf | shuf | shuf | head -n 1) + newname=$(echo | dmenu -i -p "Name for passwd (must contain no colons)?") + [ "$newname" = "" ] && newname="pleaseeditthis" + echo "$newname: $new" >> $PASS + ;; + *) + printf "${passwd##*: }\n" | xclip + ( + sleep 5 + printf '' | xclip # destroy clipboard to stop accidental pasting + # tbh, you should make a cronjob with this, run once per minute :) + ) & + ;; +esac diff --git a/.scripts/bin/misc/dotadd b/.scripts/bin/misc/dotadd @@ -9,10 +9,7 @@ git add .config/zsh/ git add ./.scripts/ git add ./.xinitrc git add .config/picom/config -git add .config/irssi/pipeline.theme -git add .config/irssi/default.theme -git add .config/irssi/scripts/autorun/ -git add .config/irssi/config +git add .config/irssi git add .config/grub/grub.cfg git add .config/grub git add .config/grub/grub @@ -36,5 +33,6 @@ git add .config/git/ git add .config/nvim/ git rm --cache .config/nvim/.netrwhist git rm --cache .config/zsh/.zcompdump +git rm --cache .config/irssi/*log* git add .config/galias git add .config/redshift/ diff --git a/.scripts/bin/misc/rmpv b/.scripts/bin/misc/rmpv @@ -6,11 +6,7 @@ # haydenvh.com # Copyright (c) 2019-2020 Hayden Hamilton. -dir=$1 -count="$2" -filetypes="mp3 opus mkv mp4 flac m4a webm wav" - -files=$(find $dir | grep -E 'mp3$|opus$|mkv$|mp4$|flac$|m4a$|webm$|wav$' | shuf | head -n $count) +files=$(find $1 -type f | grep -E 'mp3$|opus$|mkv$|mp4$|flac$|m4a$|webm$|wav$' | shuf | head -n $2) echo "$files" | grep '[[:alnum:]]' >/dev/null || { echo "No files found..." exit 1 diff --git a/.scripts/custom/dock b/.scripts/custom/dock @@ -1,9 +1,8 @@ #!/bin/sh -for d in "LVDS1" "VGA1" "HDMI2" +for d in "LVDS1" "HDMI2" do ds="$ds --output $d --off" done -xrandr $ds --output LVDS1 --mode 1280x800 --primary --pos 0x0 -xrandr --output HDMI2 --mode 1920x1080 --pos 1920x0 +xrandr $ds --output LVDS1 --mode 1280x800 --primary --pos 0x0 --output HDMI2 --mode 1920x1080 --pos 1920x0