From d0fdc807253b59f2b8db0205e7083b26189b353f Mon Sep 17 00:00:00 2001 From: Fabian Homborg Date: Wed, 27 Apr 2016 14:17:29 +0200 Subject: [PATCH] Make busctl completions useful - More accurate - Fast enough to be usable (previously, this would sometimes take a few seconds) - A bit smaller --- share/completions/busctl.fish | 250 ++++++++++++++-------------------- 1 file changed, 104 insertions(+), 146 deletions(-) diff --git a/share/completions/busctl.fish b/share/completions/busctl.fish index ebbba06fc..ea3ef2765 100644 --- a/share/completions/busctl.fish +++ b/share/completions/busctl.fish @@ -1,140 +1,107 @@ # Fish completions for systemd's "busctl" dbus tool +# TODO: +# One issue is that sometimes these will come to a dead-end e.g. when a particular interface has no properties +# Another is that some busnames aren't accesible by the current user +# but this can't be predicted via the user that owns that name, e.g. `org.freedesktop.login1` +# is usually owned by a root-owned process, yet accessible (at least in part) by normal users -# A ton of functions, all working by the same principle -# __fish_busctl_*: Get the available options for * via busctl "list" or "introspect" -# __fish_busctl_has_*: Return 0 when a * is specified (and echo it), else return 1 -# Unfortunately these become a bit slow as they stack because we don't keep state -# i.e. __fish_busctl_methods needs to know the interface, which needs the object, which needs the busname -# To speed this up, we'd need to either keep state or just assume e.g. the first non-option argument to "call" is a busname - -function __fish_busctl_busnames +# A simple wrapper to call busctl with the correct mode and output +function __fish_busctl + # TODO: If there's a "--address" argument we need to pass that + # We also need to pass the _last_ of these (`busctl --user --system` operates on the system bus) set -l mode if __fish_contains_opt user set mode "--user" else set mode "--system" end - command busctl $mode list --no-legend --no-pager ^/dev/null | while read a b; echo $a; end -end - -function __fish_busctl_has_busname - for busname in (__fish_busctl_busnames) - if contains -- $busname (commandline -opc) - echo $busname - return 0 - end - end - return 1 -end - -function __fish_busctl_has_object - for object in (__fish_busctl_objects) - if contains -- $object (commandline -opc) - echo $object - return 0 - end - end - return 1 -end - -function __fish_busctl_has_interface - for interface in (__fish_busctl_interfaces) - if contains -- $interface (commandline -opc) - echo $interface - return 0 - end - end - return 1 -end - -function __fish_busctl_has_member - for member in (__fish_busctl_members $argv) - if contains -- $member (commandline -opc) - echo $member - return 0 - end - end - return 1 -end - -function __fish_busctl_has_method - __fish_busctl_has_member method -end - -function __fish_busctl_has_property - __fish_busctl_has_member property -end - -function __fish_busctl_has_signature - for signature in (__fish_busctl_signature) - if contains -- $signature (commandline -opc) - echo $signature - return 0 - end - end - return 1 -end - -function __fish_busctl_objects - set -l mode - if __fish_contains_opt user - set mode "--user" - else - set mode "--system" - end - set -l busname (__fish_busctl_has_busname) - command busctl $mode tree --list --no-legend --no-pager $busname ^/dev/null | while read a b; echo $a; end -end - -function __fish_busctl_interfaces - set -l mode - if __fish_contains_opt user - set mode "--user" - else - set mode "--system" - end - set -l busname (__fish_busctl_has_busname) - set -l object (__fish_busctl_has_object) - command busctl $mode introspect --list --no-legend --no-pager $busname $object ^/dev/null | while read a b; echo $a; end + command busctl $mode $argv --no-legend --no-pager ^/dev/null end -function __fish_busctl_members - set -l mode - if __fish_contains_opt user - set mode "--user" - else - set mode "--system" +# Only get the arguments to the actual command, skipping all options and arguments to options +function __fish_busctl_get_command_args + set -l skip + set -l skip_next 0 + set -l cmd (commandline -opc) + for token in $cmd + switch $token + # Options that take arguments - when given without "=", the next token is the arg - skip it + case '--address' '--match' '--expect-reply' '--auto-start' '--allow-interactive-authorization' \ + '--timeout' '--augment-creds' '-H' '--host' '-M' '--machine' + set skip_next 1 + continue + # Skip all options themselves + case '-*' + continue + # Command args only start after a command + case 'status' 'monitor' 'capture' 'tree' 'introspect' 'call' 'get-property' 'set-property' + set -e skip + continue + # These take no arguments, so abort completion + case 'list' 'help' + break + case '*' + if test "$skip_next" -eq 1; or set -q skip + set skip_next 0 + continue + end + echo $token + end end - set -l busname (__fish_busctl_has_busname) - set -l object (__fish_busctl_has_object) - set -l interface (__fish_busctl_has_interface) - command busctl $mode introspect --list --no-legend --no-pager $busname $object $interface ^/dev/null | grep "$argv" | while read a b; echo $a; end | sed -e "s/^\.//" +end + +function __fish_busctl_busnames + __fish_busctl list --acquired | string replace -r '\s+.*$' '' + # Describe unique names (":1.32") with their process (e.g. `:1.32\tsteam`) + __fish_busctl list --unique | string replace -r '\s+\S+\s+(\S+)\s+.*$' '\t$1' end -function __fish_busctl_methods - __fish_busctl_members method +function __fish_busctl_objects -a busname + __fish_busctl tree --list $busname | string replace -r '\s+.*$' '' end -function __fish_busctl_properties - __fish_busctl_members property +function __fish_busctl_interfaces -a busname -a object + __fish_busctl introspect --list $busname $object | string replace -r '\s+.*$' '' +end + +function __fish_busctl_members -a type -a busname -a object -a interface + __fish_busctl introspect --list $busname $object $interface \ + | string match -- "* $type *" | string replace -r '.(\S+) .*' '$1' end -function __fish_busctl_signals - __fish_busctl_members signal +function __fish_busctl_signature -a busname -a object -a interface -a member + __fish_busctl introspect --list $busname $object $interface \ + | string match ".$member *" | while read a b c d; echo $c; end end -function __fish_busctl_signature - set -l mode - if __fish_contains_opt user - set mode "--user" - else - set mode "--system" +# This function completes service/busname, object, interface and then whatever the arguments are +# i.e. if argv[1] is "method", complete methods in the fourth place +function __fish_busctl_soi + set -l args (__fish_busctl_get_command_args) + set -l num (count $args) + switch $num + case 0 # We have nothing, need busname + __fish_busctl_busnames + case 1 # We have busname, need object + __fish_busctl_objects $args + case 2 # We have busname and object, need interface + __fish_busctl_interfaces $args + case '*' # We have busname and object and interface, what we need now depends on the command, so we get it as argument + if test $num -ge 4; and set -q argv[2] # Check >= 4 to repeat the last type for get-property + # Signatures have to be handled specially, because they're dependent on the member (method/property) + # that's at the beginning of the line and prefixed with a "." + # I.e. `busctl introspect` will print ".Capacity", but the argument to give to `get-property` is "Capacity" + if test "$argv[2]" = "signature" + __fish_busctl_signature $args + else + __fish_busctl_members $argv[2] $args + end + else if test $num -ge 3; and set -q argv[1] + __fish_busctl_members $argv[1] $args + else + return 1 + end end - set -l busname (__fish_busctl_has_busname) - set -l object (__fish_busctl_has_object) - set -l interface (__fish_busctl_has_interface) - set -l member (__fish_busctl_has_member) - command busctl $mode introspect --list --no-legend --no-pager $busname $object $interface ^/dev/null | grep "^.$member " | while read a b c d; echo $c; end end ### Commands @@ -145,42 +112,33 @@ complete -A -f -c busctl -n "not __fish_seen_subcommand_from $commands" -a "$com ### Arguments to commands # "status" only takes a single service as argument -complete -f -c busctl -n "__fish_seen_subcommand_from status; and not __fish_busctl_has_busname" -a "(__fish_busctl_busnames)" +complete -f -c busctl -n "__fish_seen_subcommand_from status; and not count (__fish_busctl_get_command_args)" -a "(__fish_busctl_busnames)" # These take multiple services -complete -f -c busctl -n "__fish_seen_subcommand_from monitor capture tree" -a "(__fish_busctl_busnames)" - -# These take "service object interface" (and then maybe something else) -set -l service_object_interface_commands introspect call get-property set-property -complete -f -c busctl -n "__fish_seen_subcommand_from $service_object_interface_commands; and not __fish_busctl_has_busname" -a "(__fish_busctl_busnames)" -complete -f -c busctl -n "__fish_seen_subcommand_from $service_object_interface_commands; and __fish_busctl_has_busname; and not __fish_busctl_has_object" -a "(__fish_busctl_objects)" -# Having an object implies having a busname, so we only need to check has_object; and not has_interface -complete -f -c busctl -n "__fish_seen_subcommand_from $service_object_interface_commands; and __fish_busctl_has_object; and not __fish_busctl_has_interface" -a "(__fish_busctl_interfaces)" - -# Call takes service object interface method signature arguments -# We can't complete the arguments (or we'd need to parse the signature) -complete -f -c busctl -n "__fish_seen_subcommand_from call; and __fish_busctl_has_interface; and not __fish_busctl_has_method" -a "(__fish_busctl_methods)" -complete -f -c busctl -n "__fish_seen_subcommand_from call; and __fish_busctl_has_method; and not __fish_busctl_has_signature" -a "(__fish_busctl_signature)" - -complete -f -c busctl -n "__fish_seen_subcommand_from get-property; and __fish_busctl_has_interface" -a "(__fish_busctl_properties)" -complete -f -c busctl -n "__fish_seen_subcommand_from set-property; and __fish_busctl_has_interface; and not __fish_busctl_has_property" -a "(__fish_busctl_properties)" -complete -f -c busctl -n "__fish_seen_subcommand_from set-property; and __fish_busctl_has_property; and not __fish_busctl_has_signature" -a "(__fish_busctl_signature)" +complete -x -c busctl -n "__fish_seen_subcommand_from monitor capture tree" -a "(__fish_busctl_busnames)" +# Read the busctl_soi calls as "Complete service, then object, then interface and then the arguments +# e.g. `call` takes service object interface method signature arguments +# We can't complete the arguments (without parsing the signature, which can look like "a{sv}" for an array of string-to-variant dictionaries) +complete -x -c busctl -n "__fish_seen_subcommand_from call" -a "(__fish_busctl_soi method signature)" +complete -x -c busctl -n "__fish_seen_subcommand_from get-property" -a "(__fish_busctl_soi property)" +complete -x -c busctl -n "__fish_seen_subcommand_from set-property" -a "(__fish_busctl_soi property signature)" +complete -x -c busctl -n "__fish_seen_subcommand_from introspect" -a "(__fish_busctl_soi)" # Flags # These are incomplete as I have no idea how to complete --address= or --match= -complete -f -c busctl -n "__fish_seen_subcommand_from call get-property" -l quiet +complete -f -c busctl -n "__fish_seen_subcommand_from call" -l quiet -d 'Suppress message payload display' complete -f -c busctl -n "__fish_seen_subcommand_from call get-property" -l verbose -complete -f -c busctl -n "__fish_seen_subcommand_from tree introspect" -l list -complete -f -c busctl -n "__fish_seen_subcommand_from call" -l expect-reply= -a "yes no" -complete -f -c busctl -n "__fish_seen_subcommand_from call" -l auto-start= -a "yes no" -complete -f -c busctl -n "__fish_seen_subcommand_from call" -l allow-interactive-authorization= -a "yes no" -complete -f -c busctl -n "__fish_seen_subcommand_from call" -l timeout= -a "(seq 0 100)" -complete -f -c busctl -n "__fish_seen_subcommand_from list" -l unique -complete -f -c busctl -n "__fish_seen_subcommand_from list" -l acquired -complete -f -c busctl -n "__fish_seen_subcommand_from list" -l activatable -complete -f -c busctl -n "__fish_seen_subcommand_from list" -l show-machine -complete -f -c busctl -n "__fish_seen_subcommand_from list status" -l augment-creds= -a "yes no" +complete -f -c busctl -n "__fish_seen_subcommand_from tree" -l list -d 'Show a flat list instead of a tree' +complete -x -c busctl -n "__fish_seen_subcommand_from call" -l expect-reply -a "yes no" +complete -x -c busctl -n "__fish_seen_subcommand_from call" -l auto-start -a "yes no" -d 'Activate the peer if necessary' +complete -x -c busctl -n "__fish_seen_subcommand_from call" -l allow-interactive-authorization -a "yes no" +complete -x -c busctl -n "__fish_seen_subcommand_from call" -l timeout -a "(seq 0 100)" +complete -f -c busctl -n "__fish_seen_subcommand_from list" -l unique -d 'Only show unique (:X.Y) names' +complete -f -c busctl -n "__fish_seen_subcommand_from list" -l acquired -d 'Only show well-known names' +complete -f -c busctl -n "__fish_seen_subcommand_from list" -l activatable -d 'Only show peers that have not been activated yet but can be' +complete -f -c busctl -n "__fish_seen_subcommand_from list" -l show-machine -d 'Show the machine the peers belong to' +complete -x -c busctl -n "__fish_seen_subcommand_from list status" -l augment-creds -a "yes no" complete -f -c busctl -l user complete -f -c busctl -l system complete -f -c busctl -s H -l host= -a "(__fish_print_hostnames)"