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", "gpui",
"lazy_static", "lazy_static",
"log", "log",
"lsp",
"parking_lot", "parking_lot",
"postage",
"rand 0.8.3", "rand 0.8.3",
"rpc", "rpc",
"serde 1.0.125", "serde 1.0.125",

View file

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

View file

@ -19,8 +19,7 @@ pub use rope::{Chunks, Rope, TextSummary};
use rpc::proto; use rpc::proto;
pub use selection::*; pub use selection::*;
use std::{ use std::{
cmp, cmp::{self, Reverse},
collections::{BTreeMap, BTreeSet},
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
iter::Iterator, iter::Iterator,
ops::Range, ops::Range,
@ -534,6 +533,8 @@ impl Buffer {
pub fn snapshot(&self) -> Snapshot { pub fn snapshot(&self) -> Snapshot {
Snapshot { Snapshot {
visible_text: self.visible_text.clone(), visible_text: self.visible_text.clone(),
deleted_text: self.deleted_text.clone(),
undo_map: self.undo_map.clone(),
fragments: self.fragments.clone(), fragments: self.fragments.clone(),
version: self.version.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> { pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator<Item = Edit> {
let since_2 = since.clone(); self.content().edits_since(since)
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(),
}
} }
} }
@ -1522,6 +1503,8 @@ impl Buffer {
#[derive(Clone)] #[derive(Clone)]
pub struct Snapshot { pub struct Snapshot {
visible_text: Rope, visible_text: Rope,
deleted_text: Rope,
undo_map: UndoMap,
fragments: SumTree<Fragment>, fragments: SumTree<Fragment>,
version: clock::Global, version: clock::Global,
} }
@ -1596,6 +1579,14 @@ impl Snapshot {
self.content().anchor_at(position, Bias::Right) 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 { pub fn content(&self) -> Content {
self.into() self.into()
} }
@ -1603,6 +1594,8 @@ impl Snapshot {
pub struct Content<'a> { pub struct Content<'a> {
visible_text: &'a Rope, visible_text: &'a Rope,
deleted_text: &'a Rope,
undo_map: &'a UndoMap,
fragments: &'a SumTree<Fragment>, fragments: &'a SumTree<Fragment>,
version: &'a clock::Global, version: &'a clock::Global,
} }
@ -1611,6 +1604,8 @@ impl<'a> From<&'a Snapshot> for Content<'a> {
fn from(snapshot: &'a Snapshot) -> Self { fn from(snapshot: &'a Snapshot) -> Self {
Self { Self {
visible_text: &snapshot.visible_text, visible_text: &snapshot.visible_text,
deleted_text: &snapshot.deleted_text,
undo_map: &snapshot.undo_map,
fragments: &snapshot.fragments, fragments: &snapshot.fragments,
version: &snapshot.version, version: &snapshot.version,
} }
@ -1621,6 +1616,8 @@ impl<'a> From<&'a Buffer> for Content<'a> {
fn from(buffer: &'a Buffer) -> Self { fn from(buffer: &'a Buffer) -> Self {
Self { Self {
visible_text: &buffer.visible_text, visible_text: &buffer.visible_text,
deleted_text: &buffer.deleted_text,
undo_map: &buffer.undo_map,
fragments: &buffer.fragments, fragments: &buffer.fragments,
version: &buffer.version, version: &buffer.version,
} }
@ -1631,6 +1628,8 @@ impl<'a> From<&'a mut Buffer> for Content<'a> {
fn from(buffer: &'a mut Buffer) -> Self { fn from(buffer: &'a mut Buffer) -> Self {
Self { Self {
visible_text: &buffer.visible_text, visible_text: &buffer.visible_text,
deleted_text: &buffer.deleted_text,
undo_map: &buffer.undo_map,
fragments: &buffer.fragments, fragments: &buffer.fragments,
version: &buffer.version, version: &buffer.version,
} }
@ -1641,6 +1640,8 @@ impl<'a> From<&'a Content<'a>> for Content<'a> {
fn from(content: &'a Content) -> Self { fn from(content: &'a Content) -> Self {
Self { Self {
visible_text: &content.visible_text, visible_text: &content.visible_text,
deleted_text: &content.deleted_text,
undo_map: &content.undo_map,
fragments: &content.fragments, fragments: &content.fragments,
version: &content.version, version: &content.version,
} }
@ -1848,39 +1849,19 @@ impl<'a> Content<'a> {
E: IntoIterator<Item = (Range<O>, T)>, E: IntoIterator<Item = (Range<O>, T)>,
O: ToOffset, O: ToOffset,
{ {
let mut items = Vec::new(); let mut entries = entries
let mut endpoints = BTreeMap::new(); .into_iter()
for (ix, (range, value)) in entries.into_iter().enumerate() { .map(|(range, value)| AnchorRangeMultimapEntry {
items.push(AnchorRangeMultimapEntry { range: FullOffsetRange {
range: FullOffsetRange { start: 0, end: 0 }, start: range.start.to_full_offset(self, start_bias),
end: range.end.to_full_offset(self, end_bias),
},
value, value,
}); })
endpoints .collect::<Vec<_>>();
.entry((range.start.to_offset(self), start_bias)) entries.sort_unstable_by_key(|i| (i.range.start, Reverse(i.range.end)));
.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));
AnchorRangeMultimap { AnchorRangeMultimap {
entries: SumTree::from_iter(items, &()), entries: SumTree::from_iter(entries, &()),
version: self.version.clone(), version: self.version.clone(),
start_bias, start_bias,
end_bias, end_bias,
@ -1913,6 +1894,31 @@ impl<'a> Content<'a> {
Err(anyhow!("offset out of bounds")) 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> { struct RopeBuilder<'a> {

View file

@ -702,7 +702,7 @@ mod tests {
lang.set_theme(&theme); lang.set_theme(&theme);
let buffer = cx.add_model(|cx| { 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; buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
@ -790,7 +790,7 @@ mod tests {
lang.set_theme(&theme); lang.set_theme(&theme);
let buffer = cx.add_model(|cx| { 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; buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;

View file

@ -4422,7 +4422,7 @@ mod tests {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let history = History::new(text.into()); 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)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
@ -4581,7 +4581,7 @@ mod tests {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let history = History::new(text.into()); 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)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing())
@ -4696,7 +4696,7 @@ mod tests {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let history = History::new(text.into()); 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)); let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing()) 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" } buffer = { path = "../buffer" }
clock = { path = "../clock" } clock = { path = "../clock" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" } rpc = { path = "../rpc" }
theme = { path = "../theme" } theme = { path = "../theme" }
util = { path = "../util" } util = { path = "../util" }
@ -18,6 +19,7 @@ futures = "0.3"
lazy_static = "1.4" lazy_static = "1.4"
log = "0.4" log = "0.4"
parking_lot = "0.11.1" parking_lot = "0.11.1"
postage = { version = "0.4.1", features = ["futures-traits"] }
rand = { version = "0.8.3", optional = true } rand = { version = "0.8.3", optional = true }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
similar = "1.3" similar = "1.3"

View file

@ -14,6 +14,7 @@ use futures::FutureExt as _;
use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::{prelude::Stream, sink::Sink, watch};
use rpc::proto; use rpc::proto;
use similar::{ChangeTag, TextDiff}; use similar::{ChangeTag, TextDiff};
use smol::future::yield_now; use smol::future::yield_now;
@ -32,7 +33,7 @@ use std::{
time::{Duration, Instant, SystemTime, UNIX_EPOCH}, time::{Duration, Instant, SystemTime, UNIX_EPOCH},
}; };
use tree_sitter::{InputEdit, Parser, QueryCursor, Tree}; use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
use util::TryFutureExt as _; use util::{post_inc, TryFutureExt as _};
thread_local! { thread_local! {
static PARSER: RefCell<Parser> = RefCell::new(Parser::new()); static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
@ -57,6 +58,8 @@ pub struct Buffer {
syntax_tree: Mutex<Option<SyntaxTree>>, syntax_tree: Mutex<Option<SyntaxTree>>,
parsing_in_background: bool, parsing_in_background: bool,
parse_count: usize, parse_count: usize,
diagnostics: AnchorRangeMultimap<()>,
language_server: Option<LanguageServerState>,
#[cfg(test)] #[cfg(test)]
operations: Vec<Operation>, operations: Vec<Operation>,
} }
@ -69,6 +72,20 @@ pub struct Snapshot {
query_cursor: QueryCursorHandle, 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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Event { pub enum Event {
Edited, Edited,
@ -87,8 +104,14 @@ pub trait File {
fn mtime(&self) -> SystemTime; fn mtime(&self) -> SystemTime;
/// Returns the path of this file relative to the worktree's root directory.
fn path(&self) -> &Arc<Path>; 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; fn full_path(&self, cx: &AppContext) -> PathBuf;
/// Returns the last component of this handle's absolute path. If this handle refers to the root /// 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, None,
None,
cx, cx,
) )
} }
@ -182,12 +206,14 @@ impl Buffer {
history: History, history: History,
file: Option<Box<dyn File>>, file: Option<Box<dyn File>>,
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
language_server: Option<Arc<lsp::LanguageServer>>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
Self::build( Self::build(
TextBuffer::new(replica_id, cx.model_id() as u64, history), TextBuffer::new(replica_id, cx.model_id() as u64, history),
file, file,
language, language,
language_server,
cx, cx,
) )
} }
@ -203,6 +229,7 @@ impl Buffer {
TextBuffer::from_proto(replica_id, message)?, TextBuffer::from_proto(replica_id, message)?,
file, file,
language, language,
None,
cx, cx,
)) ))
} }
@ -211,6 +238,7 @@ impl Buffer {
buffer: TextBuffer, buffer: TextBuffer,
file: Option<Box<dyn File>>, file: Option<Box<dyn File>>,
language: Option<Arc<Language>>, language: Option<Arc<Language>>,
language_server: Option<Arc<lsp::LanguageServer>>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
let saved_mtime; let saved_mtime;
@ -231,12 +259,13 @@ impl Buffer {
sync_parse_timeout: Duration::from_millis(1), sync_parse_timeout: Duration::from_millis(1),
autoindent_requests: Default::default(), autoindent_requests: Default::default(),
pending_autoindent: Default::default(), pending_autoindent: Default::default(),
language, language: None,
diagnostics: Default::default(),
language_server: None,
#[cfg(test)] #[cfg(test)]
operations: Default::default(), operations: Default::default(),
}; };
result.reparse(cx); result.set_language(language, language_server, cx);
result 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 = 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.reparse(cx);
self.update_language_server(cx);
} }
pub fn did_save( pub fn did_save(
@ -486,6 +596,45 @@ impl Buffer {
cx.notify(); 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>) { fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
if let Some(indent_columns) = self.compute_autoindents() { if let Some(indent_columns) = self.compute_autoindents() {
let indent_columns = cx.background().spawn(indent_columns); let indent_columns = cx.background().spawn(indent_columns);
@ -811,17 +960,38 @@ impl Buffer {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Result<()> { ) -> Result<()> {
if let Some(start_version) = self.text.end_transaction_at(selection_set_ids, now) { 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 was_dirty = start_version != self.saved_version;
let edited = self.edits_since(start_version).next().is_some(); self.did_edit(start_version, was_dirty, cx);
if edited {
self.did_edit(was_dirty, cx);
self.reparse(cx);
}
} }
Ok(()) 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>) pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
where where
I: IntoIterator<Item = Range<S>>, I: IntoIterator<Item = Range<S>>,
@ -929,11 +1099,24 @@ impl Buffer {
self.send_operation(Operation::Edit(edit), cx); 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); cx.emit(Event::Edited);
if !was_dirty { if !was_dirty {
cx.emit(Event::Dirtied); cx.emit(Event::Dirtied);
} }
cx.notify();
} }
pub fn add_selection_set( pub fn add_selection_set(
@ -991,18 +1174,10 @@ impl Buffer {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Result<()> { ) -> Result<()> {
self.pending_autoindent.take(); self.pending_autoindent.take();
let was_dirty = self.is_dirty(); let was_dirty = self.is_dirty();
let old_version = self.version.clone(); let old_version = self.version.clone();
self.text.apply_ops(ops)?; self.text.apply_ops(ops)?;
self.did_edit(old_version, was_dirty, cx);
cx.notify();
if self.edits_since(old_version).next().is_some() {
self.did_edit(was_dirty, cx);
self.reparse(cx);
}
Ok(()) Ok(())
} }
@ -1031,11 +1206,7 @@ impl Buffer {
self.send_operation(operation, cx); self.send_operation(operation, cx);
} }
cx.notify(); self.did_edit(old_version, was_dirty, cx);
if self.edits_since(old_version).next().is_some() {
self.did_edit(was_dirty, cx);
self.reparse(cx);
}
} }
pub fn redo(&mut self, cx: &mut ModelContext<Self>) { pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
@ -1046,11 +1217,7 @@ impl Buffer {
self.send_operation(operation, cx); self.send_operation(operation, cx);
} }
cx.notify(); self.did_edit(old_version, was_dirty, cx);
if self.edits_since(old_version).next().is_some() {
self.did_edit(was_dirty, cx);
self.reparse(cx);
}
} }
} }
@ -1081,6 +1248,7 @@ impl Entity for Buffer {
} }
} }
// TODO: Do we need to clone a buffer?
impl Clone for Buffer { impl Clone for Buffer {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -1095,7 +1263,8 @@ impl Clone for Buffer {
parse_count: self.parse_count, parse_count: self.parse_count,
autoindent_requests: Default::default(), autoindent_requests: Default::default(),
pending_autoindent: Default::default(), pending_autoindent: Default::default(),
diagnostics: self.diagnostics.clone(),
language_server: None,
#[cfg(test)] #[cfg(test)]
operations: self.operations.clone(), 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) { async fn test_reparse(mut cx: gpui::TestAppContext) {
let buffer = cx.add_model(|cx| { let buffer = cx.add_model(|cx| {
let text = "fn a() {}".into(); 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 // Wait for the initial text to parse
@ -224,7 +224,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
" "
.unindent() .unindent()
.into(); .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); let buffer = buffer.read(cx);
assert_eq!( assert_eq!(
@ -254,7 +254,8 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
fn test_edit_with_autoindent(cx: &mut MutableAppContext) { fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
cx.add_model(|cx| { cx.add_model(|cx| {
let text = "fn a() {}".into(); 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); buffer.edit_with_autoindent([8..8], "\n\n", cx);
assert_eq!(buffer.text(), "fn a() {\n \n}"); 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) { fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
cx.add_model(|cx| { cx.add_model(|cx| {
let text = History::new("fn a() {}".into()); 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); let selection_set_id = buffer.add_selection_set(Vec::new(), cx);
buffer.start_transaction(Some(selection_set_id)).unwrap(); 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() .unindent()
.into(); .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, // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
// their indentation is not adjusted. // their indentation is not adjusted.
@ -383,7 +385,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
.unindent() .unindent()
.into(), .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); buffer.edit_with_autoindent([5..5], "\nb", cx);
assert_eq!( assert_eq!(

View file

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

View file

@ -40,7 +40,7 @@ use std::{
}; };
use sum_tree::Bias; use sum_tree::Bias;
use sum_tree::{Edit, SeekTarget, SumTree}; use sum_tree::{Edit, SeekTarget, SumTree};
use util::TryFutureExt; use util::{ResultExt, TryFutureExt};
lazy_static! { lazy_static! {
static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore"); 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( pub fn handle_add_peer(
&mut self, &mut self,
envelope: TypedEnvelope<proto::AddPeer>, envelope: TypedEnvelope<proto::AddPeer>,
@ -667,9 +674,10 @@ pub struct LocalWorktree {
share: Option<ShareState>, share: Option<ShareState>,
open_buffers: HashMap<usize, WeakModelHandle<Buffer>>, open_buffers: HashMap<usize, WeakModelHandle<Buffer>>,
shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>, shared_buffers: HashMap<PeerId, HashMap<u64, ModelHandle<Buffer>>>,
diagnostics: HashMap<PathBuf, Vec<lsp::Diagnostic>>,
peers: HashMap<PeerId, ReplicaId>, peers: HashMap<PeerId, ReplicaId>,
languages: Arc<LanguageRegistry>,
queued_operations: Vec<(u64, Operation)>, queued_operations: Vec<(u64, Operation)>,
languages: Arc<LanguageRegistry>,
rpc: Arc<Client>, rpc: Arc<Client>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
language_server: Option<Arc<LanguageServer>>, language_server: Option<Arc<LanguageServer>>,
@ -781,6 +789,7 @@ impl LocalWorktree {
poll_task: None, poll_task: None,
open_buffers: Default::default(), open_buffers: Default::default(),
shared_buffers: Default::default(), shared_buffers: Default::default(),
diagnostics: Default::default(),
queued_operations: Default::default(), queued_operations: Default::default(),
peers: Default::default(), peers: Default::default(),
languages, languages,
@ -828,7 +837,7 @@ impl LocalWorktree {
if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
handle.update(&mut cx, |this, cx| { handle.update(&mut cx, |this, cx| {
let this = this.as_local_mut().unwrap(); let this = this.as_local_mut().unwrap();
this.update_diagnostics(diagnostics, cx); this.update_diagnostics(diagnostics, cx).log_err();
}); });
} else { } else {
break; break;
@ -867,6 +876,7 @@ impl LocalWorktree {
}); });
let path = Arc::from(path); let path = Arc::from(path);
let language_server = self.language_server.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(existing_buffer) = existing_buffer { if let Some(existing_buffer) = existing_buffer {
Ok(existing_buffer) Ok(existing_buffer)
@ -887,6 +897,7 @@ impl LocalWorktree {
History::new(contents.into()), History::new(contents.into()),
Some(Box::new(file)), Some(Box::new(file)),
language, language,
language_server,
cx, cx,
) )
}); });
@ -1187,9 +1198,29 @@ impl LocalWorktree {
fn update_diagnostics( fn update_diagnostics(
&mut self, &mut self,
diagnostics: lsp::PublishDiagnosticsParams, params: lsp::PublishDiagnosticsParams,
cx: &mut ModelContext<Worktree>, 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 &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 { fn full_path(&self, cx: &AppContext) -> PathBuf {
let worktree = self.worktree.read(cx); let worktree = self.worktree.read(cx);
let mut full_path = PathBuf::new(); let mut full_path = PathBuf::new();

View file

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