英文:
Issue with ANSI cursor movement in goroutine
问题
背景
我正在尝试编写一个Go库,用于创建终端任务列表,灵感来自于Node库listr。
我的库golist在后台的goroutine中打印任务列表,并使用ANSI转义序列更新文本和状态字符。
问题
存在一个问题,即列表的最终打印会偶尔包含额外的空格,导致一些空格或重复的行。以下是两个示例 - 一个正确的示例和一个不正确的示例 - 都是使用__完全相同的代码__运行的(这里是代码的链接)。
示例
这是一个应该是正确的示例:
这是一个有时候会出现的示例:
如果你查看不正确版本的gist中的第184行和185行,会发现两个空行不在正确版本中。
为什么会发生这种情况,而且为什么只会偶尔发生?
代码
我在以下循环中将列表打印到终端:
go func() {
defer donePrinting() // 告诉Stop函数我们已经打印完毕
ts := l.getTaskStates()
l.print(ts)
for {
select {
case <-ctx.Done(): // 检查打印循环是否应该停止
// 执行最后的清除和根据`ClearOnComplete`进行可选的打印
ts := l.getTaskStates()
if l.ClearOnComplete {
l.clear(ts)
return
}
l.clearThenPrint(ts)
return
case s := <-l.printQ: // 检查是否有要打印的消息
fmt.Fprintln(l.Writer, s)
default: // 否则,打印列表
ts := l.getTaskStates()
l.clearThenPrint(ts)
l.StatusIndicator.Next()
time.Sleep(l.Delay)
}
}
}()
列表被格式化为字符串,然后打印。以下函数格式化字符串:
// fmtPrint返回格式化的消息和状态列表,使用提供的TaskStates
func (l *List) fmtPrint(ts []*TaskState) string {
s := make([]string, 0)
for _, t := range ts {
s = append(s, l.formatMessage(t))
}
return strings.Join(s, "\n")
}
以下函数构建用于清除行的ANSI转义字符串:
// fmtClear返回一个包含ANSI转义字符的字符串,用于清除先前打印的`n`行。
func (l *List) fmtClear(n int) string {
s := "3[1A" // 上移一行
s += "3[K" // 清除行
s += "\r" // 移动到行的开头
return strings.Repeat(s, n)
}
我在这个网站上参考ANSI代码。
提前感谢您对此问题可能发生原因的任何建议!
如果有任何其他信息可以帮助,请告诉我。
英文:
Background
I'm trying to write a Go library for creating terminal task-lists, inspired by the Node library listr.
My library, golist, prints the task list out in a background goroutine and updates the text and status characters using ANSI escape sequences.
The Problem
There's an issue where the final print of the list will occasionally have extra spaces included, leading to some spaces or repeated lines. Here are two examples – one correct, one not – both from runs of the same exact code (here's a link to the code).
Example
Here's an example of what it should look like:
(Here's a gist of the raw text output for the correct output)
And here's an example of what it sometimes looks like:
(Here's a gist of the raw text output for the incorrect output)
If you look at lines 184 and 185 in the gist of the incorrect version, there are two blank lines that aren't in the correct version.
Why is this happening and why is it only happening sometimes?
Code
I'm printing the list to the terminal in the following loop:
go func() {
defer donePrinting() // Tell the Stop function that we're done printing
ts := l.getTaskStates()
l.print(ts)
for {
select {
case <-ctx.Done(): // Check if the print loop should stop
// Perform a final clear and an optional print depending on `ClearOnComplete`
ts := l.getTaskStates()
if l.ClearOnComplete {
l.clear(ts)
return
}
l.clearThenPrint(ts)
return
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
default: // Otherwise, print the list
ts := l.getTaskStates()
l.clearThenPrint(ts)
l.StatusIndicator.Next()
time.Sleep(l.Delay)
}
}
}()
The list is formatted as a string and then printed. The following function formats the string:
// fmtPrint returns the formatted list of messages
// and statuses, using the supplied TaskStates
func (l *List) fmtPrint(ts []*TaskState) string {
s := make([]string, 0)
for _, t := range ts {
s = append(s, l.formatMessage(t))
}
return strings.Join(s, "\n")
}
and the following function builds the ANSI escape string to clear the lines:
// fmtClear returns a string of ANSI escape characters
// to clear the `n` lines previously printed.
func (l *List) fmtClear(n int) string {
s := "\033[1A" // Move up a line
s += "\033[K" // Clear the line
s += "\r" // Move back to the beginning of the line
return strings.Repeat(s, n)
}
I'm using this site as a reference for the ANSI codes.
Thanks in advance for any suggestions you might have about why this is happening!
Let me know if there's any other information I can add that can help.
答案1
得分: 1
我认为 ANSI 代码只是一个误导。我下载了该库并尝试在本地运行,发现以下部分是导致此问题的原因:
case s := <-l.printQ: // 检查是否有要打印的消息
fmt.Fprintln(l.Writer, s)
当 printQ
通道关闭时,有时会执行这个 case,即使没有打印任何内容,光标似乎也会向下移动。当我将关闭通道的调用移到 l.printDone
被调用之后时,这种行为消失了。
...
// 等待打印循环完成
<-l.printDone
if l.printQ != nil {
close(l.printQ)
}
...
这确保了当通道关闭时,循环不再运行,因此 s := <-l.printQ
这个 case 就不会执行。
英文:
I think the ANSI codes are just a red herring. I pulled down the library and tried running it locally, and found that the following section is what is creating this issue:
case s := <-l.printQ: // Check if there's a message to print
fmt.Fprintln(l.Writer, s)
When the printQ
channel is getting closed, this case is sometimes running, which seems to be moving the cursor down even though nothing is getting printed. This behaviour went away when I moved the call to close the channel after l.printDone
is called.
...
// Wait for the print loop to finish
<-l.printDone
if l.printQ != nil {
close(l.printQ)
}
...
This ensures that the loop is no longer running when the channel is closed, and thus the s := <-l.printQ
case cannot run.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论