vimgolf 0.4.4 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54dea0f839cd9e4b251b1b3d9f1b8d1be31fb0dc
4
+ data.tar.gz: 0e96fa1acc4894b104702e6492748742ea42204c
5
+ SHA512:
6
+ metadata.gz: 2d413648da95e0c81b4f0668841605732e8265d373f56c5a98afd771d304ed547b19d8f2551e4cd007bbed2ab77d0607c2b81eedb90d22735c6159eb74b46e12
7
+ data.tar.gz: bf03e208e358a3522d4c0fb6e3fcbb301a44b6202f3f9b9900c1d3a8e78820558ea7783b5ee208d180f5b22c0cfd79a687ed462ff372846158ab87cf84f20714
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # [VimGolf.com](http://www.vimgolf.com) Client
1
+ # [VimGolf.com](http://www.vimgolf.com) CLI Client
2
2
 
3
3
  Real Vim ninjas count every keystroke - do you? Head on over to vimgolf.com, pick a challenge, and show us what you've got!
4
4
 
@@ -14,16 +14,8 @@ When you launch a challenge from the command line, it will be downloaded from th
14
14
  $> gem install vimgolf
15
15
 
16
16
  (Go to vimgolf.com, sign in, and grab your API key)
17
- $> vimgolf setup
17
+ $> vimgolf setup
18
18
 
19
19
  (Pick a challenge on vimgolf.com)
20
20
  $> vimgolf put [challenge ID]
