VIxcel

Custom key-mappings, to make using Excel a bit more like using VIM

This is still proof of concept. I'll keep this page updated as I develop it further.

Note: this code should be placed in a workbook module.


' VIxcel
' ======
' A VBA extension to Microsoft Excel, implementing
' key mappings and functions similar to those of the
' VI (specifically VIM) text editors.
'
' Version 0.0.1 25 June 2002.  Pre-alpha.
'
' Author: osfameron (osfameron@abelgratis.co.uk / hakim@earthling.net)
' Copyright (c) osfameron 2002
'
' This program may be freely distributed under:
'   the GNU GPL
'   OR the Perl Artistic license
'
' No fitness for absolutely any purpose is implied, and
' no liability for absolutely anything is admitted.
'
' For an overview, please look at the DOC page of the workbook

Option Explicit

Private m_Count As String
Private m_Command As String
Private m_Mode As Integer
Private m_LastLifeSign As Long
Private m_Head As Range
Private m_Range As Range
Private m_ExpectCallback As String
Const VI_TimeoutTime As Integer = 5 ' seconds
Const VI_Normal As Integer = 1
Const VI_ExpectDirection As Integer = 2
Const VI_VisualCell As Integer = 3
Const VI_VisualLine As Integer = 4

Sub Auto_Open()
    Dim char As Variant, char2$
    For char = Asc("a") To Asc("z")
        VI_Bind Chr(char)
    Next
    For char = Asc("A") To Asc("Z")
        VI_Bind Chr(char), Chr(char) & "_CAP"
    Next
    For char = Asc("0") To Asc("9")
        VI_Bind Chr(char)
    Next
    For Each char In Array("HOME", "INSERT", "END", "ESC")
        char2$ = char
        VI_Bind "{" & char2$ & "}", char2$
    Next
End Sub

Sub VI_Bind(char As String, Optional alt_name As String = "")
    Dim bind_name As String
    If alt_name <> "" Then bind_name = alt_name Else bind_name = char
    Application.OnKey char, "VI_Key_" & bind_name
    Debug.Print "Sub VI_Key_" & bind_name
    Debug.Print vbTab & "VI_Key """ & bind_name & """"
    Debug.Print vbTab & "VI_Error ""Command not implemented: '" & bind_name & "'"""
    Debug.Print vbTab & "VI_Cancel_Command"
    Debug.Print "End Sub"
End Sub
Sub VI_Error(errstring As String)
    MsgBox errstring, vbCritical, "VI-like extension: error"
    End
End Sub
Function VI_Count() As Integer
    If m_Count = "" Then VI_Count = 1 Else VI_Count = m_Count
    Debug.Print VI_Count
End Function
Sub VI_Cancel_Command()
    m_Command = ""
    m_Count = ""
    Set m_Head = Nothing
    m_Mode = VI_Normal
    m_ExpectCallback = ""
    Application.StatusBar = "Cancelled"
    ActiveCell.Select
End Sub
Sub VI_TimeOut_Command()
    If Timer - m_LastLifeSign >= VI_TimeoutTime - 1 Then
        VI_Cancel_Command
    Else
        Application.StatusBar = "Not cancelled: " & Timer & "," & m_LastLifeSign
    End If
End Sub

Sub VI_StatusBar()
    Application.StatusBar = m_Count & " " & m_Command
End Sub
Sub VI_Set_TimeOut()
    m_LastLifeSign = Timer
    Application.OnTime Now + TimeValue("00:00:" & VI_TimeoutTime), "VI_TimeOut_Command"
End Sub
Sub VI_Jump_Command_End()
    
    If m_Mode = VI_ExpectDirection Then
        Application.OnTime Now, m_ExpectCallback
        Debug.Print "Callback: " & m_ExpectCallback
    ElseIf m_Mode = VI_VisualCell Then
        VI_VisualCell_Callback
        m_Count = ""
    ElseIf m_Mode = VI_VisualLine Then
        VI_VisualLine_Callback
        m_Count = ""
    Else
        VI_Cancel_Command
    End If
End Sub
Sub VI_VisualCell_Callback()
    Dim tail As Range
    Set tail = ActiveCell
    Range(m_Head, tail).Select
    tail.Activate
End Sub
Sub VI_VisualLine_Callback()
    Dim tail As Range
    Set tail = ActiveCell
    Dim t As Integer, b As Integer, l As Integer, r As Integer
    l = m_Range.Column
    r = m_Range.Column + m_Range.Columns.count - 1
    If tail.Row < m_Head.Row Then
        t = tail.Row
        b = m_Head.Row
    Else
        b = tail.Row
        t = m_Head.Row
    End If
    Range(Cells(t, l), Cells(b, r)).Select
    tail.Activate
End Sub


Sub VI_Key(char As String)
    m_Command = m_Command & char
    VI_StatusBar
    VI_Set_TimeOut
End Sub
Sub VI_Key_Count(char As String)
    m_Count = m_Count & char
    VI_StatusBar
    VI_Set_TimeOut
End Sub

 Sub VI_Key_a()
    VI_Key "a"
    Application.SendKeys "{F2}"
    VI_Cancel_Command
End Sub
Sub VI_Key_b()
    VI_Key "b"
    VI_Error "Command not implemented: 'b'"
    VI_Cancel_Command
End Sub
Sub VI_Key_c()
    VI_Key "c"
    SendKeys "r"
End Sub
Sub VI_Key_d()
    VI_Key "d"
    If m_Mode = VI_ExpectDirection Then
        Dim count As Integer
        count = VI_Count
        VI_Cancel_Command
        Application.SendKeys "V" & count - 1 & "jd"
    ElseIf m_Mode = VI_VisualCell Then
        m_Head.Activate
        Application.Dialogs(xlDialogEditDelete).Show
        VI_Cancel_Command
    ElseIf m_Mode = VI_VisualLine Then
        m_Head.Activate
        Selection.Delete (xlShiftUp)
        VI_Cancel_Command
    Else
        m_Mode = VI_ExpectDirection
        Set m_Head = ActiveCell
        m_ExpectCallback = "VI_Key_d_callback"
        Debug.Print "Setting: " & m_ExpectCallback
    End If
End Sub
Sub VI_Key_d_callback()
    If m_Mode = VI_ExpectDirection Then
        Dim tail As Range
        Set tail = ActiveCell
        Range(m_Head, tail).Select
        m_Head.Activate
        Application.Dialogs(xlDialogEditDelete).Show
        ActiveCell.Select
    End If
    VI_Cancel_Command
End Sub
Sub VI_Key_e()
    VI_Key "e"
    VI_Error "Command not implemented: 'e'"
    VI_Cancel_Command
End Sub
Sub VI_Key_f()
    VI_Key "f"
    VI_Error "Command not implemented: 'f'"
    VI_Cancel_Command
End Sub
Sub VI_Key_g()
    VI_Key "g"
    VI_Error "Command not implemented: 'g'"
    VI_Cancel_Command
End Sub
Sub VI_Key_h()
    VI_Key "h"
    On Error GoTo VI_Error
    ActiveCell.Offset(0, -VI_Count).Activate
    VI_Jump_Command_End
    Exit Sub
VI_Error:
    VI_Cancel_Command
    Cells(ActiveCell.Row, 1).Activate
    Beep
End Sub
Sub VI_Key_i()
    VI_Key "i"
    Application.SendKeys "{F2}{HOME}"
    VI_Cancel_Command
End Sub
Sub VI_Key_j()
    VI_Key "j"
    On Error GoTo VI_Error
    ActiveCell.Offset(VI_Count, 0).Activate
    VI_Jump_Command_End
    Exit Sub
