use strict; use warnings; use Irssi; use Irssi::TextUI; # for sbar_items_redraw use Data::Dumper; # TODO: maybe eval { use Term::Size } and use tthat if poss. our $VERSION = "0.2"; our %IRSSI = ( authors => "shabble", contact => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode', name => "overlays", description => "Library script for drawing overlays on irssi UI", license => "MIT", changed => "24/7/2010" ); # overlay := { $num1 => line1, $num2 => line2 } # line := [ region, region, region ] # region := { start => x, end => y, ...? } my @regions; my ($term_w, $term_h) = (0, 0); my $overlay_active = 0; my $prompt_len = 5; my $region_id = 0; sub DEBUG () { 1 } sub update_terminal_size { my @stty_data = qx/stty -a/; my $line = $stty_data[0]; # linux # speed 38400 baud; rows 36; columns 126; line = 0; if ($line =~ m/rows (\d+); columns (\d+);/) { $term_h = $1; $term_w = $2; # osx # speed 9600 baud; 40 rows; 235 columns; } elsif ($line =~ m/(\d+) rows; (\d+) columns;/) { $term_h = $1; $term_w = $2; } else { # guess? $term_h = 24; $term_w = 80; } print "Terminal detected as $term_w cols by $term_h rows" if DEBUG; } sub find_region { my ($pos) = @_; foreach my $region (@regions) { next unless $pos > $region->{start}; return $region if $pos <= $region->{end}; } print "failed to find region for pos: $pos"; return undef; } sub redraw_overlay { # TODO: we can't assume the active win is the only one with overlays. #Irssi::active_win->view->redraw(); foreach my $region (@regions) { if ($region->{draw}) { my $str = $region->{style} . $region->{text} . '%n'; my $x_offset = $region->{start} + $prompt_len; Irssi::gui_printtext($x_offset, $term_h, $str); } } # my $inp = Irssi::parse_special('$L'); # Irssi::gui_input_set($inp . ''); } sub augment_redraw { #print "Redraw called" if DEBUG; #redraw_overlay(); Irssi::timeout_add_once(20, \&redraw_overlay, 0); } sub intercept_keypress { my $key = shift; # intercept C-l for redraw, and force it to call # /redraw instead of the internal function. if ($key == 12) { # C-L print "C-l pressed" if DEBUG; Irssi::command("redraw"); Irssi::signal_stop; } } sub observe_keypress { my $key = shift; print "Key " . chr ($key) . " pressed, pos: " . _pos(); if ($key > 31 && $key <= 127) { # see if we're still appending to the last region: #print "Observed printable key: " . chr($key) if DEBUG; #print ''; my $latest_region = $regions[-1]; $latest_region = {} unless defined $latest_region; my $pos = _pos(); my $reg = find_region($pos); if (defined $reg) { insert_into_region($key, $reg); } elsif (not $latest_region->{open}) { my $style = $overlay_active?'%_':''; new_region($style, $key); } else { insert_into_region($key, $latest_region); } #Irssi::signal_stop; # TODO: if the cursor pos is inside a region, handle it # extend the region on addition? redraw_overlay(); } } sub init { die "This script requires uberprompt.pl" unless script_is_loaded('uberprompt'); Irssi::signal_add_last ('command redraw', \&augment_redraw); Irssi::signal_add_first('gui key pressed', \&intercept_keypress); Irssi::signal_add_last ('gui key pressed', \&observe_keypress); Irssi::signal_add ('terminal resized', \&update_terminal_size); Irssi::signal_add_first('gui print text finished', \&augment_redraw); setup_bindings(); Irssi::signal_add('prompt changed', sub { print "Updated prompt length: $_[1]"; $prompt_len = $_[1]; }); Irssi::signal_emit('prompt length request'); update_terminal_size(); } sub setup_bindings { Irssi::command_bind('region_start', \®ion_toggle); Irssi::command_bind('region_clear', \®ion_clear); Irssi::command_bind('region_print', \&print_regions); Irssi::command('/bind ^C /region_start'); ##Irssi::command('/bind ^D /region_clear'); Irssi::command('/bind ^D /region_print'); } ################################################################################ sub escape_style { my ($style) = @_; $style =~ s/%/%%/g; return $style; } sub print_regions { foreach my $reg (@regions) { printf("start: %d end: %d style: %s, text: \"%s\", open: %d, draw: %d", $reg->{start}, $reg->{end}, escape_style($reg->{style}), $reg->{text}, $reg->{open}, $reg->{draw}); } } sub new_region { my ($style, $key) = @_; my $new_id = $region_id++; _debug("Creating new Region: $new_id"); my $new_region = { id => $region_id++, text => '', start => _pos(), end => _pos(), style => $style, open => 1, draw => 1, }; insert_into_region($key, $new_region); push @regions, $new_region; } sub delete_region { my ($region) = @_; my $idx = 0; foreach my $i (0..$#regions) { if ($regions[$i]->{id} == $region->{id}) { $idx = $i; last; } } print "Deleting region: $idx"; splice(@regions, $idx, 1); # remove the selected region. } sub insert_into_region { my ($key, $region) = @_; my $pos = _pos(); if ($key == 127) { # backspace substr($region->{text}, -1, 1) = ''; $region->{end}--; if ($region->{end} <= $region->{start}) { delete_region($region); } } else { printf("text: '%s', pos: %d, offset: %d", $region->{text}, $pos, $pos - $region->{start}); if ( $region->{end} < $pos) { $region->{text} .= chr $key; } else { substr($region->{text}, $pos - $region->{start}, 0) = chr $key; } $region->{end}++; } } sub region_clear { @regions = (); Irssi::signal_emit('command redraw'); } sub region_toggle { $overlay_active = not $overlay_active; _debug("Region is %sactive", $overlay_active?'':'in'); #@regions = (); # terminate the previous region my $region = find_region(_pos()); if (defined $region) { $region->{open} = 0; $region->{end} = _pos(); debug("Region closed: %d-%d", $region->{start}, $region->{end}); } } sub script_is_loaded { my $name = shift; _debug("Checking if $name is loaded"); no strict 'refs'; my $retval = %{ "Irssi::Script::${name}::" }; use strict 'refs'; return $retval; } sub _pos { return Irssi::gui_input_get_pos(); } sub _input { return Irssi::parse_special('$L'); } sub _debug { printf @_ if DEBUG(); } init();