lib/vimgolf/keylog.rb in vimgolf-0.4.8 vs lib/vimgolf/keylog.rb in vimgolf-0.4.9
- old
+ new
@@ -1,258 +1,271 @@
-# encoding: UTF-8
+# encoding: ASCII-8BIT
+# Force encoding of string literals. Must match solution text.
module VimGolf
class Keylog
include Enumerable
- alias_method :convert , :to_s
- alias_method :score , :count
-
- def initialize(input)
- @input = input
+ 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)
- output = ""
- until scanner.eos?
- c = scanner.get_byte
- n = c.unpack('C').first
+ # 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
- out_char = \
- case n
+ # 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>"
- # Special platform-independent encoding stuff
- when 0x80
- code = scanner.get_byte + scanner.get_byte
+ # Between these dates, assume KE_SNIFF is removed.
+ NO_SNIFF_DATE_RANGE = [Time.utc(2016, 4), Time.utc(2017, 7)]
- # This list has been populated by looking at
- # :h terminal-options and vim source files:
- # keymap.h and misc2.c
- case code
- when "k1"; "<F1>"
- when "k2"; "<F2>"
- when "k3"; "<F3>"
- when "k4"; "<F4>"
- when "k5"; "<F5>"
- when "k6"; "<F6>"
- when "k7"; "<F7>"
- when "k8"; "<F8>"
- when "k9"; "<F9>"
- when "k;"; "<F10>"
- when "F1"; "<F11>"
- when "F2"; "<F12>"
- when "F3"; "<F13>"
- when "F4"; "<F14>"
- when "F5"; "<F15>"
- when "F6"; "<F16>"
- when "F7"; "<F17>"
- when "F8"; "<F18>"
- when "F9"; "<F19>"
+ 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>",
- when "%1"; "<Help>"
- when "&8"; "<Undo>"
- when "#2"; "<S-Home>"
- when "*7"; "<S-End>"
- when "K1"; "<kHome>"
- when "K4"; "<kEnd>"
- when "K3"; "<kPageUp>"
- when "K5"; "<kPageDown>"
- when "K6"; "<kPlus>"
- when "K7"; "<kMinus>"
- when "K8"; "<kDivide>"
- when "K9"; "<kMultiply>"
- when "KA"; "<kEnter>"
- when "KB"; "<kPoint>"
- when "KC"; "<k0>"
- when "KD"; "<k1>"
- when "KE"; "<k2>"
- when "KF"; "<k3>"
- when "KG"; "<k4>"
- when "KH"; "<k5>"
- when "KI"; "<k6>"
- when "KJ"; "<k7>"
- when "KK"; "<k8>"
- when "KL"; "<k9>"
+ "%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>",
- when "kP"; "<PageUp>"
- when "kN"; "<PageDown>"
- when "kh"; "<Home>"
- when "@7"; "<End>"
- when "kI"; "<Insert>"
- when "kD"; "<Del>"
- when "kb"; "<BS>"
+ "kP" => "<PageUp>",
+ "kN" => "<PageDown>",
+ "kh" => "<Home>",
+ "@7" => "<End>",
+ "kI" => "<Insert>",
+ "kD" => "<Del>",
+ "kb" => "<BS>",
- when "ku"; "<Up>"
- when "kd"; "<Down>"
- when "kl"; "<Left>"
- when "kr"; "<Right>"
- when "#4"; "<S-Left>"
- when "%i"; "<S-Right>"
+ "ku" => "<Up>",
+ "kd" => "<Down>",
+ "kl" => "<Left>",
+ "kr" => "<Right>",
+ "#4" => "<S-Left>",
+ "%i" => "<S-Right>",
- when "kB"; "<S-Tab>"
- when "\xffX"; "<C-@>"
+ "kB" => "<S-Tab>",
+ "\xffX" => "<C-@>",
- # This is how you escape literal 0x80
- when "\xfeX"; "<0x80>"
+ # 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
- when "\xfc\x02"; "<S->"
- when "\xfc\x04"; "<C->"
- when "\xfc\x06"; "<C-S->"
- when "\xfc\x08"; "<A->"
- when "\xfc\x0a"; "<A-S->"
- when "\xfc\x0c"; "<C-A>"
- when "\xfc\x0e"; "<C-A-S->"
- when "\xfc\x10"; "<M->"
- when "\xfc\x12"; "<M-S->"
- when "\xfc\x14"; "<M-C->"
- when "\xfc\x16"; "<M-C-S->"
- when "\xfc\x18"; "<M-A->"
- when "\xfc\x1a"; "<M-A-S->"
- when "\xfc\x1c"; "<M-C-A>"
- when "\xfc\x1e"; "<M-C-A-S->"
+ # 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->",
- when "\xfd\x4"; "<S-Up>"
- when "\xfd\x5"; "<S-Down>"
- when "\xfd\x6"; "<S-F1>"
- when "\xfd\x7"; "<S-F2>"
- when "\xfd\x8"; "<S-F3>"
- when "\xfd\x9"; "<S-F4>"
- when "\xfd\xa"; "<S-F5>"
- when "\xfd\xb"; "<S-F6>"
- when "\xfd\xc"; "<S-F7>"
- when "\xfd\xd"; "<S-F9>"
- when "\xfd\xe"; "<S-F10>"
- when "\xfd\xf"; "<S-F10>"
- when "\xfd\x10"; "<S-F11>"
- when "\xfd\x11"; "<S-F12>"
- when "\xfd\x12"; "<S-F13>"
- when "\xfd\x13"; "<S-F14>"
- when "\xfd\x14"; "<S-F15>"
- when "\xfd\x15"; "<S-F16>"
- when "\xfd\x16"; "<S-F17>"
- when "\xfd\x17"; "<S-F18>"
- when "\xfd\x18"; "<S-F19>"
- when "\xfd\x19"; "<S-F20>"
- when "\xfd\x1a"; "<S-F21>"
- when "\xfd\x1b"; "<S-F22>"
- when "\xfd\x1c"; "<S-F23>"
- when "\xfd\x1d"; "<S-F24>"
- when "\xfd\x1e"; "<S-F25>"
- when "\xfd\x1f"; "<S-F26>"
- when "\xfd\x20"; "<S-F27>"
- when "\xfd\x21"; "<S-F28>"
- when "\xfd\x22"; "<S-F29>"
- when "\xfd\x23"; "<S-F30>"
- when "\xfd\x24"; "<S-F31>"
- when "\xfd\x25"; "<S-F32>"
- when "\xfd\x26"; "<S-F33>"
- when "\xfd\x27"; "<S-F34>"
- when "\xfd\x28"; "<S-F35>"
- when "\xfd\x29"; "<S-F36>"
- when "\xfd\x2a"; "<S-F37>"
- when "\xfd\x2b"; "<Mouse>"
- when "\xfd\x2c"; "<LeftMouse>"
- when "\xfd\x2d"; "<LeftDrag>"
- when "\xfd\x2e"; "<LeftRelease>"
- when "\xfd\x2f"; "<MiddleMouse>"
- when "\xfd\x30"; "<MiddleDrag>"
- when "\xfd\x31"; "<MiddleRelease>"
- when "\xfd\x32"; "<RightMouse>"
- when "\xfd\x33"; "<RightDrag>"
- when "\xfd\x34"; "<RightRelease>"
- when "\xfd\x35"; nil # KE_IGNORE
- #when "\xfd\x36"; "KE_TAB"
- #when "\xfd\x37"; "KE_S_TAB_OLD"
- #when "\xfd\x38"; "KE_SNIFF"
- #when "\xfd\x39"; "KE_XF1"
- #when "\xfd\x3a"; "KE_XF2"
- #when "\xfd\x3b"; "KE_XF3"
- #when "\xfd\x3c"; "KE_XF4"
- #when "\xfd\x3d"; "KE_XEND"
- #when "\xfd\x3e"; "KE_ZEND"
- #when "\xfd\x3f"; "KE_XHOME"
- #when "\xfd\x40"; "KE_ZHOME"
- #when "\xfd\x41"; "KE_XUP"
- #when "\xfd\x42"; "KE_XDOWN"
- #when "\xfd\x43"; "KE_XLEFT"
- #when "\xfd\x44"; "KE_XRIGHT"
- #when "\xfd\x45"; "KE_LEFTMOUSE_NM"
- #when "\xfd\x46"; "KE_LEFTRELEASE_NM"
- #when "\xfd\x47"; "KE_S_XF1"
- #when "\xfd\x48"; "KE_S_XF2"
- #when "\xfd\x49"; "KE_S_XF3"
- #when "\xfd\x4a"; "KE_S_XF4"
- when "\xfd\x4b"; "<ScrollWheelUp>"
- when "\xfd\x4c"; "<ScrollWheelDown>"
+ # 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",
- # 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.
- when "\xfd\x4d"; "<ScrollWheelRight>"
- when "\xfd\x4e"; "<ScrollWheelLeft>"
- when "\xfd\x4f"; "<kInsert>"
- when "\xfd\x50"; "<kDel>"
- when "\xfd\x51"; "<0x9b>" # :help <CSI>
- #when "\xfd\x52"; "KE_SNR"
- #when "\xfd\x53"; "KE_PLUG" # never used
- when "\xfd\x53"; "<C-Left>" # 7.2 compat
- #when "\xfd\x54"; "KE_CMDWIN" # never used
- when "\xfd\x54"; "<C-Right>" # 7.2 compat
- when "\xfd\x55"; "<C-Left>" # 7.2 <C-Home> conflict
- when "\xfd\x56"; "<C-Right>" # 7.2 <C-End> conflict
- when "\xfd\x57"; "<C-Home>"
- when "\xfd\x58"; "<C-End>"
- #when "\xfd\x59"; "KE_X1MOUSE"
- #when "\xfd\x5a"; "KE_X1DRAG"
- #when "\xfd\x5b"; "KE_X1RELEASE"
- #when "\xfd\x5c"; "KE_X2MOUSE"
- #when "\xfd\x5d"; "KE_X2DRAG"
- #when "\xfd\x5e"; "KE_X2RELEASE"
- when "\xfd\x5e"; nil # 7.2 compat (I think?)
- #when "\xfd\x5f"; "KE_DROP"
- #when "\xfd\x60"; "KE_CURSORHOLD"
- when "\xfd\x60"; nil # 7.2 Focus Gained compat
- #when "\xfd\x61"; "KE_NOP"
- when "\xfd\x62"; nil # Focus Gained (GVIM)
- when "\xfd\x63"; nil # Focus Lost (GVIM)
+ # 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>",
- else
- #puts "Unknown Vim code: #{code.inspect}"
- '<%02x-%02x>' % code.unpack('CC')
- end
+ # 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",
- # Printable ASCII
- when 32..126; c
-
- # Control characters with special names
- when 0; "<Nul>"
- when 9; "<Tab>"
- when 10; "<NL>"
- when 13; "<CR>"
- when 27; "<Esc>"
-
- # Otherwise, use <C-x> format. Flip bit 7
- when 0..127; "<C-#{(n ^ 0x40).chr}>"
-
- else
- #puts "Unexpected extended ASCII: #{'%#04x' % n}"
- '<%#04x>' % n
-
- end
-
- yield out_char if out_char
- end
- end
+ # 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