omlish 0.0.0.dev186__py3-none-any.whl → 0.0.0.dev188__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/logs/color.py +6 -6
- omlish/term/__init__.py +0 -0
- omlish/{term.py → term/codes.py} +1 -139
- omlish/term/progressbar.py +139 -0
- omlish/term/vt100.py +271 -0
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/RECORD +12 -9
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev186.dist-info → omlish-0.0.0.dev188.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/logs/color.py
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
import logging
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from .. import
|
5
|
+
from ..term import codes as tc
|
6
6
|
from .standard import StandardLogFormatter
|
7
7
|
|
8
8
|
|
9
9
|
class ColorLogFormatter(StandardLogFormatter):
|
10
|
-
LEVEL_COLORS: ta.Mapping[int,
|
11
|
-
logging.WARNING:
|
12
|
-
logging.ERROR:
|
13
|
-
logging.CRITICAL:
|
10
|
+
LEVEL_COLORS: ta.Mapping[int, tc.SGRs.FG] = {
|
11
|
+
logging.WARNING: tc.SGRs.FG.BRIGHT_YELLOW,
|
12
|
+
logging.ERROR: tc.SGRs.FG.BRIGHT_RED,
|
13
|
+
logging.CRITICAL: tc.SGRs.FG.BRIGHT_RED,
|
14
14
|
}
|
15
15
|
|
16
16
|
def formatMessage(self, record):
|
@@ -20,5 +20,5 @@ class ColorLogFormatter(StandardLogFormatter):
|
|
20
20
|
except KeyError:
|
21
21
|
pass
|
22
22
|
else:
|
23
|
-
buf =
|
23
|
+
buf = tc.SGR(c) + buf + tc.SGR(tc.SGRs.RESET)
|
24
24
|
return buf
|
omlish/term/__init__.py
ADDED
File without changes
|
omlish/{term.py → term/codes.py}
RENAMED
@@ -1,13 +1,8 @@
|
|
1
1
|
import enum
|
2
2
|
import re
|
3
|
-
import sys
|
4
|
-
import time
|
5
3
|
import typing as ta
|
6
4
|
|
7
|
-
from
|
8
|
-
|
9
|
-
|
10
|
-
T = ta.TypeVar('T')
|
5
|
+
from .. import lang
|
11
6
|
|
12
7
|
|
13
8
|
##
|
@@ -233,139 +228,6 @@ BG24_RGB = ControlSequence(
|
|
233
228
|
##
|
234
229
|
|
235
230
|
|
236
|
-
class ProgressBar:
|
237
|
-
"""
|
238
|
-
TODO:
|
239
|
-
- ProgressBarRenderer
|
240
|
-
- right-justify
|
241
|
-
- ProgressBarGroup
|
242
|
-
- animate
|
243
|
-
"""
|
244
|
-
|
245
|
-
def __init__(
|
246
|
-
self,
|
247
|
-
total: int | None = None,
|
248
|
-
*,
|
249
|
-
length: int = 40,
|
250
|
-
interval: float = .2,
|
251
|
-
start_time: float | None = None,
|
252
|
-
out: ta.TextIO | None = None,
|
253
|
-
) -> None:
|
254
|
-
super().__init__()
|
255
|
-
|
256
|
-
self._total = total
|
257
|
-
self._length = length
|
258
|
-
self._interval = interval
|
259
|
-
if start_time is None:
|
260
|
-
start_time = time.time()
|
261
|
-
self._start_time = start_time
|
262
|
-
if out is None:
|
263
|
-
out = sys.stdout
|
264
|
-
self._out = out
|
265
|
-
|
266
|
-
self._i = 0
|
267
|
-
self._elapsed = 0.
|
268
|
-
self._last_print = 0.
|
269
|
-
|
270
|
-
def render(
|
271
|
-
self,
|
272
|
-
*,
|
273
|
-
complete: bool = False,
|
274
|
-
) -> str:
|
275
|
-
iter_per_sec = self._i / self._elapsed if self._elapsed > 0 else 0
|
276
|
-
|
277
|
-
if self._total is not None:
|
278
|
-
remaining = (self._total - self._i) / iter_per_sec if iter_per_sec > 0 else 0
|
279
|
-
done = int(self._length * self._i / self._total)
|
280
|
-
|
281
|
-
bar = f'[{"█" * done}{"." * (self._length - done)}]'
|
282
|
-
info_parts = [
|
283
|
-
f'{self._i}/{self._total}',
|
284
|
-
f'{iter_per_sec:.2f} it/s',
|
285
|
-
f'{self._elapsed:.2f}s elapsed',
|
286
|
-
f'{remaining:.2f}s left',
|
287
|
-
]
|
288
|
-
|
289
|
-
else:
|
290
|
-
bar = f'[{("█" if complete else "?") * self._length}]'
|
291
|
-
info_parts = [
|
292
|
-
f'{self._i}',
|
293
|
-
f'{iter_per_sec:.2f} it/s',
|
294
|
-
f'{self._elapsed:.2f}s elapsed',
|
295
|
-
]
|
296
|
-
|
297
|
-
info = ' | '.join(info_parts)
|
298
|
-
return f'{bar} {info}'
|
299
|
-
|
300
|
-
def print(
|
301
|
-
self,
|
302
|
-
*,
|
303
|
-
now: float | None = None,
|
304
|
-
**kwargs: ta.Any,
|
305
|
-
) -> None:
|
306
|
-
if now is None:
|
307
|
-
now = time.time()
|
308
|
-
|
309
|
-
line = self.render(**kwargs)
|
310
|
-
self._out.write(f'\033[2K\033[G{line}')
|
311
|
-
self._out.flush()
|
312
|
-
|
313
|
-
self._last_print = now
|
314
|
-
|
315
|
-
def update(
|
316
|
-
self,
|
317
|
-
n: int = 1,
|
318
|
-
*,
|
319
|
-
now: float | None = None,
|
320
|
-
silent: bool = False,
|
321
|
-
) -> None:
|
322
|
-
if now is None:
|
323
|
-
now = time.time()
|
324
|
-
|
325
|
-
self._i += n
|
326
|
-
self._elapsed = now - self._start_time
|
327
|
-
|
328
|
-
if not silent:
|
329
|
-
if now - self._last_print >= self._interval:
|
330
|
-
self.print(now=now)
|
331
|
-
|
332
|
-
|
333
|
-
def progress_bar(
|
334
|
-
seq: ta.Iterable[T],
|
335
|
-
*,
|
336
|
-
no_tty_check: bool = False,
|
337
|
-
total: int | None = None,
|
338
|
-
out: ta.TextIO | None = None,
|
339
|
-
**kwargs: ta.Any,
|
340
|
-
) -> ta.Generator[T, None, None]:
|
341
|
-
if out is None:
|
342
|
-
out = sys.stdout
|
343
|
-
|
344
|
-
if not no_tty_check and not out.isatty():
|
345
|
-
yield from seq
|
346
|
-
return
|
347
|
-
|
348
|
-
if total is None:
|
349
|
-
if isinstance(seq, ta.Sized):
|
350
|
-
total = len(seq)
|
351
|
-
|
352
|
-
pb = ProgressBar(
|
353
|
-
total=total,
|
354
|
-
out=out,
|
355
|
-
**kwargs,
|
356
|
-
)
|
357
|
-
|
358
|
-
for item in seq:
|
359
|
-
pb.update()
|
360
|
-
yield item
|
361
|
-
|
362
|
-
pb.print(complete=True)
|
363
|
-
out.write('\n')
|
364
|
-
|
365
|
-
|
366
|
-
##
|
367
|
-
|
368
|
-
|
369
231
|
def main() -> None:
|
370
232
|
import sys
|
371
233
|
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import sys
|
2
|
+
import time
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
|
6
|
+
T = ta.TypeVar('T')
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
class ProgressBar:
|
13
|
+
"""
|
14
|
+
TODO:
|
15
|
+
- ProgressBarRenderer
|
16
|
+
- right-justify
|
17
|
+
- ProgressBarGroup
|
18
|
+
- animate
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
total: int | None = None,
|
24
|
+
*,
|
25
|
+
length: int = 40,
|
26
|
+
interval: float = .2,
|
27
|
+
start_time: float | None = None,
|
28
|
+
out: ta.TextIO | None = None,
|
29
|
+
) -> None:
|
30
|
+
super().__init__()
|
31
|
+
|
32
|
+
self._total = total
|
33
|
+
self._length = length
|
34
|
+
self._interval = interval
|
35
|
+
if start_time is None:
|
36
|
+
start_time = time.time()
|
37
|
+
self._start_time = start_time
|
38
|
+
if out is None:
|
39
|
+
out = sys.stdout
|
40
|
+
self._out = out
|
41
|
+
|
42
|
+
self._i = 0
|
43
|
+
self._elapsed = 0.
|
44
|
+
self._last_print = 0.
|
45
|
+
|
46
|
+
def render(
|
47
|
+
self,
|
48
|
+
*,
|
49
|
+
complete: bool = False,
|
50
|
+
) -> str:
|
51
|
+
iter_per_sec = self._i / self._elapsed if self._elapsed > 0 else 0
|
52
|
+
|
53
|
+
if self._total is not None:
|
54
|
+
remaining = (self._total - self._i) / iter_per_sec if iter_per_sec > 0 else 0
|
55
|
+
done = int(self._length * self._i / self._total)
|
56
|
+
|
57
|
+
bar = f'[{"█" * done}{"." * (self._length - done)}]'
|
58
|
+
info_parts = [
|
59
|
+
f'{self._i}/{self._total}',
|
60
|
+
f'{iter_per_sec:.2f} it/s',
|
61
|
+
f'{self._elapsed:.2f}s elapsed',
|
62
|
+
f'{remaining:.2f}s left',
|
63
|
+
]
|
64
|
+
|
65
|
+
else:
|
66
|
+
bar = f'[{("█" if complete else "?") * self._length}]'
|
67
|
+
info_parts = [
|
68
|
+
f'{self._i}',
|
69
|
+
f'{iter_per_sec:.2f} it/s',
|
70
|
+
f'{self._elapsed:.2f}s elapsed',
|
71
|
+
]
|
72
|
+
|
73
|
+
info = ' | '.join(info_parts)
|
74
|
+
return f'{bar} {info}'
|
75
|
+
|
76
|
+
def print(
|
77
|
+
self,
|
78
|
+
*,
|
79
|
+
now: float | None = None,
|
80
|
+
**kwargs: ta.Any,
|
81
|
+
) -> None:
|
82
|
+
if now is None:
|
83
|
+
now = time.time()
|
84
|
+
|
85
|
+
line = self.render(**kwargs)
|
86
|
+
self._out.write(f'\033[2K\033[G{line}')
|
87
|
+
self._out.flush()
|
88
|
+
|
89
|
+
self._last_print = now
|
90
|
+
|
91
|
+
def update(
|
92
|
+
self,
|
93
|
+
n: int = 1,
|
94
|
+
*,
|
95
|
+
now: float | None = None,
|
96
|
+
silent: bool = False,
|
97
|
+
) -> None:
|
98
|
+
if now is None:
|
99
|
+
now = time.time()
|
100
|
+
|
101
|
+
self._i += n
|
102
|
+
self._elapsed = now - self._start_time
|
103
|
+
|
104
|
+
if not silent:
|
105
|
+
if now - self._last_print >= self._interval:
|
106
|
+
self.print(now=now)
|
107
|
+
|
108
|
+
|
109
|
+
def progress_bar(
|
110
|
+
seq: ta.Iterable[T],
|
111
|
+
*,
|
112
|
+
no_tty_check: bool = False,
|
113
|
+
total: int | None = None,
|
114
|
+
out: ta.TextIO | None = None,
|
115
|
+
**kwargs: ta.Any,
|
116
|
+
) -> ta.Generator[T, None, None]:
|
117
|
+
if out is None:
|
118
|
+
out = sys.stdout
|
119
|
+
|
120
|
+
if not no_tty_check and not out.isatty():
|
121
|
+
yield from seq
|
122
|
+
return
|
123
|
+
|
124
|
+
if total is None:
|
125
|
+
if isinstance(seq, ta.Sized):
|
126
|
+
total = len(seq)
|
127
|
+
|
128
|
+
pb = ProgressBar(
|
129
|
+
total=total,
|
130
|
+
out=out,
|
131
|
+
**kwargs,
|
132
|
+
)
|
133
|
+
|
134
|
+
for item in seq:
|
135
|
+
pb.update()
|
136
|
+
yield item
|
137
|
+
|
138
|
+
pb.print(complete=True)
|
139
|
+
out.write('\n')
|
omlish/term/vt100.py
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import string
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.check import check
|
6
|
+
|
7
|
+
|
8
|
+
T = ta.TypeVar('T')
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
@dc.dataclass()
|
15
|
+
class Cell:
|
16
|
+
"""Represents a single character cell in the terminal with attributes."""
|
17
|
+
|
18
|
+
char: str = ' '
|
19
|
+
|
20
|
+
fg: str = 'default'
|
21
|
+
bg: str = 'default'
|
22
|
+
|
23
|
+
bold: bool = False
|
24
|
+
underline: bool = False
|
25
|
+
reverse: bool = False
|
26
|
+
|
27
|
+
def __repr__(self):
|
28
|
+
return f'Cell({self.char!r}, bold={self.bold}, underline={self.underline})'
|
29
|
+
|
30
|
+
|
31
|
+
class Vt100Terminal:
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
rows: int = 24,
|
35
|
+
cols: int = 80,
|
36
|
+
) -> None:
|
37
|
+
super().__init__()
|
38
|
+
|
39
|
+
self._rows = rows
|
40
|
+
self._cols = cols
|
41
|
+
|
42
|
+
# 2D array of Cell objects
|
43
|
+
self._screen = [
|
44
|
+
[Cell() for _ in range(cols)]
|
45
|
+
for _ in range(rows)
|
46
|
+
]
|
47
|
+
|
48
|
+
# Current cursor position (row, col), 0-based internally
|
49
|
+
self._cursor_row = 0
|
50
|
+
self._cursor_col = 0
|
51
|
+
|
52
|
+
# Current text attributes
|
53
|
+
self._current_fg = 'default'
|
54
|
+
self._current_bg = 'default'
|
55
|
+
self._current_bold = False
|
56
|
+
self._current_underline = False
|
57
|
+
self._current_reverse = False
|
58
|
+
|
59
|
+
# Parser state
|
60
|
+
self._state: ta.Literal['normal', 'esc', 'csi'] = 'normal'
|
61
|
+
self._escape_buffer: list[str] = []
|
62
|
+
|
63
|
+
def parse_byte(self, byte: int | str) -> None:
|
64
|
+
"""Parse a single byte of input (as an integer or a single-character string)."""
|
65
|
+
|
66
|
+
if isinstance(byte, int):
|
67
|
+
byte = chr(byte)
|
68
|
+
|
69
|
+
if self._state == 'normal':
|
70
|
+
if byte == '\x1b':
|
71
|
+
# Start of escape sequence
|
72
|
+
self._state = 'esc'
|
73
|
+
self._escape_buffer = [byte]
|
74
|
+
elif byte == '\r':
|
75
|
+
# Carriage return
|
76
|
+
self._cursor_col = 0
|
77
|
+
elif byte == '\n':
|
78
|
+
# Line feed
|
79
|
+
self._cursor_row = min(self._cursor_row + 1, self._rows - 1)
|
80
|
+
elif byte == '\b':
|
81
|
+
# Backspace
|
82
|
+
self._cursor_col = max(self._cursor_col - 1, 0)
|
83
|
+
elif byte in string.printable and byte not in ['\x0b', '\x0c']:
|
84
|
+
# Printable ASCII (excluding form feeds, vertical tabs, etc.)
|
85
|
+
self._put_char(byte)
|
86
|
+
else:
|
87
|
+
# Ignore other control characters
|
88
|
+
pass
|
89
|
+
|
90
|
+
elif self._state == 'esc':
|
91
|
+
self._escape_buffer.append(byte)
|
92
|
+
if byte == '[':
|
93
|
+
# Move to CSI state (Control Sequence Introducer)
|
94
|
+
self._state = 'csi'
|
95
|
+
# Some escape codes like ESCc, ESC7, etc. are possible but we'll ignore or handle them in a minimal way. If
|
96
|
+
# no further instructions, revert to normal
|
97
|
+
elif len(self._escape_buffer) == 2:
|
98
|
+
# We only support ESC + [ in this example, so revert
|
99
|
+
self._state = 'normal'
|
100
|
+
|
101
|
+
elif self._state == 'csi':
|
102
|
+
self._escape_buffer.append(byte)
|
103
|
+
# Check if this byte ends the sequence (typical final bytes are A-Z, @, etc.)
|
104
|
+
if byte.isalpha() or byte in '@`~':
|
105
|
+
# We have a complete CSI sequence: parse it
|
106
|
+
self._parse_csi(''.join(self._escape_buffer))
|
107
|
+
# Reset state
|
108
|
+
self._state = 'normal'
|
109
|
+
self._escape_buffer = []
|
110
|
+
|
111
|
+
def _put_char(self, ch: str) -> None:
|
112
|
+
"""Write a character at the current cursor position, and advance the cursor."""
|
113
|
+
|
114
|
+
if 0 <= self._cursor_row < self._rows and 0 <= self._cursor_col < self._cols:
|
115
|
+
cell = self._screen[self._cursor_row][self._cursor_col]
|
116
|
+
cell.char = ch
|
117
|
+
cell.fg = self._current_fg
|
118
|
+
cell.bg = self._current_bg
|
119
|
+
cell.bold = self._current_bold
|
120
|
+
cell.underline = self._current_underline
|
121
|
+
cell.reverse = self._current_reverse
|
122
|
+
|
123
|
+
self._cursor_col += 1
|
124
|
+
if self._cursor_col >= self._cols:
|
125
|
+
self._cursor_col = 0
|
126
|
+
self._cursor_row = min(self._cursor_row + 1, self._rows - 1)
|
127
|
+
|
128
|
+
def _parse_csi(self, seq: str) -> None:
|
129
|
+
"""
|
130
|
+
Parse a CSI (Control Sequence Introducer) escape sequence. Typically looks like: ESC [ parameters letter For
|
131
|
+
example: ESC [ 2 J, ESC [ 10 ; 20 H, etc.
|
132
|
+
"""
|
133
|
+
|
134
|
+
# seq includes the initial ESC[. e.g. "\x1b[10;20H"
|
135
|
+
# We'll strip ESC[ and parse what remains.
|
136
|
+
check.state(seq.startswith('\x1b['))
|
137
|
+
body = seq[2:] # everything after ESC[
|
138
|
+
|
139
|
+
# Find final character that determines the command (e.g. 'A', 'B', 'H', 'J', 'K', 'm', etc.)
|
140
|
+
final = body[-1]
|
141
|
+
params = body[:-1]
|
142
|
+
|
143
|
+
# Split params by semicolons, handle empty as '0'
|
144
|
+
if params.strip() == '':
|
145
|
+
numbers = [0]
|
146
|
+
else:
|
147
|
+
numbers = [int(x) if x.isdigit() else 0 for x in params.split(';')]
|
148
|
+
|
149
|
+
if final in 'ABCD':
|
150
|
+
# Cursor movement
|
151
|
+
n = numbers[0] if numbers else 1
|
152
|
+
if final == 'A':
|
153
|
+
# Up
|
154
|
+
self._cursor_row = max(self._cursor_row - n, 0)
|
155
|
+
elif final == 'B':
|
156
|
+
# Down
|
157
|
+
self._cursor_row = min(self._cursor_row + n, self._rows - 1)
|
158
|
+
elif final == 'C':
|
159
|
+
# Right
|
160
|
+
self._cursor_col = min(self._cursor_col + n, self._cols - 1)
|
161
|
+
elif final == 'D':
|
162
|
+
# Left
|
163
|
+
self._cursor_col = max(self._cursor_col - n, 0)
|
164
|
+
|
165
|
+
elif final in 'Hf':
|
166
|
+
# Cursor position
|
167
|
+
# CSI row;colH or CSI row;colf (1-based coords)
|
168
|
+
row = numbers[0] if len(numbers) > 0 else 1
|
169
|
+
col = numbers[1] if len(numbers) > 1 else 1
|
170
|
+
self._cursor_row = min(max(row - 1, 0), self._rows - 1)
|
171
|
+
self._cursor_col = min(max(col - 1, 0), self._cols - 1)
|
172
|
+
|
173
|
+
elif final == 'J':
|
174
|
+
# Erase display
|
175
|
+
# n=0 -> clear from cursor down,
|
176
|
+
# n=1 -> clear from cursor up,
|
177
|
+
# n=2 -> clear entire screen
|
178
|
+
n = numbers[0] if numbers else 0
|
179
|
+
if n == 2:
|
180
|
+
self._clear_screen()
|
181
|
+
elif n == 0:
|
182
|
+
self._clear_down()
|
183
|
+
elif n == 1:
|
184
|
+
self._clear_up()
|
185
|
+
# else: unsupported J mode, ignore
|
186
|
+
|
187
|
+
elif final == 'K':
|
188
|
+
# Erase line
|
189
|
+
# n=0 -> clear from cursor right
|
190
|
+
# n=1 -> clear from cursor left
|
191
|
+
# n=2 -> clear entire line
|
192
|
+
n = numbers[0] if numbers else 0
|
193
|
+
if n == 2:
|
194
|
+
self._clear_line(self._cursor_row)
|
195
|
+
elif n == 0:
|
196
|
+
self._clear_right(self._cursor_row, self._cursor_col)
|
197
|
+
elif n == 1:
|
198
|
+
self._clear_left(self._cursor_row, self._cursor_col)
|
199
|
+
# else: ignore
|
200
|
+
|
201
|
+
elif final == 'm':
|
202
|
+
# SGR - Select Graphic Rendition
|
203
|
+
# We handle a subset: 0 (reset), 1 (bold), 4 (underline), 7 (reverse)
|
204
|
+
# Colors could be extended, but here we keep it minimal
|
205
|
+
for code in numbers:
|
206
|
+
if code == 0:
|
207
|
+
self._current_fg = 'default'
|
208
|
+
self._current_bg = 'default'
|
209
|
+
self._current_bold = False
|
210
|
+
self._current_underline = False
|
211
|
+
self._current_reverse = False
|
212
|
+
elif code == 1:
|
213
|
+
self._current_bold = True
|
214
|
+
elif code == 4:
|
215
|
+
self._current_underline = True
|
216
|
+
elif code == 7:
|
217
|
+
self._current_reverse = True
|
218
|
+
# You can add more codes for color, etc.
|
219
|
+
else:
|
220
|
+
# Unsupported SGR code - ignore gracefully
|
221
|
+
pass
|
222
|
+
|
223
|
+
else:
|
224
|
+
# Unsupported final - ignore gracefully
|
225
|
+
pass
|
226
|
+
|
227
|
+
# hods for Erase Operations
|
228
|
+
|
229
|
+
def _clear_screen(self) -> None:
|
230
|
+
for r in range(self._rows):
|
231
|
+
for c in range(self._cols):
|
232
|
+
self._screen[r][c] = Cell()
|
233
|
+
|
234
|
+
def _clear_down(self) -> None:
|
235
|
+
"""Clear from cursor to the end of the screen."""
|
236
|
+
|
237
|
+
# Clear current line from cursor forward
|
238
|
+
self._clear_right(self._cursor_row, self._cursor_col)
|
239
|
+
|
240
|
+
# Clear all lines below cursor
|
241
|
+
for r in range(self._cursor_row + 1, self._rows):
|
242
|
+
self._clear_line(r)
|
243
|
+
|
244
|
+
def _clear_up(self) -> None:
|
245
|
+
"""Clear from the start of the screen up to the cursor."""
|
246
|
+
|
247
|
+
# Clear current line from start to cursor
|
248
|
+
self._clear_left(self._cursor_row, self._cursor_col)
|
249
|
+
|
250
|
+
# Clear all lines above cursor
|
251
|
+
for r in range(self._cursor_row):
|
252
|
+
self._clear_line(r)
|
253
|
+
|
254
|
+
def _clear_line(self, row: int) -> None:
|
255
|
+
for c in range(self._cols):
|
256
|
+
self._screen[row][c] = Cell()
|
257
|
+
|
258
|
+
def _clear_right(self, row: int, col: int) -> None:
|
259
|
+
for c in range(col, self._cols):
|
260
|
+
self._screen[row][c] = Cell()
|
261
|
+
|
262
|
+
def _clear_left(self, row: int, col: int) -> None:
|
263
|
+
for c in range(col + 1):
|
264
|
+
self._screen[row][c] = Cell()
|
265
|
+
|
266
|
+
# Debug/Utility Methods
|
267
|
+
|
268
|
+
def get_screen_as_strings(self) -> list[str]:
|
269
|
+
"""Return a list of strings representing each row (ignoring attributes). Useful for debugging/testing."""
|
270
|
+
|
271
|
+
return [''.join(cell.char for cell in row) for row in self._screen]
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=lRkBDFxlAbf6lN5upo3WSf-owW8YG1T21dfpbQL-XHM,7598
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=qa6ubyF59208I-I_fc4EQ_bi5Jw1Ytht_Nnwgez5qFg,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -13,7 +13,6 @@ omlish/runmodule.py,sha256=PWvuAaJ9wQQn6bx9ftEL3_d04DyotNn8dR_twm2pgw0,700
|
|
13
13
|
omlish/shlex.py,sha256=bsW2XUD8GiMTUTDefJejZ5AyqT1pTgWMPD0BMoF02jE,248
|
14
14
|
omlish/subprocesses.py,sha256=n6pk0nUaTFHzD_A6duyKNJ4ggncU7uNepfh_T90etHE,8671
|
15
15
|
omlish/sync.py,sha256=QJ79kxmIqDP9SeHDoZAf--DpFIhDQe1jACy8H4N0yZI,2928
|
16
|
-
omlish/term.py,sha256=EVHm3lEEIc9hT4f8BPmzbNUwlqZ8nrRpCwyQMN7LBm0,9313
|
17
16
|
omlish/antlr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
17
|
omlish/antlr/delimit.py,sha256=3Byvh9_Ip8ftM_SeSEmMbnNo1jrxk-xm8HnHDp_nDaI,3466
|
19
18
|
omlish/antlr/dot.py,sha256=uH2X7-8xNLYDQNJ30uW8ssv1MLkZSm07GsalcRuunYI,817
|
@@ -395,7 +394,7 @@ omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
|
|
395
394
|
omlish/logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
396
395
|
omlish/logs/abc.py,sha256=ho4ABKYMKX-V7g4sp1BByuOLzslYzLlQ0MESmjEpT-o,8005
|
397
396
|
omlish/logs/all.py,sha256=4Z6cNB0E1xbX0IOQWZEWLA0Jw-yyjhXa3PD_Nvfewu8,556
|
398
|
-
omlish/logs/color.py,sha256=
|
397
|
+
omlish/logs/color.py,sha256=CM-ceoPXs0j5_klnZDJkFCFRgHOToFzJyLjC6LsnPEk,665
|
399
398
|
omlish/logs/configs.py,sha256=XOc8rWxfPpPMxJESVD2mLCUoLtbQnGnZwvYhhqe7DD8,772
|
400
399
|
omlish/logs/filters.py,sha256=2noFRyBez3y519fpfsDSt1vo8wX-85b8sMXZi5o_xyE,208
|
401
400
|
omlish/logs/handlers.py,sha256=zgSnKQA5q9Fu7T0Nkd7twog9H1Wg9-bDCzz4_F1TOBo,319
|
@@ -545,6 +544,10 @@ omlish/sql/tabledefs/elements.py,sha256=lP_Ch19hKmiGYPQVeC8HpFaKdTYnXi2FfpfwKMxZ
|
|
545
544
|
omlish/sql/tabledefs/lower.py,sha256=YQf8gl1kxD5Fm-vOxV6G0Feh_D9PP1pYwz_vz6XjTPQ,1405
|
546
545
|
omlish/sql/tabledefs/marshal.py,sha256=j-Rz1HsiXmABv39-2VoJdzSSB3kbxqaVevbdkZWMyG8,504
|
547
546
|
omlish/sql/tabledefs/tabledefs.py,sha256=lIhvlt0pk6G7RZAtDFsFXm5j0l9BvRfnP7vNGeydHtE,816
|
547
|
+
omlish/term/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
548
|
+
omlish/term/codes.py,sha256=gqouA7KDyGzyCmmu8ejGhT_8XJGsejdTQh2pSCMbWAQ,6150
|
549
|
+
omlish/term/progressbar.py,sha256=TiwdmPSMa5jQj35i1NQURTWQGy4eWUNx_XiPM38JtvQ,3184
|
550
|
+
omlish/term/vt100.py,sha256=sYLddUSrubCJPnI6f2t5Pzp0tFGLLBKyYdDfKmkwREM,9363
|
548
551
|
omlish/testing/__init__.py,sha256=M_BQrcCHkoL-ZvE-UpQ8XxXNYRRawhjUz4rCJnAqM2A,152
|
549
552
|
omlish/testing/testing.py,sha256=TT2wwSzPZ_KhIvKxpM1qc1yHKD-LHDNgGrcr_h8vs7c,2895
|
550
553
|
omlish/testing/pytest/__init__.py,sha256=B2nyJrjIoNcEopbg0IZ5UUDs4OHmQ8qqElFJfGcDdas,257
|
@@ -573,9 +576,9 @@ omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
|
|
573
576
|
omlish/text/minja.py,sha256=KAmZ2POcLcxwF4DPKxdWa16uWxXmVz1UnJXLSwt4oZo,5761
|
574
577
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
575
578
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
576
|
-
omlish-0.0.0.
|
577
|
-
omlish-0.0.0.
|
578
|
-
omlish-0.0.0.
|
579
|
-
omlish-0.0.0.
|
580
|
-
omlish-0.0.0.
|
581
|
-
omlish-0.0.0.
|
579
|
+
omlish-0.0.0.dev188.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
580
|
+
omlish-0.0.0.dev188.dist-info/METADATA,sha256=B8AyLpz8d0yITN04acY73gPMv_SDjqP5Z7lGCaOwpS0,4264
|
581
|
+
omlish-0.0.0.dev188.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
582
|
+
omlish-0.0.0.dev188.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
583
|
+
omlish-0.0.0.dev188.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
584
|
+
omlish-0.0.0.dev188.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|