Merge pull request #1202 from zed-industries/avoid-bad-markdown-autoindent

Max Brunsfeld created

Disable auto-indent entirely for markdown

Change summary

crates/editor/src/editor.rs     | 44 +++++++++++++-----------
crates/language/src/buffer.rs   | 60 +++++++++++++++++---------------
crates/language/src/language.rs | 63 +++++++++++++++-------------------
crates/language/src/tests.rs    | 41 ++++++++++++++++++++++
4 files changed, 125 insertions(+), 83 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -9810,26 +9810,30 @@ mod tests {
     #[gpui::test]
     async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
         cx.update(|cx| cx.set_global(Settings::test(cx)));
-        let language = Arc::new(Language::new(
-            LanguageConfig {
-                brackets: vec![
-                    BracketPair {
-                        start: "{".to_string(),
-                        end: "}".to_string(),
-                        close: true,
-                        newline: true,
-                    },
-                    BracketPair {
-                        start: "/* ".to_string(),
-                        end: " */".to_string(),
-                        close: true,
-                        newline: true,
-                    },
-                ],
-                ..Default::default()
-            },
-            Some(tree_sitter_rust::language()),
-        ));
+        let language = Arc::new(
+            Language::new(
+                LanguageConfig {
+                    brackets: vec![
+                        BracketPair {
+                            start: "{".to_string(),
+                            end: "}".to_string(),
+                            close: true,
+                            newline: true,
+                        },
+                        BracketPair {
+                            start: "/* ".to_string(),
+                            end: " */".to_string(),
+                            close: true,
+                            newline: true,
+                        },
+                    ],
+                    ..Default::default()
+                },
+                Some(tree_sitter_rust::language()),
+            )
+            .with_indents_query("")
+            .unwrap(),
+        );
 
         let text = concat!(
             "{   }\n",     // Suppress rustfmt

crates/language/src/buffer.rs 🔗

@@ -1523,16 +1523,17 @@ impl BufferSnapshot {
         // Get the "indentation ranges" that intersect this row range.
         let grammar = self.grammar()?;
         let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
+        let indents_query = grammar.indents_query.as_ref()?;
         let mut query_cursor = QueryCursorHandle::new();
-        let indent_capture_ix = grammar.indents_query.capture_index_for_name("indent");
-        let end_capture_ix = grammar.indents_query.capture_index_for_name("end");
+        let indent_capture_ix = indents_query.capture_index_for_name("indent");
+        let end_capture_ix = indents_query.capture_index_for_name("end");
         query_cursor.set_point_range(
             Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).to_ts_point()
                 ..Point::new(row_range.end, 0).to_ts_point(),
         );
         let mut indentation_ranges = Vec::<Range<Point>>::new();
         for mat in query_cursor.matches(
-            &grammar.indents_query,
+            indents_query,
             self.tree.as_ref()?.root_node(),
             TextProvider(self.as_rope()),
         ) {
@@ -1787,20 +1788,20 @@ impl BufferSnapshot {
             .as_ref()
             .and_then(|language| language.grammar.as_ref())?;
 
+        let outline_query = grammar.outline_query.as_ref()?;
         let mut cursor = QueryCursorHandle::new();
         cursor.set_byte_range(range.clone());
         let matches = cursor.matches(
-            &grammar.outline_query,
+            outline_query,
             tree.root_node(),
             TextProvider(self.as_rope()),
         );
 
         let mut chunks = self.chunks(0..self.len(), true);
 
-        let item_capture_ix = grammar.outline_query.capture_index_for_name("item")?;
-        let name_capture_ix = grammar.outline_query.capture_index_for_name("name")?;
-        let context_capture_ix = grammar
-            .outline_query
+        let item_capture_ix = outline_query.capture_index_for_name("item")?;
+        let name_capture_ix = outline_query.capture_index_for_name("name")?;
+        let context_capture_ix = outline_query
             .capture_index_for_name("context")
             .unwrap_or(u32::MAX);
 
@@ -1892,14 +1893,15 @@ impl BufferSnapshot {
         range: Range<T>,
     ) -> Option<(Range<usize>, Range<usize>)> {
         let (grammar, tree) = self.grammar().zip(self.tree.as_ref())?;
-        let open_capture_ix = grammar.brackets_query.capture_index_for_name("open")?;
-        let close_capture_ix = grammar.brackets_query.capture_index_for_name("close")?;
+        let brackets_query = grammar.brackets_query.as_ref()?;
+        let open_capture_ix = brackets_query.capture_index_for_name("open")?;
+        let close_capture_ix = brackets_query.capture_index_for_name("close")?;
 
         // Find bracket pairs that *inclusively* contain the given range.
         let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
         let mut cursor = QueryCursorHandle::new();
         let matches = cursor.set_byte_range(range).matches(
-            &grammar.brackets_query,
+            &brackets_query,
             tree.root_node(),
             TextProvider(self.as_rope()),
         );
@@ -2071,24 +2073,26 @@ impl<'a> BufferChunks<'a> {
     ) -> Self {
         let mut highlights = None;
         if let Some((grammar, tree)) = grammar.zip(tree) {
-            let mut query_cursor = QueryCursorHandle::new();
+            if let Some(highlights_query) = grammar.highlights_query.as_ref() {
+                let mut query_cursor = QueryCursorHandle::new();
 
-            // TODO - add a Tree-sitter API to remove the need for this.
-            let cursor = unsafe {
-                std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut())
-            };
-            let captures = cursor.set_byte_range(range.clone()).captures(
-                &grammar.highlights_query,
-                tree.root_node(),
-                TextProvider(text),
-            );
-            highlights = Some(BufferChunkHighlights {
-                captures,
-                next_capture: None,
-                stack: Default::default(),
-                highlight_map: grammar.highlight_map(),
-                _query_cursor: query_cursor,
-            })
+                // TODO - add a Tree-sitter API to remove the need for this.
+                let cursor = unsafe {
+                    std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut())
+                };
+                let captures = cursor.set_byte_range(range.clone()).captures(
+                    highlights_query,
+                    tree.root_node(),
+                    TextProvider(text),
+                );
+                highlights = Some(BufferChunkHighlights {
+                    captures,
+                    next_capture: None,
+                    stack: Default::default(),
+                    highlight_map: grammar.highlight_map(),
+                    _query_cursor: query_cursor,
+                })
+            }
         }
 
         let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable();

crates/language/src/language.rs 🔗

@@ -171,10 +171,10 @@ pub struct Language {
 
 pub struct Grammar {
     pub(crate) ts_language: tree_sitter::Language,
-    pub(crate) highlights_query: Query,
-    pub(crate) brackets_query: Query,
-    pub(crate) indents_query: Query,
-    pub(crate) outline_query: Query,
+    pub(crate) highlights_query: Option<Query>,
+    pub(crate) brackets_query: Option<Query>,
+    pub(crate) indents_query: Option<Query>,
+    pub(crate) outline_query: Option<Query>,
     pub(crate) highlight_map: Mutex<HighlightMap>,
 }
 
@@ -437,10 +437,10 @@ impl Language {
             config,
             grammar: ts_language.map(|ts_language| {
                 Arc::new(Grammar {
-                    brackets_query: Query::new(ts_language, "").unwrap(),
-                    highlights_query: Query::new(ts_language, "").unwrap(),
-                    indents_query: Query::new(ts_language, "").unwrap(),
-                    outline_query: Query::new(ts_language, "").unwrap(),
+                    highlights_query: None,
+                    brackets_query: None,
+                    indents_query: None,
+                    outline_query: None,
                     ts_language,
                     highlight_map: Default::default(),
                 })
@@ -457,45 +457,33 @@ impl Language {
     }
 
     pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self
-            .grammar
-            .as_mut()
-            .and_then(Arc::get_mut)
-            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
-        grammar.highlights_query = Query::new(grammar.ts_language, source)?;
+        let grammar = self.grammar_mut();
+        grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
     pub fn with_brackets_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self
-            .grammar
-            .as_mut()
-            .and_then(Arc::get_mut)
-            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
-        grammar.brackets_query = Query::new(grammar.ts_language, source)?;
+        let grammar = self.grammar_mut();
+        grammar.brackets_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
     pub fn with_indents_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self
-            .grammar
-            .as_mut()
-            .and_then(Arc::get_mut)
-            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
-        grammar.indents_query = Query::new(grammar.ts_language, source)?;
+        let grammar = self.grammar_mut();
+        grammar.indents_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
     pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
-        let grammar = self
-            .grammar
-            .as_mut()
-            .and_then(Arc::get_mut)
-            .ok_or_else(|| anyhow!("grammar does not exist or is already being used"))?;
-        grammar.outline_query = Query::new(grammar.ts_language, source)?;
+        let grammar = self.grammar_mut();
+        grammar.outline_query = Some(Query::new(grammar.ts_language, source)?);
         Ok(self)
     }
 
+    fn grammar_mut(&mut self) -> &mut Grammar {
+        Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
+    }
+
     pub fn with_lsp_adapter(mut self, lsp_adapter: Arc<dyn LspAdapter>) -> Self {
         self.adapter = Some(lsp_adapter);
         self
@@ -586,8 +574,10 @@ impl Language {
 
     pub fn set_theme(&self, theme: &SyntaxTheme) {
         if let Some(grammar) = self.grammar.as_ref() {
-            *grammar.highlight_map.lock() =
-                HighlightMap::new(grammar.highlights_query.capture_names(), theme);
+            if let Some(highlights_query) = &grammar.highlights_query {
+                *grammar.highlight_map.lock() =
+                    HighlightMap::new(highlights_query.capture_names(), theme);
+            }
         }
     }
 
@@ -621,7 +611,10 @@ impl Grammar {
     }
 
     pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
-        let capture_id = self.highlights_query.capture_index_for_name(name)?;
+        let capture_id = self
+            .highlights_query
+            .as_ref()?
+            .capture_index_for_name(name)?;
         Some(self.highlight_map.lock().get(capture_id))
     }
 }

crates/language/src/tests.rs 🔗

@@ -784,6 +784,47 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut MutableAppContext) {
     });
 }
 
+#[gpui::test]
+fn test_autoindent_disabled(cx: &mut MutableAppContext) {
+    cx.add_model(|cx| {
+        let text = "
+            * one
+                - a
+                - b
+            * two
+        "
+        .unindent();
+
+        let mut buffer = Buffer::new(0, text, cx).with_language(
+            Arc::new(Language::new(
+                LanguageConfig {
+                    name: "Markdown".into(),
+                    ..Default::default()
+                },
+                Some(tree_sitter_json::language()),
+            )),
+            cx,
+        );
+        buffer.edit_with_autoindent(
+            [(Point::new(3, 0)..Point::new(3, 0), "\n")],
+            IndentSize::spaces(4),
+            cx,
+        );
+        assert_eq!(
+            buffer.text(),
+            "
+            * one
+                - a
+                - b
+
+            * two
+            "
+            .unindent()
+        );
+        buffer
+    });
+}
+
 #[gpui::test]
 fn test_serialization(cx: &mut gpui::MutableAppContext) {
     let mut now = Instant::now();