use strict; use warnings; use Irssi; use Irssi::TextUI; # for sbar_items_redraw # /statusbar window add vim_mode to get the status. 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 $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' }, }, }; sub cmd_jump_word { my ($params) = @_; } 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(); if ($dir eq 'left') { _input_pos($current_pos -1) if $current_pos; } elsif ($dir eq 'right') { my $current_len = _input_len(); _input_pos($current_pos +1) unless $current_pos == $current_len; } else { print "Unknown direction: $dir"; } _stop(); } 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) = @_; # goals: # * all keys should work normally in insert mode (including Arrow keys) # * we should be able to reliably detect a single press of the esc key and # switch to command mode # * # whenever we see an escape, we need to be sure it's not part of a # longer escape sequence (eg ^[[A for arrowkeys or whatever) # when we see an escape (27), we start a timer for a short period, and # capture all additional keystrokes into a buffer. # once the timer expires, we examine the buffer to see if it's a plain escape # (hopefully time is short enough that we don't get user-repeated keypresses) # or an escape sequence, which we can then parse and do whatever with. # issues: # do the buffered commands get evaluated? (ie: do we sig_stop them?) # return if ($should_ignore); if ($key == 27) { print "Esc seen, starting buffer"; $key_buf_enabled = 1; $key_buf_timer = Irssi::timeout_add_once(10, \&handle_key_buffer, undef); } if ($key_buf_enabled) { push @key_buf, $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 { $should_ignore = 1; for my $key (@key_buf) { Irssi::signal_emit('gui key pressed', $key); } $should_ignore = 0; } } @key_buf = (); $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 (Irssi::parse_special('$L', 0, 0)); } sub _input_pos { my ($pos) = @_; if (defined $pos) { Irssi::gui_input_set_pos($pos); } else { $pos = Irssi::gui_input_get_pos(); } return $pos; } sub _stop() { Irssi::signal_stop(); } sub _update_mode() { Irssi::statusbar_items_redraw("vim_mode"); }