@@ -67,6 +67,11 @@ Follow these principles when working with indices and lengths:
6767 - Instead of ` == 0 `
6868 - More idiomatic with newtype wrappers
6969
70+ 5 . ** Distinguish navigation from measurement**
71+ - ** Navigation** (` index - offset → index ` ): Moving backward in position space
72+ - ** Measurement** (` index.distance_from(other) → length ` ): Calculating distance between positions
73+ - Use ` - ` for cursor movement, use ` distance_from() ` for calculating spans
74+
7075## Common Imports
7176
7277``` rust
@@ -82,6 +87,9 @@ use r3bl_tui::{
8287
8388 // Type constructors
8489 col, row, width, height, idx, len,
90+
91+ // Terminal delta types (relative cursor movement)
92+ TermRowDelta , TermColDelta , term_row_delta, term_col_delta,
8593};
8694```
8795
@@ -95,6 +103,7 @@ use r3bl_tui::{
95103| ** Range validation** | ` RangeBoundsExt ` | ` range.check_range_is_valid_for_length(len) ` | Iterator bounds, algorithm parameters |
96104| ** Range membership** | ` RangeBoundsExt ` | ` range.check_index_is_within(index) ` | VT-100 scroll regions, text selections |
97105| ** Range conversion** | ` RangeConvertExt ` | ` inclusive_range.to_exclusive() ` | Converting VT-100 ranges for Rust iteration |
106+ | ** Relative movement** | ` TermRowDelta ` /` TermColDelta ` | ` TermRowDelta::new(n) ` returns ` Option ` | ANSI cursor movement preventing CSI zero bug |
98107
99108## Detailed Examples
100109
@@ -236,6 +245,80 @@ for line in rust_range {
236245}
237246```
238247
248+ ### Example 7: Navigation vs Measurement
249+
250+ ** Use ` - ` for navigation (moving cursor), ` distance_from() ` for measurement (calculating spans).**
251+
252+ ``` rust
253+ use r3bl_tui :: {row, height, RowIndex , RowHeight };
254+
255+ // Navigation: Move cursor backward by offset (returns RowIndex).
256+ let cursor_pos = row (10 );
257+ let new_pos = cursor_pos - row (3 ); // row(7) - moved 3 positions back
258+ // Uses saturating subtraction: row(2) - row(5) = row(0), not overflow
259+
260+ // Measurement: Calculate distance between two positions (returns RowHeight).
261+ let start = row (5 );
262+ let end = row (15 );
263+ let distance : RowHeight = end . distance_from (start ); // height(10) - 10 rows apart
264+ // Panics if start > end (negative distance)
265+ ```
266+
267+ ** When to use which:**
268+ - Moving cursor up/down/left/right → ` - ` operator
269+ - Calculating scroll amount, viewport span, selection size → ` distance_from() `
270+
271+ ### Example 8: Terminal Cursor Movement (Make Illegal States Unrepresentable)
272+
273+ ** Use ` TermRowDelta ` /` TermColDelta ` for relative cursor movement in ANSI sequences.**
274+
275+ The CSI zero problem: ANSI cursor movement commands interpret parameter 0 as 1:
276+ - ` CSI 0 A ` (` CursorUp ` with n=0) moves cursor ** 1 row up** , not 0
277+ - ` CSI 0 C ` (` CursorForward ` with n=0) moves cursor ** 1 column right** , not 0
278+
279+ ** Solution:** ` TermRowDelta ` and ` TermColDelta ` wrap ` NonZeroU16 ` internally, making zero-valued
280+ deltas ** impossible to represent** . Construction is fallible:
281+
282+ ``` rust
283+ use r3bl_tui :: {TermRowDelta , TermColDelta , CsiSequence };
284+ use std :: io :: Write ;
285+
286+ // Calculate cursor movement from position on 80-column terminal.
287+ let position : u16 = 240 ; // 240 chars from start
288+ let term_width : u16 = 80 ;
289+
290+ // Fallible construction - must handle the None case.
291+ // For position 240: rows = 3 (Some), cols = 0 (None).
292+ if let Some (delta ) = TermRowDelta :: new (position / term_width ) {
293+ // delta is guaranteed non-zero, safe to emit
294+ term . write_all (CsiSequence :: CursorDown (delta ). to_string (). as_bytes ())? ;
295+ }
296+ if let Some (delta ) = TermColDelta :: new (position % term_width ) {
297+ // This branch is NOT taken for position 240 (cols = 0)
298+ // Zero cannot be represented, so the bug is prevented at the type level!
299+ term . write_all (CsiSequence :: CursorForward (delta ). to_string (). as_bytes ())? ;
300+ }
301+ ```
302+
303+ ** Using the ONE constant for common case:**
304+
305+ ``` rust
306+ use r3bl_tui :: {TermRowDelta , TermColDelta , CsiSequence };
307+
308+ // For the common case of moving exactly 1 row/column, use the ONE constant.
309+ // This avoids the need for fallible construction or unwrap().
310+ let up_one = CsiSequence :: CursorUp (TermRowDelta :: ONE );
311+ let right_one = CsiSequence :: CursorForward (TermColDelta :: ONE );
312+ ```
313+
314+ ** Mathematical law:**
315+ - Zero deltas ** cannot exist** - prevented at compile time by ` NonZeroU16 ` wrapper
316+ - ` new() ` returns ` None ` for zero, ` Some(delta) ` for non-zero
317+
318+ ** Key difference from absolute positioning:**
319+ - ` TermRow ` /` TermCol ` : 1-based absolute coordinates (for ` CursorPosition ` )
320+ - ` TermRowDelta ` /` TermColDelta ` : Relative movement amounts (for ` CursorUp/Down/Forward/Backward ` )
321+
239322## Decision Trees
240323
241324See the accompanying ` decision-trees.md ` file for flowcharts showing which trait to use for
@@ -309,6 +392,41 @@ if row < width { // Compile error! Can't compare RowIndex with ColWidth
309392
310393This is actually GOOD - the type system prevents nonsensical comparisons!
311394
395+ ### ❌ Mistake 4: Using ` - ` when you need distance
396+
397+ ``` rust
398+ // Bad - using subtraction to calculate distance.
399+ // This returns RowIndex, not RowHeight!
400+ let current_row = row (5 );
401+ let target_row = row (15 );
402+ let rows_to_scroll = target_row - current_row ; // Returns row(10), a position!
403+ ```
404+
405+ ** Fix:**
406+ ``` rust
407+ // Good - use distance_from() for measurement.
408+ let rows_to_scroll : RowHeight = target_row . distance_from (current_row ); // height(10)
409+ ```
410+
411+ ### ❌ Mistake 5: Emitting CSI zero for cursor movement
412+
413+ ``` rust
414+ // Bad - CSI 0 C moves 1 column right, not 0!
415+ // This won't even compile now - CursorForward requires TermColDelta, not u16!
416+ let cols = position % term_width ; // Could be 0!
417+ let seq = CsiSequence :: CursorForward (cols ); // Compile error!
418+ ```
419+
420+ ** Fix:**
421+ ``` rust
422+ // Good - use fallible delta construction (zero is unrepresentable)
423+ if let Some (delta ) = TermColDelta :: new (position % term_width ) {
424+ // delta is guaranteed non-zero
425+ term . write_all (CsiSequence :: CursorForward (delta ). to_string (). as_bytes ())? ;
426+ }
427+ // No sequence emitted when cols = 0 (correct behavior - branch not taken)
428+ ```
429+
312430## Reporting Results
313431
314432When applying bounds checking:
@@ -343,3 +461,4 @@ No dedicated command, but used throughout the codebase for safe index/length han
343461- Main implementation: ` tui/src/core/units/bounds_check/mod.rs `
344462- Type definitions: ` tui/src/core/units/ `
345463- Examples in tests: ` tui/src/core/units/bounds_check/tests/ `
464+ - Terminal delta types: ` tui/src/core/coordinates/vt_100_ansi_coords/term_row_delta.rs ` and ` term_col_delta.rs `
0 commit comments