aboutsummaryrefslogblamecommitdiffstats
path: root/prompt_info/uberprompt.pl
blob: 3ef220e15715a4dbd5772062e13e1e1f9be86127 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13



                                                                          
          







                                                                         



                                                                    



                                                                           

                                     

                                                                                

                                                                              


                                            





                                                                                 

                                                   

                                                         

                    

                                 


                                                                          

                                                                                      



                                                                                   




                                                                                  




                                                                          
 
                                                                            
 
                                                            











                                                                               





                                                                                   























































                                                                             





                                                                        







                                                                  





















                                                                               





                                                       
 

                       
                     



                                                                            
                                   

                                                                  
                            


                                 

                      
                            
 
                         



                                 
 



                                                                            

                     


                          


              


                                             
 
                           
                                    
                                                             

                                                           
 










                                                                



















                                                                                      
                                                                                
                                                          


                                                                                       















                                                                                  
 
            


             

                        


                                    



                                                       
 
          
                                                                       
 



                                                         
                                                                             
                                                                  
                                                                      

                                                                         
 

                                                               
                                                              
                                                        
                                       


                                                                                
 


                                                                      

                                               
                                                          



                                  
                                         





                                                                          

                                                                          
                                                            
 





                                                           
 
                                                                     
                                                                  




                                                                   


                                                                         

 




                                                                        







                                                                     







                                                                                    












                                               
                                        
 
                                           
                              
                                      





                                                                     





                               

                     

                                                                        

                                                                  






                                                                      
                                                           
 
                                 
                                              
                              
                                             
                                                                  






                                                                      
                                                               


                                                                  


     

                          


                      
                                                                      

 
                           
                          
 


                                                                    
                                                               
 


                                                                             
 

                             
 
                                        
                                         

                             

 



                            



                           
                

 
                              
 
                                   
                        
 
                                                 
                                        
                                         
            
                                   

     

                                                                 
 
                                                                     
                                                                      
 







                                                                           
 





                                                                      
 

                                                       
 
            








                                                                  
     
 
                                                                        
 
 
                                                     
 
                                                         

                                                                    
 


                                                               







                                            
                                                                        
 
                





                                                

                           
                                             





                                                      
                                                
                                                                    
 
                                               



                          
                                                    
 
                                              
 
                                     

 




                                       
                                                          

                                                 
                                                       
 
                                              


                             
# This script replaces the default prompt status-bar item with one capable
# of displaying additional information, under either user control or via
# scripts.
#
# INSTALL:
#
# Place script in ~/.irssi/scripts/ and potentially symlink into autorun/
# to ensure it starts at irssi startup.
#
# If not using autorun, manually load the script via:
#
# /script load uberprompt.pl
#
# If you have a custom prompt format, you may need to copy it to the
# uberprompt_format setting. See below for details.
#
# USAGE:
#
# Although the script is designed primarily for other scripts to set
# status information into the prompt, the following commands are available:
#
# TODO: Document positional settings.
#
# /prompt set   - sets the prompt to the given argument. $p in the argument will
#                 be replaced by the original prompt content.
#                 A parameter corresponding to the UP_* constants listed below
#                 is required, in the format `/prompt set -inner Hello!'
#
# /prompt set [-inner|-pre|-post|only] <msg>
#
# /prompt clear - clears the additional data provided to the prompt.
# /prompt on    - enables the uberprompt (things may get confused if this is used
#                 whilst the prompt is already enabled)
# /prompt off   - restore the original irssi prompt and prompt_empty statusbars.
#                 unloading the script has the same effect.
#
# /help prompt  - show help for uberprompt commands
#
# Additionally, the format for the prompt can be set via:
#
# UBERPROMPT FORMAT:
#
# /set uberprompt_format <format>
#
# The default is [$*$uber], which is the same as the default provided in
# default.theme. $uber is a placeholder variable to contain your additions
# to the prompt when using the -inner mode.
# Changing this setting will update the prompt immediately, unlike editing your theme.
#
# An additional variable available within this format is '$uber', which expands to
# the content of prompt data provided with the UP_INNER placement argument. For all
# other placement arguments, it will expand to the empty string ''.
#
# NOTE: this setting completely overrides the prompt="..." line in your .theme
#       file, and may cause unexpected behaviour if your theme wishes to set a
#       different form of prompt. It can be simply copied from the theme file into
#       the above setting.
#
# Usage from other Scripts: signal 'change prompt' => 'string' => position
#
# eg:
#
# signal_emit 'change prompt' 'some_string', UberPrompt::UP_INNER;
#
# will set the prompt to include that content, by default '[$* some_string]'
#
# The possible position arguments are the following strings:
#
# UP_PRE   - place the provided string before the prompt -- $string$prompt
# UP_INNER - place the provided string inside the prompt -- {prompt $* $string}
# UP_POST  - place the provided string after the prompt  -- $prompt$string
# UP_ONLY  - replace the prompt with the provided string -- $string
#
# All strings may use the special variable '$prompt' to include the prompt
# verbatim at that position in the string.  It is probably only useful for
# the UP_ONLY mode however. '$prompt_nt' will include the prompt, minus any
# trailing whitespace.
#
# NOTIFICATIONS:
#
# You can also be notified when the prompt changes in response to the previous
# signal or manual commands via:
#
# signal_add 'prompt changed', sub { my ($text, $len) = @_; ... do something ... };
#
#
# NOTES FOR SCRIPT WRITERS:
#
# The following code snippet can be used within your own script as a preamble
# to ensure that uberprompt is loaded before your script to avoid
# any issues with load order.  It first checks if uberprompt is loaded, and
# if not, attempts to load it.  If the load fails, the script will die
# with an error message, otherwise it will call your app_init() function.
#
# ---- start of snippet ----

