ibm5250 0.1.0.dev5__tar.gz → 0.1.0.dev7__tar.gz
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.
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/PKG-INFO +1 -1
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/pyproject.toml +1 -1
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/constants.py +11 -4
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/screen.py +25 -21
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/terminal.py +5 -3
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/uv.lock +1 -1
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/.github/copilot-instructions.md +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/.github/workflows/README.md +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/.github/workflows/publish.yml +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/.gitignore +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/LICENSE +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/README.md +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/scripts/compute_version.py +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/__init__.py +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/connection.py +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/data-stream-logic.md +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/datastream.py +0 -0
- {ibm5250-0.1.0.dev5 → ibm5250-0.1.0.dev7}/src/ibm5250/exceptions.py +0 -0
|
@@ -541,10 +541,17 @@ def decode_addr(hi: int, lo: int, cols: int = SCREEN_COLS) -> int:
|
|
|
541
541
|
|
|
542
542
|
|
|
543
543
|
def pos_to_rowcol(pos: int, cols: int = SCREEN_COLS) -> tuple[int, int]:
|
|
544
|
-
"""Convert a linear position to (row, col)
|
|
545
|
-
|
|
544
|
+
"""Convert a zero-based linear position to a 1-based ``(row, col)``."""
|
|
545
|
+
if pos < 0:
|
|
546
|
+
raise ValueError("Buffer position must be non-negative")
|
|
547
|
+
row, col = divmod(pos, cols)
|
|
548
|
+
return row + 1, col + 1
|
|
546
549
|
|
|
547
550
|
|
|
548
551
|
def rowcol_to_pos(row: int, col: int, cols: int = SCREEN_COLS) -> int:
|
|
549
|
-
"""Convert (row, col) to a linear position
|
|
550
|
-
|
|
552
|
+
"""Convert a 1-based ``(row, col)`` to a zero-based linear position."""
|
|
553
|
+
if row < 1 or col < 1:
|
|
554
|
+
raise ValueError("Screen coordinates start at (1, 1)")
|
|
555
|
+
if col > cols:
|
|
556
|
+
raise ValueError(f"Column must be between 1 and {cols}")
|
|
557
|
+
return (row - 1) * cols + col - 1
|
|
@@ -66,6 +66,7 @@ class Field:
|
|
|
66
66
|
)
|
|
67
67
|
_saved_data: bytes = b"" # original content before user modification
|
|
68
68
|
_explicit_length: int = 0
|
|
69
|
+
_screen_cols: int = SCREEN_COLS
|
|
69
70
|
|
|
70
71
|
# ---- derived properties ----
|
|
71
72
|
|
|
@@ -114,13 +115,13 @@ class Field:
|
|
|
114
115
|
|
|
115
116
|
@property
|
|
116
117
|
def row(self) -> int:
|
|
117
|
-
"""
|
|
118
|
-
return pos_to_rowcol(self.data_pos)[0]
|
|
118
|
+
"""1-based screen row of the first data byte."""
|
|
119
|
+
return pos_to_rowcol(self.data_pos, self._screen_cols)[0]
|
|
119
120
|
|
|
120
121
|
@property
|
|
121
122
|
def col(self) -> int:
|
|
122
|
-
"""
|
|
123
|
-
return pos_to_rowcol(self.data_pos)[1]
|
|
123
|
+
"""1-based screen column of the first data byte."""
|
|
124
|
+
return pos_to_rowcol(self.data_pos, self._screen_cols)[1]
|
|
124
125
|
|
|
125
126
|
def __repr__(self) -> str:
|
|
126
127
|
protection_indicator = "P" if self.is_protected else "I"
|
|
@@ -281,11 +282,11 @@ class Screen:
|
|
|
281
282
|
|
|
282
283
|
@property
|
|
283
284
|
def cursor_row(self) -> int:
|
|
284
|
-
return self._cursor
|
|
285
|
+
return pos_to_rowcol(self._cursor, self.cols)[0]
|
|
285
286
|
|
|
286
287
|
@property
|
|
287
288
|
def cursor_col(self) -> int:
|
|
288
|
-
return self._cursor
|
|
289
|
+
return pos_to_rowcol(self._cursor, self.cols)[1]
|
|
289
290
|
|
|
290
291
|
@property
|
|
291
292
|
def keyboard_locked(self) -> bool:
|
|
@@ -308,21 +309,19 @@ class Screen:
|
|
|
308
309
|
|
|
309
310
|
Wraps across row boundaries if needed.
|
|
310
311
|
"""
|
|
311
|
-
|
|
312
|
-
zero_based_col = col - 1
|
|
313
|
-
buffer_pos = rowcol_to_pos(zero_based_row, zero_based_col, self.cols)
|
|
312
|
+
buffer_pos = self._coordinate_to_pos(row, col)
|
|
314
313
|
positions = [(buffer_pos + offset) % self._size for offset in range(length)]
|
|
315
314
|
return ebcdic_decode(self._display_bytes(positions))
|
|
316
315
|
|
|
317
316
|
def get_row(self, row: int) -> str:
|
|
318
|
-
"""Return the full text of *row* (
|
|
319
|
-
row_start_pos = row
|
|
317
|
+
"""Return the full text of *row* (1-based) as a Python string."""
|
|
318
|
+
row_start_pos = self._coordinate_to_pos(row, 1)
|
|
320
319
|
positions = range(row_start_pos, row_start_pos + self.cols)
|
|
321
320
|
return ebcdic_decode(self._display_bytes(positions))
|
|
322
321
|
|
|
323
322
|
def get_all_rows(self) -> list[str]:
|
|
324
323
|
"""Return all rows as a list of strings."""
|
|
325
|
-
return [self.get_row(
|
|
324
|
+
return [self.get_row(row) for row in range(1, self.rows + 1)]
|
|
326
325
|
|
|
327
326
|
def get_screen_text(self) -> str:
|
|
328
327
|
"""Return the entire screen as a single newline-separated string."""
|
|
@@ -372,8 +371,8 @@ class Screen:
|
|
|
372
371
|
# ---- field lookups ----
|
|
373
372
|
|
|
374
373
|
def find_field_at(self, row: int, col: int) -> Field | None:
|
|
375
|
-
"""Return the field
|
|
376
|
-
pos =
|
|
374
|
+
"""Return the field at 1-based *(row, col)*, or ``None``."""
|
|
375
|
+
pos = self._coordinate_to_pos(row, col)
|
|
377
376
|
for f in self._fields:
|
|
378
377
|
if f.data_pos <= pos < f.data_pos + f.length:
|
|
379
378
|
return f
|
|
@@ -548,14 +547,14 @@ class Screen:
|
|
|
548
547
|
def find_text(
|
|
549
548
|
self, text: str, *, case_sensitive: bool = False
|
|
550
549
|
) -> tuple[int, int] | None:
|
|
551
|
-
"""Return *(row, col)*
|
|
550
|
+
"""Return 1-based *(row, col)* for the first occurrence, or ``None``."""
|
|
552
551
|
rows = self.get_all_rows()
|
|
553
|
-
for
|
|
552
|
+
for row, row_text in enumerate(rows, start=1):
|
|
554
553
|
s = row_text if case_sensitive else row_text.lower()
|
|
555
554
|
t = text if case_sensitive else text.lower()
|
|
556
555
|
idx = s.find(t)
|
|
557
556
|
if idx >= 0:
|
|
558
|
-
return
|
|
557
|
+
return row, idx + 1
|
|
559
558
|
return None
|
|
560
559
|
|
|
561
560
|
# ---- modified fields (for AID response) ----
|
|
@@ -577,6 +576,11 @@ class Screen:
|
|
|
577
576
|
# Internal helpers
|
|
578
577
|
# ------------------------------------------------------------------
|
|
579
578
|
|
|
579
|
+
def _coordinate_to_pos(self, row: int, col: int) -> int:
|
|
580
|
+
if row > self.rows:
|
|
581
|
+
raise ValueError(f"Row must be between 1 and {self.rows}")
|
|
582
|
+
return rowcol_to_pos(row, col, self.cols)
|
|
583
|
+
|
|
580
584
|
def _clear(self) -> None:
|
|
581
585
|
self._chars[:] = bytearray([EBCDIC_SPACE] * self._size)
|
|
582
586
|
self._attrs[:] = bytearray(self._size)
|
|
@@ -638,6 +642,7 @@ class Screen:
|
|
|
638
642
|
field_format_word=event.field_format_word,
|
|
639
643
|
field_control_words=event.field_control_words,
|
|
640
644
|
_explicit_length=event.field_length,
|
|
645
|
+
_screen_cols=self.cols,
|
|
641
646
|
)
|
|
642
647
|
# Replace any existing field at this position (dedup, preserving order)
|
|
643
648
|
for i, existing in enumerate(self._fields):
|
|
@@ -686,8 +691,8 @@ class Screen:
|
|
|
686
691
|
"""Pretty-print the screen with a border (useful for debugging)."""
|
|
687
692
|
divider = "+" + "-" * self.cols + "+"
|
|
688
693
|
lines = [divider]
|
|
689
|
-
for
|
|
690
|
-
lines.append("|" + self.get_row(
|
|
694
|
+
for row in range(1, self.rows + 1):
|
|
695
|
+
lines.append("|" + self.get_row(row) + "|")
|
|
691
696
|
lines.append(divider)
|
|
692
697
|
return "\n".join(lines)
|
|
693
698
|
|
|
@@ -770,8 +775,7 @@ class Screen:
|
|
|
770
775
|
|
|
771
776
|
rows = []
|
|
772
777
|
for display_row in range(display_start_row, display_end_row + 1):
|
|
773
|
-
|
|
774
|
-
row_text = self.get_row(row_idx)
|
|
778
|
+
row_text = self.get_row(display_row)
|
|
775
779
|
columns = [row_text[start:end].strip() for start, end in resolved]
|
|
776
780
|
rows.append([] if all(c == "" for c in columns) else columns)
|
|
777
781
|
return rows
|
|
@@ -260,7 +260,9 @@ class Terminal:
|
|
|
260
260
|
self._screen.set_field_text(f, existing + text)
|
|
261
261
|
|
|
262
262
|
def move_cursor(self, row: int, col: int) -> None:
|
|
263
|
-
"""Move the cursor to *(row, col)*
|
|
263
|
+
"""Move the cursor to 1-based *(row, col)* without sending a key."""
|
|
264
|
+
if row > self._rows:
|
|
265
|
+
raise ValueError(f"Row must be between 1 and {self._rows}")
|
|
264
266
|
pos = rowcol_to_pos(row, col, self._cols)
|
|
265
267
|
self._screen._cursor = pos
|
|
266
268
|
|
|
@@ -331,7 +333,7 @@ class Terminal:
|
|
|
331
333
|
*,
|
|
332
334
|
timeout: float | None = None,
|
|
333
335
|
) -> None:
|
|
334
|
-
"""Block until the cursor lands on *(row, col)
|
|
336
|
+
"""Block until the cursor lands on 1-based *(row, col)*."""
|
|
335
337
|
self._wait_for(
|
|
336
338
|
lambda: self._screen.cursor_row == row and self._screen.cursor_col == col,
|
|
337
339
|
desc=f"cursor at ({row},{col})",
|
|
@@ -385,7 +387,7 @@ class Terminal:
|
|
|
385
387
|
return FieldProxy(self, f)
|
|
386
388
|
|
|
387
389
|
def find_field_at(self, row: int, col: int) -> FieldProxy:
|
|
388
|
-
"""Return the
|
|
390
|
+
"""Return the unprotected field at 1-based *(row, col)*.
|
|
389
391
|
|
|
390
392
|
Raises :class:`~ibm5250.exceptions.FieldNotFound` if none exists there.
|
|
391
393
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|