This commit is contained in:
Antonio Scandurra 2021-10-26 19:42:40 +02:00
parent 60abc5f090
commit 0674e76864
11 changed files with 341 additions and 111 deletions

2
Cargo.lock generated
View file

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

View file

@ -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,

View file

@ -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> {

View file

@ -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;

View file

@ -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())

View file

@ -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"

View file

@ -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(),
}

View file

@ -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!(

View file

@ -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?;

View file

@ -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();

View file

@ -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);
});
})
})