Search this site


Metadata

Articles

Projects

Presentations

Flashback 2003: vim + screen trick

Matt Simmons suggested that this week be a flashback week. That's easy enough to do, so I'm doing one per year starting in 2003.

A common feature request of vim is to be able to split window and have a shell be there. Well, as it turns out, that's easy to do if you run vim inside screen (gvim not applicable here).

The original post details a simple vim keybinding that will split the screen and attach a shell and close the split when the shell exits.

August 2003: Neat vim/screen trick

However, I've got a better version now. You'll want this script, "screener.sh":

#!/bin/sh

screen -X split
screen -X focus down
screen -X screen sh -c "cd $PWD; $SHELL; screen -X remove"
Then in your .vimrc:
:map \s :silent !screener.sh<CR>
The new version mostly puts the screen invocations into a shell script. Another improvement is to change to $PWD so the new shell will be in the same working directory as vim started in.

Terminals, titles, and prompts.

Drew Stephens spent some time on Christmas to share some of his shell configuration, including different ways he uses prompts and colors.

I'll start with prompts.

I use zsh. My prompt looks like this:

# Plain
snack(~) % 

# Long directory is truncated at the left
snack(...jects/grok/ruby/test/general) % 

# I get exit status only if it is nonzero:
snack(~) % true
snack(~) % false
snack(~) !1! % 

# if I am root, and using zsh, the '%' becomes '#'
snack(~) # 
This is all achieved with the following PS1 in zsh:
PS1='%m(%35<...<%~) %(?..!%?! )%# '
We have configurable prompts to give us all kinds of information, why? It's a place to gather context from. I include host, directory, exit status, and an "am i root" flag.

PS1 isn't the only place you can store useful state. I like to have similar information in my terminal's titlebar, too. I use screen and xterm, and both can be fed some delicious data.

I use this in my .screenrc, which tells screen to have some default status format and tells screen how to change xterm's title. I have it include the screen window number (%n), hostname (%h), and terminal title (%t):

