spreadsheet

terminal spreadsheet application
git clone https://git.ce9e.org/spreadsheet.git

commit
864914d7aa02b1ac360dfc58fa39fc9c0eacf39c
parent
13b37062afeec5a888192cdae8601c0d58210644
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-07-05 18:27
boon UI

Diffstat

M sheet/__main__.py 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
M sheet/term.py 36 ------------------------------------

2 files changed, 122 insertions, 40 deletions


diff --git a/sheet/__main__.py b/sheet/__main__.py

@@ -1,9 +1,127 @@
    1     1 import sys
    2     2 
   -1     3 import boon
   -1     4 
    3     5 from .csv import load_csv
    4    -1 from .term import render
   -1     6 from .expression import x2col
   -1     7 from .term import align_center
   -1     8 from .term import align_left
   -1     9 from .term import align_right
   -1    10 from .term import red
   -1    11 from .term import invert
   -1    12 
   -1    13 
   -1    14 def to_cell(value: float|int|str|None|Exception, width: int) -> str:
   -1    15     if isinstance(value, float):
   -1    16         return align_right(str(value), width)
   -1    17     elif isinstance(value, int):
   -1    18         return align_right(str(value), width)
   -1    19     elif isinstance(value, str):
   -1    20         return align_left(value, width)
   -1    21     elif value is None:
   -1    22         return ' ' * width
   -1    23     elif isinstance(value, Exception):
   -1    24         return red(align_left(repr(value), width))
   -1    25 
   -1    26 
   -1    27 class App(boon.App):
   -1    28     def __init__(self):
   -1    29         super().__init__()
   -1    30         with open(sys.argv[1]) as fh:
   -1    31             self.sheet = load_csv(fh)
   -1    32         self.x0 = 0
   -1    33         self.y0 = 0
   -1    34         self.cursor_x = 0
   -1    35         self.cursor_y = 0
   -1    36         self.widths = {}
   -1    37 
   -1    38     def scroll_into_view(self, rows, cols):
   -1    39         if self.cursor_y < self.y0:
   -1    40             self.y0 = self.cursor_y
   -1    41         elif self.cursor_y > self.y0 + rows - 3:
   -1    42             self.y0 = self.cursor_y - rows + 3
   -1    43 
   -1    44         if self.cursor_x < self.x0:
   -1    45             self.x0 = self.cursor_x
   -1    46         else:
   -1    47             widths = [self.get_width(x) for x in range(self.x0, self.cursor_x + 1)]
   -1    48             offset = 0
   -1    49             while sum(widths[offset:]) > cols:
   -1    50                 offset += 1
   -1    51             self.x0 += offset
   -1    52 
   -1    53     def render(self, rows, cols):
   -1    54         self.scroll_into_view(rows, cols)
   -1    55 
   -1    56         lines = []
   -1    57 
   -1    58         lines.append(' ' * 4)
   -1    59         x = self.x0
   -1    60         while len(lines[-1]) < cols:
   -1    61             row_head = align_center(x2col(x), self.get_width(x))
   -1    62             if x == self.cursor_x:
   -1    63                 row_head = invert(row_head)
   -1    64             lines[-1] += row_head
   -1    65             x += 1
   -1    66 
   -1    67         for dy in range(rows - 2):
   -1    68             y = self.y0 + dy
   -1    69             # FIXME: col_head vanishes
   -1    70             col_head = align_right(str(y + 1), 4)
   -1    71             if y == self.cursor_y:
   -1    72                 col_head = invert(col_head)
   -1    73             lines.append(col_head)
   -1    74 
   -1    75             x = self.x0
   -1    76             # FIXME: do not count ANSI codes for length
   -1    77             # FIXME; lines are longer than they should be (we stop once we are >=)
   -1    78             while len(lines[-1]) < cols:
   -1    79                 value = self.sheet.get_value((x, y))
   -1    80                 cell = to_cell(value, self.get_width(x))
   -1    81                 if x == self.cursor_x and y == self.cursor_y:
   -1    82                     cell = invert(cell)
   -1    83                 lines[-1] += cell
   -1    84                 x += 1
   -1    85 
   -1    86         lines.append(self.sheet.get_raw((self.cursor_x, self.cursor_y)))
   -1    87 
   -1    88         return lines
   -1    89 
   -1    90     def get_width(self, x):
   -1    91         return self.widths.get(x, 10)
   -1    92 
   -1    93     def set_width(self, x, value):
   -1    94         self.widths[x] = max(value, 3)
   -1    95 
   -1    96     def change_width(self, x, d):
   -1    97         old = self.get_width(x)
   -1    98         self.set_width(x, old + d)
   -1    99 
   -1   100     def on_key(self, key):
   -1   101         if key == 'q':
   -1   102             self.running = False
   -1   103         elif key == boon.KEY_DOWN:
   -1   104             self.cursor_y += 1
   -1   105         elif key == boon.KEY_UP:
   -1   106             self.cursor_y = max(self.cursor_y - 1, 0)
   -1   107         elif key == boon.KEY_NPAGE:
   -1   108             self.cursor_y += 20  # TODO: relativ to rows
   -1   109         elif key == boon.KEY_PPAGE:
   -1   110             self.cursor_y = max(self.cursor_y - 20, 0)
   -1   111         elif key == boon.KEY_RIGHT:
   -1   112             self.cursor_x += 1
   -1   113         elif key == boon.KEY_LEFT:
   -1   114             self.cursor_x = max(self.cursor_x - 1, 0)
   -1   115         elif key == '>':
   -1   116             self.change_width(self.cursor_x, 1)
   -1   117         elif key == '<':
   -1   118             self.change_width(self.cursor_x, -1)
   -1   119         elif key == '=':
   -1   120             pass
   -1   121             # self.set_width(self.cursor_x, max()  # TODO auto width
   -1   122         elif key == '\n':
   -1   123             pass  # TODO: edit
    5   124 
    6    -1 with open(sys.argv[1]) as fh:
    7    -1     sheet = load_csv(fh)
    8   125 
    9    -1 print(render(sheet, 80, 40, (0, 0), 10))
   -1   126 app = App()
   -1   127 app.run()

