Implement scope-specific bracket matching and comment toggling
Co-authored-by: Julia Risley <julia@zed.dev>
This commit is contained in:
parent
1851e2e77c
commit
2967b46a17
7 changed files with 129 additions and 38 deletions
|
@ -1737,7 +1737,7 @@ impl Editor {
|
||||||
for (selection, autoclose_region) in
|
for (selection, autoclose_region) in
|
||||||
self.selections_with_autoclose_regions(selections, &snapshot)
|
self.selections_with_autoclose_regions(selections, &snapshot)
|
||||||
{
|
{
|
||||||
if let Some(language) = snapshot.language_config_at(selection.head()) {
|
if let Some(language) = snapshot.language_scope_at(selection.head()) {
|
||||||
// Determine if the inserted text matches the opening or closing
|
// Determine if the inserted text matches the opening or closing
|
||||||
// bracket of any of this language's bracket pairs.
|
// bracket of any of this language's bracket pairs.
|
||||||
let mut bracket_pair = None;
|
let mut bracket_pair = None;
|
||||||
|
@ -1898,7 +1898,7 @@ impl Editor {
|
||||||
let end = selection.end;
|
let end = selection.end;
|
||||||
|
|
||||||
let mut insert_extra_newline = false;
|
let mut insert_extra_newline = false;
|
||||||
if let Some(language) = buffer.language_config_at(start) {
|
if let Some(language) = buffer.language_scope_at(start) {
|
||||||
let leading_whitespace_len = buffer
|
let leading_whitespace_len = buffer
|
||||||
.reversed_chars_at(start)
|
.reversed_chars_at(start)
|
||||||
.take_while(|c| c.is_whitespace() && *c != '\n')
|
.take_while(|c| c.is_whitespace() && *c != '\n')
|
||||||
|
@ -4535,7 +4535,7 @@ impl Editor {
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
let start_column = snapshot.indent_size_for_line(selection.start.row).len;
|
let start_column = snapshot.indent_size_for_line(selection.start.row).len;
|
||||||
let language = if let Some(language) =
|
let language = if let Some(language) =
|
||||||
snapshot.language_config_at(Point::new(selection.start.row, start_column))
|
snapshot.language_scope_at(Point::new(selection.start.row, start_column))
|
||||||
{
|
{
|
||||||
language
|
language
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||||
pub use language::Completion;
|
pub use language::Completion;
|
||||||
use language::{
|
use language::{
|
||||||
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||||
DiagnosticEntry, IndentSize, Language, LanguageConfigYeet, OffsetRangeExt, OffsetUtf16,
|
DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
|
||||||
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
|
OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
|
||||||
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -2691,9 +2691,9 @@ impl MultiBufferSnapshot {
|
||||||
.and_then(|(buffer, offset)| buffer.language_at(offset))
|
.and_then(|(buffer, offset)| buffer.language_at(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_config_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageConfigYeet> {
|
pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
|
||||||
self.point_to_buffer_offset(point)
|
self.point_to_buffer_offset(point)
|
||||||
.and_then(|(buffer, offset)| buffer.language_config_at(offset))
|
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
syntax_map::{
|
syntax_map::{
|
||||||
SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
|
SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
|
||||||
},
|
},
|
||||||
CodeLabel, LanguageConfigYeet, Outline,
|
CodeLabel, LanguageScope, Outline,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
@ -2015,7 +2015,7 @@ impl BufferSnapshot {
|
||||||
.or(self.language.as_ref())
|
.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);
|
let offset = position.to_offset(self);
|
||||||
|
|
||||||
if let Some(layer_info) = self
|
if let Some(layer_info) = self
|
||||||
|
@ -2024,12 +2024,12 @@ impl BufferSnapshot {
|
||||||
.filter(|l| l.node.end_byte() > offset)
|
.filter(|l| l.node.end_byte() > offset)
|
||||||
.last()
|
.last()
|
||||||
{
|
{
|
||||||
Some(LanguageConfigYeet {
|
Some(LanguageScope {
|
||||||
language: layer_info.language.clone(),
|
language: layer_info.language.clone(),
|
||||||
override_id: layer_info.override_id(offset, &self.text),
|
override_id: layer_info.override_id(offset, &self.text),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.language.clone().map(|language| LanguageConfigYeet {
|
self.language.clone().map(|language| LanguageScope {
|
||||||
language,
|
language,
|
||||||
override_id: None,
|
override_id: None,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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]
|
#[gpui::test]
|
||||||
fn test_serialization(cx: &mut gpui::MutableAppContext) {
|
fn test_serialization(cx: &mut gpui::MutableAppContext) {
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
|
|
@ -22,10 +22,7 @@ use lazy_static::lazy_static;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{
|
use serde::{de, Deserialize, Deserializer};
|
||||||
de::{self},
|
|
||||||
Deserialize, Deserializer,
|
|
||||||
};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
@ -251,20 +248,22 @@ pub struct LanguageConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LanguageConfigYeet {
|
pub struct LanguageScope {
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
override_id: Option<u32>,
|
override_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Default, Debug)]
|
||||||
pub struct LanguageConfigOverride {
|
pub struct LanguageConfigOverride {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub line_comment: Override<Arc<str>>,
|
pub line_comment: Override<Arc<str>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub block_comment: Override<(Arc<str>, Arc<str>)>,
|
pub block_comment: Override<(Arc<str>, Arc<str>)>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub brackets: Override<Vec<BracketPair>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Override<T> {
|
pub enum Override<T> {
|
||||||
Remove { remove: bool },
|
Remove { remove: bool },
|
||||||
|
@ -278,11 +277,11 @@ impl<T> Default for Override<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> 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 {
|
match this {
|
||||||
Some(Self::Set(value)) => Some(value),
|
Some(Self::Set(value)) => Some(value),
|
||||||
Some(Self::Remove { remove: true }) => None,
|
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>> {
|
pub fn line_comment_prefix(&self) -> Option<&Arc<str>> {
|
||||||
Override::as_option(
|
Override::as_option(
|
||||||
self.over_ride().map(|o| &o.line_comment),
|
self.config_override().map(|o| &o.line_comment),
|
||||||
&self.language.config.line_comment,
|
self.language.config.line_comment.as_ref(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block_comment_delimiters(&self) -> Option<(&Arc<str>, &Arc<str>)> {
|
pub fn block_comment_delimiters(&self) -> Option<(&Arc<str>, &Arc<str>)> {
|
||||||
Override::as_option(
|
Override::as_option(
|
||||||
self.over_ride().map(|o| &o.block_comment),
|
self.config_override().map(|o| &o.block_comment),
|
||||||
&self.language.config.block_comment,
|
self.language.config.block_comment.as_ref(),
|
||||||
)
|
)
|
||||||
.map(|e| (&e.0, &e.1))
|
.map(|e| (&e.0, &e.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn brackets(&self) -> &[BracketPair] {
|
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 {
|
pub fn should_autoclose_before(&self, c: char) -> bool {
|
||||||
c.is_whitespace() || self.language.config.autoclose_before.contains(c)
|
c.is_whitespace() || self.language.config.autoclose_before.contains(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn over_ride(&self) -> Option<&LanguageConfigOverride> {
|
fn config_override(&self) -> Option<&LanguageConfigOverride> {
|
||||||
self.override_id.and_then(|id| {
|
let id = self.override_id?;
|
||||||
self.language
|
let grammar = self.language.grammar.as_ref()?;
|
||||||
.grammar
|
let override_config = grammar.override_config.as_ref()?;
|
||||||
.as_ref()?
|
override_config.values.get(&id)
|
||||||
.override_config
|
|
||||||
.as_ref()?
|
|
||||||
.values
|
|
||||||
.get(&id)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,11 @@ pub(crate) fn language(
|
||||||
.with_injection_query(query.as_ref())
|
.with_injection_query(query.as_ref())
|
||||||
.expect("failed to load injection query");
|
.expect("failed to load injection query");
|
||||||
}
|
}
|
||||||
|
if let Some(query) = load_query(name, "/overrides") {
|
||||||
|
language = language
|
||||||
|
.with_override_query(query.as_ref())
|
||||||
|
.expect("failed to load override query");
|
||||||
|
}
|
||||||
if let Some(lsp_adapter) = lsp_adapter {
|
if let Some(lsp_adapter) = lsp_adapter {
|
||||||
language = language.with_lsp_adapter(lsp_adapter)
|
language = language.with_lsp_adapter(lsp_adapter)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
(jsx_element) @override.element
|
[
|
||||||
|
(jsx_element)
|
||||||
|
(jsx_fragment)
|
||||||
|
(jsx_self_closing_element)
|
||||||
|
(jsx_expression)
|
||||||
|
] @override.element
|
||||||
(string) @override.string
|
(string) @override.string
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue