mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
WIP
This commit is contained in:
parent
60abc5f090
commit
0674e76864
11 changed files with 341 additions and 111 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue