date: 2015-12-09

less 可以控制是否使用 alternate-screen, 我们希望 tmux 不要使用 alternate-screen, 其他绝大多数场景使用 alternate-screen 都是没有问题的.

sudo apt-get source less 把 less 的源码拉下来, 尝试分析 less 如何控制是否使用 alternate-screen.

# man less
-X or --no-init
      Disables  sending  the  termcap  initialization  and  deinitialization strings to the terminal.  This is sometimes
      desirable if the deinitialization string does something unnecessary, like clearing the screen.
# vim opttbl.c
public int no_init;		/* Disable sending ti/te termcap strings */
# man 5 termcap
te   End program that uses cursor motion
ti   Begin program that uses cursor motion
// screen.c
/*
 * Initialize terminal
 */
	public void
init()
{
#if !MSDOS_COMPILER
	if (!no_init)
		tputs(sc_init, sc_height, putchr);
	if (!no_keypad)
		tputs(sc_s_keypad, sc_height, putchr);
	if (top_scroll) 
	{
		int i;

		/*
		 * This is nice to terminals with no alternate screen,
		 * but with saved scrolled-off-the-top lines.  This way,
		 * no previous line is lost, but we start with a whole
		 * screen to ourself.
		 */
		for (i = 1; i < sc_height; i++)
			putchr('\n');
	} else
		line_left();
#else
#if MSDOS_COMPILER==WIN32C
	if (!no_init)
		win32_init_term();
#endif
	initcolor();
	flush();
#endif
	init_done = 1;
}

/*
 * Deinitialize terminal
 */
	public void
deinit()
{
	if (!init_done)
		return;
#if !MSDOS_COMPILER
	if (!no_keypad)
		tputs(sc_e_keypad, sc_height, putchr);
	if (!no_init)
		tputs(sc_deinit, sc_height, putchr);
#else
	/* Restore system colors. */
	SETCOLORS(sy_fg_color, sy_bg_color);
#if MSDOS_COMPILER==WIN32C
	if (!no_init)
		win32_deinit_term();
#else
	/* Need clreol to make SETCOLORS take effect. */
	clreol();
#endif
#endif
	init_done = 0;
}

sc_init, sc_deinit 是什么呢? 再从源码中找到:

sc_init = ltgetstr("ti", &sp);
if (sc_init == NULL)
	sc_init = "";

sc_deinit= ltgetstr("te", &sp);
if (sc_deinit == NULL)
	sc_deinit = "";

less 的源码中没找到 tputs() 的源码, 猜想应该是 ncurses 的一个函数, apt-get build-dep less 安装 less 编译依赖, 果然看到安装了 libncurses5-dev.

确认 tputs() 是在 ncurses 的头文件中定义的:

$ grep -F -e 'tputs' $(dpkg -L libncurses5-dev | grep -F '.h')
... ...
/usr/include/term.h:extern NCURSES_EXPORT(int) tputs (const char *, int, int (*)(int));
/usr/include/term.h:extern NCURSES_EXPORT(int)     NCURSES_SP_NAME(tputs) (SCREEN*, const char *, int, NCURSES_SP_OUTC);

奇怪的是 ltgetstr 在系统所有头文件中都没找到, 原来这是 less 基于 tgetstr 包装的一个函数, 而 tgetstr 也在 term.h 头文件中定义.

apt-get install ncurses-doc 安装 ncurses 文档后, 即可通过 man tputs 查看相关函数的说明文档.

找到这两个根源函数, 尝试分析 tmux 的代码. 很遗憾, tmux 源码中没发现使用这两个函数的地方. 可能是被其他更高层次的函数包装了(?).

ncurses 接口封装默认就会使用 alternate-screen, 除非应用刻意使用了 python2 -c 'import sys, time, curses; curses.initscr() ; range(5); print "hello\r\n", ; sys.stdout.flush(); time.sleep(3); curses.endwin()'

vim -c 'tab help t_ti'
t_te	out of "termcap" mode				*t_te* *'t_te'*
t_ti	put terminal in "termcap" mode			*t_ti* *'t_ti'*

不修改应用, 可以重新编译 terminfo 去掉 smcup, rmcup:

( x=xterm f=$(mktemp tmp_termXXXX.src) && infocmp -l $x | sed -r -e 's#^'$x'\|#'$x'-noalt|#' -e 's#[rs]mcup=[^,]*, ?##' > $f && test -n "$(cat $f)" && tic $f && rm $f && echo success)
sudo rsync -av .terminfo/ /usr/share/terminfo/

最后发现 tmux 可通过一个设置修改 smcup, rmcup, 参考 Use terminal scrollbar with tmux

set -ga terminal-overrides ',xterm*:smcup=:rmcup='

控制台控制序列文档可参考 man console_codes, 完整 xterm 控制序列文档可参考 XTerm Control Sequences.

其他相关讨论和文档:

Exorcising the Evil Alternate Screen
http://www.shallowsky.com/linux/noaltscreen.html

Stop gnome-terminal screen clear
http://fixlog.blogspot.com/2006/09/stop-gnome-terminal-screen-clear.html

Why doesn't the screen clear when running vi?
http://invisible-island.net/xterm/xterm.faq.html#xterm_tite

The alternate screen terminal emulator plague
https://utcc.utoronto.ca/~cks/space/blog/unix/AlternateScreenPlague