21
21
  ```
22
-
23
- **Emacs**: yes, it is true, you can [play vimgolf in emacs too](https://github.com/igrigorik/vimgolf/tree/master/emacs)!
24
-
25
- Patches, tips and ideas are welcome!
26
-
27
- ## License
28
-
29
- (MIT License) - Copyright (c) 2010 Ilya Grigorik
data/lib/vimgolf.rb CHANGED
@@ -1,9 +1,12 @@
1
+ require 'rubygems'
1
2
  require 'fileutils'
2
3
  require 'net/http'
3
4
  require 'strscan'
4
5
  require 'json'
5
6
  require 'yaml'
6
7
  require 'thor'
8
+ require 'shellwords'
9
+ require 'tempfile'
7
10
 
8
11
  require 'vimgolf/version'
9
12
  require 'vimgolf/config'
@@ -1,6 +1,6 @@
1
1
  module VimGolf
2
2
  class Challenge
3
- attr_reader :id, :type
3
+ attr_reader :id, :type, :otype, :remote
4
4
 
5
5
  def self.path(path)
6
6
  @@path = path if path
@@ -11,7 +11,32 @@ module VimGolf
11
11
  @id = id
12
12
  end
13
13
 
14
+ def local(infile, outfile)
15
+ @remote = false
16
+ @input_path = File.expand_path(infile)
17
+ @output_path = File.expand_path(outfile)
18
+ @type = File.basename(@input_path) # extension? use the whole thing
19
+ @otype = File.basename(@output_path)
20
+
21
+ work_files
22
+ end
23
+
24
+ def work_files
25
+ @vimrc_path = File.expand_path('../vimgolf.vimrc', __FILE__)
26
+
27
+ # keep these Tempfile's around so they don't unlink
28
+ @work = Tempfile.new(['vimgolf', ".#{@type}"])
29
+ @log = Tempfile.new('golflog')
30
+ # close tmp files, but don't unlink
31
+ @work.close
32
+ @log.close
33
+
34
+ @work_path = @work.path()
35
+ @log_path = @log.path()
36
+ end
37
+
14
38
  def download
39
+ @remote = true
15
40
  begin
16
41
  url = URI.parse("#{GOLFHOST}/challenges/#{@id}.json")
17
42
  req = Net::HTTP::Get.new(url.path)
@@ -34,9 +59,14 @@ module VimGolf
34
59
  @data['in']['data'].gsub!(/\r\n/, "\n")
35
60
  @data['out']['data'].gsub!(/\r\n/, "\n")
36
61
 
37
- @type = @data['in']['type']
62
+ # be sure to sanitize the types
63
+ @type = @data['in']['type'].gsub(/[^\w-]/, '.')
64
+ @otype = @data['out']['type'].gsub(/[^\w-]/, '.')
65
+ @input_path = path + ".input.#{@type}"
66
+ @output_path = path + ".output.#{@otype}"
67
+
38
68
  save
39
- start
69
+ work_files
40
70
  rescue Exception => e
41
71
  debug(e)
42
72
  raise "Uh oh, couldn't download or parse challenge, please verify your challenge id & client version."
@@ -44,13 +74,12 @@ module VimGolf
44
74
  end
45
75
 
46
76
  def start
47
- File.open(work_path, "w") {|f| f.puts @data['in']['data']}
77
+ FileUtils.cp(@input_path, @work_path)
48
78
  end
49
79
 
50
80
  def save
51
81
  File.open(input_path, "w") {|f| f.puts @data['in']['data']}
52
82
  File.open(output_path, "w") {|f| f.puts @data['out']['data']}
53
- File.open(vimrc_path, "w") {|f| f.puts @data['vimrc']}
54
83
  end
55
84
 
56
85
  def upload
@@ -62,7 +91,7 @@ module VimGolf
62
91
 
63
92
  proxy.start(url.host, url.port) do |http|
64
93
  request = Net::HTTP::Post.new(url.request_uri)
65
- request.set_form_data({"challenge_id" => @id, "apikey" => Config.load['key'], "entry" => IO.read(log_path)})
94
+ request.set_form_data({"challenge_id" => @id, "apikey" => Config.load['key'], "entry" => IO.binread(log_path)})
66
95
  request["Accept"] = "application/json"
67
96
 
68
97
  res = http.request(request)
@@ -78,11 +107,15 @@ module VimGolf
78
107
  end
79
108
  end
80
109
 
81
- def input_path; path + ".#{@type}"; end
82
- def work_path; path + ".work.#{@type}"; end
83
- def output_path; path + ".output"; end
84
- def log_path; path + ".log"; end
85
- def vimrc_path; path + ".golfrc"; end
110
+ attr_reader :input_path
111
+ attr_reader :work_path
112
+ attr_reader :output_path
113
+ attr_reader :log_path
114
+ attr_reader :vimrc_path
115
+
116
+ def correct?
117
+ FileUtils.compare_file(@work_path, @output_path)
118
+ end
86
119
 
87
120
  def path
88
121
  @@path + "/#{@id}"
data/lib/vimgolf/cli.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  module VimGolf
2
2
 
3
3
  GOLFDEBUG = ENV['GOLFDEBUG'].to_sym rescue false
4
- GOLFHOST = ENV['GOLFHOST'] || "http://vimgolf.com"
5
- GOLFDIFF = ENV['GOLFDIFF'] || 'diff'
4
+ GOLFHOST = ENV['GOLFHOST'] || "http://www.vimgolf.com"
6
5
  GOLFSHOWDIFF = ENV['GOLFSHOWDIFF'] || 'vim -d -n'
7
6
  GOLFVIM = ENV['GOLFVIM'] || 'vim'
8
7
  PROXY = ENV['http_proxy'] || ''
@@ -33,6 +32,15 @@ module VimGolf
33
32
  super
34
33
  end
35
34
 
35
+ desc "version", "print version of Vimgolf client"
36
+ long_desc <<-DESC
37
+ Print version of the Vimgolf client.
38
+ DESC
39
+
40
+ def version
41
+ VimGolf.ui.info "Client #{Vimgolf::VERSION}"
42
+ end
43
+
36
44
  desc "setup", "configure your VimGolf credentials"
37
45
  long_desc <<-DESC
38
46
  To participate in the challenge please go to vimgolf.com and register an
@@ -43,7 +51,12 @@ module VimGolf
43
51
  DESC
44
52
 
45
53
  def setup
46
- key = VimGolf.ui.ask "Please specify your VimGolf API key (register on vimgolf.com to get it):"
54
+ VimGolf.ui.info "\nLet's setup your VimGolf key..."
55
+ VimGolf.ui.warn "1) Open vimgolf.com in your browser."
56
+ VimGolf.ui.warn "2) Click \"Sign in with Twitter\"."
57
+ VimGolf.ui.warn "3) Once signed in, copy your key (black box, top right)."
58
+
59
+ key = VimGolf.ui.ask "\nPaste your VimGolf key:"
47
60
 
48
61
  if key =~ /[\w\d]{32}/
49
62
  FileUtils.mkdir_p Config.path
@@ -56,41 +69,74 @@ module VimGolf
56
69
  end
57
70
  end
58
71
 
59
- desc "put [ID]", "launch Vim session"
72
+ desc "put CHALLENGE_ID", "launch vimgolf.com challenge"
60
73
  long_desc <<-DESC
61
74
  Launch a VimGolf session for the specified challenge ID. To find a currently
62
75
  active challenge ID, please visit vimgolf.com!
63
76
  DESC
64
77
 
65
- def put(id = nil)
78
+ def put(id)
79
+ FileUtils.mkdir_p Config.put_path
66
80
  VimGolf.ui.warn "Downloading Vimgolf challenge: #{id}"
67
81
  VimGolf::Challenge.path(Config.put_path)
68
82
  challenge = Challenge.new(id)
69
83
  challenge.download
70
84
 
85
+ play(challenge)
86
+ end
87
+
88
+ desc "local INFILE OUTFILE", "launch local challenge"
89
+ long_desc <<-DESC
90
+ Launch a local VimGolf challenge. A temporary copy of INFILE is made; the original files will not be touched.
91
+ DESC
92
+
93
+ def local(infile, outfile)
94
+ # make sure our files are sane
95
+ if !(File.file?(infile) and File.file?(outfile))
96
+ VimGolf.ui.error "INFILE and OUTFILE must exist and be regular files."
97
+ exit 1
98
+ end
99
+
100
+ challenge = Challenge.new(infile) # use the filename as id
101
+ challenge.local(infile, outfile)
102
+
103
+ play(challenge)
104
+ end
105
+
106
+ private
107
+
108
+ def play(challenge)
71
109
  begin
72
- VimGolf.ui.warn "Launching VimGolf session for challenge: #{id}"
73
- # - n - no swap file, memory only editing
74
- # - +0 - always start on line 0
75
- # - --noplugin - don't load any plugins, lets be fair!
76
- # -i NONE - don't load .viminfo (for saved macros and the like)
77
- # - u - load vimgolf .vimrc to level the playing field
78
- vimcmd = "#{GOLFVIM} -Z -n --noplugin -i NONE +0 -u \"#{challenge.vimrc_path}\" -W \"#{challenge.log_path}\" \"#{challenge.work_path}\""
110
+ challenge.start
111
+ VimGolf.ui.warn "Launching VimGolf session for challenge: #{challenge.id}"
112
+ # -Z - restricted mode, utilities not allowed
113
+ # -n - no swap file, memory only editing
114
+ # --noplugin - don't load any plugins, lets be fair!
115
+ # --nofork - otherwise GOLFVIM=gvim forks and returns immediately
116
+ # -i NONE - don't load .viminfo (for saved macros and the like)
117
+ # +0 - always start on line 0
118
+ # -u vimrc - load vimgolf .vimrc to level the playing field
119
+ # -U NONE - don't load .gvimrc
120
+ # -W logfile - keylog file (overwrites if already exists)
121
+ vimcmd = GOLFVIM.shellsplit + %W{-Z -n --noplugin -i NONE +0 -u #{challenge.vimrc_path} -U NONE -W #{challenge.log_path} #{challenge.work_path}}
122
+ if GOLFVIM == "gvim"
123
+ vimcmd += %W{ --nofork}
124
+ end
79
125
  debug(vimcmd)
80
- system(vimcmd)
126
+ system(*vimcmd) # assembled as an array, bypasses the shell
81
127
 
82
128
  if $?.exitstatus.zero?
83
- diff_files = "\"#{challenge.work_path}\" \"#{challenge.output_path}\""
84
- diff = `#{GOLFDIFF} #{diff_files}`
85
- log = Keylog.new(IO.read(challenge.log_path))
129
+ log = Keylog.new(IO.binread(challenge.log_path))
130
+
131
+ VimGolf.ui.info "\nHere are your keystrokes:"
132
+ VimGolf.ui.print_log log
86
133
 
