Zsh: univ_open: A Universal Directory/File Opener (no more bashrun)

UPDATE July 1, 2010: univ_open() has been updated here.

Like my previous post, the goal is to use a single alias to open up any kind of file or directory intelligently. I’ve decided to write a new post because of various improvements over the old code. For one, I’ve implemented everything in Zsh — now everything is much faster, especially for older systems. So now the components are: (1) a custom zsh function called univ_open and (2) some custom options in ~/.zshrc to complement univ_open.

Here is the new univ_open:

 1 # Author: Shinobu (https://zuttobenkyou.wordpress.com)
 2 # Date: December 2009
 3 # License: PUBLIC DOMAIN
 4 # Program Name: univ_open
 5 # Description: open up directories and files intelligently
 6 
 7 univ_open() {
 8     ls="ls -Ahs --color=auto"
 9     if [[ -z $@ ]]; then
10         # if we do not provide any arguments, go to the home directory
11         cd && ${=ls}
12     elif [[ ! -e $@ ]]; then
13         # go to the nearest valid parent directory if file does not exist; we
14         # use a while loop to find the closest valid directory, just in case
15         # the user gave a butchered-up path
16         d=$@:h
17         while [[ ! -d $d ]]
18         do
19             d=$d:h
20         done
21         cd $d && ${=ls}
22     elif [[ -d $@ ]]; then
23         cd $@ && ${=ls}
24     else
25         case $@:e:l in
26             (doc|odf|odt|rtf)
27                 soffice -writer $@ &>/dev/null & disown
28             ;;
29             (htm|html)
30                 firefox $@ &>/dev/null & disown
31             ;;
32             (pdf|ps)
33                 evince $@ &>/dev/null & disown
34             ;;
35             (bmp|gif|jpg|jpeg|png|svg|tiff)
36                 eog $@ &>/dev/null & disown
37             ;;
38             (psd|xcf)
39                 gimp $@ &>/dev/null & disown
40             ;;
41             (aac|flac|mp3|ogg|wav|wma)
42                 smplayer $@ &>/dev/null & disown
43             ;;
44             (mid|midi)
45                 timidity $@
46             ;;
47             (avi|flv|ogv|mkv|mov|mp4|mpg|mpeg|wmv)
48                 smplayer $@ &>/dev/null & disown
49             ;;
50             *)
51             vim $@
52             ;;
53         esac
54     fi
55 }

The basic operation of this function remains the same: if no arguments given, go to the home directory (this is cd‘s default behavior); if path is invalid, go to the nearest valid parent directory; if path is a valid directory, cd to it; otherwise, it must be a valid file, so open it up with a program based on the file’s extension, and use vim as the default program for unrecognized extensions (or files with no extensions). Again, filename extensions are lowercased to ensure that we are case-insensitive.

Some improvements over the old code: (1) univ_open now correctly handles messed-up, invalid directories by trying to find the closest valid parent directory — this is achieved with a simple while loop, and zsh’s neat little “:h” method to extract the parent directory (same functionality as the UNIX dirname utility); if there is not a single valid directory in the given path, then it will simply cd into the current directory (i.e., nothing happens); (2) the file extensions are handled much better depending on filetype — for commands that are GUI-based, we redirect its STDOUT and STDERR to /dev/null to silence any error messages, immediately background it with the “&” operator, and finally disown it so that we can continue other work from the same terminal where we used our alias from. Terminal-bound commands, such as timidity and vim, are left as-is.

The neat thing about this new version of univ_open is that this makes bashrun for me obsolete! I can do pretty much everything from the terminal as it is.

And now, the pertinent options in ~/.zshrc to complement our use of univ_open:

fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload -U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func
zmodload zsh/complist
setopt auto_menu
unsetopt listambiguous
zstyle ':completion:*' menu select=1 _complete _ignored _approximate
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
bindkey -M menuselect '^M' .accept-line
LISTMAX=9999
alias d='univ_open' # alias to use univ_open()

auto_menu enables the menu selection system after pressing TAB. unsetopt listambiguous makes zsh automatically complete an ambiguous word to the nearest, partial-word match (combined with auto_menu, this makes it so that you only have to type TAB once, and only once, in all situations). The zstyle lines affect the style of how the menu is displayed. The bindkey portion makes the ENTER key execute the command when using the menu. (Note, ^M is a special character that represents a newline; in vim, you have to type CTRL-V, then CTRL-M to insert it). Finally, setting LISTMAX to a high number prevents zsh from asking “do you wish to see all N possibilities?”, unless N > 9999.

