Skip to main content

ratatui_code_editor/
code.rs

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