87
134
  # Handle incorrect solutions
88
- if diff.size > 0
135
+ if !challenge.correct?()
89
136
  VimGolf.ui.error "\nUh oh, looks like your entry does not match the desired output."
90
137
  VimGolf.ui.error "Your score for this failed attempt was: #{log.score}"
91
-
92
138
  loop do
93
- VimGolf.ui.warn "[d] Show a diff"
139
+ VimGolf.ui.warn "[d] Show diff"
94
140
  VimGolf.ui.warn "[r] Retry the current challenge"
95
141
  VimGolf.ui.warn "[q] Quit vimgolf"
96
142
 
@@ -99,10 +145,9 @@ module VimGolf
99
145
  :choices => [:diff, :retry, :quit]
100
146
  when :diff
101
147
  VimGolf.ui.warn "Showing vimdiff of your attempt (left) and correct output (right)"
102
- system("#{GOLFSHOWDIFF} #{diff_files}")
148
+ system(*GOLFSHOWDIFF.shellsplit + [challenge.work_path, challenge.output_path])
103
149
  when :retry
104
150
  VimGolf.ui.warn "Retrying current challenge..."
105
- challenge.start
106
151
  raise RetryException
107
152
  when :quit
108
153
  raise Interrupt
@@ -114,23 +159,32 @@ module VimGolf
114
159
  VimGolf.ui.info "\nSuccess! Your output matches. Your score: #{log.score}"
115
160
 
116
161
  loop do
117
- VimGolf.ui.warn "[w] Upload result and retry the challenge"
118
- VimGolf.ui.warn "[x] Upload result and quit"
162
+ choices = []
163
+ begin
164
+ Config.load # raises error if user hasn't finished setup
165
+ choices = [:w, :x]
166
+ VimGolf.ui.warn "[w] Upload result and retry the challenge"
167
+ VimGolf.ui.warn "[x] Upload result and quit"
168
+ rescue
169
+ choices = [:setup]
170
+ VimGolf.ui.warn "[s] Set up vimgolf.com key to submit result"
171
+ end if challenge.remote
119
172
  VimGolf.ui.warn "[r] Do not upload result and retry the challenge"
120
173
  VimGolf.ui.warn "[q] Do not upload result and quit"
121
174
 
122
175
  case VimGolf.ui.ask_question "Choice> ",
123
176
  :type => :warn,
124
- :choices => [:w, :x, :retry, :quit]
177
+ :choices => choices + [:retry, :quit]
125
178
  when :w
126
179
  next unless upload?(challenge)
127
- challenge.start
128
180
  raise RetryException
129
181
  when :x
130
182
  next unless upload?(challenge)
131
183
  raise Interrupt
184
+ when :setup
185
+ setup
186
+ next # we can hopefully submit this time
132
187
  when :retry
133
- challenge.start
134
188
  raise RetryException
135
189
  when :quit
136
190
  raise Interrupt
