ft's z-shell setup

This page used to be the home of my zsh setup. I've since moved all the code to a git repository at 0x50.de: zsh setup

Now, I'm using this page to outline a few of the things in my setup, that might be of use for other people, as well.

zsh integration: tmux

I am using sessions in tmux obsessively.

Every job gets its session. Hack on a project? New session. Write on a paper? New session. Hack on another project? New session. That makes everything really tidy. I love it. Never going back to my old habits again. But with that, you'll need a way to create new or attach to old session very easily. Also, I want a session to have its default path for new programs to be somewhere that's not my home directory. And while I know that tmux 1.6 can create new windows in the same directory as the current window, I'd very much like to remain in control - so, that's not for me.

tmux attach or create session

If you want to switch a tmux client from session to session, that's easily done from within tmux. No problem. If you want a new session in a new terminal, then that's still pretty simple. If you want a little more convenience, you need a way to look if a session already exists and if so attach to it and only create a new one otherwise. That's what my ‘ta’ function is for. Its usage description looks like this:

    ta: attach or switch to existing or new tmux session


### SHORT DESCRIPTION ###########################

At its core, the objective of this function can be summarised like
this:

1. Check if a named session exists in the tmux server.
2. If is does not, ask tmux to create the session.
3. Then finally, in case...
    a. ...we're outside of tmux: use attach-session
    b. ...we're inside of tmux: use switch-client


### USAGE #######################################

Basic call structure:

        ta [OPTION(s)] <session-name>

`session-name' may be an existing or a new session name. If it is an
existing session's name, the session is attached to; otherwise, a new
session is registered with tmux and then attached to.

Options:

-h      Display ta's reference manual.

-d      When attaching to an existing session, pass the `-d'
        option to tmux, which will detach any other clients
        attached to the session.

-x      Do *not* call "tmux attach" using `exec', but without.
        So, when present and the tmux session is detached from
        again, control will be picked up be the (still existing)
        parent shell again.

-w xx   Use xx as the working directory for a new session, if a
        new session is what `ta' will create.

-L xx   Set tmux's socket-name; this override the style of the
        same name.

-S xx   Set tmux's socket-path; this override the style of the
        same name. This option also wins if both `-L' and `-S'
        are supplied.

The `-x' option has no effect, when `ta' is being used from inside of
tmux. The `-d' option's effect can be completely inhibited by setting
a `dispatcher' style for the "outside". Finally, for more information
about the `-S' and `-L' options see tmux(1).

    Use tab completion to complete option characters and
    names of existing sessions!

Oh yeah, it comes with support for zsh's completion. And that looks something like this (depending on your compsys setup you'll be able to choose sessions using cursor keys):

ta completion

The function itself and the completion each live in a file that holds their code. If you're unfamiliar with that concept, take a look at zsh function files over at the zshwiki.

Basically, you're adding a directory to zsh's function-search-path and add files to it, which you'd then mark for autoloading. Say ‘~/.zfunctions’. And you'd drop both files (ta and _ta) into that directory. The code for that would then be:

fpath=( ~/.zfunctions "${fpath[@]}" )
autoload -Uz ta

Make sure that this is done before you're calling ‘compinit’ to initialise zsh's completion system.

Finally, here are the links to the functions:

Completing words from tmux scrollback

Tmux provides access to the contents of the terminal buffers it emulates. Wouldn't it be cool if you could complete words from those buffers? “Sure it would!” is what Julius Plenz probably thought, when he came up with the idea.

My setup contains a function that is inspired by Julius's idea. But it also expands on it, adds configurability and uses more modern tmux features to pry the information out of the multiplexer:

It allows you to define multiple variants of the basic idea, so that you may complete words from:

  • The currently focused tmux pane.
  • The currently visible tmux window.
  • The currently active tmux session.
  • Any scrollback buffer in the entire tmux server.

It also allows for some configuration: For example, the user may define the options that are used for tmux's ‘capture-pane’ command (the default is -J). This may be used to query not only the visible portion of a pane but it's entire scrollback buffer:

zstyle ":completion:words-from-tmux-*:*" capture-options -J -E - -S -

The function also allows some post-processing of the generated list of words. For example, you might want to get rid of any backslashes within the generated words:

zstyle ":completion:words-from-tmux-*:*" trim-pattern '[\\]'

And then you might want to turn words like "<foo>" into "foo":

zstyle ":completion:words-from-tmux-*:*" pre-trim-pattern '[<]'
zstyle ":completion:words-from-tmux-*:*" post-trim-pattern '[>]'

If you're interested take a look at the code I liked above. It comes with a big comment on top of the file that contains a complete example setup for this function.

Changing a tmux sessions's default path on the fly

The second thing you'd like to automate with many sessions is the working directory of tmux's sessions. That way, you get the feel of a session "living" in a directory. For that I hacked up another function: cdm

The idea is to use it as the first ‘cd’ after a new session was created. Say there is a session called "grmldev" and you'd like it to "live" in ‘~/src/grml’. A new session (obviously created using ‘ta’, which can actually even automate these initial ‘cdm’ calls if you bother to set it up that way — see above) will drop you into your home directory, so the whole command trail would look like this:

% ta grmldev
% cdm src/grml

And that's it. Tmux will now set the initial path for new commands (including new shells) to that directory. Fun!

Update 2016-03-16: First a tmux feature (‘default-path’) that got removed and then a hacky shell parameter (‘$ZRC_PLEASE_GOTO`’). Surely there is a third alternative: To add more versatility to the function, I am now using one of tmux's user-defined variables (variables with names that start with an ‘@’) to keep track of my intended working directory. The new function has the ability to take you back to a session's default working directory by calling it without argument. This was not possible before, since there was no way for the function to update the parameter that I used to be updated in already running shells. Since this parameter lives in the tmux server, different zsh sessions can use that server information to keep in sync.

The new initialisation looks like this (assuming you've got the ‘cdm‘ function file in ‘$fpath’ and marked it for autoloading):

[ -n $TMUX ] && cdm

The help text reads like this now:

usage: cdm [-h] [DIRECTORY]

Switches the default working directory of a tmux session to DIRECTORY. If
DIRECTORY is not supplied, cdm changes zsh's current working directory to
the current tmux session's default working directory.

Internally, the function uses a tmux-user-option named "@ft-zsh-wd".

Miscellaneous Bits

A bit of dmesg: ‘dm’

If you're like me, you take a look at dmesg every once in a while. And guess what, in most cases I am only interested in the last few lines. That is when constructs like this come up:

% dmesg | tail -n 20

I think that zsh can do that dance for me. It can ask the terminal how many lines it can display and then fill about a page with dmesg output. That is pretty much what my ‘dm’ function does:

Display the last few line of ‘dmesg’ output. “few” is just a bit
less lines than there are lines in the running terminal, so the
function's output will fit into the terminal, unless the output
is very long with lots of wrapping lines.

If the number of lines of a terminal could not be determined, 24
is used as a default. The ratio to determine how many lines to
actually print defaults to 85%.

The following styles may be used to customise the behaviour of
the function:

- default-lines: An integer that will be used instead of 24 as
                the default number of lines in a terminal, that
                didn't allow the number of lines to be
                determined.

- ratio: The ratio of lines to be printed.

All configuration is done in the ':fnc:dm:options' context.

To get a pageful of dmesg:

% dm

To get exactly 20 lines of dmesg:

% dm 20