Detailed changes
@@ -1018,7 +1018,7 @@ impl Buffer {
let offset = position.to_offset(self);
self.syntax_map
.lock()
- .layers_for_range(offset..offset, &self.text)
+ .layers_for_range(offset..offset, &self.text, false)
.last()
.map(|info| info.language.clone())
.or_else(|| self.language.clone())
@@ -2625,13 +2625,14 @@ impl BufferSnapshot {
/// Iterates over every [`SyntaxLayer`] in the buffer.
pub fn syntax_layers(&self) -> impl Iterator<Item = SyntaxLayer> + '_ {
- self.syntax.layers_for_range(0..self.len(), &self.text)
+ self.syntax
+ .layers_for_range(0..self.len(), &self.text, true)
}
pub fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayer> {
let offset = position.to_offset(self);
self.syntax
- .layers_for_range(offset..offset, &self.text)
+ .layers_for_range(offset..offset, &self.text, false)
.filter(|l| l.node().end_byte() > offset)
.last()
}
@@ -2664,7 +2665,10 @@ impl BufferSnapshot {
let mut smallest_range: Option<Range<usize>> = None;
// Use the layer that has the smallest node intersecting the given point.
- for layer in self.syntax.layers_for_range(offset..offset, &self.text) {
+ for layer in self
+ .syntax
+ .layers_for_range(offset..offset, &self.text, false)
+ {
let mut cursor = layer.node().walk();
let mut range = None;
@@ -2740,7 +2744,10 @@ impl BufferSnapshot {
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut result: Option<Range<usize>> = None;
- 'outer: for layer in self.syntax.layers_for_range(range.clone(), &self.text) {
+ 'outer: for layer in self
+ .syntax
+ .layers_for_range(range.clone(), &self.text, true)
+ {
let mut cursor = layer.node().walk();
// Descend to the first leaf that touches the start of the range,
@@ -2216,6 +2216,45 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
});
}
+#[gpui::test]
+fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
+ init_settings(cx, |_| {});
+
+ cx.new_model(|cx| {
+ let text = r#"
+ this is an *emphasized* word.
+ "#
+ .unindent();
+
+ let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
+ language_registry.add(Arc::new(markdown_lang()));
+ language_registry.add(Arc::new(markdown_inline_lang()));
+
+ let mut buffer = Buffer::local(text, cx);
+ buffer.set_language_registry(language_registry.clone());
+ buffer.set_language(
+ language_registry
+ .language_for_name("Markdown")
+ .now_or_never()
+ .unwrap()
+ .ok(),
+ cx,
+ );
+
+ let snapshot = buffer.snapshot();
+
+ for point in [Point::new(0, 4), Point::new(0, 16)] {
+ let config = snapshot.language_scope_at(point).unwrap();
+ assert_eq!(config.language_name().as_ref(), "Markdown");
+
+ let language = snapshot.language_at(point).unwrap();
+ assert_eq!(language.name().as_ref(), "Markdown");
+ }
+
+ buffer
+ });
+}
+
#[gpui::test]
fn test_serialization(cx: &mut gpui::AppContext) {
let mut now = Instant::now();
@@ -2868,6 +2907,45 @@ fn javascript_lang() -> Language {
.unwrap()
}
+pub fn markdown_lang() -> Language {
+ Language::new(
+ LanguageConfig {
+ name: "Markdown".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["md".into()],
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ Some(tree_sitter_md::language()),
+ )
+ .with_injection_query(
+ r#"
+ (fenced_code_block
+ (info_string
+ (language) @language)
+ (code_fence_content) @content)
+
+ ((inline) @content
+ (#set! "language" "markdown-inline"))
+ "#,
+ )
+ .unwrap()
+}
+
+pub fn markdown_inline_lang() -> Language {
+ Language::new(
+ LanguageConfig {
+ name: "Markdown-Inline".into(),
+ hidden: true,
+ ..LanguageConfig::default()
+ },
+ Some(tree_sitter_md::inline_language()),
+ )
+ .with_highlights_query("(emphasis) @emphasis")
+ .unwrap()
+}
+
fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
buffer.update(cx, |buffer, _| {
let snapshot = buffer.snapshot();
@@ -17,7 +17,7 @@ mod syntax_map;
mod task_context;
#[cfg(test)]
-mod buffer_tests;
+pub mod buffer_tests;
pub mod markdown;
use crate::language_settings::SoftWrap;
@@ -627,6 +627,10 @@ pub struct LanguageConfig {
/// If there's a parser name in the language settings, that will be used instead.
#[serde(default)]
pub prettier_parser_name: Option<String>,
+ /// If true, this language is only for syntax highlighting via an injection into other
+ /// languages, but should not appear to the user as a distinct language.
+ #[serde(default)]
+ pub hidden: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
@@ -713,6 +717,7 @@ impl Default for LanguageConfig {
tab_size: None,
soft_wrap: None,
prettier_parser_name: None,
+ hidden: false,
}
}
}
@@ -1414,6 +1419,10 @@ impl Language {
}
impl LanguageScope {
+ pub fn language_name(&self) -> Arc<str> {
+ self.language.config.name.clone()
+ }
+
pub fn collapsed_placeholder(&self) -> &str {
self.language.config.collapsed_placeholder.as_ref()
}
@@ -782,7 +782,7 @@ impl SyntaxSnapshot {
SyntaxMapCaptures::new(
range.clone(),
buffer.as_rope(),
- self.layers_for_range(range, buffer),
+ self.layers_for_range(range, buffer, true),
query,
)
}
@@ -796,20 +796,22 @@ impl SyntaxSnapshot {
SyntaxMapMatches::new(
range.clone(),
buffer.as_rope(),
- self.layers_for_range(range, buffer),
+ self.layers_for_range(range, buffer, true),
query,
)
}
#[cfg(test)]
pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer> {
- self.layers_for_range(0..buffer.len(), buffer).collect()
+ self.layers_for_range(0..buffer.len(), buffer, true)
+ .collect()
}
pub fn layers_for_range<'a, T: ToOffset>(
&'a self,
range: Range<T>,
buffer: &'a BufferSnapshot,
+ include_hidden: bool,
) -> impl 'a + Iterator<Item = SyntaxLayer> {
let start_offset = range.start.to_offset(buffer);
let end_offset = range.end.to_offset(buffer);
@@ -833,13 +835,14 @@ impl SyntaxSnapshot {
if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
let layer_start_offset = layer.range.start.to_offset(buffer);
let layer_start_point = layer.range.start.to_point(buffer).to_ts_point();
-
- info = Some(SyntaxLayer {
- tree,
- language,
- depth: layer.depth,
- offset: (layer_start_offset, layer_start_point),
- });
+ if include_hidden || !language.config.hidden {
+ info = Some(SyntaxLayer {
+ tree,
+ language,
+ depth: layer.depth,
+ offset: (layer_start_offset, layer_start_point),
+ });
+ }
}
cursor.next(buffer);
if info.is_some() {
@@ -1,5 +1,8 @@
use super::*;
-use crate::{LanguageConfig, LanguageMatcher};
+use crate::{
+ buffer_tests::{markdown_inline_lang, markdown_lang},
+ LanguageConfig, LanguageMatcher,
+};
use gpui::AppContext;
use rand::rngs::StdRng;
use std::{env, ops::Range, sync::Arc};
@@ -779,8 +782,13 @@ fn test_empty_combined_injections_inside_injections(cx: &mut AppContext) {
&buffer,
Point::new(0, 0)..Point::new(5, 0),
&[
+ // Markdown document
"(document (section (fenced_code_block (fenced_code_block_delimiter) (info_string (language)) (block_continuation) (code_fence_content (block_continuation)) (fenced_code_block_delimiter)) (paragraph (inline))))",
+ // ERB template in the code block
"(template...",
+ // Markdown inline content
+ "(inline)",
+ // HTML within the ERB
"(document (text))",
// The ruby syntax tree should be empty, since there are
// no interpolations in the ERB template.
@@ -1229,39 +1237,6 @@ fn rust_lang() -> Language {
.unwrap()
}
-fn markdown_lang() -> Language {
- Language::new(
- LanguageConfig {
- name: "Markdown".into(),
- matcher: LanguageMatcher {
- path_suffixes: vec!["md".into()],
- ..Default::default()
- },
- ..Default::default()
- },
- Some(tree_sitter_md::language()),
- )
- .with_injection_query(
- r#"
- (fenced_code_block
- (info_string
- (language) @language)
- (code_fence_content) @content)
- "#,
- )
- .unwrap()
-}
-
-fn markdown_inline_lang() -> Language {
- Language::new(
- LanguageConfig {
- name: "Markdown-Inline".into(),
- ..LanguageConfig::default()
- },
- Some(tree_sitter_md::inline_language()),
- )
-}
-
fn elixir_lang() -> Language {
Language::new(
LanguageConfig {
@@ -1327,7 +1302,7 @@ fn assert_layers_for_range(
expected_layers: &[&str],
) {
let layers = syntax_map
- .layers_for_range(range, &buffer)
+ .layers_for_range(range, &buffer, true)
.collect::<Vec<_>>();
assert_eq!(
layers.len(),
@@ -1,14 +1,3 @@
name = "Markdown-Inline"
grammar = "markdown-inline"
-path_suffixes = []
-brackets = [
- { start = "{", end = "}", close = true, newline = true },
- { start = "[", end = "]", close = true, newline = true },
- { start = "(", end = ")", close = true, newline = true },
- { start = "<", end = ">", close = true, newline = true },
- { start = "\"", end = "\"", close = false, newline = false },
- { start = "'", end = "'", close = false, newline = false },
- { start = "`", end = "`", close = false, newline = false },
-]
-
-tab_size = 2
+hidden = true