ass_editor/core/position/points.rs
1//! Single-point position types: byte offset and line/column.
2//!
3//! Defines [`Position`] (byte offset based) and [`LineColumn`]
4//! (1-indexed line/column) along with their conversions and display
5//! formatting.
6
7use crate::core::errors::{EditorError, Result};
8use core::fmt;
9
10/// A position in a document represented as byte offset
11///
12/// This is the primary position representation used internally
13/// for efficiency. Can be converted to/from line/column positions.
14///
15/// # Examples
16///
17/// ```
18/// use ass_editor::{Position, EditorDocument};
19///
20/// let doc = EditorDocument::from_content("Hello World").unwrap();
21/// let pos = Position::new(6); // Position before "World"
22///
23/// // Basic operations
24/// assert_eq!(pos.offset, 6);
25/// assert!(!pos.is_start());
26///
27/// // Position arithmetic
28/// let advanced = pos.advance(5);
29/// assert_eq!(advanced.offset, 11);
30///
31/// let retreated = pos.retreat(3);
32/// assert_eq!(retreated.offset, 3);
33/// ```
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Position {
36 /// Byte offset from the beginning of the document
37 pub offset: usize,
38}
39
40impl Position {
41 /// Create a new position from byte offset
42 #[must_use]
43 pub const fn new(offset: usize) -> Self {
44 Self { offset }
45 }
46
47 /// Create a position at the start of the document
48 #[must_use]
49 pub const fn start() -> Self {
50 Self { offset: 0 }
51 }
52
53 /// Check if this position is at the start
54 #[must_use]
55 pub const fn is_start(&self) -> bool {
56 self.offset == 0
57 }
58
59 /// Advance position by given bytes
60 #[must_use]
61 pub const fn advance(&self, bytes: usize) -> Self {
62 Self {
63 offset: self.offset.saturating_add(bytes),
64 }
65 }
66
67 /// Move position back by given bytes
68 #[must_use]
69 pub const fn retreat(&self, bytes: usize) -> Self {
70 Self {
71 offset: self.offset.saturating_sub(bytes),
72 }
73 }
74}
75
76impl Default for Position {
77 fn default() -> Self {
78 Self::start()
79 }
80}
81
82impl fmt::Display for Position {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{}", self.offset)
85 }
86}
87
88/// A line/column position in a document
89///
90/// Lines and columns are 1-indexed for user-facing display.
91/// Used for UI display and error reporting.
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub struct LineColumn {
94 /// 1-indexed line number
95 pub line: usize,
96 /// 1-indexed column number (in Unicode scalar values)
97 pub column: usize,
98}
99
100impl LineColumn {
101 /// Create a new line/column position
102 ///
103 /// # Errors
104 /// Returns error if line or column is 0
105 pub fn new(line: usize, column: usize) -> Result<Self> {
106 if line == 0 || column == 0 {
107 return Err(EditorError::InvalidPosition { line, column });
108 }
109 Ok(Self { line, column })
110 }
111
112 /// Create at start of document (1, 1)
113 #[must_use]
114 pub const fn start() -> Self {
115 Self { line: 1, column: 1 }
116 }
117}
118
119impl fmt::Display for LineColumn {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 write!(f, "{}:{}", self.line, self.column)
122 }
123}