From 5b49a12a60224826d593f9d45e91bc61c6d85d9a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 2 Nov 2019 10:28:58 +0100 Subject: [PATCH] first attempt at multibar, working without stdout/stderr redirection --- progressbar/bar.py | 21 +++++--- progressbar/utils.py | 111 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 12 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 1549e86f..97a7882d 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -33,6 +33,7 @@ class ProgressBarMixinBase(object): def __init__(self, **kwargs): self._finished = False + self.offset = None def start(self, **kwargs): pass @@ -86,6 +87,17 @@ def __init__(self, fd=sys.stderr, is_terminal=None, line_breaks=None, ProgressBarMixinBase.__init__(self, **kwargs) + def add_rewrite_or_offset(self, line): + if self.line_breaks: + line = line.rstrip() + '\n' + elif self.offset: + # line = utils.add_ansi_offset(line, *self.offset) + line = utils.add_ansi_offset(line + '\n', *self.offset) + else: + line = '\r' + line + + return line + def update(self, *args, **kwargs): ProgressBarMixinBase.update(self, *args, **kwargs) @@ -93,12 +105,7 @@ def update(self, *args, **kwargs): if not self.enable_colors: line = utils.no_color(line) - if self.line_breaks: - line = line.rstrip() + '\n' - else: - line = '\r' + line - - self.fd.write(line) + self.fd.write(self.add_rewrite_or_offset(line)) def finish(self, *args, **kwargs): # pragma: no cover if self._finished: @@ -107,7 +114,7 @@ def finish(self, *args, **kwargs): # pragma: no cover end = kwargs.pop('end', '\n') ProgressBarMixinBase.finish(self, *args, **kwargs) - if end and not self.line_breaks: + if end and not self.line_breaks and not self.offset: self.fd.write(end) self.fd.flush() diff --git a/progressbar/utils.py b/progressbar/utils.py index a341ff8f..191c0b6b 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -6,6 +6,7 @@ import sys import logging import datetime +import functools from python_utils.time import timedelta_to_seconds, epoch, format_time from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size @@ -24,6 +25,8 @@ def is_terminal(fd, is_terminal=None): if is_terminal is None: if 'JPY_PARENT_PID' in os.environ: is_terminal = True + elif 'VIM' in os.environ: + is_terminal = True else: is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None) @@ -36,6 +39,69 @@ def is_terminal(fd, is_terminal=None): return is_terminal +def ansi_esc(value, command): + return u'\u001B[{}{}'.format(value, command) + + +class ANSI: + + # For more options: + # https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_output_sequences + + def esc(command, default_value=None): + def _esc(value=default_value): + return u'\u001B[{}{}'.format(value, command) + return u'\u001B[{}{}{}{}'.format(value, command, value, command) + + return _esc + + def y(line, y0, y1=None): + a, b = ANSI.up, ANSI.down + if y0 < 0: + a, b = b, a + + if y1 is None: + y1 = y0 + + return a(y0) + line + b(y1) + + def x(line, x0, x1=None): + a, b = ANSI.left, ANSI.right + if x0 < 0: + a, b = b, a + + if x1 is None: + x1 = x0 + + return a(x0) + line + b(x1) + + up = esc('A') + up = esc('F') + down = esc('B') + down = esc('E') + right = esc('C') + left = esc('D') + begin_of_line = esc('G', 1) + clear = esc('K', 2) + # clear = esc('J', 1) + + +def add_ansi_offset(line, x0=0, y0=0, x1=None, y1=None): + + line = '({x0},{y0})({x1},{y1}) {line}'.format( + x0=x0, y0=y0, x1=x1, y1=y0, line=line) + + # line = ANSI.clear() + line + + if x0 or x1: + line = ANSI.x(line, x0, x1) + + if y0 or y1: + line = ANSI.y(line, y0, y1) + + return line + + def deltas_to_seconds(*deltas, **kwargs): # default=ValueError): ''' Convert timedeltas and seconds as int to seconds as float while coalescing @@ -133,20 +199,55 @@ def env_flag(name, default=None): class WrappingIO: - def __init__(self, target, capturing=False, listeners=set()): + def __init__(self, target, capturing=False, listeners=None): self.buffer = six.StringIO() self.target = target self.capturing = capturing - self.listeners = listeners + self.listeners = list(listeners or []) self.needs_clear = False + self.update_offset() + + def update_offset(self): + x = y = None + for listener in self.listeners: + if listener.offset: + x_new, y_new = listener.offset + x = max(x, x_new) + y = max(y, y_new) + + if x is None and y is None: + offset = None + else: + offset = x or 0, y or 0 + + self.offset = offset def write(self, value): + # value = ANSI.up(4) + value + ANSI.down(4) + + if self.offset: + ... + # value = ANSI.up(4) + value + ANSI.down(1) + # value = add_ansi_offset(value, *self.offset) + # print('writing %r\n\n\n\n\n' % value, file=sys.__stderr__) + if self.capturing: - self.buffer.write(value) + # value = ANSI.y(value + '\n', 4, 1) + # if '\n' in value: + # value = value + 2 * '\n' + ANSI.up(2) + + self.buffer.write(ANSI.up(4)) + self.buffer.write(value + '\n' * 3) if '\n' in value: + # self.buffer.write('\n') + # self.buffer.write(ANSI.up(3)) + # self.buffer.write('\n' + ANSI.up(1)) + self.needs_clear = not self.offset self.needs_clear = True for listener in self.listeners: # pragma: no branch listener.update() + + self.buffer.write(ANSI.down(4)) else: self.target.write(value) @@ -174,7 +275,7 @@ def __init__(self): self.wrapped_stderr = 0 self.wrapped_excepthook = 0 self.capturing = 0 - self.listeners = set() + self.listeners = [] if env_flag('WRAP_STDOUT', default=False): # pragma: no cover self.wrap_stdout() @@ -184,7 +285,7 @@ def __init__(self): def start_capturing(self, bar=None): if bar: # pragma: no branch - self.listeners.add(bar) + self.listeners.append(bar) self.capturing += 1 self.update_capturing()