I want to create a custom command for ranger, however, I have no Idea about how to get an user inputs since it freezes when I use the traditional way input()
.
I already spent hours googling it but nothing worthy so far.
class user_input(Command):
def execute(self):
file = self.fm.ui.console.input('user input...') # '.input()' doesn't exist
self.fm.notify(file)
I'd really appreciate the help.
Use self.arg(1)
.
The ranger file manager provides a command system wherein users can issue a
command by typing :
followed by the command's name along with any arguments to
that command. A simple example is the search command, which you can use by
typing :search [your search term here]
.
Ranger is extensible with the Python programming language. One can write custom keybindings, commands, and even plugins for ranger in Python. I explain the process further in an aside at the end of this post.
Our focus here is that one can define a custom command in the form of a Python
class. This class must inherit from ranger's Command
class and supply an entry
point method called execute
. Ranger will call this method when the command is
invoked. The name of the derived class is the name that you type in ranger's
console to invoke the command.
Input is supplied to a command in the form of arguments. To write a command
that takes input, simply use the arguments passed to that command. Let's
consider a minimal working example. You can append the following code snippet to
the commands.py
file located in the ~/.config/ranger
directory.
from ranger.api.commands import Command
class myCommand(Command):
def execute(self):
filename = self.arg(1) # <--- This is your answer.
self.fm.notify(filename) # in lieu of something more interesting
Understand that input is passed into a command manually by the user as part of
that command's invocation. To demonstrate, in ranger you would type :myCommand flowers.jpg
and then press enter. Here myCommand
is the command and
flowers.jpg
is its argument. It is all done in one go. Hence, there is no need
to prompt the user for input inside your execute
method because ranger already
takes care of input for you from the outside. All you have to know is how to
access your command's arguments.
The methods and attributes of the Command
class are described in ranger's
source code file commands.py. Among them are ones pertaining to argument
handling. Use self.arg(n)
and friends to access a command's input arguments.
self.line
The whole line that was written in the console.self.args
A list of all (space-separated) arguments to the command.self.quantifier
If this command was mapped to the key "x" and the user pressed 6x, self.quantifier would be 6.self.arg(n)
The n-th argument, or an empty string if it doesn't exist.self.rest(n)
The n-th argument plus everything that followed. For example, if the command was "search foo bar a b c", rest(2) would be "bar a b c".self.start(n)
Anything before the n-th argument. For example, if the command was "search foo bar a b c", start(2) will be "search foo".
Consider looking at the implementation of ranger's echo
command in the file
linked just above.
Based on your question, I presume that you would prefer something more
interactive analogous to Python's built-in input()
function. If that is so,
then consider the following alternative.
One limitation of the above solution is that the user's input is tethered to the
entry point of the command. It would be nice if there was a distinct
user_input
function that could be called with a custom prompt multiple times
throughout a body of code independent of a command's invocation. As far as I
know, ranger does not provide anything like this, but we can write it ourselves.
Therefore, let's consider a more sophisticated solution.
The ranger file manager uses the curses programming library to handle input
and much else. You can write your own user_input
function like this. I have
also included an example of how to use the function.
from ranger.api.commands import Command
import curses
class greet(Command):
def execute(self):
greeting = "Hello, {person}!"
name = user_input("Please enter your name. ")
# You can call user_input as many times as you'd like.
name = user_input("What? ")
self.fm.notify(greeting.format(person=name))
def user_input(prompt):
"""
Prompt the user for input. For use with the ranger file manager.
:param str prompt: The prompt to the user
:return: The user's input
:rtype: str
"""
# Tested with
# ranger version 1.9.3
# python version 3.10.12
# start a curses window
window = curses.initscr()
# get the coordinates of the farthest row and column, subtracting one
rows, cols = [coord - 1 for coord in window.getmaxyx()]
# add a prompt in ranger's status bar
window.addstr(rows, 0, prompt)
# enable the echoing of entered characters
curses.echo()
# get and display user input after the prompt
user_input_bytes = window.getstr(rows, len(prompt), cols)
# disable the echoing of entered characters
curses.noecho()
# clear ranger's status bar for the next use
window.addstr(rows, 0, " " * cols)
# end the window
curses.endwin()
# return the user input as a string
return user_input_bytes.decode(encoding="utf-8")
Once you have saved the above code in
~/.config/ranger/plugins/plugin_greeter.py
, you can run it by opening ranger
at the command line and typing :greet
followed by the enter key. You will
then be prompted for a string in the bottom left-hand corner of the screen.
It is possible to achieve more intricate functionality. Instead of prompting
the user for a string, imagine that you want to implement your own custom
keybindings, particularly something that you couldn't otherwise do with ranger's
mapping facilities. You can use window.getch()
for that. I recently did this
in my own ranger plugin.
Ranger provides a plugin system. A plugin is typically a single file
conventionally called plugin_[name].py
. They are stored in the
~/.config/ranger/plugins
directory. Ranger's GitHub repository contains
several examples. To learn more, read the PLUGINS section of ranger's man page.
The --debug
flag is also helpful when writing plugins. Smaller, standalone
commands can be put in ~/.config/ranger/commands.py
and mapped to in
~/.config/ranger/rc.conf
as described here. Create a plugin when you
have a longer command or several interrelated commands, keybindings, etc. In
general, writing a plugin is a good way to isolate your own code from ranger's
code, since commands.py
is already populated with built-ins.