use std::rc::Rc; use std::{cell::RefCell, ops::Range}; use gpui::{App, SharedString}; use ropey::Rope; use tree_sitter::{InputEdit, Point}; use crate::input::RopeExt as _; use crate::{highlighter::SyntaxHighlighter, input::marker::Marker}; use super::text_wrapper::TextWrapper; #[derive(Debug, Copy, Clone)] pub struct TabSize { /// Default is 2 pub tab_size: usize, /// Set true to use `\t` as tab indent, default is false pub hard_tabs: bool, } impl Default for TabSize { fn default() -> Self { Self { tab_size: 2, hard_tabs: false, } } } impl TabSize { pub(super) fn to_string(&self) -> SharedString { if self.hard_tabs { "\t".into() } else { " ".repeat(self.tab_size).into() } } } #[derive(Default, Clone)] pub enum InputMode { #[default] SingleLine, MultiLine { tab: TabSize, rows: usize, }, AutoGrow { rows: usize, min_rows: usize, max_rows: usize, }, CodeEditor { tab: TabSize, rows: usize, /// Show line number line_number: bool, language: SharedString, highlighter: Rc>>, markers: Rc>, }, } #[allow(unused)] impl InputMode { #[inline] pub(super) fn is_single_line(&self) -> bool { matches!(self, InputMode::SingleLine) } #[inline] pub(super) fn is_code_editor(&self) -> bool { matches!(self, InputMode::CodeEditor { .. }) } #[inline] pub(super) fn is_auto_grow(&self) -> bool { matches!(self, InputMode::AutoGrow { .. }) } #[inline] pub(super) fn is_multi_line(&self) -> bool { matches!( self, InputMode::MultiLine { .. } | InputMode::AutoGrow { .. } | InputMode::CodeEditor { .. } ) } pub(super) fn set_rows(&mut self, new_rows: usize) { match self { InputMode::MultiLine { rows, .. } => { *rows = new_rows; } InputMode::CodeEditor { rows, .. } => { *rows = new_rows; } InputMode::AutoGrow { rows, min_rows, max_rows, } => { *rows = new_rows.clamp(*min_rows, *max_rows); } _ => {} } } pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) { let wrapped_lines = text_wrapper.wrapped_lines.len(); self.set_rows(wrapped_lines); } /// At least 1 row be return. pub(super) fn rows(&self) -> usize { match self { InputMode::MultiLine { rows, .. } => *rows, InputMode::CodeEditor { rows, .. } => *rows, InputMode::AutoGrow { rows, .. } => *rows, _ => 1, } .max(1) } /// At least 1 row be return. #[allow(unused)] pub(super) fn min_rows(&self) -> usize { match self { InputMode::MultiLine { .. } | InputMode::CodeEditor { .. } => 1, InputMode::AutoGrow { min_rows, .. } => *min_rows, _ => 1, } .max(1) } #[allow(unused)] pub(super) fn max_rows(&self) -> usize { match self { InputMode::MultiLine { .. } | InputMode::CodeEditor { .. } => usize::MAX, InputMode::AutoGrow { max_rows, .. } => *max_rows, _ => 1, } } /// Return false if the mode is not [`InputMode::CodeEditor`]. #[allow(unused)] #[inline] pub(super) fn line_number(&self) -> bool { match self { InputMode::CodeEditor { line_number, .. } => *line_number, _ => false, } } #[inline] pub(super) fn tab_size(&self) -> Option<&TabSize> { match self { InputMode::MultiLine { tab, .. } => Some(tab), InputMode::CodeEditor { tab, .. } => Some(tab), _ => None, } } pub(super) fn update_highlighter( &mut self, selected_range: &Range, text: &Rope, new_text: &str, force: bool, cx: &mut App, ) { match &self { InputMode::CodeEditor { language, highlighter, .. } => { if !force && highlighter.borrow().is_some() { return; } let mut highlighter = highlighter.borrow_mut(); if highlighter.is_none() { let new_highlighter = SyntaxHighlighter::new(language, cx); highlighter.replace(new_highlighter); } let Some(highlighter) = highlighter.as_mut() else { return; }; // When full text changed, the selected_range may be out of bound (The before version). let mut selected_range = selected_range.clone(); selected_range.end = selected_range.end.min(text.len_bytes()); // If insert a chart, this is 1. // If backspace or delete, this is -1. // If selected to delete, this is the length of the selected text. // let changed_len = new_text.len() as isize - selected_range.len() as isize; let changed_len = new_text.len() as isize - selected_range.len() as isize; let new_end = (selected_range.end as isize + changed_len) as usize; let start_pos = text.line_column(selected_range.start); let old_end_pos = text.line_column(selected_range.end); let new_end_pos = text.line_column(new_end); let edit = InputEdit { start_byte: selected_range.start, old_end_byte: selected_range.end, new_end_byte: new_end, start_position: Point::new(start_pos.0, start_pos.1), old_end_position: Point::new(old_end_pos.0, old_end_pos.1), new_end_position: Point::new(new_end_pos.0, new_end_pos.1), }; highlighter.update(Some(edit), text, cx); } _ => {} } } pub(super) fn clear_markers(&mut self) { match self { InputMode::CodeEditor { markers, .. } => *markers = Rc::new(vec![]), _ => {} } } #[allow(unused)] pub(super) fn markers(&self) -> Option<&Rc>> { match self { InputMode::CodeEditor { markers, .. } => Some(markers), _ => None, } } pub(super) fn set_markers(&mut self, new_markers: Vec) { match self { InputMode::CodeEditor { markers, .. } => *markers = Rc::new(new_markers), _ => {} } } pub(super) fn marker_for_offset(&self, offset: usize) -> Option<&Marker> { let Some(markers) = self.markers() else { return None; }; for marker in markers.iter() { if let Some(range) = marker.range.as_ref() { if range.contains(&offset) { return Some(marker); } } } None } } #[cfg(test)] mod tests { use super::TabSize; #[test] fn test_tab_size() { let tab = TabSize { tab_size: 2, hard_tabs: false, }; assert_eq!(tab.to_string(), " "); let tab = TabSize { tab_size: 4, hard_tabs: false, }; assert_eq!(tab.to_string(), " "); let tab = TabSize { tab_size: 2, hard_tabs: true, }; assert_eq!(tab.to_string(), "\t"); let tab = TabSize { tab_size: 4, hard_tabs: true, }; assert_eq!(tab.to_string(), "\t"); } }