Tk-TextVi

 view release on metacpan or  search on metacpan

lib/Tk/TextVi.pm  view on Meta::CPAN

# Only invoke the special split-variant functions when we're using
# :split
my @split_func = qw| delete insert tagAdd tagConfigure tagRemove |;

{
    no strict;
    for my $func ( @split_func ) {
        *{ "split_$func" } = sub {
            my ($w,@args) = @_;

            if( defined $w->{VI_SPLIT_SHARE} ) {
                for my $win ( @{ $w->{VI_SPLIT_SHARE} } ) {
                    "Tk::TextUndo::$func"->( $win, @args );
                }
            }
            else {
                "Tk::TextUndo::$func"->( $w, @args );
            }
        }
    }
}

sub vi_split {
    my ($w,$newwin) = @_;

    # First time replace all the functions with the magical split versions
    if( not defined $w->{VI_SPLIT_SHARE} ) {
        $w->{VI_SPLIT_SHARE} = [ $w ];

        no strict;
        for my $func (@split_func) {
            *{"Tk::TextVi::$func"} = \&{"split_$func"};
        }
    }

    $newwin->Contents( $w->Contents );
    $newwin->SetCursor( $w->index('insert') );
    $newwin->yviewMoveto( ($w->yview)[0] );

    push @{$w->{VI_SPLIT_SHARE}}, $newwin;
    $newwin->{VI_SPLIT_SHARE} = $w->{VI_SPLIT_SHARE}
}

# Public Methods #####################################################

sub viMode {
    my ($w, $mode) = @_;
    my $rv = $w->{VI_SUBMODE} . $w->{VI_MODE};
    $rv .= 'q' if defined $w->{VI_RECORD_REGISTER};

    if( defined $mode ) {
        croak "Tk::TextVi received invalid mode '$mode'"
            if $mode !~ m[ ^ [nicvVR/] $ ]x;
        $w->{VI_MODE} = $mode;
        $w->{VI_SUBMODE} = '';
        $w->{VI_PENDING} = '';
        $w->{VI_REPLACE_CHARS} = '';
        $w->tagRemove( 'sel', '1.0', 'end' );

        # XXX: Hack
        if( (caller)[0] eq 'Tk::TextVi' ) {
            $w->{VI_FLAGS} |= F_STAT;
        }
        else {
            # TODO: this is broken
            $w->Callback( '-statuscommand', $w->{VI_MODE}, $w->{VI_PENDING} );
        }
    }

    return $rv;
}

sub viPending {
    my ($w) = @_;
    return $w->{VI_PENDING};
}

sub viError {
    my ($w) = @_;
    return shift @{ $w->{VI_ERROR} };
}

sub viMessage {
    my ($w) = @_;
    return shift @{ $w->{VI_MESSAGE} };
}

