diff --git a/crates/buffer/src/language.rs b/crates/buffer/src/language.rs index 8970aaa444855eafbbadbdb04711a4b98bb58e38..1a9a29aac5a233479fef6ee6c16ddfce83a7d8cf 100644 --- a/crates/buffer/src/language.rs +++ b/crates/buffer/src/language.rs @@ -1,29 +1,31 @@ -use crate::{HighlightMap}; +use crate::HighlightMap; +use anyhow::Result; use parking_lot::Mutex; use serde::Deserialize; use std::{path::Path, str, sync::Arc}; +use theme::SyntaxTheme; use tree_sitter::{Language as Grammar, Query}; pub use tree_sitter::{Parser, Tree}; -use theme::SyntaxTheme; #[derive(Default, Deserialize)] pub struct LanguageConfig { pub name: String, pub path_suffixes: Vec, + pub autoclose_pairs: Vec, } -#[derive(Deserialize)] -pub struct BracketPair { +#[derive(Clone, Deserialize)] +pub struct AutoclosePair { pub start: String, pub end: String, } pub struct Language { - pub config: LanguageConfig, - pub grammar: Grammar, - pub highlight_query: Query, - pub brackets_query: Query, - pub highlight_map: Mutex, + pub(crate) config: LanguageConfig, + pub(crate) grammar: Grammar, + pub(crate) highlight_query: Query, + pub(crate) brackets_query: Query, + pub(crate) highlight_map: Mutex, } #[derive(Default)] @@ -62,10 +64,34 @@ impl LanguageRegistry { } impl Language { + pub fn new(config: LanguageConfig, grammar: Grammar) -> Self { + Self { + config, + brackets_query: Query::new(grammar, "").unwrap(), + highlight_query: Query::new(grammar, "").unwrap(), + grammar, + highlight_map: Default::default(), + } + } + + pub fn with_highlights_query(mut self, highlights_query_source: &str) -> Result { + self.highlight_query = Query::new(self.grammar, highlights_query_source)?; + Ok(self) + } + + pub fn with_brackets_query(mut self, brackets_query_source: &str) -> Result { + self.brackets_query = Query::new(self.grammar, brackets_query_source)?; + Ok(self) + } + pub fn name(&self) -> &str { self.config.name.as_str() } + pub fn autoclose_pairs(&self) -> &[AutoclosePair] { + &self.config.autoclose_pairs + } + pub fn highlight_map(&self) -> HighlightMap { self.highlight_map.lock().clone() } diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index cb1fe11c6e019691d31e1a8d9643b48b3ab497c6..a2f34a5d78ffd8734306c15d87f5e792b0ec3dd3 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -14,7 +14,7 @@ use clock::ReplicaId; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; pub use highlight_map::{HighlightId, HighlightMap}; use language::Tree; -pub use language::{Language, LanguageConfig, LanguageRegistry}; +pub use language::{AutoclosePair, Language, LanguageConfig, LanguageRegistry}; use lazy_static::lazy_static; use operation_queue::OperationQueue; use parking_lot::Mutex; @@ -1110,6 +1110,23 @@ impl Buffer { self.visible_text.chars_at(offset) } + pub fn bytes_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.bytes_at(offset) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_at(position) + .take(needle.len()) + .eq(needle.bytes()) + } + pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator { let since_2 = since.clone(); let cursor = if since == self.version { @@ -4078,19 +4095,17 @@ mod tests { } fn rust_lang() -> Arc { - let lang = tree_sitter_rust::language(); - let brackets_query = r#" - ("{" @open "}" @close) - "#; - Arc::new(Language { - config: LanguageConfig { - name: "Rust".to_string(), - path_suffixes: vec!["rs".to_string()], - }, - grammar: tree_sitter_rust::language(), - highlight_query: tree_sitter::Query::new(lang.clone(), "").unwrap(), - brackets_query: tree_sitter::Query::new(lang.clone(), brackets_query).unwrap(), - highlight_map: Default::default(), - }) + Arc::new( + Language::new( + LanguageConfig { + name: "Rust".to_string(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + tree_sitter_rust::language(), + ) + .with_brackets_query(r#" ("{" @open "}" @close) "#) + .unwrap(), + ) } } diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 8467a98947f2e7d2f821a44b330305c013e7fb8b..a5f4b905ba1de9b554d0457d47d363c7749090f3 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -115,6 +115,10 @@ impl Rope { self.chunks_in_range(start..self.len()).flat_map(str::chars) } + pub fn bytes_at(&self, start: usize) -> impl Iterator + '_ { + self.chunks_in_range(start..self.len()).flat_map(str::bytes) + } + pub fn chunks<'a>(&'a self) -> Chunks<'a> { self.chunks_in_range(0..self.len()) } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 8bc6881aed735fb61abf6515586c90bf8c9bfb59..d9655d9a9c13386413ec83ce2cfcb56afafaf07d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -670,7 +670,6 @@ mod tests { async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) { use unindent::Unindent as _; - let grammar = tree_sitter_rust::language(); let text = r#" fn outer() {} @@ -678,28 +677,28 @@ mod tests { fn inner() {} }"# .unindent(); - let highlight_query = tree_sitter::Query::new( - grammar, - r#" - (mod_item name: (identifier) body: _ @mod.body) - (function_item name: (identifier) @fn.name)"#, - ) - .unwrap(); + let theme = SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ]); - let lang = Arc::new(Language { - config: LanguageConfig { - name: "Test".to_string(), - path_suffixes: vec![".test".to_string()], - ..Default::default() - }, - grammar: grammar.clone(), - highlight_query, - brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }); + let lang = Arc::new( + Language::new( + LanguageConfig { + name: "Test".to_string(), + path_suffixes: vec![".test".to_string()], + ..Default::default() + }, + tree_sitter_rust::language(), + ) + .with_highlights_query( + r#" + (mod_item name: (identifier) body: _ @mod.body) + (function_item name: (identifier) @fn.name) + "#, + ) + .unwrap(), + ); lang.set_theme(&theme); let buffer = cx.add_model(|cx| { @@ -759,7 +758,6 @@ mod tests { cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); - let grammar = tree_sitter_rust::language(); let text = r#" fn outer() {} @@ -767,28 +765,28 @@ mod tests { fn inner() {} }"# .unindent(); - let highlight_query = tree_sitter::Query::new( - grammar, - r#" - (mod_item name: (identifier) body: _ @mod.body) - (function_item name: (identifier) @fn.name)"#, - ) - .unwrap(); + let theme = SyntaxTheme::new(vec![ ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), ]); - let lang = Arc::new(Language { - config: LanguageConfig { - name: "Test".to_string(), - path_suffixes: vec![".test".to_string()], - ..Default::default() - }, - grammar: grammar.clone(), - highlight_query, - brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - }); + let lang = Arc::new( + Language::new( + LanguageConfig { + name: "Test".to_string(), + path_suffixes: vec![".test".to_string()], + ..Default::default() + }, + tree_sitter_rust::language(), + ) + .with_highlights_query( + r#" + (mod_item name: (identifier) body: _ @mod.body) + (function_item name: (identifier) @fn.name) + "#, + ) + .unwrap(), + ); lang.set_theme(&theme); let buffer = cx.add_model(|cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 76bab6a1fd7031b31552610e6049ce7357a816d3..e3e48e475c2dd1bbb9043044ea55ac64dfb89d62 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,5 +1,5 @@ use super::{ - DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Insert, Scroll, Select, + DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, SelectPhase, Snapshot, MAX_LINE_LEN, }; use buffer::HighlightId; @@ -143,7 +143,7 @@ impl EditorElement { if chars.chars().any(|c| c.is_control()) || keystroke.cmd || keystroke.ctrl { false } else { - cx.dispatch_action(Insert(chars.to_string())); + cx.dispatch_action(Input(chars.to_string())); true } } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index ad0585837b0f1ae0f4f5810cb3f3192cbe37d97e..856aa377996ce0f2fa382800fe33f23070b2f1d0 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -37,7 +37,7 @@ const MAX_LINE_LEN: usize = 1024; action!(Cancel); action!(Backspace); action!(Delete); -action!(Insert, String); +action!(Input, String); action!(DeleteLine); action!(DeleteToPreviousWordBoundary); action!(DeleteToNextWordBoundary); @@ -95,13 +95,13 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("ctrl-h", Backspace, Some("Editor")), Binding::new("delete", Delete, Some("Editor")), Binding::new("ctrl-d", Delete, Some("Editor")), - Binding::new("enter", Insert("\n".into()), Some("Editor && mode == full")), + Binding::new("enter", Input("\n".into()), Some("Editor && mode == full")), Binding::new( "alt-enter", - Insert("\n".into()), + Input("\n".into()), Some("Editor && mode == auto_height"), ), - Binding::new("tab", Insert("\t".into()), Some("Editor")), + Binding::new("tab", Input("\t".into()), Some("Editor")), Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")), Binding::new( "alt-backspace", @@ -192,7 +192,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx)); cx.add_action(Editor::select); cx.add_action(Editor::cancel); - cx.add_action(Editor::insert); + cx.add_action(Editor::handle_input); cx.add_action(Editor::backspace); cx.add_action(Editor::delete); cx.add_action(Editor::delete_line); @@ -292,6 +292,7 @@ pub struct Editor { pending_selection: Option, next_selection_id: usize, add_selections_state: Option, + autoclose_stack: Vec, select_larger_syntax_node_stack: Vec>, scroll_position: Vector2F, scroll_top_anchor: Anchor, @@ -319,6 +320,11 @@ struct AddSelectionsState { stack: Vec, } +struct AutoclosePairState { + ranges: SmallVec<[Range; 32]>, + pair: AutoclosePair, +} + #[derive(Serialize, Deserialize)] struct ClipboardSelection { len: usize, @@ -404,6 +410,7 @@ impl Editor { pending_selection: None, next_selection_id, add_selections_state: None, + autoclose_stack: Default::default(), select_larger_syntax_node_stack: Vec::new(), build_settings, scroll_position: Vector2F::zero(), @@ -733,7 +740,18 @@ impl Editor { Ok(()) } - pub fn insert(&mut self, action: &Insert, cx: &mut ViewContext) { + pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext) { + let text = action.0.as_ref(); + if !self.skip_autoclose_end(text, cx) { + self.start_transaction(cx); + self.insert(text, cx); + self.autoclose_pairs(cx); + self.end_transaction(cx); + } + } + + fn insert(&mut self, text: &str, cx: &mut ViewContext) { + self.start_transaction(cx); let mut old_selections = SmallVec::<[_; 32]>::new(); { let selections = self.selections(cx); @@ -745,12 +763,11 @@ impl Editor { } } - self.start_transaction(cx); let mut new_selections = Vec::new(); self.buffer.update(cx, |buffer, cx| { let edit_ranges = old_selections.iter().map(|(_, range)| range.clone()); - buffer.edit(edit_ranges, action.0.as_str(), cx); - let text_len = action.0.len() as isize; + buffer.edit(edit_ranges, text, cx); + let text_len = text.len() as isize; let mut delta = 0_isize; new_selections = old_selections .into_iter() @@ -775,10 +792,115 @@ impl Editor { self.end_transaction(cx); } + fn autoclose_pairs(&mut self, cx: &mut ViewContext) { + let selections = self.selections(cx); + let new_autoclose_pair_state = self.buffer.update(cx, |buffer, cx| { + let autoclose_pair = buffer.language().and_then(|language| { + let first_selection_start = selections.first().unwrap().start.to_offset(&*buffer); + let pair = language.autoclose_pairs().iter().find(|pair| { + buffer.contains_str_at( + first_selection_start.saturating_sub(pair.start.len()), + &pair.start, + ) + }); + pair.and_then(|pair| { + let should_autoclose = selections[1..].iter().all(|selection| { + let selection_start = selection.start.to_offset(&*buffer); + buffer.contains_str_at( + selection_start.saturating_sub(pair.start.len()), + &pair.start, + ) + }); + + if should_autoclose { + Some(pair.clone()) + } else { + None + } + }) + }); + + autoclose_pair.and_then(|pair| { + let selection_ranges = selections + .iter() + .map(|selection| { + let start = selection.start.to_offset(&*buffer); + start..start + }) + .collect::>(); + + buffer.edit(selection_ranges, &pair.end, cx); + + if pair.end.len() == 1 { + Some(AutoclosePairState { + ranges: selections + .iter() + .map(|selection| { + selection.start.bias_left(buffer) + ..selection.start.bias_right(buffer) + }) + .collect(), + pair, + }) + } else { + None + } + }) + }); + self.autoclose_stack.extend(new_autoclose_pair_state); + } + + fn skip_autoclose_end(&mut self, text: &str, cx: &mut ViewContext) -> bool { + let old_selections = self.selections(cx); + let autoclose_pair_state = if let Some(autoclose_pair_state) = self.autoclose_stack.last() { + autoclose_pair_state + } else { + return false; + }; + if text != autoclose_pair_state.pair.end { + return false; + } + + debug_assert_eq!(old_selections.len(), autoclose_pair_state.ranges.len()); + + let buffer = self.buffer.read(cx); + let old_selection_ranges: SmallVec<[_; 32]> = old_selections + .iter() + .map(|selection| (selection.id, selection.offset_range(buffer))) + .collect(); + if old_selection_ranges + .iter() + .zip(&autoclose_pair_state.ranges) + .all(|((_, selection_range), autoclose_range)| { + let autoclose_range_end = autoclose_range.end.to_offset(buffer); + selection_range.is_empty() && selection_range.start == autoclose_range_end + }) + { + let new_selections = old_selection_ranges + .into_iter() + .map(|(id, range)| { + let new_head = buffer.anchor_before(range.start + 1); + Selection { + id, + start: new_head.clone(), + end: new_head, + reversed: false, + goal: SelectionGoal::None, + } + }) + .collect(); + self.autoclose_stack.pop(); + self.update_selections(new_selections, true, cx); + true + } else { + false + } + } + pub fn clear(&mut self, cx: &mut ViewContext) { self.start_transaction(cx); self.select_all(&SelectAll, cx); - self.insert(&Insert(String::new()), cx); + self.insert("", cx); self.end_transaction(cx); } @@ -801,7 +923,7 @@ impl Editor { } self.update_selections(selections, true, cx); - self.insert(&Insert(String::new()), cx); + self.insert("", cx); self.end_transaction(cx); } @@ -824,7 +946,7 @@ impl Editor { } self.update_selections(selections, true, cx); - self.insert(&Insert(String::new()), cx); + self.insert(&"", cx); self.end_transaction(cx); } @@ -1172,7 +1294,7 @@ impl Editor { } } self.update_selections(selections, true, cx); - self.insert(&Insert(String::new()), cx); + self.insert("", cx); self.end_transaction(cx); cx.as_mut() @@ -1219,7 +1341,6 @@ impl Editor { clipboard_selections.clear(); } - self.start_transaction(cx); let mut start_offset = 0; let mut new_selections = Vec::with_capacity(selections.len()); for (i, selection) in selections.iter().enumerate() { @@ -1262,9 +1383,8 @@ impl Editor { }); } self.update_selections(new_selections, true, cx); - self.end_transaction(cx); } else { - self.insert(&Insert(clipboard_text.into()), cx); + self.insert(clipboard_text, cx); } } } @@ -1506,7 +1626,7 @@ impl Editor { } self.update_selections(selections, true, cx); - self.insert(&Insert(String::new()), cx); + self.insert("", cx); self.end_transaction(cx); } @@ -1576,7 +1696,7 @@ impl Editor { } self.update_selections(selections, true, cx); - self.insert(&Insert(String::new()), cx); + self.insert("", cx); self.end_transaction(cx); } @@ -2104,20 +2224,41 @@ impl Editor { } } - self.buffer.update(cx, |buffer, cx| { - buffer - .update_selection_set(self.selection_set_id, selections, cx) - .unwrap(); - }); - self.pause_cursor_blinking(cx); + self.add_selections_state = None; + self.select_larger_syntax_node_stack.clear(); + while let Some(autoclose_pair_state) = self.autoclose_stack.last() { + let all_selections_inside_autoclose_ranges = + if selections.len() == autoclose_pair_state.ranges.len() { + selections.iter().zip(&autoclose_pair_state.ranges).all( + |(selection, autoclose_range)| { + let head = selection.head(); + autoclose_range.start.cmp(head, buffer).unwrap() <= Ordering::Equal + && autoclose_range.end.cmp(head, buffer).unwrap() >= Ordering::Equal + }, + ) + } else { + false + }; + + if all_selections_inside_autoclose_ranges { + break; + } else { + self.autoclose_stack.pop(); + } + } if autoscroll { self.autoscroll_requested = true; cx.notify(); } - self.add_selections_state = None; - self.select_larger_syntax_node_stack.clear(); + self.pause_cursor_blinking(cx); + + self.buffer.update(cx, |buffer, cx| { + buffer + .update_selection_set(self.selection_set_id, selections, cx) + .unwrap(); + }); } fn start_transaction(&self, cx: &mut ViewContext) { @@ -3666,9 +3807,9 @@ mod tests { // is pasted at each cursor. view.update(cx, |view, cx| { view.select_ranges(vec![0..0, 31..31], false, cx); - view.insert(&Insert("( ".into()), cx); + view.handle_input(&Input("( ".into()), cx); view.paste(&Paste, cx); - view.insert(&Insert(") ".into()), cx); + view.handle_input(&Input(") ".into()), cx); assert_eq!( view.display_text(cx), "( one✅ three five ) two one✅ four three six five ( one✅ three five ) " @@ -3677,7 +3818,7 @@ mod tests { view.update(cx, |view, cx| { view.select_ranges(vec![0..0], false, cx); - view.insert(&Insert("123\n4567\n89\n".into()), cx); + view.handle_input(&Input("123\n4567\n89\n".into()), cx); assert_eq!( view.display_text(cx), "123\n4567\n89\n( one✅ three five ) two one✅ four three six five ( one✅ three five ) " @@ -4068,15 +4209,10 @@ mod tests { #[gpui::test] async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - - let grammar = tree_sitter_rust::language(); - let language = Arc::new(Language { - config: LanguageConfig::default(), - brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - grammar, - }); + let language = Arc::new(Language::new( + LanguageConfig::default(), + tree_sitter_rust::language(), + )); let text = r#" use mod1::mod2::{mod3, mod4}; @@ -4086,6 +4222,7 @@ mod tests { } "# .unindent(); + let buffer = cx.add_model(|cx| { let history = History::new(text.into()); Buffer::from_history(0, history, None, Some(language), cx) @@ -4213,6 +4350,117 @@ mod tests { ); } + #[gpui::test] + async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) { + let settings = cx.read(EditorSettings::test); + let language = Arc::new(Language::new( + LanguageConfig { + autoclose_pairs: vec![ + AutoclosePair { + start: "{".to_string(), + end: "}".to_string(), + }, + AutoclosePair { + start: "/*".to_string(), + end: " */".to_string(), + }, + ], + ..Default::default() + }, + tree_sitter_rust::language(), + )); + + let text = r#" + a + + / + + "# + .unindent(); + + let buffer = cx.add_model(|cx| { + let history = History::new(text.into()); + Buffer::from_history(0, history, None, Some(language), cx) + }); + let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx)); + view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) + .await; + + view.update(&mut cx, |view, cx| { + view.select_display_ranges( + &[ + DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1), + DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), + ], + cx, + ) + .unwrap(); + view.handle_input(&Input("{".to_string()), cx); + view.handle_input(&Input("{".to_string()), cx); + view.handle_input(&Input("{".to_string()), cx); + assert_eq!( + view.text(cx), + " + {{{}}} + {{{}}} + / + + " + .unindent() + ); + + view.move_right(&MoveRight, cx); + view.handle_input(&Input("}".to_string()), cx); + view.handle_input(&Input("}".to_string()), cx); + view.handle_input(&Input("}".to_string()), cx); + assert_eq!( + view.text(cx), + " + {{{}}}} + {{{}}}} + / + + " + .unindent() + ); + + view.undo(&Undo, cx); + view.handle_input(&Input("/".to_string()), cx); + view.handle_input(&Input("*".to_string()), cx); + assert_eq!( + view.text(cx), + " + /* */ + /* */ + / + + " + .unindent() + ); + + view.undo(&Undo, cx); + view.select_display_ranges( + &[ + DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1), + DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0), + ], + cx, + ) + .unwrap(); + view.handle_input(&Input("*".to_string()), cx); + assert_eq!( + view.text(cx), + " + a + + /* + * + " + .unindent() + ); + }); + } + impl Editor { fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec> { self.selections_in_range( diff --git a/crates/file_finder/src/lib.rs b/crates/file_finder/src/lib.rs index 95c4ae9249554d302da6058e9dcda8494bab61ee..bd52023de8a9c641551b3f16c918f03f23f45c28 100644 --- a/crates/file_finder/src/lib.rs +++ b/crates/file_finder/src/lib.rs @@ -422,7 +422,7 @@ impl FileFinder { #[cfg(test)] mod tests { use super::*; - use editor::Insert; + use editor::Input; use serde_json::json; use std::path::PathBuf; use workspace::{Workspace, WorkspaceParams}; @@ -471,9 +471,9 @@ mod tests { let query_buffer = cx.read(|cx| finder.read(cx).query_editor.clone()); let chain = vec![finder.id(), query_buffer.id()]; - cx.dispatch_action(window_id, chain.clone(), Insert("b".into())); - cx.dispatch_action(window_id, chain.clone(), Insert("n".into())); - cx.dispatch_action(window_id, chain.clone(), Insert("a".into())); + cx.dispatch_action(window_id, chain.clone(), Input("b".into())); + cx.dispatch_action(window_id, chain.clone(), Input("n".into())); + cx.dispatch_action(window_id, chain.clone(), Input("a".into())); finder .condition(&cx, |finder, _| finder.matches.len() == 2) .await; diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 1de7d68185f92b322c6a7e7701bfdf7e3ba4176e..147a655f72e902aa8e3d437c3750bd48c60ab444 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -981,7 +981,7 @@ mod tests { self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials, EstablishConnectionError, UserStore, }, - editor::{Editor, EditorSettings, Insert}, + editor::{Editor, EditorSettings, Input}, fs::{FakeFs, Fs as _}, people_panel::JoinWorktree, project::{ProjectPath, Worktree}, @@ -1068,7 +1068,7 @@ mod tests { // Edit the buffer as client B and see that edit as client A. editor_b.update(&mut cx_b, |editor, cx| { - editor.insert(&Insert("ok, ".into()), cx) + editor.handle_input(&Input("ok, ".into()), cx) }); buffer_a .condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents") diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index 6a5fc7f76b200b3a7cc91f72cfd6c6acdca851d9..c227ee61bd892ea4dbb2f1a0894469abe69a0178 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -270,19 +270,15 @@ pub struct WorkspaceParams { impl WorkspaceParams { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut MutableAppContext) -> Self { - let grammar = tree_sitter_rust::language(); - let language = Arc::new(buffer::Language { - config: buffer::LanguageConfig { + let mut languages = LanguageRegistry::new(); + languages.add(Arc::new(buffer::Language::new( + buffer::LanguageConfig { name: "Rust".to_string(), path_suffixes: vec!["rs".to_string()], + ..Default::default() }, - brackets_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_query: tree_sitter::Query::new(grammar, "").unwrap(), - highlight_map: Default::default(), - grammar, - }); - let mut languages = LanguageRegistry::new(); - languages.add(language); + tree_sitter_rust::language(), + ))); let client = Client::new(); let http_client = client::test::FakeHttpClient::new(|_| async move { @@ -1074,7 +1070,7 @@ impl WorkspaceHandle for ViewHandle { #[cfg(test)] mod tests { use super::*; - use editor::{Editor, Insert}; + use editor::{Editor, Input}; use serde_json::json; use std::collections::HashSet; @@ -1286,7 +1282,7 @@ mod tests { item.to_any().downcast::().unwrap() }); - cx.update(|cx| editor.update(cx, |editor, cx| editor.insert(&Insert("x".into()), cx))); + cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input(&Input("x".into()), cx))); fs.insert_file("/root/a.txt", "changed".to_string()) .await .unwrap(); @@ -1339,7 +1335,7 @@ mod tests { assert!(!editor.is_dirty(cx.as_ref())); assert_eq!(editor.title(cx.as_ref()), "untitled"); assert!(editor.language(cx).is_none()); - editor.insert(&Insert("hi".into()), cx); + editor.handle_input(&Input("hi".into()), cx); assert!(editor.is_dirty(cx.as_ref())); }); @@ -1371,7 +1367,7 @@ mod tests { // Edit the file and save it again. This time, there is no filename prompt. editor.update(&mut cx, |editor, cx| { - editor.insert(&Insert(" there".into()), cx); + editor.handle_input(&Input(" there".into()), cx); assert_eq!(editor.is_dirty(cx.as_ref()), true); }); workspace.update(&mut cx, |workspace, cx| { @@ -1432,7 +1428,7 @@ mod tests { editor.update(&mut cx, |editor, cx| { assert!(editor.language(cx).is_none()); - editor.insert(&Insert("hi".into()), cx); + editor.handle_input(&Input("hi".into()), cx); assert!(editor.is_dirty(cx.as_ref())); }); diff --git a/crates/zed/languages/rust/config.toml b/crates/zed/languages/rust/config.toml index 4cde06f6f80fb035ce02e0f6d7e88cb657e88336..ece9b57ca25767d47995a80990db6152c6b5c228 100644 --- a/crates/zed/languages/rust/config.toml +++ b/crates/zed/languages/rust/config.toml @@ -1,8 +1,9 @@ name = "Rust" path_suffixes = ["rs"] -bracket_pairs = [ +autoclose_pairs = [ { start = "{", end = "}" }, { start = "[", end = "]" }, { start = "(", end = ")" }, - { start = "<", end = ">" }, + { start = "\"", end = "\"" }, + { start = "/*", end = " */" }, ] diff --git a/crates/zed/src/language.rs b/crates/zed/src/language.rs index af0581ed8ddf850a39160825d01830989d6baef2..774d8c650275d0ac5c495be1e5069f8a01d75330 100644 --- a/crates/zed/src/language.rs +++ b/crates/zed/src/language.rs @@ -1,8 +1,7 @@ -use buffer::{HighlightMap, Language, LanguageRegistry}; -use parking_lot::Mutex; +use buffer::{Language, LanguageRegistry}; use rust_embed::RustEmbed; +use std::borrow::Cow; use std::{str, sync::Arc}; -use tree_sitter::Query; #[derive(RustEmbed)] #[folder = "languages"] @@ -18,19 +17,16 @@ fn rust() -> Language { let grammar = tree_sitter_rust::language(); let rust_config = toml::from_slice(&LanguageDir::get("rust/config.toml").unwrap().data).unwrap(); - Language { - config: rust_config, - grammar, - highlight_query: load_query(grammar, "rust/highlights.scm"), - brackets_query: load_query(grammar, "rust/brackets.scm"), - highlight_map: Mutex::new(HighlightMap::default()), - } + Language::new(rust_config, grammar) + .with_highlights_query(load_query("rust/highlights.scm").as_ref()) + .unwrap() + .with_brackets_query(load_query("rust/brackets.scm").as_ref()) + .unwrap() } -fn load_query(grammar: tree_sitter::Language, path: &str) -> Query { - Query::new( - grammar, - str::from_utf8(&LanguageDir::get(path).unwrap().data).unwrap(), - ) - .unwrap() +fn load_query(path: &str) -> Cow<'static, str> { + match LanguageDir::get(path).unwrap().data { + Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), + Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), + } }