Skip to main content

ass_editor/core/fluent/
document_ext.rs

1//! Fluent API entry points implemented on [`EditorDocument`].
2
3use super::event_ops::EventOps;
4use super::karaoke::KaraokeOps;
5use super::media::{FontsOps, GraphicsOps};
6use super::script_info::ScriptInfoOps;
7use super::style::StyleOps;
8use super::tag::TagOps;
9use super::{AtPosition, SelectRange};
10use crate::core::{EditorDocument, Position, Range};
11
12#[cfg(feature = "rope")]
13use crate::core::errors::EditorError;
14#[cfg(feature = "rope")]
15use crate::core::Result;
16
17/// Extension trait to add fluent API to EditorDocument
18impl EditorDocument {
19    /// Start a fluent operation at a position
20    pub fn at_pos(&mut self, position: Position) -> AtPosition<'_> {
21        AtPosition::new(self, position)
22    }
23
24    /// Start a fluent operation at a line
25    #[cfg(feature = "rope")]
26    pub fn at_line(&mut self, line: usize) -> Result<AtPosition<'_>> {
27        let line_idx = line.saturating_sub(1);
28        if line_idx >= self.rope().len_lines() {
29            return Err(EditorError::InvalidPosition { line, column: 1 });
30        }
31
32        let byte_pos = self.rope().line_to_byte(line_idx);
33        Ok(AtPosition::new(self, Position::new(byte_pos)))
34    }
35
36    /// Start a fluent operation at the start of the document
37    pub fn at_start(&mut self) -> AtPosition<'_> {
38        AtPosition::new(self, Position::start())
39    }
40
41    /// Start a fluent operation at the end of the document
42    pub fn at_end(&mut self) -> AtPosition<'_> {
43        let end_pos = Position::new(self.len());
44        AtPosition::new(self, end_pos)
45    }
46
47    /// Start a fluent operation on a range
48    pub fn select(&mut self, range: Range) -> SelectRange<'_> {
49        SelectRange::new(self, range)
50    }
51
52    /// Start fluent style operations
53    pub fn styles(&mut self) -> StyleOps<'_> {
54        StyleOps::new(self)
55    }
56
57    /// Start fluent event operations
58    pub fn events(&mut self) -> EventOps<'_> {
59        EventOps::new(self)
60    }
61
62    /// Start fluent tag operations
63    pub fn tags(&mut self) -> TagOps<'_> {
64        TagOps::new(self)
65    }
66
67    /// Start fluent karaoke operations
68    pub fn karaoke(&mut self) -> KaraokeOps<'_> {
69        KaraokeOps::new(self)
70    }
71
72    /// Start fluent script info operations
73    pub fn info(&mut self) -> ScriptInfoOps<'_> {
74        ScriptInfoOps::new(self)
75    }
76
77    /// Start fluent fonts operations
78    pub fn fonts(&mut self) -> FontsOps<'_> {
79        FontsOps::new(self)
80    }
81
82    /// Start fluent graphics operations
83    pub fn graphics(&mut self) -> GraphicsOps<'_> {
84        GraphicsOps::new(self)
85    }
86
87    /// Convert a Position to line/column tuple
88    #[cfg(feature = "rope")]
89    pub fn position_to_line_col(&self, pos: Position) -> Result<(usize, usize)> {
90        if pos.offset > self.len() {
91            return Err(EditorError::PositionOutOfBounds {
92                position: pos.offset,
93                length: self.len(),
94            });
95        }
96
97        let line_idx = self.rope().byte_to_line(pos.offset);
98        let line_start = self.rope().line_to_byte(line_idx);
99        let col_offset = pos.offset - line_start;
100
101        // Convert byte offset to character offset
102        let line = self.rope().line(line_idx);
103        let mut char_col = 0;
104        let mut byte_count = 0;
105
106        for ch in line.chars() {
107            if byte_count >= col_offset {
108                break;
109            }
110            byte_count += ch.len_utf8();
111            char_col += 1;
112        }
113
114        Ok((line_idx + 1, char_col + 1)) // Convert to 1-indexed
115    }
116
117    /// Convert line/column to Position
118    #[cfg(feature = "rope")]
119    pub fn line_column_to_position(&self, line: usize, column: usize) -> Result<Position> {
120        use crate::core::PositionBuilder;
121
122        PositionBuilder::new()
123            .line(line)
124            .column(column)
125            .build(self.rope())
126    }
127}