From 00008907af4638ec2ff83e0b1928594e74a5f973 Mon Sep 17 00:00:00 2001 From: Yigit Colakoglu Date: Sat, 1 Jan 2022 19:13:43 +0100 Subject: [PATCH] Edited neovim and stuff --- .config/khal/config | 6 +- .config/nvim/plugin/plugins.vim | 13 +- .config/vdirsyncer/config | 4 +- .config/vim/vimrc | 16 +- .config/weechat/alias.conf | 2 +- .config/weechat/autosort.conf | 24 - .config/weechat/buflist.conf | 5 +- .config/weechat/charset.conf | 2 +- .config/weechat/colorize_nicks.conf | 20 - .config/weechat/exec.conf | 2 +- .config/weechat/fifo.conf | 4 +- .config/weechat/fset.conf | 2 +- .config/weechat/guile.conf | 2 +- .config/weechat/irc.conf | 189 +- .config/weechat/irc.upgrade | Bin 69642 -> 0 bytes .config/weechat/iset.conf | 38 - .config/weechat/logger.conf | 4 +- .config/weechat/lua.conf | 2 +- .config/weechat/perl.conf | 2 +- .config/weechat/perl/atcomplete.pl | 89 - .config/weechat/perl/autoload/atcomplete.pl | 89 - .config/weechat/perl/autoload/awaylog.pl | 100 - .config/weechat/perl/autoload/highmon.pl | 1154 ---------- .config/weechat/perl/autoload/iset.pl | 1645 -------------- .config/weechat/perl/awaylog.pl | 100 - .config/weechat/perl/highmon.pl | 1154 ---------- .config/weechat/perl/iset.pl | 1645 -------------- .config/weechat/plugins.conf | 149 +- .config/weechat/python.conf | 2 +- .config/weechat/python/aesthetic.py | 70 - .config/weechat/python/anotify.py | 476 ----- .config/weechat/python/autoload/aesthetic.py | 70 - .config/weechat/python/autoload/anotify.py | 476 ----- .config/weechat/python/autoload/autosort.py | 1075 ---------- .../weechat/python/autoload/colorize_nicks.py | 409 ---- .config/weechat/python/autoload/go.py | 563 ----- .config/weechat/python/autoload/styurl.py | 178 -- .config/weechat/python/autoload/vimode.py | 1884 ----------------- .config/weechat/python/autosort.py | 1075 ---------- .config/weechat/python/colorize_nicks.py | 409 ---- .config/weechat/python/go.py | 563 ----- .config/weechat/python/styurl.py | 178 -- .config/weechat/python/vimode.py | 1884 ----------------- .config/weechat/relay.conf | 4 +- .config/weechat/relay.upgrade | Bin 64 -> 0 bytes .config/weechat/ruby.conf | 2 +- .config/weechat/script.conf | 8 +- .config/weechat/script/plugins.xml.gz | Bin 129457 -> 0 bytes .config/weechat/sec.conf | 13 +- .config/weechat/spell.conf | 2 +- .config/weechat/tcl.conf | 2 +- .config/weechat/trigger.conf | 2 +- .config/weechat/weechat.conf | 110 +- .config/weechat/weechat.upgrade | Bin 992061 -> 0 bytes .config/weechat/xfer.conf | 8 +- .config/weechat/xfer.upgrade | Bin 64 -> 0 bytes .local/bin/backup | 2 + .local/bin/calconsync | 6 +- .local/bin/dmenu-refresh | 14 +- .local/bin/dmenu-web | 2 +- .local/share/bookmarks | 4 +- .local/share/dwm/autostart.sh | 4 + .profile | 2 +- 63 files changed, 180 insertions(+), 15779 deletions(-) delete mode 100644 .config/weechat/autosort.conf delete mode 100644 .config/weechat/colorize_nicks.conf delete mode 100644 .config/weechat/irc.upgrade delete mode 100644 .config/weechat/iset.conf delete mode 100644 .config/weechat/perl/atcomplete.pl delete mode 100644 .config/weechat/perl/autoload/atcomplete.pl delete mode 100644 .config/weechat/perl/autoload/awaylog.pl delete mode 100644 .config/weechat/perl/autoload/highmon.pl delete mode 100644 .config/weechat/perl/autoload/iset.pl delete mode 100644 .config/weechat/perl/awaylog.pl delete mode 100644 .config/weechat/perl/highmon.pl delete mode 100644 .config/weechat/perl/iset.pl delete mode 100644 .config/weechat/python/aesthetic.py delete mode 100644 .config/weechat/python/anotify.py delete mode 100644 .config/weechat/python/autoload/aesthetic.py delete mode 100644 .config/weechat/python/autoload/anotify.py delete mode 100644 .config/weechat/python/autoload/autosort.py delete mode 100644 .config/weechat/python/autoload/colorize_nicks.py delete mode 100644 .config/weechat/python/autoload/go.py delete mode 100644 .config/weechat/python/autoload/styurl.py delete mode 100644 .config/weechat/python/autoload/vimode.py delete mode 100644 .config/weechat/python/autosort.py delete mode 100644 .config/weechat/python/colorize_nicks.py delete mode 100644 .config/weechat/python/go.py delete mode 100644 .config/weechat/python/styurl.py delete mode 100644 .config/weechat/python/vimode.py delete mode 100644 .config/weechat/relay.upgrade delete mode 100644 .config/weechat/script/plugins.xml.gz delete mode 100644 .config/weechat/weechat.upgrade delete mode 100644 .config/weechat/xfer.upgrade diff --git a/.config/khal/config b/.config/khal/config index d45d4e82..30a2d14e 100644 --- a/.config/khal/config +++ b/.config/khal/config @@ -1,7 +1,7 @@ [calendars] [[holidays_local]] -path = ~/.local/share/calendars/holidays/* +path = ~/.local/share/calendars/holidays/ type = discover [[nx_calendar_local]] @@ -9,7 +9,7 @@ path = ~/.local/share/calendars/nx_calendar/* type = discover [[tudelft_local]] -path = ~/.local/share/calendars/tudelft/* +path = ~/.local/share/calendars/tudelft/ type = discover [locale] @@ -20,4 +20,4 @@ datetimeformat = %d/%m/%Y %H:%M longdatetimeformat = %d/%m/%Y %H:%M [default] -default_calendar = tudelft_local +default_calendar = diff --git a/.config/nvim/plugin/plugins.vim b/.config/nvim/plugin/plugins.vim index 5c2be3b0..659a68f8 100644 --- a/.config/nvim/plugin/plugins.vim +++ b/.config/nvim/plugin/plugins.vim @@ -21,7 +21,7 @@ Plug 'vim-scripts/indentpython.vim' Plug 'preservim/nerdcommenter' Plug 'mileszs/ack.vim' Plug 'yegappan/taglist' -Plug 'puremourning/vimspector' +"Plug 'puremourning/vimspector' Plug 'lervag/vimtex' Plug 'gu-fan/riv.vim' @@ -29,15 +29,18 @@ Plug 'isene/hyperlist.vim' Plug 'neomutt/neomutt.vim' Plug 'VebbNix/lf-vim' -" Browser editor support -Plug 'glacambre/firenvim', { 'do': { _ -> firenvim#install(0) } } - " Tmux integration Plug 'benmills/vimux' Plug 'christoomey/vim-tmux-navigator' +" Neuron +Plug 'oberblastmeister/neuron.nvim', { 'branch' : 'unstable' } +Plug 'nvim-lua/popup.nvim' +Plug 'iamcco/markdown-preview.nvim', { 'do': { -> mkdp#util#install() }, 'for': ['markdown', 'vim-plug']} + " File system navigation -Plug 'junegunn/fzf.vim' +Plug 'nvim-lua/plenary.nvim' +Plug 'nvim-telescope/telescope.nvim' " OCS Yank PLugin for use with Blink Shell Plug 'ojroques/vim-oscyank' diff --git a/.config/vdirsyncer/config b/.config/vdirsyncer/config index 54ad20a3..1d33307e 100644 --- a/.config/vdirsyncer/config +++ b/.config/vdirsyncer/config @@ -24,7 +24,7 @@ collections = null [storage tudelft_local] type = "filesystem" -path = "~/.local/share/calendars/tudelft/" +path = "~/.local/share/calendars/tudelft" fileext = ".ics" [storage tudelft_remote] @@ -42,5 +42,5 @@ url = "https://www.mozilla.org/media/caldata/TurkeyHolidays.ics" [storage holidays_local] type = "filesystem" -path = "~/.local/share/calendars/holidays/" +path = "~/.local/share/calendars/holidays" fileext = ".ics" diff --git a/.config/vim/vimrc b/.config/vim/vimrc index 1482b10f..04324b3f 100644 --- a/.config/vim/vimrc +++ b/.config/vim/vimrc @@ -42,8 +42,12 @@ nnoremap Q gq map h :History " CtrlP use FZF (faster!) -nnoremap :Files +nnoremap :Telescope find_files + +" ,b Buffers nnoremap b :Buffers + +" ,u urlview noremap u :wsilent !urlview " System clipboard copy @@ -203,6 +207,16 @@ if has('nvim') " Widths fot several plugins let g:goyo_width = 120 " Goyo let g:Tlist_WinWidth = 40 " Tlist +lua <Z~_Zfy5~z>WL*BYz}C3luO=8?Xx`0Scr= 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 Qg<~-hZZ+Bq7COrQKU#w&o&W#< diff --git a/.config/weechat/iset.conf b/.config/weechat/iset.conf deleted file mode 100644 index 8508e97d..00000000 --- a/.config/weechat/iset.conf +++ /dev/null @@ -1,38 +0,0 @@ -# -# weechat -- iset.conf -# -# WARNING: It is NOT recommended to edit this file by hand, -# especially if WeeChat is running. -# -# Use /set or similar command to change settings in WeeChat. -# -# For more info, see: https://weechat.org/doc/quickstart -# - -[color] -bg_selected = red -help_default_value = green -help_option_name = white -help_text = default -option = default -option_selected = white -type = brown -type_selected = yellow -value = cyan -value_diff = magenta -value_diff_selected = lightmagenta -value_selected = lightcyan -value_undef = green -value_undef_selected = lightgreen - -[help] -show_help_bar = on -show_help_extra_info = on -show_plugin_description = off - -[look] -scroll_horiz = 10 -show_current_line = on -use_color = off -use_mute = off -value_search_char = "=" diff --git a/.config/weechat/logger.conf b/.config/weechat/logger.conf index d0d3218d..1c21d652 100644 --- a/.config/weechat/logger.conf +++ b/.config/weechat/logger.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -27,7 +27,7 @@ mask = "$plugin.$name.weechatlog" name_lower_case = on nick_prefix = "" nick_suffix = "" -path = "%h/logs/" +path = "${weechat_data_dir}/logs" replacement_char = "_" time_format = "%Y-%m-%d %H:%M:%S" diff --git a/.config/weechat/lua.conf b/.config/weechat/lua.conf index fa4966b2..6b41572f 100644 --- a/.config/weechat/lua.conf +++ b/.config/weechat/lua.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/perl.conf b/.config/weechat/perl.conf index 31924b91..21ccaf82 100644 --- a/.config/weechat/perl.conf +++ b/.config/weechat/perl.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/perl/atcomplete.pl b/.config/weechat/perl/atcomplete.pl deleted file mode 100644 index e0b5d5c8..00000000 --- a/.config/weechat/perl/atcomplete.pl +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2015 by David A. Golden. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# ABOUT -# -# atcomplete.pl -# -# Adds nick completion when prefixed with '@' for use with IRC gateways -# for Slack, Flowdock, etc. as these require the '@' to highlight users -# -# CONFIG -# -# /set plugins.var.perl.atcomplete.enabled -# -# HISTORY -# -# 0.001 -- xdg, 2016-04-06 -# -# - initial release -# -# REPOSITORY -# -# https://github.com/xdg/weechat-atcomplete - -use strict; -use warnings; -my $SCRIPT_NAME = "atcomplete"; -my $VERSION = "0.001"; - -my %options_default = ( - 'enabled' => ['on', 'enable completion of nicks starting with @'], -); -my %options = (); - -weechat::register($SCRIPT_NAME, "David A. Golden", $VERSION, - "Apache2", "atcomplete - do nick completion following @", "", ""); -init_config(); - -weechat::hook_config("plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", ""); -weechat::hook_completion("nicks", "Add @ prefix to nick completion", "complete_at_nicks", ""); - -sub complete_at_nicks { - my ($data, $completion_item, $buffer, $completion ) = @_; - return weechat::WEECHAT_RC_OK() unless $options{enabled} eq 'on'; - - my $nicklist = weechat::infolist_get("nicklist", weechat::current_buffer(), ""); - - if ($nicklist ne "") { - while (weechat::infolist_next($nicklist)) { - next unless weechat::infolist_string($nicklist, "type") eq "nick"; - my $nick = weechat::infolist_string($nicklist, "name"); - weechat::hook_completion_list_add($completion, "\@$nick", 1, weechat::WEECHAT_LIST_POS_SORT()); - } - } - - weechat::infolist_free($nicklist); - - return weechat::WEECHAT_RC_OK(); -} - -sub toggle_config_by_set { - my ($pointer, $name, $value) = @_; - $name = substr($name, length("plugins.var.perl.".$SCRIPT_NAME."."), length($name)); - $options{$name} = $value; - return weechat::WEECHAT_RC_OK(); -} - -sub init_config { - my $version = weechat::info_get("version_number", "") || 0; - foreach my $option (keys %options_default) - { - if (!weechat::config_is_set_plugin($option)) - { - weechat::config_set_plugin($option, $options_default{$option}[0]); - $options{$option} = $options_default{$option}[0]; - } - else - { - $options{$option} = weechat::config_get_plugin($option); - } - if ($version >= 0x00030500) - { - weechat::config_set_desc_plugin($option, $options_default{$option}[1]." (default: \"".$options_default{$option}[0]."\")"); - } - } -} diff --git a/.config/weechat/perl/autoload/atcomplete.pl b/.config/weechat/perl/autoload/atcomplete.pl deleted file mode 100644 index e0b5d5c8..00000000 --- a/.config/weechat/perl/autoload/atcomplete.pl +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2015 by David A. Golden. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# ABOUT -# -# atcomplete.pl -# -# Adds nick completion when prefixed with '@' for use with IRC gateways -# for Slack, Flowdock, etc. as these require the '@' to highlight users -# -# CONFIG -# -# /set plugins.var.perl.atcomplete.enabled -# -# HISTORY -# -# 0.001 -- xdg, 2016-04-06 -# -# - initial release -# -# REPOSITORY -# -# https://github.com/xdg/weechat-atcomplete - -use strict; -use warnings; -my $SCRIPT_NAME = "atcomplete"; -my $VERSION = "0.001"; - -my %options_default = ( - 'enabled' => ['on', 'enable completion of nicks starting with @'], -); -my %options = (); - -weechat::register($SCRIPT_NAME, "David A. Golden", $VERSION, - "Apache2", "atcomplete - do nick completion following @", "", ""); -init_config(); - -weechat::hook_config("plugins.var.perl.$SCRIPT_NAME.*", "toggle_config_by_set", ""); -weechat::hook_completion("nicks", "Add @ prefix to nick completion", "complete_at_nicks", ""); - -sub complete_at_nicks { - my ($data, $completion_item, $buffer, $completion ) = @_; - return weechat::WEECHAT_RC_OK() unless $options{enabled} eq 'on'; - - my $nicklist = weechat::infolist_get("nicklist", weechat::current_buffer(), ""); - - if ($nicklist ne "") { - while (weechat::infolist_next($nicklist)) { - next unless weechat::infolist_string($nicklist, "type") eq "nick"; - my $nick = weechat::infolist_string($nicklist, "name"); - weechat::hook_completion_list_add($completion, "\@$nick", 1, weechat::WEECHAT_LIST_POS_SORT()); - } - } - - weechat::infolist_free($nicklist); - - return weechat::WEECHAT_RC_OK(); -} - -sub toggle_config_by_set { - my ($pointer, $name, $value) = @_; - $name = substr($name, length("plugins.var.perl.".$SCRIPT_NAME."."), length($name)); - $options{$name} = $value; - return weechat::WEECHAT_RC_OK(); -} - -sub init_config { - my $version = weechat::info_get("version_number", "") || 0; - foreach my $option (keys %options_default) - { - if (!weechat::config_is_set_plugin($option)) - { - weechat::config_set_plugin($option, $options_default{$option}[0]); - $options{$option} = $options_default{$option}[0]; - } - else - { - $options{$option} = weechat::config_get_plugin($option); - } - if ($version >= 0x00030500) - { - weechat::config_set_desc_plugin($option, $options_default{$option}[1]." (default: \"".$options_default{$option}[0]."\")"); - } - } -} diff --git a/.config/weechat/perl/autoload/awaylog.pl b/.config/weechat/perl/autoload/awaylog.pl deleted file mode 100644 index 91e9b1b8..00000000 --- a/.config/weechat/perl/autoload/awaylog.pl +++ /dev/null @@ -1,100 +0,0 @@ -############################################################################### -# -# Copyright (c) 2008 by GolemJ -# -# 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 program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -############################################################################### -# -# Log highlights msg to core buffer -# You need to set "notify" to "yes" and "command" to proper command to run -# external command. You also need "shell" script to run external command. -# -# History: -# 2010-06-20, GolemJ -# version 0.8, add posibility to execute command for notification -# 2010-02-14, Emmanuel Bouthenot -# version 0.7, add colors and notifications support -# 2009-05-02, FlashCode : -# version 0.6, sync with last API changes -# 2008-11-30, GolemJ : -# version 0.5, conversion to WeeChat 0.3.0+ -# -############################################################################### - -use strict; - -weechat::register( "awaylog", "Jiri Golembiovsky", "0.8", "GPL", "Prints highlights to core buffer", "", "" ); -weechat::hook_print( "", "", "", 1, "highlight_cb", "" ); - -if( weechat::config_get_plugin( "on_away_only" ) eq "" ) { - weechat::config_set_plugin( "on_away_only", "off" ); -} - -if( weechat::config_get_plugin( "plugin_color" ) eq "" ) { - weechat::config_set_plugin( "plugin_color", "default" ); -} - -if( weechat::config_get_plugin( "name_color" ) eq "" ) { - weechat::config_set_plugin( "name_color", "default" ); -} - -if( weechat::config_get_plugin( "notify" ) eq "" ) { - weechat::config_set_plugin( "notify", "off" ); -} - -if( weechat::config_get_plugin( "command" ) eq "") { - weechat::config_set_plugin( "command", "" ); -} - -sub highlight_cb { - if( $_[5] == 1 ) { - my $away = weechat::buffer_get_string($_[1], "localvar_away"); - if (($away ne "") || (weechat::config_get_plugin( "on_away_only" ) ne "on")) - { - my $buffer_color = weechat::color(weechat::config_get_plugin( "plugin_color")) - . weechat::buffer_get_string($_[1], "plugin") - . "." - . weechat::buffer_get_string($_[1], "name") - . weechat::color("default"); - my $buffer = weechat::buffer_get_string($_[1], "plugin") - . "." - . weechat::buffer_get_string($_[1], "name"); - my $name_color = weechat::color(weechat::config_get_plugin( "name_color")) - . $_[6] - . weechat::color("default"); - my $name = $_[6]; - my $message_color = "${buffer_color} -- ${name_color} :: $_[7]"; - my $message = "${buffer} -- ${name} :: $_[7]"; - if( weechat::config_get_plugin( "notify" ) ne "on" ) { - my $command = weechat::config_get_plugin( "command" ); - if( $command ne "" ) { - if( $command =~ /\$msg/ ) { - $command =~ s/\$msg/\'$message\'/; - } else { - $command = "$command '$message'"; - } - weechat::command( "", "/shell $command" ); - } else { - weechat::print("", $message_color); - } - } else { - weechat::print_date_tags("", 0, "notify_highlight", $message_color); - } - } - } - - return weechat::WEECHAT_RC_OK; -} diff --git a/.config/weechat/perl/autoload/highmon.pl b/.config/weechat/perl/autoload/highmon.pl deleted file mode 100644 index f843cade..00000000 --- a/.config/weechat/perl/autoload/highmon.pl +++ /dev/null @@ -1,1154 +0,0 @@ -# -# highmon.pl - Highlight Monitoring for weechat 0.3.0 -# -# Add 'Highlight Monitor' buffer/bar to log all highlights in one spot -# -# Usage: -# /highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar -# Command wrapper for highmon commands -# -# /highmon clean default|orphan|all will clean the config section of default 'on' entries, -# channels you are no longer joined, or both -# -# /highmon clearbar will clear the contents of highmon's bar output -# -# /highmon monitor [channel] [server] is used to toggle a highlight monitoring on and off, this -# can be used in the channel buffer for the channel you wish to toggle, or be given -# with arguments e.g. /highmon monitor #weechat freenode -# -# /set plugins.var.perl.highmon.alignment -# The config setting "alignment" can be changed to; -# "channel", "schannel", "nchannel", "channel,nick", "schannel,nick", "nchannel,nick" -# to change how the monitor appears -# The 'channel' value will show: "#weechat" -# The 'schannel' value will show: "6" -# The 'nchannel' value will show: "6:#weechat" -# -# /set plugins.var.perl.highmon.short_names -# Setting this to 'on' will trim the network name from highmon, ala buffers.pl -# -# /set plugins.var.perl.highmon.merge_private -# Setting this to 'on' will merge private messages to highmon's display -# -# /set plugins.var.perl.highmon.color_buf -# This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. -# This *must* be a valid color name, or weechat will likely do unexpected things :) -# -# /set plugins.var.perl.highmon.hotlist_show -# Setting this to 'on' will let the highmon buffer appear in hotlists -# (status bar/buffer.pl) -# -# /set plugins.var.perl.highmon.away_only -# Setting this to 'on' will only put messages in the highmon buffer when -# you set your status to away -# -# /set plugins.var.perl.highmon.logging -# Toggles logging status for highmon buffer (default: off) -# -# /set plugins.var.perl.highmon.output -# Changes where output method of highmon; takes either "bar" or "buffer" (default; buffer) -# /set plugins.var.perl.highmon.bar_lines -# Changes the amount of lines the output bar will hold. -# (Only appears once output has been set to bar, defaults to 10) -# /set plugins.var.perl.highmon.bar_scrolldown -# Toggles the bar scrolling at the bottom when new highlights are received -# (Only appears once output has been set to bar, defaults to off) -# -# /set plugins.var.perl.highmon.nick_prefix -# /set plugins.var.perl.highmon.nick_suffix -# Sets the prefix and suffix chars in the highmon buffer -# (Defaults to <> if nothing set, and blank if there is) -# -# servername.#channel -# servername is the internal name for the server (set when you use /server add) -# #channel is the channel name, (where # is whatever channel type that channel happens to be) -# -# Optional, set up tweaks; Hide the status and input lines on highmon -# -# /set weechat.bar.status.conditions "${window.buffer.full_name} != perl.highmon" -# /set weechat.bar.input.conditions "${window.buffer.full_name} != perl.highmon" -# - -# Bugs and feature requests at: https://github.com/KenjiE20/highmon - -# History: -# 2020-06-21, Sebastien Helleu : -# v2.7: make call to bar_new compatible with WeeChat >= 2.9 -# 2019-05-13, HubbeKing -# v2.6: -add: send "logger_backlog" signal on buffer open if logging is enabled -# 2014-08-16, KenjiE20 : -# v2.5: -add: clearbar command to clear bar output -# -add: firstrun output prompt to check the help text for set up hints as they were being missed -# and update hint for conditions to use eval -# -change: Make all outputs use the date callback for more accurate timestamps (thanks Germainz) -# 2013-12-04, KenjiE20 : -# v2.4: -add: Support for eval style colour codes in time format used for bar output -# 2013-10-22, KenjiE20 : -# v2.3.3.2: -fix: Typo in fix command -# 2013-10-10, KenjiE20 : -# v2.3.3.1: -fix: Typo in closed buffer warning -# 2013-10-07, KenjiE20 : -# v2.3.3: -add: Warning and fixer for accidental buffer closes -# 2013-01-15, KenjiE20 : -# v2.3.2: -fix: Let bar output use the string set in weechat's config option -# -add: github info -# 2012-04-15, KenjiE20 : -# v2.3.1: -fix: Colour tags in bar timestamp string -# 2012-02-28, KenjiE20 : -# v2.3: -feature: Added merge_private option to display private messages (default: off) -# -fix: Channel name colours now show correctly when set to on -# 2011-08-07, Sitaktif : -# v2.2.1: -feature: Add "bar_scrolldown" option to have the bar display the latest hl at anytime -# -fix: Set up bar-specific config at startup if 'output' is already configured as 'bar' -# 2010-12-22, KenjiE20 : -# v2.2: -change: Use API instead of config to find channel colours, ready for 0.3.4 and 256 colours -# 2010-12-13, idl0r & KenjiE20 : -# v2.1.3: -fix: perl errors caused by bar line counter -# -fix: Add command list to inbuilt help -# 2010-09-30, KenjiE20 : -# v2.1.2: -fix: logging config was not correctly toggling back on (thanks to sleo for noticing) -# -version sync w/ chanmon -# 2010-08-27, KenjiE20 : -# v2.1: -feature: Add 'nchannel' option to alignment to display buffer and name -# 2010-04-25, KenjiE20 : -# v2.0: Release as version 2.0 -# 2010-04-24, KenjiE20 : -# v1.9: Rewrite for v2.0 -# Bring feature set in line with chanmon 2.0 -# -code change: Made more subs to shrink the code down in places -# -fix: Stop highmon attempting to double load/hook -# -fix: Add version dependant check for away status -# 2010-01-25, KenjiE20 : -# v1.7: -fixture: Let highmon be aware of nick_prefix/suffix -# and allow custom prefix/suffix for chanmon buffer -# (Defaults to <> if nothing set, and blank if there is) -# (Thanks to m4v for these) -# 2009-09-07, KenjiE20 : -# v1.6: -feature: colored buffer names -# -change: version sync with chanmon -# 2009-09-05, KenjiE20 : -# v1.2: -fix: disable buffer highlight -# 2009-09-02, KenjiE20 : -# v.1.1.1 -change: Stop unsightly text block on '/help' -# 2009-08-10, KenjiE20 : -# v1.1: In-client help added -# 2009-08-02, KenjiE20 : -# v1.0: Initial Public Release - -# Copyright (c) 2009 by KenjiE20 -# -# 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 3 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 program. If not, see . -# - -@bar_lines = (); -@bar_lines_time = (); -# Replicate info earlier for in-client help - -$highmonhelp = weechat::color("bold")."/highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar".weechat::color("-bold")." - Command wrapper for highmon commands - -".weechat::color("bold")."/highmon clean default|orphan|all".weechat::color("-bold")." will clean the config section of default 'on' entries, channels you are no longer joined, or both - -".weechat::color("bold")."/highmon clearbar".weechat::color("-bold")." will clear the contents of highmon's bar output - -".weechat::color("bold")."/highmon monitor [channel] [server]".weechat::color("-bold")." is used to toggle a highlight monitoring on and off, this can be used in the channel buffer for the channel you wish to toggle, or be given with arguments e.g. /highmon monitor #weechat freenode - -".weechat::color("bold")."/set plugins.var.perl.highmon.alignment".weechat::color("-bold")." - The config setting \"alignment\" can be changed to; - \"channel\", \"schannel\", \"nchannel\", \"channel,nick\", \"schannel,nick\", \"nchannel,nick\" - to change how the monitor appears - The 'channel' value will show: \"#weechat\" - The 'schannel' value will show: \"6\" - The 'nchannel' value will show: \"6:#weechat\" - -".weechat::color("bold")."/set plugins.var.perl.highmon.short_names".weechat::color("-bold")." - Setting this to 'on' will trim the network name from highmon, ala buffers.pl - -".weechat::color("bold")."/set plugins.var.perl.highmon.merge_private".weechat::color("-bold")." - Setting this to 'on' will merge private messages to highmon's display - -".weechat::color("bold")."/set plugins.var.perl.highmon.color_buf".weechat::color("-bold")." - This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. - This ".weechat::color("bold")."must".weechat::color("-bold")." be a valid color name, or weechat will likely do unexpected things :) - -".weechat::color("bold")."/set plugins.var.perl.highmon.hotlist_show".weechat::color("-bold")." -Setting this to 'on' will let the highmon buffer appear in hotlists (status bar/buffer.pl) - -".weechat::color("bold")."/set plugins.var.perl.highmon.away_only".weechat::color("-bold")." -Setting this to 'on' will only put messages in the highmon buffer when you set your status to away - -".weechat::color("bold")."/set plugins.var.perl.highmon.logging".weechat::color("-bold")." - Toggles logging status for highmon buffer (default: off) - -".weechat::color("bold")."/set plugins.var.perl.highmon.output".weechat::color("-bold")." - Changes where output method of highmon; takes either \"bar\" or \"buffer\" (default; buffer) -".weechat::color("bold")."/set plugins.var.perl.highmon.bar_lines".weechat::color("-bold")." - Changes the amount of lines the output bar will hold. - (Only appears once output has been set to bar, defaults to 10) -".weechat::color("bold")."/set plugins.var.perl.highmon.bar_scrolldown".weechat::color("-bold")." - Toggles the bar scrolling at the bottom when new highlights are received - (Only appears once output has been set to bar, defaults to off) - -".weechat::color("bold")."/set plugins.var.perl.highmon.nick_prefix".weechat::color("-bold")." -".weechat::color("bold")."/set plugins.var.perl.highmon.nick_suffix".weechat::color("-bold")." - Sets the prefix and suffix chars in the highmon buffer - (Defaults to <> if nothing set, and blank if there is) - -".weechat::color("bold")."servername.#channel".weechat::color("-bold")." - servername is the internal name for the server (set when you use /server add) - #channel is the channel name, (where # is whatever channel type that channel happens to be) - -".weechat::color("bold")."Optional, set up tweaks;".weechat::color("-bold")." Hide the status and input lines on highmon - -".weechat::color("bold")."/set weechat.bar.status.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold")." -".weechat::color("bold")."/set weechat.bar.input.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold"); -# Print verbose help -sub print_help -{ - weechat::print("", "\t".weechat::color("bold")."Highmon Help".weechat::color("-bold")."\n\n"); - weechat::print("", "\t".$highmonhelp); - return weechat::WEECHAT_RC_OK; -} - -# Bar item build -sub highmon_bar_build -{ - # Get max lines - $max_lines = weechat::config_get_plugin("bar_lines"); - $max_lines = $max_lines ? $max_lines : 10; - $str = ''; - $align_num = 0; - $count = 0; - # Keep lines within max - while ($#bar_lines > $max_lines) - { - shift(@bar_lines); - shift(@bar_lines_time); - } - # So long as we have some lines, build a string - if (@bar_lines) - { - # Build loop - $sep = " ".weechat::config_string(weechat::config_get("weechat.look.prefix_suffix"))." "; - foreach(@bar_lines) - { - # Find max align needed - $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); - $align_num = $prefix_num if ($prefix_num > $align_num); - } - foreach(@bar_lines) - { - # Get align for this line - $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); - - # Make string - $str = $str.$bar_lines_time[$count]." ".(" " x ($align_num - $prefix_num)).$_."\n"; - # Increment count for sync with time list - $count++; - } - } - return $str; -} - -# Make a new bar -sub highmon_bar_open -{ - # Make the bar item - weechat::bar_item_new("highmon", "highmon_bar_build", ""); - - if (weechat::info_get("version_number", "") >= 0x02090000) - { - $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "default", "on", "highmon"); - } - else - { - $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "on", "highmon"); - } - - return weechat::WEECHAT_RC_OK; -} -# Close bar -sub highmon_bar_close -{ - # Find if bar exists - $highmon_bar = weechat::bar_search("highmon"); - # If is does, close it - if ($highmon_bar ne "") - { - weechat::bar_remove($highmon_bar); - } - - # Find if bar item exists - $highmon_bar_item = weechat::bar_item_search("highmon_bar"); - # If is does, close it - if ($highmon_bar_item ne "") - { - weechat::bar_remove($highmon_bar_item); - } - - @bar_lines = (); - return weechat::WEECHAT_RC_OK; -} - -# Make a new buffer -sub highmon_buffer_open -{ - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - - # Make a new buffer - if ($highmon_buffer eq "") - { - $highmon_buffer = weechat::buffer_new("highmon", "highmon_buffer_input", "", "highmon_buffer_close", ""); - } - - # Turn off notify, highlights - if ($highmon_buffer ne "") - { - if (weechat::config_get_plugin("hotlist_show") eq "off") - { - weechat::buffer_set($highmon_buffer, "notify", "0"); - } - weechat::buffer_set($highmon_buffer, "highlight_words", "-"); - weechat::buffer_set($highmon_buffer, "title", "Highlight Monitor"); - # Set no_log - if (weechat::config_get_plugin("logging") eq "off") - { - weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); - } - # send "logger_backlog" signal if logging is enabled to display backlog - if (weechat::config_get_plugin("logging") eq "on") - { - weechat::hook_signal_send("logger_backlog", weechat::WEECHAT_HOOK_SIGNAL_POINTER, $highmon_buffer) - } - } - return weechat::WEECHAT_RC_OK; -} -# Buffer input has no action -sub highmon_buffer_input -{ - return weechat::WEECHAT_RC_OK; -} -# Close up -sub highmon_buffer_close -{ - $highmon_buffer = ""; - # If user hasn't changed output style warn user - if (weechat::config_get_plugin("output") eq "buffer") - { - weechat::print("", "\tHighmon buffer has been closed but output is still set to buffer, unusual results may occur. To recreate the buffer use ".weechat::color("bold")."/highmon fix".weechat::color("-bold")); - } - return weechat::WEECHAT_RC_OK; -} - -# Highmon command wrapper -sub highmon_command_cb -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - my $cmd = ''; - my $arg = ''; - - if ($args ne "") - { - # Split argument up - @arg_array = split(/ /,$args); - # Take first as command - $cmd = shift(@arg_array); - # Rebuild string to pass to subs - if (@arg_array) - { - $arg = join(" ", @arg_array); - } - } - - # Help command - if ($cmd eq "" || $cmd eq "help") - { - print_help(); - } - # /monitor command - elsif ($cmd eq "monitor") - { - highmon_toggle($data, $buffer, $arg); - } - # /highclean command - elsif ($cmd eq "clean") - { - highmon_config_clean($data, $buffer, $arg); - } - # clearbar command - elsif ($cmd eq "clearbar") - { - if (weechat::config_get_plugin("output") eq "bar") - { - @bar_lines = (); - weechat::bar_item_update("highmon"); - } - } - # Fix closed buffer - elsif ($cmd eq "fix") - { - if (weechat::config_get_plugin("output") eq "buffer" && $highmon_buffer eq "") - { - highmon_buffer_open(); - } - } - return weechat::WEECHAT_RC_OK; -} - -# Clean up config entries -sub highmon_config_clean -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - - # Don't do anything if bad option given - if ($args ne "default" && $args ne "orphan" && $args ne "all") - { - weechat::print("", "\thighmon.pl: Unknown option"); - return weechat::WEECHAT_RC_OK; - } - - @chans = (); - # Load an infolist of highmon options - $infolist = weechat::infolist_get("option", "", "*highmon*"); - while (weechat::infolist_next($infolist)) - { - $name = weechat::infolist_string($infolist, "option_name"); - $name =~ s/perl\.highmon\.(\w*)\.([#&\+!])(.*)/$1.$2$3/; - if ($name =~ /^(.*)\.([#&\+!])(.*)$/) - { - $action = 0; - # Clean up all 'on's - if ($args eq "default" || $args eq "all") - { - # If value in config is "on" - if (weechat::config_get_plugin($name) eq "on") - { - # Unset and if successful flag as changed - $rc = weechat::config_unset_plugin($name); - if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) - { - $action = 1; - } - } - } - # Clean non joined - if ($args eq "orphan" || $args eq "all") - { - # If we can't find the buffer for this entry - if (weechat::buffer_search("irc", $name) eq "") - { - # Unset and if successful flag as changed - $rc = weechat::config_unset_plugin($name); - if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) - { - $action = 1; - } - } - } - # Add changed entry names to list - push (@chans, $name) if ($action); - } - } - weechat::infolist_free($infolist); - # If channels were cleaned from config - if (@chans) - { - # If only one entry - if (@chans == 1) - { - $str = "\thighmon.pl: Cleaned ".@chans." entry from the config:"; - } - else - { - $str = "\thighmon.pl: Cleaned ".@chans." entries from the config:"; - } - # Build a list of channels - foreach(@chans) - { - $str = $str." ".$_; - } - # Print what happened - weechat::print("",$str); - } - # Config seemed to be clean - else - { - weechat::print("", "\thighmon.pl: No entries removed"); - } - return weechat::WEECHAT_RC_OK; -} - -# Check config elements -sub highmon_config_init -{ - # First run default - if (!(weechat::config_is_set_plugin ("first_run"))) - { - if (weechat::config_get_plugin("first_run") ne "true") - { - weechat::print("", "\tThis appears to be the first time highmon has been run. For help and common set up hints see /highmon help"); - weechat::config_set_plugin("first_run", "true"); - } - } - # Alignment default - if (!(weechat::config_is_set_plugin ("alignment"))) - { - weechat::config_set_plugin("alignment", "channel"); - } - if (weechat::config_get_plugin("alignment") eq "") - { - weechat::config_set_plugin("alignment", "none"); - } - - # Short name default - if (!(weechat::config_is_set_plugin ("short_names"))) - { - weechat::config_set_plugin("short_names", "off"); - } - - # Coloured names default - if (!(weechat::config_is_set_plugin ("color_buf"))) - { - weechat::config_set_plugin("color_buf", "on"); - } - - # Hotlist show default - if (!(weechat::config_is_set_plugin ("hotlist_show"))) - { - weechat::config_set_plugin("hotlist_show", "off"); - } - - # Away only default - if (!(weechat::config_is_set_plugin ("away_only"))) - { - weechat::config_set_plugin("away_only", "off"); - } - - # highmon log default - if (!(weechat::config_is_set_plugin ("logging"))) - { - weechat::config_set_plugin("logging", "off"); - } - - # Output default - if (!(weechat::config_is_set_plugin ("output"))) - { - weechat::config_set_plugin("output", "buffer"); - } - - # Private message merging - if (!(weechat::config_is_set_plugin ("merge_private"))) - { - weechat::config_set_plugin("merge_private", "off"); - } - - # Set bar config in case output was set to "bar" before even changing the setting - if (weechat::config_get_plugin("output") eq "bar") - { - # Output bar lines default - if (!(weechat::config_is_set_plugin ("bar_lines"))) - { - weechat::config_set_plugin("bar_lines", "10"); - } - if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) - { - weechat::config_set_plugin("bar_scrolldown", "off"); - } - } - - # Check for exisiting prefix/suffix chars, and setup accordingly - $prefix = weechat::config_get("irc.look.nick_prefix"); - $prefix = weechat::config_string($prefix); - $suffix = weechat::config_get("irc.look.nick_suffix"); - $suffix = weechat::config_string($suffix); - - if (!(weechat::config_is_set_plugin("nick_prefix"))) - { - if ($prefix eq "" && $suffix eq "") - { - weechat::config_set_plugin("nick_prefix", "<"); - } - else - { - weechat::config_set_plugin("nick_prefix", ""); - } - } - - if (!(weechat::config_is_set_plugin("nick_suffix"))) - { - if ($prefix eq "" && $suffix eq "") - { - weechat::config_set_plugin("nick_suffix", ">"); - } - else - { - weechat::config_set_plugin("nick_suffix", ""); - } - } -} - -# Get config updates -sub highmon_config_cb -{ - $point = $_[0]; - $name = $_[1]; - $value = $_[2]; - - $name =~ s/^plugins\.var\.perl\.highmon\.//; - - # Set logging on buffer - if ($name eq "logging") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - if ($value eq "off") - { - weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); - } - else - { - weechat::buffer_set($highmon_buffer, "localvar_del_no_log", ""); - } - } - # Output changer - elsif ($name eq "output") - { - if ($value eq "bar") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - # Close if it exists - if ($highmon_buffer ne "") - { - weechat::buffer_close($highmon_buffer) - } - - # Output bar lines default - if (!(weechat::config_is_set_plugin ("bar_lines"))) - { - weechat::config_set_plugin("bar_lines", "10"); - } - if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) - { - weechat::config_set_plugin("bar_scrolldown", "off"); - } - # Make a bar if doesn't exist - highmon_bar_open(); - } - elsif ($value eq "buffer") - { - # If a bar exists, close it - highmon_bar_close(); - # Open buffer - highmon_buffer_open(); - } - - } - # Change if hotlist config changes - elsif ($name eq "hotlist_show") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - if ($value eq "off" && $highmon_buffer) - { - weechat::buffer_set($highmon_buffer, "notify", "0"); - } - elsif ($value ne "off" && $highmon_buffer) - { - weechat::buffer_set($highmon_buffer, "notify", "3"); - } - } - elsif ($name eq "weechat.look.prefix_suffix") - { - if (weechat::config_get_plugin("output") eq "bar") - { - @bar_lines = (); - weechat::print("", "\thighmon: weechat.look.prefix_suffix changed, clearing highmon bar"); - weechat::bar_item_update("highmon"); - } - } - return weechat::WEECHAT_RC_OK; -} - -# Set up weechat hooks / commands -sub highmon_hook -{ - weechat::hook_print("", "", "", 0, "highmon_new_message", ""); - weechat::hook_command("highclean", "Highmon config clean up", "default|orphan|all", " default: Cleans all config entries with the default \"on\" value\n orphan: Cleans all config entries for channels you aren't currently joined\n all: Does both defaults and orphan", "default|orphan|all", "highmon_config_clean", ""); - - weechat::hook_command("highmon", "Highmon help", "[help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar", " help: Print help on config options for highmon\n monitor: Toggles monitoring for a channel\n clean: Highmon config clean up (/highclean)\nclearbar: Clear Highmon bar", "help || monitor %(irc_channels) %(irc_servers) || clean default|orphan|all || clearbar", "highmon_command_cb", ""); - - weechat::hook_config("plugins.var.perl.highmon.*", "highmon_config_cb", ""); - weechat::hook_config("weechat.look.prefix_suffix", "highmon_config_cb", ""); -} - -# Main body, Callback for hook_print -sub highmon_new_message -{ - my $net = ""; - my $chan = ""; - my $nick = ""; - my $outstr = ""; - my $window_displayed = ""; - my $dyncheck = "0"; - -# DEBUG point -# $string = "\t"."0: ".$_[0]." 1: ".$_[1]." 2: ".$_[2]." 3: ".$_[3]." 4: ".$_[4]." 5: ".$_[5]." 6: ".$_[6]." 7: ".$_[7]; -# weechat::print("", "\t".$string); - - $cb_datap = $_[0]; - $cb_bufferp = $_[1]; - $cb_date = $_[2]; - $cb_tags = $_[3]; - $cb_disp = $_[4]; - $cb_high = $_[5]; - $cb_prefix = $_[6]; - $cb_msg = $_[7]; - - # Only work on highlighted messages or private message when enabled - if ($cb_high == "1" || (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/)) - { - # Pre bug #29618 (0.3.3) away detect - if (weechat::info_get("version_number", "") <= 0x00030200) - { - $away = ''; - # Get infolist for this server - $infolist = weechat::infolist_get("irc_server", "", weechat::buffer_get_string($cb_bufferp, "localvar_server")); - while (weechat::infolist_next($infolist)) - { - # Get away message is is_away is on - $away = weechat::infolist_string($infolist, "away_message") if (weechat::infolist_integer($infolist, "is_away")); - } - weechat::infolist_free($infolist); - } - # Post bug #29618 fix - else - { - $away = weechat::buffer_get_string($cb_bufferp, "localvar_away"); - } - if (weechat::config_get_plugin("away_only") ne "on" || ($away ne "")) - { - # Check buffer name is an IRC channel - $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); - if ($bufname =~ /(.*)\.([#&\+!])(.*)/) - { - # Are we running on this channel - if (weechat::config_get_plugin($bufname) ne "off" && $cb_disp eq "1") - { - # Format nick - # Line isn't action or topic notify - if (!($cb_tags =~ /irc_action/) && !($cb_tags =~ /irc_topic/)) - { - # Strip nick colour - $uncolnick = weechat::string_remove_color($cb_prefix, ""); - # Format nick - $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); - } - # Topic line - elsif ($cb_tags =~ /irc_topic/) - { - $nick = " ".$cb_prefix.weechat::color("reset"); - } - # Action line - else - { - $uncolnick = weechat::string_remove_color($cb_prefix, ""); - $nick = weechat::color("chat_highlight").$uncolnick.weechat::color("reset"); - } - # Send to output - highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); - } - } - # Or is private message - elsif (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/) - { - # Strip nick colour - $uncolnick = weechat::buffer_get_string($cb_bufferp, 'short_name'); - # Format nick - $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); - #Send to output - highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); - } - } - } - return weechat::WEECHAT_RC_OK; -} - -# Output formatter and printer takes (msg bufpointer nick) -sub highmon_print -{ - $cb_msg = $_[0]; - my $cb_bufferp = $_[1] if ($_[1]); - my $nick = $_[2] if ($_[2]); - my $cb_date = $_[3] if ($_[3]); - my $cb_tags = $_[4] if ($_[4]); - - #Normal channel message - if ($cb_bufferp && $nick) - { - # Format buffer name - $bufname = format_buffer_name($cb_bufferp); - - # If alignment is #channel | nick msg - if (weechat::config_get_plugin("alignment") eq "channel") - { - $nick =~ s/\s(.*)/$1/; - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is channel number | nick msg - elsif (weechat::config_get_plugin("alignment") eq "schannel") - { - $nick =~ s/\s(.*)/$1/; - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is number:#channel | nick msg - elsif (weechat::config_get_plugin("alignment") eq "nchannel") - { - $nick =~ s/\s(.*)/$1/; - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is #channel nick | msg - elsif (weechat::config_get_plugin("alignment") eq "channel,nick") - { - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or if it is channel number nick | msg - elsif (weechat::config_get_plugin("alignment") eq "schannel,nick") - { - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or if it is number:#channel nick | msg - elsif (weechat::config_get_plugin("alignment") eq "nchannel,nick") - { - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or finally | #channel nick msg - else - { - # Build string - $outstr = "\t".$bufname.":".$nick." ".$cb_msg; - } - } - # highmon channel toggle message - elsif ($cb_bufferp && !$nick) - { - # Format buffer name - $bufname = format_buffer_name($cb_bufferp); - - # If alignment is #channel * | * - if (weechat::config_get_plugin("alignment") =~ /channel/) - { - # If it's actually channel number * | * - if (weechat::config_get_plugin("alignment") =~ /schannel/) - { - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - } - # Or if it's actually number:#channel * | * - if (weechat::config_get_plugin("alignment") =~ /nchannel/) - { - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - } - $outstr = $bufname."\t".$cb_msg; - } - # or if alignment is | * - else - { - $outstr = $bufname.": ".$cb_msg; - } - } - # highmon dynmon - elsif (!$cb_bufferp && !$nick) - { - $outstr = "\t".$cb_msg; - } - - # Send string to buffer - if (weechat::config_get_plugin("output") eq "buffer") - { - # Search for and confirm buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - # Print - if ($cb_date) - { - weechat::print_date_tags($highmon_buffer, $cb_date, $cb_tags, $outstr); - } - else - { - weechat::print($highmon_buffer, $outstr); - } - } - elsif (weechat::config_get_plugin("output") eq "bar") - { - # Add time string - use POSIX qw(strftime); - if ($cb_date) - { - $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime($cb_date)); - } - else - { - $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime); - } - # Colourise - if ($time =~ /\$\{(?:color:)?[\w,]+\}/) # Coloured string - { - while ($time =~ /\$\{(?:color:)?([\w,]+)\}/) - { - $color = weechat::color($1); - $time =~ s/\$\{(?:color:)?[\w,]+\}/$color/; - } - $time .= weechat::color("reset"); - } - else # Default string - { - $colour = weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_time_delimiters"))); - $reset = weechat::color("reset"); - $time =~ s/(\d*)(.)(\d*)/$1$colour$2$reset$3/g; - } - # Push updates to bar lists - push (@bar_lines_time, $time); - - # Change tab char - $delim = " ".weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_delimiters"))).weechat::config_string(weechat::config_get("weechat.look.prefix_suffix")).weechat::color("reset")." "; - $outstr =~ s/\t/$delim/; - - push (@bar_lines, $outstr); - # Trigger update - weechat::bar_item_update("highmon"); - - if (weechat::config_get_plugin("bar_scrolldown") eq "on") - { - weechat::command("", "/bar scroll highmon * ye") - } - } -} - -# Start the output display -sub highmon_start -{ - if (weechat::config_get_plugin("output") eq "buffer") - { - highmon_buffer_open(); - } - elsif (weechat::config_get_plugin("output") eq "bar") - { - highmon_bar_open(); - } -} - -# Takes two optional args (channel server), toggles monitoring on/off -sub highmon_toggle -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - - # Check if we've been told what channel to act on - if ($args ne "") - { - # Split argument up - @arg_array = split(/ /,$args); - # Check if a server was given - if ($arg_array[1]) - { - # Find matching - $bufp = weechat::buffer_search("irc", $arg_array[1].".".$arg_array[0]); - } - else - { - $found_chans = 0; - # Loop through defined servers - $infolist = weechat::infolist_get("buffer", "", ""); - while (weechat::infolist_next($infolist)) - { - # Only interesting in IRC buffers - if (weechat::infolist_string($infolist, "plugin_name") eq "irc") - { - # Find buffers that maych - $sname = weechat::infolist_string($infolist, "short_name"); - if ($sname eq $arg_array[0]) - { - $found_chans++; - $bufp = weechat::infolist_pointer($infolist, "pointer"); - } - } - } - weechat::infolist_free($infolist); - # If the infolist found more than one channel, halt as we need to know which one - if ($found_chans > 1) - { - weechat::print("", "Channel name is not unique, please define server"); - return weechat::WEECHAT_RC_OK; - } - } - # Something didn't return right - if ($bufp eq "") - { - weechat::print("", "Could not find buffer"); - return weechat::WEECHAT_RC_OK; - } - } - else - { - # Get pointer from where we are - $bufp = weechat::current_buffer(); - } - # Get buffer name - $bufname = weechat::buffer_get_string($bufp, 'name'); - # Test if buffer is an IRC channel - if ($bufname =~ /(.*)\.([#&\+!])(.*)/) - { - if (weechat::config_get_plugin($bufname) eq "off") - { - # If currently off, set on - weechat::config_set_plugin($bufname, "on"); - - # Send to output formatter - highmon_print("Highlight Monitoring Enabled", $bufp); - return weechat::WEECHAT_RC_OK; - } - elsif (weechat::config_get_plugin($bufname) eq "on" || weechat::config_get_plugin($bufname) eq "") - { - # If currently on, set off - weechat::config_set_plugin($bufname, "off"); - - # Send to output formatter - highmon_print("Highlight Monitoring Disabled", $bufp); - return weechat::WEECHAT_RC_OK; - } - } -} - -# Takes a buffer pointer and returns a formatted name -sub format_buffer_name -{ - $cb_bufferp = $_[0]; - $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); - - # Set colour from buffer name - if (weechat::config_get_plugin("color_buf") eq "on") - { - # Determine what colour to use - $color = weechat::info_get("irc_nick_color", $bufname); - if (!$color) - { - $color = 0; - @char_array = split(//,$bufname); - foreach $char (@char_array) - { - $color += ord($char); - } - $color %= 10; - $color = sprintf "weechat.color.chat_nick_color%02d", $color+1; - $color = weechat::config_get($color); - $color = weechat::config_string($color); - $color = weechat::color($color); - } - - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - - # Build a coloured string - $bufname = $color.$bufname.weechat::color("reset"); - } - # User set colour name - elsif (weechat::config_get_plugin("color_buf") ne "off") - { - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - - $color = weechat::config_get_plugin("color_buf"); - $bufname = weechat::color($color).$bufname.weechat::color("reset"); - } - # Stick with default colour - else - { - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - } - - return $bufname; -} - -# Check result of register, and attempt to behave in a sane manner -if (!weechat::register("highmon", "KenjiE20", "2.7", "GPL3", "Highlight Monitor", "", "")) -{ - # Double load - weechat::print ("", "\tHighmon is already loaded"); - return weechat::WEECHAT_RC_OK; -} -else -{ - # Start everything - highmon_hook(); - highmon_config_init(); - highmon_start(); -} diff --git a/.config/weechat/perl/autoload/iset.pl b/.config/weechat/perl/autoload/iset.pl deleted file mode 100644 index a4a8e35f..00000000 --- a/.config/weechat/perl/autoload/iset.pl +++ /dev/null @@ -1,1645 +0,0 @@ -# -# Copyright (C) 2008-2017 Sebastien Helleu -# Copyright (C) 2010-2017 Nils Görs -# -# 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 3 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 program. If not, see . -# -# Set WeeChat and plugins options interactively. -# -# History: -# -# 2020-06-21, Sebastien Helleu : -# version 4.4: make call to bar_new compatible with WeeChat >= 2.9 -# 2017-04-14, nils_2 -# version 4.3: add option "use_color" (https://github.com/weechat/scripts/issues/93) -# 2016-07-08, nils_2 -# version 4.2: add diff function -# 2016-02-06, Sebastien Helleu : -# version 4.1: remove debug print -# 2015-12-24, Sebastien Helleu : -# version 4.0: add support of parent options (inherited values in irc servers) -# with WeeChat >= 1.4 -# 2015-05-16, Sebastien Helleu : -# version 3.9: fix cursor position when editing an option with WeeChat >= 1.2 -# 2015-05-02, arza : -# version 3.8: don't append "null" to /set when setting an undefined setting -# 2015-05-01, nils_2 : -# version 3.7: fix two perl warnings (reported by t3chguy) -# 2014-09-30, arza : -# version 3.6: fix current line counter when options aren't found -# 2014-06-03, nils_2 : -# version 3.5: add new option "use_mute" -# 2014-01-30, stfn : -# version 3.4: add new options "color_value_diff" and "color_value_diff_selected" -# 2014-01-16, luz : -# version 3.3: fix bug with column alignment in iset buffer when option -# name contains unicode characters -# 2013-08-03, Sebastien Helleu : -# version 3.2: allow "q" as input in iset buffer to close it -# 2013-07-14, Sebastien Helleu : -# version 3.1: remove unneeded calls to iset_refresh() in mouse callback -# (faster mouse actions when lot of options are displayed), -# fix bug when clicking on a line after the last option displayed -# 2013-04-30, arza : -# version 3.0: simpler title, fix refresh on unset -# 2012-12-16, nils_2 : -# version 2.9: fix focus window with iset buffer on mouse click -# 2012-08-25, nils_2 : -# version 2.8: most important key and mouse bindings for iset buffer added to title-bar (idea The-Compiler) -# 2012-07-31, nils_2 : -# version 2.7: add combined option and value search (see /help iset) -# : add exact value search (see /help iset) -# : fix problem with metacharacter in value search -# : fix use of uninitialized value for unset option and reset value of option -# 2012-07-25, nils_2 : -# version 2.6: switch to iset buffer (if existing) when command /iset is called with arguments -# 2012-03-17, Sebastien Helleu : -# version 2.5: fix check of sections when creating config file -# 2012-03-09, Sebastien Helleu : -# version 2.4: fix reload of config file -# 2012-02-02, nils_2 : -# version 2.3: fixed: refresh problem with new search results and cursor was outside window. -# : add: new option "current_line" in title bar -# version 2.2: fixed: refresh error when toggling plugins description -# 2011-11-05, nils_2 : -# version 2.1: use own config file (iset.conf), fix own help color (used immediately) -# 2011-10-16, nils_2 : -# version 2.0: add support for left-mouse-button and more sensitive mouse gesture (for integer/color options) -# add help text for mouse support -# 2011-09-20, Sebastien Helleu : -# version 1.9: add mouse support, fix iset buffer, fix errors on first load under FreeBSD -# 2011-07-21, nils_2 : -# version 1.8: added: option "show_plugin_description" (alt+p) -# fixed: typos in /help iset (lower case for alt+'x' keys) -# 2011-05-29, nils_2 : -# version 1.7: added: version check for future needs -# added: new option (scroll_horiz) and usage of scroll_horiz function (weechat >= 0.3.6 required) -# fixed: help_bar did not pop up immediately using key-shortcut -# 2011-02-19, nils_2 : -# version 1.6: added: display of all possible values in help bar (show_help_extra_info) -# fixed: external user options never loaded when starting iset first time -# 2011-02-13, Sebastien Helleu : -# version 1.5: use new help format for command arguments -# 2011-02-03, nils_2 : -# version 1.4: fixed: restore value filter after /upgrade using buffer local variable. -# 2011-01-14, nils_2 : -# version 1.3: added function to search for values (option value_search_char). -# code optimization. -# 2010-12-26, Sebastien Helleu : -# version 1.2: improve speed of /upgrade when iset buffer is open, -# restore filter used after /upgrade using buffer local variable, -# use /iset filter argument if buffer is open. -# 2010-11-21, drubin : -# version 1.1.1: fix bugs with cursor position -# 2010-11-20, nils_2 : -# version 1.1: cursor position set to value -# 2010-08-03, Sebastien Helleu : -# version 1.0: move misplaced call to infolist_free() -# 2010-02-02, rettub : -# version 0.9: turn all the help stuff off if option 'show_help_bar' is 'off', -# new key binding - to toggle help_bar and help stuff on/off -# 2010-01-30, nils_2 : -# version 0.8: fix error when option does not exist -# 2010-01-24, Sebastien Helleu : -# version 0.7: display iset bar only on iset buffer -# 2010-01-22, nils_2 and drubin: -# version 0.6: add description in a bar, fix singular/plural bug in title bar, -# fix selected line when switching buffer -# 2009-06-21, Sebastien Helleu : -# version 0.5: fix bug with iset buffer after /upgrade -# 2009-05-02, Sebastien Helleu : -# version 0.4: sync with last API changes -# 2009-01-04, Sebastien Helleu : -# version 0.3: open iset buffer when /iset command is executed -# 2009-01-04, Sebastien Helleu : -# version 0.2: use null values for options, add colors, fix refresh bugs, -# use new keys to reset/unset options, sort options by name, -# display number of options in buffer's title -# 2008-11-05, Sebastien Helleu : -# version 0.1: first official version -# 2008-04-19, Sebastien Helleu : -# script creation - -use strict; - -my $PRGNAME = "iset"; -my $VERSION = "4.4"; -my $DESCR = "Interactive Set for configuration options"; -my $AUTHOR = "Sebastien Helleu "; -my $LICENSE = "GPL3"; -my $LANG = "perl"; -my $ISET_CONFIG_FILE_NAME = "iset"; - -my $iset_config_file; -my $iset_buffer = ""; -my $wee_version_number = 0; -my @iset_focus = (); -my @options_names = (); -my @options_parent_names = (); -my @options_types = (); -my @options_values = (); -my @options_default_values = (); -my @options_parent_values = (); -my @options_is_null = (); -my $option_max_length = 0; -my $current_line = 0; -my $filter = "*"; -my $description = ""; -my $options_name_copy = ""; -my $iset_filter_title = ""; -# search modes: 0 = index() on value, 1 = grep() on value, 2 = grep() on option, 3 = grep on option & value, 4 = diff all, 5 = diff parts -my $search_mode = 2; -my $search_value = ""; -my $help_text_keys = "alt + space: toggle, +/-: increase/decrease, enter: change, ir: reset, iu: unset, v: toggle help bar"; -my $help_text_mouse = "Mouse: left: select, right: toggle/set, right + drag left/right: increase/decrease"; -my %options_iset; - -my %mouse_keys = ("\@chat(perl.$PRGNAME):button1" => "hsignal:iset_mouse", - "\@chat(perl.$PRGNAME):button2*" => "hsignal:iset_mouse", - "\@chat(perl.$PRGNAME):wheelup" => "/repeat 5 /iset **up", - "\@chat(perl.$PRGNAME):wheeldown" => "/repeat 5 /iset **down"); - - -sub iset_title -{ - if ($iset_buffer ne "") - { - my $current_line_counter = ""; - if (weechat::config_boolean($options_iset{"show_current_line"}) == 1) - { - if (@options_names eq 0) - { - $current_line_counter = "0/"; - } - else - { - $current_line_counter = ($current_line + 1) . "/"; - } - } - my $show_filter = ""; - if ($search_mode eq 0) - { - $iset_filter_title = "(value) "; - $show_filter = $search_value; - if ( substr($show_filter,0,1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $show_filter = substr($show_filter,1,length($show_filter)); - } - } - elsif ($search_mode eq 1) - { - $iset_filter_title = "(value) "; - $show_filter = "*".$search_value."*"; - } - elsif ($search_mode eq 2) - { - $iset_filter_title = ""; - $filter = "*" if ($filter eq ""); - $show_filter = $filter; - } - elsif ($search_mode == 4 or $search_mode == 5) - { - $iset_filter_title = "diff: "; - $show_filter = "all"; - $show_filter = $search_value if $search_mode == 5; - } - elsif ($search_mode eq 3) - { - $iset_filter_title = "(option) "; - $show_filter = $filter - .weechat::color("default") - ." / (value) " - .weechat::color("yellow") - ."*".$search_value."*"; - } - weechat::buffer_set($iset_buffer, "title", - $iset_filter_title - .weechat::color("yellow") - .$show_filter - .weechat::color("default")." | " - .$current_line_counter - .@options_names - ." | " - .$help_text_keys - ." | " - .$help_text_mouse); - } -} - -sub iset_create_filter -{ - $filter = $_[0]; - if ( $search_mode == 3 ) - { - my @cmd_array = split(/ /,$filter); - my $array_count = @cmd_array; - $filter = $cmd_array[0]; - $filter = $cmd_array[0] . " " . $cmd_array[1] if ( $array_count >2 ); - } - $filter = "$1.*" if ($filter =~ /f (.*)/); # search file - $filter = "*.$1.*" if ($filter =~ /s (.*)/); # search section - if ((substr($filter, 0, 1) ne "*") && (substr($filter, -1, 1) ne "*")) - { - $filter = "*".$filter."*"; - } - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); - } -} - -sub iset_buffer_input -{ - my ($data, $buffer, $string) = ($_[0], $_[1], $_[2]); - - # string begins with space? - return weechat::WEECHAT_RC_OK if (substr($string, 0, 1 ) eq " "); - - if ($string eq "q") - { - weechat::buffer_close($buffer); - return weechat::WEECHAT_RC_OK; - } - $search_value = ""; - my @cmd_array = split(/ /,$string); - my $array_count = @cmd_array; - my $string2 = substr($string, 0, 1); - if ($string2 eq weechat::config_string($options_iset{"value_search_char"}) - or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) - { - $search_mode = 1; - $search_value = substr($string, 1); - iset_get_values($search_value); - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } - } - # show all diff values - elsif ($string eq "d") - { - $search_mode = 4; -# iset_title(); - iset_create_filter("*"); - iset_get_options("*"); - } - elsif ( $array_count >= 2 and $cmd_array[0] eq "d") - { - $search_mode = 5; - $search_value = substr($cmd_array[1], 0); # cut value_search_char - $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char - iset_create_filter($search_value); - iset_get_options($search_value); - - } - else - { - $search_mode = 2; - if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s" ) - { - if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $search_mode = 3; - $search_value = substr($cmd_array[1], 1); # cut value_search_char - $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char - } - } - if ( $search_mode == 3) - { - iset_create_filter($string); - iset_get_options($search_value); - } - else - { - iset_create_filter($string); - iset_get_options(""); - } - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_clear($buffer); - $current_line = 0; - iset_refresh(); - return weechat::WEECHAT_RC_OK; -} - -sub iset_buffer_close -{ - $iset_buffer = ""; - - return weechat::WEECHAT_RC_OK; -} - -sub iset_init -{ - $current_line = 0; - $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); - if ($iset_buffer eq "") - { - $iset_buffer = weechat::buffer_new($PRGNAME, "iset_buffer_input", "", "iset_buffer_close", ""); - } - else - { - my $new_filter = weechat::buffer_get_string($iset_buffer, "localvar_iset_filter"); - $search_mode = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_mode"); - $search_value = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_value"); - $filter = $new_filter if ($new_filter ne ""); - } - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "type", "free"); - iset_title(); - weechat::buffer_set($iset_buffer, "key_bind_ctrl-L", "/iset **refresh"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-A", "/iset **up"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-B", "/iset **down"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-23~", "/iset **left"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-24~" , "/iset **right"); - weechat::buffer_set($iset_buffer, "key_bind_meta- ", "/iset **toggle"); - weechat::buffer_set($iset_buffer, "key_bind_meta-+", "/iset **incr"); - weechat::buffer_set($iset_buffer, "key_bind_meta--", "/iset **decr"); - weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-r", "/iset **reset"); - weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-u", "/iset **unset"); - weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-J", "/iset **set"); - weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-M", "/iset **set"); - weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-1~", "/iset **scroll_top"); - weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-4~", "/iset **scroll_bottom"); - weechat::buffer_set($iset_buffer, "key_bind_meta-v", "/iset **toggle_help"); - weechat::buffer_set($iset_buffer, "key_bind_meta-p", "/iset **toggle_show_plugin_desc"); - weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } -} - -sub iset_get_options -{ - my $var_value = $_[0]; - $var_value = "" if (not defined $var_value); - $var_value = lc($var_value); - $search_value = $var_value; - @iset_focus = (); - @options_names = (); - @options_parent_names = (); - @options_types = (); - @options_values = (); - @options_default_values = (); - @options_parent_values = (); - @options_is_null = (); - $option_max_length = 0; - my %options_internal = (); - my $i = 0; - my $key; - my $iset_struct; - my %iset_struct; - - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value) if ($search_mode == 3); - - my $infolist = weechat::infolist_get("option", "", $filter); - while (weechat::infolist_next($infolist)) - { - $key = sprintf("%08d", $i); - my $name = weechat::infolist_string($infolist, "full_name"); - my $parent_name = weechat::infolist_string($infolist, "parent_name"); - next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); - my $type = weechat::infolist_string($infolist, "type"); - my $value = weechat::infolist_string($infolist, "value"); - my $default_value = weechat::infolist_string($infolist, "default_value"); - my $parent_value; - if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $parent_value = weechat::infolist_string($infolist, "parent_value"); - } - my $is_null = weechat::infolist_integer($infolist, "value_is_null"); - - if ($search_mode == 3) - { - my $value = weechat::infolist_string($infolist, "value"); - if ( grep /\Q$var_value/,lc($value) ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - } - # search for diff? - elsif ( $search_mode == 4 or $search_mode == 5) - { - if ($value ne $default_value ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - } - else - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - $i++; - } - weechat::infolist_free($infolist); - - foreach my $name (sort keys %options_internal) - { - push(@options_names, $name); - push(@options_parent_names, $options_internal{$name}{"parent_name"}); - push(@options_types, $options_internal{$name}{"type"}); - push(@options_values, $options_internal{$name}{"value"}); - push(@options_default_values, $options_internal{$name}{"default_value"}); - push(@options_parent_values, $options_internal{$name}{"parent_value"}); - push(@options_is_null, $options_internal{$name}{"is_null"}); - } -} - -sub iset_get_values -{ - my $var_value = $_[0]; - $var_value = lc($var_value); - if (substr($var_value,0,1) eq weechat::config_string($options_iset{"value_search_char"}) and $var_value ne weechat::config_string($options_iset{"value_search_char"})) - { - $var_value = substr($var_value,1,length($var_value)); - $search_mode = 0; - } - iset_search_values($var_value,$search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value); - $search_value = $var_value; -} -sub iset_search_values -{ - my ($var_value,$search_mode) = ($_[0],$_[1]); - @options_names = (); - @options_parent_names = (); - @options_types = (); - @options_values = (); - @options_default_values = (); - @options_parent_values = (); - @options_is_null = (); - $option_max_length = 0; - my %options_internal = (); - my $i = 0; - my $infolist = weechat::infolist_get("option", "", "*"); - while (weechat::infolist_next($infolist)) - { - my $name = weechat::infolist_string($infolist, "full_name"); - my $parent_name = weechat::infolist_string($infolist, "parent_name"); - next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); - my $type = weechat::infolist_string($infolist, "type"); - my $is_null = weechat::infolist_integer($infolist, "value_is_null"); - my $value = weechat::infolist_string($infolist, "value"); - my $default_value = weechat::infolist_string($infolist, "default_value"); - my $parent_value; - if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $parent_value = weechat::infolist_string($infolist, "parent_value"); - } - if ($search_mode) - { - if ( grep /\Q$var_value/,lc($value) ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - } - } - else - { -# if ($value =~ /\Q$var_value/si) - if (lc($value) eq $var_value) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - } - } - $i++; - } - weechat::infolist_free($infolist); - foreach my $name (sort keys %options_internal) - { - push(@options_names, $name); - push(@options_parent_names, $options_internal{$name}{"parent_name"}); - push(@options_types, $options_internal{$name}{"type"}); - push(@options_values, $options_internal{$name}{"value"}); - push(@options_default_values, $options_internal{$name}{"default_value"}); - push(@options_parent_values, $options_internal{$name}{"parent_value"}); - push(@options_is_null, $options_internal{$name}{"is_null"}); - } -} - -sub iset_refresh_line -{ - if ($iset_buffer ne "") - { - my $y = $_[0]; - if ($y <= $#options_names) - { - return if (! defined($options_types[$y])); - my $format = sprintf("%%s%%s%%s %%s %%-7s %%s %%s%%s%%s"); - my $padding; - if ($wee_version_number >= 0x00040200) - { - $padding = " " x ($option_max_length - weechat::strlen_screen($options_names[$y])); - } - else - { - $padding = " " x ($option_max_length - length($options_names[$y])); - } - my $around = ""; - $around = "\"" if ((!$options_is_null[$y]) && ($options_types[$y] eq "string")); - - my $color1 = weechat::color(weechat::config_color($options_iset{"color_option"})); - my $color2 = weechat::color(weechat::config_color($options_iset{"color_type"})); - my $color3 = ""; - my $color4 = ""; - if ($options_is_null[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef"})); - $color4 = weechat::color(weechat::config_color($options_iset{"color_value"})); - } - elsif ($options_values[$y] ne $options_default_values[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff"})); - } - else - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value"})); - } - if ($y == $current_line) - { - $color1 = weechat::color(weechat::config_color($options_iset{"color_option_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - $color2 = weechat::color(weechat::config_color($options_iset{"color_type_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - if ($options_is_null[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - $color4 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - elsif ($options_values[$y] ne $options_default_values[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - else - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - } - my $value = $options_values[$y]; - if (weechat::config_boolean($options_iset{"use_color"}) == 1 and $options_types[$y] eq "color") - { - $value = weechat::color($options_values[$y]) . $options_values[$y]; - } - if ($options_is_null[$y]) - { - $value = "null"; - if ($options_parent_names[$y]) - { - if (defined $options_parent_values[$y]) - { - my $around_parent = ""; - $around_parent = "\"" if ($options_types[$y] eq "string"); - $value .= $color1." -> ".$color4.$around_parent.$options_parent_values[$y].$around_parent; - } - else - { - $value .= $color1." -> ".$color3."null"; - } - } - } - my $strline = sprintf($format, - $color1, $options_names[$y], $padding, - $color2, $options_types[$y], - $color3, $around, $value, $around); - weechat::print_y($iset_buffer, $y, $strline); - } - } -} - -sub iset_refresh -{ - iset_title(); - if (($iset_buffer ne "") && ($#options_names >= 0)) - { - foreach my $y (0 .. $#options_names) - { - iset_refresh_line($y); - } - } - - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); -} - -sub iset_full_refresh -{ - $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); - if ($iset_buffer ne "") - { - weechat::buffer_clear($iset_buffer) unless defined $_[0]; # iset_full_refresh(1) does a full refresh without clearing buffer - # search for "*" in $filter. - if ($filter =~ m/\*/ and $search_mode == 2) - { - iset_get_options(""); - } - else - { - if ($search_mode == 0) - { - $search_value = "=" . $search_value; - iset_get_values($search_value); - } - elsif ($search_mode == 1) - { - iset_get_values($search_value); - } - elsif ($search_mode == 3) - { - iset_create_filter($filter); - iset_get_options($search_value); - } - } - if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) - { - iset_set_current_line($current_line); - }else - { - $current_line = $#options_names if ($current_line > $#options_names); - } - iset_refresh(); - weechat::command($iset_buffer, "/window refresh"); - } -} - -sub iset_set_current_line -{ - my $new_current_line = $_[0]; - if ($new_current_line >= 0) - { - my $old_current_line = $current_line; - $current_line = $new_current_line; - $current_line = $#options_names if ($current_line > $#options_names); - if ($old_current_line != $current_line) - { - iset_refresh_line($old_current_line); - iset_refresh_line($current_line); - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - } - } -} - -sub iset_signal_window_scrolled_cb -{ - my ($data, $signal, $signal_data) = ($_[0], $_[1], $_[2]); - if ($iset_buffer ne "") - { - my $infolist = weechat::infolist_get("window", $signal_data, ""); - if (weechat::infolist_next($infolist)) - { - if (weechat::infolist_pointer($infolist, "buffer") eq $iset_buffer) - { - my $old_current_line = $current_line; - my $new_current_line = $current_line; - my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); - my $chat_height = weechat::infolist_integer($infolist, "chat_height"); - $new_current_line += $chat_height if ($new_current_line < $start_line_y); - $new_current_line -= $chat_height if ($new_current_line >= $start_line_y + $chat_height); - $new_current_line = $start_line_y if ($new_current_line < $start_line_y); - $new_current_line = $start_line_y + $chat_height - 1 if ($new_current_line >= $start_line_y + $chat_height); - iset_set_current_line($new_current_line); - } - } - weechat::infolist_free($infolist); - } - - return weechat::WEECHAT_RC_OK; -} - -sub iset_get_window_number -{ - if ($iset_buffer ne "") - { - my $window = weechat::window_search_with_buffer($iset_buffer); - return "-window ".weechat::window_get_integer ($window, "number")." " if ($window ne ""); - } - return ""; -} - -sub iset_check_line_outside_window -{ - if ($iset_buffer ne "") - { - undef my $infolist; - if ($wee_version_number >= 0x00030500) - { - my $window = weechat::window_search_with_buffer($iset_buffer); - $infolist = weechat::infolist_get("window", $window, "") if $window; - } - else - { - $infolist = weechat::infolist_get("window", "", "current"); - } - if ($infolist) - { - if (weechat::infolist_next($infolist)) - { - my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); - my $chat_height = weechat::infolist_integer($infolist, "chat_height"); - my $window_number = ""; - if ($wee_version_number >= 0x00030500) - { - $window_number = "-window ".weechat::infolist_integer($infolist, "number")." "; - } - if ($start_line_y > $current_line) - { - weechat::command($iset_buffer, "/window scroll ".$window_number."-".($start_line_y - $current_line)); - } - else - { - if ($start_line_y <= $current_line - $chat_height) - { - weechat::command($iset_buffer, "/window scroll ".$window_number."+".($current_line - $start_line_y - $chat_height + 1)); - - } - } - } - weechat::infolist_free($infolist); - } - } -} - -sub iset_get_option_name_index -{ - my $option_name = $_[0]; - my $index = 0; - while ($index <= $#options_names) - { - return -1 if ($options_names[$index] gt $option_name); - return $index if ($options_names[$index] eq $option_name); - $index++; - } - return -1; -} - -sub iset_refresh_option -{ - my $option_name = $_[0]; - my $index = $_[1]; - my $infolist = weechat::infolist_get("option", "", $option_name); - if ($infolist) - { - weechat::infolist_next($infolist); - if (weechat::infolist_fields($infolist)) - { - $options_parent_names[$index] = weechat::infolist_string($infolist, "parent_name"); - $options_types[$index] = weechat::infolist_string($infolist, "type"); - $options_values[$index] = weechat::infolist_string($infolist, "value"); - $options_default_values[$index] = weechat::infolist_string($infolist, "default_value"); - $options_is_null[$index] = weechat::infolist_integer($infolist, "value_is_null"); - $options_parent_values[$index] = undef; - if ($options_parent_names[$index] - && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $options_parent_values[$index] = weechat::infolist_string($infolist, "parent_value"); - } - iset_refresh_line($index); - iset_title() if ($option_name eq "iset.look.show_current_line"); - } - else - { - iset_full_refresh(1); # if not found, refresh fully without clearing buffer - weechat::print_y($iset_buffer, $#options_names + 1, ""); - } - weechat::infolist_free($infolist); - } -} - -sub iset_config_cb -{ - my ($data, $option_name, $value) = ($_[0], $_[1], $_[2]); - - if ($iset_buffer ne "") - { - return weechat::WEECHAT_RC_OK if (weechat::info_get("weechat_upgrading", "") eq "1"); - - my $index = iset_get_option_name_index($option_name); - if ($index >= 0) - { - # refresh info about changed option - iset_refresh_option($option_name, $index); - # refresh any other option having this changed option as parent - foreach my $i (0 .. $#options_names) - { - if ($options_parent_names[$i] eq $option_name) - { - iset_refresh_option($options_names[$i], $i); - } - } - } - else - { - iset_full_refresh() if ($option_name ne "weechat.bar.isetbar.hidden"); - } - } - - return weechat::WEECHAT_RC_OK; -} - -sub iset_set_option -{ - my ($option, $value) = ($_[0],$_[1]); - if (defined $option and defined $value) - { - $option = weechat::config_get($option); - weechat::config_option_set($option, $value, 1) if ($option ne ""); - } -} - -sub iset_reset_option -{ - my $option = $_[0]; - if (defined $option) - { - $option = weechat::config_get($option); - weechat::config_option_reset($option, 1) if ($option ne ""); - } -} - -sub iset_unset_option -{ - my $option = $_[0]; - if (defined $option) - { - $option = weechat::config_get($option); - weechat::config_option_unset($option) if ($option ne ""); - } -} - - -sub iset_cmd_cb -{ - my ($data, $buffer, $args) = ($_[0], $_[1], $_[2]); - my $filter_set = 0; -# $search_value = ""; - if (($args ne "") && (substr($args, 0, 2) ne "**")) - { - my @cmd_array = split(/ /,$args); - my $array_count = @cmd_array; - if (substr($args, 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) - { - $search_mode = 1; - my $search_value = substr($args, 1); # cut value_search_char - if ($iset_buffer ne "") - { - weechat::buffer_clear($iset_buffer); - weechat::command($iset_buffer, "/window refresh"); - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - iset_init(); - iset_get_values($search_value); - iset_refresh(); - weechat::buffer_set($iset_buffer, "display", "1"); -# $filter = $var_value; - return weechat::WEECHAT_RC_OK; - } - else - { - # f/s option =value - # option =value - $search_mode = 2; # grep on option - if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s") - { - if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $search_mode = 3; # grep on option and value - $search_value = substr($cmd_array[1], 1); # cut value_search_char - $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char - } - } - - # show all diff values - if ( $args eq "d") - { - $search_mode = 4; - $search_value = "*"; - $args = $search_value; - } - if ( $array_count >= 2 and $cmd_array[0] eq "d") - { - $search_mode = 5; - $search_value = substr($cmd_array[1], 0); # cut value_search_char - $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char - $args = $search_value; - } - - iset_create_filter($args); - $filter_set = 1; - my $ptrbuf = weechat::buffer_search($LANG, $PRGNAME); - - if ($ptrbuf eq "") - { - iset_init(); - iset_get_options($search_value); - iset_full_refresh(); - weechat::buffer_set(weechat::buffer_search($LANG, $PRGNAME), "display", "1"); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - return weechat::WEECHAT_RC_OK; - } - else - { - iset_get_options($search_value); - iset_full_refresh(); - weechat::buffer_set($ptrbuf, "display", "1"); - } - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } - if ($iset_buffer eq "") - { - iset_init(); - iset_get_options(""); - iset_refresh(); - } - else - { -# iset_get_options($search_value); - iset_full_refresh() if ($filter_set); - } - - if ($args eq "") - { - weechat::buffer_set($iset_buffer, "display", "1"); - } - else - { - if ($args eq "**refresh") - { - iset_full_refresh(); - } - if ($args eq "**up") - { - if ($current_line > 0) - { - $current_line--; - iset_refresh_line($current_line + 1); - iset_refresh_line($current_line); - iset_check_line_outside_window(); - } - } - if ($args eq "**down") - { - if ($current_line < $#options_names) - { - $current_line++; - iset_refresh_line($current_line - 1); - iset_refresh_line($current_line); - iset_check_line_outside_window(); - } - } - if ($args eq "**left" && $wee_version_number >= 0x00030600) - { - weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number()."-".weechat::config_integer($options_iset{"scroll_horiz"})."%"); - } - if ($args eq "**right" && $wee_version_number >= 0x00030600) - { - weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number().weechat::config_integer($options_iset{"scroll_horiz"})."%"); - } - if ($args eq "**scroll_top") - { - my $old_current_line = $current_line; - $current_line = 0; - iset_refresh_line ($old_current_line); - iset_refresh_line ($current_line); - iset_title(); - weechat::command($iset_buffer, "/window scroll_top ".iset_get_window_number()); - } - if ($args eq "**scroll_bottom") - { - my $old_current_line = $current_line; - $current_line = $#options_names; - iset_refresh_line ($old_current_line); - iset_refresh_line ($current_line); - iset_title(); - weechat::command($iset_buffer, "/window scroll_bottom ".iset_get_window_number()); - } - if ($args eq "**toggle") - { - if ($options_types[$current_line] eq "boolean") - { - iset_set_option($options_names[$current_line], "toggle"); - } - } - if ($args eq "**incr") - { - if (($options_types[$current_line] eq "integer") - || ($options_types[$current_line] eq "color")) - { - iset_set_option($options_names[$current_line], "++1"); - } - } - if ($args eq "**decr") - { - if (($options_types[$current_line] eq "integer") - || ($options_types[$current_line] eq "color")) - { - iset_set_option($options_names[$current_line], "--1"); - } - } - if ($args eq "**reset") - { - iset_reset_option($options_names[$current_line]); - } - if ($args eq "**unset") - { - iset_unset_option($options_names[$current_line]); - } - if ($args eq "**toggle_help") - { - if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) - { - weechat::config_option_set($options_iset{"show_help_bar"},0,1); - iset_show_bar(0); - } - else - { - weechat::config_option_set($options_iset{"show_help_bar"},1,1); - iset_show_bar(1); - } - } - if ($args eq "**toggle_show_plugin_desc") - { - if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) - { - weechat::config_option_set($options_iset{"show_plugin_description"},0,1); - iset_full_refresh(); - iset_check_line_outside_window(); - iset_title(); - } - else - { - weechat::config_option_set($options_iset{"show_plugin_description"},1,1); - iset_full_refresh(); - iset_check_line_outside_window(); - iset_title(); - } - } - if ($args eq "**set") - { - my $quote = ""; - my $value = $options_values[$current_line]; - if ($options_is_null[$current_line]) - { - $value = ""; - } - else - { - $quote = "\"" if ($options_types[$current_line] eq "string"); - } - $value = " ".$quote.$value.$quote if ($value ne "" or $quote ne ""); - - my $set_command = "/set"; - my $start_index = 5; - if (weechat::config_boolean($options_iset{"use_mute"}) == 1) - { - $set_command = "/mute ".$set_command; - $start_index += 11; - } - $set_command = $set_command." ".$options_names[$current_line].$value; - my $pos_space = index($set_command, " ", $start_index); - if ($pos_space < 0) - { - $pos_space = 9999; - } - else - { - $pos_space = $pos_space + 1; - $pos_space = $pos_space + 1 if ($quote ne ""); - } - weechat::buffer_set($iset_buffer, "input", $set_command); - weechat::buffer_set($iset_buffer, "input_pos", "".$pos_space); - } - } - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - return weechat::WEECHAT_RC_OK; -} - -sub iset_get_help -{ - my ($redraw) = ($_[0]); - - return '' if (weechat::config_boolean($options_iset{"show_help_bar"}) == 0); - - if (not defined $options_names[$current_line]) - { - return "No option selected. Set a new filter using command line (use '*' to see all options)"; - } - if ($options_name_copy eq $options_names[$current_line] and not defined $redraw) - { - return $description; - } - $options_name_copy = $options_names[$current_line]; - my $optionlist =""; - $optionlist = weechat::infolist_get("option", "", $options_names[$current_line]); - weechat::infolist_next($optionlist); - my $full_name = weechat::infolist_string($optionlist,"full_name"); - my $option_desc = ""; - my $option_default_value = ""; - my $option_range = ""; - my $possible_values = ""; - my $re = qq(\Q$full_name); - if (grep (/^$re$/,$options_names[$current_line])) - { - $option_desc = weechat::infolist_string($optionlist, "description_nls"); - $option_desc = weechat::infolist_string($optionlist, "description") if ($option_desc eq ""); - $option_desc = "No help found" if ($option_desc eq ""); - $option_default_value = weechat::infolist_string($optionlist, "default_value"); - $possible_values = weechat::infolist_string($optionlist, "string_values") if (weechat::infolist_string($optionlist, "string_values") ne ""); - if ((weechat::infolist_string($optionlist, "type") eq "integer") && ($possible_values eq "")) - { - $option_range = weechat::infolist_integer($optionlist, "min") - ." .. ".weechat::infolist_integer($optionlist, "max"); - } - } - weechat::infolist_free($optionlist); - iset_title(); - - $description = weechat::color(weechat::config_color($options_iset{"color_help_option_name"})).$options_names[$current_line] - .weechat::color("bar_fg").": " - .weechat::color(weechat::config_color($options_iset{"color_help_text"})).$option_desc; - - # show additional infos like default value and possible values - - if (weechat::config_boolean($options_iset{"show_help_extra_info"}) == 1) - { - $description .= - weechat::color("bar_delim")." [" - .weechat::color("bar_fg")."default: " - .weechat::color("bar_delim")."\"" - .weechat::color(weechat::config_color($options_iset{"color_help_default_value"})).$option_default_value - .weechat::color("bar_delim")."\""; - if ($option_range ne "") - { - $description .= weechat::color("bar_fg").", values: ".$option_range; - } - if ($possible_values ne "") - { - $possible_values =~ s/\|/", "/g; # replace '|' to '", "' - $description .= weechat::color("bar_fg").", values: ". "\"" . $possible_values . "\""; - - } - $description .= weechat::color("bar_delim")."]"; - } - return $description; -} - -sub iset_check_condition_isetbar_cb -{ - my ($data, $modifier, $modifier_data, $string) = ($_[0], $_[1], $_[2], $_[3]); - my $buffer = weechat::window_get_pointer($modifier_data, "buffer"); - if ($buffer ne "") - { - if ((weechat::buffer_get_string($buffer, "plugin") eq $LANG) - && (weechat::buffer_get_string($buffer, "name") eq $PRGNAME)) - { - return "1"; - } - } - return "0"; -} - -sub iset_show_bar -{ - my $show = $_[0]; - my $barhidden = weechat::config_get("weechat.bar.isetbar.hidden"); - if ($barhidden) - { - if ($show) - { - if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) - { - if (weechat::config_boolean($barhidden)) - { - weechat::config_option_set($barhidden, 0, 1); - } - } - } - else - { - if (!weechat::config_boolean($barhidden)) - { - weechat::config_option_set($barhidden, 1, 1); - } - } - } -} - -sub iset_signal_buffer_switch_cb -{ - my $buffer_pointer = $_[2]; - my $show_bar = 0; - $show_bar = 1 if (weechat::buffer_get_integer($iset_buffer, "num_displayed") > 0); - iset_show_bar($show_bar); - iset_check_line_outside_window() if ($buffer_pointer eq $iset_buffer); - return weechat::WEECHAT_RC_OK; -} - -sub iset_item_cb -{ - return iset_get_help(); -} - -sub iset_upgrade_ended -{ - iset_full_refresh(); -} - -sub iset_end -{ - # when script is unloaded, we hide bar - iset_show_bar(0); -} - -# -------------------------------[ mouse support ]------------------------------------- - -sub hook_focus_iset_cb -{ - my %info = %{$_[1]}; - my $bar_item_line = int($info{"_bar_item_line"}); - undef my $hash; - if (($info{"_buffer_name"} eq $PRGNAME) && $info{"_buffer_plugin"} eq $LANG && ($bar_item_line >= 0) && ($bar_item_line <= $#iset_focus)) - { - $hash = $iset_focus[$bar_item_line]; - } - else - { - $hash = {}; - my $hash_focus = $iset_focus[0]; - foreach my $key (keys %$hash_focus) - { - $hash->{$key} = "?"; - } - } - return $hash; -} - -# _chat_line_y contains selected line -sub iset_hsignal_mouse_cb -{ - my ($data, $signal, %hash) = ($_[0], $_[1], %{$_[2]}); - - return weechat::WEECHAT_RC_OK unless (@options_types); - - if ($hash{"_buffer_name"} eq $PRGNAME && ($hash{"_buffer_plugin"} eq $LANG)) - { - if ($hash{"_key"} eq "button1") - { - iset_set_current_line($hash{"_chat_line_y"}); - } - elsif ($hash{"_key"} eq "button2") - { - if ($options_types[$hash{"_chat_line_y"}] eq "boolean") - { - iset_set_option($options_names[$hash{"_chat_line_y"}], "toggle"); - iset_set_current_line($hash{"_chat_line_y"}); - } - elsif ($options_types[$hash{"_chat_line_y"}] eq "string") - { - iset_set_current_line($hash{"_chat_line_y"}); - weechat::command("", "/$PRGNAME **set"); - } - } - elsif ($hash{"_key"} eq "button2-gesture-left" or $hash{"_key"} eq "button2-gesture-left-long") - { - if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) - { - iset_set_current_line($hash{"_chat_line_y"}); - my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); - weechat::command("", "/repeat $distance /$PRGNAME **decr"); - } - } - elsif ($hash{"_key"} eq "button2-gesture-right" or $hash{"_key"} eq "button2-gesture-right-long") - { - if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) - { - iset_set_current_line($hash{"_chat_line_y"}); - my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); - weechat::command("", "/repeat $distance /$PRGNAME **incr"); - } - } - } - window_switch(); -} - -sub window_switch -{ - my $current_window = weechat::current_window(); - my $dest_window = weechat::window_search_with_buffer(weechat::buffer_search("perl","iset")); - return 0 if ($dest_window eq "" or $current_window eq $dest_window); - - my $infolist = weechat::infolist_get("window", $dest_window, ""); - weechat::infolist_next($infolist); - my $number = weechat::infolist_integer($infolist, "number"); - weechat::infolist_free($infolist); - weechat::command("","/window " . $number); -} - -sub distance -{ - my ($x1,$x2) = ($_[0], $_[1]); - my $distance; - $distance = $x1 - $x2; - $distance = abs($distance); - if ($distance > 0) - { - use integer; - $distance = $distance / 3; - $distance = 1 if ($distance == 0); - } - elsif ($distance == 0) - { - $distance = 1; - } - return $distance; -} - -# -----------------------------------[ config ]--------------------------------------- - -sub iset_config_init -{ - $iset_config_file = weechat::config_new($ISET_CONFIG_FILE_NAME,"iset_config_reload_cb",""); - return if ($iset_config_file eq ""); - - # section "color" - my $section_color = weechat::config_new_section($iset_config_file,"color", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_color eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"color_option"} = weechat::config_new_option( - $iset_config_file, $section_color, - "option", "color", "Color for option name in iset buffer", "", 0, 0, - "default", "default", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_option_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "option_selected", "color", "Color for selected option name in iset buffer", "", 0, 0, - "white", "white", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_type"} = weechat::config_new_option( - $iset_config_file, $section_color, - "type", "color", "Color for option type (integer, boolean, string)", "", 0, 0, - "brown", "brown", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_type_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "type_selected", "color", "Color for selected option type (integer, boolean, string)", "", 0, 0, - "yellow", "yellow", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value", "color", "Color for option value", "", 0, 0, - "cyan", "cyan", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_selected", "color", "Color for selected option value", "", 0, 0, - "lightcyan", "lightcyan", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_diff"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_diff", "color", "Color for option value different from default", "", 0, 0, - "magenta", "magenta", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_diff_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_diff_selected", "color", "Color for selected option value different from default", "", 0, 0, - "lightmagenta", "lightmagenta", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_undef"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_undef", "color", "Color for option value undef", "", 0, 0, - "green", "green", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_undef_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_undef_selected", "color", "Color for selected option value undef", "", 0, 0, - "lightgreen", "lightgreen", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_bg_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "bg_selected", "color", "Background color for current selected option", "", 0, 0, - "red", "red", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_help_option_name"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_option_name", "color", "Color for option name in help-bar", "", 0, 0, - "white", "white", 0, "", "", "bar_refresh", "", "", ""); - $options_iset{"color_help_text"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_text", "color", "Color for option description in help-bar", "", 0, 0, - "default", "default", 0, "", "", "bar_refresh", "", "", ""); - $options_iset{"color_help_default_value"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_default_value", "color", "Color for default option value in help-bar", "", 0, 0, - "green", "green", 0, "", "", "bar_refresh", "", "", ""); - - # section "help" - my $section_help = weechat::config_new_section($iset_config_file,"help", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_help eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"show_help_bar"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_help_bar", "boolean", "Show help bar", "", 0, 0, - "on", "on", 0, "", "", "toggle_help_cb", "", "", ""); - $options_iset{"show_help_extra_info"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_help_extra_info", "boolean", "Show additional information in help bar (default value, max./min. value) ", "", 0, 0, - "on", "on", 0, "", "", "", "", "", ""); - $options_iset{"show_plugin_description"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_plugin_description", "boolean", "Show plugin description in iset buffer", "", 0, 0, - "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); - - # section "look" - my $section_look = weechat::config_new_section($iset_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_look eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"value_search_char"} = weechat::config_new_option( - $iset_config_file, $section_look, - "value_search_char", "string", "Trigger char to tell iset to search for value instead of option (for example: =red)", "", 0, 0, - "=", "=", 0, "", "", "", "", "", ""); - $options_iset{"scroll_horiz"} = weechat::config_new_option( - $iset_config_file, $section_look, - "scroll_horiz", "integer", "scroll content of iset buffer n%", "", 1, 100, - "10", "10", 0, "", "", "", "", "", ""); - $options_iset{"show_current_line"} = weechat::config_new_option( - $iset_config_file, $section_look, - "show_current_line", "boolean", "show current line in title bar.", "", 0, 0, - "on", "on", 0, "", "", "", "", "", ""); - $options_iset{"use_mute"} = weechat::config_new_option( - $iset_config_file, $section_look, - "use_mute", "boolean", "/mute command will be used in input bar", "", 0, 0, - "off", "off", 0, "", "", "", "", "", ""); - $options_iset{"use_color"} = weechat::config_new_option( - $iset_config_file, $section_look, - "use_color", "boolean", "display the color value in the corresponding color", "", 0, 0, - "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); -} - -sub iset_config_reload_cb -{ - my ($data,$config_file) = ($_[0], $_[1]); - return weechat::config_reload($config_file) -} - -sub iset_config_read -{ - return weechat::config_read($iset_config_file) if ($iset_config_file ne ""); -} - -sub iset_config_write -{ - return weechat::config_write($iset_config_file) if ($iset_config_file ne ""); -} - -sub full_refresh_cb -{ - iset_full_refresh(); - return weechat::WEECHAT_RC_OK; -} - -sub bar_refresh -{ - iset_get_help(1); - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - return weechat::WEECHAT_RC_OK; -} - -sub toggle_help_cb -{ - my $value = weechat::config_boolean($options_iset{"show_help_bar"}); - iset_show_bar($value); - return weechat::WEECHAT_RC_OK; -} - -# -----------------------------------[ main ]----------------------------------------- - -weechat::register($PRGNAME, $AUTHOR, $VERSION, $LICENSE, - $DESCR, "iset_end", ""); - -$wee_version_number = weechat::info_get("version_number", "") || 0; - -iset_config_init(); -iset_config_read(); - -weechat::hook_command($PRGNAME, "Interactive set", "d || f || s
|| [=][=]", - "d : show only changed options\n". - "f file : show options for a file\n". - "s section: show options for a section\n". - "text : show options with 'text' in name\n". - weechat::config_string($options_iset{"value_search_char"})."text : show options with 'text' in value\n". - weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})."text : show options with exact 'text' in value\n\n". - "Keys for iset buffer:\n". - "f11,f12 : move iset content left/right\n". - "up,down : move one option up/down\n". - "pgup,pdwn : move one page up/down\n". - "home,end : move to first/last option\n". - "ctrl+'L' : refresh options and screen\n". - "alt+space : toggle boolean on/off\n". - "alt+'+' : increase value (for integer or color)\n". - "alt+'-' : decrease value (for integer or color)\n". - "alt+'i',alt+'r': reset value of option\n". - "alt+'i',alt+'u': unset option\n". - "alt+enter : set new value for option (edit it with command line)\n". - "text,enter : set a new filter using command line (use '*' to see all options)\n". - "alt+'v' : toggle help bar on/off\n". - "alt+'p' : toggle option \"show_plugin_description\" on/off\n". - "q : as input in iset buffer to close it\n". - "\n". - "Mouse actions:\n". - "wheel up/down : move cursor up/down\n". - "left button : select an option from list\n". - "right button : toggle boolean (on/off) or set a new value for option (edit it with command line)\n". - "right button + drag left/right: increase/decrease value (for integer or color)\n". - "\n". - "Examples:\n". - " show changed options in 'aspell' plugin\n". - " /iset d aspell\n". - " show options for file 'irc'\n". - " /iset f irc\n". - " show options for section 'look'\n". - " /iset s look\n". - " show all options with text 'nicklist' in name\n". - " /iset nicklist\n". - " show all values which contain 'red'. ('" . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". - " /iset ". weechat::config_string($options_iset{"value_search_char"}) ."red\n". - " show all values which hit 'off'. ('" . weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". - " /iset ". weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) ."off\n". - " show options for file 'weechat' which contains value 'off'\n". - " /iset f weechat ".weechat::config_string($options_iset{"value_search_char"})."off\n". - "", - "", "iset_cmd_cb", ""); -weechat::hook_signal("upgrade_ended", "iset_upgrade_ended", ""); -weechat::hook_signal("window_scrolled", "iset_signal_window_scrolled_cb", ""); -weechat::hook_signal("buffer_switch", "iset_signal_buffer_switch_cb",""); -weechat::bar_item_new("isetbar_help", "iset_item_cb", ""); -if ($wee_version_number >= 0x02090000) -{ - weechat::bar_new("isetbar", "on", "0", "window", "", "top", "horizontal", - "vertical", "3", "3", "default", "cyan", "default", "default", "1", - "isetbar_help"); -} -else -{ - weechat::bar_new("isetbar", "on", "0", "window", "", "top", "horizontal", - "vertical", "3", "3", "default", "cyan", "default", "1", - "isetbar_help"); -} -weechat::hook_modifier("bar_condition_isetbar", "iset_check_condition_isetbar_cb", ""); -weechat::hook_config("*", "iset_config_cb", ""); -$iset_buffer = weechat::buffer_search($LANG, $PRGNAME); -iset_init() if ($iset_buffer ne ""); - -if ($wee_version_number >= 0x00030600) -{ - weechat::hook_focus("chat", "hook_focus_iset_cb", ""); - weechat::hook_hsignal($PRGNAME."_mouse", "iset_hsignal_mouse_cb", ""); - weechat::key_bind("mouse", \%mouse_keys); -} diff --git a/.config/weechat/perl/awaylog.pl b/.config/weechat/perl/awaylog.pl deleted file mode 100644 index 91e9b1b8..00000000 --- a/.config/weechat/perl/awaylog.pl +++ /dev/null @@ -1,100 +0,0 @@ -############################################################################### -# -# Copyright (c) 2008 by GolemJ -# -# 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 program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -############################################################################### -# -# Log highlights msg to core buffer -# You need to set "notify" to "yes" and "command" to proper command to run -# external command. You also need "shell" script to run external command. -# -# History: -# 2010-06-20, GolemJ -# version 0.8, add posibility to execute command for notification -# 2010-02-14, Emmanuel Bouthenot -# version 0.7, add colors and notifications support -# 2009-05-02, FlashCode : -# version 0.6, sync with last API changes -# 2008-11-30, GolemJ : -# version 0.5, conversion to WeeChat 0.3.0+ -# -############################################################################### - -use strict; - -weechat::register( "awaylog", "Jiri Golembiovsky", "0.8", "GPL", "Prints highlights to core buffer", "", "" ); -weechat::hook_print( "", "", "", 1, "highlight_cb", "" ); - -if( weechat::config_get_plugin( "on_away_only" ) eq "" ) { - weechat::config_set_plugin( "on_away_only", "off" ); -} - -if( weechat::config_get_plugin( "plugin_color" ) eq "" ) { - weechat::config_set_plugin( "plugin_color", "default" ); -} - -if( weechat::config_get_plugin( "name_color" ) eq "" ) { - weechat::config_set_plugin( "name_color", "default" ); -} - -if( weechat::config_get_plugin( "notify" ) eq "" ) { - weechat::config_set_plugin( "notify", "off" ); -} - -if( weechat::config_get_plugin( "command" ) eq "") { - weechat::config_set_plugin( "command", "" ); -} - -sub highlight_cb { - if( $_[5] == 1 ) { - my $away = weechat::buffer_get_string($_[1], "localvar_away"); - if (($away ne "") || (weechat::config_get_plugin( "on_away_only" ) ne "on")) - { - my $buffer_color = weechat::color(weechat::config_get_plugin( "plugin_color")) - . weechat::buffer_get_string($_[1], "plugin") - . "." - . weechat::buffer_get_string($_[1], "name") - . weechat::color("default"); - my $buffer = weechat::buffer_get_string($_[1], "plugin") - . "." - . weechat::buffer_get_string($_[1], "name"); - my $name_color = weechat::color(weechat::config_get_plugin( "name_color")) - . $_[6] - . weechat::color("default"); - my $name = $_[6]; - my $message_color = "${buffer_color} -- ${name_color} :: $_[7]"; - my $message = "${buffer} -- ${name} :: $_[7]"; - if( weechat::config_get_plugin( "notify" ) ne "on" ) { - my $command = weechat::config_get_plugin( "command" ); - if( $command ne "" ) { - if( $command =~ /\$msg/ ) { - $command =~ s/\$msg/\'$message\'/; - } else { - $command = "$command '$message'"; - } - weechat::command( "", "/shell $command" ); - } else { - weechat::print("", $message_color); - } - } else { - weechat::print_date_tags("", 0, "notify_highlight", $message_color); - } - } - } - - return weechat::WEECHAT_RC_OK; -} diff --git a/.config/weechat/perl/highmon.pl b/.config/weechat/perl/highmon.pl deleted file mode 100644 index f843cade..00000000 --- a/.config/weechat/perl/highmon.pl +++ /dev/null @@ -1,1154 +0,0 @@ -# -# highmon.pl - Highlight Monitoring for weechat 0.3.0 -# -# Add 'Highlight Monitor' buffer/bar to log all highlights in one spot -# -# Usage: -# /highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar -# Command wrapper for highmon commands -# -# /highmon clean default|orphan|all will clean the config section of default 'on' entries, -# channels you are no longer joined, or both -# -# /highmon clearbar will clear the contents of highmon's bar output -# -# /highmon monitor [channel] [server] is used to toggle a highlight monitoring on and off, this -# can be used in the channel buffer for the channel you wish to toggle, or be given -# with arguments e.g. /highmon monitor #weechat freenode -# -# /set plugins.var.perl.highmon.alignment -# The config setting "alignment" can be changed to; -# "channel", "schannel", "nchannel", "channel,nick", "schannel,nick", "nchannel,nick" -# to change how the monitor appears -# The 'channel' value will show: "#weechat" -# The 'schannel' value will show: "6" -# The 'nchannel' value will show: "6:#weechat" -# -# /set plugins.var.perl.highmon.short_names -# Setting this to 'on' will trim the network name from highmon, ala buffers.pl -# -# /set plugins.var.perl.highmon.merge_private -# Setting this to 'on' will merge private messages to highmon's display -# -# /set plugins.var.perl.highmon.color_buf -# This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. -# This *must* be a valid color name, or weechat will likely do unexpected things :) -# -# /set plugins.var.perl.highmon.hotlist_show -# Setting this to 'on' will let the highmon buffer appear in hotlists -# (status bar/buffer.pl) -# -# /set plugins.var.perl.highmon.away_only -# Setting this to 'on' will only put messages in the highmon buffer when -# you set your status to away -# -# /set plugins.var.perl.highmon.logging -# Toggles logging status for highmon buffer (default: off) -# -# /set plugins.var.perl.highmon.output -# Changes where output method of highmon; takes either "bar" or "buffer" (default; buffer) -# /set plugins.var.perl.highmon.bar_lines -# Changes the amount of lines the output bar will hold. -# (Only appears once output has been set to bar, defaults to 10) -# /set plugins.var.perl.highmon.bar_scrolldown -# Toggles the bar scrolling at the bottom when new highlights are received -# (Only appears once output has been set to bar, defaults to off) -# -# /set plugins.var.perl.highmon.nick_prefix -# /set plugins.var.perl.highmon.nick_suffix -# Sets the prefix and suffix chars in the highmon buffer -# (Defaults to <> if nothing set, and blank if there is) -# -# servername.#channel -# servername is the internal name for the server (set when you use /server add) -# #channel is the channel name, (where # is whatever channel type that channel happens to be) -# -# Optional, set up tweaks; Hide the status and input lines on highmon -# -# /set weechat.bar.status.conditions "${window.buffer.full_name} != perl.highmon" -# /set weechat.bar.input.conditions "${window.buffer.full_name} != perl.highmon" -# - -# Bugs and feature requests at: https://github.com/KenjiE20/highmon - -# History: -# 2020-06-21, Sebastien Helleu : -# v2.7: make call to bar_new compatible with WeeChat >= 2.9 -# 2019-05-13, HubbeKing -# v2.6: -add: send "logger_backlog" signal on buffer open if logging is enabled -# 2014-08-16, KenjiE20 : -# v2.5: -add: clearbar command to clear bar output -# -add: firstrun output prompt to check the help text for set up hints as they were being missed -# and update hint for conditions to use eval -# -change: Make all outputs use the date callback for more accurate timestamps (thanks Germainz) -# 2013-12-04, KenjiE20 : -# v2.4: -add: Support for eval style colour codes in time format used for bar output -# 2013-10-22, KenjiE20 : -# v2.3.3.2: -fix: Typo in fix command -# 2013-10-10, KenjiE20 : -# v2.3.3.1: -fix: Typo in closed buffer warning -# 2013-10-07, KenjiE20 : -# v2.3.3: -add: Warning and fixer for accidental buffer closes -# 2013-01-15, KenjiE20 : -# v2.3.2: -fix: Let bar output use the string set in weechat's config option -# -add: github info -# 2012-04-15, KenjiE20 : -# v2.3.1: -fix: Colour tags in bar timestamp string -# 2012-02-28, KenjiE20 : -# v2.3: -feature: Added merge_private option to display private messages (default: off) -# -fix: Channel name colours now show correctly when set to on -# 2011-08-07, Sitaktif : -# v2.2.1: -feature: Add "bar_scrolldown" option to have the bar display the latest hl at anytime -# -fix: Set up bar-specific config at startup if 'output' is already configured as 'bar' -# 2010-12-22, KenjiE20 : -# v2.2: -change: Use API instead of config to find channel colours, ready for 0.3.4 and 256 colours -# 2010-12-13, idl0r & KenjiE20 : -# v2.1.3: -fix: perl errors caused by bar line counter -# -fix: Add command list to inbuilt help -# 2010-09-30, KenjiE20 : -# v2.1.2: -fix: logging config was not correctly toggling back on (thanks to sleo for noticing) -# -version sync w/ chanmon -# 2010-08-27, KenjiE20 : -# v2.1: -feature: Add 'nchannel' option to alignment to display buffer and name -# 2010-04-25, KenjiE20 : -# v2.0: Release as version 2.0 -# 2010-04-24, KenjiE20 : -# v1.9: Rewrite for v2.0 -# Bring feature set in line with chanmon 2.0 -# -code change: Made more subs to shrink the code down in places -# -fix: Stop highmon attempting to double load/hook -# -fix: Add version dependant check for away status -# 2010-01-25, KenjiE20 : -# v1.7: -fixture: Let highmon be aware of nick_prefix/suffix -# and allow custom prefix/suffix for chanmon buffer -# (Defaults to <> if nothing set, and blank if there is) -# (Thanks to m4v for these) -# 2009-09-07, KenjiE20 : -# v1.6: -feature: colored buffer names -# -change: version sync with chanmon -# 2009-09-05, KenjiE20 : -# v1.2: -fix: disable buffer highlight -# 2009-09-02, KenjiE20 : -# v.1.1.1 -change: Stop unsightly text block on '/help' -# 2009-08-10, KenjiE20 : -# v1.1: In-client help added -# 2009-08-02, KenjiE20 : -# v1.0: Initial Public Release - -# Copyright (c) 2009 by KenjiE20 -# -# 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 3 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 program. If not, see . -# - -@bar_lines = (); -@bar_lines_time = (); -# Replicate info earlier for in-client help - -$highmonhelp = weechat::color("bold")."/highmon [help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar".weechat::color("-bold")." - Command wrapper for highmon commands - -".weechat::color("bold")."/highmon clean default|orphan|all".weechat::color("-bold")." will clean the config section of default 'on' entries, channels you are no longer joined, or both - -".weechat::color("bold")."/highmon clearbar".weechat::color("-bold")." will clear the contents of highmon's bar output - -".weechat::color("bold")."/highmon monitor [channel] [server]".weechat::color("-bold")." is used to toggle a highlight monitoring on and off, this can be used in the channel buffer for the channel you wish to toggle, or be given with arguments e.g. /highmon monitor #weechat freenode - -".weechat::color("bold")."/set plugins.var.perl.highmon.alignment".weechat::color("-bold")." - The config setting \"alignment\" can be changed to; - \"channel\", \"schannel\", \"nchannel\", \"channel,nick\", \"schannel,nick\", \"nchannel,nick\" - to change how the monitor appears - The 'channel' value will show: \"#weechat\" - The 'schannel' value will show: \"6\" - The 'nchannel' value will show: \"6:#weechat\" - -".weechat::color("bold")."/set plugins.var.perl.highmon.short_names".weechat::color("-bold")." - Setting this to 'on' will trim the network name from highmon, ala buffers.pl - -".weechat::color("bold")."/set plugins.var.perl.highmon.merge_private".weechat::color("-bold")." - Setting this to 'on' will merge private messages to highmon's display - -".weechat::color("bold")."/set plugins.var.perl.highmon.color_buf".weechat::color("-bold")." - This turns colored buffer names on or off, you can also set a single fixed color by using a weechat color name. - This ".weechat::color("bold")."must".weechat::color("-bold")." be a valid color name, or weechat will likely do unexpected things :) - -".weechat::color("bold")."/set plugins.var.perl.highmon.hotlist_show".weechat::color("-bold")." -Setting this to 'on' will let the highmon buffer appear in hotlists (status bar/buffer.pl) - -".weechat::color("bold")."/set plugins.var.perl.highmon.away_only".weechat::color("-bold")." -Setting this to 'on' will only put messages in the highmon buffer when you set your status to away - -".weechat::color("bold")."/set plugins.var.perl.highmon.logging".weechat::color("-bold")." - Toggles logging status for highmon buffer (default: off) - -".weechat::color("bold")."/set plugins.var.perl.highmon.output".weechat::color("-bold")." - Changes where output method of highmon; takes either \"bar\" or \"buffer\" (default; buffer) -".weechat::color("bold")."/set plugins.var.perl.highmon.bar_lines".weechat::color("-bold")." - Changes the amount of lines the output bar will hold. - (Only appears once output has been set to bar, defaults to 10) -".weechat::color("bold")."/set plugins.var.perl.highmon.bar_scrolldown".weechat::color("-bold")." - Toggles the bar scrolling at the bottom when new highlights are received - (Only appears once output has been set to bar, defaults to off) - -".weechat::color("bold")."/set plugins.var.perl.highmon.nick_prefix".weechat::color("-bold")." -".weechat::color("bold")."/set plugins.var.perl.highmon.nick_suffix".weechat::color("-bold")." - Sets the prefix and suffix chars in the highmon buffer - (Defaults to <> if nothing set, and blank if there is) - -".weechat::color("bold")."servername.#channel".weechat::color("-bold")." - servername is the internal name for the server (set when you use /server add) - #channel is the channel name, (where # is whatever channel type that channel happens to be) - -".weechat::color("bold")."Optional, set up tweaks;".weechat::color("-bold")." Hide the status and input lines on highmon - -".weechat::color("bold")."/set weechat.bar.status.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold")." -".weechat::color("bold")."/set weechat.bar.input.conditions \"\${window.buffer.full_name} != perl.highmon\"".weechat::color("-bold"); -# Print verbose help -sub print_help -{ - weechat::print("", "\t".weechat::color("bold")."Highmon Help".weechat::color("-bold")."\n\n"); - weechat::print("", "\t".$highmonhelp); - return weechat::WEECHAT_RC_OK; -} - -# Bar item build -sub highmon_bar_build -{ - # Get max lines - $max_lines = weechat::config_get_plugin("bar_lines"); - $max_lines = $max_lines ? $max_lines : 10; - $str = ''; - $align_num = 0; - $count = 0; - # Keep lines within max - while ($#bar_lines > $max_lines) - { - shift(@bar_lines); - shift(@bar_lines_time); - } - # So long as we have some lines, build a string - if (@bar_lines) - { - # Build loop - $sep = " ".weechat::config_string(weechat::config_get("weechat.look.prefix_suffix"))." "; - foreach(@bar_lines) - { - # Find max align needed - $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); - $align_num = $prefix_num if ($prefix_num > $align_num); - } - foreach(@bar_lines) - { - # Get align for this line - $prefix_num = (index(weechat::string_remove_color($_, ""), $sep)); - - # Make string - $str = $str.$bar_lines_time[$count]." ".(" " x ($align_num - $prefix_num)).$_."\n"; - # Increment count for sync with time list - $count++; - } - } - return $str; -} - -# Make a new bar -sub highmon_bar_open -{ - # Make the bar item - weechat::bar_item_new("highmon", "highmon_bar_build", ""); - - if (weechat::info_get("version_number", "") >= 0x02090000) - { - $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "default", "on", "highmon"); - } - else - { - $highmon_bar = weechat::bar_new ("highmon", "off", 100, "root", "", "bottom", "vertical", "vertical", 0, 0, "default", "cyan", "default", "on", "highmon"); - } - - return weechat::WEECHAT_RC_OK; -} -# Close bar -sub highmon_bar_close -{ - # Find if bar exists - $highmon_bar = weechat::bar_search("highmon"); - # If is does, close it - if ($highmon_bar ne "") - { - weechat::bar_remove($highmon_bar); - } - - # Find if bar item exists - $highmon_bar_item = weechat::bar_item_search("highmon_bar"); - # If is does, close it - if ($highmon_bar_item ne "") - { - weechat::bar_remove($highmon_bar_item); - } - - @bar_lines = (); - return weechat::WEECHAT_RC_OK; -} - -# Make a new buffer -sub highmon_buffer_open -{ - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - - # Make a new buffer - if ($highmon_buffer eq "") - { - $highmon_buffer = weechat::buffer_new("highmon", "highmon_buffer_input", "", "highmon_buffer_close", ""); - } - - # Turn off notify, highlights - if ($highmon_buffer ne "") - { - if (weechat::config_get_plugin("hotlist_show") eq "off") - { - weechat::buffer_set($highmon_buffer, "notify", "0"); - } - weechat::buffer_set($highmon_buffer, "highlight_words", "-"); - weechat::buffer_set($highmon_buffer, "title", "Highlight Monitor"); - # Set no_log - if (weechat::config_get_plugin("logging") eq "off") - { - weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); - } - # send "logger_backlog" signal if logging is enabled to display backlog - if (weechat::config_get_plugin("logging") eq "on") - { - weechat::hook_signal_send("logger_backlog", weechat::WEECHAT_HOOK_SIGNAL_POINTER, $highmon_buffer) - } - } - return weechat::WEECHAT_RC_OK; -} -# Buffer input has no action -sub highmon_buffer_input -{ - return weechat::WEECHAT_RC_OK; -} -# Close up -sub highmon_buffer_close -{ - $highmon_buffer = ""; - # If user hasn't changed output style warn user - if (weechat::config_get_plugin("output") eq "buffer") - { - weechat::print("", "\tHighmon buffer has been closed but output is still set to buffer, unusual results may occur. To recreate the buffer use ".weechat::color("bold")."/highmon fix".weechat::color("-bold")); - } - return weechat::WEECHAT_RC_OK; -} - -# Highmon command wrapper -sub highmon_command_cb -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - my $cmd = ''; - my $arg = ''; - - if ($args ne "") - { - # Split argument up - @arg_array = split(/ /,$args); - # Take first as command - $cmd = shift(@arg_array); - # Rebuild string to pass to subs - if (@arg_array) - { - $arg = join(" ", @arg_array); - } - } - - # Help command - if ($cmd eq "" || $cmd eq "help") - { - print_help(); - } - # /monitor command - elsif ($cmd eq "monitor") - { - highmon_toggle($data, $buffer, $arg); - } - # /highclean command - elsif ($cmd eq "clean") - { - highmon_config_clean($data, $buffer, $arg); - } - # clearbar command - elsif ($cmd eq "clearbar") - { - if (weechat::config_get_plugin("output") eq "bar") - { - @bar_lines = (); - weechat::bar_item_update("highmon"); - } - } - # Fix closed buffer - elsif ($cmd eq "fix") - { - if (weechat::config_get_plugin("output") eq "buffer" && $highmon_buffer eq "") - { - highmon_buffer_open(); - } - } - return weechat::WEECHAT_RC_OK; -} - -# Clean up config entries -sub highmon_config_clean -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - - # Don't do anything if bad option given - if ($args ne "default" && $args ne "orphan" && $args ne "all") - { - weechat::print("", "\thighmon.pl: Unknown option"); - return weechat::WEECHAT_RC_OK; - } - - @chans = (); - # Load an infolist of highmon options - $infolist = weechat::infolist_get("option", "", "*highmon*"); - while (weechat::infolist_next($infolist)) - { - $name = weechat::infolist_string($infolist, "option_name"); - $name =~ s/perl\.highmon\.(\w*)\.([#&\+!])(.*)/$1.$2$3/; - if ($name =~ /^(.*)\.([#&\+!])(.*)$/) - { - $action = 0; - # Clean up all 'on's - if ($args eq "default" || $args eq "all") - { - # If value in config is "on" - if (weechat::config_get_plugin($name) eq "on") - { - # Unset and if successful flag as changed - $rc = weechat::config_unset_plugin($name); - if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) - { - $action = 1; - } - } - } - # Clean non joined - if ($args eq "orphan" || $args eq "all") - { - # If we can't find the buffer for this entry - if (weechat::buffer_search("irc", $name) eq "") - { - # Unset and if successful flag as changed - $rc = weechat::config_unset_plugin($name); - if ($rc eq weechat::WEECHAT_CONFIG_OPTION_UNSET_OK_REMOVED) - { - $action = 1; - } - } - } - # Add changed entry names to list - push (@chans, $name) if ($action); - } - } - weechat::infolist_free($infolist); - # If channels were cleaned from config - if (@chans) - { - # If only one entry - if (@chans == 1) - { - $str = "\thighmon.pl: Cleaned ".@chans." entry from the config:"; - } - else - { - $str = "\thighmon.pl: Cleaned ".@chans." entries from the config:"; - } - # Build a list of channels - foreach(@chans) - { - $str = $str." ".$_; - } - # Print what happened - weechat::print("",$str); - } - # Config seemed to be clean - else - { - weechat::print("", "\thighmon.pl: No entries removed"); - } - return weechat::WEECHAT_RC_OK; -} - -# Check config elements -sub highmon_config_init -{ - # First run default - if (!(weechat::config_is_set_plugin ("first_run"))) - { - if (weechat::config_get_plugin("first_run") ne "true") - { - weechat::print("", "\tThis appears to be the first time highmon has been run. For help and common set up hints see /highmon help"); - weechat::config_set_plugin("first_run", "true"); - } - } - # Alignment default - if (!(weechat::config_is_set_plugin ("alignment"))) - { - weechat::config_set_plugin("alignment", "channel"); - } - if (weechat::config_get_plugin("alignment") eq "") - { - weechat::config_set_plugin("alignment", "none"); - } - - # Short name default - if (!(weechat::config_is_set_plugin ("short_names"))) - { - weechat::config_set_plugin("short_names", "off"); - } - - # Coloured names default - if (!(weechat::config_is_set_plugin ("color_buf"))) - { - weechat::config_set_plugin("color_buf", "on"); - } - - # Hotlist show default - if (!(weechat::config_is_set_plugin ("hotlist_show"))) - { - weechat::config_set_plugin("hotlist_show", "off"); - } - - # Away only default - if (!(weechat::config_is_set_plugin ("away_only"))) - { - weechat::config_set_plugin("away_only", "off"); - } - - # highmon log default - if (!(weechat::config_is_set_plugin ("logging"))) - { - weechat::config_set_plugin("logging", "off"); - } - - # Output default - if (!(weechat::config_is_set_plugin ("output"))) - { - weechat::config_set_plugin("output", "buffer"); - } - - # Private message merging - if (!(weechat::config_is_set_plugin ("merge_private"))) - { - weechat::config_set_plugin("merge_private", "off"); - } - - # Set bar config in case output was set to "bar" before even changing the setting - if (weechat::config_get_plugin("output") eq "bar") - { - # Output bar lines default - if (!(weechat::config_is_set_plugin ("bar_lines"))) - { - weechat::config_set_plugin("bar_lines", "10"); - } - if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) - { - weechat::config_set_plugin("bar_scrolldown", "off"); - } - } - - # Check for exisiting prefix/suffix chars, and setup accordingly - $prefix = weechat::config_get("irc.look.nick_prefix"); - $prefix = weechat::config_string($prefix); - $suffix = weechat::config_get("irc.look.nick_suffix"); - $suffix = weechat::config_string($suffix); - - if (!(weechat::config_is_set_plugin("nick_prefix"))) - { - if ($prefix eq "" && $suffix eq "") - { - weechat::config_set_plugin("nick_prefix", "<"); - } - else - { - weechat::config_set_plugin("nick_prefix", ""); - } - } - - if (!(weechat::config_is_set_plugin("nick_suffix"))) - { - if ($prefix eq "" && $suffix eq "") - { - weechat::config_set_plugin("nick_suffix", ">"); - } - else - { - weechat::config_set_plugin("nick_suffix", ""); - } - } -} - -# Get config updates -sub highmon_config_cb -{ - $point = $_[0]; - $name = $_[1]; - $value = $_[2]; - - $name =~ s/^plugins\.var\.perl\.highmon\.//; - - # Set logging on buffer - if ($name eq "logging") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - if ($value eq "off") - { - weechat::buffer_set($highmon_buffer, "localvar_set_no_log", "1"); - } - else - { - weechat::buffer_set($highmon_buffer, "localvar_del_no_log", ""); - } - } - # Output changer - elsif ($name eq "output") - { - if ($value eq "bar") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - # Close if it exists - if ($highmon_buffer ne "") - { - weechat::buffer_close($highmon_buffer) - } - - # Output bar lines default - if (!(weechat::config_is_set_plugin ("bar_lines"))) - { - weechat::config_set_plugin("bar_lines", "10"); - } - if (!(weechat::config_is_set_plugin ("bar_scrolldown"))) - { - weechat::config_set_plugin("bar_scrolldown", "off"); - } - # Make a bar if doesn't exist - highmon_bar_open(); - } - elsif ($value eq "buffer") - { - # If a bar exists, close it - highmon_bar_close(); - # Open buffer - highmon_buffer_open(); - } - - } - # Change if hotlist config changes - elsif ($name eq "hotlist_show") - { - # Search for pre-existing buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - if ($value eq "off" && $highmon_buffer) - { - weechat::buffer_set($highmon_buffer, "notify", "0"); - } - elsif ($value ne "off" && $highmon_buffer) - { - weechat::buffer_set($highmon_buffer, "notify", "3"); - } - } - elsif ($name eq "weechat.look.prefix_suffix") - { - if (weechat::config_get_plugin("output") eq "bar") - { - @bar_lines = (); - weechat::print("", "\thighmon: weechat.look.prefix_suffix changed, clearing highmon bar"); - weechat::bar_item_update("highmon"); - } - } - return weechat::WEECHAT_RC_OK; -} - -# Set up weechat hooks / commands -sub highmon_hook -{ - weechat::hook_print("", "", "", 0, "highmon_new_message", ""); - weechat::hook_command("highclean", "Highmon config clean up", "default|orphan|all", " default: Cleans all config entries with the default \"on\" value\n orphan: Cleans all config entries for channels you aren't currently joined\n all: Does both defaults and orphan", "default|orphan|all", "highmon_config_clean", ""); - - weechat::hook_command("highmon", "Highmon help", "[help] | [monitor [channel [server]]] | [clean default|orphan|all] | clearbar", " help: Print help on config options for highmon\n monitor: Toggles monitoring for a channel\n clean: Highmon config clean up (/highclean)\nclearbar: Clear Highmon bar", "help || monitor %(irc_channels) %(irc_servers) || clean default|orphan|all || clearbar", "highmon_command_cb", ""); - - weechat::hook_config("plugins.var.perl.highmon.*", "highmon_config_cb", ""); - weechat::hook_config("weechat.look.prefix_suffix", "highmon_config_cb", ""); -} - -# Main body, Callback for hook_print -sub highmon_new_message -{ - my $net = ""; - my $chan = ""; - my $nick = ""; - my $outstr = ""; - my $window_displayed = ""; - my $dyncheck = "0"; - -# DEBUG point -# $string = "\t"."0: ".$_[0]." 1: ".$_[1]." 2: ".$_[2]." 3: ".$_[3]." 4: ".$_[4]." 5: ".$_[5]." 6: ".$_[6]." 7: ".$_[7]; -# weechat::print("", "\t".$string); - - $cb_datap = $_[0]; - $cb_bufferp = $_[1]; - $cb_date = $_[2]; - $cb_tags = $_[3]; - $cb_disp = $_[4]; - $cb_high = $_[5]; - $cb_prefix = $_[6]; - $cb_msg = $_[7]; - - # Only work on highlighted messages or private message when enabled - if ($cb_high == "1" || (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/)) - { - # Pre bug #29618 (0.3.3) away detect - if (weechat::info_get("version_number", "") <= 0x00030200) - { - $away = ''; - # Get infolist for this server - $infolist = weechat::infolist_get("irc_server", "", weechat::buffer_get_string($cb_bufferp, "localvar_server")); - while (weechat::infolist_next($infolist)) - { - # Get away message is is_away is on - $away = weechat::infolist_string($infolist, "away_message") if (weechat::infolist_integer($infolist, "is_away")); - } - weechat::infolist_free($infolist); - } - # Post bug #29618 fix - else - { - $away = weechat::buffer_get_string($cb_bufferp, "localvar_away"); - } - if (weechat::config_get_plugin("away_only") ne "on" || ($away ne "")) - { - # Check buffer name is an IRC channel - $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); - if ($bufname =~ /(.*)\.([#&\+!])(.*)/) - { - # Are we running on this channel - if (weechat::config_get_plugin($bufname) ne "off" && $cb_disp eq "1") - { - # Format nick - # Line isn't action or topic notify - if (!($cb_tags =~ /irc_action/) && !($cb_tags =~ /irc_topic/)) - { - # Strip nick colour - $uncolnick = weechat::string_remove_color($cb_prefix, ""); - # Format nick - $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); - } - # Topic line - elsif ($cb_tags =~ /irc_topic/) - { - $nick = " ".$cb_prefix.weechat::color("reset"); - } - # Action line - else - { - $uncolnick = weechat::string_remove_color($cb_prefix, ""); - $nick = weechat::color("chat_highlight").$uncolnick.weechat::color("reset"); - } - # Send to output - highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); - } - } - # Or is private message - elsif (weechat::config_get_plugin("merge_private") eq "on" && $cb_tags =~ /notify_private/) - { - # Strip nick colour - $uncolnick = weechat::buffer_get_string($cb_bufferp, 'short_name'); - # Format nick - $nick = " ".weechat::config_get_plugin("nick_prefix").weechat::color("chat_highlight").$uncolnick.weechat::color("reset").weechat::config_get_plugin("nick_suffix"); - #Send to output - highmon_print ($cb_msg, $cb_bufferp, $nick, $cb_date, $cb_tags); - } - } - } - return weechat::WEECHAT_RC_OK; -} - -# Output formatter and printer takes (msg bufpointer nick) -sub highmon_print -{ - $cb_msg = $_[0]; - my $cb_bufferp = $_[1] if ($_[1]); - my $nick = $_[2] if ($_[2]); - my $cb_date = $_[3] if ($_[3]); - my $cb_tags = $_[4] if ($_[4]); - - #Normal channel message - if ($cb_bufferp && $nick) - { - # Format buffer name - $bufname = format_buffer_name($cb_bufferp); - - # If alignment is #channel | nick msg - if (weechat::config_get_plugin("alignment") eq "channel") - { - $nick =~ s/\s(.*)/$1/; - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is channel number | nick msg - elsif (weechat::config_get_plugin("alignment") eq "schannel") - { - $nick =~ s/\s(.*)/$1/; - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is number:#channel | nick msg - elsif (weechat::config_get_plugin("alignment") eq "nchannel") - { - $nick =~ s/\s(.*)/$1/; - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - # Build string - $outstr = $bufname."\t".$nick." ".$cb_msg; - } - # or if it is #channel nick | msg - elsif (weechat::config_get_plugin("alignment") eq "channel,nick") - { - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or if it is channel number nick | msg - elsif (weechat::config_get_plugin("alignment") eq "schannel,nick") - { - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or if it is number:#channel nick | msg - elsif (weechat::config_get_plugin("alignment") eq "nchannel,nick") - { - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - # Build string - $outstr = $bufname.":".$nick."\t".$cb_msg; - } - # or finally | #channel nick msg - else - { - # Build string - $outstr = "\t".$bufname.":".$nick." ".$cb_msg; - } - } - # highmon channel toggle message - elsif ($cb_bufferp && !$nick) - { - # Format buffer name - $bufname = format_buffer_name($cb_bufferp); - - # If alignment is #channel * | * - if (weechat::config_get_plugin("alignment") =~ /channel/) - { - # If it's actually channel number * | * - if (weechat::config_get_plugin("alignment") =~ /schannel/) - { - # Use channel number instead - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').weechat::color("reset"); - } - # Or if it's actually number:#channel * | * - if (weechat::config_get_plugin("alignment") =~ /nchannel/) - { - # Place channel number in front of formatted name - $bufname = weechat::color("chat_prefix_buffer").weechat::buffer_get_integer($cb_bufferp, 'number').":".weechat::color("reset").$bufname; - } - $outstr = $bufname."\t".$cb_msg; - } - # or if alignment is | * - else - { - $outstr = $bufname.": ".$cb_msg; - } - } - # highmon dynmon - elsif (!$cb_bufferp && !$nick) - { - $outstr = "\t".$cb_msg; - } - - # Send string to buffer - if (weechat::config_get_plugin("output") eq "buffer") - { - # Search for and confirm buffer - $highmon_buffer = weechat::buffer_search("perl", "highmon"); - # Print - if ($cb_date) - { - weechat::print_date_tags($highmon_buffer, $cb_date, $cb_tags, $outstr); - } - else - { - weechat::print($highmon_buffer, $outstr); - } - } - elsif (weechat::config_get_plugin("output") eq "bar") - { - # Add time string - use POSIX qw(strftime); - if ($cb_date) - { - $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime($cb_date)); - } - else - { - $time = strftime(weechat::config_string(weechat::config_get("weechat.look.buffer_time_format")), localtime); - } - # Colourise - if ($time =~ /\$\{(?:color:)?[\w,]+\}/) # Coloured string - { - while ($time =~ /\$\{(?:color:)?([\w,]+)\}/) - { - $color = weechat::color($1); - $time =~ s/\$\{(?:color:)?[\w,]+\}/$color/; - } - $time .= weechat::color("reset"); - } - else # Default string - { - $colour = weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_time_delimiters"))); - $reset = weechat::color("reset"); - $time =~ s/(\d*)(.)(\d*)/$1$colour$2$reset$3/g; - } - # Push updates to bar lists - push (@bar_lines_time, $time); - - # Change tab char - $delim = " ".weechat::color(weechat::config_string(weechat::config_get("weechat.color.chat_delimiters"))).weechat::config_string(weechat::config_get("weechat.look.prefix_suffix")).weechat::color("reset")." "; - $outstr =~ s/\t/$delim/; - - push (@bar_lines, $outstr); - # Trigger update - weechat::bar_item_update("highmon"); - - if (weechat::config_get_plugin("bar_scrolldown") eq "on") - { - weechat::command("", "/bar scroll highmon * ye") - } - } -} - -# Start the output display -sub highmon_start -{ - if (weechat::config_get_plugin("output") eq "buffer") - { - highmon_buffer_open(); - } - elsif (weechat::config_get_plugin("output") eq "bar") - { - highmon_bar_open(); - } -} - -# Takes two optional args (channel server), toggles monitoring on/off -sub highmon_toggle -{ - $data = $_[0]; - $buffer = $_[1]; - $args = $_[2]; - - # Check if we've been told what channel to act on - if ($args ne "") - { - # Split argument up - @arg_array = split(/ /,$args); - # Check if a server was given - if ($arg_array[1]) - { - # Find matching - $bufp = weechat::buffer_search("irc", $arg_array[1].".".$arg_array[0]); - } - else - { - $found_chans = 0; - # Loop through defined servers - $infolist = weechat::infolist_get("buffer", "", ""); - while (weechat::infolist_next($infolist)) - { - # Only interesting in IRC buffers - if (weechat::infolist_string($infolist, "plugin_name") eq "irc") - { - # Find buffers that maych - $sname = weechat::infolist_string($infolist, "short_name"); - if ($sname eq $arg_array[0]) - { - $found_chans++; - $bufp = weechat::infolist_pointer($infolist, "pointer"); - } - } - } - weechat::infolist_free($infolist); - # If the infolist found more than one channel, halt as we need to know which one - if ($found_chans > 1) - { - weechat::print("", "Channel name is not unique, please define server"); - return weechat::WEECHAT_RC_OK; - } - } - # Something didn't return right - if ($bufp eq "") - { - weechat::print("", "Could not find buffer"); - return weechat::WEECHAT_RC_OK; - } - } - else - { - # Get pointer from where we are - $bufp = weechat::current_buffer(); - } - # Get buffer name - $bufname = weechat::buffer_get_string($bufp, 'name'); - # Test if buffer is an IRC channel - if ($bufname =~ /(.*)\.([#&\+!])(.*)/) - { - if (weechat::config_get_plugin($bufname) eq "off") - { - # If currently off, set on - weechat::config_set_plugin($bufname, "on"); - - # Send to output formatter - highmon_print("Highlight Monitoring Enabled", $bufp); - return weechat::WEECHAT_RC_OK; - } - elsif (weechat::config_get_plugin($bufname) eq "on" || weechat::config_get_plugin($bufname) eq "") - { - # If currently on, set off - weechat::config_set_plugin($bufname, "off"); - - # Send to output formatter - highmon_print("Highlight Monitoring Disabled", $bufp); - return weechat::WEECHAT_RC_OK; - } - } -} - -# Takes a buffer pointer and returns a formatted name -sub format_buffer_name -{ - $cb_bufferp = $_[0]; - $bufname = weechat::buffer_get_string($cb_bufferp, 'name'); - - # Set colour from buffer name - if (weechat::config_get_plugin("color_buf") eq "on") - { - # Determine what colour to use - $color = weechat::info_get("irc_nick_color", $bufname); - if (!$color) - { - $color = 0; - @char_array = split(//,$bufname); - foreach $char (@char_array) - { - $color += ord($char); - } - $color %= 10; - $color = sprintf "weechat.color.chat_nick_color%02d", $color+1; - $color = weechat::config_get($color); - $color = weechat::config_string($color); - $color = weechat::color($color); - } - - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - - # Build a coloured string - $bufname = $color.$bufname.weechat::color("reset"); - } - # User set colour name - elsif (weechat::config_get_plugin("color_buf") ne "off") - { - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - - $color = weechat::config_get_plugin("color_buf"); - $bufname = weechat::color($color).$bufname.weechat::color("reset"); - } - # Stick with default colour - else - { - # Private message just show network - if (weechat::config_get_plugin("merge_private") eq "on" && weechat::buffer_get_string($cb_bufferp, "localvar_type") eq "private") - { - $bufname = weechat::buffer_get_string($cb_bufferp, "localvar_server"); - } - # Format name to short or 'nicename' - elsif (weechat::config_get_plugin("short_names") eq "on") - { - $bufname = weechat::buffer_get_string($cb_bufferp, 'short_name'); - } - else - { - $bufname =~ s/(.*)\.([#&\+!])(.*)/$1$2$3/; - } - } - - return $bufname; -} - -# Check result of register, and attempt to behave in a sane manner -if (!weechat::register("highmon", "KenjiE20", "2.7", "GPL3", "Highlight Monitor", "", "")) -{ - # Double load - weechat::print ("", "\tHighmon is already loaded"); - return weechat::WEECHAT_RC_OK; -} -else -{ - # Start everything - highmon_hook(); - highmon_config_init(); - highmon_start(); -} diff --git a/.config/weechat/perl/iset.pl b/.config/weechat/perl/iset.pl deleted file mode 100644 index a4a8e35f..00000000 --- a/.config/weechat/perl/iset.pl +++ /dev/null @@ -1,1645 +0,0 @@ -# -# Copyright (C) 2008-2017 Sebastien Helleu -# Copyright (C) 2010-2017 Nils Görs -# -# 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 3 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 program. If not, see . -# -# Set WeeChat and plugins options interactively. -# -# History: -# -# 2020-06-21, Sebastien Helleu : -# version 4.4: make call to bar_new compatible with WeeChat >= 2.9 -# 2017-04-14, nils_2 -# version 4.3: add option "use_color" (https://github.com/weechat/scripts/issues/93) -# 2016-07-08, nils_2 -# version 4.2: add diff function -# 2016-02-06, Sebastien Helleu : -# version 4.1: remove debug print -# 2015-12-24, Sebastien Helleu : -# version 4.0: add support of parent options (inherited values in irc servers) -# with WeeChat >= 1.4 -# 2015-05-16, Sebastien Helleu : -# version 3.9: fix cursor position when editing an option with WeeChat >= 1.2 -# 2015-05-02, arza : -# version 3.8: don't append "null" to /set when setting an undefined setting -# 2015-05-01, nils_2 : -# version 3.7: fix two perl warnings (reported by t3chguy) -# 2014-09-30, arza : -# version 3.6: fix current line counter when options aren't found -# 2014-06-03, nils_2 : -# version 3.5: add new option "use_mute" -# 2014-01-30, stfn : -# version 3.4: add new options "color_value_diff" and "color_value_diff_selected" -# 2014-01-16, luz : -# version 3.3: fix bug with column alignment in iset buffer when option -# name contains unicode characters -# 2013-08-03, Sebastien Helleu : -# version 3.2: allow "q" as input in iset buffer to close it -# 2013-07-14, Sebastien Helleu : -# version 3.1: remove unneeded calls to iset_refresh() in mouse callback -# (faster mouse actions when lot of options are displayed), -# fix bug when clicking on a line after the last option displayed -# 2013-04-30, arza : -# version 3.0: simpler title, fix refresh on unset -# 2012-12-16, nils_2 : -# version 2.9: fix focus window with iset buffer on mouse click -# 2012-08-25, nils_2 : -# version 2.8: most important key and mouse bindings for iset buffer added to title-bar (idea The-Compiler) -# 2012-07-31, nils_2 : -# version 2.7: add combined option and value search (see /help iset) -# : add exact value search (see /help iset) -# : fix problem with metacharacter in value search -# : fix use of uninitialized value for unset option and reset value of option -# 2012-07-25, nils_2 : -# version 2.6: switch to iset buffer (if existing) when command /iset is called with arguments -# 2012-03-17, Sebastien Helleu : -# version 2.5: fix check of sections when creating config file -# 2012-03-09, Sebastien Helleu : -# version 2.4: fix reload of config file -# 2012-02-02, nils_2 : -# version 2.3: fixed: refresh problem with new search results and cursor was outside window. -# : add: new option "current_line" in title bar -# version 2.2: fixed: refresh error when toggling plugins description -# 2011-11-05, nils_2 : -# version 2.1: use own config file (iset.conf), fix own help color (used immediately) -# 2011-10-16, nils_2 : -# version 2.0: add support for left-mouse-button and more sensitive mouse gesture (for integer/color options) -# add help text for mouse support -# 2011-09-20, Sebastien Helleu : -# version 1.9: add mouse support, fix iset buffer, fix errors on first load under FreeBSD -# 2011-07-21, nils_2 : -# version 1.8: added: option "show_plugin_description" (alt+p) -# fixed: typos in /help iset (lower case for alt+'x' keys) -# 2011-05-29, nils_2 : -# version 1.7: added: version check for future needs -# added: new option (scroll_horiz) and usage of scroll_horiz function (weechat >= 0.3.6 required) -# fixed: help_bar did not pop up immediately using key-shortcut -# 2011-02-19, nils_2 : -# version 1.6: added: display of all possible values in help bar (show_help_extra_info) -# fixed: external user options never loaded when starting iset first time -# 2011-02-13, Sebastien Helleu : -# version 1.5: use new help format for command arguments -# 2011-02-03, nils_2 : -# version 1.4: fixed: restore value filter after /upgrade using buffer local variable. -# 2011-01-14, nils_2 : -# version 1.3: added function to search for values (option value_search_char). -# code optimization. -# 2010-12-26, Sebastien Helleu : -# version 1.2: improve speed of /upgrade when iset buffer is open, -# restore filter used after /upgrade using buffer local variable, -# use /iset filter argument if buffer is open. -# 2010-11-21, drubin : -# version 1.1.1: fix bugs with cursor position -# 2010-11-20, nils_2 : -# version 1.1: cursor position set to value -# 2010-08-03, Sebastien Helleu : -# version 1.0: move misplaced call to infolist_free() -# 2010-02-02, rettub : -# version 0.9: turn all the help stuff off if option 'show_help_bar' is 'off', -# new key binding - to toggle help_bar and help stuff on/off -# 2010-01-30, nils_2 : -# version 0.8: fix error when option does not exist -# 2010-01-24, Sebastien Helleu : -# version 0.7: display iset bar only on iset buffer -# 2010-01-22, nils_2 and drubin: -# version 0.6: add description in a bar, fix singular/plural bug in title bar, -# fix selected line when switching buffer -# 2009-06-21, Sebastien Helleu : -# version 0.5: fix bug with iset buffer after /upgrade -# 2009-05-02, Sebastien Helleu : -# version 0.4: sync with last API changes -# 2009-01-04, Sebastien Helleu : -# version 0.3: open iset buffer when /iset command is executed -# 2009-01-04, Sebastien Helleu : -# version 0.2: use null values for options, add colors, fix refresh bugs, -# use new keys to reset/unset options, sort options by name, -# display number of options in buffer's title -# 2008-11-05, Sebastien Helleu : -# version 0.1: first official version -# 2008-04-19, Sebastien Helleu : -# script creation - -use strict; - -my $PRGNAME = "iset"; -my $VERSION = "4.4"; -my $DESCR = "Interactive Set for configuration options"; -my $AUTHOR = "Sebastien Helleu "; -my $LICENSE = "GPL3"; -my $LANG = "perl"; -my $ISET_CONFIG_FILE_NAME = "iset"; - -my $iset_config_file; -my $iset_buffer = ""; -my $wee_version_number = 0; -my @iset_focus = (); -my @options_names = (); -my @options_parent_names = (); -my @options_types = (); -my @options_values = (); -my @options_default_values = (); -my @options_parent_values = (); -my @options_is_null = (); -my $option_max_length = 0; -my $current_line = 0; -my $filter = "*"; -my $description = ""; -my $options_name_copy = ""; -my $iset_filter_title = ""; -# search modes: 0 = index() on value, 1 = grep() on value, 2 = grep() on option, 3 = grep on option & value, 4 = diff all, 5 = diff parts -my $search_mode = 2; -my $search_value = ""; -my $help_text_keys = "alt + space: toggle, +/-: increase/decrease, enter: change, ir: reset, iu: unset, v: toggle help bar"; -my $help_text_mouse = "Mouse: left: select, right: toggle/set, right + drag left/right: increase/decrease"; -my %options_iset; - -my %mouse_keys = ("\@chat(perl.$PRGNAME):button1" => "hsignal:iset_mouse", - "\@chat(perl.$PRGNAME):button2*" => "hsignal:iset_mouse", - "\@chat(perl.$PRGNAME):wheelup" => "/repeat 5 /iset **up", - "\@chat(perl.$PRGNAME):wheeldown" => "/repeat 5 /iset **down"); - - -sub iset_title -{ - if ($iset_buffer ne "") - { - my $current_line_counter = ""; - if (weechat::config_boolean($options_iset{"show_current_line"}) == 1) - { - if (@options_names eq 0) - { - $current_line_counter = "0/"; - } - else - { - $current_line_counter = ($current_line + 1) . "/"; - } - } - my $show_filter = ""; - if ($search_mode eq 0) - { - $iset_filter_title = "(value) "; - $show_filter = $search_value; - if ( substr($show_filter,0,1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $show_filter = substr($show_filter,1,length($show_filter)); - } - } - elsif ($search_mode eq 1) - { - $iset_filter_title = "(value) "; - $show_filter = "*".$search_value."*"; - } - elsif ($search_mode eq 2) - { - $iset_filter_title = ""; - $filter = "*" if ($filter eq ""); - $show_filter = $filter; - } - elsif ($search_mode == 4 or $search_mode == 5) - { - $iset_filter_title = "diff: "; - $show_filter = "all"; - $show_filter = $search_value if $search_mode == 5; - } - elsif ($search_mode eq 3) - { - $iset_filter_title = "(option) "; - $show_filter = $filter - .weechat::color("default") - ." / (value) " - .weechat::color("yellow") - ."*".$search_value."*"; - } - weechat::buffer_set($iset_buffer, "title", - $iset_filter_title - .weechat::color("yellow") - .$show_filter - .weechat::color("default")." | " - .$current_line_counter - .@options_names - ." | " - .$help_text_keys - ." | " - .$help_text_mouse); - } -} - -sub iset_create_filter -{ - $filter = $_[0]; - if ( $search_mode == 3 ) - { - my @cmd_array = split(/ /,$filter); - my $array_count = @cmd_array; - $filter = $cmd_array[0]; - $filter = $cmd_array[0] . " " . $cmd_array[1] if ( $array_count >2 ); - } - $filter = "$1.*" if ($filter =~ /f (.*)/); # search file - $filter = "*.$1.*" if ($filter =~ /s (.*)/); # search section - if ((substr($filter, 0, 1) ne "*") && (substr($filter, -1, 1) ne "*")) - { - $filter = "*".$filter."*"; - } - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); - } -} - -sub iset_buffer_input -{ - my ($data, $buffer, $string) = ($_[0], $_[1], $_[2]); - - # string begins with space? - return weechat::WEECHAT_RC_OK if (substr($string, 0, 1 ) eq " "); - - if ($string eq "q") - { - weechat::buffer_close($buffer); - return weechat::WEECHAT_RC_OK; - } - $search_value = ""; - my @cmd_array = split(/ /,$string); - my $array_count = @cmd_array; - my $string2 = substr($string, 0, 1); - if ($string2 eq weechat::config_string($options_iset{"value_search_char"}) - or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) - { - $search_mode = 1; - $search_value = substr($string, 1); - iset_get_values($search_value); - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } - } - # show all diff values - elsif ($string eq "d") - { - $search_mode = 4; -# iset_title(); - iset_create_filter("*"); - iset_get_options("*"); - } - elsif ( $array_count >= 2 and $cmd_array[0] eq "d") - { - $search_mode = 5; - $search_value = substr($cmd_array[1], 0); # cut value_search_char - $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char - iset_create_filter($search_value); - iset_get_options($search_value); - - } - else - { - $search_mode = 2; - if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s" ) - { - if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $search_mode = 3; - $search_value = substr($cmd_array[1], 1); # cut value_search_char - $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char - } - } - if ( $search_mode == 3) - { - iset_create_filter($string); - iset_get_options($search_value); - } - else - { - iset_create_filter($string); - iset_get_options(""); - } - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_clear($buffer); - $current_line = 0; - iset_refresh(); - return weechat::WEECHAT_RC_OK; -} - -sub iset_buffer_close -{ - $iset_buffer = ""; - - return weechat::WEECHAT_RC_OK; -} - -sub iset_init -{ - $current_line = 0; - $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); - if ($iset_buffer eq "") - { - $iset_buffer = weechat::buffer_new($PRGNAME, "iset_buffer_input", "", "iset_buffer_close", ""); - } - else - { - my $new_filter = weechat::buffer_get_string($iset_buffer, "localvar_iset_filter"); - $search_mode = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_mode"); - $search_value = weechat::buffer_get_string($iset_buffer, "localvar_iset_search_value"); - $filter = $new_filter if ($new_filter ne ""); - } - if ($iset_buffer ne "") - { - weechat::buffer_set($iset_buffer, "type", "free"); - iset_title(); - weechat::buffer_set($iset_buffer, "key_bind_ctrl-L", "/iset **refresh"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-A", "/iset **up"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-B", "/iset **down"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-23~", "/iset **left"); - weechat::buffer_set($iset_buffer, "key_bind_meta2-24~" , "/iset **right"); - weechat::buffer_set($iset_buffer, "key_bind_meta- ", "/iset **toggle"); - weechat::buffer_set($iset_buffer, "key_bind_meta-+", "/iset **incr"); - weechat::buffer_set($iset_buffer, "key_bind_meta--", "/iset **decr"); - weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-r", "/iset **reset"); - weechat::buffer_set($iset_buffer, "key_bind_meta-imeta-u", "/iset **unset"); - weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-J", "/iset **set"); - weechat::buffer_set($iset_buffer, "key_bind_meta-ctrl-M", "/iset **set"); - weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-1~", "/iset **scroll_top"); - weechat::buffer_set($iset_buffer, "key_bind_meta-meta2-4~", "/iset **scroll_bottom"); - weechat::buffer_set($iset_buffer, "key_bind_meta-v", "/iset **toggle_help"); - weechat::buffer_set($iset_buffer, "key_bind_meta-p", "/iset **toggle_show_plugin_desc"); - weechat::buffer_set($iset_buffer, "localvar_set_iset_filter", $filter); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } -} - -sub iset_get_options -{ - my $var_value = $_[0]; - $var_value = "" if (not defined $var_value); - $var_value = lc($var_value); - $search_value = $var_value; - @iset_focus = (); - @options_names = (); - @options_parent_names = (); - @options_types = (); - @options_values = (); - @options_default_values = (); - @options_parent_values = (); - @options_is_null = (); - $option_max_length = 0; - my %options_internal = (); - my $i = 0; - my $key; - my $iset_struct; - my %iset_struct; - - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value) if ($search_mode == 3); - - my $infolist = weechat::infolist_get("option", "", $filter); - while (weechat::infolist_next($infolist)) - { - $key = sprintf("%08d", $i); - my $name = weechat::infolist_string($infolist, "full_name"); - my $parent_name = weechat::infolist_string($infolist, "parent_name"); - next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); - my $type = weechat::infolist_string($infolist, "type"); - my $value = weechat::infolist_string($infolist, "value"); - my $default_value = weechat::infolist_string($infolist, "default_value"); - my $parent_value; - if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $parent_value = weechat::infolist_string($infolist, "parent_value"); - } - my $is_null = weechat::infolist_integer($infolist, "value_is_null"); - - if ($search_mode == 3) - { - my $value = weechat::infolist_string($infolist, "value"); - if ( grep /\Q$var_value/,lc($value) ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - } - # search for diff? - elsif ( $search_mode == 4 or $search_mode == 5) - { - if ($value ne $default_value ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - } - else - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - $iset_struct{$key} = $options_internal{$name}; - push(@iset_focus, $iset_struct{$key}); - } - $i++; - } - weechat::infolist_free($infolist); - - foreach my $name (sort keys %options_internal) - { - push(@options_names, $name); - push(@options_parent_names, $options_internal{$name}{"parent_name"}); - push(@options_types, $options_internal{$name}{"type"}); - push(@options_values, $options_internal{$name}{"value"}); - push(@options_default_values, $options_internal{$name}{"default_value"}); - push(@options_parent_values, $options_internal{$name}{"parent_value"}); - push(@options_is_null, $options_internal{$name}{"is_null"}); - } -} - -sub iset_get_values -{ - my $var_value = $_[0]; - $var_value = lc($var_value); - if (substr($var_value,0,1) eq weechat::config_string($options_iset{"value_search_char"}) and $var_value ne weechat::config_string($options_iset{"value_search_char"})) - { - $var_value = substr($var_value,1,length($var_value)); - $search_mode = 0; - } - iset_search_values($var_value,$search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $var_value); - $search_value = $var_value; -} -sub iset_search_values -{ - my ($var_value,$search_mode) = ($_[0],$_[1]); - @options_names = (); - @options_parent_names = (); - @options_types = (); - @options_values = (); - @options_default_values = (); - @options_parent_values = (); - @options_is_null = (); - $option_max_length = 0; - my %options_internal = (); - my $i = 0; - my $infolist = weechat::infolist_get("option", "", "*"); - while (weechat::infolist_next($infolist)) - { - my $name = weechat::infolist_string($infolist, "full_name"); - my $parent_name = weechat::infolist_string($infolist, "parent_name"); - next if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 0 and index ($name, "plugins.desc.") != -1); - my $type = weechat::infolist_string($infolist, "type"); - my $is_null = weechat::infolist_integer($infolist, "value_is_null"); - my $value = weechat::infolist_string($infolist, "value"); - my $default_value = weechat::infolist_string($infolist, "default_value"); - my $parent_value; - if ($parent_name && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $parent_value = weechat::infolist_string($infolist, "parent_value"); - } - if ($search_mode) - { - if ( grep /\Q$var_value/,lc($value) ) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - } - } - else - { -# if ($value =~ /\Q$var_value/si) - if (lc($value) eq $var_value) - { - $options_internal{$name}{"parent_name"} = $parent_name; - $options_internal{$name}{"type"} = $type; - $options_internal{$name}{"value"} = $value; - $options_internal{$name}{"default_value"} = $default_value; - $options_internal{$name}{"parent_value"} = $parent_value; - $options_internal{$name}{"is_null"} = $is_null; - $option_max_length = length($name) if (length($name) > $option_max_length); - } - } - $i++; - } - weechat::infolist_free($infolist); - foreach my $name (sort keys %options_internal) - { - push(@options_names, $name); - push(@options_parent_names, $options_internal{$name}{"parent_name"}); - push(@options_types, $options_internal{$name}{"type"}); - push(@options_values, $options_internal{$name}{"value"}); - push(@options_default_values, $options_internal{$name}{"default_value"}); - push(@options_parent_values, $options_internal{$name}{"parent_value"}); - push(@options_is_null, $options_internal{$name}{"is_null"}); - } -} - -sub iset_refresh_line -{ - if ($iset_buffer ne "") - { - my $y = $_[0]; - if ($y <= $#options_names) - { - return if (! defined($options_types[$y])); - my $format = sprintf("%%s%%s%%s %%s %%-7s %%s %%s%%s%%s"); - my $padding; - if ($wee_version_number >= 0x00040200) - { - $padding = " " x ($option_max_length - weechat::strlen_screen($options_names[$y])); - } - else - { - $padding = " " x ($option_max_length - length($options_names[$y])); - } - my $around = ""; - $around = "\"" if ((!$options_is_null[$y]) && ($options_types[$y] eq "string")); - - my $color1 = weechat::color(weechat::config_color($options_iset{"color_option"})); - my $color2 = weechat::color(weechat::config_color($options_iset{"color_type"})); - my $color3 = ""; - my $color4 = ""; - if ($options_is_null[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef"})); - $color4 = weechat::color(weechat::config_color($options_iset{"color_value"})); - } - elsif ($options_values[$y] ne $options_default_values[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff"})); - } - else - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value"})); - } - if ($y == $current_line) - { - $color1 = weechat::color(weechat::config_color($options_iset{"color_option_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - $color2 = weechat::color(weechat::config_color($options_iset{"color_type_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - if ($options_is_null[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_undef_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - $color4 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - elsif ($options_values[$y] ne $options_default_values[$y]) - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_diff_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - else - { - $color3 = weechat::color(weechat::config_color($options_iset{"color_value_selected"}).",".weechat::config_color($options_iset{"color_bg_selected"})); - } - } - my $value = $options_values[$y]; - if (weechat::config_boolean($options_iset{"use_color"}) == 1 and $options_types[$y] eq "color") - { - $value = weechat::color($options_values[$y]) . $options_values[$y]; - } - if ($options_is_null[$y]) - { - $value = "null"; - if ($options_parent_names[$y]) - { - if (defined $options_parent_values[$y]) - { - my $around_parent = ""; - $around_parent = "\"" if ($options_types[$y] eq "string"); - $value .= $color1." -> ".$color4.$around_parent.$options_parent_values[$y].$around_parent; - } - else - { - $value .= $color1." -> ".$color3."null"; - } - } - } - my $strline = sprintf($format, - $color1, $options_names[$y], $padding, - $color2, $options_types[$y], - $color3, $around, $value, $around); - weechat::print_y($iset_buffer, $y, $strline); - } - } -} - -sub iset_refresh -{ - iset_title(); - if (($iset_buffer ne "") && ($#options_names >= 0)) - { - foreach my $y (0 .. $#options_names) - { - iset_refresh_line($y); - } - } - - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); -} - -sub iset_full_refresh -{ - $iset_buffer = weechat::buffer_search($LANG, $PRGNAME); - if ($iset_buffer ne "") - { - weechat::buffer_clear($iset_buffer) unless defined $_[0]; # iset_full_refresh(1) does a full refresh without clearing buffer - # search for "*" in $filter. - if ($filter =~ m/\*/ and $search_mode == 2) - { - iset_get_options(""); - } - else - { - if ($search_mode == 0) - { - $search_value = "=" . $search_value; - iset_get_values($search_value); - } - elsif ($search_mode == 1) - { - iset_get_values($search_value); - } - elsif ($search_mode == 3) - { - iset_create_filter($filter); - iset_get_options($search_value); - } - } - if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) - { - iset_set_current_line($current_line); - }else - { - $current_line = $#options_names if ($current_line > $#options_names); - } - iset_refresh(); - weechat::command($iset_buffer, "/window refresh"); - } -} - -sub iset_set_current_line -{ - my $new_current_line = $_[0]; - if ($new_current_line >= 0) - { - my $old_current_line = $current_line; - $current_line = $new_current_line; - $current_line = $#options_names if ($current_line > $#options_names); - if ($old_current_line != $current_line) - { - iset_refresh_line($old_current_line); - iset_refresh_line($current_line); - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - } - } -} - -sub iset_signal_window_scrolled_cb -{ - my ($data, $signal, $signal_data) = ($_[0], $_[1], $_[2]); - if ($iset_buffer ne "") - { - my $infolist = weechat::infolist_get("window", $signal_data, ""); - if (weechat::infolist_next($infolist)) - { - if (weechat::infolist_pointer($infolist, "buffer") eq $iset_buffer) - { - my $old_current_line = $current_line; - my $new_current_line = $current_line; - my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); - my $chat_height = weechat::infolist_integer($infolist, "chat_height"); - $new_current_line += $chat_height if ($new_current_line < $start_line_y); - $new_current_line -= $chat_height if ($new_current_line >= $start_line_y + $chat_height); - $new_current_line = $start_line_y if ($new_current_line < $start_line_y); - $new_current_line = $start_line_y + $chat_height - 1 if ($new_current_line >= $start_line_y + $chat_height); - iset_set_current_line($new_current_line); - } - } - weechat::infolist_free($infolist); - } - - return weechat::WEECHAT_RC_OK; -} - -sub iset_get_window_number -{ - if ($iset_buffer ne "") - { - my $window = weechat::window_search_with_buffer($iset_buffer); - return "-window ".weechat::window_get_integer ($window, "number")." " if ($window ne ""); - } - return ""; -} - -sub iset_check_line_outside_window -{ - if ($iset_buffer ne "") - { - undef my $infolist; - if ($wee_version_number >= 0x00030500) - { - my $window = weechat::window_search_with_buffer($iset_buffer); - $infolist = weechat::infolist_get("window", $window, "") if $window; - } - else - { - $infolist = weechat::infolist_get("window", "", "current"); - } - if ($infolist) - { - if (weechat::infolist_next($infolist)) - { - my $start_line_y = weechat::infolist_integer($infolist, "start_line_y"); - my $chat_height = weechat::infolist_integer($infolist, "chat_height"); - my $window_number = ""; - if ($wee_version_number >= 0x00030500) - { - $window_number = "-window ".weechat::infolist_integer($infolist, "number")." "; - } - if ($start_line_y > $current_line) - { - weechat::command($iset_buffer, "/window scroll ".$window_number."-".($start_line_y - $current_line)); - } - else - { - if ($start_line_y <= $current_line - $chat_height) - { - weechat::command($iset_buffer, "/window scroll ".$window_number."+".($current_line - $start_line_y - $chat_height + 1)); - - } - } - } - weechat::infolist_free($infolist); - } - } -} - -sub iset_get_option_name_index -{ - my $option_name = $_[0]; - my $index = 0; - while ($index <= $#options_names) - { - return -1 if ($options_names[$index] gt $option_name); - return $index if ($options_names[$index] eq $option_name); - $index++; - } - return -1; -} - -sub iset_refresh_option -{ - my $option_name = $_[0]; - my $index = $_[1]; - my $infolist = weechat::infolist_get("option", "", $option_name); - if ($infolist) - { - weechat::infolist_next($infolist); - if (weechat::infolist_fields($infolist)) - { - $options_parent_names[$index] = weechat::infolist_string($infolist, "parent_name"); - $options_types[$index] = weechat::infolist_string($infolist, "type"); - $options_values[$index] = weechat::infolist_string($infolist, "value"); - $options_default_values[$index] = weechat::infolist_string($infolist, "default_value"); - $options_is_null[$index] = weechat::infolist_integer($infolist, "value_is_null"); - $options_parent_values[$index] = undef; - if ($options_parent_names[$index] - && (($wee_version_number < 0x00040300) || (weechat::infolist_search_var($infolist, "parent_value")))) - { - $options_parent_values[$index] = weechat::infolist_string($infolist, "parent_value"); - } - iset_refresh_line($index); - iset_title() if ($option_name eq "iset.look.show_current_line"); - } - else - { - iset_full_refresh(1); # if not found, refresh fully without clearing buffer - weechat::print_y($iset_buffer, $#options_names + 1, ""); - } - weechat::infolist_free($infolist); - } -} - -sub iset_config_cb -{ - my ($data, $option_name, $value) = ($_[0], $_[1], $_[2]); - - if ($iset_buffer ne "") - { - return weechat::WEECHAT_RC_OK if (weechat::info_get("weechat_upgrading", "") eq "1"); - - my $index = iset_get_option_name_index($option_name); - if ($index >= 0) - { - # refresh info about changed option - iset_refresh_option($option_name, $index); - # refresh any other option having this changed option as parent - foreach my $i (0 .. $#options_names) - { - if ($options_parent_names[$i] eq $option_name) - { - iset_refresh_option($options_names[$i], $i); - } - } - } - else - { - iset_full_refresh() if ($option_name ne "weechat.bar.isetbar.hidden"); - } - } - - return weechat::WEECHAT_RC_OK; -} - -sub iset_set_option -{ - my ($option, $value) = ($_[0],$_[1]); - if (defined $option and defined $value) - { - $option = weechat::config_get($option); - weechat::config_option_set($option, $value, 1) if ($option ne ""); - } -} - -sub iset_reset_option -{ - my $option = $_[0]; - if (defined $option) - { - $option = weechat::config_get($option); - weechat::config_option_reset($option, 1) if ($option ne ""); - } -} - -sub iset_unset_option -{ - my $option = $_[0]; - if (defined $option) - { - $option = weechat::config_get($option); - weechat::config_option_unset($option) if ($option ne ""); - } -} - - -sub iset_cmd_cb -{ - my ($data, $buffer, $args) = ($_[0], $_[1], $_[2]); - my $filter_set = 0; -# $search_value = ""; - if (($args ne "") && (substr($args, 0, 2) ne "**")) - { - my @cmd_array = split(/ /,$args); - my $array_count = @cmd_array; - if (substr($args, 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or (defined $cmd_array[0] and $cmd_array[0] eq weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})) ) - { - $search_mode = 1; - my $search_value = substr($args, 1); # cut value_search_char - if ($iset_buffer ne "") - { - weechat::buffer_clear($iset_buffer); - weechat::command($iset_buffer, "/window refresh"); - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - iset_init(); - iset_get_values($search_value); - iset_refresh(); - weechat::buffer_set($iset_buffer, "display", "1"); -# $filter = $var_value; - return weechat::WEECHAT_RC_OK; - } - else - { - # f/s option =value - # option =value - $search_mode = 2; # grep on option - if ( $array_count >= 2 and $cmd_array[0] ne "f" or $cmd_array[0] ne "s") - { - if ( defined $cmd_array[1] and substr($cmd_array[1], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) - or defined $cmd_array[2] and substr($cmd_array[2], 0, 1) eq weechat::config_string($options_iset{"value_search_char"}) ) - { - $search_mode = 3; # grep on option and value - $search_value = substr($cmd_array[1], 1); # cut value_search_char - $search_value = substr($cmd_array[2], 1) if ( $array_count > 2); # cut value_search_char - } - } - - # show all diff values - if ( $args eq "d") - { - $search_mode = 4; - $search_value = "*"; - $args = $search_value; - } - if ( $array_count >= 2 and $cmd_array[0] eq "d") - { - $search_mode = 5; - $search_value = substr($cmd_array[1], 0); # cut value_search_char - $search_value = substr($cmd_array[2], 0) if ( $array_count > 2); # cut value_search_char - $args = $search_value; - } - - iset_create_filter($args); - $filter_set = 1; - my $ptrbuf = weechat::buffer_search($LANG, $PRGNAME); - - if ($ptrbuf eq "") - { - iset_init(); - iset_get_options($search_value); - iset_full_refresh(); - weechat::buffer_set(weechat::buffer_search($LANG, $PRGNAME), "display", "1"); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - return weechat::WEECHAT_RC_OK; - } - else - { - iset_get_options($search_value); - iset_full_refresh(); - weechat::buffer_set($ptrbuf, "display", "1"); - } - } - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_mode", $search_mode); - weechat::buffer_set($iset_buffer, "localvar_set_iset_search_value", $search_value); - } - if ($iset_buffer eq "") - { - iset_init(); - iset_get_options(""); - iset_refresh(); - } - else - { -# iset_get_options($search_value); - iset_full_refresh() if ($filter_set); - } - - if ($args eq "") - { - weechat::buffer_set($iset_buffer, "display", "1"); - } - else - { - if ($args eq "**refresh") - { - iset_full_refresh(); - } - if ($args eq "**up") - { - if ($current_line > 0) - { - $current_line--; - iset_refresh_line($current_line + 1); - iset_refresh_line($current_line); - iset_check_line_outside_window(); - } - } - if ($args eq "**down") - { - if ($current_line < $#options_names) - { - $current_line++; - iset_refresh_line($current_line - 1); - iset_refresh_line($current_line); - iset_check_line_outside_window(); - } - } - if ($args eq "**left" && $wee_version_number >= 0x00030600) - { - weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number()."-".weechat::config_integer($options_iset{"scroll_horiz"})."%"); - } - if ($args eq "**right" && $wee_version_number >= 0x00030600) - { - weechat::command($iset_buffer, "/window scroll_horiz ".iset_get_window_number().weechat::config_integer($options_iset{"scroll_horiz"})."%"); - } - if ($args eq "**scroll_top") - { - my $old_current_line = $current_line; - $current_line = 0; - iset_refresh_line ($old_current_line); - iset_refresh_line ($current_line); - iset_title(); - weechat::command($iset_buffer, "/window scroll_top ".iset_get_window_number()); - } - if ($args eq "**scroll_bottom") - { - my $old_current_line = $current_line; - $current_line = $#options_names; - iset_refresh_line ($old_current_line); - iset_refresh_line ($current_line); - iset_title(); - weechat::command($iset_buffer, "/window scroll_bottom ".iset_get_window_number()); - } - if ($args eq "**toggle") - { - if ($options_types[$current_line] eq "boolean") - { - iset_set_option($options_names[$current_line], "toggle"); - } - } - if ($args eq "**incr") - { - if (($options_types[$current_line] eq "integer") - || ($options_types[$current_line] eq "color")) - { - iset_set_option($options_names[$current_line], "++1"); - } - } - if ($args eq "**decr") - { - if (($options_types[$current_line] eq "integer") - || ($options_types[$current_line] eq "color")) - { - iset_set_option($options_names[$current_line], "--1"); - } - } - if ($args eq "**reset") - { - iset_reset_option($options_names[$current_line]); - } - if ($args eq "**unset") - { - iset_unset_option($options_names[$current_line]); - } - if ($args eq "**toggle_help") - { - if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) - { - weechat::config_option_set($options_iset{"show_help_bar"},0,1); - iset_show_bar(0); - } - else - { - weechat::config_option_set($options_iset{"show_help_bar"},1,1); - iset_show_bar(1); - } - } - if ($args eq "**toggle_show_plugin_desc") - { - if (weechat::config_boolean($options_iset{"show_plugin_description"}) == 1) - { - weechat::config_option_set($options_iset{"show_plugin_description"},0,1); - iset_full_refresh(); - iset_check_line_outside_window(); - iset_title(); - } - else - { - weechat::config_option_set($options_iset{"show_plugin_description"},1,1); - iset_full_refresh(); - iset_check_line_outside_window(); - iset_title(); - } - } - if ($args eq "**set") - { - my $quote = ""; - my $value = $options_values[$current_line]; - if ($options_is_null[$current_line]) - { - $value = ""; - } - else - { - $quote = "\"" if ($options_types[$current_line] eq "string"); - } - $value = " ".$quote.$value.$quote if ($value ne "" or $quote ne ""); - - my $set_command = "/set"; - my $start_index = 5; - if (weechat::config_boolean($options_iset{"use_mute"}) == 1) - { - $set_command = "/mute ".$set_command; - $start_index += 11; - } - $set_command = $set_command." ".$options_names[$current_line].$value; - my $pos_space = index($set_command, " ", $start_index); - if ($pos_space < 0) - { - $pos_space = 9999; - } - else - { - $pos_space = $pos_space + 1; - $pos_space = $pos_space + 1 if ($quote ne ""); - } - weechat::buffer_set($iset_buffer, "input", $set_command); - weechat::buffer_set($iset_buffer, "input_pos", "".$pos_space); - } - } - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - return weechat::WEECHAT_RC_OK; -} - -sub iset_get_help -{ - my ($redraw) = ($_[0]); - - return '' if (weechat::config_boolean($options_iset{"show_help_bar"}) == 0); - - if (not defined $options_names[$current_line]) - { - return "No option selected. Set a new filter using command line (use '*' to see all options)"; - } - if ($options_name_copy eq $options_names[$current_line] and not defined $redraw) - { - return $description; - } - $options_name_copy = $options_names[$current_line]; - my $optionlist =""; - $optionlist = weechat::infolist_get("option", "", $options_names[$current_line]); - weechat::infolist_next($optionlist); - my $full_name = weechat::infolist_string($optionlist,"full_name"); - my $option_desc = ""; - my $option_default_value = ""; - my $option_range = ""; - my $possible_values = ""; - my $re = qq(\Q$full_name); - if (grep (/^$re$/,$options_names[$current_line])) - { - $option_desc = weechat::infolist_string($optionlist, "description_nls"); - $option_desc = weechat::infolist_string($optionlist, "description") if ($option_desc eq ""); - $option_desc = "No help found" if ($option_desc eq ""); - $option_default_value = weechat::infolist_string($optionlist, "default_value"); - $possible_values = weechat::infolist_string($optionlist, "string_values") if (weechat::infolist_string($optionlist, "string_values") ne ""); - if ((weechat::infolist_string($optionlist, "type") eq "integer") && ($possible_values eq "")) - { - $option_range = weechat::infolist_integer($optionlist, "min") - ." .. ".weechat::infolist_integer($optionlist, "max"); - } - } - weechat::infolist_free($optionlist); - iset_title(); - - $description = weechat::color(weechat::config_color($options_iset{"color_help_option_name"})).$options_names[$current_line] - .weechat::color("bar_fg").": " - .weechat::color(weechat::config_color($options_iset{"color_help_text"})).$option_desc; - - # show additional infos like default value and possible values - - if (weechat::config_boolean($options_iset{"show_help_extra_info"}) == 1) - { - $description .= - weechat::color("bar_delim")." [" - .weechat::color("bar_fg")."default: " - .weechat::color("bar_delim")."\"" - .weechat::color(weechat::config_color($options_iset{"color_help_default_value"})).$option_default_value - .weechat::color("bar_delim")."\""; - if ($option_range ne "") - { - $description .= weechat::color("bar_fg").", values: ".$option_range; - } - if ($possible_values ne "") - { - $possible_values =~ s/\|/", "/g; # replace '|' to '", "' - $description .= weechat::color("bar_fg").", values: ". "\"" . $possible_values . "\""; - - } - $description .= weechat::color("bar_delim")."]"; - } - return $description; -} - -sub iset_check_condition_isetbar_cb -{ - my ($data, $modifier, $modifier_data, $string) = ($_[0], $_[1], $_[2], $_[3]); - my $buffer = weechat::window_get_pointer($modifier_data, "buffer"); - if ($buffer ne "") - { - if ((weechat::buffer_get_string($buffer, "plugin") eq $LANG) - && (weechat::buffer_get_string($buffer, "name") eq $PRGNAME)) - { - return "1"; - } - } - return "0"; -} - -sub iset_show_bar -{ - my $show = $_[0]; - my $barhidden = weechat::config_get("weechat.bar.isetbar.hidden"); - if ($barhidden) - { - if ($show) - { - if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1) - { - if (weechat::config_boolean($barhidden)) - { - weechat::config_option_set($barhidden, 0, 1); - } - } - } - else - { - if (!weechat::config_boolean($barhidden)) - { - weechat::config_option_set($barhidden, 1, 1); - } - } - } -} - -sub iset_signal_buffer_switch_cb -{ - my $buffer_pointer = $_[2]; - my $show_bar = 0; - $show_bar = 1 if (weechat::buffer_get_integer($iset_buffer, "num_displayed") > 0); - iset_show_bar($show_bar); - iset_check_line_outside_window() if ($buffer_pointer eq $iset_buffer); - return weechat::WEECHAT_RC_OK; -} - -sub iset_item_cb -{ - return iset_get_help(); -} - -sub iset_upgrade_ended -{ - iset_full_refresh(); -} - -sub iset_end -{ - # when script is unloaded, we hide bar - iset_show_bar(0); -} - -# -------------------------------[ mouse support ]------------------------------------- - -sub hook_focus_iset_cb -{ - my %info = %{$_[1]}; - my $bar_item_line = int($info{"_bar_item_line"}); - undef my $hash; - if (($info{"_buffer_name"} eq $PRGNAME) && $info{"_buffer_plugin"} eq $LANG && ($bar_item_line >= 0) && ($bar_item_line <= $#iset_focus)) - { - $hash = $iset_focus[$bar_item_line]; - } - else - { - $hash = {}; - my $hash_focus = $iset_focus[0]; - foreach my $key (keys %$hash_focus) - { - $hash->{$key} = "?"; - } - } - return $hash; -} - -# _chat_line_y contains selected line -sub iset_hsignal_mouse_cb -{ - my ($data, $signal, %hash) = ($_[0], $_[1], %{$_[2]}); - - return weechat::WEECHAT_RC_OK unless (@options_types); - - if ($hash{"_buffer_name"} eq $PRGNAME && ($hash{"_buffer_plugin"} eq $LANG)) - { - if ($hash{"_key"} eq "button1") - { - iset_set_current_line($hash{"_chat_line_y"}); - } - elsif ($hash{"_key"} eq "button2") - { - if ($options_types[$hash{"_chat_line_y"}] eq "boolean") - { - iset_set_option($options_names[$hash{"_chat_line_y"}], "toggle"); - iset_set_current_line($hash{"_chat_line_y"}); - } - elsif ($options_types[$hash{"_chat_line_y"}] eq "string") - { - iset_set_current_line($hash{"_chat_line_y"}); - weechat::command("", "/$PRGNAME **set"); - } - } - elsif ($hash{"_key"} eq "button2-gesture-left" or $hash{"_key"} eq "button2-gesture-left-long") - { - if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) - { - iset_set_current_line($hash{"_chat_line_y"}); - my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); - weechat::command("", "/repeat $distance /$PRGNAME **decr"); - } - } - elsif ($hash{"_key"} eq "button2-gesture-right" or $hash{"_key"} eq "button2-gesture-right-long") - { - if ($options_types[$hash{"_chat_line_y"}] eq "integer" or ($options_types[$hash{"_chat_line_y"}] eq "color")) - { - iset_set_current_line($hash{"_chat_line_y"}); - my $distance = distance($hash{"_chat_line_x"},$hash{"_chat_line_x2"}); - weechat::command("", "/repeat $distance /$PRGNAME **incr"); - } - } - } - window_switch(); -} - -sub window_switch -{ - my $current_window = weechat::current_window(); - my $dest_window = weechat::window_search_with_buffer(weechat::buffer_search("perl","iset")); - return 0 if ($dest_window eq "" or $current_window eq $dest_window); - - my $infolist = weechat::infolist_get("window", $dest_window, ""); - weechat::infolist_next($infolist); - my $number = weechat::infolist_integer($infolist, "number"); - weechat::infolist_free($infolist); - weechat::command("","/window " . $number); -} - -sub distance -{ - my ($x1,$x2) = ($_[0], $_[1]); - my $distance; - $distance = $x1 - $x2; - $distance = abs($distance); - if ($distance > 0) - { - use integer; - $distance = $distance / 3; - $distance = 1 if ($distance == 0); - } - elsif ($distance == 0) - { - $distance = 1; - } - return $distance; -} - -# -----------------------------------[ config ]--------------------------------------- - -sub iset_config_init -{ - $iset_config_file = weechat::config_new($ISET_CONFIG_FILE_NAME,"iset_config_reload_cb",""); - return if ($iset_config_file eq ""); - - # section "color" - my $section_color = weechat::config_new_section($iset_config_file,"color", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_color eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"color_option"} = weechat::config_new_option( - $iset_config_file, $section_color, - "option", "color", "Color for option name in iset buffer", "", 0, 0, - "default", "default", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_option_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "option_selected", "color", "Color for selected option name in iset buffer", "", 0, 0, - "white", "white", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_type"} = weechat::config_new_option( - $iset_config_file, $section_color, - "type", "color", "Color for option type (integer, boolean, string)", "", 0, 0, - "brown", "brown", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_type_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "type_selected", "color", "Color for selected option type (integer, boolean, string)", "", 0, 0, - "yellow", "yellow", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value", "color", "Color for option value", "", 0, 0, - "cyan", "cyan", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_selected", "color", "Color for selected option value", "", 0, 0, - "lightcyan", "lightcyan", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_diff"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_diff", "color", "Color for option value different from default", "", 0, 0, - "magenta", "magenta", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_diff_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_diff_selected", "color", "Color for selected option value different from default", "", 0, 0, - "lightmagenta", "lightmagenta", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_undef"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_undef", "color", "Color for option value undef", "", 0, 0, - "green", "green", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_value_undef_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "value_undef_selected", "color", "Color for selected option value undef", "", 0, 0, - "lightgreen", "lightgreen", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_bg_selected"} = weechat::config_new_option( - $iset_config_file, $section_color, - "bg_selected", "color", "Background color for current selected option", "", 0, 0, - "red", "red", 0, "", "", "full_refresh_cb", "", "", ""); - $options_iset{"color_help_option_name"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_option_name", "color", "Color for option name in help-bar", "", 0, 0, - "white", "white", 0, "", "", "bar_refresh", "", "", ""); - $options_iset{"color_help_text"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_text", "color", "Color for option description in help-bar", "", 0, 0, - "default", "default", 0, "", "", "bar_refresh", "", "", ""); - $options_iset{"color_help_default_value"} = weechat::config_new_option( - $iset_config_file, $section_color, - "help_default_value", "color", "Color for default option value in help-bar", "", 0, 0, - "green", "green", 0, "", "", "bar_refresh", "", "", ""); - - # section "help" - my $section_help = weechat::config_new_section($iset_config_file,"help", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_help eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"show_help_bar"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_help_bar", "boolean", "Show help bar", "", 0, 0, - "on", "on", 0, "", "", "toggle_help_cb", "", "", ""); - $options_iset{"show_help_extra_info"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_help_extra_info", "boolean", "Show additional information in help bar (default value, max./min. value) ", "", 0, 0, - "on", "on", 0, "", "", "", "", "", ""); - $options_iset{"show_plugin_description"} = weechat::config_new_option( - $iset_config_file, $section_help, - "show_plugin_description", "boolean", "Show plugin description in iset buffer", "", 0, 0, - "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); - - # section "look" - my $section_look = weechat::config_new_section($iset_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", ""); - if ($section_look eq "") - { - weechat::config_free($iset_config_file); - return; - } - $options_iset{"value_search_char"} = weechat::config_new_option( - $iset_config_file, $section_look, - "value_search_char", "string", "Trigger char to tell iset to search for value instead of option (for example: =red)", "", 0, 0, - "=", "=", 0, "", "", "", "", "", ""); - $options_iset{"scroll_horiz"} = weechat::config_new_option( - $iset_config_file, $section_look, - "scroll_horiz", "integer", "scroll content of iset buffer n%", "", 1, 100, - "10", "10", 0, "", "", "", "", "", ""); - $options_iset{"show_current_line"} = weechat::config_new_option( - $iset_config_file, $section_look, - "show_current_line", "boolean", "show current line in title bar.", "", 0, 0, - "on", "on", 0, "", "", "", "", "", ""); - $options_iset{"use_mute"} = weechat::config_new_option( - $iset_config_file, $section_look, - "use_mute", "boolean", "/mute command will be used in input bar", "", 0, 0, - "off", "off", 0, "", "", "", "", "", ""); - $options_iset{"use_color"} = weechat::config_new_option( - $iset_config_file, $section_look, - "use_color", "boolean", "display the color value in the corresponding color", "", 0, 0, - "off", "off", 0, "", "", "full_refresh_cb", "", "", ""); -} - -sub iset_config_reload_cb -{ - my ($data,$config_file) = ($_[0], $_[1]); - return weechat::config_reload($config_file) -} - -sub iset_config_read -{ - return weechat::config_read($iset_config_file) if ($iset_config_file ne ""); -} - -sub iset_config_write -{ - return weechat::config_write($iset_config_file) if ($iset_config_file ne ""); -} - -sub full_refresh_cb -{ - iset_full_refresh(); - return weechat::WEECHAT_RC_OK; -} - -sub bar_refresh -{ - iset_get_help(1); - weechat::bar_item_update("isetbar_help") if (weechat::config_boolean($options_iset{"show_help_bar"}) == 1); - return weechat::WEECHAT_RC_OK; -} - -sub toggle_help_cb -{ - my $value = weechat::config_boolean($options_iset{"show_help_bar"}); - iset_show_bar($value); - return weechat::WEECHAT_RC_OK; -} - -# -----------------------------------[ main ]----------------------------------------- - -weechat::register($PRGNAME, $AUTHOR, $VERSION, $LICENSE, - $DESCR, "iset_end", ""); - -$wee_version_number = weechat::info_get("version_number", "") || 0; - -iset_config_init(); -iset_config_read(); - -weechat::hook_command($PRGNAME, "Interactive set", "d || f || s
|| [=][=]", - "d : show only changed options\n". - "f file : show options for a file\n". - "s section: show options for a section\n". - "text : show options with 'text' in name\n". - weechat::config_string($options_iset{"value_search_char"})."text : show options with 'text' in value\n". - weechat::config_string($options_iset{"value_search_char"}).weechat::config_string($options_iset{"value_search_char"})."text : show options with exact 'text' in value\n\n". - "Keys for iset buffer:\n". - "f11,f12 : move iset content left/right\n". - "up,down : move one option up/down\n". - "pgup,pdwn : move one page up/down\n". - "home,end : move to first/last option\n". - "ctrl+'L' : refresh options and screen\n". - "alt+space : toggle boolean on/off\n". - "alt+'+' : increase value (for integer or color)\n". - "alt+'-' : decrease value (for integer or color)\n". - "alt+'i',alt+'r': reset value of option\n". - "alt+'i',alt+'u': unset option\n". - "alt+enter : set new value for option (edit it with command line)\n". - "text,enter : set a new filter using command line (use '*' to see all options)\n". - "alt+'v' : toggle help bar on/off\n". - "alt+'p' : toggle option \"show_plugin_description\" on/off\n". - "q : as input in iset buffer to close it\n". - "\n". - "Mouse actions:\n". - "wheel up/down : move cursor up/down\n". - "left button : select an option from list\n". - "right button : toggle boolean (on/off) or set a new value for option (edit it with command line)\n". - "right button + drag left/right: increase/decrease value (for integer or color)\n". - "\n". - "Examples:\n". - " show changed options in 'aspell' plugin\n". - " /iset d aspell\n". - " show options for file 'irc'\n". - " /iset f irc\n". - " show options for section 'look'\n". - " /iset s look\n". - " show all options with text 'nicklist' in name\n". - " /iset nicklist\n". - " show all values which contain 'red'. ('" . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". - " /iset ". weechat::config_string($options_iset{"value_search_char"}) ."red\n". - " show all values which hit 'off'. ('" . weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) . "' is a trigger char).\n". - " /iset ". weechat::config_string($options_iset{"value_search_char"}) . weechat::config_string($options_iset{"value_search_char"}) ."off\n". - " show options for file 'weechat' which contains value 'off'\n". - " /iset f weechat ".weechat::config_string($options_iset{"value_search_char"})."off\n". - "", - "", "iset_cmd_cb", ""); -weechat::hook_signal("upgrade_ended", "iset_upgrade_ended", ""); -weechat::hook_signal("window_scrolled", "iset_signal_window_scrolled_cb", ""); -weechat::hook_signal("buffer_switch", "iset_signal_buffer_switch_cb",""); -weechat::bar_item_new("isetbar_help", "iset_item_cb", ""); -if ($wee_version_number >= 0x02090000) -{ - weechat::bar_new("isetbar", "on", "0", "window", "", "top", "horizontal", - "vertical", "3", "3", "default", "cyan", "default", "default", "1", - "isetbar_help"); -} -else -{ - weechat::bar_new("isetbar", "on", "0", "window", "", "top", "horizontal", - "vertical", "3", "3", "default", "cyan", "default", "1", - "isetbar_help"); -} -weechat::hook_modifier("bar_condition_isetbar", "iset_check_condition_isetbar_cb", ""); -weechat::hook_config("*", "iset_config_cb", ""); -$iset_buffer = weechat::buffer_search($LANG, $PRGNAME); -iset_init() if ($iset_buffer ne ""); - -if ($wee_version_number >= 0x00030600) -{ - weechat::hook_focus("chat", "hook_focus_iset_cb", ""); - weechat::hook_hsignal($PRGNAME."_mouse", "iset_hsignal_mouse_cb", ""); - weechat::key_bind("mouse", \%mouse_keys); -} diff --git a/.config/weechat/plugins.conf b/.config/weechat/plugins.conf index dac65f79..0685c804 100644 --- a/.config/weechat/plugins.conf +++ b/.config/weechat/plugins.conf @@ -4,158 +4,11 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # [var] -perl.atcomplete.enabled = "on" -perl.awaylog.command = "" -perl.awaylog.name_color = "default" -perl.awaylog.notify = "off" -perl.awaylog.on_away_only = "off" -perl.awaylog.plugin_color = "default" -perl.highmon.alignment = "channel" -perl.highmon.away_only = "off" -perl.highmon.color_buf = "on" -perl.highmon.first_run = "true" -perl.highmon.hotlist_show = "off" -perl.highmon.logging = "off" -perl.highmon.merge_private = "off" -perl.highmon.nick_prefix = "<" -perl.highmon.nick_suffix = ">" -perl.highmon.output = "buffer" -perl.highmon.short_names = "off" -perl.multiline.char = "↩" -perl.multiline.hide_magic_nl = "on" -perl.multiline.ipl = "on" -perl.multiline.lead_linebreak = "on" -perl.multiline.magic = "‼" -perl.multiline.magic_enter_time = "1000" -perl.multiline.magic_paste_only = "off" -perl.multiline.modify_keys = "on" -perl.multiline.paste_lock = "1" -perl.multiline.send_empty = "on" -perl.multiline.tab = "──▶▏" -perl.multiline.weechat_paste_fix = "on" -perl.notify_send.command = "notify-send $type: $name &>/dev/null" -perl.notify_send.ignore_nicks = "" -perl.notify_send.wait_highlight = "60" -perl.notify_send.wait_pm = "180" -python.anotify.icon = "/usr/share/pixmaps/weechat.xpm" -python.anotify.show_channel_topic = "on" -python.anotify.show_dcc = "on" -python.anotify.show_highlighted_message = "on" -python.anotify.show_invite_message = "on" -python.anotify.show_notice_message = "off" -python.anotify.show_private_action_message = "on" -python.anotify.show_private_message = "on" -python.anotify.show_public_action_message = "off" -python.anotify.show_public_message = "off" -python.anotify.show_server = "on" -python.anotify.show_upgrade_ended = "on" -python.anotify.sticky = "off" -python.anotify.sticky_away = "on" -python.chanotify.filters = "*:*" -python.chanotify.status = "on" -python.go.auto_jump = "off" -python.go.buffer_number = "on" -python.go.color_name = "black,cyan" -python.go.color_name_highlight = "red,cyan" -python.go.color_name_highlight_selected = "red,brown" -python.go.color_name_selected = "black,brown" -python.go.color_number = "yellow,magenta" -python.go.color_number_selected = "yellow,red" -python.go.fuzzy_search = "off" -python.go.message = "Go to: " -python.go.short_name = "off" -python.go.sort = "number,beginning" -python.go.use_core_instead_weechat = "off" -python.styurl.buffer_type = "formatted" -python.styurl.format = "${color:*_32}" -python.styurl.ignored_buffers = "core.weechat,python.grep" -python.styurl.ignored_tags = "irc_quit,irc_join" -python.styurl.regex = "((?:https?|ftp)://[^\s/$.?#].\S*)" -python.vimode.copy_clipboard_cmd = "xclip -selection c" -python.vimode.imap_esc = "" -python.vimode.imap_esc_timeout = "1000" -python.vimode.line_number_prefix = "" -python.vimode.line_number_suffix = ": " -python.vimode.mode_indicator_cmd_color = "13" -python.vimode.mode_indicator_cmd_color_bg = "default" -python.vimode.mode_indicator_cmd_color_fg = "13" -python.vimode.mode_indicator_insert_color = "2" -python.vimode.mode_indicator_insert_color_bg = "default" -python.vimode.mode_indicator_insert_color_fg = "2" -python.vimode.mode_indicator_normal_color = "4" -python.vimode.mode_indicator_normal_color_bg = "default" -python.vimode.mode_indicator_normal_color_fg = "19" -python.vimode.mode_indicator_prefix = "--" -python.vimode.mode_indicator_replace_color = "white" -python.vimode.mode_indicator_replace_color_bg = "red" -python.vimode.mode_indicator_search_color = "1" -python.vimode.mode_indicator_search_color_bg = "default" -python.vimode.mode_indicator_suffix = "--" -python.vimode.no_warn = "off" -python.vimode.paste_clipboard_cmd = "xclip -selection c -o" -python.vimode.search_vim = "on" -python.vimode.user_mappings = "" [desc] -perl.atcomplete.enabled = "enable completion of nicks starting with @ (default: "on")" -perl.multiline.char = "character(s) which should be displayed to indicate end of line" -perl.multiline.hide_magic_nl = "whether the new line inserted by magic enter key will be hidden" -perl.multiline.ipl = "this setting controls override of ctrl-M (enter key) by script. Turn it off if you don't want multiline.pl to set and re-set the key binding." -perl.multiline.lead_linebreak = "if turned on, multi-line messages always start on a new line" -perl.multiline.magic = "indicator displayed when message will be sent soon" -perl.multiline.magic_enter_time = "delay after pressing enter before sending automatically (in ms), or 0 to disable" -perl.multiline.magic_paste_only = "only use multi-line messages for multi-line pastes (multi-line on enter is disabled by this)" -perl.multiline.modify_keys = "if turned on, cursor keys are modified so that they respect line boundaries instead of treating the whole multi-line message as a single line" -perl.multiline.paste_lock = "time-out to detect pastes (disable the weechat built-in paste detection if you want to use this)" -perl.multiline.send_empty = "set to on to automatically disregard enter key on empty line" -perl.multiline.tab = "character(s) which should be displayed instead of Tab key character" -perl.multiline.weechat_paste_fix = "disable ctrl-J binding when paste is detected to stop silly weechat sending out pastes without allowing to edit them" -perl.notify_send.command = "systemcommand to be executed ($type, $name, and $messagewill be interpreted as values) (default: "notify-send $type: $name &>/dev/null")" -perl.notify_send.ignore_nicks = "comma-separated list of nicks to ignore (default: "")" -perl.notify_send.wait_highlight = "necessary time delay between highlights(seconds) for command to be executed (default: "60")" -perl.notify_send.wait_pm = "necessary time delay between private messages(seconds) for command to be executed (default: "180")" -python.go.auto_jump = "automatically jump to buffer when it is uniquely selected (default: "off")" -python.go.buffer_number = "display buffer number (default: "on")" -python.go.color_name = "color for buffer name (not selected) (default: "black,cyan")" -python.go.color_name_highlight = "color for highlight in buffer name (not selected) (default: "red,cyan")" -python.go.color_name_highlight_selected = "color for highlight in a selected buffer name (default: "red,brown")" -python.go.color_name_selected = "color for a selected buffer name (default: "black,brown")" -python.go.color_number = "color for buffer number (not selected) (default: "yellow,magenta")" -python.go.color_number_selected = "color for selected buffer number (default: "yellow,red")" -python.go.fuzzy_search = "search buffer matches using approximation (default: "off")" -python.go.message = "message to display before list of buffers (default: "Go to: ")" -python.go.short_name = "display and search in short names instead of buffer name (default: "off")" -python.go.sort = "comma-separated list of keys to sort buffers (the order is important, sorts are performed in the given order): name = sort by name (or short name), (default: "number,beginning")" -python.go.use_core_instead_weechat = "use name "core" instead of "weechat" for core buffer (default: "off")" -python.styurl.buffer_type = "the type of buffers to run on (options are "formatted", "free", or "*" for both) (default: "formatted")" -python.styurl.format = "the style that should be applied to the URL(evaluated, see /help eval) (default: "${color:*_32}")" -python.styurl.ignored_buffers = "comma-separated list of buffers to ignore URLs in (full name like "irc.freenode.#alacritty") (default: "core.weechat,python.grep")" -python.styurl.ignored_tags = "comma-separated list of tags to ignore URLs from (default: "irc_quit,irc_join")" -python.styurl.regex = "the URL-parsing regex using Python syntax (make sure capturing group 1 is the full URL) (default: "((?:https?|ftp)://[^\s/$.?#].\S*)")" -python.vimode.copy_clipboard_cmd = "command used to copy to clipboard; must read input from stdin (default: "xclip -selection c")" -python.vimode.imap_esc = "use alternate mapping to enter Normal mode while in Insert mode; having it set to 'jk' is similar to `:imap jk ` in vim (default: "")" -python.vimode.imap_esc_timeout = "time in ms to wait for the imap_esc sequence to complete (default: "1000")" -python.vimode.line_number_prefix = "prefix for line numbers (default: "")" -python.vimode.line_number_suffix = "suffix for line numbers (default: " ")" -python.vimode.mode_indicator_cmd_color = "color for mode indicator in Command mode (default: "white")" -python.vimode.mode_indicator_cmd_color_bg = "background color for mode indicator in Command mode (default: "cyan")" -python.vimode.mode_indicator_insert_color = "color for mode indicator in Insert mode (default: "white")" -python.vimode.mode_indicator_insert_color_bg = "background color for mode indicator in Insert mode (default: "blue")" -python.vimode.mode_indicator_normal_color = "color for mode indicator in Normal mode (default: "white")" -python.vimode.mode_indicator_normal_color_bg = "background color for mode indicator in Normal mode (default: "gray")" -python.vimode.mode_indicator_prefix = "prefix for the bar item mode_indicator (default: "")" -python.vimode.mode_indicator_replace_color = "color for mode indicator in Replace mode (default: "white")" -python.vimode.mode_indicator_replace_color_bg = "background color for mode indicator in Replace mode (default: "red")" -python.vimode.mode_indicator_search_color = "color for mode indicator in Search mode (default: "white")" -python.vimode.mode_indicator_search_color_bg = "background color for mode indicator in Search mode (default: "magenta")" -python.vimode.mode_indicator_suffix = "suffix for the bar item mode_indicator (default: "")" -python.vimode.no_warn = "don't warn about problematic keybindings and tmux/screen (default: "off")" -python.vimode.paste_clipboard_cmd = "command used to paste clipboard; must output content to stdout (default: "xclip -selection c -o")" -python.vimode.search_vim = "allow n/N usage after searching (requires an extra to return to normal mode) (default: "off")" -python.vimode.user_mappings = "see the `:nmap` command in the README for more info; please do not modify this field manually unless you know what you're doing (default: "")" diff --git a/.config/weechat/python.conf b/.config/weechat/python.conf index 187b778d..febee01e 100644 --- a/.config/weechat/python.conf +++ b/.config/weechat/python.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/python/aesthetic.py b/.config/weechat/python/aesthetic.py deleted file mode 100644 index 988071d6..00000000 --- a/.config/weechat/python/aesthetic.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Script Name: aesthetic.py -# Script Author: Wojciech Siewierski -# Script License: GPL3 -# Contact: vifon @ irc.freenode.net - -SCRIPT_NAME = 'aesthetic' -SCRIPT_AUTHOR = 'Wojciech Siewierski' -SCRIPT_VERSION = '1.0.6' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Make messages more A E S T H E T I C A L L Y pleasing.' - -import_ok = True - -try: - import weechat -except ImportError: - print('This script must be run under WeeChat') - print('You can obtain a copy of WeeChat, for free, at https://weechat.org') - import_ok = False - -weechat_version = 0 - -import shlex -import sys - -def aesthetic_(args): - for arg in args: - try: - arg = arg.decode('utf8') - except AttributeError: - pass - yield " ".join(arg.upper()) - for n, char in enumerate(arg[1:]): - yield " ".join(" "*(n+1)).join(char.upper()*2) - -def aesthetic(args): - if sys.version_info < (3,): - return (x.encode('utf8') for x in aesthetic_(args)) - else: - return aesthetic_(args) - -def aesthetic_cb(data, buffer, args): - for x in aesthetic(shlex.split(args)): - weechat.command(buffer, x) - return weechat.WEECHAT_RC_OK - -if __name__ == "__main__" and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): - weechat_version = weechat.info_get("version_number", "") or 0 - weechat.hook_command( - "aesthetic", - """Format a message like this: - -E X A M P L E -X X -A A -M M -P P -L L -E E - -Each argument is formatted separately, use sh-like quotes for grouping. For example '/aesthetic foo bar' will send two such blocks while '/aesthetic "foo bar"' would send one larger one. - -Use with care to not cause undesirable message spam.""", - "message", "", - "", - "aesthetic_cb", "" - ) diff --git a/.config/weechat/python/anotify.py b/.config/weechat/python/anotify.py deleted file mode 100644 index 0a047b42..00000000 --- a/.config/weechat/python/anotify.py +++ /dev/null @@ -1,476 +0,0 @@ -# -*- coding: utf-8 -*- -# -# anotify.py -# Copyright (c) 2012 magnific0 -# -# based on: -# growl.py -# Copyright (c) 2011 Sorin Ionescu -# -# 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. - - -SCRIPT_NAME = 'anotify' -SCRIPT_AUTHOR = 'magnific0' -SCRIPT_VERSION = '1.0.2' -SCRIPT_LICENSE = 'MIT' -SCRIPT_DESC = 'Sends libnotify notifications upon events.' - - -# Changelog -# 2014-05-10: v1.0.1 Change hook_print callback argument type of -# displayed/highlight (WeeChat >= 1.0) -# 2012-09-20: v1.0.0 Forked from original and adapted for libnotify. - -# ----------------------------------------------------------------------------- -# Settings -# ----------------------------------------------------------------------------- -SETTINGS = { - 'show_public_message': 'off', - 'show_private_message': 'on', - 'show_public_action_message': 'off', - 'show_private_action_message': 'on', - 'show_notice_message': 'off', - 'show_invite_message': 'on', - 'show_highlighted_message': 'on', - 'show_server': 'on', - 'show_channel_topic': 'on', - 'show_dcc': 'on', - 'show_upgrade_ended': 'on', - 'sticky': 'off', - 'sticky_away': 'on', - 'icon': '/usr/share/pixmaps/weechat.xpm', -} - - -# ----------------------------------------------------------------------------- -# Imports -# ----------------------------------------------------------------------------- -try: - import re - import weechat - import gi - import notify2 - import subprocess - IMPORT_OK = True -except ImportError as error: - IMPORT_OK = False - if str(error).find('weechat') != -1: - print('This script must be run under WeeChat.') - print('Get WeeChat at http://www.weechat.org.') - else: - weechat.prnt('', 'anotify: {0}'.format(error)) - -# ----------------------------------------------------------------------------- -# Globals -# ----------------------------------------------------------------------------- -TAGGED_MESSAGES = { - 'public message or action': set(['irc_privmsg', 'notify_message']), - 'private message or action': set(['irc_privmsg', 'notify_private']), - 'notice message': set(['irc_notice', 'notify_private']), - 'invite message': set(['irc_invite', 'notify_highlight']), - 'channel topic': set(['irc_topic', ]), - #'away status': set(['away_info', ]), -} - - -UNTAGGED_MESSAGES = { - 'away status': - re.compile(r'^You ((\w+).){2,3}marked as being away', re.UNICODE), - 'dcc chat request': - re.compile(r'^xfer: incoming chat request from (\w+)', re.UNICODE), - 'dcc chat closed': - re.compile(r'^xfer: chat closed with (\w+)', re.UNICODE), - 'dcc get request': - re.compile( - r'^xfer: incoming file from (\w+) [^:]+: ((?:,\w|[^,])+),', - re.UNICODE), - 'dcc get completed': - re.compile(r'^xfer: file ([^\s]+) received from \w+: OK', re.UNICODE), - 'dcc get failed': - re.compile( - r'^xfer: file ([^\s]+) received from \w+: FAILED', - re.UNICODE), - 'dcc send completed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: OK', re.UNICODE), - 'dcc send failed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: FAILED', re.UNICODE), -} - - -DISPATCH_TABLE = { - 'away status': 'set_away_status', - 'public message or action': 'notify_public_message_or_action', - 'private message or action': 'notify_private_message_or_action', - 'notice message': 'notify_notice_message', - 'invite message': 'notify_invite_message', - 'channel topic': 'notify_channel_topic', - 'dcc chat request': 'notify_dcc_chat_request', - 'dcc chat closed': 'notify_dcc_chat_closed', - 'dcc get request': 'notify_dcc_get_request', - 'dcc get completed': 'notify_dcc_get_completed', - 'dcc get failed': 'notify_dcc_get_failed', - 'dcc send completed': 'notify_dcc_send_completed', - 'dcc send failed': 'notify_dcc_send_failed', -} - - -STATE = { - 'icon': None, - 'is_away': False -} - - -# ----------------------------------------------------------------------------- -# Notifiers -# ----------------------------------------------------------------------------- -def cb_irc_server_connected(data, signal, signal_data): - '''Notify when connected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': - a_notify( - 'Server', - 'Server Connected', - 'Connected to network {0}.'.format(signal_data)) - return weechat.WEECHAT_RC_OK - - -def cb_irc_server_disconnected(data, signal, signal_data): - '''Notify when disconnected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': - a_notify( - 'Server', - 'Server Disconnected', - 'Disconnected from network {0}.'.format(signal_data)) - return weechat.WEECHAT_RC_OK - - -def cb_notify_upgrade_ended(data, signal, signal_data): - '''Notify on end of WeeChat upgrade.''' - if weechat.config_get_plugin('show_upgrade_ended') == 'on': - a_notify( - 'WeeChat', - 'WeeChat Upgraded', - 'WeeChat has been upgraded.') - return weechat.WEECHAT_RC_OK - - -def notify_highlighted_message(prefix, message): - '''Notify on highlighted message.''' - if weechat.config_get_plugin("show_highlighted_message") == "on": - a_notify( - 'Highlight', - 'Highlighted Message', - "{0}: {1}".format(prefix, message), - priority=notify2.URGENCY_CRITICAL) - - -def notify_public_message_or_action(prefix, message, highlighted): - '''Notify on public message or action.''' - if prefix == ' *': - regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - notify_public_action_message(prefix, message, highlighted) - else: - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_public_message") == "on": - a_notify( - 'Public', - 'Public Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_private_message_or_action(prefix, message, highlighted): - '''Notify on private message or action.''' - regex = re.compile(r'^CTCP_MESSAGE.+?ACTION (.+)$', re.UNICODE) - match = regex.match(message) - if match: - notify_private_action_message(prefix, match.group(1), highlighted) - else: - if prefix == ' *': - regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - notify_private_action_message(prefix, message, highlighted) - else: - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_private_message") == "on": - a_notify( - 'Private', - 'Private Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_public_action_message(prefix, message, highlighted): - '''Notify on public action message.''' - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_public_action_message") == "on": - a_notify( - 'Action', - 'Public Action Message', - '{0}: {1}'.format(prefix, message), - priority=notify2.URGENCY_NORMAL) - - -def notify_private_action_message(prefix, message, highlighted): - '''Notify on private action message.''' - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_private_action_message") == "on": - a_notify( - 'Action', - 'Private Action Message', - '{0}: {1}'.format(prefix, message), - priority=notify2.URGENCY_NORMAL) - - -def notify_notice_message(prefix, message, highlighted): - '''Notify on notice message.''' - regex = re.compile(r'^([^\s]*) [^:]*: (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_notice_message") == "on": - a_notify( - 'Notice', - 'Notice Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_invite_message(prefix, message, highlighted): - '''Notify on channel invitation message.''' - if weechat.config_get_plugin("show_invite_message") == "on": - regex = re.compile( - r'^You have been invited to ([^\s]+) by ([^\s]+)$', re.UNICODE) - match = regex.match(message) - if match: - channel = match.group(1) - nick = match.group(2) - a_notify( - 'Invite', - 'Channel Invitation', - '{0} has invited you to join {1}.'.format(nick, channel)) - - -def notify_channel_topic(prefix, message, highlighted): - '''Notify on channel topic change.''' - if weechat.config_get_plugin("show_channel_topic") == "on": - regex = re.compile( - r'^\w+ has (?:changed|unset) topic for ([^\s]+)' + - '(?:(?: from "(?:(?:"\w|[^"])+)")? to "((?:"\w|[^"])+)")?', - re.UNICODE) - match = regex.match(message) - if match: - channel = match.group(1) - topic = match.group(2) or '' - a_notify( - 'Channel', - 'Channel Topic', - "{0}: {1}".format(channel, topic)) - - -def notify_dcc_chat_request(match): - '''Notify on DCC chat request.''' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - a_notify( - 'DCC', - 'Direct Chat Request', - '{0} wants to chat directly.'.format(nick)) - - -def notify_dcc_chat_closed(match): - '''Notify on DCC chat termination.''' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - a_notify( - 'DCC', - 'Direct Chat Ended', - 'Direct chat with {0} has ended.'.format(nick)) - - -def notify_dcc_get_request(match): - 'Notify on DCC get request.' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - file_name = match.group(2) - a_notify( - 'DCC', - 'File Transfer Request', - '{0} wants to send you {1}.'.format(nick, file_name)) - - -def notify_dcc_get_completed(match): - 'Notify on DCC get completion.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Download Complete', file_name) - - -def notify_dcc_get_failed(match): - 'Notify on DCC get failure.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Download Failed', file_name) - - -def notify_dcc_send_completed(match): - 'Notify on DCC send completion.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Upload Complete', file_name) - - -def notify_dcc_send_failed(match): - 'Notify on DCC send failure.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Upload Failed', file_name) - - -# ----------------------------------------------------------------------------- -# Utility -# ----------------------------------------------------------------------------- -def set_away_status(match): - status = match.group(1) - if status == 'been ': - STATE['is_away'] = True - if status == 'longer ': - STATE['is_away'] = False - - -def cb_process_message( - data, - wbuffer, - date, - tags, - displayed, - highlight, - prefix, - message -): - '''Delegates incoming messages to appropriate handlers.''' - tags = set(tags.split(',')) - functions = globals() - is_public_message = tags.issuperset( - TAGGED_MESSAGES['public message or action']) - buffer_name = weechat.buffer_get_string(wbuffer, 'name') - dcc_buffer_regex = re.compile(r'^irc_dcc\.', re.UNICODE) - dcc_buffer_match = dcc_buffer_regex.match(buffer_name) - highlighted = False - if int(highlight): - highlighted = True - # Private DCC message identifies itself as public. - if is_public_message and dcc_buffer_match: - notify_private_message_or_action(prefix, message, highlighted) - return weechat.WEECHAT_RC_OK - # Pass identified, untagged message to its designated function. - for key, value in UNTAGGED_MESSAGES.items(): - match = value.match(message) - if match: - functions[DISPATCH_TABLE[key]](match) - return weechat.WEECHAT_RC_OK - # Pass identified, tagged message to its designated function. - for key, value in TAGGED_MESSAGES.items(): - if tags.issuperset(value): - functions[DISPATCH_TABLE[key]](prefix, message, highlighted) - return weechat.WEECHAT_RC_OK - return weechat.WEECHAT_RC_OK - - -def a_notify(notification, title, description, priority=notify2.URGENCY_LOW): - '''Returns whether notifications should be sticky.''' - is_away = STATE['is_away'] - icon = STATE['icon'] - time_out = 5000 - if weechat.config_get_plugin('sticky') == 'on': - time_out = 0 - if weechat.config_get_plugin('sticky_away') == 'on' and is_away: - time_out = 0 - try: - # notify2.init("wee-notifier") - # wn = notify2.Notification(title, description, icon) - # wn.set_urgency(priority) - # wn.set_timeout(time_out) - # wn.show() - subprocess.Popen(["notify-send", "-a", " WeeChat", title, description]) - if title != "Server Connected" and title != "Server Disconnected": - subprocess.Popen(["canberra-gtk-play", "-i", "message-new-instant", "-V", "15"]) - except Exception as error: - weechat.prnt('', 'anotify: {0}'.format(error)) - - -# ----------------------------------------------------------------------------- -# Main -# ----------------------------------------------------------------------------- -def main(): - '''Sets up WeeChat notifications.''' - # Initialize options. - for option, value in SETTINGS.items(): - if not weechat.config_is_set_plugin(option): - weechat.config_set_plugin(option, value) - # Initialize. - name = "WeeChat" - icon = "/usr/share/pixmaps/weechat.xpm" - notifications = [ - 'Public', - 'Private', - 'Action', - 'Notice', - 'Invite', - 'Highlight', - 'Server', - 'Channel', - 'DCC', - 'WeeChat' - ] - STATE['icon'] = icon - # Register hooks. - weechat.hook_signal( - 'irc_server_connected', - 'cb_irc_server_connected', - '') - weechat.hook_signal( - 'irc_server_disconnected', - 'cb_irc_server_disconnected', - '') - weechat.hook_signal('upgrade_ended', 'cb_upgrade_ended', '') - weechat.hook_print('', '', '', 1, 'cb_process_message', '') - - -if __name__ == '__main__' and IMPORT_OK and weechat.register( - SCRIPT_NAME, - SCRIPT_AUTHOR, - SCRIPT_VERSION, - SCRIPT_LICENSE, - SCRIPT_DESC, - '', - '' -): - main() diff --git a/.config/weechat/python/autoload/aesthetic.py b/.config/weechat/python/autoload/aesthetic.py deleted file mode 100644 index 988071d6..00000000 --- a/.config/weechat/python/autoload/aesthetic.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Script Name: aesthetic.py -# Script Author: Wojciech Siewierski -# Script License: GPL3 -# Contact: vifon @ irc.freenode.net - -SCRIPT_NAME = 'aesthetic' -SCRIPT_AUTHOR = 'Wojciech Siewierski' -SCRIPT_VERSION = '1.0.6' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Make messages more A E S T H E T I C A L L Y pleasing.' - -import_ok = True - -try: - import weechat -except ImportError: - print('This script must be run under WeeChat') - print('You can obtain a copy of WeeChat, for free, at https://weechat.org') - import_ok = False - -weechat_version = 0 - -import shlex -import sys - -def aesthetic_(args): - for arg in args: - try: - arg = arg.decode('utf8') - except AttributeError: - pass - yield " ".join(arg.upper()) - for n, char in enumerate(arg[1:]): - yield " ".join(" "*(n+1)).join(char.upper()*2) - -def aesthetic(args): - if sys.version_info < (3,): - return (x.encode('utf8') for x in aesthetic_(args)) - else: - return aesthetic_(args) - -def aesthetic_cb(data, buffer, args): - for x in aesthetic(shlex.split(args)): - weechat.command(buffer, x) - return weechat.WEECHAT_RC_OK - -if __name__ == "__main__" and import_ok: - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): - weechat_version = weechat.info_get("version_number", "") or 0 - weechat.hook_command( - "aesthetic", - """Format a message like this: - -E X A M P L E -X X -A A -M M -P P -L L -E E - -Each argument is formatted separately, use sh-like quotes for grouping. For example '/aesthetic foo bar' will send two such blocks while '/aesthetic "foo bar"' would send one larger one. - -Use with care to not cause undesirable message spam.""", - "message", "", - "", - "aesthetic_cb", "" - ) diff --git a/.config/weechat/python/autoload/anotify.py b/.config/weechat/python/autoload/anotify.py deleted file mode 100644 index 0a047b42..00000000 --- a/.config/weechat/python/autoload/anotify.py +++ /dev/null @@ -1,476 +0,0 @@ -# -*- coding: utf-8 -*- -# -# anotify.py -# Copyright (c) 2012 magnific0 -# -# based on: -# growl.py -# Copyright (c) 2011 Sorin Ionescu -# -# 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. - - -SCRIPT_NAME = 'anotify' -SCRIPT_AUTHOR = 'magnific0' -SCRIPT_VERSION = '1.0.2' -SCRIPT_LICENSE = 'MIT' -SCRIPT_DESC = 'Sends libnotify notifications upon events.' - - -# Changelog -# 2014-05-10: v1.0.1 Change hook_print callback argument type of -# displayed/highlight (WeeChat >= 1.0) -# 2012-09-20: v1.0.0 Forked from original and adapted for libnotify. - -# ----------------------------------------------------------------------------- -# Settings -# ----------------------------------------------------------------------------- -SETTINGS = { - 'show_public_message': 'off', - 'show_private_message': 'on', - 'show_public_action_message': 'off', - 'show_private_action_message': 'on', - 'show_notice_message': 'off', - 'show_invite_message': 'on', - 'show_highlighted_message': 'on', - 'show_server': 'on', - 'show_channel_topic': 'on', - 'show_dcc': 'on', - 'show_upgrade_ended': 'on', - 'sticky': 'off', - 'sticky_away': 'on', - 'icon': '/usr/share/pixmaps/weechat.xpm', -} - - -# ----------------------------------------------------------------------------- -# Imports -# ----------------------------------------------------------------------------- -try: - import re - import weechat - import gi - import notify2 - import subprocess - IMPORT_OK = True -except ImportError as error: - IMPORT_OK = False - if str(error).find('weechat') != -1: - print('This script must be run under WeeChat.') - print('Get WeeChat at http://www.weechat.org.') - else: - weechat.prnt('', 'anotify: {0}'.format(error)) - -# ----------------------------------------------------------------------------- -# Globals -# ----------------------------------------------------------------------------- -TAGGED_MESSAGES = { - 'public message or action': set(['irc_privmsg', 'notify_message']), - 'private message or action': set(['irc_privmsg', 'notify_private']), - 'notice message': set(['irc_notice', 'notify_private']), - 'invite message': set(['irc_invite', 'notify_highlight']), - 'channel topic': set(['irc_topic', ]), - #'away status': set(['away_info', ]), -} - - -UNTAGGED_MESSAGES = { - 'away status': - re.compile(r'^You ((\w+).){2,3}marked as being away', re.UNICODE), - 'dcc chat request': - re.compile(r'^xfer: incoming chat request from (\w+)', re.UNICODE), - 'dcc chat closed': - re.compile(r'^xfer: chat closed with (\w+)', re.UNICODE), - 'dcc get request': - re.compile( - r'^xfer: incoming file from (\w+) [^:]+: ((?:,\w|[^,])+),', - re.UNICODE), - 'dcc get completed': - re.compile(r'^xfer: file ([^\s]+) received from \w+: OK', re.UNICODE), - 'dcc get failed': - re.compile( - r'^xfer: file ([^\s]+) received from \w+: FAILED', - re.UNICODE), - 'dcc send completed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: OK', re.UNICODE), - 'dcc send failed': - re.compile(r'^xfer: file ([^\s]+) sent to \w+: FAILED', re.UNICODE), -} - - -DISPATCH_TABLE = { - 'away status': 'set_away_status', - 'public message or action': 'notify_public_message_or_action', - 'private message or action': 'notify_private_message_or_action', - 'notice message': 'notify_notice_message', - 'invite message': 'notify_invite_message', - 'channel topic': 'notify_channel_topic', - 'dcc chat request': 'notify_dcc_chat_request', - 'dcc chat closed': 'notify_dcc_chat_closed', - 'dcc get request': 'notify_dcc_get_request', - 'dcc get completed': 'notify_dcc_get_completed', - 'dcc get failed': 'notify_dcc_get_failed', - 'dcc send completed': 'notify_dcc_send_completed', - 'dcc send failed': 'notify_dcc_send_failed', -} - - -STATE = { - 'icon': None, - 'is_away': False -} - - -# ----------------------------------------------------------------------------- -# Notifiers -# ----------------------------------------------------------------------------- -def cb_irc_server_connected(data, signal, signal_data): - '''Notify when connected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': - a_notify( - 'Server', - 'Server Connected', - 'Connected to network {0}.'.format(signal_data)) - return weechat.WEECHAT_RC_OK - - -def cb_irc_server_disconnected(data, signal, signal_data): - '''Notify when disconnected to IRC server.''' - if weechat.config_get_plugin('show_server') == 'on': - a_notify( - 'Server', - 'Server Disconnected', - 'Disconnected from network {0}.'.format(signal_data)) - return weechat.WEECHAT_RC_OK - - -def cb_notify_upgrade_ended(data, signal, signal_data): - '''Notify on end of WeeChat upgrade.''' - if weechat.config_get_plugin('show_upgrade_ended') == 'on': - a_notify( - 'WeeChat', - 'WeeChat Upgraded', - 'WeeChat has been upgraded.') - return weechat.WEECHAT_RC_OK - - -def notify_highlighted_message(prefix, message): - '''Notify on highlighted message.''' - if weechat.config_get_plugin("show_highlighted_message") == "on": - a_notify( - 'Highlight', - 'Highlighted Message', - "{0}: {1}".format(prefix, message), - priority=notify2.URGENCY_CRITICAL) - - -def notify_public_message_or_action(prefix, message, highlighted): - '''Notify on public message or action.''' - if prefix == ' *': - regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - notify_public_action_message(prefix, message, highlighted) - else: - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_public_message") == "on": - a_notify( - 'Public', - 'Public Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_private_message_or_action(prefix, message, highlighted): - '''Notify on private message or action.''' - regex = re.compile(r'^CTCP_MESSAGE.+?ACTION (.+)$', re.UNICODE) - match = regex.match(message) - if match: - notify_private_action_message(prefix, match.group(1), highlighted) - else: - if prefix == ' *': - regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - notify_private_action_message(prefix, message, highlighted) - else: - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_private_message") == "on": - a_notify( - 'Private', - 'Private Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_public_action_message(prefix, message, highlighted): - '''Notify on public action message.''' - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_public_action_message") == "on": - a_notify( - 'Action', - 'Public Action Message', - '{0}: {1}'.format(prefix, message), - priority=notify2.URGENCY_NORMAL) - - -def notify_private_action_message(prefix, message, highlighted): - '''Notify on private action message.''' - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_private_action_message") == "on": - a_notify( - 'Action', - 'Private Action Message', - '{0}: {1}'.format(prefix, message), - priority=notify2.URGENCY_NORMAL) - - -def notify_notice_message(prefix, message, highlighted): - '''Notify on notice message.''' - regex = re.compile(r'^([^\s]*) [^:]*: (.+)$', re.UNICODE) - match = regex.match(message) - if match: - prefix = match.group(1) - message = match.group(2) - if highlighted: - notify_highlighted_message(prefix, message) - elif weechat.config_get_plugin("show_notice_message") == "on": - a_notify( - 'Notice', - 'Notice Message', - '{0}: {1}'.format(prefix, message)) - - -def notify_invite_message(prefix, message, highlighted): - '''Notify on channel invitation message.''' - if weechat.config_get_plugin("show_invite_message") == "on": - regex = re.compile( - r'^You have been invited to ([^\s]+) by ([^\s]+)$', re.UNICODE) - match = regex.match(message) - if match: - channel = match.group(1) - nick = match.group(2) - a_notify( - 'Invite', - 'Channel Invitation', - '{0} has invited you to join {1}.'.format(nick, channel)) - - -def notify_channel_topic(prefix, message, highlighted): - '''Notify on channel topic change.''' - if weechat.config_get_plugin("show_channel_topic") == "on": - regex = re.compile( - r'^\w+ has (?:changed|unset) topic for ([^\s]+)' + - '(?:(?: from "(?:(?:"\w|[^"])+)")? to "((?:"\w|[^"])+)")?', - re.UNICODE) - match = regex.match(message) - if match: - channel = match.group(1) - topic = match.group(2) or '' - a_notify( - 'Channel', - 'Channel Topic', - "{0}: {1}".format(channel, topic)) - - -def notify_dcc_chat_request(match): - '''Notify on DCC chat request.''' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - a_notify( - 'DCC', - 'Direct Chat Request', - '{0} wants to chat directly.'.format(nick)) - - -def notify_dcc_chat_closed(match): - '''Notify on DCC chat termination.''' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - a_notify( - 'DCC', - 'Direct Chat Ended', - 'Direct chat with {0} has ended.'.format(nick)) - - -def notify_dcc_get_request(match): - 'Notify on DCC get request.' - if weechat.config_get_plugin("show_dcc") == "on": - nick = match.group(1) - file_name = match.group(2) - a_notify( - 'DCC', - 'File Transfer Request', - '{0} wants to send you {1}.'.format(nick, file_name)) - - -def notify_dcc_get_completed(match): - 'Notify on DCC get completion.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Download Complete', file_name) - - -def notify_dcc_get_failed(match): - 'Notify on DCC get failure.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Download Failed', file_name) - - -def notify_dcc_send_completed(match): - 'Notify on DCC send completion.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Upload Complete', file_name) - - -def notify_dcc_send_failed(match): - 'Notify on DCC send failure.' - if weechat.config_get_plugin("show_dcc") == "on": - file_name = match.group(1) - a_notify('DCC', 'Upload Failed', file_name) - - -# ----------------------------------------------------------------------------- -# Utility -# ----------------------------------------------------------------------------- -def set_away_status(match): - status = match.group(1) - if status == 'been ': - STATE['is_away'] = True - if status == 'longer ': - STATE['is_away'] = False - - -def cb_process_message( - data, - wbuffer, - date, - tags, - displayed, - highlight, - prefix, - message -): - '''Delegates incoming messages to appropriate handlers.''' - tags = set(tags.split(',')) - functions = globals() - is_public_message = tags.issuperset( - TAGGED_MESSAGES['public message or action']) - buffer_name = weechat.buffer_get_string(wbuffer, 'name') - dcc_buffer_regex = re.compile(r'^irc_dcc\.', re.UNICODE) - dcc_buffer_match = dcc_buffer_regex.match(buffer_name) - highlighted = False - if int(highlight): - highlighted = True - # Private DCC message identifies itself as public. - if is_public_message and dcc_buffer_match: - notify_private_message_or_action(prefix, message, highlighted) - return weechat.WEECHAT_RC_OK - # Pass identified, untagged message to its designated function. - for key, value in UNTAGGED_MESSAGES.items(): - match = value.match(message) - if match: - functions[DISPATCH_TABLE[key]](match) - return weechat.WEECHAT_RC_OK - # Pass identified, tagged message to its designated function. - for key, value in TAGGED_MESSAGES.items(): - if tags.issuperset(value): - functions[DISPATCH_TABLE[key]](prefix, message, highlighted) - return weechat.WEECHAT_RC_OK - return weechat.WEECHAT_RC_OK - - -def a_notify(notification, title, description, priority=notify2.URGENCY_LOW): - '''Returns whether notifications should be sticky.''' - is_away = STATE['is_away'] - icon = STATE['icon'] - time_out = 5000 - if weechat.config_get_plugin('sticky') == 'on': - time_out = 0 - if weechat.config_get_plugin('sticky_away') == 'on' and is_away: - time_out = 0 - try: - # notify2.init("wee-notifier") - # wn = notify2.Notification(title, description, icon) - # wn.set_urgency(priority) - # wn.set_timeout(time_out) - # wn.show() - subprocess.Popen(["notify-send", "-a", " WeeChat", title, description]) - if title != "Server Connected" and title != "Server Disconnected": - subprocess.Popen(["canberra-gtk-play", "-i", "message-new-instant", "-V", "15"]) - except Exception as error: - weechat.prnt('', 'anotify: {0}'.format(error)) - - -# ----------------------------------------------------------------------------- -# Main -# ----------------------------------------------------------------------------- -def main(): - '''Sets up WeeChat notifications.''' - # Initialize options. - for option, value in SETTINGS.items(): - if not weechat.config_is_set_plugin(option): - weechat.config_set_plugin(option, value) - # Initialize. - name = "WeeChat" - icon = "/usr/share/pixmaps/weechat.xpm" - notifications = [ - 'Public', - 'Private', - 'Action', - 'Notice', - 'Invite', - 'Highlight', - 'Server', - 'Channel', - 'DCC', - 'WeeChat' - ] - STATE['icon'] = icon - # Register hooks. - weechat.hook_signal( - 'irc_server_connected', - 'cb_irc_server_connected', - '') - weechat.hook_signal( - 'irc_server_disconnected', - 'cb_irc_server_disconnected', - '') - weechat.hook_signal('upgrade_ended', 'cb_upgrade_ended', '') - weechat.hook_print('', '', '', 1, 'cb_process_message', '') - - -if __name__ == '__main__' and IMPORT_OK and weechat.register( - SCRIPT_NAME, - SCRIPT_AUTHOR, - SCRIPT_VERSION, - SCRIPT_LICENSE, - SCRIPT_DESC, - '', - '' -): - main() diff --git a/.config/weechat/python/autoload/autosort.py b/.config/weechat/python/autoload/autosort.py deleted file mode 100644 index 4312cda7..00000000 --- a/.config/weechat/python/autoload/autosort.py +++ /dev/null @@ -1,1075 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Maarten de Vries -# -# 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 3 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 program. If not, see . -# - -# -# Autosort automatically keeps your buffers sorted and grouped by server. -# You can define your own sorting rules. See /help autosort for more details. -# -# https://github.com/de-vri-es/weechat-autosort -# - -# -# Changelog: -# 3.9: -# * Remove `buffers.pl` from recommended settings. -# 3,8: -# * Fix relative sorting on script name in default rules. -# * Document a useful property of stable sort algorithms. -# 3.7: -# * Make default rules work with bitlbee, matrix and slack. -# 3.6: -# * Add more documentation on provided info hooks. -# 3.5: -# * Add ${info:autosort_escape,...} to escape arguments for other info hooks. -# 3.4: -# * Fix rate-limit of sorting to prevent high CPU load and lock-ups. -# * Fix bug in parsing empty arguments for info hooks. -# * Add debug_log option to aid with debugging. -# * Correct a few typos. -# 3.3: -# * Fix the /autosort debug command for unicode. -# * Update the default rules to work better with Slack. -# 3.2: -# * Fix python3 compatiblity. -# 3.1: -# * Use colors to format the help text. -# 3.0: -# * Switch to evaluated expressions for sorting. -# * Add `/autosort debug` command. -# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules. -# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules. -# * Make tab completion context aware. -# 2.8: -# * Fix compatibility with python 3 regarding unicode handling. -# 2.7: -# * Fix sorting of buffers with spaces in their name. -# 2.6: -# * Ignore case in rules when doing case insensitive sorting. -# 2.5: -# * Fix handling unicode buffer names. -# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on. -# 2.4: -# * Make script python3 compatible. -# 2.3: -# * Fix sorting items without score last (regressed in 2.2). -# 2.2: -# * Add configuration option for signals that trigger a sort. -# * Add command to manually trigger a sort (/autosort sort). -# * Add replacement patterns to apply before sorting. -# 2.1: -# * Fix some minor style issues. -# 2.0: -# * Allow for custom sort rules. -# - - -import json -import math -import re -import sys -import time -import weechat - -SCRIPT_NAME = 'autosort' -SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '3.9' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' - - -config = None -hooks = [] -signal_delay_timer = None -sort_limit_timer = None -sort_queued = False - - -# Make sure that unicode, bytes and str are always available in python2 and 3. -# For python 2, str == bytes -# For python 3, str == unicode -if sys.version_info[0] >= 3: - unicode = str - -def ensure_str(input): - ''' - Make sure the given type if the correct string type for the current python version. - That means bytes for python2 and unicode for python3. - ''' - if not isinstance(input, str): - if isinstance(input, bytes): - return input.encode('utf-8') - if isinstance(input, unicode): - return input.decode('utf-8') - return input - - -if hasattr(time, 'perf_counter'): - perf_counter = time.perf_counter -else: - perf_counter = time.clock - -def casefold(string): - if hasattr(string, 'casefold'): return string.casefold() - # Fall back to lowercasing for python2. - return string.lower() - -def list_swap(values, a, b): - values[a], values[b] = values[b], values[a] - -def list_move(values, old_index, new_index): - values.insert(new_index, values.pop(old_index)) - -def list_find(collection, value): - for i, elem in enumerate(collection): - if elem == value: return i - return None - -class HumanReadableError(Exception): - pass - -def parse_int(arg, arg_name = 'argument'): - ''' Parse an integer and provide a more human readable error. ''' - arg = arg.strip() - try: - return int(arg) - except ValueError: - raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg)) - -def decode_rules(blob): - parsed = json.loads(blob) - if not isinstance(parsed, list): - log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed))) - return [] - - for i, entry in enumerate(parsed): - if not isinstance(entry, (str, unicode)): - log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry))) - return [] - - return parsed - -def decode_helpers(blob): - parsed = json.loads(blob) - if not isinstance(parsed, dict): - log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed))) - return {} - - for key, value in parsed.items(): - if not isinstance(value, (str, unicode)): - log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value))) - return {} - return parsed - -class Config: - ''' The autosort configuration. ''' - - default_rules = json.dumps([ - '${core_first}', - '${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}', - '${script_or_plugin}', - '${irc_raw_first}', - '${server}', - '${info:autosort_order,${type},server,*,channel,private}', - '${hashless_name}', - '${buffer.full_name}', - ]) - - default_helpers = json.dumps({ - 'core_first': '${if:${buffer.full_name}!=core.weechat}', - 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}', - 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}', - 'hashless_name': '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}', - 'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}', - }) - - default_signal_delay = 5 - default_sort_limit = 100 - - default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' - - def __init__(self, filename): - ''' Initialize the configuration. ''' - - self.filename = filename - self.config_file = weechat.config_new(self.filename, '', '') - self.sorting_section = None - self.v3_section = None - - self.case_sensitive = False - self.rules = [] - self.helpers = {} - self.signals = [] - self.signal_delay = Config.default_signal_delay, - self.sort_limit = Config.default_sort_limit, - self.sort_on_config = True - self.debug_log = False - - self.__case_sensitive = None - self.__rules = None - self.__helpers = None - self.__signals = None - self.__signal_delay = None - self.__sort_limit = None - self.__sort_on_config = None - self.__debug_log = None - - if not self.config_file: - log('Failed to initialize configuration file "{0}".'.format(self.filename)) - return - - self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '') - self.v3_section = weechat.config_new_section(self.config_file, 'v3', False, False, '', '', '', '', '', '', '', '', '', '') - - if not self.sorting_section: - log('Failed to initialize section "sorting" of configuration file.') - weechat.config_free(self.config_file) - return - - self.__case_sensitive = weechat.config_new_option( - self.config_file, self.sorting_section, - 'case_sensitive', 'boolean', - 'If this option is on, sorting is case sensitive.', - '', 0, 0, 'off', 'off', 0, - '', '', '', '', '', '' - ) - - weechat.config_new_option( - self.config_file, self.sorting_section, - 'rules', 'string', - 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.', - '', 0, 0, '', '', 0, - '', '', '', '', '', '' - ) - - weechat.config_new_option( - self.config_file, self.sorting_section, - 'replacements', 'string', - 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.', - '', 0, 0, '', '', 0, - '', '', '', '', '', '' - ) - - self.__rules = weechat.config_new_option( - self.config_file, self.v3_section, - 'rules', 'string', - 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.', - '', 0, 0, Config.default_rules, Config.default_rules, 0, - '', '', '', '', '', '' - ) - - self.__helpers = weechat.config_new_option( - self.config_file, self.v3_section, - 'helpers', 'string', - 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.', - '', 0, 0, Config.default_helpers, Config.default_helpers, 0, - '', '', '', '', '', '' - ) - - self.__signals = weechat.config_new_option( - self.config_file, self.sorting_section, - 'signals', 'string', - 'A space separated list of signals that will cause autosort to resort your buffer list.', - '', 0, 0, Config.default_signals, Config.default_signals, 0, - '', '', '', '', '', '' - ) - - self.__signal_delay = weechat.config_new_option( - self.config_file, self.sorting_section, - 'signal_delay', 'integer', - 'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.', - '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0, - '', '', '', '', '', '' - ) - - self.__sort_limit = weechat.config_new_option( - self.config_file, self.sorting_section, - 'sort_limit', 'integer', - 'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.', - '', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 0, - '', '', '', '', '', '' - ) - - self.__sort_on_config = weechat.config_new_option( - self.config_file, self.sorting_section, - 'sort_on_config_change', 'boolean', - 'Decides if the buffer list should be sorted when autosort configuration changes.', - '', 0, 0, 'on', 'on', 0, - '', '', '', '', '', '' - ) - - self.__debug_log = weechat.config_new_option( - self.config_file, self.sorting_section, - 'debug_log', 'boolean', - 'If enabled, print more debug messages. Not recommended for normal usage.', - '', 0, 0, 'off', 'off', 0, - '', '', '', '', '', '' - ) - - if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK: - log('Failed to load configuration file.') - - if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK: - log('Failed to write configuration file.') - - self.reload() - - def reload(self): - ''' Load configuration variables. ''' - - self.case_sensitive = weechat.config_boolean(self.__case_sensitive) - - rules_blob = weechat.config_string(self.__rules) - helpers_blob = weechat.config_string(self.__helpers) - signals_blob = weechat.config_string(self.__signals) - - self.rules = decode_rules(rules_blob) - self.helpers = decode_helpers(helpers_blob) - self.signals = signals_blob.split() - self.signal_delay = weechat.config_integer(self.__signal_delay) - self.sort_limit = weechat.config_integer(self.__sort_limit) - self.sort_on_config = weechat.config_boolean(self.__sort_on_config) - self.debug_log = weechat.config_boolean(self.__debug_log) - - def save_rules(self, run_callback = True): - ''' Save the current rules to the configuration. ''' - weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback) - - def save_helpers(self, run_callback = True): - ''' Save the current helpers to the configuration. ''' - weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback) - - -def pad(sequence, length, padding = None): - ''' Pad a list until is has a certain length. ''' - return sequence + [padding] * max(0, (length - len(sequence))) - -def log(message, buffer = 'NULL'): - weechat.prnt(buffer, 'autosort: {0}'.format(message)) - -def debug(message, buffer = 'NULL'): - if config.debug_log: - weechat.prnt(buffer, 'autosort: debug: {0}'.format(message)) - -def get_buffers(): - ''' Get a list of all the buffers in weechat. ''' - hdata = weechat.hdata_get('buffer') - buffer = weechat.hdata_get_list(hdata, "gui_buffers"); - - result = [] - while buffer: - number = weechat.hdata_integer(hdata, buffer, 'number') - result.append((number, buffer)) - buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer') - return hdata, result - -class MergedBuffers(list): - """ A list of merged buffers, possibly of size 1. """ - def __init__(self, number): - super(MergedBuffers, self).__init__() - self.number = number - -def merge_buffer_list(buffers): - ''' - Group merged buffers together. - The output is a list of MergedBuffers. - ''' - if not buffers: return [] - result = {} - for number, buffer in buffers: - if number not in result: result[number] = MergedBuffers(number) - result[number].append(buffer) - return result.values() - -def sort_buffers(hdata, buffers, rules, helpers, case_sensitive): - for merged in buffers: - for buffer in merged: - name = weechat.hdata_string(hdata, buffer, 'name') - - return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive)) - -def buffer_sort_key(rules, helpers, case_sensitive): - ''' Create a sort key function for a list of lists of merged buffers. ''' - def key(buffer): - extra_vars = {} - for helper_name, helper in sorted(helpers.items()): - expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {}) - extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded) - result = [] - for rule in rules: - expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {}) - result.append(expanded if case_sensitive else casefold(expanded)) - return result - - return key - -def merged_sort_key(rules, helpers, case_sensitive): - buffer_key = buffer_sort_key(rules, helpers, case_sensitive) - def key(merged): - best = None - for buffer in merged: - this = buffer_key(buffer) - if best is None or this < best: best = this - return best - return key - -def apply_buffer_order(buffers): - ''' Sort the buffers in weechat according to the given order. ''' - for i, buffer in enumerate(buffers): - weechat.buffer_set(buffer[0], "number", str(i + 1)) - -def split_args(args, expected, optional = 0): - ''' Split an argument string in the desired number of arguments. ''' - split = args.split(' ', expected - 1) - if (len(split) < expected): - raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split))) - return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '') - -def do_sort(verbose = False): - start = perf_counter() - - hdata, buffers = get_buffers() - buffers = merge_buffer_list(buffers) - buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive) - apply_buffer_order(buffers) - - elapsed = perf_counter() - start - if verbose: - log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) - else: - debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) - -def command_sort(buffer, command, args): - ''' Sort the buffers and print a confirmation. ''' - do_sort(True) - return weechat.WEECHAT_RC_OK - -def command_debug(buffer, command, args): - hdata, buffers = get_buffers() - buffers = merge_buffer_list(buffers) - - # Show evaluation results. - log('Individual evaluation results:') - start = perf_counter() - key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive) - results = [] - for merged in buffers: - for buffer in merged: - fullname = weechat.hdata_string(hdata, buffer, 'full_name') - results.append((fullname, key(buffer))) - elapsed = perf_counter() - start - - for fullname, result in results: - fullname = ensure_str(fullname) - result = [ensure_str(x) for x in result] - log('{0}: {1}'.format(fullname, result)) - log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed)) - - return weechat.WEECHAT_RC_OK - -def command_rule_list(buffer, command, args): - ''' Show the list of sorting rules. ''' - output = 'Sorting rules:\n' - for i, rule in enumerate(config.rules): - output += ' {0}: {1}\n'.format(i, rule) - if not len(config.rules): - output += ' No sorting rules configured.\n' - log(output ) - - return weechat.WEECHAT_RC_OK - - -def command_rule_add(buffer, command, args): - ''' Add a rule to the rule list. ''' - config.rules.append(args) - config.save_rules() - command_rule_list(buffer, command, '') - - return weechat.WEECHAT_RC_OK - - -def command_rule_insert(buffer, command, args): - ''' Insert a rule at the desired position in the rule list. ''' - index, rule = split_args(args, 2) - index = parse_int(index, 'index') - - config.rules.insert(index, rule) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_update(buffer, command, args): - ''' Update a rule in the rule list. ''' - index, rule = split_args(args, 2) - index = parse_int(index, 'index') - - config.rules[index] = rule - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_delete(buffer, command, args): - ''' Delete a rule from the rule list. ''' - index = args.strip() - index = parse_int(index, 'index') - - config.rules.pop(index) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_move(buffer, command, args): - ''' Move a rule to a new position. ''' - index_a, index_b = split_args(args, 2) - index_a = parse_int(index_a, 'index') - index_b = parse_int(index_b, 'index') - - list_move(config.rules, index_a, index_b) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_swap(buffer, command, args): - ''' Swap two rules. ''' - index_a, index_b = split_args(args, 2) - index_a = parse_int(index_a, 'index') - index_b = parse_int(index_b, 'index') - - list_swap(config.rules, index_a, index_b) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_list(buffer, command, args): - ''' Show the list of helpers. ''' - output = 'Helper variables:\n' - - width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys())) - - for name, expression in sorted(config.helpers.items()): - output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width) - if not len(config.helpers): - output += ' No helper variables configured.' - log(output) - - return weechat.WEECHAT_RC_OK - - -def command_helper_set(buffer, command, args): - ''' Add/update a helper to the helper list. ''' - name, expression = split_args(args, 2) - - config.helpers[name] = expression - config.save_helpers() - command_helper_list(buffer, command, '') - - return weechat.WEECHAT_RC_OK - -def command_helper_delete(buffer, command, args): - ''' Delete a helper from the helper list. ''' - name = args.strip() - - del config.helpers[name] - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_rename(buffer, command, args): - ''' Rename a helper to a new position. ''' - old_name, new_name = split_args(args, 2) - - try: - config.helpers[new_name] = config.helpers[old_name] - del config.helpers[old_name] - except KeyError: - raise HumanReadableError('No such helper: {0}'.format(old_name)) - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_swap(buffer, command, args): - ''' Swap two helpers. ''' - a, b = split_args(args, 2) - try: - config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b] - except KeyError as e: - raise HumanReadableError('No such helper: {0}'.format(e.args[0])) - - config.helpers.swap(index_a, index_b) - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - -def call_command(buffer, command, args, subcommands): - ''' Call a subcommand from a dictionary. ''' - subcommand, tail = pad(args.split(' ', 1), 2, '') - subcommand = subcommand.strip() - if (subcommand == ''): - child = subcommands.get(' ') - else: - command = command + [subcommand] - child = subcommands.get(subcommand) - - if isinstance(child, dict): - return call_command(buffer, command, tail, child) - elif callable(child): - return child(buffer, command, tail) - - log('{0}: command not found'.format(' '.join(command))) - return weechat.WEECHAT_RC_ERROR - -def on_signal(data, signal, signal_data): - global signal_delay_timer - global sort_queued - - # If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort. - if sort_limit_timer is not None: - if sort_queued: - debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal)) - else: - debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal)) - sort_queued = True - return weechat.WEECHAT_RC_OK - - # If the signal delay timeout is started, a signal was recently received, so ignore this signal. - if signal_delay_timer is not None: - debug('Signal {0} ignored, signal delay timeout active.'.format(signal)) - return weechat.WEECHAT_RC_OK - - # Otherwise, start the signal delay timeout. - debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay)) - weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "") - return weechat.WEECHAT_RC_OK - -def on_signal_delay_timeout(pointer, remaining_calls): - """ Called when the signal_delay_timer triggers. """ - global signal_delay_timer - global sort_limit_timer - global sort_queued - - signal_delay_timer = None - - # If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort. - if sort_limit_timer is not None: - debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.') - sort_queued = True - return weechat.WEECHAT_RC_OK - - # Time to sort! - debug('Signal delay timeout expired, starting sort.') - do_sort() - - # Start the sort limit timeout if not disabled. - if config.sort_limit > 0: - debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) - sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") - - return weechat.WEECHAT_RC_OK - -def on_sort_limit_timeout(pointer, remainin_calls): - """ Called when de sort_limit_timer triggers. """ - global sort_limit_timer - global sort_queued - - # If no signal was received during the timeout, we're done. - if not sort_queued: - debug('Sort limit timeout expired without receiving a signal.') - sort_limit_timer = None - return weechat.WEECHAT_RC_OK - - # Otherwise it's time to sort. - debug('Signal received during sort limit timeout, starting queued sort.') - do_sort() - sort_queued = False - - # Start the sort limit timeout again if not disabled. - if config.sort_limit > 0: - debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) - sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") - - return weechat.WEECHAT_RC_OK - - -def apply_config(): - # Unhook all signals and hook the new ones. - for hook in hooks: - weechat.unhook(hook) - for signal in config.signals: - hooks.append(weechat.hook_signal(signal, 'on_signal', '')) - - if config.sort_on_config: - debug('Sorting because configuration changed.') - do_sort() - -def on_config_changed(*args, **kwargs): - ''' Called whenever the configuration changes. ''' - config.reload() - apply_config() - - return weechat.WEECHAT_RC_OK - -def parse_arg(args): - if not args: return '', None - - result = '' - escaped = False - for i, c in enumerate(args): - if not escaped: - if c == '\\': - escaped = True - continue - elif c == ',': - return result, args[i+1:] - result += c - escaped = False - return result, None - -def parse_args(args, max = None): - result = [] - i = 0 - while max is None or i < max: - i += 1 - arg, args = parse_arg(args) - if arg is None: break - result.append(arg) - if args is None: break - return result, args - -def on_info_escape(pointer, name, arguments): - result = '' - for c in arguments: - if c == '\\': - result += '\\\\' - elif c == ',': - result += '\\,' - else: - result +=c - return result - -def on_info_replace(pointer, name, arguments): - arguments, rest = parse_args(arguments, 3) - if rest or len(arguments) < 3: - log('usage: ${{info:{0},old,new,text}}'.format(name)) - return '' - old, new, text = arguments - - return text.replace(old, new) - -def on_info_order(pointer, name, arguments): - arguments, rest = parse_args(arguments) - if len(arguments) < 1: - log('usage: ${{info:{0},value,first,second,third,...}}'.format(name)) - return '' - - value = arguments[0] - keys = arguments[1:] - if not keys: return '0' - - # Find the value in the keys (or '*' if we can't find it) - result = list_find(keys, value) - if result is None: result = list_find(keys, '*') - if result is None: result = len(keys) - - # Pad result with leading zero to make sure string sorting works. - width = int(math.log10(len(keys))) + 1 - return '{0:0{1}}'.format(result, width) - - -def on_autosort_command(data, buffer, args): - ''' Called when the autosort command is invoked. ''' - try: - return call_command(buffer, ['/autosort'], args, { - ' ': command_sort, - 'sort': command_sort, - 'debug': command_debug, - - 'rules': { - ' ': command_rule_list, - 'list': command_rule_list, - 'add': command_rule_add, - 'insert': command_rule_insert, - 'update': command_rule_update, - 'delete': command_rule_delete, - 'move': command_rule_move, - 'swap': command_rule_swap, - }, - 'helpers': { - ' ': command_helper_list, - 'list': command_helper_list, - 'set': command_helper_set, - 'delete': command_helper_delete, - 'rename': command_helper_rename, - 'swap': command_helper_swap, - }, - }) - except HumanReadableError as e: - log(e) - return weechat.WEECHAT_RC_ERROR - -def add_completions(completion, words): - for word in words: - weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END) - -def autosort_complete_rules(words, completion): - if len(words) == 0: - add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update']) - if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'): - add_completions(completion, map(str, range(len(config.rules)))) - if len(words) == 2 and words[0] in ('move', 'swap'): - add_completions(completion, map(str, range(len(config.rules)))) - if len(words) == 2 and words[0] in ('update'): - try: - add_completions(completion, [config.rules[int(words[1])]]) - except KeyError: pass - except ValueError: pass - else: - add_completions(completion, ['']) - return weechat.WEECHAT_RC_OK - -def autosort_complete_helpers(words, completion): - if len(words) == 0: - add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap']) - elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'): - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'swap': - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'rename': - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'set': - try: - add_completions(completion, [config.helpers[words[1]]]) - except KeyError: pass - return weechat.WEECHAT_RC_OK - -def on_autosort_complete(data, name, buffer, completion): - cmdline = weechat.buffer_get_string(buffer, "input") - cursor = weechat.buffer_get_integer(buffer, "input_pos") - prefix = cmdline[:cursor] - words = prefix.split()[1:] - - # If the current word isn't finished yet, - # ignore it for coming up with completion suggestions. - if prefix[-1] != ' ': words = words[:-1] - - if len(words) == 0: - add_completions(completion, ['debug', 'helpers', 'rules', 'sort']) - elif words[0] == 'rules': - return autosort_complete_rules(words[1:], completion) - elif words[0] == 'helpers': - return autosort_complete_helpers(words[1:], completion) - return weechat.WEECHAT_RC_OK - -command_description = r'''{*white}# General commands{reset} - -{*white}/autosort {brown}sort{reset} -Manually trigger the buffer sorting. - -{*white}/autosort {brown}debug{reset} -Show the evaluation results of the sort rules for each buffer. - - -{*white}# Sorting rule commands{reset} - -{*white}/autosort{brown} rules list{reset} -Print the list of sort rules. - -{*white}/autosort {brown}rules add {cyan}{reset} -Add a new rule at the end of the list. - -{*white}/autosort {brown}rules insert {cyan} {reset} -Insert a new rule at the given index in the list. - -{*white}/autosort {brown}rules update {cyan} {reset} -Update a rule in the list with a new expression. - -{*white}/autosort {brown}rules delete {cyan} -Delete a rule from the list. - -{*white}/autosort {brown}rules move {cyan} {reset} -Move a rule from one position in the list to another. - -{*white}/autosort {brown}rules swap {cyan} {reset} -Swap two rules in the list - - -{*white}# Helper variable commands{reset} - -{*white}/autosort {brown}helpers list -Print the list of helper variables. - -{*white}/autosort {brown}helpers set {cyan} -Add or update a helper variable with the given name. - -{*white}/autosort {brown}helpers delete {cyan} -Delete a helper variable. - -{*white}/autosort {brown}helpers rename {cyan} -Rename a helper variable. - -{*white}/autosort {brown}helpers swap {cyan} -Swap the expressions of two helper variables in the list. - - -{*white}# Info hooks{reset} -Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings. -Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}. - -Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash. - -{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset} -Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}. -Can be used to ignore certain strings when sorting by replacing them with an empty string. - -For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing". - -{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}} -Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options. -If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position. -Otherwise, any value that does not match an option is assigned the highest number available. -Can be used to easily sort buffers based on a manual sequence. - -For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers. -Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet. - -{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset} -Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash. -This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. -Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments. - -For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so: -{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}. - - -{*white}# Description -Autosort is a weechat script to automatically keep your buffers sorted. The sort -order can be customized by defining your own sort rules, but the default should -be sane enough for most people. It can also group IRC channel/private buffers -under their server buffer if you like. - -Autosort uses a stable sorting algorithm, meaning that you can manually move buffers -to change their relative order, if they sort equal with your rule set. - -{*white}# Sort rules{reset} -Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the -buffers based on evaluated result. Earlier rules will be considered first. Only -if earlier rules produced identical results is the result of the next rule -considered for sorting purposes. - -You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will -print the evaluation results of each rule for each buffer. - -{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice -versa. You will have to manually port your old rules to version 3 if you have any. - -{*white}# Helper variables{reset} -You may define helper variables for the main sort rules to keep your rules -readable. They can be used in the main sort rules as variables. For example, -a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the -string `{cyan}${{foo}}{reset}`. - -{*white}# Automatic or manual sorting{reset} -By default, autosort will automatically sort your buffer list whenever a buffer -is opened, merged, unmerged or renamed. This should keep your buffers sorted in -almost all situations. However, you may wish to change the list of signals that -cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}` -option to add or remove any signal you like. - -If you remove all signals you can still sort your buffers manually with the -`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option -`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled. - -{*white}# Recommended settings -For the best visual effect, consider setting the following options: - {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset} - -This setting allows server buffers to be sorted independently, which is -needed to create a hierarchical tree view of the server and channel buffers. - -If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree -structure with the following setting (modify to suit your need): - {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset} -''' - -command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)' - -info_replace_description = ( - 'Replace all occurrences of `pattern` with `replacement` in the string `source`. ' - 'Can be used to ignore certain strings when sorting by replacing them with an empty string. ' - 'See /help autosort for examples.' -) -info_replace_arguments = 'pattern,replacement,source' - -info_order_description = ( - 'Generate a zero-padded number that corresponds to the index of `value` in the list of options. ' - 'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. ' - 'Otherwise, any value that does not match an option is assigned the highest number available. ' - 'Can be used to easily sort buffers based on a manual sequence. ' - 'See /help autosort for examples.' -) -info_order_arguments = 'value,first,second,third,...' - -info_escape_description = ( - 'Escape commas and backslashes in `text` by prepending them with a backslash. ' - 'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. ' - 'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.' - 'See /help autosort for examples.' -) -info_escape_arguments = 'text' - - -if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): - config = Config('autosort') - - colors = { - 'default': weechat.color('default'), - 'reset': weechat.color('reset'), - 'black': weechat.color('black'), - 'red': weechat.color('red'), - 'green': weechat.color('green'), - 'brown': weechat.color('brown'), - 'yellow': weechat.color('yellow'), - 'blue': weechat.color('blue'), - 'magenta': weechat.color('magenta'), - 'cyan': weechat.color('cyan'), - 'white': weechat.color('white'), - '*default': weechat.color('*default'), - '*black': weechat.color('*black'), - '*red': weechat.color('*red'), - '*green': weechat.color('*green'), - '*brown': weechat.color('*brown'), - '*yellow': weechat.color('*yellow'), - '*blue': weechat.color('*blue'), - '*magenta': weechat.color('*magenta'), - '*cyan': weechat.color('*cyan'), - '*white': weechat.color('*white'), - } - - weechat.hook_config('autosort.*', 'on_config_changed', '') - weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '') - weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '') - weechat.hook_info('autosort_escape', info_escape_description, info_escape_arguments, 'on_info_escape', '') - weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '') - weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '') - - apply_config() diff --git a/.config/weechat/python/autoload/colorize_nicks.py b/.config/weechat/python/autoload/colorize_nicks.py deleted file mode 100644 index cb95a0d6..00000000 --- a/.config/weechat/python/autoload/colorize_nicks.py +++ /dev/null @@ -1,409 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2010 by xt -# -# 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 3 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 program. If not, see . -# - -# This script colors nicks in IRC channels in the actual message -# not just in the prefix section. -# -# -# History: -# 2020-11-29: jess -# version 28: fix ignore_tags having been broken by weechat 2.9 changes -# 2020-05-09: Sébastien Helleu -# version 27: add compatibility with new weechat_print modifier data -# (WeeChat >= 2.9) -# 2018-04-06: Joey Pabalinas -# version 26: fix freezes with too many nicks in one line -# 2018-03-18: nils_2 -# version 25: fix unable to run function colorize_config_reload_cb() -# 2017-06-20: lbeziaud -# version 24: colorize utf8 nicks -# 2017-03-01, arza -# version 23: don't colorize nicklist group names -# 2016-05-01, Simmo Saan -# version 22: invalidate cached colors on hash algorithm change -# 2015-07-28, xt -# version 21: fix problems with nicks with commas in them -# 2015-04-19, xt -# version 20: fix ignore of nicks in URLs -# 2015-04-18, xt -# version 19: new option ignore nicks in URLs -# 2015-03-03, xt -# version 18: iterate buffers looking for nicklists instead of servers -# 2015-02-23, holomorph -# version 17: fix coloring in non-channel buffers (#58) -# 2014-09-17, holomorph -# version 16: use weechat config facilities -# clean unused, minor linting, some simplification -# 2014-05-05, holomorph -# version 15: fix python2-specific re.search check -# 2013-01-29, nils_2 -# version 14: make script compatible with Python 3.x -# 2012-10-19, ldvx -# version 13: Iterate over every word to prevent incorrect colorization of -# nicks. Added option greedy_matching. -# 2012-04-28, ldvx -# version 12: added ignore_tags to avoid colorizing nicks if tags are present -# 2012-01-14, nesthib -# version 11: input_text_display hook and modifier to colorize nicks in input bar -# 2010-12-22, xt -# version 10: hook config option for updating blacklist -# 2010-12-20, xt -# version 0.9: hook new config option for weechat 0.3.4 -# 2010-11-01, nils_2 -# version 0.8: hook_modifier() added to communicate with rainbow_text -# 2010-10-01, xt -# version 0.7: changes to support non-irc-plugins -# 2010-07-29, xt -# version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com -# 2010-07-19, xt -# version 0.5: fix bug with incorrect coloring of own nick -# 2010-06-02, xt -# version 0.4: update to reflect API changes -# 2010-03-26, xt -# version 0.3: fix error with exception -# 2010-03-24, xt -# version 0.2: use ignore_channels when populating to increase performance. -# 2010-02-03, xt -# version 0.1: initial (based on ruby script by dominikh) -# -# Known issues: nicks will not get colorized if they begin with a character -# such as ~ (which some irc networks do happen to accept) - -import weechat -import re -w = weechat - -SCRIPT_NAME = "colorize_nicks" -SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "28" -SCRIPT_LICENSE = "GPL" -SCRIPT_DESC = "Use the weechat nick colors in the chat area" - -# Based on the recommendations in RFC 7613. A valid nick is composed -# of anything but " ,*?.!@". -VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@]+)' -valid_nick_re = re.compile(VALID_NICK) -ignore_channels = [] -ignore_nicks = [] - -# Dict with every nick on every channel with its color as lookup value -colored_nicks = {} - -CONFIG_FILE_NAME = "colorize_nicks" - -# config file and options -colorize_config_file = "" -colorize_config_option = {} - -def colorize_config_init(): - ''' - Initialization of configuration file. - Sections: look. - ''' - global colorize_config_file, colorize_config_option - colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, - "", "") - if colorize_config_file == "": - return - - # section "look" - section_look = weechat.config_new_section( - colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") - if section_look == "": - weechat.config_free(colorize_config_file) - return - colorize_config_option["blacklist_channels"] = weechat.config_new_option( - colorize_config_file, section_look, "blacklist_channels", - "string", "Comma separated list of channels", "", 0, 0, - "", "", 0, "", "", "", "", "", "") - colorize_config_option["blacklist_nicks"] = weechat.config_new_option( - colorize_config_file, section_look, "blacklist_nicks", - "string", "Comma separated list of nicks", "", 0, 0, - "so,root", "so,root", 0, "", "", "", "", "", "") - colorize_config_option["min_nick_length"] = weechat.config_new_option( - colorize_config_file, section_look, "min_nick_length", - "integer", "Minimum length nick to colorize", "", - 2, 20, "", "", 0, "", "", "", "", "", "") - colorize_config_option["colorize_input"] = weechat.config_new_option( - colorize_config_file, section_look, "colorize_input", - "boolean", "Whether to colorize input", "", 0, - 0, "off", "off", 0, "", "", "", "", "", "") - colorize_config_option["ignore_tags"] = weechat.config_new_option( - colorize_config_file, section_look, "ignore_tags", - "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0, - "", "", 0, "", "", "", "", "", "") - colorize_config_option["greedy_matching"] = weechat.config_new_option( - colorize_config_file, section_look, "greedy_matching", - "boolean", "If off, then use lazy matching instead", "", 0, - 0, "on", "on", 0, "", "", "", "", "", "") - colorize_config_option["match_limit"] = weechat.config_new_option( - colorize_config_file, section_look, "match_limit", - "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", - 20, 1000, "", "", 0, "", "", "", "", "", "") - colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( - colorize_config_file, section_look, "ignore_nicks_in_urls", - "boolean", "If on, don't colorize nicks inside URLs", "", 0, - 0, "off", "off", 0, "", "", "", "", "", "") - -def colorize_config_read(): - ''' Read configuration file. ''' - global colorize_config_file - return weechat.config_read(colorize_config_file) - -def colorize_nick_color(nick, my_nick): - ''' Retrieve nick color from weechat. ''' - if nick == my_nick: - return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) - else: - return w.info_get('irc_nick_color', nick) - -def colorize_cb(data, modifier, modifier_data, line): - ''' Callback that does the colorizing, and returns new line if changed ''' - - global ignore_nicks, ignore_channels, colored_nicks - - if modifier_data.startswith('0x'): - # WeeChat >= 2.9 - buffer, tags = modifier_data.split(';', 1) - else: - # WeeChat <= 2.8 - plugin, buffer_name, tags = modifier_data.split(';', 2) - buffer = w.buffer_search(plugin, buffer_name) - - channel = w.buffer_get_string(buffer, 'localvar_channel') - tags = tags.split(',') - - # Check if buffer has colorized nicks - if buffer not in colored_nicks: - return line - - if channel and channel in ignore_channels: - return line - - min_length = w.config_integer(colorize_config_option['min_nick_length']) - reset = w.color('reset') - - # Don't colorize if the ignored tag is present in message - tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',') - for tag in tags: - if tag in tag_ignores: - return line - - for words in valid_nick_re.findall(line): - nick = words[1] - # Check that nick is not ignored and longer than minimum length - if len(nick) < min_length or nick in ignore_nicks: - continue - - # If the matched word is not a known nick, we try to match the - # word without its first or last character (if not a letter). - # This is necessary as "foo:" is a valid nick, which could be - # adressed as "foo::". - if nick not in colored_nicks[buffer]: - if not nick[-1].isalpha() and not nick[0].isalpha(): - if nick[1:-1] in colored_nicks[buffer]: - nick = nick[1:-1] - elif not nick[0].isalpha(): - if nick[1:] in colored_nicks[buffer]: - nick = nick[1:] - elif not nick[-1].isalpha(): - if nick[:-1] in colored_nicks[buffer]: - nick = nick[:-1] - - # Check that nick is in the dictionary colored_nicks - if nick in colored_nicks[buffer]: - nick_color = colored_nicks[buffer][nick] - - try: - # Let's use greedy matching. Will check against every word in a line. - if w.config_boolean(colorize_config_option['greedy_matching']): - cnt = 0 - limit = w.config_integer(colorize_config_option['match_limit']) - - for word in line.split(): - cnt += 1 - assert cnt < limit - # if cnt > limit: - # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); - - if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ - word.startswith(('http://', 'https://')): - continue - - if nick in word: - # Is there a nick that contains nick and has a greater lenght? - # If so let's save that nick into var biggest_nick - biggest_nick = "" - for i in colored_nicks[buffer]: - cnt += 1 - assert cnt < limit - - if nick in i and nick != i and len(i) > len(nick): - if i in word: - # If a nick with greater len is found, and that word - # also happens to be in word, then let's save this nick - biggest_nick = i - # If there's a nick with greater len, then let's skip this - # As we will have the chance to colorize when biggest_nick - # iterates being nick. - if len(biggest_nick) > 0 and biggest_nick in word: - pass - elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: - new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) - line = line.replace(word, new_word) - - # Switch to lazy matching - else: - raise AssertionError - - except AssertionError: - # Let's use lazy matching for nick - nick_color = colored_nicks[buffer][nick] - # The two .? are in case somebody writes "nick:", "nick,", etc - # to address somebody - regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick) - match = re.search(regex, line) - if match is not None: - new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] - line = new_line - - return line - -def colorize_input_cb(data, modifier, modifier_data, line): - ''' Callback that does the colorizing in input ''' - - global ignore_nicks, ignore_channels, colored_nicks - - min_length = w.config_integer(colorize_config_option['min_nick_length']) - - if not w.config_boolean(colorize_config_option['colorize_input']): - return line - - buffer = w.current_buffer() - # Check if buffer has colorized nicks - if buffer not in colored_nicks: - return line - - channel = w.buffer_get_string(buffer, 'name') - if channel and channel in ignore_channels: - return line - - reset = w.color('reset') - - for words in valid_nick_re.findall(line): - nick = words[1] - # Check that nick is not ignored and longer than minimum length - if len(nick) < min_length or nick in ignore_nicks: - continue - if nick in colored_nicks[buffer]: - nick_color = colored_nicks[buffer][nick] - line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset)) - - return line - -def populate_nicks(*args): - ''' Fills entire dict with all nicks weechat can see and what color it has - assigned to it. ''' - global colored_nicks - - colored_nicks = {} - - buffers = w.infolist_get('buffer', '', '') - while w.infolist_next(buffers): - buffer_ptr = w.infolist_pointer(buffers, 'pointer') - my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick') - nicklist = w.infolist_get('nicklist', buffer_ptr, '') - while w.infolist_next(nicklist): - if buffer_ptr not in colored_nicks: - colored_nicks[buffer_ptr] = {} - - if w.infolist_string(nicklist, 'type') != 'nick': - continue - - nick = w.infolist_string(nicklist, 'name') - nick_color = colorize_nick_color(nick, my_nick) - - colored_nicks[buffer_ptr][nick] = nick_color - - w.infolist_free(nicklist) - - w.infolist_free(buffers) - - return w.WEECHAT_RC_OK - -def add_nick(data, signal, type_data): - ''' Add nick to dict of colored nicks ''' - global colored_nicks - - # Nicks can have , in them in some protocols - splitted = type_data.split(',') - pointer = splitted[0] - nick = ",".join(splitted[1:]) - if pointer not in colored_nicks: - colored_nicks[pointer] = {} - - my_nick = w.buffer_get_string(pointer, 'localvar_nick') - nick_color = colorize_nick_color(nick, my_nick) - - colored_nicks[pointer][nick] = nick_color - - return w.WEECHAT_RC_OK - -def remove_nick(data, signal, type_data): - ''' Remove nick from dict with colored nicks ''' - global colored_nicks - - # Nicks can have , in them in some protocols - splitted = type_data.split(',') - pointer = splitted[0] - nick = ",".join(splitted[1:]) - - if pointer in colored_nicks and nick in colored_nicks[pointer]: - del colored_nicks[pointer][nick] - - return w.WEECHAT_RC_OK - -def update_blacklist(*args): - ''' Set the blacklist for channels and nicks. ''' - global ignore_channels, ignore_nicks - ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',') - ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',') - return w.WEECHAT_RC_OK - -if __name__ == "__main__": - if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "", ""): - colorize_config_init() - colorize_config_read() - - # Run once to get data ready - update_blacklist() - populate_nicks() - - w.hook_signal('nicklist_nick_added', 'add_nick', '') - w.hook_signal('nicklist_nick_removed', 'remove_nick', '') - w.hook_modifier('weechat_print', 'colorize_cb', '') - # Hook config for changing colors - w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '') - w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '') - # Hook for working togheter with other scripts (like colorize_lines) - w.hook_modifier('colorize_nicks', 'colorize_cb', '') - # Hook for modifying input - w.hook_modifier('250|input_text_display', 'colorize_input_cb', '') - # Hook for updating blacklist (this could be improved to use fnmatch) - weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '') diff --git a/.config/weechat/python/autoload/go.py b/.config/weechat/python/autoload/go.py deleted file mode 100644 index 2ab47ed4..00000000 --- a/.config/weechat/python/autoload/go.py +++ /dev/null @@ -1,563 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2009-2014 Sébastien Helleu -# Copyright (C) 2010 m4v -# Copyright (C) 2011 stfn -# -# 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 3 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 program. If not, see . -# - -# -# History: -# -# 2019-07-11, Simmo Saan -# version 2.6: fix detection of "/input search_text_here" -# 2017-04-01, Sébastien Helleu : -# version 2.5: add option "buffer_number" -# 2017-03-02, Sébastien Helleu : -# version 2.4: fix syntax and indentation error -# 2017-02-25, Simmo Saan -# version 2.3: fix fuzzy search breaking buffer number search display -# 2016-01-28, ylambda -# version 2.2: add option "fuzzy_search" -# 2015-11-12, nils_2 -# version 2.1: fix problem with buffer short_name "weechat", using option -# "use_core_instead_weechat", see: -# https://github.com/weechat/weechat/issues/574 -# 2014-05-12, Sébastien Helleu : -# version 2.0: add help on options, replace option "sort_by_activity" by -# "sort" (add sort by name and first match at beginning of -# name and by number), PEP8 compliance -# 2012-11-26, Nei -# version 1.9: add auto_jump option to automatically go to buffer when it -# is uniquely selected -# 2012-09-17, Sébastien Helleu : -# version 1.8: fix jump to non-active merged buffers (jump with buffer name -# instead of number) -# 2012-01-03 nils_2 -# version 1.7: add option "use_core_instead_weechat" -# 2012-01-03, Sébastien Helleu : -# version 1.6: make script compatible with Python 3.x -# 2011-08-24, stfn : -# version 1.5: /go with name argument jumps directly to buffer -# Remember cursor position in buffer input -# 2011-05-31, Elián Hanisch : -# version 1.4: Sort list of buffers by activity. -# 2011-04-25, Sébastien Helleu : -# version 1.3: add info "go_running" (used by script input_lock.rb) -# 2010-11-01, Sébastien Helleu : -# version 1.2: use high priority for hooks to prevent conflict with other -# plugins/scripts (WeeChat >= 0.3.4 only) -# 2010-03-25, Elián Hanisch : -# version 1.1: use a space to match the end of a string -# 2009-11-16, Sébastien Helleu : -# version 1.0: add new option to display short names -# 2009-06-15, Sébastien Helleu : -# version 0.9: fix typo in /help go with command /key -# 2009-05-16, Sébastien Helleu : -# version 0.8: search buffer by number, fix bug when window is split -# 2009-05-03, Sébastien Helleu : -# version 0.7: eat tab key (do not complete input, just move buffer -# pointer) -# 2009-05-02, Sébastien Helleu : -# version 0.6: sync with last API changes -# 2009-03-22, Sébastien Helleu : -# version 0.5: update modifier signal name for input text display, -# fix arguments for function string_remove_color -# 2009-02-18, Sébastien Helleu : -# version 0.4: do not hook command and init options if register failed -# 2009-02-08, Sébastien Helleu : -# version 0.3: case insensitive search for buffers names -# 2009-02-08, Sébastien Helleu : -# version 0.2: add help about Tab key -# 2009-02-08, Sébastien Helleu : -# version 0.1: initial release -# - -""" -Quick jump to buffers. -(this script requires WeeChat 0.3.0 or newer) -""" - -from __future__ import print_function - -SCRIPT_NAME = 'go' -SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.6' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Quick jump to buffers' - -SCRIPT_COMMAND = 'go' - -IMPORT_OK = True - -try: - import weechat -except ImportError: - print('This script must be run under WeeChat.') - print('Get WeeChat now at: http://www.weechat.org/') - IMPORT_OK = False - -import re - -# script options -SETTINGS = { - 'color_number': ( - 'yellow,magenta', - 'color for buffer number (not selected)'), - 'color_number_selected': ( - 'yellow,red', - 'color for selected buffer number'), - 'color_name': ( - 'black,cyan', - 'color for buffer name (not selected)'), - 'color_name_selected': ( - 'black,brown', - 'color for a selected buffer name'), - 'color_name_highlight': ( - 'red,cyan', - 'color for highlight in buffer name (not selected)'), - 'color_name_highlight_selected': ( - 'red,brown', - 'color for highlight in a selected buffer name'), - 'message': ( - 'Go to: ', - 'message to display before list of buffers'), - 'short_name': ( - 'off', - 'display and search in short names instead of buffer name'), - 'sort': ( - 'number,beginning', - 'comma-separated list of keys to sort buffers ' - '(the order is important, sorts are performed in the given order): ' - 'name = sort by name (or short name), ', - 'hotlist = sort by hotlist order, ' - 'number = first match a buffer number before digits in name, ' - 'beginning = first match at beginning of names (or short names); ' - 'the default sort of buffers is by numbers'), - 'use_core_instead_weechat': ( - 'off', - 'use name "core" instead of "weechat" for core buffer'), - 'auto_jump': ( - 'off', - 'automatically jump to buffer when it is uniquely selected'), - 'fuzzy_search': ( - 'off', - 'search buffer matches using approximation'), - 'buffer_number': ( - 'on', - 'display buffer number'), -} - -# hooks management -HOOK_COMMAND_RUN = { - 'input': ('/input *', 'go_command_run_input'), - 'buffer': ('/buffer *', 'go_command_run_buffer'), - 'window': ('/window *', 'go_command_run_window'), -} -hooks = {} - -# input before command /go (we'll restore it later) -saved_input = '' -saved_input_pos = 0 - -# last user input (if changed, we'll update list of matching buffers) -old_input = None - -# matching buffers -buffers = [] -buffers_pos = 0 - - -def go_option_enabled(option): - """Checks if a boolean script option is enabled or not.""" - return weechat.config_string_to_boolean(weechat.config_get_plugin(option)) - - -def go_info_running(data, info_name, arguments): - """Returns "1" if go is running, otherwise "0".""" - return '1' if 'modifier' in hooks else '0' - - -def go_unhook_one(hook): - """Unhook something hooked by this script.""" - global hooks - if hook in hooks: - weechat.unhook(hooks[hook]) - del hooks[hook] - - -def go_unhook_all(): - """Unhook all.""" - go_unhook_one('modifier') - for hook in HOOK_COMMAND_RUN: - go_unhook_one(hook) - - -def go_hook_all(): - """Hook command_run and modifier.""" - global hooks - priority = '' - version = weechat.info_get('version_number', '') or 0 - # use high priority for hook to prevent conflict with other plugins/scripts - # (WeeChat >= 0.3.4 only) - if int(version) >= 0x00030400: - priority = '2000|' - for hook, value in HOOK_COMMAND_RUN.items(): - if hook not in hooks: - hooks[hook] = weechat.hook_command_run( - '%s%s' % (priority, value[0]), - value[1], '') - if 'modifier' not in hooks: - hooks['modifier'] = weechat.hook_modifier( - 'input_text_display_with_cursor', 'go_input_modifier', '') - - -def go_start(buf): - """Start go on buffer.""" - global saved_input, saved_input_pos, old_input, buffers_pos - go_hook_all() - saved_input = weechat.buffer_get_string(buf, 'input') - saved_input_pos = weechat.buffer_get_integer(buf, 'input_pos') - weechat.buffer_set(buf, 'input', '') - old_input = None - buffers_pos = 0 - - -def go_end(buf): - """End go on buffer.""" - global saved_input, saved_input_pos, old_input - go_unhook_all() - weechat.buffer_set(buf, 'input', saved_input) - weechat.buffer_set(buf, 'input_pos', str(saved_input_pos)) - old_input = None - - -def go_match_beginning(buf, string): - """Check if a string matches the beginning of buffer name/short name.""" - if not string: - return False - esc_str = re.escape(string) - if re.search(r'^#?' + esc_str, buf['name']) \ - or re.search(r'^#?' + esc_str, buf['short_name']): - return True - return False - - -def go_match_fuzzy(name, string): - """Check if string matches name using approximation.""" - if not string: - return False - - name_len = len(name) - string_len = len(string) - - if string_len > name_len: - return False - if name_len == string_len: - return name == string - - # Attempt to match all chars somewhere in name - prev_index = -1 - for i, char in enumerate(string): - index = name.find(char, prev_index+1) - if index == -1: - return False - prev_index = index - return True - - -def go_now(buf, args): - """Go to buffer specified by args.""" - listbuf = go_matching_buffers(args) - if not listbuf: - return - - # prefer buffer that matches at beginning (if option is enabled) - if 'beginning' in weechat.config_get_plugin('sort').split(','): - for index in range(len(listbuf)): - if go_match_beginning(listbuf[index], args): - weechat.command(buf, - '/buffer ' + str(listbuf[index]['full_name'])) - return - - # jump to first buffer in matching buffers by default - weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name'])) - - -def go_cmd(data, buf, args): - """Command "/go": just hook what we need.""" - global hooks - if args: - go_now(buf, args) - elif 'modifier' in hooks: - go_end(buf) - else: - go_start(buf) - return weechat.WEECHAT_RC_OK - - -def go_matching_buffers(strinput): - """Return a list with buffers matching user input.""" - global buffers_pos - listbuf = [] - if len(strinput) == 0: - buffers_pos = 0 - strinput = strinput.lower() - infolist = weechat.infolist_get('buffer', '', '') - while weechat.infolist_next(infolist): - short_name = weechat.infolist_string(infolist, 'short_name') - if go_option_enabled('short_name'): - name = weechat.infolist_string(infolist, 'short_name') - else: - name = weechat.infolist_string(infolist, 'name') - if name == 'weechat' \ - and go_option_enabled('use_core_instead_weechat') \ - and weechat.infolist_string(infolist, 'plugin_name') == 'core': - name = 'core' - number = weechat.infolist_integer(infolist, 'number') - full_name = weechat.infolist_string(infolist, 'full_name') - if not full_name: - full_name = '%s.%s' % ( - weechat.infolist_string(infolist, 'plugin_name'), - weechat.infolist_string(infolist, 'name')) - pointer = weechat.infolist_pointer(infolist, 'pointer') - matching = name.lower().find(strinput) >= 0 - if not matching and strinput[-1] == ' ': - matching = name.lower().endswith(strinput.strip()) - if not matching and go_option_enabled('fuzzy_search'): - matching = go_match_fuzzy(name.lower(), strinput) - if not matching and strinput.isdigit(): - matching = str(number).startswith(strinput) - if len(strinput) == 0 or matching: - listbuf.append({ - 'number': number, - 'short_name': short_name, - 'name': name, - 'full_name': full_name, - 'pointer': pointer, - }) - weechat.infolist_free(infolist) - - # sort buffers - hotlist = [] - infolist = weechat.infolist_get('hotlist', '', '') - while weechat.infolist_next(infolist): - hotlist.append( - weechat.infolist_pointer(infolist, 'buffer_pointer')) - weechat.infolist_free(infolist) - last_index_hotlist = len(hotlist) - - def _sort_name(buf): - """Sort buffers by name (or short name).""" - return buf['name'] - - def _sort_hotlist(buf): - """Sort buffers by hotlist order.""" - try: - return hotlist.index(buf['pointer']) - except ValueError: - # not in hotlist, always last. - return last_index_hotlist - - def _sort_match_number(buf): - """Sort buffers by match on number.""" - return 0 if str(buf['number']) == strinput else 1 - - def _sort_match_beginning(buf): - """Sort buffers by match at beginning.""" - return 0 if go_match_beginning(buf, strinput) else 1 - - funcs = { - 'name': _sort_name, - 'hotlist': _sort_hotlist, - 'number': _sort_match_number, - 'beginning': _sort_match_beginning, - } - - for key in weechat.config_get_plugin('sort').split(','): - if key in funcs: - listbuf = sorted(listbuf, key=funcs[key]) - - if not strinput: - index = [i for i, buf in enumerate(listbuf) - if buf['pointer'] == weechat.current_buffer()] - if index: - buffers_pos = index[0] - - return listbuf - - -def go_buffers_to_string(listbuf, pos, strinput): - """Return string built with list of buffers found (matching user input).""" - string = '' - strinput = strinput.lower() - for i in range(len(listbuf)): - selected = '_selected' if i == pos else '' - buffer_name = listbuf[i]['name'] - index = buffer_name.lower().find(strinput) - if index >= 0: - index2 = index + len(strinput) - name = '%s%s%s%s%s' % ( - buffer_name[:index], - weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)), - buffer_name[index:index2], - weechat.color(weechat.config_get_plugin( - 'color_name' + selected)), - buffer_name[index2:]) - elif go_option_enabled("fuzzy_search") and \ - go_match_fuzzy(buffer_name.lower(), strinput): - name = "" - prev_index = -1 - for char in strinput.lower(): - index = buffer_name.lower().find(char, prev_index+1) - if prev_index < 0: - name += buffer_name[:index] - name += weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)) - if prev_index >= 0 and index > prev_index+1: - name += weechat.color(weechat.config_get_plugin( - 'color_name' + selected)) - name += buffer_name[prev_index+1:index] - name += weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)) - name += buffer_name[index] - prev_index = index - - name += weechat.color(weechat.config_get_plugin( - 'color_name' + selected)) - name += buffer_name[prev_index+1:] - else: - name = buffer_name - string += ' ' - if go_option_enabled('buffer_number'): - string += '%s%s' % ( - weechat.color(weechat.config_get_plugin( - 'color_number' + selected)), - str(listbuf[i]['number'])) - string += '%s%s%s' % ( - weechat.color(weechat.config_get_plugin( - 'color_name' + selected)), - name, - weechat.color('reset')) - return ' ' + string if string else '' - - -def go_input_modifier(data, modifier, modifier_data, string): - """This modifier is called when input text item is built by WeeChat. - - This is commonly called after changes in input or cursor move: it builds - a new input with prefix ("Go to:"), and suffix (list of buffers found). - """ - global old_input, buffers, buffers_pos - if modifier_data != weechat.current_buffer(): - return '' - names = '' - new_input = weechat.string_remove_color(string, '') - new_input = new_input.lstrip() - if old_input is None or new_input != old_input: - old_buffers = buffers - buffers = go_matching_buffers(new_input) - if buffers != old_buffers and len(new_input) > 0: - if len(buffers) == 1 and go_option_enabled('auto_jump'): - weechat.command(modifier_data, '/wait 1ms /input return') - buffers_pos = 0 - old_input = new_input - names = go_buffers_to_string(buffers, buffers_pos, new_input.strip()) - return weechat.config_get_plugin('message') + string + names - - -def go_command_run_input(data, buf, command): - """Function called when a command "/input xxx" is run.""" - global buffers, buffers_pos - if command.startswith('/input search_text') or command.startswith('/input jump'): - # search text or jump to another buffer is forbidden now - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input complete_next': - # choose next buffer in list - buffers_pos += 1 - if buffers_pos >= len(buffers): - buffers_pos = 0 - weechat.hook_signal_send('input_text_changed', - weechat.WEECHAT_HOOK_SIGNAL_STRING, '') - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input complete_previous': - # choose previous buffer in list - buffers_pos -= 1 - if buffers_pos < 0: - buffers_pos = len(buffers) - 1 - weechat.hook_signal_send('input_text_changed', - weechat.WEECHAT_HOOK_SIGNAL_STRING, '') - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input return': - # switch to selected buffer (if any) - go_end(buf) - if len(buffers) > 0: - weechat.command( - buf, '/buffer ' + str(buffers[buffers_pos]['full_name'])) - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - -def go_command_run_buffer(data, buf, command): - """Function called when a command "/buffer xxx" is run.""" - return weechat.WEECHAT_RC_OK_EAT - - -def go_command_run_window(data, buf, command): - """Function called when a command "/window xxx" is run.""" - return weechat.WEECHAT_RC_OK_EAT - - -def go_unload_script(): - """Function called when script is unloaded.""" - go_unhook_all() - return weechat.WEECHAT_RC_OK - - -def go_main(): - """Entry point.""" - if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, - 'go_unload_script', ''): - return - weechat.hook_command( - SCRIPT_COMMAND, - 'Quick jump to buffers', '[name]', - 'name: directly jump to buffer by name (without argument, list is ' - 'displayed)\n\n' - 'You can bind command to a key, for example:\n' - ' /key bind meta-g /go\n\n' - 'You can use completion key (commonly Tab and shift-Tab) to select ' - 'next/previous buffer in list.', - '%(buffers_names)', - 'go_cmd', '') - - # set default settings - version = weechat.info_get('version_number', '') or 0 - for option, value in SETTINGS.items(): - if not weechat.config_is_set_plugin(option): - weechat.config_set_plugin(option, value[0]) - if int(version) >= 0x00030500: - weechat.config_set_desc_plugin( - option, '%s (default: "%s")' % (value[1], value[0])) - weechat.hook_info('go_running', - 'Return "1" if go is running, otherwise "0"', - '', - 'go_info_running', '') - - -if __name__ == "__main__" and IMPORT_OK: - go_main() diff --git a/.config/weechat/python/autoload/styurl.py b/.config/weechat/python/autoload/styurl.py deleted file mode 100644 index 69716505..00000000 --- a/.config/weechat/python/autoload/styurl.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2019 Cole Helbling -# -# 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 3 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 program. If not, see . -# - -# Changelog: -# 2019-12-14, Cole Helbling -# version 1.0: initial release - -SCRIPT_NAME = "styurl" -SCRIPT_AUTHOR = "Cole Helbling " -SCRIPT_VERSION = "1.0" -SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Style URLs with a Python regex" - -import_ok = True -try: - import weechat as w -except ImportError: - print("This script must be run under WeeChat.") - print("Get WeeChat now at: https://weechat.org") - import_ok = False - -try: - import re -except ImportError as message: - print("Missing package for %s: %s" % (SCRIPT_NAME, message)) - import_ok = False - -# https://mathiasbynens.be/demo/url-regex -# If you don't want to create your own regex, see the above link for options or -# ideas on creating your own - -styurl_settings = { - "buffer_type": ( - "formatted", - "the type of buffers to run on (options are \"formatted\", \"free\", " - "or \"*\" for both)" - ), - "format": ( - "${color:*_32}", - "the style that should be applied to the URL" - "(evaluated, see /help eval)" - ), - "ignored_buffers": ( - "core.weechat,python.grep", - "comma-separated list of buffers to ignore URLs in " - "(full name like \"irc.freenode.#alacritty\")" - ), - "ignored_tags": ( - "irc_quit,irc_join", - "comma-separated list of tags to ignore URLs from" - ), - "regex": ( - r"((?:https?|ftp)://[^\s/$.?#].\S*)", - "the URL-parsing regex using Python syntax " - "(make sure capturing group 1 is the full URL)" - ), -} - -line_hook = None - - -def styurl_line_cb(data, line): - """ - Callback called when a line is displayed. - This parses the message for any URLs and styles them according to - styurl_settings["format"]. - """ - global styurl_settings - - # Don't style the line if it's not going to be displayed... duh - if line["displayed"] != "1": - return line - - tags = line["tags"].split(',') - ignored_tags = styurl_settings["ignored_tags"] - - # Ignore specified message tags - if ignored_tags: - if any(tag in tags for tag in ignored_tags.split(',')): - return line - - bufname = line["buffer_name"] - ignored_buffers = styurl_settings["ignored_buffers"] - - # Ignore specified buffers - if ignored_buffers and bufname in ignored_buffers.split(','): - return line - - message = line["message"] - - # TODO: enforce presence of a properly-formatted color object at - # styurl_settings["format"] (eval object would also be valid, if it eval'd - # to a color) - - regex = re.compile(styurl_settings["regex"]) - url_style = w.string_eval_expression(styurl_settings["format"], {}, {}, {}) - reset = w.color("reset") - - # Search for URLs and surround them with the defined URL styling - formatted = regex.sub(r"%s\1%s" % (url_style, reset), message) - line["message"] = line["message"].replace(message, formatted) - - return line - - -def styurl_config_cb(data, option, value): - """Callback called when a script option is changed.""" - global styurl_settings, line_hook - - pos = option.rfind('.') - if pos > 0: - name = option[pos+1:] - if name in styurl_settings: - # Changing the buffer target requires us to re-hook to prevent - # obsolete buffer types from getting styled - if name == "buffer_type": - if value in ("free", "formatted", "*"): - w.unhook(line_hook) - line_hook = w.hook_line(value, "", "", "styurl_line_cb", - "") - else: - # Don't change buffer type if it is invalid - w.prnt("", SCRIPT_NAME + ": Invalid buffer type: '%s', " - "not changing." % value) - w.config_set_plugin(name, styurl_settings[name]) - return w.WEECHAT_RC_ERROR - - styurl_settings[name] = value - - return w.WEECHAT_RC_OK - - -def styurl_unload_cb(): - """Callback called when the script is unloaded.""" - global line_hook - - w.unhook(line_hook) - del line_hook - return w.WEECHAT_RC_OK - - -if __name__ == "__main__" and import_ok: - if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "styurl_unload_cb", ""): - - version = w.info_get("version_number", "") or 0 - - for option, value in styurl_settings.items(): - if w.config_is_set_plugin(option): - styurl_settings[option] = w.config_get_plugin(option) - else: - w.config_set_plugin(option, value[0]) - styurl_settings[option] = value[0] - if int(version) >= 0x00030500: - w.config_set_desc_plugin(option, "%s (default: \"%s\")" - % (value[1], value[0])) - - w.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", - "styurl_config_cb", "") - - # Style URLs - line_hook = w.hook_line(styurl_settings["buffer_type"], "", "", - "styurl_line_cb", "") diff --git a/.config/weechat/python/autoload/vimode.py b/.config/weechat/python/autoload/vimode.py deleted file mode 100644 index b1e66410..00000000 --- a/.config/weechat/python/autoload/vimode.py +++ /dev/null @@ -1,1884 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2014 Germain Z. -# -# 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 3 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 program. If not, see . -# - -# -# Add vi/vim-like modes to WeeChat. -# - - -import csv -import json -import os -import re -import subprocess -try: - from StringIO import StringIO -except ImportError: - from io import StringIO -import time - -import weechat - - -# Script info. -# ============ - -SCRIPT_NAME = "vimode" -SCRIPT_AUTHOR = "GermainZ " -SCRIPT_VERSION = "0.8.1" -SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = ("Add vi/vim-like modes and keybindings to WeeChat.") - - -# Global variables. -# ================= - -# General. -# -------- - -# Halp! Halp! Halp! -GITHUB_BASE = "https://github.com/GermainZ/weechat-vimode/blob/master/" -README_URL = GITHUB_BASE + "README.md" -FAQ_KEYBINDINGS = GITHUB_BASE + "FAQ.md#problematic-key-bindings" -FAQ_ESC = GITHUB_BASE + "FAQ.md#esc-key-not-being-detected-instantly" - -# Holds the text of the tab-completions for the command-line mode. -cmd_compl_text = "" -# Holds the original text of the command-line mode, used for completion. -cmd_text_orig = None -# Index of current suggestion, used for completion. -cmd_compl_pos = 0 -# Used for command-line mode history. -cmd_history = [] -cmd_history_index = 0 -# Used to store the content of the input line when going into COMMAND mode. -input_line_backup = {} -# Mode we're in. One of INSERT, NORMAL, REPLACE, COMMAND or SEARCH. -# SEARCH is only used if search_vim is enabled. -mode = "INSERT" -# Holds normal commands (e.g. "dd"). -vi_buffer = "" -# See `cb_key_combo_default()`. -esc_pressed = 0 -# See `cb_key_pressed()`. -last_signal_time = 0 -# See `start_catching_keys()` for more info. -catching_keys_data = {'amount': 0} -# Used for ; and , to store the last f/F/t/T motion. -last_search_motion = {'motion': None, 'data': None} -# Used for undo history. -undo_history = {} -undo_history_index = {} -# Holds mode colors (loaded from vimode_settings). -mode_colors = {} - -# Script options. -vimode_settings = { - 'no_warn': ("off", ("don't warn about problematic keybindings and " - "tmux/screen")), - 'copy_clipboard_cmd': ("xclip -selection c", - ("command used to copy to clipboard; must read " - "input from stdin")), - 'paste_clipboard_cmd': ("xclip -selection c -o", - ("command used to paste clipboard; must output " - "content to stdout")), - 'imap_esc': ("", ("use alternate mapping to enter Normal mode while in " - "Insert mode; having it set to 'jk' is similar to " - "`:imap jk ` in vim")), - 'imap_esc_timeout': ("1000", ("time in ms to wait for the imap_esc " - "sequence to complete")), - 'search_vim': ("off", ("allow n/N usage after searching (requires an extra" - " to return to normal mode)")), - 'user_mappings': ("", ("see the `:nmap` command in the README for more " - "info; please do not modify this field manually " - "unless you know what you're doing")), - 'mode_indicator_prefix': ("", "prefix for the bar item mode_indicator"), - 'mode_indicator_suffix': ("", "suffix for the bar item mode_indicator"), - 'mode_indicator_normal_color': ("white", - "color for mode indicator in Normal mode"), - 'mode_indicator_normal_color_bg': ("gray", - ("background color for mode indicator " - "in Normal mode")), - 'mode_indicator_insert_color': ("white", - "color for mode indicator in Insert mode"), - 'mode_indicator_insert_color_bg': ("blue", - ("background color for mode indicator " - "in Insert mode")), - 'mode_indicator_replace_color': ("white", - "color for mode indicator in Replace mode"), - 'mode_indicator_replace_color_bg': ("red", - ("background color for mode indicator " - "in Replace mode")), - 'mode_indicator_cmd_color': ("white", - "color for mode indicator in Command mode"), - 'mode_indicator_cmd_color_bg': ("cyan", - ("background color for mode indicator in " - "Command mode")), - 'mode_indicator_search_color': ("white", - "color for mode indicator in Search mode"), - 'mode_indicator_search_color_bg': ("magenta", - ("background color for mode indicator " - "in Search mode")), - 'line_number_prefix': ("", "prefix for line numbers"), - 'line_number_suffix': (" ", "suffix for line numbers") -} - - -# Regex patterns. -# --------------- - -WHITESPACE = re.compile(r"\s") -IS_KEYWORD = re.compile(r"[a-zA-Z0-9_@À-ÿ]") -REGEX_MOTION_LOWERCASE_W = re.compile(r"\b\S|(?<=\s)\S") -REGEX_MOTION_UPPERCASE_W = re.compile(r"(?<=\s)\S") -REGEX_MOTION_UPPERCASE_E = re.compile(r"\S(?!\S)") -REGEX_MOTION_UPPERCASE_B = REGEX_MOTION_UPPERCASE_E -REGEX_MOTION_G_UPPERCASE_E = REGEX_MOTION_UPPERCASE_W -REGEX_MOTION_CARRET = re.compile(r"\S") -REGEX_INT = r"[0-9]" -REGEX_MAP_KEYS_1 = { - re.compile("<([^>]*-)Left>", re.IGNORECASE): '<\\1\x01[[D>', - re.compile("<([^>]*-)Right>", re.IGNORECASE): '<\\1\x01[[C>', - re.compile("<([^>]*-)Up>", re.IGNORECASE): '<\\1\x01[[A>', - re.compile("<([^>]*-)Down>", re.IGNORECASE): '<\\1\x01[[B>', - re.compile("", re.IGNORECASE): '\x01[[D', - re.compile("", re.IGNORECASE): '\x01[[C', - re.compile("", re.IGNORECASE): '\x01[[A', - re.compile("", re.IGNORECASE): '\x01[[B' -} -REGEX_MAP_KEYS_2 = { - re.compile(r"]*)>", re.IGNORECASE): '\x01\\1', - re.compile(r"]*)>", re.IGNORECASE): '\x01[\\1' -} - -# Regex used to detect problematic keybindings. -# For example: meta-wmeta-s is bound by default to ``/window swap``. -# If the user pressed Esc-w, WeeChat will detect it as meta-w and will not -# send any signal to `cb_key_combo_default()` just yet, since it's the -# beginning of a known key combo. -# Instead, `cb_key_combo_default()` will receive the Esc-ws signal, which -# becomes "ws" after removing the Esc part, and won't know how to handle it. -REGEX_PROBLEMATIC_KEYBINDINGS = re.compile(r"meta-\w(meta|ctrl)") - - -# Vi commands. -# ------------ - -def cmd_nmap(args): - """Add a user-defined key mapping. - - Some (but not all) vim-like key codes are supported to simplify things for - the user: , , , , and . - - See Also: - `cmd_unmap()`. - """ - args = args.strip() - if not args: - mappings = vimode_settings['user_mappings'] - if mappings: - weechat.prnt("", "User-defined key mappings:") - for key, mapping in mappings.items(): - weechat.prnt("", "{} -> {}".format(key, mapping)) - else: - weechat.prnt("", "nmap: no mapping found.") - elif not " " in args: - weechat.prnt("", "nmap syntax -> :nmap {lhs} {rhs}") - else: - key, mapping = args.split(" ", 1) - # First pass of replacements. We perform two passes as a simple way to - # avoid incorrect replacements due to dictionaries not being - # insertion-ordered prior to Python 3.7. - for regex, repl in REGEX_MAP_KEYS_1.items(): - key = regex.sub(repl, key) - mapping = regex.sub(repl, mapping) - # Second pass of replacements. - for regex, repl in REGEX_MAP_KEYS_2.items(): - key = regex.sub(repl, key) - mapping = regex.sub(repl, mapping) - mappings = vimode_settings['user_mappings'] - mappings[key] = mapping - weechat.config_set_plugin('user_mappings', json.dumps(mappings)) - vimode_settings['user_mappings'] = mappings - -def cmd_nunmap(args): - """Remove a user-defined key mapping. - - See Also: - `cmd_map()`. - """ - args = args.strip() - if not args: - weechat.prnt("", "nunmap syntax -> :unmap {lhs}") - else: - key = args - for regex, repl in REGEX_MAP_KEYS_1.items(): - key = regex.sub(repl, key) - for regex, repl in REGEX_MAP_KEYS_2.items(): - key = regex.sub(repl, key) - mappings = vimode_settings['user_mappings'] - if key in mappings: - del mappings[key] - weechat.config_set_plugin('user_mappings', json.dumps(mappings)) - vimode_settings['user_mappings'] = mappings - else: - weechat.prnt("", "nunmap: No such mapping") - -# See Also: `cb_exec_cmd()`. -VI_COMMAND_GROUPS = {('h', 'help'): "/help", - ('qa', 'qall', 'quita', 'quitall'): "/exit", - ('q', 'quit'): "/close", - ('w', 'write'): "/save", - ('bN', 'bNext', 'bp', 'bprevious'): "/buffer -1", - ('bn', 'bnext'): "/buffer +1", - ('bd', 'bdel', 'bdelete'): "/close", - ('b#',): "/input jump_last_buffer_displayed", - ('b', 'bu', 'buf', 'buffer'): "/buffer", - ('sp', 'split'): "/window splith", - ('vs', 'vsplit'): "/window splitv", - ('nm', 'nmap'): cmd_nmap, - ('nun', 'nunmap'): cmd_nunmap} - -VI_COMMANDS = dict() -for T, v in VI_COMMAND_GROUPS.items(): - VI_COMMANDS.update(dict.fromkeys(T, v)) - - -# Vi operators. -# ------------- - -# Each operator must have a corresponding function, called "operator_X" where -# X is the operator. For example: `operator_c()`. -VI_OPERATORS = ["c", "d", "y"] - - -# Vi motions. -# ----------- - -# Vi motions. Each motion must have a corresponding function, called -# "motion_X" where X is the motion (e.g. `motion_w()`). -# See Also: `SPECIAL_CHARS`. -VI_MOTIONS = ["w", "e", "b", "^", "$", "h", "l", "W", "E", "B", "f", "F", "t", - "T", "ge", "gE", "0"] - -# Special characters for motions. The corresponding function's name is -# converted before calling. For example, "^" will call `motion_carret` instead -# of `motion_^` (which isn't allowed because of illegal characters). -SPECIAL_CHARS = {'^': "carret", - '$': "dollar"} - - -# Methods for vi operators, motions and key bindings. -# =================================================== - -# Documented base examples: -# ------------------------- - -def operator_base(buf, input_line, pos1, pos2, overwrite): - """Operator method example. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - pos1 (int): the starting position of the motion. - pos2 (int): the ending position of the motion. - overwrite (bool, optional): whether the character at the cursor's new - position should be overwritten or not (for inclusive motions). - Defaults to False. - - Notes: - Should be called "operator_X", where X is the operator, and defined in - `VI_OPERATORS`. - Must perform actions (e.g. modifying the input line) on its own, - using the WeeChat API. - - See Also: - For additional examples, see `operator_d()` and - `operator_y()`. - """ - # Get start and end positions. - start = min(pos1, pos2) - end = max(pos1, pos2) - # Print the text the operator should go over. - weechat.prnt("", "Selection: %s" % input_line[start:end]) - -def motion_base(input_line, cur, count): - """Motion method example. - - Args: - input_line (str): the content of the input line. - cur (int): the position of the cursor. - count (int): the amount of times to multiply or iterate the action. - - Returns: - A tuple containing three values: - int: the new position of the cursor. - bool: True if the motion is inclusive, False otherwise. - bool: True if the motion is catching, False otherwise. - See `start_catching_keys()` for more info on catching motions. - - Notes: - Should be called "motion_X", where X is the motion, and defined in - `VI_MOTIONS`. - Must not modify the input line directly. - - See Also: - For additional examples, see `motion_w()` (normal motion) and - `motion_f()` (catching motion). - """ - # Find (relative to cur) position of next number. - pos = get_pos(input_line, REGEX_INT, cur, True, count) - # Return the new (absolute) cursor position. - # This motion is exclusive, so overwrite is False. - return cur + pos, False - -def key_base(buf, input_line, cur, count): - """Key method example. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - cur (int): the position of the cursor. - count (int): the amount of times to multiply or iterate the action. - - Notes: - Should be called `key_X`, where X represents the key(s), and defined - in `VI_KEYS`. - Must perform actions on its own (using the WeeChat API). - - See Also: - For additional examples, see `key_a()` (normal key) and - `key_r()` (catching key). - """ - # Key was pressed. Go to Insert mode (similar to "i"). - set_mode("INSERT") - - -# Operators: -# ---------- - -def operator_d(buf, input_line, pos1, pos2, overwrite=False): - """Delete text from `pos1` to `pos2` from the input line. - - If `overwrite` is set to True, the character at the cursor's new position - is removed as well (the motion is inclusive). - - See Also: - `operator_base()`. - """ - start = min(pos1, pos2) - end = max(pos1, pos2) - if overwrite: - end += 1 - input_line = list(input_line) - del input_line[start:end] - input_line = "".join(input_line) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, pos1) - -def operator_c(buf, input_line, pos1, pos2, overwrite=False): - """Delete text from `pos1` to `pos2` from the input and enter Insert mode. - - If `overwrite` is set to True, the character at the cursor's new position - is removed as well (the motion is inclusive.) - - See Also: - `operator_base()`. - """ - operator_d(buf, input_line, pos1, pos2, overwrite) - set_mode("INSERT") - -def operator_y(buf, input_line, pos1, pos2, _): - """Yank text from `pos1` to `pos2` from the input line. - - See Also: - `operator_base()`. - """ - start = min(pos1, pos2) - end = max(pos1, pos2) - cmd = vimode_settings['copy_clipboard_cmd'] - proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE) - proc.communicate(input=input_line[start:end].encode()) - - -# Motions: -# -------- - -def motion_0(input_line, cur, count): - """Go to the first character of the line. - - See Also; - `motion_base()`. - """ - return 0, False, False - -def motion_w(input_line, cur, count): - """Go `count` words forward and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_LOWERCASE_W, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, False, False - -def motion_W(input_line, cur, count): - """Go `count` WORDS forward and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_UPPERCASE_W, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, False, False - -def motion_e(input_line, cur, count): - """Go to the end of `count` words and return position. - - See Also: - `motion_base()`. - """ - for _ in range(max(1, count)): - found = False - pos = cur - for pos in range(cur + 1, len(input_line) - 1): - # Whitespace, keep going. - if WHITESPACE.match(input_line[pos]): - pass - # End of sequence made from 'iskeyword' characters only, - # or end of sequence made from non 'iskeyword' characters only. - elif ((IS_KEYWORD.match(input_line[pos]) and - (not IS_KEYWORD.match(input_line[pos + 1]) or - WHITESPACE.match(input_line[pos + 1]))) or - (not IS_KEYWORD.match(input_line[pos]) and - (IS_KEYWORD.match(input_line[pos + 1]) or - WHITESPACE.match(input_line[pos + 1])))): - found = True - cur = pos - break - # We're at the character before the last and we still found nothing. - # Go to the last character. - if not found: - cur = pos + 1 - return cur, True, False - -def motion_E(input_line, cur, count): - """Go to the end of `count` WORDS and return cusor position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_UPPERCASE_E, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, True, False - -def motion_b(input_line, cur, count): - """Go `count` words backwards and return position. - - See Also: - `motion_base()`. - """ - # "b" is just "e" on inverted data (e.g. "olleH" instead of "Hello"). - pos_inv = motion_e(input_line[::-1], len(input_line) - cur - 1, count)[0] - pos = len(input_line) - pos_inv - 1 - return pos, True, False - -def motion_B(input_line, cur, count): - """Go `count` WORDS backwards and return position. - - See Also: - `motion_base()`. - """ - new_cur = len(input_line) - cur - pos = get_pos(input_line[::-1], REGEX_MOTION_UPPERCASE_B, new_cur, - count=count) - if pos == -1: - return 0, False, False - pos = len(input_line) - (pos + new_cur + 1) - return pos, True, False - -def motion_ge(input_line, cur, count): - """Go to end of `count` words backwards and return position. - - See Also: - `motion_base()`. - """ - # "ge is just "w" on inverted data (e.g. "olleH" instead of "Hello"). - pos_inv = motion_w(input_line[::-1], len(input_line) - cur - 1, count)[0] - pos = len(input_line) - pos_inv - 1 - return pos, True, False - -def motion_gE(input_line, cur, count): - """Go to end of `count` WORDS backwards and return position. - - See Also: - `motion_base()`. - """ - new_cur = len(input_line) - cur - 1 - pos = get_pos(input_line[::-1], REGEX_MOTION_G_UPPERCASE_E, new_cur, - True, count) - if pos == -1: - return 0, False, False - pos = len(input_line) - (pos + new_cur + 1) - return pos, True, False - -def motion_h(input_line, cur, count): - """Go `count` characters to the left and return position. - - See Also: - `motion_base()`. - """ - return max(0, cur - max(count, 1)), False, False - -def motion_l(input_line, cur, count): - """Go `count` characters to the right and return position. - - See Also: - `motion_base()`. - """ - return cur + max(count, 1), False, False - -def motion_carret(input_line, cur, count): - """Go to first non-blank character of line and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_CARRET, 0) - return pos, False, False - -def motion_dollar(input_line, cur, count): - """Go to end of line and return position. - - See Also: - `motion_base()`. - """ - pos = len(input_line) - return pos, False, False - -def motion_f(input_line, cur, count): - """Go to `count`'th occurence of character and return position. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_f", input_line, cur, count) - -def cb_motion_f(update_last=True): - """Callback for `motion_f()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'], re.escape(pattern), - catching_keys_data['cur'], True, - catching_keys_data['count']) - catching_keys_data['new_cur'] = max(0, pos) + catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "f", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_F(input_line, cur, count): - """Go to `count`'th occurence of char to the right and return position. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_F", input_line, cur, count) - -def cb_motion_F(update_last=True): - """Callback for `motion_F()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - cur = len(catching_keys_data['input_line']) - catching_keys_data['cur'] - pos = get_pos(catching_keys_data['input_line'][::-1], - re.escape(pattern), - cur, - False, - catching_keys_data['count']) - catching_keys_data['new_cur'] = catching_keys_data['cur'] - max(0, pos + 1) - if update_last: - last_search_motion = {'motion': "F", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_t(input_line, cur, count): - """Go to `count`'th occurence of char and return position. - - The position returned is the position of the character to the left of char. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_t", input_line, cur, count) - -def cb_motion_t(update_last=True): - """Callback for `motion_t()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'], re.escape(pattern), - catching_keys_data['cur'] + 1, - True, catching_keys_data['count']) - pos += 1 - if pos > 0: - catching_keys_data['new_cur'] = pos + catching_keys_data['cur'] - 1 - else: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "t", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_T(input_line, cur, count): - """Go to `count`'th occurence of char to the left and return position. - - The position returned is the position of the character to the right of - char. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_T", input_line, cur, count) - -def cb_motion_T(update_last=True): - """Callback for `motion_T()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'][::-1], re.escape(pattern), - (len(catching_keys_data['input_line']) - - (catching_keys_data['cur'] + 1)) + 1, - True, catching_keys_data['count']) - pos += 1 - if pos > 0: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - pos + 1 - else: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "T", 'data': pattern} - cb_key_combo_default(None, None, "") - - -# Keys: -# ----- - -def key_cc(buf, input_line, cur, count): - """Delete line and start Insert mode. - - See Also: - `key_base()`. - """ - weechat.command("", "/input delete_line") - set_mode("INSERT") - -def key_C(buf, input_line, cur, count): - """Delete from cursor to end of line and start Insert mode. - - See Also: - `key_base()`. - """ - weechat.command("", "/input delete_end_of_line") - set_mode("INSERT") - -def key_yy(buf, input_line, cur, count): - """Yank line. - - See Also: - `key_base()`. - """ - cmd = vimode_settings['copy_clipboard_cmd'] - proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE) - proc.communicate(input=input_line.encode()) - -def key_p(buf, input_line, cur, count): - """Paste text. - - See Also: - `key_base()`. - """ - cmd = vimode_settings['paste_clipboard_cmd'] - weechat.hook_process(cmd, 10 * 1000, "cb_key_p", weechat.current_buffer()) - -def cb_key_p(data, command, return_code, output, err): - """Callback for fetching clipboard text and pasting it.""" - buf = "" - this_buffer = data - if output != "": - buf += output.strip() - if return_code == 0: - my_input = weechat.buffer_get_string(this_buffer, "input") - pos = weechat.buffer_get_integer(this_buffer, "input_pos") - my_input = my_input[:pos] + buf + my_input[pos:] - pos += len(buf) - weechat.buffer_set(this_buffer, "input", my_input) - weechat.buffer_set(this_buffer, "input_pos", str(pos)) - return weechat.WEECHAT_RC_OK - -def key_i(buf, input_line, cur, count): - """Start Insert mode. - - See Also: - `key_base()`. - """ - set_mode("INSERT") - -def key_a(buf, input_line, cur, count): - """Move cursor one character to the right and start Insert mode. - - See Also: - `key_base()`. - """ - set_cur(buf, input_line, cur + 1, False) - set_mode("INSERT") - -def key_A(buf, input_line, cur, count): - """Move cursor to end of line and start Insert mode. - - See Also: - `key_base()`. - """ - set_cur(buf, input_line, len(input_line), False) - set_mode("INSERT") - -def key_I(buf, input_line, cur, count): - """Move cursor to first non-blank character and start Insert mode. - - See Also: - `key_base()`. - """ - pos, _, _ = motion_carret(input_line, cur, 0) - set_cur(buf, input_line, pos) - set_mode("INSERT") - -def key_G(buf, input_line, cur, count): - """Scroll to specified line or bottom of buffer. - - See Also: - `key_base()`. - """ - if count > 0: - # This is necessary to prevent weird scroll jumps. - weechat.command("", "/window scroll_top") - weechat.command("", "/window scroll %s" % (count - 1)) - else: - weechat.command("", "/window scroll_bottom") - -def key_r(buf, input_line, cur, count): - """Replace `count` characters under the cursor. - - See Also: - `key_base()`. - """ - start_catching_keys(1, "cb_key_r", input_line, cur, count, buf) - -def cb_key_r(): - """Callback for `key_r()`. - - See Also: - `start_catching_keys()`. - """ - global catching_keys_data - input_line = list(catching_keys_data['input_line']) - count = max(catching_keys_data['count'], 1) - cur = catching_keys_data['cur'] - if cur + count <= len(input_line): - for _ in range(count): - input_line[cur] = catching_keys_data['keys'] - cur += 1 - input_line = "".join(input_line) - weechat.buffer_set(catching_keys_data['buf'], "input", input_line) - set_cur(catching_keys_data['buf'], input_line, cur - 1) - catching_keys_data = {'amount': 0} - -def key_R(buf, input_line, cur, count): - """Start Replace mode. - - See Also: - `key_base()`. - """ - set_mode("REPLACE") - -def key_tilda(buf, input_line, cur, count): - """Switch the case of `count` characters under the cursor. - - See Also: - `key_base()`. - """ - input_line = list(input_line) - count = max(1, count) - while count and cur < len(input_line): - input_line[cur] = input_line[cur].swapcase() - count -= 1 - cur += 1 - input_line = "".join(input_line) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, cur) - -def key_alt_j(buf, input_line, cur, count): - """Go to WeeChat buffer. - - Called to preserve WeeChat's alt-j buffer switching. - - This is only called when alt-j is pressed after pressing Esc, because - \x01\x01j is received in key_combo_default which becomes \x01j after - removing the detected Esc key. - If Esc isn't the last pressed key, \x01j is directly received in - key_combo_default. - """ - start_catching_keys(2, "cb_key_alt_j", input_line, cur, count) - -def cb_key_alt_j(): - """Callback for `key_alt_j()`. - - See Also: - `start_catching_keys()`. - """ - global catching_keys_data - weechat.command("", "/buffer " + catching_keys_data['keys']) - catching_keys_data = {'amount': 0} - -def key_semicolon(buf, input_line, cur, count, swap=False): - """Repeat last f, t, F, T `count` times. - - Args: - swap (bool, optional): if True, the last motion will be repeated in the - opposite direction (e.g. "f" instead of "F"). Defaults to False. - - See Also: - `key_base()`. - """ - global catching_keys_data, vi_buffer - catching_keys_data = ({'amount': 0, - 'input_line': input_line, - 'cur': cur, - 'keys': last_search_motion['data'], - 'count': count, - 'new_cur': 0, - 'buf': buf}) - # Swap the motion's case if called from key_comma. - if swap: - motion = last_search_motion['motion'].swapcase() - else: - motion = last_search_motion['motion'] - func = "cb_motion_%s" % motion - vi_buffer = motion - globals()[func](False) - -def key_comma(buf, input_line, cur, count): - """Repeat last f, t, F, T in opposite direction `count` times. - - See Also: - `key_base()`. - """ - key_semicolon(buf, input_line, cur, count, True) - -def key_u(buf, input_line, cur, count): - """Undo change `count` times. - - See Also: - `key_base()`. - """ - buf = weechat.current_buffer() - if buf not in undo_history: - return - for _ in range(max(count, 1)): - if undo_history_index[buf] > -len(undo_history[buf]): - undo_history_index[buf] -= 1 - input_line = undo_history[buf][undo_history_index[buf]] - weechat.buffer_set(buf, "input", input_line) - else: - break - -def key_ctrl_r(buf, input_line, cur, count): - """Redo change `count` times. - - See Also: - `key_base()`. - """ - if buf not in undo_history: - return - for _ in range(max(count, 1)): - if undo_history_index[buf] < -1: - undo_history_index[buf] += 1 - input_line = undo_history[buf][undo_history_index[buf]] - weechat.buffer_set(buf, "input", input_line) - else: - break - - -# Vi key bindings. -# ================ - -# String values will be executed as normal WeeChat commands. -# For functions, see `key_base()` for reference. -VI_KEYS = {'j': "/window scroll_down", - 'k': "/window scroll_up", - 'G': key_G, - 'gg': "/window scroll_top", - 'x': "/input delete_next_char", - 'X': "/input delete_previous_char", - 'dd': "/input delete_line", - 'D': "/input delete_end_of_line", - 'cc': key_cc, - 'C': key_C, - 'i': key_i, - 'a': key_a, - 'A': key_A, - 'I': key_I, - 'yy': key_yy, - 'p': key_p, - 'gt': "/buffer -1", - 'K': "/buffer -1", - 'gT': "/buffer +1", - 'J': "/buffer +1", - 'r': key_r, - 'R': key_R, - '~': key_tilda, - 'nt': "/bar scroll nicklist * -100%", - 'nT': "/bar scroll nicklist * +100%", - '\x01[[A': "/input history_previous", - '\x01[[B': "/input history_next", - '\x01[[C': "/input move_next_char", - '\x01[[D': "/input move_previous_char", - '\x01[[H': "/input move_beginning_of_line", - '\x01[[F': "/input move_end_of_line", - '\x01[[5~': "/window page_up", - '\x01[[6~': "/window page_down", - '\x01[[3~': "/input delete_next_char", - '\x01[[2~': key_i, - '\x01M': "/input return", - '\x01?': "/input move_previous_char", - ' ': "/input move_next_char", - '\x01[j': key_alt_j, - '\x01[1': "/buffer *1", - '\x01[2': "/buffer *2", - '\x01[3': "/buffer *3", - '\x01[4': "/buffer *4", - '\x01[5': "/buffer *5", - '\x01[6': "/buffer *6", - '\x01[7': "/buffer *7", - '\x01[8': "/buffer *8", - '\x01[9': "/buffer *9", - '\x01[0': "/buffer *10", - '\x01^': "/input jump_last_buffer_displayed", - '\x01D': "/window page_down", - '\x01U': "/window page_up", - '\x01Wh': "/window left", - '\x01Wj': "/window down", - '\x01Wk': "/window up", - '\x01Wl': "/window right", - '\x01W=': "/window balance", - '\x01Wx': "/window swap", - '\x01Ws': "/window splith", - '\x01Wv': "/window splitv", - '\x01Wq': "/window merge", - ';': key_semicolon, - ',': key_comma, - 'u': key_u, - '\x01R': key_ctrl_r} - -# Add alt-j bindings. -for i in range(10, 99): - VI_KEYS['\x01[j%s' % i] = "/buffer %s" % i - - -# Key handling. -# ============= - -def cb_key_pressed(data, signal, signal_data): - """Detect potential Esc presses. - - Alt and Esc are detected as the same key in most terminals. The difference - is that Alt signal is sent just before the other pressed key's signal. - We therefore use a timeout (50ms) to detect whether Alt or Esc was pressed. - """ - global last_signal_time - last_signal_time = time.time() - if signal_data == "\x01[": - # In 50ms, check if any other keys were pressed. If not, it's Esc! - weechat.hook_timer(50, 0, 1, "cb_check_esc", - "{:f}".format(last_signal_time)) - return weechat.WEECHAT_RC_OK - -def cb_check_esc(data, remaining_calls): - """Check if the Esc key was pressed and change the mode accordingly.""" - global esc_pressed, vi_buffer, catching_keys_data - # Not perfect, would be better to use direct comparison (==) but that only - # works for py2 and not for py3. - if abs(last_signal_time - float(data)) <= 0.000001: - esc_pressed += 1 - if mode == "SEARCH": - weechat.command("", "/input search_stop_here") - set_mode("NORMAL") - # Cancel any current partial commands. - vi_buffer = "" - catching_keys_data = {'amount': 0} - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK - -def cb_key_combo_default(data, signal, signal_data): - """Eat and handle key events when in Normal mode, if needed. - - The key_combo_default signal is sent when a key combo is pressed. For - example, alt-k will send the "\x01[k" signal. - - Esc is handled a bit differently to avoid delays, see `cb_key_pressed()`. - """ - global esc_pressed, vi_buffer, cmd_compl_text, cmd_text_orig, \ - cmd_compl_pos, cmd_history_index - - # If Esc was pressed, strip the Esc part from the pressed keys. - # Example: user presses Esc followed by i. This is detected as "\x01[i", - # but we only want to handle "i". - keys = signal_data - if esc_pressed or esc_pressed == -2: - if keys.startswith("\x01[" * esc_pressed): - # Multiples of 3 seem to "cancel" themselves, - # e.g. Esc-Esc-Esc-Alt-j-11 is detected as "\x01[\x01[\x01" - # followed by "\x01[j11" (two different signals). - if signal_data == "\x01[" * 3: - esc_pressed = -1 # `cb_check_esc()` will increment it to 0. - else: - esc_pressed = 0 - # This can happen if a valid combination is started but interrupted - # with Esc, such as Ctrl-W→Esc→w which would send two signals: - # "\x01W\x01[" then "\x01W\x01[w". - # In that case, we still need to handle the next signal ("\x01W\x01[w") - # so we use the special value "-2". - else: - esc_pressed = -2 - keys = keys.split("\x01[")[-1] # Remove the "Esc" part(s). - # Ctrl-Space. - elif keys == "\x01@": - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK_EAT - - # Clear the undo history for this buffer on . - if keys == "\x01M": - buf = weechat.current_buffer() - clear_undo_history(buf) - - # Detect imap_esc presses if any. - if mode == "INSERT": - imap_esc = vimode_settings['imap_esc'] - if not imap_esc: - return weechat.WEECHAT_RC_OK - if (imap_esc.startswith(vi_buffer) and - imap_esc[len(vi_buffer):len(vi_buffer)+1] == keys): - vi_buffer += keys - weechat.bar_item_update("vi_buffer") - weechat.hook_timer(int(vimode_settings['imap_esc_timeout']), 0, 1, - "cb_check_imap_esc", vi_buffer) - elif (vi_buffer and imap_esc.startswith(vi_buffer) and - imap_esc[len(vi_buffer):len(vi_buffer)+1] != keys): - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - # imap_esc sequence detected -- remove the sequence keys from the - # Weechat input bar and enter Normal mode. - if imap_esc == vi_buffer: - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - cur = weechat.buffer_get_integer(buf, "input_pos") - input_line = (input_line[:cur-len(imap_esc)+1] + - input_line[cur:]) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, cur-len(imap_esc)+1, False) - set_mode("NORMAL") - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - # We're in Replace mode — allow "normal" key presses (e.g. "a") and - # overwrite the next character with them, but let the other key presses - # pass normally (e.g. backspace, arrow keys, etc). - if mode == "REPLACE": - if len(keys) == 1: - weechat.command("", "/input delete_next_char") - elif keys == "\x01?": - weechat.command("", "/input move_previous_char") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - # We're catching keys! Only "normal" key presses interest us (e.g. "a"), - # not complex ones (e.g. backspace). - if len(keys) == 1 and catching_keys_data['amount']: - catching_keys_data['keys'] += keys - catching_keys_data['amount'] -= 1 - # Done catching keys, execute the callback. - if catching_keys_data['amount'] == 0: - globals()[catching_keys_data['callback']]() - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - - # We're in command-line mode. - if mode == "COMMAND": - buf = weechat.current_buffer() - cmd_text = weechat.buffer_get_string(buf, "input") - weechat.hook_timer(1, 0, 1, "cb_check_cmd_mode", "") - # Return key. - if keys == "\x01M": - weechat.hook_timer(1, 0, 1, "cb_exec_cmd", cmd_text) - if len(cmd_text) > 1 and (not cmd_history or - cmd_history[-1] != cmd_text): - cmd_history.append(cmd_text) - cmd_history_index = 0 - set_mode("NORMAL") - buf = weechat.current_buffer() - input_line = input_line_backup[buf]['input_line'] - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, input_line_backup[buf]['cur'], False) - # Up arrow. - elif keys == "\x01[[A": - if cmd_history_index > -len(cmd_history): - cmd_history_index -= 1 - cmd_text = cmd_history[cmd_history_index] - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Down arrow. - elif keys == "\x01[[B": - if cmd_history_index < -1: - cmd_history_index += 1 - cmd_text = cmd_history[cmd_history_index] - else: - cmd_history_index = 0 - cmd_text = ":" - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Tab key. No completion when searching ("/"). - elif keys == "\x01I" and cmd_text[0] == ":": - if cmd_text_orig is None: - input_ = list(cmd_text) - del input_[0] - cmd_text_orig = "".join(input_) - cmd_compl_list = [] - for cmd in VI_COMMANDS.keys(): - if cmd.startswith(cmd_text_orig): - cmd_compl_list.append(cmd) - if cmd_compl_list: - curr_suggestion = cmd_compl_list[cmd_compl_pos] - cmd_text = ":%s" % curr_suggestion - cmd_compl_list[cmd_compl_pos] = weechat.string_eval_expression( - "${color:bold}%s${color:-bold}" % curr_suggestion, - {}, {}, {}) - cmd_compl_text = ", ".join(cmd_compl_list) - cmd_compl_pos = (cmd_compl_pos + 1) % len(cmd_compl_list) - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Input. - else: - cmd_compl_text = "" - cmd_text_orig = None - cmd_compl_pos = 0 - weechat.bar_item_update("cmd_completion") - if keys in ["\x01M", "\x01[[A", "\x01[[B"]: - cmd_compl_text = "" - return weechat.WEECHAT_RC_OK_EAT - else: - return weechat.WEECHAT_RC_OK - # Enter command mode. - elif keys in [":", "/"]: - if keys == "/": - weechat.command("", "/input search_text_here") - if not weechat.config_string_to_boolean( - vimode_settings['search_vim']): - return weechat.WEECHAT_RC_OK - else: - buf = weechat.current_buffer() - cur = weechat.buffer_get_integer(buf, "input_pos") - input_line = weechat.buffer_get_string(buf, "input") - input_line_backup[buf] = {'input_line': input_line, 'cur': cur} - input_line = ":" - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, 1, False) - set_mode("COMMAND") - cmd_compl_text = "" - cmd_text_orig = None - cmd_compl_pos = 0 - return weechat.WEECHAT_RC_OK_EAT - - # Add key to the buffer. - vi_buffer += keys - weechat.bar_item_update("vi_buffer") - if not vi_buffer: - return weechat.WEECHAT_RC_OK - - # Check if the keys have a (partial or full) match. If so, also get the - # keys without the count. (These are the actual keys we should handle.) - # After that, `vi_buffer` is only used for display purposes — only - # `vi_keys` is checked for all the handling. - # If no matches are found, the keys buffer is cleared. - matched, vi_keys, count = get_keys_and_count(vi_buffer) - if not matched: - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - # Check if it's a command (user defined key mapped to a :cmd). - if vi_keys.startswith(":"): - weechat.hook_timer(1, 0, 1, "cb_exec_cmd", "{} {}".format(vi_keys, - count)) - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - # It's a WeeChat command (user defined key mapped to a /cmd). - if vi_keys.startswith("/"): - weechat.command("", vi_keys) - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - cur = weechat.buffer_get_integer(buf, "input_pos") - - # It's a default mapping. If the corresponding value is a string, we assume - # it's a WeeChat command. Otherwise, it's a method we'll call. - if vi_keys in VI_KEYS: - if vi_keys not in ['u', '\x01R']: - add_undo_history(buf, input_line) - if isinstance(VI_KEYS[vi_keys], str): - for _ in range(max(count, 1)): - # This is to avoid crashing WeeChat on script reloads/unloads, - # because no hooks must still be running when a script is - # reloaded or unloaded. - if (VI_KEYS[vi_keys] == "/input return" and - input_line.startswith("/script ")): - return weechat.WEECHAT_RC_OK - weechat.command("", VI_KEYS[vi_keys]) - current_cur = weechat.buffer_get_integer(buf, "input_pos") - set_cur(buf, input_line, current_cur) - else: - VI_KEYS[vi_keys](buf, input_line, cur, count) - # It's a motion (e.g. "w") — call `motion_X()` where X is the motion, then - # set the cursor's position to what that function returned. - elif vi_keys in VI_MOTIONS: - if vi_keys in SPECIAL_CHARS: - func = "motion_%s" % SPECIAL_CHARS[vi_keys] - else: - func = "motion_%s" % vi_keys - end, _, _ = globals()[func](input_line, cur, count) - set_cur(buf, input_line, end) - # It's an operator + motion (e.g. "dw") — call `motion_X()` (where X is - # the motion), then we call `operator_Y()` (where Y is the operator) - # with the position `motion_X()` returned. `operator_Y()` should then - # handle changing the input line. - elif (len(vi_keys) > 1 and - vi_keys[0] in VI_OPERATORS and - vi_keys[1:] in VI_MOTIONS): - add_undo_history(buf, input_line) - if vi_keys[1:] in SPECIAL_CHARS: - func = "motion_%s" % SPECIAL_CHARS[vi_keys[1:]] - else: - func = "motion_%s" % vi_keys[1:] - pos, overwrite, catching = globals()[func](input_line, cur, count) - # If it's a catching motion, we don't want to call the operator just - # yet -- this code will run again when the motion is complete, at which - # point we will. - if not catching: - oper = "operator_%s" % vi_keys[0] - globals()[oper](buf, input_line, cur, pos, overwrite) - # The combo isn't completed yet (e.g. just "d"). - else: - return weechat.WEECHAT_RC_OK_EAT - - # We've already handled the key combo, so clear the keys buffer. - if not catching_keys_data['amount']: - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - -def cb_check_imap_esc(data, remaining_calls): - """Clear the imap_esc sequence after some time if nothing was pressed.""" - global vi_buffer - if vi_buffer == data: - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK - -def cb_key_combo_search(data, signal, signal_data): - """Handle keys while search mode is active (if search_vim is enabled).""" - if not weechat.config_string_to_boolean(vimode_settings['search_vim']): - return weechat.WEECHAT_RC_OK - if mode == "COMMAND": - if signal_data == "\x01M": - set_mode("SEARCH") - return weechat.WEECHAT_RC_OK_EAT - elif mode == "SEARCH": - if signal_data == "\x01M": - set_mode("NORMAL") - else: - if signal_data == "n": - weechat.command("", "/input search_next") - elif signal_data == "N": - weechat.command("", "/input search_previous") - # Start a new search. - elif signal_data == "/": - weechat.command("", "/input search_stop_here") - set_mode("NORMAL") - weechat.command("", "/input search_text_here") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - -# Callbacks. -# ========== - -# Bar items. -# ---------- - -def cb_vi_buffer(data, item, window): - """Return the content of the vi buffer (pressed keys on hold).""" - return vi_buffer - -def cb_cmd_completion(data, item, window): - """Return the text of the command line.""" - return cmd_compl_text - -def cb_mode_indicator(data, item, window): - """Return the current mode (INSERT/NORMAL/REPLACE/...).""" - return "{}{}{}{}{}".format( - weechat.color(mode_colors[mode]), - vimode_settings['mode_indicator_prefix'], mode, - vimode_settings['mode_indicator_suffix'], weechat.color("reset")) - -def cb_line_numbers(data, item, window): - """Fill the line numbers bar item.""" - bar_height = weechat.window_get_integer(window, "win_chat_height") - content = "" - for i in range(1, bar_height + 1): - content += "{}{}{}\n".format(vimode_settings['line_number_prefix'], i, - vimode_settings['line_number_suffix']) - return content - -# Callbacks for the line numbers bar. -# ................................... - -def cb_update_line_numbers(data, signal, signal_data): - """Call `cb_timer_update_line_numbers()` when switching buffers. - - A timer is required because the bar item is refreshed before the new buffer - is actually displayed, so ``win_chat_height`` would refer to the old - buffer. Using a timer refreshes the item after the new buffer is displayed. - """ - weechat.hook_timer(10, 0, 1, "cb_timer_update_line_numbers", "") - return weechat.WEECHAT_RC_OK - -def cb_timer_update_line_numbers(data, remaining_calls): - """Update the line numbers bar item.""" - weechat.bar_item_update("line_numbers") - return weechat.WEECHAT_RC_OK - - -# Config. -# ------- - -def cb_config(data, option, value): - """Script option changed, update our copy.""" - option_name = option.split(".")[-1] - if option_name in vimode_settings: - vimode_settings[option_name] = value - if option_name == 'user_mappings': - load_user_mappings() - if "_color" in option_name: - load_mode_colors() - return weechat.WEECHAT_RC_OK - -def load_mode_colors(): - mode_colors.update({ - 'NORMAL': "{},{}".format( - vimode_settings['mode_indicator_normal_color'], - vimode_settings['mode_indicator_normal_color_bg']), - 'INSERT': "{},{}".format( - vimode_settings['mode_indicator_insert_color'], - vimode_settings['mode_indicator_insert_color_bg']), - 'REPLACE': "{},{}".format( - vimode_settings['mode_indicator_replace_color'], - vimode_settings['mode_indicator_replace_color_bg']), - 'COMMAND': "{},{}".format( - vimode_settings['mode_indicator_cmd_color'], - vimode_settings['mode_indicator_cmd_color_bg']), - 'SEARCH': "{},{}".format( - vimode_settings['mode_indicator_search_color'], - vimode_settings['mode_indicator_search_color_bg']) - }) - -def load_user_mappings(): - """Load user-defined mappings.""" - mappings = {} - if vimode_settings['user_mappings']: - mappings.update(json.loads(vimode_settings['user_mappings'])) - vimode_settings['user_mappings'] = mappings - - -# Command-line execution. -# ----------------------- - -def cb_exec_cmd(data, remaining_calls): - """Translate and execute our custom commands to WeeChat command.""" - # Process the entered command. - data = list(data) - del data[0] - data = "".join(data) - # s/foo/bar command. - if data.startswith("s/"): - cmd = data - parsed_cmd = next(csv.reader(StringIO(cmd), delimiter="/", - escapechar="\\")) - pattern = re.escape(parsed_cmd[1]) - repl = parsed_cmd[2] - repl = re.sub(r"([^\\])&", r"\1" + pattern, repl) - flag = None - if len(parsed_cmd) == 4: - flag = parsed_cmd[3] - count = 1 - if flag == "g": - count = 0 - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - input_line = re.sub(pattern, repl, input_line, count) - weechat.buffer_set(buf, "input", input_line) - # Shell command. - elif data.startswith("!"): - weechat.command("", "/exec -buffer shell %s" % data[1:]) - # Commands like `:22`. This should start cursor mode (``/cursor``) and take - # us to the relevant line. - elif data.isdigit(): - line_number = int(data) - hdata_window = weechat.hdata_get("window") - window = weechat.current_window() - x = weechat.hdata_integer(hdata_window, window, "win_chat_x") - y = (weechat.hdata_integer(hdata_window, window, "win_chat_y") + - (line_number - 1)) - weechat.command("", "/cursor go {},{}".format(x, y)) - # Check againt defined commands. - elif data: - raw_data = data - data = data.split(" ", 1) - cmd = data[0] - args = "" - if len(data) == 2: - args = data[1] - if cmd in VI_COMMANDS: - if isinstance(VI_COMMANDS[cmd], str): - weechat.command("", "%s %s" % (VI_COMMANDS[cmd], args)) - else: - VI_COMMANDS[cmd](args) - else: - # Check for commands not sepearated by space (e.g. "b2") - for i in range(1, len(raw_data)): - tmp_cmd = raw_data[:i] - tmp_args = raw_data[i:] - if tmp_cmd in VI_COMMANDS and tmp_args.isdigit(): - weechat.command("", "%s %s" % (VI_COMMANDS[tmp_cmd], - tmp_args)) - return weechat.WEECHAT_RC_OK - # No vi commands found, run the command as WeeChat command - weechat.command("", "/{} {}".format(cmd, args)) - return weechat.WEECHAT_RC_OK - -def cb_vimode_go_to_normal(data, buf, args): - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK - -# Script commands. -# ---------------- - -def cb_vimode_cmd(data, buf, args): - """Handle script commands (``/vimode ``).""" - # ``/vimode`` or ``/vimode help`` - if not args or args == "help": - weechat.prnt("", "[vimode.py] %s" % README_URL) - # ``/vimode bind_keys`` or ``/vimode bind_keys --list`` - elif args.startswith("bind_keys"): - infolist = weechat.infolist_get("key", "", "default") - weechat.infolist_reset_item_cursor(infolist) - commands = ["/key unbind ctrl-W", - "/key bind ctrl-W /input delete_previous_word", - "/key bind ctrl-^ /input jump_last_buffer_displayed", - "/key bind ctrl-Wh /window left", - "/key bind ctrl-Wj /window down", - "/key bind ctrl-Wk /window up", - "/key bind ctrl-Wl /window right", - "/key bind ctrl-W= /window balance", - "/key bind ctrl-Wx /window swap", - "/key bind ctrl-Ws /window splith", - "/key bind ctrl-Wv /window splitv", - "/key bind ctrl-Wq /window merge"] - while weechat.infolist_next(infolist): - key = weechat.infolist_string(infolist, "key") - if re.match(REGEX_PROBLEMATIC_KEYBINDINGS, key): - commands.append("/key unbind %s" % key) - weechat.infolist_free(infolist) - if args == "bind_keys": - weechat.prnt("", "Running commands:") - for command in commands: - weechat.command("", command) - weechat.prnt("", "Done.") - elif args == "bind_keys --list": - weechat.prnt("", "Listing commands we'll run:") - for command in commands: - weechat.prnt("", " %s" % command) - weechat.prnt("", "Done.") - return weechat.WEECHAT_RC_OK - - -# Helpers. -# ======== - -# Motions/keys helpers. -# --------------------- - -def get_pos(data, regex, cur, ignore_cur=False, count=0): - """Return the position of `regex` match in `data`, starting at `cur`. - - Args: - data (str): the data to search in. - regex (pattern): regex pattern to search for. - cur (int): where to start the search. - ignore_cur (bool, optional): should the first match be ignored if it's - also the character at `cur`? - Defaults to False. - count (int, optional): the index of the match to return. Defaults to 0. - - Returns: - int: position of the match. -1 if no matches are found. - """ - # List of the *positions* of the found patterns. - matches = [m.start() for m in re.finditer(regex, data[cur:])] - pos = -1 - if count: - if len(matches) > count - 1: - if ignore_cur and matches[0] == 0: - if len(matches) > count: - pos = matches[count] - else: - pos = matches[count - 1] - elif matches: - if ignore_cur and matches[0] == 0: - if len(matches) > 1: - pos = matches[1] - else: - pos = matches[0] - return pos - -def set_cur(buf, input_line, pos, cap=True): - """Set the cursor's position. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - pos (int): the position to set the cursor to. - cap (bool, optional): if True, the `pos` will shortened to the length - of `input_line` if it's too long. Defaults to True. - """ - if cap: - pos = min(pos, len(input_line) - 1) - weechat.buffer_set(buf, "input_pos", str(pos)) - -def start_catching_keys(amount, callback, input_line, cur, count, buf=None): - """Start catching keys. Used for special commands (e.g. "f", "r"). - - amount (int): amount of keys to catch. - callback (str): name of method to call once all keys are caught. - input_line (str): input line's content. - cur (int): cursor's position. - count (int): count, e.g. "2" for "2fs". - buf (str, optional): pointer to the current WeeChat buffer. - Defaults to None. - - `catching_keys_data` is a dict with the above arguments, as well as: - keys (str): pressed keys will be added under this key. - new_cur (int): the new cursor's position, set in the callback. - - When catching keys is active, normal pressed keys (e.g. "a" but not arrows) - will get added to `catching_keys_data` under the key "keys", and will not - be handled any further. - Once all keys are caught, the method defined in the "callback" key is - called, and can use the data in `catching_keys_data` to perform its action. - """ - global catching_keys_data - if "new_cur" in catching_keys_data: - new_cur = catching_keys_data['new_cur'] - catching_keys_data = {'amount': 0} - return new_cur, True, False - catching_keys_data = ({'amount': amount, - 'callback': callback, - 'input_line': input_line, - 'cur': cur, - 'keys': "", - 'count': count, - 'new_cur': 0, - 'buf': buf}) - return cur, False, True - -def get_keys_and_count(combo): - """Check if `combo` is a valid combo and extract keys/counts if so. - - Args: - combo (str): pressed keys combo. - - Returns: - matched (bool): True if the combo has a (partial or full) match, False - otherwise. - combo (str): `combo` with the count removed. These are the actual keys - we should handle. User mappings are also expanded. - count (int): count for `combo`. - """ - # Look for a potential match (e.g. "d" might become "dw" or "dd" so we - # accept it, but "d9" is invalid). - matched = False - # Digits are allowed at the beginning (counts or "0"). - count = 0 - if combo.isdigit(): - matched = True - elif combo and combo[0].isdigit(): - count = "" - for char in combo: - if char.isdigit(): - count += char - else: - break - combo = combo.replace(count, "", 1) - count = int(count) - # It's a user defined key. Expand it. - if combo in vimode_settings['user_mappings']: - combo = vimode_settings['user_mappings'][combo] - # It's a WeeChat command. - if not matched and combo.startswith("/"): - matched = True - # Check against defined keys. - if not matched: - for key in VI_KEYS: - if key.startswith(combo): - matched = True - break - # Check against defined motions. - if not matched: - for motion in VI_MOTIONS: - if motion.startswith(combo): - matched = True - break - # Check against defined operators + motions. - if not matched: - for operator in VI_OPERATORS: - if combo.startswith(operator): - # Check for counts before the motion (but after the operator). - vi_keys_no_op = combo[len(operator):] - # There's no motion yet. - if vi_keys_no_op.isdigit(): - matched = True - break - # Get the motion count, then multiply the operator count by - # it, similar to vim's behavior. - elif vi_keys_no_op and vi_keys_no_op[0].isdigit(): - motion_count = "" - for char in vi_keys_no_op: - if char.isdigit(): - motion_count += char - else: - break - # Remove counts from `vi_keys_no_op`. - combo = combo.replace(motion_count, "", 1) - motion_count = int(motion_count) - count = max(count, 1) * motion_count - # Check against defined motions. - for motion in VI_MOTIONS: - if motion.startswith(combo[1:]): - matched = True - break - return matched, combo, count - - -# Other helpers. -# -------------- - -def set_mode(arg): - """Set the current mode and update the bar mode indicator.""" - global mode - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - if mode == "INSERT" and arg == "NORMAL": - add_undo_history(buf, input_line) - mode = arg - # If we're going to Normal mode, the cursor must move one character to the - # left. - if mode == "NORMAL": - cur = weechat.buffer_get_integer(buf, "input_pos") - set_cur(buf, input_line, cur - 1, False) - weechat.bar_item_update("mode_indicator") - -def cb_check_cmd_mode(data, remaining_calls): - """Exit command mode if user erases the leading ':' character.""" - buf = weechat.current_buffer() - cmd_text = weechat.buffer_get_string(buf, "input") - if not cmd_text: - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK - -def add_undo_history(buf, input_line): - """Add an item to the per-buffer undo history.""" - if buf in undo_history: - if not undo_history[buf] or undo_history[buf][-1] != input_line: - undo_history[buf].append(input_line) - undo_history_index[buf] = -1 - else: - undo_history[buf] = ['', input_line] - undo_history_index[buf] = -1 - -def clear_undo_history(buf): - """Clear the undo history for a given buffer.""" - undo_history[buf] = [''] - undo_history_index[buf] = -1 - -def print_warning(text): - """Print warning, in red, to the current buffer.""" - weechat.prnt("", ("%s[vimode.py] %s" % (weechat.color("red"), text))) - -def check_warnings(): - """Warn the user about problematic key bindings and tmux/screen.""" - user_warned = False - # Warn the user about problematic key bindings that may conflict with - # vimode. - # The solution is to remove these key bindings, but that's up to the user. - infolist = weechat.infolist_get("key", "", "default") - problematic_keybindings = [] - while weechat.infolist_next(infolist): - key = weechat.infolist_string(infolist, "key") - command = weechat.infolist_string(infolist, "command") - if re.match(REGEX_PROBLEMATIC_KEYBINDINGS, key): - problematic_keybindings.append("%s -> %s" % (key, command)) - weechat.infolist_free(infolist) - if problematic_keybindings: - user_warned = True - print_warning("Problematic keybindings detected:") - for keybinding in problematic_keybindings: - print_warning(" %s" % keybinding) - print_warning("These keybindings may conflict with vimode.") - print_warning("You can remove problematic key bindings and add" - " recommended ones by using /vimode bind_keys, or only" - " list them with /vimode bind_keys --list") - print_warning("For help, see: %s" % FAQ_KEYBINDINGS) - del problematic_keybindings - # Warn tmux/screen users about possible Esc detection delays. - if "STY" in os.environ or "TMUX" in os.environ: - if user_warned: - weechat.prnt("", "") - user_warned = True - print_warning("tmux/screen users, see: %s" % FAQ_ESC) - if (user_warned and not - weechat.config_string_to_boolean(vimode_settings['no_warn'])): - if user_warned: - weechat.prnt("", "") - print_warning("To force disable warnings, you can set" - " plugins.var.python.vimode.no_warn to 'on'") - - -# Main script. -# ============ - -if __name__ == "__main__": - weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, "", "") - # Warn the user if he's using an unsupported WeeChat version. - VERSION = weechat.info_get("version_number", "") - if int(VERSION) < 0x01000000: - print_warning("Please upgrade to WeeChat ≥ 1.0.0. Previous versions" - " are not supported.") - # Set up script options. - for option, value in list(vimode_settings.items()): - if weechat.config_is_set_plugin(option): - vimode_settings[option] = weechat.config_get_plugin(option) - else: - weechat.config_set_plugin(option, value[0]) - vimode_settings[option] = value[0] - weechat.config_set_desc_plugin(option, - "%s (default: \"%s\")" % (value[1], - value[0])) - load_user_mappings() - load_mode_colors() - # Warn the user about possible problems if necessary. - if not weechat.config_string_to_boolean(vimode_settings['no_warn']): - check_warnings() - # Create bar items and setup hooks. - weechat.bar_item_new("mode_indicator", "cb_mode_indicator", "") - weechat.bar_item_new("cmd_completion", "cb_cmd_completion", "") - weechat.bar_item_new("vi_buffer", "cb_vi_buffer", "") - weechat.bar_item_new("line_numbers", "cb_line_numbers", "") - if int(VERSION) >= 0x02090000: - weechat.bar_new("vi_line_numbers", "on", "0", "window", "", "left", - "vertical", "vertical", "0", "0", "default", "default", - "default", "default", "0", "line_numbers") - else: - weechat.bar_new("vi_line_numbers", "on", "0", "window", "", "left", - "vertical", "vertical", "0", "0", "default", "default", - "default", "0", "line_numbers") - weechat.hook_config("plugins.var.python.%s.*" % SCRIPT_NAME, "cb_config", - "") - weechat.hook_signal("key_pressed", "cb_key_pressed", "") - weechat.hook_signal("key_combo_default", "cb_key_combo_default", "") - weechat.hook_signal("key_combo_search", "cb_key_combo_search", "") - weechat.hook_signal("buffer_switch", "cb_update_line_numbers", "") - weechat.hook_command("vimode", SCRIPT_DESC, "[help | bind_keys [--list]]", - " help: show help\n" - "bind_keys: unbind problematic keys, and bind" - " recommended keys to use in WeeChat\n" - " --list: only list changes", - "help || bind_keys |--list", - "cb_vimode_cmd", "") - weechat.hook_command("vimode_go_to_normal", - ("This command can be used for key bindings to go to " - "normal mode."), - "", "", "", "cb_vimode_go_to_normal", "") - # Remove obsolete bar. - vi_cmd_bar = weechat.bar_search("vi_cmd") - weechat.bar_remove(vi_cmd_bar) diff --git a/.config/weechat/python/autosort.py b/.config/weechat/python/autosort.py deleted file mode 100644 index 4312cda7..00000000 --- a/.config/weechat/python/autosort.py +++ /dev/null @@ -1,1075 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2017 Maarten de Vries -# -# 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 3 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 program. If not, see . -# - -# -# Autosort automatically keeps your buffers sorted and grouped by server. -# You can define your own sorting rules. See /help autosort for more details. -# -# https://github.com/de-vri-es/weechat-autosort -# - -# -# Changelog: -# 3.9: -# * Remove `buffers.pl` from recommended settings. -# 3,8: -# * Fix relative sorting on script name in default rules. -# * Document a useful property of stable sort algorithms. -# 3.7: -# * Make default rules work with bitlbee, matrix and slack. -# 3.6: -# * Add more documentation on provided info hooks. -# 3.5: -# * Add ${info:autosort_escape,...} to escape arguments for other info hooks. -# 3.4: -# * Fix rate-limit of sorting to prevent high CPU load and lock-ups. -# * Fix bug in parsing empty arguments for info hooks. -# * Add debug_log option to aid with debugging. -# * Correct a few typos. -# 3.3: -# * Fix the /autosort debug command for unicode. -# * Update the default rules to work better with Slack. -# 3.2: -# * Fix python3 compatiblity. -# 3.1: -# * Use colors to format the help text. -# 3.0: -# * Switch to evaluated expressions for sorting. -# * Add `/autosort debug` command. -# * Add ${info:autosort_replace,from,to,text} to replace substrings in sort rules. -# * Add ${info:autosort_order,value,first,second,third} to ease writing sort rules. -# * Make tab completion context aware. -# 2.8: -# * Fix compatibility with python 3 regarding unicode handling. -# 2.7: -# * Fix sorting of buffers with spaces in their name. -# 2.6: -# * Ignore case in rules when doing case insensitive sorting. -# 2.5: -# * Fix handling unicode buffer names. -# * Add hint to set irc.look.server_buffer to independent and buffers.look.indenting to on. -# 2.4: -# * Make script python3 compatible. -# 2.3: -# * Fix sorting items without score last (regressed in 2.2). -# 2.2: -# * Add configuration option for signals that trigger a sort. -# * Add command to manually trigger a sort (/autosort sort). -# * Add replacement patterns to apply before sorting. -# 2.1: -# * Fix some minor style issues. -# 2.0: -# * Allow for custom sort rules. -# - - -import json -import math -import re -import sys -import time -import weechat - -SCRIPT_NAME = 'autosort' -SCRIPT_AUTHOR = 'Maarten de Vries ' -SCRIPT_VERSION = '3.9' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Flexible automatic (or manual) buffer sorting based on eval expressions.' - - -config = None -hooks = [] -signal_delay_timer = None -sort_limit_timer = None -sort_queued = False - - -# Make sure that unicode, bytes and str are always available in python2 and 3. -# For python 2, str == bytes -# For python 3, str == unicode -if sys.version_info[0] >= 3: - unicode = str - -def ensure_str(input): - ''' - Make sure the given type if the correct string type for the current python version. - That means bytes for python2 and unicode for python3. - ''' - if not isinstance(input, str): - if isinstance(input, bytes): - return input.encode('utf-8') - if isinstance(input, unicode): - return input.decode('utf-8') - return input - - -if hasattr(time, 'perf_counter'): - perf_counter = time.perf_counter -else: - perf_counter = time.clock - -def casefold(string): - if hasattr(string, 'casefold'): return string.casefold() - # Fall back to lowercasing for python2. - return string.lower() - -def list_swap(values, a, b): - values[a], values[b] = values[b], values[a] - -def list_move(values, old_index, new_index): - values.insert(new_index, values.pop(old_index)) - -def list_find(collection, value): - for i, elem in enumerate(collection): - if elem == value: return i - return None - -class HumanReadableError(Exception): - pass - -def parse_int(arg, arg_name = 'argument'): - ''' Parse an integer and provide a more human readable error. ''' - arg = arg.strip() - try: - return int(arg) - except ValueError: - raise HumanReadableError('Invalid {0}: expected integer, got "{1}".'.format(arg_name, arg)) - -def decode_rules(blob): - parsed = json.loads(blob) - if not isinstance(parsed, list): - log('Malformed rules, expected a JSON encoded list of strings, but got a {0}. No rules have been loaded. Please fix the setting manually.'.format(type(parsed))) - return [] - - for i, entry in enumerate(parsed): - if not isinstance(entry, (str, unicode)): - log('Rule #{0} is not a string but a {1}. No rules have been loaded. Please fix the setting manually.'.format(i, type(entry))) - return [] - - return parsed - -def decode_helpers(blob): - parsed = json.loads(blob) - if not isinstance(parsed, dict): - log('Malformed helpers, expected a JSON encoded dictionary but got a {0}. No helpers have been loaded. Please fix the setting manually.'.format(type(parsed))) - return {} - - for key, value in parsed.items(): - if not isinstance(value, (str, unicode)): - log('Helper "{0}" is not a string but a {1}. No helpers have been loaded. Please fix setting manually.'.format(key, type(value))) - return {} - return parsed - -class Config: - ''' The autosort configuration. ''' - - default_rules = json.dumps([ - '${core_first}', - '${info:autosort_order,${info:autosort_escape,${script_or_plugin}},core,*,irc,bitlbee,matrix,slack}', - '${script_or_plugin}', - '${irc_raw_first}', - '${server}', - '${info:autosort_order,${type},server,*,channel,private}', - '${hashless_name}', - '${buffer.full_name}', - ]) - - default_helpers = json.dumps({ - 'core_first': '${if:${buffer.full_name}!=core.weechat}', - 'irc_raw_first': '${if:${buffer.full_name}!=irc.irc_raw}', - 'irc_raw_last': '${if:${buffer.full_name}==irc.irc_raw}', - 'hashless_name': '${info:autosort_replace,#,,${info:autosort_escape,${buffer.name}}}', - 'script_or_plugin': '${if:${script_name}?${script_name}:${plugin}}', - }) - - default_signal_delay = 5 - default_sort_limit = 100 - - default_signals = 'buffer_opened buffer_merged buffer_unmerged buffer_renamed' - - def __init__(self, filename): - ''' Initialize the configuration. ''' - - self.filename = filename - self.config_file = weechat.config_new(self.filename, '', '') - self.sorting_section = None - self.v3_section = None - - self.case_sensitive = False - self.rules = [] - self.helpers = {} - self.signals = [] - self.signal_delay = Config.default_signal_delay, - self.sort_limit = Config.default_sort_limit, - self.sort_on_config = True - self.debug_log = False - - self.__case_sensitive = None - self.__rules = None - self.__helpers = None - self.__signals = None - self.__signal_delay = None - self.__sort_limit = None - self.__sort_on_config = None - self.__debug_log = None - - if not self.config_file: - log('Failed to initialize configuration file "{0}".'.format(self.filename)) - return - - self.sorting_section = weechat.config_new_section(self.config_file, 'sorting', False, False, '', '', '', '', '', '', '', '', '', '') - self.v3_section = weechat.config_new_section(self.config_file, 'v3', False, False, '', '', '', '', '', '', '', '', '', '') - - if not self.sorting_section: - log('Failed to initialize section "sorting" of configuration file.') - weechat.config_free(self.config_file) - return - - self.__case_sensitive = weechat.config_new_option( - self.config_file, self.sorting_section, - 'case_sensitive', 'boolean', - 'If this option is on, sorting is case sensitive.', - '', 0, 0, 'off', 'off', 0, - '', '', '', '', '', '' - ) - - weechat.config_new_option( - self.config_file, self.sorting_section, - 'rules', 'string', - 'Sort rules used by autosort v2.x and below. Not used by autosort anymore.', - '', 0, 0, '', '', 0, - '', '', '', '', '', '' - ) - - weechat.config_new_option( - self.config_file, self.sorting_section, - 'replacements', 'string', - 'Replacement patterns used by autosort v2.x and below. Not used by autosort anymore.', - '', 0, 0, '', '', 0, - '', '', '', '', '', '' - ) - - self.__rules = weechat.config_new_option( - self.config_file, self.v3_section, - 'rules', 'string', - 'An ordered list of sorting rules encoded as JSON. See /help autosort for commands to manipulate these rules.', - '', 0, 0, Config.default_rules, Config.default_rules, 0, - '', '', '', '', '', '' - ) - - self.__helpers = weechat.config_new_option( - self.config_file, self.v3_section, - 'helpers', 'string', - 'A dictionary helper variables to use in the sorting rules, encoded as JSON. See /help autosort for commands to manipulate these helpers.', - '', 0, 0, Config.default_helpers, Config.default_helpers, 0, - '', '', '', '', '', '' - ) - - self.__signals = weechat.config_new_option( - self.config_file, self.sorting_section, - 'signals', 'string', - 'A space separated list of signals that will cause autosort to resort your buffer list.', - '', 0, 0, Config.default_signals, Config.default_signals, 0, - '', '', '', '', '', '' - ) - - self.__signal_delay = weechat.config_new_option( - self.config_file, self.sorting_section, - 'signal_delay', 'integer', - 'Delay in milliseconds to wait after a signal before sorting the buffer list. This prevents triggering many times if multiple signals arrive in a short time. It can also be needed to wait for buffer localvars to be available.', - '', 0, 1000, str(Config.default_signal_delay), str(Config.default_signal_delay), 0, - '', '', '', '', '', '' - ) - - self.__sort_limit = weechat.config_new_option( - self.config_file, self.sorting_section, - 'sort_limit', 'integer', - 'Minimum delay in milliseconds to wait after sorting before signals can trigger a sort again. This is effectively a rate limit on sorting. Keeping signal_delay low while setting this higher can reduce excessive sorting without a long initial delay.', - '', 0, 1000, str(Config.default_sort_limit), str(Config.default_sort_limit), 0, - '', '', '', '', '', '' - ) - - self.__sort_on_config = weechat.config_new_option( - self.config_file, self.sorting_section, - 'sort_on_config_change', 'boolean', - 'Decides if the buffer list should be sorted when autosort configuration changes.', - '', 0, 0, 'on', 'on', 0, - '', '', '', '', '', '' - ) - - self.__debug_log = weechat.config_new_option( - self.config_file, self.sorting_section, - 'debug_log', 'boolean', - 'If enabled, print more debug messages. Not recommended for normal usage.', - '', 0, 0, 'off', 'off', 0, - '', '', '', '', '', '' - ) - - if weechat.config_read(self.config_file) != weechat.WEECHAT_RC_OK: - log('Failed to load configuration file.') - - if weechat.config_write(self.config_file) != weechat.WEECHAT_RC_OK: - log('Failed to write configuration file.') - - self.reload() - - def reload(self): - ''' Load configuration variables. ''' - - self.case_sensitive = weechat.config_boolean(self.__case_sensitive) - - rules_blob = weechat.config_string(self.__rules) - helpers_blob = weechat.config_string(self.__helpers) - signals_blob = weechat.config_string(self.__signals) - - self.rules = decode_rules(rules_blob) - self.helpers = decode_helpers(helpers_blob) - self.signals = signals_blob.split() - self.signal_delay = weechat.config_integer(self.__signal_delay) - self.sort_limit = weechat.config_integer(self.__sort_limit) - self.sort_on_config = weechat.config_boolean(self.__sort_on_config) - self.debug_log = weechat.config_boolean(self.__debug_log) - - def save_rules(self, run_callback = True): - ''' Save the current rules to the configuration. ''' - weechat.config_option_set(self.__rules, json.dumps(self.rules), run_callback) - - def save_helpers(self, run_callback = True): - ''' Save the current helpers to the configuration. ''' - weechat.config_option_set(self.__helpers, json.dumps(self.helpers), run_callback) - - -def pad(sequence, length, padding = None): - ''' Pad a list until is has a certain length. ''' - return sequence + [padding] * max(0, (length - len(sequence))) - -def log(message, buffer = 'NULL'): - weechat.prnt(buffer, 'autosort: {0}'.format(message)) - -def debug(message, buffer = 'NULL'): - if config.debug_log: - weechat.prnt(buffer, 'autosort: debug: {0}'.format(message)) - -def get_buffers(): - ''' Get a list of all the buffers in weechat. ''' - hdata = weechat.hdata_get('buffer') - buffer = weechat.hdata_get_list(hdata, "gui_buffers"); - - result = [] - while buffer: - number = weechat.hdata_integer(hdata, buffer, 'number') - result.append((number, buffer)) - buffer = weechat.hdata_pointer(hdata, buffer, 'next_buffer') - return hdata, result - -class MergedBuffers(list): - """ A list of merged buffers, possibly of size 1. """ - def __init__(self, number): - super(MergedBuffers, self).__init__() - self.number = number - -def merge_buffer_list(buffers): - ''' - Group merged buffers together. - The output is a list of MergedBuffers. - ''' - if not buffers: return [] - result = {} - for number, buffer in buffers: - if number not in result: result[number] = MergedBuffers(number) - result[number].append(buffer) - return result.values() - -def sort_buffers(hdata, buffers, rules, helpers, case_sensitive): - for merged in buffers: - for buffer in merged: - name = weechat.hdata_string(hdata, buffer, 'name') - - return sorted(buffers, key=merged_sort_key(rules, helpers, case_sensitive)) - -def buffer_sort_key(rules, helpers, case_sensitive): - ''' Create a sort key function for a list of lists of merged buffers. ''' - def key(buffer): - extra_vars = {} - for helper_name, helper in sorted(helpers.items()): - expanded = weechat.string_eval_expression(helper, {"buffer": buffer}, {}, {}) - extra_vars[helper_name] = expanded if case_sensitive else casefold(expanded) - result = [] - for rule in rules: - expanded = weechat.string_eval_expression(rule, {"buffer": buffer}, extra_vars, {}) - result.append(expanded if case_sensitive else casefold(expanded)) - return result - - return key - -def merged_sort_key(rules, helpers, case_sensitive): - buffer_key = buffer_sort_key(rules, helpers, case_sensitive) - def key(merged): - best = None - for buffer in merged: - this = buffer_key(buffer) - if best is None or this < best: best = this - return best - return key - -def apply_buffer_order(buffers): - ''' Sort the buffers in weechat according to the given order. ''' - for i, buffer in enumerate(buffers): - weechat.buffer_set(buffer[0], "number", str(i + 1)) - -def split_args(args, expected, optional = 0): - ''' Split an argument string in the desired number of arguments. ''' - split = args.split(' ', expected - 1) - if (len(split) < expected): - raise HumanReadableError('Expected at least {0} arguments, got {1}.'.format(expected, len(split))) - return split[:-1] + pad(split[-1].split(' ', optional), optional + 1, '') - -def do_sort(verbose = False): - start = perf_counter() - - hdata, buffers = get_buffers() - buffers = merge_buffer_list(buffers) - buffers = sort_buffers(hdata, buffers, config.rules, config.helpers, config.case_sensitive) - apply_buffer_order(buffers) - - elapsed = perf_counter() - start - if verbose: - log("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) - else: - debug("Finished sorting buffers in {0:.4f} seconds.".format(elapsed)) - -def command_sort(buffer, command, args): - ''' Sort the buffers and print a confirmation. ''' - do_sort(True) - return weechat.WEECHAT_RC_OK - -def command_debug(buffer, command, args): - hdata, buffers = get_buffers() - buffers = merge_buffer_list(buffers) - - # Show evaluation results. - log('Individual evaluation results:') - start = perf_counter() - key = buffer_sort_key(config.rules, config.helpers, config.case_sensitive) - results = [] - for merged in buffers: - for buffer in merged: - fullname = weechat.hdata_string(hdata, buffer, 'full_name') - results.append((fullname, key(buffer))) - elapsed = perf_counter() - start - - for fullname, result in results: - fullname = ensure_str(fullname) - result = [ensure_str(x) for x in result] - log('{0}: {1}'.format(fullname, result)) - log('Computing evaluation results took {0:.4f} seconds.'.format(elapsed)) - - return weechat.WEECHAT_RC_OK - -def command_rule_list(buffer, command, args): - ''' Show the list of sorting rules. ''' - output = 'Sorting rules:\n' - for i, rule in enumerate(config.rules): - output += ' {0}: {1}\n'.format(i, rule) - if not len(config.rules): - output += ' No sorting rules configured.\n' - log(output ) - - return weechat.WEECHAT_RC_OK - - -def command_rule_add(buffer, command, args): - ''' Add a rule to the rule list. ''' - config.rules.append(args) - config.save_rules() - command_rule_list(buffer, command, '') - - return weechat.WEECHAT_RC_OK - - -def command_rule_insert(buffer, command, args): - ''' Insert a rule at the desired position in the rule list. ''' - index, rule = split_args(args, 2) - index = parse_int(index, 'index') - - config.rules.insert(index, rule) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_update(buffer, command, args): - ''' Update a rule in the rule list. ''' - index, rule = split_args(args, 2) - index = parse_int(index, 'index') - - config.rules[index] = rule - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_delete(buffer, command, args): - ''' Delete a rule from the rule list. ''' - index = args.strip() - index = parse_int(index, 'index') - - config.rules.pop(index) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_move(buffer, command, args): - ''' Move a rule to a new position. ''' - index_a, index_b = split_args(args, 2) - index_a = parse_int(index_a, 'index') - index_b = parse_int(index_b, 'index') - - list_move(config.rules, index_a, index_b) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_rule_swap(buffer, command, args): - ''' Swap two rules. ''' - index_a, index_b = split_args(args, 2) - index_a = parse_int(index_a, 'index') - index_b = parse_int(index_b, 'index') - - list_swap(config.rules, index_a, index_b) - config.save_rules() - command_rule_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_list(buffer, command, args): - ''' Show the list of helpers. ''' - output = 'Helper variables:\n' - - width = max(map(lambda x: len(x) if len(x) <= 30 else 0, config.helpers.keys())) - - for name, expression in sorted(config.helpers.items()): - output += ' {0:>{width}}: {1}\n'.format(name, expression, width=width) - if not len(config.helpers): - output += ' No helper variables configured.' - log(output) - - return weechat.WEECHAT_RC_OK - - -def command_helper_set(buffer, command, args): - ''' Add/update a helper to the helper list. ''' - name, expression = split_args(args, 2) - - config.helpers[name] = expression - config.save_helpers() - command_helper_list(buffer, command, '') - - return weechat.WEECHAT_RC_OK - -def command_helper_delete(buffer, command, args): - ''' Delete a helper from the helper list. ''' - name = args.strip() - - del config.helpers[name] - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_rename(buffer, command, args): - ''' Rename a helper to a new position. ''' - old_name, new_name = split_args(args, 2) - - try: - config.helpers[new_name] = config.helpers[old_name] - del config.helpers[old_name] - except KeyError: - raise HumanReadableError('No such helper: {0}'.format(old_name)) - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - - -def command_helper_swap(buffer, command, args): - ''' Swap two helpers. ''' - a, b = split_args(args, 2) - try: - config.helpers[b], config.helpers[a] = config.helpers[a], config.helpers[b] - except KeyError as e: - raise HumanReadableError('No such helper: {0}'.format(e.args[0])) - - config.helpers.swap(index_a, index_b) - config.save_helpers() - command_helper_list(buffer, command, '') - return weechat.WEECHAT_RC_OK - -def call_command(buffer, command, args, subcommands): - ''' Call a subcommand from a dictionary. ''' - subcommand, tail = pad(args.split(' ', 1), 2, '') - subcommand = subcommand.strip() - if (subcommand == ''): - child = subcommands.get(' ') - else: - command = command + [subcommand] - child = subcommands.get(subcommand) - - if isinstance(child, dict): - return call_command(buffer, command, tail, child) - elif callable(child): - return child(buffer, command, tail) - - log('{0}: command not found'.format(' '.join(command))) - return weechat.WEECHAT_RC_ERROR - -def on_signal(data, signal, signal_data): - global signal_delay_timer - global sort_queued - - # If the sort limit timeout is started, we're in the hold-off time after sorting, just queue a sort. - if sort_limit_timer is not None: - if sort_queued: - debug('Signal {0} ignored, sort limit timeout is active and sort is already queued.'.format(signal)) - else: - debug('Signal {0} received but sort limit timeout is active, sort is now queued.'.format(signal)) - sort_queued = True - return weechat.WEECHAT_RC_OK - - # If the signal delay timeout is started, a signal was recently received, so ignore this signal. - if signal_delay_timer is not None: - debug('Signal {0} ignored, signal delay timeout active.'.format(signal)) - return weechat.WEECHAT_RC_OK - - # Otherwise, start the signal delay timeout. - debug('Signal {0} received, starting signal delay timeout of {1} ms.'.format(signal, config.signal_delay)) - weechat.hook_timer(config.signal_delay, 0, 1, "on_signal_delay_timeout", "") - return weechat.WEECHAT_RC_OK - -def on_signal_delay_timeout(pointer, remaining_calls): - """ Called when the signal_delay_timer triggers. """ - global signal_delay_timer - global sort_limit_timer - global sort_queued - - signal_delay_timer = None - - # If the sort limit timeout was started, we're still in the no-sort period, so just queue a sort. - if sort_limit_timer is not None: - debug('Signal delay timeout expired, but sort limit timeout is active, sort is now queued.') - sort_queued = True - return weechat.WEECHAT_RC_OK - - # Time to sort! - debug('Signal delay timeout expired, starting sort.') - do_sort() - - # Start the sort limit timeout if not disabled. - if config.sort_limit > 0: - debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) - sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") - - return weechat.WEECHAT_RC_OK - -def on_sort_limit_timeout(pointer, remainin_calls): - """ Called when de sort_limit_timer triggers. """ - global sort_limit_timer - global sort_queued - - # If no signal was received during the timeout, we're done. - if not sort_queued: - debug('Sort limit timeout expired without receiving a signal.') - sort_limit_timer = None - return weechat.WEECHAT_RC_OK - - # Otherwise it's time to sort. - debug('Signal received during sort limit timeout, starting queued sort.') - do_sort() - sort_queued = False - - # Start the sort limit timeout again if not disabled. - if config.sort_limit > 0: - debug('Starting sort limit timeout of {0} ms.'.format(config.sort_limit)) - sort_limit_timer = weechat.hook_timer(config.sort_limit, 0, 1, "on_sort_limit_timeout", "") - - return weechat.WEECHAT_RC_OK - - -def apply_config(): - # Unhook all signals and hook the new ones. - for hook in hooks: - weechat.unhook(hook) - for signal in config.signals: - hooks.append(weechat.hook_signal(signal, 'on_signal', '')) - - if config.sort_on_config: - debug('Sorting because configuration changed.') - do_sort() - -def on_config_changed(*args, **kwargs): - ''' Called whenever the configuration changes. ''' - config.reload() - apply_config() - - return weechat.WEECHAT_RC_OK - -def parse_arg(args): - if not args: return '', None - - result = '' - escaped = False - for i, c in enumerate(args): - if not escaped: - if c == '\\': - escaped = True - continue - elif c == ',': - return result, args[i+1:] - result += c - escaped = False - return result, None - -def parse_args(args, max = None): - result = [] - i = 0 - while max is None or i < max: - i += 1 - arg, args = parse_arg(args) - if arg is None: break - result.append(arg) - if args is None: break - return result, args - -def on_info_escape(pointer, name, arguments): - result = '' - for c in arguments: - if c == '\\': - result += '\\\\' - elif c == ',': - result += '\\,' - else: - result +=c - return result - -def on_info_replace(pointer, name, arguments): - arguments, rest = parse_args(arguments, 3) - if rest or len(arguments) < 3: - log('usage: ${{info:{0},old,new,text}}'.format(name)) - return '' - old, new, text = arguments - - return text.replace(old, new) - -def on_info_order(pointer, name, arguments): - arguments, rest = parse_args(arguments) - if len(arguments) < 1: - log('usage: ${{info:{0},value,first,second,third,...}}'.format(name)) - return '' - - value = arguments[0] - keys = arguments[1:] - if not keys: return '0' - - # Find the value in the keys (or '*' if we can't find it) - result = list_find(keys, value) - if result is None: result = list_find(keys, '*') - if result is None: result = len(keys) - - # Pad result with leading zero to make sure string sorting works. - width = int(math.log10(len(keys))) + 1 - return '{0:0{1}}'.format(result, width) - - -def on_autosort_command(data, buffer, args): - ''' Called when the autosort command is invoked. ''' - try: - return call_command(buffer, ['/autosort'], args, { - ' ': command_sort, - 'sort': command_sort, - 'debug': command_debug, - - 'rules': { - ' ': command_rule_list, - 'list': command_rule_list, - 'add': command_rule_add, - 'insert': command_rule_insert, - 'update': command_rule_update, - 'delete': command_rule_delete, - 'move': command_rule_move, - 'swap': command_rule_swap, - }, - 'helpers': { - ' ': command_helper_list, - 'list': command_helper_list, - 'set': command_helper_set, - 'delete': command_helper_delete, - 'rename': command_helper_rename, - 'swap': command_helper_swap, - }, - }) - except HumanReadableError as e: - log(e) - return weechat.WEECHAT_RC_ERROR - -def add_completions(completion, words): - for word in words: - weechat.hook_completion_list_add(completion, word, 0, weechat.WEECHAT_LIST_POS_END) - -def autosort_complete_rules(words, completion): - if len(words) == 0: - add_completions(completion, ['add', 'delete', 'insert', 'list', 'move', 'swap', 'update']) - if len(words) == 1 and words[0] in ('delete', 'insert', 'move', 'swap', 'update'): - add_completions(completion, map(str, range(len(config.rules)))) - if len(words) == 2 and words[0] in ('move', 'swap'): - add_completions(completion, map(str, range(len(config.rules)))) - if len(words) == 2 and words[0] in ('update'): - try: - add_completions(completion, [config.rules[int(words[1])]]) - except KeyError: pass - except ValueError: pass - else: - add_completions(completion, ['']) - return weechat.WEECHAT_RC_OK - -def autosort_complete_helpers(words, completion): - if len(words) == 0: - add_completions(completion, ['delete', 'list', 'rename', 'set', 'swap']) - elif len(words) == 1 and words[0] in ('delete', 'rename', 'set', 'swap'): - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'swap': - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'rename': - add_completions(completion, sorted(config.helpers.keys())) - elif len(words) == 2 and words[0] == 'set': - try: - add_completions(completion, [config.helpers[words[1]]]) - except KeyError: pass - return weechat.WEECHAT_RC_OK - -def on_autosort_complete(data, name, buffer, completion): - cmdline = weechat.buffer_get_string(buffer, "input") - cursor = weechat.buffer_get_integer(buffer, "input_pos") - prefix = cmdline[:cursor] - words = prefix.split()[1:] - - # If the current word isn't finished yet, - # ignore it for coming up with completion suggestions. - if prefix[-1] != ' ': words = words[:-1] - - if len(words) == 0: - add_completions(completion, ['debug', 'helpers', 'rules', 'sort']) - elif words[0] == 'rules': - return autosort_complete_rules(words[1:], completion) - elif words[0] == 'helpers': - return autosort_complete_helpers(words[1:], completion) - return weechat.WEECHAT_RC_OK - -command_description = r'''{*white}# General commands{reset} - -{*white}/autosort {brown}sort{reset} -Manually trigger the buffer sorting. - -{*white}/autosort {brown}debug{reset} -Show the evaluation results of the sort rules for each buffer. - - -{*white}# Sorting rule commands{reset} - -{*white}/autosort{brown} rules list{reset} -Print the list of sort rules. - -{*white}/autosort {brown}rules add {cyan}{reset} -Add a new rule at the end of the list. - -{*white}/autosort {brown}rules insert {cyan} {reset} -Insert a new rule at the given index in the list. - -{*white}/autosort {brown}rules update {cyan} {reset} -Update a rule in the list with a new expression. - -{*white}/autosort {brown}rules delete {cyan} -Delete a rule from the list. - -{*white}/autosort {brown}rules move {cyan} {reset} -Move a rule from one position in the list to another. - -{*white}/autosort {brown}rules swap {cyan} {reset} -Swap two rules in the list - - -{*white}# Helper variable commands{reset} - -{*white}/autosort {brown}helpers list -Print the list of helper variables. - -{*white}/autosort {brown}helpers set {cyan} -Add or update a helper variable with the given name. - -{*white}/autosort {brown}helpers delete {cyan} -Delete a helper variable. - -{*white}/autosort {brown}helpers rename {cyan} -Rename a helper variable. - -{*white}/autosort {brown}helpers swap {cyan} -Swap the expressions of two helper variables in the list. - - -{*white}# Info hooks{reset} -Autosort comes with a number of info hooks to add some extra functionality to regular weechat eval strings. -Info hooks can be used in eval strings in the form of {cyan}${{info:some_hook,arguments}}{reset}. - -Commas and backslashes in arguments to autosort info hooks (except for {cyan}${{info:autosort_escape}}{reset}) must be escaped with a backslash. - -{*white}${{info:{brown}autosort_replace{white},{cyan}pattern{white},{cyan}replacement{white},{cyan}source{white}}}{reset} -Replace all occurrences of {cyan}pattern{reset} with {cyan}replacement{reset} in the string {cyan}source{reset}. -Can be used to ignore certain strings when sorting by replacing them with an empty string. - -For example: {cyan}${{info:autosort_replace,cat,dog,the dog is meowing}}{reset} expands to "the cat is meowing". - -{*white}${{info:{brown}autosort_order{white},{cyan}value{white},{cyan}option0{white},{cyan}option1{white},{cyan}option2{white},{cyan}...{white}}} -Generate a zero-padded number that corresponds to the index of {cyan}value{reset} in the list of options. -If one of the options is the special value {brown}*{reset}, then any value not explicitly mentioned will be sorted at that position. -Otherwise, any value that does not match an option is assigned the highest number available. -Can be used to easily sort buffers based on a manual sequence. - -For example: {cyan}${{info:autosort_order,${{server}},freenode,oftc,efnet}}{reset} will sort freenode before oftc, followed by efnet and then any remaining servers. -Alternatively, {cyan}${{info:autosort_order,${{server}},freenode,oftc,*,efnet}}{reset} will sort any unlisted servers after freenode and oftc, but before efnet. - -{*white}${{info:{brown}autosort_escape{white},{cyan}text{white}}}{reset} -Escape commas and backslashes in {cyan}text{reset} by prepending them with a backslash. -This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. -Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments. - -For example, it can be used to safely pass buffer names to {cyan}${{info:autosort_replace}}{reset} like so: -{cyan}${{info:autosort_replace,##,#,${{info:autosort_escape,${{buffer.name}}}}}}{reset}. - - -{*white}# Description -Autosort is a weechat script to automatically keep your buffers sorted. The sort -order can be customized by defining your own sort rules, but the default should -be sane enough for most people. It can also group IRC channel/private buffers -under their server buffer if you like. - -Autosort uses a stable sorting algorithm, meaning that you can manually move buffers -to change their relative order, if they sort equal with your rule set. - -{*white}# Sort rules{reset} -Autosort evaluates a list of eval expressions (see {*default}/help eval{reset}) and sorts the -buffers based on evaluated result. Earlier rules will be considered first. Only -if earlier rules produced identical results is the result of the next rule -considered for sorting purposes. - -You can debug your sort rules with the `{*default}/autosort debug{reset}` command, which will -print the evaluation results of each rule for each buffer. - -{*brown}NOTE:{reset} The sort rules for version 3 are not compatible with version 2 or vice -versa. You will have to manually port your old rules to version 3 if you have any. - -{*white}# Helper variables{reset} -You may define helper variables for the main sort rules to keep your rules -readable. They can be used in the main sort rules as variables. For example, -a helper variable named `{cyan}foo{reset}` can be accessed in a main rule with the -string `{cyan}${{foo}}{reset}`. - -{*white}# Automatic or manual sorting{reset} -By default, autosort will automatically sort your buffer list whenever a buffer -is opened, merged, unmerged or renamed. This should keep your buffers sorted in -almost all situations. However, you may wish to change the list of signals that -cause your buffer list to be sorted. Simply edit the `{cyan}autosort.sorting.signals{reset}` -option to add or remove any signal you like. - -If you remove all signals you can still sort your buffers manually with the -`{*default}/autosort sort{reset}` command. To prevent all automatic sorting, the option -`{cyan}autosort.sorting.sort_on_config_change{reset}` should also be disabled. - -{*white}# Recommended settings -For the best visual effect, consider setting the following options: - {*white}/set {cyan}irc.look.server_buffer{reset} {brown}independent{reset} - -This setting allows server buffers to be sorted independently, which is -needed to create a hierarchical tree view of the server and channel buffers. - -If you are using the {*default}buflist{reset} plugin you can (ab)use Unicode to draw a tree -structure with the following setting (modify to suit your need): - {*white}/set {cyan}buflist.format.indent {brown}"${{color:237}}${{if:${{buffer.next_buffer.local_variables.type}}=~^(channel|private)$?├─:└─}}"{reset} -''' - -command_completion = '%(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort) %(plugin_autosort)' - -info_replace_description = ( - 'Replace all occurrences of `pattern` with `replacement` in the string `source`. ' - 'Can be used to ignore certain strings when sorting by replacing them with an empty string. ' - 'See /help autosort for examples.' -) -info_replace_arguments = 'pattern,replacement,source' - -info_order_description = ( - 'Generate a zero-padded number that corresponds to the index of `value` in the list of options. ' - 'If one of the options is the special value `*`, then any value not explicitly mentioned will be sorted at that position. ' - 'Otherwise, any value that does not match an option is assigned the highest number available. ' - 'Can be used to easily sort buffers based on a manual sequence. ' - 'See /help autosort for examples.' -) -info_order_arguments = 'value,first,second,third,...' - -info_escape_description = ( - 'Escape commas and backslashes in `text` by prepending them with a backslash. ' - 'This is mainly useful to pass arbitrary eval strings as arguments to other autosort info hooks. ' - 'Otherwise, an eval string that expands to something with a comma would be interpreted as multiple arguments.' - 'See /help autosort for examples.' -) -info_escape_arguments = 'text' - - -if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): - config = Config('autosort') - - colors = { - 'default': weechat.color('default'), - 'reset': weechat.color('reset'), - 'black': weechat.color('black'), - 'red': weechat.color('red'), - 'green': weechat.color('green'), - 'brown': weechat.color('brown'), - 'yellow': weechat.color('yellow'), - 'blue': weechat.color('blue'), - 'magenta': weechat.color('magenta'), - 'cyan': weechat.color('cyan'), - 'white': weechat.color('white'), - '*default': weechat.color('*default'), - '*black': weechat.color('*black'), - '*red': weechat.color('*red'), - '*green': weechat.color('*green'), - '*brown': weechat.color('*brown'), - '*yellow': weechat.color('*yellow'), - '*blue': weechat.color('*blue'), - '*magenta': weechat.color('*magenta'), - '*cyan': weechat.color('*cyan'), - '*white': weechat.color('*white'), - } - - weechat.hook_config('autosort.*', 'on_config_changed', '') - weechat.hook_completion('plugin_autosort', '', 'on_autosort_complete', '') - weechat.hook_command('autosort', command_description.format(**colors), '', '', command_completion, 'on_autosort_command', '') - weechat.hook_info('autosort_escape', info_escape_description, info_escape_arguments, 'on_info_escape', '') - weechat.hook_info('autosort_replace', info_replace_description, info_replace_arguments, 'on_info_replace', '') - weechat.hook_info('autosort_order', info_order_description, info_order_arguments, 'on_info_order', '') - - apply_config() diff --git a/.config/weechat/python/colorize_nicks.py b/.config/weechat/python/colorize_nicks.py deleted file mode 100644 index cb95a0d6..00000000 --- a/.config/weechat/python/colorize_nicks.py +++ /dev/null @@ -1,409 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2010 by xt -# -# 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 3 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 program. If not, see . -# - -# This script colors nicks in IRC channels in the actual message -# not just in the prefix section. -# -# -# History: -# 2020-11-29: jess -# version 28: fix ignore_tags having been broken by weechat 2.9 changes -# 2020-05-09: Sébastien Helleu -# version 27: add compatibility with new weechat_print modifier data -# (WeeChat >= 2.9) -# 2018-04-06: Joey Pabalinas -# version 26: fix freezes with too many nicks in one line -# 2018-03-18: nils_2 -# version 25: fix unable to run function colorize_config_reload_cb() -# 2017-06-20: lbeziaud -# version 24: colorize utf8 nicks -# 2017-03-01, arza -# version 23: don't colorize nicklist group names -# 2016-05-01, Simmo Saan -# version 22: invalidate cached colors on hash algorithm change -# 2015-07-28, xt -# version 21: fix problems with nicks with commas in them -# 2015-04-19, xt -# version 20: fix ignore of nicks in URLs -# 2015-04-18, xt -# version 19: new option ignore nicks in URLs -# 2015-03-03, xt -# version 18: iterate buffers looking for nicklists instead of servers -# 2015-02-23, holomorph -# version 17: fix coloring in non-channel buffers (#58) -# 2014-09-17, holomorph -# version 16: use weechat config facilities -# clean unused, minor linting, some simplification -# 2014-05-05, holomorph -# version 15: fix python2-specific re.search check -# 2013-01-29, nils_2 -# version 14: make script compatible with Python 3.x -# 2012-10-19, ldvx -# version 13: Iterate over every word to prevent incorrect colorization of -# nicks. Added option greedy_matching. -# 2012-04-28, ldvx -# version 12: added ignore_tags to avoid colorizing nicks if tags are present -# 2012-01-14, nesthib -# version 11: input_text_display hook and modifier to colorize nicks in input bar -# 2010-12-22, xt -# version 10: hook config option for updating blacklist -# 2010-12-20, xt -# version 0.9: hook new config option for weechat 0.3.4 -# 2010-11-01, nils_2 -# version 0.8: hook_modifier() added to communicate with rainbow_text -# 2010-10-01, xt -# version 0.7: changes to support non-irc-plugins -# 2010-07-29, xt -# version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com -# 2010-07-19, xt -# version 0.5: fix bug with incorrect coloring of own nick -# 2010-06-02, xt -# version 0.4: update to reflect API changes -# 2010-03-26, xt -# version 0.3: fix error with exception -# 2010-03-24, xt -# version 0.2: use ignore_channels when populating to increase performance. -# 2010-02-03, xt -# version 0.1: initial (based on ruby script by dominikh) -# -# Known issues: nicks will not get colorized if they begin with a character -# such as ~ (which some irc networks do happen to accept) - -import weechat -import re -w = weechat - -SCRIPT_NAME = "colorize_nicks" -SCRIPT_AUTHOR = "xt " -SCRIPT_VERSION = "28" -SCRIPT_LICENSE = "GPL" -SCRIPT_DESC = "Use the weechat nick colors in the chat area" - -# Based on the recommendations in RFC 7613. A valid nick is composed -# of anything but " ,*?.!@". -VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@]+)' -valid_nick_re = re.compile(VALID_NICK) -ignore_channels = [] -ignore_nicks = [] - -# Dict with every nick on every channel with its color as lookup value -colored_nicks = {} - -CONFIG_FILE_NAME = "colorize_nicks" - -# config file and options -colorize_config_file = "" -colorize_config_option = {} - -def colorize_config_init(): - ''' - Initialization of configuration file. - Sections: look. - ''' - global colorize_config_file, colorize_config_option - colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, - "", "") - if colorize_config_file == "": - return - - # section "look" - section_look = weechat.config_new_section( - colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") - if section_look == "": - weechat.config_free(colorize_config_file) - return - colorize_config_option["blacklist_channels"] = weechat.config_new_option( - colorize_config_file, section_look, "blacklist_channels", - "string", "Comma separated list of channels", "", 0, 0, - "", "", 0, "", "", "", "", "", "") - colorize_config_option["blacklist_nicks"] = weechat.config_new_option( - colorize_config_file, section_look, "blacklist_nicks", - "string", "Comma separated list of nicks", "", 0, 0, - "so,root", "so,root", 0, "", "", "", "", "", "") - colorize_config_option["min_nick_length"] = weechat.config_new_option( - colorize_config_file, section_look, "min_nick_length", - "integer", "Minimum length nick to colorize", "", - 2, 20, "", "", 0, "", "", "", "", "", "") - colorize_config_option["colorize_input"] = weechat.config_new_option( - colorize_config_file, section_look, "colorize_input", - "boolean", "Whether to colorize input", "", 0, - 0, "off", "off", 0, "", "", "", "", "", "") - colorize_config_option["ignore_tags"] = weechat.config_new_option( - colorize_config_file, section_look, "ignore_tags", - "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0, - "", "", 0, "", "", "", "", "", "") - colorize_config_option["greedy_matching"] = weechat.config_new_option( - colorize_config_file, section_look, "greedy_matching", - "boolean", "If off, then use lazy matching instead", "", 0, - 0, "on", "on", 0, "", "", "", "", "", "") - colorize_config_option["match_limit"] = weechat.config_new_option( - colorize_config_file, section_look, "match_limit", - "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", - 20, 1000, "", "", 0, "", "", "", "", "", "") - colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( - colorize_config_file, section_look, "ignore_nicks_in_urls", - "boolean", "If on, don't colorize nicks inside URLs", "", 0, - 0, "off", "off", 0, "", "", "", "", "", "") - -def colorize_config_read(): - ''' Read configuration file. ''' - global colorize_config_file - return weechat.config_read(colorize_config_file) - -def colorize_nick_color(nick, my_nick): - ''' Retrieve nick color from weechat. ''' - if nick == my_nick: - return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) - else: - return w.info_get('irc_nick_color', nick) - -def colorize_cb(data, modifier, modifier_data, line): - ''' Callback that does the colorizing, and returns new line if changed ''' - - global ignore_nicks, ignore_channels, colored_nicks - - if modifier_data.startswith('0x'): - # WeeChat >= 2.9 - buffer, tags = modifier_data.split(';', 1) - else: - # WeeChat <= 2.8 - plugin, buffer_name, tags = modifier_data.split(';', 2) - buffer = w.buffer_search(plugin, buffer_name) - - channel = w.buffer_get_string(buffer, 'localvar_channel') - tags = tags.split(',') - - # Check if buffer has colorized nicks - if buffer not in colored_nicks: - return line - - if channel and channel in ignore_channels: - return line - - min_length = w.config_integer(colorize_config_option['min_nick_length']) - reset = w.color('reset') - - # Don't colorize if the ignored tag is present in message - tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',') - for tag in tags: - if tag in tag_ignores: - return line - - for words in valid_nick_re.findall(line): - nick = words[1] - # Check that nick is not ignored and longer than minimum length - if len(nick) < min_length or nick in ignore_nicks: - continue - - # If the matched word is not a known nick, we try to match the - # word without its first or last character (if not a letter). - # This is necessary as "foo:" is a valid nick, which could be - # adressed as "foo::". - if nick not in colored_nicks[buffer]: - if not nick[-1].isalpha() and not nick[0].isalpha(): - if nick[1:-1] in colored_nicks[buffer]: - nick = nick[1:-1] - elif not nick[0].isalpha(): - if nick[1:] in colored_nicks[buffer]: - nick = nick[1:] - elif not nick[-1].isalpha(): - if nick[:-1] in colored_nicks[buffer]: - nick = nick[:-1] - - # Check that nick is in the dictionary colored_nicks - if nick in colored_nicks[buffer]: - nick_color = colored_nicks[buffer][nick] - - try: - # Let's use greedy matching. Will check against every word in a line. - if w.config_boolean(colorize_config_option['greedy_matching']): - cnt = 0 - limit = w.config_integer(colorize_config_option['match_limit']) - - for word in line.split(): - cnt += 1 - assert cnt < limit - # if cnt > limit: - # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); - - if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ - word.startswith(('http://', 'https://')): - continue - - if nick in word: - # Is there a nick that contains nick and has a greater lenght? - # If so let's save that nick into var biggest_nick - biggest_nick = "" - for i in colored_nicks[buffer]: - cnt += 1 - assert cnt < limit - - if nick in i and nick != i and len(i) > len(nick): - if i in word: - # If a nick with greater len is found, and that word - # also happens to be in word, then let's save this nick - biggest_nick = i - # If there's a nick with greater len, then let's skip this - # As we will have the chance to colorize when biggest_nick - # iterates being nick. - if len(biggest_nick) > 0 and biggest_nick in word: - pass - elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: - new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) - line = line.replace(word, new_word) - - # Switch to lazy matching - else: - raise AssertionError - - except AssertionError: - # Let's use lazy matching for nick - nick_color = colored_nicks[buffer][nick] - # The two .? are in case somebody writes "nick:", "nick,", etc - # to address somebody - regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick) - match = re.search(regex, line) - if match is not None: - new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] - line = new_line - - return line - -def colorize_input_cb(data, modifier, modifier_data, line): - ''' Callback that does the colorizing in input ''' - - global ignore_nicks, ignore_channels, colored_nicks - - min_length = w.config_integer(colorize_config_option['min_nick_length']) - - if not w.config_boolean(colorize_config_option['colorize_input']): - return line - - buffer = w.current_buffer() - # Check if buffer has colorized nicks - if buffer not in colored_nicks: - return line - - channel = w.buffer_get_string(buffer, 'name') - if channel and channel in ignore_channels: - return line - - reset = w.color('reset') - - for words in valid_nick_re.findall(line): - nick = words[1] - # Check that nick is not ignored and longer than minimum length - if len(nick) < min_length or nick in ignore_nicks: - continue - if nick in colored_nicks[buffer]: - nick_color = colored_nicks[buffer][nick] - line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset)) - - return line - -def populate_nicks(*args): - ''' Fills entire dict with all nicks weechat can see and what color it has - assigned to it. ''' - global colored_nicks - - colored_nicks = {} - - buffers = w.infolist_get('buffer', '', '') - while w.infolist_next(buffers): - buffer_ptr = w.infolist_pointer(buffers, 'pointer') - my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick') - nicklist = w.infolist_get('nicklist', buffer_ptr, '') - while w.infolist_next(nicklist): - if buffer_ptr not in colored_nicks: - colored_nicks[buffer_ptr] = {} - - if w.infolist_string(nicklist, 'type') != 'nick': - continue - - nick = w.infolist_string(nicklist, 'name') - nick_color = colorize_nick_color(nick, my_nick) - - colored_nicks[buffer_ptr][nick] = nick_color - - w.infolist_free(nicklist) - - w.infolist_free(buffers) - - return w.WEECHAT_RC_OK - -def add_nick(data, signal, type_data): - ''' Add nick to dict of colored nicks ''' - global colored_nicks - - # Nicks can have , in them in some protocols - splitted = type_data.split(',') - pointer = splitted[0] - nick = ",".join(splitted[1:]) - if pointer not in colored_nicks: - colored_nicks[pointer] = {} - - my_nick = w.buffer_get_string(pointer, 'localvar_nick') - nick_color = colorize_nick_color(nick, my_nick) - - colored_nicks[pointer][nick] = nick_color - - return w.WEECHAT_RC_OK - -def remove_nick(data, signal, type_data): - ''' Remove nick from dict with colored nicks ''' - global colored_nicks - - # Nicks can have , in them in some protocols - splitted = type_data.split(',') - pointer = splitted[0] - nick = ",".join(splitted[1:]) - - if pointer in colored_nicks and nick in colored_nicks[pointer]: - del colored_nicks[pointer][nick] - - return w.WEECHAT_RC_OK - -def update_blacklist(*args): - ''' Set the blacklist for channels and nicks. ''' - global ignore_channels, ignore_nicks - ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',') - ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',') - return w.WEECHAT_RC_OK - -if __name__ == "__main__": - if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "", ""): - colorize_config_init() - colorize_config_read() - - # Run once to get data ready - update_blacklist() - populate_nicks() - - w.hook_signal('nicklist_nick_added', 'add_nick', '') - w.hook_signal('nicklist_nick_removed', 'remove_nick', '') - w.hook_modifier('weechat_print', 'colorize_cb', '') - # Hook config for changing colors - w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '') - w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '') - # Hook for working togheter with other scripts (like colorize_lines) - w.hook_modifier('colorize_nicks', 'colorize_cb', '') - # Hook for modifying input - w.hook_modifier('250|input_text_display', 'colorize_input_cb', '') - # Hook for updating blacklist (this could be improved to use fnmatch) - weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '') diff --git a/.config/weechat/python/go.py b/.config/weechat/python/go.py deleted file mode 100644 index 2ab47ed4..00000000 --- a/.config/weechat/python/go.py +++ /dev/null @@ -1,563 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2009-2014 Sébastien Helleu -# Copyright (C) 2010 m4v -# Copyright (C) 2011 stfn -# -# 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 3 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 program. If not, see . -# - -# -# History: -# -# 2019-07-11, Simmo Saan -# version 2.6: fix detection of "/input search_text_here" -# 2017-04-01, Sébastien Helleu : -# version 2.5: add option "buffer_number" -# 2017-03-02, Sébastien Helleu : -# version 2.4: fix syntax and indentation error -# 2017-02-25, Simmo Saan -# version 2.3: fix fuzzy search breaking buffer number search display -# 2016-01-28, ylambda -# version 2.2: add option "fuzzy_search" -# 2015-11-12, nils_2 -# version 2.1: fix problem with buffer short_name "weechat", using option -# "use_core_instead_weechat", see: -# https://github.com/weechat/weechat/issues/574 -# 2014-05-12, Sébastien Helleu : -# version 2.0: add help on options, replace option "sort_by_activity" by -# "sort" (add sort by name and first match at beginning of -# name and by number), PEP8 compliance -# 2012-11-26, Nei -# version 1.9: add auto_jump option to automatically go to buffer when it -# is uniquely selected -# 2012-09-17, Sébastien Helleu : -# version 1.8: fix jump to non-active merged buffers (jump with buffer name -# instead of number) -# 2012-01-03 nils_2 -# version 1.7: add option "use_core_instead_weechat" -# 2012-01-03, Sébastien Helleu : -# version 1.6: make script compatible with Python 3.x -# 2011-08-24, stfn : -# version 1.5: /go with name argument jumps directly to buffer -# Remember cursor position in buffer input -# 2011-05-31, Elián Hanisch : -# version 1.4: Sort list of buffers by activity. -# 2011-04-25, Sébastien Helleu : -# version 1.3: add info "go_running" (used by script input_lock.rb) -# 2010-11-01, Sébastien Helleu : -# version 1.2: use high priority for hooks to prevent conflict with other -# plugins/scripts (WeeChat >= 0.3.4 only) -# 2010-03-25, Elián Hanisch : -# version 1.1: use a space to match the end of a string -# 2009-11-16, Sébastien Helleu : -# version 1.0: add new option to display short names -# 2009-06-15, Sébastien Helleu : -# version 0.9: fix typo in /help go with command /key -# 2009-05-16, Sébastien Helleu : -# version 0.8: search buffer by number, fix bug when window is split -# 2009-05-03, Sébastien Helleu : -# version 0.7: eat tab key (do not complete input, just move buffer -# pointer) -# 2009-05-02, Sébastien Helleu : -# version 0.6: sync with last API changes -# 2009-03-22, Sébastien Helleu : -# version 0.5: update modifier signal name for input text display, -# fix arguments for function string_remove_color -# 2009-02-18, Sébastien Helleu : -# version 0.4: do not hook command and init options if register failed -# 2009-02-08, Sébastien Helleu : -# version 0.3: case insensitive search for buffers names -# 2009-02-08, Sébastien Helleu : -# version 0.2: add help about Tab key -# 2009-02-08, Sébastien Helleu : -# version 0.1: initial release -# - -""" -Quick jump to buffers. -(this script requires WeeChat 0.3.0 or newer) -""" - -from __future__ import print_function - -SCRIPT_NAME = 'go' -SCRIPT_AUTHOR = 'Sébastien Helleu ' -SCRIPT_VERSION = '2.6' -SCRIPT_LICENSE = 'GPL3' -SCRIPT_DESC = 'Quick jump to buffers' - -SCRIPT_COMMAND = 'go' - -IMPORT_OK = True - -try: - import weechat -except ImportError: - print('This script must be run under WeeChat.') - print('Get WeeChat now at: http://www.weechat.org/') - IMPORT_OK = False - -import re - -# script options -SETTINGS = { - 'color_number': ( - 'yellow,magenta', - 'color for buffer number (not selected)'), - 'color_number_selected': ( - 'yellow,red', - 'color for selected buffer number'), - 'color_name': ( - 'black,cyan', - 'color for buffer name (not selected)'), - 'color_name_selected': ( - 'black,brown', - 'color for a selected buffer name'), - 'color_name_highlight': ( - 'red,cyan', - 'color for highlight in buffer name (not selected)'), - 'color_name_highlight_selected': ( - 'red,brown', - 'color for highlight in a selected buffer name'), - 'message': ( - 'Go to: ', - 'message to display before list of buffers'), - 'short_name': ( - 'off', - 'display and search in short names instead of buffer name'), - 'sort': ( - 'number,beginning', - 'comma-separated list of keys to sort buffers ' - '(the order is important, sorts are performed in the given order): ' - 'name = sort by name (or short name), ', - 'hotlist = sort by hotlist order, ' - 'number = first match a buffer number before digits in name, ' - 'beginning = first match at beginning of names (or short names); ' - 'the default sort of buffers is by numbers'), - 'use_core_instead_weechat': ( - 'off', - 'use name "core" instead of "weechat" for core buffer'), - 'auto_jump': ( - 'off', - 'automatically jump to buffer when it is uniquely selected'), - 'fuzzy_search': ( - 'off', - 'search buffer matches using approximation'), - 'buffer_number': ( - 'on', - 'display buffer number'), -} - -# hooks management -HOOK_COMMAND_RUN = { - 'input': ('/input *', 'go_command_run_input'), - 'buffer': ('/buffer *', 'go_command_run_buffer'), - 'window': ('/window *', 'go_command_run_window'), -} -hooks = {} - -# input before command /go (we'll restore it later) -saved_input = '' -saved_input_pos = 0 - -# last user input (if changed, we'll update list of matching buffers) -old_input = None - -# matching buffers -buffers = [] -buffers_pos = 0 - - -def go_option_enabled(option): - """Checks if a boolean script option is enabled or not.""" - return weechat.config_string_to_boolean(weechat.config_get_plugin(option)) - - -def go_info_running(data, info_name, arguments): - """Returns "1" if go is running, otherwise "0".""" - return '1' if 'modifier' in hooks else '0' - - -def go_unhook_one(hook): - """Unhook something hooked by this script.""" - global hooks - if hook in hooks: - weechat.unhook(hooks[hook]) - del hooks[hook] - - -def go_unhook_all(): - """Unhook all.""" - go_unhook_one('modifier') - for hook in HOOK_COMMAND_RUN: - go_unhook_one(hook) - - -def go_hook_all(): - """Hook command_run and modifier.""" - global hooks - priority = '' - version = weechat.info_get('version_number', '') or 0 - # use high priority for hook to prevent conflict with other plugins/scripts - # (WeeChat >= 0.3.4 only) - if int(version) >= 0x00030400: - priority = '2000|' - for hook, value in HOOK_COMMAND_RUN.items(): - if hook not in hooks: - hooks[hook] = weechat.hook_command_run( - '%s%s' % (priority, value[0]), - value[1], '') - if 'modifier' not in hooks: - hooks['modifier'] = weechat.hook_modifier( - 'input_text_display_with_cursor', 'go_input_modifier', '') - - -def go_start(buf): - """Start go on buffer.""" - global saved_input, saved_input_pos, old_input, buffers_pos - go_hook_all() - saved_input = weechat.buffer_get_string(buf, 'input') - saved_input_pos = weechat.buffer_get_integer(buf, 'input_pos') - weechat.buffer_set(buf, 'input', '') - old_input = None - buffers_pos = 0 - - -def go_end(buf): - """End go on buffer.""" - global saved_input, saved_input_pos, old_input - go_unhook_all() - weechat.buffer_set(buf, 'input', saved_input) - weechat.buffer_set(buf, 'input_pos', str(saved_input_pos)) - old_input = None - - -def go_match_beginning(buf, string): - """Check if a string matches the beginning of buffer name/short name.""" - if not string: - return False - esc_str = re.escape(string) - if re.search(r'^#?' + esc_str, buf['name']) \ - or re.search(r'^#?' + esc_str, buf['short_name']): - return True - return False - - -def go_match_fuzzy(name, string): - """Check if string matches name using approximation.""" - if not string: - return False - - name_len = len(name) - string_len = len(string) - - if string_len > name_len: - return False - if name_len == string_len: - return name == string - - # Attempt to match all chars somewhere in name - prev_index = -1 - for i, char in enumerate(string): - index = name.find(char, prev_index+1) - if index == -1: - return False - prev_index = index - return True - - -def go_now(buf, args): - """Go to buffer specified by args.""" - listbuf = go_matching_buffers(args) - if not listbuf: - return - - # prefer buffer that matches at beginning (if option is enabled) - if 'beginning' in weechat.config_get_plugin('sort').split(','): - for index in range(len(listbuf)): - if go_match_beginning(listbuf[index], args): - weechat.command(buf, - '/buffer ' + str(listbuf[index]['full_name'])) - return - - # jump to first buffer in matching buffers by default - weechat.command(buf, '/buffer ' + str(listbuf[0]['full_name'])) - - -def go_cmd(data, buf, args): - """Command "/go": just hook what we need.""" - global hooks - if args: - go_now(buf, args) - elif 'modifier' in hooks: - go_end(buf) - else: - go_start(buf) - return weechat.WEECHAT_RC_OK - - -def go_matching_buffers(strinput): - """Return a list with buffers matching user input.""" - global buffers_pos - listbuf = [] - if len(strinput) == 0: - buffers_pos = 0 - strinput = strinput.lower() - infolist = weechat.infolist_get('buffer', '', '') - while weechat.infolist_next(infolist): - short_name = weechat.infolist_string(infolist, 'short_name') - if go_option_enabled('short_name'): - name = weechat.infolist_string(infolist, 'short_name') - else: - name = weechat.infolist_string(infolist, 'name') - if name == 'weechat' \ - and go_option_enabled('use_core_instead_weechat') \ - and weechat.infolist_string(infolist, 'plugin_name') == 'core': - name = 'core' - number = weechat.infolist_integer(infolist, 'number') - full_name = weechat.infolist_string(infolist, 'full_name') - if not full_name: - full_name = '%s.%s' % ( - weechat.infolist_string(infolist, 'plugin_name'), - weechat.infolist_string(infolist, 'name')) - pointer = weechat.infolist_pointer(infolist, 'pointer') - matching = name.lower().find(strinput) >= 0 - if not matching and strinput[-1] == ' ': - matching = name.lower().endswith(strinput.strip()) - if not matching and go_option_enabled('fuzzy_search'): - matching = go_match_fuzzy(name.lower(), strinput) - if not matching and strinput.isdigit(): - matching = str(number).startswith(strinput) - if len(strinput) == 0 or matching: - listbuf.append({ - 'number': number, - 'short_name': short_name, - 'name': name, - 'full_name': full_name, - 'pointer': pointer, - }) - weechat.infolist_free(infolist) - - # sort buffers - hotlist = [] - infolist = weechat.infolist_get('hotlist', '', '') - while weechat.infolist_next(infolist): - hotlist.append( - weechat.infolist_pointer(infolist, 'buffer_pointer')) - weechat.infolist_free(infolist) - last_index_hotlist = len(hotlist) - - def _sort_name(buf): - """Sort buffers by name (or short name).""" - return buf['name'] - - def _sort_hotlist(buf): - """Sort buffers by hotlist order.""" - try: - return hotlist.index(buf['pointer']) - except ValueError: - # not in hotlist, always last. - return last_index_hotlist - - def _sort_match_number(buf): - """Sort buffers by match on number.""" - return 0 if str(buf['number']) == strinput else 1 - - def _sort_match_beginning(buf): - """Sort buffers by match at beginning.""" - return 0 if go_match_beginning(buf, strinput) else 1 - - funcs = { - 'name': _sort_name, - 'hotlist': _sort_hotlist, - 'number': _sort_match_number, - 'beginning': _sort_match_beginning, - } - - for key in weechat.config_get_plugin('sort').split(','): - if key in funcs: - listbuf = sorted(listbuf, key=funcs[key]) - - if not strinput: - index = [i for i, buf in enumerate(listbuf) - if buf['pointer'] == weechat.current_buffer()] - if index: - buffers_pos = index[0] - - return listbuf - - -def go_buffers_to_string(listbuf, pos, strinput): - """Return string built with list of buffers found (matching user input).""" - string = '' - strinput = strinput.lower() - for i in range(len(listbuf)): - selected = '_selected' if i == pos else '' - buffer_name = listbuf[i]['name'] - index = buffer_name.lower().find(strinput) - if index >= 0: - index2 = index + len(strinput) - name = '%s%s%s%s%s' % ( - buffer_name[:index], - weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)), - buffer_name[index:index2], - weechat.color(weechat.config_get_plugin( - 'color_name' + selected)), - buffer_name[index2:]) - elif go_option_enabled("fuzzy_search") and \ - go_match_fuzzy(buffer_name.lower(), strinput): - name = "" - prev_index = -1 - for char in strinput.lower(): - index = buffer_name.lower().find(char, prev_index+1) - if prev_index < 0: - name += buffer_name[:index] - name += weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)) - if prev_index >= 0 and index > prev_index+1: - name += weechat.color(weechat.config_get_plugin( - 'color_name' + selected)) - name += buffer_name[prev_index+1:index] - name += weechat.color(weechat.config_get_plugin( - 'color_name_highlight' + selected)) - name += buffer_name[index] - prev_index = index - - name += weechat.color(weechat.config_get_plugin( - 'color_name' + selected)) - name += buffer_name[prev_index+1:] - else: - name = buffer_name - string += ' ' - if go_option_enabled('buffer_number'): - string += '%s%s' % ( - weechat.color(weechat.config_get_plugin( - 'color_number' + selected)), - str(listbuf[i]['number'])) - string += '%s%s%s' % ( - weechat.color(weechat.config_get_plugin( - 'color_name' + selected)), - name, - weechat.color('reset')) - return ' ' + string if string else '' - - -def go_input_modifier(data, modifier, modifier_data, string): - """This modifier is called when input text item is built by WeeChat. - - This is commonly called after changes in input or cursor move: it builds - a new input with prefix ("Go to:"), and suffix (list of buffers found). - """ - global old_input, buffers, buffers_pos - if modifier_data != weechat.current_buffer(): - return '' - names = '' - new_input = weechat.string_remove_color(string, '') - new_input = new_input.lstrip() - if old_input is None or new_input != old_input: - old_buffers = buffers - buffers = go_matching_buffers(new_input) - if buffers != old_buffers and len(new_input) > 0: - if len(buffers) == 1 and go_option_enabled('auto_jump'): - weechat.command(modifier_data, '/wait 1ms /input return') - buffers_pos = 0 - old_input = new_input - names = go_buffers_to_string(buffers, buffers_pos, new_input.strip()) - return weechat.config_get_plugin('message') + string + names - - -def go_command_run_input(data, buf, command): - """Function called when a command "/input xxx" is run.""" - global buffers, buffers_pos - if command.startswith('/input search_text') or command.startswith('/input jump'): - # search text or jump to another buffer is forbidden now - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input complete_next': - # choose next buffer in list - buffers_pos += 1 - if buffers_pos >= len(buffers): - buffers_pos = 0 - weechat.hook_signal_send('input_text_changed', - weechat.WEECHAT_HOOK_SIGNAL_STRING, '') - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input complete_previous': - # choose previous buffer in list - buffers_pos -= 1 - if buffers_pos < 0: - buffers_pos = len(buffers) - 1 - weechat.hook_signal_send('input_text_changed', - weechat.WEECHAT_HOOK_SIGNAL_STRING, '') - return weechat.WEECHAT_RC_OK_EAT - elif command == '/input return': - # switch to selected buffer (if any) - go_end(buf) - if len(buffers) > 0: - weechat.command( - buf, '/buffer ' + str(buffers[buffers_pos]['full_name'])) - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - -def go_command_run_buffer(data, buf, command): - """Function called when a command "/buffer xxx" is run.""" - return weechat.WEECHAT_RC_OK_EAT - - -def go_command_run_window(data, buf, command): - """Function called when a command "/window xxx" is run.""" - return weechat.WEECHAT_RC_OK_EAT - - -def go_unload_script(): - """Function called when script is unloaded.""" - go_unhook_all() - return weechat.WEECHAT_RC_OK - - -def go_main(): - """Entry point.""" - if not weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, - 'go_unload_script', ''): - return - weechat.hook_command( - SCRIPT_COMMAND, - 'Quick jump to buffers', '[name]', - 'name: directly jump to buffer by name (without argument, list is ' - 'displayed)\n\n' - 'You can bind command to a key, for example:\n' - ' /key bind meta-g /go\n\n' - 'You can use completion key (commonly Tab and shift-Tab) to select ' - 'next/previous buffer in list.', - '%(buffers_names)', - 'go_cmd', '') - - # set default settings - version = weechat.info_get('version_number', '') or 0 - for option, value in SETTINGS.items(): - if not weechat.config_is_set_plugin(option): - weechat.config_set_plugin(option, value[0]) - if int(version) >= 0x00030500: - weechat.config_set_desc_plugin( - option, '%s (default: "%s")' % (value[1], value[0])) - weechat.hook_info('go_running', - 'Return "1" if go is running, otherwise "0"', - '', - 'go_info_running', '') - - -if __name__ == "__main__" and IMPORT_OK: - go_main() diff --git a/.config/weechat/python/styurl.py b/.config/weechat/python/styurl.py deleted file mode 100644 index 69716505..00000000 --- a/.config/weechat/python/styurl.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2019 Cole Helbling -# -# 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 3 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 program. If not, see . -# - -# Changelog: -# 2019-12-14, Cole Helbling -# version 1.0: initial release - -SCRIPT_NAME = "styurl" -SCRIPT_AUTHOR = "Cole Helbling " -SCRIPT_VERSION = "1.0" -SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = "Style URLs with a Python regex" - -import_ok = True -try: - import weechat as w -except ImportError: - print("This script must be run under WeeChat.") - print("Get WeeChat now at: https://weechat.org") - import_ok = False - -try: - import re -except ImportError as message: - print("Missing package for %s: %s" % (SCRIPT_NAME, message)) - import_ok = False - -# https://mathiasbynens.be/demo/url-regex -# If you don't want to create your own regex, see the above link for options or -# ideas on creating your own - -styurl_settings = { - "buffer_type": ( - "formatted", - "the type of buffers to run on (options are \"formatted\", \"free\", " - "or \"*\" for both)" - ), - "format": ( - "${color:*_32}", - "the style that should be applied to the URL" - "(evaluated, see /help eval)" - ), - "ignored_buffers": ( - "core.weechat,python.grep", - "comma-separated list of buffers to ignore URLs in " - "(full name like \"irc.freenode.#alacritty\")" - ), - "ignored_tags": ( - "irc_quit,irc_join", - "comma-separated list of tags to ignore URLs from" - ), - "regex": ( - r"((?:https?|ftp)://[^\s/$.?#].\S*)", - "the URL-parsing regex using Python syntax " - "(make sure capturing group 1 is the full URL)" - ), -} - -line_hook = None - - -def styurl_line_cb(data, line): - """ - Callback called when a line is displayed. - This parses the message for any URLs and styles them according to - styurl_settings["format"]. - """ - global styurl_settings - - # Don't style the line if it's not going to be displayed... duh - if line["displayed"] != "1": - return line - - tags = line["tags"].split(',') - ignored_tags = styurl_settings["ignored_tags"] - - # Ignore specified message tags - if ignored_tags: - if any(tag in tags for tag in ignored_tags.split(',')): - return line - - bufname = line["buffer_name"] - ignored_buffers = styurl_settings["ignored_buffers"] - - # Ignore specified buffers - if ignored_buffers and bufname in ignored_buffers.split(','): - return line - - message = line["message"] - - # TODO: enforce presence of a properly-formatted color object at - # styurl_settings["format"] (eval object would also be valid, if it eval'd - # to a color) - - regex = re.compile(styurl_settings["regex"]) - url_style = w.string_eval_expression(styurl_settings["format"], {}, {}, {}) - reset = w.color("reset") - - # Search for URLs and surround them with the defined URL styling - formatted = regex.sub(r"%s\1%s" % (url_style, reset), message) - line["message"] = line["message"].replace(message, formatted) - - return line - - -def styurl_config_cb(data, option, value): - """Callback called when a script option is changed.""" - global styurl_settings, line_hook - - pos = option.rfind('.') - if pos > 0: - name = option[pos+1:] - if name in styurl_settings: - # Changing the buffer target requires us to re-hook to prevent - # obsolete buffer types from getting styled - if name == "buffer_type": - if value in ("free", "formatted", "*"): - w.unhook(line_hook) - line_hook = w.hook_line(value, "", "", "styurl_line_cb", - "") - else: - # Don't change buffer type if it is invalid - w.prnt("", SCRIPT_NAME + ": Invalid buffer type: '%s', " - "not changing." % value) - w.config_set_plugin(name, styurl_settings[name]) - return w.WEECHAT_RC_ERROR - - styurl_settings[name] = value - - return w.WEECHAT_RC_OK - - -def styurl_unload_cb(): - """Callback called when the script is unloaded.""" - global line_hook - - w.unhook(line_hook) - del line_hook - return w.WEECHAT_RC_OK - - -if __name__ == "__main__" and import_ok: - if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, - SCRIPT_DESC, "styurl_unload_cb", ""): - - version = w.info_get("version_number", "") or 0 - - for option, value in styurl_settings.items(): - if w.config_is_set_plugin(option): - styurl_settings[option] = w.config_get_plugin(option) - else: - w.config_set_plugin(option, value[0]) - styurl_settings[option] = value[0] - if int(version) >= 0x00030500: - w.config_set_desc_plugin(option, "%s (default: \"%s\")" - % (value[1], value[0])) - - w.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", - "styurl_config_cb", "") - - # Style URLs - line_hook = w.hook_line(styurl_settings["buffer_type"], "", "", - "styurl_line_cb", "") diff --git a/.config/weechat/python/vimode.py b/.config/weechat/python/vimode.py deleted file mode 100644 index b1e66410..00000000 --- a/.config/weechat/python/vimode.py +++ /dev/null @@ -1,1884 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013-2014 Germain Z. -# -# 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 3 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 program. If not, see . -# - -# -# Add vi/vim-like modes to WeeChat. -# - - -import csv -import json -import os -import re -import subprocess -try: - from StringIO import StringIO -except ImportError: - from io import StringIO -import time - -import weechat - - -# Script info. -# ============ - -SCRIPT_NAME = "vimode" -SCRIPT_AUTHOR = "GermainZ " -SCRIPT_VERSION = "0.8.1" -SCRIPT_LICENSE = "GPL3" -SCRIPT_DESC = ("Add vi/vim-like modes and keybindings to WeeChat.") - - -# Global variables. -# ================= - -# General. -# -------- - -# Halp! Halp! Halp! -GITHUB_BASE = "https://github.com/GermainZ/weechat-vimode/blob/master/" -README_URL = GITHUB_BASE + "README.md" -FAQ_KEYBINDINGS = GITHUB_BASE + "FAQ.md#problematic-key-bindings" -FAQ_ESC = GITHUB_BASE + "FAQ.md#esc-key-not-being-detected-instantly" - -# Holds the text of the tab-completions for the command-line mode. -cmd_compl_text = "" -# Holds the original text of the command-line mode, used for completion. -cmd_text_orig = None -# Index of current suggestion, used for completion. -cmd_compl_pos = 0 -# Used for command-line mode history. -cmd_history = [] -cmd_history_index = 0 -# Used to store the content of the input line when going into COMMAND mode. -input_line_backup = {} -# Mode we're in. One of INSERT, NORMAL, REPLACE, COMMAND or SEARCH. -# SEARCH is only used if search_vim is enabled. -mode = "INSERT" -# Holds normal commands (e.g. "dd"). -vi_buffer = "" -# See `cb_key_combo_default()`. -esc_pressed = 0 -# See `cb_key_pressed()`. -last_signal_time = 0 -# See `start_catching_keys()` for more info. -catching_keys_data = {'amount': 0} -# Used for ; and , to store the last f/F/t/T motion. -last_search_motion = {'motion': None, 'data': None} -# Used for undo history. -undo_history = {} -undo_history_index = {} -# Holds mode colors (loaded from vimode_settings). -mode_colors = {} - -# Script options. -vimode_settings = { - 'no_warn': ("off", ("don't warn about problematic keybindings and " - "tmux/screen")), - 'copy_clipboard_cmd': ("xclip -selection c", - ("command used to copy to clipboard; must read " - "input from stdin")), - 'paste_clipboard_cmd': ("xclip -selection c -o", - ("command used to paste clipboard; must output " - "content to stdout")), - 'imap_esc': ("", ("use alternate mapping to enter Normal mode while in " - "Insert mode; having it set to 'jk' is similar to " - "`:imap jk ` in vim")), - 'imap_esc_timeout': ("1000", ("time in ms to wait for the imap_esc " - "sequence to complete")), - 'search_vim': ("off", ("allow n/N usage after searching (requires an extra" - " to return to normal mode)")), - 'user_mappings': ("", ("see the `:nmap` command in the README for more " - "info; please do not modify this field manually " - "unless you know what you're doing")), - 'mode_indicator_prefix': ("", "prefix for the bar item mode_indicator"), - 'mode_indicator_suffix': ("", "suffix for the bar item mode_indicator"), - 'mode_indicator_normal_color': ("white", - "color for mode indicator in Normal mode"), - 'mode_indicator_normal_color_bg': ("gray", - ("background color for mode indicator " - "in Normal mode")), - 'mode_indicator_insert_color': ("white", - "color for mode indicator in Insert mode"), - 'mode_indicator_insert_color_bg': ("blue", - ("background color for mode indicator " - "in Insert mode")), - 'mode_indicator_replace_color': ("white", - "color for mode indicator in Replace mode"), - 'mode_indicator_replace_color_bg': ("red", - ("background color for mode indicator " - "in Replace mode")), - 'mode_indicator_cmd_color': ("white", - "color for mode indicator in Command mode"), - 'mode_indicator_cmd_color_bg': ("cyan", - ("background color for mode indicator in " - "Command mode")), - 'mode_indicator_search_color': ("white", - "color for mode indicator in Search mode"), - 'mode_indicator_search_color_bg': ("magenta", - ("background color for mode indicator " - "in Search mode")), - 'line_number_prefix': ("", "prefix for line numbers"), - 'line_number_suffix': (" ", "suffix for line numbers") -} - - -# Regex patterns. -# --------------- - -WHITESPACE = re.compile(r"\s") -IS_KEYWORD = re.compile(r"[a-zA-Z0-9_@À-ÿ]") -REGEX_MOTION_LOWERCASE_W = re.compile(r"\b\S|(?<=\s)\S") -REGEX_MOTION_UPPERCASE_W = re.compile(r"(?<=\s)\S") -REGEX_MOTION_UPPERCASE_E = re.compile(r"\S(?!\S)") -REGEX_MOTION_UPPERCASE_B = REGEX_MOTION_UPPERCASE_E -REGEX_MOTION_G_UPPERCASE_E = REGEX_MOTION_UPPERCASE_W -REGEX_MOTION_CARRET = re.compile(r"\S") -REGEX_INT = r"[0-9]" -REGEX_MAP_KEYS_1 = { - re.compile("<([^>]*-)Left>", re.IGNORECASE): '<\\1\x01[[D>', - re.compile("<([^>]*-)Right>", re.IGNORECASE): '<\\1\x01[[C>', - re.compile("<([^>]*-)Up>", re.IGNORECASE): '<\\1\x01[[A>', - re.compile("<([^>]*-)Down>", re.IGNORECASE): '<\\1\x01[[B>', - re.compile("", re.IGNORECASE): '\x01[[D', - re.compile("", re.IGNORECASE): '\x01[[C', - re.compile("", re.IGNORECASE): '\x01[[A', - re.compile("", re.IGNORECASE): '\x01[[B' -} -REGEX_MAP_KEYS_2 = { - re.compile(r"]*)>", re.IGNORECASE): '\x01\\1', - re.compile(r"]*)>", re.IGNORECASE): '\x01[\\1' -} - -# Regex used to detect problematic keybindings. -# For example: meta-wmeta-s is bound by default to ``/window swap``. -# If the user pressed Esc-w, WeeChat will detect it as meta-w and will not -# send any signal to `cb_key_combo_default()` just yet, since it's the -# beginning of a known key combo. -# Instead, `cb_key_combo_default()` will receive the Esc-ws signal, which -# becomes "ws" after removing the Esc part, and won't know how to handle it. -REGEX_PROBLEMATIC_KEYBINDINGS = re.compile(r"meta-\w(meta|ctrl)") - - -# Vi commands. -# ------------ - -def cmd_nmap(args): - """Add a user-defined key mapping. - - Some (but not all) vim-like key codes are supported to simplify things for - the user: , , , , and . - - See Also: - `cmd_unmap()`. - """ - args = args.strip() - if not args: - mappings = vimode_settings['user_mappings'] - if mappings: - weechat.prnt("", "User-defined key mappings:") - for key, mapping in mappings.items(): - weechat.prnt("", "{} -> {}".format(key, mapping)) - else: - weechat.prnt("", "nmap: no mapping found.") - elif not " " in args: - weechat.prnt("", "nmap syntax -> :nmap {lhs} {rhs}") - else: - key, mapping = args.split(" ", 1) - # First pass of replacements. We perform two passes as a simple way to - # avoid incorrect replacements due to dictionaries not being - # insertion-ordered prior to Python 3.7. - for regex, repl in REGEX_MAP_KEYS_1.items(): - key = regex.sub(repl, key) - mapping = regex.sub(repl, mapping) - # Second pass of replacements. - for regex, repl in REGEX_MAP_KEYS_2.items(): - key = regex.sub(repl, key) - mapping = regex.sub(repl, mapping) - mappings = vimode_settings['user_mappings'] - mappings[key] = mapping - weechat.config_set_plugin('user_mappings', json.dumps(mappings)) - vimode_settings['user_mappings'] = mappings - -def cmd_nunmap(args): - """Remove a user-defined key mapping. - - See Also: - `cmd_map()`. - """ - args = args.strip() - if not args: - weechat.prnt("", "nunmap syntax -> :unmap {lhs}") - else: - key = args - for regex, repl in REGEX_MAP_KEYS_1.items(): - key = regex.sub(repl, key) - for regex, repl in REGEX_MAP_KEYS_2.items(): - key = regex.sub(repl, key) - mappings = vimode_settings['user_mappings'] - if key in mappings: - del mappings[key] - weechat.config_set_plugin('user_mappings', json.dumps(mappings)) - vimode_settings['user_mappings'] = mappings - else: - weechat.prnt("", "nunmap: No such mapping") - -# See Also: `cb_exec_cmd()`. -VI_COMMAND_GROUPS = {('h', 'help'): "/help", - ('qa', 'qall', 'quita', 'quitall'): "/exit", - ('q', 'quit'): "/close", - ('w', 'write'): "/save", - ('bN', 'bNext', 'bp', 'bprevious'): "/buffer -1", - ('bn', 'bnext'): "/buffer +1", - ('bd', 'bdel', 'bdelete'): "/close", - ('b#',): "/input jump_last_buffer_displayed", - ('b', 'bu', 'buf', 'buffer'): "/buffer", - ('sp', 'split'): "/window splith", - ('vs', 'vsplit'): "/window splitv", - ('nm', 'nmap'): cmd_nmap, - ('nun', 'nunmap'): cmd_nunmap} - -VI_COMMANDS = dict() -for T, v in VI_COMMAND_GROUPS.items(): - VI_COMMANDS.update(dict.fromkeys(T, v)) - - -# Vi operators. -# ------------- - -# Each operator must have a corresponding function, called "operator_X" where -# X is the operator. For example: `operator_c()`. -VI_OPERATORS = ["c", "d", "y"] - - -# Vi motions. -# ----------- - -# Vi motions. Each motion must have a corresponding function, called -# "motion_X" where X is the motion (e.g. `motion_w()`). -# See Also: `SPECIAL_CHARS`. -VI_MOTIONS = ["w", "e", "b", "^", "$", "h", "l", "W", "E", "B", "f", "F", "t", - "T", "ge", "gE", "0"] - -# Special characters for motions. The corresponding function's name is -# converted before calling. For example, "^" will call `motion_carret` instead -# of `motion_^` (which isn't allowed because of illegal characters). -SPECIAL_CHARS = {'^': "carret", - '$': "dollar"} - - -# Methods for vi operators, motions and key bindings. -# =================================================== - -# Documented base examples: -# ------------------------- - -def operator_base(buf, input_line, pos1, pos2, overwrite): - """Operator method example. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - pos1 (int): the starting position of the motion. - pos2 (int): the ending position of the motion. - overwrite (bool, optional): whether the character at the cursor's new - position should be overwritten or not (for inclusive motions). - Defaults to False. - - Notes: - Should be called "operator_X", where X is the operator, and defined in - `VI_OPERATORS`. - Must perform actions (e.g. modifying the input line) on its own, - using the WeeChat API. - - See Also: - For additional examples, see `operator_d()` and - `operator_y()`. - """ - # Get start and end positions. - start = min(pos1, pos2) - end = max(pos1, pos2) - # Print the text the operator should go over. - weechat.prnt("", "Selection: %s" % input_line[start:end]) - -def motion_base(input_line, cur, count): - """Motion method example. - - Args: - input_line (str): the content of the input line. - cur (int): the position of the cursor. - count (int): the amount of times to multiply or iterate the action. - - Returns: - A tuple containing three values: - int: the new position of the cursor. - bool: True if the motion is inclusive, False otherwise. - bool: True if the motion is catching, False otherwise. - See `start_catching_keys()` for more info on catching motions. - - Notes: - Should be called "motion_X", where X is the motion, and defined in - `VI_MOTIONS`. - Must not modify the input line directly. - - See Also: - For additional examples, see `motion_w()` (normal motion) and - `motion_f()` (catching motion). - """ - # Find (relative to cur) position of next number. - pos = get_pos(input_line, REGEX_INT, cur, True, count) - # Return the new (absolute) cursor position. - # This motion is exclusive, so overwrite is False. - return cur + pos, False - -def key_base(buf, input_line, cur, count): - """Key method example. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - cur (int): the position of the cursor. - count (int): the amount of times to multiply or iterate the action. - - Notes: - Should be called `key_X`, where X represents the key(s), and defined - in `VI_KEYS`. - Must perform actions on its own (using the WeeChat API). - - See Also: - For additional examples, see `key_a()` (normal key) and - `key_r()` (catching key). - """ - # Key was pressed. Go to Insert mode (similar to "i"). - set_mode("INSERT") - - -# Operators: -# ---------- - -def operator_d(buf, input_line, pos1, pos2, overwrite=False): - """Delete text from `pos1` to `pos2` from the input line. - - If `overwrite` is set to True, the character at the cursor's new position - is removed as well (the motion is inclusive). - - See Also: - `operator_base()`. - """ - start = min(pos1, pos2) - end = max(pos1, pos2) - if overwrite: - end += 1 - input_line = list(input_line) - del input_line[start:end] - input_line = "".join(input_line) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, pos1) - -def operator_c(buf, input_line, pos1, pos2, overwrite=False): - """Delete text from `pos1` to `pos2` from the input and enter Insert mode. - - If `overwrite` is set to True, the character at the cursor's new position - is removed as well (the motion is inclusive.) - - See Also: - `operator_base()`. - """ - operator_d(buf, input_line, pos1, pos2, overwrite) - set_mode("INSERT") - -def operator_y(buf, input_line, pos1, pos2, _): - """Yank text from `pos1` to `pos2` from the input line. - - See Also: - `operator_base()`. - """ - start = min(pos1, pos2) - end = max(pos1, pos2) - cmd = vimode_settings['copy_clipboard_cmd'] - proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE) - proc.communicate(input=input_line[start:end].encode()) - - -# Motions: -# -------- - -def motion_0(input_line, cur, count): - """Go to the first character of the line. - - See Also; - `motion_base()`. - """ - return 0, False, False - -def motion_w(input_line, cur, count): - """Go `count` words forward and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_LOWERCASE_W, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, False, False - -def motion_W(input_line, cur, count): - """Go `count` WORDS forward and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_UPPERCASE_W, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, False, False - -def motion_e(input_line, cur, count): - """Go to the end of `count` words and return position. - - See Also: - `motion_base()`. - """ - for _ in range(max(1, count)): - found = False - pos = cur - for pos in range(cur + 1, len(input_line) - 1): - # Whitespace, keep going. - if WHITESPACE.match(input_line[pos]): - pass - # End of sequence made from 'iskeyword' characters only, - # or end of sequence made from non 'iskeyword' characters only. - elif ((IS_KEYWORD.match(input_line[pos]) and - (not IS_KEYWORD.match(input_line[pos + 1]) or - WHITESPACE.match(input_line[pos + 1]))) or - (not IS_KEYWORD.match(input_line[pos]) and - (IS_KEYWORD.match(input_line[pos + 1]) or - WHITESPACE.match(input_line[pos + 1])))): - found = True - cur = pos - break - # We're at the character before the last and we still found nothing. - # Go to the last character. - if not found: - cur = pos + 1 - return cur, True, False - -def motion_E(input_line, cur, count): - """Go to the end of `count` WORDS and return cusor position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_UPPERCASE_E, cur, True, count) - if pos == -1: - return len(input_line), False, False - return cur + pos, True, False - -def motion_b(input_line, cur, count): - """Go `count` words backwards and return position. - - See Also: - `motion_base()`. - """ - # "b" is just "e" on inverted data (e.g. "olleH" instead of "Hello"). - pos_inv = motion_e(input_line[::-1], len(input_line) - cur - 1, count)[0] - pos = len(input_line) - pos_inv - 1 - return pos, True, False - -def motion_B(input_line, cur, count): - """Go `count` WORDS backwards and return position. - - See Also: - `motion_base()`. - """ - new_cur = len(input_line) - cur - pos = get_pos(input_line[::-1], REGEX_MOTION_UPPERCASE_B, new_cur, - count=count) - if pos == -1: - return 0, False, False - pos = len(input_line) - (pos + new_cur + 1) - return pos, True, False - -def motion_ge(input_line, cur, count): - """Go to end of `count` words backwards and return position. - - See Also: - `motion_base()`. - """ - # "ge is just "w" on inverted data (e.g. "olleH" instead of "Hello"). - pos_inv = motion_w(input_line[::-1], len(input_line) - cur - 1, count)[0] - pos = len(input_line) - pos_inv - 1 - return pos, True, False - -def motion_gE(input_line, cur, count): - """Go to end of `count` WORDS backwards and return position. - - See Also: - `motion_base()`. - """ - new_cur = len(input_line) - cur - 1 - pos = get_pos(input_line[::-1], REGEX_MOTION_G_UPPERCASE_E, new_cur, - True, count) - if pos == -1: - return 0, False, False - pos = len(input_line) - (pos + new_cur + 1) - return pos, True, False - -def motion_h(input_line, cur, count): - """Go `count` characters to the left and return position. - - See Also: - `motion_base()`. - """ - return max(0, cur - max(count, 1)), False, False - -def motion_l(input_line, cur, count): - """Go `count` characters to the right and return position. - - See Also: - `motion_base()`. - """ - return cur + max(count, 1), False, False - -def motion_carret(input_line, cur, count): - """Go to first non-blank character of line and return position. - - See Also: - `motion_base()`. - """ - pos = get_pos(input_line, REGEX_MOTION_CARRET, 0) - return pos, False, False - -def motion_dollar(input_line, cur, count): - """Go to end of line and return position. - - See Also: - `motion_base()`. - """ - pos = len(input_line) - return pos, False, False - -def motion_f(input_line, cur, count): - """Go to `count`'th occurence of character and return position. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_f", input_line, cur, count) - -def cb_motion_f(update_last=True): - """Callback for `motion_f()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'], re.escape(pattern), - catching_keys_data['cur'], True, - catching_keys_data['count']) - catching_keys_data['new_cur'] = max(0, pos) + catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "f", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_F(input_line, cur, count): - """Go to `count`'th occurence of char to the right and return position. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_F", input_line, cur, count) - -def cb_motion_F(update_last=True): - """Callback for `motion_F()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - cur = len(catching_keys_data['input_line']) - catching_keys_data['cur'] - pos = get_pos(catching_keys_data['input_line'][::-1], - re.escape(pattern), - cur, - False, - catching_keys_data['count']) - catching_keys_data['new_cur'] = catching_keys_data['cur'] - max(0, pos + 1) - if update_last: - last_search_motion = {'motion': "F", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_t(input_line, cur, count): - """Go to `count`'th occurence of char and return position. - - The position returned is the position of the character to the left of char. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_t", input_line, cur, count) - -def cb_motion_t(update_last=True): - """Callback for `motion_t()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'], re.escape(pattern), - catching_keys_data['cur'] + 1, - True, catching_keys_data['count']) - pos += 1 - if pos > 0: - catching_keys_data['new_cur'] = pos + catching_keys_data['cur'] - 1 - else: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "t", 'data': pattern} - cb_key_combo_default(None, None, "") - -def motion_T(input_line, cur, count): - """Go to `count`'th occurence of char to the left and return position. - - The position returned is the position of the character to the right of - char. - - See Also: - `motion_base()`. - """ - return start_catching_keys(1, "cb_motion_T", input_line, cur, count) - -def cb_motion_T(update_last=True): - """Callback for `motion_T()`. - - Args: - update_last (bool, optional): should `last_search_motion` be updated? - Set to False when calling from `key_semicolon()` or `key_comma()` - so that the last search motion isn't overwritten. - Defaults to True. - - See Also: - `start_catching_keys()`. - """ - global last_search_motion - pattern = catching_keys_data['keys'] - pos = get_pos(catching_keys_data['input_line'][::-1], re.escape(pattern), - (len(catching_keys_data['input_line']) - - (catching_keys_data['cur'] + 1)) + 1, - True, catching_keys_data['count']) - pos += 1 - if pos > 0: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - pos + 1 - else: - catching_keys_data['new_cur'] = catching_keys_data['cur'] - if update_last: - last_search_motion = {'motion': "T", 'data': pattern} - cb_key_combo_default(None, None, "") - - -# Keys: -# ----- - -def key_cc(buf, input_line, cur, count): - """Delete line and start Insert mode. - - See Also: - `key_base()`. - """ - weechat.command("", "/input delete_line") - set_mode("INSERT") - -def key_C(buf, input_line, cur, count): - """Delete from cursor to end of line and start Insert mode. - - See Also: - `key_base()`. - """ - weechat.command("", "/input delete_end_of_line") - set_mode("INSERT") - -def key_yy(buf, input_line, cur, count): - """Yank line. - - See Also: - `key_base()`. - """ - cmd = vimode_settings['copy_clipboard_cmd'] - proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE) - proc.communicate(input=input_line.encode()) - -def key_p(buf, input_line, cur, count): - """Paste text. - - See Also: - `key_base()`. - """ - cmd = vimode_settings['paste_clipboard_cmd'] - weechat.hook_process(cmd, 10 * 1000, "cb_key_p", weechat.current_buffer()) - -def cb_key_p(data, command, return_code, output, err): - """Callback for fetching clipboard text and pasting it.""" - buf = "" - this_buffer = data - if output != "": - buf += output.strip() - if return_code == 0: - my_input = weechat.buffer_get_string(this_buffer, "input") - pos = weechat.buffer_get_integer(this_buffer, "input_pos") - my_input = my_input[:pos] + buf + my_input[pos:] - pos += len(buf) - weechat.buffer_set(this_buffer, "input", my_input) - weechat.buffer_set(this_buffer, "input_pos", str(pos)) - return weechat.WEECHAT_RC_OK - -def key_i(buf, input_line, cur, count): - """Start Insert mode. - - See Also: - `key_base()`. - """ - set_mode("INSERT") - -def key_a(buf, input_line, cur, count): - """Move cursor one character to the right and start Insert mode. - - See Also: - `key_base()`. - """ - set_cur(buf, input_line, cur + 1, False) - set_mode("INSERT") - -def key_A(buf, input_line, cur, count): - """Move cursor to end of line and start Insert mode. - - See Also: - `key_base()`. - """ - set_cur(buf, input_line, len(input_line), False) - set_mode("INSERT") - -def key_I(buf, input_line, cur, count): - """Move cursor to first non-blank character and start Insert mode. - - See Also: - `key_base()`. - """ - pos, _, _ = motion_carret(input_line, cur, 0) - set_cur(buf, input_line, pos) - set_mode("INSERT") - -def key_G(buf, input_line, cur, count): - """Scroll to specified line or bottom of buffer. - - See Also: - `key_base()`. - """ - if count > 0: - # This is necessary to prevent weird scroll jumps. - weechat.command("", "/window scroll_top") - weechat.command("", "/window scroll %s" % (count - 1)) - else: - weechat.command("", "/window scroll_bottom") - -def key_r(buf, input_line, cur, count): - """Replace `count` characters under the cursor. - - See Also: - `key_base()`. - """ - start_catching_keys(1, "cb_key_r", input_line, cur, count, buf) - -def cb_key_r(): - """Callback for `key_r()`. - - See Also: - `start_catching_keys()`. - """ - global catching_keys_data - input_line = list(catching_keys_data['input_line']) - count = max(catching_keys_data['count'], 1) - cur = catching_keys_data['cur'] - if cur + count <= len(input_line): - for _ in range(count): - input_line[cur] = catching_keys_data['keys'] - cur += 1 - input_line = "".join(input_line) - weechat.buffer_set(catching_keys_data['buf'], "input", input_line) - set_cur(catching_keys_data['buf'], input_line, cur - 1) - catching_keys_data = {'amount': 0} - -def key_R(buf, input_line, cur, count): - """Start Replace mode. - - See Also: - `key_base()`. - """ - set_mode("REPLACE") - -def key_tilda(buf, input_line, cur, count): - """Switch the case of `count` characters under the cursor. - - See Also: - `key_base()`. - """ - input_line = list(input_line) - count = max(1, count) - while count and cur < len(input_line): - input_line[cur] = input_line[cur].swapcase() - count -= 1 - cur += 1 - input_line = "".join(input_line) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, cur) - -def key_alt_j(buf, input_line, cur, count): - """Go to WeeChat buffer. - - Called to preserve WeeChat's alt-j buffer switching. - - This is only called when alt-j is pressed after pressing Esc, because - \x01\x01j is received in key_combo_default which becomes \x01j after - removing the detected Esc key. - If Esc isn't the last pressed key, \x01j is directly received in - key_combo_default. - """ - start_catching_keys(2, "cb_key_alt_j", input_line, cur, count) - -def cb_key_alt_j(): - """Callback for `key_alt_j()`. - - See Also: - `start_catching_keys()`. - """ - global catching_keys_data - weechat.command("", "/buffer " + catching_keys_data['keys']) - catching_keys_data = {'amount': 0} - -def key_semicolon(buf, input_line, cur, count, swap=False): - """Repeat last f, t, F, T `count` times. - - Args: - swap (bool, optional): if True, the last motion will be repeated in the - opposite direction (e.g. "f" instead of "F"). Defaults to False. - - See Also: - `key_base()`. - """ - global catching_keys_data, vi_buffer - catching_keys_data = ({'amount': 0, - 'input_line': input_line, - 'cur': cur, - 'keys': last_search_motion['data'], - 'count': count, - 'new_cur': 0, - 'buf': buf}) - # Swap the motion's case if called from key_comma. - if swap: - motion = last_search_motion['motion'].swapcase() - else: - motion = last_search_motion['motion'] - func = "cb_motion_%s" % motion - vi_buffer = motion - globals()[func](False) - -def key_comma(buf, input_line, cur, count): - """Repeat last f, t, F, T in opposite direction `count` times. - - See Also: - `key_base()`. - """ - key_semicolon(buf, input_line, cur, count, True) - -def key_u(buf, input_line, cur, count): - """Undo change `count` times. - - See Also: - `key_base()`. - """ - buf = weechat.current_buffer() - if buf not in undo_history: - return - for _ in range(max(count, 1)): - if undo_history_index[buf] > -len(undo_history[buf]): - undo_history_index[buf] -= 1 - input_line = undo_history[buf][undo_history_index[buf]] - weechat.buffer_set(buf, "input", input_line) - else: - break - -def key_ctrl_r(buf, input_line, cur, count): - """Redo change `count` times. - - See Also: - `key_base()`. - """ - if buf not in undo_history: - return - for _ in range(max(count, 1)): - if undo_history_index[buf] < -1: - undo_history_index[buf] += 1 - input_line = undo_history[buf][undo_history_index[buf]] - weechat.buffer_set(buf, "input", input_line) - else: - break - - -# Vi key bindings. -# ================ - -# String values will be executed as normal WeeChat commands. -# For functions, see `key_base()` for reference. -VI_KEYS = {'j': "/window scroll_down", - 'k': "/window scroll_up", - 'G': key_G, - 'gg': "/window scroll_top", - 'x': "/input delete_next_char", - 'X': "/input delete_previous_char", - 'dd': "/input delete_line", - 'D': "/input delete_end_of_line", - 'cc': key_cc, - 'C': key_C, - 'i': key_i, - 'a': key_a, - 'A': key_A, - 'I': key_I, - 'yy': key_yy, - 'p': key_p, - 'gt': "/buffer -1", - 'K': "/buffer -1", - 'gT': "/buffer +1", - 'J': "/buffer +1", - 'r': key_r, - 'R': key_R, - '~': key_tilda, - 'nt': "/bar scroll nicklist * -100%", - 'nT': "/bar scroll nicklist * +100%", - '\x01[[A': "/input history_previous", - '\x01[[B': "/input history_next", - '\x01[[C': "/input move_next_char", - '\x01[[D': "/input move_previous_char", - '\x01[[H': "/input move_beginning_of_line", - '\x01[[F': "/input move_end_of_line", - '\x01[[5~': "/window page_up", - '\x01[[6~': "/window page_down", - '\x01[[3~': "/input delete_next_char", - '\x01[[2~': key_i, - '\x01M': "/input return", - '\x01?': "/input move_previous_char", - ' ': "/input move_next_char", - '\x01[j': key_alt_j, - '\x01[1': "/buffer *1", - '\x01[2': "/buffer *2", - '\x01[3': "/buffer *3", - '\x01[4': "/buffer *4", - '\x01[5': "/buffer *5", - '\x01[6': "/buffer *6", - '\x01[7': "/buffer *7", - '\x01[8': "/buffer *8", - '\x01[9': "/buffer *9", - '\x01[0': "/buffer *10", - '\x01^': "/input jump_last_buffer_displayed", - '\x01D': "/window page_down", - '\x01U': "/window page_up", - '\x01Wh': "/window left", - '\x01Wj': "/window down", - '\x01Wk': "/window up", - '\x01Wl': "/window right", - '\x01W=': "/window balance", - '\x01Wx': "/window swap", - '\x01Ws': "/window splith", - '\x01Wv': "/window splitv", - '\x01Wq': "/window merge", - ';': key_semicolon, - ',': key_comma, - 'u': key_u, - '\x01R': key_ctrl_r} - -# Add alt-j bindings. -for i in range(10, 99): - VI_KEYS['\x01[j%s' % i] = "/buffer %s" % i - - -# Key handling. -# ============= - -def cb_key_pressed(data, signal, signal_data): - """Detect potential Esc presses. - - Alt and Esc are detected as the same key in most terminals. The difference - is that Alt signal is sent just before the other pressed key's signal. - We therefore use a timeout (50ms) to detect whether Alt or Esc was pressed. - """ - global last_signal_time - last_signal_time = time.time() - if signal_data == "\x01[": - # In 50ms, check if any other keys were pressed. If not, it's Esc! - weechat.hook_timer(50, 0, 1, "cb_check_esc", - "{:f}".format(last_signal_time)) - return weechat.WEECHAT_RC_OK - -def cb_check_esc(data, remaining_calls): - """Check if the Esc key was pressed and change the mode accordingly.""" - global esc_pressed, vi_buffer, catching_keys_data - # Not perfect, would be better to use direct comparison (==) but that only - # works for py2 and not for py3. - if abs(last_signal_time - float(data)) <= 0.000001: - esc_pressed += 1 - if mode == "SEARCH": - weechat.command("", "/input search_stop_here") - set_mode("NORMAL") - # Cancel any current partial commands. - vi_buffer = "" - catching_keys_data = {'amount': 0} - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK - -def cb_key_combo_default(data, signal, signal_data): - """Eat and handle key events when in Normal mode, if needed. - - The key_combo_default signal is sent when a key combo is pressed. For - example, alt-k will send the "\x01[k" signal. - - Esc is handled a bit differently to avoid delays, see `cb_key_pressed()`. - """ - global esc_pressed, vi_buffer, cmd_compl_text, cmd_text_orig, \ - cmd_compl_pos, cmd_history_index - - # If Esc was pressed, strip the Esc part from the pressed keys. - # Example: user presses Esc followed by i. This is detected as "\x01[i", - # but we only want to handle "i". - keys = signal_data - if esc_pressed or esc_pressed == -2: - if keys.startswith("\x01[" * esc_pressed): - # Multiples of 3 seem to "cancel" themselves, - # e.g. Esc-Esc-Esc-Alt-j-11 is detected as "\x01[\x01[\x01" - # followed by "\x01[j11" (two different signals). - if signal_data == "\x01[" * 3: - esc_pressed = -1 # `cb_check_esc()` will increment it to 0. - else: - esc_pressed = 0 - # This can happen if a valid combination is started but interrupted - # with Esc, such as Ctrl-W→Esc→w which would send two signals: - # "\x01W\x01[" then "\x01W\x01[w". - # In that case, we still need to handle the next signal ("\x01W\x01[w") - # so we use the special value "-2". - else: - esc_pressed = -2 - keys = keys.split("\x01[")[-1] # Remove the "Esc" part(s). - # Ctrl-Space. - elif keys == "\x01@": - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK_EAT - - # Clear the undo history for this buffer on . - if keys == "\x01M": - buf = weechat.current_buffer() - clear_undo_history(buf) - - # Detect imap_esc presses if any. - if mode == "INSERT": - imap_esc = vimode_settings['imap_esc'] - if not imap_esc: - return weechat.WEECHAT_RC_OK - if (imap_esc.startswith(vi_buffer) and - imap_esc[len(vi_buffer):len(vi_buffer)+1] == keys): - vi_buffer += keys - weechat.bar_item_update("vi_buffer") - weechat.hook_timer(int(vimode_settings['imap_esc_timeout']), 0, 1, - "cb_check_imap_esc", vi_buffer) - elif (vi_buffer and imap_esc.startswith(vi_buffer) and - imap_esc[len(vi_buffer):len(vi_buffer)+1] != keys): - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - # imap_esc sequence detected -- remove the sequence keys from the - # Weechat input bar and enter Normal mode. - if imap_esc == vi_buffer: - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - cur = weechat.buffer_get_integer(buf, "input_pos") - input_line = (input_line[:cur-len(imap_esc)+1] + - input_line[cur:]) - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, cur-len(imap_esc)+1, False) - set_mode("NORMAL") - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - # We're in Replace mode — allow "normal" key presses (e.g. "a") and - # overwrite the next character with them, but let the other key presses - # pass normally (e.g. backspace, arrow keys, etc). - if mode == "REPLACE": - if len(keys) == 1: - weechat.command("", "/input delete_next_char") - elif keys == "\x01?": - weechat.command("", "/input move_previous_char") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - - # We're catching keys! Only "normal" key presses interest us (e.g. "a"), - # not complex ones (e.g. backspace). - if len(keys) == 1 and catching_keys_data['amount']: - catching_keys_data['keys'] += keys - catching_keys_data['amount'] -= 1 - # Done catching keys, execute the callback. - if catching_keys_data['amount'] == 0: - globals()[catching_keys_data['callback']]() - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - - # We're in command-line mode. - if mode == "COMMAND": - buf = weechat.current_buffer() - cmd_text = weechat.buffer_get_string(buf, "input") - weechat.hook_timer(1, 0, 1, "cb_check_cmd_mode", "") - # Return key. - if keys == "\x01M": - weechat.hook_timer(1, 0, 1, "cb_exec_cmd", cmd_text) - if len(cmd_text) > 1 and (not cmd_history or - cmd_history[-1] != cmd_text): - cmd_history.append(cmd_text) - cmd_history_index = 0 - set_mode("NORMAL") - buf = weechat.current_buffer() - input_line = input_line_backup[buf]['input_line'] - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, input_line_backup[buf]['cur'], False) - # Up arrow. - elif keys == "\x01[[A": - if cmd_history_index > -len(cmd_history): - cmd_history_index -= 1 - cmd_text = cmd_history[cmd_history_index] - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Down arrow. - elif keys == "\x01[[B": - if cmd_history_index < -1: - cmd_history_index += 1 - cmd_text = cmd_history[cmd_history_index] - else: - cmd_history_index = 0 - cmd_text = ":" - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Tab key. No completion when searching ("/"). - elif keys == "\x01I" and cmd_text[0] == ":": - if cmd_text_orig is None: - input_ = list(cmd_text) - del input_[0] - cmd_text_orig = "".join(input_) - cmd_compl_list = [] - for cmd in VI_COMMANDS.keys(): - if cmd.startswith(cmd_text_orig): - cmd_compl_list.append(cmd) - if cmd_compl_list: - curr_suggestion = cmd_compl_list[cmd_compl_pos] - cmd_text = ":%s" % curr_suggestion - cmd_compl_list[cmd_compl_pos] = weechat.string_eval_expression( - "${color:bold}%s${color:-bold}" % curr_suggestion, - {}, {}, {}) - cmd_compl_text = ", ".join(cmd_compl_list) - cmd_compl_pos = (cmd_compl_pos + 1) % len(cmd_compl_list) - weechat.buffer_set(buf, "input", cmd_text) - set_cur(buf, cmd_text, len(cmd_text), False) - # Input. - else: - cmd_compl_text = "" - cmd_text_orig = None - cmd_compl_pos = 0 - weechat.bar_item_update("cmd_completion") - if keys in ["\x01M", "\x01[[A", "\x01[[B"]: - cmd_compl_text = "" - return weechat.WEECHAT_RC_OK_EAT - else: - return weechat.WEECHAT_RC_OK - # Enter command mode. - elif keys in [":", "/"]: - if keys == "/": - weechat.command("", "/input search_text_here") - if not weechat.config_string_to_boolean( - vimode_settings['search_vim']): - return weechat.WEECHAT_RC_OK - else: - buf = weechat.current_buffer() - cur = weechat.buffer_get_integer(buf, "input_pos") - input_line = weechat.buffer_get_string(buf, "input") - input_line_backup[buf] = {'input_line': input_line, 'cur': cur} - input_line = ":" - weechat.buffer_set(buf, "input", input_line) - set_cur(buf, input_line, 1, False) - set_mode("COMMAND") - cmd_compl_text = "" - cmd_text_orig = None - cmd_compl_pos = 0 - return weechat.WEECHAT_RC_OK_EAT - - # Add key to the buffer. - vi_buffer += keys - weechat.bar_item_update("vi_buffer") - if not vi_buffer: - return weechat.WEECHAT_RC_OK - - # Check if the keys have a (partial or full) match. If so, also get the - # keys without the count. (These are the actual keys we should handle.) - # After that, `vi_buffer` is only used for display purposes — only - # `vi_keys` is checked for all the handling. - # If no matches are found, the keys buffer is cleared. - matched, vi_keys, count = get_keys_and_count(vi_buffer) - if not matched: - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - # Check if it's a command (user defined key mapped to a :cmd). - if vi_keys.startswith(":"): - weechat.hook_timer(1, 0, 1, "cb_exec_cmd", "{} {}".format(vi_keys, - count)) - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - # It's a WeeChat command (user defined key mapped to a /cmd). - if vi_keys.startswith("/"): - weechat.command("", vi_keys) - vi_buffer = "" - return weechat.WEECHAT_RC_OK_EAT - - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - cur = weechat.buffer_get_integer(buf, "input_pos") - - # It's a default mapping. If the corresponding value is a string, we assume - # it's a WeeChat command. Otherwise, it's a method we'll call. - if vi_keys in VI_KEYS: - if vi_keys not in ['u', '\x01R']: - add_undo_history(buf, input_line) - if isinstance(VI_KEYS[vi_keys], str): - for _ in range(max(count, 1)): - # This is to avoid crashing WeeChat on script reloads/unloads, - # because no hooks must still be running when a script is - # reloaded or unloaded. - if (VI_KEYS[vi_keys] == "/input return" and - input_line.startswith("/script ")): - return weechat.WEECHAT_RC_OK - weechat.command("", VI_KEYS[vi_keys]) - current_cur = weechat.buffer_get_integer(buf, "input_pos") - set_cur(buf, input_line, current_cur) - else: - VI_KEYS[vi_keys](buf, input_line, cur, count) - # It's a motion (e.g. "w") — call `motion_X()` where X is the motion, then - # set the cursor's position to what that function returned. - elif vi_keys in VI_MOTIONS: - if vi_keys in SPECIAL_CHARS: - func = "motion_%s" % SPECIAL_CHARS[vi_keys] - else: - func = "motion_%s" % vi_keys - end, _, _ = globals()[func](input_line, cur, count) - set_cur(buf, input_line, end) - # It's an operator + motion (e.g. "dw") — call `motion_X()` (where X is - # the motion), then we call `operator_Y()` (where Y is the operator) - # with the position `motion_X()` returned. `operator_Y()` should then - # handle changing the input line. - elif (len(vi_keys) > 1 and - vi_keys[0] in VI_OPERATORS and - vi_keys[1:] in VI_MOTIONS): - add_undo_history(buf, input_line) - if vi_keys[1:] in SPECIAL_CHARS: - func = "motion_%s" % SPECIAL_CHARS[vi_keys[1:]] - else: - func = "motion_%s" % vi_keys[1:] - pos, overwrite, catching = globals()[func](input_line, cur, count) - # If it's a catching motion, we don't want to call the operator just - # yet -- this code will run again when the motion is complete, at which - # point we will. - if not catching: - oper = "operator_%s" % vi_keys[0] - globals()[oper](buf, input_line, cur, pos, overwrite) - # The combo isn't completed yet (e.g. just "d"). - else: - return weechat.WEECHAT_RC_OK_EAT - - # We've already handled the key combo, so clear the keys buffer. - if not catching_keys_data['amount']: - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK_EAT - -def cb_check_imap_esc(data, remaining_calls): - """Clear the imap_esc sequence after some time if nothing was pressed.""" - global vi_buffer - if vi_buffer == data: - vi_buffer = "" - weechat.bar_item_update("vi_buffer") - return weechat.WEECHAT_RC_OK - -def cb_key_combo_search(data, signal, signal_data): - """Handle keys while search mode is active (if search_vim is enabled).""" - if not weechat.config_string_to_boolean(vimode_settings['search_vim']): - return weechat.WEECHAT_RC_OK - if mode == "COMMAND": - if signal_data == "\x01M": - set_mode("SEARCH") - return weechat.WEECHAT_RC_OK_EAT - elif mode == "SEARCH": - if signal_data == "\x01M": - set_mode("NORMAL") - else: - if signal_data == "n": - weechat.command("", "/input search_next") - elif signal_data == "N": - weechat.command("", "/input search_previous") - # Start a new search. - elif signal_data == "/": - weechat.command("", "/input search_stop_here") - set_mode("NORMAL") - weechat.command("", "/input search_text_here") - return weechat.WEECHAT_RC_OK_EAT - return weechat.WEECHAT_RC_OK - -# Callbacks. -# ========== - -# Bar items. -# ---------- - -def cb_vi_buffer(data, item, window): - """Return the content of the vi buffer (pressed keys on hold).""" - return vi_buffer - -def cb_cmd_completion(data, item, window): - """Return the text of the command line.""" - return cmd_compl_text - -def cb_mode_indicator(data, item, window): - """Return the current mode (INSERT/NORMAL/REPLACE/...).""" - return "{}{}{}{}{}".format( - weechat.color(mode_colors[mode]), - vimode_settings['mode_indicator_prefix'], mode, - vimode_settings['mode_indicator_suffix'], weechat.color("reset")) - -def cb_line_numbers(data, item, window): - """Fill the line numbers bar item.""" - bar_height = weechat.window_get_integer(window, "win_chat_height") - content = "" - for i in range(1, bar_height + 1): - content += "{}{}{}\n".format(vimode_settings['line_number_prefix'], i, - vimode_settings['line_number_suffix']) - return content - -# Callbacks for the line numbers bar. -# ................................... - -def cb_update_line_numbers(data, signal, signal_data): - """Call `cb_timer_update_line_numbers()` when switching buffers. - - A timer is required because the bar item is refreshed before the new buffer - is actually displayed, so ``win_chat_height`` would refer to the old - buffer. Using a timer refreshes the item after the new buffer is displayed. - """ - weechat.hook_timer(10, 0, 1, "cb_timer_update_line_numbers", "") - return weechat.WEECHAT_RC_OK - -def cb_timer_update_line_numbers(data, remaining_calls): - """Update the line numbers bar item.""" - weechat.bar_item_update("line_numbers") - return weechat.WEECHAT_RC_OK - - -# Config. -# ------- - -def cb_config(data, option, value): - """Script option changed, update our copy.""" - option_name = option.split(".")[-1] - if option_name in vimode_settings: - vimode_settings[option_name] = value - if option_name == 'user_mappings': - load_user_mappings() - if "_color" in option_name: - load_mode_colors() - return weechat.WEECHAT_RC_OK - -def load_mode_colors(): - mode_colors.update({ - 'NORMAL': "{},{}".format( - vimode_settings['mode_indicator_normal_color'], - vimode_settings['mode_indicator_normal_color_bg']), - 'INSERT': "{},{}".format( - vimode_settings['mode_indicator_insert_color'], - vimode_settings['mode_indicator_insert_color_bg']), - 'REPLACE': "{},{}".format( - vimode_settings['mode_indicator_replace_color'], - vimode_settings['mode_indicator_replace_color_bg']), - 'COMMAND': "{},{}".format( - vimode_settings['mode_indicator_cmd_color'], - vimode_settings['mode_indicator_cmd_color_bg']), - 'SEARCH': "{},{}".format( - vimode_settings['mode_indicator_search_color'], - vimode_settings['mode_indicator_search_color_bg']) - }) - -def load_user_mappings(): - """Load user-defined mappings.""" - mappings = {} - if vimode_settings['user_mappings']: - mappings.update(json.loads(vimode_settings['user_mappings'])) - vimode_settings['user_mappings'] = mappings - - -# Command-line execution. -# ----------------------- - -def cb_exec_cmd(data, remaining_calls): - """Translate and execute our custom commands to WeeChat command.""" - # Process the entered command. - data = list(data) - del data[0] - data = "".join(data) - # s/foo/bar command. - if data.startswith("s/"): - cmd = data - parsed_cmd = next(csv.reader(StringIO(cmd), delimiter="/", - escapechar="\\")) - pattern = re.escape(parsed_cmd[1]) - repl = parsed_cmd[2] - repl = re.sub(r"([^\\])&", r"\1" + pattern, repl) - flag = None - if len(parsed_cmd) == 4: - flag = parsed_cmd[3] - count = 1 - if flag == "g": - count = 0 - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - input_line = re.sub(pattern, repl, input_line, count) - weechat.buffer_set(buf, "input", input_line) - # Shell command. - elif data.startswith("!"): - weechat.command("", "/exec -buffer shell %s" % data[1:]) - # Commands like `:22`. This should start cursor mode (``/cursor``) and take - # us to the relevant line. - elif data.isdigit(): - line_number = int(data) - hdata_window = weechat.hdata_get("window") - window = weechat.current_window() - x = weechat.hdata_integer(hdata_window, window, "win_chat_x") - y = (weechat.hdata_integer(hdata_window, window, "win_chat_y") + - (line_number - 1)) - weechat.command("", "/cursor go {},{}".format(x, y)) - # Check againt defined commands. - elif data: - raw_data = data - data = data.split(" ", 1) - cmd = data[0] - args = "" - if len(data) == 2: - args = data[1] - if cmd in VI_COMMANDS: - if isinstance(VI_COMMANDS[cmd], str): - weechat.command("", "%s %s" % (VI_COMMANDS[cmd], args)) - else: - VI_COMMANDS[cmd](args) - else: - # Check for commands not sepearated by space (e.g. "b2") - for i in range(1, len(raw_data)): - tmp_cmd = raw_data[:i] - tmp_args = raw_data[i:] - if tmp_cmd in VI_COMMANDS and tmp_args.isdigit(): - weechat.command("", "%s %s" % (VI_COMMANDS[tmp_cmd], - tmp_args)) - return weechat.WEECHAT_RC_OK - # No vi commands found, run the command as WeeChat command - weechat.command("", "/{} {}".format(cmd, args)) - return weechat.WEECHAT_RC_OK - -def cb_vimode_go_to_normal(data, buf, args): - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK - -# Script commands. -# ---------------- - -def cb_vimode_cmd(data, buf, args): - """Handle script commands (``/vimode ``).""" - # ``/vimode`` or ``/vimode help`` - if not args or args == "help": - weechat.prnt("", "[vimode.py] %s" % README_URL) - # ``/vimode bind_keys`` or ``/vimode bind_keys --list`` - elif args.startswith("bind_keys"): - infolist = weechat.infolist_get("key", "", "default") - weechat.infolist_reset_item_cursor(infolist) - commands = ["/key unbind ctrl-W", - "/key bind ctrl-W /input delete_previous_word", - "/key bind ctrl-^ /input jump_last_buffer_displayed", - "/key bind ctrl-Wh /window left", - "/key bind ctrl-Wj /window down", - "/key bind ctrl-Wk /window up", - "/key bind ctrl-Wl /window right", - "/key bind ctrl-W= /window balance", - "/key bind ctrl-Wx /window swap", - "/key bind ctrl-Ws /window splith", - "/key bind ctrl-Wv /window splitv", - "/key bind ctrl-Wq /window merge"] - while weechat.infolist_next(infolist): - key = weechat.infolist_string(infolist, "key") - if re.match(REGEX_PROBLEMATIC_KEYBINDINGS, key): - commands.append("/key unbind %s" % key) - weechat.infolist_free(infolist) - if args == "bind_keys": - weechat.prnt("", "Running commands:") - for command in commands: - weechat.command("", command) - weechat.prnt("", "Done.") - elif args == "bind_keys --list": - weechat.prnt("", "Listing commands we'll run:") - for command in commands: - weechat.prnt("", " %s" % command) - weechat.prnt("", "Done.") - return weechat.WEECHAT_RC_OK - - -# Helpers. -# ======== - -# Motions/keys helpers. -# --------------------- - -def get_pos(data, regex, cur, ignore_cur=False, count=0): - """Return the position of `regex` match in `data`, starting at `cur`. - - Args: - data (str): the data to search in. - regex (pattern): regex pattern to search for. - cur (int): where to start the search. - ignore_cur (bool, optional): should the first match be ignored if it's - also the character at `cur`? - Defaults to False. - count (int, optional): the index of the match to return. Defaults to 0. - - Returns: - int: position of the match. -1 if no matches are found. - """ - # List of the *positions* of the found patterns. - matches = [m.start() for m in re.finditer(regex, data[cur:])] - pos = -1 - if count: - if len(matches) > count - 1: - if ignore_cur and matches[0] == 0: - if len(matches) > count: - pos = matches[count] - else: - pos = matches[count - 1] - elif matches: - if ignore_cur and matches[0] == 0: - if len(matches) > 1: - pos = matches[1] - else: - pos = matches[0] - return pos - -def set_cur(buf, input_line, pos, cap=True): - """Set the cursor's position. - - Args: - buf (str): pointer to the current WeeChat buffer. - input_line (str): the content of the input line. - pos (int): the position to set the cursor to. - cap (bool, optional): if True, the `pos` will shortened to the length - of `input_line` if it's too long. Defaults to True. - """ - if cap: - pos = min(pos, len(input_line) - 1) - weechat.buffer_set(buf, "input_pos", str(pos)) - -def start_catching_keys(amount, callback, input_line, cur, count, buf=None): - """Start catching keys. Used for special commands (e.g. "f", "r"). - - amount (int): amount of keys to catch. - callback (str): name of method to call once all keys are caught. - input_line (str): input line's content. - cur (int): cursor's position. - count (int): count, e.g. "2" for "2fs". - buf (str, optional): pointer to the current WeeChat buffer. - Defaults to None. - - `catching_keys_data` is a dict with the above arguments, as well as: - keys (str): pressed keys will be added under this key. - new_cur (int): the new cursor's position, set in the callback. - - When catching keys is active, normal pressed keys (e.g. "a" but not arrows) - will get added to `catching_keys_data` under the key "keys", and will not - be handled any further. - Once all keys are caught, the method defined in the "callback" key is - called, and can use the data in `catching_keys_data` to perform its action. - """ - global catching_keys_data - if "new_cur" in catching_keys_data: - new_cur = catching_keys_data['new_cur'] - catching_keys_data = {'amount': 0} - return new_cur, True, False - catching_keys_data = ({'amount': amount, - 'callback': callback, - 'input_line': input_line, - 'cur': cur, - 'keys': "", - 'count': count, - 'new_cur': 0, - 'buf': buf}) - return cur, False, True - -def get_keys_and_count(combo): - """Check if `combo` is a valid combo and extract keys/counts if so. - - Args: - combo (str): pressed keys combo. - - Returns: - matched (bool): True if the combo has a (partial or full) match, False - otherwise. - combo (str): `combo` with the count removed. These are the actual keys - we should handle. User mappings are also expanded. - count (int): count for `combo`. - """ - # Look for a potential match (e.g. "d" might become "dw" or "dd" so we - # accept it, but "d9" is invalid). - matched = False - # Digits are allowed at the beginning (counts or "0"). - count = 0 - if combo.isdigit(): - matched = True - elif combo and combo[0].isdigit(): - count = "" - for char in combo: - if char.isdigit(): - count += char - else: - break - combo = combo.replace(count, "", 1) - count = int(count) - # It's a user defined key. Expand it. - if combo in vimode_settings['user_mappings']: - combo = vimode_settings['user_mappings'][combo] - # It's a WeeChat command. - if not matched and combo.startswith("/"): - matched = True - # Check against defined keys. - if not matched: - for key in VI_KEYS: - if key.startswith(combo): - matched = True - break - # Check against defined motions. - if not matched: - for motion in VI_MOTIONS: - if motion.startswith(combo): - matched = True - break - # Check against defined operators + motions. - if not matched: - for operator in VI_OPERATORS: - if combo.startswith(operator): - # Check for counts before the motion (but after the operator). - vi_keys_no_op = combo[len(operator):] - # There's no motion yet. - if vi_keys_no_op.isdigit(): - matched = True - break - # Get the motion count, then multiply the operator count by - # it, similar to vim's behavior. - elif vi_keys_no_op and vi_keys_no_op[0].isdigit(): - motion_count = "" - for char in vi_keys_no_op: - if char.isdigit(): - motion_count += char - else: - break - # Remove counts from `vi_keys_no_op`. - combo = combo.replace(motion_count, "", 1) - motion_count = int(motion_count) - count = max(count, 1) * motion_count - # Check against defined motions. - for motion in VI_MOTIONS: - if motion.startswith(combo[1:]): - matched = True - break - return matched, combo, count - - -# Other helpers. -# -------------- - -def set_mode(arg): - """Set the current mode and update the bar mode indicator.""" - global mode - buf = weechat.current_buffer() - input_line = weechat.buffer_get_string(buf, "input") - if mode == "INSERT" and arg == "NORMAL": - add_undo_history(buf, input_line) - mode = arg - # If we're going to Normal mode, the cursor must move one character to the - # left. - if mode == "NORMAL": - cur = weechat.buffer_get_integer(buf, "input_pos") - set_cur(buf, input_line, cur - 1, False) - weechat.bar_item_update("mode_indicator") - -def cb_check_cmd_mode(data, remaining_calls): - """Exit command mode if user erases the leading ':' character.""" - buf = weechat.current_buffer() - cmd_text = weechat.buffer_get_string(buf, "input") - if not cmd_text: - set_mode("NORMAL") - return weechat.WEECHAT_RC_OK - -def add_undo_history(buf, input_line): - """Add an item to the per-buffer undo history.""" - if buf in undo_history: - if not undo_history[buf] or undo_history[buf][-1] != input_line: - undo_history[buf].append(input_line) - undo_history_index[buf] = -1 - else: - undo_history[buf] = ['', input_line] - undo_history_index[buf] = -1 - -def clear_undo_history(buf): - """Clear the undo history for a given buffer.""" - undo_history[buf] = [''] - undo_history_index[buf] = -1 - -def print_warning(text): - """Print warning, in red, to the current buffer.""" - weechat.prnt("", ("%s[vimode.py] %s" % (weechat.color("red"), text))) - -def check_warnings(): - """Warn the user about problematic key bindings and tmux/screen.""" - user_warned = False - # Warn the user about problematic key bindings that may conflict with - # vimode. - # The solution is to remove these key bindings, but that's up to the user. - infolist = weechat.infolist_get("key", "", "default") - problematic_keybindings = [] - while weechat.infolist_next(infolist): - key = weechat.infolist_string(infolist, "key") - command = weechat.infolist_string(infolist, "command") - if re.match(REGEX_PROBLEMATIC_KEYBINDINGS, key): - problematic_keybindings.append("%s -> %s" % (key, command)) - weechat.infolist_free(infolist) - if problematic_keybindings: - user_warned = True - print_warning("Problematic keybindings detected:") - for keybinding in problematic_keybindings: - print_warning(" %s" % keybinding) - print_warning("These keybindings may conflict with vimode.") - print_warning("You can remove problematic key bindings and add" - " recommended ones by using /vimode bind_keys, or only" - " list them with /vimode bind_keys --list") - print_warning("For help, see: %s" % FAQ_KEYBINDINGS) - del problematic_keybindings - # Warn tmux/screen users about possible Esc detection delays. - if "STY" in os.environ or "TMUX" in os.environ: - if user_warned: - weechat.prnt("", "") - user_warned = True - print_warning("tmux/screen users, see: %s" % FAQ_ESC) - if (user_warned and not - weechat.config_string_to_boolean(vimode_settings['no_warn'])): - if user_warned: - weechat.prnt("", "") - print_warning("To force disable warnings, you can set" - " plugins.var.python.vimode.no_warn to 'on'") - - -# Main script. -# ============ - -if __name__ == "__main__": - weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, - SCRIPT_LICENSE, SCRIPT_DESC, "", "") - # Warn the user if he's using an unsupported WeeChat version. - VERSION = weechat.info_get("version_number", "") - if int(VERSION) < 0x01000000: - print_warning("Please upgrade to WeeChat ≥ 1.0.0. Previous versions" - " are not supported.") - # Set up script options. - for option, value in list(vimode_settings.items()): - if weechat.config_is_set_plugin(option): - vimode_settings[option] = weechat.config_get_plugin(option) - else: - weechat.config_set_plugin(option, value[0]) - vimode_settings[option] = value[0] - weechat.config_set_desc_plugin(option, - "%s (default: \"%s\")" % (value[1], - value[0])) - load_user_mappings() - load_mode_colors() - # Warn the user about possible problems if necessary. - if not weechat.config_string_to_boolean(vimode_settings['no_warn']): - check_warnings() - # Create bar items and setup hooks. - weechat.bar_item_new("mode_indicator", "cb_mode_indicator", "") - weechat.bar_item_new("cmd_completion", "cb_cmd_completion", "") - weechat.bar_item_new("vi_buffer", "cb_vi_buffer", "") - weechat.bar_item_new("line_numbers", "cb_line_numbers", "") - if int(VERSION) >= 0x02090000: - weechat.bar_new("vi_line_numbers", "on", "0", "window", "", "left", - "vertical", "vertical", "0", "0", "default", "default", - "default", "default", "0", "line_numbers") - else: - weechat.bar_new("vi_line_numbers", "on", "0", "window", "", "left", - "vertical", "vertical", "0", "0", "default", "default", - "default", "0", "line_numbers") - weechat.hook_config("plugins.var.python.%s.*" % SCRIPT_NAME, "cb_config", - "") - weechat.hook_signal("key_pressed", "cb_key_pressed", "") - weechat.hook_signal("key_combo_default", "cb_key_combo_default", "") - weechat.hook_signal("key_combo_search", "cb_key_combo_search", "") - weechat.hook_signal("buffer_switch", "cb_update_line_numbers", "") - weechat.hook_command("vimode", SCRIPT_DESC, "[help | bind_keys [--list]]", - " help: show help\n" - "bind_keys: unbind problematic keys, and bind" - " recommended keys to use in WeeChat\n" - " --list: only list changes", - "help || bind_keys |--list", - "cb_vimode_cmd", "") - weechat.hook_command("vimode_go_to_normal", - ("This command can be used for key bindings to go to " - "normal mode."), - "", "", "", "cb_vimode_go_to_normal", "") - # Remove obsolete bar. - vi_cmd_bar = weechat.bar_search("vi_cmd") - weechat.bar_remove(vi_cmd_bar) diff --git a/.config/weechat/relay.conf b/.config/weechat/relay.conf index 15346c33..9c632cd0 100644 --- a/.config/weechat/relay.conf +++ b/.config/weechat/relay.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -37,7 +37,7 @@ nonce_size = 16 password = "" password_hash_algo = "*" password_hash_iterations = 100000 -ssl_cert_key = "%h/ssl/relay.pem" +ssl_cert_key = "${weechat_config_dir}/ssl/relay.pem" ssl_priorities = "NORMAL:-VERS-SSL3.0" totp_secret = "" totp_window = 0 diff --git a/.config/weechat/relay.upgrade b/.config/weechat/relay.upgrade deleted file mode 100644 index 79f42e9f95be82e85f92cd08d3b9ff0c06f23b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmcCvU|_HX0fq3?ROgJu5{1x$^rFO+RE4z6oK%G}BRwMpU4^8~yu_kP9fg#9g}nR{ Nh18VH5=8~DHULST61e~X diff --git a/.config/weechat/ruby.conf b/.config/weechat/ruby.conf index f6de7fbf..cf78cf88 100644 --- a/.config/weechat/ruby.conf +++ b/.config/weechat/ruby.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/script.conf b/.config/weechat/script.conf index 7d175db2..26c442d5 100644 --- a/.config/weechat/script.conf +++ b/.config/weechat/script.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -28,7 +28,7 @@ status_popular = yellow status_running = lightgreen status_unknown = lightred text = default -text_bg = 2 +text_bg = default text_bg_selected = red text_date = default text_date_selected = white @@ -50,8 +50,8 @@ text_version_selected = lightmagenta [scripts] autoload = on cache_expire = 1440 -download_enabled = on +download_enabled = off download_timeout = 30 hold = "" -path = "%h/script" +path = "${weechat_cache_dir}/script" url = "https://weechat.org/files/plugins.xml.gz" diff --git a/.config/weechat/script/plugins.xml.gz b/.config/weechat/script/plugins.xml.gz deleted file mode 100644 index b611cd117808e91b9330ff63fce4aa3c13ade47b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129457 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

fEG8y0Cqr$zp(s#IzklZ6g^%X`{J=a8N~=; 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

h(4 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Ƴy6*Ax43yo$$u{Ssak&QbXxavNTyFk}cH!OX z#;`dVY|TB~?Ked{M;9#%-J|h%F)3G<8PI&YirWJUCaqY=!}xk>GQcrI)vL~1Uv8%1 zGlJ+76AwRLh0nR`4%&h=$_K6pU>Xp3rLdwrDWN>m)=O^v0_Qd6qq6~cyse= zWwG@&AF#E2XR-rS7#r9vZS8gT>TpX{PF73d<8rg|W-y^-5jGRl44@tn22_B(E&;

Z?H}mwG8Vr4XB|Xat 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 W9C#w6&He_P^8WzW`qKFTss{iN^ARZk diff --git a/.config/weechat/sec.conf b/.config/weechat/sec.conf index db0b8d5a..efee2d56 100644 --- a/.config/weechat/sec.conf +++ b/.config/weechat/sec.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -12,14 +12,9 @@ [crypt] cipher = aes256 hash_algo = sha256 -passphrase_command = "pass show AppPass/weechat/startup" +passphrase_command = "" salt = on [data] -__passphrase__ = on -matrix_password = "26762728EDC040D09887EDD961E4202A788F2523D45D326F3F9A745E8C24ACD4498A8F7F12EC55099E5DA7BED920AAA6281F56" -oftc_password = "B4C0AF3E0107FBBFDEB1D895D419EADA81A8483AB057A6E776B84E122F58FC9F557AC5CCD9AEE3CFBDBFF4B854E3BBE2A4230D9625B428" -sec.data.oftc_password = "D720E7F4DF99A63FDB65F316030E780D9C8DE5881A90D7B6C25BA3E8C728ED3EBB853E3381FD5D49DE6C3DF2FB14A62A254FAF3F3163F26C4571700F60F889D08CD6" -freenode = "179524EC2D9F957B73558EEE4CA58060303538D3647012CB6EE8F7CFE3775297797F30611CEE08C671509B1C93F0590DC793A7E0E8D4775960B2EF" -matrix = "AFA0A16CDD5B7C0109C87EA46F428E0DDCC52DAF43A23F0F2C655D22B8E6AFAC8ED6E41DB931808FDDBCC548DF221A9296DEBB" -rizon_password = "8EE35ED68992AE6EC99395DC7AD7D0D481B5E46F66E53CA57E4E10BF8AA3717759B266640B15E15E50317538D8B55A061922EAD24D8C7A69ECBE9C38209CD7A5A683" +__passphrase__ = off +relay = "+)jmWE@b=93p2>FQ" diff --git a/.config/weechat/spell.conf b/.config/weechat/spell.conf index f65348de..eebb2a68 100644 --- a/.config/weechat/spell.conf +++ b/.config/weechat/spell.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/tcl.conf b/.config/weechat/tcl.conf index 4c0e3896..a7777067 100644 --- a/.config/weechat/tcl.conf +++ b/.config/weechat/tcl.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/trigger.conf b/.config/weechat/trigger.conf index b5977ba0..0ea6e8e8 100644 --- a/.config/weechat/trigger.conf +++ b/.config/weechat/trigger.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # diff --git a/.config/weechat/weechat.conf b/.config/weechat/weechat.conf index bc6d150d..29f9f004 100644 --- a/.config/weechat/weechat.conf +++ b/.config/weechat/weechat.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -147,7 +147,7 @@ word_chars_input = "!\u00A0,-,_,|,alnum" bar_more = lightmagenta chat = default chat_bg = default -chat_buffer = 2 +chat_buffer = white chat_channel = white chat_day_change = cyan chat_delimiters = green @@ -163,7 +163,7 @@ chat_nick_offline_highlight = default chat_nick_offline_highlight_bg = blue chat_nick_other = cyan chat_nick_prefix = green -chat_nick_self = 2 +chat_nick_self = white chat_nick_suffix = green chat_prefix_action = white chat_prefix_buffer = brown @@ -203,11 +203,11 @@ status_data_private = lightgreen status_filter = green status_more = yellow status_mouse = green -status_name = 5 +status_name = white status_name_ssl = lightgreen status_nicklist_count = default -status_number = 9 -status_time = 2 +status_number = yellow +status_time = default [completion] base_word_until_cursor = on @@ -236,7 +236,8 @@ max_visited_buffers = 50 [network] connection_timeout = 60 -gnutls_ca_file = "/etc/ssl/certs/ca-certificates.crt" +gnutls_ca_system = on +gnutls_ca_user = "" gnutls_handshake_timeout = 30 proxy_curl = "" @@ -244,12 +245,19 @@ proxy_curl = "" autoload = "*" debug = off extension = ".so,.dll" -path = "%h/plugins" +path = "${weechat_data_dir}/plugins" save_config_on_unload = on +[signal] +sighup = "${if:${info:weechat_headless}?/reload:/quit -yes}" +sigquit = "/quit -yes" +sigterm = "/quit -yes" +sigusr1 = "" +sigusr2 = "" + [bar] buflist.color_bg = default -buflist.color_bg_inactive = 1 +buflist.color_bg_inactive = default buflist.color_delim = default buflist.color_fg = default buflist.conditions = "" @@ -286,28 +294,13 @@ input.conditions = "" input.filling_left_right = vertical input.filling_top_bottom = horizontal input.hidden = off -input.items = "mode_indicator+[input_prompt]+(away),[input_search],[input_paste],input_text,[vi_buffer]" +input.items = "[input_prompt]+(away),[input_search],[input_paste],input_text" input.position = bottom input.priority = 1000 input.separator = off input.size = 0 input.size_max = 0 input.type = window -isetbar.color_bg = default -isetbar.color_bg_inactive = default -isetbar.color_delim = cyan -isetbar.color_fg = default -isetbar.conditions = "" -isetbar.filling_left_right = vertical -isetbar.filling_top_bottom = horizontal -isetbar.hidden = on -isetbar.items = "isetbar_help" -isetbar.position = top -isetbar.priority = 0 -isetbar.separator = on -isetbar.size = 3 -isetbar.size_max = 3 -isetbar.type = window nicklist.color_bg = default nicklist.color_bg_inactive = default nicklist.color_delim = cyan @@ -323,25 +316,25 @@ nicklist.separator = on nicklist.size = 0 nicklist.size_max = 0 nicklist.type = window -status.color_bg = 8 -status.color_bg_inactive = default -status.color_delim = 3 -status.color_fg = 15 +status.color_bg = blue +status.color_bg_inactive = darkgray +status.color_delim = cyan +status.color_fg = default status.conditions = "" status.filling_left_right = vertical status.filling_top_bottom = horizontal status.hidden = off -status.items = "[time],[buffer_last_number],[buffer_plugin],buffer_number+:+buffer_name+(buffer_modes)+{buffer_nicklist_count}+buffer_zoom+buffer_filter,scroll,[lag],[hotlist],completion,cmd_completion" +status.items = "[time],[buffer_last_number],[buffer_plugin],buffer_number+:+buffer_name+(buffer_modes)+{buffer_nicklist_count}+buffer_zoom+buffer_filter,scroll,[lag],[hotlist],[typing],completion" status.position = bottom -status.priority = 0 +status.priority = 500 status.separator = off status.size = 1 status.size_max = 0 status.type = window -title.color_bg = 8 +title.color_bg = blue title.color_bg_inactive = darkgray title.color_delim = cyan -title.color_fg = 7 +title.color_fg = default title.conditions = "" title.filling_left_right = vertical title.filling_top_bottom = horizontal @@ -353,29 +346,12 @@ title.separator = off title.size = 1 title.size_max = 0 title.type = window -vi_line_numbers.color_bg = default -vi_line_numbers.color_bg_inactive = default -vi_line_numbers.color_delim = default -vi_line_numbers.color_fg = default -vi_line_numbers.conditions = "" -vi_line_numbers.filling_left_right = vertical -vi_line_numbers.filling_top_bottom = vertical -vi_line_numbers.hidden = on -vi_line_numbers.items = "line_numbers" -vi_line_numbers.position = left -vi_line_numbers.priority = 0 -vi_line_numbers.separator = off -vi_line_numbers.size = 0 -vi_line_numbers.size_max = 0 -vi_line_numbers.type = window [layout] [notify] -perl.highmon = none [filter] -irc_smart = on;*;irc_smart_filter;* [key] ctrl-? = "/input delete_previous_char" @@ -403,15 +379,6 @@ ctrl-Sctrl-U = "/input set_unread" ctrl-T = "/input transpose_chars" ctrl-U = "/input delete_beginning_of_line" ctrl-W = "/input delete_previous_word" -ctrl-W= = "/window balance" -ctrl-Wh = "/window left" -ctrl-Wj = "/window down" -ctrl-Wk = "/window up" -ctrl-Wl = "/window right" -ctrl-Wq = "/window merge" -ctrl-Ws = "/window splith" -ctrl-Wv = "/window splitv" -ctrl-Wx = "/window swap" ctrl-X = "/input switch_active_buffer" ctrl-Y = "/input clipboard_paste" meta-ctrl-M = "/input insert \n" @@ -518,7 +485,14 @@ meta-a = "/input jump_smart" meta-b = "/input move_previous_word" meta-d = "/input delete_next_word" meta-f = "/input move_next_word" -meta-h = "/input hotlist_clear" +meta-hmeta-R = "/input hotlist_restore_all" +meta-hmeta-c = "/input hotlist_clear" +meta-hmeta-m = "/input hotlist_remove_buffer" +meta-hmeta-r = "/input hotlist_restore_buffer" +meta-jmeta-f = "/buffer -" +meta-jmeta-l = "/buffer +" +meta-jmeta-r = "/server raw" +meta-jmeta-s = "/server jump" meta-j01 = "/buffer *1" meta-j02 = "/buffer *2" meta-j03 = "/buffer *3" @@ -626,9 +600,18 @@ meta-p = "/window scroll_previous_highlight" meta-r = "/input delete_line" meta-s = "/mute spell toggle" meta-u = "/window scroll_unread" +meta-wmeta-meta2-A = "/window up" +meta-wmeta-meta2-B = "/window down" +meta-wmeta-meta2-C = "/window right" +meta-wmeta-meta2-D = "/window left" +meta-wmeta2-1;3A = "/window up" +meta-wmeta2-1;3B = "/window down" +meta-wmeta2-1;3C = "/window right" +meta-wmeta2-1;3D = "/window left" +meta-wmeta-b = "/window balance" +meta-wmeta-s = "/window swap" meta-x = "/input zoom_merged_buffer" meta-z = "/window zoom" -ctrl-^ = "/input jump_last_buffer_displayed" ctrl-_ = "/input undo" [key_search] @@ -656,7 +639,6 @@ meta2-A = "/cursor move up" meta2-B = "/cursor move down" meta2-C = "/cursor move right" meta2-D = "/cursor move left" -@chat(python.matrix.*):r = "hsignal:matrix_cursor_reply" @item(buffer_nicklist):K = "/window ${_window_number};/kickban ${nick}" @item(buffer_nicklist):b = "/window ${_window_number};/ban ${nick}" @item(buffer_nicklist):k = "/window ${_window_number};/kick ${nick}" @@ -678,10 +660,6 @@ meta2-D = "/cursor move left" @chat(fset.fset):button2* = "hsignal:fset_mouse" @chat(fset.fset):wheeldown = "/fset -down 5" @chat(fset.fset):wheelup = "/fset -up 5" -@chat(perl.iset):button1 = "hsignal:iset_mouse" -@chat(perl.iset):button2* = "hsignal:iset_mouse" -@chat(perl.iset):wheeldown = "/repeat 5 /iset **down" -@chat(perl.iset):wheelup = "/repeat 5 /iset **up" @chat(script.scripts):button1 = "/window ${_window_number};/script go ${_chat_line_y}" @chat(script.scripts):button2 = "/window ${_window_number};/script go ${_chat_line_y};/script installremove -q ${script_name_with_extension}" @chat(script.scripts):wheeldown = "/script down 5" diff --git a/.config/weechat/weechat.upgrade b/.config/weechat/weechat.upgrade deleted file mode 100644 index ad42bd8a2ed898fe8f765499e57b70e9c5b14cdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 992061 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 D1+d+N diff --git a/.config/weechat/xfer.conf b/.config/weechat/xfer.conf index 7302518d..d438f700 100644 --- a/.config/weechat/xfer.conf +++ b/.config/weechat/xfer.conf @@ -4,7 +4,7 @@ # WARNING: It is NOT recommended to edit this file by hand, # especially if WeeChat is running. # -# Use /set or similar command to change settings in WeeChat. +# Use commands like /set or /fset to change settings in WeeChat. # # For more info, see: https://weechat.org/doc/quickstart # @@ -22,8 +22,8 @@ status_done = lightgreen status_failed = lightred status_waiting = lightcyan text = default -text_bg = 2 -text_selected = black +text_bg = default +text_selected = white [network] blocksize = 65536 @@ -43,7 +43,7 @@ auto_check_crc32 = off auto_rename = on auto_resume = on convert_spaces = on -download_path = "%h/xfer" +download_path = "${weechat_data_dir}/xfer" download_temporary_suffix = ".part" upload_path = "~" use_nick_in_filename = on diff --git a/.config/weechat/xfer.upgrade b/.config/weechat/xfer.upgrade deleted file mode 100644 index 79f42e9f95be82e85f92cd08d3b9ff0c06f23b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmcCvU|_HX0fq3?ROgJu5{1x$^rFO+RE4z6oK%G}BRwMpU4^8~yu_kP9fg#9g}nR{ Nh18VH5=8~DHULST61e~X diff --git a/.local/bin/backup b/.local/bin/backup index 8d4ced87..44923e26 100755 --- a/.local/bin/backup +++ b/.local/bin/backup @@ -38,6 +38,7 @@ DEVICE=tatooine OPT="-aPh" SNAP="$RSYNC_PROFILE/$DEVICE/" date=$(date "+%d.%m.%Y_%I:%M") +older=$(date --date="4 days ago" "+%d.%m.%Y_%I:%M") # You should enter absolute paths DIRS="/home/yigit /etc @@ -47,6 +48,7 @@ DIRS="/home/yigit # Check whether backup server is available on network timeout 3 ssh -i "$SSH_KEY" $SSH_USER@$SSH_HOST id < /dev/null > /dev/null 2> /dev/null || echo "SSH Failed." || exit +ssh -i "$SSH_KEY" $SSH_USER@$SSH_HOST rm -rf "$BACKUP_DESTINATION/$DEVICE/$older" > /dev/null 2> /dev/null # Run rsync to create snapshot while IFS= read -r DIR diff --git a/.local/bin/calconsync b/.local/bin/calconsync index 580f3f6a..86ba1091 100755 --- a/.local/bin/calconsync +++ b/.local/bin/calconsync @@ -7,7 +7,9 @@ eval "$(grep -h -- \ "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.config/zsh/.zshrc" "$HOME/.pam_environment" 2>/dev/null)" export CALCURSE_CALDAV_PASSWORD=$(pass show Server/drive.yigitcolakoglu.com/yigitcolakoglu) -calcurse-caldav --lockfile $(mktemp) # Prevent lock file conflicts +lock=$(mktemp) +rm -f $lock +calcurse-caldav --lockfile $lock # Prevent lock file conflicts tmpfile=$(mktemp) @@ -24,6 +26,8 @@ abook --convert \ --outformat abook \ --outfile $destfile +vdirsyncer sync + rm $tmpfile chmod 600 $destfile sed -Ei 's/([0-9]) ([0-9])/\1\2/g' $destfile diff --git a/.local/bin/dmenu-refresh b/.local/bin/dmenu-refresh index 4256c870..579b8d13 100755 --- a/.local/bin/dmenu-refresh +++ b/.local/bin/dmenu-refresh @@ -17,8 +17,7 @@ clipmenud darkhttpd devmon emacs -mpd -mpDris2" +spotifyd" # Open menu selection=$(printf '%s' "$items" | $DMENU) @@ -39,14 +38,9 @@ case $selection in pkill -f clipmenud clipmenud > $XDG_RUNTIME_DIR/clipmenud.out 2> $XDG_RUNTIME_DIR/clipmenud.err & ;; - mpd) - kill -9 $(pidof mpd) - mpd & - mpd-mpris & - ;; - mpDris2) - kill -9 $(pidof mpDris2) - mpDris2 & + spotifyd) + kill -9 $(pidof spotifyd) + spotifyd ;; dunst) kill -9 $(pidof dunst) diff --git a/.local/bin/dmenu-web b/.local/bin/dmenu-web index 95cc9e3e..9c11f3d6 100755 --- a/.local/bin/dmenu-web +++ b/.local/bin/dmenu-web @@ -9,7 +9,7 @@ # A browser-independent address bar with bookmark support. When the # cursor is on a web browser it acts as the address bar of that browser. -engine='https://searx.fmac.xyz/search?q=%s' +engine='https://google.com/search?q=%s' bookmarks="$HOME/.local/share/bookmarks" gotourl() { diff --git a/.local/share/bookmarks b/.local/share/bookmarks index 170af403..f895dde5 100644 --- a/.local/share/bookmarks +++ b/.local/share/bookmarks @@ -4,6 +4,8 @@ https://my.tudelft.nl https://queue.tudelft.nl https://weblab.tudelft.nl https://stackoverflow.com/c/tud-cs/questions -https://tudelft.zoom.us/j/98183250143?pwd=SXI4dUhBS0NxM1g2czd0azJTVitUdz09#success https://collegerama.tudelft.nl/Mediasite/Channel/eemcs-bsc-cse/browse/null/most-recent/null/0/17117c0553d341f588d577f39bfe09ae14 +https://tudelft.zoom.us/j/95264306309 https://wiki.fr1nge.xyz +https://ad-cs.ewi.tudelft.nl +https://calibre.fr1nge.xyz diff --git a/.local/share/dwm/autostart.sh b/.local/share/dwm/autostart.sh index 6aa5ffc4..890a965b 100755 --- a/.local/share/dwm/autostart.sh +++ b/.local/share/dwm/autostart.sh @@ -63,6 +63,10 @@ if [ "$ACTIVITYWATCHER" = true ] ; then aw-watcher-afk & fi +if [ "$SPOTIFYD" = true ] ; then + spotifyd +fi + mpd restart_if_fails mpDris2 diff --git a/.profile b/.profile index e76ee1c3..63126f54 100755 --- a/.profile +++ b/.profile @@ -75,7 +75,7 @@ export SCREENRC="$XDG_CONFIG_HOME"/screen/screenrc export PATH=$ANDROID_HOME/tools:$PATH export PATH=$ANDROID_HOME/platform-tools:$PATH export PATH=$FLUTTER_HOME/bin:$PATH -export PATH="$PATH:/usr/lib/w3m:$HOME/.local/bin:$HOME/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport" +export PATH="$PATH:/usr/lib/w3m:$HOME/.local/bin:$HOME/.gem/ruby/2.7.0/bin:$GOPATH/bin:$GOPATH/binexport:$XDG_DATA_HOME/cargo/bin/" export CPATH=/usr/include/opencv4 # Setup LF Icons (Doing this everytime lf start might cause some overhead)