diff --git a/Cargo.lock b/Cargo.lock index 3a9736c564..accd0a0939 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2828,7 +2828,9 @@ dependencies = [ "gpui", "lazy_static", "log", + "lsp", "parking_lot", + "postage", "rand 0.8.3", "rpc", "serde 1.0.125", diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index bb8186ed5f..9cafd673a9 100644 --- a/crates/buffer/src/anchor.rs +++ b/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 { #[derive(Clone)] pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>); +#[derive(Clone)] pub struct AnchorRangeMultimap { pub(crate) entries: SumTree>, pub(crate) version: clock::Global, @@ -164,6 +163,17 @@ impl AnchorRangeSet { } } +impl Default for AnchorRangeMultimap { + fn default() -> Self { + Self { + entries: Default::default(), + version: Default::default(), + start_bias: Bias::Left, + end_bias: Bias::Left, + } + } +} + impl AnchorRangeMultimap { fn intersecting_point_ranges<'a, O>( &'a self, diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index c037c38697..4fac433781 100644 --- a/crates/buffer/src/lib.rs +++ b/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 { - 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, 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 { + 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, 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, 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::(); - 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::>(); + 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 { + 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> { diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 30a506ea92..f343442392 100644 --- a/crates/editor/src/display_map.rs +++ b/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; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 61414d5dc7..b2a97982b7 100644 --- a/crates/editor/src/lib.rs +++ b/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()) diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 3cbfb3ae12..4aba95f97a 100644 --- a/crates/language/Cargo.toml +++ b/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" diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index b80eed7e33..6249b613c9 100644 --- a/crates/language/src/lib.rs +++ b/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 = RefCell::new(Parser::new()); @@ -57,6 +58,8 @@ pub struct Buffer { syntax_tree: Mutex>, parsing_in_background: bool, parse_count: usize, + diagnostics: AnchorRangeMultimap<()>, + language_server: Option, #[cfg(test)] operations: Vec, } @@ -69,6 +72,20 @@ pub struct Snapshot { query_cursor: QueryCursorHandle, } +struct LanguageServerState { + latest_snapshot: watch::Sender>, + pending_snapshots: BTreeMap, + next_version: usize, + _maintain_server: Task>, +} + +#[derive(Clone)] +struct LanguageServerSnapshot { + buffer_snapshot: buffer::Snapshot, + version: usize, + path: Arc, +} + #[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; + /// Returns the absolute path of this file. + fn abs_path(&self, cx: &AppContext) -> Option; + + /// 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>, language: Option>, + language_server: Option>, cx: &mut ModelContext, ) -> 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>, language: Option>, + language_server: Option>, cx: &mut ModelContext, ) -> 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>, cx: &mut ModelContext) { + pub fn set_language( + &mut self, + language: Option>, + language_server: Option>, + cx: &mut ModelContext, + ) { 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 = 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::(changes) + .await?; + } else { + server + .notify::( + 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, + ) -> Result<()> { + dbg!(¶ms); + 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::>(); + for version in versions_to_delete { + language_server.pending_snapshots.remove(&version); + } + + cx.notify(); + Ok(()) + } + fn request_autoindent(&mut self, cx: &mut ModelContext) { 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, ) -> 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(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext) where I: IntoIterator>, @@ -929,11 +1099,24 @@ impl Buffer { self.send_operation(Operation::Edit(edit), cx); } - fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext) { + fn did_edit( + &mut self, + old_version: clock::Global, + was_dirty: bool, + cx: &mut ModelContext, + ) { + 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, ) -> 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) { @@ -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(), } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 23cdced4c7..cc1382a098 100644 --- a/crates/language/src/tests.rs +++ b/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!( diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 60e5ba9065..1aba87e375 100644 --- a/crates/lsp/src/lib.rs +++ b/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?; diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 642bc596af..47e400aa09 100644 --- a/crates/project/src/worktree.rs +++ b/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> { + match self { + Worktree::Local(worktree) => worktree.language_server.as_ref(), + Worktree::Remote(_) => None, + } + } + pub fn handle_add_peer( &mut self, envelope: TypedEnvelope, @@ -667,9 +674,10 @@ pub struct LocalWorktree { share: Option, open_buffers: HashMap>, shared_buffers: HashMap>>, + diagnostics: HashMap>, peers: HashMap, - languages: Arc, queued_operations: Vec<(u64, Operation)>, + languages: Arc, rpc: Arc, fs: Arc, language_server: Option>, @@ -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, - ) { + ) -> 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 { + 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(); diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 07c511602c..9370ac7f85 100644 --- a/crates/workspace/src/items.rs +++ b/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); }); }) })