@@ -139,8 +193,9 @@ module VimGolf
139
193
 
140
194
  else
141
195
  error = <<-MSG
142
- Uh oh, Vim did not exit properly. If the problem persists, please
143
- report the error on github.com/igrigorik/vimgolf
196
+ Uh oh, Vim did not exit properly.
197
+ Please ensure you can execute 'Vim' from the commandline.
198
+ If the problem persists, please report the error on github.com/igrigorik/vimgolf
144
199
  MSG
145
200
 
146
201
  VimGolf.ui.error error
@@ -150,15 +205,13 @@ module VimGolf
150
205
  retry
151
206
  end
152
207
 
153
- rescue Interrupt, StandardError
208
+ rescue Interrupt
154
209
  VimGolf.ui.info "\nThanks for playing!"
155
- rescue Exception => e
210
+ rescue RuntimeError, Exception => e
156
211
  VimGolf.ui.error "Uh oh, something went wrong! Error: #{e}"
157
212
  VimGolf.ui.error "If the error persists, please report it to github.com/igrigorik/vimgolf"
158
213
  end
159
214
 
160
- private
161
-
162
215
  def upload?(challenge)
163
216
  VimGolf.ui.warn "Uploading to VimGolf..."
164
217
 
@@ -1,225 +1,271 @@
1
+ # encoding: ASCII-8BIT
2
+ # Force encoding of string literals. Must match solution text.
3
+
1
4
  module VimGolf
2
5
  class Keylog
3
6
  include Enumerable
4
7
 
5
- alias_method :convert , :to_s
6
- alias_method :score , :count
7
-
8
- def initialize(input)
9
- @input = input
8
+ def initialize(input, time=Time.now.utc)
9
+ # Force encoding of solution text. Must match string literals.
10
+ # .force_encoding CHANGES THE ORIGINAL STRING!
11
+ @input = input.force_encoding(Encoding::ASCII_8BIT)
12
+ @time = time
10
13
  end
11
14
 
12
15
  def to_s(sep = '')
13
16
  to_a.join(sep)
14
17
  end
15
18
 
19
+ alias_method :convert , :to_s
20
+ alias_method :score , :count
21
+
16
22
  def each
17
23
  scanner = StringScanner.new(@input)
