@@ -2145,23 +2145,27 @@ impl BufferSnapshot {
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
+ let mut range = 0..self.len();
+ let mut scope = self.language.clone().map(|language| LanguageScope {
+ language,
+ override_id: None,
+ });
- if let Some(layer_info) = self
- .syntax
- .layers_for_range(offset..offset, &self.text)
- .filter(|l| l.node().end_byte() > offset)
- .last()
- {
- Some(LanguageScope {
- language: layer_info.language.clone(),
- override_id: layer_info.override_id(offset, &self.text),
- })
- } else {
- self.language.clone().map(|language| LanguageScope {
- language,
- override_id: 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) {
+ let mut cursor = layer.node().walk();
+ while cursor.goto_first_child_for_byte(offset).is_some() {}
+ let node_range = cursor.node().byte_range();
+ if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() {
+ range = node_range;
+ scope = Some(LanguageScope {
+ language: layer.language.clone(),
+ override_id: layer.override_id(offset, &self.text),
+ });
+ }
}
+
+ scope
}
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
@@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
])
});
- let html_language = Arc::new(
- Language::new(
- LanguageConfig {
- name: "HTML".into(),
- ..Default::default()
- },
- Some(tree_sitter_html::language()),
- )
- .with_indents_query(
- "
- (element
- (start_tag) @start
- (end_tag)? @end) @indent
- ",
- )
- .unwrap()
- .with_injection_query(
- r#"
- (script_element
- (raw_text) @content
- (#set! "language" "javascript"))
- "#,
- )
- .unwrap(),
- );
+ let html_language = Arc::new(html_lang());
- let javascript_language = Arc::new(
- Language::new(
- LanguageConfig {
- name: "JavaScript".into(),
- ..Default::default()
- },
- Some(tree_sitter_javascript::language()),
- )
- .with_indents_query(
- r#"
- (object "}" @end) @indent
- "#,
- )
- .unwrap(),
- );
+ let javascript_language = Arc::new(javascript_lang());
let language_registry = Arc::new(LanguageRegistry::test());
language_registry.add(html_language.clone());
@@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
}
#[gpui::test]
-fn test_language_config_at(cx: &mut AppContext) {
+fn test_language_scope_at(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
@@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) {
});
}
+#[gpui::test]
+fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
+ init_settings(cx, |_| {});
+
+ cx.add_model(|cx| {
+ let text = r#"
+ <ol>
+ <% people.each do |person| %>
+ <li>
+ <%= person.name %>
+ </li>
+ <% end %>
+ </ol>
+ "#
+ .unindent();
+
+ let language_registry = Arc::new(LanguageRegistry::test());
+ language_registry.add(Arc::new(ruby_lang()));
+ language_registry.add(Arc::new(html_lang()));
+ language_registry.add(Arc::new(erb_lang()));
+
+ let mut buffer = Buffer::new(0, text, cx);
+ buffer.set_language_registry(language_registry.clone());
+ buffer.set_language(
+ language_registry
+ .language_for_name("ERB")
+ .now_or_never()
+ .unwrap()
+ .ok(),
+ cx,
+ );
+
+ let snapshot = buffer.snapshot();
+ let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
+ assert_eq!(html_config.line_comment_prefix(), None);
+ assert_eq!(
+ html_config.block_comment_delimiters(),
+ Some((&"<!--".into(), &"-->".into()))
+ );
+
+ let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
+ assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# ");
+ assert_eq!(ruby_config.block_comment_delimiters(), None);
+
+ buffer
+ });
+}
+
#[gpui::test]
fn test_serialization(cx: &mut gpui::AppContext) {
let mut now = Instant::now();
@@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language {
LanguageConfig {
name: "Ruby".into(),
path_suffixes: vec!["rb".to_string()],
+ line_comment: Some("# ".into()),
..Default::default()
},
Some(tree_sitter_ruby::language()),
@@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language {
.unwrap()
}
+fn html_lang() -> Language {
+ Language::new(
+ LanguageConfig {
+ name: "HTML".into(),
+ block_comment: Some(("<!--".into(), "-->".into())),
+ ..Default::default()
+ },
+ Some(tree_sitter_html::language()),
+ )
+ .with_indents_query(
+ "
+ (element
+ (start_tag) @start
+ (end_tag)? @end) @indent
+ ",
+ )
+ .unwrap()
+ .with_injection_query(
+ r#"
+ (script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+ "#,
+ )
+ .unwrap()
+}
+
+fn erb_lang() -> Language {
+ Language::new(
+ LanguageConfig {
+ name: "ERB".into(),
+ path_suffixes: vec!["erb".to_string()],
+ block_comment: Some(("<%#".into(), "%>".into())),
+ ..Default::default()
+ },
+ Some(tree_sitter_embedded_template::language()),
+ )
+ .with_injection_query(
+ r#"
+ (
+ (code) @content
+ (#set! "language" "ruby")
+ (#set! "combined")
+ )
+
+ (
+ (content) @content
+ (#set! "language" "html")
+ (#set! "combined")
+ )
+ "#,
+ )
+ .unwrap()
+}
+
fn rust_lang() -> Language {
Language::new(
LanguageConfig {
@@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language {
"#,
)
.unwrap()
+ .with_indents_query(
+ r#"
+ (object "}" @end) @indent
+ "#,
+ )
+ .unwrap()
}
fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
@@ -771,8 +771,10 @@ impl SyntaxSnapshot {
range: Range<T>,
buffer: &'a BufferSnapshot,
) -> impl 'a + Iterator<Item = SyntaxLayerInfo> {
- let start = buffer.anchor_before(range.start.to_offset(buffer));
- let end = buffer.anchor_after(range.end.to_offset(buffer));
+ let start_offset = range.start.to_offset(buffer);
+ let end_offset = range.end.to_offset(buffer);
+ let start = buffer.anchor_before(start_offset);
+ let end = buffer.anchor_after(end_offset);
let mut cursor = self.layers.filter::<_, ()>(move |summary| {
if summary.max_depth > summary.min_depth {
@@ -787,20 +789,21 @@ impl SyntaxSnapshot {
cursor.next(buffer);
iter::from_fn(move || {
while let Some(layer) = cursor.item() {
+ let mut info = None;
if let SyntaxLayerContent::Parsed { tree, language } = &layer.content {
- let info = SyntaxLayerInfo {
+ 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(SyntaxLayerInfo {
tree,
language,
depth: layer.depth,
- offset: (
- layer.range.start.to_offset(buffer),
- layer.range.start.to_point(buffer).to_ts_point(),
- ),
- };
- cursor.next(buffer);
- return Some(info);
- } else {
- cursor.next(buffer);
+ offset: (layer_start_offset, layer_start_point),
+ });
+ }
+ cursor.next(buffer);
+ if info.is_some() {
+ return info;
}
}
None
@@ -3045,6 +3045,8 @@ impl Project {
) -> Task<(Option<PathBuf>, Vec<WorktreeId>)> {
let key = (worktree_id, adapter_name);
if let Some(server_id) = self.language_server_ids.remove(&key) {
+ log::info!("stopping language server {}", key.1 .0);
+
// Remove other entries for this language server as well
let mut orphaned_worktrees = vec![worktree_id];
let other_keys = self.language_server_ids.keys().cloned().collect::<Vec<_>>();
@@ -4,4 +4,4 @@ autoclose_before = ">})"
brackets = [
{ start = "<", end = ">", close = true, newline = true },
]
-block_comment = ["<%#", "%>"]
+block_comment = ["<%!--", "--%>"]