diff options
Diffstat (limited to 'vim-mode')
| -rw-r--r-- | vim-mode/README.pod | 584 | ||||
| -rw-r--r-- | vim-mode/input.txt | 58 | ||||
| -rw-r--r-- | vim-mode/irssi/config | 38 | ||||
| l--------- | vim-mode/irssi/scripts/autorun/uberprompt.pl | 1 | ||||
| l--------- | vim-mode/irssi/scripts/autorun/vim_mode.pl | 1 | ||||
| -rw-r--r-- | vim-mode/tests.pl | 171 | ||||
| -rw-r--r-- | vim-mode/vim_mode.pl | 1190 | 
7 files changed, 1717 insertions, 326 deletions
| diff --git a/vim-mode/README.pod b/vim-mode/README.pod new file mode 100644 index 0000000..83962ff --- /dev/null +++ b/vim-mode/README.pod @@ -0,0 +1,584 @@ +=pod + +=head1 NAME + +vim_mode.pl + +=head1 DESCRIPTION + +An Irssi script to emulate some of the vi(m) features for the Irssi inputline. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD vim_mode.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head2 DEPENDENCIES + +For proper :ex mode support, vim-mode requires the installation of F<uberprompt.pl> +Uberprompt can be downloaded from: + +L<https://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl> + +and follow the instructions at the top of that file for installation. + +If you don't need Ex-mode, you can run vim_mode.pl without the +uberprompt.pl script, but it is strongly recommended that you use it. + +=head3 Irssi requirements + +0.8.12 and above should work fine. However the following features are +disabled in irssi < 0.8.13: + +=over 4 + +=item * C<j> C<k> (only with count, they work fine without count in older versions) + +=item * C<gg>, C<G> + +=back + +It is intended to work with at Irssi  0.8.12 and later versions. However some +features are disabled in older versions (see below for details). + +Perl >= 5.8.1 is recommended for UTF-8 support (which can be disabled if +necessary).  Please report bugs in older versions as well, we'll try to fix +them.  Later versions of Perl are also faster, which is probably beneficial +to a script of this size and complexity. + +=head2 SETUP + +Vim Mode provides certain feedback to the user, such as the currently active +mode (command, insert, ex), and if switching buffers, which buffer(s) currently +match the search terms. + +There are two ways to go about displaying this information, as described in +the following sections: + +=head3 Statusbar Items + +Run the following command to add a statusbar item that shows which mode +you're in. + +C</statusbar window add vim_mode> + +And the following to let C<:b [str]> display a list of channels matching your +search string. + +C</statusbar window add vim_windows> + +B<Note:> Remember to C</save> after adding these statusbar items to make them +permanent. + +B<Note:> If you would rather have these statusbar items on your prompt +line rather than thte window statusbar, please follow these steps. + +For technical reasons, I<uberprompt> must occasionally call C</statusbar prompt +reset>, which will remove or deactivate any manually added items on the prompt +statusbar.  To get around this, uberprompt provides two command hooks, +C<uberprompt_load_hook> and C<uberprompt_unload_hook>.  Both of these settings +can contain one (or more, using C</EVAL>) commands to be executed when the prompt +is enabled and disabled, respectively. + +See the L<uberprompt documentation|https://github.com/shabble/irssi-scripts/blob/master/prompt_info/README.pod> for further details. + +For I<right-aligned> items (that is, after the input field: + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -after input -alignment right vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +For I<left-aligned> items (before the prompt): + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -before prompt -alignment left vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +If you wish to add both C<vim_mode> and C<vim_windows> items, replace steps 1 and 2 +above with the following (right-aligned): + +=over 4 + +=item 1 C</alias vm_add /^eval /^statusbar prompt add -after input -alightment right vim_mode ; /^statusbar prompt add -after input -alignment right vim_windows> + +=item 2 C</alias vm_del /^eval /^statusbar prompt remove vim_mode ; /^statusbar prompt remove vim_windows> + +=back + +and then complete stages 3 and 4 as above.  Replace the C<-after> and C<-alignment> +to suit your location preferences. + +B<Note:> It is also possible to place the items between the prompt and input field, +by specifying C<-after prompt> or C<-before input> as appropriate. + +=head3 Expando Variables + +Vim mode augments the existing Irssi expando (automatic variables) with two +additional ones: C<$vim_cmd_mode> and C<$vim_wins>. + +C<$vim_cmd_mode> is the equivalent of the C<vim_mode> statusbar item, and +C<$vim_wins> is the counterpart of C<vim_windows>. + +They can be added to your theme, or inserted into your uberprompt using +a command such as: + +"C</set uberprompt_format [$vim_cmd_mode] $*$uber] >" + +=head3 FILE-BASED CONFIGURATION + +Additionally to the irssi settings described in L<settings|/SETTINGS>, vim_mode +can be configured through an external configuration file named "vim_moderc" +located in F<~/.irssi/vim_moderc>. If available it's loaded on startup and every +supported ex-command is run. Its syntax is similar to "vimrc". To (re)load it +while vim_mode is running use C<:so[urce]>. + +Currently Supported ex-commands: + +=over 4 + +=item * C<:map> + +=back + +=head1 USAGE + +The following section is divided into the different modes as supported by Vim itself: + +=head2 COMMAND MODE + +It supports most commonly used command mode features: + +=over 2 + +=item * Insert/Command mode. + +C<Esc> and C<Ctrl-C> enter command mode.  C</set vim_mode_cmd_seq j> allows +to use C<jj> as Escape (any other character can be used as well). + +=item * Cursor motion: + +C<h l 0 ^ $ E<lt>SpaceE<gt> E<lt>BSE<gt> f t F T> + +=item * History motion: + +C<j k gg G> C<gg> moves to the oldest (first) history line.  C<G> without a +count moves to the current input line, with a count it goes to the I<count-th> +history line (1 is the oldest). + +=item * Cursor word motion: + +C<w b ge e W gE B E> + +=item * Word objects (only the following work yet): + +C<aw aW> + +=item * Yank and paste: + + C<y p P> + +=item * Change and delete: + +C<c d> + +=item * Delete at cursor: + +C<x X> + +=item * Replace at cursor: + +C<r> + +=item * Insert mode: + +C<i a I A> + +=item * Switch case: + +C<~> + +=item * Repeat change: + +C<.> + +=item * Repeat + +C<ftFT: ; ,> + +=item * Registers: + +C<"a-"z "" "0 "* "+ "_> (black hole) + +=item * Line-wise shortcuts: + +C<dd cc yy> + +=item * Shortcuts: + +C<s S C D> + +=item * Scroll the scrollback buffer: + +C<Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B> + +=item * Switch to last active window: + +C<Ctrl-6/Ctrl-^> + +=item * Switch split windows: + +<Ctrl-W j Ctrl-W k> + +=item * Undo/Redo: + +C<u Ctrl-R> + +=back + +Counts and combinations work as well, e.g. C<d5fx> or C<3iabcE<lt>escE<gt>>. Counts also work with mapped ex-commands (see below), e.g. if you map C<gb> to do C<:bn>, then C<2gb> will switch to the second next buffer.  Repeat also supports counts. + +=head3 REGISTERS + +=over 4 + +=item * Appending to register with C<"A-"Z> + +=item * C<""> is the default yank/delete register. + +=item * C<"0> contains the last yank (if no register was specified). + +=item * The special registers C<"* "+> both contain irssi's internal cut-buffer. + +=back + +=head2 INSERT MODE + +The following insert mode mappings are supported: + +=over 4 + +=item * Insert register content: Ctrl-R x (where x is the register to insert) + +=back + +=head2 EX-MODE + +Ex-mode (activated by C<:> in command mode) supports the following commands: + +=over 4 + +=item * Command History: + +C<E<lt>uparrowE<gt>> - cycle backwards through history + +C<E<lt>downarrowE<gt>> - cycle forwards through history + +C<:eh> - show ex history + +=item * Switching buffers: + +C<:[N]b [N]> - switch to channel number + +C<:b#>       - switch to last channel + +C<:b> E<lt>partial-channel-nameE<gt> + +C<:b> <partial-server>/<partial-channel> + +C<:buffer {args}> (same as C<:b>) + +C<:[N]bn[ext] [N]> - switch to next window + +C<:[N]bp[rev] [N]> - switch to previous window + +=item * Close window: + +C<:[N]bd[elete] [N]> + +=item * Display windows: + +C<:ls>, C<:buffers> + +=item * Display registers: + +C<:reg[isters] {args}>, C<:di[splay] {args}> + +=item * Display undolist: + +C<:undol[ist]> (mostly used for debugging) + +=item * Source files: + +C<:so[urce]> - only sources vim_moderc at the moment, +                         F<{file}> not supported + +=item * Mappings: + +C<:map> - display custom mappings + +=item * Saving mappings: + +C<:mkv[imrc][!]> - like in Vim, but [file] not supported + +=item * Substitution: + +C<:s///> - I<i> and I<g> are supported as flags, only C<///> can be used as +eparator, and it uses Perl regex syntax instead of Vim syntax. + +=item * Settings: + +C<:se[t]> - display all options + +C<:se[t] {option}>         - display all matching options + +C<:se[t] {option} {value}> - change option to value + +=back + +=head3 MAPPINGS + +=head4 Commands + +=over 4 + +=item * C<:map {lhs}>       - display mappings starting with {lhs} + +=item * C<:map {lhs} {rhs}> - add mapping + +=item * C<:unm[ap] {lhs}>   - remove custom mapping + +=back + +=head4 Parameters + +I<{lhs}> is the key combination to be mapped, I<{rhs}> the target. The +C<E<lt>E<gt>> notation is used + +(e.g. C<E<lt>C-FE<gt>> is I<Ctrl-F>), case is ignored. + Supported C<E<lt>E<gt>> keys are: + +=over 4 + +=item * C<E<lt>C-AE<gt>> - C<E<lt>C-ZE<gt>>, + +=item * C<E<lt>C-^E<gt>>, C<E<lt>C-6E<gt>> + +=item * C<E<lt>SpaceE<gt>> + +=item * C<E<lt>CRE<gt>> - Enter + +=item * C<E<lt>BSE<gt>> - Backspace + +=item * C<E<lt>NopE<gt>> - No-op (Do Nothing). + +=back + +Mapping ex-mode and irssi commands is supported. When mapping ex-mode commands +the trailing C<E<lt>CrE<gt>> is not necessary. Only default mappings can be used +in I<{rhs}>. + +Examples: + +=over 4 + +=item * C<:map w  W>      - to remap w to work like W + +=item * C<:map gb :bnext> - to map gb to call :bnext + +=item * C<:map gB :bprev> + +=item * C<:map g1 :b 1>   - to map g1 to switch to buffer 1 + +=item * C<:map gb :b>     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 + +=item * C<:map E<lt>C-LE<gt> /clear> - map Ctrl-L to irssi command /clear + +=item * C<:map E<lt>C-GE<gt> /window goto 1> + +=item * C<:map E<lt>C-EE<gt> <Nop>> - disable E<lt>C-EE<gt>, it does nothing now + +=item * C<:unmap E<lt>C-EE<gt>> - restore default behavior of C<E<lt>C-EE<gt>> +after disabling it + +=back + +Note that you must use C</> for irssi commands (like C</clear>), the current value +of Irssi's cmdchars does B<not> work! This is necessary do differentiate between +ex-commands and irssi commands. + +=head2 SETTINGS + +The settings are stored as irssi settings and can be set using C</set> as usual +(prepend C<vim_mode_> to setting name) or using the C<:set> ex-command. The +following settings are available: + +=over 4 + +=item * utf8 - Support UTF-8 characters, boolean, default on + +=item * debug - Enable debug output, boolean, default off + +=item * cmd_seq - Char that when double-pressed simulates C<E<lt>EscE<gt>>, string, default '' (disabled) + +=item * start_cmd - Start every line in command mode, boolean, default off + +=item * max_undo_lines - Sze of the undo buffer. Integer, default 50 items. + +=item * ex_history_size - Number of items stored in the ex-mode history. Integer, default 100. + +=item * prompt_leading_space - Ddetermines whether ex mode prepends a space to the displayed input. Boolean, default on + +=back + +In contrast to irssi's settings, C<:set> accepts 0 and 1 as values for boolean +settings, but only vim_mode's settings can be set/displayed. + +Examples: + +   :set cmd_seq=j   # set cmd_seq to j +   :set cmd_seq=    # disable cmd_seq +   :set debug=on    # enable debug +   :set debug=off   # disable debug + +=head1 SUPPORT + +Any behavior different from Vim (unless explicitly documented) should be +considered a bug and reported. + +B<NOTE:> This script is still under heavy development, and there may be bugs. +Please submit reproducible sequences to the bug-tracker at: +L<http://github.com/shabble/irssi-scripts/issues/new> + +or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +Copyright E<copy> 2010-2011 Simon Ruderich C<E<lt>simon@ruderich.orgE<gt>> + +=head1 THANKS + +Particular thanks go to + +=over 4 + +=item * estragib: a lot of testing and many bug reports and feature requests + +=item * iaj: testing + +=item * tmr: explaining how various bits of vim work + +=back + +as well as the rest of C<#irssi> and C<#irssi_vim> on Freenode IRC. + +=head1 LICENCE + +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. + +=head1 BUGS + +=over 4 + +=item * + +count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does + +=item * + +mapping an incomplete ex-command doesn't open the ex-mode with the partial +command (e.g. C<:map gb :b> causes an error instead of opening the ex-mode and +displaying C<:bE<lt>cursorE<gt>>) + +=item * + + undo/redo cursor positions are mostly wrong + +=back + +=head1 TODO + +=over 4 + +=item * + +History: + +=over 4 + +=item * + + C< * /,?,n,N> to search through history (like rl_history_search.pl) + +=back + +=item * + +Window switching (C<:b>) + +=over 4 + +=item * + +Tab completion of window(-item) names + +=item * + +non-sequential matches(?) + +=back + +=back + +See also the TODO file at +L<github|https://github.com/shabble/irssi-scripts/blob/master/vim-mode/TODO> for +many many more things. + +=head2 WONTFIX + +Things we're not ever likely to do: + +=over 4 + +=item * Macros + +=back + + + +=cut + diff --git a/vim-mode/input.txt b/vim-mode/input.txt new file mode 100644 index 0000000..790060a --- /dev/null +++ b/vim-mode/input.txt @@ -0,0 +1,58 @@ +How the input system for vim_mode should work. +============================================= + +* insert mode mappings +* normal (command)  mode mappings +* timeout for ambiguous mappings +**  but support for no timeout for some mappings (like Ctrl-R in insert mode) +* maybe 'timeout' and 'ttimeout' settings (and 'timeoutlen' and 'ttimeoutlen') +* partial commands/mappings (like :map a :b a which inserts ex mode and +* displays :b a there without running any command) +* support for mappings entering insert mode: :map gX itexthere<esc> (also without esc!) +** (.) (repeat) for all (most) commands +* more general command order: 3"ap doesn't work but "a3p does +* arbitrary mappings, like 3dwjjihi<esc>10G (at the moment even :map Y y$ doesn't work) +* support to disable mappings temporarily (:set paste, 'pastetoggle') +* (better) support multiple bindings for the same action (especially <BS> and <C-H>) +* unmapping of internal mappings (for example <C-C> to <Nop> as it's also used for colors in irssi) +* better approach to detecting single ESC push (compared to F-keys or meta-* keys) + +We base the input model on a simple state machine. +It can exist in any of 4 (5) states: + - insert mode +   - insert escape mode +     a transitional mode to distingush that we've received an escape char. + - command mode + - ex mode + - visual mode (can be mostly ignored for now, until I sort my overlays stuff +   out) + +to swap between these modes, there are various bindings available. +The most challenging is going from insert -> command.  Typically this is done +by a single push of the <esc> key, but C-c and <jj> are both alternatives that +people use. + +The single entrypoint we have into the input system is detecting keystrokes. +when a 'gui key pressed' signal is fired, we can receive, process and interrupt +it such that it never gets passed to irssi proper. + +in order to detect single <esc> presses from their meta comrades, we operate +as the following: + +if (key == 27) { +    mode = insert_escape +    add_timeout(short, check_escape_buffer) +} elsif (mode == insert_escape) { +    insert(key, escape_buffer) +} else { +   # do nothing +} + +sub check_escape_buffer { +# when the timeout fires, we can assume that only meta-keys would be fast enough +# to insert additional characters into the buffer.  In which case, we replay +# them and clear the buffer. + mode = insert_mode; +# if the buffer is otherwise empty, we have received a single escape press. + mode = command_mode; +} diff --git a/vim-mode/irssi/config b/vim-mode/irssi/config new file mode 100644 index 0000000..d0b0805 --- /dev/null +++ b/vim-mode/irssi/config @@ -0,0 +1,38 @@ +# Minimal default irssi configuration file - with vim-mode statusbars. + + +servers = ( ); + +chatnets = { }; + +channels = ( ); + +aliases = { }; + +statusbar = { +  default = { +    window = { +      items = { +        barstart = { priority = "100"; }; +        time = { }; +        user = { }; +        window = { }; +        window_empty = { }; +        lag = { priority = "-1"; }; +        act = { priority = "10"; }; +        more = { priority = "-1"; alignment = "right"; }; +        barend = { priority = "100"; alignment = "right"; }; +        vim_mode = { }; +        vim_windows = { }; +      }; +    }; + +    prompt = { +      items = { +        uberprompt = { priority = "-1"; }; +        input = { priority = "10"; }; +      }; +      position = "100"; +    }; +  }; +}; diff --git a/vim-mode/irssi/scripts/autorun/uberprompt.pl b/vim-mode/irssi/scripts/autorun/uberprompt.pl new file mode 120000 index 0000000..3d4d97a --- /dev/null +++ b/vim-mode/irssi/scripts/autorun/uberprompt.pl @@ -0,0 +1 @@ +../../../../prompt_info/uberprompt.pl
\ No newline at end of file diff --git a/vim-mode/irssi/scripts/autorun/vim_mode.pl b/vim-mode/irssi/scripts/autorun/vim_mode.pl new file mode 120000 index 0000000..ed78131 --- /dev/null +++ b/vim-mode/irssi/scripts/autorun/vim_mode.pl @@ -0,0 +1 @@ +../../../../vim-mode/vim_mode.pl
\ No newline at end of file diff --git a/vim-mode/tests.pl b/vim-mode/tests.pl new file mode 100644 index 0000000..9007171 --- /dev/null +++ b/vim-mode/tests.pl @@ -0,0 +1,171 @@ +#!/usr/bin/env perl + +# Must be run in a 80x24 terminal unless a fixed POE is released. + + +use strict; +use warnings; + +use lib '../testing/blib/lib'; + +use Test::Irssi; + +# Mode constants: C(ommand), I(nsert). +sub C () { 0 } +sub I () { 1 } + + +sub statusbar_mode { +    my ($test, $mode) = @_; + +    $test->add_pattern_match(qr/^ \[\d{2}:\d{2}\] \[\] \[1\] \[$mode\]\s+$/, +        'window_sbar', "[$mode] in vim-mode statusbar"); +} +sub cursor_position { +    my ($test, $position) = @_; + +    $test->test_cursor_position($position, 24, "Checking cursor position"); +} + +sub check { +    my ($test, $input, $mode, $position, $delay) = @_; + +    if (defined $input) { +        $test->add_input_sequence($input); +        if (not defined $delay) { +            $delay = 0.1; +            $delay += 0.4 if $input =~ /\e/; # esc needs a longer delay +        } +        $test->add_delay($delay); +    } + +    cursor_position($test, $position); +    if ($mode == C) { +        statusbar_mode($test, 'Command'); +    } elsif ($mode == I) { +        statusbar_mode($test, 'Insert'); +    } else { +        die "Wrong mode: $mode"; +    } +} + +my $tester = Test::Irssi->new +  (irssi_binary  => "irssi", +   irssi_homedir => "./irssi/"); + + +$tester->run_headless(1); +$tester->generate_tap(1); + +my $test = $tester->new_test('insert-command-mode'); +$test->description("switching between insert and command mode"); +# Make sure irssi is finished - not entirely sure why this is necessary. +$test->add_delay(2); + +# We start in insert mode. +check $test, undef, I, 12 + 0; +check $test, "\e",  C, 12 + 0; +check $test, 'i',   I, 12 + 0; + +# Quit irssi, necessary to terminate the test. +#$test->add_input_sequence("\n/quit\n"); + + +# FIXME: multiple tests don't work +#$test = $tester->new_test('basic-movement'); +#$test->description('basic movement'); +#$test->add_delay(2); + +my $test_string = +    'Test $tring. with a 4711, words , w.r#s42  etc.   and more123! ..'; + +check $test, $test_string, I, 12 + length $test_string; +check $test, "\e",         C, 12 + 64; + +# h l +check $test, "0",    C, 12 + 0; +check $test, "l",    C, 12 + 1; +check $test, "l",    C, 12 + 2; +check $test, "l",    C, 12 + 3; +check $test, "l",    C, 12 + 4; +check $test, "l",    C, 12 + 5; +check $test, "l",    C, 12 + 6; +check $test, "l",    C, 12 + 7; +check $test, "h",    C, 12 + 6; +check $test, "h",    C, 12 + 5; +check $test, "h",    C, 12 + 4; +check $test, "h",    C, 12 + 3; +check $test, "h",    C, 12 + 2; +check $test, "10l",  C, 12 + 12; +check $test, "7l",   C, 12 + 19; +check $test, "3l",   C, 12 + 22; +check $test, "3h",   C, 12 + 19; +check $test, "50l",  C, 12 + 64; +check $test, "10l",  C, 12 + 64; +check $test, "24h",  C, 12 + 40; +check $test, "100h", C, 12 + 0; + +# 0 ^ $ +check $test, "I     \e", C, 12 + 4; # insert test string for ^ +check $test, "0",        C, 12 + 0; +check $test, "^",        C, 12 + 5; +check $test, "3^",       C, 12 + 5; +check $test, "12^",      C, 12 + 5; +check $test, "\$",       C, 12 + 46; +check $test, "05x",      C, 12 + 0; # remove test string for ^ + +# <Space> <BS> +check $test, "0",      C, 12 + 0; +check $test, " ",      C, 12 + 1; +check $test, " ",      C, 12 + 2; +check $test, " ",      C, 12 + 3; +check $test, " ",      C, 12 + 4; +check $test, " ",      C, 12 + 5; +check $test, " ",      C, 12 + 6; +check $test, " ",      C, 12 + 7; +check $test, " ",      C, 12 + 8; +check $test, "5 ",     C, 12 + 13; +check $test, "2 ",     C, 12 + 15; +check $test, "10 ",    C, 12 + 25; +check $test, "30 ",    C, 12 + 55; +check $test, "20 ",    C, 12 + 64; +check $test, "10 ",    C, 12 + 64; +check $test, " ",      C, 12 + 64; +check $test, "\x7f",   C, 12 + 63; +check $test, "\x7f",   C, 12 + 62; +check $test, "\x7f",   C, 12 + 61; +check $test, "\x7f",   C, 12 + 60; +check $test, "1\x7f",  C, 12 + 59; +check $test, "3\x7f",  C, 12 + 56; +check $test, "5\x7f",  C, 12 + 51; +check $test, "10\x7f", C, 12 + 41; +check $test, "50\x7f", C, 12 + 0; +check $test, "\x7f",   C, 12 + 0; +check $test, "5\x7f",  C, 12 + 0; + +# f t +check $test, "0",   C, 12 + 0; +check $test, "fe",  C, 12 + 1; +check $test, "fs",  C, 12 + 2; +check $test, "ft",  C, 12 + 3; +check $test, "f ",  C, 12 + 4; +check $test, "2f ", C, 12 + 17; +check $test, "5f,", C, 12 + 17; +check $test, "2f,", C, 12 + 32; +check $test, "tw",  C, 12 + 33; +check $test, "t ",  C, 12 + 40; +check $test, "t ",  C, 12 + 40; +check $test, "t ",  C, 12 + 40; +check $test, "2t ", C, 12 + 41; +check $test, "2t ", C, 12 + 46; +check $test, "3t ", C, 12 + 48; +check $test, "t!",  C, 12 + 60; +check $test, "t ",  C, 12 + 61; +check $test, "t.",  C, 12 + 62; +check $test, "t.",  C, 12 + 62; +check $test, "5t.", C, 12 + 62; +check $test, "\$",  C, 12 + 64; + +$test->add_input_sequence("\n/quit\n"); + +$tester->run; diff --git a/vim-mode/vim_mode.pl b/vim-mode/vim_mode.pl index 7d898ab..b0f739f 100644 --- a/vim-mode/vim_mode.pl +++ b/vim-mode/vim_mode.pl @@ -1,238 +1,584 @@ -# A script to emulate some of the vi(m) features for the Irssi inputline. -# -# It should work fine with at least 0.8.12 and later versions. However some -# features are disabled in older versions (see below for details). Perl >= -# 5.8.1 is recommended for UTF-8 support (which can be disabled if necessary). -# Please report bugs in older versions as well, we'll try to fix them. -# -# Any behavior different from Vim (unless explicitly documented) should be -# considered a bug and reported. -# -# NOTE: This script is still under heavy development, and there may be bugs. -# Please submit reproducible sequences to the bug-tracker at: -# http://github.com/shabble/irssi-scripts/issues -# -# or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) -# -# -# Features: -# -# It supports most commonly used command mode features: -# -# * Insert/Command mode. Escape and Ctrl-C enter command mode. -#   /set vim_mode_cmd_seq j allows to use jj as Escape (any other character -#   can be used as well). -# * Cursor motion: h l 0 ^ $ <Space> <BS> f t F T -# * History motion: j k gg G -#   gg moves to the oldest (first) history line. -#   G without a count moves to the current input line, with a count it goes to -#   the count-th history line (1 is the oldest). -# * Cursor word motion: w b ge e W gE B E -# * Word objects (only the following work yet): aw aW -# * Yank and paste: y p P -# * Change and delete: c d -# * Delete at cursor: x X -# * Replace at cursor: r -# * Insert mode: i a I A -# * Switch case: ~ -# * Repeat change: . -# * Repeat ftFT: ; , -# * Registers: "a-"z "" "0 "* "+ "_ (black hole) -#   Appending to register with "A-"Z -#   "" is the default yank/delete register. -#   "0 contains the last yank (if no register was specified). -#   The special registers "* "+ contain both irssi's cut-buffer. -# * Line-wise shortcuts: dd cc yy -# * Shortcuts: s S C D -# * Scroll the scrollback buffer: Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B -# * Switch to last active window: Ctrl-6/Ctrl-^ -# * Switch split windows: Ctrl-W j Ctrl-W k -# * Undo/Redo: u Ctrl-R -# -# Counts and combinations work as well, e.g. d5fx or 3iabc<esc>. Counts also -# work with mapped ex-commands (see below), e.g. if you map gb to do :bn, then -# 2gb will switch to the second next buffer. -# Repeat also supports counts. -# -# The following insert mode mappings are supported: -# -# * Insert register content: Ctrl-R x (where x is the register to insert) -# -# Ex-mode supports (activated by : in command mode) the following commands: -# -# * Switching buffers: :[N]b [N] - switch to channel number -#                      :b#       - switch to last channel -#                      :b <partial-channel-name> -#                      :b <partial-server>/<partial-channel> -#                      :buffer {args} (same as :b) -#                      :[N]bn[ext] [N] - switch to next window -#                      :[N]bp[rev] [N] - switch to previous window -# * Close window:      :[N]bd[elete] [N] -# * Display windows:   :ls :buffers -# * Display registers: :reg[isters] {args} :di[splay] {args} -# * Display undolist:  :undol[ist] (mostly used for debugging) -# * Source files       :so[urce] - only sources vim_moderc at the moment, -#                                  {file} not supported -# * Mappings:          :map             - display custom mappings -#                      :map {lhs}       - display mappings starting with {lhs} -#                      :map {lhs} {rhs} - add mapping -#                      :unm[ap] {lhs}   - remove custom mapping -# * Save mappings:     :mkv[imrc][!] - like in Vim, but [file] not supported -# * Substitute:        :s/// - i and g are supported as flags, only /// can be -#                              used as separator, uses Perl regex instead of -#                              Vim regex -# * Settings:          :se[t]                  - display all options -#                      :se[t] {option}         - display all matching options -#                      :se[t] {option} {value} - change option to value -# -# -# Mappings: -# -# {lhs} is the key combination to be mapped, {rhs} the target. The <> notation -# is used (e.g. <C-F> is Ctrl-F), case is ignored. Supported <> keys: -# <C-A>-<C-Z>, <C-^>, <C-6>, <Space>, <CR>, <BS>, <Nop>. Mapping ex-mode and -# irssi commands is supported. When mapping ex-mode commands the trailing <Cr> -# is not necessary. Only default mappings can be used in {rhs}. -# Examples: -#     :map w  W      - to remap w to work like W -#     :map gb :bnext - to map gb to call :bnext -#     :map gB :bprev -#     :map g1 :b 1   - to map g1 to switch to buffer 1 -#     :map gb :b     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 -#     :map <C-L> /clear - map Ctrl-L to irssi command /clear -#     :map <C-G> /window goto 1 -#     :map <C-E> <Nop> - disable <C-E>, it does nothing now -#     :unmap <C-E>     - restore default behavior of <C-E> after disabling it -# -# Note that you must use / for irssi commands (like /clear), the current value -# of cmdchars does _not_ work! This is necessary do differentiate between -# ex-commands and irssi commands. -# -# -# Settings: -# -# The settings are stored as irssi settings and can be set using /set as usual -# (prepend vim_mode_ to setting name) or using the :set ex-command. The -# following settings are available: -# -# * utf8: support UTF-8 characters, boolean, default on -# * debug: enable debug output, boolean, default off -# * cmd_seq: char that when double-pressed simulates <esc>, string, default '' -# * start_cmd: start every line in command mode, boolean, default off -# -# In contrast to irssi's settings, :set accepts 0 and 1 as values for boolean -# settings, but only vim_mode's settings can be set/displayed. -# Examples: -#    :set cmd_seq=j   # set cmd_seq to j -#    :set cmd_seq=    # disable cmd_seq -#    :set debug=on    # enable debug -#    :set debug=off   # disable debug -# -# -# The following statusbar items are available: -# -# * vim_mode: displays current mode -# * vim_windows: displays windows selected with :b -# -# -# Configuration -# -# Additionally to the irssi settings described above vim_mode can be -# configured through an external configuration file named "vim_moderc" located -# in ~/.irssi/vim_moderc. If available it's loaded on startup and every -# supported ex-command is run. It's syntax is similar to "vimrc". To (re)load -# it while vim_mode is running use :so[urce]. -# -# Supported ex-commands: -# -# * :map -# -# -# Installation: -# -# As always copy the script into .irssi/scripts and load it with -#     /script load # vim_mode.pl -# -# Use the following command to get a statusbar item that shows which mode -# you're in. -# -#     /statusbar window add vim_mode -# -# And the following to let :b name display a list of matching channels -# -#     /statusbar window add vim_windows -# -# -# Dependencies: -# -# For proper :ex mode support, requires the installation of uberprompt.pl -# Uberprompt can be downloaded from: -# -# http://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl -# -# and follow the instructions at the top of that file for installation. -# -# If you don't need Ex-mode, you can run vim_mode.pl without the -# uberprompt.pl script, but it is recommended. -# -# -# Irssi requirements: -# -# 0.8.12 and above should work fine. However the following features are -# disabled in irssi < 0.8.13: -# -# * j k (only with count, they work fine without count in older versions) -# * gg G -# -# -# Known bugs: -# -# * count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does -# * mapping an incomplete ex-command doesn't open the ex-mode with the partial -#   command (e.g. :map gb :b causes an error instead of opening the ex-mode -#   and displaying :b<cursor>) -# * undo/redo positions are mostly wrong -# -# -# TODO: -# * History: -#   * /,?,n,N to search through history (like history_search.pl) -# * Window switching (:b) -#  * Tab completion of window(-item) names -#  * non-sequential matches(?) -# -# WONTFIX - things we're not ever likely to do -# * Macros -# -# THANKS: -# -# * estragib: a lot of testing and many bug reports and feature requests -# * iaj: testing -# -# LICENCE: -# -# Copyright (c) 2010 Tom Feist & Simon Ruderich -# -# 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. -# -# -# Have fun! +=pod + +=head1 NAME + +vim_mode.pl + +=head1 DESCRIPTION + +An Irssi script to emulate some of the vi(m) features for the Irssi inputline. + +=head1 INSTALLATION + +Copy into your F<~/.irssi/scripts/> directory and load with +C</SCRIPT LOAD vim_mode.pl>. You may wish to have it autoload in one of the +L<usual ways|https://github.com/shabble/irssi-docs/wiki/Guide#Autorunning_Scripts>. + +=head2 DEPENDENCIES + +For proper :ex mode support, vim-mode requires the installation of F<uberprompt.pl> +Uberprompt can be downloaded from: + +L<https://github.com/shabble/irssi-scripts/raw/master/prompt_info/uberprompt.pl> + +and follow the instructions at the top of that file for installation. + +If you don't need Ex-mode, you can run vim_mode.pl without the +uberprompt.pl script, but it is strongly recommended that you use it. + +=head3 Irssi requirements + +0.8.12 and above should work fine. However the following features are +disabled in irssi < 0.8.13: + +=over 4 + +=item * C<j> C<k> (only with count, they work fine without count in older versions) + +=item * C<gg>, C<G> + +=back + +It is intended to work with at Irssi  0.8.12 and later versions. However some +features are disabled in older versions (see below for details). + +Perl >= 5.8.1 is recommended for UTF-8 support (which can be disabled if +necessary).  Please report bugs in older versions as well, we'll try to fix +them.  Later versions of Perl are also faster, which is probably beneficial +to a script of this size and complexity. + +=head2 SETUP + +Vim Mode provides certain feedback to the user, such as the currently active +mode (command, insert, ex), and if switching buffers, which buffer(s) currently +match the search terms. + +There are two ways to go about displaying this information, as described in +the following sections: + +=head3 Statusbar Items + +Run the following command to add a statusbar item that shows which mode +you're in. + +C</statusbar window add vim_mode> + +And the following to let C<:b [str]> display a list of channels matching your +search string. + +C</statusbar window add vim_windows> + +B<Note:> Remember to C</save> after adding these statusbar items to make them +permanent. + +B<Note:> If you would rather have these statusbar items on your prompt +line rather than thte window statusbar, please follow these steps. + +For technical reasons, I<uberprompt> must occasionally call C</statusbar prompt +reset>, which will remove or deactivate any manually added items on the prompt +statusbar.  To get around this, uberprompt provides two command hooks, +C<uberprompt_load_hook> and C<uberprompt_unload_hook>.  Both of these settings +can contain one (or more, using C</EVAL>) commands to be executed when the prompt +is enabled and disabled, respectively. + +See the L<uberprompt documentation|https://github.com/shabble/irssi-scripts/blob/master/prompt_info/README.pod> for further details. + +For I<right-aligned> items (that is, after the input field: + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -after input -alignment right vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +For I<left-aligned> items (before the prompt): + +=over 4 + +=item 1 C</alias vm_add /^statusbar prompt add -before prompt -alignment left vim_mode> + +=item 2 C</alias vm_del /^statusbar prompt remove vim_mode> + +=item 3 C</set uberprompt_load_hook /^vm_add> + +=item 4 C</set uberprompt_unload_hook /^vm_del> + +=back + +If you wish to add both C<vim_mode> and C<vim_windows> items, replace steps 1 and 2 +above with the following (right-aligned): + +=over 4 + +=item 1 C</alias vm_add /^eval /^statusbar prompt add -after input -alightment right vim_mode ; /^statusbar prompt add -after input -alignment right vim_windows> + +=item 2 C</alias vm_del /^eval /^statusbar prompt remove vim_mode ; /^statusbar prompt remove vim_windows> + +=back + +and then complete stages 3 and 4 as above.  Replace the C<-after> and C<-alignment> +to suit your location preferences. + +B<Note:> It is also possible to place the items between the prompt and input field, +by specifying C<-after prompt> or C<-before input> as appropriate. + +=head3 Expando Variables + +Vim mode augments the existing Irssi expando (automatic variables) with two +additional ones: C<$vim_cmd_mode> and C<$vim_wins>. + +C<$vim_cmd_mode> is the equivalent of the C<vim_mode> statusbar item, and +C<$vim_wins> is the counterpart of C<vim_windows>. + +They can be added to your theme, or inserted into your uberprompt using +a command such as: + +"C</set uberprompt_format [$vim_cmd_mode] $*$uber] >" + +=head3 FILE-BASED CONFIGURATION + +Additionally to the irssi settings described in L<settings|/SETTINGS>, vim_mode +can be configured through an external configuration file named "vim_moderc" +located in F<~/.irssi/vim_moderc>. If available it's loaded on startup and every +supported ex-command is run. Its syntax is similar to "vimrc". To (re)load it +while vim_mode is running use C<:so[urce]>. + +Currently Supported ex-commands: + +=over 4 + +=item * C<:map> + +=back + +=head1 USAGE + +The following section is divided into the different modes as supported by Vim itself: + +=head2 COMMAND MODE + +It supports most commonly used command mode features: + +=over 2 + +=item * Insert/Command mode. + +C<Esc> and C<Ctrl-C> enter command mode.  C</set vim_mode_cmd_seq j> allows +to use C<jj> as Escape (any other character can be used as well). + +=item * Cursor motion: + +C<h l 0 ^ $ E<lt>SpaceE<gt> E<lt>BSE<gt> f t F T> + +=item * History motion: + +C<j k gg G> C<gg> moves to the oldest (first) history line.  C<G> without a +count moves to the current input line, with a count it goes to the I<count-th> +history line (1 is the oldest). + +=item * Cursor word motion: + +C<w b ge e W gE B E> + +=item * Word objects (only the following work yet): + +C<aw aW> + +=item * Yank and paste: + + C<y p P> + +=item * Change and delete: + +C<c d> + +=item * Delete at cursor: + +C<x X> + +=item * Replace at cursor: + +C<r> + +=item * Insert mode: + +C<i a I A> + +=item * Switch case: + +C<~> + +=item * Repeat change: + +C<.> + +=item * Repeat + +C<ftFT: ; ,> + +=item * Registers: + +C<"a-"z "" "0 "* "+ "_> (black hole) + +=item * Line-wise shortcuts: + +C<dd cc yy> + +=item * Shortcuts: + +C<s S C D> + +=item * Scroll the scrollback buffer: + +C<Ctrl-E Ctrl-D Ctrl-Y Ctrl-U Ctrl-F Ctrl-B> + +=item * Switch to last active window: + +C<Ctrl-6/Ctrl-^> + +=item * Switch split windows: + +<Ctrl-W j Ctrl-W k> + +=item * Undo/Redo: + +C<u Ctrl-R> + +=back + +Counts and combinations work as well, e.g. C<d5fx> or C<3iabcE<lt>escE<gt>>. Counts also work with mapped ex-commands (see below), e.g. if you map C<gb> to do C<:bn>, then C<2gb> will switch to the second next buffer.  Repeat also supports counts. + +=head3 REGISTERS + +=over 4 + +=item * Appending to register with C<"A-"Z> + +=item * C<""> is the default yank/delete register. + +=item * C<"0> contains the last yank (if no register was specified). + +=item * The special registers C<"* "+> both contain irssi's internal cut-buffer. + +=back + +=head2 INSERT MODE + +The following insert mode mappings are supported: + +=over 4 + +=item * Insert register content: Ctrl-R x (where x is the register to insert) + +=back + +=head2 EX-MODE + +Ex-mode (activated by C<:> in command mode) supports the following commands: + +=over 4 + +=item * Command History: + +C<E<lt>uparrowE<gt>> - cycle backwards through history + +C<E<lt>downarrowE<gt>> - cycle forwards through history + +C<:eh> - show ex history + +=item * Switching buffers: + +C<:[N]b [N]> - switch to channel number + +C<:b#>       - switch to last channel + +C<:b> E<lt>partial-channel-nameE<gt> + +C<:b> <partial-server>/<partial-channel> + +C<:buffer {args}> (same as C<:b>) + +C<:[N]bn[ext] [N]> - switch to next window + +C<:[N]bp[rev] [N]> - switch to previous window + +=item * Close window: + +C<:[N]bd[elete] [N]> + +=item * Display windows: + +C<:ls>, C<:buffers> + +=item * Display registers: + +C<:reg[isters] {args}>, C<:di[splay] {args}> + +=item * Display undolist: + +C<:undol[ist]> (mostly used for debugging) + +=item * Source files: + +C<:so[urce]> - only sources vim_moderc at the moment, +                         F<{file}> not supported + +=item * Mappings: + +C<:map> - display custom mappings + +=item * Saving mappings: + +C<:mkv[imrc][!]> - like in Vim, but [file] not supported + +=item * Substitution: + +C<:s///> - I<i> and I<g> are supported as flags, only C<///> can be used as +eparator, and it uses Perl regex syntax instead of Vim syntax. + +=item * Settings: + +C<:se[t]> - display all options + +C<:se[t] {option}>         - display all matching options + +C<:se[t] {option} {value}> - change option to value + +=back + +=head3 MAPPINGS + +=head4 Commands + +=over 4 + +=item * C<:map {lhs}>       - display mappings starting with {lhs} + +=item * C<:map {lhs} {rhs}> - add mapping + +=item * C<:unm[ap] {lhs}>   - remove custom mapping + +=back + +=head4 Parameters + +I<{lhs}> is the key combination to be mapped, I<{rhs}> the target. The +C<E<lt>E<gt>> notation is used + +(e.g. C<E<lt>C-FE<gt>> is I<Ctrl-F>), case is ignored. + Supported C<E<lt>E<gt>> keys are: + +=over 4 + +=item * C<E<lt>C-AE<gt>> - C<E<lt>C-ZE<gt>>, + +=item * C<E<lt>C-^E<gt>>, C<E<lt>C-6E<gt>> + +=item * C<E<lt>SpaceE<gt>> + +=item * C<E<lt>CRE<gt>> - Enter + +=item * C<E<lt>BSE<gt>> - Backspace + +=item * C<E<lt>NopE<gt>> - No-op (Do Nothing). + +=back + +Mapping ex-mode and irssi commands is supported. When mapping ex-mode commands +the trailing C<E<lt>CrE<gt>> is not necessary. Only default mappings can be used +in I<{rhs}>. + +Examples: + +=over 4 + +=item * C<:map w  W>      - to remap w to work like W + +=item * C<:map gb :bnext> - to map gb to call :bnext + +=item * C<:map gB :bprev> + +=item * C<:map g1 :b 1>   - to map g1 to switch to buffer 1 + +=item * C<:map gb :b>     - to map gb to :b, 1gb switches to buffer 1, 5gb to 5 + +=item * C<:map E<lt>C-LE<gt> /clear> - map Ctrl-L to irssi command /clear + +=item * C<:map E<lt>C-GE<gt> /window goto 1> + +=item * C<:map E<lt>C-EE<gt> <Nop>> - disable E<lt>C-EE<gt>, it does nothing now + +=item * C<:unmap E<lt>C-EE<gt>> - restore default behavior of C<E<lt>C-EE<gt>> +after disabling it + +=back + +Note that you must use C</> for irssi commands (like C</clear>), the current value +of Irssi's cmdchars does B<not> work! This is necessary do differentiate between +ex-commands and irssi commands. + +=head2 SETTINGS + +The settings are stored as irssi settings and can be set using C</set> as usual +(prepend C<vim_mode_> to setting name) or using the C<:set> ex-command. The +following settings are available: + +=over 4 + +=item * utf8 - Support UTF-8 characters, boolean, default on + +=item * debug - Enable debug output, boolean, default off + +=item * cmd_seq - Char that when double-pressed simulates C<E<lt>EscE<gt>>, string, default '' (disabled) + +=item * start_cmd - Start every line in command mode, boolean, default off + +=item * max_undo_lines - Sze of the undo buffer. Integer, default 50 items. + +=item * ex_history_size - Number of items stored in the ex-mode history. Integer, default 100. + +=item * prompt_leading_space - Ddetermines whether ex mode prepends a space to the displayed input. Boolean, default on + +=back + +In contrast to irssi's settings, C<:set> accepts 0 and 1 as values for boolean +settings, but only vim_mode's settings can be set/displayed. + +Examples: + +   :set cmd_seq=j   # set cmd_seq to j +   :set cmd_seq=    # disable cmd_seq +   :set debug=on    # enable debug +   :set debug=off   # disable debug + +=head1 SUPPORT + +Any behavior different from Vim (unless explicitly documented) should be +considered a bug and reported. + +B<NOTE:> This script is still under heavy development, and there may be bugs. +Please submit reproducible sequences to the bug-tracker at: +L<http://github.com/shabble/irssi-scripts/issues/new> + +or contact rudi_s or shabble on irc.freenode.net (#irssi and #irssi_vim) + +=head1 AUTHORS + +Copyright E<copy> 2010-2011 Tom Feist C<E<lt>shabble+irssi@metavore.orgE<gt>> and + +Copyright E<copy> 2010-2011 Simon Ruderich C<E<lt>simon@ruderich.orgE<gt>> + +=head1 THANKS + +Particular thanks go to + +=over 4 + +=item * estragib: a lot of testing and many bug reports and feature requests + +=item * iaj: testing + +=item * tmr: explaining how various bits of vim work + +=back + +as well as the rest of C<#irssi> and C<#irssi_vim> on Freenode IRC. + +=head1 LICENCE + +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. + +=head1 BUGS + +=over 4 + +=item * + +count before register doesn't work: e.g. 3"ap doesn't work, but "a3p does + +=item * + +mapping an incomplete ex-command doesn't open the ex-mode with the partial +command (e.g. C<:map gb :b> causes an error instead of opening the ex-mode and +displaying C<:bE<lt>cursorE<gt>>) + +=item * + + undo/redo cursor positions are mostly wrong + +=back + +=head1 TODO + +=over 4 + +=item * + +History: + +=over 4 + +=item * + + C< * /,?,n,N> to search through history (like rl_history_search.pl) + +=back + +=item * + +Window switching (C<:b>) + +=over 4 + +=item * + +Tab completion of window(-item) names + +=item * + +non-sequential matches(?) + +=back + +=back + +See also the TODO file at +L<github|https://github.com/shabble/irssi-scripts/blob/master/vim-mode/TODO> for +many many more things. + +=head2 WONTFIX + +Things we're not ever likely to do: + +=over 4 + +=item * Macros + +=back + +=cut  use strict;  use warnings; @@ -245,9 +591,9 @@ use Irssi::TextUI;              # for sbar_items_redraw  use Irssi::Irc;                 # necessary for 0.8.14 -use vars qw($VERSION %IRSSI); -$VERSION = "1.0.1"; -%IRSSI = + +our $VERSION = "1.0.2"; +our %IRSSI   =    (     authors         => "Tom Feist (shabble), Simon Ruderich (rudi_s)",     contact         => 'shabble+irssi@metavore.org, ' @@ -267,24 +613,24 @@ sub M_CMD () { 1 }  # insert mode  sub M_INS () { 0 }  # extended mode (after a :?) -sub M_EX () { 2 } +sub M_EX  () { 2 }  # operator command -sub C_OPERATOR () { 0 } +sub C_OPERATOR   () { 0 }  # normal command, no special handling necessary -sub C_NORMAL () { 1 } +sub C_NORMAL     () { 1 }  # command taking another key as argument -sub C_NEEDSKEY () { 2 } +sub C_NEEDSKEY   () { 2 }  # text-object command (i a)  sub C_TEXTOBJECT () { 3 }  # commands entering insert mode -sub C_INSERT () { 4 } +sub C_INSERT     () { 4 }  # ex-mode commands -sub C_EX () { 5 } +sub C_EX         () { 5 }  # irssi commands -sub C_IRSSI () { 6 } +sub C_IRSSI      () { 6 }  # does nothing -sub C_NOP () { 7 } +sub C_NOP        () { 7 }  # setting types, match irssi types as they are stored as irssi settings  sub S_BOOL () { 0 } @@ -310,19 +656,19 @@ 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 },       "\x7F" => { char => '<BS>',    func => \&cmd_h, type => C_NORMAL },       ' '    => { char => '<Space>', func => \&cmd_l, type => C_NORMAL },       # history movement -     j  => { char => 'j',  func => \&cmd_j,  type => C_NORMAL, -             no_operator => 1 }, -     k  => { char => 'k',  func => \&cmd_k,  type => C_NORMAL, -             no_operator => 1 }, -     gg => { char => 'gg', func => \&cmd_gg, type => C_NORMAL, -             no_operator => 1 }, -     G  => { char => 'G',  func => \&cmd_G,  type => C_NORMAL, -             needs_count => 1, no_operator => 1 }, +     j      => { char => 'j',  func => \&cmd_j,  type => C_NORMAL, +                 no_operator => 1 }, +     k      => { char => 'k',  func => \&cmd_k,  type => C_NORMAL, +                 no_operator => 1 }, +     gg     => { char => 'gg', func => \&cmd_gg, type => C_NORMAL, +                 no_operator => 1 }, +     G      => { char => 'G',  func => \&cmd_G,  type => C_NORMAL, +                 needs_count => 1, no_operator => 1 },       # char movement, take an additional parameter and use $movement        f  => { char => 'f', func => \&cmd_f, type => C_NEEDSKEY,                selection_needs_move_right => 1 }, @@ -345,11 +691,11 @@ my $commands       gE => { char => 'gE', func => \&cmd_gE, type => C_NORMAL,               selection_needs_move_right => 1 },       # text-objects, leading _ means can't be mapped! -     _i => { char => 'i', func => \&cmd__i, type => C_TEXTOBJECT }, -     _a => { char => 'a', func => \&cmd__a, type => C_TEXTOBJECT }, +     _i => { char => 'i', func => \&cmd__i,      type => C_TEXTOBJECT }, +     _a => { char => 'a', func => \&cmd__a,      type => C_TEXTOBJECT },       # line movement -     '0' => { char => '0', func => \&cmd_0, type => C_NORMAL }, -     '^' => { char => '^', func => \&cmd_caret, type => C_NORMAL }, +     '0' => { char => '0', func => \&cmd_0,      type => C_NORMAL }, +     '^' => { char => '^', func => \&cmd_caret,  type => C_NORMAL },       '$' => { char => '$', func => \&cmd_dollar, type => C_NORMAL },       # delete chars       x => { char => 'x', func => \&cmd_x, type => C_NORMAL, @@ -423,6 +769,16 @@ my $commands  # All available commands in Ex-Mode.  my $commands_ex    = { +     # arrow keys - not actually used, see handle_input_buffer() +     # TODO: make these work. +     "\e[A"    => { char => ':exprev',    func => \&ex_history_back, +                    type => C_EX }, +     "\e[B"    => { char => ':exnext',    func => \&ex_history_fwd, +                    type => C_EX }, + +     # normal Ex mode commands. +     eh        => { char => ':exhist',    func => \&ex_history_show, +                    type => C_EX },       s         => { char => ':s',         func => \&ex_substitute,                      type => C_EX },       bnext     => { char => ':bnext',     func => \&ex_bnext, @@ -499,11 +855,16 @@ my $commands_ex  # default command mode mappings  my $maps = {}; -# Add all default mappings. -foreach my $char (keys %$commands) { -    next if $char =~ /^_/; # skip private commands (text-objects for now) -    add_map($char, $commands->{$char}); -} +# current imap still pending (first character entered) +my $imap = undef; + +# maps for insert mode +my $imaps +  = { +     # CTRL-R, insert register +     "\x12" => { map  => undef, func => \&insert_ctrl_r }, +    }; +  # GLOBAL VARIABLES @@ -520,8 +881,12 @@ my $settings       start_cmd      => { type => S_BOOL, value => 0 },       # not used yet       max_undo_lines => { type => S_INT,  value => 50 }, +     # size of history buffer for Ex mode. +     ex_history_size => { type => S_INT, value => 100 },       # prompt_leading_space       prompt_leading_space => { type => S_BOOL, value => 1 }, +     # <Leader> value for prepending to commands. +     map_leader     => { type => S_STR,  value => '\\' },      };  sub DEBUG { $settings->{debug}->{value} } @@ -541,6 +906,10 @@ my $should_ignore = 0;  # ex mode buffer  my @ex_buf; +# ex mode history storage. +my @ex_history; +my $ex_history_index = 0; +  # we are waiting for another mapped key (e.g. g pressed, but there are  # multiple mappings like gg gE etc.)  my $pending_map = undef; @@ -582,19 +951,6 @@ my $registers       '*' => '', # same       '_' => '', # black hole register, always empty      }; -foreach my $char ('a' .. 'z') { -    $registers->{$char} = ''; -} - -# current imap still pending (first character entered) -my $imap = undef; - -# maps for insert mode -my $imaps -  = { -     # CTRL-R, insert register -     "\x12" => { map  => undef, func => \&insert_ctrl_r }, -    };  # index into the history list (for j,k)  my $history_index = undef; @@ -615,24 +971,20 @@ my $completion_active = 0;  my $completion_string = '';  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; +    return exists($Irssi::Script::{shift(@_) . '::'});  } -vim_mode_init(); +  # INSERT MODE COMMANDS  sub insert_ctrl_r {      my ($key) = @_; - +    _debug("ctrl-r called");      my $char = chr($key); +    _debug("ctrl-r called with $char"); +      return if not defined $registers->{$char} or $registers->{$char} eq '';      my $pos = _insert_at_position($registers->{$char}, 1, _input_pos()); @@ -1640,6 +1992,10 @@ sub cmd_ex_command {          return _warn("Ex-mode $1$2 doesn't exist!");      } +    # add this item to the ex mode history +    ex_history_add($arg_str); +    $ex_history_index = 0; # and reset the history position. +      my $count = $1;      if ($count eq '') {          $count = undef; @@ -2022,6 +2378,7 @@ sub _parse_mapping {      my ($string) = @_;      $string =~ s/<([^>]+)>/_parse_mapping_bracket($1)/ge; +    _warn("Parse mapping: $string");      if (index($string, '<invalid>') != -1) {          return undef;      } @@ -2047,6 +2404,8 @@ sub _parse_mapping_bracket {      # <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.      } else {          $string = '<invalid>'; @@ -2056,6 +2415,14 @@ sub _parse_mapping_bracket {  sub _parse_mapping_reverse {      my ($string) = @_; +    if (not defined $string) { +        _warn("Unable to reverse-map command: " . join('', @ex_buf)); +        return; +    } + +    my $escaped_leader = quotemeta($settings->{map_leader}->{value}); +    $string =~ s/$escaped_leader/<Leader>/g; +      # Convert char to <char-name>.      $string =~ s/ /<Space>/g;      $string =~ s/\n/<CR>/g; @@ -2070,6 +2437,9 @@ sub _parse_mapping_reverse {  sub _parse_partial_command_reverse {      my ($string) = @_; +    my $escaped_leader = quotemeta($settings->{map_leader}->{value}); +    $string =~ s/$escaped_leader/<Leader>/g; +      # Convert Ctrl-X to ^X.      $string =~ s/([\x01-\x1A])/"^" . chr(ord($1) + 64)/ge;      # Convert Ctrl-6 and Ctrl-^ to <C-^>. @@ -2227,9 +2597,9 @@ sub _matching_windows {  # STATUS ITEMS -# vi mode status item. -sub vim_mode_cb { -    my ($sb_item, $get_size_only) = @_; +#TODO: give these things better names. +sub vim_mode_cmd { +      my $mode_str = '';      if ($mode == M_INS) {          $mode_str = 'Insert'; @@ -2238,7 +2608,7 @@ sub vim_mode_cb {      } else {          $mode_str = '%_Command%_';          if ($register ne '"' or $numeric_prefix or $operator or $movement or -                                $pending_map) { +            $pending_map) {              my $partial = '';              if ($register ne '"') {                  $partial .= '"' . $register; @@ -2260,13 +2630,10 @@ sub vim_mode_cb {              $mode_str .= " ($partial)";          }      } -    $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0); +    return $mode_str;  } -# :b window list item. -sub b_windows_cb { -    my ($sb_item, $get_size_only) = @_; - +sub vim_wins_data {      my $windows = '';      # A little code duplication of cmd_ex_command(), but \s+ instead of \s* so @@ -2286,6 +2653,31 @@ sub b_windows_cb {              }          }      } +    return $windows; +} + +sub vim_exp_mode { +    my ($server, $witem, $arg) = @_; +    return vim_mode_cmd(); +} + +sub vim_exp_wins { +    my ($server, $witem, $arg) = @_; +    return vim_wins_data(); +} + +# vi mode status item. +sub vim_mode_cb { +    my ($sb_item, $get_size_only) = @_; +    my $mode_str = vim_mode_cmd(); +    $sb_item->default_handler($get_size_only, "{sb $mode_str}", '', 0); +} + +# :b window list item. +sub b_windows_cb { +    my ($sb_item, $get_size_only) = @_; + +    my $windows = vim_wins_data();      $sb_item->default_handler($get_size_only, "{sb $windows}", '', 0);  } @@ -2309,8 +2701,10 @@ sub got_key {          $input_buf_timer            = Irssi::timeout_add_once(10, \&handle_input_buffer, undef);          print "Buffer Timer tag: $input_buf_timer" if DEBUG; +      } elsif ($mode == M_INS) { -        if ($key == 3) { # Ctrl-C enter command mode + +        if ($key == 3) { # Ctrl-C enters command mode              _update_mode(M_CMD);              _stop();              return; @@ -2370,6 +2764,13 @@ sub got_key {          Irssi::statusbar_items_redraw("vim_mode");      } elsif ($mode == M_EX) { + +        if ($key == 3) { # C-c cancels Ex mdoe as well. +            _update_mode(M_CMD); +            _stop(); +            return; +        } +          handle_command_ex($key);      }  } @@ -2389,20 +2790,33 @@ sub handle_input_buffer {          _update_mode(M_CMD);      } 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 } @input_buf; -        #     if ($key_str =~ m/^\e\[([ABCD])/) { -        #         print "Arrow key: $1" if DEBUG; -        #     } else { -        #         print "Dunno what that is." if DEBUG; -        #     } -        # } else { -        #     _emulate_keystrokes(@input_buf); -        # } -        _emulate_keystrokes(@input_buf); +        # 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 } @input_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(@input_buf); +        }          # Clear insert buffer, pressing "special" keys (like arrow keys)          # resets it. @@ -2414,7 +2828,7 @@ sub handle_input_buffer {  }  sub flush_input_buffer { -    Irssi::timeout_remove($input_buf_timer); +    Irssi::timeout_remove($input_buf_timer) if defined $input_buf_timer;      $input_buf_timer = undef;      # see what we've collected.      print "Input buffer flushed" if DEBUG; @@ -2537,13 +2951,7 @@ sub handle_command_cmd {      } elsif ($cmd->{type} == C_IRSSI) {          print "Processing irssi-command: $map->{char} ($cmd->{char})" if DEBUG; -        # TODO: fix me more better (general server/win/none context?) -        my $server = Irssi::active_server; -        if (defined $server) { -            $server->command($cmd->{func}); -        } else { -            Irssi::command($cmd->{func}); -        } +        _command_with_context($cmd->{func});          $numeric_prefix = undef;          return 1; # call _stop(); @@ -2746,7 +3154,7 @@ sub handle_command_ex {      # DEL key - remove last character      if ($key == 127) {          print "Delete" if DEBUG; -        if (scalar @ex_buf > 0) { +        if (@ex_buf > 0) {              pop @ex_buf;              _set_prompt(':' . join '', @ex_buf);          # Backspacing over : exits ex-mode. @@ -2764,14 +3172,21 @@ sub handle_command_ex {          print "Tab pressed" if DEBUG;          print "Ex buf contains: " . join('', @ex_buf) if DEBUG;          @tab_candidates = _tab_complete(join('', @ex_buf), [keys %$commands_ex]); - +        _debug("Candidates: " . join(", ", @tab_candidates)); +        if (@tab_candidates == 1) { +            @ex_buf = ( split('', $tab_candidates[0]), ' '); +            _set_prompt(':' . join '', @ex_buf); +        }      # Ignore control characters for now. -    } elsif ($key < 32) { +    } elsif ($key > 0 && $key < 32) {          # TODO: use them later, e.g. completion      # Append entered key      } else { -        push @ex_buf, chr $key; +        if ($key != -1) { +            # check we're not called from an ex_history_* function +            push @ex_buf, chr $key; +        }          _set_prompt(':' . join '', @ex_buf);      } @@ -2794,15 +3209,32 @@ sub _tab_complete {  sub vim_mode_init {      Irssi::signal_add_first 'gui key pressed' => \&got_key; -    Irssi::signal_add 'setup changed' => \&setup_changed; -    Irssi::statusbar_item_register ('vim_mode', 0, 'vim_mode_cb'); +    Irssi::statusbar_item_register ('vim_mode',    0, 'vim_mode_cb');      Irssi::statusbar_item_register ('vim_windows', 0, 'b_windows_cb'); +    Irssi::expando_create('vim_cmd_mode' => \&vim_exp_mode, {}); +    Irssi::expando_create('vim_wins'     => \&vim_exp_wins, {}); + +      # Register all available settings.      foreach my $name (keys %$settings) {          _setting_register($name);      } +    foreach my $char ('a' .. 'z') { +        $registers->{$char} = ''; +    } + +    setup_changed(); + +    Irssi::signal_add 'setup changed' => \&setup_changed; + +    # Add all default mappings. +    foreach my $char (keys %$commands) { +        next if $char =~ /^_/; # skip private commands (text-objects for now) +        add_map($char, $commands->{$char}); +    } +      # Load the vim_moderc file if it exists.      ex_source('source'); @@ -2811,6 +3243,8 @@ sub vim_mode_init {      if ($settings->{start_cmd}->{value}) {          _update_mode(M_CMD); +    } else { +        _update_mode(M_INS);      }  } @@ -2995,7 +3429,7 @@ sub delete_map {      }      push @add, $keys; -    # Restore default keybindings in case we :unmaped a <Nop> or a remapped +    # Restore default keybindings in case we :unmapped a <Nop> or a remapped      # key.      foreach my $key (@add) {          if (exists $commands->{$key}) { @@ -3129,6 +3563,8 @@ sub _update_mode {      }      Irssi::statusbar_items_redraw("vim_mode"); +    Irssi::statusbar_items_redraw ('uberprompt'); +  }  sub _set_prompt { @@ -3206,3 +3642,105 @@ sub _warn {      print '%_vim_mode: ', $warning, '%_';  } + +sub _debug { +    return unless DEBUG; + +    my ($format, @args) = @_; +    my $str = sprintf($format, @args); +    print $str; +} + +sub _command_with_context { +    my ($command) = @_; +    my $context; +    my $window = Irssi::active_win; +    if (defined $window) { +        my $witem = $window->{active}; +        if (defined $witem and ref($witem) eq 'Irssi::Windowitem') { +            $context = $witem; +        } else { +            $context = $window; +        } +    } else { +        my $server = Irssi::active_server; +        if (defined $server) { +            $context = $server; +        } +    } +    if (defined $context) { +        print "Command $command Using context: " . ref($context) if DEBUG; +        $context->command($command); +    } else { +        print "Command $command has no context" if DEBUG; +        Irssi::command($command); +    } +} + +sub ex_history_add { +    my ($line) = @_; + +    # check it's not an exact dupe of the previous history line + +    my $last_hist = $ex_history[$ex_history_index]; +    $last_hist = '' unless defined $last_hist; + +    return if $last_hist eq $line; + +    _debug("Adding $line to ex command history"); + +    # add it to the history +    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. +    } +} + +sub ex_history_fwd { + +    my $hist_max = $#ex_history; +    $ex_history_index++; +    if ($ex_history_index > $hist_max) { +        $ex_history_index = 0; +        _debug("ex history hit top, wrapping to 0"); +    } + +    my $line = $ex_history[$ex_history_index]; +    $line = '' if not defined $line; + +    _debug("Ex history line: $line"); + +    @ex_buf = split '', $line; +    handle_command_ex(-1); +} + +sub ex_history_back { +    my $hist_max = $#ex_history; +    $ex_history_index--; +    if ($ex_history_index == -1) { +        $ex_history_index = $hist_max; +        _debug("ex history hit bottom, wrapping to $hist_max"); + +    } + +    my $line = $ex_history[$ex_history_index]; +    $line = '' if not defined $line; + +    _debug("Ex history line: $line"); +    @ex_buf = split '', $line; +    handle_command_ex(-1); + +} + +sub ex_history_show { +    my $win = Irssi::active_win(); +    $win->print("Ex command history:"); +    for my $i (0 .. $#ex_history) { +        my $flag = $i == $ex_history_index +          ? ' <' +          : ''; +        $win->print("$i " . $ex_history[$i] . $flag); +    } +} +vim_mode_init(); | 
