Skip to main content

ass_editor/core/fluent/
selection.rs

1//! Fluent API builder for operations on a selected range.
2
3use crate::core::{EditorDocument, Range, Result};
4
5#[cfg(feature = "rope")]
6use crate::core::Position;
7
8#[cfg(not(feature = "std"))]
9use alloc::string::{String, ToString};
10
11#[cfg(all(not(feature = "std"), feature = "rope"))]
12use alloc::vec::Vec;
13
14/// Fluent API builder for operations on a selected range
15pub struct SelectRange<'a> {
16    document: &'a mut EditorDocument,
17    range: Range,
18}
19
20impl<'a> SelectRange<'a> {
21    /// Create a new fluent API for range
22    pub(crate) fn new(document: &'a mut EditorDocument, range: Range) -> Self {
23        Self { document, range }
24    }
25
26    /// Replace the selected range with text
27    pub fn replace_with(self, text: &str) -> Result<&'a mut EditorDocument> {
28        self.document.replace(self.range, text)?;
29        Ok(self.document)
30    }
31
32    /// Delete the selected range
33    pub fn delete(self) -> Result<&'a mut EditorDocument> {
34        self.document.delete(self.range)?;
35        Ok(self.document)
36    }
37
38    /// Wrap the selection with ASS tags
39    pub fn wrap_with_tag(self, open_tag: &str, close_tag: &str) -> Result<&'a mut EditorDocument> {
40        // Get the selected text
41        let selected = self
42            .document
43            .rope()
44            .byte_slice(self.range.start.offset..self.range.end.offset);
45        let mut wrapped =
46            String::with_capacity(open_tag.len() + selected.len_bytes() + close_tag.len());
47        wrapped.push_str(open_tag);
48        wrapped.push_str(&selected.to_string());
49        wrapped.push_str(close_tag);
50
51        self.document.replace(self.range, &wrapped)?;
52        Ok(self.document)
53    }
54
55    /// Indent the selected lines
56    #[cfg(feature = "rope")]
57    pub fn indent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
58        // Get line information before mutating
59        let start_line = self.document.rope().byte_to_line(self.range.start.offset);
60        let end_line = self.document.rope().byte_to_line(self.range.end.offset);
61        let indent = " ".repeat(spaces);
62
63        // Collect line positions
64        let mut line_positions = Vec::new();
65        for line_idx in (start_line..=end_line).rev() {
66            let line_start = self.document.rope().line_to_byte(line_idx);
67            line_positions.push(line_start);
68        }
69
70        // Apply indentation
71        for line_start in line_positions {
72            let pos = Position::new(line_start);
73            let range = Range::empty(pos);
74            self.document.replace(range, &indent)?;
75        }
76
77        Ok(self.document)
78    }
79
80    /// Unindent the selected lines
81    #[cfg(feature = "rope")]
82    pub fn unindent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
83        // Get line information before mutating
84        let start_line = self.document.rope().byte_to_line(self.range.start.offset);
85        let end_line = self.document.rope().byte_to_line(self.range.end.offset);
86
87        // Collect unindent operations
88        let mut unindent_ops = Vec::new();
89        for line_idx in (start_line..=end_line).rev() {
90            let line_start = self.document.rope().line_to_byte(line_idx);
91            let line = self.document.rope().line(line_idx);
92
93            // Count spaces to remove
94            let mut space_count = 0;
95            for ch in line.chars().take(spaces) {
96                if ch == ' ' {
97                    space_count += 1;
98                } else {
99                    break;
100                }
101            }
102
103            if space_count > 0 {
104                unindent_ops.push((line_start, space_count));
105            }
106        }
107
108        // Apply unindent operations
109        for (line_start, space_count) in unindent_ops {
110            let range = Range::new(
111                Position::new(line_start),
112                Position::new(line_start + space_count),
113            );
114            self.document.delete(range)?;
115        }
116
117        Ok(self.document)
118    }
119
120    /// Get the selected text
121    pub fn text(&self) -> String {
122        self.document
123            .rope()
124            .byte_slice(self.range.start.offset..self.range.end.offset)
125            .to_string()
126    }
127
128    /// Get the range
129    pub const fn range(&self) -> Range {
130        self.range
131    }
132}