Adjust disk-based diagnostics based on edits since the last save

Nathan Sobo and Max Brunsfeld created

Still need to add tests... not sure if this is right yet.

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/buffer/src/lib.rs        |  3 ++
crates/language/src/language.rs |  8 +++---
crates/language/src/lib.rs      | 42 +++++++++++++++++++++++++++++++---
3 files changed, 45 insertions(+), 8 deletions(-)

Detailed changes

crates/buffer/src/lib.rs 🔗

@@ -332,6 +332,7 @@ pub struct Edit {
     pub old_bytes: Range<usize>,
     pub new_bytes: Range<usize>,
     pub old_lines: Range<Point>,
+    pub new_lines: Range<Point>,
 }
 
 impl Edit {
@@ -2014,6 +2015,7 @@ impl<'a, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
                         old_bytes: self.old_offset..self.old_offset,
                         new_bytes: self.new_offset..self.new_offset + fragment.len,
                         old_lines: self.old_point..self.old_point,
+                        new_lines: self.new_point..self.new_point + fragment_lines,
                     });
                 }
 
@@ -2035,6 +2037,7 @@ impl<'a, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
                         old_bytes: self.old_offset..self.old_offset + fragment.len,
                         new_bytes: self.new_offset..self.new_offset,
                         old_lines: self.old_point..self.old_point + &fragment_lines,
+                        new_lines: self.new_point..self.new_point,
                     });
                 }
 

crates/language/src/language.rs 🔗

@@ -3,7 +3,7 @@ use anyhow::Result;
 use gpui::AppContext;
 use parking_lot::Mutex;
 use serde::Deserialize;
-use std::{path::Path, str, sync::Arc};
+use std::{collections::HashSet, path::Path, str, sync::Arc};
 use theme::SyntaxTheme;
 use tree_sitter::{Language as Grammar, Query};
 pub use tree_sitter::{Parser, Tree};
@@ -19,7 +19,7 @@ pub struct LanguageConfig {
 #[derive(Deserialize)]
 pub struct LanguageServerConfig {
     pub binary: String,
-    pub disk_based_diagnostic_sources: Vec<String>,
+    pub disk_based_diagnostic_sources: HashSet<String>,
 }
 
 #[derive(Clone, Debug, Deserialize)]
@@ -130,11 +130,11 @@ impl Language {
         }
     }
 
-    pub fn disk_based_diagnostic_sources(&self) -> &[String] {
+    pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet<String>> {
         self.config
             .language_server
             .as_ref()
-            .map_or(&[], |config| &config.disk_based_diagnostic_sources)
+            .map(|config| &config.disk_based_diagnostic_sources)
     }
 
     pub fn brackets(&self) -> &[BracketPair] {

crates/language/src/lib.rs 🔗

@@ -661,18 +661,52 @@ impl Buffer {
         } else {
             self.content()
         };
+
+        let empty_set = HashSet::new();
+        let disk_based_sources = self
+            .language
+            .as_ref()
+            .and_then(|language| language.disk_based_diagnostic_sources())
+            .unwrap_or(&empty_set);
+
+        let mut edits_since_save = self.text.edits_since(self.saved_version.clone()).peekable();
+        let mut last_edit_old_end = Point::zero();
+        let mut last_edit_new_end = Point::zero();
+
         self.diagnostics = content.anchor_range_multimap(
             Bias::Left,
             Bias::Right,
-            diagnostics.into_iter().map(|diagnostic| {
+            diagnostics.into_iter().filter_map(|diagnostic| {
                 // TODO: Use UTF-16 positions.
-                let start = Point::new(
+                let mut start = Point::new(
                     diagnostic.range.start.line,
                     diagnostic.range.start.character,
                 );
-                let end = Point::new(diagnostic.range.end.line, diagnostic.range.end.character);
+                let mut end = Point::new(diagnostic.range.end.line, diagnostic.range.end.character);
                 let severity = diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR);
-                (start..end, (severity, diagnostic.message))
+
+                if diagnostic
+                    .source
+                    .as_ref()
+                    .map_or(false, |source| disk_based_sources.contains(source))
+                {
+                    while let Some(edit) = edits_since_save.peek() {
+                        if edit.old_lines.end <= start {
+                            last_edit_old_end = edit.old_lines.end;
+                            last_edit_new_end = edit.new_lines.end;
+                            edits_since_save.next();
+                        } else if edit.old_lines.start <= end && edit.old_lines.end >= start {
+                            return None;
+                        } else {
+                            break;
+                        }
+                    }
+
+                    start = last_edit_new_end + (start - last_edit_old_end);
+                    end = last_edit_new_end + (end - last_edit_old_end);
+                }
+
+                Some((start..end, (severity, diagnostic.message)))
             }),
         );