sub viMap {
    my ( $w, $mode, $sequence, $ref, $force ) = @_;

    # TODO: nmap,imap,vmap etc. support
    my @mapmodes = map { $w->{MAPS}{$_} } split //, $mode;

    while( length( $sequence ) > 1 ) {
        # Get the next character in the sequence
        my $c = substr $sequence, 0, 1, '';

        # Advance the mapping locations
        for my $map ( @mapmodes ) {

            # Nothing at this location yet, add a hash
            if( not defined $map->{$c} ) {
                $map->{$c} = { };
            }
            # Something is already mapped here
            elsif( 'HASH' ne ref $map->{$c} ) {
                return unless $force;
                # If $force was defined, nuke the previous entry
                $map->{$c} = { };
            }

            $map = $map->{$c};
        }
    }

    # Check that a mapping can be placed here
    for my $map ( @mapmodes ) {
        if( defined $map->{$sequence} and       # Something is here
            'HASH' eq ref $map->{$sequence} and # it's a longer mapping
            scalar keys %{ $map->{$sequence}} ) # and its in use
        {

lib/Tk/TextVi.pm  view on Meta::CPAN

        }
        elsif( $key eq TAB ) {
            my $sts = $w->settingGet( 'softtabstop' );

            if( $sts > 0 ) {
                my $col = $w->index('insert');
                (undef,$col) = split /\./, $col;
                # Perl's modulus is well behaved so this works fine
                $col = (-$col % $sts) || $sts;
                $w->insert( 'insert', ' ' x $col );
            }
            else {
                $w->insert( 'insert', "\t" );
            }
        }
        elsif( $key eq "\cO" ) {
            $w->viMode('n');
            $w->{VI_SUBMODE} = 'i';
        }
        else {
            $w->insert( 'insert', $key );
            $w->see( 'insert' );
        }
    }
    elsif( $w->{VI_MODE} eq 'R') {
        if( $key eq ESC ) {
            $w->addGlobEnd;
            $w->viMode('n');
            $w->SetCursor( 'insert -1c' )
                if( $w->compare( 'insert', '!=', 'insert linestart' ) );
        }
        elsif( $key eq BKSP ) {
            my $r = chop $w->{VI_REPLACE_CHARS};
            if( $r ne '' ) {
                $w->delete( "insert -1c" );
                if( $r ne "\0" ) {
                    $w->insert( 'insert', $r );
                    $w->SetCursor( 'insert -1c' );
                }
            }
            else {
                $w->SetCursor( 'insert -1c' );
            }
        }
        elsif( $w->get( 'insert' ) ne "\n" ) {
            $w->{VI_REPLACE_CHARS} .= $w->get( 'insert' );
            $w->delete( 'insert' );
            $w->insert( 'insert', $key );
        }
        else {
            $w->{VI_REPLACE_CHARS} .= "\0";
            $w->insert( 'insert', $key );
        }
    }
    else {
        die "Tk::TextVi internal state corrupted";
    }

    # Does the UI need to update?
    # XXX: HACK
    if( (caller)[0] ne 'Tk::TextVi' ) {
        $w->Callback( '-statuscommand',
            $w->viMode,
            $w->{VI_PENDING} ) if( $w->{VI_FLAGS} & F_STAT );
        $w->Callback( '-messagecommand' ) if $w->{VI_FLAGS} & F_MSG ;
        $w->Callback( '-errorcommand' ) if $w->{VI_FLAGS} & F_ERR ;

        $w->{VI_FLAGS} = 0;
    }

    # Command may have moved insert cursor out of window
    $w->see('insert');
}

# Handles the command processing shared between Normal
# and visual mode commands
sub InsertKeypressNormal {
    my ($w,$key) = @_;
    my $res;

    my $keys = $w->{VI_PENDING} . $key;
    $w->{VI_PENDING} = '';              # Assume command will work

    eval { $res = $w->EvalKeys($keys); };# try to process as a command

    if( $@ ) {
        die $@ if $@ !~ /^VI_/;         # wasn't our exception

        if( $@ eq X_NO_KEYS ) {         # Restore pending keys
            $w->{VI_PENDING} = $keys;
        }
    }
    elsif ( lc $w->{VI_MODE} eq 'v' ) {
        # hack, clear visual mode after command
        ref $res or $w->viMode('n');
    }
    else {
        # Restore mode
        $w->viMode( $w->{VI_SUBMODE} ) if $w->{VI_SUBMODE};
    }

    $w->{VI_FLAGS} |= F_STAT;
    return $res;
}

# Takes a string of keypresses and dispatches it to the right command
sub EvalKeys {
    my ($w, $keys, $count, $register, $motion) = @_;
    my $res;
    my $mode = lc substr $w->{VI_MODE}, 0, 1;       # V and v use the same maps

    $count = 0 unless defined $count;

    # Use the currently pending keys by default
    $keys = $w->{VI_PENDING} unless defined $keys;

    # Extract the count
    if( $keys =~ s/^([1-9]\d*)// ) {
        $count ||= 1;
        $count *= $1;
    }



( run in 1.083 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )