Joseph Strom
and 1 contributors


Tk::TextVi - Tk::Text widget with Vi-like commands


    use Tk::TextVi;

    $textvi = $window->TextVi( -option => value, ... );


Tk::TextVi is a Tk::TextUndo widget that replaces InsertKeypress() to handle user input similar to vi. All other methods remain the same (and most code should be using $text->insert( ... ) rather than $text->InsertKeypress()). This only implements the text widget and key press logic; the status bar must be drawn by the application (see for an example of this).

To use Tk::TextVi as a drop-in replacement for other text widgets, see Tk::EditorVi which encapsulates Tk::TextVi and the status bar into a composite widget. (This module is included in the Tk::TextVi distribution, but is not installed.)

Functions in Vi that require interaction with the system (such as reading or writing files) are not (currently) handled by this module (This is a feature since you probably don't want :quit to do exactly that). Instead a callback is provided so that the application using the Tk::TextVi widget may decide how to act on them.

The cursor in a Tk::Text widget is a mark placed between two characters. Vi's idea of a cursor is placed on a non-newline character or a blank line. Tk::TextVi treats the cursor as on (in the Vi-sense) the characters following the cursor (in the Tk::Text sense). This means that $ will place the cursor just before the final character on the line.



Callback invoked when the mode or the keys in the pending command change. The current mode and pending keys will be passed to this function.


Callback invoked when messages need to be displayed.


Callback invoked when error messages need to be displayed.


Stores callbacks to handle command-mode commands which require external action. See the commands() method below for more details.


All methods present in Tk::Text and Tk::TextUndo are inherited by Tk::TextVi. Additional or overridden methods are as follows:

$text->InsertKeypress( $char );

This replaces InsertKeypress() in Tk::Text to recognise vi commands.

$text->SetCursor( $index );

This replaces SetCursor() in Tk::Text with one that is aware of the visual selection.

$text->viMode( $mode );

Returns the current mode of the widget:

    'i'     # insert
    'n'     # normal
    'c'     # command
    'R'     # replace
    'v'     # visual character
    'V'     # visual line

There is also a fake mode:

    '/'     # Incremental search

If the 'q' command (record macro) is currently active, a q will be appended to the mode.

The insert-XXX modes (entered by Control-O from insert mode) are indicated by a two-character sequence, 'i' followed by the character of the mode that is active. (e.g. 'in' is insert-normal).

If the $mode parameter is supplied, it will set the mode as well. Any pending keystrokes will be cleared (this brings the widget to a known state). Macro-recording or incremental search cannot be enabled from this function.


Returns the current buffer of pending keystrokes. In normal or visual mode this is the pending command, in command mode this is the partial command line.


Returns a list of all pending error messages.


Returns a list of all pending non-error messages (for example the result of normal-ga)

$text->viMap( $mode, $sequence, $ref, $force )

$mode should be one of qw( n c v ) for normal, command and visual mode respectively. Mappings are shared between the different visual modes. $sequence is the keypress sequence to map the action to. To map another sequence of keys to be interpreted by Tk::TextVi as keystrokes, pass a scalar reference. A code reference will be called by Tk::Text (the signature of the function is described below). A hash reference can be used to restore several mappings (as described below). If $ref is the empty string the current mapping is deleted.

The function may fail--returning undef--in two cases:

  • You attempt to map to a sequence that begins another command (for example you cannot map to 'g' since there is a 'ga' command). Setting $force to a true value will force the mapping and will remove all other mappings that begin with that sequence.

  • You attempt to map to a sequence that starts with an existing command (for example, you cannot map to 'aa' since there is an 'a' command). Setting $force to a true value will remove the mapping that conflicts with the requested sequence.

$w->commands( $key => $sub, $key2 => $sub2 ) =item $w->commands( { $key => $sub } )

Sets the commands configuration setting. In the first form, the given commands are updated with the listed commands. Passing 'undef' for a subroutine will remove that entry. In the second form, the passed hashref replaces the current command list. The subroutine associated with the key 'NOT_SUPPORTED' is called when a typed command is not found in the hash.

Each callback receives arguments of the form:

    my ( $textvi, $force, $args, $range ) = @_;

Where $textvi is the Tk::TextVi instance, $force is a true value if the command was followed by an exclaimation point, $args is any text following the command and $range is an arrayref giving any lines entered before the command. The elements of this array are valid input to the Tk::Text->index() method.

The NOT_SUPPORTED callback takes an additional argument containing the typed command:

    my ( $textvi, $cmd, $force, $args, $range ) = @_;

The following commands are defined by TextVi and may take different arguments than the above:


Called for the :split command. The callback should return a Tk::TextVi instance to use as the newly created window. See EXPERIMENTAL FEATURES below for more details. None of the arguments are currently meaningful.


Called for the :! (filter) command. $args is the command line and $range gives the lines to process ($textvi->get( @$range ) will return the text to be filtered). The $force argument is not meaningful.

The callback should return the text filtered through the given program specified, or undef if the text should not be modified (either due to an error executing the command, or the callback has updated the widget itself).

All commands listed in the implemented commands that are not listed above are implemented internally and ignore these callbacks.


The bindings present in Tk::Text are inherited by Tk::TextVi, however it is not safe to rely on the control key bindings since many of these are used by vi.



This setting is a combination of the softtabstop and expandtab found in Vi/Vim. Setting it to a non-zero value has the following effects: The backspace key will delete spaces to reach a column number that is an even multiple of the softtabstop value; the tab key will insert places to reach the next column that is an even multiple of the softtabstop value. When set to zero, backspace always deletes one character and tab inserts a literal tab. (default value is 4)


Supported Commands

Insert Mode

Keypresses in insert mode are added to the text literally except for the following special keys.

    Tab         Insert spaces up to the next softtabstop
    Backspace   Delete a character or spaces back to the last softtabstop
    Control-O   Enter a single normal-mode command

Normal Mode

    a - enter insert mode after the current character
    d - delete over 'motion' and store in 'register'
        dd - delete a line
    f - find next occurrence of 'character' on this line
    g - one of the two-character commands below
        ga - print ASCII code of character at cursor
        gg - go to the 'count' line
    h - left one character on this line
    i - enter insert mode
    j - down one line
    k - up one line
    l - right one character on this line
    m - set 'mark' at cursor location
    n - next occurrance of last match
    o - open a line below cursor 
    p - insert contents of 'register'
    q - record keystrokes
    r - replace character
    t - move one character before the next occurrence of 'character'
    u - undo
    v - enter visual mode
    w - advance one word [1]
    x - delete character
    y - yank over 'motion' into 'register'
        yy - yank a line

    D - delete until end of line
    G - jump to 'count' line
    O - open line above cursor
    R - enter replace mode
    V - enter visual line mode
    W - advance one word

    ` - move cursor to 'mark'
    ~ - toggle case of next character
    @ - execute keys from register
    $ - go to last character of current line
    % - find matching bracketing character
    0 - go to start of current line
    : - enter command mode
    / - search using a regex [2]

    [1] The w command is currently mapped to W
    [2] The / command uses a perl regex not the vi or vim syntax

Visual Mode

Normal-mode motion commands will move the end of the visual area. Normal-mode commands that operate over a motion will use the visual selection.

There are currently no commands defined specific to visual mode.

Command Mode

        - places the cursor at the last item in range
    :map sequence commands
        - maps sequence to commands
        - clear the highlighting from the last search
    :set setting?
        - prints the value of a setting [1]
    :set setting=value
        - set the value of a setting
        - split the window
        - filter text through program

    [1] :set does not display a setting's value as a result of the command
        :set non-bool-setting.  The final ? must be supplied.

Commands may have a ! suffix to force completion (e.g. :map! with map a command even if it will overwrite existing mappings). They may also be prefixed with a range of text to operate on:

    .           # The current line number
    $           # The final line
    %           # The entire text, same as 1,$
    NUM         # Line number NUM
    'x          # The line containing mark x
    RANGE+NUM   # NUM lines after RANGE

Multiple values may be separated with , or ; (Tk::TextVi does not currently distinguish between the delimiters).



First, :split is only included as a "look at this cool feature" do not count on it to work the same way in the future, or work at all now. It doesn't even support the ":split file" syntax. The current implementation is a bit memory-intensive and slows many basic methods of the Tk::Text widget (don't use :split and you won't get penalized).

Second, none of the supporting commands are implemented. :quit will not close only one window, and there are no Normal-^W commands.

The split callback should return a new Tk::TextVi widget to the caller or a string to be used as an error message. The module will copy the contents and make sure all changes in the text are visible in both widgets.


Perl subroutines can be mapped to keystrokes using the viMap() method described above. Normal and visual mode commands receive arguments like:

    my ( $widget, $keys, $count, $register, $wantmotion ) = @_;

Where $widget is the Tk::TextVi object, $keys are any key presses entered after those that triggered the function. Unless you've raised X_NO_KEYS this should be an empty string. $count is the current count, zero if none has been set. $register contains the name of the entered register. $wantmotion will be a true value if this command is being called in a context that requires a cursor motion (such as from a d command).

Commands receive arguments in the following format:

    my ( $widget, $forced, $argument, $range ) = @_;

$forced is set to a true value if the command ended with an exclamation point. $argument is set to anything that comes after the command. $range is an array reference that contains any line numbers removed from the front of the command. The elements are valid input to Tk::Text::index.

To move the cursor a normal-mode command should return an array reference. The first parameter is a string representing the new character position in a format suitable to Tk::Text. The second is either 'line' or 'char' to specify line-wise or character-wise motion. Character-wise motion should also specify 'inc' or 'exc' for inclusive or exclusive motion as the third parameter.

Scalar references will be treated as a sequence of keys to process. All other return values will be ignored, but avoid returning references (any future expansion will use leave plain scalar returns alone).



If a true value is passed for $wantmotion and the function is not a motion command, die with this value.


Use when additional key presses are required to complete the command.


For when the command can't complete and panic is more appropriate than doing nothing.


$text->EvalKeys( $keys, $count, $register, $wantmotion )

Uses keys to determine the function to call passing it the count, register and wantmotion parameters specified. The return value will be whatever that function returns. If wantmotion is a true value the return value will always be an array reference as described above.

Normally you want to call this function like this, passing in the set of keystrokes after the command, the current count, the current register and setting wantmotion to true:

    $w->EvalKeys( @_[1..3], 1 )
$text->setMessage( $msg )

Queue a message to be displayed and generate the associated event.

$text->setError( $msg )

Same as setMessage, but the message is added to the error list and the error message event is generated.

$text->registerStore( $register, $text )

Store the contents of $text into the specified register. The text will also be stored in the unnamed register. If the '*' register is specified, the clipboard will be used. If the black-hole or a read-only register is specified nothing will happen.

$text->registerGet( $register )

Returns the text stored in a register

$text->settingGet( $setting )

Returns the value of the specified setting.


If you find a bug in the handling of a vi-command, please try to produce an example that looks something like this:

    $text->Contents( <<END );

    $text->InsertKeypress( $_ ) for split //, 'commands in error';

Along with the expected final state of the widget (contents, cursor location, register contents etc).

If the bug relates to the location of the cursor after the command, note the difference between Tk::Text cursor positions and vi cursor positions described above. The command may be correct, but the cursor location looks wrong due to this difference.

Known Bugs

  • Using the mouse or $text->SetCursor you may illegally place the cursor after the last character in the line.

  • Similarly, movement with the mouse or arrow keys can cause problems when a the state of the widget depends on cursor location.

  • Counts are not implemented on insert commands like normal-i or normal-o.

  • Commands that use mappings internally (D and x) do not correctly use the count or registers.

  • Normal-/ should behave like a motion, but doesn't.

  • Normal-/ and normal-n will not wrap after hitting end of file.

  • Normal-u undoes individual Tk::Text actions rather than vi-commands.

  • Insert-Control-O does not work with visual-mode

  • Keypresses that map to a movement command do not work as motions.

  • This modules makes it much easier to commit the programmer's third deadly sin.


Tk::TextUndo and Tk::Text for details on the inherited functionality.

:help index (in vim) for details on the commands emulated.


Joseph Strom, <>


Copyright 2008 Joseph Strom, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.