Skip to main content

ass_editor/core/fluent/
position.rs

1//! Fluent API builder for document operations at a specific position.
2
3use crate::core::{EditorDocument, Position, Range, Result};
4
5#[cfg(not(feature = "rope"))]
6use crate::core::errors::EditorError;
7
8#[cfg(all(not(feature = "std"), not(feature = "rope")))]
9use alloc::string::ToString;
10
11/// Fluent API builder for document operations at a specific position
12pub struct AtPosition<'a> {
13    document: &'a mut EditorDocument,
14    position: Position,
15}
16
17impl<'a> AtPosition<'a> {
18    /// Create a new fluent API at position
19    pub(crate) fn new(document: &'a mut EditorDocument, position: Position) -> Self {
20        Self { document, position }
21    }
22
23    /// Insert text at the current position
24    pub fn insert_text(self, text: &str) -> Result<&'a mut EditorDocument> {
25        let range = Range::empty(self.position);
26        self.document.replace(range, text)?;
27        Ok(self.document)
28    }
29
30    /// Insert a line break at the current position
31    pub fn insert_line(self) -> Result<&'a mut EditorDocument> {
32        self.insert_text("\n")
33    }
34
35    /// Delete a number of characters forward from position
36    pub fn delete(self, count: usize) -> Result<&'a mut EditorDocument> {
37        let end = self.position.advance(count);
38        let range = Range::new(self.position, end);
39        self.document.delete(range)?;
40        Ok(self.document)
41    }
42
43    /// Delete characters backward from position (backspace)
44    pub fn backspace(self, count: usize) -> Result<&'a mut EditorDocument> {
45        let start = self.position.retreat(count);
46        let range = Range::new(start, self.position);
47        self.document.delete(range)?;
48        Ok(self.document)
49    }
50
51    /// Replace text from position to end of line
52    pub fn replace_to_line_end(self, text: &str) -> Result<&'a mut EditorDocument> {
53        #[cfg(feature = "rope")]
54        {
55            let rope = self.document.rope();
56            let line_idx = rope.byte_to_line(self.position.offset);
57            let line_end_byte = if line_idx + 1 < rope.len_lines() {
58                rope.line_to_byte(line_idx + 1).saturating_sub(1)
59            } else {
60                rope.len_bytes()
61            };
62            let range = Range::new(self.position, Position::new(line_end_byte));
63            self.document.replace(range, text)?;
64            Ok(self.document)
65        }
66
67        #[cfg(not(feature = "rope"))]
68        {
69            Err(EditorError::FeatureNotEnabled {
70                feature: "line-based operations".to_string(),
71                required_feature: "rope".to_string(),
72            })
73        }
74    }
75
76    /// Get the current position
77    pub const fn position(&self) -> Position {
78        self.position
79    }
80
81    /// Convert position to line/column
82    #[cfg(feature = "rope")]
83    pub fn to_line_column(&self) -> Result<(usize, usize)> {
84        let rope = self.document.rope();
85        let line_idx = rope.byte_to_line(self.position.offset);
86        let line_start = rope.line_to_byte(line_idx);
87        let col_offset = self.position.offset - line_start;
88
89        // Convert byte offset to character offset
90        let line = rope.line(line_idx);
91        let mut char_col = 0;
92        let mut byte_count = 0;
93
94        for ch in line.chars() {
95            if byte_count >= col_offset {
96                break;
97            }
98            byte_count += ch.len_utf8();
99            char_col += 1;
100        }
101
102        Ok((line_idx + 1, char_col + 1)) // Convert to 1-indexed
103    }
104}