ass_editor/core/position/
builder.rs1#[cfg(feature = "rope")]
8use super::LineColumn;
9use super::Position;
10use crate::core::errors::{EditorError, Result};
11
12#[derive(Debug, Clone, Default)]
27pub struct PositionBuilder {
28 line: Option<usize>,
29 column: Option<usize>,
30 offset: Option<usize>,
31}
32
33impl PositionBuilder {
34 #[must_use]
36 pub const fn new() -> Self {
37 Self {
38 line: None,
39 column: None,
40 offset: None,
41 }
42 }
43
44 #[must_use]
46 pub const fn line(mut self, line: usize) -> Self {
47 self.line = Some(line);
48 self
49 }
50
51 #[must_use]
53 pub const fn column(mut self, column: usize) -> Self {
54 self.column = Some(column);
55 self
56 }
57
58 #[must_use]
60 pub const fn offset(mut self, offset: usize) -> Self {
61 self.offset = Some(offset);
62 self
63 }
64
65 #[must_use]
67 pub const fn at_line_start(mut self, line: usize) -> Self {
68 self.line = Some(line);
69 self.column = Some(1);
70 self
71 }
72
73 #[must_use]
75 pub const fn at_line_end(mut self, line: usize) -> Self {
76 self.line = Some(line);
77 self.column = None; self
79 }
80
81 #[must_use]
83 pub const fn at_start() -> Self {
84 Self {
85 line: Some(1),
86 column: Some(1),
87 offset: Some(0),
88 }
89 }
90
91 #[cfg(feature = "rope")]
96 pub fn build(self, rope: &ropey::Rope) -> Result<Position> {
97 if let Some(offset) = self.offset {
98 if offset > rope.len_bytes() {
99 return Err(EditorError::PositionOutOfBounds {
100 position: offset,
101 length: rope.len_bytes(),
102 });
103 }
104 Ok(Position::new(offset))
105 } else if let Some(line) = self.line {
106 let line_idx = line.saturating_sub(1);
108
109 if line_idx >= rope.len_lines() {
110 return Err(EditorError::InvalidPosition { line, column: 1 });
111 }
112
113 let line_start = rope.line_to_byte(line_idx);
114
115 if let Some(column) = self.column {
116 LineColumn::new(line, column)?;
117 let col_idx = column.saturating_sub(1);
118 let line = rope.line(line_idx);
119
120 let mut byte_pos = 0;
122 let mut char_pos = 0;
123
124 for ch in line.chars() {
125 if char_pos == col_idx {
126 break;
127 }
128 byte_pos += ch.len_utf8();
129 char_pos += 1;
130 }
131
132 if char_pos < col_idx {
133 return Err(EditorError::InvalidPosition {
134 line: self.line.unwrap_or(0),
135 column,
136 });
137 }
138
139 Ok(Position::new(line_start + byte_pos))
140 } else {
141 let line_end = if line_idx + 1 < rope.len_lines() {
143 rope.line_to_byte(line_idx + 1).saturating_sub(1)
144 } else {
145 rope.len_bytes()
146 };
147 Ok(Position::new(line_end))
148 }
149 } else {
150 Ok(Position::start())
152 }
153 }
154
155 #[cfg(not(feature = "rope"))]
157 pub fn build(self) -> Result<Position> {
158 if let Some(offset) = self.offset {
159 Ok(Position::new(offset))
160 } else {
161 Err(EditorError::FeatureNotEnabled {
162 feature: "line/column position".to_string(),
163 required_feature: "rope".to_string(),
164 })
165 }
166 }
167}