18
- output = ""
19
-
20
- until scanner.eos?
21
- c = scanner.get_byte
22
- n = c.unpack('C').first
23
-
24
- out_char = \
25
- case n
26
-
27
- # Special platform-independent encoding stuff
28
- when 0x80
29
- code = scanner.get_byte + scanner.get_byte
30
-
31
- # This list has been populated by looking at
32
- # :h terminal-options and vim source files:
33
- # keymap.h and misc2.c
34
- case code
35
- when "k1"; "<F1>"
36
- when "k2"; "<F2>"
37
- when "k3"; "<F3>"
38
- when "k4"; "<F4>"
39
- when "k5"; "<F5>"
40
- when "k6"; "<F6>"
41
- when "k7"; "<F7>"
42
- when "k8"; "<F8>"
43
- when "k9"; "<F9>"
44
- when "k;"; "<F10>"
45
- when "F1"; "<F11>"
46
- when "F2"; "<F12>"
47
- when "F3"; "<F13>"
48
- when "F4"; "<F14>"
49
- when "F5"; "<F15>"
50
- when "F6"; "<F16>"
51
- when "F7"; "<F17>"
52
- when "F8"; "<F18>"
53
- when "F9"; "<F19>"
54
-
55
- when "%1"; "<Help>"
56
- when "&8"; "<Undo>"
57
- when "#2"; "<S-Home>"
58
- when "*7"; "<S-End>"
59
- when "K1"; "<kHome>"
60
- when "K4"; "<kEnd>"
61
- when "K3"; "<kPageUp>"
62
- when "K5"; "<kPageDown>"
63
- when "K6"; "<kPlus>"
64
- when "K7"; "<kMinus>"
65
- when "K8"; "<kDivide>"
66
- when "K9"; "<kMultiply>"
67
- when "KA"; "<kEnter>"
68
- when "KB"; "<kPoint>"
69
- when "KC"; "<k0>"
70
- when "KD"; "<k1>"
71
- when "KE"; "<k2>"
72
- when "KF"; "<k3>"
73
- when "KG"; "<k4>"
74
- when "KH"; "<k5>"
75
- when "KI"; "<k6>"
76
- when "KJ"; "<k7>"
77
- when "KK"; "<k8>"
78
- when "KL"; "<k9>"
79
-
80
- when "kP"; "<PageUp>"
81
- when "kN"; "<PageDown>"
82
- when "kh"; "<Home>"
83
- when "@7"; "<End>"
84
- when "kI"; "<Insert>"
85
- when "kD"; "<Del>"
86
- when "kb"; "<BS>"
87
-
88
- when "ku"; "<Up>"
89
- when "kd"; "<Down>"
90
- when "kl"; "<Left>"
91
- when "kr"; "<Right>"
92
- when "#4"; "<S-Left>"
93
- when "%i"; "<S-Right>"
94
-
95
- when "kB"; "<S-Tab>"
96
- when "\xffX"; "<C-@>"
97
-
98
- when "\xfd\x4"; "<S-Up>"
99
- when "\xfd\x5"; "<S-Down>"
100
- when "\xfd\x6"; "<S-F1>"
101
- when "\xfd\x7"; "<S-F2>"
102
- when "\xfd\x8"; "<S-F3>"
103
- when "\xfd\x9"; "<S-F4>"
104
- when "\xfd\xa"; "<S-F5>"
105
- when "\xfd\xb"; "<S-F6>"
106
- when "\xfd\xc"; "<S-F7>"
107
- when "\xfd\xd"; "<S-F9>"
108
- when "\xfd\xe"; "<S-F10>"
109
- when "\xfd\xf"; "<S-F10>"
110
- when "\xfd\x10"; "<S-F11>"
111
- when "\xfd\x11"; "<S-F12>"
112
- when "\xfd\x12"; "<S-F13>"
113
- when "\xfd\x13"; "<S-F14>"
114
- when "\xfd\x14"; "<S-F15>"
115
- when "\xfd\x15"; "<S-F16>"
116
- when "\xfd\x16"; "<S-F17>"
117
- when "\xfd\x17"; "<S-F18>"
118
- when "\xfd\x18"; "<S-F19>"
119
- when "\xfd\x19"; "<S-F20>"
120
- when "\xfd\x1a"; "<S-F21>"
121
- when "\xfd\x1b"; "<S-F22>"
122
- when "\xfd\x1c"; "<S-F23>"
123
- when "\xfd\x1d"; "<S-F24>"
124
- when "\xfd\x1e"; "<S-F25>"
125
- when "\xfd\x1f"; "<S-F26>"
126
- when "\xfd\x20"; "<S-F27>"
127
- when "\xfd\x21"; "<S-F28>"
128
- when "\xfd\x22"; "<S-F29>"
129
- when "\xfd\x23"; "<S-F30>"
130
- when "\xfd\x24"; "<S-F31>"
131
- when "\xfd\x25"; "<S-F32>"
132
- when "\xfd\x26"; "<S-F33>"
133
- when "\xfd\x27"; "<S-F34>"
134
- when "\xfd\x28"; "<S-F35>"
135
- when "\xfd\x29"; "<S-F36>"
136
- when "\xfd\x2a"; "<S-F37>"
137
- when "\xfd\x2b"; "<Mouse>"
138
- when "\xfd\x2c"; "<LeftMouse>"
139
- when "\xfd\x2d"; "<LeftDrag>"
140
- when "\xfd\x2e"; "<LeftRelease>"
141
- when "\xfd\x2f"; "<MiddleMouse>"
142
- when "\xfd\x30"; "<MiddleDrag>"
143
- when "\xfd\x31"; "<MiddleRelease>"
144
- when "\xfd\x32"; "<RightMouse>"
145
- when "\xfd\x33"; "<RightDrag>"
146
- when "\xfd\x34"; "<RightRelease>"
147
- #when "\xfd\x35"; "KE_IGNORE"
148
- #when "\xfd\x36"; "KE_TAB"
149
- #when "\xfd\x37"; "KE_S_TAB_OLD"
150
- #when "\xfd\x38"; "KE_SNIFF"
151
- #when "\xfd\x39"; "KE_XF1"
152
- #when "\xfd\x3a"; "KE_XF2"
153
- #when "\xfd\x3b"; "KE_XF3"
154
- #when "\xfd\x3c"; "KE_XF4"
155
- #when "\xfd\x3d"; "KE_XEND"
156
- #when "\xfd\x3e"; "KE_ZEND"
157
- #when "\xfd\x3f"; "KE_XHOME"
158
- #when "\xfd\x40"; "KE_ZHOME"
159
- #when "\xfd\x41"; "KE_XUP"
160
- #when "\xfd\x42"; "KE_XDOWN"
161
- #when "\xfd\x43"; "KE_XLEFT"
162
- #when "\xfd\x44"; "KE_XRIGHT"
163
- #when "\xfd\x45"; "KE_LEFTMOUSE_NM"
164
- #when "\xfd\x46"; "KE_LEFTRELEASE_NM"
165
- #when "\xfd\x47"; "KE_S_XF1"
166
- #when "\xfd\x48"; "KE_S_XF2"
167
- #when "\xfd\x49"; "KE_S_XF3"
168
- #when "\xfd\x4a"; "KE_S_XF4"
169
- when "\xfd\x4b"; "<MouseDown>"
170
- when "\xfd\x4c"; "<MouseUp>"
171
- when "\xfd\x4d"; "<MouseLeft>"
172
- when "\xfd\x4e"; "<MouseRight>"
173
- #when "\xfd\x4f"; "KE_KINS"
174
- #when "\xfd\x50"; "KE_KDEL"
175
- #when "\xfd\x51"; "KE_CSI"
176
- #when "\xfd\x52"; "KE_SNR"
177
- #when "\xfd\x53"; "KE_PLUG"
178
- #when "\xfd\x54"; "KE_CMDWIN"
179
- when "\xfd\x55"; "<C-Left>"
180
- when "\xfd\x56"; "<C-Right>"
181
- when "\xfd\x57"; "<C-Home>"
182
- when "\xfd\x58"; "<C-End>"
183
- #when "\xfd\x59"; "KE_X1MOUSE"
184
- #when "\xfd\x5a"; "KE_X1DRAG"
185
- #when "\xfd\x5b"; "KE_X1RELEASE"
186
- #when "\xfd\x5c"; "KE_X2MOUSE"
187
- #when "\xfd\x5d"; "KE_X2DRAG"
188
- #when "\xfd\x5e"; "KE_X2RELEASE"
189
- #when "\xfd\x5f"; "KE_DROP"
190
- #when "\xfd\x5e"; "KE_CURSORHOLD"
191
- #when "\xfd\x61"; "KE_NOP"
192
- when "\xfd\x62"; nil # Focus Gained (GVIM)
193
- when "\xfd\x63"; nil # Focus Lost (GVIM)
194
-
195
- else
196
- #puts "Unknown Vim code: #{code.inspect}"
197
- '<%02x-%02x>' % code.unpack('CC')
198
- end
199
-
200
- # Control characters with special names
201
- when 0; "<Nul>"
202
- when 9; "<Tab>"
203
- when 10; "<NL>"
204
- when 13; "<CR>"
205
- when 27; "<Esc>"
206
-
207
- when 127; "<Del>"
208
-
209
- # Otherwise, use <C-x> format
210
- when 0..31; "<C-#{(n + 64).chr}>"
211
-
212
- # The rest of ANSI is printable
213
- when 32..126; c
214
-
215
- else
216
- #puts "Unexpected extended ASCII: #{'%#04x' % n}"
217
- '<%#04x>' % n
218
24
 
