WIP

Antonio Scandurra created

Change summary

Cargo.lock                       |   2 
crates/buffer/src/anchor.rs      |  16 +
crates/buffer/src/lib.rs         | 114 ++++++++-------
crates/editor/src/display_map.rs |   4 
crates/editor/src/lib.rs         |   6 
crates/language/Cargo.toml       |   2 
crates/language/src/lib.rs       | 233 +++++++++++++++++++++++++++++----
crates/language/src/tests.rs     |  14 +
crates/lsp/src/lib.rs            |   2 
crates/project/src/worktree.rs   |  48 ++++++
crates/workspace/src/items.rs    |  11 
11 files changed, 341 insertions(+), 111 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2828,7 +2828,9 @@ dependencies = [
  "gpui",
  "lazy_static",
  "log",
+ "lsp",
  "parking_lot",
+ "postage",
  "rand 0.8.3",
  "rpc",
  "serde 1.0.125",

crates/buffer/src/anchor.rs 🔗

@@ -1,6 +1,4 @@
-use crate::{Point, ToOffset};
-
-use super::{Buffer, Content};
+use super::{Buffer, Content, Point, ToOffset};
 use anyhow::Result;
 use std::{cmp::Ordering, ops::Range};
 use sum_tree::{Bias, SumTree};
@@ -30,6 +28,7 @@ pub struct AnchorRangeMap<T> {
 #[derive(Clone)]
 pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>);
 
+#[derive(Clone)]
 pub struct AnchorRangeMultimap<T: Clone> {
     pub(crate) entries: SumTree<AnchorRangeMultimapEntry<T>>,
     pub(crate) version: clock::Global,
@@ -164,6 +163,17 @@ impl AnchorRangeSet {
     }
 }
 
+impl<T: Clone> Default for AnchorRangeMultimap<T> {
+    fn default() -> Self {
+        Self {
+            entries: Default::default(),
+            version: Default::default(),
+            start_bias: Bias::Left,
+            end_bias: Bias::Left,
+        }
+    }
+}
+
 impl<T: Clone> AnchorRangeMultimap<T> {
     fn intersecting_point_ranges<'a, O>(
         &'a self,

crates/buffer/src/lib.rs 🔗

@@ -19,8 +19,7 @@ pub use rope::{Chunks, Rope, TextSummary};
 use rpc::proto;
 pub use selection::*;
 use std::{
-    cmp,
-    collections::{BTreeMap, BTreeSet},
+    cmp::{self, Reverse},
     convert::{TryFrom, TryInto},
     iter::Iterator,
     ops::Range,
@@ -534,6 +533,8 @@ impl Buffer {
     pub fn snapshot(&self) -> Snapshot {
         Snapshot {
             visible_text: self.visible_text.clone(),
+            deleted_text: self.deleted_text.clone(),
+            undo_map: self.undo_map.clone(),
             fragments: self.fragments.clone(),
             version: self.version.clone(),
         }
@@ -1344,27 +1345,7 @@ impl Buffer {
     }
 
     pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
-        let since_2 = since.clone();
-        let cursor = if since == self.version {
-            None
-        } else {
-            Some(self.fragments.filter(
-                move |summary| summary.max_version.changed_since(&since_2),
-                &None,
-            ))
-        };
-
-        Edits {
-            visible_text: &self.visible_text,
-            deleted_text: &self.deleted_text,
-            cursor,
-            undos: &self.undo_map,
-            since,
-            old_offset: 0,
-            new_offset: 0,
-            old_point: Point::zero(),
-            new_point: Point::zero(),
-        }
+        self.content().edits_since(since)
     }
 }
 
@@ -1522,6 +1503,8 @@ impl Buffer {
 #[derive(Clone)]
 pub struct Snapshot {
     visible_text: Rope,
+    deleted_text: Rope,
+    undo_map: UndoMap,
     fragments: SumTree<Fragment>,
     version: clock::Global,
 }
@@ -1596,6 +1579,14 @@ impl Snapshot {
         self.content().anchor_at(position, Bias::Right)
     }
 
+    pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
+        self.content().edits_since(since)
+    }
+
+    pub fn version(&self) -> &clock::Global {
+        &self.version
+    }
+
     pub fn content(&self) -> Content {
         self.into()
     }
@@ -1603,6 +1594,8 @@ impl Snapshot {
 
 pub struct Content<'a> {
     visible_text: &'a Rope,
+    deleted_text: &'a Rope,
+    undo_map: &'a UndoMap,
     fragments: &'a SumTree<Fragment>,
     version: &'a clock::Global,
 }
@@ -1611,6 +1604,8 @@ impl<'a> From<&'a Snapshot> for Content<'a> {
     fn from(snapshot: &'a Snapshot) -> Self {
         Self {
             visible_text: &snapshot.visible_text,
+            deleted_text: &snapshot.deleted_text,
+            undo_map: &snapshot.undo_map,
             fragments: &snapshot.fragments,
             version: &snapshot.version,
         }
@@ -1621,6 +1616,8 @@ impl<'a> From<&'a Buffer> for Content<'a> {
     fn from(buffer: &'a Buffer) -> Self {
         Self {
             visible_text: &buffer.visible_text,
+            deleted_text: &buffer.deleted_text,
+            undo_map: &buffer.undo_map,
             fragments: &buffer.fragments,
             version: &buffer.version,
         }
@@ -1631,6 +1628,8 @@ impl<'a> From<&'a mut Buffer> for Content<'a> {
     fn from(buffer: &'a mut Buffer) -> Self {
         Self {
             visible_text: &buffer.visible_text,
+            deleted_text: &buffer.deleted_text,
+            undo_map: &buffer.undo_map,
             fragments: &buffer.fragments,
             version: &buffer.version,
         }
@@ -1641,6 +1640,8 @@ impl<'a> From<&'a Content<'a>> for Content<'a> {
     fn from(content: &'a Content) -> Self {
         Self {
             visible_text: &content.visible_text,
+            deleted_text: &content.deleted_text,
+            undo_map: &content.undo_map,
             fragments: &content.fragments,
             version: &content.version,
         }
@@ -1848,39 +1849,19 @@ impl<'a> Content<'a> {
         E: IntoIterator<Item = (Range<O>, T)>,
         O: ToOffset,
     {
-        let mut items = Vec::new();
-        let mut endpoints = BTreeMap::new();
-        for (ix, (range, value)) in entries.into_iter().enumerate() {
-            items.push(AnchorRangeMultimapEntry {
-                range: FullOffsetRange { start: 0, end: 0 },
+        let mut entries = entries
+            .into_iter()
+            .map(|(range, value)| AnchorRangeMultimapEntry {
+                range: FullOffsetRange {
+                    start: range.start.to_full_offset(self, start_bias),
+                    end: range.end.to_full_offset(self, end_bias),
+                },
                 value,
-            });
-            endpoints
-                .entry((range.start.to_offset(self), start_bias))
-                .or_insert(Vec::new())
-                .push((ix, true));
-            endpoints
-                .entry((range.end.to_offset(self), end_bias))
-                .or_insert(Vec::new())
-                .push((ix, false));
-        }
-
-        let mut cursor = self.fragments.cursor::<FragmentTextSummary>();
-        for ((endpoint, bias), item_ixs) in endpoints {
-            cursor.seek_forward(&endpoint, bias, &None);
-            let full_offset = cursor.start().deleted + endpoint;
-            for (item_ix, is_start) in item_ixs {
-                if is_start {
-                    items[item_ix].range.start = full_offset;
-                } else {
-                    items[item_ix].range.end = full_offset;
-                }
-            }
-        }
-        items.sort_unstable_by_key(|i| (i.range.start, i.range.end));
-
+            })
+            .collect::<Vec<_>>();
+        entries.sort_unstable_by_key(|i| (i.range.start, Reverse(i.range.end)));
         AnchorRangeMultimap {
-            entries: SumTree::from_iter(items, &()),
+            entries: SumTree::from_iter(entries, &()),
             version: self.version.clone(),
             start_bias,
             end_bias,
@@ -1913,6 +1894,31 @@ impl<'a> Content<'a> {
             Err(anyhow!("offset out of bounds"))
         }
     }
+
+    // TODO: take a reference to clock::Global.
+    pub fn edits_since(&self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
+        let since_2 = since.clone();
+        let cursor = if since == *self.version {
+            None
+        } else {
+            Some(self.fragments.filter(
+                move |summary| summary.max_version.changed_since(&since_2),
+                &None,
+            ))
+        };
+
+        Edits {
+            visible_text: &self.visible_text,
+            deleted_text: &self.deleted_text,
+            cursor,
+            undos: &self.undo_map,
+            since,
+            old_offset: 0,
+            new_offset: 0,
+            old_point: Point::zero(),
+            new_point: Point::zero(),
+        }
+    }
 }
 
 struct RopeBuilder<'a> {

crates/editor/src/display_map.rs 🔗

@@ -702,7 +702,7 @@ mod tests {
         lang.set_theme(&theme);
 
         let buffer = cx.add_model(|cx| {
-            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
+            Buffer::from_history(0, History::new(text.into()), None, Some(lang), None, cx)
         });
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 
@@ -790,7 +790,7 @@ mod tests {
         lang.set_theme(&theme);
 
         let buffer = cx.add_model(|cx| {
-            Buffer::from_history(0, History::new(text.into()), None, Some(lang), cx)
+            Buffer::from_history(0, History::new(text.into()), None, Some(lang), None, cx)
         });
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
 

crates/editor/src/lib.rs 🔗

@@ -4422,7 +4422,7 @@ mod tests {
 
         let buffer = cx.add_model(|cx| {
             let history = History::new(text.into());
-            Buffer::from_history(0, history, None, Some(language), cx)
+            Buffer::from_history(0, history, None, Some(language), None, cx)
         });
         let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
         view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
@@ -4581,7 +4581,7 @@ mod tests {
 
         let buffer = cx.add_model(|cx| {
             let history = History::new(text.into());
-            Buffer::from_history(0, history, None, Some(language), cx)
+            Buffer::from_history(0, history, None, Some(language), None, cx)
         });
         let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
         view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
@@ -4696,7 +4696,7 @@ mod tests {
 
         let buffer = cx.add_model(|cx| {
             let history = History::new(text.into());
-            Buffer::from_history(0, history, None, Some(language), cx)
+            Buffer::from_history(0, history, None, Some(language), None, cx)
         });
         let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
         view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())

crates/language/Cargo.toml 🔗

@@ -10,6 +10,7 @@ test-support = ["rand", "buffer/test-support"]
 buffer = { path = "../buffer" }
 clock = { path = "../clock" }
 gpui = { path = "../gpui" }
+lsp = { path = "../lsp" }
 rpc = { path = "../rpc" }
 theme = { path = "../theme" }
 util = { path = "../util" }
@@ -18,6 +19,7 @@ futures = "0.3"
 lazy_static = "1.4"
 log = "0.4"
 parking_lot = "0.11.1"
+postage = { version = "0.4.1", features = ["futures-traits"] }
 rand = { version = "0.8.3", optional = true }
 serde = { version = "1", features = ["derive"] }
 similar = "1.3"

crates/language/src/lib.rs 🔗

@@ -14,6 +14,7 @@ use futures::FutureExt as _;
 use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
+use postage::{prelude::Stream, sink::Sink, watch};
 use rpc::proto;
 use similar::{ChangeTag, TextDiff};
 use smol::future::yield_now;
@@ -32,7 +33,7 @@ use std::{
     time::{Duration, Instant, SystemTime, UNIX_EPOCH},
 };
 use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
-use util::TryFutureExt as _;
+use util::{post_inc, TryFutureExt as _};
 
 thread_local! {
     static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
@@ -57,6 +58,8 @@ pub struct Buffer {
     syntax_tree: Mutex<Option<SyntaxTree>>,
     parsing_in_background: bool,
     parse_count: usize,
+    diagnostics: AnchorRangeMultimap<()>,
+    language_server: Option<LanguageServerState>,
     #[cfg(test)]
     operations: Vec<Operation>,
 }
@@ -69,6 +72,20 @@ pub struct Snapshot {
     query_cursor: QueryCursorHandle,
 }
 
+struct LanguageServerState {
+    latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,
+    pending_snapshots: BTreeMap<usize, LanguageServerSnapshot>,
+    next_version: usize,
+    _maintain_server: Task<Option<()>>,
+}
+
+#[derive(Clone)]
+struct LanguageServerSnapshot {
+    buffer_snapshot: buffer::Snapshot,
+    version: usize,
+    path: Arc<Path>,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub enum Event {
     Edited,
@@ -87,8 +104,14 @@ pub trait File {
 
     fn mtime(&self) -> SystemTime;
 
+    /// Returns the path of this file relative to the worktree's root directory.
     fn path(&self) -> &Arc<Path>;
 
+    /// Returns the absolute path of this file.
+    fn abs_path(&self, cx: &AppContext) -> Option<PathBuf>;
+
+    /// Returns the path of this file relative to the worktree's parent directory (this means it
+    /// includes the name of the worktree's root folder).
     fn full_path(&self, cx: &AppContext) -> PathBuf;
 
     /// Returns the last component of this handle's absolute path. If this handle refers to the root
@@ -173,6 +196,7 @@ impl Buffer {
             ),
             None,
             None,
+            None,
             cx,
         )
     }
@@ -182,12 +206,14 @@ impl Buffer {
         history: History,
         file: Option<Box<dyn File>>,
         language: Option<Arc<Language>>,
+        language_server: Option<Arc<lsp::LanguageServer>>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         Self::build(
             TextBuffer::new(replica_id, cx.model_id() as u64, history),
             file,
             language,
+            language_server,
             cx,
         )
     }
@@ -203,6 +229,7 @@ impl Buffer {
             TextBuffer::from_proto(replica_id, message)?,
             file,
             language,
+            None,
             cx,
         ))
     }
@@ -211,6 +238,7 @@ impl Buffer {
         buffer: TextBuffer,
         file: Option<Box<dyn File>>,
         language: Option<Arc<Language>>,
+        language_server: Option<Arc<lsp::LanguageServer>>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let saved_mtime;
@@ -231,12 +259,13 @@ impl Buffer {
             sync_parse_timeout: Duration::from_millis(1),
             autoindent_requests: Default::default(),
             pending_autoindent: Default::default(),
-            language,
-
+            language: None,
+            diagnostics: Default::default(),
+            language_server: None,
             #[cfg(test)]
             operations: Default::default(),
         };
-        result.reparse(cx);
+        result.set_language(language, language_server, cx);
         result
     }
 
@@ -274,9 +303,90 @@ impl Buffer {
         }))
     }
 
-    pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
+    pub fn set_language(
+        &mut self,
+        language: Option<Arc<Language>>,
+        language_server: Option<Arc<lsp::LanguageServer>>,
+        cx: &mut ModelContext<Self>,
+    ) {
         self.language = language;
+        self.language_server = if let Some(server) = language_server {
+            let (latest_snapshot_tx, mut latest_snapshot_rx) = watch::channel();
+            Some(LanguageServerState {
+                latest_snapshot: latest_snapshot_tx,
+                pending_snapshots: Default::default(),
+                next_version: 0,
+                _maintain_server: cx.background().spawn(
+                    async move {
+                        let mut prev_snapshot: Option<LanguageServerSnapshot> = None;
+                        while let Some(snapshot) = latest_snapshot_rx.recv().await {
+                            if let Some(snapshot) = snapshot {
+                                let uri = lsp::Url::from_file_path(&snapshot.path).unwrap();
+                                if let Some(prev_snapshot) = prev_snapshot {
+                                    let changes = lsp::DidChangeTextDocumentParams {
+                                        text_document: lsp::VersionedTextDocumentIdentifier::new(
+                                            uri,
+                                            snapshot.version as i32,
+                                        ),
+                                        content_changes: snapshot
+                                            .buffer_snapshot
+                                            .edits_since(
+                                                prev_snapshot.buffer_snapshot.version().clone(),
+                                            )
+                                            .map(|edit| {
+                                                lsp::TextDocumentContentChangeEvent {
+                                                    // TODO: Use UTF-16 positions.
+                                                    range: Some(lsp::Range::new(
+                                                        lsp::Position::new(
+                                                            edit.old_lines.start.row,
+                                                            edit.old_lines.start.column,
+                                                        ),
+                                                        lsp::Position::new(
+                                                            edit.old_lines.end.row,
+                                                            edit.old_lines.end.column,
+                                                        ),
+                                                    )),
+                                                    range_length: None,
+                                                    text: snapshot
+                                                        .buffer_snapshot
+                                                        .text_for_range(edit.new_bytes)
+                                                        .collect(),
+                                                }
+                                            })
+                                            .collect(),
+                                    };
+                                    server
+                                        .notify::<lsp::notification::DidChangeTextDocument>(changes)
+                                        .await?;
+                                } else {
+                                    server
+                                        .notify::<lsp::notification::DidOpenTextDocument>(
+                                            lsp::DidOpenTextDocumentParams {
+                                                text_document: lsp::TextDocumentItem::new(
+                                                    uri,
+                                                    Default::default(),
+                                                    snapshot.version as i32,
+                                                    snapshot.buffer_snapshot.text().into(),
+                                                ),
+                                            },
+                                        )
+                                        .await?;
+                                }
+
+                                prev_snapshot = Some(snapshot);
+                            }
+                        }
+                        Ok(())
+                    }
+                    .log_err(),
+                ),
+            })
+        } else {
+            None
+        };
+
         self.reparse(cx);
+        self.update_language_server(cx);
     }
 
     pub fn did_save(
@@ -486,6 +596,45 @@ impl Buffer {
         cx.notify();
     }
 
+    pub fn update_diagnostics(
+        &mut self,
+        params: lsp::PublishDiagnosticsParams,
+        cx: &mut ModelContext<Self>,
+    ) -> Result<()> {
+        dbg!(&params);
+        let language_server = self.language_server.as_mut().unwrap();
+        let version = params.version.ok_or_else(|| anyhow!("missing version"))? as usize;
+        let snapshot = language_server
+            .pending_snapshots
+            .get(&version)
+            .ok_or_else(|| anyhow!("missing snapshot"))?;
+        self.diagnostics = snapshot.buffer_snapshot.content().anchor_range_multimap(
+            Bias::Left,
+            Bias::Right,
+            params.diagnostics.into_iter().map(|diagnostic| {
+                // TODO: Use UTF-16 positions.
+                let start = Point::new(
+                    diagnostic.range.start.line,
+                    diagnostic.range.start.character,
+                );
+                let end = Point::new(diagnostic.range.end.line, diagnostic.range.end.character);
+                (start..end, ())
+            }),
+        );
+
+        let versions_to_delete = language_server
+            .pending_snapshots
+            .range(..version)
+            .map(|(v, _)| *v)
+            .collect::<Vec<_>>();
+        for version in versions_to_delete {
+            language_server.pending_snapshots.remove(&version);
+        }
+
+        cx.notify();
+        Ok(())
+    }
+
     fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
         if let Some(indent_columns) = self.compute_autoindents() {
             let indent_columns = cx.background().spawn(indent_columns);
@@ -811,17 +960,38 @@ impl Buffer {
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Some(start_version) = self.text.end_transaction_at(selection_set_ids, now) {
-            cx.notify();
             let was_dirty = start_version != self.saved_version;
-            let edited = self.edits_since(start_version).next().is_some();
-            if edited {
-                self.did_edit(was_dirty, cx);
-                self.reparse(cx);
-            }
+            self.did_edit(start_version, was_dirty, cx);
         }
         Ok(())
     }
 
+    fn update_language_server(&mut self, cx: &AppContext) {
+        let language_server = if let Some(language_server) = self.language_server.as_mut() {
+            language_server
+        } else {
+            return;
+        };
+        let file = if let Some(file) = self.file.as_ref() {
+            file
+        } else {
+            return;
+        };
+
+        let version = post_inc(&mut language_server.next_version);
+        let snapshot = LanguageServerSnapshot {
+            buffer_snapshot: self.text.snapshot(),
+            version,
+            path: Arc::from(file.abs_path(cx).unwrap()),
+        };
+        language_server
+            .pending_snapshots
+            .insert(version, snapshot.clone());
+        let _ = language_server
+            .latest_snapshot
+            .blocking_send(Some(snapshot));
+    }
+
     pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
     where
         I: IntoIterator<Item = Range<S>>,
@@ -929,11 +1099,24 @@ impl Buffer {
         self.send_operation(Operation::Edit(edit), cx);
     }
 
-    fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext<Self>) {
+    fn did_edit(
+        &mut self,
+        old_version: clock::Global,
+        was_dirty: bool,
+        cx: &mut ModelContext<Self>,
+    ) {
+        if self.edits_since(old_version).next().is_none() {
+            return;
+        }
+
+        self.reparse(cx);
+        self.update_language_server(cx);
+
         cx.emit(Event::Edited);
         if !was_dirty {
             cx.emit(Event::Dirtied);
         }
+        cx.notify();
     }
 
     pub fn add_selection_set(
@@ -991,18 +1174,10 @@ impl Buffer {
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         self.pending_autoindent.take();
-
         let was_dirty = self.is_dirty();
         let old_version = self.version.clone();
-
         self.text.apply_ops(ops)?;
-
-        cx.notify();
-        if self.edits_since(old_version).next().is_some() {
-            self.did_edit(was_dirty, cx);
-            self.reparse(cx);
-        }
-
+        self.did_edit(old_version, was_dirty, cx);
         Ok(())
     }
 
@@ -1031,11 +1206,7 @@ impl Buffer {
             self.send_operation(operation, cx);
         }
 
-        cx.notify();
-        if self.edits_since(old_version).next().is_some() {
-            self.did_edit(was_dirty, cx);
-            self.reparse(cx);
-        }
+        self.did_edit(old_version, was_dirty, cx);
     }
 
     pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
@@ -1046,11 +1217,7 @@ impl Buffer {
             self.send_operation(operation, cx);
         }
 
-        cx.notify();
-        if self.edits_since(old_version).next().is_some() {
-            self.did_edit(was_dirty, cx);
-            self.reparse(cx);
-        }
+        self.did_edit(old_version, was_dirty, cx);
     }
 }
 
@@ -1081,6 +1248,7 @@ impl Entity for Buffer {
     }
 }
 
+// TODO: Do we need to clone a buffer?
 impl Clone for Buffer {
     fn clone(&self) -> Self {
         Self {
@@ -1095,7 +1263,8 @@ impl Clone for Buffer {
             parse_count: self.parse_count,
             autoindent_requests: Default::default(),
             pending_autoindent: Default::default(),
-
+            diagnostics: self.diagnostics.clone(),
+            language_server: None,
             #[cfg(test)]
             operations: self.operations.clone(),
         }

crates/language/src/tests.rs 🔗

@@ -80,7 +80,7 @@ async fn test_apply_diff(mut cx: gpui::TestAppContext) {
 async fn test_reparse(mut cx: gpui::TestAppContext) {
     let buffer = cx.add_model(|cx| {
         let text = "fn a() {}".into();
-        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
+        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), None, cx)
     });
 
     // Wait for the initial text to parse
@@ -224,7 +224,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
         "
         .unindent()
         .into();
-        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx)
+        Buffer::from_history(0, History::new(text), None, Some(rust_lang()), None, cx)
     });
     let buffer = buffer.read(cx);
     assert_eq!(
@@ -254,7 +254,8 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
 fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
     cx.add_model(|cx| {
         let text = "fn a() {}".into();
-        let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
+        let mut buffer =
+            Buffer::from_history(0, History::new(text), None, Some(rust_lang()), None, cx);
 
         buffer.edit_with_autoindent([8..8], "\n\n", cx);
         assert_eq!(buffer.text(), "fn a() {\n    \n}");
@@ -273,7 +274,7 @@ fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
 fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
     cx.add_model(|cx| {
         let text = History::new("fn a() {}".into());
-        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
+        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), None, cx);
 
         let selection_set_id = buffer.add_selection_set(Vec::new(), cx);
         buffer.start_transaction(Some(selection_set_id)).unwrap();
@@ -332,7 +333,8 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
         "
         .unindent()
         .into();
-        let mut buffer = Buffer::from_history(0, History::new(text), None, Some(rust_lang()), cx);
+        let mut buffer =
+            Buffer::from_history(0, History::new(text), None, Some(rust_lang()), None, cx);
 
         // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
         // their indentation is not adjusted.
@@ -383,7 +385,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
             .unindent()
             .into(),
         );
-        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), cx);
+        let mut buffer = Buffer::from_history(0, text, None, Some(rust_lang()), None, cx);
 
         buffer.edit_with_autoindent([5..5], "\nb", cx);
         assert_eq!(

crates/lsp/src/lib.rs 🔗

@@ -157,6 +157,7 @@ impl LanguageServer {
                         buffer.resize(message_len, 0);
                         stdout.read_exact(&mut buffer).await?;
 
+                        println!("{}", std::str::from_utf8(&buffer).unwrap());
                         if let Ok(AnyNotification { method, params }) =
                             serde_json::from_slice(&buffer)
                         {
@@ -200,6 +201,7 @@ impl LanguageServer {
                     content_len_buffer.clear();
 
                     let message = outbound_rx.recv().await?;
+                    println!("{}", std::str::from_utf8(&message).unwrap());
                     write!(content_len_buffer, "{}", message.len()).unwrap();
                     stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
                     stdin.write_all(&content_len_buffer).await?;

crates/project/src/worktree.rs 🔗

@@ -40,7 +40,7 @@ use std::{
 };
 use sum_tree::Bias;
 use sum_tree::{Edit, SeekTarget, SumTree};
-use util::TryFutureExt;
+use util::{ResultExt, TryFutureExt};
 
 lazy_static! {
     static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore");
@@ -295,6 +295,13 @@ impl Worktree {
         }
     }
 
+    pub fn language_server(&self) -> Option<&Arc<lsp::LanguageServer>> {
+        match self {
+            Worktree::Local(worktree) => worktree.language_server.as_ref(),
+            Worktree::Remote(_) => None,
+        }
+    }
+
     pub fn handle_add_peer(
         &mut self,
         envelope: TypedEnvelope<proto::AddPeer>,
@@ -667,9 +674,10 @@ pub struct LocalWorktree {
     share: Option<ShareState>,
     open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
     shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
+    diagnostics: HashMap<PathBuf, Vec<lsp::Diagnostic>>,
     peers: HashMap<PeerId, ReplicaId>,
-    languages: Arc<LanguageRegistry>,
     queued_operations: Vec<(u64, Operation)>,
+    languages: Arc<LanguageRegistry>,
     rpc: Arc<Client>,
     fs: Arc<dyn Fs>,
     language_server: Option<Arc<LanguageServer>>,
@@ -781,6 +789,7 @@ impl LocalWorktree {
                 poll_task: None,
                 open_buffers: Default::default(),
                 shared_buffers: Default::default(),
+                diagnostics: Default::default(),
                 queued_operations: Default::default(),
                 peers: Default::default(),
                 languages,
@@ -828,7 +837,7 @@ impl LocalWorktree {
                         if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
                             handle.update(&mut cx, |this, cx| {
                                 let this = this.as_local_mut().unwrap();
-                                this.update_diagnostics(diagnostics, cx);
+                                this.update_diagnostics(diagnostics, cx).log_err();
                             });
                         } else {
                             break;
@@ -867,6 +876,7 @@ impl LocalWorktree {
         });
 
         let path = Arc::from(path);
+        let language_server = self.language_server.clone();
         cx.spawn(|this, mut cx| async move {
             if let Some(existing_buffer) = existing_buffer {
                 Ok(existing_buffer)
@@ -887,6 +897,7 @@ impl LocalWorktree {
                         History::new(contents.into()),
                         Some(Box::new(file)),
                         language,
+                        language_server,
                         cx,
                     )
                 });
@@ -1187,9 +1198,29 @@ impl LocalWorktree {
 
     fn update_diagnostics(
         &mut self,
-        diagnostics: lsp::PublishDiagnosticsParams,
+        params: lsp::PublishDiagnosticsParams,
         cx: &mut ModelContext<Worktree>,
-    ) {
+    ) -> Result<()> {
+        let file_path = params
+            .uri
+            .to_file_path()
+            .map_err(|_| anyhow!("URI is not a file"))?;
+
+        for buffer in self.open_buffers.values() {
+            if let Some(buffer) = buffer.upgrade(cx) {
+                if buffer
+                    .read(cx)
+                    .file()
+                    .map_or(false, |file| file.path().as_ref() == file_path)
+                {
+                    buffer.update(cx, |buffer, cx| buffer.update_diagnostics(params, cx))?;
+                    return Ok(());
+                }
+            }
+        }
+
+        self.diagnostics.insert(file_path, params.diagnostics);
+        Ok(())
     }
 }
 
@@ -1809,6 +1840,13 @@ impl language::File for File {
         &self.path
     }
 
+    fn abs_path(&self, cx: &AppContext) -> Option<PathBuf> {
+        let worktree = self.worktree.read(cx);
+        worktree
+            .as_local()
+            .map(|worktree| worktree.absolutize(&self.path))
+    }
+
     fn full_path(&self, cx: &AppContext) -> PathBuf {
         let worktree = self.worktree.read(cx);
         let mut full_path = PathBuf::new();

crates/workspace/src/items.rs 🔗

@@ -127,16 +127,15 @@ impl ItemView for Editor {
 
             cx.spawn(|buffer, mut cx| async move {
                 save_as.await.map(|new_file| {
-                    let language = worktree.read_with(&cx, |worktree, cx| {
-                        worktree
-                            .languages()
-                            .select_language(new_file.full_path(cx))
-                            .cloned()
+                    let (language, language_server) = worktree.read_with(&cx, |worktree, cx| {
+                        let language = worktree.languages().select_language(new_file.full_path(cx));
+                        let language_server = worktree.language_server();
+                        (language.cloned(), language_server.cloned())
                     });
 
                     buffer.update(&mut cx, |buffer, cx| {
                         buffer.did_save(version, new_file.mtime, Some(Box::new(new_file)), cx);
-                        buffer.set_language(language, cx);
+                        buffer.set_language(language, language_server, cx);
                     });
                 })
             })