The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

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

SYNOPSIS

    use Tk::TextVi;

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

DESCRIPTION

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 TextViDemo.pl for an example of this).

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

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).

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.

Options

-statuscommand

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.

-messagecommand

Callback invoked when messages need to be displayed.

-errorcommand

Callback invoked when error messages need to be displayed.

-systemcommand

Callback invoked when the parent application needs to take action. If you return 'undef' the widget will pretend that command doesn't exist and do nothing. Currently, the argument can be:

    'filter'    Text is to be filtered from the :! command
                The command and text are passed as arguments.
    'split'     :split (see EXPERIMENTAL FEATURES below)
    'quit'      The :quit command has been entered

See the demonstration program for examples.

Methods

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.

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.

$text->viPending;

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.

$text->viError;

Returns a list of all pending error messages.

$text->viMessage;

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.

Bindings

All bindings present in Tk::Text are inherited by Tk::TextVi. Do not rely on this as these bindings will be replaced once the vi meaning of those keystrokes is implemented.

SETTINGS

softtabstop =item sts

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)

COMMANDS

Supported Commands

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
    :noh
    :nohl
    :nohlsearch
        - clear the highlighting from the last search
    :quit
    :q
        - signal quit
    :set setting?
        - prints the value of a setting [1]
    :set setting=value
        - set the value of a setting
    :split
        - split the window
    :!program
        - 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).

EXPERIMENTAL COMMANDS

:split

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.

When the -systemcommand callback receives the 'split' action, it 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 the changes in the text are visible in both widgets.

WRITING COMMANDS

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 specific '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).

Exceptions

X_NO_MOTION

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

X_NO_KEYS

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

X_BAD_STATE

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

Methods

$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.

BUGS AND CAVEATS

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 );
    Some
    Initial
    State
    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.

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

SEE ALSO

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

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

AUTHOR

Joseph Strom, <j-strom@verizon.net>

COPYRIGHT & LICENSE

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.