lib/vimgolf/lib/vimgolf/keylog.rb
# encoding: ASCII-8BIT
# Force encoding of string literals. Must match solution text.
module VimGolf
class Keylog
include Enumerable
def initialize(input, time=Time.now.utc)
# Force encoding of solution text. Must match string literals.
# .force_encoding CHANGES THE ORIGINAL STRING!
@input = input.force_encoding(Encoding::ASCII_8BIT)
@time = time
end
def to_s(sep = '')
to_a.join(sep)
end
alias_method :convert , :to_s
alias_method :score , :count
def each
scanner = StringScanner.new(@input)
# A Vim keycode is either a single byte, or a 3-byte sequence starting
# with 0x80.
while (c = scanner.get_byte)
n = c.ord
if n == 0x80
b2, b3 = scanner.get_byte, scanner.get_byte
if b2 == "\xfd" && b3 >= "\x38" && @time.between?(*NO_SNIFF_DATE_RANGE)
# Should we account for KE_SNIFF removal?
b3 = (b3.ord + 1).chr
end
code = KC_MBYTE[b2+b3]
yield code if code # ignore "nil" keystrokes (like window focus)
else
yield KC_1BYTE[n]
end
end
end
# Quick lookup array for single-byte keycodes
KC_1BYTE = []
(0..255).each {|n| KC_1BYTE.push("<%#04x>" % n)} # Fallback for non-ASCII
(1..127).each {|n| KC_1BYTE[n] = "<C-#{(n ^ 0x40).chr}>"}
(32..126).each {|c| KC_1BYTE[c] = c.chr } # Printing chars
KC_1BYTE[0x1b] = "<Esc>" # Special names for a few control chars
KC_1BYTE[0x0d] = "<CR>"
KC_1BYTE[0x0a] = "<NL>"
KC_1BYTE[0x09] = "<Tab>"
# Between these dates, assume KE_SNIFF is removed.
NO_SNIFF_DATE_RANGE = [Time.utc(2016, 4), Time.utc(2017, 7)]
KC_MBYTE = Hash.new do |_h,k|
'<' + k.bytes.map {|b| "%02x" % b}.join('-') + '>' # For missing keycodes
end.update({
# This list has been populated by looking at
# :h terminal-options and vim source files:
# keymap.h and misc2.c
"k1" => "<F1>",
"k2" => "<F2>",
"k3" => "<F3>",
"k4" => "<F4>",
"k5" => "<F5>",
"k6" => "<F6>",
"k7" => "<F7>",
"k8" => "<F8>",
"k9" => "<F9>",
"k;" => "<F10>",
"F1" => "<F11>",
"F2" => "<F12>",
"F3" => "<F13>",
"F4" => "<F14>",
"F5" => "<F15>",
"F6" => "<F16>",
"F7" => "<F17>",
"F8" => "<F18>",
"F9" => "<F19>",
"%1" => "<Help>",
"&8" => "<Undo>",
"#2" => "<S-Home>",
"*7" => "<S-End>",
"K1" => "<kHome>",
"K4" => "<kEnd>",
"K3" => "<kPageUp>",
"K5" => "<kPageDown>",
"K6" => "<kPlus>",
"K7" => "<kMinus>",
"K8" => "<kDivide>",
"K9" => "<kMultiply>",
"KA" => "<kEnter>",
"KB" => "<kPoint>",
"KC" => "<k0>",
"KD" => "<k1>",
"KE" => "<k2>",
"KF" => "<k3>",
"KG" => "<k4>",
"KH" => "<k5>",
"KI" => "<k6>",
"KJ" => "<k7>",
"KK" => "<k8>",
"KL" => "<k9>",
"kP" => "<PageUp>",
"kN" => "<PageDown>",
"kh" => "<Home>",
"@7" => "<End>",
"kI" => "<Insert>",
"kD" => "<Del>",
"kb" => "<BS>",
"ku" => "<Up>",
"kd" => "<Down>",
"kl" => "<Left>",
"kr" => "<Right>",
"#4" => "<S-Left>",
"%i" => "<S-Right>",
"kB" => "<S-Tab>",
"\xffX" => "<C-@>",
# This is how you escape literal 0x80
"\xfeX" => "<0x80>",
# These rarely-used modifiers should be combined with the next
# stroke (like <S-Space>), but let's put them here for now
"\xfc\x02" => "<S->",
"\xfc\x04" => "<C->",
"\xfc\x06" => "<C-S->",
"\xfc\x08" => "<A->",
"\xfc\x0a" => "<A-S->",
"\xfc\x0c" => "<C-A>",
"\xfc\x0e" => "<C-A-S->",
"\xfc\x10" => "<M->",
"\xfc\x12" => "<M-S->",
"\xfc\x14" => "<M-C->",
"\xfc\x16" => "<M-C-S->",
"\xfc\x18" => "<M-A->",
"\xfc\x1a" => "<M-A-S->",
"\xfc\x1c" => "<M-C-A>",
"\xfc\x1e" => "<M-C-A-S->",
# KS_EXTRA keycodes (starting with 0x80 0xfd) are defined by an enum in
# Vim's keymap.h. Sometimes, a new Vim adds or removes a keycode, which
# changes the binary representation of every keycode after it. Very
# annoying.
"\xfd\x4" => "<S-Up>",
"\xfd\x5" => "<S-Down>",
"\xfd\x6" => "<S-F1>",
"\xfd\x7" => "<S-F2>",
"\xfd\x8" => "<S-F3>",
"\xfd\x9" => "<S-F4>",
"\xfd\xa" => "<S-F5>",
"\xfd\xb" => "<S-F6>",
"\xfd\xc" => "<S-F7>",
"\xfd\xd" => "<S-F9>",
"\xfd\xe" => "<S-F10>",
"\xfd\xf" => "<S-F10>",
"\xfd\x10" => "<S-F11>",
"\xfd\x11" => "<S-F12>",
"\xfd\x12" => "<S-F13>",
"\xfd\x13" => "<S-F14>",
"\xfd\x14" => "<S-F15>",
"\xfd\x15" => "<S-F16>",
"\xfd\x16" => "<S-F17>",
"\xfd\x17" => "<S-F18>",
"\xfd\x18" => "<S-F19>",
"\xfd\x19" => "<S-F20>",
"\xfd\x1a" => "<S-F21>",
"\xfd\x1b" => "<S-F22>",
"\xfd\x1c" => "<S-F23>",
"\xfd\x1d" => "<S-F24>",
"\xfd\x1e" => "<S-F25>",
"\xfd\x1f" => "<S-F26>",
"\xfd\x20" => "<S-F27>",
"\xfd\x21" => "<S-F28>",
"\xfd\x22" => "<S-F29>",
"\xfd\x23" => "<S-F30>",
"\xfd\x24" => "<S-F31>",
"\xfd\x25" => "<S-F32>",
"\xfd\x26" => "<S-F33>",
"\xfd\x27" => "<S-F34>",
"\xfd\x28" => "<S-F35>",
"\xfd\x29" => "<S-F36>",
"\xfd\x2a" => "<S-F37>",
"\xfd\x2b" => "<Mouse>",
"\xfd\x2c" => "<LeftMouse>",
"\xfd\x2d" => "<LeftDrag>",
"\xfd\x2e" => "<LeftRelease>",
"\xfd\x2f" => "<MiddleMouse>",
"\xfd\x30" => "<MiddleDrag>",
"\xfd\x31" => "<MiddleRelease>",
"\xfd\x32" => "<RightMouse>",
"\xfd\x33" => "<RightDrag>",
"\xfd\x34" => "<RightRelease>",
"\xfd\x35" => nil, # KE_IGNORE
#"\xfd\x36" => "KE_TAB",
#"\xfd\x37" => "KE_S_TAB_OLD",
# Vim 7.4.1433 removed KE_SNIFF. Unfortunately, this changed the
# offset of every keycode after it.
# Vim 8.0.0697 added back a KE_SNIFF_UNUSED to fill in for the
# removed KE_SNIFF.
# Keycodes after this point should be accurate for vim < 7.4.1433
# and vim > 8.0.0697.
#"\xfd\x38" => "KE_SNIFF",
#"\xfd\x39" => "KE_XF1",
#"\xfd\x3a" => "KE_XF2",
#"\xfd\x3b" => "KE_XF3",
#"\xfd\x3c" => "KE_XF4",
#"\xfd\x3d" => "KE_XEND",
#"\xfd\x3e" => "KE_ZEND",
#"\xfd\x3f" => "KE_XHOME",
#"\xfd\x40" => "KE_ZHOME",
#"\xfd\x41" => "KE_XUP",
#"\xfd\x42" => "KE_XDOWN",
#"\xfd\x43" => "KE_XLEFT",
#"\xfd\x44" => "KE_XRIGHT",
#"\xfd\x45" => "KE_LEFTMOUSE_NM",
#"\xfd\x46" => "KE_LEFTRELEASE_NM",
#"\xfd\x47" => "KE_S_XF1",
#"\xfd\x48" => "KE_S_XF2",
#"\xfd\x49" => "KE_S_XF3",
#"\xfd\x4a" => "KE_S_XF4",
"\xfd\x4b" => "<ScrollWheelUp>",
"\xfd\x4c" => "<ScrollWheelDown>",
# Horizontal scroll wheel support was added in Vim 7.3c. These
# 2 entries shifted the rest of the KS_EXTRA mappings down 2.
# Though Vim 7.2 is rare today, it was common soon after
# vimgolf.com was launched. In cases where the 7.3 code is
# never used but the 7.2 code was common, it makes sense to use
# the 7.2 code. There are conflicts though, so some legacy
# keycodes have to stay wrong.
"\xfd\x4d" => "<ScrollWheelRight>",
"\xfd\x4e" => "<ScrollWheelLeft>",
"\xfd\x4f" => "<kInsert>",
"\xfd\x50" => "<kDel>",
"\xfd\x51" => "<0x9b>", # :help <CSI>
#"\xfd\x52" => "KE_SNR",
#"\xfd\x53" => "KE_PLUG", # never used
"\xfd\x53" => "<C-Left>", # 7.2 compat
#"\xfd\x54" => "KE_CMDWIN", # never used
"\xfd\x54" => "<C-Right>", # 7.2 compat
"\xfd\x55" => "<C-Left>", # 7.2 <C-Home> conflict
"\xfd\x56" => "<C-Right>", # 7.2 <C-End> conflict
"\xfd\x57" => "<C-Home>",
"\xfd\x58" => "<C-End>",
#"\xfd\x59" => "KE_X1MOUSE",
#"\xfd\x5a" => "KE_X1DRAG",
#"\xfd\x5b" => "KE_X1RELEASE",
#"\xfd\x5c" => "KE_X2MOUSE",
#"\xfd\x5d" => "KE_X2DRAG",
#"\xfd\x5e" => "KE_X2RELEASE",
"\xfd\x5e" => nil, # 7.2 compat (I think?)
#"\xfd\x5f" => "KE_DROP",
#"\xfd\x60" => "KE_CURSORHOLD",
# If you use gvim, you'll get an entry in your keylog every time the
# window gains or loses focus. These "keystrokes" should not show and
# should not be counted.
"\xfd\x60" => nil, # 7.2 Focus Gained compat
"\xfd\x61" => nil, # Focus Gained (GVIM) (>7.4.1433)
"\xfd\x62" => nil, # Focus Gained (GVIM)
"\xfd\x63" => nil, # Focus Lost (GVIM)
})
end
end