25
+ # A Vim keycode is either a single byte, or a 3-byte sequence starting
26
+ # with 0x80.
27
+ while (c = scanner.get_byte)
28
+ n = c.ord
29
+ if n == 0x80
30
+ b2, b3 = scanner.get_byte, scanner.get_byte
31
+ if b2 == "\xfd" && b3 >= "\x38" && @time.between?(*NO_SNIFF_DATE_RANGE)
32
+ # Should we account for KE_SNIFF removal?
33
+ b3 = (b3.ord + 1).chr
34
+ end
35
+ code = KC_MBYTE[b2+b3]
36
+ yield code if code # ignore "nil" keystrokes (like window focus)
37
+ else
38
+ yield KC_1BYTE[n]
219
39
  end
220
-
221
- yield out_char if out_char
222
40
  end
223
41
  end
42
+
43
+ # Quick lookup array for single-byte keycodes
44
+ KC_1BYTE = []
45
+ (0..255).each {|n| KC_1BYTE.push("<%#04x>" % n)} # Fallback for non-ASCII
46
+ (1..127).each {|n| KC_1BYTE[n] = "<C-#{(n ^ 0x40).chr}>"}
47
+ (32..126).each {|c| KC_1BYTE[c] = c.chr } # Printing chars
48
+ KC_1BYTE[0x1b] = "<Esc>" # Special names for a few control chars
49
+ KC_1BYTE[0x0d] = "<CR>"
50
+ KC_1BYTE[0x0a] = "<NL>"
51
+ KC_1BYTE[0x09] = "<Tab>"
52
+
53
+ # Between these dates, assume KE_SNIFF is removed.
54
+ NO_SNIFF_DATE_RANGE = [Time.utc(2016, 4), Time.utc(2017, 7)]
55
+
56
+ KC_MBYTE = Hash.new do |_h,k|
57
+ '<' + k.bytes.map {|b| "%02x" % b}.join('-') + '>' # For missing keycodes
58
+ end.update({
59
+ # This list has been populated by looking at
60
+ # :h terminal-options and vim source files:
61
+ # keymap.h and misc2.c
62
+ "k1" => "<F1>",
63
+ "k2" => "<F2>",
64
+ "k3" => "<F3>",
65
+ "k4" => "<F4>",
66
+ "k5" => "<F5>",
67
+ "k6" => "<F6>",
68
+ "k7" => "<F7>",
69
+ "k8" => "<F8>",
70
+ "k9" => "<F9>",
71
+ "k;" => "<F10>",
72
+ "F1" => "<F11>",
73
+ "F2" => "<F12>",
74
+ "F3" => "<F13>",
75
+ "F4" => "<F14>",
76
+ "F5" => "<F15>",
77
+ "F6" => "<F16>",
78
+ "F7" => "<F17>",
79
+ "F8" => "<F18>",
80
+ "F9" => "<F19>",
81
+
82
+ "%1" => "<Help>",
83
+ "&8" => "<Undo>",
84
+ "#2" => "<S-Home>",
85
+ "*7" => "<S-End>",
86
+ "K1" => "<kHome>",
87
+ "K4" => "<kEnd>",
88
+ "K3" => "<kPageUp>",
89
+ "K5" => "<kPageDown>",
90
+ "K6" => "<kPlus>",
91
+ "K7" => "<kMinus>",
92
+ "K8" => "<kDivide>",
93
+ "K9" => "<kMultiply>",
94
+ "KA" => "<kEnter>",
95
+ "KB" => "<kPoint>",
96
+ "KC" => "<k0>",
97
+ "KD" => "<k1>",
98
+ "KE" => "<k2>",
99
+ "KF" => "<k3>",
100
+ "KG" => "<k4>",
101
+ "KH" => "<k5>",
102
+ "KI" => "<k6>",
103
+ "KJ" => "<k7>",
104
+ "KK" => "<k8>",
105
+ "KL" => "<k9>",
106
+
107
+ "kP" => "<PageUp>",
108
+ "kN" => "<PageDown>",
109
+ "kh" => "<Home>",
110
+ "@7" => "<End>",
111
+ "kI" => "<Insert>",
112
+ "kD" => "<Del>",
113
+ "kb" => "<BS>",
114
+
115
+ "ku" => "<Up>",
116
+ "kd" => "<Down>",
117
+ "kl" => "<Left>",
118
+ "kr" => "<Right>",
119
+ "#4" => "<S-Left>",
120
+ "%i" => "<S-Right>",
121
+
122
+ "kB" => "<S-Tab>",
123
+ "\xffX" => "<C-@>",
124
+
125
+ # This is how you escape literal 0x80
126
+ "\xfeX" => "<0x80>",
127
+
128
+ # These rarely-used modifiers should be combined with the next
129
+ # stroke (like <S-Space>), but let's put them here for now
130
+ "\xfc\x02" => "<S->",
131
+ "\xfc\x04" => "<C->",
132
+ "\xfc\x06" => "<C-S->",
133
+ "\xfc\x08" => "<A->",
134
+ "\xfc\x0a" => "<A-S->",
135
+ "\xfc\x0c" => "<C-A>",
136
+ "\xfc\x0e" => "<C-A-S->",
137
+ "\xfc\x10" => "<M->",
138
+ "\xfc\x12" => "<M-S->",
139
+ "\xfc\x14" => "<M-C->",
140
+ "\xfc\x16" => "<M-C-S->",
141
+ "\xfc\x18" => "<M-A->",
142
+ "\xfc\x1a" => "<M-A-S->",
143
+ "\xfc\x1c" => "<M-C-A>",
144
+ "\xfc\x1e" => "<M-C-A-S->",
145
+
146
+ # KS_EXTRA keycodes (starting with 0x80 0xfd) are defined by an enum in
147
+ # Vim's keymap.h. Sometimes, a new Vim adds or removes a keycode, which
148
+ # changes the binary representation of every keycode after it. Very
149
+ # annoying.
150
+ "\xfd\x4" => "<S-Up>",
151
+ "\xfd\x5" => "<S-Down>",
152
+ "\xfd\x6" => "<S-F1>",
153
+ "\xfd\x7" => "<S-F2>",
154
+ "\xfd\x8" => "<S-F3>",
155
+ "\xfd\x9" => "<S-F4>",
156
+ "\xfd\xa" => "<S-F5>",
157
+ "\xfd\xb" => "<S-F6>",
158
+ "\xfd\xc" => "<S-F7>",
159
+ "\xfd\xd" => "<S-F9>",
160
+ "\xfd\xe" => "<S-F10>",
161
+ "\xfd\xf" => "<S-F10>",
162
+ "\xfd\x10" => "<S-F11>",
163
+ "\xfd\x11" => "<S-F12>",
164
+ "\xfd\x12" => "<S-F13>",
165
+ "\xfd\x13" => "<S-F14>",
166
+ "\xfd\x14" => "<S-F15>",
167
+ "\xfd\x15" => "<S-F16>",
168
+ "\xfd\x16" => "<S-F17>",
169
+ "\xfd\x17" => "<S-F18>",
170
+ "\xfd\x18" => "<S-F19>",
171
+ "\xfd\x19" => "<S-F20>",
172
+ "\xfd\x1a" => "<S-F21>",
173
+ "\xfd\x1b" => "<S-F22>",
174
+ "\xfd\x1c" => "<S-F23>",
175
+ "\xfd\x1d" => "<S-F24>",
176
+ "\xfd\x1e" => "<S-F25>",
177
+ "\xfd\x1f" => "<S-F26>",
178
+ "\xfd\x20" => "<S-F27>",
179
+ "\xfd\x21" => "<S-F28>",
180
+ "\xfd\x22" => "<S-F29>",
181
+ "\xfd\x23" => "<S-F30>",
182
+ "\xfd\x24" => "<S-F31>",
183
+ "\xfd\x25" => "<S-F32>",
184
+ "\xfd\x26" => "<S-F33>",
185
+ "\xfd\x27" => "<S-F34>",
186
+ "\xfd\x28" => "<S-F35>",
187
+ "\xfd\x29" => "<S-F36>",
188
+ "\xfd\x2a" => "<S-F37>",
189
+ "\xfd\x2b" => "<Mouse>",
190
+ "\xfd\x2c" => "<LeftMouse>",
191
+ "\xfd\x2d" => "<LeftDrag>",
192
+ "\xfd\x2e" => "<LeftRelease>",
193
+ "\xfd\x2f" => "<MiddleMouse>",
194
+ "\xfd\x30" => "<MiddleDrag>",
195
+ "\xfd\x31" => "<MiddleRelease>",
196
+ "\xfd\x32" => "<RightMouse>",
197
+ "\xfd\x33" => "<RightDrag>",
198
+ "\xfd\x34" => "<RightRelease>",
199
+ "\xfd\x35" => nil, # KE_IGNORE
200
+ #"\xfd\x36" => "KE_TAB",
201
+ #"\xfd\x37" => "KE_S_TAB_OLD",
202
+
203
+ # Vim 7.4.1433 removed KE_SNIFF. Unfortunately, this changed the
204
+ # offset of every keycode after it.
205
+ # Vim 8.0.0697 added back a KE_SNIFF_UNUSED to fill in for the
206
+ # removed KE_SNIFF.
207
+ # Keycodes after this point should be accurate for vim < 7.4.1433
208
+ # and vim > 8.0.0697.
209
+ #"\xfd\x38" => "KE_SNIFF",
210
+ #"\xfd\x39" => "KE_XF1",
211
+ #"\xfd\x3a" => "KE_XF2",
212
+ #"\xfd\x3b" => "KE_XF3",
213
+ #"\xfd\x3c" => "KE_XF4",
214
+ #"\xfd\x3d" => "KE_XEND",
215
+ #"\xfd\x3e" => "KE_ZEND",
216
+ #"\xfd\x3f" => "KE_XHOME",
217
+ #"\xfd\x40" => "KE_ZHOME",
218
+ #"\xfd\x41" => "KE_XUP",
219
+ #"\xfd\x42" => "KE_XDOWN",
220
+ #"\xfd\x43" => "KE_XLEFT",
221
+ #"\xfd\x44" => "KE_XRIGHT",
222
+ #"\xfd\x45" => "KE_LEFTMOUSE_NM",
223
+ #"\xfd\x46" => "KE_LEFTRELEASE_NM",
224
+ #"\xfd\x47" => "KE_S_XF1",
225
+ #"\xfd\x48" => "KE_S_XF2",
226
+ #"\xfd\x49" => "KE_S_XF3",
227
+ #"\xfd\x4a" => "KE_S_XF4",
228
+ "\xfd\x4b" => "<ScrollWheelUp>",
229
+ "\xfd\x4c" => "<ScrollWheelDown>",
230
+
231
+ # Horizontal scroll wheel support was added in Vim 7.3c. These
232
+ # 2 entries shifted the rest of the KS_EXTRA mappings down 2.
233
+ # Though Vim 7.2 is rare today, it was common soon after
234
+ # vimgolf.com was launched. In cases where the 7.3 code is
235
+ # never used but the 7.2 code was common, it makes sense to use
236
+ # the 7.2 code. There are conflicts though, so some legacy
237
+ # keycodes have to stay wrong.
238
+ "\xfd\x4d" => "<ScrollWheelRight>",
239
+ "\xfd\x4e" => "<ScrollWheelLeft>",
240
+ "\xfd\x4f" => "<kInsert>",
241
+ "\xfd\x50" => "<kDel>",
242
+ "\xfd\x51" => "<0x9b>", # :help <CSI>
243
+ #"\xfd\x52" => "KE_SNR",
244
+ #"\xfd\x53" => "KE_PLUG", # never used
245
+ "\xfd\x53" => "<C-Left>", # 7.2 compat
246
+ #"\xfd\x54" => "KE_CMDWIN", # never used
247
+ "\xfd\x54" => "<C-Right>", # 7.2 compat
248
+ "\xfd\x55" => "<C-Left>", # 7.2 <C-Home> conflict
249
+ "\xfd\x56" => "<C-Right>", # 7.2 <C-End> conflict
250
+ "\xfd\x57" => "<C-Home>",
251
+ "\xfd\x58" => "<C-End>",
252
+ #"\xfd\x59" => "KE_X1MOUSE",
253
+ #"\xfd\x5a" => "KE_X1DRAG",
254
+ #"\xfd\x5b" => "KE_X1RELEASE",
255
+ #"\xfd\x5c" => "KE_X2MOUSE",
256
+ #"\xfd\x5d" => "KE_X2DRAG",
257
+ #"\xfd\x5e" => "KE_X2RELEASE",
258
+ "\xfd\x5e" => nil, # 7.2 compat (I think?)
259
+ #"\xfd\x5f" => "KE_DROP",
260
+ #"\xfd\x60" => "KE_CURSORHOLD",
261
+
262
+ # If you use gvim, you'll get an entry in your keylog every time the
263
+ # window gains or loses focus. These "keystrokes" should not show and
264
+ # should not be counted.
265
+ "\xfd\x60" => nil, # 7.2 Focus Gained compat
266
+ "\xfd\x61" => nil, # Focus Gained (GVIM) (>7.4.1433)
267
+ "\xfd\x62" => nil, # Focus Gained (GVIM)
268
+ "\xfd\x63" => nil, # Focus Lost (GVIM)
269
+ })
224
270
  end
225
271
  end