diff --git a/sheet/term.py b/sheet/term.py

@@ -1,6 +1,3 @@
    1    -1 from .expression import x2col
    2    -1 
    3    -1 
    4     1 def align_right(s, width):
    5     2     if len(s) > width:
    6     3         s = '###'
@@ -26,36 +23,3 @@ def red(s):
   26    23 
   27    24 def invert(s):
   28    25     return f'\033[7m{s}\033[0m'
   29    -1 
   30    -1 
   31    -1 def to_cell(value: float|int|str|None|Exception, width: int) -> str:
   32    -1     if isinstance(value, float):
   33    -1         return align_right(str(value), width)
   34    -1     elif isinstance(value, int):
   35    -1         return align_right(str(value), width)
   36    -1     elif isinstance(value, str):
   37    -1         return align_left(value, width)
   38    -1     elif value is None:
   39    -1         return ' ' * width
   40    -1     elif isinstance(value, Exception):
   41    -1         return red(align_left(repr(value), width))
   42    -1 
   43    -1 
   44    -1 def render(sheet, width, height, cell_offset, cell_width):
   45    -1     x0, y0 = cell_offset
   46    -1     rows = []
   47    -1     w = width // cell_width
   48    -1     rows.append([
   49    -1         ' ' * cell_width,
   50    -1     ] + [
   51    -1         align_center(x2col(x0 + dx), cell_width)
   52    -1         for dx in range(w - 1)
   53    -1     ])
   54    -1     for dy in range(height - 1):
   55    -1         rows.append([
   56    -1             align_right(str(y0 + dy + 1), cell_width),
   57    -1         ] + [
   58    -1             to_cell(sheet.get_value((x0 + dx, y0 + dy)), cell_width)
   59    -1             for dx in range(w - 1)
   60    -1         ])
   61    -1     return '\n'.join([''.join(row) for row in rows])