diff --git a/.config/X11/xinitrc b/.config/X11/xinitrc index f4a82371..d86aecaa 100755 --- a/.config/X11/xinitrc +++ b/.config/X11/xinitrc @@ -12,65 +12,14 @@ rm -rf $XDG_RUNTIME_DIR/day_cache export XSESSION_PID="$$" source ~/.config/config.env -gpgconf --dry-run --create-socketdir -#eval $(/usr/bin/gnome-keyring-daemon --start --components=pkcs11,secrets,ssh) -#export SSH_AUTH_SOCK - -clipmenud > $XDG_RUNTIME_DIR/clipmenud.out 2> $XDG_RUNTIME_DIR/clipmenud.err & -rm -f ~/.surf/tabbed-surf.xid -/bin/polkit-dumb-agent & -~/.local/bin/daily-update -~/.local/bin/keyboard > $XDG_RUNTIME_DIR/keyboard.out 2> $XDG_RUNTIME_DIR/keyboard.err -xrdb ~/.config/X11/Xresources & -~/.local/bin/mailsync & - -if [ "$NEXTCLOUD" = true ] ; then - nextcloud --background & -fi -mkdir -p ~/Downloads/neomutt -if [ "$MCONNECT" = true ] ; then - mkdir -p ~/Downloads/mconnect - (cd ~/Downloads/mconnect; mconnect -d > $XDG_RUNTIME_DIR/mconnect 2> $XDG_RUNTIME_DIR/mconnect.err &) -fi -if [ "$ACTIVITYWATCHER" = true ] ; then - pkill -f aw-watcher-window - pkill -f aw-watcher-afk - pkill -f aw-server - aw-server & - aw-watcher-window & - aw-watcher-afk & -fi -mpd -mpd-mpris & -touch ~/.cache/nextcloud-track -xss-lock -- slock & -picom --no-fading-openclose & -~/.local/bin/firefox-sync -curl 'http://yeetclock/setcolor?R=136&G=192&B=208' & - -dunst & - -xbanish -s & pactl upload-sample /usr/share/sounds/freedesktop/stereo/bell.oga x11-bell pactl load-module module-x11-bell sample=x11-bell display=$DISPLAY xset b 100 xset dpms 600 600 600 +xrdb ~/.config/X11/Xresources & -~/.local/bin/devmon --exec-on-drive "/sbin/notify-send '禍 drive mounted' '%l (%f) at %d '" \ - --exec-on-remove "/sbin/notify-send '禍 drive removed' '%l (%f) from %d '" \ - --exec-on-unmount "/sbin/notify-send '禍 drive unmounted' '%l (%f) from %d '" \ - --no-unmount --no-gui & - - -#$BROWSER & -#pass 2> /dev/null > /dev/null && qtpass & - -redshift -x 2> /dev/null > /dev/null -redshift -r -l "$LATLONG" > /dev/null 2> /dev/null & - -tmux new-session -s weechat -d weechat > /dev/null 2> /dev/null restarted=0 while true; do @@ -80,9 +29,6 @@ do notify-send -a " Desktop Manager" "Dwm Restarted" fi - dwmblocks > $XDG_RUNTIME_DIR/dwmblocks.out 2> $XDG_RUNTIME_DIR/dwmblocks.err & dwm > $XDG_RUNTIME_DIR/dwm.log 2> $XDG_RUNTIME_DIR/dwm.err sleep 0.5 done - -firefox-sync diff --git a/.config/isync/mbsyncrc b/.config/isync/mbsyncrc index c8b7c7b8..e15a4d18 100644 --- a/.config/isync/mbsyncrc +++ b/.config/isync/mbsyncrc @@ -15,8 +15,8 @@ Inbox /home/yigit/.local/share/mail/yigitcolakoglu@hotmail.com/INBOX Channel yigitcolakoglu@hotmail.com Expunge Both -Far :yigitcolakoglu@hotmail.com-remote: -Near :yigitcolakoglu@hotmail.com-local: +Master :yigitcolakoglu@hotmail.com-remote: +Slave :yigitcolakoglu@hotmail.com-local: Patterns * !"[Gmail]/All Mail" Create Both SyncState * @@ -41,8 +41,8 @@ Inbox /home/yigit/.local/share/mail/yigit@yigitcolakoglu.com/INBOX Channel yigit@yigitcolakoglu.com Expunge Both -Far :yigit@yigitcolakoglu.com-remote: -Near :yigit@yigitcolakoglu.com-local: +Master :yigit@yigitcolakoglu.com-remote: +Slave :yigit@yigitcolakoglu.com-local: Patterns * !"[Gmail]/All Mail" Create Both SyncState * diff --git a/.config/mpv/scripts/mpris.so b/.config/mpv/scripts/mpris.so deleted file mode 100755 index 251c077f..00000000 Binary files a/.config/mpv/scripts/mpris.so and /dev/null differ diff --git a/.config/ncmpcpp/config b/.config/ncmpcpp/config index 1fb00d59..626c4573 100644 --- a/.config/ncmpcpp/config +++ b/.config/ncmpcpp/config @@ -90,4 +90,4 @@ empty_tag_marker = "" mpd_crossfade_time=3 -execute_on_song_change="kill -63 $(pidof dwmblocks); mpd-notif" +execute_on_song_change="mpd-notif" diff --git a/.config/neofetch/config.conf b/.config/neofetch/config.conf index 507ea87b..9d0b2d9e 100644 --- a/.config/neofetch/config.conf +++ b/.config/neofetch/config.conf @@ -18,10 +18,10 @@ print_info() { info "CPU" cpu info "Memory" memory - # info "CPU Usage" cpu_usage + info "CPU Usage" cpu_usage # info "Disk" disk # info "Battery" battery - # info "Font" font + info "Font" font # info "Song" song # [[ "$player" ]] && prin "Music Player" "$player" # info "Local IP" local_ip diff --git a/.config/qt5ct/qt5ct.conf b/.config/qt5ct/qt5ct.conf index 6861e195..f943e719 100644 --- a/.config/qt5ct/qt5ct.conf +++ b/.config/qt5ct/qt5ct.conf @@ -25,4 +25,4 @@ underline_shortcut=2 wheel_scroll_lines=3 [SettingsWindow] -geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\x1e\0\0\0/\0\0\x3\xba\0\0\x4-\0\0\0!\0\0\0\x32\0\0\x3\xb7\0\0\x4*\0\0\0\0\0\0\0\0\a\x80\0\0\0!\0\0\0\x32\0\0\x3\xb7\0\0\x4*) +geometry="@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\x1e\0\0\0/\0\0\aa\0\0\x4-\0\0\0\x1f\0\0\0\x30\0\0\a`\0\0\x4,\0\0\0\0\0\0\0\0\a\x80\0\0\0\x1f\0\0\0\x30\0\0\a`\0\0\x4,)" diff --git a/.config/tmux/tmux.conf b/.config/tmux/tmux.conf index c0a8381a..2086bf72 100644 --- a/.config/tmux/tmux.conf +++ b/.config/tmux/tmux.conf @@ -28,7 +28,6 @@ set -g @thumbs-key f # use 256 colors set -g default-terminal screen-256color -set-option -g default-shell /sbin/zsh set -ag terminal-overrides ",xterm-256color:Tc" # Keep plenty of history for scrollback diff --git a/.config/vim/plugin/plugins.vim b/.config/vim/plugin/plugins.vim index dfc00eee..7d6f590a 100644 --- a/.config/vim/plugin/plugins.vim +++ b/.config/vim/plugin/plugins.vim @@ -25,12 +25,10 @@ Plug 'jlanzarotta/bufexplorer' Plug 'jeffkreeftmeijer/vim-numbertoggle' Plug 'rhysd/vim-grammarous' Plug 'chrisbra/NrrwRgn' -Plug 'robertbasic/vim-hugo-helper' -"Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' } Plug 'sheerun/vim-polyglot' Plug 'M4R7iNP/vim-inky' -" Plug 'ycm-core/YouCompleteMe', { 'do': './install.py' } Plug 'neoclide/coc.nvim', {'branch': 'release'} +Plug 'junegunn/goyo.vim' Plug 'vim-scripts/indentpython.vim' Plug 'frazrepo/vim-rainbow' @@ -41,8 +39,6 @@ Plug 'puremourning/vimspector' Plug 'lervag/vimtex' " Plug 'wakatime/vim-wakatime' Plug 'gu-fan/riv.vim' -Plug 'gu-fan/InstantRst' -Plug 'prettier/vim-prettier', { 'do': 'yarn install' } Plug 'isene/hyperlist.vim' Plug 'neomutt/neomutt.vim' Plug 'VebbNix/lf-vim' @@ -57,8 +53,6 @@ Plug 'junegunn/fzf.vim' " Syntax highlighting " Plug 'vim-ruby/vim-ruby' -Plug 'noscript/cSyntaxAfter' -Plug 'uiiaoo/java-syntax.vim' Plug 'elzr/vim-json' Plug 'tpope/vim-markdown' Plug 'vim-scripts/cool.vim' @@ -69,17 +63,7 @@ Plug 'leafgarland/typescript-vim' Plug 'arrufat/vala.vim' " Syntax errors -" Plug 'vim-syntastic/syntastic' Plug 'dense-analysis/ale' Plug 'ntpeters/vim-better-whitespace' -" Git support -Plug 'tpope/vim-fugitive' - -" Testing -Plug 'janko-m/vim-test' - -" Gist -Plug 'mattn/webapi-vim' | Plug 'mattn/gist-vim' - call plug#end() diff --git a/.config/weechat/perl/autoload/atcomplete.pl b/.config/weechat/perl/autoload/atcomplete.pl deleted file mode 120000 index bd3e9ea3..00000000 --- a/.config/weechat/perl/autoload/atcomplete.pl +++ /dev/null @@ -1 +0,0 @@ -../atcomplete.pl \ No newline at end of file diff --git a/.config/weechat/perl/autoload/atcomplete.pl b/.config/weechat/perl/autoload/atcomplete.pl new file mode 100644 index 00000000..e0b5d5c8 --- /dev/null +++ b/.config/weechat/perl/autoload/atcomplete.pl @@ -0,0 +1,89 @@ +# 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 120000 index 200ecd52..00000000 --- a/.config/weechat/perl/autoload/awaylog.pl +++ /dev/null @@ -1 +0,0 @@ -../awaylog.pl \ No newline at end of file diff --git a/.config/weechat/perl/autoload/awaylog.pl b/.config/weechat/perl/autoload/awaylog.pl new file mode 100644 index 00000000..91e9b1b8 --- /dev/null +++ b/.config/weechat/perl/autoload/awaylog.pl @@ -0,0 +1,100 @@ +############################################################################### +# +# 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 120000 index 2eb5e1e5..00000000 --- a/.config/weechat/perl/autoload/highmon.pl +++ /dev/null @@ -1 +0,0 @@ -../highmon.pl \ No newline at end of file diff --git a/.config/weechat/perl/autoload/highmon.pl b/.config/weechat/perl/autoload/highmon.pl new file mode 100644 index 00000000..f843cade --- /dev/null +++ b/.config/weechat/perl/autoload/highmon.pl @@ -0,0 +1,1154 @@ +# +# 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 120000 index 2746e0d8..00000000 --- a/.config/weechat/perl/autoload/iset.pl +++ /dev/null @@ -1 +0,0 @@ -../iset.pl \ No newline at end of file diff --git a/.config/weechat/perl/autoload/iset.pl b/.config/weechat/perl/autoload/iset.pl new file mode 100644 index 00000000..a4a8e35f --- /dev/null +++ b/.config/weechat/perl/autoload/iset.pl @@ -0,0 +1,1645 @@ +# +# 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/python/autoload/aesthetic.py b/.config/weechat/python/autoload/aesthetic.py deleted file mode 120000 index 9c60e1b2..00000000 --- a/.config/weechat/python/autoload/aesthetic.py +++ /dev/null @@ -1 +0,0 @@ -../aesthetic.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/aesthetic.py b/.config/weechat/python/autoload/aesthetic.py new file mode 100644 index 00000000..988071d6 --- /dev/null +++ b/.config/weechat/python/autoload/aesthetic.py @@ -0,0 +1,70 @@ +# -*- 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 120000 index 1517fefe..00000000 --- a/.config/weechat/python/autoload/anotify.py +++ /dev/null @@ -1 +0,0 @@ -../anotify.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/anotify.py b/.config/weechat/python/autoload/anotify.py new file mode 100644 index 00000000..0a047b42 --- /dev/null +++ b/.config/weechat/python/autoload/anotify.py @@ -0,0 +1,476 @@ +# -*- 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 120000 index 1850897f..00000000 --- a/.config/weechat/python/autoload/autosort.py +++ /dev/null @@ -1 +0,0 @@ -../autosort.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/autosort.py b/.config/weechat/python/autoload/autosort.py new file mode 100644 index 00000000..4312cda7 --- /dev/null +++ b/.config/weechat/python/autoload/autosort.py @@ -0,0 +1,1075 @@ +# -*- 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 120000 index 3ee34e96..00000000 --- a/.config/weechat/python/autoload/colorize_nicks.py +++ /dev/null @@ -1 +0,0 @@ -../colorize_nicks.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/colorize_nicks.py b/.config/weechat/python/autoload/colorize_nicks.py new file mode 100644 index 00000000..cb95a0d6 --- /dev/null +++ b/.config/weechat/python/autoload/colorize_nicks.py @@ -0,0 +1,409 @@ +# -*- 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 120000 index bdfb7ddf..00000000 --- a/.config/weechat/python/autoload/go.py +++ /dev/null @@ -1 +0,0 @@ -../go.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/go.py b/.config/weechat/python/autoload/go.py new file mode 100644 index 00000000..2ab47ed4 --- /dev/null +++ b/.config/weechat/python/autoload/go.py @@ -0,0 +1,563 @@ +# -*- 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 120000 index 30732091..00000000 --- a/.config/weechat/python/autoload/styurl.py +++ /dev/null @@ -1 +0,0 @@ -../styurl.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/styurl.py b/.config/weechat/python/autoload/styurl.py new file mode 100644 index 00000000..69716505 --- /dev/null +++ b/.config/weechat/python/autoload/styurl.py @@ -0,0 +1,178 @@ +# -*- 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 120000 index c6303a5c..00000000 --- a/.config/weechat/python/autoload/vimode.py +++ /dev/null @@ -1 +0,0 @@ -../vimode.py \ No newline at end of file diff --git a/.config/weechat/python/autoload/vimode.py b/.config/weechat/python/autoload/vimode.py new file mode 100644 index 00000000..b1e66410 --- /dev/null +++ b/.config/weechat/python/autoload/vimode.py @@ -0,0 +1,1884 @@ +# -*- 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/wgetrc b/.config/wgetrc old mode 100644 new mode 100755 diff --git a/.config/zsh/.zshrc b/.config/zsh/.zshrc index 16f8db77..3a8a0427 100644 --- a/.config/zsh/.zshrc +++ b/.config/zsh/.zshrc @@ -1,6 +1,6 @@ #zmodload zsh/zprof eval "$(direnv hook zsh)" >> $XDG_RUNTIME_DIR/direnv -paleofetch +#paleofetch if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" fi diff --git a/.config/zsh/aliases b/.config/zsh/aliases index 80c21cfc..caf12a95 100755 --- a/.config/zsh/aliases +++ b/.config/zsh/aliases @@ -1,9 +1,6 @@ #!/usr/bin/zsh alias feh="feh --scale-down --auto-zoom --no-fehbg" -alias cclear="$(which clear)" -alias clear="clear && paleofetch" -alias neofetch="neofetch --ascii ~/.config/neofetch/ascii.txt" alias open=xdg-open alias rm="rm -i" alias clip="xclip -selection clipboard" @@ -28,7 +25,7 @@ weechat(){ } # Suffix aliases -alias -g G=" | rg" +alias -g G=" | rg -i " alias gshh="gcloud cloud-shell ssh --authorize-session" # Git @@ -52,3 +49,5 @@ alias dpall="dots remote | xargs -I R git --git-dir=$HOME/.dotfiles.git/ --work- alias dignore="dots update-index --assume-unchanged {pkg.list,install.sh,README.md}" alias da="dots add -u" alias dcm="dots commit" + +alias bon="sudo bash -c 'rc-service bluetooth start && rfkill unblock bluetooth'" diff --git a/.local/bin/backup b/.local/bin/backup index 9662f126..c75b9017 100755 --- a/.local/bin/backup +++ b/.local/bin/backup @@ -20,7 +20,8 @@ date=$(date "+%I:%M-%d.%m.%Y") DIRS="/home/yigit /etc /var/lib -/var/spool" +/var/spool +/usr/src/linux" # 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 diff --git a/.local/bin/mpd-notif b/.local/bin/mpd-notif index 34a7d4c7..f5987433 100755 --- a/.local/bin/mpd-notif +++ b/.local/bin/mpd-notif @@ -1,6 +1,6 @@ #!/bin/sh -title=$(mpc -f "%title%" 2> /dev/null | head -n 1 | xargs) -artist=$(mpc -f "%artist%" 2> /dev/null | head -n 1 | xargs) +title=$(mpc -f "%title%" 2> /dev/null | head -n 1 | xargs -0 ) +artist=$(mpc -f "%artist%" 2> /dev/null | head -n 1 | xargs -0 ) notify-send -a " $artist" -t 1500 "~ $title ~ " diff --git a/.local/bin/status-bar/cpu-temp b/.local/bin/status-bar/cpu-temp index c3c5405f..dee2f9d8 100755 --- a/.local/bin/status-bar/cpu-temp +++ b/.local/bin/status-bar/cpu-temp @@ -1,8 +1,14 @@ #!/bin/bash -source ~/.config/config.env +i=0 +max=0 +while [ -f "/sys/class/thermal/thermal_zone$i/temp" ]; do + if [ "$(sed 's/[0-9][0-9][0-9]$//' "/sys/class/thermal/thermal_zone$i/temp")" -gt "$max" ]; then + max="$(sed 's/[0-9][0-9][0-9]$//' "/sys/class/thermal/thermal_zone$i/temp")" + fi + i=$((i+1)) +done -temp=$(sed 's/[0-9][0-9][0-9]$/°C/' $TEMP_ZONE) icon= -echo "^c#bf616a^$icon^d^ $temp" +echo "^c#bf616a^$icon^d^ $max°C" diff --git a/.local/bin/status-bar/volume b/.local/bin/status-bar/volume index 8fab85dc..cfc5dd83 100755 --- a/.local/bin/status-bar/volume +++ b/.local/bin/status-bar/volume @@ -14,5 +14,5 @@ else fi case $BLOCK_BUTTON in - 1) setsid -f st -c stpulse -n stpulse -e ncpamixer ;; + 1) setsid -f st -c stpulse -n stpulse -e pamix ;; esac diff --git a/.local/share/dwm/autostart.sh b/.local/share/dwm/autostart.sh new file mode 100755 index 00000000..64aa09d1 --- /dev/null +++ b/.local/share/dwm/autostart.sh @@ -0,0 +1,54 @@ +#!/bin/sh + + +~/.local/bin/daily-update +dwmblocks > $XDG_RUNTIME_DIR/dwmblocks.out 2> $XDG_RUNTIME_DIR/dwmblocks.err & + +~/.local/bin/keyboard > $XDG_RUNTIME_DIR/keyboard.out 2> $XDG_RUNTIME_DIR/keyboard.err +~/.local/bin/mailsync & + +if [ "$NEXTCLOUD" = true ] ; then + nextcloud --background & +fi +mkdir -p ~/Downloads/neomutt +if [ "$MCONNECT" = true ] ; then + mkdir -p ~/Downloads/mconnect + (cd ~/Downloads/mconnect; mconnect -d > $XDG_RUNTIME_DIR/mconnect 2> $XDG_RUNTIME_DIR/mconnect.err &) +fi +if [ "$ACTIVITYWATCHER" = true ] ; then + pkill -f aw-watcher-window + pkill -f aw-watcher-afk + pkill -f aw-server + aw-server & + aw-watcher-window & + aw-watcher-afk & +fi +mpd +mpd-mpris & +touch ~/.cache/nextcloud-track +xss-lock -- slock & +picom --no-fading-openclose & + + +~/.local/bin/firefox-sync +curl 'http://yeetclock/setcolor?R=136&G=192&B=208' & + +dunst & + +xbanish -s & + +redshift -x 2> /dev/null > /dev/null +redshift -r -l "$LATLONG" > /dev/null 2> /dev/null & + + +tmux new-session -s weechat -d weechat > /dev/null 2> /dev/null + + +~/.local/bin/devmon --exec-on-drive "/sbin/notify-send '禍 drive mounted' '%l (%f) at %d '" \ + --exec-on-remove "/sbin/notify-send '禍 drive removed' '%l (%f) from %d '" \ + --exec-on-unmount "/sbin/notify-send '禍 drive unmounted' '%l (%f) from %d '" \ + --no-unmount --no-gui & + +clipmenud > $XDG_RUNTIME_DIR/clipmenud.out 2> $XDG_RUNTIME_DIR/clipmenud.err & +rm -f ~/.surf/tabbed-surf.xid +/bin/polkit-dumb-agent & diff --git a/.local/share/gnupg/gpg-agent.conf b/.local/share/gnupg/gpg-agent.conf index 80e2992c..f87cd6a1 100644 --- a/.local/share/gnupg/gpg-agent.conf +++ b/.local/share/gnupg/gpg-agent.conf @@ -1,3 +1,5 @@ +pinentry-program /usr/bin/pinentry +no-grab allow-preset-passphrase max-cache-ttl 172800 enable-ssh-support diff --git a/.local/src/dwm/dwm.1 b/.local/src/dwm/dwm.1 index 829047b8..53cabd88 100644 --- a/.local/src/dwm/dwm.1 +++ b/.local/src/dwm/dwm.1 @@ -30,6 +30,14 @@ top left corner. The tags which are applied to one or more windows are indicated with an empty square in the top left corner. .P dwm draws a small border around windows to indicate the focus state. +.P +On start, dwm can start additional programs that may be specified in two special +shell scripts (see the FILES section below), autostart_blocking.sh and +autostart.sh. The former is executed first and dwm will wait for its +termination before starting. The latter is executed in the background before +dwm enters its handler loop. +.P +Either of these files may be omitted. .SH OPTIONS .TP .B \-v @@ -158,6 +166,21 @@ Toggles focused window between floating and tiled state. .TP .B Mod1\-Button3 Resize focused window while dragging. Tiled windows will be toggled to the floating state. +.SH FILES +The files containing programs to be started along with dwm are searched for in +the following directories: +.IP "1. $XDG_DATA_HOME/dwm" +.IP "2. $HOME/.local/share/dwm" +.IP "3. $HOME/.dwm" +.P +The first existing directory is scanned for any of the autostart files below. +.TP 15 +autostart.sh +This file is started as a shell background process before dwm enters its handler +loop. +.TP 15 +autostart_blocking.sh +This file is started before any autostart.sh; dwm waits for its termination. .SH CUSTOMIZATION dwm is customized by creating a custom config.h and (re)compiling the source code. This keeps it fast, secure and simple. diff --git a/.local/src/dwm/dwm.c b/.local/src/dwm/dwm.c index 25bf3cd1..c3d12680 100644 --- a/.local/src/dwm/dwm.c +++ b/.local/src/dwm/dwm.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -231,6 +232,7 @@ static void resizeclient(Client *c, int x, int y, int w, int h); static void resizemouse(const Arg *arg); static void restack(Monitor *m); static void run(void); +static void runautostart(void); static void scan(void); static int sendevent(Client *c, Atom proto); static void sendmon(Client *c, Monitor *m); @@ -285,7 +287,11 @@ static Client *termforwin(const Client *c); static pid_t winpid(Window w); /* variables */ +static const char autostartblocksh[] = "autostart_blocking.sh"; +static const char autostartsh[] = "autostart.sh"; static const char broken[] = "broken"; +static const char dwmdir[] = "dwm"; +static const char localshare[] = ".local/share"; static char stext[1024]; static char rawstext[1024]; static int dwmblockssig; @@ -2016,6 +2022,83 @@ run(void) handler[ev.type](&ev); /* call handler */ } +void +runautostart(void) +{ + char *pathpfx; + char *path; + char *xdgdatahome; + char *home; + struct stat sb; + + if ((home = getenv("HOME")) == NULL) + /* this is almost impossible */ + return; + + /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm, + * otherwise use ~/.local/share/dwm as autostart script directory + */ + xdgdatahome = getenv("XDG_DATA_HOME"); + if (xdgdatahome != NULL && *xdgdatahome != '\0') { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2); + + if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { + free(pathpfx); + return; + } + } else { + /* space for path segments, separators and nul */ + pathpfx = ecalloc(1, strlen(home) + strlen(localshare) + + strlen(dwmdir) + 3); + + if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { + free(pathpfx); + return; + } + } + + /* check if the autostart script directory exists */ + if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { + /* the XDG conformant path does not exist or is no directory + * so we try ~/.dwm instead + */ + char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3); + if(pathpfx_new == NULL) { + free(pathpfx); + return; + } + pathpfx = pathpfx_new; + + if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { + free(pathpfx); + return; + } + } + + /* try the blocking script first */ + path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2); + if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(path); + + /* now the non-blocking script */ + if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { + free(path); + free(pathpfx); + } + + if (access(path, X_OK) == 0) + system(strcat(path, " &")); + + free(pathpfx); + free(path); +} + void scan(void) { @@ -3074,6 +3157,7 @@ main(int argc, char *argv[]) die("pledge"); #endif /* __OpenBSD__ */ scan(); + runautostart(); run(); cleanup(); XCloseDisplay(dpy); diff --git a/.local/src/dwm/patches/dwm-autostart-20210120-cb3f58a.diff b/.local/src/dwm/patches/dwm-autostart-20210120-cb3f58a.diff new file mode 100644 index 00000000..efee6761 --- /dev/null +++ b/.local/src/dwm/patches/dwm-autostart-20210120-cb3f58a.diff @@ -0,0 +1,179 @@ +From 37e970479dc5d40e57fc0cbfeaa5e39941483237 Mon Sep 17 00:00:00 2001 +From: Gan Ainm +Date: Wed, 10 Jun 2020 10:59:02 +0000 +Subject: [PATCH] dwm-xdgautostart-6.2.diff + +=================================================================== +--- + dwm.1 | 23 +++++++++++++++++ + dwm.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 105 insertions(+) + +diff --git a/dwm.1 b/dwm.1 +index 13b3729..9533aa6 100644 +--- a/dwm.1 ++++ b/dwm.1 +@@ -30,6 +30,14 @@ top left corner. The tags which are applied to one or more windows are + indicated with an empty square in the top left corner. + .P + dwm draws a small border around windows to indicate the focus state. ++.P ++On start, dwm can start additional programs that may be specified in two special ++shell scripts (see the FILES section below), autostart_blocking.sh and ++autostart.sh. The former is executed first and dwm will wait for its ++termination before starting. The latter is executed in the background before ++dwm enters its handler loop. ++.P ++Either of these files may be omitted. + .SH OPTIONS + .TP + .B \-v +@@ -152,6 +160,21 @@ Toggles focused window between floating and tiled state. + .TP + .B Mod1\-Button3 + Resize focused window while dragging. Tiled windows will be toggled to the floating state. ++.SH FILES ++The files containing programs to be started along with dwm are searched for in ++the following directories: ++.IP "1. $XDG_DATA_HOME/dwm" ++.IP "2. $HOME/.local/share/dwm" ++.IP "3. $HOME/.dwm" ++.P ++The first existing directory is scanned for any of the autostart files below. ++.TP 15 ++autostart.sh ++This file is started as a shell background process before dwm enters its handler ++loop. ++.TP 15 ++autostart_blocking.sh ++This file is started before any autostart.sh; dwm waits for its termination. + .SH CUSTOMIZATION + dwm is customized by creating a custom config.h and (re)compiling the source + code. This keeps it fast, secure and simple. +diff --git a/dwm.c b/dwm.c +index 4465af1..2156b49 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -193,6 +194,7 @@ static void resizeclient(Client *c, int x, int y, int w, int h); + static void resizemouse(const Arg *arg); + static void restack(Monitor *m); + static void run(void); ++static void runautostart(void); + static void scan(void); + static int sendevent(Client *c, Atom proto); + static void sendmon(Client *c, Monitor *m); +@@ -235,7 +237,11 @@ static int xerrorstart(Display *dpy, XErrorEvent *ee); + static void zoom(const Arg *arg); + + /* variables */ ++static const char autostartblocksh[] = "autostart_blocking.sh"; ++static const char autostartsh[] = "autostart.sh"; + static const char broken[] = "broken"; ++static const char dwmdir[] = "dwm"; ++static const char localshare[] = ".local/share"; + static char stext[256]; + static int screen; + static int sw, sh; /* X display screen geometry width, height */ +@@ -1380,6 +1386,83 @@ run(void) + handler[ev.type](&ev); /* call handler */ + } + ++void ++runautostart(void) ++{ ++ char *pathpfx; ++ char *path; ++ char *xdgdatahome; ++ char *home; ++ struct stat sb; ++ ++ if ((home = getenv("HOME")) == NULL) ++ /* this is almost impossible */ ++ return; ++ ++ /* if $XDG_DATA_HOME is set and not empty, use $XDG_DATA_HOME/dwm, ++ * otherwise use ~/.local/share/dwm as autostart script directory ++ */ ++ xdgdatahome = getenv("XDG_DATA_HOME"); ++ if (xdgdatahome != NULL && *xdgdatahome != '\0') { ++ /* space for path segments, separators and nul */ ++ pathpfx = ecalloc(1, strlen(xdgdatahome) + strlen(dwmdir) + 2); ++ ++ if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { ++ free(pathpfx); ++ return; ++ } ++ } else { ++ /* space for path segments, separators and nul */ ++ pathpfx = ecalloc(1, strlen(home) + strlen(localshare) ++ + strlen(dwmdir) + 3); ++ ++ if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { ++ free(pathpfx); ++ return; ++ } ++ } ++ ++ /* check if the autostart script directory exists */ ++ if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { ++ /* the XDG conformant path does not exist or is no directory ++ * so we try ~/.dwm instead ++ */ ++ char *pathpfx_new = realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3); ++ if(pathpfx_new == NULL) { ++ free(pathpfx); ++ return; ++ } ++ pathpfx = pathpfx_new; ++ ++ if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { ++ free(pathpfx); ++ return; ++ } ++ } ++ ++ /* try the blocking script first */ ++ path = ecalloc(1, strlen(pathpfx) + strlen(autostartblocksh) + 2); ++ if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { ++ free(path); ++ free(pathpfx); ++ } ++ ++ if (access(path, X_OK) == 0) ++ system(path); ++ ++ /* now the non-blocking script */ ++ if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { ++ free(path); ++ free(pathpfx); ++ } ++ ++ if (access(path, X_OK) == 0) ++ system(strcat(path, " &")); ++ ++ free(pathpfx); ++ free(path); ++} ++ + void + scan(void) + { +@@ -2142,6 +2223,7 @@ main(int argc, char *argv[]) + die("pledge"); + #endif /* __OpenBSD__ */ + scan(); ++ runautostart(); + run(); + cleanup(); + XCloseDisplay(dpy); +-- +2.27.0 + diff --git a/.local/src/dwm/patches/dwm-losefullscreen-6.2.diff b/.local/src/dwm/patches/dwm-losefullscreen-6.2.diff new file mode 100644 index 00000000..990bf732 --- /dev/null +++ b/.local/src/dwm/patches/dwm-losefullscreen-6.2.diff @@ -0,0 +1,98 @@ +From b0f56cb0228afc5bda80ea233d1cffe1a19f292f Mon Sep 17 00:00:00 2001 +From: bakkeby +Date: Tue, 7 Apr 2020 11:40:30 +0200 +Subject: [PATCH] Lose fullscreen on focus change + +--- + dwm.c | 20 ++++++++++++++------ + 1 file changed, 14 insertions(+), 6 deletions(-) + +diff --git a/dwm.c b/dwm.c +index 4465af1..a300ba0 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -418,15 +418,16 @@ buttonpress(XEvent *e) + { + unsigned int i, x, click; + Arg arg = {0}; +- Client *c; ++ Client *c, *sel; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { +- unfocus(selmon->sel, 1); ++ sel = selmon->sel; + selmon = m; ++ unfocus(sel, 1); + focus(NULL); + } + if (ev->window == selmon->barwin) { +@@ -754,7 +755,7 @@ drawbars(void) + void + enternotify(XEvent *e) + { +- Client *c; ++ Client *c, *sel; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + +@@ -763,8 +764,9 @@ enternotify(XEvent *e) + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { +- unfocus(selmon->sel, 1); ++ sel = selmon->sel; + selmon = m; ++ unfocus(sel, 1); + } else if (!c || c == selmon->sel) + return; + focus(c); +@@ -819,13 +821,15 @@ void + focusmon(const Arg *arg) + { + Monitor *m; ++ Client *sel; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; +- unfocus(selmon->sel, 0); ++ sel = selmon->sel; + selmon = m; ++ unfocus(sel, 0); + focus(NULL); + } + +@@ -1120,13 +1124,15 @@ motionnotify(XEvent *e) + { + static Monitor *mon = NULL; + Monitor *m; ++ Client *sel; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { +- unfocus(selmon->sel, 1); ++ sel = selmon->sel; + selmon = m; ++ unfocus(sel, 1); + focus(NULL); + } + mon = m; +@@ -1751,6 +1757,8 @@ unfocus(Client *c, int setfocus) + { + if (!c) + return; ++ if (c->isfullscreen && ISVISIBLE(c) && c->mon == selmon) ++ setfullscreen(c, 0); + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { +-- +2.19.1 + diff --git a/.local/src/dwm/rules.h b/.local/src/dwm/rules.h index e7a11d8a..6a5377c6 100644 --- a/.local/src/dwm/rules.h +++ b/.local/src/dwm/rules.h @@ -20,7 +20,7 @@ static const Rule rules[] = { /* class instance title tags mask isfloating floatpos isterminal noswallow monitor */ { "discord" , NULL , NULL , 1 << 8 , NULL , NULL , 0 , 0 , -1 }, { "Brave-browser" , NULL , NULL , 1 << 1 , NULL , NULL , 0 , 0 , -1 }, -{ "firefox" , NULL , NULL , 1 << 1 , NULL , NULL , 0 , 0 , -1 }, +{ "Firefox" , NULL , NULL , 1 << 1 , NULL , NULL , 0 , 0 , -1 }, { "tabbed-surf" , NULL , NULL , 1 << 1 , NULL , NULL , 0 , 0 , -1 }, { "bitwarden" , NULL , NULL , 1 << 6 , NULL , NULL , 0 , 0 , -1 }, { "QtPass" , NULL , NULL , 1 << 6 , NULL , NULL , 0 , 0 , -1 }, diff --git a/.local/src/dwmblocks/config.h b/.local/src/dwmblocks/config.h index eba1199d..c28be264 100644 --- a/.local/src/dwmblocks/config.h +++ b/.local/src/dwmblocks/config.h @@ -16,7 +16,7 @@ static Block blocks[] = { { "", PATH("cpu-temp"), 30, 17}, { "", PATH("memory"), 120, 21}, { "", PATH("weather"), 60, 16}, - { "", PATH("arch"), 120, 15}, +// { "", PATH("arch"), 120, 15}, { "", PATH("volume"), 5, 14}, { "", PATH("network"), 120, 13}, { "", PATH("battery"), 60, 12}, diff --git a/.local/src/grabc/grabc b/.local/src/grabc/grabc index 4962cc06..f5821833 100755 Binary files a/.local/src/grabc/grabc and b/.local/src/grabc/grabc differ diff --git a/.local/src/grabc/grabc.o b/.local/src/grabc/grabc.o index 8a99c567..3c572a80 100644 Binary files a/.local/src/grabc/grabc.o and b/.local/src/grabc/grabc.o differ diff --git a/.local/src/paleofetch/paleofetch.c b/.local/src/paleofetch/paleofetch.c index 72ce8263..6f0c38ea 100644 --- a/.local/src/paleofetch/paleofetch.c +++ b/.local/src/paleofetch/paleofetch.c @@ -222,7 +222,7 @@ static char *get_uptime() { char *uptime = malloc(BUF_SIZE); for (int i = 0; i < 3; ++i ) { if ((n = seconds / units[i].secs) || i == 2) /* always print minutes */ - len += snprintf(uptime + len, BUF_SIZE - len, + len += snprintf(uptime + len, BUF_SIZE - len, "%d %s%s, ", n, units[i].name, n != 1 ? "s": ""); seconds %= units[i].secs; } @@ -308,10 +308,10 @@ static char *get_shell() { static char *get_resolution() { int screen, width, height; char *resolution = malloc(BUF_SIZE); - + if (display != NULL) { screen = DefaultScreen(display); - + width = DisplayWidth(display, screen); height = DisplayHeight(display, screen); @@ -324,7 +324,7 @@ static char *get_resolution() { FILE *modes; char *line = NULL; size_t len; - + /* preload resolution with empty string, in case we cant find a resolution through parsing */ strncpy(resolution, "", BUF_SIZE); @@ -354,7 +354,7 @@ static char *get_resolution() { } } } - + closedir(dir); } @@ -366,10 +366,10 @@ static char *get_terminal() { char *terminal = malloc(BUF_SIZE); /* check if xserver is running or if we are running in a straight tty */ - if (display != NULL) { + if (display != NULL) { unsigned long _, // not unused, but we don't need the results - window = RootWindow(display, XDefaultScreen(display)); + window = RootWindow(display, XDefaultScreen(display)); Atom a, active = XInternAtom(display, "_NET_ACTIVE_WINDOW", True), class = XInternAtom(display, "WM_CLASS", True); @@ -748,7 +748,7 @@ int main(int argc, char *argv[]) { free(cache); free(cache_data); - if(display != NULL) { + if(display != NULL) { XCloseDisplay(display); } diff --git a/.local/src/sent/sent b/.local/src/sent/sent index 43c6a65f..84edefb2 100755 Binary files a/.local/src/sent/sent and b/.local/src/sent/sent differ diff --git a/.local/src/st/st b/.local/src/st/st index e92a2f83..8737adcd 100755 Binary files a/.local/src/st/st and b/.local/src/st/st differ diff --git a/.local/src/st/x.o b/.local/src/st/x.o index 663e82d4..60ed9194 100644 Binary files a/.local/src/st/x.o and b/.local/src/st/x.o differ