1## trackbar.pl
2#
3# This little script will do just one thing: it will draw a line each time you
4# switch away from a window. This way, you always know just upto where you've
5# been reading that window :) It also removes the previous drawn line, so you
6# don't see double lines.
7#
8#  redraw trackbar only works on irssi 0.8.17 or higher.
9#
10##
11
12## Usage:
13#
14#     The script works right out of the box, but if you want you can change
15#     the working by /set'ing the following variables:
16#
17#    Setting:     trackbar_style
18#    Description: This setting will be the color of your trackbar line.
19#                 By default the value will be '%K', only Irssi color
20#                 formats are allowed. If you don't know the color formats
21#                 by heart, you can take a look at the formats documentation.
22#                 You will find the proper docs on http://www.irssi.org/docs.
23#
24#    Setting:     trackbar_string
25#    Description: This is the string that your line will display. This can
26#                 be multiple characters or just one. For example: '~-~-'
27#                 The default setting is '-'.
28#                 Here are some unicode characters you can try:
29#                     "───" => U+2500 => a line
30#                     "═══" => U+2550 => a double line
31#                     "━━━" => U+2501 => a wide line
32#                     "▭  " => U+25ad => a white rectangle
33#
34#    Setting:     trackbar_use_status_window
35#    Description: If this setting is set to OFF, Irssi won't print a trackbar
36#                 in the statuswindow
37#
38#    Setting:     trackbar_ignore_windows
39#    Description: A list of windows where no trackbar should be printed
40#
41#    Setting:     trackbar_print_timestamp
42#    Description: If this setting is set to ON, Irssi will print the formatted
43#                 timestamp in front of the trackbar.
44#
45#    Setting:     trackbar_require_seen
46#    Description: Only clear the trackbar if it has been scrolled to.
47#
48#    Setting:     trackbar_all_manual
49#    Description: Never clear the trackbar until you do /mark.
50#
51#     /mark is a command that will redraw the line at the bottom.
52#
53#    Command:     /trackbar, /trackbar goto
54#    Description: Jump to where the trackbar is, to pick up reading
55#
56#    Command:     /trackbar keep
57#    Description: Keep this window's trackbar where it is the next time
58#                 you switch windows (then this flag is cleared again)
59#
60#    Command:     /mark, /trackbar mark
61#    Description: Remove the old trackbar and mark the bottom of this
62#                 window with a new trackbar
63#
64#    Command:     /trackbar markvisible
65#    Description: Like mark for all visible windows
66#
67#    Command:     /trackbar markall
68#    Description: Like mark for all windows
69#
70#    Command:     /trackbar remove
71#    Description: Remove this window's trackbar
72#
73#    Command:     /trackbar removeall
74#    Description: Remove all windows' trackbars
75#
76#    Command:     /trackbar redraw
77#    Description: Force redraw of trackbars
78#
79##
80
81##
82#
83# For bugreports and other improvements contact one of the authors.
84#
85#    This program is free software; you can redistribute it and/or modify
86#    it under the terms of the GNU General Public License as published by
87#    the Free Software Foundation; either version 2 of the License, or
88#    (at your option) any later version.
89#
90#    This program is distributed in the hope that it will be useful,
91#    but WITHOUT ANY WARRANTY; without even the implied warranty of
92#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
93#    GNU General Public License for more details.
94#
95#    You should have received a copy of the GNU General Public License
96#    along with this script; if not, write to the Free Software
97#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
98#
99##
100
101use strict;
102use warnings;
103use vars qw($VERSION %IRSSI);
104
105$VERSION = "2.6"; # c99d17e529ad9ce
106
107%IRSSI = (
108    authors     => "Peter 'kinlo' Leurs, Uwe Dudenhoeffer, " .
109                   "Michiel Holtkamp, Nico R. Wohlgemuth, " .
110                   "Geert Hauwaerts",
111    contact     => 'peter@pfoe.be',
112    patchers    => 'Johan Kiviniemi (UTF-8), Uwe Dudenhoeffer (on-upgrade-remove-line)',
113    name        => 'trackbar',
114    description => 'Shows a bar where you have last read a window.',
115    license     => 'GNU General Public License',
116    url         => 'http://www.pfoe.be/~peter/trackbar/',
117    commands    => 'trackbar',
118);
119
120## Comments and remarks.
121#
122# This script uses settings.
123# Use /SET  to change the value or /TOGGLE to switch it on or off.
124#
125#
126#    Tip:     The command 'trackbar' is very usefull if you bind that to a key,
127#             so you can easily jump to the trackbar. Please see 'help bind' for
128#             more information about keybindings in Irssi.
129#
130#    Command: /BIND meta2-P key F1
131#             /BIND F1 command trackbar
132#
133##
134
135## Bugfixes and new items in this rewrite.
136#
137# * Remove all the trackbars before upgrading.
138# * New setting trackbar_use_status_window to control the statuswindow trackbar.
139# * New setting trackbar_print_timestamp to print a timestamp or not.
140# * New command 'trackbar' to scroll up to the trackbar.
141# * When resizing your terminal, Irssi will update all the trackbars to the new size.
142# * When changing trackbar settings, change all the trackbars to the new settings.
143# * New command 'trackbar mark' to draw a new trackbar (The old '/mark').
144# * New command 'trackbar markall' to draw a new trackbar in each window.
145# * New command 'trackbar remove' to remove the trackbar from the current window.
146# * New command 'trackbar removeall' to remove all the trackbars.
147# * Don't draw a trackbar in empty windows.
148# * Added a version check to prevent Irssi redraw errors.
149# * Fixed a bookmark NULL versus 0 bug.
150# * Fixed a remove-line bug in Uwe Dudenhoeffer his patch.
151# * New command 'help trackbar' to display the trackbar commands.
152# * Fixed an Irssi startup bug, now processing each auto-created window.
153#
154##
155
156## Known bugs and the todolist.
157#
158#    Todo: * Instead of drawing a line, invert the line.
159#
160##
161
162## Authors:
163#
164#   - Main maintainer & author: Peter 'kinlo' Leurs
165#   - Many thanks to Timo 'cras' Sirainen for placing me on my way
166#   - on-upgrade-remove-line patch by Uwe Dudenhoeffer
167#   - trackbar resizing by Michiel Holtkamp (02 Jul 2012)
168#   - scroll to trackbar, window excludes, and timestamp options by Nico R.
169#     Wohlgemuth (22 Sep 2012)
170#
171##
172
173## Version history:
174#
175#  2.7: - add /set trackbar_all_manual option
176#  2.5: - merge back on scripts.irssi.org
177#       - fix /trackbar redraw broken in 2.4
178#       - fix legacy encodings
179#       - add workaround for irssi issue #271
180#  2.4: - add support for horizontal splits
181#  2.3: - add some features for seen tracking using other scripts
182#  2.0: - big rewrite based on 1.4
183#         * removed /tb, you can have it with /alias tb trackbar if you want
184#         * subcommand and settings changes:
185#              /trackbar vmark  => /trackbar markvisible
186#              /trackbar scroll => /trackbar goto (or just /trackbar)
187#              /trackbar help   => /help trackbar
188#              /set trackbar_hide_windows => /set trackbar_ignore_windows
189#              /set trackbar_timestamp    => /set trackbar_print_timestamp
190#         * magic line strings were removed, just paste the unicode you want!
191#         * trackbar_timestamp_styled is not currently supported
192#  1.9: - add version guard
193#  1.8: - sub draw_bar
194#  1.7: - Added /tb scroll, trackbar_hide_windows, trackbar_timestamp_timestamp
195#         and trackbar_timestamp_styled
196#  1.6: - Work around Irssi resize bug, please do /upgrade! (see below)
197#  1.5: - Resize trackbars in all windows when terminal is resized
198#  1.4: - Changed our's by my's so the irssi script header is valid
199#       - Removed utf-8 support.  In theory, the script should work w/o any
200#         problems for utf-8, just set trackbar_string to a valid utf-8 character
201#         and everything *should* work.  However, this script is being plagued by
202#         irssi internal bugs.  The function Irssi::settings_get_str does NOT handle
203#         unicode strings properly, hence you will notice problems when setting the bar
204#         to a unicode char.  For changing your bar to utf-8 symbols, read the line sub.
205#  1.3: - Upgrade now removes the trackbars.
206#       - Some code cleanups, other defaults
207#       - /mark sets the line to the bottom
208#  1.2: - Support for utf-8
209#       - How the bar looks can now be configured with trackbar_string
210#         and trackbar_style
211#  1.1: - Fixed bug when closing window
212#  1.0: - Initial release
213#
214##
215
216use Irssi;
217use Irssi::TextUI;
218use Encode;
219
220use POSIX qw(strftime);
221
222sub cmd_help {
223    my ($args) = @_;
224    if ($args =~ /^trackbar *$/i) {
225        print CLIENTCRAP <<HELP
226%9Syntax:%9
227
228TRACKBAR
229TRACKBAR GOTO
230TRACKBAR KEEP
231TRACKBAR MARK
232TRACKBAR MARKVISIBLE
233TRACKBAR MARKALL
234TRACKBAR REMOVE
235TRACKBAR REMOVEALL
236TRACKBAR REDRAW
237
238%9Parameters:%9
239
240    GOTO:        Jump to where the trackbar is, to pick up reading
241    KEEP:        Keep this window's trackbar where it is the next time
242                 you switch windows (then this flag is cleared again)
243    MARK:        Remove the old trackbar and mark the bottom of this
244                 window with a new trackbar
245    MARKVISIBLE: Like mark for all visible windows
246    MARKALL:     Like mark for all windows
247    REMOVE:      Remove this window's trackbar
248    REMOVEALL:   Remove all windows' trackbars
249    REDRAW:      Force redraw of trackbars
250
251%9Description:%9
252
253    Manage a trackbar. Without arguments, it will scroll up to the trackbar.
254
255%9Examples:%9
256
257    /TRACKBAR MARK
258    /TRACKBAR REMOVE
259HELP
260    }
261}
262
263Irssi::theme_register([
264    'trackbar_loaded', '%R>>%n %_Scriptinfo:%_ Loaded $0 version $1 by $2.',
265    'trackbar_wrong_version', '%R>>%n %_Trackbar:%_ Please upgrade your client to 0.8.17 or above if you would like to use this feature of trackbar.',
266    'trackbar_all_removed', '%R>>%n %_Trackbar:%_ All the trackbars have been removed.',
267    'trackbar_not_found', '%R>>%n %_Trackbar:%_ No trackbar found in this window.',
268]);
269
270my $old_irssi = Irssi::version < 20140701;
271sub check_version {
272    if ($old_irssi) {
273        Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_wrong_version');
274        return;
275    } else {
276        return 1;
277    }
278}
279
280sub is_utf8 {
281    lc Irssi::settings_get_str('term_charset') eq 'utf-8'
282}
283
284my (%config, %keep_trackbar, %unseen_trackbar);
285
286sub remove_one_trackbar {
287    my $win = shift;
288    my $view = shift || $win->view;
289    my $line = $view->get_bookmark('trackbar');
290    if (defined $line) {
291        my $bottom = $view->{bottom};
292        $view->remove_line($line);
293        $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
294        $view->redraw;
295    }
296}
297
298sub add_one_trackbar {
299    my $win = shift;
300    my $view = shift || $win->view;
301    $win->print(line($win->{width}), MSGLEVEL_NEVER);
302    $view->set_bookmark_bottom('trackbar');
303    $unseen_trackbar{ $win->{_irssi} } = 1;
304    Irssi::signal_emit("window trackbar added", $win);
305    $view->redraw;
306}
307
308sub update_one_trackbar {
309    my $win = shift;
310    my $view = shift || $win->view;
311    my $force = shift;
312    my $ignored = win_ignored($win, $view);
313    remove_one_trackbar($win, $view)
314	if $force || !defined $force || !$ignored;
315    add_one_trackbar($win, $view)
316	if $force || !$ignored;
317}
318
319sub win_ignored {
320    my $win = shift;
321    my $view = shift || $win->view;
322    return 1 unless $view->{buffer}{lines_count};
323    return 1 if $win->{name} eq '(status)' && !$config{use_status_window};
324    no warnings 'uninitialized';
325    return 1 if grep { $win->{name} eq $_ || $win->{refnum} eq $_
326			   || $win->get_active_name eq $_ } @{ $config{ignore_windows} };
327    return 0;
328}
329
330sub sig_window_changed {
331    my ($newwindow, $oldwindow) = @_;
332    return unless $oldwindow;
333    redraw_one_trackbar($newwindow) unless $old_irssi;
334    trackbar_update_seen($newwindow);
335    return if delete $keep_trackbar{ $oldwindow->{_irssi} };
336    trackbar_update_seen($oldwindow);
337    return if $config{require_seen} && $unseen_trackbar{ $oldwindow->{_irssi } };
338    return if $config{all_manual};
339    update_one_trackbar($oldwindow, undef, 0);
340}
341
342sub trackbar_update_seen {
343    my $win = shift;
344    return unless $win;
345    return unless $unseen_trackbar{ $win->{_irssi} };
346
347    my $view = $win->view;
348    my $line = $view->get_bookmark('trackbar');
349    unless ($line) {
350        delete $unseen_trackbar{ $win->{_irssi} };
351        Irssi::signal_emit("window trackbar seen", $win);
352        return;
353    }
354    my $startline = $view->{startline};
355    return unless $startline;
356
357    if ($startline->{info}{time} < $line->{info}{time}
358            || $startline->{_irssi} == $line->{_irssi}) {
359        delete $unseen_trackbar{ $win->{_irssi} };
360        Irssi::signal_emit("window trackbar seen", $win);
361    }
362}
363
364sub screen_length;
365{ local $@;
366  eval { require Text::CharWidth; };
367  unless ($@) {
368      *screen_length = sub { Text::CharWidth::mbswidth($_[0]) };
369  }
370  else {
371      *screen_length = sub {
372          my $temp = shift;
373          Encode::_utf8_on($temp) if is_utf8();
374          length($temp)
375      };
376  }
377}
378
379{ my %strip_table = (
380    (map { $_ => '' } (split //, '04261537' .  'kbgcrmyw' . 'KBGCRMYW' . 'U9_8I:|FnN>#[' . 'pP')),
381    (map { $_ => $_ } (split //, '{}%')),
382   );
383  sub c_length {
384      my $o = Irssi::strip_codes($_[0]);
385      $o =~ s/(%(%|Z.{6}|z.{6}|X..|x..|.))/exists $strip_table{$2} ? $strip_table{$2} :
386          $2 =~ m{x(?:0[a-f]|[1-6][0-9a-z]|7[a-x])|z[0-9a-f]{6}}i ? '' : $1/gex;
387      screen_length($o)
388  }
389}
390
391sub line {
392    my ($width, $time)  = @_;
393    my $string = $config{string};
394    $string = ' ' unless length $string;
395    $time ||= time;
396
397    Encode::_utf8_on($string) if is_utf8();
398    my $length = c_length($string);
399
400    my $format = '';
401    if ($config{print_timestamp}) {
402        $format = $config{timestamp_str};
403        $format =~ y/%/\01/;
404        $format =~ s/\01\01/%/g;
405        $format = strftime($format, localtime $time);
406        $format =~ y/\01/%/;
407    }
408
409    my $times = $width / $length;
410    $times += 1 if $times != int $times;
411    my $style = "$config{style}";
412    Encode::_utf8_on($style) if is_utf8();
413    $format .= $style;
414    $width -= c_length($format);
415    $string x= $times;
416    chop $string while length $string && c_length($string) > $width;
417    return $format . $string;
418}
419
420sub remove_all_trackbars {
421    for my $window (Irssi::windows) {
422        next unless ref $window;
423        remove_one_trackbar($window);
424    }
425}
426
427sub UNLOAD {
428    remove_all_trackbars();
429}
430
431sub redraw_one_trackbar {
432    my $win = shift;
433    my $view = $win->view;
434    my $line = $view->get_bookmark('trackbar');
435    return unless $line;
436    my $bottom = $view->{bottom};
437    $win->print_after($line, MSGLEVEL_NEVER, line($win->{width}, $line->{info}{time}),
438		      $line->{info}{time});
439    $view->set_bookmark('trackbar', $win->last_line_insert);
440    $view->remove_line($line);
441    $win->command('^scrollback end') if $bottom && !$win->view->{bottom};
442    $view->redraw;
443}
444
445sub redraw_trackbars {
446    return unless check_version();
447    for my $win (Irssi::windows) {
448        next unless ref $win;
449        redraw_one_trackbar($win);
450    }
451}
452
453sub goto_trackbar {
454    my $win = Irssi::active_win;
455    my $line = $win->view->get_bookmark('trackbar');
456
457    if ($line) {
458        $win->command("scrollback goto ". strftime("%d %H:%M:%S", localtime($line->{info}{time})));
459    } else {
460        $win->printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_not_found');
461    }
462}
463
464sub cmd_mark {
465    update_one_trackbar(Irssi::active_win, undef, 1);
466}
467
468sub cmd_markall {
469    for my $window (Irssi::windows) {
470        next unless ref $window;
471        update_one_trackbar($window);
472    }
473}
474
475sub signal_stop {
476    Irssi::signal_stop;
477}
478
479sub cmd_markvisible {
480    my @wins = Irssi::windows;
481    my $awin =
482        my $bwin = Irssi::active_win;
483    my $awin_counter = 0;
484    Irssi::signal_add_priority('window changed' => 'signal_stop', -99);
485    do {
486        Irssi::active_win->command('window up');
487        $awin = Irssi::active_win;
488        update_one_trackbar($awin);
489        ++$awin_counter;
490    } until ($awin->{refnum} == $bwin->{refnum} || $awin_counter >= @wins);
491    Irssi::signal_remove('window changed' => 'signal_stop');
492}
493
494sub cmd_trackbar_remove_one {
495    remove_one_trackbar(Irssi::active_win);
496}
497
498sub cmd_remove_all_trackbars {
499    remove_all_trackbars();
500    Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_all_removed');
501}
502
503sub cmd_keep_once {
504    $keep_trackbar{ Irssi::active_win->{_irssi} } = 1;
505}
506
507sub trackbar_runsub {
508    my ($data, $server, $item) = @_;
509    $data =~ s/\s+$//g;
510
511    if ($data) {
512        Irssi::command_runsub('trackbar', $data, $server, $item);
513    } else {
514        goto_trackbar();
515    }
516}
517
518sub update_config {
519    my $was_status_window = $config{use_status_window};
520    $config{style} = Irssi::settings_get_str('trackbar_style');
521    $config{string} = Irssi::settings_get_str('trackbar_string');
522    $config{require_seen} = Irssi::settings_get_bool('trackbar_require_seen');
523    $config{all_manual} = Irssi::settings_get_bool('trackbar_all_manual');
524    $config{ignore_windows} = [ split /[,\s]+/, Irssi::settings_get_str('trackbar_ignore_windows') ];
525    $config{use_status_window} = Irssi::settings_get_bool('trackbar_use_status_window');
526    $config{print_timestamp} = Irssi::settings_get_bool('trackbar_print_timestamp');
527    if (defined $was_status_window && $was_status_window != $config{use_status_window}) {
528        if (my $swin = Irssi::window_find_name('(status)')) {
529            if ($config{use_status_window}) {
530                update_one_trackbar($swin);
531            }
532            else {
533                remove_one_trackbar($swin);
534            }
535        }
536    }
537    if ($config{print_timestamp}) {
538        my $ts_format = Irssi::settings_get_str('timestamp_format');
539        my $ts_theme = Irssi::current_theme->get_format('fe-common/core', 'timestamp');
540        my $render_str = Irssi::current_theme->format_expand($ts_theme);
541        (my $ts_escaped = $ts_format) =~ s/([%\$])/$1$1/g;
542        $render_str =~ s/(?|\$(.)(?!\w)|\$\{(\w+)\})/$1 eq 'Z' ? $ts_escaped : $1/ge;
543        $config{timestamp_str} = $render_str;
544    }
545    redraw_trackbars() unless $old_irssi;
546}
547
548Irssi::settings_add_str('trackbar', 'trackbar_string', is_utf8() ? "\x{2500}" : '-');
549Irssi::settings_add_str('trackbar', 'trackbar_style', '%K');
550Irssi::settings_add_str('trackbar', 'trackbar_ignore_windows', '');
551Irssi::settings_add_bool('trackbar', 'trackbar_use_status_window', 1);
552Irssi::settings_add_bool('trackbar', 'trackbar_print_timestamp', 0);
553Irssi::settings_add_bool('trackbar', 'trackbar_require_seen', 0);
554Irssi::settings_add_bool('trackbar', 'trackbar_all_manual', 0);
555
556update_config();
557
558Irssi::signal_add_last( 'mainwindow resized' => 'redraw_trackbars')
559    unless $old_irssi;
560
561Irssi::signal_register({'window trackbar added' => [qw/Irssi::UI::Window/]});
562Irssi::signal_register({'window trackbar seen' => [qw/Irssi::UI::Window/]});
563Irssi::signal_register({'gui page scrolled' => [qw/Irssi::UI::Window/]});
564Irssi::signal_add_last('gui page scrolled' => 'trackbar_update_seen');
565
566Irssi::signal_add('setup changed' => 'update_config');
567Irssi::signal_add_priority('session save' => 'remove_all_trackbars', Irssi::SIGNAL_PRIORITY_HIGH-1);
568
569Irssi::signal_add('window changed' => 'sig_window_changed');
570
571Irssi::command_bind('trackbar goto'      => 'goto_trackbar');
572Irssi::command_bind('trackbar keep'      => 'cmd_keep_once');
573Irssi::command_bind('trackbar mark'      => 'cmd_mark');
574Irssi::command_bind('trackbar markvisible' => 'cmd_markvisible');
575Irssi::command_bind('trackbar markall'   => 'cmd_markall');
576Irssi::command_bind('trackbar remove'    => 'cmd_trackbar_remove_one');
577Irssi::command_bind('trackbar removeall' => 'cmd_remove_all_trackbars');
578Irssi::command_bind('trackbar redraw'    => 'redraw_trackbars');
579Irssi::command_bind('trackbar'           => 'trackbar_runsub');
580Irssi::command_bind('mark'               => 'cmd_mark');
581Irssi::command_bind_last('help' => 'cmd_help');
582
583Irssi::printformat(MSGLEVEL_CLIENTCRAP, 'trackbar_loaded', $IRSSI{name}, $VERSION, $IRSSI{authors});
584
585# workaround for issue #271
586{ package Irssi::Nick }
587