Find the layer with the smallest enclosing node in language_scope_at

This commit is contained in:
Max Brunsfeld 2023-07-11 14:15:45 -07:00
parent bf9dfa3b51
commit 2e2333107a
6 changed files with 151 additions and 69 deletions

View file

@ -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>) {

View file

@ -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 {

View file

@ -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

View file

@ -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<_>>();

View file

@ -397,6 +397,7 @@ impl Worktree {
}))
}
// abcdefghi
pub fn remote(
project_remote_id: u64,
replica_id: ReplicaId,

View file

@ -4,4 +4,4 @@ autoclose_before = ">})"
brackets = [
{ start = "<", end = ">", close = true, newline = true },
]
block_comment = ["<%#", "%>"]
block_comment = ["<%!--", "--%>"]