Finish completion for ssh kitten

Fixes #3760
This commit is contained in:
Kovid Goyal 2021-06-26 12:28:12 +05:30
parent 051374cd55
commit 8a4b326127
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 119 additions and 9 deletions

View File

@ -19,6 +19,8 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- ssh kitten: Support systems where the login shell is a non-POSIX shell - ssh kitten: Support systems where the login shell is a non-POSIX shell
(:iss:`3405`) (:iss:`3405`)
- ssh kitten: Add completion (:iss:`3760`)
- ssh kitten: Fix "Connection closed" message being printed by ssh when running - ssh kitten: Fix "Connection closed" message being printed by ssh when running
remote commands remote commands

View File

@ -162,8 +162,103 @@ def option_help_map() -> Dict[str, str]:
# }}} # }}}
def complete_choices(ans: Completions, prefix: str, title: str, key: str, comma_separated: bool) -> None: # option names {{{
choices = {} @run_once
def option_names() -> Tuple[str, ...]:
return tuple(filter(None, (
line.strip() for line in '''
AddKeysToAgent
AddressFamily
BatchMode
BindAddress
CanonicalDomains
CanonicalizeFallbackLocal
CanonicalizeHostname
CanonicalizeMaxDots
CanonicalizePermittedCNAMEs
CASignatureAlgorithms
CertificateFile
ChallengeResponseAuthentication
CheckHostIP
Ciphers
ClearAllForwardings
Compression
ConnectionAttempts
ConnectTimeout
ControlMaster
ControlPath
ControlPersist
DynamicForward
EscapeChar
ExitOnForwardFailure
FingerprintHash
ForwardAgent
ForwardX11
ForwardX11Timeout
ForwardX11Trusted
GatewayPorts
GlobalKnownHostsFile
GSSAPIAuthentication
GSSAPIDelegateCredentials
HashKnownHosts
Host
HostbasedAcceptedAlgorithms
HostbasedAuthentication
HostKeyAlgorithms
HostKeyAlias
Hostname
IdentitiesOnly
IdentityAgent
IdentityFile
IPQoS
KbdInteractiveAuthentication
KbdInteractiveDevices
KexAlgorithms
KnownHostsCommand
LocalCommand
LocalForward
LogLevel
MACs
Match
NoHostAuthenticationForLocalhost
NumberOfPasswordPrompts
PasswordAuthentication
PermitLocalCommand
PermitRemoteOpen
PKCS11Provider
Port
PreferredAuthentications
ProxyCommand
ProxyJump
ProxyUseFdpass
PubkeyAcceptedAlgorithms
PubkeyAuthentication
RekeyLimit
RemoteCommand
RemoteForward
RequestTTY
SendEnv
ServerAliveInterval
ServerAliveCountMax
SetEnv
StreamLocalBindMask
StreamLocalBindUnlink
StrictHostKeyChecking
TCPKeepAlive
Tunnel
TunnelDevice
UpdateHostKeys
User
UserKnownHostsFile
VerifyHostKeyDNS
VisualHostKey
XAuthLocation
'''.splitlines())))
# }}}
def complete_choices(ans: Completions, prefix: str, title: str, choices: Iterable[str], comma_separated: bool = False) -> None:
matches: Dict[str, str] = {}
word_transforms = {} word_transforms = {}
effective_prefix = prefix effective_prefix = prefix
hidden_prefix = '' hidden_prefix = ''
@ -172,15 +267,19 @@ def complete_choices(ans: Completions, prefix: str, title: str, key: str, comma_
hidden_prefix = ','.join(prefix.split(',')[:-1]) hidden_prefix = ','.join(prefix.split(',')[:-1])
if hidden_prefix: if hidden_prefix:
hidden_prefix += ',' hidden_prefix += ','
for line in lines_from_command('ssh', '-Q', key): for q in choices:
q = line.strip()
if q.startswith(effective_prefix): if q.startswith(effective_prefix):
if comma_separated: if comma_separated:
tq = q tq = q
q = hidden_prefix + q + ',' q = hidden_prefix + q + ','
word_transforms[q] = tq word_transforms[q] = tq
choices[q] = '' matches[q] = ''
ans.add_match_group(title, choices, trailing_space=not comma_separated, word_transforms=word_transforms) ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)
def complete_q_choices(ans: Completions, prefix: str, title: str, key: str, comma_separated: bool) -> None:
choices = (line.strip() for line in lines_from_command('ssh', '-Q', key))
complete_choices(ans, prefix, title, choices, comma_separated)
def complete_arg(ans: Completions, option_flag: str, prefix: str = '') -> None: def complete_arg(ans: Completions, option_flag: str, prefix: str = '') -> None:
@ -189,11 +288,20 @@ def complete_arg(ans: Completions, option_flag: str, prefix: str = '') -> None:
if option_name.endswith('file') or option_name.endswith('path'): if option_name.endswith('file') or option_name.endswith('path'):
return complete_files_and_dirs(ans, prefix, option_name) return complete_files_and_dirs(ans, prefix, option_name)
choices = { choices = {
'mac_spec': ('MAC algorithms', 'mac', True), 'mac_spec': ('MAC algorithm', 'mac', True),
'cipher_spec': ('encryption ciphers', 'cipher', True), 'cipher_spec': ('encryption cipher', 'cipher', True),
'query_option': ('query option', 'help', False),
} }
if option_name in choices: if option_name in choices:
return complete_choices(ans, prefix, *choices[option_name]) return complete_q_choices(ans, prefix, *choices[option_name])
if option_name == 'destination':
return complete_destination(ans, prefix)
if option_name == 'ctl_cmd':
return complete_choices(ans, prefix, 'control command', ('check', 'forward', 'cancel', 'exit'))
if option_name == 'option':
matches = (x+'=' for x in option_names() if x.startswith(prefix))
word_transforms = {x+'=': x for x in option_names()}
ans.add_match_group('configure file option', matches, trailing_space=False, word_transforms=word_transforms)
def complete_destination(ans: Completions, prefix: str = '') -> None: def complete_destination(ans: Completions, prefix: str = '') -> None: