blob: 1ad40059e185abfb22a2a831d68c6aa803be98d9 [file] [log] [blame]
Bram Moolenaara44c7812022-11-15 22:59:07 +00001vim9script
2
3# Script to get various codes that keys send, depending on the protocol used.
4#
5# Usage: vim -u keycode_check.vim
6#
7# Author: Bram Moolenaar
8# Last Update: 2022 Nov 15
9#
10# The codes are stored in the file "keycode_check.json", so that you can
11# compare the results of various terminals.
12#
13# You can select what protocol to enable:
14# - None
15# - modifyOtherKeys level 2
16# - kitty keyboard protocol
17
18# Change directory to where this script is, so that the json file is found
19# there.
20exe 'cd ' .. expand('<sfile>:h')
21echo 'working in directory: ' .. getcwd()
22
23const filename = 'keycode_check.json'
24
25# Dictionary of dictionaries with the results in the form:
26# {'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
27# 'xterm2': {protocol: 'mok2', 'Tab': '09', 'S-Tab': '09'},
28# 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '09'},
29# }
30# The values are in hex form.
31var keycodes = {}
32
33if filereadable(filename)
34 keycodes = readfile(filename)->join()->json_decode()
35else
36 # Use some dummy entries to try out with
37 keycodes = {
38 'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
39 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '1b5b393b3275'},
40 }
41endif
42var orig_keycodes = deepcopy(keycodes) # used to detect something changed
43
44# Write the "keycodes" variable in JSON form to "filename".
45def WriteKeycodes()
46 # If the file already exists move it to become the backup file.
47 if filereadable(filename)
48 if rename(filename, filename .. '~')
49 echoerr $'Renaming {filename} to {filename}~ failed!'
50 return
51 endif
52 endif
53
54 if writefile([json_encode(keycodes)], filename) != 0
55 echoerr $'Writing {filename} failed!'
56 endif
57enddef
58
59# The key entries that we want to list, in this order.
60# The first item is displayed in the prompt, the second is the key in
61# the keycodes dictionary.
62var key_entries = [
63 ['Tab', 'Tab'],
64 ['Shift-Tab', 'S-Tab'],
65 ['Ctrl-Tab', 'C-Tab'],
66 ['Alt-Tab', 'A-Tab'],
67 ['Ctrl-I', 'C-I'],
68 ['Shift-Ctrl-I', 'S-C-I'],
69 ['Esc', 'Esc'],
70 ['Shift-Esc', 'S-Esc'],
71 ['Ctrl-Esc', 'C-Esc'],
72 ['Alt-Esc', 'A-Esc'],
73 ['Space', 'Space'],
74 ['Shift-Space', 'S-Space'],
75 ['Ctrl-Space', 'C-Space'],
76 ['Alt-Space', 'A-Space'],
77 ]
78
79
80# Action: list the information in "keycodes" in a more or less nice way.
81def ActionList()
82 var terms = keys(keycodes)
83 if len(terms) == 0
84 echo 'No terminal results yet'
85 return
86 endif
87
88 # Use one column of width 10 for the item name, then columns of 20
89 # characters to fit most codes. You will need to increase the terminal
90 # width to avoid wrapping.
91 echon printf(' ')
92 for term in terms
93 echon printf('%-20s', term)
94 endfor
95 echo "\n"
96
97 var items = ['protocol'] + key_entries->copy()->map((_, v) => v[1])
98
99 for item in items
100 echon printf('%8s ', item)
101 for term in terms
102 var val = get(keycodes[term], item, '')
103
104 # see if we can pretty-print this one
105 var pretty = val
106 if val[0 : 1] == '1b'
107 pretty = 'ESC'
108 var idx = 2
109
110 if val[0 : 3] == '1b5b'
111 pretty = 'CSI'
112 idx = 4
113 endif
114
115 var digits = false
116 while idx < len(val)
117 var cc = val[idx : idx + 1]
118 var nr = str2nr('0x' .. cc, 16)
119 idx += 2
120 if nr >= char2nr('0') && nr <= char2nr('9')
121 if !digits
122 pretty ..= ' '
123 endif
124 digits = true
125 pretty ..= cc[1]
126 else
127 digits = false
128 if nr >= char2nr(' ') && nr <= char2nr('~')
129 # printable character
130 pretty ..= ' ' .. printf('%c', nr)
131 else
132 # non-printable, use hex code
133 pretty = val
134 break
135 endif
136 endif
137 endwhile
138 endif
139
140 echon printf('%-20s', pretty)
141 endfor
142 echo ''
143 endfor
144 echo "\n"
145enddef
146
147def GetTermName(): string
148 var name = input('Enter the name of the terminal: ')
149 return name
150enddef
151
152# Gather key codes for terminal "name".
153def DoTerm(name: string)
154 var proto = inputlist([$'What protocol to enable for {name}:',
155 '1. None',
156 '2. modifyOtherKeys level 2',
157 '3. kitty',
158 ])
159 echo "\n"
160 &t_TE = "\<Esc>[>4;m"
161 var proto_name = 'none'
162 if proto == 1
163 &t_TI = ""
164 elseif proto == 2
165 &t_TI = "\<Esc>[>4;2m"
166 proto_name = 'mok2'
167 elseif proto == 3
168 &t_TI = "\<Esc>[>1u"
169 proto_name = 'kitty'
170 else
171 echoerr 'invalid protocol choice'
172 return
173 endif
174
175 # executing a dummy shell command will output t_TI
176 !echo >/dev/null
177
178 if !has_key(keycodes, name)
179 keycodes[name] = {}
180 endif
181 keycodes[name]['protocol'] = proto_name
182
183 echo "When a key press doesn't get to Vim (e.g. when using Alt) press Space"
184
185 for entry in key_entries
186 ch_logfile('keylog', 'w')
187 echo $'Press the {entry[0]} key (q to quit):'
188 var r = getcharstr()
189 ch_logfile('', '')
190 if r == 'q'
191 break
192 endif
193 var log = readfile('keylog')
194 delete('keylog')
195 if len(log) < 2
196 echoerr 'failed to read result'
197 return
198 endif
199 var done = false
200 for line in log
201 if line =~ 'raw key input'
202 var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
203
204 # convert the literal bytes into hex
205 var hex = ''
206 for i in range(len(code))
207 hex ..= printf('%02x', char2nr(code[i]))
208 endfor
209 keycodes[name][entry[1]] = hex
210 done = true
211 break
212 endif
213 endfor
214 if !done
215 echo 'Code not found in log'
216 endif
217 endfor
218enddef
219
220# Action: Add key codes for a new terminal.
221def ActionAdd()
222 var name = input('Enter name of the terminal: ')
223 echo "\n"
224 if index(keys(keycodes), name) >= 0
225 echoerr $'Terminal {name} already exists'
226 return
227 endif
228
229 DoTerm(name)
230enddef
231
232# Action: Replace key codes for an already known terminal.
233def ActionReplace()
234 var terms = keys(keycodes)
235 if len(terms) == 0
236 echo 'No terminal results yet'
237 return
238 endif
239
240 var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
241 echo "\n"
242 if choice > 0 && choice <= len(terms)
243 DoTerm(terms[choice - 1])
244 endif
245 echo 'invalid index'
246enddef
247
248# Action: Quit, possibly after saving the results first.
249def ActionQuit()
250 # If nothing was changed just quit
251 if keycodes == orig_keycodes
252 quit
253 endif
254
255 while true
256 var res = input("Save the changed key codes (y/n)? ")
257 if res == 'n'
258 quit
259 endif
260 if res == 'y'
261 WriteKeycodes()
262 quit
263 endif
264 echo 'invalid reply'
265 endwhile
266enddef
267
268# The main loop
269while true
270 var action = inputlist(['Select operation:',
271 '1. List results',
272 '2. Add results for a new terminal',
273 '3. Replace results',
274 '4. Quit',
275 ])
276 echo "\n"
277 if action == 1
278 ActionList()
279 elseif action == 2
280 ActionAdd()
281 elseif action == 3
282 ActionReplace()
283 elseif action == 4
284 ActionQuit()
285 endif
286endwhile