hardstatus string "[%n] %h - %t"
termcapinfo xterm 'hs:ts=\E]2;:fs=\007:ds=\E]2;screen (not title yet)\007'
windowlist title "Num Name%=Location Flags"
windowlist string "%03n %t%=%h %f"
I also use this bit of .vimrc, which tells vim what kind of title I want, and if the $TERM is screen, how to tell screen about it.
" Set title string and push it to xterm/screen window title
set titlestring=vim\ %<%F%(\ %)%m%h%w%=%l/%L-%P
set titlelen=70
if &term == "screen"
  set t_ts=^[k
  set t_fs=^[\
endif
if &term == "screen" || &term == "xterm" 
  set title
endif
And then use this bit of my zshrc.

All of these combined together make for some pretty good terminal and screen titles. The functions preexec, precmd, and title, mentioned below, come from the above zshrc link.

The preexec function in my zshrc runs before each command execution and allows me to change the terminal title to reflect the command I am running. It also supports resumed execution of a process: if you run 'cat', then hit ^Z, then type 'fg', the title will correctly be set to 'cat' again.

The precmd function runs before each prompt. Rather than cluttering up $PS1 with byte strings to set the title, I just make precmd set the title to 'zsh - $PWD'.

The title function takes care of any necessary escaping and also does nice things like string truncation if it is too long (similar to how my $PS1 is configured).

I only use vim's titlestring options because it gives me some better context on what I am doing in vim at the time, mainly because vim allows you to edit multiple files at once.

Here's an example of a few screen windows in a single screen session when viewed in the windowlist:

The first 3 columns are most meaningful: number, name, and location. Note that each location correctly identifies the host that shell is using. My zshrc 'title' function manages setting the name and the location.

The same data listed above is combined into the actual terminal's title. Window 2 above would have this title in xterm:

[2] jls - zsh - /home/jsissel

I mentioned above that I use screen and xterm together. I do this for everything using run-xterm.sh. This script will run screen in an xterm with a randomly chosen, dark color background. I find the dark-random color selection quite a nice deviation from the solid-black my desktop used to bear. Here's what it looks like if I run a 20+ xterms on a blank desktop:

Find that lost screen session: Episode 3.

Previous posts about screen have shown a few new tools for searching your list of open screen sessions.o

Today, I finally sat down and worked on the next installment: Being able to query any screen window and the window list. The difference between the previous script is that we can now grep screen windows other than the 0th one. Additionally, we can now grep the screen window list (which, by the way, has some excellent information).

To that end, I present now two scripts:

You need both for this to work optimally, but they exist separately because the functionality is somewhat distinct.

The 'hardcopy' script takes a single argument, a screen session. It will hardcopy all windows in that screen session including the window list. If you specify OUTDIR in your environment, the screen hardcopies will be put in that directory; otherwise, the output directory is printed to stdout for consumption by another script.

The 'search' script runs the hardcopy script on all active screen sessions (in parallel, yay xargs). Once it has all of the copies, it will grep through the output for your query string (regular expression). It supports 3 flags:

  • -t - only search 'window titles' (ie; only window list output)
  • -w - only search window contents (ie; exclude window list output)
  • -l - only search the 'location' field of the window list
Now, with a single command, I can find out where that ssh session to 'foo' disappeared to. Here's an example screen window list capture (accessed with Ctrl+A " (doublequote))
Num Name                                                              Location Flags

  0 zsh                                                                        syn $
  1 zsh                                                                      scorn $
Now, I want to find all sessions open to 'scorn':
% screen-session-search.sh -t 'scorn'
sty 18210.pts-8.snack window 1
sty 18556.pts-0.snack window 0
It found 2 sessions. I can attach to the first one with:
screen -x 18210.pts-8.snack -p 1
          ^ screen session     ^ window
caveat: I've been hacking on things all night, so the code may or may not be very readable. Apologies if you go blind while trying to read it ;)

Find that lost screen session, episode 2.

Like I said, I run screen in all of my xterms...

xterm sets an environment variable in child processes: WINDOWID. This is the X window id of the xterm window. Using this, we can extend upon my last post and come up with a much neater solution. Knowing what screen session you want to bring forward (assuming it's running in an xterm), we can run a command inside that session that grabs the $WINDOWID variable in the shell and uses xdotool to activate the window.

session=$(sh screen-find.sh "#freebsdhelp")
STY=$session screen -X screen sh -c 'xdotool windowactivate $WINDOWID'
Running this causes my IRC window to be activated by xdotool, which means it is now active, focused, and on top.

This isn't entirely optimal, because it assumes the xterm attached to that screen session is the xterm that launched it. If you run 'xterm -e screen -RR' and close the xterm (don't log out of the shell), then rerun 'xterm -e screen -RR' it will attach to that previous screen session, but the WINDOWID will understandably not be correct any longer.

So what do we do? Using the screen session given, we create a new window in that session and set the title of that window to a generated string. We then use xdotool to search for that string and activate the window. Once we get there, we can kill that new screen window we created and we are left with the terminal holding our screen session sitting in front of us.

I wrote a script to do just that tonight: screen-activate.sh. Example usage: screen-activate.sh 24072.pts-25.snack

This has a great benefit of supporting every terminal program that understands how to set the terminal window title when screen changes it's title. I have tested my .screenrc in Eterm, Konsole, gnome-terminal, and xterm - all know when screen changes it's title if you put this in your .screenrc:

hardstatus string "[%n] %h - %t"
termcapinfo xterm 'hs:ts=\E]2;:fs=\007:ds=\E]2;screen (not title yet)\007'

# Might need this:
termcapinfo  * '' 'hs:ts=\E_:fs=\E\\:ds=\E_\E\\'

Find that lost screen session

Scenario: I run lots of xterms. Each xterm runs a single screen session(*). At any given time, I can only see some of the xterm windows (the others are hidden).

(*) All my xterms run with: 'xterm -e screen -RR'. This causes them to attach to the first-found detached screen, and if none exist creates a new screen session. See run-xterm.sh for my pleasant, random-colored xterm script.

Problem: I forget where I put things. I can't find that terminal where I'm editing foo.c!

Possible Solutions:

  1. Bad: Kill the vim session that's editing the file, and rerun vim somewhere else.
  2. Good: Use xdotool to search window titles for 'foo.c'
  3. Great: Find the screen STY variable for the process 'vim foo.c'
  4. Great: Ask each open screen session about what it is on screen
Today, we'll cover the two 'great' solutions. I wrote both of these a while ago, but I totally forgot to post about them. Here you go :)

Find a screen by it's child processes
Tool: screenps.sh

This tool takes a regexp pattern as the only argument and will output a list of screen sessions having child process commands that match that pattern. This is useful for finding what screen is running 'vim foo.c'

% ./screenps.sh 'vim foo.c'
23464.pts-0.snack
Find a screen by what is being displayed
Tool: screen-find.sh

This tool takes a regexp pattern as the only argument. It uses screen's hardcopy command to save the on-screen buffer and then applies the regexp given to the buffer. If it matches, the screen session is output. There is special behavior if only one screen session is found: If the screen session is currently attached, it will flash that screen session giving you a visual clue about where it is; if it is not attached, it will attach to it.

% ./screen-find.sh "keynav"
28504.pts-27.snack
In case you still aren't clear, the two tools help you find your lost screen sessions. Maybe they aren't lost, but certainly it's easier to search for them by text than by eyeballs if you know what's in them.

A short summary: screenps.sh will search for commands running in a screen session and screen-find.sh will search for literal text displayed in a screen session. Both are super useful.

Note: Currently, screen-find.sh can only capture the contents of the 0th screen window (screen sessions can have multiple windows). I worked for a while on solving this, but for whatever reason I couldn't get it working properly.

Get a screen terminfo entry on Solaris.

Solaris doesn't have a 'screen' terminfo entry by default, so you might notice that if you set TERM to 'screen' things don't work right.

Screen itself comes with a termcap entry. If you install using the sunfreeware package, the termcap file will be: /usr/local/doc/screen/terminfo/screencap

We need to convert this to a terminfo file and compile it. All tools used below come standard with Solaris.

# captoinfo screencap > screen.ti
"screencap", line 2, col 24, terminal 'screen': unknown capability 'G0'
"screencap", line 18, col 58, terminal 'screen': unknown capability 'AX'
# tic screen.ti
# ls -d /usr/share/lib/terminfo/s/screen* 
/usr/share/lib/terminfo/s/screen     /usr/share/lib/terminfo/s/screen-w
Note: Make sure you're running /usr/bin/tic, not /usr/local/bin/tic (comes with some sunfreeware package). The /usr/local/bin/tic will write it's terminfo file to /usr/local/share/terminfo which isn't used by any shell I've tried, not by default anyway. Something to keep in mind.

Now you've got a screen terminfo entry. Setting your TERM to 'screen' should work now, which is the default inside new screen sessions.