summaryrefslogtreecommitdiffstats
path: root/src/testdir/keycode_check.vim
diff options
context:
space:
mode:
Diffstat (limited to 'src/testdir/keycode_check.vim')
-rw-r--r--src/testdir/keycode_check.vim470
1 files changed, 470 insertions, 0 deletions
diff --git a/src/testdir/keycode_check.vim b/src/testdir/keycode_check.vim
new file mode 100644
index 0000000..8320341
--- /dev/null
+++ b/src/testdir/keycode_check.vim
@@ -0,0 +1,470 @@
+vim9script
+
+# Script to get various codes that keys send, depending on the protocol used.
+#
+# Usage: vim -u NONE -S keycode_check.vim
+#
+# Author: Bram Moolenaar
+# Last Update: 2022 Nov 15
+#
+# The codes are stored in the file "keycode_check.json", so that you can
+# compare the results of various terminals.
+#
+# You can select what protocol to enable:
+# - None
+# - modifyOtherKeys level 2
+# - kitty keyboard protocol
+
+# Change directory to where this script is, so that the json file is found
+# there.
+exe 'cd ' .. expand('<sfile>:h')
+echo 'working in directory: ' .. getcwd()
+
+const filename = 'keycode_check.json'
+
+# Dictionary of dictionaries with the results in the form:
+# {'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
+# 'xterm2': {protocol: 'mok2', 'Tab': '09', 'S-Tab': '09'},
+# 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '09'},
+# }
+# The values are in hex form.
+var keycodes = {}
+
+if filereadable(filename)
+ keycodes = readfile(filename)->join()->json_decode()
+else
+ # Use some dummy entries to try out with
+ keycodes = {
+ 'xterm': {protocol: 'none', 'Tab': '09', 'S-Tab': '09'},
+ 'kitty': {protocol: 'kitty', 'Tab': '09', 'S-Tab': '1b5b393b3275'},
+ }
+endif
+var orig_keycodes = deepcopy(keycodes) # used to detect something changed
+
+# Write the "keycodes" variable in JSON form to "filename".
+def WriteKeycodes()
+ # If the file already exists move it to become the backup file.
+ if filereadable(filename)
+ if rename(filename, filename .. '~')
+ echoerr $'Renaming {filename} to {filename}~ failed!'
+ return
+ endif
+ endif
+
+ if writefile([json_encode(keycodes)], filename) != 0
+ echoerr $'Writing {filename} failed!'
+ endif
+enddef
+
+# The key entries that we want to list, in this order.
+# The first item is displayed in the prompt, the second is the key in
+# the keycodes dictionary.
+var key_entries = [
+ ['Tab', 'Tab'],
+ ['Shift-Tab', 'S-Tab'],
+ ['Ctrl-Tab', 'C-Tab'],
+ ['Alt-Tab', 'A-Tab'],
+ ['Ctrl-I', 'C-I'],
+ ['Shift-Ctrl-I', 'S-C-I'],
+ ['Esc', 'Esc'],
+ ['Shift-Esc', 'S-Esc'],
+ ['Ctrl-Esc', 'C-Esc'],
+ ['Alt-Esc', 'A-Esc'],
+ ['Space', 'Space'],
+ ['Shift-Space', 'S-Space'],
+ ['Ctrl-Space', 'C-Space'],
+ ['Alt-Space', 'A-Space'],
+ ]
+
+# Given a terminal name and a item name, return the text to display.
+def GetItemDisplay(term: string, item: string): string
+ var val = get(keycodes[term], item, '')
+
+ # see if we can pretty-print this one
+ var pretty = val
+ if val[0 : 1] == '1b'
+ pretty = 'ESC'
+ var idx = 2
+
+ if val[0 : 3] == '1b5b'
+ pretty = 'CSI'
+ idx = 4
+ endif
+
+ var digits = false
+ while idx < len(val)
+ var cc = val[idx : idx + 1]
+ var nr = str2nr('0x' .. cc, 16)
+ idx += 2
+ if nr >= char2nr('0') && nr <= char2nr('9')
+ if !digits
+ pretty ..= ' '
+ endif
+ digits = true
+ pretty ..= cc[1]
+ else
+ if nr == char2nr(';') && digits
+ # don't use space between semicolon and digits to keep it short
+ pretty ..= ';'
+ else
+ digits = false
+ if nr >= char2nr(' ') && nr <= char2nr('~')
+ # printable character
+ pretty ..= ' ' .. printf('%c', nr)
+ else
+ # non-printable, use hex code
+ pretty = val
+ break
+ endif
+ endif
+ endif
+ endwhile
+ endif
+
+ return pretty
+enddef
+
+
+# Action: list the information in "keycodes" in a more or less nice way.
+def ActionList()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+ sort(terms)
+
+ var items = ['protocol', 'version', 'kitty', 'modkeys']
+ + key_entries->copy()->map((_, v) => v[1])
+
+ # For each terminal compute the needed width, add two.
+ # You may need to increase the terminal width to avoid wrapping.
+ var widths = []
+ for [idx, term] in items(terms)
+ widths[idx] = len(term) + 2
+ endfor
+
+ for item in items
+ for [idx, term] in items(terms)
+ var l = len(GetItemDisplay(term, item))
+ if widths[idx] < l + 2
+ widths[idx] = l + 2
+ endif
+ endfor
+ endfor
+
+ # Use one column of width 10 for the item name.
+ echo "\n"
+ echon ' '
+ for [idx, term] in items(terms)
+ echon printf('%-' .. widths[idx] .. 's', term)
+ endfor
+ echo "\n"
+
+ for item in items
+ echon printf('%8s ', item)
+ for [idx, term] in items(terms)
+ echon printf('%-' .. widths[idx] .. 's', GetItemDisplay(term, item))
+ endfor
+ echo ''
+ endfor
+ echo "\n"
+enddef
+
+# Convert the literal string after "raw key input" into hex form.
+def Literal2hex(code: string): string
+ var hex = ''
+ for i in range(len(code))
+ hex ..= printf('%02x', char2nr(code[i]))
+ endfor
+ return hex
+enddef
+
+def GetTermName(): string
+ var name = input('Enter the name of the terminal: ')
+ return name
+enddef
+
+# Gather key codes for terminal "name".
+def DoTerm(name: string)
+ var proto = inputlist([$'What protocol to enable for {name}:',
+ '1. None',
+ '2. modifyOtherKeys level 2',
+ '3. kitty',
+ ])
+ echo "\n"
+ &t_TE = "\<Esc>[>4;m"
+ var proto_name = 'unknown'
+ if proto == 1
+ # Request the XTQMODKEYS value and request the kitty keyboard protocol status.
+ &t_TI = "\<Esc>[?4m" .. "\<Esc>[?u"
+ proto_name = 'none'
+ elseif proto == 2
+ # Enable modifyOtherKeys level 2 and request the XTQMODKEYS value.
+ &t_TI = "\<Esc>[>4;2m" .. "\<Esc>[?4m"
+ proto_name = 'mok2'
+ elseif proto == 3
+ # Enable Kitty keyboard protocol and request the status.
+ &t_TI = "\<Esc>[>1u" .. "\<Esc>[?u"
+ proto_name = 'kitty'
+ else
+ echoerr 'invalid protocol choice'
+ return
+ endif
+
+ # Append the request for the version response, this is used to check we have
+ # the results.
+ &t_TI ..= "\<Esc>[>c"
+
+ # Pattern that matches the line with the version response.
+ const version_pattern = "\<Esc>\\[>\\d\\+;\\d\\+;\\d*c"
+
+ # Pattern that matches the XTQMODKEYS response:
+ # CSI > 4;Pv m
+ # where Pv indicates the modifyOtherKeys level
+ const modkeys_pattern = "\<Esc>\\[>4;\\dm"
+
+ # Pattern that matches the line with the status. Currently what terminals
+ # return for the Kitty keyboard protocol.
+ const kitty_status_pattern = "\<Esc>\\[?\\d\\+u"
+
+ ch_logfile('keylog', 'w')
+
+ # executing a dummy shell command will output t_TI
+ !echo >/dev/null
+
+ # Wait until the log file has the version response.
+ var startTime = reltime()
+ var seenVersion = false
+ while !seenVersion
+ var log = readfile('keylog')
+ if len(log) > 2
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+ if code =~ version_pattern
+ seenVersion = true
+ echo 'Found the version response'
+ break
+ endif
+ endif
+ endfor
+ endif
+ if reltime(startTime)->reltimefloat() > 3
+ # break out after three seconds
+ break
+ endif
+ endwhile
+
+ echo 'seenVersion: ' seenVersion
+
+ # Prepare the terminal entry, set protocol and clear status and version.
+ if !has_key(keycodes, name)
+ keycodes[name] = {}
+ endif
+ keycodes[name]['protocol'] = proto_name
+ keycodes[name]['version'] = ''
+ keycodes[name]['kitty'] = ''
+ keycodes[name]['modkeys'] = ''
+
+ # Check the log file for a status and the version response
+ ch_logfile('', '')
+ var log = readfile('keylog')
+ delete('keylog')
+
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+
+ # Check for the XTQMODKEYS response.
+ if code =~ modkeys_pattern
+ var modkeys = substitute(code, '.*\(' .. modkeys_pattern .. '\).*', '\1', '')
+ # We could get the level out of the response, but showing the response
+ # itself provides more information.
+ # modkeys = substitute(modkeys, '.*4;\(\d\)m', '\1', '')
+
+ if keycodes[name]['modkeys'] != ''
+ echomsg 'Another modkeys found after ' .. keycodes[name]['modkeys']
+ endif
+ keycodes[name]['modkeys'] = modkeys
+ endif
+
+ # Check for kitty keyboard protocol status
+ if code =~ kitty_status_pattern
+ var status = substitute(code, '.*\(' .. kitty_status_pattern .. '\).*', '\1', '')
+ # use the response itself as the status
+ status = Literal2hex(status)
+
+ if keycodes[name]['kitty'] != ''
+ echomsg 'Another status found after ' .. keycodes[name]['kitty']
+ endif
+ keycodes[name]['kitty'] = status
+ endif
+
+ if code =~ version_pattern
+ var version = substitute(code, '.*\(' .. version_pattern .. '\).*', '\1', '')
+ keycodes[name]['version'] = Literal2hex(version)
+ break
+ endif
+ endif
+ endfor
+
+ echo "For Alt to work you may need to press the Windows/Super key as well"
+ echo "When a key press doesn't get to Vim (e.g. when using Alt) press x"
+
+ # The log of ignored typeahead is left around for debugging, start with an
+ # empty file here.
+ delete('keylog-ignore')
+
+ for entry in key_entries
+ # Consume any typeahead. Wait a bit for any responses to arrive.
+ ch_logfile('keylog-ignore', 'a')
+ while 1
+ sleep 100m
+ if getchar(1) == 0
+ break
+ endif
+ while getchar(1) != 0
+ getchar()
+ endwhile
+ endwhile
+ ch_logfile('', '')
+
+ ch_logfile('keylog', 'w')
+ echo $'Press the {entry[0]} key (q to quit):'
+ var r = getcharstr()
+ ch_logfile('', '')
+ if r == 'q'
+ break
+ endif
+
+ log = readfile('keylog')
+ delete('keylog')
+ if len(log) < 2
+ echoerr 'failed to read result'
+ return
+ endif
+ var done = false
+ for line in log
+ if line =~ 'raw key input'
+ var code = substitute(line, '.*raw key input: "\([^"]*\).*', '\1', '')
+
+ # Remove any version termresponse
+ code = substitute(code, version_pattern, '', 'g')
+
+ # Remove any XTGETTCAP replies.
+ const cappat = "\<Esc>P[01]+\\k\\+=\\x*\<Esc>\\\\"
+ code = substitute(code, cappat, '', 'g')
+
+ # Remove any kitty status reply
+ code = substitute(code, kitty_status_pattern, '', 'g')
+ if code == ''
+ continue
+ endif
+
+ # Convert the literal bytes into hex. If 'x' was pressed then clear
+ # the entry.
+ var hex = ''
+ if code != 'x'
+ hex = Literal2hex(code)
+ endif
+
+ keycodes[name][entry[1]] = hex
+ done = true
+ break
+ endif
+ endfor
+ if !done
+ echo 'Code not found in log'
+ endif
+ endfor
+enddef
+
+# Action: Add key codes for a new terminal.
+def ActionAdd()
+ var name = input('Enter name of the terminal: ')
+ echo "\n"
+ if index(keys(keycodes), name) >= 0
+ echoerr $'Terminal {name} already exists'
+ return
+ endif
+
+ DoTerm(name)
+enddef
+
+# Action: Replace key codes for an already known terminal.
+def ActionReplace()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+
+ var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
+ echo "\n"
+ if choice > 0 && choice <= len(terms)
+ DoTerm(terms[choice - 1])
+ else
+ echo 'invalid index'
+ endif
+enddef
+
+# Action: Clear key codes for an already known terminal.
+def ActionClear()
+ var terms = keys(keycodes)
+ if len(terms) == 0
+ echo 'No terminal results yet'
+ return
+ endif
+
+ var choice = inputlist(['Select:'] + terms->copy()->map((idx, arg) => (idx + 1) .. ': ' .. arg))
+ echo "\n"
+ if choice > 0 && choice <= len(terms)
+ remove(keycodes, terms[choice - 1])
+ else
+ echo 'invalid index'
+ endif
+enddef
+
+# Action: Quit, possibly after saving the results first.
+def ActionQuit()
+ # If nothing was changed just quit
+ if keycodes == orig_keycodes
+ quit
+ endif
+
+ while true
+ var res = input("Save the changed key codes (y/n)? ")
+ if res == 'n'
+ quit
+ endif
+ if res == 'y'
+ WriteKeycodes()
+ quit
+ endif
+ echo 'invalid reply'
+ endwhile
+enddef
+
+# The main loop
+while true
+ var action = inputlist(['Select operation:',
+ '1. List results',
+ '2. Add results for a new terminal',
+ '3. Replace results',
+ '4. Clear results',
+ '5. Quit',
+ ])
+ echo "\n"
+ if action == 1
+ ActionList()
+ elseif action == 2
+ ActionAdd()
+ elseif action == 3
+ ActionReplace()
+ elseif action == 4
+ ActionClear()
+ elseif action == 5
+ ActionQuit()
+ endif
+endwhile