Skip to content

Latest commit

 

History

History
152 lines (120 loc) · 5.74 KB

ssh.org

File metadata and controls

152 lines (120 loc) · 5.74 KB

Elvish completions for ssh

Completions for ssh, scp and sftp.

This file is written in literate programming style, to make it easy to explain. See ssh.elv for the generated file.

Table of Contents

Usage

Install the elvish-completions package using epm:

use epm
epm:install github.com/zzamboni/elvish-completions

In your rc.elv, load this module:

use github.com/zzamboni/elvish-completions/ssh

Hosts for the completions will be read from the files listed in the $config-files variable. Here is its default value:

config-files = [ ~/.ssh/config /etc/ssh/ssh_config /etc/ssh_config ]

All hosts listed in Host sections of the config files will be provided for completion. Patterns including any metacharacters (*, ? and !) will not be shown.

[~]─> ssh <tab>
 COMPLETING argument
 host1                host2                  host3

Completions are also provided for config options. If you type -o<space> and press Tab, a list of valid configuration options will be provided. The valid configuration options are automatically extracted from the ssh_config man page, if it’s available.

[~]─> ssh -o <tab>
 COMPLETING argument _
 AddKeysToAgent=                   ControlPath=                HostKeyAlias=                  NoHostAuthenticationForLocalhost=  ServerAliveCountMax=
 AddressFamily=                    ControlPersist=             HostName=                      NumberOfPasswordPrompts=           ServerAliveInterval=
 BatchMode=                        DynamicForward=             HostbasedAuthentication=       PKCS11Provider=                    StreamLocalBindMask=
 ...

Implementation

Libraries and global variables

We first load a number of libraries, including comp, the Elvish completion framework.

use ./comp
use re
use str

List of config files from which to extract hostnames.

var config-files = [ ~/.ssh/config /etc/ssh/ssh_config /etc/ssh_config ]

Initialization

The -ssh-hosts function extracts all hostnames from the files listed in $config-files. Nonexistent files in the list are ignored, and only hostnames which do not include glob characters (*, ?, !) are returned.

fn -ssh-hosts {
  var hosts = [&]
  all $config-files | each {|file|
    set _ = ?(cat $file 2>&-) | eawk {|_ @f|
      if (re:match '^(?i)host$' $f[0]) {
        all $f[1..] | each {|p|
          if (not (re:match '[*?!]' $p)) {
            set hosts[$p] = $true
  }}}}}
  keys $hosts
}

We store in -ssh-options all the possible configuration options, by parsing them directly from the ssh_config man page (if available). These are initialized by the -gen-ssh-options on first use to reduce load time, and cached so that any delay is only incurred once.

var -ssh-options = []
fn -gen-ssh-options {
  if (eq $-ssh-options []) {
    set -ssh-options = [(
        set _ = ?(cat (man -w ssh_config 2>&-)) |
        eawk {|l @f| if (re:match '^\.It Cm' $l) { put $f[2] } } |
        comp:decorate &suffix='='
    )]
  }
  all $-ssh-options
}

The $ssh-opts array stores the definitions of command-line options. For now we only complete:

  • -o (including completions for its argument) generated with -gen-ssh-options defined above
  • -i/--inventory generated with comp:files defined in comp.elv
var ssh-opts = [
  [ &short= o
    &arg-required= $true
    &arg-completer= $-gen-ssh-options~
  ]
  [ &short= i
    &long= inventory
    &arg-required= $true
    &arg-completer= $comp:files~
  ]
]

-ssh-host-completions dynamically generates the completion definition for hostnames for ssh-related commands. The hostnames are extracted from the user’s ssh config files by the -ssh-hosts function defined above. The completions for ssh and scp, for example, are the same except for the suffix that needs to be added to the hostnames in the completion, so we allow the suffix to be specified as an option. We also allow for a username to be specified at the beginning of the hostname (user@), and still generate the completions correctly, so you can type ssh user@abc<Tab> and the corresponding hostnames will be completed.

fn -ssh-host-completions {|arg &suffix=''|
  var user-given = (str:join '' [(re:find '^(.*@)' $arg)[groups][1][text]])
  -ssh-hosts | each {|host| put $user-given$host } | comp:decorate &suffix=$suffix
}

We use -ssh-host-completions to produce the actual completion definitions for ssh, sftp and scp. For scp we also complete local filenames.

set edit:completion:arg-completer[ssh]  = (comp:sequence &opts=$ssh-opts [$-ssh-host-completions~])
set edit:completion:arg-completer[sftp] = (comp:sequence &opts=$ssh-opts [$-ssh-host-completions~])
set edit:completion:arg-completer[scp]  = (comp:sequence &opts=$ssh-opts [
    {|arg|
      -ssh-host-completions &suffix=":" $arg
      edit:complete-filename $arg
    }
    ...
])