# my $DEBUG_ENABLED = 0;
# sub DEBUG () { $DEBUG_ENABLED }
#
# # check we have uberprompt loaded.
#
# sub script_is_loaded {
#     my $name = shift;
#     print "Checking if $name is loaded" if DEBUG;
#     no strict 'refs';
#     my $retval = defined %{ "Irssi::Script::${name}::" };
#     use strict 'refs';
#
#     return $retval;
# }
#
# if (not script_is_loaded('uberprompt')) {
#
#     print "This script requires 'uberprompt.pl' in order to work. "
#       . "Attempting to load it now...";
#
#     Irssi::signal_add('script error', 'load_uberprompt_failed');
#     Irssi::command("script load uberprompt.pl");
#
#     unless(script_is_loaded('uberprompt')) {
#         load_uberprompt_failed("File does not exist");
#     }
#     app_init();
# } else {
#    app_init();
# }
#
# sub load_uberprompt_failed {
#     Irssi::signal_remove('script error', 'load_prompt_failed');
#
#     print "Script could not be loaded. Script cannot continue. "
#       . "Check you have uberprompt.pl installed in your path and "
#         .  "try again.";
#
#     die "Script Load Failed: " . join(" ", @_);
# }
#
# ---- end of snippet ----
#
#
#
# Bugs:
#
# * Resizing the terminal rapidly whilst using this script in debug mode
#    may cause irssi to crash. See bug report at
#    http://bugs.irssi.org/index.php?do=details&task_id=772 for details.
#
# TODO:
#
# * report failure (somehow) to clients if hte prompt is disabled.
# * fix issue at autorun startup with sbar item doesn't exist.
#
#
#
#
# LICENCE:
#
# Copyright (c) 2010 Tom Feist
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use strict;
use warnings;

use Irssi;
use Irssi::TextUI;              # for sbar_items_redraw
use Data::Dumper;

{ package Irssi::Nick }

our $VERSION = "0.2";
our %IRSSI =
  (
   authors         => "shabble",
   contact         => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
   name            => "uberprompt",
   description     => "Helper script for dynamically adding text "
   . "into the input-bar prompt.",
   license         => "MIT",
   changed         => "24/7/2010"
  );


my $DEBUG_ENABLED = 0;
sub DEBUG { $DEBUG_ENABLED }

my $prompt_data     = '';
my $prompt_data_pos = 'UP_INNER';

my $prompt_last     = '';
my $prompt_format   = '';

# flag to indicate whether rendering of hte prompt should allow the replaces
# theme formats to be applied to the content.
my $use_replaces = 0;

my $emit_request = 0;

my $expando_refresh_timer;
my $expando_vars = {};

pre_init();

sub pre_init {
    Irssi::command('statusbar prompt reset');
    init();
}

sub prompt_subcmd_handler {
    my ($data, $server, $item) = @_;
    #$data =~ s/\s+$//g;         # strip trailing whitespace.
    Irssi::command_runsub('prompt', $data, $server, $item);
}

sub _error($) {
    my ($msg) = @_;
    Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTERROR);
}

sub _debug_print($) {
    return unless DEBUG;
    my ($msg) = @_;
    Irssi::active_win->print($msg, Irssi::MSGLEVEL_CLIENTCRAP);
}

sub _print_help {
    my ($args) = @_;
    if ($args =~ m/^\s*prompt/i) {
        my @help_lines =
          (
           "",
           "PROMPT ON",
           "PROMPT OFF",
           "PROMPT CLEAR",
           "PROMPT SET { -pre | -post | -only | -inner } <content>",
           "",
           "Commands for manipulating the UberPrompt.",
           "",
           "/PROMPT ON    enables uberprompt, replacing the existing prompt ",
           "              statusbar-item",
           "/PROMPT OFF   disables it, and restores the original prompt item",
           "/PROMPT CLEAR resets the value of any additional data set by /PROMPT SET",
           "              or a script",
           "/PROMPT SET   changes the contents of the prompt, according to the mode",
           "              and content provided.",
           "      { -inner sets the value of the \$uber psuedo-variable in the",
           "             /set uberprompt_format setting.",
           "      | -pre places the content before the current prompt string",
           "      | -post places the content after the prompt string",
           "      | -only replaces the entire prompt contents with the given string }",
           "",
           "See Also:",
           '',
           '/SET uberprompt_format    -- defaults to [$*$uber]',
           "/SET uberprompt_autostart -- determines whether /PROMPT ON is run",
           "                             automatically when the script loads",
           "/set uberprompt_use_replaces -- toggles the use of the current theme",
           "                                \"replaces\" setting. Especially",
           "                                noticeable on brackets \"[ ]\" ",
           "",
          );

        Irssi::print($_, Irssi::MSGLEVEL_CLIENTCRAP) for @help_lines;
        Irssi::signal_stop;
    }
}

sub UNLOAD {
    deinit();
}

sub exp_lbrace() { '{' }
sub exp_rbrace() { '}' }

sub deinit {
    Irssi::expando_destroy('brace');
    # remove uberprompt and return the original ones.
    print "Removing uberprompt and restoring original";
    restore_prompt_items();
}

sub init {
    Irssi::statusbar_item_register('uberprompt', 0, 'uberprompt_draw');

    # TODO: flags to prevent these from being recomputed?
    Irssi::expando_create('lbrace', \&exp_lbrace, {});
    Irssi::expando_create('rbrace', \&exp_rbrace, {});

    Irssi::settings_add_str('uberprompt', 'uberprompt_format', '[$*$uber] ');
    Irssi::settings_add_bool('uberprompt', 'uberprompt_debug', 0);
    Irssi::settings_add_bool('uberprompt', 'uberprompt_autostart', 1);
    Irssi::settings_add_bool('uberprompt', 'uberprompt_use_replaces', 0);


    Irssi::command_bind("prompt",     \&prompt_subcmd_handler);
    Irssi::command_bind('prompt on',  \&replace_prompt_items);
    Irssi::command_bind('prompt off', \&restore_prompt_items);
    Irssi::command_bind('prompt set', \&cmd_prompt_set);
    Irssi::command_bind('prompt clear',
                        sub {
                            Irssi::signal_emit 'change prompt', '$p', 'UP_POST';
                        });

    my $prompt_set_args_format = "inner pre post only";
    Irssi::command_set_options('prompt set', $prompt_set_args_format);

    Irssi::command_bind('help', \&_print_help);

    Irssi::signal_add('setup changed', \&reload_settings);

    # intialise the prompt format.
    reload_settings();

    # make sure we redraw when necessary.
    Irssi::signal_add('window changed',             \&uberprompt_refresh);
    Irssi::signal_add('window name changed',        \&uberprompt_refresh);
    Irssi::signal_add('window changed automatic',   \&uberprompt_refresh);
    Irssi::signal_add('window item changed',        \&uberprompt_refresh);
    Irssi::signal_add('window item server changed', \&uberprompt_refresh);
    Irssi::signal_add('window server changed',      \&uberprompt_refresh);
    Irssi::signal_add('server nick changed',        \&uberprompt_refresh);

    Irssi::signal_add('nick mode changed', \&refresh_if_me);

    # install our statusbars if required.
    if (Irssi::settings_get_bool('uberprompt_autostart')) {
        replace_prompt_items();
    }

    # API signals

    Irssi::signal_register({'change prompt' => [qw/string string/]});
    Irssi::signal_add('change prompt' => \&change_prompt_handler);

    # other scripts (specifically overlay/visual) can subscribe to
    # this event to be notified when the prompt changes.
    # arguments are new contents (string), new length (int)
    Irssi::signal_register({'prompt changed' => [qw/string int/]});
    Irssi::signal_register({'prompt length request' => []});

    Irssi::signal_add('prompt length request', \&length_request_handler);
}