VI_Error:
    VI_Cancel_Command
    Cells(65536, ActiveCell.Column).Activate
    Beep
End Sub
Sub VI_Key_k()
    VI_Key "k"
    On Error GoTo VI_Error
    ActiveCell.Offset(-VI_Count, 0).Activate
    VI_Jump_Command_End
    Exit Sub
VI_Error:
    VI_Cancel_Command
    Cells(1, ActiveCell.Column).Activate
    Beep
End Sub
Sub VI_Key_l()
    VI_Key "l"
    On Error GoTo VI_Error
    ActiveCell.Offset(0, VI_Count).Activate
    VI_Jump_Command_End
    Exit Sub
VI_Error:
    VI_Cancel_Command
    Cells(ActiveCell.Row, 256).Activate
    Beep
End Sub

Sub VI_Key_m()
    VI_Key "m"
    VI_Error "Command not implemented: 'm'"
    VI_Cancel_Command
End Sub
Sub VI_Key_n()
    VI_Key "n"
    VI_Error "Command not implemented: 'n'"
    VI_Cancel_Command
End Sub
Sub VI_Key_o()
    VI_Key "o"
    If m_Mode = VI_VisualCell Or m_Mode = VI_VisualLine Then
        Dim temp As Range
        Set temp = m_Head
        Set m_Head = ActiveCell
        temp.Activate
    Else
        Set m_Head = ActiveCell
        Set m_Range = m_Head.CurrentRegion
        VI_VisualLine_Callback
        Dim i As Integer
        For i = 1 To VI_Count
            Selection.Offset(1, 0).Insert xlShiftDown
        Next
        VI_Cancel_Command
    End If
End Sub
Sub VI_Key_p()
    VI_Key "p"
    VI_Error "Command not implemented: 'p'"
    VI_Cancel_Command
End Sub
Sub VI_Key_q()
    VI_Key "q"
    VI_Error "Command not implemented: 'q'"
    VI_Cancel_Command
End Sub
Sub VI_Key_r()
    VI_Key "r"
    Application.SendKeys "{F2}+{HOME}"
    VI_Cancel_Command
End Sub
Sub VI_Key_s()
    VI_Key "s"
    VI_Error "Command not implemented: 's'"
    VI_Cancel_Command
End Sub
Sub VI_Key_t()
    VI_Key "t"
    VI_Error "Command not implemented: 't'"
    VI_Cancel_Command
End Sub
Sub VI_Key_u()
    VI_Key "u"
    Application.Undo
    VI_Cancel_Command
End Sub
Sub VI_Key_v()
    VI_Key "v"
    If m_Mode = VI_VisualCell Then
        m_Mode = VI_Normal
        ActiveCell.Select
    Else
        m_Mode = VI_VisualCell
        Set m_Head = ActiveCell
    End If
End Sub
Sub VI_Key_w()
    VI_Key "w"
    VI_Error "Command not implemented: 'w'"
    VI_Cancel_Command
End Sub
Sub VI_Key_x()
    VI_Key "x"
    VI_Error "Command not implemented: 'x'"
    VI_Cancel_Command
End Sub
Sub VI_Key_y()
    VI_Key "y"
    VI_Error "Command not implemented: 'y'"
    VI_Cancel_Command
End Sub
Sub VI_Key_z()
    VI_Key "z"
    VI_Error "Command not implemented: 'z'"
    VI_Cancel_Command
