行与终端中的行

时间:2014-09-06 23:55:22

标签: python terminal terminal-emulator ansi-escape

在终端模拟器中似乎有一些行与行的概念,我想知道更多。

演示我的意思是行与行

下面的Python脚本显示三行'a'并等待,然后有三行'b'。

import sys, struct, fcntl, termios

write = sys.stdout.write
def clear_screen(): write('\x1b[2J')
def move_cursor(row, col): write('\x1b['+str(row)+';'+str(col)+'H')
def current_width(): #taken from blessings so this example doesn't have dependencies
    return struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, '\000' * 8))[1]

clear_screen()
for c in 'ab':
    #clear_screen between loops changes this behavior
    width = current_width()
    move_cursor(5, 1)
    write(c*width+'\n')
    move_cursor(6, 1)
    write(c*width+'\n')
    move_cursor(7, 1)
    write(c*width+'\n')

    sys.stdout.flush()
    try: input() # pause and wait for ENTER in python 2 and 3
    except: pass

enter image description here

如果在此休息期间将终端窗口宽度缩小一个字符,则会看到

enter image description here

这似乎很合理 - 每条线都是单独包裹的。当我们再次按Enter键打印b s时,

enter image description here

一切都按预期工作。我已经使用了绝对光标定位,并写入了我之前写过的相同行 - 这当然不会覆盖所有的a,因为其中很多都在其他行上。

然而,当我们将窗口缩小一个字符时,包装的工作方式不同:

enter image description here

为什么b的第二行和第三行换行,为什么a的最后一行与b的第一行合并?为什么在上面的顶部可见行中有一个提示 - 我们看到两个a因为它们仍然链接了两行 - 当然如果我们再次移动窗口,那一行将继续以相同的方式换行。即使是我们替换了整行的行也似乎正在发生这种情况。

事实证明,以前包装过的行现在已链接到相应的父行;一旦我们大量扩展终端,它们就属于同一条逻辑线更为明显:

enter image description here

我的问题

实际上,我的问题是如何防止或预测这些行被组合成行。清除整个屏幕消除了行为,但是如果可能的话,仅对需要它的单个行执行此操作会很好,因此我可以按线保持缓存,这显着加快了我的应用程序。清除到行的末尾会将该行与其下方的行取消链接,但清除到行的开头不会取消该行与其上一行的链接。

我好奇 - 这些线条是什么?我在哪里可以阅读它们?我可以找出哪一行是同一行的一部分吗?

我用terminal.app和iterm,with和w / o tmux观察到了这种行为。我想,即使没有规范,源代码潜入其中任何一个都会产生答案 - 但我想在某个地方有一个规范!


背景:我想创建一个终端用户界面,可以预测如果用户减小窗口宽度,终端包装的方式。我知道像全屏模式(tput smcuppython -c 'print "\x1b[?1049h"'这些是ncurses使用的),它们可以用来防止换行,但是不想在这里使用它。

编辑:更清楚地表明我已经理解了脚本的覆盖行为,并想要解释包装行为。

1 个答案:

答案 0 :(得分:7)

确定。让我们从您所看到的行为的原因开始:

我测试了你的代码并注意到它只发生在调整窗口大小时。当窗口被单独放置时,它会写出a,并且在按下回车时会用b覆盖它们(我认为这是预期的行为)。

似乎正在发生的事情是,当您在中途调整窗口大小时,线索引会发生变化,因此在下一次迭代中,当您调用move_cursor()时,您无法信任相同的坐标。

有趣的是,当您调整窗口大小时,自动换行会在光标向上之前推送文本。我认为这是终端仿真器代码的一部分(因为我们几乎总是希望将焦点保持在光标上,如果光标位于屏幕的底部,调整大小可能会使其超出窗口的高度如果自动换行将其向下推。)

您会注意到,在按Enter键调整大小后,只有两行可见(而不是全部3行)。以下是正在发生的事情:

首先我们从初始输出开始。 (为清晰起见,添加了行号)

1
2
3
4
5 aaaaaaaaaaaaaaa\n
6 aaaaaaaaaaaaaaa\n
7 aaaaaaaaaaaaaaa\n
8 

请注意,每条线的末尾都有一个换行符(这就是为什么你的光标出现在最后一个下方,尽管你没有再次移动光标)

当您将窗口缩小一个字符时,会发生以下情况:

1
2 aaaaaaaaaaaaaa
3 a\n
4 aaaaaaaaaaaaaa
5 a\n
6 aaaaaaaaaaaaaa
7 a\n
8

