2

I use zsh as my default shell on OSX. I would like for my shell to always have a horizontal split showing pwd and ls of the current directory I'm in. How do I go about it ?

  • Shell is a line-based program; it reads from a stream and writes a stream of output. It has no notion of a screen as something with addressable locations to place output. – chepner May 23 '15 at 14:27
  • Few to no shells, and certainly not the Z shell, the Bourne Again shell, the TENEX C shell, the Debian Almquist shell, or the Korn shell, do line-based reading these days, chepner. And (with ZLE, libedit, readline and whatever) they do keep track of multiline input and do cursor motions to place output. – JdeBP May 23 '15 at 19:01

2 Answers2

3

Your use case is not very convincing. Most people include the current directory as part of their prompt; zsh has extremely rich prompt configuration possibilities (including multiline prompts, left and right prompts, etc.). With completion (which again in zsh is very configurable), you can show lists of files in the contexts where you need them. That being said, there are ways to achieve what you describe, but none is both easy and nice.

With the shell only

The shell is only in control of its own input and output. When you execute a command, the shell isn't in control of anything. If you want to reserve a part of the terminal for stuff like ls output, you need the cooperation of the terminal. What is the exact difference between a 'terminal', a 'shell', a 'tty' and a 'console'? may be useful background.

On some terminals, you can define a scrollable region; what is outside the scrollable region stays in place. I know that xterm supports this feature, I don't know if other popular terminal emulators do. Here's a bash proof-of-concept for displaying the output of date, pwd and ls in the bottom third of the terminal.

reset () {
  tput reset
  scrollable_lines=$((LINES*2/3))
  tput csr 0 $((scrollable_lines-1))
} 
update_status () {
  tput sc
  tput cup $((scrollable_lines+2)) 0
  tput ed
  date
  pwd
  ls -x --color=always | head -n $((LINES-scrollable_lines-3))
  tput rc
}
PROMPT_COMMAND='update_status'
reset

A few words of explanation:

  • The tput command sends control sequences obtained via terminfo to the terminal.
  • The call to tput csr defines the scroll region to the top 2/3 lines.
  • The reset function needs to be executed on a terminal reset, because a terminal reset resets the scroll region to be the whole terminal.
  • tput sc saves the cursor position, to be restored later with tput rc.
  • tput cup moves the cursor to the top of the non-scrolling region. tput ed erases what is already there.
  • The function update_status is executed each time bash is about to display a new prompt.

I tried this in zsh, but it interacts with the terminal more than bash does, so even a proof-of-concept requires more tuning.

With a terminal multiplexer

Both Screen and Tmux can split the terminal into multiple sub-windows (Screen calls them regions, tmux calls them panes). You can run a multiplexer, run your shell in the top window, and make it run other stuff in the bottom window.

It's easy to trigger something in the shell to cause things to be displayed in the bottom window: just redirect the output to the right terminal device. The cumbersome part here is obtaining the device name and keeping the bottom window open as long as necessary but no longer.

Here's a proof-of-concept using zsh and screen. Run screen -c ~/etc/split.screenrc where ~/etc/split.screenrc contains

escape ^\\\
hardstatus off
split
focus
resize 10
screen ~/bin/bottom_tty
focus
screen zsh

This creates a 10-line bottom region that runs the program ~/bin/bottom_tty and runs zsh in the upper region. In ~/bin/bottom_tty, obtain a few parameters then sleep forever:

#!/bin/sh
cat <<EOF >~/.split-screen.$PPID.tmp
bottom_tty=$(tty)
bottom_lines=$(tput lines)
bottom_pid=$$
EOF
mv ~/.split-screen.$PPID.tmp ~/.split-screen.$PPID
while true; do sleep 999999999; done

In .zshrc, read the information and set up a couple of things:

  • Each time a prompt is displayed, run refresh_bottom_tty to refresh the content of the bottom region.
  • When zsh exits, kill the program in the bottom region, so that the script will terminate.
refresh_bottom_tty () {
  printf %s $terminfo[clear]
  date
  pwd
  ls -x --color | head -n $((bottom_lines-2))
}
precmd () {
  refresh_bottom_tty <>$bottom_tty 1>&0 2>&0
}
zshexit () {
  kill -HUP -$bottom_pid
}

while [[ ! -e ~/.split-screen.$PPID ]]; do
  sleep 1
done
. ~/.split-screen.$PPID
rm ~/.split-screen.$PPID

Dedicated terminal wrapper

It would be possible to do a much better and more robust job with a dedicated wrapper that understands what you want to do with it. The wrapper would define a virtual terminal in which the shell is executed, and there would be an interface to allow the shell to update what is displayed outside the virtual terminal.

Screen and tmux are actually pretty close to providing the required functionality, via the status line. However both are limited to a single status line. If they were extended to support multi-line status lines, you could display what you want there.

Alternatively, you could use the zpty module to write a dedicated wrapper. There would then be two instances of zsh: one for the wrapper, one to run the commands. Getting this even to a proof of concept is more code than I care to write in this answer.

  • Was away for a couple of days, going to try your proposed approaches soon. Thank you for being so thorough. – madhukar93 May 27 '15 at 05:36
1

I don't know abouut showins ls output, but zsh has a nifty feature RPROMPT. Put this in .zshrc and see if it meets your needs:

PROMPT=$'%n@%m\n%! %% '
RPROMPT='# %d'

That puts current working directory on the right-hand-side of the prompt for me. I include an example of PROMPT with a newline in it, so that if RPROMPT doesn't work as you'd like, you can see how to put "%d" into the value of PROMPT so that it can be on its own line.