Ros2cli, meet fzf

ros2cli Interactive Selection: Fuzzy Finding

ros2cli just got a UX upgrade: fuzzy finding!

ros2cli_fzf

Tab completion is nice, but it still requires you to remember how the name starts. Now you can just type any part you remember and see everything that matches. Think “search” not “autocomplete”.

Tab Completion vs. Fuzzy Finding

Tab completion:


$ ros2 topic echo /rob<TAB>

# Shows: /robot/

$ ros2 topic echo /robot/<TAB><TAB><TAB>...

# Cycles through: base_controller, cmd_vel, diagnostics, joint_states...

# Was it under /robot? Or /robot/sensors? Or was it /sensors/robot?

# Start over: Ctrl+C

Fuzzy finding (new):


$ ros2 topic echo

# Type: "lidar"

# Instantly see ALL topics with "lidar" anywhere in the name:

/robot/sensors/lidar/scan

/front_lidar/points

/safety/lidar_monitor

# Pick with arrows, done!

What Works Now?

  • ros2 topic echo / info / hz / bw / type - Find topics by any part of their name

  • ros2 node info - Browse all nodes, filter as you type

  • ros2 param get - Pick node, then browse its parameters

  • ros2 run - Find packages/executables without remembering exact names

There are plenty more opportunities where we could integrate fzf, not only in more verbs of ros2cli (e.g. ros2 service) but also in other tools in the ROS ecosystem (e.g. colcon).

I’d love to to see this practice propagate but for this I need the help of the community!

Links

  • PR: #1151 (currently only available on rolling)

  • Powered by: fzf

19 Likes

Wow, this is great!

I’m confused by the error message about missing fzf mentioned in the PR. So is fzf only a soft dependency? Or is this just a transitional warning for users who would update a source checkout without running rosdep?

And what about backports? Are they possible? Will you go for them?

This is great! Would love to see that integrated into ros2 cli! I have been using a custom fzf extension over the last couple of years that has been incredibly useful, but also has its limitations

Maybe that’s helpful for the impatient that don’t want to build ros2cli from source in their env or people not using Rolling right now :slight_smile:

I am especially interested in the param and run extensions of your PR, as my quick solution doesn’t cover that.

Is everything needed for fzf completion included in the main package? So if I want to add that support e.g. for the ros2controlcli, I can update the verbs directly as in Add fzf-based interactive selection to ros2cli commands by tonynajjar · Pull Request #1151 · ros2/ros2cli · GitHub? I would probably have to provide my own list generator as ros2topic.api.get_topic_names in that example, right?

fzf is a dependency in the package.xml so should be handled by rosdep. But since fzf is internally called via subprocess and is not a python import, if it’s not installed, it won’t fail with a clear ModuleNotFoundError so I added a an extra check in the code that will check if it’s installed. If it’s not it will print out a clearer error message.

Regading backports, rolling and kilted have diverged quite a bit - I think for a clean backport of this feature we would need to backport about 4 other commits before this one (and that’s if they are not API-breaking, which I think some are)… so quite a hassle

1 Like

Is everything needed for fzf completion included in the main package? So if I want to add that support e.g. for the ros2controlcli, I can update the verbs directly as in Add fzf-based interactive selection to ros2cli commands by tonynajjar · Pull Request #1151 · ros2/ros2cli · GitHub? I would probably have to provide my own list generator as ros2topic.api.get_topic_names in that example, right?

That’s right! ros2controlcli already depends on ros2cli so fzf should be installed. Then you need to:
1- Update your verb to make the positional argument optional
2- Provide your own list generator function
3- Make use of the interactive_select function with that list you generated

pseudo-code example:

def get_loaded_controller_names(
    node,
    controller_manager: str,
    valid_states:
) -> list[str]:
    """Get list of loaded controller names filtered by state."""
    controllers = list_controllers(node, controller_manager).controller
    return [c.name for c in controllers if c.state in valid_states]
from ros2cli.helpers import interactive_select

# In add_arguments:
arg = parser.add_argument(
    "controller_name", 
    nargs="?", 
    default=None,
    help="Controller name (optional, interactive if not provided)"
)

# In main:
if args.controller_name is None:
    controller_names = get_loaded_controller_names(node, args.controller_manager)
    if not controller_names:
        return 'No controllers available.'
    selected = interactive_select(controller_names, prompt='Select controller:')
    if selected is None:
        return None  # User cancelled
    args.controller_name = selected