123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783 |
- use strict; use warnings;
- $INC{'Encode/ConfigLocal.pm'}=1;
- require Encode;
- use utf8;
- # multiline.pl is written by Nei <anti.teamidiot.de>
- # and licensed under the under GNU General Public License v3
- # or any later version
- # to read the following docs, you can use "perldoc multiline.pl"
- =head1 NAME
- multiline - Multi-line edit box for WeeChat (weechat edition)
- =head1 DESCRIPTION
- multiline will draw a multi-line edit box to your WeeChat window so
- that when you hit the return key, you can first compose a complete
- multi-line message before sending it all at once.
- Furthermore, if you have multi-line pastes then you can edit them
- before sending out all the lines.
- =head1 USAGE
- make a key binding to send the finished message:
- /key bind meta-s /input return
- then you can send the multi-line message with Alt+S
- =head1 SETTINGS
- the settings are usually found in the
- plugins.var.perl.multiline
- namespace, that is, type
- /set plugins.var.perl.multiline.*
- to see them and
- /set plugins.var.perl.multiline.SETTINGNAME VALUE
- to change a setting C<SETTINGNAME> to a new value C<VALUE>. Finally,
- /unset plugins.var.perl.multiline.SETTINGNAME
- will reset a setting to its default value.
- the following settings are available:
- =head2 char
- character(s) which should be displayed to indicate end of line
- =head2 tab
- character(s) which should be displayed instead of Tab key character
- =head2 lead_linebreak
- if turned on, multi-line messages always start on a new line
- =head2 modify_keys
- if turned on, cursor keys are modified so that they respect line
- boundaries instead of treating the whole multi-line message as a
- single line
- =head2 magic
- indicator displayed when message will be sent soon
- =head2 magic_enter_time
- delay after pressing enter before sending automatically (in ms), or 0
- to disable
- =head2 magic_paste_only
- only use multi-line messages for multi-line pastes (multi-line on
- enter is disabled by this)
- =head2 paste_lock
- time-out to detect pastes (disable the weechat built-in paste
- detection if you want to use this)
- =head2 send_empty
- set to on to automatically disregard enter key on empty line
- =head2 hide_magic_nl
- whether the new line inserted by magic enter key will be hidden
- =head2 weechat_paste_fix
- disable ctrl-J binding when paste is detected to stop silly weechat
- sending out pastes without allowing to edit them
- =head2 ipl
- this setting controls override of ctrl-M (enter key) by script. Turn
- it off if you don't want multiline.pl to set and re-set the key binding.
- =head1 FUNCTION DESCRIPTION
- for full pod documentation, filter this script with
- perl -pE'
- (s/^## (.*?) -- (.*)/=head2 $1\n\n$2\n\n=over\n/ and $o=1) or
- s/^## (.*?) - (.*)/=item I<$1>\n\n$2\n/ or
- (s/^## (.*)/=back\n\n$1\n\n=cut\n/ and $o=0,1) or
- ($o and $o=0,1 and s/^sub /=back\n\n=cut\n\nsub /)'
- =cut
- use constant SCRIPT_NAME => 'multiline';
- our $VERSION = '0.6.3'; # af2e0a17b659a16
- weechat::register(SCRIPT_NAME,
- 'Nei <anti.teamidiot.de>', # Author
- $VERSION,
- 'GPL3', # License
- 'Multi-line edit box', # Description
- 'stop_multiline', '') || return;
- sub SCRIPT_FILE() {
- my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME);
- my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr);
- weechat::infolist_free($infolistptr);
- return $filename unless @_;
- }
- {
- package Nlib;
- # this is a weechat perl library
- use strict; use warnings; no warnings 'redefine';
- ## i2h -- copy weechat infolist content into perl hash
- ## $infolist - name of the infolist in weechat
- ## $ptr - pointer argument (infolist dependend)
- ## @args - arguments to the infolist (list dependend)
- ## $fields - string of ref type "fields" if only certain keys are needed (optional)
- ## returns perl list with perl hashes for each infolist entry
- sub i2h {
- my %i2htm = (i => 'integer', s => 'string', p => 'pointer', b => 'buffer', t => 'time');
- local *weechat::infolist_buffer = sub { '(not implemented)' };
- my ($infolist, $ptr, @args) = @_;
- $ptr ||= "";
- my $fields = ref $args[-1] eq 'fields' ? ${ pop @args } : undef;
- my $infptr = weechat::infolist_get($infolist, $ptr, do { local $" = ','; "@args" });
- my @infolist;
- while (weechat::infolist_next($infptr)) {
- my @fields = map {
- my ($t, $v) = split ':', $_, 2;
- bless \$v, $i2htm{$t};
- }
- split ',',
- ($fields || weechat::infolist_fields($infptr));
- push @infolist, +{ do {
- my (%list, %local, @local);
- map {
- my $fn = 'weechat::infolist_'.ref $_;
- my $r = do { no strict 'refs'; &$fn($infptr, $$_) };
- if ($$_ =~ /^localvar_name_(\d+)$/) {
- $local[$1] = $r;
- ()
- }
- elsif ($$_ =~ /^(localvar)_value_(\d+)$/) {
- $local{$local[$2]} = $r;
- $1 => \%local
- }
- elsif ($$_ =~ /(.*?)((?:_\d+)+)$/) {
- my ($key, $idx) = ($1, $2);
- my @idx = split '_', $idx; shift @idx;
- my $target = \$list{$key};
- for my $x (@idx) {
- my $o = 1;
- if ($key eq 'key' or $key eq 'key_command') {
- $o = 0;
- }
- if ($x-$o < 0) {
- local $" = '|';
- weechat::print('',"list error: $target/$$_/$key/$x/$idx/@idx(@_)");
- $o = 0;
- }
- $target = \$$target->[$x-$o]
- }
- $$target = $r;
- $key => $list{$key}
- }
- else {
- $$_ => $r
- }
- } @fields
- } };
- }
- weechat::infolist_free($infptr);
- !wantarray && @infolist ? \@infolist : @infolist
- }
- ## hdh -- hdata helper
- ## $_[0] - arg pointer or hdata list name
- ## $_[1] - hdata name
- ## $_[2..$#_] - hdata variable name
- ## $_[-1] - hashref with key/value to update (optional)
- ## returns value of hdata, and hdata name in list ctx, or number of variables updated
- sub hdh {
- if (@_ > 1 && $_[0] !~ /^0x/ && $_[0] !~ /^\d+$/) {
- my $arg = shift;
- unshift @_, weechat::hdata_get_list(weechat::hdata_get($_[0]), $arg);
- }
- while (@_ > 2) {
- my ($arg, $name, $var) = splice @_, 0, 3;
- my $hdata = weechat::hdata_get($name);
- unless (ref $var eq 'HASH') {
- $var =~ s/!(.*)/weechat::hdata_get_string($hdata, $1)/e;
- (my $plain_var = $var) =~ s/^\d+\|//;
- my $type = weechat::hdata_get_var_type_string($hdata, $plain_var);
- if ($type eq 'pointer') {
- my $name = weechat::hdata_get_var_hdata($hdata, $var);
- unshift @_, $name if $name;
- }
- my $fn = "weechat::hdata_$type";
- unshift @_, do { no strict 'refs';
- &$fn($hdata, $arg, $var) };
- }
- else {
- return weechat::hdata_update($hdata, $arg, $var);
- }
- }
- wantarray ? @_ : $_[0]
- }
- use Pod::Select qw();
- use Pod::Simple::TextContent;
- ## get_desc_from_pod -- return setting description from pod documentation
- ## $file - filename with pod
- ## $setting - name of setting
- ## returns description as text
- sub get_desc_from_pod {
- my $file = shift;
- return unless -s $file;
- my $setting = shift;
- open my $pod_sel, '>', \my $ss;
- Pod::Select::podselect({
- -output => $pod_sel,
- -sections => ["SETTINGS/$setting"]}, $file);
- my $pt = new Pod::Simple::TextContent;
- $pt->output_string(\my $ss_f);
- $pt->parse_string_document($ss);
- my ($res) = $ss_f =~ /^\s*\Q$setting\E\s+(.*)\s*/;
- $res
- }
- ## get_settings_from_pod -- retrieve all settings in settings section of pod
- ## $file - file with pod
- ## returns list of all settings
- sub get_settings_from_pod {
- my $file = shift;
- return unless -s $file;
- open my $pod_sel, '>', \my $ss;
- Pod::Select::podselect({
- -output => $pod_sel,
- -sections => ["SETTINGS//!.+"]}, $file);
- $ss =~ /^=head2\s+(.*)\s*$/mg
- }
- ## mangle_man_for_wee -- turn man output into weechat codes
- ## @_ - list of grotty lines that should be turned into weechat attributes
- ## returns modified lines and modifies lines in-place
- sub mangle_man_for_wee {
- for (@_) {
- s/_\x08(.)/weechat::color('underline').$1.weechat::color('-underline')/ge;
- s/(.)\x08\1/weechat::color('bold').$1.weechat::color('-bold')/ge;
- }
- wantarray ? @_ : $_[0]
- }
- ## read_manpage -- read a man page in weechat window
- ## $file - file with pod
- ## $name - buffer name
- sub read_manpage {
- my $caller_package = (caller)[0];
- my $file = shift;
- my $name = shift;
- if (my $obuf = weechat::buffer_search('perl', "man $name")) {
- eval qq{
- package $caller_package;
- weechat::buffer_close(\$obuf);
- };
- }
- my @wee_keys = Nlib::i2h('key');
- my @keys;
- my $winptr = weechat::current_window();
- my ($wininfo) = Nlib::i2h('window', $winptr);
- my $buf = weechat::buffer_new("man $name", '', '', '', '');
- return weechat::WEECHAT_RC_OK unless $buf;
- my $width = $wininfo->{chat_width};
- --$width if $wininfo->{chat_width} < $wininfo->{width} || ($wininfo->{width_pct} < 100 && (grep { $_->{y} == $wininfo->{y} } Nlib::i2h('window'))[-1]{x} > $wininfo->{x});
- $width -= 2; # when prefix is shown
- weechat::buffer_set($buf, 'time_for_each_line', 0);
- eval qq{
- package $caller_package;
- weechat::buffer_set(\$buf, 'display', 'auto');
- };
- die $@ if $@;
- @keys = map { $_->{key} }
- grep { $_->{command} eq '/input history_previous' ||
- $_->{command} eq '/input history_global_previous' } @wee_keys;
- @keys = 'meta2-A' unless @keys;
- weechat::buffer_set($buf, "key_bind_$_", '/window scroll -1') for @keys;
- @keys = map { $_->{key} }
- grep { $_->{command} eq '/input history_next' ||
- $_->{command} eq '/input history_global_next' } @wee_keys;
- @keys = 'meta2-B' unless @keys;
- weechat::buffer_set($buf, "key_bind_$_", '/window scroll +1') for @keys;
- weechat::buffer_set($buf, 'key_bind_ ', '/window page_down');
- @keys = map { $_->{key} }
- grep { $_->{command} eq '/input delete_previous_char' } @wee_keys;
- @keys = ('ctrl-?', 'ctrl-H') unless @keys;
- weechat::buffer_set($buf, "key_bind_$_", '/window page_up') for @keys;
- weechat::buffer_set($buf, 'key_bind_g', '/window scroll_top');
- weechat::buffer_set($buf, 'key_bind_G', '/window scroll_bottom');
- weechat::buffer_set($buf, 'key_bind_q', '/buffer close');
- weechat::print($buf, " \t".mangle_man_for_wee($_)) # weird bug with \t\t showing nothing?
- for `pod2man \Q$file\E 2>/dev/null | GROFF_NO_SGR=1 nroff -mandoc -rLL=${width}n -rLT=${width}n -Tutf8 2>/dev/null`;
- weechat::command($buf, '/window scroll_top');
- unless (hdh($buf, 'buffer', 'lines', 'lines_count') > 0) {
- weechat::print($buf, weechat::prefix('error').$_)
- for "Unfortunately, your @{[weechat::color('underline')]}nroff".
- "@{[weechat::color('-underline')]} command did not produce".
- " any output.",
- "Working pod2man and nroff commands are required for the ".
- "help viewer to work.",
- "In the meantime, please use the command ", '',
- "\tperldoc $file", '',
- "on your shell instead in order to read the manual.",
- "Thank you and sorry for the inconvenience."
- }
- }
- 1
- }
- our $MAGIC_ENTER_TIMER;
- our $MAGIC_LOCK;
- our $MAGIC_LOCK_TIMER;
- our $WEECHAT_PASTE_FIX_CTRLJ_CMD;
- our $INPUT_CHANGED_EATER_FLAG;
- our $IGNORE_INPUT_CHANGED;
- our $IGNORE_INPUT_CHANGED2;
- use constant KEY_RET => 'ctrl-M';
- use constant INPUT_NL => '/input insert \x0a';
- use constant INPUT_MAGIC => '/input magic_enter';
- our $NL = "\x0a";
- init_multiline();
- my $magic_enter_cancel_dynamic = 1;
- my $paste_undo_start_ignore_dynamic = 0;
- my $input_changed_eater_dynamic = 0;
- my $multiline_complete_fix_dynamic = 1;
- sub magic_enter_cancel_dynamic { $magic_enter_cancel_dynamic ? &magic_enter_cancel : weechat::WEECHAT_RC_OK }
- sub paste_undo_start_ignore_dynamic { $paste_undo_start_ignore_dynamic ? &paste_undo_start_ignore : weechat::WEECHAT_RC_OK }
- sub input_changed_eater_dynamic { $input_changed_eater_dynamic ? &input_changed_eater : weechat::WEECHAT_RC_OK }
- sub multiline_complete_fix_dynamic { $multiline_complete_fix_dynamic ? &multiline_complete_fix : weechat::WEECHAT_RC_OK }
- weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', '');
- weechat::hook_modifier('input_text_display_with_cursor', 'multiline_display', '');
- weechat::hook_command_run('/help '.SCRIPT_NAME, 'help_cmd', '');
- weechat::hook_command_run(INPUT_MAGIC, 'magic_enter', '');
- weechat::hook_signal('input_text_*', 'magic_enter_cancel_dynamic', '');
- weechat::hook_command_run('/input *', 'paste_undo_start_ignore_dynamic', '');
- weechat::hook_signal('2000|input_text_changed', 'input_changed_eater_dynamic', '');
- weechat::hook_signal('key_pressed', 'magic_lock_hatch', '');
- # we need lower than default priority here or the first character is separated
- weechat::hook_signal('500|input_text_changed', 'paste_undo_hack', '')
- # can only do this on weechat 0.4.0
- if (weechat::info_get('version_number', '') || 0) >= 0x00040000;
- weechat::hook_command_run("1500|/input complete*", 'multiline_complete_fix_dynamic', 'complete*');
- weechat::hook_command_run("1500|/input delete_*", 'multiline_complete_fix_dynamic', 'delete_*');
- weechat::hook_command_run("1500|/input move_*", 'multiline_complete_fix_dynamic', 'move_*');
- sub _stack_depth {
- my $depth = -1;
- 1 while caller(++$depth);
- $depth;
- }
- ## multiline_display -- show multi-lines on display of input string
- ## () - modifier handler
- ## $_[2] - buffer pointer
- ## $_[3] - input string
- ## returns modified input string
- sub multiline_display {
- Encode::_utf8_on($_[3]);
- Encode::_utf8_on(my $nl = weechat::config_get_plugin('char') || ' ');
- Encode::_utf8_on(my $tab = weechat::config_get_plugin('tab'));
- my $cb = weechat::current_buffer() eq $_[2] && $MAGIC_ENTER_TIMER;
- if ($cb) {
- $_[3] =~ s/$NL\x19b#/\x19b#/ if weechat::config_string_to_boolean(weechat::config_get_plugin('hide_magic_nl'));
- }
- if ($_[3] =~ s/$NL/$nl\x0d/g) {
- $_[3] =~ s/\A/ \x0d/ if weechat::config_string_to_boolean(weechat::config_get_plugin('lead_linebreak'));
- }
- $_[3] =~ s/\x09/$tab/g if $tab;
- if ($cb) {
- Encode::_utf8_on(my $magic = weechat::config_get_plugin('magic'));
- $_[3] =~ s/\Z/$magic/ if $magic;
- }
- $_[3]
- }
- ## lock_timer_exp -- expire the magic lock timer
- sub lock_timer_exp {
- if ($MAGIC_LOCK_TIMER) {
- weechat::unhook($MAGIC_LOCK_TIMER);
- $MAGIC_LOCK_TIMER = undef;
- }
- weechat::WEECHAT_RC_OK
- }
- ## paste_undo_stop_ignore -- unset ignore2 flag
- sub paste_undo_stop_ignore {
- $IGNORE_INPUT_CHANGED2 = undef;
- weechat::WEECHAT_RC_OK
- }
- ## paste_undo_start_ignore -- set ignore2 flag when /input is received so to allow /input undo/redo
- ## () - command_run handler
- ## $_[2] - command that was called
- sub paste_undo_start_ignore {
- return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
- return weechat::WEECHAT_RC_OK if $_[2] =~ /insert/;
- $IGNORE_INPUT_CHANGED2 = 1;
- weechat::WEECHAT_RC_OK
- }
- ## paste_undo_hack -- fix up undo stack when paste is detected by calling /input undo
- ## () - signal handler
- ## $_[2] - buffer pointer
- sub paste_undo_hack {
- return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
- return paste_undo_stop_ignore() if $IGNORE_INPUT_CHANGED2;
- if ($MAGIC_LOCK > 0 && get_lock_enabled()) {
- signall_ignore_input_changed(1);
- $paste_undo_start_ignore_dynamic = 1;
- Encode::_utf8_on(my $input = weechat::buffer_get_string($_[2], 'input'));
- my $pos = weechat::buffer_get_integer($_[2], 'input_pos');
- weechat::command($_[2], '/input undo') for 1..2;
- weechat::buffer_set($_[2], 'input', $input);
- weechat::buffer_set($_[2], 'input_pos', $pos);
- $paste_undo_start_ignore_dynamic = 0;
- signall_ignore_input_changed(0);
- }
- weechat::WEECHAT_RC_OK
- }
- ## input_changed_eater -- suppress input_text_changed signal on new weechats
- ## () - signal handler
- sub input_changed_eater {
- $INPUT_CHANGED_EATER_FLAG = undef;
- weechat::WEECHAT_RC_OK_EAT
- }
- ## signall_ignore_input_changed -- use various methods to "ignore" input_text_changed signal
- ## $_[0] - start ignore or stop ignore
- sub signall_ignore_input_changed {
- if ($_[0]) {
- weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 1);
- $input_changed_eater_dynamic = 1;
- $IGNORE_INPUT_CHANGED = 1;
- weechat::buffer_set('', 'completion_freeze', '1');
- }
- else {
- weechat::buffer_set('', 'completion_freeze', '0');
- $IGNORE_INPUT_CHANGED = undef;
- $input_changed_eater_dynamic = 0;
- weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 0);
- }
- }
- ## multiline_complete_fix -- add per line /input handling for completion, movement and deletion
- ## () - command_run handler
- ## $_[0] - original bound data
- ## $_[1] - buffer pointer
- ## $_[2] - original command
- sub multiline_complete_fix {
- $magic_enter_cancel_dynamic = 0;
- $multiline_complete_fix_dynamic = 0;
- if ($_[2] =~ s/_message$/_line/ || !weechat::config_string_to_boolean(weechat::config_get_plugin('modify_keys'))) {
- weechat::command($_[1], $_[2]);
- }
- else {
- signall_ignore_input_changed(1);
- Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
- my $pos = weechat::buffer_get_integer($_[1], 'input_pos');
- if ($pos && $_[2] =~ /(?:previous|beginning_of)_/ && (substr $input, $pos-1, 1) eq $NL) {
- substr $input, $pos-1, 1, "\0"
- }
- elsif ($pos < length $input && $_[2] =~ /(?:next|end_of)_/ && (substr $input, $pos, 1) eq $NL) {
- substr $input, $pos, 1, "\0"
- }
- my @lines = $pos ? (split /$NL/, (substr $input, 0, $pos), -1) : '';
- my @after = $pos < length $input ? (split /$NL/, (substr $input, $pos), -1) : '';
- $lines[-1] =~ s/\0$/$NL/;
- $after[0] =~ s/^\0/$NL/;
- my ($p1, $p2) = (pop @lines, shift @after);
- weechat::buffer_set($_[1], 'input', $p1.$p2);
- weechat::buffer_set($_[1], 'input_pos', length $p1);
- $magic_enter_cancel_dynamic = 1;
- $INPUT_CHANGED_EATER_FLAG = 1;
- weechat::command($_[1], $_[2]);
- my $changed_later = !$INPUT_CHANGED_EATER_FLAG;
- magic_enter_cancel() if $changed_later;
- $magic_enter_cancel_dynamic = 0;
- Encode::_utf8_on(my $p = weechat::buffer_get_string($_[1], 'input'));
- $pos = weechat::buffer_get_integer($_[1], 'input_pos');
- weechat::command($_[1], '/input undo') if @lines || @after;
- weechat::command($_[1], '/input undo');
- weechat::buffer_set($_[1], 'input', join $NL, @lines, $p, @after);
- weechat::buffer_set($_[1], 'input_pos', $pos+length join $NL, @lines, '');
- signall_ignore_input_changed(0);
- weechat::hook_signal_send('input_text_changed', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $_[1]) if $changed_later;
- }
- $multiline_complete_fix_dynamic = 1;
- $magic_enter_cancel_dynamic = 1;
- weechat::WEECHAT_RC_OK_EAT
- }
- ## help_cmd -- show multi-line script documentation
- ## () - command_run handler
- sub help_cmd {
- Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME);
- weechat::WEECHAT_RC_OK_EAT
- }
- ## get_lock_time -- gets timeout for paste detection according to setting
- ## returns timeout (at least 1)
- sub get_lock_time {
- my $lock_time = weechat::config_get_plugin('paste_lock');
- $lock_time = 1 unless $lock_time =~ /^\d+$/ && $lock_time;
- $lock_time
- }
- ## get_lock_enabled -- checks whether the paste detection lock is enabled
- ## returns bool
- sub get_lock_enabled {
- my $lock = weechat::config_get_plugin('paste_lock');
- $lock = weechat::config_string_to_boolean($lock)
- unless $lock =~ /^\d+$/;
- $lock
- }
- ## magic_lock_hatch -- set a timer for paste detection
- ## () - signal handler
- sub magic_lock_hatch {
- lock_timer_exp();
- $MAGIC_LOCK_TIMER = weechat::hook_timer(get_lock_time(), 0, 1, 'lock_timer_exp', '');
- weechat::WEECHAT_RC_OK
- }
- ## magic_unlock -- reduce the lock added by paste detection
- ## () - timer handler
- sub magic_unlock {
- if ($MAGIC_LOCK_TIMER) {
- weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
- }
- else {
- --$MAGIC_LOCK;
- if (!$MAGIC_LOCK && $WEECHAT_PASTE_FIX_CTRLJ_CMD) {
- do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD);
- $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
- }
- }
- weechat::WEECHAT_RC_OK
- }
- ## get_magic_enter_time -- get timeout for auto-sending messages according to config
- ## returns timeout
- sub get_magic_enter_time {
- my $magic_enter = weechat::config_get_plugin('magic_enter_time');
- $magic_enter = 1000 * weechat::config_string_to_boolean($magic_enter)
- unless $magic_enter =~ /^\d+$/;
- $magic_enter
- }
- ## magic_enter -- receive enter key and do magic things: set up a timer for sending the message, add newline
- ## () - command_run handler
- ## $_[1] - buffer pointer
- sub magic_enter {
- Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
- if (!length $input && weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty'))) {
- weechat::command($_[1], '/input return');
- }
- else {
- magic_enter_cancel();
- weechat::command($_[1], INPUT_NL);
- unless (get_lock_enabled() && $MAGIC_LOCK) {
- if (weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only')) &&
- $input !~ /$NL/) {
- magic_enter_send($_[1]);
- }
- elsif (my $magic_enter = get_magic_enter_time()) {
- $MAGIC_ENTER_TIMER = weechat::hook_timer($magic_enter, 0, 1, 'magic_enter_send', $_[1]);
- }
- }
- }
- weechat::WEECHAT_RC_OK_EAT
- }
- ## magic_enter_send -- actually send enter key when triggered by magic_enter, remove preceding newline
- ## $_[0] - buffer pointer
- ## sending is delayed by 1ms to circumvent crash bug in api
- sub magic_enter_send {
- magic_enter_cancel();
- weechat::command($_[0], '/input delete_previous_char');
- weechat::command($_[0], '/wait 1ms /input return');
- weechat::WEECHAT_RC_OK
- }
- ## magic_enter_cancel -- cancel the timer for automatic sending of message, for example when more text was added, increase the paste lock for paste detection when used as signal handler
- ## () - signal handler when @_ is set
- sub magic_enter_cancel {
- if ($MAGIC_ENTER_TIMER) {
- weechat::unhook($MAGIC_ENTER_TIMER);
- $MAGIC_ENTER_TIMER = undef;
- }
- if ($MAGIC_LOCK_TIMER && @_) {
- if (!$MAGIC_LOCK && !$WEECHAT_PASTE_FIX_CTRLJ_CMD &&
- weechat::config_string_to_boolean(weechat::config_get_plugin('weechat_paste_fix'))) {
- ($WEECHAT_PASTE_FIX_CTRLJ_CMD) = get_key_command('ctrl-J');
- $WEECHAT_PASTE_FIX_CTRLJ_CMD = '-' unless defined $WEECHAT_PASTE_FIX_CTRLJ_CMD;
- do_key_bind('ctrl-J', '-');
- }
- if ($MAGIC_LOCK < 1) {
- my $lock_time = get_lock_time();
- ++$MAGIC_LOCK;
- weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
- }
- }
- weechat::WEECHAT_RC_OK
- }
- ## need_magic_enter -- check if magic enter keybinding is needed according to config settings
- ## returns bool
- sub need_magic_enter {
- weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty')) || get_magic_enter_time() ||
- weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only'))
- }
- ## do_key_bind -- mute execute a key binding, or unbind if $_[-1] is '-'
- ## @_ - arguments to /key bind
- sub do_key_bind {
- if ($_[-1] eq '-') {
- pop;
- weechat::command('', "/mute /key unbind @_");
- }
- elsif ($_[-1] eq '!') {
- pop;
- weechat::command('', "/mute /key reset @_");
- }
- else {
- weechat::command('', "/mute /key bind @_");
- }
- }
- { my %keys;
- ## get_key_command -- get the command bound to a key
- ## $_[0] - key in weechat syntax
- ## returns the command
- sub get_key_command {
- unless (exists $keys{$_[0]}) {
- ($keys{$_[0]}) =
- map { $_->{command} } grep { $_->{key} eq $_[0] }
- Nlib::i2h('key')
- }
- $keys{$_[0]}
- }
- }
- ## default_options -- set up default option values on start and when unset
- ## () - config handler if @_ is set
- sub default_options {
- my %defaults = (
- char => '↩',
- tab => '──▶▏',
- magic => '‼',
- ipl => 'on',
- lead_linebreak => 'on',
- modify_keys => 'on',
- send_empty => 'on',
- magic_enter_time => '1000',
- paste_lock => '1',
- magic_paste_only => 'off',
- hide_magic_nl => 'on',
- weechat_paste_fix => 'on',
- );
- unless (weechat::config_is_set_plugin('ipl')) {
- if (my $bar = weechat::bar_search('input')) {
- weechat::bar_set($bar, $_, '0') for 'size', 'size_max';
- }
- }
- for (keys %defaults) {
- weechat::config_set_plugin($_, $defaults{$_})
- unless weechat::config_is_set_plugin($_);
- }
- do_key_bind(KEY_RET, INPUT_NL)
- if weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'));
- my ($enter_key) = get_key_command(KEY_RET);
- if (need_magic_enter()) {
- do_key_bind(KEY_RET, INPUT_MAGIC)
- if $enter_key eq INPUT_NL;
- }
- else {
- do_key_bind(KEY_RET, INPUT_NL)
- if $enter_key eq INPUT_MAGIC;
- }
- weechat::WEECHAT_RC_OK
- }
- sub init_multiline {
- $MAGIC_LOCK = -1;
- default_options();
- my $sf = SCRIPT_FILE;
- for (Nlib::get_settings_from_pod($sf)) {
- weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_));
- }
- weechat::WEECHAT_RC_OK
- }
- sub stop_multiline {
- magic_enter_cancel();
- if (need_magic_enter()) {
- my ($enter_key) = get_key_command(KEY_RET);
- do_key_bind(KEY_RET, INPUT_NL)
- if $enter_key eq INPUT_MAGIC;
- }
- if ($WEECHAT_PASTE_FIX_CTRLJ_CMD) {
- do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD);
- $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
- }
- if (weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'))) {
- do_key_bind(KEY_RET, '!');
- }
- weechat::WEECHAT_RC_OK
- }
|