Implement scope-specific bracket matching and comment toggling

Co-authored-by: Julia Risley <julia@zed.dev>
This commit is contained in:
Max Brunsfeld 2023-01-19 15:04:27 -08:00
parent 1851e2e77c
commit 2967b46a17
7 changed files with 129 additions and 38 deletions

View file

@ -9,7 +9,7 @@ use crate::{
syntax_map::{
SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
},
CodeLabel, LanguageConfigYeet, Outline,
CodeLabel, LanguageScope, Outline,
};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
@ -2015,7 +2015,7 @@ impl BufferSnapshot {
.or(self.language.as_ref())
}
pub fn language_config_at<D: ToOffset>(&self, position: D) -> Option<LanguageConfigYeet> {
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
if let Some(layer_info) = self
@ -2024,12 +2024,12 @@ impl BufferSnapshot {
.filter(|l| l.node.end_byte() > offset)
.last()
{
Some(LanguageConfigYeet {
Some(LanguageScope {
language: layer_info.language.clone(),
override_id: layer_info.override_id(offset, &self.text),
})
} else {
self.language.clone().map(|language| LanguageConfigYeet {
self.language.clone().map(|language| LanguageScope {
language,
override_id: None,
})

View file

@ -1369,6 +1369,89 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut MutableAppContext) {
});
}
#[gpui::test]
fn test_language_config_at(cx: &mut MutableAppContext) {
cx.set_global(Settings::test(cx));
cx.add_model(|cx| {
let language = Language::new(
LanguageConfig {
name: "JavaScript".into(),
line_comment: Some("// ".into()),
brackets: vec![
BracketPair {
start: "{".into(),
end: "}".into(),
close: true,
newline: false,
},
BracketPair {
start: "'".into(),
end: "'".into(),
close: true,
newline: false,
},
],
overrides: [
(
"element".into(),
LanguageConfigOverride {
line_comment: Override::Remove { remove: true },
block_comment: Override::Set(("{/*".into(), "*/}".into())),
..Default::default()
},
),
(
"string".into(),
LanguageConfigOverride {
brackets: Override::Set(vec![BracketPair {
start: "{".into(),
end: "}".into(),
close: true,
newline: false,
}]),
..Default::default()
},
),
]
.into_iter()
.collect(),
..Default::default()
},
Some(tree_sitter_javascript::language()),
)
.with_override_query(
r#"
(jsx_element) @override.element
(string) @override.string
"#,
)
.unwrap();
let text = r#"a["b"] = <C d="e"></C>;"#;
let buffer = Buffer::new(0, text, cx).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot();
let config = snapshot.language_scope_at(0).unwrap();
assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// ");
assert_eq!(config.brackets().len(), 2);
let string_config = snapshot.language_scope_at(3).unwrap();
assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// ");
assert_eq!(string_config.brackets().len(), 1);
let element_config = snapshot.language_scope_at(10).unwrap();
assert_eq!(element_config.line_comment_prefix(), None);
assert_eq!(
element_config.block_comment_delimiters(),
Some((&"{/*".into(), &"*/}".into()))
);
assert_eq!(element_config.brackets().len(), 2);
buffer
});
}
#[gpui::test]
fn test_serialization(cx: &mut gpui::MutableAppContext) {
let mut now = Instant::now();

View file

@ -22,10 +22,7 @@ use lazy_static::lazy_static;
use parking_lot::{Mutex, RwLock};
use postage::watch;
use regex::Regex;
use serde::{
de::{self},
Deserialize, Deserializer,
};
use serde::{de, Deserialize, Deserializer};
use serde_json::Value;
use std::{
any::Any,
@ -251,20 +248,22 @@ pub struct LanguageConfig {
}
#[derive(Clone)]
pub struct LanguageConfigYeet {
pub struct LanguageScope {
language: Arc<Language>,
override_id: Option<u32>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Default, Debug)]
pub struct LanguageConfigOverride {
#[serde(default)]
pub line_comment: Override<Arc<str>>,
#[serde(default)]
pub block_comment: Override<(Arc<str>, Arc<str>)>,
#[serde(default)]
pub brackets: Override<Vec<BracketPair>>,
}
#[derive(Deserialize)]
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum Override<T> {
Remove { remove: bool },
@ -278,11 +277,11 @@ impl<T> Default for Override<T> {
}
impl<T> Override<T> {
fn as_option<'a>(this: Option<&'a Self>, original: &'a Option<T>) -> Option<&'a T> {
fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> {
match this {
Some(Self::Set(value)) => Some(value),
Some(Self::Remove { remove: true }) => None,
Some(Self::Remove { remove: false }) | None => original.as_ref(),
Some(Self::Remove { remove: false }) | None => original,
}
}
}
@ -966,40 +965,39 @@ impl Language {
}
}
impl LanguageConfigYeet {
impl LanguageScope {
pub fn line_comment_prefix(&self) -> Option<&Arc<str>> {
Override::as_option(
self.over_ride().map(|o| &o.line_comment),
&self.language.config.line_comment,
self.config_override().map(|o| &o.line_comment),
self.language.config.line_comment.as_ref(),
)
}
pub fn block_comment_delimiters(&self) -> Option<(&Arc<str>, &Arc<str>)> {
Override::as_option(
self.over_ride().map(|o| &o.block_comment),
&self.language.config.block_comment,
self.config_override().map(|o| &o.block_comment),
self.language.config.block_comment.as_ref(),
)
.map(|e| (&e.0, &e.1))
}
pub fn brackets(&self) -> &[BracketPair] {
&self.language.config.brackets
Override::as_option(
self.config_override().map(|o| &o.brackets),
Some(&self.language.config.brackets),
)
.map_or(&[], Vec::as_slice)
}
pub fn should_autoclose_before(&self, c: char) -> bool {
c.is_whitespace() || self.language.config.autoclose_before.contains(c)
}
fn over_ride(&self) -> Option<&LanguageConfigOverride> {
self.override_id.and_then(|id| {
self.language
.grammar
.as_ref()?
.override_config
.as_ref()?
.values
.get(&id)
})
fn config_override(&self) -> Option<&LanguageConfigOverride> {
let id = self.override_id?;
let grammar = self.language.grammar.as_ref()?;
let override_config = grammar.override_config.as_ref()?;
override_config.values.get(&id)
}
}