End Sub
Sub VI_Key_A_CAP()
    VI_Key "A_CAP"
    VI_Error "Command not implemented: 'A_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_B_CAP()
    VI_Key "B_CAP"
    VI_Error "Command not implemented: 'B_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_C_CAP()
    VI_Key "C_CAP"
    VI_Error "Command not implemented: 'C_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_D_CAP()
    VI_Key "D_CAP"
    VI_Error "Command not implemented: 'D_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_E_CAP()
    VI_Key "E_CAP"
    VI_Error "Command not implemented: 'E_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_F_CAP()
    VI_Key "F_CAP"
    VI_Error "Command not implemented: 'F_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_G_CAP()
    VI_Key "G_CAP"
    VI_Error "Command not implemented: 'G_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_H_CAP()
    VI_Key "H_CAP"
    VI_Error "Command not implemented: 'H_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_I_CAP()
    VI_Key "I_CAP"
    VI_Error "Command not implemented: 'I_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_J_CAP()
    VI_Key "J_CAP"
    VI_Error "Command not implemented: 'J_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_K_CAP()
    VI_Key "K_CAP"
    VI_Error "Command not implemented: 'K_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_L_CAP()
    VI_Key "L_CAP"
    VI_Error "Command not implemented: 'L_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_M_CAP()
    VI_Key "M_CAP"
    VI_Error "Command not implemented: 'M_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_N_CAP()
    VI_Key "N_CAP"
    VI_Error "Command not implemented: 'N_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_O_CAP()
    VI_Key "O_CAP"
    VI_Error "Command not implemented: 'O_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_P_CAP()
    VI_Key "P_CAP"
    VI_Error "Command not implemented: 'P_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_Q_CAP()
    VI_Key "Q_CAP"
    VI_Error "Command not implemented: 'Q_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_R_CAP()
    VI_Key "R_CAP"
    VI_Error "Command not implemented: 'R_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_S_CAP()
    VI_Key "S_CAP"
    VI_Error "Command not implemented: 'S_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_T_CAP()
    VI_Key "T_CAP"
    VI_Error "Command not implemented: 'T_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_U_CAP()
    VI_Key "U_CAP"
    VI_Error "Command not implemented: 'U_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_V_CAP()
    VI_Key "V"
    If m_Mode = VI_VisualLine Then
        m_Mode = VI_Normal
        ActiveCell.Select
    Else
        m_Mode = VI_VisualLine
        Set m_Head = ActiveCell
        Set m_Range = ActiveCell.CurrentRegion
        VI_VisualLine_Callback
    End If
End Sub
Sub VI_Key_W_CAP()
    VI_Key "W_CAP"
    VI_Error "Command not implemented: 'W_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_X_CAP()
    VI_Key "X_CAP"
    VI_Error "Command not implemented: 'X_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_Y_CAP()
    VI_Key "Y_CAP"
    VI_Error "Command not implemented: 'Y_CAP'"
    VI_Cancel_Command
End Sub
Sub VI_Key_Z_CAP()
    VI_Key "Z_CAP"
End Sub
Sub VI_Key_0()
    VI_Key_Count "0"
End Sub
Sub VI_Key_1()
    VI_Key_Count "1"
End Sub
Sub VI_Key_2()
    VI_Key_Count "2"
End Sub
Sub VI_Key_3()
    VI_Key_Count "3"
End Sub
Sub VI_Key_4()
    VI_Key_Count "4"
End Sub
Sub VI_Key_5()
    VI_Key_Count "5"
End Sub
Sub VI_Key_6()
    VI_Key_Count "6"
End Sub
Sub VI_Key_7()
    VI_Key_Count "7"
End Sub
Sub VI_Key_8()
    VI_Key_Count "8"
End Sub
Sub VI_Key_9()
    VI_Key_Count "9"
End Sub
Sub VI_Key_HOME()
    VI_Key "HOME"
    VI_Error "Command not implemented: 'HOME'"
    VI_Cancel_Command
End Sub
Sub VI_Key_INSERT()
    VI_Key "INSERT"
    VI_Error "Command not implemented: 'INSERT'"
    VI_Cancel_Command
End Sub
Sub VI_Key_END()
    VI_Key "END"
    VI_Error "Command not implemented: 'END'"
    VI_Cancel_Command
End Sub
Sub VI_Key_ESC()
    VI_Key "ESC"
    ActiveCell.Select
    VI_Cancel_Command
End Sub