Merge branch 'main' into editor2-autocomplete
This commit is contained in:
commit
27cd7b204d
92 changed files with 3003 additions and 1855 deletions
|
@ -17,8 +17,9 @@ use crate::{
|
|||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
pub use clock::ReplicaId;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task};
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::Mutex;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
|
@ -45,23 +46,33 @@ pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *};
|
|||
use theme::SyntaxTheme;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use util::RandomCharIter;
|
||||
use util::{RangeExt, TryFutureExt as _};
|
||||
use util::RangeExt;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
|
||||
pub use lsp::DiagnosticSeverity;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
|
||||
}
|
||||
|
||||
pub struct Buffer {
|
||||
text: TextBuffer,
|
||||
diff_base: Option<String>,
|
||||
git_diff: git::diff::BufferDiff,
|
||||
file: Option<Arc<dyn File>>,
|
||||
saved_version: clock::Global,
|
||||
saved_version_fingerprint: RopeFingerprint,
|
||||
/// The mtime of the file when this buffer was last loaded from
|
||||
/// or saved to disk.
|
||||
saved_mtime: SystemTime,
|
||||
/// The version vector when this buffer was last loaded from
|
||||
/// or saved to disk.
|
||||
saved_version: clock::Global,
|
||||
/// A hash of the current contents of the buffer's file.
|
||||
file_fingerprint: RopeFingerprint,
|
||||
transaction_depth: usize,
|
||||
was_dirty_before_starting_transaction: Option<bool>,
|
||||
reload_task: Option<Task<Result<()>>>,
|
||||
language: Option<Arc<Language>>,
|
||||
autoindent_requests: Vec<Arc<AutoindentRequest>>,
|
||||
pending_autoindent: Option<Task<()>>,
|
||||
|
@ -421,8 +432,7 @@ impl Buffer {
|
|||
.ok_or_else(|| anyhow!("missing line_ending"))?,
|
||||
));
|
||||
this.saved_version = proto::deserialize_version(&message.saved_version);
|
||||
this.saved_version_fingerprint =
|
||||
proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
|
||||
this.file_fingerprint = proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
|
||||
this.saved_mtime = message
|
||||
.saved_mtime
|
||||
.ok_or_else(|| anyhow!("invalid saved_mtime"))?
|
||||
|
@ -438,7 +448,7 @@ impl Buffer {
|
|||
diff_base: self.diff_base.as_ref().map(|h| h.to_string()),
|
||||
line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
|
||||
saved_version: proto::serialize_version(&self.saved_version),
|
||||
saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint),
|
||||
saved_version_fingerprint: proto::serialize_fingerprint(self.file_fingerprint),
|
||||
saved_mtime: Some(self.saved_mtime.into()),
|
||||
}
|
||||
}
|
||||
|
@ -508,7 +518,8 @@ impl Buffer {
|
|||
Self {
|
||||
saved_mtime,
|
||||
saved_version: buffer.version(),
|
||||
saved_version_fingerprint: buffer.as_rope().fingerprint(),
|
||||
file_fingerprint: buffer.as_rope().fingerprint(),
|
||||
reload_task: None,
|
||||
transaction_depth: 0,
|
||||
was_dirty_before_starting_transaction: None,
|
||||
text: buffer,
|
||||
|
@ -574,7 +585,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
|
||||
self.saved_version_fingerprint
|
||||
self.file_fingerprint
|
||||
}
|
||||
|
||||
pub fn saved_mtime(&self) -> SystemTime {
|
||||
|
@ -602,43 +613,58 @@ impl Buffer {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.saved_version = version;
|
||||
self.saved_version_fingerprint = fingerprint;
|
||||
self.file_fingerprint = fingerprint;
|
||||
self.saved_mtime = mtime;
|
||||
cx.emit(Event::Saved);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<Option<Transaction>>> {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
|
||||
pub fn reload(
|
||||
&mut self,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> oneshot::Receiver<Option<Transaction>> {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let prev_version = self.text.version();
|
||||
self.reload_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
|
||||
let file = this.file.as_ref()?.as_local()?;
|
||||
Some((file.mtime(), file.load(cx)))
|
||||
})? {
|
||||
let new_text = new_text.await?;
|
||||
let diff = this
|
||||
.update(&mut cx, |this, cx| this.diff(new_text, cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.version() == diff.base_version {
|
||||
this.finalize_last_transaction();
|
||||
this.apply_diff(diff, cx);
|
||||
if let Some(transaction) = this.finalize_last_transaction().cloned() {
|
||||
this.did_reload(
|
||||
this.version(),
|
||||
this.as_rope().fingerprint(),
|
||||
this.line_ending(),
|
||||
new_mtime,
|
||||
cx,
|
||||
);
|
||||
return Some(transaction);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
})
|
||||
})?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let new_text = new_text.await?;
|
||||
let diff = this
|
||||
.update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.version() == diff.base_version {
|
||||
this.finalize_last_transaction();
|
||||
this.apply_diff(diff, cx);
|
||||
tx.send(this.finalize_last_transaction().cloned()).ok();
|
||||
|
||||
this.did_reload(
|
||||
this.version(),
|
||||
this.as_rope().fingerprint(),
|
||||
this.line_ending(),
|
||||
new_mtime,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
this.did_reload(
|
||||
prev_version,
|
||||
Rope::text_fingerprint(&new_text),
|
||||
this.line_ending(),
|
||||
this.saved_mtime,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
this.reload_task.take();
|
||||
})
|
||||
}));
|
||||
rx
|
||||
}
|
||||
|
||||
pub fn did_reload(
|
||||
|
@ -650,14 +676,14 @@ impl Buffer {
|
|||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.saved_version = version;
|
||||
self.saved_version_fingerprint = fingerprint;
|
||||
self.file_fingerprint = fingerprint;
|
||||
self.text.set_line_ending(line_ending);
|
||||
self.saved_mtime = mtime;
|
||||
if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
|
||||
file.buffer_reloaded(
|
||||
self.remote_id(),
|
||||
&self.saved_version,
|
||||
self.saved_version_fingerprint,
|
||||
self.file_fingerprint,
|
||||
self.line_ending(),
|
||||
self.saved_mtime,
|
||||
cx,
|
||||
|
@ -667,13 +693,8 @@ impl Buffer {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn file_updated(
|
||||
&mut self,
|
||||
new_file: Arc<dyn File>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<()> {
|
||||
pub fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) {
|
||||
let mut file_changed = false;
|
||||
let mut task = Task::ready(());
|
||||
|
||||
if let Some(old_file) = self.file.as_ref() {
|
||||
if new_file.path() != old_file.path() {
|
||||
|
@ -693,8 +714,7 @@ impl Buffer {
|
|||
file_changed = true;
|
||||
|
||||
if !self.is_dirty() {
|
||||
let reload = self.reload(cx).log_err().map(drop);
|
||||
task = cx.background_executor().spawn(reload);
|
||||
self.reload(cx).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -708,7 +728,6 @@ impl Buffer {
|
|||
cx.emit(Event::FileHandleChanged);
|
||||
cx.notify();
|
||||
}
|
||||
task
|
||||
}
|
||||
|
||||
pub fn diff_base(&self) -> Option<&str> {
|
||||
|
@ -1159,36 +1178,72 @@ impl Buffer {
|
|||
pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
|
||||
let old_text = self.as_rope().clone();
|
||||
let base_version = self.version();
|
||||
cx.background_executor().spawn(async move {
|
||||
let old_text = old_text.to_string();
|
||||
let line_ending = LineEnding::detect(&new_text);
|
||||
LineEnding::normalize(&mut new_text);
|
||||
let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
|
||||
let mut edits = Vec::new();
|
||||
let mut offset = 0;
|
||||
let empty: Arc<str> = "".into();
|
||||
for change in diff.iter_all_changes() {
|
||||
let value = change.value();
|
||||
let end_offset = offset + value.len();
|
||||
match change.tag() {
|
||||
ChangeTag::Equal => {
|
||||
offset = end_offset;
|
||||
cx.background_executor()
|
||||
.spawn_labeled(*BUFFER_DIFF_TASK, async move {
|
||||
let old_text = old_text.to_string();
|
||||
let line_ending = LineEnding::detect(&new_text);
|
||||
LineEnding::normalize(&mut new_text);
|
||||
|
||||
let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
|
||||
let empty: Arc<str> = "".into();
|
||||
|
||||
let mut edits = Vec::new();
|
||||
let mut old_offset = 0;
|
||||
let mut new_offset = 0;
|
||||
let mut last_edit: Option<(Range<usize>, Range<usize>)> = None;
|
||||
for change in diff.iter_all_changes().map(Some).chain([None]) {
|
||||
if let Some(change) = &change {
|
||||
let len = change.value().len();
|
||||
match change.tag() {
|
||||
ChangeTag::Equal => {
|
||||
old_offset += len;
|
||||
new_offset += len;
|
||||
}
|
||||
ChangeTag::Delete => {
|
||||
let old_end_offset = old_offset + len;
|
||||
if let Some((last_old_range, _)) = &mut last_edit {
|
||||
last_old_range.end = old_end_offset;
|
||||
} else {
|
||||
last_edit =
|
||||
Some((old_offset..old_end_offset, new_offset..new_offset));
|
||||
}
|
||||
old_offset = old_end_offset;
|
||||
}
|
||||
ChangeTag::Insert => {
|
||||
let new_end_offset = new_offset + len;
|
||||
if let Some((_, last_new_range)) = &mut last_edit {
|
||||
last_new_range.end = new_end_offset;
|
||||
} else {
|
||||
last_edit =
|
||||
Some((old_offset..old_offset, new_offset..new_end_offset));
|
||||
}
|
||||
new_offset = new_end_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
ChangeTag::Delete => {
|
||||
edits.push((offset..end_offset, empty.clone()));
|
||||
offset = end_offset;
|
||||
}
|
||||
ChangeTag::Insert => {
|
||||
edits.push((offset..offset, value.into()));
|
||||
|
||||
if let Some((old_range, new_range)) = &last_edit {
|
||||
if old_offset > old_range.end
|
||||
|| new_offset > new_range.end
|
||||
|| change.is_none()
|
||||
{
|
||||
let text = if new_range.is_empty() {
|
||||
empty.clone()
|
||||
} else {
|
||||
new_text[new_range.clone()].into()
|
||||
};
|
||||
edits.push((old_range.clone(), text));
|
||||
last_edit.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Diff {
|
||||
base_version,
|
||||
line_ending,
|
||||
edits,
|
||||
}
|
||||
})
|
||||
|
||||
Diff {
|
||||
base_version,
|
||||
line_ending,
|
||||
edits,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a background task that searches the buffer for any whitespace
|
||||
|
@ -1272,12 +1327,12 @@ impl Buffer {
|
|||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.saved_version_fingerprint != self.as_rope().fingerprint()
|
||||
self.file_fingerprint != self.as_rope().fingerprint()
|
||||
|| self.file.as_ref().map_or(false, |file| file.is_deleted())
|
||||
}
|
||||
|
||||
pub fn has_conflict(&self) -> bool {
|
||||
self.saved_version_fingerprint != self.as_rope().fingerprint()
|
||||
self.file_fingerprint != self.as_rope().fingerprint()
|
||||
&& self
|
||||
.file
|
||||
.as_ref()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue