aboutsummaryrefslogblamecommitdiffstats
path: root/vim-mode/vim_mode.pl
blob: 97105d1849948beb41e22a06938353e21cf5d7b3 (plain) (tree)


























                                                                                





                                          

















                                                                               
                







                                
 


























                                                      


















                                                       

      
                     
                      







                                               
 











                                   







                                 
                  

                   
 






                                   

                       
                         
                      
            
                      
     
                              




                                       
                         









                                                                       
 




                                          



                                                                   




                                                                    
                                    



                         


                             

 

















                                                               
                                                        





                                               
                                          



                  
                 

                         





                                     
                                                              

                                         
                                          


     


                                                              










                                                
                           



                   

                                             
                       
                                                           
            
                        
     
 


                







                                                    






                                              
# A script to emulate some of the vi(m) features for the Irssi inputline.
#
# Currently supported features:
#
# * Insert/Command mode. Escape enter command mode.
# * cursor motion with: h, l
# * cursor word motion with: w, b
# * delete at cursor: x
# * Insert mode at pos: i
# * Insert mode at end: I
# * insert mode at start: A

# Installation:
#
# The usual, stick in scripts dir, /script load vim_mode.pl ...
#
# Use the following command to get a statusbar item that shows which mode you're
# in. Annoying vi bleeping not yet supported :)

# /statusbar window add vim_mode to get the status.

# NOTE: This is still under extreme development, and there's a whole bunch of
# debugging output. Edit the script to remove all the print statements if it
# bothers you.

# Have fun!

use strict;
use warnings;

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


use vars qw($VERSION %IRSSI);
$VERSION = "1.0.1";
%IRSSI =
  (
   authors         => "shabble",
   contact         => 'shabble+irssi@metavore.org, shabble@#irssi/Freenode',
   name            => "vim_mode",
   description     => "Give Irssi Vim-like commands for editing the inputline",
   license         => "Public Domain",
   changed         => "20/9/2010"
  );

#sub DEBUG () { 1 }
sub DEBUG () { 0 }

# circular buffer to keep track of the last N keystrokes.
my @key_buf;
my $buf_idx = 0;
my $key_buf_timer;
my $key_buf_enabled = 0;
my $should_ignore = 0;

sub M_CMD() { 1 } # command mode
sub M_INS() { 0 } # insert mode

my $mode = M_INS;


my $commands
  = {
     'i' => { command => 'insert at cur',
              func => \&cmd_insert,
              params => {pos => sub { _input_pos() }},
            },

     'I' => { command => 'insert at end',
              func => \&cmd_insert,
              params => {pos => sub { _input_len() }},
            },

     'A' => { command => 'insert at start',
              func => \&cmd_insert,
              params => { pos => sub { 0 } },
            },

     'h' => { command => 'move left',
              func => \&cmd_move,
              params => { 'dir' => 'left' },
            },
     'l' => { command => 'move right',
              func => \&cmd_move,
              params => { 'dir' => 'right' },
            },

     'w' => { command => 'move forward word',
              func => \&cmd_jump_word,
              params => { 'dir' => 'fwd',
                          'pos' => sub { _input_pos() }
                        },
            },
     'b' => { command => 'move backward word',
              func => \&cmd_jump_word,
              params => { 'dir' => 'back',
                          'pos' => sub { _input_pos() }
                        },
            },

     'x' => { command => 'delete char forward',
              func => \&cmd_delete_char,
              params => { 'dir' => 'fwd',
                          'pos' => sub { _input_pos() }
                        },
            },
    };

sub cmd_delete_char {
    my ($params) = @_;
    my $pos = $params->{pos}->();
    my $direction = $params->{dir};
    print "Sending keystrokes for delete-char";
    _stop();
    my @buf = (4);
    _emulate_keystrokes(@buf);

}

