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