Cargo.lock 🔗
@@ -2828,7 +2828,9 @@ dependencies = [
"gpui",
"lazy_static",
"log",
+ "lsp",
"parking_lot",
+ "postage",
"rand 0.8.3",
"rpc",
"serde 1.0.125",
Antonio Scandurra created
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(-)
@@ -2828,7 +2828,9 @@ dependencies = [
"gpui",
"lazy_static",
"log",
+ "lsp",
"parking_lot",
+ "postage",
"rand 0.8.3",
"rpc",
"serde 1.0.125",
@@ -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,
@@ -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> {
@@ -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;
@@ -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())
@@ -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"
@@ -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!(¶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::<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(),
}
@@ -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!(
@@ -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?;
@@ -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();
@@ -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);
});
})
})