Skip to main content

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}