From 53b37bb2662f3194d665c98380ee5cdbe63c59f3 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Thu, 4 Oct 2018 00:47:21 +0300 Subject: [PATCH] show: use asciimatics to provide scrollable graph Fixes #1177 Signed-off-by: Ruslan Kuprieiev --- dvc/dagascii.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++-- requirements.txt | 1 + setup.py | 1 + 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/dvc/dagascii.py b/dvc/dagascii.py index ec6689fa8..c39a2a4dd 100644 --- a/dvc/dagascii.py +++ b/dvc/dagascii.py @@ -1,4 +1,9 @@ +import sys import math +import select + +from asciimatics.screen import Screen +from asciimatics.event import KeyboardEvent from grandalf.graphs import Vertex, Edge, Graph from grandalf.layouts import SugiyamaLayout @@ -12,11 +17,84 @@ class AsciiCanvas(object): self.cols = cols self.lines = lines + self.canvas = [[' '] * cols for l in range(lines)] def draw(self): - for line in self.canvas: - print(''.join(line)) + if sys.stdout.isatty(): + Screen.wrapper(self._do_draw) + else: + for line in self.canvas: + print(''.join(line)) + + def _do_draw(self, screen): + offset_x = 0 + offset_y = 0 + smaxrow, smaxcol = screen.dimensions + assert smaxrow > 1 + assert smaxcol > 1 + smaxrow -= 1 + smaxcol -= 1 + + if self.lines + 1 > smaxrow: + max_y = self.lines + 1 - smaxrow + else: + max_y = 0 + + if self.cols + 1 > smaxcol: + max_x = self.cols + 1 - smaxcol + else: + max_x = 0 + + while True: + for y in range(smaxrow + 1): + y_index = offset_y + y + line = [] + for x in range(smaxcol + 1): + x_index = offset_x + x + if len(self.canvas) > y_index \ + and len(self.canvas[y_index]) > x_index: + line.append(self.canvas[y_index][x_index]) + else: + line.append(' ') + assert len(line) == (smaxcol + 1) + screen.print_at(''.join(line), 0, y) + + screen.refresh() + + # NOTE: get_event() doesn't block by itself, + # so we have to do the blocking ourselves. + select.select([sys.stdin], [], [], None) + + event = screen.get_event() + if not isinstance(event, KeyboardEvent): + continue + + k = event.key_code + if k == screen.KEY_DOWN: + offset_y += 1 + elif k == screen.KEY_PAGE_DOWN: + offset_y += smaxrow + elif k == screen.KEY_UP: + offset_y -= 1 + elif k == screen.KEY_PAGE_UP: + offset_y -= smaxrow + elif k == screen.KEY_RIGHT: + offset_x += 1 + elif k == screen.KEY_LEFT: + offset_x -= 1 + elif k == ord('q'): + break + + if offset_y > max_y: + offset_y = max_y + elif offset_y < 0: + offset_y = 0 + + if offset_x > max_x: + offset_x = max_x + elif offset_x < 0: + offset_x = 0 def point(self, x, y, char): assert x >= 0 diff --git a/requirements.txt b/requirements.txt index 1cd3fa88c..13db7a5c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ wheel>=0.31.1 futures>=3.2.0; python_version == "2.7" grandalf==0.6 pydot>=1.2.4 +asciimatics>=1.10.0 diff --git a/setup.py b/setup.py index 62b03b7a0..98b4889f1 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ install_requires = [ "jsonpath-rw==1.4.0", "requests>=2.18.4", "grandalf==0.6", + "asciimatics>=1.10.0", ] # Extra dependencies for remote integrations