Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions ros2cli/completion/ros2-fzf-completion.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
if [ -z "$BASH_VERSION" ]; then
return 0 2>/dev/null || exit 0
fi

ros2() {
local list_cmd=""
local fzf_prompt=""
local preview_cmd=""

case "$*" in
"topic echo" | "topic info" | "topic hz" | \
"topic bw" | "topic pub" | "topic type" | \
"topic delay")
Comment on lines +10 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this matches "$*" (all args joined by space) against exact strings. i think there are some problems here,

  • ros2 topic echo (double space) does not.
  • ros2 --log-level debug topic echo does not match.
  • any future subcommands (e.g., ros2 topic inspect) won't be caught without updating these scripts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it is fragile, will remove the pattern matching.

list_cmd="ros2 topic list"
fzf_prompt="topic> "
preview_cmd="ros2 topic info {} 2>/dev/null"
;;
"service call" | "service type" | "service find")
list_cmd="ros2 service list"
fzf_prompt="service> "
preview_cmd="ros2 service type {} 2>/dev/null"
;;
"node info")
list_cmd="ros2 node list"
fzf_prompt="node> "
preview_cmd="ros2 node info {} 2>/dev/null"
;;
"param get" | "param set" | "param list" | \
"param describe" | "param delete" | \
"param dump" | "param load")
list_cmd="ros2 node list"
fzf_prompt="node> "
preview_cmd="ros2 node info {} 2>/dev/null"
;;
*)
command ros2 "$@"
return $?
;;
esac

if ! command -v fzf &> /dev/null; then
echo "fzf is not installed. Install with: sudo apt install fzf" >&2
command ros2 "$@"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it replaces the ros2 binary with a shell function for the entire session. that said, this will intercept all ros2 calls, including from other scripts. it changes behavior for every user who sources this file, not just in interactive completion contexts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes shadowing ros2() will indeed start intercepting everything

return $?
fi

local selected
selected=$(
command $list_cmd 2>/dev/null | fzf \
--height 40% --reverse --border \
--prompt "$fzf_prompt" \
--preview "$preview_cmd" \
--preview-window "right:50%:wrap"
)

if [ -z "$selected" ]; then
command ros2 "$@"
return $?
fi

printf '\e[A\r\e[2K'

local prefilled="ros2 $* ${selected} "
local full_cmd
read -e -p "${PS1@P}" -i "$prefilled" full_cmd

if [ -z "$full_cmd" ]; then
return 0
fi

history -s -- "$full_cmd"
eval -- "$full_cmd"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a security concern? the user is presented with a pre-filled prompt they can edit freely, and the result is passed to eval?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, using eval will introduce a security risk, allowing execution of additional commands, will not use it.

}
87 changes: 87 additions & 0 deletions ros2cli/completion/ros2-zle-completion.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
if [ -z "$ZSH_VERSION" ]; then
return 0
fi

__ros2_zle_last_offered=""

_ros2_zle_accept_line() {
local trimmed="${BUFFER%"${BUFFER##*[![:space:]]}"}"
local selected=""
local list_cmd=""
local fzf_prompt=""
local preview_cmd=""

case "$trimmed" in
"ros2 topic echo" | \
"ros2 topic info" | \
"ros2 topic hz" | \
"ros2 topic bw" | \
"ros2 topic pub" | \
"ros2 topic type" | \
"ros2 topic delay")
list_cmd="ros2 topic list"
fzf_prompt="topic> "
preview_cmd="ros2 topic info {}"
;;
"ros2 service call" | \
"ros2 service type" | \
"ros2 service find")
list_cmd="ros2 service list"
fzf_prompt="service> "
preview_cmd="ros2 service type {}"
;;
"ros2 node info")
list_cmd="ros2 node list"
fzf_prompt="node> "
preview_cmd="ros2 node info {}"
;;
"ros2 param get" | \
"ros2 param set" | \
"ros2 param list" | \
"ros2 param describe" | \
"ros2 param delete" | \
"ros2 param dump" | \
"ros2 param load")
list_cmd="ros2 node list"
fzf_prompt="node> "
preview_cmd="ros2 node info {}"
;;
*)
__ros2_zle_last_offered=""
zle .accept-line
return
;;
esac

if [[ "$trimmed" == "$__ros2_zle_last_offered" ]]; then
__ros2_zle_last_offered=""
zle .accept-line
return
fi

if ! command -v fzf &> /dev/null; then
zle -M "fzf is not installed. Install with: sudo apt install fzf"
zle .accept-line
return
fi

selected=$(
${=list_cmd} 2>/dev/null | fzf \
--height 40% --reverse --border \
--prompt "$fzf_prompt" \
--preview "$preview_cmd 2>/dev/null" \
--preview-window "right:50%:wrap"
)

if [[ -n "$selected" ]]; then
__ros2_zle_last_offered=""
BUFFER="${trimmed} ${selected} "
CURSOR=${#BUFFER}
else
__ros2_zle_last_offered="$trimmed"
fi

zle reset-prompt
}

zle -N accept-line _ros2_zle_accept_line
4 changes: 3 additions & 1 deletion ros2cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
]),
('share/ros2cli/environment', [
'completion/ros2-argcomplete.bash',
'completion/ros2-argcomplete.zsh'
'completion/ros2-argcomplete.zsh',
'completion/ros2-fzf-completion.bash',
'completion/ros2-zle-completion.zsh',
]),
],
package_data={'': ['py.typed']},
Expand Down