Bloerg
       

There haven’t been in any posts for a while and I sincerely have to apologize that this will continue for at least two months. This will also affect the comment section, which I will deactivate to reduce the amount of moderation.

I knew, Google was scanning my mail to present personalized ads to me but I never saw them though because of AdblockPlus. I got back to reality, when Gmail offered me to track the delivery of an Amazon order …

2014-04-29/amazon-gmail.png

I use git-annex to distribute and synchronize fairly large and mostly static files across different machines. However, being based on Git makes it pretty uncomfortable to use from the command line. So, why not integrating it into our favorite command line file manager, ranger? Because I struggled a bit with ranger’s internals, I will outline how I wrote the plugin.

Initialization

First of all, we need a place for our plugin. By default ranger imports every Python module from $XDG_CONFIG_HOME/ranger/plugins in lexicographic order. If $XDG_CONFIG_HOME is not set, ~/.config is used as the default alternative.

To use the plugin from within ranger, you need to provide code that hooks into one of the two methods provided by the ranger API. hook_init is called before the UI is ready, which means you can dump output on stdout, whereas the UI can be used in hook_ready. The suggested way is not to replace the original function but chaining up like this:

import ranger.api

old_hook_init = ranger.api.hook_init

def hook_init(fm):
    # setup
    return old_hook_init(fm)

ranger.api.hook_init = hook_init

Adding new commands

In the introductory post, I briefly explained how to write custom commands which you add to your commands.py file: You simply subclass from ranger.api.commands.Command and write code in the execute method. However, commands defined in a plugin are not automatically added to the global command list. For this you need to extend the commands dictionary of the file manager instance, i.e.

def hook_init(fm):
    fm.commands.commands['annex_copy'] = copy

When using the annex_copy command, tab-completion should cycle through all available remotes. This is done by returning an iterable in the tab method. In the execute method you can access arguments, by calls to the arg method:

class copy(ranger.api.commands.Command):
    def tab(self):
        return ('annex_copy {}'.format(r) for r in remotes)

    def execute(self):
        remote = self.arg(1)

Asynchronous calls

The git-annex plugin works on the current or currently selected list of files, which you can get via fm.env.get_selection(). To avoid blocking the UI while fetching large files, I use the CommandLoader to run the git annex commands in the background (thanks @hut). The loader emits a signal when the action is finished to which we subscribe in order to refresh the directory content:

class copy(ranger.api.commands.Command):
    def execute(self):
        remote = self.arg(1)

        def reload_dir():
            self.fm.thisdir.unload()
            self.fm.thisdir.load_content()

        for path in (str(p) for p in self.fm.env.get_selection()):
            fname = os.path.basename(path)
            cmd = ['git', 'annex', 'copy', '-t', remote, fname]
            loader = CommandLoader(cmd, "annex_copy to remote")
            loader.signal_bind('after', reload_dir)
            fm.loader.add(loader)

Long running actions can be cancelled by the user, so if you need to clean up you should add that extra code to the cancel method.

Wrap up

That’s it, a plugin that registers new commands for interacting with git-annex an asynchronous way. Bug reports and pull requests are welcome.

Matthias Beyer published two tips which according to him “are very critical when talking about speed when using vim”. I beg to differ and want to give my reasons.

Yes, leader mappings are important but not to gain speed but to increase the available mapping space by one additional dimension. For example, I map :Make<CR> to F6, whereas he maps it to <Leader>m. Speed gain in my opinion: zero. Moreover, there is some truth in it that “leader maps are pretty lame”.

Matthias’ next tip was: “don’t use the arrow keys!”. I agree but replacing them by using hjkl exclusively won’t cut it. You gain a little bit by staying in the home row but at the end of the day you still drag your cursor around one character/line after another. By far, my main navigation speedups come from moving by words with b, e and w, by paragraphs with { and }, by half-screens with Ctrl+u and Ctrl+d. However, navigation only makes up a tiny fraction of a typical Vim day: I gain a lot more by using Vim’s grammar to its full extent.

So here is my Vim tip of the day: learn it as good as you can.