-
Notifications
You must be signed in to change notification settings - Fork 198
Add fzf-based interactive selection to ros2cli commands #1151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rolling
Are you sure you want to change the base?
Conversation
Signed-off-by: Tony Najjar <tony.najjar@dexory.com>
Signed-off-by: Tony Najjar <tony.najjar@dexory.com>
Signed-off-by: Tony Najjar <tony.najjar@dexory.com>
|
This PR still needs some testing and refinement but before I iterate further I'd like to know if you would merge this idea in. Thanks |
fujitatomoya
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tonynajjar generally i like this enhancement, thanks 🚀
there are several comments that i think we need to address but overall i think this is gonna be better user-experience for ROS 2 users and developers.
we already have type completion via shell command prompt but tab completion requires us to know the prefix and type sequentially from the beginning, while fzf lets us match any part of the name in any order (e.g., typing "wrist depth" finds /arm/wrist_camera/depth/image_raw). fzf also provides visual browsing of all available options with real-time filtering, making it ideal for exploring unfamiliar systems or when you only remember fragments of a name.
before fixing up my comments, let's hear out more feedback on this 👍
| def interactive_select( | ||
| items: List[str], | ||
| prompt: str = 'Select an item:' | ||
| ) -> Optional[str]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it miss tty detection? no check for whether stdin/stdout is a tty? i think this will cause scripts and CI/CD pipelines to hang, if it meets this case. related to this issue, fzf reads keyboard input from /dev/tty, not stdin. however, when ros2cli commands are piped or redirected, fzf may not be able to access the tty properly?
the followings are pseudo code,
# Check if we're in an interactive terminal
if not sys.stdin.isatty() or not sys.stdout.isatty():
print('Error: Interactive selection requires a TTY terminal.',
file=sys.stderr)
return Noneand
try:
# fzf needs direct access to /dev/tty for keyboard input
tty = open('/dev/tty', 'r')
process = subprocess.Popen(
['fzf', '--prompt', prompt + ' ', '--height', '40%', '--reverse'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=tty, # fzf uses stderr for display
text=True
)
# ...
finally:
tty.close()| ) | ||
|
|
||
| # Send items to fzf | ||
| stdout, _ = process.communicate(input='\n'.join(items)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not need to have any signal control here when opening the child process? If the user presses Ctrl+C, this may leave the terminal in a bad state or not clean up properly... maybe we can add try/except statement to catch the KeyboardInterrupt to call process.terminate()?
import signal
try:
stdout, _ = process.communicate(input='\n'.join(items))
except KeyboardInterrupt:
process.terminate()
process.wait()
return NoneThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
besides that, i would add the timeout just in case if fzf hangs for some reason, the entire command hangs indefinitely.
| try: | ||
| # Check if fzf is available | ||
| result = subprocess.run( | ||
| ['fzf', '--version'], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=1 | ||
| ) | ||
| if result.returncode != 0: | ||
| raise FileNotFoundError() | ||
| except (FileNotFoundError, subprocess.TimeoutExpired): | ||
| print( | ||
| 'Error: fzf is not installed but is a dependency for this package. You can install it with rosdep', | ||
| file=sys.stderr | ||
| ) | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe this is redundant. i would do the following instead to check fzf availability.
import shutil
if shutil.which('fzf') is None:
print('Error: fzf is not installed...', file=sys.stderr)
return None| except Exception as e: | ||
| print(f'Error during interactive selection: {e}', file=sys.stderr) | ||
| return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching all exceptions hides bugs and makes debugging harder.
| except Exception as e: | |
| print(f'Error during interactive selection: {e}', file=sys.stderr) | |
| return None | |
| except (OSError, subprocess.SubprocessError) as e: | |
| print(f'Error during interactive selection: {e}', file=sys.stderr) | |
| return None |
|
Thanks for the feedback @fujitatomoya . Like you said I will start working on your comments once I get another opinion on the general idea, to make sure this can ultimately get merged in |
|
Maybe @ahcorde you can give your general opinion on the feature and I can continue to iterate with @fujitatomoya? Thanks! |
Description
This PR adds optional fuzzy search functionality to ros2cli commands using fzf, inspired by the ros2-aliases project. When arguments are omitted from commands, an interactive fzf selector is launched instead of throwing an error.
Commands enhanced:
ros2 topic echo- Select topic interactivelyros2 topic hz- Select topic interactivelyros2 topic info- Select topic interactivelyros2 topic bw- Select topic interactivelyros2 topic pub- Select topic interactivelyros2 node info- Select node interactivelyros2 service call- Select service interactivelyros2 param get- Cascading selection (node → parameter)ros2 interface show- Select interface interactivelyImplementation:
interactive_select()helper function inros2cli/helpers.pynargs='?') in affected verbsros2cli/package.xmlIs this user-facing behavior change?
Yes. Users can now omit arguments and use fuzzy search instead:
Before:
After:
If fzf is not installed, users get a clear error message directing them to install via rosdep.
Did you use Generative AI?
Yes, GitHub Copilot was used
Additional Information