Skip to content
Julius Kibunjia's Blog
Go back

Writing a Command for Ranger

4 min read

Table of Contents

Open Table of Contents

Background

ranger is a terminal-based file manager that is written in Python. This is interesting for a couple of reasons. One is that it nicely integrates with the rest of the terminal. Another is that it can be extended fairly easily. It also supports vim-like key bindings. That last one may not seem like a merit but the interface ends up being really intuitive.

As part of my ongoing effort to be more organized I decided it’s about time I sort my wallpaper collection. At first glance there’s a couple of ways to do this. But, by far the simplest is using sub-directories for each distinct category. The problem is, even using ranger with its simple and intuitive interface ends up still being a lot of busy work. We all hate busy work but as programmers we can potentially do something about it (relevant xkcd).

Organizing Images Into Sub-directories

The best way I could think of to save myself some time is to somehow bind a key (like Space) to an arbitrary sequence of commands. Performing an action which in this case would be moving whichever file is selected to some sub-directory would just involve hitting a key. All that was left was actually implementing it.

Ranger is extended simply by adding a subclass of the Command class to a file called commands.py in the ranger config directory. A sample commands.py can be obtained by running

ranger --copy-config=commands

The two methods of the Command class you should be concerned with are execute and tab. The former should contain the code you want to run when your command is executed. The latter can be used to provide completion options when a user hits the tab key. For our purposes implementing tab-completion isn’t really necessary.

From what we’ve discussed so far, our new command needs to:

The Code


class bspace(Command):
    """:bspace [command]

    Temporarily bind a command to the space bar. {file} is
    replaced with the currently selected item and {cwd} with
    the current working directory. Run without arguments to
    restore the original binding.
    """

    def execute(self):
        # default binding for <Space>
        orig_binding = "mark_files toggle=True"
        if len(self.args) == 1:
            new_binding = orig_binding
        else:
            resolved_args = []
            for arg in self.args[1:]:
                # replace {file} with current file in command
                if arg == '{file}':
                    resolved_args.append('%p')
                # replace {cwd} with current working directory
                elif arg == '{cwd}':
                    resolved_args.append('%d')
                else:
                    resolved_args.append(arg)
            new_binding = ' '.join(resolved_args)
        self.fm.execute_console(f"map <Space> {new_binding}")

To use the command simply enter a command that you want to temporarily bind to the Space key after :bspace in the ranger console. A couple of useful examples are:

Here’s a gif of our command in action

Further Reading



Previous Post
Colouring arbitrary shell output
Next Post
Data flow on the shell