From d2c7ebba9768de5e896a67d7bcac12532c88f568 Mon Sep 17 00:00:00 2001 From: ArkhamKnight25 Date: Tue, 17 Feb 2026 19:36:59 +0900 Subject: [PATCH] feat: fzf inline editing completion Signed-off-by: ArkhamKnight25 --- ros2cli/completion/ros2-fzf-completion.bash | 73 +++++++++++++++++ ros2cli/completion/ros2-zle-completion.zsh | 87 +++++++++++++++++++++ ros2cli/setup.py | 4 +- 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 ros2cli/completion/ros2-fzf-completion.bash create mode 100644 ros2cli/completion/ros2-zle-completion.zsh diff --git a/ros2cli/completion/ros2-fzf-completion.bash b/ros2cli/completion/ros2-fzf-completion.bash new file mode 100644 index 000000000..94d337f87 --- /dev/null +++ b/ros2cli/completion/ros2-fzf-completion.bash @@ -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") + 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 "$@" + 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" +} diff --git a/ros2cli/completion/ros2-zle-completion.zsh b/ros2cli/completion/ros2-zle-completion.zsh new file mode 100644 index 000000000..2a5325911 --- /dev/null +++ b/ros2cli/completion/ros2-zle-completion.zsh @@ -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 diff --git a/ros2cli/setup.py b/ros2cli/setup.py index 7534ffe7d..31c43f939 100644 --- a/ros2cli/setup.py +++ b/ros2cli/setup.py @@ -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']},