你会注意到我的意思"向上推文字"

现在,当您按Enter键并重复循环时,光标将被发送到第5行第1行(由您的代码指定),并直接放在第二行的最后一行。当它开始写b时,它用b' s覆盖第二行的最后一行,以及随后的行。

1
2 aaaaaaaaaaaaaa
3 a\n
4 aaaaaaaaaaaaaa
5 bbbbbbbbbbbbbb\n
6 bbbbbbbbbbbbbb
7 bbbbbbbbbbbbbb\n
8

重要的是,这个会覆盖第二行末尾的换行符。这意味着现在没有新行划分第二行和第一行b,所以当你展开窗口时:它们显示为一行。

1
2
3 
4
5 aaaaaaaaaaaaaaa\n
6 aaaaaaaaaaaaaabbbbbbbbbbbbbb\n
7 bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n
8

我不完全确定为什么第二行的b也会被放在一起但似乎可能与第一行的第二行有关覆盖现在已经错过了自己的新线终止。但是,这只是猜测。

如果你试图通过另一个字符缩小窗口,你得到两个换行符的原因是因为现在你缩小了同一行文本的两半,这意味着一个推动另一半,导致两个字符而不是最后一个字符。

例如:在我已经显示的这些测试窗口中,宽度从15个字符开始,然后我将其缩小到14并打印出b。还有一行是15个字符长,现在是一行14个字符串& 14 b是以14个字符包裹的。同样(出于某种原因)对于最后两行b(它们是一行28个字符,包裹在14)是正确的。所以当你将窗口缩小一个字符(低至13)时:15 a的第一行现在有两个尾随字符(15 - 13 = 2);下一行28个字符现在必须适合13个字符宽的窗口(28/13 = 2 R2),同样适用于最后一个b。

0 aaaaaaaaaaaaa
1 aa\n
2 aaaaaaaaaaaaa
3 abbbbbbbbbbbb
4 bb\n
5 bbbbbbbbbbbbb
6 bbbbbbbbbbbbb
7 bb\n
8

为什么它会像这样工作?:

当您尝试在另一个能够根据需要重新定位文本的程序中运行程序时,遇到这种问题很困难。如果调整大小,您的索引将变得不可靠。您的终端模拟器正在尝试为您处理重新排列,并在回滚之前在提示之前推送文本(在第8行修复),以确保您始终可以看到您的活动提示。

行和列是终端/终端仿真器定义的内容,由它来决定相应的位置。当给出适当的控制序列时,终端会相应地解释它们以便正确显示。

请注意,某些终端的行为有所不同,并且在模拟终端中通常会有一个设置来更改它所模拟的终端类型,这也可能会影响某些转义序列的响应方式。这就是为什么UNIX环境通常有一个设置或环境变量($ TERM),它告诉它与哪个终端通信,以便它知道要发送什么控制序列。

大多数终端使用标准的ANSI兼容控制序列,或基于DEC VT系列硬件终端的系统。

在Preferences-> Settings-> Advanced下的Terminal.app偏好设置中,您可以在"旁边的下拉菜单中实际查看(或更改)您的窗口模拟的终端类型终端为:"

如何克服这个:

您可以通过存储上一个已知宽度并检查是否有更改来缓解此问题。在这种情况下,您可以更改光标逻辑以补偿更改。

或者,您可以考虑使用为相对光标移动而设计的转义序列(而不是绝对值),以避免在调整大小后意外覆盖前一行。还可以仅使用转义序列来保存和恢复特定的光标位置。

Esc[<value>A  Up
Esc[<value>B  Down
Esc[<value>C  Forward
Esc[<value>D  Backward
Esc[s         Save Current Position
Esc[u         Restore Last Saved Position
Esc[K         Erase from cursor position to end of line

但是,您并不能确保所有终端模拟器都以相同的方式处理窗口大小调整(它不是任何终端标准的一部分,AFAIK),或者它未来不会发生变化。如果您希望制作一个真正的终端模拟器,我建议首先设置您的GUI窗口,以便您可以控制所有调整大小的逻辑。

但是,如果您希望在终端模拟器窗口中运行,并为您正在编写的给定命令行实用程序处理缓解窗口大小调整。我建议查看python的curses库。这是我所知道的所有窗口大小调整程序所使用的功能(vim,yum,irssi),并且可以处理这种更改。虽然我个人没有任何使用它的经验。

通过curses模块可用于python。

(并且,如果您计划重新分发您的程序,请考虑使用Python3编写。请为孩子们做:D)

资源:

这些链接可能会有所帮助:

我希望有所帮助!