1use crate::core::errors::{EditorError, Result};
8use core::cmp::{max, min};
9use core::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct Position {
37 pub offset: usize,
39}
40
41impl Position {
42 #[must_use]
44 pub const fn new(offset: usize) -> Self {
45 Self { offset }
46 }
47
48 #[must_use]
50 pub const fn start() -> Self {
51 Self { offset: 0 }
52 }
53
54 #[must_use]
56 pub const fn is_start(&self) -> bool {
57 self.offset == 0
58 }
59
60 #[must_use]
62 pub const fn advance(&self, bytes: usize) -> Self {
63 Self {
64 offset: self.offset.saturating_add(bytes),
65 }
66 }
67
68 #[must_use]
70 pub const fn retreat(&self, bytes: usize) -> Self {
71 Self {
72 offset: self.offset.saturating_sub(bytes),
73 }
74 }
75}
76
77impl Default for Position {
78 fn default() -> Self {
79 Self::start()
80 }
81}
82
83impl fmt::Display for Position {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 write!(f, "{}", self.offset)
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94pub struct LineColumn {
95 pub line: usize,
97 pub column: usize,
99}
100
101impl LineColumn {
102 pub fn new(line: usize, column: usize) -> Result<Self> {
107 if line == 0 || column == 0 {
108 return Err(EditorError::InvalidPosition { line, column });
109 }
110 Ok(Self { line, column })
111 }
112
113 #[must_use]
115 pub const fn start() -> Self {
116 Self { line: 1, column: 1 }
117 }
118}
119
120impl fmt::Display for LineColumn {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{}:{}", self.line, self.column)
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
154pub struct Range {
155 pub start: Position,
157 pub end: Position,
159}
160
161impl Range {
162 #[must_use]
166 pub fn new(start: Position, end: Position) -> Self {
167 if start.offset <= end.offset {
168 Self { start, end }
169 } else {
170 Self {
171 start: end,
172 end: start,
173 }
174 }
175 }
176
177 #[must_use]
179 pub const fn empty(pos: Position) -> Self {
180 Self {
181 start: pos,
182 end: pos,
183 }
184 }
185
186 #[must_use]
188 pub const fn is_empty(&self) -> bool {
189 self.start.offset == self.end.offset
190 }
191
192 #[must_use]
194 pub const fn len(&self) -> usize {
195 self.end.offset.saturating_sub(self.start.offset)
196 }
197
198 #[must_use]
200 pub const fn contains(&self, pos: Position) -> bool {
201 pos.offset >= self.start.offset && pos.offset < self.end.offset
202 }
203
204 #[must_use]
206 pub const fn overlaps(&self, other: &Self) -> bool {
207 self.start.offset < other.end.offset && other.start.offset < self.end.offset
208 }
209
210 #[must_use]
212 pub fn extend_to(&self, pos: Position) -> Self {
213 Self {
214 start: Position::new(min(self.start.offset, pos.offset)),
215 end: Position::new(max(self.end.offset, pos.offset)),
216 }
217 }
218
219 #[must_use]
221 pub fn union(&self, other: &Self) -> Self {
222 Self {
223 start: Position::new(min(self.start.offset, other.start.offset)),
224 end: Position::new(max(self.end.offset, other.end.offset)),
225 }
226 }
227
228 #[must_use]
230 pub fn intersection(&self, other: &Self) -> Option<Self> {
231 let start = max(self.start.offset, other.start.offset);
232 let end = min(self.end.offset, other.end.offset);
233
234 if start < end {
235 Some(Self::new(Position::new(start), Position::new(end)))
236 } else {
237 None
238 }
239 }
240}
241
242impl fmt::Display for Range {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 if self.is_empty() {
245 write!(f, "{}", self.start)
246 } else {
247 write!(f, "{}-{}", self.start, self.end)
248 }
249 }
250}
251
252#[derive(Debug, Clone, Default)]
267pub struct PositionBuilder {
268 line: Option<usize>,
269 column: Option<usize>,
270 offset: Option<usize>,
271}
272
273impl PositionBuilder {
274 #[must_use]
276 pub const fn new() -> Self {
277 Self {
278 line: None,
279 column: None,
280 offset: None,
281 }
282 }
283
284 #[must_use]
286 pub const fn line(mut self, line: usize) -> Self {
287 self.line = Some(line);
288 self
289 }
290
291 #[must_use]
293 pub const fn column(mut self, column: usize) -> Self {
294 self.column = Some(column);
295 self
296 }
297
298 #[must_use]
300 pub const fn offset(mut self, offset: usize) -> Self {
301 self.offset = Some(offset);
302 self
303 }
304
305 #[must_use]
307 pub const fn at_line_start(mut self, line: usize) -> Self {
308 self.line = Some(line);
309 self.column = Some(1);
310 self
311 }
312
313 #[must_use]
315 pub const fn at_line_end(mut self, line: usize) -> Self {
316 self.line = Some(line);
317 self.column = None; self
319 }
320
321 #[must_use]
323 pub const fn at_start() -> Self {
324 Self {
325 line: Some(1),
326 column: Some(1),
327 offset: Some(0),
328 }
329 }
330
331 #[cfg(feature = "rope")]
336 pub fn build(self, rope: &ropey::Rope) -> Result<Position> {
337 if let Some(offset) = self.offset {
338 if offset > rope.len_bytes() {
339 return Err(EditorError::PositionOutOfBounds {
340 position: offset,
341 length: rope.len_bytes(),
342 });
343 }
344 Ok(Position::new(offset))
345 } else if let Some(line) = self.line {
346 let line_idx = line.saturating_sub(1);
348
349 if line_idx >= rope.len_lines() {
350 return Err(EditorError::InvalidPosition { line, column: 1 });
351 }
352
353 let line_start = rope.line_to_byte(line_idx);
354
355 if let Some(column) = self.column {
356 LineColumn::new(line, column)?;
357 let col_idx = column.saturating_sub(1);
358 let line = rope.line(line_idx);
359
360 let mut byte_pos = 0;
362 let mut char_pos = 0;
363
364 for ch in line.chars() {
365 if char_pos == col_idx {
366 break;
367 }
368 byte_pos += ch.len_utf8();
369 char_pos += 1;
370 }
371
372 if char_pos < col_idx {
373 return Err(EditorError::InvalidPosition {
374 line: self.line.unwrap_or(0),
375 column,
376 });
377 }
378
379 Ok(Position::new(line_start + byte_pos))
380 } else {
381 let line_end = if line_idx + 1 < rope.len_lines() {
383 rope.line_to_byte(line_idx + 1).saturating_sub(1)
384 } else {
385 rope.len_bytes()
386 };
387 Ok(Position::new(line_end))
388 }
389 } else {
390 Ok(Position::start())
392 }
393 }
394
395 #[cfg(not(feature = "rope"))]
397 pub fn build(self) -> Result<Position> {
398 if let Some(offset) = self.offset {
399 Ok(Position::new(offset))
400 } else {
401 Err(EditorError::FeatureNotEnabled {
402 feature: "line/column position".to_string(),
403 required_feature: "rope".to_string(),
404 })
405 }
406 }
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
414pub struct Selection {
415 pub anchor: Position,
417 pub cursor: Position,
419}
420
421impl Selection {
422 #[must_use]
424 pub const fn new(anchor: Position, cursor: Position) -> Self {
425 Self { anchor, cursor }
426 }
427
428 #[must_use]
430 pub const fn empty(pos: Position) -> Self {
431 Self {
432 anchor: pos,
433 cursor: pos,
434 }
435 }
436
437 #[must_use]
439 pub const fn is_empty(&self) -> bool {
440 self.anchor.offset == self.cursor.offset
441 }
442
443 #[must_use]
445 pub fn range(&self) -> Range {
446 Range::new(self.anchor, self.cursor)
447 }
448
449 #[must_use]
451 pub const fn is_reversed(&self) -> bool {
452 self.cursor.offset < self.anchor.offset
453 }
454
455 #[must_use]
457 pub const fn extend_to(&self, pos: Position) -> Self {
458 Self {
459 anchor: self.anchor,
460 cursor: pos,
461 }
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn position_operations() {
471 let pos = Position::new(10);
472 assert_eq!(pos.advance(5).offset, 15);
473 assert_eq!(pos.retreat(5).offset, 5);
474 assert_eq!(pos.retreat(20).offset, 0); }
476
477 #[test]
478 fn line_column_validation() {
479 assert!(LineColumn::new(0, 1).is_err());
480 assert!(LineColumn::new(1, 0).is_err());
481 assert!(LineColumn::new(1, 1).is_ok());
482 }
483
484 #[test]
485 fn range_normalization() {
486 let r = Range::new(Position::new(10), Position::new(5));
487 assert_eq!(r.start.offset, 5);
488 assert_eq!(r.end.offset, 10);
489 }
490
491 #[test]
492 fn range_operations() {
493 let r1 = Range::new(Position::new(5), Position::new(10));
494 let r2 = Range::new(Position::new(8), Position::new(15));
495
496 assert!(r1.overlaps(&r2));
497 assert_eq!(r1.union(&r2).start.offset, 5);
498 assert_eq!(r1.union(&r2).end.offset, 15);
499
500 let intersection = r1.intersection(&r2).unwrap();
501 assert_eq!(intersection.start.offset, 8);
502 assert_eq!(intersection.end.offset, 10);
503 }
504
505 #[test]
506 fn selection_direction() {
507 let sel = Selection::new(Position::new(10), Position::new(5));
508 assert!(sel.is_reversed());
509 assert_eq!(sel.range().start.offset, 5);
510 assert_eq!(sel.range().end.offset, 10);
511 }
512
513 #[test]
514 #[cfg(feature = "rope")]
515 fn position_builder_with_rope() {
516 let rope = ropey::Rope::from_str("Line 1\nLine 2\nLine 3");
517 let pos = PositionBuilder::new()
518 .line(2)
519 .column(1)
520 .build(&rope)
521 .unwrap();
522 assert_eq!(pos.offset, 7); }
524
525 #[test]
526 #[cfg(not(feature = "rope"))]
527 fn position_builder_offset() {
528 let pos = PositionBuilder::new().offset(42).build().unwrap();
529 assert_eq!(pos.offset, 42);
530 }
531}