python - 终端中的行与行

标签 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键以打印b时,



一切正常。我使用了绝对游标定位,并写入了我之前写过的相同行-当然,它不会覆盖所有a,因为其中许多都在其他行上。

但是,当我们将窗口再缩小一个字符时,换行的效果就会不同:



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

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



我的问题

实际上,我的问题是如何防止或预测这些行合并为行。清除整个屏幕可以消除此行为,但是如果可能的话,只对需要它的单个行执行此操作会很不错,这样我就可以保持逐行缓存,从而显着加快我的应用程序的速度。清除到一行的末尾会取消该行与其下一行的链接,但是清除到一行的开头并不会取消该行与其上一行的链接。

我很好奇-这些东西是什么?我在哪里可以读到它们?我可以找出哪些行是同一行的一部分吗?

我已经观察到了terminal.app和iterm以及不带tmux的这种行为。我想即使没有任何规范,将源划分为任何一个都会产生答案-但我想某处有规范!



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

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

最佳答案

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

我测试了您的代码,发现它仅在您调整窗口大小时才发生。当窗口单独放置时,它将写出a,然后按Enter将用b覆盖它们(我假设这是预期的行为)。

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

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

您会注意到,在按Enter键调整大小后,仅保留a的两行(而不是全部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以及随后的行覆盖第二行的最后a。

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


重要的是,这还将覆盖a的第二行末尾的换行符。这意味着现在不存在将a的第二行和b的第一行分开的换行符,因此,当您展开窗口时:它们显示为单行。

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


我不太确定为什么b的第二行也放在一起,但似乎与第一个被覆盖的a的行现在丢失了它自己的换行符有关。但是,这只是一个猜测。

如果您尝试将窗口缩小另一个字符,则会得到两个换行符,这是因为现在您要缩小同一行文本的两半,这意味着一个推压另一行,从而导致两个字符最后一个。

例如:在我显示的这些测试窗口中,宽度以15个字符开始,然后将其缩小为14,然后打印出b。仍然有一行a的长度为15个字符,现在一行有14个a和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系列硬件终端的系统。

在“首选项”->“设置”->“高级”下的“ 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窗口设置,以便您可以控制所有大小调整逻辑。

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

它可以通过curses模块用于python。

(并且,如果您打算重新分发程序,请考虑使用Python3编写。针对孩子们:D)

资源:

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


ANSI Escape Sequences
VT100 Escape Sequences


希望对您有所帮助!

关于python - 终端中的行与行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25705842/

相关文章:

python - 写树、篮子数量和压缩(连根拔起)

Python:抓取网页结果,其结果是在服务器端生成的

python - PySpark使用RDD和json.load解析Json

terminal - 从命令行调用 PHP 脚本中的函数

android-emulator - 如何在 android 2.3 模拟器上获得 root 访问权限

python - 为什么 django 有collectstatic?

linux - 重新加载 Linux 桌面而不重新启动或注销并重新登录?

c - 设置进程组阻止子进程启动

ip - 远程连接时隐藏您的IP

emacs - 在 emacs 中无法在终端模式下工作的 block 绑定(bind)?