Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

782 lines
24 KiB

4 years ago
  1. use strict; use warnings;
  2. $INC{'Encode/ConfigLocal.pm'}=1;
  3. require Encode;
  4. use utf8;
  5. # multiline.pl is written by Nei <anti.teamidiot.de>
  6. # and licensed under the under GNU General Public License v3
  7. # or any later version
  8. # to read the following docs, you can use "perldoc multiline.pl"
  9. =head1 NAME
  10. multiline - Multi-line edit box for WeeChat (weechat edition)
  11. =head1 DESCRIPTION
  12. multiline will draw a multi-line edit box to your WeeChat window so
  13. that when you hit the return key, you can first compose a complete
  14. multi-line message before sending it all at once.
  15. Furthermore, if you have multi-line pastes then you can edit them
  16. before sending out all the lines.
  17. =head1 USAGE
  18. make a key binding to send the finished message:
  19. /key bind meta-s /input return
  20. then you can send the multi-line message with Alt+S
  21. =head1 SETTINGS
  22. the settings are usually found in the
  23. plugins.var.perl.multiline
  24. namespace, that is, type
  25. /set plugins.var.perl.multiline.*
  26. to see them and
  27. /set plugins.var.perl.multiline.SETTINGNAME VALUE
  28. to change a setting C<SETTINGNAME> to a new value C<VALUE>. Finally,
  29. /unset plugins.var.perl.multiline.SETTINGNAME
  30. will reset a setting to its default value.
  31. the following settings are available:
  32. =head2 char
  33. character(s) which should be displayed to indicate end of line
  34. =head2 tab
  35. character(s) which should be displayed instead of Tab key character
  36. =head2 lead_linebreak
  37. if turned on, multi-line messages always start on a new line
  38. =head2 modify_keys
  39. if turned on, cursor keys are modified so that they respect line
  40. boundaries instead of treating the whole multi-line message as a
  41. single line
  42. =head2 magic
  43. indicator displayed when message will be sent soon
  44. =head2 magic_enter_time
  45. delay after pressing enter before sending automatically (in ms), or 0
  46. to disable
  47. =head2 magic_paste_only
  48. only use multi-line messages for multi-line pastes (multi-line on
  49. enter is disabled by this)
  50. =head2 paste_lock
  51. time-out to detect pastes (disable the weechat built-in paste
  52. detection if you want to use this)
  53. =head2 send_empty
  54. set to on to automatically disregard enter key on empty line
  55. =head2 hide_magic_nl
  56. whether the new line inserted by magic enter key will be hidden
  57. =head2 weechat_paste_fix
  58. disable ctrl-J binding when paste is detected to stop silly weechat
  59. sending out pastes without allowing to edit them
  60. =head2 ipl
  61. this setting controls override of ctrl-M (enter key) by script. Turn
  62. it off if you don't want multiline.pl to set and re-set the key binding.
  63. =head1 FUNCTION DESCRIPTION
  64. for full pod documentation, filter this script with
  65. perl -pE'
  66. (s/^## (.*?) -- (.*)/=head2 $1\n\n$2\n\n=over\n/ and $o=1) or
  67. s/^## (.*?) - (.*)/=item I<$1>\n\n$2\n/ or
  68. (s/^## (.*)/=back\n\n$1\n\n=cut\n/ and $o=0,1) or
  69. ($o and $o=0,1 and s/^sub /=back\n\n=cut\n\nsub /)'
  70. =cut
  71. use constant SCRIPT_NAME => 'multiline';
  72. our $VERSION = '0.6.3'; # af2e0a17b659a16
  73. weechat::register(SCRIPT_NAME,
  74. 'Nei <anti.teamidiot.de>', # Author
  75. $VERSION,
  76. 'GPL3', # License
  77. 'Multi-line edit box', # Description
  78. 'stop_multiline', '') || return;
  79. sub SCRIPT_FILE() {
  80. my $infolistptr = weechat::infolist_get('perl_script', '', SCRIPT_NAME);
  81. my $filename = weechat::infolist_string($infolistptr, 'filename') if weechat::infolist_next($infolistptr);
  82. weechat::infolist_free($infolistptr);
  83. return $filename unless @_;
  84. }
  85. {
  86. package Nlib;
  87. # this is a weechat perl library
  88. use strict; use warnings; no warnings 'redefine';
  89. ## i2h -- copy weechat infolist content into perl hash
  90. ## $infolist - name of the infolist in weechat
  91. ## $ptr - pointer argument (infolist dependend)
  92. ## @args - arguments to the infolist (list dependend)
  93. ## $fields - string of ref type "fields" if only certain keys are needed (optional)
  94. ## returns perl list with perl hashes for each infolist entry
  95. sub i2h {
  96. my %i2htm = (i => 'integer', s => 'string', p => 'pointer', b => 'buffer', t => 'time');
  97. local *weechat::infolist_buffer = sub { '(not implemented)' };
  98. my ($infolist, $ptr, @args) = @_;
  99. $ptr ||= "";
  100. my $fields = ref $args[-1] eq 'fields' ? ${ pop @args } : undef;
  101. my $infptr = weechat::infolist_get($infolist, $ptr, do { local $" = ','; "@args" });
  102. my @infolist;
  103. while (weechat::infolist_next($infptr)) {
  104. my @fields = map {
  105. my ($t, $v) = split ':', $_, 2;
  106. bless \$v, $i2htm{$t};
  107. }
  108. split ',',
  109. ($fields || weechat::infolist_fields($infptr));
  110. push @infolist, +{ do {
  111. my (%list, %local, @local);
  112. map {
  113. my $fn = 'weechat::infolist_'.ref $_;
  114. my $r = do { no strict 'refs'; &$fn($infptr, $$_) };
  115. if ($$_ =~ /^localvar_name_(\d+)$/) {
  116. $local[$1] = $r;
  117. ()
  118. }
  119. elsif ($$_ =~ /^(localvar)_value_(\d+)$/) {
  120. $local{$local[$2]} = $r;
  121. $1 => \%local
  122. }
  123. elsif ($$_ =~ /(.*?)((?:_\d+)+)$/) {
  124. my ($key, $idx) = ($1, $2);
  125. my @idx = split '_', $idx; shift @idx;
  126. my $target = \$list{$key};
  127. for my $x (@idx) {
  128. my $o = 1;
  129. if ($key eq 'key' or $key eq 'key_command') {
  130. $o = 0;
  131. }
  132. if ($x-$o < 0) {
  133. local $" = '|';
  134. weechat::print('',"list error: $target/$$_/$key/$x/$idx/@idx(@_)");
  135. $o = 0;
  136. }
  137. $target = \$$target->[$x-$o]
  138. }
  139. $$target = $r;
  140. $key => $list{$key}
  141. }
  142. else {
  143. $$_ => $r
  144. }
  145. } @fields
  146. } };
  147. }
  148. weechat::infolist_free($infptr);
  149. !wantarray && @infolist ? \@infolist : @infolist
  150. }
  151. ## hdh -- hdata helper
  152. ## $_[0] - arg pointer or hdata list name
  153. ## $_[1] - hdata name
  154. ## $_[2..$#_] - hdata variable name
  155. ## $_[-1] - hashref with key/value to update (optional)
  156. ## returns value of hdata, and hdata name in list ctx, or number of variables updated
  157. sub hdh {
  158. if (@_ > 1 && $_[0] !~ /^0x/ && $_[0] !~ /^\d+$/) {
  159. my $arg = shift;
  160. unshift @_, weechat::hdata_get_list(weechat::hdata_get($_[0]), $arg);
  161. }
  162. while (@_ > 2) {
  163. my ($arg, $name, $var) = splice @_, 0, 3;
  164. my $hdata = weechat::hdata_get($name);
  165. unless (ref $var eq 'HASH') {
  166. $var =~ s/!(.*)/weechat::hdata_get_string($hdata, $1)/e;
  167. (my $plain_var = $var) =~ s/^\d+\|//;
  168. my $type = weechat::hdata_get_var_type_string($hdata, $plain_var);
  169. if ($type eq 'pointer') {
  170. my $name = weechat::hdata_get_var_hdata($hdata, $var);
  171. unshift @_, $name if $name;
  172. }
  173. my $fn = "weechat::hdata_$type";
  174. unshift @_, do { no strict 'refs';
  175. &$fn($hdata, $arg, $var) };
  176. }
  177. else {
  178. return weechat::hdata_update($hdata, $arg, $var);
  179. }
  180. }
  181. wantarray ? @_ : $_[0]
  182. }
  183. use Pod::Select qw();
  184. use Pod::Simple::TextContent;
  185. ## get_desc_from_pod -- return setting description from pod documentation
  186. ## $file - filename with pod
  187. ## $setting - name of setting
  188. ## returns description as text
  189. sub get_desc_from_pod {
  190. my $file = shift;
  191. return unless -s $file;
  192. my $setting = shift;
  193. open my $pod_sel, '>', \my $ss;
  194. Pod::Select::podselect({
  195. -output => $pod_sel,
  196. -sections => ["SETTINGS/$setting"]}, $file);
  197. my $pt = new Pod::Simple::TextContent;
  198. $pt->output_string(\my $ss_f);
  199. $pt->parse_string_document($ss);
  200. my ($res) = $ss_f =~ /^\s*\Q$setting\E\s+(.*)\s*/;
  201. $res
  202. }
  203. ## get_settings_from_pod -- retrieve all settings in settings section of pod
  204. ## $file - file with pod
  205. ## returns list of all settings
  206. sub get_settings_from_pod {
  207. my $file = shift;
  208. return unless -s $file;
  209. open my $pod_sel, '>', \my $ss;
  210. Pod::Select::podselect({
  211. -output => $pod_sel,
  212. -sections => ["SETTINGS//!.+"]}, $file);
  213. $ss =~ /^=head2\s+(.*)\s*$/mg
  214. }
  215. ## mangle_man_for_wee -- turn man output into weechat codes
  216. ## @_ - list of grotty lines that should be turned into weechat attributes
  217. ## returns modified lines and modifies lines in-place
  218. sub mangle_man_for_wee {
  219. for (@_) {
  220. s/_\x08(.)/weechat::color('underline').$1.weechat::color('-underline')/ge;
  221. s/(.)\x08\1/weechat::color('bold').$1.weechat::color('-bold')/ge;
  222. }
  223. wantarray ? @_ : $_[0]
  224. }
  225. ## read_manpage -- read a man page in weechat window
  226. ## $file - file with pod
  227. ## $name - buffer name
  228. sub read_manpage {
  229. my $caller_package = (caller)[0];
  230. my $file = shift;
  231. my $name = shift;
  232. if (my $obuf = weechat::buffer_search('perl', "man $name")) {
  233. eval qq{
  234. package $caller_package;
  235. weechat::buffer_close(\$obuf);
  236. };
  237. }
  238. my @wee_keys = Nlib::i2h('key');
  239. my @keys;
  240. my $winptr = weechat::current_window();
  241. my ($wininfo) = Nlib::i2h('window', $winptr);
  242. my $buf = weechat::buffer_new("man $name", '', '', '', '');
  243. return weechat::WEECHAT_RC_OK unless $buf;
  244. my $width = $wininfo->{chat_width};
  245. --$width if $wininfo->{chat_width} < $wininfo->{width} || ($wininfo->{width_pct} < 100 && (grep { $_->{y} == $wininfo->{y} } Nlib::i2h('window'))[-1]{x} > $wininfo->{x});
  246. $width -= 2; # when prefix is shown
  247. weechat::buffer_set($buf, 'time_for_each_line', 0);
  248. eval qq{
  249. package $caller_package;
  250. weechat::buffer_set(\$buf, 'display', 'auto');
  251. };
  252. die $@ if $@;
  253. @keys = map { $_->{key} }
  254. grep { $_->{command} eq '/input history_previous' ||
  255. $_->{command} eq '/input history_global_previous' } @wee_keys;
  256. @keys = 'meta2-A' unless @keys;
  257. weechat::buffer_set($buf, "key_bind_$_", '/window scroll -1') for @keys;
  258. @keys = map { $_->{key} }
  259. grep { $_->{command} eq '/input history_next' ||
  260. $_->{command} eq '/input history_global_next' } @wee_keys;
  261. @keys = 'meta2-B' unless @keys;
  262. weechat::buffer_set($buf, "key_bind_$_", '/window scroll +1') for @keys;
  263. weechat::buffer_set($buf, 'key_bind_ ', '/window page_down');
  264. @keys = map { $_->{key} }
  265. grep { $_->{command} eq '/input delete_previous_char' } @wee_keys;
  266. @keys = ('ctrl-?', 'ctrl-H') unless @keys;
  267. weechat::buffer_set($buf, "key_bind_$_", '/window page_up') for @keys;
  268. weechat::buffer_set($buf, 'key_bind_g', '/window scroll_top');
  269. weechat::buffer_set($buf, 'key_bind_G', '/window scroll_bottom');
  270. weechat::buffer_set($buf, 'key_bind_q', '/buffer close');
  271. weechat::print($buf, " \t".mangle_man_for_wee($_)) # weird bug with \t\t showing nothing?
  272. for `pod2man \Q$file\E 2>/dev/null | GROFF_NO_SGR=1 nroff -mandoc -rLL=${width}n -rLT=${width}n -Tutf8 2>/dev/null`;
  273. weechat::command($buf, '/window scroll_top');
  274. unless (hdh($buf, 'buffer', 'lines', 'lines_count') > 0) {
  275. weechat::print($buf, weechat::prefix('error').$_)
  276. for "Unfortunately, your @{[weechat::color('underline')]}nroff".
  277. "@{[weechat::color('-underline')]} command did not produce".
  278. " any output.",
  279. "Working pod2man and nroff commands are required for the ".
  280. "help viewer to work.",
  281. "In the meantime, please use the command ", '',
  282. "\tperldoc $file", '',
  283. "on your shell instead in order to read the manual.",
  284. "Thank you and sorry for the inconvenience."
  285. }
  286. }
  287. 1
  288. }
  289. our $MAGIC_ENTER_TIMER;
  290. our $MAGIC_LOCK;
  291. our $MAGIC_LOCK_TIMER;
  292. our $WEECHAT_PASTE_FIX_CTRLJ_CMD;
  293. our $INPUT_CHANGED_EATER_FLAG;
  294. our $IGNORE_INPUT_CHANGED;
  295. our $IGNORE_INPUT_CHANGED2;
  296. use constant KEY_RET => 'ctrl-M';
  297. use constant INPUT_NL => '/input insert \x0a';
  298. use constant INPUT_MAGIC => '/input magic_enter';
  299. our $NL = "\x0a";
  300. init_multiline();
  301. my $magic_enter_cancel_dynamic = 1;
  302. my $paste_undo_start_ignore_dynamic = 0;
  303. my $input_changed_eater_dynamic = 0;
  304. my $multiline_complete_fix_dynamic = 1;
  305. sub magic_enter_cancel_dynamic { $magic_enter_cancel_dynamic ? &magic_enter_cancel : weechat::WEECHAT_RC_OK }
  306. sub paste_undo_start_ignore_dynamic { $paste_undo_start_ignore_dynamic ? &paste_undo_start_ignore : weechat::WEECHAT_RC_OK }
  307. sub input_changed_eater_dynamic { $input_changed_eater_dynamic ? &input_changed_eater : weechat::WEECHAT_RC_OK }
  308. sub multiline_complete_fix_dynamic { $multiline_complete_fix_dynamic ? &multiline_complete_fix : weechat::WEECHAT_RC_OK }
  309. weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', '');
  310. weechat::hook_modifier('input_text_display_with_cursor', 'multiline_display', '');
  311. weechat::hook_command_run('/help '.SCRIPT_NAME, 'help_cmd', '');
  312. weechat::hook_command_run(INPUT_MAGIC, 'magic_enter', '');
  313. weechat::hook_signal('input_text_*', 'magic_enter_cancel_dynamic', '');
  314. weechat::hook_command_run('/input *', 'paste_undo_start_ignore_dynamic', '');
  315. weechat::hook_signal('2000|input_text_changed', 'input_changed_eater_dynamic', '');
  316. weechat::hook_signal('key_pressed', 'magic_lock_hatch', '');
  317. # we need lower than default priority here or the first character is separated
  318. weechat::hook_signal('500|input_text_changed', 'paste_undo_hack', '')
  319. # can only do this on weechat 0.4.0
  320. if (weechat::info_get('version_number', '') || 0) >= 0x00040000;
  321. weechat::hook_command_run("1500|/input complete*", 'multiline_complete_fix_dynamic', 'complete*');
  322. weechat::hook_command_run("1500|/input delete_*", 'multiline_complete_fix_dynamic', 'delete_*');
  323. weechat::hook_command_run("1500|/input move_*", 'multiline_complete_fix_dynamic', 'move_*');
  324. sub _stack_depth {
  325. my $depth = -1;
  326. 1 while caller(++$depth);
  327. $depth;
  328. }
  329. ## multiline_display -- show multi-lines on display of input string
  330. ## () - modifier handler
  331. ## $_[2] - buffer pointer
  332. ## $_[3] - input string
  333. ## returns modified input string
  334. sub multiline_display {
  335. Encode::_utf8_on($_[3]);
  336. Encode::_utf8_on(my $nl = weechat::config_get_plugin('char') || ' ');
  337. Encode::_utf8_on(my $tab = weechat::config_get_plugin('tab'));
  338. my $cb = weechat::current_buffer() eq $_[2] && $MAGIC_ENTER_TIMER;
  339. if ($cb) {
  340. $_[3] =~ s/$NL\x19b#/\x19b#/ if weechat::config_string_to_boolean(weechat::config_get_plugin('hide_magic_nl'));
  341. }
  342. if ($_[3] =~ s/$NL/$nl\x0d/g) {
  343. $_[3] =~ s/\A/ \x0d/ if weechat::config_string_to_boolean(weechat::config_get_plugin('lead_linebreak'));
  344. }
  345. $_[3] =~ s/\x09/$tab/g if $tab;
  346. if ($cb) {
  347. Encode::_utf8_on(my $magic = weechat::config_get_plugin('magic'));
  348. $_[3] =~ s/\Z/$magic/ if $magic;
  349. }
  350. $_[3]
  351. }
  352. ## lock_timer_exp -- expire the magic lock timer
  353. sub lock_timer_exp {
  354. if ($MAGIC_LOCK_TIMER) {
  355. weechat::unhook($MAGIC_LOCK_TIMER);
  356. $MAGIC_LOCK_TIMER = undef;
  357. }
  358. weechat::WEECHAT_RC_OK
  359. }
  360. ## paste_undo_stop_ignore -- unset ignore2 flag
  361. sub paste_undo_stop_ignore {
  362. $IGNORE_INPUT_CHANGED2 = undef;
  363. weechat::WEECHAT_RC_OK
  364. }
  365. ## paste_undo_start_ignore -- set ignore2 flag when /input is received so to allow /input undo/redo
  366. ## () - command_run handler
  367. ## $_[2] - command that was called
  368. sub paste_undo_start_ignore {
  369. return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
  370. return weechat::WEECHAT_RC_OK if $_[2] =~ /insert/;
  371. $IGNORE_INPUT_CHANGED2 = 1;
  372. weechat::WEECHAT_RC_OK
  373. }
  374. ## paste_undo_hack -- fix up undo stack when paste is detected by calling /input undo
  375. ## () - signal handler
  376. ## $_[2] - buffer pointer
  377. sub paste_undo_hack {
  378. return weechat::WEECHAT_RC_OK if $IGNORE_INPUT_CHANGED;
  379. return paste_undo_stop_ignore() if $IGNORE_INPUT_CHANGED2;
  380. if ($MAGIC_LOCK > 0 && get_lock_enabled()) {
  381. signall_ignore_input_changed(1);
  382. $paste_undo_start_ignore_dynamic = 1;
  383. Encode::_utf8_on(my $input = weechat::buffer_get_string($_[2], 'input'));
  384. my $pos = weechat::buffer_get_integer($_[2], 'input_pos');
  385. weechat::command($_[2], '/input undo') for 1..2;
  386. weechat::buffer_set($_[2], 'input', $input);
  387. weechat::buffer_set($_[2], 'input_pos', $pos);
  388. $paste_undo_start_ignore_dynamic = 0;
  389. signall_ignore_input_changed(0);
  390. }
  391. weechat::WEECHAT_RC_OK
  392. }
  393. ## input_changed_eater -- suppress input_text_changed signal on new weechats
  394. ## () - signal handler
  395. sub input_changed_eater {
  396. $INPUT_CHANGED_EATER_FLAG = undef;
  397. weechat::WEECHAT_RC_OK_EAT
  398. }
  399. ## signall_ignore_input_changed -- use various methods to "ignore" input_text_changed signal
  400. ## $_[0] - start ignore or stop ignore
  401. sub signall_ignore_input_changed {
  402. if ($_[0]) {
  403. weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 1);
  404. $input_changed_eater_dynamic = 1;
  405. $IGNORE_INPUT_CHANGED = 1;
  406. weechat::buffer_set('', 'completion_freeze', '1');
  407. }
  408. else {
  409. weechat::buffer_set('', 'completion_freeze', '0');
  410. $IGNORE_INPUT_CHANGED = undef;
  411. $input_changed_eater_dynamic = 0;
  412. weechat::hook_signal_send('input_flow_free', weechat::WEECHAT_HOOK_SIGNAL_INT, 0);
  413. }
  414. }
  415. ## multiline_complete_fix -- add per line /input handling for completion, movement and deletion
  416. ## () - command_run handler
  417. ## $_[0] - original bound data
  418. ## $_[1] - buffer pointer
  419. ## $_[2] - original command
  420. sub multiline_complete_fix {
  421. $magic_enter_cancel_dynamic = 0;
  422. $multiline_complete_fix_dynamic = 0;
  423. if ($_[2] =~ s/_message$/_line/ || !weechat::config_string_to_boolean(weechat::config_get_plugin('modify_keys'))) {
  424. weechat::command($_[1], $_[2]);
  425. }
  426. else {
  427. signall_ignore_input_changed(1);
  428. Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
  429. my $pos = weechat::buffer_get_integer($_[1], 'input_pos');
  430. if ($pos && $_[2] =~ /(?:previous|beginning_of)_/ && (substr $input, $pos-1, 1) eq $NL) {
  431. substr $input, $pos-1, 1, "\0"
  432. }
  433. elsif ($pos < length $input && $_[2] =~ /(?:next|end_of)_/ && (substr $input, $pos, 1) eq $NL) {
  434. substr $input, $pos, 1, "\0"
  435. }
  436. my @lines = $pos ? (split /$NL/, (substr $input, 0, $pos), -1) : '';
  437. my @after = $pos < length $input ? (split /$NL/, (substr $input, $pos), -1) : '';
  438. $lines[-1] =~ s/\0$/$NL/;
  439. $after[0] =~ s/^\0/$NL/;
  440. my ($p1, $p2) = (pop @lines, shift @after);
  441. weechat::buffer_set($_[1], 'input', $p1.$p2);
  442. weechat::buffer_set($_[1], 'input_pos', length $p1);
  443. $magic_enter_cancel_dynamic = 1;
  444. $INPUT_CHANGED_EATER_FLAG = 1;
  445. weechat::command($_[1], $_[2]);
  446. my $changed_later = !$INPUT_CHANGED_EATER_FLAG;
  447. magic_enter_cancel() if $changed_later;
  448. $magic_enter_cancel_dynamic = 0;
  449. Encode::_utf8_on(my $p = weechat::buffer_get_string($_[1], 'input'));
  450. $pos = weechat::buffer_get_integer($_[1], 'input_pos');
  451. weechat::command($_[1], '/input undo') if @lines || @after;
  452. weechat::command($_[1], '/input undo');
  453. weechat::buffer_set($_[1], 'input', join $NL, @lines, $p, @after);
  454. weechat::buffer_set($_[1], 'input_pos', $pos+length join $NL, @lines, '');
  455. signall_ignore_input_changed(0);
  456. weechat::hook_signal_send('input_text_changed', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $_[1]) if $changed_later;
  457. }
  458. $multiline_complete_fix_dynamic = 1;
  459. $magic_enter_cancel_dynamic = 1;
  460. weechat::WEECHAT_RC_OK_EAT
  461. }
  462. ## help_cmd -- show multi-line script documentation
  463. ## () - command_run handler
  464. sub help_cmd {
  465. Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME);
  466. weechat::WEECHAT_RC_OK_EAT
  467. }
  468. ## get_lock_time -- gets timeout for paste detection according to setting
  469. ## returns timeout (at least 1)
  470. sub get_lock_time {
  471. my $lock_time = weechat::config_get_plugin('paste_lock');
  472. $lock_time = 1 unless $lock_time =~ /^\d+$/ && $lock_time;
  473. $lock_time
  474. }
  475. ## get_lock_enabled -- checks whether the paste detection lock is enabled
  476. ## returns bool
  477. sub get_lock_enabled {
  478. my $lock = weechat::config_get_plugin('paste_lock');
  479. $lock = weechat::config_string_to_boolean($lock)
  480. unless $lock =~ /^\d+$/;
  481. $lock
  482. }
  483. ## magic_lock_hatch -- set a timer for paste detection
  484. ## () - signal handler
  485. sub magic_lock_hatch {
  486. lock_timer_exp();
  487. $MAGIC_LOCK_TIMER = weechat::hook_timer(get_lock_time(), 0, 1, 'lock_timer_exp', '');
  488. weechat::WEECHAT_RC_OK
  489. }
  490. ## magic_unlock -- reduce the lock added by paste detection
  491. ## () - timer handler
  492. sub magic_unlock {
  493. if ($MAGIC_LOCK_TIMER) {
  494. weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
  495. }
  496. else {
  497. --$MAGIC_LOCK;
  498. if (!$MAGIC_LOCK && $WEECHAT_PASTE_FIX_CTRLJ_CMD) {
  499. do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD);
  500. $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
  501. }
  502. }
  503. weechat::WEECHAT_RC_OK
  504. }
  505. ## get_magic_enter_time -- get timeout for auto-sending messages according to config
  506. ## returns timeout
  507. sub get_magic_enter_time {
  508. my $magic_enter = weechat::config_get_plugin('magic_enter_time');
  509. $magic_enter = 1000 * weechat::config_string_to_boolean($magic_enter)
  510. unless $magic_enter =~ /^\d+$/;
  511. $magic_enter
  512. }
  513. ## magic_enter -- receive enter key and do magic things: set up a timer for sending the message, add newline
  514. ## () - command_run handler
  515. ## $_[1] - buffer pointer
  516. sub magic_enter {
  517. Encode::_utf8_on(my $input = weechat::buffer_get_string($_[1], 'input'));
  518. if (!length $input && weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty'))) {
  519. weechat::command($_[1], '/input return');
  520. }
  521. else {
  522. magic_enter_cancel();
  523. weechat::command($_[1], INPUT_NL);
  524. unless (get_lock_enabled() && $MAGIC_LOCK) {
  525. if (weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only')) &&
  526. $input !~ /$NL/) {
  527. magic_enter_send($_[1]);
  528. }
  529. elsif (my $magic_enter = get_magic_enter_time()) {
  530. $MAGIC_ENTER_TIMER = weechat::hook_timer($magic_enter, 0, 1, 'magic_enter_send', $_[1]);
  531. }
  532. }
  533. }
  534. weechat::WEECHAT_RC_OK_EAT
  535. }
  536. ## magic_enter_send -- actually send enter key when triggered by magic_enter, remove preceding newline
  537. ## $_[0] - buffer pointer
  538. ## sending is delayed by 1ms to circumvent crash bug in api
  539. sub magic_enter_send {
  540. magic_enter_cancel();
  541. weechat::command($_[0], '/input delete_previous_char');
  542. weechat::command($_[0], '/wait 1ms /input return');
  543. weechat::WEECHAT_RC_OK
  544. }
  545. ## 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
  546. ## () - signal handler when @_ is set
  547. sub magic_enter_cancel {
  548. if ($MAGIC_ENTER_TIMER) {
  549. weechat::unhook($MAGIC_ENTER_TIMER);
  550. $MAGIC_ENTER_TIMER = undef;
  551. }
  552. if ($MAGIC_LOCK_TIMER && @_) {
  553. if (!$MAGIC_LOCK && !$WEECHAT_PASTE_FIX_CTRLJ_CMD &&
  554. weechat::config_string_to_boolean(weechat::config_get_plugin('weechat_paste_fix'))) {
  555. ($WEECHAT_PASTE_FIX_CTRLJ_CMD) = get_key_command('ctrl-J');
  556. $WEECHAT_PASTE_FIX_CTRLJ_CMD = '-' unless defined $WEECHAT_PASTE_FIX_CTRLJ_CMD;
  557. do_key_bind('ctrl-J', '-');
  558. }
  559. if ($MAGIC_LOCK < 1) {
  560. my $lock_time = get_lock_time();
  561. ++$MAGIC_LOCK;
  562. weechat::hook_timer(get_lock_time(), 0, 1, 'magic_unlock', '');
  563. }
  564. }
  565. weechat::WEECHAT_RC_OK
  566. }
  567. ## need_magic_enter -- check if magic enter keybinding is needed according to config settings
  568. ## returns bool
  569. sub need_magic_enter {
  570. weechat::config_string_to_boolean(weechat::config_get_plugin('send_empty')) || get_magic_enter_time() ||
  571. weechat::config_string_to_boolean(weechat::config_get_plugin('magic_paste_only'))
  572. }
  573. ## do_key_bind -- mute execute a key binding, or unbind if $_[-1] is '-'
  574. ## @_ - arguments to /key bind
  575. sub do_key_bind {
  576. if ($_[-1] eq '-') {
  577. pop;
  578. weechat::command('', "/mute /key unbind @_");
  579. }
  580. elsif ($_[-1] eq '!') {
  581. pop;
  582. weechat::command('', "/mute /key reset @_");
  583. }
  584. else {
  585. weechat::command('', "/mute /key bind @_");
  586. }
  587. }
  588. { my %keys;
  589. ## get_key_command -- get the command bound to a key
  590. ## $_[0] - key in weechat syntax
  591. ## returns the command
  592. sub get_key_command {
  593. unless (exists $keys{$_[0]}) {
  594. ($keys{$_[0]}) =
  595. map { $_->{command} } grep { $_->{key} eq $_[0] }
  596. Nlib::i2h('key')
  597. }
  598. $keys{$_[0]}
  599. }
  600. }
  601. ## default_options -- set up default option values on start and when unset
  602. ## () - config handler if @_ is set
  603. sub default_options {
  604. my %defaults = (
  605. char => '↩',
  606. tab => '──▶▏',
  607. magic => '‼',
  608. ipl => 'on',
  609. lead_linebreak => 'on',
  610. modify_keys => 'on',
  611. send_empty => 'on',
  612. magic_enter_time => '1000',
  613. paste_lock => '1',
  614. magic_paste_only => 'off',
  615. hide_magic_nl => 'on',
  616. weechat_paste_fix => 'on',
  617. );
  618. unless (weechat::config_is_set_plugin('ipl')) {
  619. if (my $bar = weechat::bar_search('input')) {
  620. weechat::bar_set($bar, $_, '0') for 'size', 'size_max';
  621. }
  622. }
  623. for (keys %defaults) {
  624. weechat::config_set_plugin($_, $defaults{$_})
  625. unless weechat::config_is_set_plugin($_);
  626. }
  627. do_key_bind(KEY_RET, INPUT_NL)
  628. if weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'));
  629. my ($enter_key) = get_key_command(KEY_RET);
  630. if (need_magic_enter()) {
  631. do_key_bind(KEY_RET, INPUT_MAGIC)
  632. if $enter_key eq INPUT_NL;
  633. }
  634. else {
  635. do_key_bind(KEY_RET, INPUT_NL)
  636. if $enter_key eq INPUT_MAGIC;
  637. }
  638. weechat::WEECHAT_RC_OK
  639. }
  640. sub init_multiline {
  641. $MAGIC_LOCK = -1;
  642. default_options();
  643. my $sf = SCRIPT_FILE;
  644. for (Nlib::get_settings_from_pod($sf)) {
  645. weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_));
  646. }
  647. weechat::WEECHAT_RC_OK
  648. }
  649. sub stop_multiline {
  650. magic_enter_cancel();
  651. if (need_magic_enter()) {
  652. my ($enter_key) = get_key_command(KEY_RET);
  653. do_key_bind(KEY_RET, INPUT_NL)
  654. if $enter_key eq INPUT_MAGIC;
  655. }
  656. if ($WEECHAT_PASTE_FIX_CTRLJ_CMD) {
  657. do_key_bind('ctrl-J', $WEECHAT_PASTE_FIX_CTRLJ_CMD);
  658. $WEECHAT_PASTE_FIX_CTRLJ_CMD = undef;
  659. }
  660. if (weechat::config_string_to_boolean(weechat::config_get_plugin('ipl'))) {
  661. do_key_bind(KEY_RET, '!');
  662. }
  663. weechat::WEECHAT_RC_OK
  664. }