diff --git a/share/completions/ip.fish b/share/completions/ip.fish new file mode 100644 index 000000000..b7ded602e --- /dev/null +++ b/share/completions/ip.fish @@ -0,0 +1,344 @@ +# ip(8) completion for fish + +# The difficulty here is that ip allows abbreviating options, so we need to complete "ip a" like "ip address", but not "ip m" like "ip mroute" +# Also the manpage and even the grammar it accepts is utter shite (options can only be before commands, some things are only in the BNF, others only in the text) +# It also quite likes the word "dev", even though it needs it less than the BNF specifies + +set -l ip_commands link address addrlabel route rule neigh ntable tunnel tuntap maddr mroute mrule monitor xfrm netns l2tp tcp_metrics +set -l ip_addr a ad add addr addre addres address +set -l ip_link l li lin link +set -l ip_all_commands $ip_commands $ip_addr $ip_link + +function __fish_ip_commandwords + set -l skip 0 + set -l cmd (commandline -opc) + # HACK: Handle and/or/not specially because they have hardcoded completion behavior + # that doesn't remove them from the commandline + if contains -- $cmd[1] and or not + set -e cmd[1] + end + # Remove the first word because it's "ip" or an alias for it + set -e cmd[1] + set -l have_command 0 + for word in $cmd + switch $word + # Normalize the commands to their full form - `ip a` is `ip address` + # This can't be just an unambiguity check because there's also `ip addrlabel` + case a ad add addr addre addres address + # "addr add" is a thing, so we can only echo "address" if it's actually the command + if test $have_command = 0 + set have_command 1 + echo address + else + echo $word + end + case l li lin link + if test $have_command = 0 + set have_command 1 + echo link + else + echo $word + end + case "addrl*" + if test $have_command = 0 + set have_command 1 + echo addrlabel + else + echo $word + end + case r ro rou rout route + if test $have_command = 0 + set have_command 1 + echo route + else + echo $word + end + case "ru*" + if test $have_command = 0 + set have_command 1 + echo rule + else + echo $word + end + case n ne nei neig neigh + if test $have_command = 0 + set have_command 1 + echo neigh + else + echo $word + end + case nt + if test $have_command = 0 + set have_command 1 + echo ntable + else + echo $word + end + case t tu tun tunn tunne tunnel + if test $have_command = 0 + set have_command 1 + echo tunnel + else + echo $word + end + case "tunt*" + if test $have_command = 0 + set have_command 1 + echo tuntap + else + echo $word + end + case m ma mad madd maddr maddre maddres maddress + if test $have_command = 0 + set have_command 1 + echo maddress + else + echo $word + end + case mr "mro*" + if test $have_command = 0 + set have_command 1 + echo mroute + else + echo $word + end + case "mru*" + if test $have_command = 0 + set have_command 1 + echo mrule + else + echo $word + end + case "mo*" + if test $have_command = 0 + set have_command 1 + echo monitor + else + echo $word + end + case "x*" + if test $have_command = 0 + set have_command 1 + echo xfrm + else + echo $word + end + case "net*" + if test $have_command = 0 + set have_command 1 + echo netns + else + echo $word + end + case "l*" + if test $have_command = 0 + set have_command 1 + echo l2tp + else + echo $word + end + case "tc*" + if test $have_command = 0 + set have_command 1 + echo tcp_metrics + else + echo $word + end + case "to*" + if test $have_command = 0 + set have_command 1 + echo token + else + echo $word + end + case '-n' '-netns' '--netns' + if test $have_command = 0 + set skip 1 + else + echo $word + end + case '-*' + test $have_command = 0; and continue + echo $word + case '*' + if test $skip = 1 + set skip 0 + continue + end + echo $word + end + end + # Print an empty line if the current token is empty, so we know that the one before it is finished + # TODO: For some reason it is necessary to always print the current token - why doesn't the above loop catch it? + set -l token (commandline -ct) + echo $token +end + +function __fish_ip_device + ip -o link show | while read a b c + printf '%s\t%s\n' (string replace ':' '' -- $b) "Device" + end +end + +function __fish_ip_scope + if test -r /etc/iproute2/rt_scopes + string replace -r '#.*' '' < /etc/iproute2/rt_scopes \ + | string match -v '^\s*$' \ + | string replace -r '(\S+)\s*(\S+)' '$1\t$2\n$2\t$1' \ + | string match -rv '^(global|link|host).*' # Ignore scopes with better descriptions + end + # Predefined scopes + printf '%s\t%s\n' global "Address is globally valid" \ + link "Address is link-local, only valid on this device" \ + host "Address is only valid on this host" +end + +function __fish_complete_ip + set -l cmd (__fish_ip_commandwords) + set -l count (count $cmd) + switch "$cmd[1]" + case address + # We're still _on_ the second word, which is the subcommand + if not set -q cmd[3] + printf '%s\t%s\n' add "Add new protocol address" \ + delete "Delete protocol address" \ + show "Look at protocol addresses" \ + flush "Flush protocol addresses" + else + switch $cmd[2] + # Change and replace are undocumented (apart from mentions in the BNF) + case add change replace + switch $count + case 3 + # __fish_ip_complete_ip + case '*' + switch $cmd[-2] + case dev + __fish_ip_device + case scope + __fish_ip_scope + # TODO: Figure out how to complete these + case label + # Prefix + case local peer broadcast + # Address + case valid_lft preferred_lft + # Lifetime + case '*' + printf '%s\t%s\n' forever "Keep address valid forever" \ + home "(Ipv6 only) Designate address as home adress" \ + nodad "(Ipv6 only) Don't perform duplicate address detection" \ + dev "Add address to specified device" \ + scope "Set scope of address" \ + label "Tag address with label" + end + end + case delete + switch $count + case 3 + ip -o addr show | while read a b c d e + echo $d + end + case 4 + # A dev argument is mandatory, but contrary to the BNF, other things (like "scope") are also valid here + # And yes, unlike e.g. show, this _needs_ the "dev" before the device + # Otherwise it barfs and says "??? prefix is expected" + # Anyway, try to steer the user towards supplying a device + echo dev + case 5 + switch $cmd[-2] + case dev + ip -o addr show | string match "*$cmd[3]*" | while read a b c + echo $b + end + # TODO: Moar + end + case show save flush # These take the same args + switch $cmd[-2] + case dev + __fish_ip_device + case scope + __fish_ip_scope + case to + # Prefix + case label + # Label-pattern + case '*' + printf '%s\t%s\n' up "Only active devices" \ + dev "Limit to a certain device" \ + scope "Limit scope" \ + to "Limit prefix" \ + label "Limit by label" \ + dynamic "(Ipv6 only) Limit to dynamic addresses" \ + permanent "(Ipv6 only) Limit to permanent addresses" + __fish_ip_device + # TODO: Moar + end + end + end + end + case link + if not set -q cmd[3] + printf '%s\t%s\n' add "Add virtual link" \ + delete "Delete virtual link" \ + set "Change device attributes" \ + show "Display device attributes" \ + help "Display help" + else + # TODO: Add moar + switch $cmd[2] + case add + switch $cmd[-2] + case link + __fish_ip_device + case name + case type + printf '%s\t%s\n' \ + macvtap "Virtual interface based on link layer address (MAC) and TAP." \ + vcan "Virtual Controller Area Network interface" \ + veth "Virtual ethernet interface" \ + vlan "802.1q tagged virtual LAN interface" \ + vxlan "Virtual eXtended LAN" \ + ip6tnl "Virtual tunnel interface IPv4|IPv6 over IPv6" \ + ipip "Virtual tunnel interface IPv4 over IPv4" \ + sit "Virtual tunnel interface IPv6 over IPv4" \ + gre "Virtual tunnel interface GRE over IPv4" \ + gretap "Virtual L2 tunnel interface GRE over IPv4" \ + ip6gre "Virtual tunnel interface GRE over IPv6" \ + ip6gretap "Virtual L2 tunnel interface GRE over IPv6" \ + vti "Virtual tunnel interface" \ + nlmon "Netlink monitoring device" \ + ipvlan "Interface for L3 (IPv6/IPv4) based VLANs" \ + lowpan "Interface for 6LoWPAN (IPv6) over IEEE 802.15.4 / Bluetooth" \ + geneve "GEneric NEtwork Virtualization Encapsulation" + end + case delete + case set + case show + case help + end + end + end +end + +complete -f -c ip +complete -f -c ip -a '(__fish_complete_ip)' +complete -f -c ip -n "not __fish_seen_subcommand_from $ip_all_commands" -a "$ip_commands" +# Yes, ip only takes options before "objects" +complete -c ip -s b -l batch -d "Read commands from file or stdin" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -l force -d "Don't terminate on errors in batch mode" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -s V -l Version -d "Print the version" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s s -l stats -d "Output more information" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s d -l details -d "Output more detailed information" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s l -l loops -d "Specify maximum number of loops for 'ip addr flush'" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s f -l family -d "The protocol family to use" -a "inet inet6 bridge ipx dnet link any" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s 4 -d "Short for --family inet" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s 6 -d "Short for --family inet6" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s B -d "Short for --family bridge" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s D -d "Short for --family decnet" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s I -d "Short for --family ipx" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s O -d "Short for --family link" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s o -l oneline -d "Output on one line" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s r -l resolve -d "Resolve names and print them instead of addresses" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s n -l net -l netns -d "Use specified network namespace" -n "not __fish_seen_subcommand_from $ip_commands" +complete -c ip -f -s a -l all -d "Execute command for all objects" -n "not __fish_seen_subcommand_from $ip_commands"