diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 62f4c8c806..e791f5779f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -38,8 +38,8 @@ use gpui::{ use itertools::Itertools; use json::json; use language::{ - language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, - Selection, + language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, InteractionId, + OffsetUtf16, Selection, }; use project::{ project_settings::{GitGutterSetting, ProjectSettings}, @@ -1659,6 +1659,7 @@ impl EditorElement { chunk: chunk.text, style: highlight_style, is_tab: chunk.is_tab, + interaction: chunk.interaction_id, } }); @@ -1900,6 +1901,7 @@ impl EditorElement { struct HighlightedChunk<'a> { chunk: &'a str, style: Option, + interaction: Option, is_tab: bool, } @@ -1930,6 +1932,7 @@ impl LineWithInvisibles { for highlighted_chunk in chunks.chain([HighlightedChunk { chunk: "\n", style: None, + interaction: None, is_tab: false, }]) { for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() { @@ -1960,6 +1963,12 @@ impl LineWithInvisibles { Cow::Borrowed(text_style) }; + let interaction = if let Some(interaction) = highlighted_chunk.interaction { + Some(interaction) + } else { + None + }; + if line.len() + line_chunk.len() > max_line_len { let mut chunk_len = max_line_len - line.len(); while !line_chunk.is_char_boundary(chunk_len) { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 8adf6f6421..c027cb6cb3 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -5,6 +5,7 @@ pub use crate::{ }; use crate::{ diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, + interaction_map::{InteractionId, InteractionMap}, language_settings::{language_settings, LanguageSettings}, outline::OutlineItem, syntax_map::{ @@ -285,11 +286,74 @@ struct IndentSuggestion { within_error: bool, } -struct BufferChunkHighlights<'a> { +pub(crate) trait QueryFeatureMap { + type Id: Copy + Clone + std::fmt::Debug; + + fn get(&self, capture_id: u32) -> Self::Id; +} + +type BufferChunkHighlights<'a> = BufferChunkQuery<'a, HighlightMap>; + +type BufferChunkInteractions<'a> = BufferChunkQuery<'a, InteractionMap>; + +struct BufferChunkQuery<'a, T: QueryFeatureMap> { captures: SyntaxMapCaptures<'a>, next_capture: Option>, - stack: Vec<(usize, HighlightId)>, - highlight_maps: Vec, + maps: Vec, + stack: Vec<(usize, T::Id)>, +} + +impl<'a, T: QueryFeatureMap> BufferChunkQuery<'a, T> { + fn seek(&mut self, offset: usize, byte_range: Range) { + self.stack.retain(|(end_offset, _)| *end_offset > offset); + if let Some(capture) = &self.next_capture { + if offset >= capture.node.start_byte() { + let next_capture_end = capture.node.end_byte(); + if offset < next_capture_end { + self.stack.push(( + next_capture_end, + self.maps[capture.grammar_index].get(capture.index), + )); + } + self.next_capture.take(); + } + } + self.captures.set_byte_range(byte_range); + } + + fn select_current_id(&self, chunk_end: &mut usize, id: &mut Option) { + if let Some((parent_capture_end, parent_id)) = self.stack.last() { + *chunk_end = (*chunk_end).min(*parent_capture_end); + *id = Some(parent_id.clone()); + } + } + + fn next(&mut self, range_start: usize, next_capture_start: &mut usize) { + while let Some((parent_capture_end, _)) = self.stack.last() { + if *parent_capture_end <= range_start { + self.stack.pop(); + } else { + break; + } + } + + if self.next_capture.is_none() { + self.next_capture = self.captures.next(); + } + + while let Some(capture) = self.next_capture.as_ref() { + if range_start < capture.node.start_byte() { + if capture.node.start_byte() < *next_capture_start { + *next_capture_start = capture.node.start_byte(); + } + break; + } else { + let highlight_id = self.maps[capture.grammar_index].get(capture.index); + self.stack.push((capture.node.end_byte(), highlight_id)); + self.next_capture = self.captures.next(); + } + } + } } pub struct BufferChunks<'a> { @@ -302,12 +366,14 @@ pub struct BufferChunks<'a> { hint_depth: usize, unnecessary_depth: usize, highlights: Option>, + interactions: Option>, } #[derive(Clone, Copy, Debug, Default)] pub struct Chunk<'a> { pub text: &'a str, pub syntax_highlight_id: Option, + pub interaction_id: Option, pub highlight_style: Option, pub diagnostic_severity: Option, pub is_unnecessary: bool, @@ -2089,17 +2155,27 @@ impl BufferSnapshot { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut syntax = None; + let mut interactions = None; let mut diagnostic_endpoints = Vec::new(); if language_aware { let captures = self.syntax.captures(range.clone(), &self.text, |grammar| { grammar.highlights_query.as_ref() }); + let interaction_captures = self.syntax.captures(range.clone(), &self.text, |grammar| { + grammar.interactions_query.as_ref() + }); let highlight_maps = captures .grammars() .into_iter() .map(|grammar| grammar.highlight_map()) .collect(); syntax = Some((captures, highlight_maps)); + let interaction_maps = interaction_captures + .grammars() + .into_iter() + .map(|grammar| grammar.interaction_map()) + .collect(); + interactions = Some((interaction_captures, interaction_maps)); for entry in self.diagnostics_in_range::<_, usize>(range.clone(), false) { diagnostic_endpoints.push(DiagnosticEndpoint { offset: entry.range.start, @@ -2118,7 +2194,13 @@ impl BufferSnapshot { .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); } - BufferChunks::new(self.text.as_rope(), range, syntax, diagnostic_endpoints) + BufferChunks::new( + self.text.as_rope(), + range, + syntax, + interactions, + diagnostic_endpoints, + ) } pub fn for_each_line(&self, range: Range, mut callback: impl FnMut(u32, &str)) { @@ -2735,6 +2817,7 @@ impl<'a> BufferChunks<'a> { text: &'a Rope, range: Range, syntax: Option<(SyntaxMapCaptures<'a>, Vec)>, + interactions_syntax: Option<(SyntaxMapCaptures<'a>, Vec)>, diagnostic_endpoints: Vec, ) -> Self { let mut highlights = None; @@ -2743,7 +2826,17 @@ impl<'a> BufferChunks<'a> { captures, next_capture: None, stack: Default::default(), - highlight_maps, + maps: highlight_maps, + }) + } + + let mut interactions = None; + if let Some((captures, interaction_maps)) = interactions_syntax { + interactions = Some(BufferChunkInteractions { + captures, + next_capture: None, + stack: Default::default(), + maps: interaction_maps, }) } @@ -2760,6 +2853,7 @@ impl<'a> BufferChunks<'a> { hint_depth: 0, unnecessary_depth: 0, highlights, + interactions, } } @@ -2767,22 +2861,10 @@ impl<'a> BufferChunks<'a> { self.range.start = offset; self.chunks.seek(self.range.start); if let Some(highlights) = self.highlights.as_mut() { - highlights - .stack - .retain(|(end_offset, _)| *end_offset > offset); - if let Some(capture) = &highlights.next_capture { - if offset >= capture.node.start_byte() { - let next_capture_end = capture.node.end_byte(); - if offset < next_capture_end { - highlights.stack.push(( - next_capture_end, - highlights.highlight_maps[capture.grammar_index].get(capture.index), - )); - } - highlights.next_capture.take(); - } - } - highlights.captures.set_byte_range(self.range.clone()); + highlights.seek(offset, self.range.clone()); + } + if let Some(interactions) = self.interactions.as_mut() { + interactions.seek(offset, self.range.clone()); } } @@ -2840,31 +2922,10 @@ impl<'a> Iterator for BufferChunks<'a> { let mut next_diagnostic_endpoint = usize::MAX; if let Some(highlights) = self.highlights.as_mut() { - while let Some((parent_capture_end, _)) = highlights.stack.last() { - if *parent_capture_end <= self.range.start { - highlights.stack.pop(); - } else { - break; - } - } - - if highlights.next_capture.is_none() { - highlights.next_capture = highlights.captures.next(); - } - - while let Some(capture) = highlights.next_capture.as_ref() { - if self.range.start < capture.node.start_byte() { - next_capture_start = capture.node.start_byte(); - break; - } else { - let highlight_id = - highlights.highlight_maps[capture.grammar_index].get(capture.index); - highlights - .stack - .push((capture.node.end_byte(), highlight_id)); - highlights.next_capture = highlights.captures.next(); - } - } + highlights.next(self.range.start, &mut next_capture_start); + } + if let Some(interactions) = self.interactions.as_mut() { + interactions.next(self.range.start, &mut next_capture_start); } while let Some(endpoint) = self.diagnostic_endpoints.peek().copied() { @@ -2884,10 +2945,11 @@ impl<'a> Iterator for BufferChunks<'a> { .min(next_diagnostic_endpoint); let mut highlight_id = None; if let Some(highlights) = self.highlights.as_ref() { - if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() { - chunk_end = chunk_end.min(*parent_capture_end); - highlight_id = Some(*parent_highlight_id); - } + highlights.select_current_id(&mut chunk_end, &mut highlight_id); + } + let mut interaction_id = None; + if let Some(interactions) = self.interactions.as_ref() { + interactions.select_current_id(&mut chunk_end, &mut interaction_id); } let slice = @@ -2900,6 +2962,7 @@ impl<'a> Iterator for BufferChunks<'a> { Some(Chunk { text: slice, syntax_highlight_id: highlight_id, + interaction_id, diagnostic_severity: self.current_diagnostic_severity(), is_unnecessary: self.current_code_is_unnecessary(), ..Default::default() diff --git a/crates/language/src/highlight_map.rs b/crates/language/src/highlight_map.rs index 109d79cf70..04fb69f5aa 100644 --- a/crates/language/src/highlight_map.rs +++ b/crates/language/src/highlight_map.rs @@ -2,12 +2,25 @@ use gpui::fonts::HighlightStyle; use std::sync::Arc; use theme::SyntaxTheme; +use crate::QueryFeatureMap; + #[derive(Clone, Debug)] pub struct HighlightMap(Arc<[HighlightId]>); #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HighlightId(pub u32); +impl QueryFeatureMap for HighlightMap { + type Id = HighlightId; + + fn get(&self, capture_id: u32) -> HighlightId { + self.0 + .get(capture_id as usize) + .copied() + .unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID) + } +} + const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX); impl HighlightMap { @@ -41,13 +54,6 @@ impl HighlightMap { .collect(), ) } - - pub fn get(&self, capture_id: u32) -> HighlightId { - self.0 - .get(capture_id as usize) - .copied() - .unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID) - } } impl HighlightId { diff --git a/crates/language/src/interaction_map.rs b/crates/language/src/interaction_map.rs new file mode 100644 index 0000000000..9077bbe816 --- /dev/null +++ b/crates/language/src/interaction_map.rs @@ -0,0 +1,59 @@ +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct InteractionMap(Arc<[Option]>); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct InteractionId { + event_type: InteractionType, + capture: u32, +} + +const DEFAULT_INTERACTION_ID: InteractionId = InteractionId { + event_type: InteractionType::Click, + capture: u32::MAX, +}; + +impl super::buffer::QueryFeatureMap for InteractionMap { + type Id = InteractionId; + + fn get(&self, capture_id: u32) -> Self::Id { + match self.0.get(capture_id as usize) { + Some(Some(event)) => InteractionId { + event_type: *event, + capture: capture_id, + }, + _ => DEFAULT_INTERACTION_ID, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum InteractionType { + Click, +} + +impl InteractionMap { + pub fn new(capture_names: &[String]) -> Self { + InteractionMap( + capture_names + .iter() + .map(|capture_name| { + let mut capture_parts = capture_name.split(".").peekable(); + if let Some(str) = capture_parts.next() { + if str == "click" && capture_parts.peek().is_some() { + return Some(InteractionType::Click); + } + } + None + }) + .collect(), + ) + } +} + +impl Default for InteractionMap { + fn default() -> Self { + Self(Arc::new([])) + } +} diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 89d0592627..365ac5044d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1,6 +1,7 @@ mod buffer; mod diagnostic_set; mod highlight_map; +mod interaction_map; pub mod language_settings; mod outline; pub mod proto; @@ -18,7 +19,7 @@ use futures::{ FutureExt, TryFutureExt as _, }; use gpui::{executor::Background, AppContext, AsyncAppContext, Task}; -pub use highlight_map::HighlightMap; +use interaction_map::InteractionMap; use lazy_static::lazy_static; use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; @@ -54,6 +55,8 @@ use futures::channel::mpsc; pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::DiagnosticEntry; +pub use highlight_map::HighlightMap; +pub use interaction_map::InteractionId; pub use lsp::LanguageServerId; pub use outline::{Outline, OutlineItem}; pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo}; @@ -315,6 +318,12 @@ pub trait LspAdapter: 'static + Send + Sync { } } +pub trait InteractionProvider: 'static + Send + Sync { + fn on_click(&self, _capture: &str, _text: &str) -> Option { + None + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CodeLabel { pub text: String, @@ -358,6 +367,7 @@ pub struct LanguageQueries { pub embedding: Option>, pub injections: Option>, pub overrides: Option>, + pub interactions: Option>, } #[derive(Clone, Debug)] @@ -488,6 +498,7 @@ pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, pub(crate) adapters: Vec>, + pub(crate) interaction_provider: Option>, #[cfg(any(test, feature = "test-support"))] fake_adapter: Option<( @@ -501,6 +512,7 @@ pub struct Grammar { pub ts_language: tree_sitter::Language, pub(crate) error_query: Query, pub(crate) highlights_query: Option, + pub(crate) interactions_query: Option, pub(crate) brackets_config: Option, pub(crate) indents_config: Option, pub outline_config: Option, @@ -508,6 +520,7 @@ pub struct Grammar { pub(crate) injection_config: Option, pub(crate) override_config: Option, pub(crate) highlight_map: Mutex, + pub(crate) interaction_map: Mutex, } struct IndentConfig { @@ -578,6 +591,7 @@ struct AvailableLanguage { config: LanguageConfig, grammar: tree_sitter::Language, lsp_adapters: Vec>, + interaction_provider: Option>, get_queries: fn(&str) -> LanguageQueries, loaded: bool, } @@ -660,6 +674,7 @@ impl LanguageRegistry { config: LanguageConfig, grammar: tree_sitter::Language, lsp_adapters: Vec>, + interaction_provider: Option>, get_queries: fn(&str) -> LanguageQueries, ) { let state = &mut *self.state.write(); @@ -670,6 +685,7 @@ impl LanguageRegistry { grammar, lsp_adapters, get_queries, + interaction_provider, loaded: false, }); } @@ -832,6 +848,7 @@ impl LanguageRegistry { let queries = (language.get_queries)(&language.path); let language = Language::new(language.config, Some(language.grammar)) + .with_interaction_provider(language.interaction_provider) .with_lsp_adapters(language.lsp_adapters) .await; let name = language.name(); @@ -1170,12 +1187,15 @@ impl Language { indents_config: None, injection_config: None, override_config: None, + interactions_query: None, error_query: Query::new(ts_language, "(ERROR) @error").unwrap(), ts_language, highlight_map: Default::default(), + interaction_map: Default::default(), }) }), adapters: Vec::new(), + interaction_provider: None, #[cfg(any(test, feature = "test-support"))] fake_adapter: None, @@ -1226,6 +1246,19 @@ impl Language { .with_override_query(query.as_ref()) .context("Error loading override query")?; } + if let Some(query) = queries.interactions { + self = self + .with_interaction_query(query.as_ref()) + .context("Error loading interaction query")?; + } + Ok(self) + } + + pub fn with_interaction_query(mut self, source: &str) -> Result { + let grammar = self.grammar_mut(); + let query = Query::new(grammar.ts_language, source)?; + *grammar.interaction_map.lock() = InteractionMap::new(query.capture_names()); + grammar.interactions_query = Some(query); Ok(self) } @@ -1457,6 +1490,15 @@ impl Language { self } + pub fn with_interaction_provider( + mut self, + interaction_provider: Option>, + ) -> Self { + self.interaction_provider = interaction_provider; + + self + } + #[cfg(any(test, feature = "test-support"))] pub async fn set_fake_lsp_adapter( &mut self, @@ -1534,7 +1576,9 @@ impl Language { }); let highlight_maps = vec![grammar.highlight_map()]; let mut offset = 0; - for chunk in BufferChunks::new(text, range, Some((captures, highlight_maps)), vec![]) { + for chunk in + BufferChunks::new(text, range, Some((captures, highlight_maps)), None, vec![]) + { let end_offset = offset + chunk.text.len(); if let Some(highlight_id) = chunk.syntax_highlight_id { if !highlight_id.is_default() { @@ -1680,6 +1724,10 @@ impl Grammar { self.highlight_map.lock().clone() } + pub fn interaction_map(&self) -> InteractionMap { + self.interaction_map.lock().clone() + } + pub fn highlight_id_for_name(&self, name: &str) -> Option { let capture_id = self .highlights_query @@ -1824,6 +1872,7 @@ mod tests { }, tree_sitter_typescript::language_tsx(), vec![], + None, |_| Default::default(), ); @@ -1860,6 +1909,7 @@ mod tests { }, tree_sitter_json::language(), vec![], + None, |_| Default::default(), ); languages.register( @@ -1871,6 +1921,7 @@ mod tests { }, tree_sitter_rust::language(), vec![], + None, |_| Default::default(), ); assert_eq!( diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 397223c4bb..e0abcd18ab 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2601,6 +2601,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) { }, tree_sitter_rust::language(), vec![], + None, |_| Default::default(), ); diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index eb31c08dd2..03f8f55d3d 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -13,6 +13,7 @@ mod json; #[cfg(feature = "plugin_runtime")] mod language_plugin; mod lua; +mod markdown; mod php; mod python; mod ruby; @@ -36,36 +37,48 @@ mod yaml; struct LanguageDir; pub fn init(languages: Arc, node_runtime: Arc) { - let language = |name, grammar, adapters| { - languages.register(name, load_config(name), grammar, adapters, load_queries) + let language = |name, grammar, adapters, interactions| { + languages.register( + name, + load_config(name), + grammar, + adapters, + interactions, + load_queries, + ) }; - language("bash", tree_sitter_bash::language(), vec![]); + language("bash", tree_sitter_bash::language(), vec![], None); language( "c", tree_sitter_c::language(), vec![Arc::new(c::CLspAdapter) as Arc], + None, ); language( "cpp", tree_sitter_cpp::language(), vec![Arc::new(c::CLspAdapter)], + None, ); - language("css", tree_sitter_css::language(), vec![]); + language("css", tree_sitter_css::language(), vec![], None); language( "elixir", tree_sitter_elixir::language(), vec![Arc::new(elixir::ElixirLspAdapter)], + None, ); language( "go", tree_sitter_go::language(), vec![Arc::new(go::GoLspAdapter)], + None, ); language( "heex", tree_sitter_heex::language(), vec![Arc::new(elixir::ElixirLspAdapter)], + None, ); language( "json", @@ -74,21 +87,29 @@ pub fn init(languages: Arc, node_runtime: Arc) { node_runtime.clone(), languages.clone(), ))], + None, + ); + language( + "markdown", + tree_sitter_markdown::language(), + vec![], + Some(Arc::new(MarkdownInteractions)), ); - language("markdown", tree_sitter_markdown::language(), vec![]); language( "python", tree_sitter_python::language(), vec![Arc::new(python::PythonLspAdapter::new( node_runtime.clone(), ))], + None, ); language( "rust", tree_sitter_rust::language(), vec![Arc::new(rust::RustLspAdapter)], + None, ); - language("toml", tree_sitter_toml::language(), vec![]); + language("toml", tree_sitter_toml::language(), vec![], None); language( "tsx", tree_sitter_typescript::language_tsx(), @@ -96,6 +117,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), ], + None, ); language( "typescript", @@ -104,6 +126,7 @@ pub fn init(languages: Arc, node_runtime: Arc) { Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), ], + None, ); language( "javascript", @@ -112,33 +135,39 @@ pub fn init(languages: Arc, node_runtime: Arc) { Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), ], + None, ); language( "html", tree_sitter_html::language(), vec![Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))], + None, ); language( "ruby", tree_sitter_ruby::language(), vec![Arc::new(ruby::RubyLanguageServer)], + None, ); language( "erb", tree_sitter_embedded_template::language(), vec![Arc::new(ruby::RubyLanguageServer)], + None, ); - language("scheme", tree_sitter_scheme::language(), vec![]); - language("racket", tree_sitter_racket::language(), vec![]); + language("scheme", tree_sitter_scheme::language(), vec![], None); + language("racket", tree_sitter_racket::language(), vec![], None); language( "lua", tree_sitter_lua::language(), vec![Arc::new(lua::LuaLspAdapter)], + None, ); language( "yaml", tree_sitter_yaml::language(), vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))], + None, ); language( "svelte", @@ -146,16 +175,18 @@ pub fn init(languages: Arc, node_runtime: Arc) { vec![Arc::new(svelte::SvelteLspAdapter::new( node_runtime.clone(), ))], + None, ); language( "php", tree_sitter_php::language(), vec![Arc::new(php::IntelephenseLspAdapter::new(node_runtime))], + None, ); - language("elm", tree_sitter_elm::language(), vec![]); - language("glsl", tree_sitter_glsl::language(), vec![]); - language("nix", tree_sitter_nix::language(), vec![]); + language("elm", tree_sitter_elm::language(), vec![], None); + language("glsl", tree_sitter_glsl::language(), vec![], None); + language("nix", tree_sitter_nix::language(), vec![], None); } #[cfg(any(test, feature = "test-support"))] @@ -192,6 +223,7 @@ fn load_queries(name: &str) -> LanguageQueries { embedding: load_query(name, "/embedding"), injections: load_query(name, "/injections"), overrides: load_query(name, "/overrides"), + interactions: load_query(name, "/interactions"), } } diff --git a/crates/zed/src/languages/markdown.rs b/crates/zed/src/languages/markdown.rs new file mode 100644 index 0000000000..c1a8531922 --- /dev/null +++ b/crates/zed/src/languages/markdown.rs @@ -0,0 +1,15 @@ +use language::InteractionProvider; + +pub struct MarkdownInteractions; + +impl InteractionProvider for MarkdownInteractions { + fn on_click(&self, text: &str) -> Option { + if text == "[ ]" { + return Some("[x]".to_string()); + } else if text == "[x]" { + return Some("[ ]".to_string()); + } else { + return None; + } + } +} diff --git a/crates/zed/src/languages/markdown/interactions.scm b/crates/zed/src/languages/markdown/interactions.scm new file mode 100644 index 0000000000..b0fecf4260 --- /dev/null +++ b/crates/zed/src/languages/markdown/interactions.scm @@ -0,0 +1,5 @@ + +( + (list_item (paragraph (shortcut_link) @click.toggle_selected)) + (#matches? @click.toggle_selected "^([ ]|[x])") +)