mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Merge pull request #203 from zed-industries/autoclose-pairs
Autoclose pairs
This commit is contained in:
commit
a0c8b60a1b
@ -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<String>,
|
||||
pub autoclose_pairs: Vec<AutoclosePair>,
|
||||
}
|
||||
|
||||
#[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<HighlightMap>,
|
||||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Grammar,
|
||||
pub(crate) highlight_query: Query,
|
||||
pub(crate) brackets_query: Query,
|
||||
pub(crate) highlight_map: Mutex<HighlightMap>,
|
||||
}
|
||||
|
||||
#[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> {
|
||||
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> {
|
||||
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()
|
||||
}
|
||||
|
@ -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<T: ToOffset>(&self, position: T) -> impl Iterator<Item = u8> + '_ {
|
||||
let offset = position.to_offset(self);
|
||||
self.visible_text.bytes_at(offset)
|
||||
}
|
||||
|
||||
pub fn contains_str_at<T>(&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<Item = Edit> {
|
||||
let since_2 = since.clone();
|
||||
let cursor = if since == self.version {
|
||||
@ -4078,19 +4095,17 @@ mod tests {
|
||||
}
|
||||
|
||||
fn rust_lang() -> Arc<Language> {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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<Item = u8> + '_ {
|
||||
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())
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<Selection>,
|
||||
next_selection_id: usize,
|
||||
add_selections_state: Option<AddSelectionsState>,
|
||||
autoclose_stack: Vec<AutoclosePairState>,
|
||||
select_larger_syntax_node_stack: Vec<Arc<[Selection]>>,
|
||||
scroll_position: Vector2F,
|
||||
scroll_top_anchor: Anchor,
|
||||
@ -319,6 +320,11 @@ struct AddSelectionsState {
|
||||
stack: Vec<usize>,
|
||||
}
|
||||
|
||||
struct AutoclosePairState {
|
||||
ranges: SmallVec<[Range<Anchor>; 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<Self>) {
|
||||
pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext<Self>) {
|
||||
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>) {
|
||||
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<Self>) {
|
||||
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::<SmallVec<[_; 32]>>();
|
||||
|
||||
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<Self>) -> 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>) {
|
||||
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<Self>) {
|
||||
@ -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<Range<DisplayPoint>> {
|
||||
self.selections_in_range(
|
||||
|
@ -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;
|
||||
|
@ -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")
|
||||
|
@ -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<Workspace> {
|
||||
#[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::<Editor>().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()));
|
||||
});
|
||||
|
||||
|
@ -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 = " */" },
|
||||
]
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user