aboutsummaryrefslogtreecommitdiff
path: root/weechat/perl/coords.pl
diff options
context:
space:
mode:
Diffstat (limited to 'weechat/perl/coords.pl')
-rw-r--r--weechat/perl/coords.pl3223
1 files changed, 3223 insertions, 0 deletions
diff --git a/weechat/perl/coords.pl b/weechat/perl/coords.pl
new file mode 100644
index 0000000..43104af
--- /dev/null
+++ b/weechat/perl/coords.pl
@@ -0,0 +1,3223 @@
+use strict; use warnings;
+$INC{'Encode/ConfigLocal.pm'}=1;
+require Encode;
+
+# coords.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 coords.pl"
+
+=head1 NAME
+
+coords - weechat script to map screen coordinates (weechat edition)
+
+=head1 SYNOPSIS
+
+first, copy the file to your
+F<.weechat/perl> directory. Then you can type
+
+ /script load coords.pl
+
+in weechat to load the script. Use
+
+ /coords
+
+to open the coords screen, or conveniently
+
+ /key bind meta-/ /coords /
+
+to open it with the Alt+/ keybinding.
+
+=head1 DESCRIPTION
+
+coords will hilight links, allow text selection and tries to send the
+selection to xterm
+
+=head1 SETUP
+
+if you would like urls to be copied into the selection clipboard, add
+
+ xterm*disallowedWindowOps:20,21,SetXprop
+
+to your F<.Xresources> file for B<xterm>.
+
+For B<rxvt-unicode>, get the F<osc-xterm-clipboard> script from
+L<http://anti.teamidiot.de/static/nei/*/Code/urxvt/>, install it to
+your F<perl-lib> directory and add it to your F<.Xresources> like such:
+
+ URxvt.perl-ext-common: default,osc-xterm-clipboard
+
+=head1 USAGE
+
+to open the url overlay on a window, type C</coords> or use the
+keybinding you created as explained in the L</SYNOPSIS>.
+
+=head2 Selection Mode
+
+by default, the copy window will be in selection mode. you can move
+the text cursor with the arrow keys and open a selection with the
+Space key. The selection content will be transfered into clipboard.
+
+=head2 URL Mode
+
+to switch between selection mode and URL mode, use the C</> key or
+type the command C</coords /> to directly start in URL mode.
+
+inside the overlay, you can use Arrow-Up and Arrow-Down keys to select
+URLs. This script will try to copy them into your selection clipboard
+(see L</SETUP>) so you should be able to open the selected link by
+clicking the middle mouse button in your browser.
+
+once you click or hit enter, an url open signal will be sent to
+WeeChat. Use an appropriate script such as F<urlopener.pl> from
+L<http://anti.teamidiot.de/static/nei/*/Code/WeeChat/> if you would
+like to associate this with some application (web browser etc.)
+
+to leave the overlay, hit the C<q> key.
+
+for mouse support, this script will listen to mouse input
+signals. Another script is needed to supply these signals, such as
+F<mouse.pl> which can be found in the same place as F<urlopener.pl>
+and this script.
+
+=head1 CAVEATS
+
+=over
+
+=item *
+
+WeeChat does not allow for visual feedback during mouse operation
+unless you patch it and use the F<mouse.pl> or F<mouse_var.pl> script
+instead.
+
+=item *
+
+double-click word select does not work in an unpatched WeeChat for the
+same reason -- unless you set a very high close_on_release timeout (with
+the added inconvenience).
+
+=item *
+
+unfortunately, WeeChat scrolls back a buffer to the end when you
+switch to any other buffer, including the copy window
+overlay. F<coords.pl> will work together with the F<keepscroll.pl>
+script to try and remedy this a little bit, but the perfect scrolling
+position cannot be restored due to internal limitations. (no longer
+the case in recent versions of WeeChat.)
+
+=item *
+
+whether other terminal emulators support selection storage, depends on
+how well they emulate B<xterm>. Set C<xterm_compatible> to your
+I<$TERM> if you think it does.
+
+=item *
+
+B<xterm> will not allow selection storage unless you enable it in the
+Xresources as described in L</SETUP>
+
+=item *
+
+B<rxvt-unicode> will need a script to handle the selection operating
+system control. querying the selection is only supported if the
+selection is rxvt-unicodeE<apos>s (not needed for this script)
+
+=item *
+
+GNU B<screen> will not allow to pass through the operating system
+control needed for selection storage to the underlying term, try
+B<tmux> instead (L<http://tmux.sourceforge.net/>)
+
+=item *
+
+it would be possible to provide a remote clipboard, but that will
+require a clipboard server. Also see B<xsel>, B<xclip>, command mode
+
+=back
+
+=head1 TODO
+
+=over
+
+=item *
+
+set the text input cursor on click in the input bar
+
+=back
+
+=head1 BUGS
+
+=over
+
+=item *
+
+my local B<xterm> exhibits a bug where the selection is only copied if
+you I<click> into the xterm window first. still trying to figure this
+one out...
+
+=item *
+
+missing handling of scroll_beyond_end feature in weechat
+
+=item *
+
+broken handling of Day Changed messages in newer weechat
+
+=item *
+
+possibly more...
+
+=back
+
+=head1 SETTINGS
+
+the settings are usually found in the
+
+ plugins.var.perl.coords
+
+namespace, that is, type
+
+ /set plugins.var.perl.coords.*
+
+to see them and
+
+ /set plugins.var.perl.coords.SETTINGNAME VALUE
+
+to change a setting C<SETTINGNAME> to a new value C<VALUE>. Finally,
+
+ /unset plugins.var.perl.coords.SETTINGNAME
+
+will reset a setting to its default value.
+
+the following settings are available:
+
+=head2 url_regex
+
+a regular expression to identify URLs in the text. See L<perlre> for
+more information about Perl regular expressions.
+
+=head2 url_braces
+
+parenthesis-like characters which nest and should be excluded when
+found around an URL. make sure the variable setting nests properly
+when modifying this.
+
+=head2 url_non_endings
+
+this is matched against the end of a link and removed
+
+=head2 hyper_nicks
+
+make nicks to hyperlinks for menu/pm
+
+=head2 hyper_channels
+
+make channels to hyperlinks for join
+
+=head2 hyper_show
+
+set to types of hyperlinks that are shown by default
+
+=head2 use_nick_menu
+
+use nick menu when opening nick hyperlink (see I<hyper_nicks>,
+requires menu.pl script). otherwise open private message. this setting
+only applies to text mode selection, for mouse see
+I<mouse.nick_2nd_click>
+
+=head2 color.url_highlight
+
+the weechat color and/or attribute to be used for highlighting URLs in
+the copy window. seperate multiple attributes with C<.>
+
+=head2 color.url_highlight_active
+
+the same as I<color.url_highlight> except for the currently (using
+arrow keys) selected link.
+
+=head2 color.selection_cursor
+
+the weechat color and/or attribute to be used for the text cursor.
+
+=head2 color.selection
+
+the color of the currently selected text in selection mode
+
+=head2 copybuf_short_name
+
+short_name to use for coords buffer. it is set to the copy sign by
+default to not disturb buffers bar width, set to the empty string to
+have window position and size shown
+
+=head2 mouse.copy_on_click
+
+set to on if it should be possible to directly click on URLs and
+select text, set to off if mouse should only work in open coords
+buffer
+
+=head2 mouse.close_on_release
+
+set to on or a delay (in ms) to autoclose coords buffer opened by
+I<copy_on_click> on button release, set to off if the coords buffer
+should stay open after click
+
+=head2 mouse.click_select_pane
+
+set to on to use the mouse to select windows
+
+=head2 mouse.click_through_pane
+
+set to on if I<copy_on_click> should work on inactive windows (works
+only if I<click_select_pane> is set too). set to off if window needs
+to be active
+
+=head2 mouse.url_open_2nd_click
+
+if this is set, URLs are only opened when clicked twice (in the same
+incarnation of a coords buffer) instead of on first click. it can be set to
+a delay (in ms) that will be added to the I<close_on_release> delay if
+the script is waiting for a second click on the URL to happen
+
+=head2 mouse.handle_scroll
+
+set to on if coords should handle scrolling inside windows. the script
+will try to guess non-chat areas to be nicklist, top to be title and
+bottom to be status and scroll the respective bars if the cursor is in
+that area. set to off if scrolling should be handled by the default
+F<mouse.pl> script or another mouse scrolling script
+
+=head2 mouse.scroll_inactive_pane
+
+set to on if inactive windows should be scrolled instead of active
+window if the mouse cursor is over it (requires I<handle_scroll> to be
+enabled)
+
+=head2 clipboard_command
+
+if you set this, an external program may be executed to store the
+selection or URL. begin with C<|> to pipe into program or use
+parameters C<%s> for text, C<%q> for quoted text or C<%x> for quoted
+escape sequence.
+
+=head2 copywin_custom_keys
+
+You can define custom key bindings to use inside the copywin here. syntax is:
+command-letter:weechat-keycode. available commands: -+>< (up/down/left/right)
+fbae (forward word/backward word/beginning/end) !@ (open/start selection)
+/UNCunc (toggle highlights/urls/nicks/channels) q (close window)
+
+=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 MIME::Base64;
+
+use constant SCRIPT_NAME => 'coords';
+weechat::register(SCRIPT_NAME, 'Nei <anti.teamidiot.de>', '0.7.3.1', 'GPL3', 'copy text and urls', 'stop_coords', '') || 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;
+
+ my $code = qq{
+ local \$[=1;
+ \$list{"\Q$key\E"}$idx = \$r
+ };
+ $key => $list{$key}
+ }
+ else {
+ $$_ => $r
+ }
+ } @fields
+ } };
+ }
+ weechat::infolist_free($infptr);
+ !wantarray && @infolist ? \@infolist : @infolist
+}
+
+## hdh -- hdata helper
+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);
+
+ $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;
+ }
+ if ($type eq 'shared_string') {
+ $type =~ s/shared_//;
+ }
+
+ my $fn = "weechat::hdata_$type";
+ unshift @_, do { no strict 'refs';
+ &$fn($hdata, $arg, $var) };
+ }
+ wantarray ? @_ : $_[0]
+}
+
+## l2l -- copy weechat list into perl list
+## $ptr - weechat list pointer
+## $clear - if true, clear weechat list
+## returns perl list
+sub l2l {
+ my ($ptr, $clear) = @_;
+ my $itemptr = weechat::list_get($ptr, 0);
+ my @list;
+ while ($itemptr) {
+ push @list, weechat::list_string($itemptr);
+ $itemptr = weechat::list_next($itemptr);
+ }
+ weechat::list_remove_all($ptr) if $clear;
+ @list
+}
+
+## find_bar_window -- find the bar window where the coordinates belong to
+## $row - row
+## $col - column
+## returns bar window infolist and bar infolist in a array ref if found
+sub find_bar_window {
+ my ($row, $col) = @_;
+
+ my $barwinptr;
+ my $bar_info;
+ for (i2h('bar_window')) {
+ return [ $_, $bar_info ] if
+ $row > $_->{'y'} && $row <= $_->{'y'}+$_->{'height'} &&
+ $col > $_->{'x'} && $col <= $_->{'x'}+$_->{'width'} &&
+ (($bar_info)=i2h('bar', $_->{'bar'})) && !$bar_info->{'hidden'};
+ }
+
+}
+
+## in_window -- check if given coordinates are in a window
+## $row - row
+## $col - column
+## $wininfo - infolist of window to check
+## returns true if in window
+sub in_window {
+ my ($row, $col, $wininfo) = @_;
+
+ # in window?
+ $row > $wininfo->{'y'} &&
+ $row <= $wininfo->{'y'}+$wininfo->{'height'} &&
+ $col > $wininfo->{'x'} &&
+ $col <= $wininfo->{'x'}+$wininfo->{'width'}
+}
+
+## in_chat_window -- check if given coordinates are in the chat part of a window
+## $row - row
+## $col - column
+## $wininfo - infolist of window to check
+## returns true if in chat part of window
+sub in_chat_window {
+ my ($row, $col, $wininfo) = @_;
+
+ # in chat window?
+ $row > $wininfo->{'chat_y'} &&
+ $row <= $wininfo->{'chat_y'}+$wininfo->{'chat_height'} &&
+ $col > $wininfo->{'chat_x'} &&
+ $col <= $wininfo->{'chat_x'}+$wininfo->{'chat_width'}
+}
+
+## has_true_value -- some constants for "true"
+## $v - value string
+## returns true if string looks like a true thing
+sub has_true_value {
+ my $v = shift || '';
+ $v =~ /^(?:on|yes|y|true|t|1)$/i
+}
+
+## has_false_value -- some constants for "false"
+## $v - value string
+## returns true if string looks like a B<false> thing
+sub has_false_value {
+ my $v = shift || '';
+ $v =~ /^(?:off|no|n|false|f|0)?$/i
+}
+
+## bar_filling -- get current filling according to position
+## $bar_infos - info about bar (from find_bar_window)
+## returns filling as an integer number
+sub bar_filling {
+ my ($bar_infos) = @_;
+ ($bar_infos->[-1]{'position'} <= 1 ? $bar_infos->[-1]{'filling_top_bottom'}
+ : $bar_infos->[-1]{'filling_left_right'})
+}
+
+sub fu8on(@) {
+ Encode::_utf8_on($_) for @_; wantarray ? @_ : shift
+}
+
+sub screen_length($) {
+ weechat::strlen_screen($_[0])
+}
+
+## bar_column_max_length -- get max item length for column based filling
+## $bar_infos - info about bar (from find_bar_window)
+## returns max item length
+sub bar_column_max_length {
+ my ($bar_infos) = @_;
+ my @items;
+ for (@{ $bar_infos->[0]{'items_content'} }) {
+ push @items, split "\n", join "\n", @$_;
+ }
+ my $max_length = 0;
+ for (@items) {
+ my $item_length = screen_length fu8on weechat::string_remove_color($_, '');
+ $max_length = $item_length if $max_length < $item_length;
+ }
+ $max_length;
+}
+
+## find_bar_item_pos -- get position of an item in a bar structure
+## $bar_infos - instance and general info about bar (from find_bar_window)
+## $search - search pattern for item name
+## returns (outer position, inner position, true if found)
+sub find_bar_item_pos {
+ my ($bar_infos, $search) = @_;
+ my $item_pos_a = 0;
+ my $item_pos_b;
+ for (@{ $bar_infos->[-1]{'items_array'} }) {
+ $item_pos_b = 0;
+ for (@$_) {
+ return ($item_pos_a, $item_pos_b, 1)
+ if $_ =~ $search;
+ ++$item_pos_b;
+ }
+ ++$item_pos_a;
+ }
+ (undef, undef, undef)
+}
+
+## bar_line_wrap_horiz -- apply linebreak for horizontal bar filling
+## $prefix_col_r - reference to column counter
+## $prefix_y_r - reference to row counter
+## $bar_infos - info about bar (from find_bar_window)
+sub bar_line_wrap_horiz {
+ my ($prefix_col_r, $prefix_y_r, $bar_infos) = @_;
+ while ($$prefix_col_r > $bar_infos->[0]{'width'}) {
+ ++$$prefix_y_r;
+ $$prefix_col_r -= $bar_infos->[0]{'width'};
+ }
+}
+
+## bar_lines_column_vert -- count lines in column layout
+## $bar_infos - info about bar (from find_bar_window)
+## returns lines needed for columns_horizontal layout
+sub bar_lines_column_vert {
+ my ($bar_infos) = @_;
+ my @items;
+ for (@{ $bar_infos->[0]{'items_content'} }) {
+ push @items, split "\n", join "\n", @$_;
+ }
+ my $max_length = bar_column_max_length($bar_infos);
+ my $dummy_col = 1;
+ my $lines = 1;
+ for (@items) {
+ if ($dummy_col+$max_length > 1+$bar_infos->[0]{'width'}) {
+ ++$lines;
+ $dummy_col = 1;
+ }
+ $dummy_col += 1+$max_length;
+ }
+ $lines;
+}
+
+## bar_items_skip_to -- skip several bar items on search for subitem position
+## $bar_infos - info about bar (from find_bar_window)
+## $search - patter of item to skip to
+## $col - pointer column
+## $row - pointer row
+sub bar_items_skip_to {
+ my ($bar_infos, $search, $col, $row) = @_;
+ $col += $bar_infos->[0]{'scroll_x'};
+ $row += $bar_infos->[0]{'scroll_y'};
+ my ($item_pos_a, $item_pos_b, $found) =
+ find_bar_item_pos($bar_infos, $search);
+
+ return 'item position not found' unless $found;
+
+ # extract items to skip
+ my $item_join =
+ (bar_filling($bar_infos) <= 1 ? '' : "\n");
+ my @prefix;
+ for (my $i = 0; $i < $item_pos_a; ++$i) {
+ push @prefix, split "\n", join $item_join, @{ $bar_infos->[0]{'items_content'}[$i] };
+ }
+ push @prefix, split "\n", join $item_join, @{ $bar_infos->[0]{'items_content'}[$item_pos_a] }[0..$item_pos_b-1] if $item_pos_b;
+
+ # cursor
+ my $prefix_col = 1;
+ my $prefix_y = 1;
+ my $item_max_length;
+ my $col_vert_lines;
+
+ # forward cursor
+ if (!bar_filling($bar_infos)) {
+ my $prefix = join ' ', @prefix;
+ $prefix_col += screen_length fu8on weechat::string_remove_color($prefix, '');
+ ++$prefix_col if @prefix && !$item_pos_b;
+ bar_line_wrap_horiz(\($prefix_col, $prefix_y), $bar_infos);
+ }
+ elsif (bar_filling($bar_infos) == 1) {
+ $prefix_y += @prefix;
+ if ($item_pos_b) {
+ --$prefix_y;
+ $prefix_col += screen_length fu8on weechat::string_remove_color($prefix[-1], '');
+ }
+ }
+ elsif (bar_filling($bar_infos) == 2) {
+ $item_max_length = bar_column_max_length($bar_infos);
+ for (@prefix) {
+ $prefix_col += 1+$item_max_length;
+ if ($prefix_col+$item_max_length > 1+$bar_infos->[0]{'width'}) {
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ }
+ }
+ elsif (bar_filling($bar_infos) == 3) {
+ $item_max_length = bar_column_max_length($bar_infos);
+ $col_vert_lines = $bar_infos->[-1]{'position'} <= 1 ? bar_lines_column_vert($bar_infos) : $bar_infos->[0]{'height'};
+ my $pfx_idx = 0;
+ for (@prefix) {
+ $prefix_y = 1+($pfx_idx % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*(int($pfx_idx / $col_vert_lines)+1);
+ return 'in prefix'
+ if ($prefix_y == $row && $prefix_col > $col);
+ ++$pfx_idx;
+ }
+ $prefix_y = 1+(@prefix % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*int(@prefix / $col_vert_lines);
+ }
+
+ (undef,
+ $item_pos_a, $item_pos_b,
+ $prefix_col, $prefix_y,
+ (scalar @prefix),
+ $item_max_length, $col_vert_lines)
+}
+
+## bar_item_get_subitem_at -- extract subitem from a bar item at given coords
+## $bar_infos - info about bar
+## $search - search pattern for item whose subitems to get
+## $col - pointer column
+## $row - pointer row
+## returns error message, subitem index, subitem text
+sub bar_item_get_subitem_at {
+ my ($bar_infos, $search, $col, $row) = @_;
+
+ my ($error,
+ $item_pos_a, $item_pos_b,
+ $prefix_col, $prefix_y,
+ $prefix_cnt,
+ $item_max_length, $col_vert_lines) =
+ bar_items_skip_to($bar_infos, $search, $col, $row);
+
+ $col += $bar_infos->[0]{'scroll_x'};
+ $row += $bar_infos->[0]{'scroll_y'};
+
+ return $error if $error;
+
+ return 'no viable position'
+ unless (($row == $prefix_y && $col >= $prefix_col) || $row > $prefix_y || bar_filling($bar_infos) >= 3);
+
+ my @subitems = split "\n", $bar_infos->[0]{'items_content'}[$item_pos_a][$item_pos_b];
+ my $idx = 0;
+ for (@subitems) {
+ my ($beg_col, $beg_y) = ($prefix_col, $prefix_y);
+ $prefix_col += screen_length fu8on weechat::string_remove_color($_, '');
+ if (!bar_filling($bar_infos)) {
+ bar_line_wrap_horiz(\($prefix_col, $prefix_y), $bar_infos);
+ }
+
+ return (undef, $idx, $_, [$beg_col, $col, $prefix_col, $beg_y, $row, $prefix_y])
+ if (($prefix_col > $col && $row == $prefix_y) || ($row < $prefix_y && bar_filling($bar_infos) < 3));
+
+ ++$idx;
+
+ if (!bar_filling($bar_infos)) {
+ ++$prefix_col;
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+ }
+ elsif (bar_filling($bar_infos) == 1) {
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $col >= $prefix_col);
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ elsif (bar_filling($bar_infos) == 2) {
+ $prefix_col += 1+$item_max_length-(($prefix_col-1)%($item_max_length+1));
+
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+
+ if ($prefix_col+$item_max_length > 1+$bar_infos->[0]{'width'}) {
+ return ('outside item', $idx-1, $_)
+ if ($prefix_y == $row && $col >= $prefix_col);
+
+ ++$prefix_y;
+ $prefix_col = 1;
+ }
+ }
+ elsif (bar_filling($bar_infos) == 3) {
+ $prefix_col += 1+$item_max_length-(($prefix_col-1)%($item_max_length+1));
+ return ('outside', $idx-1, $_)
+ if ($prefix_y == $row && $prefix_col > $col);
+ $prefix_y = 1+(($idx+$prefix_cnt) % $col_vert_lines);
+ $prefix_col = 1+(1+$item_max_length)*int(($idx+$prefix_cnt) / $col_vert_lines);
+
+ }
+ }
+ 'not found';
+}
+
+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
+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'});
+
+ 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($_))
+ 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
+}
+
+use constant CMD_COPYWIN => SCRIPT_NAME;
+weechat::hook_command(CMD_COPYWIN, 'copy active window for hyperlink operations or free selection of text',
+ '[/] || url nicks channels',
+ 'if any or more arguments are given, go into hyperlink mode and set this filter.'."\n".
+ 'in the title bar of opened copy window, the possible key bindings are displayed.'."\n".
+ 'use '.weechat::color('bold').'/'.CMD_COPYWIN.' help'.weechat::color('-bold').
+ ' to read the manual',
+ '|| / %- || url %- || nicks %- || channels %- || url,nicks %- || url,channels %- '.
+ '|| url,nicks,channels %- || nicks,channels %- || help',
+ 'copywin_cmd', '');
+weechat::hook_signal('buffer_closed', 'garbage_str', '');
+weechat::hook_signal('upgrade', 'close_copywin', '');
+weechat::hook_config('plugins.var.perl.'.SCRIPT_NAME.'.*', 'default_options', '');
+weechat::hook_signal('mouse', 'mouse_evt', '');
+weechat::hook_signal('input_flow_free', 'binding_mouse_fix', '');
+weechat::hook_modifier('input_text_display_with_cursor', 'input_text_hlsel', '');
+# is there builtin mouse support?
+weechat::hook_hsignal(SCRIPT_NAME, 'hsignal_evt', '');
+weechat::key_bind('mouse', +{
+ map { $_ => 'hsignal:'.SCRIPT_NAME }
+ '@chat:button1*', '@chat:button1-event-*', '@chat(perl.[*):button1'
+});
+weechat::command('', '/alias copywin '.CMD_COPYWIN)
+ if 'copywin' ne CMD_COPYWIN && !Nlib::i2h('alias', '', 'copywin') && Nlib::i2h('hook', '', 'command,alias');
+
+# downloaded line fields
+use constant TRACE => -1;
+use constant LINE => 0;
+use constant LAYOUT => 1;
+
+# URLS structure fields
+use constant MSG => 0;
+use constant URL_S => 1;
+use constant URL => 2;
+use constant URL_E => 3;
+use constant URL_LINE => 4;
+use constant URL_INFO => 5;
+
+# captured control codes fields
+use constant RES => 0;
+use constant POS_S => 1;
+use constant POS_E => 2;
+
+# trace weelist container
+our $listptr;
+
+# global storage & fields
+our %STR;
+use constant BUFPTR => 0;
+use constant LINES => 1;
+use constant WINDOWS => 2;
+use constant A_LINK => 3;
+use constant URLS => 4;
+use constant MODE => 5;
+use constant CUR => 6;
+use constant MOUSE_AUTOMODE => 7;
+use constant URL_TYPE_FILTER => 8;
+# currently active storage key
+our $ACT_STR;
+
+our $last_mouse_seq;
+our $script_old_mouse_scroll;
+our $mouse_2nd_click;
+our $autoclose_in_progress;
+our $drag_speed_timer;
+our $delayed_nick_menu_timer;
+
+our $hsignal_mouse_down_sent;
+
+our $input_sel;
+
+our $LAYOUT_OK;
+
+my @SIMPLE_MODE_KEYS = ('/', 'U', 'N', 'C', 'P', 'u', 'n', 'c', 'p');
+
+init_coords();
+
+## hdh_get_lineinfo -- get lineinfo from hdata
+## $_[0] - line pointer
+## $_[1] - 'line' (automatic when using result from Nlib::hdh)
+## returns lineinfo hashref
+sub hdh_get_lineinfo {
+ my @line_data = Nlib::hdh(@_, 'data');
+ +{
+ next_line => scalar Nlib::hdh(@_, 'next_line'),
+ (map { $_ => scalar Nlib::hdh(@line_data, $_) }
+ qw(displayed message prefix str_time buffer date highlight)),
+ tag => [ map { Nlib::hdh(@line_data, "$_|tags_array") }
+ 0 .. Nlib::hdh(@line_data, 'tags_count')-1 ]
+ }
+}
+
+sub screen_length($) {
+ weechat::strlen_screen($_[0])
+}
+
+sub fu8on(@) {
+ Encode::_utf8_on($_) for @_; wantarray ? @_ : shift
+}
+
+## calculate_trace -- create a trace profile witgh word splits
+## $lineinfo - lineinfo with message to break into lines
+## $wininfo - window info hash with chat_width
+## $base_trace - ref to base trace with # fields
+## $prev_lineinfo - lineinfo of previous message for prefix erasure
+## returns (layoutinfo hashref, partial trace arrayref)
+sub calculate_trace {
+ # b : pos // break between words
+ # n : pos // new line
+ # x : pos // cut word
+ # t : x # j // time
+ # u : x # j // buffer name
+ # p : x # j // prefix (nick etc.)
+ # q : x # j // separator
+ # q : x . line
+ my ($lineinfo, $wininfo, $base_trace, $prev_lineinfo) = @_;
+ my $msg = fu8on $lineinfo->{'message'};
+ my $layoutinfo = +{ count => 1 };
+ my @trace;
+ my $show_time = $base_trace->[1];
+ my $no_prefix_align;
+ if ($base_trace->[-1] < 0) {
+ $no_prefix_align = $base_trace->[5];
+ $base_trace->[5] = $base_trace->[-1] = 0;
+ my $prefix = $lineinfo->{'prefix'};
+ if (defined $prefix) {
+
+ my ($nick_tag) = grep { /^nick_/ } @{$lineinfo->{'tag'}||[]};
+ my ($pnick_tag) = grep { /^nick_/ } @{$prev_lineinfo->{'tag'}||[]};
+ if ($nick_tag && $pnick_tag && $nick_tag eq $pnick_tag && !$lineinfo->{'highlight'} &&
+ (grep { /_action$/ && !/^nick_/ } @{$lineinfo->{'tag'}||[]}) == 0 &&
+ $prev_lineinfo && $lineinfo->{'prefix'} eq $prev_lineinfo->{'prefix'}) {
+ my $repl = weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_same_nick'));
+ $prefix = $repl if length $repl;
+ $prefix = '' if $repl eq ' ';
+ }
+ $base_trace->[5] = (sort { $a <=> $b } #($no_prefix_align?$no_prefix_align+1:0),
+ screen_length fu8on weechat::string_remove_color($prefix, ''))[0];
+ }
+ }
+ $base_trace->[1] = $show_time ? screen_length fu8on weechat::string_remove_color($lineinfo->{'str_time'}, '') : 0;
+ for (my ($i, $pos) = (0, 0); $i < @$base_trace; $i+=2) {
+ $pos += $base_trace->[$i+1] + 1 if $base_trace->[$i+1];
+ push @trace, $base_trace->[$i] . ':' . $pos . '#' if $base_trace->[$i+1];
+ }
+ my ($ctrl, $plain_msg) = capture_color_codes($msg);
+ my @words = split /(\s+)/, $plain_msg;
+ my $screen = 0;
+ if ($base_trace->[-1]) {
+ ($screen) = ((trace_cond('q', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[5]) {
+ ($screen) = ((trace_cond('p', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[3]) {
+ ($screen) = ((trace_cond('u', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ elsif ($base_trace->[1]) {
+ ($screen) = ((trace_cond('t', [\@trace], 0))[0] =~ /:(\d+)/);
+ }
+ my $new_screen = 0;
+ my $eol_pos = weechat::config_string(weechat::config_get('weechat.look.align_end_of_lines'));
+ unless ($eol_pos eq 'time') {
+ ($new_screen) = ((trace_cond('t', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[1];
+ unless ($eol_pos eq 'buffer') {
+ ($new_screen) = ((trace_cond('u', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[3];
+ unless ($eol_pos eq 'prefix') {
+ ($new_screen) = ((trace_cond('p', [\@trace], 0))[0] =~ /:(\d+)/)
+ if $base_trace->[5];
+ unless ($eol_pos eq 'suffix') {
+ $new_screen = $screen;
+ }
+ }
+ }
+ }
+ unless ($lineinfo->{'date'}) {
+ $screen = $new_screen = 0;
+ @trace = ();
+ if ($base_trace->[3]) {
+ $screen = $base_trace->[3] + 1;
+ push @trace, "u:$screen#";
+ }
+ }
+ $base_trace->[1] = $show_time;
+ if (defined $no_prefix_align) {
+ $base_trace->[5] = $no_prefix_align;
+ $base_trace->[-1] = -1;
+ }
+
+ # XXX missing special case: $wininfo->{'chat_width'} - $screen < 4
+ # `--> ignore all line break rules, just X every screenful
+ my $pos = 0;
+ 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'});
+ for (my $i = 0; $i < @words; $i+=2) {
+ my $len = defined $words[$i] ? screen_length $words[$i] : 0;
+ my $len2 = $i == $#words ? 0 : screen_length $words[$i+1];
+ if ($len <= $width - $screen) {
+ # no action needed
+ $screen += $len + $len2;
+ }
+ elsif ($len > $width - $screen) {
+ if ($len <= $width - $new_screen && $pos) { #cannot break before first word
+ push @trace, 'b:'.$pos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $screen = $new_screen + $len + $len2;
+ }
+ else {
+ my $pump = $width - $screen;
+ if ($pump <= 0) {
+ push @trace, 'b:'.$pos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $pump = $width - $new_screen;
+ }
+ if ($pump > 0) {
+ my $ipos = $pos;
+ while ($pump < $len) {
+ my $i = 0;
+ for (;;) {
+ my $clen = screen_length substr $plain_msg, $ipos, 1;
+ last if $i + $clen > $pump;
+ $i += $clen;
+ ++$ipos;
+ }
+ push @trace, 'x:'.$ipos;
+ push @trace, 'q:'.$new_screen.'.'.$layoutinfo->{'count'}++;
+ $len -= $i;
+ $pump = $width - $new_screen;
+ }
+ }
+ $screen = $new_screen + $len + $len2;
+ }
+ }
+ $pos += ($len ? length $words[$i] : 0) + ($len2 ? length $words[$i+1] : 0);
+ }
+ while (@{$ctrl->[POS_S]}) {
+ my $ctrl_s = shift @{$ctrl->[POS_S]};
+ my $ctrl_e = shift @{$ctrl->[POS_E]};
+ my $ctrl_r = shift @{$ctrl->[RES]};
+ for (@trace) {
+ s{^([bnx]:)(\d+)}{
+ $1 . ( $2 + ($2 > $ctrl_s ? length $ctrl_r : 0) )
+ }ge;
+ }
+ }
+ ($layoutinfo, \@trace)
+}
+
+## download_lines -- load all (partially) visible lines & infos
+## $wininfo - hash ref with windowinfo of window to grab
+## returns reference to all downloaded lines
+sub download_lines {
+ my ($wininfo) = @_;
+
+ my @scroll_area = Nlib::hdh($wininfo->{'pointer'}, 'window', 'scroll');
+ my @lines_in = Nlib::hdh($wininfo->{'buffer'}, 'buffer', 'lines');
+ my $lineinfo = hdh_get_lineinfo(Nlib::hdh(@lines_in, 'last_line'));
+
+ my $show_time = Nlib::hdh($wininfo->{'buffer'}, 'buffer', 'time_for_each_line');
+ my $buffer_len = (sort { $a <=> $b } Nlib::hdh(@lines_in, 'buffer_max_length'), grep { $_ > 0 } weechat::config_integer(weechat::config_get('weechat.look.prefix_buffer_align_max')))[0];
+ my $prefix_len = (sort { $a <=> $b } Nlib::hdh(@lines_in, 'prefix_max_length'), grep { $_ > 0 } weechat::config_integer(weechat::config_get('weechat.look.prefix_align_max')))[0];
+ my $separator_len = weechat::config_string(weechat::config_get('weechat.look.prefix_align')) eq 'none' ? -1 : screen_length fu8on weechat::config_string(weechat::config_get('weechat.look.prefix_suffix'));
+
+ my @base_trace = (t => $show_time, u => $buffer_len, p => $prefix_len, q => $separator_len);
+
+ my @line = Nlib::hdh(@lines_in, 'last_line');
+ my $last_read_line = weechat::config_string(weechat::config_get('weechat.look.read_marker')) eq 'line'
+ ? Nlib::hdh(@lines_in, 'last_read_line')
+ : '';
+ my $read_marker_always = weechat::config_boolean(weechat::config_get('weechat.look.read_marker_always_show'));
+ $last_read_line = '' if $last_read_line eq $line[0]
+ && !$read_marker_always;
+ my $lp;
+ my @scroll_line = Nlib::hdh(@scroll_area, 'start_line');
+ if ($scroll_line[0]) {
+ @line = @scroll_line;
+ $lineinfo = hdh_get_lineinfo(@line);
+ $wininfo->{'start_line_pos'} = Nlib::hdh
+ (@scroll_area, 'start_line_pos');
+ }
+ else {
+ $wininfo->{'start_line_pos'} = 0;
+ my $total = Nlib::hdh(@lines_in, 'lines_count');
+ return [] unless $total;
+ my $not_last_line = 0;
+ for (my ($i, $j) = (0, 0); $j < $wininfo->{'chat_height'} && $i < $total; ++$i) {
+
+ $line[0] = Nlib::hdh(@line, 'prev_line') if $i > 0;
+
+ if ($line[0] eq $last_read_line) {
+ if ($not_last_line || $read_marker_always) {
+ ++$j;
+ }
+ else {
+ $last_read_line = '';
+ }
+ }
+ my @line_data = Nlib::hdh(@line, 'data');
+ if (Nlib::hdh(@line_data, 'displayed')) {
+ my $lineinfo = +{ map { $_ => Nlib::hdh(@line_data, $_) } qw(message str_time date highlight) };
+ my $prev_lineinfo;
+ if ($base_trace[-1] < 0 && $i + 1 < $total) {
+ HAS_PREV: {
+ my @prev_line = @line;
+ my @prev_linedata;
+ do {
+ @prev_line = Nlib::hdh(@prev_line, 'prev_line');
+ last HAS_PREV unless $prev_line[0];
+ @prev_linedata = Nlib::hdh(@prev_line, 'data');
+ } until (Nlib::hdh(@prev_linedata, 'displayed'));
+ $prev_lineinfo = +{
+ (map { $_ => Nlib::hdh(@prev_linedata, $_) } qw(message str_time date highlight prefix)),
+ tag => [
+ map { Nlib::hdh(@prev_linedata, "$_|tags_array") }
+ 0 .. Nlib::hdh(@prev_linedata, 'tags_count')-1 ]
+ };
+ my ($prev_layoutinfo) = calculate_trace($prev_lineinfo,
+ $wininfo, \@base_trace);
+ my ($this_layoutinfo) = calculate_trace($lineinfo,
+ $wininfo, \@base_trace);
+ #$prev_lineinfo = undef if $prev_layoutinfo->{'count'} + $this_layoutinfo->{'count'} + $j > $wininfo->{'chat_height'};
+ }
+ $lineinfo->{'prefix'} = Nlib::hdh(@line_data, 'prefix');
+ $lineinfo->{'tag'} = [
+ map { Nlib::hdh(@line_data, "$_|tags_array") }
+ 0 .. Nlib::hdh(@line_data, 'tags_count')-1 ];
+ }
+ my ($layout_info) = calculate_trace($lineinfo,
+ $wininfo, \@base_trace,
+ $prev_lineinfo);
+ $prev_lineinfo = $lineinfo;
+ $j += $layout_info->{'count'};
+ $not_last_line = 1 if $j;
+ $wininfo->{'start_line_pos'} -= $wininfo->{'chat_height'} - $j
+ if $j > $wininfo->{'chat_height'};
+ }
+ }
+ $lineinfo = hdh_get_lineinfo(@line);
+ }
+ $lp = $line[0];
+
+ my @lines;
+ my $current_line = 0;
+ if ($lineinfo->{'displayed'}) {
+ push @lines, [+{%$lineinfo}, calculate_trace($lineinfo, $wininfo, \@base_trace)];
+ $current_line = $lines[0][LAYOUT]{'count'}-$wininfo->{'start_line_pos'};
+ }
+ my $prev_lineinfo;
+ $prev_lineinfo = $lineinfo unless $wininfo->{'start_line_pos'};
+ # XXX start_line_pos is buggy under yet uncertain multi-line messages
+ #$wininfo->{'start_line_pos'} = 6;
+
+ do {
+ if ($lp eq $last_read_line) {
+ push @lines, [+{message=>'',prefix=>''}, +{count=>1}, []];
+ ++$current_line;
+ }
+
+ $lp = $lineinfo->{'next_line'};
+ $lineinfo = hdh_get_lineinfo($lineinfo->{'next_line'}, 'line');
+
+ if ($lineinfo->{'displayed'}) {
+ push @lines, [+{%$lineinfo}, calculate_trace($lineinfo, $wininfo, \@base_trace, $prev_lineinfo)];
+ $prev_lineinfo = $lineinfo;
+ $current_line += $lines[-1][LAYOUT]{'count'};
+ }
+ }
+ while ($lineinfo->{'next_line'} && $current_line < $wininfo->{'chat_height'});
+
+ \@lines;
+}
+
+## OLD_download_lines -- load all (partially) visible lines & infos
+## $wininfo - hash ref with windowinfo of window to grab
+## returns reference to all downloaded lines
+sub OLD_download_lines {
+ my ($wininfo) = @_;
+
+ my @lines;
+
+ # get first line of buffer
+ return \@lines unless $wininfo->{'start_line'};
+ my ($lineinfo) = Nlib::i2h('buffer_lines', @{$wininfo}{'buffer','start_line'});
+ my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+ my $current_line = $layoutinfo->{'count'}-$wininfo->{'start_line_pos'};
+
+ push @lines, [+{%$lineinfo}, $layoutinfo, [Nlib::l2l($listptr, 1)]];
+
+ while ($lineinfo->{'next_line'} && $current_line < $wininfo->{'chat_height'}) {
+ ($lineinfo) = Nlib::i2h('buffer_lines', @{$lineinfo}{'buffer','next_line'});
+ next unless $lineinfo->{'displayed'};
+ my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+ $current_line += $layoutinfo->{'count'};
+
+ push @lines, [+{%$lineinfo}, $layoutinfo, [Nlib::l2l($listptr, 1)]];
+ }
+
+ \@lines
+}
+
+## message_splits -- extract word splits in the message from trace
+## $line - one line from download_lines
+## returns all split events from trace as list of [position, event]
+sub message_splits {
+ my ($line) = @_;
+ map {
+ my ($l, $d) = /(.):(\d+)/;
+ #($l =~ /[bn]/ ? 1 : 0) +
+ [ $d, $l ]
+ }
+ grep { /^[bnx]:/ } # b: break between words, n: new line, x: cut word
+ @{ $line->[TRACE] }
+}
+
+## trace_cond -- filter trace for certain condition
+## $what - event to match
+## $line - downloaded line (with trace section)
+## $lineno - line number for event
+## returns matching trace event
+sub trace_cond {
+ my ($what, $line, $lineno) = @_;
+ my $ext = $lineno ? qr/[.]$lineno$/ : qr/#/;
+ grep { /^$what:/ && /$ext/ } @{ $line->[TRACE] }
+}
+
+## to_pos -- pad string to reach a certain position
+## $tr_info - trace event from trace_cond, contains position
+## $c_ref - string reference where padding is added
+sub to_pos {
+ my ($tr_info, $c_ref) = @_;
+ $tr_info =~ /:(\d+)/;
+ my $pos = $1;
+ my $current_length = screen_length fu8on weechat::string_remove_color($$c_ref, '');
+ if ($pos-$current_length < 0) {
+ chop $$c_ref while length $$c_ref && (screen_length fu8on weechat::string_remove_color($$c_ref, '')) > $pos;
+ }
+ else {
+ $$c_ref .= ' 'x($pos-$current_length);
+ }
+}
+
+## right_align -- right align some text
+## $tr - trace event, contains position for right alignment
+## $text_ref - text reference to right align
+## $c_ref - string reference where padding is added, so that $text_ref would be right aligned
+sub right_align {
+ my ($tr, $text_ref, $c_ref) = @_;
+ $tr =~ /:(\d+)/;
+ my $pos1 = $1 -1;
+ my $text_length = screen_length fu8on weechat::string_remove_color($$text_ref, '');
+ my $current_length = screen_length fu8on weechat::string_remove_color($$c_ref, '');
+ $$c_ref .= ' 'x($pos1-$text_length-$current_length)
+}
+
+## show_time -- output timestamp and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_time {
+ my ($line, $c_ref) = @_;
+ if (my ($tr) = trace_cond('t', $line)) {
+ $$c_ref .= $line->[LINE]{'str_time'};
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_buffername -- output buffer name and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_buffername {
+ my ($line, $c_ref) = @_;
+ if (my ($tr) = trace_cond('u', $line)) {
+ my $buffer = weechat::color('chat_prefix_buffer').
+ (Nlib::i2h('buffer', $line->[LINE]{'buffer'}))[0]{'short_name'}.
+ weechat::color('reset');
+ if (weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_buffer_align')) =~ /right/) {
+ right_align($tr, \$buffer, $c_ref);
+ }
+ $$c_ref .= $buffer;
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_prefix -- output prefix and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_prefix {
+ my ($line, $c_ref, $prev_line) = @_;
+ if (my ($tr) = trace_cond('p', $line)) {
+ my $prefix = fu8on $line->[LINE]{'prefix'};
+ my ($nick_tag) = grep { /^nick_/ } @{$line->[LINE]{'tag'}||[]};
+ my ($pnick_tag) = grep { /^nick_/ } @{$prev_line->[LINE]{'tag'}||[]};
+ if ($nick_tag && $pnick_tag && $nick_tag eq $pnick_tag && !$line->[LINE]{'highlight'} &&
+ (grep { /_action$/ && !/^nick_/ } @{$line->[LINE]{'tag'}||[]}) == 0 &&
+ $prev_line && $line->[LINE]{'prefix'} eq $prev_line->[LINE]{'prefix'}) {
+ my $repl = fu8on weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_same_nick'));
+ $prefix = repeat_control_codes($prefix) . $repl if $repl;
+ }
+ if (weechat::config_string(weechat::config_get(
+ 'weechat.look.prefix_align')) =~ /right/) {
+ right_align($tr, \$prefix, $c_ref);
+ }
+ $$c_ref .= $prefix;
+ to_pos($tr, $c_ref);
+ }
+}
+
+## show_separator -- output separator and pad to next position
+## $line - downloaded line
+## $c_ref - string reference on which to output
+sub show_separator {
+ my ($line, $c_ref, $lineno) = @_;
+ if (my ($tr) = trace_cond('q', $line, $lineno)) {
+ my $separator = fu8on weechat::color('chat_prefix_suffix').
+ weechat::config_string(weechat::config_get('weechat.look.prefix_suffix'));
+ right_align($tr, \$separator, $c_ref);
+ $$c_ref .= $separator; # if XXX
+ to_pos($tr, $c_ref);
+ }
+}
+
+## repeat_control_codes -- repeat control codes previously seen (for linebreaks)
+## $text - all codes in this text will be repeated
+## returns control codes as a string
+sub repeat_control_codes {
+ my ($text) = @_;
+ my $id_control = quotemeta fu8on weechat::string_remove_color($text, "\1");
+ $id_control =~ s/\\\01/(.+?)/g;
+ my @res = $text =~ /^()$id_control()$/;
+ join '', @res
+}
+
+## calc_free_image -- create image of free window buffer
+## $wininfo - window info hash with start_line_pos & chat_height
+## $lines_ref - array ref of lines from download_lines
+## returns a array reference to all lines in the image
+sub calc_free_image {
+ my ($wininfo, $lines_ref) = @_;
+ my @image;
+ my $i = 0;
+ my $first_line = $wininfo->{'start_line_pos'};
+ my $prev_line;
+ for my $line (@$lines_ref) {
+ my $max_length = [(length $line->[LINE]{'message'}),''];
+ my @splits = ([0,''], message_splits($line), $max_length, $max_length);
+ for (0 .. $line->[LAYOUT]{'count'}-1) {
+ shift @splits if $splits[0][0] - $splits[1][0] >= 0;
+ last if $i >= $wininfo->{'chat_height'};
+ my $subm = substr $line->[LINE]{'message'}, $splits[0][0], $splits[1][0]-$splits[0][0];
+ if ($_ >= $first_line) {
+ my $construction = '';
+ unless ($_) {
+ show_time($line, \$construction);
+ show_buffername($line, \$construction);
+ show_prefix($line, \$construction, $prev_line);
+ $prev_line = $line if exists $line->[LINE]{'date'};
+ }
+ show_separator($line, \$construction, $_);
+ $construction .= weechat::color('reset');
+ $construction .= repeat_control_codes(substr $line->[LINE]{'message'}, 0, $splits[0][0]);
+ $subm =~ s/^ +// if $splits[0][1] =~ /[bn]/;
+ $construction .= $subm;
+ $construction .= weechat::color('reset');
+
+ push @image, $construction;
+ $i++;
+ }
+ shift @splits;
+ }
+ $first_line = 0;
+ }
+ \@image
+}
+
+## capture_color_codes -- extract all weechat control codes from a message and store them with their original positions
+## $msg - message from which to start
+## returns a array ref with C<RES> (control codes), C<POS_S> (start position), C<POS_E> (end position) and optionally string without all codes
+sub capture_color_codes {
+ my ($msg) = @_;
+ my $id_control = quotemeta fu8on weechat::string_remove_color($msg, "\1");
+ $id_control =~ s/(\\\01)+/(.+?)/g;
+ my @ctrl_res = $msg =~ /^()$id_control()$/;
+ my @ctrl_pos_s = @-;
+ my @ctrl_pos_e = @+;
+
+ shift @ctrl_pos_s; shift @ctrl_pos_e;
+
+ #my @chunks = split "\01+", $color_1, -1;
+
+ my @ret = \( @ctrl_res, @ctrl_pos_s, @ctrl_pos_e );
+ if (wantarray) {
+ ( \@ret, fu8on weechat::string_remove_color($msg, '') );
+ }
+ else {
+ \@ret
+ }
+}
+
+sub DEBUG_bracketing {
+ my ($bracketing_r, $msg_r, $fx) = @_;
+ return unless $fx;
+ my $dumpline = ' ' x length $$msg_r;
+ my @pair_pool = ('a'..'z');
+ for (sort { $a eq 'oc' ? -1 : $b eq 'oc' ? 1 : $a <=> $b } keys %$bracketing_r) {
+ my @l = @{ $bracketing_r->{$_} };
+ if (@l) {
+ my $ch = shift @pair_pool;
+ substr $dumpline, $l[0], 1, $ch;
+ substr $dumpline, $l[-2], 1, "\U$ch";
+ push @pair_pool, $ch;
+ }
+ }
+ print $fx $$msg_r."\n$dumpline\n";
+}
+
+## calculate_bracketing -- try to detect bracketing pairs
+## $msg_r - reference to bracketed string
+## returns hash ref to detected bracketing
+sub calculate_bracketing {
+ my ($msg_r) = @_;
+ my $braces = weechat::config_get_plugin('url_braces');
+ my %br_open;
+ while ($braces =~ s/^(.)(.*)(.)$/$2/) {
+ $br_open{$3} = $1;
+ }
+ $br_open{$braces} = $braces if length $braces;
+ my %bracketing;
+ my $br_open = join '|', map { quotemeta } values %br_open;
+ my $br_close = join '|', map { quotemeta } keys %br_open;
+ while ($$msg_r =~ /($br_open|$br_close)/g) {
+ my $char = $1;
+ my $pos = $-[0];
+ if ($char =~ /$br_close/ && @{ $bracketing{'oc'} || [] } && $bracketing{'oc'}[-1][-1] eq $br_open{$char}) {
+ my $match = pop @{ $bracketing{'oc'} };
+ push @$match, $pos, $char;
+ $bracketing{$pos} = $bracketing{$match->[0]} = $match;
+ }
+ elsif ($char =~ /$br_open/) {
+ push @{ $bracketing{'oc'} }, [ $pos, $char ];
+ }
+ else {
+ $bracketing{$pos} = [ $pos, $char ];
+ }
+ }
+ while (@{ $bracketing{'oc'} || [] }) {
+ my $match = shift @{ $bracketing{'oc'} };
+ $bracketing{$match->[0]} = $match;
+ }
+ \%bracketing
+}
+
+sub DEBUG_hyperlink {
+ my ($s, $plain_msg, $e, $fx) = @_;
+ return unless $fx;
+ my $dumpline = ' ' x length $plain_msg;
+ substr $dumpline, $s, 1, "\\";
+ substr $dumpline, $e-1, 1, '/';
+ print $fx ' '.$dumpline."\n";
+ print $fx "----------\n";
+}
+
+## hyperlink_adjust_region -- removes bracketing and word end markers if detected around/at the end of region
+## $sr - reference to start position of hyperlink
+## $msg_r - reference to string with hyperlink
+## $er - reference to end position of hyperlink
+## $bracketing_r - calculated bracketing for this string
+sub hyperlink_adjust_region {
+ my ($sr, $msg_r, $er, $bracketing_r, $no_url) = @_;
+ my $non_endings = weechat::config_get_plugin('url_non_endings');
+ my $non_beginnings = weechat::config_get_plugin('url_non_beginnings');
+ for (undef) {
+ if (exists $bracketing_r->{$$er-1} && $bracketing_r->{$$er-1}[0] == $$sr) {
+ ++$$sr; --$$er; redo;
+ }
+ elsif (exists $bracketing_r->{$$er-1} && $bracketing_r->{$$er-1}[0] < $$sr) {
+ --$$er; redo;
+ }
+ elsif (exists $bracketing_r->{$$sr} && $bracketing_r->{$$sr}[-2] > $$er-1) {
+ ++$$sr; redo;
+ }
+ unless ($no_url) {
+ if ((substr $$msg_r, $$er-1, 1) =~ /$non_endings/) {
+ --$$er; redo;
+ }
+ elsif ((substr $$msg_r, $$sr, 1) =~ /$non_beginnings/) {
+ ++$$sr; redo;
+ }
+ }
+ }
+}
+
+sub trace_to_u8 {}
+
+sub OLD_trace_to_u8 {
+ my ($line) = @_;
+ my $bytes_msg = $line->[LINE]{'message'};
+ Encode::_utf8_off($bytes_msg);
+
+ for (@{ $line->[TRACE] }) {
+ s{^([bnx]:)(\d+)}{
+ $1 . length fu8on substr $bytes_msg, 0, $2;
+ }ge;
+ }
+}
+
+## hyperlink_replay_code1 -- insert start/end of marking and adjust positions/trace
+## $line - downloaded line with trace info to adjust
+## $msg_r - reference to msg in which to insert
+## $coder - reference to code to insert
+## $tr - seek this much to the left when correcting trace profile
+## @advs - references to arrays of positions to advance, first one will be shifted
+## $fx - debug file handle
+## $DBG - output debug info, prefix with $DBG
+sub hyperlink_replay_code1 {
+ my ($line, $msg_r, $coder, $tr, @advs, $fx, $DBG) = @_;
+ unless (ref $advs[-1]) {
+ $DBG = pop @advs;
+ $fx = pop @advs;
+ }
+ my $pos_r = $advs[0];
+ my $pos = shift @$pos_r;
+ print $fx '[1]'."code:$$coder pos:$pos\n" if $fx;
+ substr $$msg_r, $pos, 0, $$coder;
+ print $fx $DBG.' '.$$msg_r."\n" if $fx;
+ for (@advs) {
+ $_ += length $$coder for @$_;
+ }
+ for (@{ $line->[TRACE] }) {
+ s{^([bnx]:)(\d+)}{
+ $1 . ( $2 + ($2 > $pos-$tr ? length $$coder : 0) )
+ }ge;
+ }
+}
+
+## hyperlink_replay_code2 -- reinsert color codes and adjust positions
+## $ctrl - captured control codes from capture_color_codes
+## $msg_r - reference to msg in which to insert
+## @advs - references to arrays of positions to advance
+## $fx - debug file handle
+## $DBG - output debug info, prefix with $DBG
+sub hyperlink_replay_code2 {
+ my ($ctrl, $msg_r, @advs, $fx, $DBG) = @_;
+ unless (ref $advs[-1]) {
+ $DBG = pop @advs;
+ $fx = pop @advs;
+ }
+ my $ctrl_s = shift @{$ctrl->[POS_S]};
+ my $ctrl_e = shift @{$ctrl->[POS_E]};
+ my $ctrl_r = shift @{$ctrl->[RES]};
+ substr $$msg_r, $ctrl_s, 0, $ctrl_r;
+ print $fx $DBG.' '.$$msg_r."\n" if $fx;
+ for (@advs) {
+ $_ += $ctrl_e-$ctrl_s for @$_;
+ }
+}
+
+
+## hyperlink_replay_codes -- insert marker codes, reinsert control codes into plain msg
+## $line - line with trace data to adjust
+## $url_sr - reference to hyperlink starting position
+## $msg_r - reference to message where positions apply
+## $url_er - reference to hyperlink ending positions
+## $ctrl - capture_color_codes result of control codes
+## $active - reference that counts no of links and actives on "0"
+## $fx - debug file handle
+sub hyperlink_replay_codes {
+ my ($line, $url_sr, $msg_r, $url_er, $ctrl, $active, $fx) = @_;
+
+ my $ul = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight');
+ #my $UL = weechat::color('-underline').weechat::color('-reverse');
+
+ my $ul_active = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight_active');
+ my $UL_active = weechat::color('reset');
+
+ my $last_url_s;
+
+ my $max_loop = 2*( @{$ctrl->[POS_S]} + @$url_sr + @$url_er );
+ while (@{$ctrl->[POS_S]} || @$url_sr || @$url_er) {
+ print $fx "<S> @$url_sr\n<E> @$url_er\n<C> @{$ctrl->[POS_S]}\n" if $fx;
+ #if (@$url_sr && $url_sr->[0] <= $url_er->[0] && (!@{$ctrl->[POS_S]} || $url_sr->[0] <= $ctrl->[POS_S][0])) # code goes before original ctl code
+ if (@$url_sr && $url_sr->[0] <= $url_er->[0] && (!@{$ctrl->[POS_S]} || $url_sr->[0] < $ctrl->[POS_S][0])) {
+ $last_url_s = $url_sr->[0];
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$ul : \$ul_active), 0,
+ $url_sr, $url_er, @{$ctrl}[POS_S,POS_E], $fx, 'S');
+ }
+ elsif (@$url_er && (!@{$ctrl->[POS_S]} || $url_er->[0] <= $ctrl->[POS_S][0])) {
+ my $UL_active1 = $UL_active;
+ if (defined $last_url_s) {
+ # get part of message constructed thus far, and remove starting bracket
+ my $msg_part1 = substr $$msg_r, 0, $url_er->[0];
+ substr $msg_part1, $last_url_s, length ($$active ? $ul : $ul_active), '';
+ $UL_active1 .= repeat_control_codes($msg_part1);
+ $last_url_s = undef;
+ }
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$UL_active1 : \$UL_active1), 1,
+ $url_er, $url_sr, @{$ctrl}[POS_S,POS_E], $fx, 'E');
+ --$$active;
+ }
+ else { # ($ctrl->[POS_S][0] <= $url_sr->[0] && $ctrl->[POS_S][0] <= $url_er->[0])
+ my $ip = $ctrl->[POS_E][0];
+ my $needs_fixup = defined $last_url_s && $ctrl->[RES][0] =~ $UL_active;
+ hyperlink_replay_code2($ctrl, $msg_r, $url_sr, $url_er, $fx, 'C');
+ if ($needs_fixup) {
+ $last_url_s = $ip;
+ hyperlink_replay_code1($line, $msg_r, ($$active ? \$ul : \$ul_active), 0,
+ [$ip], $url_sr, $url_er, @{$ctrl}[POS_S,POS_E], $fx, 'F');
+ }
+ }
+ --$max_loop; die 'endless loop' if $max_loop < 0;
+ }
+}
+
+## hyperlink_match_type_filter -- checks if current type filter applies to url info
+## $url_info - hashref with a type property
+## returns true or false
+sub hyperlink_match_type_filter {
+ my ($url_info) = @_;
+ my $t = substr $url_info->{'type'}, 0, 1;
+ $ACT_STR->[URL_TYPE_FILTER] =~ $t
+}
+
+## hyperlink_function -- highlight hyperlinks
+## $lines_ref - downloaded lines
+sub hyperlink_function {
+ my ($lines_ref) = @_;
+ my $fx;
+ #open $fx, '>', ... || weechat::print('', "error:$!");
+ my ($nicklist, $channels);
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_nicks'))) {
+ $nicklist = join '|',
+ map { quotemeta }
+ sort { length $b <=> length $a }
+ map { $_->{'name'} }
+ grep { $_->{'type'} eq 'nick' && $_->{'visible'} && length $_->{'name'} }
+ Nlib::i2h('nicklist', $ACT_STR->[WINDOWS]{'buffer'});
+ }
+ else {
+ $nicklist = '(?!)';
+ }
+ $nicklist = '(?!)' unless length $nicklist; # stop infinite loop on empty pair
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_channels'))) {
+ $channels = qr,[#]+(?:\w|[][./+^!&|~}{)(:\\*@?'-])+,;
+ }
+ else {
+ $channels = '(?!)';
+ }
+
+ my $re = weechat::config_get_plugin('url_regex');
+ $ACT_STR->[A_LINK] = -1 unless defined ${$ACT_STR}[A_LINK];
+ my $a_link = -1;
+ if (defined $ACT_STR->[URLS] && $ACT_STR->[A_LINK] < @{$ACT_STR->[URLS]}
+ && hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ for my $i (0 .. $ACT_STR->[A_LINK]) {
+ ++$a_link
+ if hyperlink_match_type_filter($ACT_STR->[URLS][$i][URL_INFO]);
+ }
+ }
+ my @urls;
+ my $i = 0; # line index
+ for my $line (@$lines_ref) {
+ my %prefix_type = (type => 'prefix');
+ my ($pfx_nick) = grep { /^nick_/ } @{ $line->[LINE]{'tag'} };
+
+ if (Nlib::has_true_value(weechat::config_get_plugin('hyper_prefix')) &&
+ $line->[LINE]{'prefix'} &&
+ defined $pfx_nick && $pfx_nick =~ s/^nick_//) {
+ push @urls, [ $line, -1, $pfx_nick, -1, $i, \%prefix_type ];
+
+ if (hyperlink_match_type_filter(\%prefix_type)) {
+ my $ul = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight');
+
+ my $ul_active = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.url_highlight_active');
+ my $UL_active = weechat::color('reset');
+
+ my ($ctrl, $plain_pfx) = capture_color_codes(fu8on $line->[LINE]{'prefix'});
+ my $my_ul = $a_link ? $ul : $ul_active;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-2], 0, $my_ul;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-1]+length $my_ul, 0, $UL_active;
+ --$a_link;
+ }
+ }
+ my $msg = fu8on $line->[LINE]{'message'};
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($msg);
+ my $bracketing_r = calculate_bracketing(\$plain_msg);
+ DEBUG_bracketing($bracketing_r, \$plain_msg, $fx);
+ my (@url_s, @url_res, @url_e);
+ while ($plain_msg =~ /\b($nicklist)(?:(?=\W)|$)|(?:^|(?<=\W))($channels)\b|$re/gx) {
+ my %typeinfo = (type => defined $1 ? 'nick' : defined $2 ? 'channel' : 'url');
+ my ($s, $e) = ($-[0], $+[0]);
+ DEBUG_hyperlink($s, $plain_msg, $e, $fx);
+
+ hyperlink_adjust_region(\($s, $plain_msg, $e), $bracketing_r, $typeinfo{'type'} ne 'url')
+ unless $typeinfo{'type'} eq 'nick';
+ my $t = substr $plain_msg, $s, $e-$s;
+ if (hyperlink_match_type_filter(\%typeinfo)) {
+ push @url_s, $s;
+ push @url_res, $t;
+ push @url_e, $e;
+ }
+ DEBUG_hyperlink($s, $plain_msg, $e, $fx);
+ push @urls, [ $line, $s, $t, $e, $i, \%typeinfo ];
+ }
+
+ print $fx "X $plain_msg\n" if $fx;
+ trace_to_u8($line);
+ hyperlink_replay_codes($line, \(@url_s, $plain_msg, @url_e), $ctrl_codes, \$a_link, $fx);
+ $line->[LINE]{'message'} = $plain_msg;
+
+ ++$i;
+ }
+ $ACT_STR->[URLS] = \@urls;
+ $ACT_STR->[A_LINK] = @{ $ACT_STR->[URLS] }
+ if $ACT_STR->[A_LINK] < 0;
+}
+
+## copy_lines -- make a copy of downloaded lines (dumb dclone)
+## $lines_ref - reference to copy
+## returns a reference copy of the reference content
+sub copy_lines {
+ my ($lines_ref) = @_;
+ my $lines_ref2 = [];
+ push @$lines_ref2, [ +{%{$_->[LINE]}}, +{%{$_->[LAYOUT]}}, [@{$_->[TRACE]}] ]
+ for @$lines_ref;
+ $lines_ref2
+}
+
+## send_clip_external -- send clipboard to external app
+## $text - text for clipboard
+## $xterm_osc - xterm-compatible escape sequence
+sub send_clip_external {
+ my ($text, $xterm_osc) = @_;
+ if (weechat::config_is_set_plugin('clipboard_command')) {
+ my %presets = ( xsel => '|xsel -c', xclip => '|xclip' );
+ my $external = weechat::config_get_plugin('clipboard_command');
+ $external = $presets{$external} if exists $presets{$external};
+ if ($external =~ /^\|/) {
+ if (open my $ext, $external) {
+ print $ext $text;
+ }
+ else {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ elsif ($external =~ s/%q/\Q$text/ || $external =~ s/%x/\Q$xterm_osc/) {
+ unless (system($external) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ else {
+ if ($external eq 'tmux') {
+ unless (( system { $external } $external, 'deleteb' ) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ $external .= ' setb';
+ }
+ my @cmd = split ' ', $external;
+ if (grep { $_ eq '%s' } @cmd) {
+ @cmd = map { $_ eq '%s' ? $text : $_ } @cmd;
+ }
+ else {
+ push @cmd, $text;
+ }
+ unless (( system { $cmd[0] } @cmd ) >= 0) {
+ weechat::print('', weechat::prefix('error').'Clipboard: '.$!);
+ }
+ }
+ }
+}
+
+## send_clip -- send text to selection clipboard
+## $text - text for clipboard
+## $stor - storage unit (optional, default s0)
+sub send_clip {
+ my ($text, $stor) = @_;
+ $stor = '' unless $stor;
+ my $text_nu = $text;
+ Encode::_utf8_off($text_nu);
+ my $xterm_osc = "\e]52;$stor;".encode_base64($text_nu, '')."\a";
+ my $compatible_terms = join '|', map { split /[,;]/ } split ' ',
+ weechat::config_get_plugin('xterm_compatible');
+ print STDERR $xterm_osc if $ENV{'TERM'} =~ /^xterm|$compatible_terms/;
+ if ($ENV{'TMUX'}) {
+ my @tmux_clients = `tmux lsc`;
+ my $active_term;
+ my $last_time = 0;
+ for (@tmux_clients) {
+ chomp;
+ my ($path, $rest) = split ':', $_;
+ next unless $rest =~ / (?:xterm|$compatible_terms)/;
+ my $atime = -A $path;
+ if ($last_time >= $atime) {
+ $last_time = $atime;
+ $active_term = $path;
+ }
+ }
+ if ($active_term) {
+ open my $pty, '>>', $active_term;
+ print $pty $xterm_osc;
+ }
+ }
+ send_clip_external($text, $xterm_osc);
+}
+
+## hyperlink_to_clip -- send currently active link to clipboard
+sub hyperlink_to_clip {
+ if ($ACT_STR->[A_LINK] >= 0 && $ACT_STR->[A_LINK] < @{ $ACT_STR->[URLS] }) {
+ my $url = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ send_clip($url);
+ }
+}
+
+## hyperlink_urlopen -- send url open signal for currently active link
+sub hyperlink_urlopen {
+ my $url = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ send_clip($url, 's0x');
+ weechat::hook_signal_send('urlopen', weechat::WEECHAT_HOOK_SIGNAL_STRING, $url);
+}
+
+## selection_to_clip -- send currently active selection to clipboard
+sub selection_to_clip {
+ if ($ACT_STR->[CUR][0*2] > -1 && $ACT_STR->[CUR][1*2] > -1) {
+ my @range = sort { $a <=> $b } @{$ACT_STR->[CUR]}[0*2,1*2];
+ my @lines = map { fu8on weechat::string_remove_color($_->[LINE]{'message'},'') } @{$ACT_STR->[LINES]}[$range[0]..$range[1]];
+ my @prefixes = map { fu8on weechat::string_remove_color($_->[LINE]{'prefix'},'') } @{$ACT_STR->[LINES]}[$range[0]..$range[1]];
+ my @cuts = map { $_->[1] } sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } ([@{$ACT_STR->[CUR]}[0*2,0*2+1]], [@{$ACT_STR->[CUR]}[1*2,1*2+1]]);
+ $lines[0] = length $lines[0] >= $cuts[0] ? substr $lines[0], $cuts[0] : ''; #(substr outside of string?)
+ $prefixes[0] = undef;# if $cuts[0];
+ $lines[-1] = substr $lines[-1], 0, $range[0]==$range[1] ? $cuts[1]-$cuts[0] : $cuts[1];
+ my $sel_text = join "\n", map { my $pfx = shift @prefixes; ($pfx ? "$pfx\t" : '') . $_ } @lines;
+ send_clip($sel_text);
+ }
+}
+
+## hyperlink_dispatch_input -- dispatch input from ** commands in hyperlink mode
+## $args - input argument
+## returns true value if processing is continued, false otherwise
+sub hyperlink_dispatch_input {
+ my ($args) = @_;
+ if ($args eq '+') {
+ ++$ACT_STR->[A_LINK];
+ $ACT_STR->[A_LINK] = 0
+ if $ACT_STR->[A_LINK] > @{ $ACT_STR->[URLS] };
+ until ($ACT_STR->[A_LINK] >= @{$ACT_STR->[URLS]}
+ || hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ ++$ACT_STR->[A_LINK];
+ }
+ hyperlink_to_clip();
+ }
+ elsif ($args eq '-') {
+ --$ACT_STR->[A_LINK];
+ until ($ACT_STR->[A_LINK] < 0
+ || hyperlink_match_type_filter($ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO])) {
+ --$ACT_STR->[A_LINK];
+ }
+ $ACT_STR->[A_LINK] = @{ $ACT_STR->[URLS] }
+ if $ACT_STR->[A_LINK] < 0;
+ hyperlink_to_clip();
+ }
+ elsif ($args eq '!') {
+ if ($ACT_STR->[A_LINK] >= 0 && $ACT_STR->[A_LINK] < @{ $ACT_STR->[URLS] }) {
+ my $link_type = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'};
+ if ($link_type eq 'nick' || $link_type eq 'prefix') {
+ my $nick = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if (Nlib::has_false_value(weechat::config_get_plugin('use_nick_menu'))) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ else {
+ delayed_nick_menu($nick);
+ close_copywin(); # XXX
+ }
+ }
+ elsif ($link_type eq 'channel') {
+ my $channel = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/join $channel");
+ }
+ else {
+ hyperlink_urlopen();
+ }
+ }
+ else {
+ weechat::command($ACT_STR->[BUFPTR], '/input return');
+ }
+ return;
+ }
+ else {
+ return;
+ }
+ 1
+}
+
+## selection_dispatch_input -- dispatch input from ** commands in selectionmode
+## $args - input argument
+## returns true value if processing is continued, false otherwise
+sub selection_dispatch_input {
+ my ($args) = @_;
+ if ($args eq '+') {
+ ++$ACT_STR->[CUR][0];
+ $ACT_STR->[CUR][0] = -1 if $ACT_STR->[CUR][0] >= @{$ACT_STR->[LINES]};
+ }
+ elsif ($args eq '-') {
+ --$ACT_STR->[CUR][0];
+ $ACT_STR->[CUR][0] = @{$ACT_STR->[LINES]}-1 if $ACT_STR->[CUR][0] < -1;
+ }
+ elsif ($args eq '>') {
+ ++$ACT_STR->[CUR][1];
+ if ($ACT_STR->[CUR][0] < 0 && $ACT_STR->[CUR][1] < 0) {
+ $ACT_STR->[CUR][1] = -1;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen if $ACT_STR->[CUR][1] > $msglen;
+ }
+ }
+ elsif ($args eq '<') {
+ --$ACT_STR->[CUR][1];
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ elsif ($ACT_STR->[CUR][1] < 0) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen-1 if $ACT_STR->[CUR][1] > $msglen;
+ }
+ }
+ elsif ($args eq 'f') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ $ACT_STR->[CUR][1] = (grep { $_ > $ACT_STR->[CUR][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][1]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ }
+ elsif ($args eq 'b') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ $ACT_STR->[CUR][1] = (grep { $_ < $ACT_STR->[CUR][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][1]) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ }
+ }
+ elsif ($args eq 'e') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ elsif ($args eq 'a') {
+ if ($ACT_STR->[CUR][0] < 0) {
+ $ACT_STR->[CUR][1] = -1 ;
+ }
+ else {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ }
+ elsif ($args eq '@') {
+ if ($ACT_STR->[CUR][2] > -1) {
+ @{$ACT_STR->[CUR]}[2,3] = (-1, -1);
+ }
+ else {
+ @{$ACT_STR->[CUR]}[2,3] = @{$ACT_STR->[CUR]}[0,1];
+ }
+ }
+ else {
+ return;
+ }
+ if ($args =~ /^[><+fbea-]$/) {
+ selection_to_clip();
+ }
+ 1
+}
+
+## selection_replay_codes -- insert marker codes, reinsert control codes into plain msg
+## $line - line with trace data to adjust
+## $sel_s - start position of selection
+## $msg_r - reference to message where positions apply
+## $sel_e - end position of selection
+## $ctrl - capture_color_codes result of control codes
+## $cup - cursor position
+## $fx - debug file handle
+sub selection_replay_codes {
+ my ($line, $sel_s, $msg_r, $sel_e, $ctrl, $cup, $fx) = @_;
+ my @cup = grep { $_ > -1 } ($cup);
+
+ my $se = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.selection');
+
+ my $cu = join '', map { weechat::color($_) } split '[.]',
+ weechat::config_get_plugin('color.selection_cursor');
+
+ my @starts = grep { $_ > -1 } sort { $a <=> $b } ($sel_s, $cup);
+ my @ends = grep { $_ > -1 } sort { $a <=> $b } ($sel_e, $cup+1);
+
+ my $last_seq_s;
+
+ print $fx "s<@starts> e<@ends> sel_s:$sel_s sel_e:$sel_e cup:$cup\n" if $fx;
+
+ my $max_loop = 2*( @{$ctrl->[POS_S]} + @starts + @ends );
+ while (@{$ctrl->[POS_S]} || @starts || @ends) {
+ #if (@starts && $starts[0] <= $ends[0] && (!@{$ctrl->[POS_S]} || $starts[0] <= $ctrl->[POS_S][0])) #urlonly
+ if (@starts && $starts[0] < $ends[0] && (!@{$ctrl->[POS_S]} || $starts[0] <= $ctrl->[POS_S][0])) {
+ $last_seq_s = $starts[0];
+ hyperlink_replay_code1($line, $msg_r, (@cup && $starts[0] == $cup[0] ? \$cu : \$se), 0,
+ \@starts, \@ends, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'S');
+ }
+ elsif (@ends && (!@{$ctrl->[POS_S]} || $ends[0] <= $ctrl->[POS_S][0])) {
+ my $active1 = weechat::color('reset');
+ if (defined $last_seq_s) {
+ # get part of message constructed thus far, and remove starting bracket
+ my $msg_part1 = substr $$msg_r, 0, $ends[0];
+ substr $msg_part1, $last_seq_s, length (@cup && $last_seq_s == $cup[0] ? $cu : $se), '';
+ $active1 .= repeat_control_codes($msg_part1);
+ $last_seq_s = undef;
+ }
+ hyperlink_replay_code1($line, $msg_r, \$active1, 1,
+ \@ends, \@starts, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'E');
+ }
+ else { # ($ctrl->[POS_S][0] <= $starts[0] && $ctrl->[POS_S][0] <= $ends[0])
+ my $ip = $ctrl->[POS_E][0];
+ my $needs_fixup = defined $last_seq_s && $ctrl->[RES][0] =~ weechat::color('reset');
+ hyperlink_replay_code2($ctrl, $msg_r, \@starts, \@ends, \@cup, $fx, 'C');
+ if ($needs_fixup) {
+ $last_seq_s = $ip;
+ hyperlink_replay_code1($line, $msg_r, (@cup && $last_seq_s == $cup[0] ? \$cu : \$se), 0,
+ [$ip], \@starts, \@ends, \@cup, @{$ctrl}[POS_S,POS_E], $fx, 'F');
+ }
+ }
+ --$max_loop; die 'endless loop' if $max_loop < 0;
+ }
+}
+
+## selection_function -- select text with cursor
+## $lines_ref - downloaded lines
+sub selection_function {
+ my ($lines_ref) = @_;
+ $ACT_STR->[CUR] = [ -1, -1, -1, -1 ] unless defined ${$ACT_STR}[CUR];
+ my $cur = $ACT_STR->[CUR];
+ my $lineno = 0;
+ my $fx;
+ #open $fx, '>', ...;
+ print $fx "cur: @$cur\n" if $fx;
+ for my $line (@$lines_ref) {
+ my $msg = fu8on $line->[LINE]{'message'};
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($msg);
+ my ($sel_s, $sel_e) = (-1, -1);
+ my $lcur = $cur->[0*2] == $lineno ? $cur->[0*2+1] : -2;
+ my $msglen = length $plain_msg;
+ $lcur = -1+$msglen if $lcur >= $msglen;
+ if ($cur->[0*2] > -1 && $cur->[1*2] > -1) { # we have a selection
+ if ($cur->[0*2] < $cur->[1*2]) { # cursor is on line before selection
+ if ($cur->[0*2] == $lineno) {
+ #($sel_s, $sel_e) = ($cur->[0*2+1]+1, $msglen);
+ ($sel_s, $sel_e) = ($lcur+1, $msglen);
+ }
+ elsif ($cur->[0*2] < $lineno && $cur->[1*2] > $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $msglen);
+ }
+ elsif ($cur->[1*2] == $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $cur->[1*2+1]);
+ }
+ }
+ elsif ($cur->[0*2] > $cur->[1*2]) { # cursor is on line after selection
+ if ($cur->[0*2] == $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $cur->[0*2+1]);
+ }
+ elsif ($cur->[0*2] > $lineno && $cur->[1*2] < $lineno) {
+ ($sel_s, $sel_e) = ('0 but true', $msglen);
+ }
+ elsif ($cur->[1*2] == $lineno) {
+ ($sel_s, $sel_e) = ($cur->[1*2+1], $msglen);
+ }
+ }
+ elsif ($cur->[0*2] == $lineno && $cur->[1*2] == $lineno) { # cursor is on same line as selection
+ if ($cur->[0*2+1] < $cur->[1*2+1]) {
+ ($sel_s, $sel_e) = ($cur->[0*2+1]+1, $cur->[1*2+1]);
+ }
+ elsif ($cur->[0*2+1] > $cur->[1*2+1]) {
+ ($sel_s, $sel_e) = ($cur->[1*2+1], $cur->[0*2+1]);
+ }
+ else {
+ ($sel_s, $sel_e) = ($cur->[0*2+1], $cur->[0*2+1]+1);
+ $lcur = -2;
+ }
+ }
+ }
+ if ($sel_s && $sel_s == 0) {
+ $sel_s+=0;
+ my $hl = weechat::color('reverse');
+ my $HL = weechat::color('-reverse');
+ if ($line->[LINE]{'prefix'}) {
+ my $ctrl = capture_color_codes(fu8on $line->[LINE]{'prefix'});
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-2], 0, $hl;
+ substr $line->[LINE]{'prefix'}, $ctrl->[POS_E][-1]+length $hl, 0, $HL;
+ }
+ }
+ $sel_e = $msglen if $sel_e > $msglen;
+ ($sel_s, $sel_e) = (-1, -1)
+ if $sel_s == $sel_e;
+ trace_to_u8($line);
+ selection_replay_codes($line, $sel_s, \$plain_msg, $sel_e, $ctrl_codes, $lcur, $fx);
+ $line->[LINE]{'message'} = $plain_msg;
+ ++$lineno;
+ }
+}
+
+## switchmode -- toggle between modes
+sub switchmode {
+ $ACT_STR->[MODE] = $ACT_STR->[MODE] eq 'hyperlink' ? 'selection' : 'hyperlink';
+ my ($r_, $R_) = (weechat::color('reverse'), weechat::color('-reverse'));
+ my $I = '▐';
+ my $t_flt = hyper_get_valid_keys('t');
+ $t_flt =~ s/$_/$_/i for split '', $ACT_STR->[URL_TYPE_FILTER];
+ weechat::buffer_set($ACT_STR->[BUFPTR], 'title',
+ ($ACT_STR->[MODE] eq 'hyperlink' ?
+ $r_.' ↑ ↓'.$R_.'move to url'.
+ $I.$r_.'RET' .$I.$R_.'send open'.
+ $I.$r_.'/' .$I.$R_.'sel.mode'.
+ $I.$r_.$t_flt.$I.$R_.
+ $I.$r_.'q' .$I.$R_.'close'
+ :
+ $r_.'←↑→↓'.$R_.'move cursor'.
+ $I.$r_.'SPC' .$I.$R_.'start selection'.
+ $I.$r_.'/' .$I.$R_.'url mode'.
+ $I.$r_.'q' .$I.$R_.'close').' '.
+ $r_.$I.$R_.
+ weechat::buffer_get_string($ACT_STR->[WINDOWS]{'buffer'}, 'short_name').
+ $I);
+}
+
+## apply_keybindings -- set up key bindings for copy buffer
+sub apply_keybindings {
+ my @wee_keys = Nlib::i2h('key');
+ my @keys;
+ my $custom_keys = weechat::config_get_plugin('copywin_custom_keys');
+ my %custom_keys;
+ if ($custom_keys) {
+ %custom_keys = ('' => split /(\S+):/, $custom_keys);
+ for (keys %custom_keys) {
+ $custom_keys{$_} = [ grep { length } split ' ', $custom_keys{$_} ];
+ }
+ }
+ @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($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **-') for @keys, @{$custom_keys{'-'}//[]}; # up arrow
+
+ @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($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **+') for @keys, @{$custom_keys{'+'}//[]}; # down arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_next_char' } @wee_keys;
+ @keys = 'meta2-C' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **>') for @keys, @{$custom_keys{'>'}//[]}; # right arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_previous_char' } @wee_keys;
+ @keys = 'meta2-D' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **<') for @keys, @{$custom_keys{'<'}//[]}; # left arrow
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_next_word' } @wee_keys;
+ @keys = 'meta-f' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **f') for @keys, @{$custom_keys{f}//[]}; # back word
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_previous_word' } @wee_keys;
+ @keys = 'meta-b' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **b') for @keys, @{$custom_keys{b}//[]}; # forward word
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_end_of_line' } @wee_keys;
+ @keys = 'ctrl-E' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **e') for @keys, @{$custom_keys{e}//[]};
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input move_beginning_of_line' } @wee_keys;
+ @keys = 'ctrl-A' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **a') for @keys, @{$custom_keys{a}//[]};
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} eq '/input return' || $_->{'command'} eq '/input magic_enter' } @wee_keys;
+ @keys = 'ctrl-M' unless @keys;
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **!') for @keys, @{$custom_keys{'!'}//[]}; # enter key
+
+ @keys = ('ctrl-@', ' ');
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **@') for @keys, @{$custom_keys{'@'}//[]}; # ctrl+space or ctrl+@
+
+ for my $cmd (@SIMPLE_MODE_KEYS) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for $cmd, @{$custom_keys{$cmd}//[]};
+ }
+
+ @keys = map { $_->{'key'} }
+ grep { $_->{'command'} =~ "^/@{[CMD_COPYWIN]}" } @wee_keys;
+ push @keys, 'q' unless @{$custom_keys{q}//[]};
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **q') for @keys, @{$custom_keys{q}//[]};
+}
+
+## binding_mouse_fix -- disable one key bindings on mouse input
+## () - signal handler
+## $data - signal has true value if mouse input in progress, false if mouse input finished
+sub binding_mouse_fix {
+ my (undef, undef, $data) = @_;
+ return weechat::WEECHAT_RC_OK unless $ACT_STR && $ACT_STR->[BUFPTR] && weechat::current_buffer() eq $ACT_STR->[BUFPTR];
+ my $custom_keys = weechat::config_get_plugin('copywin_custom_keys');
+ my %custom_keys;
+ if ($custom_keys) {
+ %custom_keys = ('' => split /(\S+):/, $custom_keys);
+ for (keys %custom_keys) {
+ $custom_keys{$_} = [ grep { length } split ' ', $custom_keys{$_} ];
+ }
+ }
+ if ($data) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_unbind_$_", '') for ' ', @SIMPLE_MODE_KEYS, 'q',
+ grep { 1 == length } map { @$_ } values %custom_keys;
+ }
+ else {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **@') for ' ';
+ weechat::buffer_set($ACT_STR->[BUFPTR], 'key_bind_/', '/'.CMD_COPYWIN.' **/');
+ for my $cmd (@SIMPLE_MODE_KEYS) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for $cmd, grep { 1 == length } @{$custom_keys{$cmd}//[]};
+ }
+ for my $cmd (split //, '@!aebf<>+-/') {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **'.$cmd) for grep { 1 == length } @{$custom_keys{$cmd}//[]};
+ }
+ if (@{$custom_keys{q}//[]}) {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_$_", '/'.CMD_COPYWIN.' **q') for @{$custom_keys{q}//[]};
+ }
+ else {
+ weechat::buffer_set($ACT_STR->[BUFPTR], "key_bind_q", '/'.CMD_COPYWIN.' **q');
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## hyper_get_valid_keys -- get keys for type filter according to enabled settings
+## $res - 't' for title, 'u' for upcase
+sub hyper_get_valid_keys {
+ my ($res) = @_;
+ $res = '' unless defined $res;
+ my $title = $res eq 't';
+ my $uc = $res eq 'u';
+ my $keys = 'u';
+ $keys .= 'n' if Nlib::has_true_value(weechat::config_get_plugin('hyper_nicks'));
+ $keys .= 'c' if Nlib::has_true_value(weechat::config_get_plugin('hyper_channels'));
+ $keys .= 'p' if Nlib::has_true_value(weechat::config_get_plugin('hyper_prefix'));
+ $keys = uc $keys if $title || $uc;
+ if ($title) {
+ length $keys == 1 ? " $keys " :
+ length $keys == 2 ? (substr $keys, 0, 1).' '.(substr $keys, 1).' ' :
+ length $keys == 3 ? "$keys " :
+ $keys;
+ }
+ else {
+ $keys = "[$keys]";
+ qr/^$keys$/;
+ }
+}
+
+## hyper_set_type_filter -- sets the type of links shown in url view based on setting and args
+## $args - command line arguments
+## returns new args
+sub hyper_set_type_filter {
+ my ($args) = @_;
+ my $valid_keys = hyper_get_valid_keys();
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc substr $_, 0, 1 } map { split /[,;]/ } split ' ',
+ weechat::config_get_plugin('hyper_show') }};
+ $args = '' unless defined $args;
+ my $urlfilter = $args;
+ if ($urlfilter =~ s/^\/// && length $urlfilter) {
+ $args = '/';
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc } split '', $urlfilter }};
+ }
+ elsif (length $urlfilter) {
+ $ACT_STR->[URL_TYPE_FILTER] = join '',
+ keys %{+{ map { $_ => undef } grep { /$valid_keys/ } map { lc substr $_, 0, 1 }
+ map { split /[,;]/ } split ' ', $urlfilter }};
+ $args = '/' if length $ACT_STR->[URL_TYPE_FILTER];
+ }
+ $ACT_STR->[URL_TYPE_FILTER] = 'u' unless length $ACT_STR->[URL_TYPE_FILTER];
+ $args
+}
+
+## make_new_copybuf -- creates a new copywin buffer from the current window
+## $args - arguments to command, can set mode
+sub make_new_copybuf {
+ my ($args) = @_;
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ my $copybuf = weechat::buffer_new('['.$wininfo->{'width'}.'x'.$wininfo->{'height'}.
+ '+'.$wininfo->{'x'}.'+'.$wininfo->{'y'}.']',
+ '', '', '', '');
+ # apply scroll keeping to current buffer if possible
+ weechat::hook_signal_send('buffer_sk', weechat::WEECHAT_HOOK_SIGNAL_POINTER, $copybuf);
+ $STR{$copybuf} = $ACT_STR = [ $copybuf ];
+ $ACT_STR->[LINES] = download_lines($wininfo);
+ $ACT_STR->[WINDOWS] = $wininfo;
+ unless ($copybuf) {
+ $ACT_STR->[MODE] = 'FAIL';
+ return;
+ }
+ $ACT_STR->[MODE] = 'hyperlink';
+ $args = hyper_set_type_filter($args);
+ weechat::buffer_set($copybuf, 'short_name', weechat::config_get_plugin('copybuf_short_name')) if $copybuf;
+ weechat::buffer_set($copybuf, 'type', 'free');
+ apply_keybindings();
+ switchmode();
+ switchmode() if $args eq '/';
+}
+
+sub copywin_cmd {
+ my (undef, $bufptr, $args, $int) = @_;
+
+ if ($int) {
+ }
+ elsif ($args =~ s/^\*\*//) {
+ return weechat::WEECHAT_RC_OK unless exists $STR{$bufptr};
+ $ACT_STR = $STR{$bufptr};
+ if ($args eq 'q') {
+ weechat::buffer_close($ACT_STR->[BUFPTR]);
+ return weechat::WEECHAT_RC_OK;
+ }
+ elsif ($args eq '/') {
+ hyper_set_type_filter()
+ unless length $ACT_STR->[URL_TYPE_FILTER];
+ switchmode();
+ }
+ elsif ($args =~ hyper_get_valid_keys()) {
+ switchmode() if $ACT_STR->[MODE] eq 'hyperlink';
+ $ACT_STR->[URL_TYPE_FILTER] = $args;
+ switchmode();
+ }
+ elsif ($args =~ hyper_get_valid_keys('u')) {
+ switchmode() if $ACT_STR->[MODE] eq 'hyperlink';
+ my $t = lc $args;
+ $ACT_STR->[URL_TYPE_FILTER] .= $t
+ unless $ACT_STR->[URL_TYPE_FILTER] =~ s/$t//;
+ switchmode() if length $ACT_STR->[URL_TYPE_FILTER];
+ }
+ elsif ($ACT_STR->[MODE] eq 'hyperlink') {
+ return weechat::WEECHAT_RC_OK
+ unless hyperlink_dispatch_input($args);
+ }
+ else {
+ return weechat::WEECHAT_RC_OK
+ unless selection_dispatch_input($args);
+ }
+ }
+ elsif ($args =~ /^\s*help\s*$/i) {
+ Nlib::read_manpage(SCRIPT_FILE, SCRIPT_NAME);
+ return weechat::WEECHAT_RC_OK
+ }
+ else {
+ check_layout()
+ unless $LAYOUT_OK;
+ make_new_copybuf($args);
+ }
+
+ my $wininfo = $ACT_STR->[WINDOWS];
+ my $lines_ref2 = copy_lines($ACT_STR->[LINES]);
+ my $copybuf = $ACT_STR->[BUFPTR];
+
+ if ($ACT_STR->[MODE] eq 'hyperlink') {
+ hyperlink_function($lines_ref2);
+ }
+ elsif ($ACT_STR->[MODE] eq 'selection') {
+ selection_function($lines_ref2);
+ }
+
+
+ my $printy = calc_free_image($wininfo, $lines_ref2);
+
+ for my $i (0..$#$printy) {
+ weechat::print_y($copybuf, $i, $printy->[$i]);
+ }
+
+ weechat::buffer_set($copybuf, 'display', 'auto');
+ weechat::WEECHAT_RC_OK
+}
+
+sub real_screen {
+ my ($screen_cursor, $text) = @_;
+ my $l = length $text;
+ for (my ($i, $j) = (0, 0); $i < $l; ++$i) {
+ $j += screen_length(substr $text, $i, 1);
+ return $i if $j > $screen_cursor;
+ }
+ $l
+}
+
+## mouse_coords_to_cursor -- calculate in line cursor according to rendering
+## $r - local (in window) row of mouse cursor
+## $c - local (in window) column of mouse cursor
+## returns line index and position in plain text if found
+sub mouse_coords_to_cursor {
+ my ($r, $c) = @_; # wanted row & column
+
+ my $wininfo = $ACT_STR->[WINDOWS];
+ my $lines_ref = copy_lines($ACT_STR->[LINES]);
+
+ my ($i, $l, $p) = (1, 0, 0); # current row, current line index, current line position
+ my $first_line = $wininfo->{'start_line_pos'};
+ my $prev_line;
+ for my $line (@$lines_ref) {
+ trace_to_u8($line);
+ my $max_length = [(length fu8on $line->[LINE]{'message'}),''];
+ my @splits = ([0,''], message_splits($line), $max_length, $max_length);
+ for (0 .. $line->[LAYOUT]{'count'}-1) {
+ shift @splits if $splits[0][0] - $splits[1][0] >= 0;
+ last if $i > $wininfo->{'chat_height'};
+ my $subm = substr $line->[LINE]{'message'}, $splits[0][0], $splits[1][0]-$splits[0][0];
+ my $subm_u = fu8on weechat::string_remove_color($subm, '');
+ my $subm_length = length $subm_u;
+ if ($_ >= $first_line) {
+ if ($r == $i) {
+ my $subm_length_screen = screen_length $subm_u;
+ my $construction = '';
+ unless ($_) {
+ show_time($line, \$construction);
+ return ($l, undef, 'time')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ show_buffername($line, \$construction);
+ return ($l, undef, 'buffername')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ show_prefix($line, \$construction, $prev_line);
+ return ($l, undef, 'prefix')
+ if (screen_length fu8on weechat::string_remove_color($construction, '')) >= $c;
+ $prev_line = $line if exists $line->[LINE]{'date'};
+ }
+ show_separator($line, \$construction, $_);
+ my $message_start_screen = screen_length fu8on weechat::string_remove_color($construction, '');
+ return ($l) if $message_start_screen >= $c;
+ if ($splits[0][1] =~ /[bn]/ && $subm =~ s/^( +)//) {
+ my $l = length $1;
+ $p += $l;
+ $subm_u = substr $subm_u, $l;
+ }
+ if ($subm_length_screen >= $c - 1 - $message_start_screen) {
+ return ($l, $p + real_screen($c - 1 - $message_start_screen, $subm_u));
+ }
+ }
+ ++$i;
+ }
+ $p += $subm_length;
+ shift @splits;
+ }
+ $first_line = 0;
+ ++$l; $p = 0;
+ }
+}
+
+## copywin_autoclose -- close copywin buffer
+## () - this small function is a timer handler
+sub copywin_autoclose {
+ weechat::buffer_close($ACT_STR->[BUFPTR])
+ if $ACT_STR && $ACT_STR->[MOUSE_AUTOMODE];
+ $autoclose_in_progress = undef;
+ weechat::WEECHAT_RC_OK
+}
+
+## get_autoclose_delay -- checks delay for autoclose of copywin buffer
+## returns delay or false if autoclose is disabled
+sub get_autoclose_delay {
+ my $autoclose_delay = weechat::config_get_plugin('mouse.close_on_release');
+ if (Nlib::has_false_value($autoclose_delay)) { 0 }
+ elsif ($autoclose_delay =~ /\D/ || $autoclose_delay < 100) { 100 }
+ else { $autoclose_delay }
+}
+
+## get_2nd_click_delay -- checks additional delay for autoclose of copywin buffer if 2nd click is needed to open url
+## returns additional delay
+sub get_2nd_click_delay {
+ my $conf_2nd_click_delay = weechat::config_get_plugin('mouse.url_open_2nd_click');
+ if (Nlib::has_false_value($conf_2nd_click_delay)) { 0 }
+ elsif ($conf_2nd_click_delay =~ /\D/) { 2000 }
+ else { $conf_2nd_click_delay }
+}
+
+## get_nick_2nd_click_delay -- checks additional delay for autoclose of copywin buffer if 2nd click is needed to query nick
+## returns additional delay
+sub get_nick_2nd_click_delay {
+ my $conf_2nd_click_delay = weechat::config_get_plugin('mouse.nick_2nd_click');
+ if (Nlib::has_false_value($conf_2nd_click_delay)) { 0 }
+ elsif ($conf_2nd_click_delay =~ /\D/) { 500 }
+ else { $conf_2nd_click_delay }
+}
+
+## mouse_window_at_pointer -- switch to window where clicked
+## $row - mouse row
+## $col - mouse column
+## $wininfo - currently active window info
+## returns true value if successful
+sub mouse_window_at_pointer {
+ my ($row, $col, $wininfo) = @_;
+ my @all_windows = Nlib::i2h('window');
+ my $in_any_win;
+ for (@all_windows) {
+ $in_any_win = $_ if Nlib::in_window($row, $col, $_);
+ }
+ return unless $in_any_win;
+ my $steps = 0;
+ for (@all_windows) {
+ weechat::command('', '/window +1');
+ ++$steps;
+ last if weechat::current_window() eq $in_any_win->{'pointer'};
+ }
+ my ($act_win_ind) = map { $_->[0] }
+ grep { $_->[-1]{'pointer'} eq $wininfo->{'pointer'} }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @all_windows
+ };
+ my ($click_win_ind) = map { $_->[0] }
+ grep { $_->[-1]{'pointer'} eq $in_any_win->{'pointer'} }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @all_windows
+ };
+ #weechat::print('',"steps:$steps, a:$act_win_ind, c:$click_win_ind, d:@{[$act_win_ind-$click_win_ind]}/@{[$click_win_ind-$act_win_ind]} #:".scalar @all_windows);
+ 1
+}
+
+## mouse_scroll_action -- handle mouse scroll
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+sub mouse_scroll_action {
+ my (undef, undef, undef, $row, $col) = @_;
+ return weechat::WEECHAT_RC_OK unless Nlib::has_true_value(weechat::config_get_plugin('mouse.handle_scroll'));
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ my $had_to_switch_win;
+
+ unless (Nlib::in_window($row, $col, $wininfo)) {
+ if (Nlib::has_true_value(weechat::config_get_plugin('mouse.scroll_inactive_pane'))) {
+ return weechat::WEECHAT_RC_OK
+ unless mouse_window_at_pointer($row, $col, $wininfo);
+ $had_to_switch_win = $winptr;
+ $winptr = weechat::current_window();
+ ($wininfo) = Nlib::i2h('window', $winptr);
+ }
+ else {
+ if ($_[2] =~ /^`/) { weechat::command('', '/window scroll_up'); } #`
+ elsif ($_[2] =~ /^a/) { weechat::command('', '/window scroll_down'); }
+ }
+ }
+
+ if (Nlib::in_window($row, $col, $wininfo)) {
+ if (Nlib::in_chat_window($row, $col, $wininfo)) {
+ if ($_[2] =~ /^`/) { weechat::command('', '/window scroll_up'); } #`
+ elsif ($_[2] =~ /^a/) { weechat::command('', '/window scroll_down'); }
+ }
+ elsif (my $bar_infos = Nlib::find_bar_window($row, $col)) {
+ my $dir = $bar_infos->[-1]{'filling_'.
+ ($bar_infos->[-1]{'position'} <= 1 ?
+ 'top_bottom' : 'left_right')} ? 'y' : 'x';
+ if ($_[2] =~ /^`/) { $dir .= '-' } #`
+ elsif ($_[2] =~ /^a/) { $dir .= '+' }
+ for ($bar_infos->[-1]{'name'}) {
+ weechat::command('', '/bar scroll '.$_.' * '.$dir.'10%')
+ if ($_ eq 'title' or $_ eq 'status' or $_ eq 'nicklist')
+ }
+ }
+ }
+
+ if ($had_to_switch_win) {
+ my $steps = 0;
+ for (Nlib::i2h('window')) {
+ weechat::command('', '/window +1');
+ ++$steps;
+ last if weechat::current_window() eq $had_to_switch_win;
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## drag_speed_hack -- delay drag events
+## () - timer handler
+sub drag_speed_hack {
+ $drag_speed_timer = undef;
+ mouse_evt(undef, undef, $_[0]);
+ weechat::WEECHAT_RC_OK
+}
+
+sub input_text_hlsel {
+ my (undef, undef, $bufptr, $text) = @_;
+ return $text unless defined $input_sel && $input_sel->[0] eq $bufptr;
+ my ($ctrl_codes, $plain_msg) = capture_color_codes($text);
+ my $npos = weechat::buffer_get_integer($bufptr, 'input_pos');
+ my ($sel_s, $sel_e) = sort { $a <=> $b } ($npos, $input_sel->[-1]);
+ return $text unless $sel_s < $sel_e;
+ selection_replay_codes([[]], $sel_s, \$plain_msg, $sel_e, $ctrl_codes, -2);
+ $plain_msg
+}
+
+sub window_of_bar {
+ my ($barinfo) = @_;
+ my @all_windows = Nlib::i2h('window');
+ my $in_any_win;
+ for (@all_windows) {
+ $in_any_win = $_ if Nlib::in_window(1+$barinfo->[0]{'y'}, 1+$barinfo->[0]{'x'}, $_);
+ }
+ $in_any_win
+}
+
+## mouse_input_sel -- mouse select in input bar
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+sub mouse_input_sel {
+ my (undef, undef, undef, $row, $col) = @_;
+ my $plain;
+ if (my $bar_infos = Nlib::find_bar_window($row, $col)) {
+ if ($bar_infos->[-1]{'name'} eq 'input') {
+ my $win = window_of_bar($bar_infos);
+ my ($error, $idx, $content, $coords) = Nlib::bar_item_get_subitem_at
+ ($bar_infos, 'input_text', $col - $bar_infos->[0]{'x'}, $row - $bar_infos->[0]{'y'});
+ if ($coords) {
+ my $col_pos = $coords->[1]-$coords->[0] + ($coords->[4]-$coords->[3])*$bar_infos->[0]{'width'};
+ $plain = fu8on weechat::string_remove_color($content, '');
+ $input_sel = [ $win->{'buffer'}, real_screen($col_pos, $plain) ];
+ }
+ elsif ($error eq 'outside') {
+ $plain = fu8on weechat::string_remove_color($content, '');
+ $input_sel = [ $win->{'buffer'}, length $plain ];
+ }
+ }
+ }
+ if (defined $plain) {
+ if ($_[2] =~ /^ /) {
+ weechat::buffer_set($input_sel->[0], 'input_pos', $input_sel->[-1]);
+ }
+ else {
+ my $npos = weechat::buffer_get_integer($input_sel->[0], 'input_pos');
+ my ($sel_s, $sel_e) = sort { $a <=> $b } ($npos, $input_sel->[-1]);
+ send_clip((substr $plain, $sel_s, $sel_e-$sel_s))
+ if $sel_s < $sel_e;
+ }
+ }
+ $input_sel = undef if $_[2] =~ /^#/;
+}
+
+## mouse_click_into_copy -- click when not in copy mode
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## $row - mouse row
+## $col - mouse column
+## returns true value if copy mode should be turned on
+sub mouse_click_into_copy {
+ my (undef, undef, undef, $row, $col) = @_;
+
+ mouse_input_sel(@_[0..2], $row, $col);
+ return unless $_[2] =~ /^ /;
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+
+ unless (Nlib::in_window($row, $col, $wininfo)) {
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.click_select_pane'));
+ return unless mouse_window_at_pointer($row, $col, $wininfo);
+ $winptr = weechat::current_window();
+ ($wininfo) = Nlib::i2h('window', $winptr);
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.click_through_pane'));
+ }
+ return unless Nlib::has_true_value(weechat::config_get_plugin('mouse.copy_on_click'));
+ return unless Nlib::in_chat_window($row, $col, $wininfo);
+ 1
+}
+
+## mouse_delay_drag -- delay drag events
+## () - signal passed on from mouse event handler
+## $_[2] - mouse code
+## returns true if this event should be delayed
+sub mouse_delay_drag {
+ my $drag_speed = weechat::config_get_plugin('mouse.drag_speed');
+ unless (Nlib::has_false_value($drag_speed)) {
+ $drag_speed = 50 if $drag_speed =~ /\D/;
+ weechat::unhook($drag_speed_timer) if defined $drag_speed_timer;
+ $drag_speed_timer = weechat::hook_timer($drag_speed, 0, 1, 'drag_speed_hack', $_[2]);
+ return 1;
+ }
+ undef
+}
+
+## delayed_nick_menu -- open the nick menu
+## () - timer handler
+sub delayed_nick_menu {
+ $delayed_nick_menu_timer = undef;
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/menu nick $_[0]")
+ if $ACT_STR && Nlib::i2h('hook', '', 'command,menu');
+ weechat::WEECHAT_RC_OK
+}
+
+## mouse_evt -- handle mouse clicks
+## () - signal handler
+## $_[2] - mouse code
+sub mouse_evt {
+ Encode::_utf8_on($_[2]);
+
+ my $this_last_mouse_seq = $last_mouse_seq || '';
+ if ($_[1] && $this_last_mouse_seq =~ /^@/ && $_[2] =~ /^@/) {
+ return weechat::WEECHAT_RC_OK if mouse_delay_drag(@_[0..2])
+ }
+ $last_mouse_seq = $_[2];
+
+ #return weechat::WEECHAT_RC_OK unless defined $ACT_STR && $ACT_STR->[BUFPTR] eq weechat::current_buffer();
+
+ my $curbufptr = weechat::current_buffer();
+
+ if ($_[2] =~ /^[#@ `a](.)(.)$/) {
+ my $row = ord($2)-32;
+ my $col = ord($1)-32;
+
+ weechat::unhook($delayed_nick_menu_timer) if defined $delayed_nick_menu_timer;
+ $delayed_nick_menu_timer = undef;
+
+ return mouse_scroll_action(@_[0..2], $row, $col) if $_[2] =~ /^[`a]/;
+
+ if (!defined $ACT_STR || $ACT_STR->[BUFPTR] ne $curbufptr) {
+ return weechat::WEECHAT_RC_OK
+ unless mouse_click_into_copy(@_[0..2], $row, $col);
+ copywin_cmd(undef, $curbufptr, '/');
+ return weechat::WEECHAT_RC_OK unless $ACT_STR->[BUFPTR] eq weechat::current_buffer();
+ $ACT_STR->[MOUSE_AUTOMODE] = 1;
+ }
+ $ACT_STR->[CUR] = [ -1, -1, -1, -1 ] unless defined ${$ACT_STR}[CUR];
+
+ my $winptr = weechat::current_window();
+ my ($wininfo) = Nlib::i2h('window', $winptr);
+ return weechat::WEECHAT_RC_OK unless Nlib::in_chat_window($row, $col, $wininfo);
+
+ my $lrow = $row - $wininfo->{'chat_y'};
+ my $lcol = $col - $wininfo->{'chat_x'};
+ #weechat::print_y($ACT_STR->[BUFPTR], $lrow-1, ' 'x($lcol-1).'x'.$lrow.'/'.$lcol);
+ my @cursor = mouse_coords_to_cursor($lrow, $lcol);
+ #weechat::print($ACT_STR->[WINDOWS]{'buffer'}, 'cursor: '.join ',', map { defined $_ ? $_ : '?' } @cursor );
+
+ my $one_click;
+ if (@cursor == 3 && $cursor[2] eq 'prefix' &&
+ $this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+
+ my @link = grep { $_->[-1][URL_LINE] == $cursor[LINE] &&
+ $_->[-1][URL_S] == -1 && $_->[-1][URL_E] == -1 }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+ };
+
+ if (@link == 1) {
+ $ACT_STR->[A_LINK] = $link[0][0];
+ my $t = substr $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'}, 0, 1;
+ unless ($ACT_STR->[URL_TYPE_FILTER] =~ $t) {
+ switchmode();
+ $ACT_STR->[URL_TYPE_FILTER] = $t;
+ switchmode();
+ }
+ }
+ if ($ACT_STR->[MODE] eq 'hyperlink' && @link == 1) {
+ hyperlink_to_clip();
+
+ my ($nick) = grep { /^nick_/ } @{ $ACT_STR->[LINES][$cursor[LINE]][LINE]{'tag'} };
+ if (defined $nick && $nick =~ s/^nick_//) {
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.nick_2nd_click'))) {
+ delayed_nick_menu($nick);
+ }
+ elsif ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'nick' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] eq $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'nick', $ACT_STR, $ACT_STR->[A_LINK] ];
+ $delayed_nick_menu_timer = weechat::hook_timer(get_nick_2nd_click_delay(), 0, 1, 'delayed_nick_menu', $nick);
+ }
+ else {
+ delayed_nick_menu($nick);
+ }
+ }
+ }
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**', 1);
+ }
+
+ unless (@cursor == 2) { # no valid text here
+ $mouse_2nd_click = $one_click if $_[2] =~ /^#/;
+ my $autoclose_delay = get_autoclose_delay();
+ $autoclose_delay += get_nick_2nd_click_delay() if $one_click;
+ $autoclose_in_progress = weechat::hook_timer($autoclose_delay, 0, 1, 'copywin_autoclose', '')
+ if $autoclose_delay && $ACT_STR->[MOUSE_AUTOMODE] && $_[2] =~ /^#/ && !$autoclose_in_progress;
+ return weechat::WEECHAT_RC_OK;
+ }
+
+ if ($_[2] =~ /^ /) { # button down
+ @{$ACT_STR->[CUR]}[2,3] = (-1, -1);
+ weechat::unhook($autoclose_in_progress) if $autoclose_in_progress;
+ $autoclose_in_progress = undef;
+ }
+ elsif ($this_last_mouse_seq =~ /^ / &&
+ ($_[2] =~ /^@/ ||
+ ($_[2] =~ /^#/ && ((substr $this_last_mouse_seq, 1) ne (substr $_[2], 1)))
+ )) { # switch to drag
+ switchmode() if $ACT_STR->[MOUSE_AUTOMODE] && $ACT_STR->[MODE] eq 'hyperlink';
+ selection_dispatch_input('@');
+ }
+ if ($this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'sel' &&
+ $mouse_2nd_click->[1] == $ACT_STR) {
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'sel', $ACT_STR, [ @cursor ] ];
+ }
+ }
+ @{$ACT_STR->[CUR]}[0,1] = @cursor;
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'sel' &&
+ $mouse_2nd_click->[1] == $ACT_STR &&
+ $ACT_STR->[MODE] eq 'selection' &&
+ $ACT_STR->[CUR][2] >= 0 && $ACT_STR->[CUR][3] >= 0) {
+ { # fix cursor to word boundary
+ my $msg = $ACT_STR->[LINES][$ACT_STR->[CUR][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ if (($ACT_STR->[CUR][0] == $mouse_2nd_click->[-1][0] && $ACT_STR->[CUR][1] > $mouse_2nd_click->[-1][1])
+ || $ACT_STR->[CUR][0] > $mouse_2nd_click->[-1][0]) { #forward
+ $ACT_STR->[CUR][1] = (grep { $_ >= $ACT_STR->[CUR][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][1]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][1] = $msglen;
+ }
+ }
+ else { #backward
+ $ACT_STR->[CUR][1] = (grep { $_ <= $ACT_STR->[CUR][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][1]) {
+ $ACT_STR->[CUR][1] = 0;
+ }
+ } }
+
+ { # fix selection to word boundary
+ my $msg = $ACT_STR->[LINES][$mouse_2nd_click->[-1][0]][LINE]{'message'};
+ my $plain_msg = fu8on weechat::string_remove_color($msg, '');
+ my @breaks;
+ push @breaks, @- while $plain_msg =~ /\b/g;
+ if (($mouse_2nd_click->[-1][0] == $ACT_STR->[CUR][0] && $mouse_2nd_click->[-1][1] > $ACT_STR->[CUR][1])
+ || $mouse_2nd_click->[-1][0] > $ACT_STR->[CUR][0]) { #forward
+ $ACT_STR->[CUR][3] = (grep { $_ >= $mouse_2nd_click->[-1][1] } @breaks)[0];
+ unless (defined $ACT_STR->[CUR][3]) {
+ my $msglen = length $plain_msg;
+ $ACT_STR->[CUR][3] = $msglen;
+ }
+ }
+ else { #backward
+ $ACT_STR->[CUR][3] = (grep { $_ <= $mouse_2nd_click->[-1][1] } @breaks)[-1];
+ unless (defined $ACT_STR->[CUR][3]) {
+ $ACT_STR->[CUR][3] = 0;
+ }
+ } }
+ }
+ my @link = grep { $_->[-1][URL_LINE] == $cursor[LINE] &&
+ $_->[-1][URL_S] <= $cursor[1] && $_->[-1][URL_E] > $cursor[1] }
+ do {
+ my $i = 0;
+ map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+ };
+
+# open my $fx, '>', ...;
+# print $fx "link:@link cur:@cursor\n";
+# map { print $fx $_->[0].'#url_line:'.$_->[-1][URL_LINE].' url_s:'.$_->[-1][URL_S].' url_e:'.$_->[-1][URL_E].' url:'.$_->[-1][URL]."\n" }
+# do {
+# my $i = 0;
+# map { [ $i++, $_ ] } @{$ACT_STR->[URLS]}
+# };
+
+ if (@link == 1) {
+ $ACT_STR->[A_LINK] = $link[0][0];
+ my $t = substr $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'}, 0, 1;
+ unless ($ACT_STR->[URL_TYPE_FILTER] =~ $t) {
+ switchmode();
+ $ACT_STR->[URL_TYPE_FILTER] = $t;
+ switchmode();
+ }
+ }
+ if ($ACT_STR->[MODE] eq 'hyperlink' && @link == 1) {
+ hyperlink_to_clip();
+ if ($this_last_mouse_seq =~ /^ / && $_[2] =~ /^#/ &&
+ ((substr $this_last_mouse_seq, 1) eq (substr $_[2], 1))) { # click
+ my $link_type = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL_INFO]{'type'};
+ if ($link_type eq 'nick') {
+ my $nick = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.nick_2nd_click'))) {
+ delayed_nick_menu($nick);
+ }
+ elsif ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/query $nick");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ $delayed_nick_menu_timer = weechat::hook_timer(get_nick_2nd_click_delay(), 0, 1, 'delayed_nick_menu', $nick);
+ }
+ else {
+ delayed_nick_menu($nick);
+ }
+ }
+ elsif ($link_type eq 'channel') {
+ my $channel = $ACT_STR->[URLS][$ACT_STR->[A_LINK]][URL];
+ if ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK]) {
+ weechat::command($ACT_STR->[WINDOWS]{'buffer'}, "/join $channel");
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ }
+ }
+ else {
+ if (Nlib::has_false_value(weechat::config_get_plugin('mouse.url_open_2nd_click')) ||
+ ($mouse_2nd_click && $mouse_2nd_click->[0] eq 'link' &&
+ $mouse_2nd_click->[1] == $ACT_STR && $mouse_2nd_click->[2] == $ACT_STR->[A_LINK])) {
+ hyperlink_dispatch_input('!');
+ }
+ elsif (!$mouse_2nd_click) {
+ $one_click = [ 'link', $ACT_STR, $ACT_STR->[A_LINK] ];
+ }
+ }
+ }
+ }
+ else {
+ selection_to_clip();
+ }
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**', 1);
+ if ($_[2] =~ /^#/) { # button up
+ $mouse_2nd_click = $one_click;
+ my $autoclose_delay = get_autoclose_delay();
+ if ($one_click && $one_click->[0] eq 'link') {
+ if ($one_click->[1][URLS][$one_click->[2]][URL_INFO]{'type'} eq 'nick') {
+ $autoclose_delay += get_nick_2nd_click_delay();
+ }
+ else {
+ $autoclose_delay += get_2nd_click_delay();
+ }
+ }
+ $autoclose_in_progress = weechat::hook_timer($autoclose_delay, 0, 1, 'copywin_autoclose', '')
+ if $autoclose_delay && $ACT_STR->[MOUSE_AUTOMODE] && !$autoclose_in_progress;
+ }
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub hsignal_evt {
+ my %data = %{$_[2]};
+ if ($data{_key} =~ /^(.*)-event-/) {
+ my $msg = "\@chat($data{_buffer_full_name}):$1";
+ for my $k (Nlib::i2h('key', '', 'mouse')) {
+ next if $k->{'key'} =~ /-event/;
+ (my $match = '^'.(quotemeta $k->{'key'})) =~ s/\\\*/.*/g;
+ my $close = $msg =~ $match;
+ last if $close and $k->{'command'} =~ /hsignal:@{[SCRIPT_NAME]}/;
+ return weechat::WEECHAT_RC_OK if $close;
+ }
+ }
+ if ($data{_key} =~ /-event-down/) {
+ mouse_evt(undef, undef, join '', ' ', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ $hsignal_mouse_down_sent = 1;
+ }
+ elsif ($data{_key} =~ /-event-drag/) {
+ mouse_evt(undef, undef, join '', '@', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ }
+ else {
+ mouse_evt(undef, undef, join '', ' ', (pack 'U', 33+$data{_x}), (pack 'U', 33+$data{_y}))
+ unless $hsignal_mouse_down_sent;
+ mouse_evt(undef, undef, join '', '#', (pack 'U', 33+$data{_x2}), (pack 'U', 33+$data{_y2}));
+ $hsignal_mouse_down_sent = undef;
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+## check_layout -- check if this weechat version knows about layout infolist
+sub check_layout {
+ return weechat::WEECHAT_RC_OK if $LAYOUT_OK;
+# my $winptr = weechat::current_window();
+# my ($wininfo) = Nlib::i2h('window', $winptr);
+# my ($lineinfo) = Nlib::i2h('buffer_lines', @{$wininfo}{'buffer','start_line'});
+# my ($layoutinfo) = Nlib::i2h('layout', $wininfo->{'pointer'}, $lineinfo->{'line'}, $listptr);
+# Nlib::l2l($listptr, 1);
+# unless ($layoutinfo) {
+# weechat::print('', weechat::prefix('error').
+# "You will need to have layout trace support in your WeeChat. Get it at\n".
+# " http://anti.teamidiot.de/static/nei/*/Code/WeeChat/display_traces.diff");
+# }
+# else {
+ $LAYOUT_OK = 1;
+# }
+# if (($ENV{'STY'} || $ENV{'TERM'} =~ /screen/) && !exists $ENV{'TMUX'}) {
+# weechat::print('', weechat::prefix('error').
+# "Your terminal most likely doesn't support the selection clipboard control.");
+# }
+ weechat::WEECHAT_RC_OK
+}
+
+
+## garbage_str -- remove copywin storage when copywin buffer is closed
+## () - signal handler
+## $bufptr - signal comes with pointer of closed buffer
+sub garbage_str {
+ my (undef, undef, $bufptr) = @_;
+ $ACT_STR = undef if $ACT_STR && $ACT_STR->[BUFPTR] eq $bufptr;
+ delete $STR{$bufptr};
+ weechat::WEECHAT_RC_OK
+}
+
+## decouple_mouse_scroll -- add coords script as mouse scrolling handler
+sub decouple_mouse_scroll {
+ my $main = weechat::buffer_search_main();
+ my $mouse_scroll_ext = weechat::buffer_string_replace_local_var($main, '$mousescroll');
+ unless (grep { $_ eq SCRIPT_NAME } split ',', $mouse_scroll_ext) {
+ $script_old_mouse_scroll = $mouse_scroll_ext;
+ if ($mouse_scroll_ext =~ /^\$/) {
+ weechat::buffer_set($main, 'localvar_set_mousescroll', SCRIPT_NAME);
+ }
+ else {
+ weechat::buffer_set($main, 'localvar_set_mousescroll', $mouse_scroll_ext.','.SCRIPT_NAME);
+ }
+ }
+}
+
+## restore_mouse_scroll -- restore mouse scrolling handler
+sub restore_mouse_scroll {
+ if (defined $script_old_mouse_scroll) {
+ my $main = weechat::buffer_search_main();
+ if ($script_old_mouse_scroll =~ /^\$/) {
+ weechat::buffer_set($main, 'localvar_del_mousescroll', '');
+ }
+ else {
+ weechat::buffer_set($main, 'localvar_set_mousescroll',
+ join ',', grep { $_ ne SCRIPT_NAME } split ',', $script_old_mouse_scroll);
+ }
+ }
+}
+
+## default_options -- set up default option values on start and when unset
+sub default_options {
+ my %defaults = (
+ url_braces => '[({<"'."''".'">})]',
+ url_regex =>
+ '\w+://\S+ | '.
+ '(?:^|(?<=\s))(?:\S+\.){2,}\w{2,5}(?:/\S*|(?=\s)|$) | '.
+ '(?:^|(?<=\s))(?:\S+\.)+\w{2,5}/(?:\S+)?',
+ url_non_endings => '[.,;:?!_-]',
+ url_non_beginnings => '\W',
+ hyper_nicks => 'off',
+ hyper_channels => 'off',
+ hyper_prefix => 'on',
+ hyper_show => 'url',
+ use_nick_menu => 'off',
+ xterm_compatible => 'rxvt-uni',
+ 'mouse.copy_on_click' => 'on',
+ 'mouse.close_on_release' => '110',
+ 'mouse.click_select_pane' => 'on',
+ 'mouse.click_through_pane' => 'off',
+ 'mouse.url_open_2nd_click' => 'off',
+ 'mouse.handle_scroll' => 'off',
+ 'mouse.scroll_inactive_pane' => 'on',
+ copybuf_short_name => '©',
+ 'color.selection_cursor' => 'reverse.underline',
+ 'color.selection' => 'reverse.brown,black',
+ 'color.url_highlight' => 'reverse.underline',
+ 'color.url_highlight_active' => 'reverse.brown,black',
+ );
+ for (keys %defaults) {
+ weechat::config_set_plugin($_, $defaults{$_})
+ unless weechat::config_is_set_plugin($_);
+ }
+ my $sf = SCRIPT_FILE;
+ for (Nlib::get_settings_from_pod($sf)) {
+ weechat::config_set_desc_plugin($_, Nlib::get_desc_from_pod($sf, $_));
+ }
+ if (Nlib::has_true_value(weechat::config_get_plugin('mouse.handle_scroll'))) {
+ decouple_mouse_scroll();
+ }
+ else {
+ restore_mouse_scroll();
+ }
+ weechat::WEECHAT_RC_OK
+}
+
+sub close_copywin {
+ copywin_cmd(undef, $ACT_STR->[BUFPTR], '**q') if $ACT_STR;
+ weechat::WEECHAT_RC_OK
+}
+
+sub init_coords {
+ $listptr = weechat::list_new();
+ weechat::hook_timer(1000, 0, 1, 'check_layout', '');
+ default_options();
+ weechat::WEECHAT_RC_OK
+}
+
+sub stop_coords {
+ close_copywin();
+ restore_mouse_scroll();
+ weechat::list_free($listptr);
+ weechat::WEECHAT_RC_OK
+}