sub cmd_prompt_set {
    my $args = shift;
    my @options_list = Irssi::command_parse_options "prompt set", $args;
    if (@options_list) {
        my ($options, $rest) = @options_list;

        my @opt_modes = keys %$options;
        if (@opt_modes != 1) {
            _error '%_/prompt set%_ must specify exactly one mode of'
              . ' {-inner, -only, -pre, -post}';
            return;
        }

        my $mode = 'UP_' . uc($opt_modes[0]);

        Irssi::signal_emit 'change prompt', $rest, $mode;
    } else {
        _error ('%_/prompt set%_ must specify a mode {-inner, -only, -pre, -post}');
    }
}

sub refresh_if_me {
    my ($channel, $nick) = @_;

    return unless $channel and $nick;

    my $server = Irssi::active_server;
    my $window = Irssi::active_win;

    return unless $server and $window;

    my $my_chan = $window->{active}->{name};
    my $my_nick = $server->parse_special('$N');

    return unless $my_chan and $my_nick;

    _debug_print "Chan: $channel->{name}, "
     . "nick: $nick->{nick}, "
     . "me: $my_nick, chan: $my_chan";

    if ($my_chan eq $channel->{name} and $my_nick eq $nick->{nick}) {
        uberprompt_refresh();
    }
}

sub length_request_handler {
    $emit_request = 1;
    uberprompt_render_prompt();
    $emit_request = 0;
}

sub reload_settings {

    $use_replaces = Irssi::settings_get_bool('uberprompt_use_replaces');

    $DEBUG_ENABLED = Irssi::settings_get_bool('uberprompt_debug');

    if (DEBUG) {
        Irssi::signal_add 'prompt changed', 'debug_prompt_changed';
    } else {
        Irssi::signal_remove 'prompt changed', 'debug_prompt_changed';
    }


    my $new = Irssi::settings_get_str('uberprompt_format');

    if ($prompt_format ne $new) {
        _debug_print("Updated prompt format");
        $prompt_format = $new;
        $prompt_format =~ s/\$uber/\$\$uber/;
        Irssi::abstracts_register(['uberprompt', $prompt_format]);

        $expando_vars = {};

        # TODO: something clever here to check if we need to schedule
        # an update timer or something, rather than just refreshing on
        # every possible activity in init()
        while ($prompt_format =~ m/(?<!\$)(\$[A-Za-z,.:;][a-z_]*)/g) {
            _debug_print("Detected Irssi expando variable $1");
            my $var_name = substr $1, 1; # strip the $
            $expando_vars->{$var_name} = Irssi::parse_special($1);
        }
    }
}

sub debug_prompt_changed {
    my ($text, $len) = @_;

    $text =~ s/%/%%/g;

    print "DEBUG_HANDLER: Prompt Changed to: \"$text\", length: $len";
}

