From 57c77b1b728f66dca3f867ed42e8ecaa2c9339ec Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Sat, 12 Apr 2025 15:29:04 -0500 Subject: [PATCH] scripts: Fixed most flickering issues in RingIO Two new tricks: 1. Hide the cursor while redrawing the ring buffer. 2. Build up the entire redraw in RAM first, and render everything in a single write call. These _mostly_ get rid of the cursor flickering issues in rapidly updating scripts. --- scripts/codemap.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/dbgbmap.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/dbgtrace.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/plot.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/tailpipe.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/treemap.py | 45 ++++++++++++++++++++++++++++++++++----------- scripts/watch.py | 45 ++++++++++++++++++++++++++++++++++----------- 7 files changed, 238 insertions(+), 77 deletions(-) diff --git a/scripts/codemap.py b/scripts/codemap.py index c7d787e9..02ff1aab 100755 --- a/scripts/codemap.py +++ b/scripts/codemap.py @@ -88,6 +88,7 @@ else: else: self.add_watch(path, flags) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -140,7 +141,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -158,24 +161,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() def iself(path): diff --git a/scripts/dbgbmap.py b/scripts/dbgbmap.py index 2a5a6047..ef829a8e 100755 --- a/scripts/dbgbmap.py +++ b/scripts/dbgbmap.py @@ -3644,6 +3644,7 @@ else: else: self.add_watch(path, flags) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -3696,7 +3697,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -3714,24 +3717,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() # a representation of optionally key-mapped attrs diff --git a/scripts/dbgtrace.py b/scripts/dbgtrace.py index 8a462482..c8edfcf9 100755 --- a/scripts/dbgtrace.py +++ b/scripts/dbgtrace.py @@ -137,6 +137,7 @@ def rbydaddr(s): return tuple(addr) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -189,7 +190,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -207,24 +210,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() # a representation of optionally key-mapped attrs diff --git a/scripts/plot.py b/scripts/plot.py index 8df22cff..3539cc65 100755 --- a/scripts/plot.py +++ b/scripts/plot.py @@ -171,6 +171,7 @@ else: else: self.add_watch(path, flags) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -223,7 +224,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -241,24 +244,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() diff --git a/scripts/tailpipe.py b/scripts/tailpipe.py index ebceed67..d218961c 100755 --- a/scripts/tailpipe.py +++ b/scripts/tailpipe.py @@ -34,6 +34,7 @@ def openio(path, mode='r', buffering=-1): else: return open(path, mode, buffering) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -86,7 +87,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -104,24 +107,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() diff --git a/scripts/treemap.py b/scripts/treemap.py index 247727e8..b3405bf0 100755 --- a/scripts/treemap.py +++ b/scripts/treemap.py @@ -81,6 +81,7 @@ else: else: self.add_watch(path, flags) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -133,7 +134,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -151,24 +154,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush() # parse different data representations diff --git a/scripts/watch.py b/scripts/watch.py index 8672a01b..ef12b34c 100755 --- a/scripts/watch.py +++ b/scripts/watch.py @@ -72,6 +72,7 @@ else: else: self.add_watch(path, flags) +# a pseudo-stdout ring buffer class RingIO: def __init__(self, maxlen=None, head=False): self.maxlen = maxlen @@ -124,7 +125,9 @@ class RingIO: if lines[-1]: self.tail.write(lines[-1]) + # keep track of maximum drawn canvas canvas_lines = 1 + def draw(self): # did terminal size change? self.resize(self.maxlen) @@ -142,24 +145,44 @@ class RingIO: else: lines.popleft() - # first thing first, give ourself a canvas + # build up the redraw in memory first and render in a single + # write call, this minimizes flickering caused by the cursor + # jumping around + canvas = [] + + # hide the cursor + canvas.append('\x1b[?25l') + + # give ourself a canvas while RingIO.canvas_lines < len(lines): - sys.stdout.write('\n') + canvas.append('\n') RingIO.canvas_lines += 1 # write lines from top to bottom so later lines overwrite earlier - # lines, note [xA/[xB stop at terminal boundaries + # lines, note xA/xB stop at terminal boundaries for i, line in enumerate(lines): - # move cursor, clear line, disable/reenable line wrapping - sys.stdout.write('\r') + # move to col 0 + canvas.append('\r') + # move up to line if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dA' % (len(lines)-1-i)) - sys.stdout.write('\x1b[K') - sys.stdout.write('\x1b[?7l') - sys.stdout.write(line) - sys.stdout.write('\x1b[?7h') + canvas.append('\x1b[%dA' % (len(lines)-1-i)) + # clear line + canvas.append('\x1b[K') + # disable line wrap + canvas.append('\x1b[?7l') + # print the line + canvas.append(line) + # enable line wrap + canvas.append('\x1b[?7h') # enable line wrap + # move back down if len(lines)-1-i > 0: - sys.stdout.write('\x1b[%dB' % (len(lines)-1-i)) + canvas.append('\x1b[%dB' % (len(lines)-1-i)) + + # show the cursor again + canvas.append('\x1b[?25h') + + # write to stdout and flush + sys.stdout.write(''.join(canvas)) sys.stdout.flush()