Access Recent Files From the Command Line

(Day 11 of 30 Days of Blogging)

If you don’t already use Z you absolutely need to check it out. Z is a CLI tool that cd’s to a recently used directory, so that typing z foo will cd /my/deeply/nested/project/foobar7, as long as you’ve cd’ed into foobar7 sometime in the past.

Z is great for jumping to recent directories… but is there an equivalent for opening recently used files? There’s fasd, but the way it works seems a bit too magical to me. It’s also limited in that files are often opened from other programs, instead of directly from the shell.

Every desktop environment since Windows 95 already has a “Recent Files” list for their file manager. On Linux, this is often handled by GTK. Conveniently, GTK provides a simple API for accessing the recent file list, and we can use it to write a small script that reads or writes to that list:

#!/usr/bin/env python3
#
# file-history: Read or write to GTK's recent file list.

import gi
import os
import re
import sys

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib

manager = Gtk.RecentManager.get_default()

if len(sys.argv) > 1:
    # Add the given files to the recent file list
    for file in sys.argv[1:]:
        uri = Gio.File.new_for_path(file).get_uri()
        manager.add_item(uri)
    GLib.idle_add(Gtk.main_quit)
    Gtk.main()

else:
    # Print the recent file list, starting with most recently used
    home = re.compile("^"+os.environ["HOME"]+"/")
    for item in sorted(manager.get_items(), key=lambda x: x.get_modified(), reverse=True):
        if item.exists():
            print(home.sub("~/", item.get_uri_display()))

Running file-history with arguments will add those files to the recent file list. Running file-history with no arguments will print the recent file list to stdout. Now we can go to town combining this with other scripts.

Reading History

Let’s write a key binding in zsh that displays the recent file history in an fzf menu.

_bind_recent () {
    local res=`file-history | fzf --reverse --height 40% --prompt "Hist> " | sed "s|^~/|$HOME/|"`
    if [ "$res" ]; then
        LBUFFER="$LBUFFER ${(q)res} "
    fi
    zle reset-prompt
}
zle -N _bind_recent
bindkey '^j' _bind_recent

Now pressing ctrl-J will allow you to pick a recent file and add it to the end of the command line. I use this all the time for processing Firefox downloads. After downloading a zip or whatever, I just go to a terminal and type unzip <ctrl-J>. Since the list is sorted, the recently downloaded zip is the first item in the list and I just press enter to expand out to unzip ~/Downloads/LongFilename.zip.

Another possibility is integrating with i3 or another window manager, so that pressing alt-J shows a recent file menu:

bindsym $mod+j exec --no-startup-id file-history | rofi -dmenu -p Hist |
    sed "s|^~/|$HOME/|" | xargs -d '\n' xdg-open

Writing History

The other half of this is populating the recent file history with useful data. Graphical GTK apps will already do this. Ideally we want every file we “open” from the CLI to be added to the history too.

Some possibilities:

  • For users of CLI file managers like LF or Ranger, call file-history <selected-file> as part of the file open handler.
  • Write a shell alias that wraps xdg-open and calls file-history. I added this to my general purpose file opener.
  • Write a vim autocmd to sends all opened files to file-history. Personally I don’t do this since vim already has an internal file history, but it might be useful for some.

So far this setup has been working pretty nicely for me! Not quite as life-changing as Z, but I still use it on a daily basis.