diff options
Diffstat (limited to 'vim-mode')
-rw-r--r-- | vim-mode/vim_mode.pl | 602 |
1 files changed, 398 insertions, 204 deletions
diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index 51094aa..963cd8f 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -587,16 +587,16 @@ Things we're not ever likely to do: use strict; use warnings; - +#use diagnostics; #TODO: Specify a minimum version of perl (5.8, probably?) #testing. # UTF-8 conversion functions. use Encode; -use List::Util; # Not currently used. +use List::Util; # Not currently used. use Irssi; -use Irssi::TextUI; # statusbar handling. -use Irssi::Irc; # necessary for 0.8.14 +use Irssi::TextUI; # statusbar handling. +use Irssi::Irc; # necessary for 0.8.14 @@ -605,8 +605,8 @@ our %IRSSI = ( authors => "Tom Feist (shabble), Simon Ruderich (rudi_s)", contact => 'shabble+irssi@metavore.org, ' - . 'shabble@#irssi/Freenode, simon@ruderich.org' - . 'rudi_s@#irssi/Freenode', + . 'shabble@#irssi/Freenode, simon@ruderich.org' + . 'rudi_s@#irssi/Freenode', name => "vim_mode", description => "Give Irssi Vim-like commands for editing the inputline", license => "MIT", @@ -655,13 +655,21 @@ sub S_STR () { 2 } sub KEY_ESC () { 27 } sub KEY_RET () { 10 } sub KEY_TAB () { 9 } -sub KEY_DEL () { 8 } # Ctrl-H -sub KEY_BS () { 127 } # Backspace (^? usually) -sub KEY_C_C () { 3 } # Ctrl-C (all Ctrl-<x> are 0 + letter (from a=1 to 26)) +sub KEY_DEL () { 8 } # Ctrl-H +sub KEY_BS () { 127 } # Backspace (^? usually) +sub KEY_C_C () { 3 } # Ctrl-C (all Ctrl-<x> are 0 + letter (from a=1 to 26)) + + +sub SIG_STOP () { 1 } # stop the signal from reaching other handlers. +sub SIG_CONT () { 0 } # allow it to continue -sub SIG_STOP () { 1 } # stop the signal from reaching other handlers. -sub SIG_CONT () { 0 } # allow it to continue +# constants for determining how timeout and ttimeout settings are used. + +sub TIMEOUT_MODE_NONE () { 0 } +sub TIMEOUT_MODE_BOTH () { 1 } +sub TIMEOUT_MODE_CODE () { 2 } +sub TIMEOUT_MODE_MAPS () { 3 } # word and non-word regex, keep in sync with setup_changed()! my $word = qr/[\w_]/o; @@ -689,8 +697,8 @@ my $commands repeatable => 1 }, # arrow like movement - h => { char => 'h', func => \&cmd_h, type => C_NORMAL }, - l => { char => 'l', func => \&cmd_l, type => C_NORMAL }, + h => { char => 'h', func => \&cmd_h, type => C_NORMAL }, + l => { char => 'l', func => \&cmd_l, type => C_NORMAL }, "\x08" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL }, "\x7F" => { char => '<BS>', func => \&cmd_h, type => C_NORMAL }, ' ' => { char => '<Space>', func => \&cmd_l, type => C_NORMAL }, @@ -710,8 +718,8 @@ my $commands selection_needs_move_right => 1 }, F => { char => 'F', func => \&cmd_F, type => C_NEEDSKEY }, T => { char => 'T', func => \&cmd_T, type => C_NEEDSKEY }, - ';' => { char => ';', func => \&cmd_semicolon, type => C_NORMAL }, - ',' => { char => ',', func => \&cmd_comma, type => C_NORMAL }, + ';' => { char => ';', func => \&cmd_semicolon, type => C_NORMAL }, + ',' => { char => ',', func => \&cmd_comma, type => C_NORMAL }, # word movement w => { char => 'w', func => \&cmd_w, type => C_NORMAL }, b => { char => 'b', func => \&cmd_b, type => C_NORMAL }, @@ -738,10 +746,10 @@ my $commands repeatable => 1, no_operator => 1 }, X => { char => 'X', func => \&cmd_X, type => C_NORMAL, repeatable => 1, no_operator => 1 }, - # C_NORMAL is correct, operator c takes care of insert mode + # C_NORMAL is correct, operator c takes care of insert mode s => { char => 's', func => \&cmd_s, type => C_NORMAL, repeatable => 1, no_operator => 1 }, - # C_NORMAL is correct, operator c takes care of insert mode + # C_NORMAL is correct, operator c takes care of insert mode S => { char => 'S', func => \&cmd_S, type => C_NORMAL, repeatable => 1, no_operator => 1 }, # insert mode @@ -782,7 +790,7 @@ my $commands "\x06" => { char => '<C-F>', func => \&cmd_ctrl_f, type => C_NORMAL, no_operator => 1 }, "\x02" => { char => '<C-B>', func => \&cmd_ctrl_b, type => C_NORMAL, - no_operator => 1 }, + no_operator => 1 }, # window switching "\x17j" => { char => '<C-W>j', func => \&cmd_ctrl_wj, type => C_NORMAL, no_operator => 1 }, @@ -896,13 +904,13 @@ my $commands_ex ################################################################ # command mode mappings -my $cmap = undef; # cmap still pending (contains first character entered) +my $cmap = undef; # cmap still pending (contains first character entered) my $cmaps = {}; # insert-mode mappings -my $imap = undef; # imap still pending (contains first character entered) +my $imap = undef; # imap still pending (contains first character entered) my $imaps = { # CTRL-R, insert register @@ -970,7 +978,9 @@ my $should_ignore = 0; # buffer to keep track of the last N keystrokes, used for Esc detection and # insert mode mappings my @escape_buf; -my $escape_buf_timer; +my $escape_buf_timeout; +my $escape_buf_ttimeout; + my $escape_buf_enabled = 0; # mode-related input buffers @@ -981,6 +991,9 @@ my @insert_mode_buf; my @cmd_mode_buf; my @ex_mode_buf; +my $keycode_timeouts_active; +my $mapping_timeouts_active; + # Ex-mode related vars # -------------------- @@ -1017,7 +1030,7 @@ my $last # last ftFT movement, used by ; and , my $last_ftFT = { - type => undef, # f, t, F or T + type => undef, # f, t, F or T char => undef, }; @@ -1030,11 +1043,11 @@ my $register = '"'; # vi registers my $registers = { - '"' => '', # default register - '0' => '', # yank register - '+' => '', # contains irssi's cut buffer - '*' => '', # same - '_' => '', # black hole register, always empty + '"' => '', # default register + '0' => '', # yank register + '+' => '', # contains irssi's cut buffer + '*' => '', # same + '_' => '', # black hole register, always empty }; # (irssi) History related vars: @@ -1102,7 +1115,7 @@ sub cmd_operator_c { $move_cmd == $commands->{W})) { my $input = _input(); if ($new_pos - $old_pos > 1 and - substr($input, $new_pos - 1, 1) =~ /\s/) { + substr($input, $new_pos - 1, 1) =~ /\s/) { $new_pos--; } } @@ -1225,10 +1238,10 @@ sub cmd_j { if (defined $history_index) { $history_index += $count; print "History Index: $history_index" if DEBUG; - # Prevent destroying the current input when pressing j after entering - # command mode. Not exactly like in default irssi, but simplest solution - # (and S can be used to clear the input line fast, which is what <down> - # does in plain irssi). + # Prevent destroying the current input when pressing j after entering + # command mode. Not exactly like in default irssi, but simplest solution + # (and S can be used to clear the input line fast, which is what <down> + # does in plain irssi). } else { return (undef, undef); } @@ -1393,8 +1406,8 @@ sub cmd_semicolon { return (undef, undef) if not defined $last_ftFT->{type}; (undef, $pos) - = $commands->{$last_ftFT->{type}} - ->{func}($count, $pos, $repeat, $last_ftFT->{char}); + = $commands->{$last_ftFT->{type}} + ->{func}($count, $pos, $repeat, $last_ftFT->{char}); return (undef, $pos); } @@ -1409,8 +1422,8 @@ sub cmd_comma { $type =~ tr/ftFT/FTft/; (undef, $pos) - = $commands->{$type} - ->{func}($count, $pos, $repeat, $last_ftFT->{char}); + = $commands->{$type} + ->{func}($count, $pos, $repeat, $last_ftFT->{char}); # Restore type as the move functions overwrites it. $last_ftFT->{type} = $save; return (undef, $pos); @@ -1492,8 +1505,7 @@ sub _end_of_word { if (substr($input, $pos + 1) =~ /^\s+/) { $pos += $+[0] + 1; $skipped = 1; - } - elsif (substr($input, $pos) =~ /^\s+/) { + } elsif (substr($input, $pos) =~ /^\s+/) { $pos += $+[0]; $skipped = 1; } @@ -1501,9 +1513,9 @@ sub _end_of_word { if (substr($input, $pos) =~ /^$word{2,}/ or substr($input, $pos) =~ /^$non_word{2,}/) { $pos += $+[0] - 1; - # We are the border of word/non-word. Skip to the end of the next one. - # But not if we've already jumped over whitespace because there could - # be only one word/non-word char after the whitespace. + # We are the border of word/non-word. Skip to the end of the next one. + # But not if we've already jumped over whitespace because there could + # be only one word/non-word char after the whitespace. } elsif (!$skipped and (substr($input, $pos) =~ /^$word($non_word+)/ or substr($input, $pos) =~ /^$non_word($word+)/)) { $pos += $+[0] - 1; @@ -1643,7 +1655,7 @@ sub cmd__a { } else { $pos = length($input); } - # WORD + # WORD } else { if (substr($input, $pos + 1) =~ /\s/) { $pos += $-[0] + 1; @@ -1652,7 +1664,7 @@ sub cmd__a { } } - # word + # word } elsif ($char eq 'w') { # Start at the beginning of this WORD. if (not defined $cur_pos and $pos > 0 @@ -1694,11 +1706,11 @@ sub cmd__a { } } - # WORD + # WORD } else { # Start at the beginning of this WORD. if (not defined $cur_pos and $pos > 0 and - substr($input, $pos - 1, 1) !~ /\s/) { + substr($input, $pos - 1, 1) !~ /\s/) { $cur_pos = _find_regex_before($input, '\s', $pos - 1, 0); if ($cur_pos < 0) { $cur_pos = 0; @@ -1756,10 +1768,10 @@ sub cmd_caret { # No whitespace at all. if ($input !~ m/^\s/) { $pos = 0; - # Some non-whitespace, go to first one. + # Some non-whitespace, go to first one. } elsif ($input =~ m/[^\s]/) { $pos = $-[0]; - # Only whitespace, go to the end. + # Only whitespace, go to the end. } else { $pos = _fix_input_pos(length $input, length $input); } @@ -2039,7 +2051,7 @@ sub cmd_undo { $undo_index++; _restore_undo_entry($undo_index); print "Undoing entry index: $undo_index of " . scalar(@undo_buffer) - if DEBUG; + if DEBUG; } else { print "No further undo." if DEBUG; } @@ -2052,7 +2064,7 @@ sub cmd_redo { if ($undo_index != 0) { $undo_index--; print "Undoing entry index: $undo_index of " . scalar(@undo_buffer) - if DEBUG; + if DEBUG; _restore_undo_entry($undo_index); } else { print "No further Redo." if DEBUG; @@ -2068,7 +2080,7 @@ sub _fix_input_pos { # correct handling of last character in line. if ($operator) { $pos = $length if $pos > $length; - # Otherwise forbid it. + # Otherwise forbid it. } else { $pos = $length - 1 if $pos > $length - 1; } @@ -2097,7 +2109,7 @@ sub cmd_ex_command { # add this item to the ex mode history ex_history_add($arg_str); - $ex_history_index = 0; # and reset the history position. + $ex_history_index = 0; # and reset the history position. my $count = $1; if ($count eq '') { @@ -2205,13 +2217,13 @@ sub ex_buffer { # :[N]:b[uffer] if (defined $count) { $window = Irssi::window_find_refnum($count); - # Go to window number. + # Go to window number. } elsif ($buffer =~ /^[0-9]+$/) { $window = Irssi::window_find_refnum($buffer); - # Go to previous window. + # Go to previous window. } elsif ($buffer eq '#') { Irssi::command('window last'); - # Go to best regex matching window. + # Go to best regex matching window. } else { eval { my $matches = _matching_windows($buffer); @@ -2416,19 +2428,19 @@ sub ex_map { type => C_EX, }; } - # Irssi command + # Irssi command } elsif (index($rhs, '/') == 0) { $command = { char => $rhs, func => substr($rhs, 1), type => C_IRSSI, }; - # <Nop> does nothing + # <Nop> does nothing } elsif (lc $rhs eq '<nop>') { $command = { char => '<Nop>', func => undef, type => C_NOP, }; - # command-mode command + # command-mode command } else { $rhs = _parse_mapping($2); if (not defined $rhs) { @@ -2441,7 +2453,7 @@ sub ex_map { } add_map($lhs, $command); - # :map [lhs] + # :map [lhs] } elsif ($arg_str =~ m/^map\s*$/ or $arg_str =~ m/^map (\S+)$/) { # Necessary for case insensitive matchings. lc alone won't work. my $search = $1; @@ -2458,7 +2470,7 @@ sub ex_map { next if $map->{char} eq '<C-H>' and $cmd->{char} eq '<BS>'; next if $map->{char} !~ /^\Q$search\E/; # skip non-matches $active_window->print(sprintf "%-15s %s", $map->{char}, - $cmd->{char}); + $cmd->{char}); } } } else { @@ -2476,7 +2488,7 @@ sub ex_unmap { my $lhs = _parse_mapping($1); if (not defined $lhs) { return _warn_ex('unmap', 'invalid {lhs}'); - # Prevent unmapping of unknown or default mappings. + # Prevent unmapping of unknown or default mappings. } elsif (not exists $cmaps->{$lhs} or not defined $cmaps->{$lhs}->{cmd} or ($commands->{$lhs} and $cmaps->{$lhs}->{cmd} == $commands->{$lhs})) { return _warn_ex('unmap', "$1 not found"); @@ -2502,21 +2514,21 @@ sub _parse_mapping_bracket { # <C-X>, get corresponding CTRL char. if ($string =~ /^c-([a-z])$/i) { $string = chr(ord($1) - 96); - # <C-6> and <C-^> + # <C-6> and <C-^> } elsif ($string =~ /^c-[6^]$/i) { $string = chr(30); - # <Space> + # <Space> } elsif ($string eq 'space') { $string = ' '; - # <CR> + # <CR> } elsif ($string eq 'cr') { $string = "\n"; - # <BS> + # <BS> } elsif ($string eq 'bs') { $string = chr(127); } elsif ($string eq 'leader') { $string = $settings->{map_leader}->{value}; - # Invalid char, return special string to recognize the error. + # Invalid char, return special string to recognize the error. } else { $string = '<invalid>'; } @@ -2630,7 +2642,7 @@ sub ex_set { _setting_set($name, $value); setup_changed(); - # :se[t] [option] + # :se[t] [option] } else { my $search = defined $1 ? $1 : ''; my $active_window = Irssi::active_win(); @@ -2752,7 +2764,7 @@ sub _set_prompt { my $msg = shift; # add a leading space unless we're trying to clear it entirely. - if (length($msg) and $settings->{prompt_leading_space}->{value}) { + if (length($msg) and $settings->{prompt_leading_space}->{value}) { $msg = ' ' . $msg; } @@ -2825,16 +2837,16 @@ sub _matching_windows { if ($window->{name} =~ /$buffer/i) { my $win_ratio = ($+[0] - $-[0]) / length($window->{name}); push @matches, { window => $window, - item => undef, - ratio => $win_ratio, - text => $window->{name} }; + item => undef, + ratio => $win_ratio, + text => $window->{name} }; print ":b $window->{name}: $win_ratio" if DEBUG; } # Matching Window item names (= channels). foreach my $item ($window->items()) { # Wrong server. if ($server and (!$item->{server} or - $item->{server}->{chatnet} !~ /^$server/i)) { + $item->{server}->{chatnet} !~ /^$server/i)) { next; } if ($item->{name} =~ /$buffer/i) { @@ -2842,9 +2854,9 @@ sub _matching_windows { $length-- if index($item->{name}, '#') == 0; my $item_ratio = ($+[0] - $-[0]) / $length; push @matches, { window => $window, - item => $item, - ratio => $item_ratio, - text => $item->{name} }; + item => $item, + ratio => $item_ratio, + text => $item->{name} }; print ":b $window->{name} $item->{name}: $item_ratio" if DEBUG; } } @@ -2992,7 +3004,7 @@ sub vim_mode_init { # Add all default mappings. foreach my $char (keys %$commands) { - next if $char =~ /^_/; # skip private commands (text-objects for now) + next if $char =~ /^_/; # skip private commands (text-objects for now) add_map($char, $commands->{$char}); } @@ -3149,8 +3161,8 @@ sub add_map { if (not exists $cmaps->{$tmp}) { $cmaps->{$tmp} = { char => _parse_mapping_reverse($tmp), cmd => undef, - maps => {} - }; + maps => {} + }; } if (not exists $cmaps->{$tmp}->{maps}->{$tmp . $map}) { $cmaps->{$tmp}->{maps}->{$tmp . $map} = undef; @@ -3160,8 +3172,8 @@ sub add_map { if (not exists $cmaps->{$keys}) { $cmaps->{$keys} = { char => undef, cmd => undef, - maps => {} - }; + maps => {} + }; } $cmaps->{$keys}->{char} = _parse_mapping_reverse($keys); $cmaps->{$keys}->{cmd} = $command; @@ -3224,9 +3236,9 @@ sub _update_mode { _input_pos($pos); $numeric_prefix = undef; - # In insert mode we are "between" characters, in command mode "on top" - # of keys. When leaving insert mode we have to move on key left to - # accomplish that. + # In insert mode we are "between" characters, in command mode "on top" + # of keys. When leaving insert mode we have to move on key left to + # accomplish that. } else { $pos = _input_pos(); if ($pos != 0) { @@ -3236,12 +3248,12 @@ sub _update_mode { # Store current line to allow undo of i/a/I/A. _add_undo_entry(_input(), _input_pos()); - # Change mode to i to support insert mode repetition. This doesn't affect - # commands like i/a/I/A because handle_command_cmd() sets $last->{cmd}. - # It's necessary when pressing enter so the next line can be repeated. + # Change mode to i to support insert mode repetition. This doesn't affect + # commands like i/a/I/A because handle_command_cmd() sets $last->{cmd}. + # It's necessary when pressing enter so the next line can be repeated. } elsif ($mode == M_CMD and $new_mode == M_INS) { $last->{cmd} = $commands->{i}; - # Make sure prompt is cleared when leaving ex mode. + # Make sure prompt is cleared when leaving ex mode. } elsif ($mode == M_EX and $new_mode != M_EX) { _set_prompt(''); } @@ -3251,8 +3263,8 @@ sub _update_mode { $history_index = undef; $register = '"'; @insert_mode_buf = (); - # Reset every command mode related status as a fallback in case something - # goes wrong. + # Reset every command mode related status as a fallback in case something + # goes wrong. } elsif ($mode == M_CMD) { $numeric_prefix = undef; $operator = undef; @@ -3362,7 +3374,7 @@ sub ex_history_add { unshift @ex_history, $line; if ($settings->{ex_history_size}->{value} < @ex_history) { - pop @ex_history; # junk the last entry if we've hit the max. + pop @ex_history; # junk the last entry if we've hit the max. } } @@ -3444,97 +3456,279 @@ sub sig_gui_keypress { sub process_input_key { my ($key, $char) = @_; - # no matter what mode we're in, we need to deal with escape (and escape - # sequences) specially. So do them first. + my ($timeout, $ttimeout) + = ($settings->{timeout}->{value}, + $settings->{ttimeout}->{value}); + + my ($timeout_len, $ttimeout_len) + = ($settings->{timeoutlen}->{value}, + $settings->{ttimeoutlen}->{value}); - # First, - if ($key == KEY_ESC) { - _debug("Escape seen, beginning escape_buf collection"); - $escape_buf_enabled = 1; + _append_to_mode_buffer($key); - push @escape_buf, $key; + if ((not $timeout) and (not $ttimeout)) { + # wait forever + _debug("process_keys_timeout none"); - # 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) - my $esc_buf_timeout = $settings->{esc_buf_timeout}->{value}; + return if $keycode_timeouts_active or $mapping_timeouts_active; - $escape_buf_timer = - Irssi::timeout_add_once($esc_buf_timeout, - \&escape_buffer_timeout, undef); + process_keys_timeout(TIMEOUT_MODE_NONE); # make these constants. + } elsif ($timeout) { + _debug("process_keys_timeout both"); - _debug("Buffer Timer tag: $escape_buf_timer"); + # timeout on both mappings and keycodes + process_keys_timeout(TIMEOUT_MODE_BOTH, $timeout_len, $ttimeout_len); + } elsif ((not $timeout) and $ttimeout) { + # timeout on codes (but not mappings? + _debug("process_keys_timeout code"); - } elsif ($escape_buf_enabled) { - attempt_escape_buffer_parse($key, $char); - # we can check at this point if we recognise the sequence, and cancel - # the timeout if we do. + return if $keycode_timeouts_active; + + process_keys_timeout(TIMEOUT_MODE_CODE, $timeout_len, $ttimeout_len); + } else { + _debug("What the buggery, shouldn't happen: T: $timeout, TT: $ttimeout"); } + } -sub attempt_escape_buffer_parse { - my ($key, $char) = @_; +# if ($key == KEY_ESC) { +# _debug("Escape seen, beginning escape_buf collection"); +# $escape_buf_enabled = 1; - if ($char eq '[') { - _debug("Spotted CSI"); - } elsif ($char eq ']') { - _debug ("Spotted OSC"); - } else { - push @escape_buf, $key; +# push @escape_buf, $key; + +# # 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) +# my $esc_buf_timeout = $settings->{esc_buf_timeout}->{value}; + +# $escape_buf_timer = +# Irssi::timeout_add_once($esc_buf_timeout, +# \&escape_buffer_timeout, undef); + +# _debug("Buffer Timer tag: $escape_buf_timer"); + +# } elsif ($escape_buf_enabled) { +# attempt_escape_buffer_parse($key, $char); +# # we can check at this point if we recognise the sequence, and cancel +# # the timeout if we do. +# } +# } + + +sub process_keys_timeout +{ + my ($timeout_mode, $timeout_len, $ttimeout_len) = @_; + + if ($timeout_mode eq TIMEOUT_MODE_NONE) { + + _try_process_keycode(); + _try_process_mapping(); + + } elsif ($timeout_mode eq TIMEOUT_MODE_BOTH) { + + my ($keycode_delay, $mapping_delay); + + if ($ttimeout_len == 0) { + return if $keycode_timeouts_active; + _try_process_keycode(); + + } elsif ($ttimeout_len > 0 and $ttimeout_len < 10) { + $mapping_delay = 10; # minimum irssi timeout. + } else { + + $timeout_len = $timeout_len < 10 ? 10 : $timeout_len; + $mapping_delay = $keycode_delay = $timeout_len; + } + + if (not $mapping_timeouts_active) { + + _debug("Setting mapping delay to %d", $mapping_delay); + $escape_buf_ttimeout + = Irssi::timeout_add_once($mapping_delay, + \&_try_process_mapping, 1); + $mapping_timeouts_active = 1; + + } else { + _debug ("Not adding new mapping timeout"); + } + + if (not $keycode_timeouts_active) { + + _debug("Setting keycode delay to %d", $keycode_delay); + $escape_buf_timeout + = Irssi::timeout_add_once($keycode_delay, + \&_try_process_keycode, 1); + $keycode_timeouts_active = 1; + + } else { + _debug ("Not adding new keycode timeout"); + } + + } elsif ($timeout_mode eq TIMEOUT_MODE_CODE) { + + $keycode_timeouts_active = 1; + + _try_process_mapping(); + $escape_buf_timeout + = Irssi::timeout_add_once($timeout_len, + \&_try_process_keycode, 1); } } -sub escape_buffer_timeout { +sub _try_process_mapping { + my ($from_timer) = @_; - $escape_buf_timer = undef; - $escape_buf_enabled = 0; + my @buf = _get_mode_buffer(); + _debug("process mapping: '%s'", _buf_to_str(@buf)); - _debug("Escape buffer contains: ", join(", ", @escape_buf)); + $mapping_timeouts_active = 0 if $from_timer; +} - if (@escape_buf == 1 && $escape_buf[0] == KEY_ESC) { +sub _try_process_keycode { + my ($from_timer) = @_; - _debug("escape only, Enter Command Mode"); + my @buf = _get_mode_buffer(); + _debug("keycode mapping: '%s'", _buf_to_str(@buf)); + if (@buf == 1 and $buf[0] == KEY_ESC) { + _debug ("Buf contains ESC!"); _update_mode(M_CMD); + } elsif (@buf == 1 and $buf[0] == ord('i')) { + _debug ("Buf contains i!"); + _update_mode(M_INS); + } else { + _debug("buf contains: '%s'", _buf_to_hex_str(@buf)); + } + + _reset_mode_buffer($_) for (M_CMD, M_INS, M_EX); + $keycode_timeouts_active = 0 if $from_timer; +} + +################################################################ +# INPUT BUFFER MODEL # +################################################################ + +sub _append_to_mode_buffer { + my ($keycode) = @_; + my $char = chr $keycode; + if ($mode == M_CMD) { + _debug("Appending %s to CMD buf", $char); + push @cmd_mode_buf, $keycode; + } elsif ($mode == M_EX) { + _debug("Appending %s to EX buf", $char); + + push @ex_mode_buf, $keycode; } else { - # we have more than a single esc, implying an escape sequence - # (meta-* or esc-*) - - # currently, we only extract escape sequences if: - # a) we're in ex mode - # b) they're arrow keys (for history control) - - if ($mode == M_EX) { - # ex mode - my $key_str = join '', map { chr } @escape_buf; - if ($key_str =~ m/^\e\[([ABCD])/) { - my $arrow = $1; - _debug( "Arrow key: $arrow"); - if ($arrow eq 'A') { # up - ex_history_back(); - } elsif ($arrow eq 'B') { # down - ex_history_fwd(); - } else { - $arrow =~ s/C/right/; - $arrow =~ s/D/left/; - _debug("Arrow key $arrow not supported"); - } - } - } else { - # otherwise, we just forward them to irssi. - _emulate_keystrokes(@escape_buf); - } + _debug("Appending %s to INS buf", $char); - # Clear insert buffer, pressing "special" keys (like arrow keys) - # resets it. - @escape_buf = (); + push @insert_mode_buf, $keycode; } +} - @escape_buf = (); - $escape_buf_enabled = 0; +sub _get_mode_buffer { + if ($mode == M_CMD) { + return @cmd_mode_buf; + } elsif ($mode == M_EX) { + return @ex_mode_buf; + } else { + return @insert_mode_buf; + } } +sub _reset_mode_buffer { + my ($override_mode) = @_; + + if (not defined $override_mode) { + $override_mode = $mode; + } + + if ($override_mode == M_CMD) { + _debug("Resetting CMD buffer"); + @cmd_mode_buf = (); + } elsif ($override_mode == M_EX) { + _debug("Resetting EX buffer"); + @ex_mode_buf = (); + } elsif ($override_mode == M_INS) { + _debug("Resetting INS buffer"); + @insert_mode_buf = (); + } else { + _warn("Reset mode buffer: Unknown mode: $override_mode"); + } +} +sub _buf_to_str { + return join '', map { chr } @_; +} +sub _buf_to_hex_str { + my @list; + foreach my $key (@_) { + push @list, sprintf('0x%02x', $key); + } + return '[' . join(' ', @list) . ']'; +} + +# sub attempt_escape_buffer_parse { +# my ($key, $char) = @_; + +# if ($char eq '[') { +# _debug("Spotted CSI"); +# } elsif ($char eq ']') { +# _debug ("Spotted OSC"); +# } else { +# push @escape_buf, $key; +# } +# } + +# sub escape_buffer_timeout { + +# $escape_buf_timer = undef; +# $escape_buf_enabled = 0; + +# _debug("Escape buffer contains: ", join(", ", @escape_buf)); + +# if (@escape_buf == 1 && $escape_buf[0] == KEY_ESC) { + +# _debug("escape only, Enter Command Mode"); +# _update_mode(M_CMD); + +# } else { +# # we have more than a single esc, implying an escape sequence +# # (meta-* or esc-*) + +# # currently, we only extract escape sequences if: +# # a) we're in ex mode +# # b) they're arrow keys (for history control) + +# if ($mode == M_EX) { +# # ex mode +# my $key_str = join '', map { chr } @escape_buf; +# if ($key_str =~ m/^\e\[([ABCD])/) { +# my $arrow = $1; +# _debug( "Arrow key: $arrow"); +# if ($arrow eq 'A') { # up +# ex_history_back(); +# } elsif ($arrow eq 'B') { # down +# ex_history_fwd(); +# } else { +# $arrow =~ s/C/right/; +# $arrow =~ s/D/left/; +# _debug("Arrow key $arrow not supported"); +# } +# } +# } else { +# # otherwise, we just forward them to irssi. +# _emulate_keystrokes(@escape_buf); +# } + +# # Clear insert buffer, pressing "special" keys (like arrow keys) +# # resets it. +# @escape_buf = (); +# } + +# @escape_buf = (); +# $escape_buf_enabled = 0; +# } # work slowly incorporating stuff back in. @@ -3627,35 +3821,35 @@ sub escape_buffer_timeout { # TODO: merge this with 'flush_input_buffer' below. -sub flush_input_buffer { - Irssi::timeout_remove($escape_buf_timer) if defined $escape_buf_timer; - $escape_buf_timer = undef; - # see what we've collected. - _debug("Escape buffer flushed"); +# sub flush_input_buffer { +# Irssi::timeout_remove($escape_buf_timer) if defined $escape_buf_timer; +# $escape_buf_timer = undef; +# # see what we've collected. +# _debug("Escape buffer flushed"); - # Add the characters to @escape_buf so they can be repeated. - push @escape_buf, map chr, @escape_buf; +# # Add the characters to @escape_buf so they can be repeated. +# push @escape_buf, map chr, @escape_buf; - _emulate_keystrokes(@escape_buf); +# _emulate_keystrokes(@escape_buf); - @escape_buf = (); - $escape_buf_enabled = 0; +# @escape_buf = (); +# $escape_buf_enabled = 0; - $imap = undef; -} +# $imap = undef; +# } -sub flush_pending_map { - my ($old_pending_map) = @_; +# sub flush_pending_map { +# my ($old_pending_map) = @_; - print "flush_pending_map(): ", $pending_map, ' ', $old_pending_map - if DEBUG; +# print "flush_pending_map(): ", $pending_map, ' ', $old_pending_map +# if DEBUG; - return if not defined $pending_map or - $pending_map ne $old_pending_map; +# return if not defined $pending_map or +# $pending_map ne $old_pending_map; - handle_command_cmd(undef); - Irssi::statusbar_items_redraw("vim_mode"); -} +# handle_command_cmd(undef); +# Irssi::statusbar_items_redraw("vim_mode"); +# } @@ -3683,7 +3877,7 @@ sub handle_command_cmd { my $char; if (defined $key) { $char = chr($key); - # We were called from flush_pending_map(). + # We were called from flush_pending_map(). } else { $char = $pending_map; $key = 0; @@ -3695,7 +3889,7 @@ sub handle_command_cmd { ($char =~ m/[1-9]/ or ($numeric_prefix && $char =~ m/[0-9]/))) { print "Processing numeric prefix: $char" if DEBUG; handle_numeric_prefix($char); - return 1; # call _stop() + return 1; # call _stop() } if (defined $pending_map and not $pending_map_flushed) { @@ -3723,16 +3917,16 @@ sub handle_command_cmd { # after a timeout. if (defined $map->{cmd}) { Irssi::timeout_add_once(1000, \&flush_pending_map, - $pending_map); + $pending_map); } - return 1; # call _stop() + return 1; # call _stop() } } else { print "No mapping found for $char" if DEBUG; $pending_map = undef; $numeric_prefix = undef; - return 1; # call _stop() + return 1; # call _stop() } $pending_map = undef; @@ -3742,7 +3936,7 @@ sub handle_command_cmd { # Make sure we have a valid $cmd. if (not defined $cmd) { print "Bug in pending_map_flushed() $map->{char}" if DEBUG; - return 1; # call _stop() + return 1; # call _stop() } # Ex-mode commands can also be bound in command mode. @@ -3752,21 +3946,21 @@ sub handle_command_cmd { $cmd->{func}->(substr($cmd->{char}, 1), $numeric_prefix); $numeric_prefix = undef; - return 1; # call _stop() - # As can irssi commands. + return 1; # call _stop() + # As can irssi commands. } elsif ($cmd->{type} == C_IRSSI) { print "Processing irssi-command: $map->{char} ($cmd->{char})" if DEBUG; _command_with_context($cmd->{func}); $numeric_prefix = undef; - return 1; # call _stop(); - # <Nop> does nothing. + return 1; # call _stop(); + # <Nop> does nothing. } elsif ($cmd->{type} == C_NOP) { print "Processing <Nop>: $map->{char}" if DEBUG; $numeric_prefix = undef; - return 1; # call _stop(); + return 1; # call _stop(); } # text-objects (i a) are simulated with $movement @@ -3790,14 +3984,14 @@ sub handle_command_cmd { print "Processing line operator: ", $map->{char}, " (", $cmd->{char} ,")" - if DEBUG; + if DEBUG; my $pos = _input_pos(); $cmd->{func}->(0, _input_len(), undef, 0); # Restore position for yy. if ($cmd == $commands->{y}) { _input_pos($pos); - # And save undo for other operators. + # And save undo for other operators. } else { _add_undo_entry(_input(), _input_pos()); } @@ -3809,26 +4003,26 @@ sub handle_command_cmd { $numeric_prefix = undef; $operator = undef; $movement = undef; - # Set new operator. + # Set new operator. } else { $operator = $cmd; } - # Start Ex mode. + # Start Ex mode. } elsif ($cmd == $commands->{':'}) { if (not script_is_loaded('uberprompt')) { _warn("Warning: Ex mode requires the 'uberprompt' script. " . - "Please load it and try again."); + "Please load it and try again."); } else { _update_mode(M_EX); _set_prompt(':'); } - # Enter key sends the current input line in command mode as well. + # Enter key sends the current input line in command mode as well. } elsif ($key == 10) { _commit_line(); - return 0; # don't call _stop() + return 0; # don't call _stop() } else { print "Processing command: $map->{char} ($cmd->{char})" if DEBUG; @@ -3858,7 +4052,7 @@ sub handle_command_cmd { # Ignore invalid operator/command combinations. if ($operator and $cmd->{no_operator}) { print "Invalid operator/command: $operator->{char} $cmd->{char}" - if DEBUG; + if DEBUG; $skip = 1; } @@ -3880,11 +4074,11 @@ sub handle_command_cmd { # Execute the movement (multiple times). if (not $movement) { ($old_pos, $new_pos) - = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat); + = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat); } else { ($old_pos, $new_pos) - = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat, - $char); + = $cmd->{func}->($numeric_prefix, $cur_pos, $repeat, + $char); } if (defined $old_pos) { print "Changing \$cur_pos from $cur_pos to $old_pos" if DEBUG; @@ -3902,7 +4096,7 @@ sub handle_command_cmd { ((defined $operator and $operator == $commands->{d}) or $cmd->{repeatable})) { print "Updating history position: $undo_buffer[0]->[0]" - if DEBUG; + if DEBUG; $undo_buffer[0]->[1] = $cur_pos; } @@ -3951,7 +4145,7 @@ sub handle_command_cmd { } - return 1; # call _stop() + return 1; # call _stop() } sub handle_command_ex { @@ -3963,18 +4157,18 @@ sub handle_command_ex { if (@ex_mode_buf > 0) { pop @ex_mode_buf; _set_prompt(':' . join '', @ex_mode_buf); - # Backspacing over : exits ex-mode. + # Backspacing over : exits ex-mode. } else { _update_mode(M_CMD); } - # Return key - execute command + # Return key - execute command } elsif ($key == 10) { print "Run ex-mode command" if DEBUG; cmd_ex_command(); _update_mode(M_CMD); - } elsif ($key == 9) { # TAB + } elsif ($key == 9) { # TAB print "Tab pressed" if DEBUG; print "Ex buf contains: " . join('', @ex_mode_buf) if DEBUG; @tab_candidates = _tab_complete(join('', @ex_mode_buf), [keys %$commands_ex]); @@ -3983,11 +4177,11 @@ sub handle_command_ex { @ex_mode_buf = ( split('', $tab_candidates[0]), ' '); _set_prompt(':' . join '', @ex_mode_buf); } - # Ignore control characters for now. + # Ignore control characters for now. } elsif ($key > 0 && $key < 32) { # TODO: use them later, e.g. completion - # Append entered key + # Append entered key } else { if ($key != -1) { # check we're not called from an ex_history_* function |