vimgolf 0.4.4 → 0.4.9

Sign up to get free protection for your applications and to get access to all the features.
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