Happy zsh-ing!

UPDATE January 13, 2010: If you’re in a hurry, then you can put bindkey ‘\t’ menu-expand-or-complete to make it so that pressing TAB once brings up the menu and select the first item in that menu. (Or, if there is just 1 possibility, then to expand to that item).

UPDATE February 25, 2010: See my comment #1 below.

Advertisements

One thought on “Zsh: univ_open: A Universal Directory/File Opener (no more bashrun)

  1. I want to avoid writing completely new posts for minor changes, so I am posting a comment here instead. Here is my updated univ_open() function, as it stands right now (it basically spits out better nonfatal error messages to the user every time the user abuses how univ_open() works):

    (Sorry about the zero-width indenting — wordpress doesn’t like leading whitespace.)

    #!/bin/zsh

    # univ_open() is intended to be used to pass either a SINGLE valid FILE or
    # DIRECTORY. For illustrative purposes, we assume “d” to be aliased to
    # univ_open() in ~/.zshrc. If optional flags are desired, then either prepend
    # or append them appropriately. E.g., if you have jpg’s to be opened by eog,
    # then doing “d -f file.jpg” or “d file.jpg -f” will be the same as “eog -f
    # file.jpg” or “eog file.jpg -f”, respectively. The only requirement when
    # passing flags is that the either the first word or last word must be a valid
    # filename.

    univ_open() {
    ls=”ls -Ahs –color=auto”
    if [[ -z $@ ]]; then
    # if we do not provide any arguments, go to the home directory
    cd && ${=ls} # (“cd” w/o any arguments goes to the home directory)
    elif [[ -f $1 || -f ${=@[-1]} ]]; then
    # if we’re here, it means that the user either (1) provided a single
    # valid file name, or (2) provided a single valid file name PLUS any
    # number of additional arguments; use of the $@ variable ensures that
    # we preserve all the arguments the user passed to us

    # $1 is the first arg; ${=@[-1]} is the last arg
    # use && and || for simple ternary operation
    [[ -f $1 ]] && file=$1 || file=${=@[-1]}
    case $file:e:l in
    (doc|odf|odt|rtf)
    soffice -writer $@ &>/dev/null & disown
    ;;
    (htm|html)
    firefox $@ &>/dev/null & disown
    ;;
    (pdf|ps)
    evince -f $@ &>/dev/null & disown
    ;;
    (bmp|gif|jpg|jpeg|png|svg|tiff)
    eog $@ &>/dev/null & disown
    ;;
    (psd|xcf)
    gimp $@ &>/dev/null & disown
    ;;
    (aac|flac|mp3|ogg|wav|wma)
    smplayer $@ &>/dev/null & disown
    ;;
    (mid|midi)
    timidity $@
    ;;
    (avi|flv|ogm|ogv|mkv|mov|mp4|mpg|mpeg|rmvb|wmv)
    smplayer $@ &>/dev/null & disown
    ;;
    *)
    vim $@
    ;;
    esac
    elif [[ -d $1 ]]; then
    # if the first argument is a valid directory, just cd into it — ignore
    # any trailing arguments
    if [[ $# -eq 1 ]]; then
    cd $@ && ${=ls}
    else
    echo ” univ_open: argument(s) ignored: \`${=@[2,-1]}\`”
    echo ” univ_open: cd-ing to \`$1’\n”
    cd $1 && ${=ls}
    fi
    elif [[ ! -e $@ ]]; then
    [[ $# -gt 1 ]] && head=$1:h || head=$@:h
    # if we’re given just 1 argument, and that argument does not exist,
    # then go to the nearest valid parent directory; we use a while loop to
    # find the closest valid directory, just in case the user gave a
    # butchered-up path
    while [[ ! -d $head ]]; do head=$head:h; done
    echo ” univ_open: path \`$@’ does not exist”
    [[ $head == “.” ]] && echo ” univ_open: staying in same directory\n” || echo ” relocating to nearest parent directory \`$head’\n”
    cd $head && ${=ls}
    else
    # possible error — should re-program the above if this ever happens,
    # but, it seems unlikely
    echo ” univ_open: error — exiting peacefully”
    fi
    }

Comments are closed.