Repository: iterative/dvc

Issue:
show: consider using ncurses to provide horizontal and vertical scrolling
https://github.com/iterative/dvc/issues/1174#issuecomment-425821854

Probably need to utilize curses pad(e.g. newpad()).


Discussion:

Resolving patch:
diff --git a/dvc/dagascii.py b/dvc/dagascii.py
--- 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 @@ def __init__(self, cols, lines):
 
         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/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,7 @@
     "jsonpath-rw==1.4.0",
     "requests>=2.18.4",
     "grandalf==0.6",
+    "asciimatics>=1.10.0",
 ]
 
 # Extra dependencies for remote integrations


Test patch: