Skip to main content

ratatui_code_editor/
code.rs

1use anyhow::{Result, anyhow};
2use ropey::{Rope, RopeSlice};
3use streaming_iterator::StreamingIterator;
4use tree_sitter::{InputEdit, Point, QueryCursor};
5use tree_sitter::{Language, Parser, Query, Tree, Node};
6use crate::history::{History};
7use crate::selection::Selection;
8use rust_embed::RustEmbed;
9use std::collections::HashMap;
10use crate::utils::{indent, count_indent_units, comment as lang_comment, calculate_end_position};
11use std::cell::RefCell;
12use std::rc::Rc;
13use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
14use unicode_width::{UnicodeWidthStr};
15
16#[derive(RustEmbed)]
17#[folder = ""]
18#[include = "langs/*/*"]
19struct LangAssets;
20
21
22#[derive(Clone)]
23pub enum EditKind {
24    Insert { offset: usize, text: String },
25    Remove { offset: usize, text: String },
26}
27
28#[derive(Clone)]
29pub struct Edit {
30    pub kind: EditKind,
31}
32
33#[derive(Clone)]
34pub struct EditBatch {
35    pub edits: Vec<Edit>,
36    pub state_before: Option<EditState>,
37    pub state_after: Option<EditState>,
38}
39
40impl EditBatch {
41    pub fn new() -> Self {
42        Self { 
43            edits: Vec::new(), 
44            state_before: None,
45            state_after: None,
46        }
47    }
48
49}
50
51#[derive(Clone, Copy)]
52pub struct EditState {
53    pub offset: usize,
54    pub selection: Option<Selection>,
55}
56
57
58pub struct Code {
59    content: ropey::Rope,
60    lang: String,
61    tree: Option<Tree>,
62    parser: Option<Parser>,
63    query: Option<Query>,
64    applying_history: bool,
65    history: History,
66    current_batch: EditBatch,
67    injection_parsers: Option<HashMap<String, Rc<RefCell<Parser>>>>,
68    injection_queries: Option<HashMap<String, Query>>,
69    change_callback: Option<Box<dyn Fn(Vec<(usize, usize, usize, usize, String)>)>>,
70    custom_highlights: Option<HashMap<String, String>>,
71}
72
73impl Code {
74    /// Create a new `Code` instance with the given text and language.
75    pub fn new(
76        text: &str,
77        lang: &str,
78        custom_highlights: Option<HashMap<String, String>>,
79    ) -> Result<Self> {
80        let mut code = Self {
81            content: Rope::from_str(text),
82            lang: lang.to_string(),
83            tree: None,
84            parser: None,
85            query: None,
86            applying_history: true,
87            history: History::new(1000),
88            current_batch: EditBatch::new(),
89            injection_parsers: None,
90            injection_queries: None,
91            change_callback: None,
92            custom_highlights,
93        };
94
95        if let Some(language) = Self::get_language(lang) {
96            let highlights = code.get_highlights(lang)?;
97            let mut parser = Parser::new();
98            parser.set_language(&language)?;
99            let tree = parser.parse(text, None);
100            let query = Query::new(&language, &highlights)?;
101            let (iparsers, iqueries) = code.init_injections(&query)?;
102            code.tree = tree;
103            code.parser = Some(parser);
104            code.query = Some(query);
105            code.injection_parsers = Some(iparsers);
106            code.injection_queries = Some(iqueries);
107        }
108
109        Ok(code)
110    }
111
112    fn get_language(lang: &str) -> Option<Language> {
113        match lang {
114            "rust" => Some(tree_sitter_rust::LANGUAGE.into()),
115            "javascript" => Some(tree_sitter_javascript::LANGUAGE.into()),
116            "typescript" => Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
117            "python" => Some(tree_sitter_python::LANGUAGE.into()),
118            "go" => Some(tree_sitter_go::LANGUAGE.into()),
119            "java" => Some(tree_sitter_java::LANGUAGE.into()),
120            "c_sharp" => Some(tree_sitter_c_sharp::LANGUAGE.into()),
121            "c" => Some(tree_sitter_c::LANGUAGE.into()),
122            "cpp" => Some(tree_sitter_cpp::LANGUAGE.into()),
123            "html" => Some(tree_sitter_html::LANGUAGE.into()),
124            "css" => Some(tree_sitter_css::LANGUAGE.into()),
125            "yaml" => Some(tree_sitter_yaml::LANGUAGE.into()),
126            "json" => Some(tree_sitter_json::LANGUAGE.into()),
127            "toml" => Some(tree_sitter_toml_ng::LANGUAGE.into()),
128            "shell" => Some(tree_sitter_bash::LANGUAGE.into()),
129            "markdown" => Some(tree_sitter_md::LANGUAGE.into()),
130            "markdown-inline" => Some(tree_sitter_md::INLINE_LANGUAGE.into()),
131            _ => None,
132        }
133    }
134
135    fn get_highlights(&self, lang: &str) -> anyhow::Result<String> {
136        if let Some(highlights_map) = &self.custom_highlights {
137            if let Some(highlights) = highlights_map.get(lang) {
138                return Ok(highlights.clone());
139            }
140        }
141        let p = format!("langs/{}/highlights.scm", lang);
142        let highlights_bytes =
143            LangAssets::get(&p).ok_or_else(|| anyhow!("No highlights found for {}", lang))?;
144        let highlights_bytes = highlights_bytes.data.as_ref();
145        let highlights = std::str::from_utf8(highlights_bytes)?;
146        Ok(highlights.to_string())
147    }
148
149    fn init_injections(
150        &self,
151        query: &Query,
152    ) -> anyhow::Result<(
153        HashMap<String, Rc<RefCell<Parser>>>,
154        HashMap<String, Query>,
155    )> {
156        let mut injection_parsers = HashMap::new();
157        let mut injection_queries = HashMap::new();
158
159        for name in query.capture_names() {
160            if let Some(lang) = name.strip_prefix("injection.content.") {
161                if injection_parsers.contains_key(lang) {
162                    continue;
163                }
164                if let Some(language) = Self::get_language(lang) {
165                    let mut parser = Parser::new();
166                    parser.set_language(&language)?;
167                    let highlights = self.get_highlights(lang)?;
168                    let inj_query = Query::new(&language, &highlights)?;
169
170                    injection_parsers.insert(lang.to_string(), Rc::new(RefCell::new(parser)));
171                    injection_queries.insert(lang.to_string(), inj_query);
172                } else {
173                    eprintln!("Unknown injection language: {}", lang);
174                }
175            }
176        }
177
178        Ok((injection_parsers, injection_queries))
179    }
180
181    pub fn point(&self, offset: usize) -> (usize, usize) {
182        let row = self.content.char_to_line(offset);
183        let line_start = self.content.line_to_char(row);
184        let col = offset - line_start;
185        (row, col)
186    }
187
188    pub fn offset(&self, row: usize, col: usize) -> usize {
189        let line_start = self.content.line_to_char(row);
190        line_start + col
191    }
192    
193    pub fn get_content(&self) -> String {
194        self.content.to_string()
195    }
196    
197    pub fn slice(&self, start: usize, end: usize) -> String {
198        self.content.slice(start..end).to_string()
199    }
200
201    pub fn len(&self) -> usize {
202        self.content.len_chars()
203    }
204
205    pub fn len_lines(&self) -> usize {
206        self.content.len_lines()
207    }
208
209    pub fn len_chars(&self) -> usize {
210        self.content.len_chars()
211    }
212
213    pub fn line_to_char(&self, line_idx: usize) -> usize {
214        self.content.line_to_char(line_idx)
215    }
216    pub fn char_to_byte(&self, char_idx: usize) -> usize {
217        self.content.char_to_byte(char_idx)
218    }
219
220    pub fn line_len(&self, idx: usize) -> usize {
221        let line = self.content.line(idx);
222        let len = line.len_chars();
223        if idx == self.content.len_lines() - 1 {
224            len
225        } else {
226            len.saturating_sub(1)
227        }
228    }
229    
230    pub fn line(&self, line_idx: usize) -> RopeSlice<'_> {
231        self.content.line(line_idx)
232    }
233
234    pub fn char_to_line(&self, char_idx: usize) -> usize {
235        self.content.char_to_line(char_idx)
236    }
237    
238    pub fn char_slice(&self, start: usize, end: usize) -> RopeSlice<'_> {
239        self.content.slice(start..end)
240    }
241    
242    pub fn byte_slice(&self, start: usize, end: usize) -> RopeSlice<'_> {
243        self.content.byte_slice(start..end)
244    }
245    
246    pub fn byte_to_line(&self, byte_idx: usize) -> usize {
247        self.content.byte_to_line(byte_idx)
248    }
249    
250    pub fn byte_to_char(&self, byte_idx: usize) -> usize {
251        self.content.byte_to_char(byte_idx)
252    }
253    
254    pub fn tx(&mut self) {
255        self.current_batch = EditBatch::new();
256    }
257
258    pub fn set_state_before(&mut self, offset: usize, selection: Option<Selection>) {
259        self.current_batch.state_before = Some(EditState { offset, selection });
260    }
261
262    pub fn set_state_after(&mut self, offset: usize, selection: Option<Selection>) {
263        self.current_batch.state_after = Some(EditState { offset, selection });
264    }
265
266    pub fn commit(&mut self) {
267        if !self.current_batch.edits.is_empty() {
268            self.notify_changes(&self.current_batch.edits);
269            self.history.push(self.current_batch.clone());
270            self.current_batch = EditBatch::new();
271        }
272    }
273    
274    pub fn insert(&mut self, from: usize, text: &str) {
275        let byte_idx = self.content.char_to_byte(from);
276        let byte_len: usize = text.chars().map(|ch| ch.len_utf8()).sum();
277        
278        self.content.insert(from, text);
279        
280        if self.applying_history {
281            self.current_batch.edits.push(Edit {
282                kind: EditKind::Insert {
283                    offset: from,
284                    text: text.to_string(),
285                },
286            });
287        }
288        
289        if self.tree.is_some() {
290            self.edit_tree(InputEdit {
291                start_byte: byte_idx,
292                old_end_byte: byte_idx,
293                new_end_byte: byte_idx + byte_len,
294                start_position: Point { row: 0, column: 0 },
295                old_end_position: Point { row: 0, column: 0 },
296                new_end_position: Point { row: 0, column: 0 },
297            });
298        }
299    }
300
301    pub fn remove(&mut self, from: usize, to: usize) {
302        let from_byte = self.content.char_to_byte(from);
303        let to_byte = self.content.char_to_byte(to);
304        let removed_text = self.content.slice(from..to).to_string();
305        
306        self.content.remove(from..to);
307        
308        if self.applying_history {
309            self.current_batch.edits.push(Edit {
310                kind: EditKind::Remove {
311                    offset: from,
312                    text: removed_text,
313                },
314            });
315        }
316        
317        if self.tree.is_some() {
318            self.edit_tree(InputEdit {
319                start_byte: from_byte,
320                old_end_byte: to_byte,
321                new_end_byte: from_byte,
322                start_position: Point { row: 0, column: 0 },
323                old_end_position: Point { row: 0, column: 0 },
324                new_end_position: Point { row: 0, column: 0 },
325            });
326        }
327    }
328
329    fn edit_tree(&mut self, edit: InputEdit) {
330        if let Some(tree) = self.tree.as_mut() {
331            tree.edit(&edit);
332            self.reparse();
333        }
334    }
335
336    fn reparse(&mut self) {
337        if let Some(parser) = self.parser.as_mut() {
338            let rope = &self.content;
339            self.tree = parser.parse_with_options(
340                &mut |byte, _| {
341                    if byte <= rope.len_bytes() {
342                        let (chunk, start, _, _) = rope.chunk_at_byte(byte);
343                        &chunk.as_bytes()[byte - start..]
344                    } else {
345                        &[]
346                    }
347                },
348                self.tree.as_ref(),
349                None,
350            );
351        }
352    }
353
354    pub fn is_highlight(&self) -> bool {
355        self.query.is_some()
356    }
357    
358    /// Highlights the interval between `start` and `end` char indices.
359    /// Returns a list of (start byte, end byte, token_name) for highlighting. 
360    pub fn highlight_interval<T: Copy>(
361        &self, start: usize, end: usize, theme: &HashMap<String, T>,
362    ) -> Vec<(usize, usize, T)> {
363        if start > end { panic!("Invalid range") }
364
365        let Some(query) = &self.query else { return vec![]; };
366        let Some(tree) = &self.tree else { return vec![]; };
367
368        let text = self.content.slice(..);
369        let root_node = tree.root_node();
370
371        let mut results = Self::highlight(
372            text,
373            start,
374            end,
375            query,
376            root_node,
377            theme,
378            self.injection_parsers.as_ref(),
379            self.injection_queries.as_ref(),
380        );
381
382        results.sort_by(|a, b| {
383            let len_a = a.1 - a.0;
384            let len_b = b.1 - b.0;
385            match len_b.cmp(&len_a) {
386                std::cmp::Ordering::Equal => b.2.cmp(&a.2),
387                other => other,
388            }
389        });
390
391        results
392            .into_iter()
393            .map(|(start, end, _, value)| (start, end, value))
394            .collect()
395    }
396
397    fn highlight<T: Copy>(
398        text: RopeSlice<'_>,
399        start_byte: usize,
400        end_byte: usize,
401        query: &Query,
402        root_node: Node,
403        theme: &HashMap<String, T>,
404        injection_parsers: Option<&HashMap<String, Rc<RefCell<Parser>>>>,
405        injection_queries: Option<&HashMap<String, Query>>,
406    ) -> Vec<(usize, usize, usize, T)> {
407        let mut cursor = QueryCursor::new();
408        cursor.set_byte_range(start_byte..end_byte);
409
410        let mut matches = cursor.matches(query, root_node, RopeProvider(text));
411
412        let mut results = Vec::new();
413        let capture_names = query.capture_names();
414
415        while let Some(m) = matches.next() {
416            for capture in m.captures {
417                let name = capture_names[capture.index as usize];
418                if let Some(value) = theme.get(name) {
419                    results.push((
420                        capture.node.start_byte(),
421                        capture.node.end_byte(),
422                        capture.index as usize,
423                        *value,
424                    ));
425                } else if let Some(lang) = name.strip_prefix("injection.content.") {
426                    let Some(injection_parsers) = injection_parsers else { continue };
427                    let Some(injection_queries) = injection_queries else { continue };
428                    let Some(parser) = injection_parsers.get(lang) else { continue };
429                    let Some(injection_query) = injection_queries.get(lang) else { continue };
430
431                    let start = capture.node.start_byte();
432                    let end = capture.node.end_byte();
433                    let slice = text.byte_slice(start..end);
434
435                    let mut parser = parser.borrow_mut();
436                    let Some(inj_tree) = parser.parse(slice.to_string(), None) else { continue };
437
438                    let injection_results = Self::highlight(
439                        slice,
440                        0,
441                        end - start,
442                        injection_query,
443                        inj_tree.root_node(),
444                        theme,
445                        injection_parsers.into(),
446                        injection_queries.into(),
447                    );
448
449                    for (s, e, i, v) in injection_results {
450                        results.push((s + start, e + start, i, v));
451                    }
452                }
453            }
454        }
455
456        results
457    }
458
459
460    pub fn undo(&mut self) -> Option<EditBatch> {
461        let batch = self.history.undo()?;
462        self.applying_history = false;
463    
464        for edit in batch.edits.iter().rev() {
465            match edit.kind {
466                EditKind::Insert { offset, ref text } => {
467                    self.remove(offset, offset + text.chars().count());
468                }
469                EditKind::Remove { offset, ref text } => {
470                    self.insert(offset, text);
471                }
472            }
473        }
474    
475        self.applying_history = true;
476        Some(batch)
477    }
478    
479    pub fn redo(&mut self) -> Option<EditBatch> {
480        let batch = self.history.redo()?;
481        self.applying_history = false;
482    
483        for edit in &batch.edits {
484            match edit.kind {
485                EditKind::Insert { offset, ref text } => {
486                    self.insert(offset, text);
487                }
488                EditKind::Remove { offset, ref text } => {
489                    self.remove(offset, offset + text.chars().count());
490                }
491            }
492        }
493    
494        self.applying_history = true;
495        Some(batch)
496    }
497    
498    pub fn word_boundaries(&self, pos: usize) -> (usize, usize) {
499        let len = self.content.len_chars();
500        if pos >= len {
501            return (pos, pos);
502        }
503    
504        let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
505    
506        let mut start = pos;
507        while start > 0 {
508            let c = self.content.char(start - 1);
509            if !is_word_char(c) {
510                break;
511            }
512            start -= 1;
513        }
514    
515        let mut end = pos;
516        while end < len {
517            let c = self.content.char(end);
518            if !is_word_char(c) {
519                break;
520            }
521            end += 1;
522        }
523    
524        (start, end)
525    }
526
527    pub fn line_boundaries(&self, pos: usize) -> (usize, usize) {
528        let total_chars = self.content.len_chars();
529        if pos >= total_chars {
530            return (pos, pos);
531        }
532
533        let line = self.content.char_to_line(pos);
534        let start = self.content.line_to_char(line);
535        let end = start + self.content.line(line).len_chars();
536
537        (start, end)
538    }
539    
540    pub fn indent(&self) -> String {
541        indent(&self.lang)
542    }
543
544    pub fn comment(&self) -> String {
545        lang_comment(&self.lang).to_string()
546    }
547
548    pub fn indentation_level(&self, line: usize, col: usize) -> usize {
549        if self.lang == "unknown" || self.lang.is_empty() { return 0; }
550        let line_str = self.line(line);
551        count_indent_units(line_str, &self.indent(), Some(col))
552    }
553
554    pub fn is_only_indentation_before(&self, r: usize, c: usize) -> bool {
555        if self.lang == "unknown" || self.lang.is_empty() { return false; }
556        if r >= self.len_lines() || c == 0 { return false; }
557    
558        let line = self.line(r);
559        let indent_unit = self.indent();
560    
561        if indent_unit.is_empty() {
562            return line.chars().take(c).all(|ch| ch.is_whitespace());
563        }
564    
565        let count_units = count_indent_units(line, &indent_unit, Some(c));
566        let only_indent = count_units * indent_unit.chars().count() >= c;
567        only_indent
568    }
569
570    pub fn find_indent_at_line_start(&self, line_idx: usize) -> Option<usize> {
571        if line_idx >= self.len_lines() { return None; }
572    
573        let line = self.line(line_idx);
574        let indent_unit = self.indent();
575        if indent_unit.is_empty() { return None; }
576    
577        let count_units = count_indent_units(line, &indent_unit, None);
578        let col = count_units * indent_unit.chars().count();
579        if col > 0 { Some(col) } else { None }
580    }
581
582    /// Paste text with **indentation awareness**.
583    /// 
584    /// 1. Determine the indentation level at the cursor (`base_level`).
585    /// 2. The first line of the pasted block is inserted at the cursor level (trimmed).
586    /// 3. Subsequent lines adjust their indentation **relative to the previous non-empty line in the pasted block**:
587    ///    - Compute `diff` = change in indentation from the previous non-empty line in the source block (clamped ±1).
588    ///    - Apply `diff` to `prev_nonempty_level` to calculate the new insertion level.
589    /// 4. Empty lines are inserted as-is and do not affect subsequent indentation.
590    /// 
591    /// This ensures that pasted blocks keep their relative structure while aligning to the cursor.
592
593
594    /// Inserts `text` with indentation-awareness at `offset`.
595    /// Returns number of characters inserted.
596    pub fn smart_paste(&mut self, offset: usize, text: &str) -> usize {
597        let (row, col) = self.point(offset);
598        let base_level = self.indentation_level(row, col);
599        let indent_unit = self.indent();
600
601        if indent_unit.is_empty() {
602            self.insert(offset, text);
603            return text.chars().count();
604        }
605
606        let lines: Vec<&str> = text.lines().collect();
607        if lines.is_empty() {
608            return 0;
609        }
610
611        // Compute indentation levels of all lines in the source block
612        let mut line_levels = Vec::with_capacity(lines.len());
613        for line in &lines {
614            let mut lvl = 0;
615            let mut rest = *line;
616            while rest.starts_with(&indent_unit) {
617                lvl += 1;
618                rest = &rest[indent_unit.len()..];
619            }
620            line_levels.push(lvl);
621        }
622
623        let mut result = Vec::with_capacity(lines.len());
624
625        let first_line_trimmed = lines[0].trim_start();
626        result.push(first_line_trimmed.to_string());
627
628        let mut prev_nonempty_level = base_level;
629        let mut prev_line_level_in_block = line_levels[0];
630
631        for i in 1..lines.len() {
632            let line = lines[i];
633
634            if line.trim().is_empty() {
635                result.push(line.to_string());
636                continue;
637            }
638
639            // diff relative to previous non-empty line in the source block
640            let diff = (line_levels[i] as isize - prev_line_level_in_block as isize).clamp(-1, 1);
641            let new_level = (prev_nonempty_level as isize + diff).max(0) as usize;
642            let indents = indent_unit.repeat(new_level);
643            let result_line = format!("{}{}", indents, line.trim_start());
644            result.push(result_line);
645
646            // update levels only for non-empty line
647            prev_nonempty_level = new_level;
648            prev_line_level_in_block = line_levels[i];
649        }
650
651        let to_insert = result.join("\n");
652        self.insert(offset, &to_insert);
653        to_insert.chars().count()
654    }
655
656    /// Set the change callback function for handling document changes
657    pub fn set_change_callback(&mut self, callback: Box<dyn Fn(Vec<(usize, usize, usize, usize, String)>)>) {
658        self.change_callback = Some(callback);
659    }
660
661    /// Notify about document changes
662    fn notify_changes(&self, edits: &[Edit]) {
663        if let Some(callback) = &self.change_callback {
664            let mut changes = Vec::new();
665            
666            for edit in edits {
667                match &edit.kind {
668                    EditKind::Insert { offset, text } => {
669                        let (start_row, start_col) = self.point(*offset);
670                        changes.push((start_row, start_col, start_row, start_col, text.clone()));
671                    }
672                    EditKind::Remove { offset, text } => {
673                        let (start_row, start_col) = self.point(*offset);
674                        let (end_row, end_col) = calculate_end_position(start_row, start_col, text);
675                        changes.push((start_row, start_col, end_row, end_col, String::new()));
676                    }
677                }
678            }
679            
680            if !changes.is_empty() {
681                callback(changes);
682            }
683        }
684    }
685    
686}
687
688/// An iterator over byte slices of Rope chunks.
689/// This is used to feed `tree-sitter` without allocating a full `String`.
690pub struct ChunksBytes<'a> {
691    chunks: ropey::iter::Chunks<'a>,
692}
693
694impl<'a> Iterator for ChunksBytes<'a> {
695    type Item = &'a [u8];
696
697    /// Returns the next chunk as a byte slice.
698    /// Internally converts a `&str` to a `&[u8]` without allocation.
699    #[inline]
700    fn next(&mut self) -> Option<Self::Item> {
701        self.chunks.next().map(str::as_bytes)
702    }
703}
704
705/// A lightweight wrapper around a `RopeSlice`
706/// that implements `tree_sitter::TextProvider`.
707/// This allows using `tree-sitter`'s `QueryCursor::matches`
708/// directly on a `Rope` without converting it to a `String`.
709pub struct RopeProvider<'a>(pub RopeSlice<'a>);
710
711impl<'a> tree_sitter::TextProvider<&'a [u8]> for RopeProvider<'a> {
712    type I = ChunksBytes<'a>;
713
714    /// Provides an iterator over chunks of text corresponding to the given node.
715    /// This avoids allocation by working directly with Rope slices.
716    #[inline]
717    fn text(&mut self, node: tree_sitter::Node) -> Self::I {
718        let fragment = self.0.byte_slice(node.start_byte()..node.end_byte());
719        ChunksBytes {
720            chunks: fragment.chunks(),
721        }
722    }
723}
724
725/// An implementation of a graphemes iterator, for iterating over the graphemes of a RopeSlice.
726pub struct RopeGraphemes<'a> {
727    text: ropey::RopeSlice<'a>,
728    chunks: ropey::iter::Chunks<'a>,
729    cur_chunk: &'a str,
730    cur_chunk_start: usize,
731    cursor: GraphemeCursor,
732}
733
734impl<'a> RopeGraphemes<'a> {
735    pub fn new<'b>(slice: &RopeSlice<'b>) -> RopeGraphemes<'b> {
736        let mut chunks = slice.chunks();
737        let first_chunk = chunks.next().unwrap_or("");
738        RopeGraphemes {
739            text: *slice,
740            chunks: chunks,
741            cur_chunk: first_chunk,
742            cur_chunk_start: 0,
743            cursor: GraphemeCursor::new(0, slice.len_bytes(), true),
744        }
745    }
746}
747
748impl<'a> Iterator for RopeGraphemes<'a> {
749    type Item = RopeSlice<'a>;
750
751    fn next(&mut self) -> Option<RopeSlice<'a>> {
752        let a = self.cursor.cur_cursor();
753        let b;
754        loop {
755            match self
756                .cursor
757                .next_boundary(self.cur_chunk, self.cur_chunk_start)
758            {
759                Ok(None) => {
760                    return None;
761                }
762                Ok(Some(n)) => {
763                    b = n;
764                    break;
765                }
766                Err(GraphemeIncomplete::NextChunk) => {
767                    self.cur_chunk_start += self.cur_chunk.len();
768                    self.cur_chunk = self.chunks.next().unwrap_or("");
769                }
770                Err(GraphemeIncomplete::PreContext(idx)) => {
771                    let (chunk, byte_idx, _, _) = self.text.chunk_at_byte(idx.saturating_sub(1));
772                    self.cursor.provide_context(chunk, byte_idx);
773                }
774                _ => unreachable!(),
775            }
776        }
777
778        if a < self.cur_chunk_start {
779            let a_char = self.text.byte_to_char(a);
780            let b_char = self.text.byte_to_char(b);
781
782            Some(self.text.slice(a_char..b_char))
783        } else {
784            let a2 = a - self.cur_chunk_start;
785            let b2 = b - self.cur_chunk_start;
786            Some((&self.cur_chunk[a2..b2]).into())
787        }
788    }
789}
790
791pub fn grapheme_width_and_chars_len(g: RopeSlice) -> (usize, usize) {
792    if let Some(g_str) = g.as_str() {
793        (UnicodeWidthStr::width(g_str), g_str.chars().count())
794    } else {
795        let g_string = g.to_string();
796        let g_str = g_string.as_str();
797        (UnicodeWidthStr::width(g_str), g_str.chars().count())
798    }
799}
800
801pub fn grapheme_width_and_bytes_len(g: RopeSlice) -> (usize, usize) {
802    if let Some(g_str) = g.as_str() {
803        (UnicodeWidthStr::width(g_str), g_str.len())
804    } else {
805        let g_string = g.to_string();
806        let g_str = g_string.as_str();
807        (UnicodeWidthStr::width(g_str), g_str.len())
808    }
809}
810
811pub fn grapheme_width(g: RopeSlice) -> usize {
812    if let Some(s) = g.as_str() {
813        UnicodeWidthStr::width(s)
814    } else {
815        let s = g.to_string();
816        UnicodeWidthStr::width(s.as_str())
817    }
818}
819
820
821#[cfg(test)]
822mod tests {
823    use super::*;
824
825    #[test]
826    fn test_insert() {
827        let mut code = Code::new("", "", None).unwrap();
828        code.insert(0, "Hello ");
829        code.insert(6, "World");
830        assert_eq!(code.content.to_string(), "Hello World");
831    }
832
833    #[test]
834    fn test_remove() {
835        let mut code = Code::new("Hello World", "", None).unwrap();
836        code.remove(5, 11);
837        assert_eq!(code.content.to_string(), "Hello");
838    }
839
840    #[test]
841    fn test_undo() {
842        let mut code = Code::new("", "", None).unwrap();
843
844        code.tx();
845        code.insert(0, "Hello ");
846        code.commit();
847
848        code.tx();
849        code.insert(6, "World");
850        code.commit();
851
852        code.undo();
853        assert_eq!(code.content.to_string(), "Hello ");
854
855        code.undo();
856        assert_eq!(code.content.to_string(), "");
857    }
858
859    #[test]
860    fn test_redo() {
861        let mut code = Code::new("", "", None).unwrap();
862
863        code.tx();
864        code.insert(0, "Hello");
865        code.commit();
866
867        code.undo();
868        assert_eq!(code.content.to_string(), "");
869
870        code.redo();
871        assert_eq!(code.content.to_string(), "Hello");
872    }
873
874    #[test]
875    fn test_indentation_level0() {
876        let mut code = Code::new("", "unknown", None).unwrap();
877        code.insert(0, "    hello world");
878        assert_eq!(code.indentation_level(0, 10), 0);
879    }
880
881    #[test]
882    fn test_indentation_level() {
883        let mut code = Code::new("", "python", None).unwrap();
884        code.insert(0, "    print('Hello, World!')");
885        assert_eq!(code.indentation_level(0, 10), 1);
886    }
887
888    #[test]
889    fn test_indentation_level2() {
890        let mut code = Code::new("", "python", None).unwrap();
891        code.insert(0, "        print('Hello, World!')");
892        assert_eq!(code.indentation_level(0, 10), 2);
893    }
894
895    #[test]
896    fn test_is_only_indentation_before() {
897        let mut code = Code::new("", "python", None).unwrap();
898        code.insert(0, "    print('Hello, World!')");
899        assert_eq!(code.is_only_indentation_before(0, 4), true);
900        assert_eq!(code.is_only_indentation_before(0, 10), false);
901    }
902
903    #[test]
904    fn test_is_only_indentation_before2() {
905        let mut code = Code::new("", "", None).unwrap();
906        code.insert(0, "    Hello, World");
907        assert_eq!(code.is_only_indentation_before(0, 4), false);
908        assert_eq!(code.is_only_indentation_before(0, 10), false);
909    }
910
911    #[test]
912    fn test_smart_paste_1() {
913        let initial = "fn foo() {\n    let x = 1;\n    \n}";
914        let mut code = Code::new(initial, "rust", None).unwrap();
915
916        let offset = 30;
917        let paste = "if start == end && start == self.code.len() {\n    return;\n}";
918        code.smart_paste(offset, paste);
919
920        let expected =
921            "fn foo() {\n    let x = 1;\n    if start == end && start == self.code.len() {\n        return;\n    }\n}";
922        assert_eq!(code.get_content(), expected);
923    }
924
925    #[test]
926    fn test_smart_paste_2() {
927        let initial = "fn foo() {\n    let x = 1;\n    \n}";
928        let mut code = Code::new(initial, "rust", None).unwrap();
929
930        let offset = 30;
931        let paste = "    if start == end && start == self.code.len() {\n        return;\n    }";
932        code.smart_paste(offset, paste);
933
934        let expected =
935            "fn foo() {\n    let x = 1;\n    if start == end && start == self.code.len() {\n        return;\n    }\n}";
936        assert_eq!(code.get_content(), expected);
937    }
938}