sub change_prompt_handler {
    my ($text, $pos) = @_;

    # fix for people who used prompt_info and hence the signal won't
    # pass the second argument.
    $pos = 'UP_INNER' unless defined $pos;
    _debug_print("Got prompt change signal with: $text, $pos");

    my ($changed_text, $changed_pos);
    $changed_text = defined $prompt_data     ? $prompt_data     ne $text : 1;
    $changed_pos  = defined $prompt_data_pos ? $prompt_data_pos ne $pos  : 1;

    $prompt_data     = $text;
    $prompt_data_pos = $pos;

    if ($changed_text || $changed_pos) {
        _debug_print("Redrawing prompt");
        uberprompt_refresh();
    }
}

sub _escape_prompt_special {
    my $str = shift;
    $str =~ s/\$/\$\$/g;
    $str =~ s/\\/\\\\/g;
    #$str =~ s/%/%%/g;
    $str =~ s/{/\$lbrace/g;
    $str =~ s/}/\$rbrace/g;

    return $str;
}

sub uberprompt_render_prompt {

    my $window = Irssi::active_win;
    my $prompt_arg = '';

    # default prompt sbar arguments (from config)
    if (scalar( () = $window->items )) {
        $prompt_arg = '$[.15]{itemname}';
    } else {
        $prompt_arg = '${winname}';
    }

    my $prompt = '';            # rendered content of the prompt.
    my $theme = Irssi::current_theme;

    my $arg = $use_replaces ? 0 : Irssi::EXPAND_FLAG_IGNORE_REPLACES;
    $prompt = $theme->format_expand("{uberprompt $prompt_arg}", $arg);

    if ($prompt_data_pos eq 'UP_ONLY') {
        $prompt =~ s/\$\$uber//; # no need for recursive prompting, I hope.

        # TODO: only compute this if necessary?
        my $prompt_nt = $prompt;
        $prompt_nt =~ s/\s+$//;

        my $pdata_copy = $prompt_data;

        $pdata_copy =~ s/\$prompt_nt/$prompt_nt/;
        $pdata_copy =~ s/\$prompt/$prompt/;

        $prompt = $pdata_copy;

    } elsif ($prompt_data_pos eq 'UP_INNER' && defined $prompt_data) {

        my $esc = _escape_prompt_special($prompt_data);
        $prompt =~ s/\$\$uber/$esc/;

    } else {
        # remove the $uber marker
        $prompt =~ s/\$\$uber//;

        # and add the additional text at the appropriate position.
        if ($prompt_data_pos eq 'UP_PRE') {
            $prompt = $prompt_data . $prompt;
        } elsif ($prompt_data_pos eq 'UP_POST') {
            $prompt .= $prompt_data;
        }
    }

    #_debug_print("Redrawing with: $prompt, size-only: $get_size_only");


    if (($prompt ne $prompt_last) or $emit_request) {

        # _debug_print("Emitting prompt changed signal");
        # my $exp = Irssi::current_theme()->format_expand($text, 0);
        my $ps = Irssi::parse_special($prompt);

        Irssi::signal_emit('prompt changed', $ps, length($ps));
        $prompt_last = $prompt;
    }
    return $prompt;
}

sub uberprompt_draw {
    my ($sb_item, $get_size_only) = @_;

    my $prompt = uberprompt_render_prompt();

    my $ret = $sb_item->default_handler($get_size_only, $prompt, '', 0);

    return $ret;
}

sub uberprompt_refresh {
    Irssi::statusbar_items_redraw('uberprompt');
}

sub replace_prompt_items {
    # remove existing ones.
    _debug_print("Removing original prompt");

    _sbar_command('prompt', 'remove', 'prompt');
    _sbar_command('prompt', 'remove', 'prompt_empty');

    # add the new one.

    _sbar_command('prompt', 'add', 'uberprompt',
                  qw/-alignment left -before input -priority '-1'/);

    _sbar_command('prompt', 'position', '100');
}

sub restore_prompt_items {

    _sbar_command('prompt', 'remove', 'uberprompt');

    _debug_print("Restoring original prompt");

    _sbar_command('prompt', 'reset');
}

sub _sbar_command {
    my ($bar, $cmd, $item, @args) = @_;

    my $args_str = join ' ', @args;

    $args_str .= ' ' if length $args_str && defined $item;

    my $command = sprintf 'STATUSBAR %s %s %s%s',
     $bar, $cmd, $args_str, defined $item ? $item : '';

    _debug_print("Running command: $command");
    Irssi::command($command);
}