sub cmd_jump_word {
    my ($params) = @_;
    my $pos = $params->{pos}->();
    my $direction = $params->{dir};
    _stop();
    my @buf;
    if ($direction eq 'fwd') {
        push @buf, (27, 102);
    } else {
        push @buf, (27, 98);
    }
    _emulate_keystrokes(@buf);
}

sub cmd_insert {
    my ($params) = @_;
    my $pos = $params->{pos}->();

    _input_pos($pos);

    $mode = M_INS;

    _update_mode();

    _stop();
}

sub cmd_move {
    my ($params) = @_;
    my $dir = $params->{dir};
    my $current_pos = _input_pos();
    _stop();
    my @buf = (27, 91);
    if ($dir eq 'left') {
        push @buf, 68;
    } else {
        push @buf, 67;
    }
    _emulate_keystrokes(@buf);
}

sub vim_mode_cb {
    my ($sb_item, $get_size_only) = @_;
    my $mode_str = '';
    if ($mode == M_INS) {
        $mode_str = 'Insert';
    } else {
        $mode_str = '%_Command%_';
    }
    $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0);
}


sub got_key {
    my ($key) = @_;

    return if ($should_ignore);

    if ($key == 27) {
        print "Esc seen, starting buffer";
        $key_buf_enabled = 1;

        # NOTE: this timeout might be too low on laggy systems, but
        # it comes at the cost of keystroke latency for things that
        # contain escape sequences (arrow keys, etc)
        $key_buf_timer
          = Irssi::timeout_add_once(10, \&handle_key_buffer, undef);
    }

    if ($key_buf_enabled) {
        $key_buf[$buf_idx++] = $key;
        _stop();
    }

    if ($mode == M_CMD) {
        # command mode
        handle_command($key);
    }
}

sub handle_key_buffer {

    Irssi::timeout_remove($key_buf_timer);
    $key_buf_timer = undef;
    # see what we've collected.
    print "Key buffer contains: ", join(", ", @key_buf);

    if (@key_buf == 1 && $key_buf[0] == 27) {

        print "Command Mode";
        $mode = M_CMD;
        _update_mode();

    } else {
        # we need to identify what we got, and either replay it
        # or pass it off to the command handler.
        if ($mode == M_CMD) {
            # command
            my $key_str = join '', map { chr } @key_buf;
            if ($key_str =~ m/^\e\[([ABCD])/) {
                print "Arrow key: $1";
            } else {
                print "Dunno what that is."
            }
        } else {
            _emulate_keystrokes(@key_buf);
        }
    }

    @key_buf = ();
    $buf_idx = 0;
    $key_buf_enabled = 0;
}

sub handle_command {
    my ($key) = @_;
    my $char = chr($key);
    if (exists $commands->{$char}) {
        my $cmd = $commands->{$char};
        # print "Going to execute command: ", $cmd->{command};
        $cmd->{func}->( $cmd->{params} );
    } else {
        _stop(); # disable everything else
    }
}

Irssi::signal_add_first 'gui key pressed' => \&got_key;
Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb');

sub _input {
    my ($data) = @_;
    if (defined $data) {
        Irssi::gui_input_set($data);
    } else {
        $data = Irssi::parse_special('$L', 0, 0)
    }
    return $data;
}

sub _input_len {
    return length _input();
}

sub _input_pos {
    my ($pos) = @_;
    my $cur_pos = Irssi::gui_input_get_pos();

    if (defined $pos) {
        Irssi::gui_input_set_pos($pos) if $pos != $cur_pos;
    } else {
        $pos = $cur_pos;
    }

    return $pos;
}

sub _emulate_keystrokes {
    my @keys = @_;
    $should_ignore = 1;
    for my $key (@keys) {
        Irssi::signal_emit('gui key pressed', $key);
    }
    $should_ignore = 0;
}
sub _stop() {
    Irssi::signal_stop();
}

sub _update_mode() {
    Irssi::statusbar_items_redraw("vim_mode");
}