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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ibm5250
3
- Version: 0.1.0.dev5
3
+ Version: 0.1.0.dev7
4
4
  Summary: IBM 5250 terminal automation library
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ibm5250"
3
- version = "0.1.0.dev5"
3
+ version = "0.1.0.dev7"
4
4
  description = "IBM 5250 terminal automation library"
5
5
  requires-python = ">=3.11"
6
6
 
@@ -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), both 0-based."""
545
- return divmod(pos, cols)
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 (both 0-based)."""
550
- return row * cols + col
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
- """0-based screen row of the first data byte."""
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
- """0-based screen column of the first data byte."""
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 // self.cols
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 % self.cols
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
- zero_based_row = row - 1
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* (0-based) as a Python string."""
319
- row_start_pos = row * self.cols
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(r) for r in range(self.rows)]
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 whose data area contains *(row, col)*, or ``None``."""
376
- pos = rowcol_to_pos(row, col, self.cols)
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)* (0-based) of the first occurrence of *text*, or ``None``."""
550
+ """Return 1-based *(row, col)* for the first occurrence, or ``None``."""
552
551
  rows = self.get_all_rows()
553
- for r, row_text in enumerate(rows):
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 r, idx
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 r in range(self.rows):
690
- lines.append("|" + self.get_row(r) + "|")
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
- row_idx = display_row - 1
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)* (0-based) without sending a key."""
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)* (0-based)."""
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 (unprotected) field at *(row, col)* (0-based).
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
  """
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "ibm5250"
16
- version = "0.1.0.dev5"
16
+ version = "0.1.0.dev7"
17
17
  source = { editable = "." }
18
18
 
19
19
  [package.dev-dependencies]
File without changes
File without changes
File without changes