Detailed changes
@@ -1624,6 +1624,7 @@ dependencies = [
"buffer",
"clock",
"gpui",
+ "language",
"lazy_static",
"log",
"parking_lot",
@@ -2816,6 +2817,32 @@ dependencies = [
"log",
]
+[[package]]
+name = "language"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "arrayvec 0.7.1",
+ "buffer",
+ "clock",
+ "gpui",
+ "lazy_static",
+ "log",
+ "parking_lot",
+ "rand 0.8.3",
+ "rpc",
+ "seahash",
+ "serde 1.0.125",
+ "similar",
+ "smallvec",
+ "smol",
+ "sum_tree",
+ "theme",
+ "tree-sitter",
+ "tree-sitter-rust",
+ "unindent",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -3760,6 +3787,7 @@ dependencies = [
"fuzzy",
"gpui",
"ignore",
+ "language",
"lazy_static",
"libc",
"log",
@@ -6106,6 +6134,7 @@ dependencies = [
"client",
"editor",
"gpui",
+ "language",
"log",
"postage",
"project",
@@ -6176,6 +6205,7 @@ dependencies = [
"ignore",
"image 0.23.14",
"indexmap",
+ "language",
"lazy_static",
"libc",
"log",
@@ -1,6 +1,6 @@
use crate::Point;
-use super::{Buffer, Content};
+use super::{Content, TextBuffer};
use anyhow::Result;
use std::{cmp::Ordering, ops::Range};
use sum_tree::Bias;
@@ -65,7 +65,7 @@ impl Anchor {
Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias)))
}
- pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
+ pub fn bias_left(&self, buffer: &TextBuffer) -> Anchor {
if self.bias == Bias::Left {
self.clone()
} else {
@@ -73,7 +73,7 @@ impl Anchor {
}
}
- pub fn bias_right(&self, buffer: &Buffer) -> Anchor {
+ pub fn bias_right(&self, buffer: &TextBuffer) -> Anchor {
if self.bias == Bias::Right {
self.clone()
} else {
@@ -1,6 +1,4 @@
mod anchor;
-mod highlight_map;
-mod language;
mod operation_queue;
mod point;
#[cfg(any(test, feature = "test-support"))]
@@ -13,13 +11,7 @@ mod tests;
pub use anchor::*;
use anyhow::{anyhow, Result};
use clock::ReplicaId;
-use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
-pub use highlight_map::{HighlightId, HighlightMap};
-use language::Tree;
-pub use language::{BracketPair, Language, LanguageConfig, LanguageRegistry};
-use lazy_static::lazy_static;
use operation_queue::OperationQueue;
-use parking_lot::Mutex;
pub use point::*;
#[cfg(any(test, feature = "test-support"))]
pub use random_char_iter::*;
@@ -27,66 +19,17 @@ pub use rope::{Chunks, Rope, TextSummary};
use rpc::proto;
use seahash::SeaHasher;
pub use selection::*;
-use similar::{ChangeTag, TextDiff};
-use smol::future::yield_now;
use std::{
- any::Any,
- cell::RefCell,
cmp,
- collections::BTreeMap,
convert::{TryFrom, TryInto},
- ffi::OsString,
- future::Future,
hash::BuildHasher,
iter::Iterator,
- ops::{Deref, DerefMut, Range},
- path::{Path, PathBuf},
+ ops::Range,
str,
sync::Arc,
- time::{Duration, Instant, SystemTime, UNIX_EPOCH},
+ time::{Duration, Instant},
};
use sum_tree::{Bias, FilterCursor, SumTree};
-use tree_sitter::{InputEdit, Parser, QueryCursor};
-
-pub trait File {
- fn worktree_id(&self) -> usize;
-
- fn entry_id(&self) -> Option<usize>;
-
- fn set_entry_id(&mut self, entry_id: Option<usize>);
-
- fn mtime(&self) -> SystemTime;
-
- fn set_mtime(&mut self, mtime: SystemTime);
-
- fn path(&self) -> &Arc<Path>;
-
- fn set_path(&mut self, path: Arc<Path>);
-
- fn full_path(&self, cx: &AppContext) -> PathBuf;
-
- /// Returns the last component of this handle's absolute path. If this handle refers to the root
- /// of its worktree, then this method will return the name of the worktree itself.
- fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString>;
-
- fn is_deleted(&self) -> bool;
-
- fn save(
- &self,
- buffer_id: u64,
- text: Rope,
- version: clock::Global,
- cx: &mut MutableAppContext,
- ) -> Task<Result<(clock::Global, SystemTime)>>;
-
- fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
-
- fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
-
- fn boxed_clone(&self) -> Box<dyn File>;
-
- fn as_any(&self) -> &dyn Any;
-}
#[derive(Clone, Default)]
struct DeterministicState;
@@ -111,53 +54,6 @@ type HashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(any(test, feature = "test-support")))]
type HashSet<T> = std::collections::HashSet<T>;
-thread_local! {
- static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
-}
-
-lazy_static! {
- static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
-}
-
-// TODO - Make this configurable
-const INDENT_SIZE: u32 = 4;
-
-struct QueryCursorHandle(Option<QueryCursor>);
-
-impl QueryCursorHandle {
- fn new() -> Self {
- QueryCursorHandle(Some(
- QUERY_CURSORS
- .lock()
- .pop()
- .unwrap_or_else(|| QueryCursor::new()),
- ))
- }
-}
-
-impl Deref for QueryCursorHandle {
- type Target = QueryCursor;
-
- fn deref(&self) -> &Self::Target {
- self.0.as_ref().unwrap()
- }
-}
-
-impl DerefMut for QueryCursorHandle {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.0.as_mut().unwrap()
- }
-}
-
-impl Drop for QueryCursorHandle {
- fn drop(&mut self) {
- let mut cursor = self.0.take().unwrap();
- cursor.set_byte_range(0..usize::MAX);
- cursor.set_point_range(Point::zero().into()..Point::MAX.into());
- QUERY_CURSORS.lock().push(cursor)
- }
-}
-
#[derive(Clone)]
pub struct TextBuffer {
fragments: SumTree<Fragment>,
@@ -176,42 +72,12 @@ pub struct TextBuffer {
lamport_clock: clock::Lamport,
}
-pub struct Buffer {
- buffer: TextBuffer,
- file: Option<Box<dyn File>>,
- saved_version: clock::Global,
- saved_mtime: SystemTime,
- language: Option<Arc<Language>>,
- autoindent_requests: Vec<Arc<AutoindentRequest>>,
- pending_autoindent: Option<Task<()>>,
- sync_parse_timeout: Duration,
- syntax_tree: Mutex<Option<SyntaxTree>>,
- parsing_in_background: bool,
- parse_count: usize,
- #[cfg(test)]
- operations: Vec<Operation>,
-}
-
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SelectionSet {
pub selections: Arc<[Selection]>,
pub active: bool,
}
-#[derive(Clone)]
-struct SyntaxTree {
- tree: Tree,
- version: clock::Global,
-}
-
-#[derive(Clone)]
-struct AutoindentRequest {
- selection_set_ids: HashSet<SelectionSetId>,
- before_edit: Snapshot,
- edited: AnchorSet,
- inserted: Option<AnchorRangeSet>,
-}
-
#[derive(Clone, Debug)]
pub struct Transaction {
start: clock::Global,
@@ -485,12 +351,6 @@ impl Edit {
}
}
-struct Diff {
- base_version: clock::Global,
- new_text: Arc<str>,
- changes: Vec<(ChangeTag, usize)>,
-}
-
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
struct InsertionTimestamp {
replica_id: ReplicaId,
@@ -580,14 +440,6 @@ pub struct UndoOperation {
version: clock::Global,
}
-impl Deref for Buffer {
- type Target = TextBuffer;
-
- fn deref(&self) -> &Self::Target {
- &self.buffer
- }
-}
-
impl TextBuffer {
pub fn new(replica_id: u16, remote_id: u64, history: History) -> TextBuffer {
let mut fragments = SumTree::new();
@@ -624,11 +476,69 @@ impl TextBuffer {
}
}
+ pub fn from_proto(replica_id: u16, message: proto::Buffer) -> Result<Self> {
+ let mut buffer =
+ TextBuffer::new(replica_id, message.id, History::new(message.content.into()));
+ let ops = message
+ .history
+ .into_iter()
+ .map(|op| Operation::Edit(op.into()));
+ buffer.apply_ops(ops)?;
+ buffer.selections = message
+ .selections
+ .into_iter()
+ .map(|set| {
+ let set_id = clock::Lamport {
+ replica_id: set.replica_id as ReplicaId,
+ value: set.local_timestamp,
+ };
+ let selections: Vec<Selection> = set
+ .selections
+ .into_iter()
+ .map(TryFrom::try_from)
+ .collect::<Result<_, _>>()?;
+ let set = SelectionSet {
+ selections: Arc::from(selections),
+ active: set.is_active,
+ };
+ Result::<_, anyhow::Error>::Ok((set_id, set))
+ })
+ .collect::<Result<_, _>>()?;
+ Ok(buffer)
+ }
+
+ pub fn to_proto(&self) -> proto::Buffer {
+ let ops = self.history.ops.values().map(Into::into).collect();
+ proto::Buffer {
+ id: self.remote_id,
+ content: self.history.base_text.to_string(),
+ history: ops,
+ selections: self
+ .selections
+ .iter()
+ .map(|(set_id, set)| proto::SelectionSetSnapshot {
+ replica_id: set_id.replica_id as u32,
+ local_timestamp: set_id.value,
+ selections: set.selections.iter().map(Into::into).collect(),
+ is_active: set.active,
+ })
+ .collect(),
+ }
+ }
+
pub fn version(&self) -> clock::Global {
self.version.clone()
}
- fn content<'a>(&'a self) -> Content<'a> {
+ pub fn snapshot(&self) -> Snapshot {
+ Snapshot {
+ visible_text: self.visible_text.clone(),
+ fragments: self.fragments.clone(),
+ version: self.version.clone(),
+ }
+ }
+
+ pub fn content<'a>(&'a self) -> Content<'a> {
self.into()
}
@@ -1250,11 +1160,10 @@ impl TextBuffer {
&mut self,
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
) -> Result<()> {
- self.start_transaction_at(selection_set_ids, Instant::now())?;
- Ok(())
+ self.start_transaction_at(selection_set_ids, Instant::now())
}
- fn start_transaction_at(
+ pub fn start_transaction_at(
&mut self,
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
now: Instant,
@@ -1274,11 +1183,11 @@ impl TextBuffer {
Ok(())
}
- fn end_transaction(&mut self, selection_set_ids: impl IntoIterator<Item = SelectionSetId>) {
+ pub fn end_transaction(&mut self, selection_set_ids: impl IntoIterator<Item = SelectionSetId>) {
self.end_transaction_at(selection_set_ids, Instant::now());
}
- fn end_transaction_at(
+ pub fn end_transaction_at(
&mut self,
selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
now: Instant,
@@ -1303,12 +1212,12 @@ impl TextBuffer {
}
}
- fn remove_peer(&mut self, replica_id: ReplicaId) {
+ pub fn remove_peer(&mut self, replica_id: ReplicaId) {
self.selections
.retain(|set_id, _| set_id.replica_id != replica_id)
}
- fn undo(&mut self) -> Vec<Operation> {
+ pub fn undo(&mut self) -> Vec<Operation> {
let mut ops = Vec::new();
if let Some(transaction) = self.history.pop_undo().cloned() {
let selections = transaction.selections_before.clone();
@@ -1320,7 +1229,7 @@ impl TextBuffer {
ops
}
- fn redo(&mut self) -> Vec<Operation> {
+ pub fn redo(&mut self) -> Vec<Operation> {
let mut ops = Vec::new();
if let Some(transaction) = self.history.pop_redo().cloned() {
let selections = transaction.selections_after.clone();
@@ -1458,1078 +1367,107 @@ impl TextBuffer {
}
}
-impl Buffer {
- pub fn new<T: Into<Arc<str>>>(
- replica_id: ReplicaId,
- base_text: T,
- cx: &mut ModelContext<Self>,
- ) -> Self {
- Self::build(
- replica_id,
- History::new(base_text.into()),
- None,
- cx.model_id() as u64,
- None,
- cx,
- )
+#[cfg(any(test, feature = "test-support"))]
+impl TextBuffer {
+ fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
+ let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
+ let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
+ start..end
}
- pub fn from_history(
- replica_id: ReplicaId,
- history: History,
- file: Option<Box<dyn File>>,
- language: Option<Arc<Language>>,
- cx: &mut ModelContext<Self>,
- ) -> Self {
- Self::build(
- replica_id,
- history,
- file,
- cx.model_id() as u64,
- language,
- cx,
- )
+ pub fn randomly_edit<T>(
+ &mut self,
+ rng: &mut T,
+ old_range_count: usize,
+ ) -> (Vec<Range<usize>>, String, Operation)
+ where
+ T: rand::Rng,
+ {
+ let mut old_ranges: Vec<Range<usize>> = Vec::new();
+ for _ in 0..old_range_count {
+ let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1);
+ if last_end > self.len() {
+ break;
+ }
+ old_ranges.push(self.random_byte_range(last_end, rng));
+ }
+ let new_text_len = rng.gen_range(0..10);
+ let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
+ .take(new_text_len)
+ .collect();
+ log::info!(
+ "mutating buffer {} at {:?}: {:?}",
+ self.replica_id,
+ old_ranges,
+ new_text
+ );
+ let op = self.edit(old_ranges.iter().cloned(), new_text.as_str());
+ (old_ranges, new_text, Operation::Edit(op))
}
- fn build(
- replica_id: ReplicaId,
- history: History,
- file: Option<Box<dyn File>>,
- remote_id: u64,
- language: Option<Arc<Language>>,
- cx: &mut ModelContext<Self>,
- ) -> Self {
- let saved_mtime;
- if let Some(file) = file.as_ref() {
- saved_mtime = file.mtime();
- } else {
- saved_mtime = UNIX_EPOCH;
- }
+ pub fn randomly_mutate<T>(&mut self, rng: &mut T) -> Vec<Operation>
+ where
+ T: rand::Rng,
+ {
+ use rand::prelude::*;
- let mut result = Self {
- buffer: TextBuffer::new(replica_id, remote_id, history),
- saved_mtime,
- saved_version: clock::Global::new(),
- file,
- syntax_tree: Mutex::new(None),
- parsing_in_background: false,
- parse_count: 0,
- sync_parse_timeout: Duration::from_millis(1),
- autoindent_requests: Default::default(),
- pending_autoindent: Default::default(),
- language,
+ let mut ops = vec![self.randomly_edit(rng, 5).2];
- #[cfg(test)]
- operations: Default::default(),
- };
- result.reparse(cx);
- result
- }
+ // Randomly add, remove or mutate selection sets.
+ let replica_selection_sets = &self
+ .selection_sets()
+ .map(|(set_id, _)| *set_id)
+ .filter(|set_id| self.replica_id == set_id.replica_id)
+ .collect::<Vec<_>>();
+ let set_id = replica_selection_sets.choose(rng);
+ if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
+ ops.push(self.remove_selection_set(*set_id.unwrap()).unwrap());
+ } else {
+ let mut ranges = Vec::new();
+ for _ in 0..5 {
+ ranges.push(self.random_byte_range(0, rng));
+ }
+ let new_selections = self.selections_from_ranges(ranges).unwrap();
- pub fn snapshot(&self) -> Snapshot {
- Snapshot {
- visible_text: self.visible_text.clone(),
- fragments: self.fragments.clone(),
- version: self.version.clone(),
- tree: self.syntax_tree(),
- is_parsing: self.parsing_in_background,
- language: self.language.clone(),
- query_cursor: QueryCursorHandle::new(),
+ let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) {
+ self.add_selection_set(new_selections)
+ } else {
+ self.update_selection_set(*set_id.unwrap(), new_selections)
+ .unwrap()
+ };
+ ops.push(op);
}
- }
- pub fn from_proto(
- replica_id: ReplicaId,
- message: proto::Buffer,
- file: Option<Box<dyn File>>,
- language: Option<Arc<Language>>,
- cx: &mut ModelContext<Self>,
- ) -> Result<Self> {
- let mut buffer = Buffer::build(
- replica_id,
- History::new(message.content.into()),
- file,
- message.id,
- language,
- cx,
- );
- let ops = message
- .history
- .into_iter()
- .map(|op| Operation::Edit(op.into()));
- buffer.apply_ops(ops, cx)?;
- buffer.buffer.selections = message
- .selections
- .into_iter()
- .map(|set| {
- let set_id = clock::Lamport {
- replica_id: set.replica_id as ReplicaId,
- value: set.local_timestamp,
- };
- let selections: Vec<Selection> = set
- .selections
- .into_iter()
- .map(TryFrom::try_from)
- .collect::<Result<_, _>>()?;
- let set = SelectionSet {
- selections: Arc::from(selections),
- active: set.is_active,
- };
- Result::<_, anyhow::Error>::Ok((set_id, set))
- })
- .collect::<Result<_, _>>()?;
- Ok(buffer)
+ ops
}
- pub fn to_proto(&self, cx: &mut ModelContext<Self>) -> proto::Buffer {
- let ops = self.history.ops.values().map(Into::into).collect();
- proto::Buffer {
- id: cx.model_id() as u64,
- content: self.history.base_text.to_string(),
- history: ops,
- selections: self
- .selections
- .iter()
- .map(|(set_id, set)| proto::SelectionSetSnapshot {
- replica_id: set_id.replica_id as u32,
- local_timestamp: set_id.value,
- selections: set.selections.iter().map(Into::into).collect(),
- is_active: set.active,
- })
- .collect(),
- }
- }
+ pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
+ use rand::prelude::*;
- pub fn file(&self) -> Option<&dyn File> {
- self.file.as_deref()
+ let mut ops = Vec::new();
+ for _ in 0..rng.gen_range(1..=5) {
+ if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() {
+ log::info!(
+ "undoing buffer {} transaction {:?}",
+ self.replica_id,
+ transaction
+ );
+ ops.push(self.undo_or_redo(transaction).unwrap());
+ }
+ }
+ ops
}
- pub fn file_mut(&mut self) -> Option<&mut dyn File> {
- self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File)
- }
+ fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
+ where
+ I: IntoIterator<Item = Range<usize>>,
+ {
+ use std::sync::atomic::{self, AtomicUsize};
- pub fn save(
- &mut self,
- cx: &mut ModelContext<Self>,
- ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
- let file = self
- .file
- .as_ref()
- .ok_or_else(|| anyhow!("buffer has no file"))?;
- let text = self.visible_text.clone();
- let version = self.version.clone();
- let save = file.save(self.remote_id, text, version, cx.as_mut());
- Ok(cx.spawn(|this, mut cx| async move {
- let (version, mtime) = save.await?;
- this.update(&mut cx, |this, cx| {
- this.did_save(version.clone(), mtime, None, cx);
- });
- Ok((version, mtime))
- }))
- }
+ static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
- pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
- self.language = language;
- self.reparse(cx);
- }
-
- pub fn did_save(
- &mut self,
- version: clock::Global,
- mtime: SystemTime,
- new_file: Option<Box<dyn File>>,
- cx: &mut ModelContext<Self>,
- ) {
- self.saved_mtime = mtime;
- self.saved_version = version;
- if let Some(new_file) = new_file {
- self.file = Some(new_file);
- }
- cx.emit(Event::Saved);
- }
-
- pub fn file_updated(
- &mut self,
- path: Arc<Path>,
- mtime: SystemTime,
- new_text: Option<String>,
- cx: &mut ModelContext<Self>,
- ) {
- let file = self.file.as_mut().unwrap();
- let mut changed = false;
- if path != *file.path() {
- file.set_path(path);
- changed = true;
- }
-
- if mtime != file.mtime() {
- file.set_mtime(mtime);
- changed = true;
- if let Some(new_text) = new_text {
- if self.version == self.saved_version {
- cx.spawn(|this, mut cx| async move {
- let diff = this
- .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
- .await;
- this.update(&mut cx, |this, cx| {
- if this.apply_diff(diff, cx) {
- this.saved_version = this.version.clone();
- this.saved_mtime = mtime;
- cx.emit(Event::Reloaded);
- }
- });
- })
- .detach();
- }
- }
- }
-
- if changed {
- cx.emit(Event::FileHandleChanged);
- }
- }
-
- pub fn file_deleted(&mut self, cx: &mut ModelContext<Self>) {
- if self.version == self.saved_version {
- cx.emit(Event::Dirtied);
- }
- cx.emit(Event::FileHandleChanged);
- }
-
- pub fn close(&mut self, cx: &mut ModelContext<Self>) {
- cx.emit(Event::Closed);
- }
-
- pub fn language(&self) -> Option<&Arc<Language>> {
- self.language.as_ref()
- }
-
- pub fn parse_count(&self) -> usize {
- self.parse_count
- }
-
- fn syntax_tree(&self) -> Option<Tree> {
- if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() {
- self.interpolate_tree(syntax_tree);
- Some(syntax_tree.tree.clone())
- } else {
- None
- }
- }
-
- #[cfg(any(test, feature = "test-support"))]
- pub fn is_parsing(&self) -> bool {
- self.parsing_in_background
- }
-
- #[cfg(test)]
- pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
- self.sync_parse_timeout = timeout;
- }
-
- fn reparse(&mut self, cx: &mut ModelContext<Self>) -> bool {
- if self.parsing_in_background {
- return false;
- }
-
- if let Some(language) = self.language.clone() {
- let old_tree = self.syntax_tree();
- let text = self.visible_text.clone();
- let parsed_version = self.version();
- let parse_task = cx.background().spawn({
- let language = language.clone();
- async move { Self::parse_text(&text, old_tree, &language) }
- });
-
- match cx
- .background()
- .block_with_timeout(self.sync_parse_timeout, parse_task)
- {
- Ok(new_tree) => {
- self.did_finish_parsing(new_tree, parsed_version, cx);
- return true;
- }
- Err(parse_task) => {
- self.parsing_in_background = true;
- cx.spawn(move |this, mut cx| async move {
- let new_tree = parse_task.await;
- this.update(&mut cx, move |this, cx| {
- let language_changed =
- this.language.as_ref().map_or(true, |curr_language| {
- !Arc::ptr_eq(curr_language, &language)
- });
- let parse_again = this.version > parsed_version || language_changed;
- this.parsing_in_background = false;
- this.did_finish_parsing(new_tree, parsed_version, cx);
-
- if parse_again && this.reparse(cx) {
- return;
- }
- });
- })
- .detach();
- }
- }
- }
- false
- }
-
- fn parse_text(text: &Rope, old_tree: Option<Tree>, language: &Language) -> Tree {
- PARSER.with(|parser| {
- let mut parser = parser.borrow_mut();
- parser
- .set_language(language.grammar)
- .expect("incompatible grammar");
- let mut chunks = text.chunks_in_range(0..text.len());
- let tree = parser
- .parse_with(
- &mut move |offset, _| {
- chunks.seek(offset);
- chunks.next().unwrap_or("").as_bytes()
- },
- old_tree.as_ref(),
- )
- .unwrap();
- tree
- })
- }
-
- fn interpolate_tree(&self, tree: &mut SyntaxTree) {
- let mut delta = 0_isize;
- for edit in self.edits_since(tree.version.clone()) {
- let start_offset = (edit.old_bytes.start as isize + delta) as usize;
- let start_point = self.visible_text.to_point(start_offset);
- tree.tree.edit(&InputEdit {
- start_byte: start_offset,
- old_end_byte: start_offset + edit.deleted_bytes(),
- new_end_byte: start_offset + edit.inserted_bytes(),
- start_position: start_point.into(),
- old_end_position: (start_point + edit.deleted_lines()).into(),
- new_end_position: self
- .visible_text
- .to_point(start_offset + edit.inserted_bytes())
- .into(),
- });
- delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize;
- }
- tree.version = self.version();
- }
-
- fn did_finish_parsing(
- &mut self,
- tree: Tree,
- version: clock::Global,
- cx: &mut ModelContext<Self>,
- ) {
- self.parse_count += 1;
- *self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
- self.request_autoindent(cx);
- cx.emit(Event::Reparsed);
- cx.notify();
- }
-
- 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);
- match cx
- .background()
- .block_with_timeout(Duration::from_micros(500), indent_columns)
- {
- Ok(indent_columns) => self.apply_autoindents(indent_columns, cx),
- Err(indent_columns) => {
- self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
- let indent_columns = indent_columns.await;
- this.update(&mut cx, |this, cx| {
- this.apply_autoindents(indent_columns, cx);
- });
- }));
- }
- }
- }
- }
-
- fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, u32>>> {
- let max_rows_between_yields = 100;
- let snapshot = self.snapshot();
- if snapshot.language.is_none()
- || snapshot.tree.is_none()
- || self.autoindent_requests.is_empty()
- {
- return None;
- }
-
- let autoindent_requests = self.autoindent_requests.clone();
- Some(async move {
- let mut indent_columns = BTreeMap::new();
- for request in autoindent_requests {
- let old_to_new_rows = request
- .edited
- .to_points(&request.before_edit)
- .map(|point| point.row)
- .zip(request.edited.to_points(&snapshot).map(|point| point.row))
- .collect::<BTreeMap<u32, u32>>();
-
- let mut old_suggestions = HashMap::default();
- let old_edited_ranges =
- contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
- for old_edited_range in old_edited_ranges {
- let suggestions = request
- .before_edit
- .suggest_autoindents(old_edited_range.clone())
- .into_iter()
- .flatten();
- for (old_row, suggestion) in old_edited_range.zip(suggestions) {
- let indentation_basis = old_to_new_rows
- .get(&suggestion.basis_row)
- .and_then(|from_row| old_suggestions.get(from_row).copied())
- .unwrap_or_else(|| {
- request
- .before_edit
- .indent_column_for_line(suggestion.basis_row)
- });
- let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
- old_suggestions.insert(
- *old_to_new_rows.get(&old_row).unwrap(),
- indentation_basis + delta,
- );
- }
- yield_now().await;
- }
-
- // At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
- // buffer before the edit, but keyed by the row for these lines after the edits were applied.
- let new_edited_row_ranges =
- contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields);
- for new_edited_row_range in new_edited_row_ranges {
- let suggestions = snapshot
- .suggest_autoindents(new_edited_row_range.clone())
- .into_iter()
- .flatten();
- for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
- let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
- let new_indentation = indent_columns
- .get(&suggestion.basis_row)
- .copied()
- .unwrap_or_else(|| {
- snapshot.indent_column_for_line(suggestion.basis_row)
- })
- + delta;
- if old_suggestions
- .get(&new_row)
- .map_or(true, |old_indentation| new_indentation != *old_indentation)
- {
- indent_columns.insert(new_row, new_indentation);
- }
- }
- yield_now().await;
- }
-
- if let Some(inserted) = request.inserted.as_ref() {
- let inserted_row_ranges = contiguous_ranges(
- inserted
- .to_point_ranges(&snapshot)
- .flat_map(|range| range.start.row..range.end.row + 1),
- max_rows_between_yields,
- );
- for inserted_row_range in inserted_row_ranges {
- let suggestions = snapshot
- .suggest_autoindents(inserted_row_range.clone())
- .into_iter()
- .flatten();
- for (row, suggestion) in inserted_row_range.zip(suggestions) {
- let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
- let new_indentation = indent_columns
- .get(&suggestion.basis_row)
- .copied()
- .unwrap_or_else(|| {
- snapshot.indent_column_for_line(suggestion.basis_row)
- })
- + delta;
- indent_columns.insert(row, new_indentation);
- }
- yield_now().await;
- }
- }
- }
- indent_columns
- })
- }
-
- fn apply_autoindents(
- &mut self,
- indent_columns: BTreeMap<u32, u32>,
- cx: &mut ModelContext<Self>,
- ) {
- let selection_set_ids = self
- .autoindent_requests
- .drain(..)
- .flat_map(|req| req.selection_set_ids.clone())
- .collect::<HashSet<_>>();
-
- self.start_transaction(selection_set_ids.iter().copied())
- .unwrap();
- for (row, indent_column) in &indent_columns {
- self.set_indent_column_for_line(*row, *indent_column, cx);
- }
-
- for selection_set_id in &selection_set_ids {
- if let Some(set) = self.selections.get(selection_set_id) {
- let new_selections = set
- .selections
- .iter()
- .map(|selection| {
- let start_point = selection.start.to_point(&self.buffer);
- if start_point.column == 0 {
- let end_point = selection.end.to_point(&self.buffer);
- let delta = Point::new(
- 0,
- indent_columns.get(&start_point.row).copied().unwrap_or(0),
- );
- if delta.column > 0 {
- return Selection {
- id: selection.id,
- goal: selection.goal,
- reversed: selection.reversed,
- start: self
- .anchor_at(start_point + delta, selection.start.bias),
- end: self.anchor_at(end_point + delta, selection.end.bias),
- };
- }
- }
- selection.clone()
- })
- .collect::<Arc<[_]>>();
- self.update_selection_set(*selection_set_id, new_selections, cx)
- .unwrap();
- }
- }
-
- self.end_transaction(selection_set_ids.iter().copied(), cx)
- .unwrap();
- }
-
- pub fn indent_column_for_line(&self, row: u32) -> u32 {
- self.content().indent_column_for_line(row)
- }
-
- fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
- let current_column = self.indent_column_for_line(row);
- if column > current_column {
- let offset = self.visible_text.to_offset(Point::new(row, 0));
- self.edit(
- [offset..offset],
- " ".repeat((column - current_column) as usize),
- cx,
- );
- } else if column < current_column {
- self.edit(
- [Point::new(row, 0)..Point::new(row, current_column - column)],
- "",
- cx,
- );
- }
- }
-
- pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
- if let Some(tree) = self.syntax_tree() {
- let root = tree.root_node();
- let range = range.start.to_offset(self)..range.end.to_offset(self);
- let mut node = root.descendant_for_byte_range(range.start, range.end);
- while node.map_or(false, |n| n.byte_range() == range) {
- node = node.unwrap().parent();
- }
- node.map(|n| n.byte_range())
- } else {
- None
- }
- }
-
- pub fn enclosing_bracket_ranges<T: ToOffset>(
- &self,
- range: Range<T>,
- ) -> Option<(Range<usize>, Range<usize>)> {
- let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?;
- let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?;
- let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?;
-
- // Find bracket pairs that *inclusively* contain the given range.
- let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
- let mut cursor = QueryCursorHandle::new();
- let matches = cursor.set_byte_range(range).matches(
- &lang.brackets_query,
- tree.root_node(),
- TextProvider(&self.visible_text),
- );
-
- // Get the ranges of the innermost pair of brackets.
- matches
- .filter_map(|mat| {
- let open = mat.nodes_for_capture_index(open_capture_ix).next()?;
- let close = mat.nodes_for_capture_index(close_capture_ix).next()?;
- Some((open.byte_range(), close.byte_range()))
- })
- .min_by_key(|(open_range, close_range)| close_range.end - open_range.start)
- }
-
- fn diff(&self, new_text: Arc<str>, cx: &AppContext) -> Task<Diff> {
- // TODO: it would be nice to not allocate here.
- let old_text = self.text();
- let base_version = self.version();
- cx.background().spawn(async move {
- let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
- .iter_all_changes()
- .map(|c| (c.tag(), c.value().len()))
- .collect::<Vec<_>>();
- Diff {
- base_version,
- new_text,
- changes,
- }
- })
- }
-
- pub fn set_text_from_disk(&self, new_text: Arc<str>, cx: &mut ModelContext<Self>) -> Task<()> {
- cx.spawn(|this, mut cx| async move {
- let diff = this
- .read_with(&cx, |this, cx| this.diff(new_text, cx))
- .await;
-
- this.update(&mut cx, |this, cx| {
- if this.apply_diff(diff, cx) {
- this.saved_version = this.version.clone();
- }
- });
- })
- }
-
- fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
- if self.version == diff.base_version {
- self.start_transaction(None).unwrap();
- let mut offset = 0;
- for (tag, len) in diff.changes {
- let range = offset..(offset + len);
- match tag {
- ChangeTag::Equal => offset += len,
- ChangeTag::Delete => self.edit(Some(range), "", cx),
- ChangeTag::Insert => {
- self.edit(Some(offset..offset), &diff.new_text[range], cx);
- offset += len;
- }
- }
- }
- self.end_transaction(None, cx).unwrap();
- true
- } else {
- false
- }
- }
-
- pub fn is_dirty(&self) -> bool {
- self.version > self.saved_version
- || self.file.as_ref().map_or(false, |file| file.is_deleted())
- }
-
- pub fn has_conflict(&self) -> bool {
- self.version > self.saved_version
- && self
- .file
- .as_ref()
- .map_or(false, |file| file.mtime() > self.saved_mtime)
- }
-
- pub fn start_transaction(
- &mut self,
- selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
- ) -> Result<()> {
- self.start_transaction_at(selection_set_ids, Instant::now())?;
- Ok(())
- }
-
- fn start_transaction_at(
- &mut self,
- selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
- now: Instant,
- ) -> Result<()> {
- self.buffer.start_transaction_at(selection_set_ids, now)?;
- Ok(())
- }
-
- pub fn end_transaction(
- &mut self,
- selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- self.end_transaction_at(selection_set_ids, Instant::now(), cx)
- }
-
- fn end_transaction_at(
- &mut self,
- selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
- now: Instant,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- if let Some(start_version) = self.buffer.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);
- }
- }
- Ok(())
- }
-
- pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
- where
- I: IntoIterator<Item = Range<S>>,
- S: ToOffset,
- T: Into<String>,
- {
- self.edit_internal(ranges_iter, new_text, false, cx)
- }
-
- pub fn edit_with_autoindent<I, S, T>(
- &mut self,
- ranges_iter: I,
- new_text: T,
- cx: &mut ModelContext<Self>,
- ) where
- I: IntoIterator<Item = Range<S>>,
- S: ToOffset,
- T: Into<String>,
- {
- self.edit_internal(ranges_iter, new_text, true, cx)
- }
-
- pub fn edit_internal<I, S, T>(
- &mut self,
- ranges_iter: I,
- new_text: T,
- autoindent: bool,
- cx: &mut ModelContext<Self>,
- ) where
- I: IntoIterator<Item = Range<S>>,
- S: ToOffset,
- T: Into<String>,
- {
- let new_text = new_text.into();
-
- // Skip invalid ranges and coalesce contiguous ones.
- let mut ranges: Vec<Range<usize>> = Vec::new();
- for range in ranges_iter {
- let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
- if !new_text.is_empty() || !range.is_empty() {
- if let Some(prev_range) = ranges.last_mut() {
- if prev_range.end >= range.start {
- prev_range.end = cmp::max(prev_range.end, range.end);
- } else {
- ranges.push(range);
- }
- } else {
- ranges.push(range);
- }
- }
- }
- if ranges.is_empty() {
- return;
- }
-
- self.start_transaction(None).unwrap();
- self.pending_autoindent.take();
- let autoindent_request = if autoindent && self.language.is_some() {
- let before_edit = self.snapshot();
- let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
- let start = range.start.to_point(&*self);
- if new_text.starts_with('\n') && start.column == self.line_len(start.row) {
- None
- } else {
- Some((range.start, Bias::Left))
- }
- }));
- Some((before_edit, edited))
- } else {
- None
- };
-
- let first_newline_ix = new_text.find('\n');
- let new_text_len = new_text.len();
-
- let edit = self.buffer.edit(ranges.iter().cloned(), new_text);
-
- if let Some((before_edit, edited)) = autoindent_request {
- let mut inserted = None;
- if let Some(first_newline_ix) = first_newline_ix {
- let mut delta = 0isize;
- inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| {
- let start = (delta + range.start as isize) as usize + first_newline_ix + 1;
- let end = (delta + range.start as isize) as usize + new_text_len;
- delta += (range.end as isize - range.start as isize) + new_text_len as isize;
- (start, Bias::Left)..(end, Bias::Right)
- })));
- }
-
- let selection_set_ids = self
- .buffer
- .peek_undo_stack()
- .unwrap()
- .starting_selection_set_ids()
- .collect();
- self.autoindent_requests.push(Arc::new(AutoindentRequest {
- selection_set_ids,
- before_edit,
- edited,
- inserted,
- }));
- }
-
- self.end_transaction(None, cx).unwrap();
- self.send_operation(Operation::Edit(edit), cx);
- }
-
- fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext<Self>) {
- cx.emit(Event::Edited);
- if !was_dirty {
- cx.emit(Event::Dirtied);
- }
- }
-
- pub fn add_selection_set(
- &mut self,
- selections: impl Into<Arc<[Selection]>>,
- cx: &mut ModelContext<Self>,
- ) -> SelectionSetId {
- let operation = self.buffer.add_selection_set(selections);
- if let Operation::UpdateSelections { set_id, .. } = &operation {
- let set_id = *set_id;
- cx.notify();
- self.send_operation(operation, cx);
- set_id
- } else {
- unreachable!()
- }
- }
-
- pub fn update_selection_set(
- &mut self,
- set_id: SelectionSetId,
- selections: impl Into<Arc<[Selection]>>,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- let operation = self.buffer.update_selection_set(set_id, selections)?;
- cx.notify();
- self.send_operation(operation, cx);
- Ok(())
- }
-
- pub fn set_active_selection_set(
- &mut self,
- set_id: Option<SelectionSetId>,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- let operation = self.buffer.set_active_selection_set(set_id)?;
- self.send_operation(operation, cx);
- Ok(())
- }
-
- pub fn remove_selection_set(
- &mut self,
- set_id: SelectionSetId,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- let operation = self.buffer.remove_selection_set(set_id)?;
- cx.notify();
- self.send_operation(operation, cx);
- Ok(())
- }
-
- pub fn apply_ops<I: IntoIterator<Item = Operation>>(
- &mut self,
- ops: I,
- cx: &mut ModelContext<Self>,
- ) -> Result<()> {
- self.pending_autoindent.take();
-
- let was_dirty = self.is_dirty();
- let old_version = self.version.clone();
-
- self.buffer.apply_ops(ops)?;
-
- cx.notify();
- if self.edits_since(old_version).next().is_some() {
- self.did_edit(was_dirty, cx);
- self.reparse(cx);
- }
-
- Ok(())
- }
-
- #[cfg(not(test))]
- pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
- if let Some(file) = &self.file {
- file.buffer_updated(self.remote_id, operation, cx.as_mut());
- }
- }
-
- #[cfg(test)]
- pub fn send_operation(&mut self, operation: Operation, _: &mut ModelContext<Self>) {
- self.operations.push(operation);
- }
-
- pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
- self.buffer.remove_peer(replica_id);
- cx.notify();
- }
-
- pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
- let was_dirty = self.is_dirty();
- let old_version = self.version.clone();
-
- for operation in self.buffer.undo() {
- 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);
- }
- }
-
- pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
- let was_dirty = self.is_dirty();
- let old_version = self.version.clone();
-
- for operation in self.buffer.redo() {
- 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);
- }
- }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl Buffer {
- pub fn randomly_edit<T>(&mut self, rng: &mut T, old_range_count: usize)
- where
- T: rand::Rng,
- {
- self.buffer.randomly_edit(rng, old_range_count);
- }
-
- pub fn randomly_mutate<T>(&mut self, rng: &mut T)
- where
- T: rand::Rng,
- {
- self.buffer.randomly_mutate(rng);
- }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-impl TextBuffer {
- fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
- let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right);
- let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right);
- start..end
- }
-
- pub fn randomly_edit<T>(
- &mut self,
- rng: &mut T,
- old_range_count: usize,
- ) -> (Vec<Range<usize>>, String, Operation)
- where
- T: rand::Rng,
- {
- let mut old_ranges: Vec<Range<usize>> = Vec::new();
- for _ in 0..old_range_count {
- let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1);
- if last_end > self.len() {
- break;
- }
- old_ranges.push(self.random_byte_range(last_end, rng));
- }
- let new_text_len = rng.gen_range(0..10);
- let new_text: String = crate::random_char_iter::RandomCharIter::new(&mut *rng)
- .take(new_text_len)
- .collect();
- log::info!(
- "mutating buffer {} at {:?}: {:?}",
- self.replica_id,
- old_ranges,
- new_text
- );
- let op = self.edit(old_ranges.iter().cloned(), new_text.as_str());
- (old_ranges, new_text, Operation::Edit(op))
- }
-
- pub fn randomly_mutate<T>(&mut self, rng: &mut T) -> Vec<Operation>
- where
- T: rand::Rng,
- {
- use rand::prelude::*;
-
- let mut ops = vec![self.randomly_edit(rng, 5).2];
-
- // Randomly add, remove or mutate selection sets.
- let replica_selection_sets = &self
- .selection_sets()
- .map(|(set_id, _)| *set_id)
- .filter(|set_id| self.replica_id == set_id.replica_id)
- .collect::<Vec<_>>();
- let set_id = replica_selection_sets.choose(rng);
- if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
- ops.push(self.remove_selection_set(*set_id.unwrap()).unwrap());
- } else {
- let mut ranges = Vec::new();
- for _ in 0..5 {
- ranges.push(self.random_byte_range(0, rng));
- }
- let new_selections = self.selections_from_ranges(ranges).unwrap();
-
- let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) {
- self.add_selection_set(new_selections)
- } else {
- self.update_selection_set(*set_id.unwrap(), new_selections)
- .unwrap()
- };
- ops.push(op);
- }
-
- ops
- }
-
- pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec<Operation> {
- use rand::prelude::*;
-
- let mut ops = Vec::new();
- for _ in 0..rng.gen_range(1..=5) {
- if let Some(transaction) = self.history.undo_stack.choose(rng).cloned() {
- log::info!(
- "undoing buffer {} transaction {:?}",
- self.replica_id,
- transaction
- );
- ops.push(self.undo_or_redo(transaction).unwrap());
- }
- }
- ops
- }
-
- fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
- where
- I: IntoIterator<Item = Range<usize>>,
- {
- use std::sync::atomic::{self, AtomicUsize};
-
- static NEXT_SELECTION_ID: AtomicUsize = AtomicUsize::new(0);
-
- let mut ranges = ranges.into_iter().collect::<Vec<_>>();
- ranges.sort_unstable_by_key(|range| range.start);
+ let mut ranges = ranges.into_iter().collect::<Vec<_>>();
+ ranges.sort_unstable_by_key(|range| range.start);
let mut selections = Vec::with_capacity(ranges.len());
for range in ranges {
@@ -1,4 +1,4 @@
-use crate::{Anchor, Buffer, Point, ToOffset as _, ToPoint as _};
+use crate::{Anchor, Point, TextBuffer, ToOffset as _, ToPoint as _};
use std::{cmp::Ordering, mem, ops::Range};
pub type SelectionSetId = clock::Lamport;
@@ -29,7 +29,7 @@ impl Selection {
}
}
- pub fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) {
+ pub fn set_head(&mut self, buffer: &TextBuffer, cursor: Anchor) {
if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal {
if !self.reversed {
mem::swap(&mut self.start, &mut self.end);
@@ -53,7 +53,7 @@ impl Selection {
}
}
- pub fn point_range(&self, buffer: &Buffer) -> Range<Point> {
+ pub fn point_range(&self, buffer: &TextBuffer) -> Range<Point> {
let start = self.start.to_point(buffer);
let end = self.end.to_point(buffer);
if self.reversed {
@@ -63,7 +63,7 @@ impl Selection {
}
}
- pub fn offset_range(&self, buffer: &Buffer) -> Range<usize> {
+ pub fn offset_range(&self, buffer: &TextBuffer) -> Range<usize> {
let start = self.start.to_offset(buffer);
let end = self.end.to_offset(buffer);
if self.reversed {
@@ -1,2 +1,658 @@
-mod buffer;
-mod syntax;
+use super::*;
+use clock::ReplicaId;
+use rand::prelude::*;
+use std::{
+ cmp::Ordering,
+ env,
+ iter::Iterator,
+ time::{Duration, Instant},
+};
+
+#[test]
+fn test_edit() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("abc".into()));
+ assert_eq!(buffer.text(), "abc");
+ buffer.edit(vec![3..3], "def");
+ assert_eq!(buffer.text(), "abcdef");
+ buffer.edit(vec![0..0], "ghi");
+ assert_eq!(buffer.text(), "ghiabcdef");
+ buffer.edit(vec![5..5], "jkl");
+ assert_eq!(buffer.text(), "ghiabjklcdef");
+ buffer.edit(vec![6..7], "");
+ assert_eq!(buffer.text(), "ghiabjlcdef");
+ buffer.edit(vec![4..9], "mno");
+ assert_eq!(buffer.text(), "ghiamnoef");
+}
+
+#[gpui::test(iterations = 100)]
+fn test_random_edits(mut rng: StdRng) {
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(10);
+
+ let reference_string_len = rng.gen_range(0..3);
+ let mut reference_string = RandomCharIter::new(&mut rng)
+ .take(reference_string_len)
+ .collect::<String>();
+ let mut buffer = TextBuffer::new(0, 0, History::new(reference_string.clone().into()));
+ buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
+ let mut buffer_versions = Vec::new();
+ log::info!(
+ "buffer text {:?}, version: {:?}",
+ buffer.text(),
+ buffer.version()
+ );
+
+ for _i in 0..operations {
+ let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5);
+ for old_range in old_ranges.iter().rev() {
+ reference_string.replace_range(old_range.clone(), &new_text);
+ }
+ assert_eq!(buffer.text(), reference_string);
+ log::info!(
+ "buffer text {:?}, version: {:?}",
+ buffer.text(),
+ buffer.version()
+ );
+
+ if rng.gen_bool(0.25) {
+ buffer.randomly_undo_redo(&mut rng);
+ reference_string = buffer.text();
+ log::info!(
+ "buffer text {:?}, version: {:?}",
+ buffer.text(),
+ buffer.version()
+ );
+ }
+
+ let range = buffer.random_byte_range(0, &mut rng);
+ assert_eq!(
+ buffer.text_summary_for_range(range.clone()),
+ TextSummary::from(&reference_string[range])
+ );
+
+ if rng.gen_bool(0.3) {
+ buffer_versions.push(buffer.clone());
+ }
+ }
+
+ for mut old_buffer in buffer_versions {
+ let edits = buffer
+ .edits_since(old_buffer.version.clone())
+ .collect::<Vec<_>>();
+
+ log::info!(
+ "mutating old buffer version {:?}, text: {:?}, edits since: {:?}",
+ old_buffer.version(),
+ old_buffer.text(),
+ edits,
+ );
+
+ let mut delta = 0_isize;
+ for edit in edits {
+ let old_start = (edit.old_bytes.start as isize + delta) as usize;
+ let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect();
+ old_buffer.edit(Some(old_start..old_start + edit.deleted_bytes()), new_text);
+ delta += edit.delta();
+ }
+ assert_eq!(old_buffer.text(), buffer.text());
+ }
+}
+
+#[test]
+fn test_line_len() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
+ buffer.edit(vec![0..0], "abcd\nefg\nhij");
+ buffer.edit(vec![12..12], "kl\nmno");
+ buffer.edit(vec![18..18], "\npqrs\n");
+ buffer.edit(vec![18..21], "\nPQ");
+
+ assert_eq!(buffer.line_len(0), 4);
+ assert_eq!(buffer.line_len(1), 3);
+ assert_eq!(buffer.line_len(2), 5);
+ assert_eq!(buffer.line_len(3), 3);
+ assert_eq!(buffer.line_len(4), 4);
+ assert_eq!(buffer.line_len(5), 0);
+}
+
+#[test]
+fn test_text_summary_for_range() {
+ let buffer = TextBuffer::new(0, 0, History::new("ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()));
+ assert_eq!(
+ buffer.text_summary_for_range(1..3),
+ TextSummary {
+ bytes: 2,
+ lines: Point::new(1, 0),
+ first_line_chars: 1,
+ last_line_chars: 0,
+ longest_row: 0,
+ longest_row_chars: 1,
+ }
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(1..12),
+ TextSummary {
+ bytes: 11,
+ lines: Point::new(3, 0),
+ first_line_chars: 1,
+ last_line_chars: 0,
+ longest_row: 2,
+ longest_row_chars: 4,
+ }
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(0..20),
+ TextSummary {
+ bytes: 20,
+ lines: Point::new(4, 1),
+ first_line_chars: 2,
+ last_line_chars: 1,
+ longest_row: 3,
+ longest_row_chars: 6,
+ }
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(0..22),
+ TextSummary {
+ bytes: 22,
+ lines: Point::new(4, 3),
+ first_line_chars: 2,
+ last_line_chars: 3,
+ longest_row: 3,
+ longest_row_chars: 6,
+ }
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(7..22),
+ TextSummary {
+ bytes: 15,
+ lines: Point::new(2, 3),
+ first_line_chars: 4,
+ last_line_chars: 3,
+ longest_row: 1,
+ longest_row_chars: 6,
+ }
+ );
+}
+
+#[test]
+fn test_chars_at() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
+ buffer.edit(vec![0..0], "abcd\nefgh\nij");
+ buffer.edit(vec![12..12], "kl\nmno");
+ buffer.edit(vec![18..18], "\npqrs");
+ buffer.edit(vec![18..21], "\nPQ");
+
+ let chars = buffer.chars_at(Point::new(0, 0));
+ assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
+
+ let chars = buffer.chars_at(Point::new(1, 0));
+ assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
+
+ let chars = buffer.chars_at(Point::new(2, 0));
+ assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
+
+ let chars = buffer.chars_at(Point::new(3, 0));
+ assert_eq!(chars.collect::<String>(), "mno\nPQrs");
+
+ let chars = buffer.chars_at(Point::new(4, 0));
+ assert_eq!(chars.collect::<String>(), "PQrs");
+
+ // Regression test:
+ let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
+ buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n");
+ buffer.edit(vec![60..60], "\n");
+
+ let chars = buffer.chars_at(Point::new(6, 0));
+ assert_eq!(chars.collect::<String>(), " \"xray_wasm\",\n]\n");
+}
+
+#[test]
+fn test_anchors() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
+ buffer.edit(vec![0..0], "abc");
+ let left_anchor = buffer.anchor_before(2);
+ let right_anchor = buffer.anchor_after(2);
+
+ buffer.edit(vec![1..1], "def\n");
+ assert_eq!(buffer.text(), "adef\nbc");
+ assert_eq!(left_anchor.to_offset(&buffer), 6);
+ assert_eq!(right_anchor.to_offset(&buffer), 6);
+ assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+ assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+
+ buffer.edit(vec![2..3], "");
+ assert_eq!(buffer.text(), "adf\nbc");
+ assert_eq!(left_anchor.to_offset(&buffer), 5);
+ assert_eq!(right_anchor.to_offset(&buffer), 5);
+ assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+ assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+
+ buffer.edit(vec![5..5], "ghi\n");
+ assert_eq!(buffer.text(), "adf\nbghi\nc");
+ assert_eq!(left_anchor.to_offset(&buffer), 5);
+ assert_eq!(right_anchor.to_offset(&buffer), 9);
+ assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
+ assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
+
+ buffer.edit(vec![7..9], "");
+ assert_eq!(buffer.text(), "adf\nbghc");
+ assert_eq!(left_anchor.to_offset(&buffer), 5);
+ assert_eq!(right_anchor.to_offset(&buffer), 7);
+ assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
+ assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
+
+ // Ensure anchoring to a point is equivalent to anchoring to an offset.
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 0 }),
+ buffer.anchor_before(0)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 1 }),
+ buffer.anchor_before(1)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 2 }),
+ buffer.anchor_before(2)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 3 }),
+ buffer.anchor_before(3)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 0 }),
+ buffer.anchor_before(4)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 1 }),
+ buffer.anchor_before(5)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 2 }),
+ buffer.anchor_before(6)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 3 }),
+ buffer.anchor_before(7)
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 4 }),
+ buffer.anchor_before(8)
+ );
+
+ // Comparison between anchors.
+ let anchor_at_offset_0 = buffer.anchor_before(0);
+ let anchor_at_offset_1 = buffer.anchor_before(1);
+ let anchor_at_offset_2 = buffer.anchor_before(2);
+
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
+
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+}
+
+#[test]
+fn test_anchors_at_start_and_end() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
+ let before_start_anchor = buffer.anchor_before(0);
+ let after_end_anchor = buffer.anchor_after(0);
+
+ buffer.edit(vec![0..0], "abc");
+ assert_eq!(buffer.text(), "abc");
+ assert_eq!(before_start_anchor.to_offset(&buffer), 0);
+ assert_eq!(after_end_anchor.to_offset(&buffer), 3);
+
+ let after_start_anchor = buffer.anchor_after(0);
+ let before_end_anchor = buffer.anchor_before(3);
+
+ buffer.edit(vec![3..3], "def");
+ buffer.edit(vec![0..0], "ghi");
+ assert_eq!(buffer.text(), "ghiabcdef");
+ assert_eq!(before_start_anchor.to_offset(&buffer), 0);
+ assert_eq!(after_start_anchor.to_offset(&buffer), 3);
+ assert_eq!(before_end_anchor.to_offset(&buffer), 6);
+ assert_eq!(after_end_anchor.to_offset(&buffer), 9);
+}
+
+#[test]
+fn test_undo_redo() {
+ let mut buffer = TextBuffer::new(0, 0, History::new("1234".into()));
+ // Set group interval to zero so as to not group edits in the undo stack.
+ buffer.history.group_interval = Duration::from_secs(0);
+
+ buffer.edit(vec![1..1], "abx");
+ buffer.edit(vec![3..4], "yzef");
+ buffer.edit(vec![3..5], "cd");
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ let transactions = buffer.history.undo_stack.clone();
+ assert_eq!(transactions.len(), 3);
+
+ buffer.undo_or_redo(transactions[0].clone()).unwrap();
+ assert_eq!(buffer.text(), "1cdef234");
+ buffer.undo_or_redo(transactions[0].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ buffer.undo_or_redo(transactions[1].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abcdx234");
+ buffer.undo_or_redo(transactions[2].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abx234");
+ buffer.undo_or_redo(transactions[1].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abyzef234");
+ buffer.undo_or_redo(transactions[2].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ buffer.undo_or_redo(transactions[2].clone()).unwrap();
+ assert_eq!(buffer.text(), "1abyzef234");
+ buffer.undo_or_redo(transactions[0].clone()).unwrap();
+ assert_eq!(buffer.text(), "1yzef234");
+ buffer.undo_or_redo(transactions[1].clone()).unwrap();
+ assert_eq!(buffer.text(), "1234");
+}
+
+#[test]
+fn test_history() {
+ let mut now = Instant::now();
+ let mut buffer = TextBuffer::new(0, 0, History::new("123456".into()));
+
+ let set_id = if let Operation::UpdateSelections { set_id, .. } =
+ buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap())
+ {
+ set_id
+ } else {
+ unreachable!()
+ };
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer.edit(vec![2..4], "cd");
+ buffer.end_transaction_at(Some(set_id), now).unwrap();
+ assert_eq!(buffer.text(), "12cd56");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer
+ .update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3]).unwrap())
+ .unwrap();
+ buffer.edit(vec![4..5], "e");
+ buffer.end_transaction_at(Some(set_id), now).unwrap();
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+ now += buffer.history.group_interval + Duration::from_millis(1);
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer
+ .update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2]).unwrap())
+ .unwrap();
+ buffer.edit(vec![0..1], "a");
+ buffer.edit(vec![1..1], "b");
+ buffer.end_transaction_at(Some(set_id), now).unwrap();
+ assert_eq!(buffer.text(), "ab2cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+ // Last transaction happened past the group interval, undo it on its
+ // own.
+ buffer.undo();
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+ // First two transactions happened within the group interval, undo them
+ // together.
+ buffer.undo();
+ assert_eq!(buffer.text(), "123456");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+ // Redo the first two transactions together.
+ buffer.redo();
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+ // Redo the last transaction on its own.
+ buffer.redo();
+ assert_eq!(buffer.text(), "ab2cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+ buffer.start_transaction_at(None, now).unwrap();
+ assert!(buffer.end_transaction_at(None, now).is_none());
+ buffer.undo();
+ assert_eq!(buffer.text(), "12cde6");
+}
+
+#[test]
+fn test_concurrent_edits() {
+ let text = "abcdef";
+
+ let mut buffer1 = TextBuffer::new(1, 0, History::new(text.into()));
+ let mut buffer2 = TextBuffer::new(2, 0, History::new(text.into()));
+ let mut buffer3 = TextBuffer::new(3, 0, History::new(text.into()));
+
+ let buf1_op = buffer1.edit(vec![1..2], "12");
+ assert_eq!(buffer1.text(), "a12cdef");
+ let buf2_op = buffer2.edit(vec![3..4], "34");
+ assert_eq!(buffer2.text(), "abc34ef");
+ let buf3_op = buffer3.edit(vec![5..6], "56");
+ assert_eq!(buffer3.text(), "abcde56");
+
+ buffer1.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
+ buffer1.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
+ buffer2.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
+ buffer2.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
+ buffer3.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
+ buffer3.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
+
+ assert_eq!(buffer1.text(), "a12c34e56");
+ assert_eq!(buffer2.text(), "a12c34e56");
+ assert_eq!(buffer3.text(), "a12c34e56");
+}
+
+#[gpui::test(iterations = 100)]
+fn test_random_concurrent_edits(mut rng: StdRng) {
+ let peers = env::var("PEERS")
+ .map(|i| i.parse().expect("invalid `PEERS` variable"))
+ .unwrap_or(5);
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(10);
+
+ let base_text_len = rng.gen_range(0..10);
+ let base_text = RandomCharIter::new(&mut rng)
+ .take(base_text_len)
+ .collect::<String>();
+ let mut replica_ids = Vec::new();
+ let mut buffers = Vec::new();
+ let mut network = Network::new(rng.clone());
+
+ for i in 0..peers {
+ let mut buffer = TextBuffer::new(i as ReplicaId, 0, History::new(base_text.clone().into()));
+ buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
+ buffers.push(buffer);
+ replica_ids.push(i as u16);
+ network.add_peer(i as u16);
+ }
+
+ log::info!("initial text: {:?}", base_text);
+
+ let mut mutation_count = operations;
+ loop {
+ let replica_index = rng.gen_range(0..peers);
+ let replica_id = replica_ids[replica_index];
+ let buffer = &mut buffers[replica_index];
+ match rng.gen_range(0..=100) {
+ 0..=50 if mutation_count != 0 => {
+ let ops = buffer.randomly_mutate(&mut rng);
+ network.broadcast(buffer.replica_id, ops);
+ log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
+ mutation_count -= 1;
+ }
+ 51..=70 if mutation_count != 0 => {
+ let ops = buffer.randomly_undo_redo(&mut rng);
+ network.broadcast(buffer.replica_id, ops);
+ mutation_count -= 1;
+ }
+ 71..=100 if network.has_unreceived(replica_id) => {
+ let ops = network.receive(replica_id);
+ if !ops.is_empty() {
+ log::info!(
+ "peer {} applying {} ops from the network.",
+ replica_id,
+ ops.len()
+ );
+ buffer.apply_ops(ops).unwrap();
+ }
+ }
+ _ => {}
+ }
+
+ if mutation_count == 0 && network.is_idle() {
+ break;
+ }
+ }
+
+ let first_buffer = &buffers[0];
+ for buffer in &buffers[1..] {
+ assert_eq!(
+ buffer.text(),
+ first_buffer.text(),
+ "Replica {} text != Replica 0 text",
+ buffer.replica_id
+ );
+ assert_eq!(
+ buffer.selection_sets().collect::<HashMap<_, _>>(),
+ first_buffer.selection_sets().collect::<HashMap<_, _>>()
+ );
+ assert_eq!(
+ buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
+ first_buffer
+ .all_selection_ranges()
+ .collect::<HashMap<_, _>>()
+ );
+ }
+}
+
+#[derive(Clone)]
+struct Envelope<T: Clone> {
+ message: T,
+ sender: ReplicaId,
+}
+
+struct Network<T: Clone, R: rand::Rng> {
+ inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
+ all_messages: Vec<T>,
+ rng: R,
+}
+
+impl<T: Clone, R: rand::Rng> Network<T, R> {
+ fn new(rng: R) -> Self {
+ Network {
+ inboxes: Default::default(),
+ all_messages: Vec::new(),
+ rng,
+ }
+ }
+
+ fn add_peer(&mut self, id: ReplicaId) {
+ self.inboxes.insert(id, Vec::new());
+ }
+
+ fn is_idle(&self) -> bool {
+ self.inboxes.values().all(|i| i.is_empty())
+ }
+
+ fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
+ for (replica, inbox) in self.inboxes.iter_mut() {
+ if *replica != sender {
+ for message in &messages {
+ let min_index = inbox
+ .iter()
+ .enumerate()
+ .rev()
+ .find_map(|(index, envelope)| {
+ if sender == envelope.sender {
+ Some(index + 1)
+ } else {
+ None
+ }
+ })
+ .unwrap_or(0);
+
+ // Insert one or more duplicates of this message *after* the previous
+ // message delivered by this replica.
+ for _ in 0..self.rng.gen_range(1..4) {
+ let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
+ inbox.insert(
+ insertion_index,
+ Envelope {
+ message: message.clone(),
+ sender,
+ },
+ );
+ }
+ }
+ }
+ }
+ self.all_messages.extend(messages);
+ }
+
+ fn has_unreceived(&self, receiver: ReplicaId) -> bool {
+ !self.inboxes[&receiver].is_empty()
+ }
+
+ fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
+ let inbox = self.inboxes.get_mut(&receiver).unwrap();
+ let count = self.rng.gen_range(0..inbox.len() + 1);
+ inbox
+ .drain(0..count)
+ .map(|envelope| envelope.message)
+ .collect()
+ }
+}
@@ -1,733 +0,0 @@
-use crate::*;
-use clock::ReplicaId;
-use rand::prelude::*;
-use std::{
- cell::RefCell,
- cmp::Ordering,
- env,
- iter::Iterator,
- rc::Rc,
- time::{Duration, Instant},
-};
-
-#[test]
-fn test_edit() {
- let mut buffer = TextBuffer::new(0, 0, History::new("abc".into()));
- assert_eq!(buffer.text(), "abc");
- buffer.edit(vec![3..3], "def");
- assert_eq!(buffer.text(), "abcdef");
- buffer.edit(vec![0..0], "ghi");
- assert_eq!(buffer.text(), "ghiabcdef");
- buffer.edit(vec![5..5], "jkl");
- assert_eq!(buffer.text(), "ghiabjklcdef");
- buffer.edit(vec![6..7], "");
- assert_eq!(buffer.text(), "ghiabjlcdef");
- buffer.edit(vec![4..9], "mno");
- assert_eq!(buffer.text(), "ghiamnoef");
-}
-
-#[gpui::test]
-fn test_edit_events(cx: &mut gpui::MutableAppContext) {
- let mut now = Instant::now();
- let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
- let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
-
- let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
- let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
- let buffer_ops = buffer1.update(cx, |buffer, cx| {
- let buffer_1_events = buffer_1_events.clone();
- cx.subscribe(&buffer1, move |_, _, event, _| {
- buffer_1_events.borrow_mut().push(event.clone())
- })
- .detach();
- let buffer_2_events = buffer_2_events.clone();
- cx.subscribe(&buffer2, move |_, _, event, _| {
- buffer_2_events.borrow_mut().push(event.clone())
- })
- .detach();
-
- // An edit emits an edited event, followed by a dirtied event,
- // since the buffer was previously in a clean state.
- buffer.edit(Some(2..4), "XYZ", cx);
-
- // An empty transaction does not emit any events.
- buffer.start_transaction(None).unwrap();
- buffer.end_transaction(None, cx).unwrap();
-
- // A transaction containing two edits emits one edited event.
- now += Duration::from_secs(1);
- buffer.start_transaction_at(None, now).unwrap();
- buffer.edit(Some(5..5), "u", cx);
- buffer.edit(Some(6..6), "w", cx);
- buffer.end_transaction_at(None, now, cx).unwrap();
-
- // Undoing a transaction emits one edited event.
- buffer.undo(cx);
-
- buffer.operations.clone()
- });
-
- // Incorporating a set of remote ops emits a single edited event,
- // followed by a dirtied event.
- buffer2.update(cx, |buffer, cx| {
- buffer.apply_ops(buffer_ops, cx).unwrap();
- });
-
- let buffer_1_events = buffer_1_events.borrow();
- assert_eq!(
- *buffer_1_events,
- vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
- );
-
- let buffer_2_events = buffer_2_events.borrow();
- assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_edits(mut rng: StdRng) {
- let operations = env::var("OPERATIONS")
- .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
- .unwrap_or(10);
-
- let reference_string_len = rng.gen_range(0..3);
- let mut reference_string = RandomCharIter::new(&mut rng)
- .take(reference_string_len)
- .collect::<String>();
- let mut buffer = TextBuffer::new(0, 0, History::new(reference_string.clone().into()));
- buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
- let mut buffer_versions = Vec::new();
- log::info!(
- "buffer text {:?}, version: {:?}",
- buffer.text(),
- buffer.version()
- );
-
- for _i in 0..operations {
- let (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5);
- for old_range in old_ranges.iter().rev() {
- reference_string.replace_range(old_range.clone(), &new_text);
- }
- assert_eq!(buffer.text(), reference_string);
- log::info!(
- "buffer text {:?}, version: {:?}",
- buffer.text(),
- buffer.version()
- );
-
- if rng.gen_bool(0.25) {
- buffer.randomly_undo_redo(&mut rng);
- reference_string = buffer.text();
- log::info!(
- "buffer text {:?}, version: {:?}",
- buffer.text(),
- buffer.version()
- );
- }
-
- let range = buffer.random_byte_range(0, &mut rng);
- assert_eq!(
- buffer.text_summary_for_range(range.clone()),
- TextSummary::from(&reference_string[range])
- );
-
- if rng.gen_bool(0.3) {
- buffer_versions.push(buffer.clone());
- }
- }
-
- for mut old_buffer in buffer_versions {
- let edits = buffer
- .edits_since(old_buffer.version.clone())
- .collect::<Vec<_>>();
-
- log::info!(
- "mutating old buffer version {:?}, text: {:?}, edits since: {:?}",
- old_buffer.version(),
- old_buffer.text(),
- edits,
- );
-
- let mut delta = 0_isize;
- for edit in edits {
- let old_start = (edit.old_bytes.start as isize + delta) as usize;
- let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect();
- old_buffer.edit(Some(old_start..old_start + edit.deleted_bytes()), new_text);
- delta += edit.delta();
- }
- assert_eq!(old_buffer.text(), buffer.text());
- }
-}
-
-#[test]
-fn test_line_len() {
- let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
- buffer.edit(vec![0..0], "abcd\nefg\nhij");
- buffer.edit(vec![12..12], "kl\nmno");
- buffer.edit(vec![18..18], "\npqrs\n");
- buffer.edit(vec![18..21], "\nPQ");
-
- assert_eq!(buffer.line_len(0), 4);
- assert_eq!(buffer.line_len(1), 3);
- assert_eq!(buffer.line_len(2), 5);
- assert_eq!(buffer.line_len(3), 3);
- assert_eq!(buffer.line_len(4), 4);
- assert_eq!(buffer.line_len(5), 0);
-}
-
-#[test]
-fn test_text_summary_for_range() {
- let buffer = TextBuffer::new(0, 0, History::new("ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()));
- assert_eq!(
- buffer.text_summary_for_range(1..3),
- TextSummary {
- bytes: 2,
- lines: Point::new(1, 0),
- first_line_chars: 1,
- last_line_chars: 0,
- longest_row: 0,
- longest_row_chars: 1,
- }
- );
- assert_eq!(
- buffer.text_summary_for_range(1..12),
- TextSummary {
- bytes: 11,
- lines: Point::new(3, 0),
- first_line_chars: 1,
- last_line_chars: 0,
- longest_row: 2,
- longest_row_chars: 4,
- }
- );
- assert_eq!(
- buffer.text_summary_for_range(0..20),
- TextSummary {
- bytes: 20,
- lines: Point::new(4, 1),
- first_line_chars: 2,
- last_line_chars: 1,
- longest_row: 3,
- longest_row_chars: 6,
- }
- );
- assert_eq!(
- buffer.text_summary_for_range(0..22),
- TextSummary {
- bytes: 22,
- lines: Point::new(4, 3),
- first_line_chars: 2,
- last_line_chars: 3,
- longest_row: 3,
- longest_row_chars: 6,
- }
- );
- assert_eq!(
- buffer.text_summary_for_range(7..22),
- TextSummary {
- bytes: 15,
- lines: Point::new(2, 3),
- first_line_chars: 4,
- last_line_chars: 3,
- longest_row: 1,
- longest_row_chars: 6,
- }
- );
-}
-
-#[test]
-fn test_chars_at() {
- let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
- buffer.edit(vec![0..0], "abcd\nefgh\nij");
- buffer.edit(vec![12..12], "kl\nmno");
- buffer.edit(vec![18..18], "\npqrs");
- buffer.edit(vec![18..21], "\nPQ");
-
- let chars = buffer.chars_at(Point::new(0, 0));
- assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
-
- let chars = buffer.chars_at(Point::new(1, 0));
- assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
-
- let chars = buffer.chars_at(Point::new(2, 0));
- assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
-
- let chars = buffer.chars_at(Point::new(3, 0));
- assert_eq!(chars.collect::<String>(), "mno\nPQrs");
-
- let chars = buffer.chars_at(Point::new(4, 0));
- assert_eq!(chars.collect::<String>(), "PQrs");
-
- // Regression test:
- let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
- buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n");
- buffer.edit(vec![60..60], "\n");
-
- let chars = buffer.chars_at(Point::new(6, 0));
- assert_eq!(chars.collect::<String>(), " \"xray_wasm\",\n]\n");
-}
-
-#[test]
-fn test_anchors() {
- let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
- buffer.edit(vec![0..0], "abc");
- let left_anchor = buffer.anchor_before(2);
- let right_anchor = buffer.anchor_after(2);
-
- buffer.edit(vec![1..1], "def\n");
- assert_eq!(buffer.text(), "adef\nbc");
- assert_eq!(left_anchor.to_offset(&buffer), 6);
- assert_eq!(right_anchor.to_offset(&buffer), 6);
- assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
- assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
- buffer.edit(vec![2..3], "");
- assert_eq!(buffer.text(), "adf\nbc");
- assert_eq!(left_anchor.to_offset(&buffer), 5);
- assert_eq!(right_anchor.to_offset(&buffer), 5);
- assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
- assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 });
-
- buffer.edit(vec![5..5], "ghi\n");
- assert_eq!(buffer.text(), "adf\nbghi\nc");
- assert_eq!(left_anchor.to_offset(&buffer), 5);
- assert_eq!(right_anchor.to_offset(&buffer), 9);
- assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 });
- assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 });
-
- buffer.edit(vec![7..9], "");
- assert_eq!(buffer.text(), "adf\nbghc");
- assert_eq!(left_anchor.to_offset(&buffer), 5);
- assert_eq!(right_anchor.to_offset(&buffer), 7);
- assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },);
- assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 });
-
- // Ensure anchoring to a point is equivalent to anchoring to an offset.
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 0 }),
- buffer.anchor_before(0)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 1 }),
- buffer.anchor_before(1)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 2 }),
- buffer.anchor_before(2)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 3 }),
- buffer.anchor_before(3)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 0 }),
- buffer.anchor_before(4)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 1 }),
- buffer.anchor_before(5)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 2 }),
- buffer.anchor_before(6)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 3 }),
- buffer.anchor_before(7)
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 4 }),
- buffer.anchor_before(8)
- );
-
- // Comparison between anchors.
- let anchor_at_offset_0 = buffer.anchor_before(0);
- let anchor_at_offset_1 = buffer.anchor_before(1);
- let anchor_at_offset_2 = buffer.anchor_before(2);
-
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Equal
- );
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Equal
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Equal
- );
-
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Less
- );
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Less
- );
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Less
- );
-
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Greater
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Greater
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Greater
- );
-}
-
-#[test]
-fn test_anchors_at_start_and_end() {
- let mut buffer = TextBuffer::new(0, 0, History::new("".into()));
- let before_start_anchor = buffer.anchor_before(0);
- let after_end_anchor = buffer.anchor_after(0);
-
- buffer.edit(vec![0..0], "abc");
- assert_eq!(buffer.text(), "abc");
- assert_eq!(before_start_anchor.to_offset(&buffer), 0);
- assert_eq!(after_end_anchor.to_offset(&buffer), 3);
-
- let after_start_anchor = buffer.anchor_after(0);
- let before_end_anchor = buffer.anchor_before(3);
-
- buffer.edit(vec![3..3], "def");
- buffer.edit(vec![0..0], "ghi");
- assert_eq!(buffer.text(), "ghiabcdef");
- assert_eq!(before_start_anchor.to_offset(&buffer), 0);
- assert_eq!(after_start_anchor.to_offset(&buffer), 3);
- assert_eq!(before_end_anchor.to_offset(&buffer), 6);
- assert_eq!(after_end_anchor.to_offset(&buffer), 9);
-}
-
-#[gpui::test]
-async fn test_apply_diff(mut cx: gpui::TestAppContext) {
- let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
- let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-
- let text = "a\nccc\ndddd\nffffff\n";
- let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
- buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
- cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
-
- let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
- let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
- buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
- cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
-}
-
-#[test]
-fn test_undo_redo() {
- let mut buffer = TextBuffer::new(0, 0, History::new("1234".into()));
- // Set group interval to zero so as to not group edits in the undo stack.
- buffer.history.group_interval = Duration::from_secs(0);
-
- buffer.edit(vec![1..1], "abx");
- buffer.edit(vec![3..4], "yzef");
- buffer.edit(vec![3..5], "cd");
- assert_eq!(buffer.text(), "1abcdef234");
-
- let transactions = buffer.history.undo_stack.clone();
- assert_eq!(transactions.len(), 3);
-
- buffer.undo_or_redo(transactions[0].clone()).unwrap();
- assert_eq!(buffer.text(), "1cdef234");
- buffer.undo_or_redo(transactions[0].clone()).unwrap();
- assert_eq!(buffer.text(), "1abcdef234");
-
- buffer.undo_or_redo(transactions[1].clone()).unwrap();
- assert_eq!(buffer.text(), "1abcdx234");
- buffer.undo_or_redo(transactions[2].clone()).unwrap();
- assert_eq!(buffer.text(), "1abx234");
- buffer.undo_or_redo(transactions[1].clone()).unwrap();
- assert_eq!(buffer.text(), "1abyzef234");
- buffer.undo_or_redo(transactions[2].clone()).unwrap();
- assert_eq!(buffer.text(), "1abcdef234");
-
- buffer.undo_or_redo(transactions[2].clone()).unwrap();
- assert_eq!(buffer.text(), "1abyzef234");
- buffer.undo_or_redo(transactions[0].clone()).unwrap();
- assert_eq!(buffer.text(), "1yzef234");
- buffer.undo_or_redo(transactions[1].clone()).unwrap();
- assert_eq!(buffer.text(), "1234");
-}
-
-#[test]
-fn test_history() {
- let mut now = Instant::now();
- let mut buffer = TextBuffer::new(0, 0, History::new("123456".into()));
-
- let set_id = if let Operation::UpdateSelections { set_id, .. } =
- buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap())
- {
- set_id
- } else {
- unreachable!()
- };
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer.edit(vec![2..4], "cd");
- buffer.end_transaction_at(Some(set_id), now).unwrap();
- assert_eq!(buffer.text(), "12cd56");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer
- .update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3]).unwrap())
- .unwrap();
- buffer.edit(vec![4..5], "e");
- buffer.end_transaction_at(Some(set_id), now).unwrap();
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
- now += buffer.history.group_interval + Duration::from_millis(1);
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer
- .update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2]).unwrap())
- .unwrap();
- buffer.edit(vec![0..1], "a");
- buffer.edit(vec![1..1], "b");
- buffer.end_transaction_at(Some(set_id), now).unwrap();
- assert_eq!(buffer.text(), "ab2cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
-
- // Last transaction happened past the group interval, undo it on its
- // own.
- buffer.undo();
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
- // First two transactions happened within the group interval, undo them
- // together.
- buffer.undo();
- assert_eq!(buffer.text(), "123456");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
- // Redo the first two transactions together.
- buffer.redo();
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
- // Redo the last transaction on its own.
- buffer.redo();
- assert_eq!(buffer.text(), "ab2cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
-
- buffer.start_transaction_at(None, now).unwrap();
- assert!(buffer.end_transaction_at(None, now).is_none());
- buffer.undo();
- assert_eq!(buffer.text(), "12cde6");
-}
-
-#[test]
-fn test_concurrent_edits() {
- let text = "abcdef";
-
- let mut buffer1 = TextBuffer::new(1, 0, History::new(text.into()));
- let mut buffer2 = TextBuffer::new(2, 0, History::new(text.into()));
- let mut buffer3 = TextBuffer::new(3, 0, History::new(text.into()));
-
- let buf1_op = buffer1.edit(vec![1..2], "12");
- assert_eq!(buffer1.text(), "a12cdef");
- let buf2_op = buffer2.edit(vec![3..4], "34");
- assert_eq!(buffer2.text(), "abc34ef");
- let buf3_op = buffer3.edit(vec![5..6], "56");
- assert_eq!(buffer3.text(), "abcde56");
-
- buffer1.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
- buffer1.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
- buffer2.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
- buffer2.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
- buffer3.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
- buffer3.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
-
- assert_eq!(buffer1.text(), "a12c34e56");
- assert_eq!(buffer2.text(), "a12c34e56");
- assert_eq!(buffer3.text(), "a12c34e56");
-}
-
-#[gpui::test(iterations = 100)]
-fn test_random_concurrent_edits(mut rng: StdRng) {
- let peers = env::var("PEERS")
- .map(|i| i.parse().expect("invalid `PEERS` variable"))
- .unwrap_or(5);
- let operations = env::var("OPERATIONS")
- .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
- .unwrap_or(10);
-
- let base_text_len = rng.gen_range(0..10);
- let base_text = RandomCharIter::new(&mut rng)
- .take(base_text_len)
- .collect::<String>();
- let mut replica_ids = Vec::new();
- let mut buffers = Vec::new();
- let mut network = Network::new(rng.clone());
-
- for i in 0..peers {
- let mut buffer = TextBuffer::new(i as ReplicaId, 0, History::new(base_text.clone().into()));
- buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200));
- buffers.push(buffer);
- replica_ids.push(i as u16);
- network.add_peer(i as u16);
- }
-
- log::info!("initial text: {:?}", base_text);
-
- let mut mutation_count = operations;
- loop {
- let replica_index = rng.gen_range(0..peers);
- let replica_id = replica_ids[replica_index];
- let buffer = &mut buffers[replica_index];
- match rng.gen_range(0..=100) {
- 0..=50 if mutation_count != 0 => {
- let ops = buffer.randomly_mutate(&mut rng);
- network.broadcast(buffer.replica_id, ops);
- log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text());
- mutation_count -= 1;
- }
- 51..=70 if mutation_count != 0 => {
- let ops = buffer.randomly_undo_redo(&mut rng);
- network.broadcast(buffer.replica_id, ops);
- mutation_count -= 1;
- }
- 71..=100 if network.has_unreceived(replica_id) => {
- let ops = network.receive(replica_id);
- if !ops.is_empty() {
- log::info!(
- "peer {} applying {} ops from the network.",
- replica_id,
- ops.len()
- );
- buffer.apply_ops(ops).unwrap();
- }
- }
- _ => {}
- }
-
- if mutation_count == 0 && network.is_idle() {
- break;
- }
- }
-
- let first_buffer = &buffers[0];
- for buffer in &buffers[1..] {
- assert_eq!(
- buffer.text(),
- first_buffer.text(),
- "Replica {} text != Replica 0 text",
- buffer.replica_id
- );
- assert_eq!(
- buffer.selection_sets().collect::<HashMap<_, _>>(),
- first_buffer.selection_sets().collect::<HashMap<_, _>>()
- );
- assert_eq!(
- buffer.all_selection_ranges().collect::<HashMap<_, _>>(),
- first_buffer
- .all_selection_ranges()
- .collect::<HashMap<_, _>>()
- );
- }
-}
-
-#[derive(Clone)]
-struct Envelope<T: Clone> {
- message: T,
- sender: ReplicaId,
-}
-
-struct Network<T: Clone, R: rand::Rng> {
- inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
- all_messages: Vec<T>,
- rng: R,
-}
-
-impl<T: Clone, R: rand::Rng> Network<T, R> {
- fn new(rng: R) -> Self {
- Network {
- inboxes: Default::default(),
- all_messages: Vec::new(),
- rng,
- }
- }
-
- fn add_peer(&mut self, id: ReplicaId) {
- self.inboxes.insert(id, Vec::new());
- }
-
- fn is_idle(&self) -> bool {
- self.inboxes.values().all(|i| i.is_empty())
- }
-
- fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
- for (replica, inbox) in self.inboxes.iter_mut() {
- if *replica != sender {
- for message in &messages {
- let min_index = inbox
- .iter()
- .enumerate()
- .rev()
- .find_map(|(index, envelope)| {
- if sender == envelope.sender {
- Some(index + 1)
- } else {
- None
- }
- })
- .unwrap_or(0);
-
- // Insert one or more duplicates of this message *after* the previous
- // message delivered by this replica.
- for _ in 0..self.rng.gen_range(1..4) {
- let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
- inbox.insert(
- insertion_index,
- Envelope {
- message: message.clone(),
- sender,
- },
- );
- }
- }
- }
- }
- self.all_messages.extend(messages);
- }
-
- fn has_unreceived(&self, receiver: ReplicaId) -> bool {
- !self.inboxes[&receiver].is_empty()
- }
-
- fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
- let inbox = self.inboxes.get_mut(&receiver).unwrap();
- let count = self.rng.gen_range(0..inbox.len() + 1);
- inbox
- .drain(0..count)
- .map(|envelope| envelope.message)
- .collect()
- }
-}
@@ -10,6 +10,7 @@ test-support = ["buffer/test-support", "gpui/test-support"]
buffer = { path = "../buffer" }
clock = { path = "../clock" }
gpui = { path = "../gpui" }
+language = { path = "../language" }
sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
util = { path = "../util" }
@@ -24,6 +25,7 @@ smol = "1.2"
[dev-dependencies]
buffer = { path = "../buffer", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rand = "0.8"
unindent = "0.1.7"
@@ -2,9 +2,9 @@ mod fold_map;
mod tab_map;
mod wrap_map;
-use buffer::{Anchor, Buffer, Point, ToOffset, ToPoint};
use fold_map::{FoldMap, ToFoldPoint as _};
use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle};
+use language::{Anchor, Buffer, Point, ToOffset, ToPoint};
use std::ops::Range;
use sum_tree::Bias;
use tab_map::TabMap;
@@ -109,7 +109,7 @@ impl DisplayMap {
}
pub struct DisplayMapSnapshot {
- buffer_snapshot: buffer::Snapshot,
+ buffer_snapshot: language::Snapshot,
folds_snapshot: fold_map::Snapshot,
tabs_snapshot: tab_map::Snapshot,
wraps_snapshot: wrap_map::Snapshot,
@@ -358,8 +358,8 @@ impl ToDisplayPoint for Anchor {
mod tests {
use super::*;
use crate::{movement, test::*};
- use buffer::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
use gpui::{color::Color, MutableAppContext};
+ use language::{History, Language, LanguageConfig, RandomCharIter, SelectionGoal};
use rand::{prelude::StdRng, Rng};
use std::{env, sync::Arc};
use theme::SyntaxTheme;
@@ -436,7 +436,7 @@ mod tests {
}
}
_ => {
- buffer.update(&mut cx, |buffer, cx| buffer.randomly_edit(&mut rng, 5));
+ buffer.update(&mut cx, |buffer, _| buffer.randomly_edit(&mut rng, 5));
}
}
@@ -1,5 +1,5 @@
-use buffer::{Anchor, AnchorRangeExt, Buffer, HighlightId, Point, TextSummary, ToOffset};
use gpui::{AppContext, ModelHandle};
+use language::{Anchor, AnchorRangeExt, Buffer, HighlightId, Point, TextSummary, ToOffset};
use parking_lot::Mutex;
use std::{
cmp::{self, Ordering},
@@ -485,7 +485,7 @@ impl FoldMap {
pub struct Snapshot {
transforms: SumTree<Transform>,
folds: SumTree<Fold>,
- buffer_snapshot: buffer::Snapshot,
+ buffer_snapshot: language::Snapshot,
pub version: usize,
}
@@ -994,7 +994,7 @@ impl<'a> Iterator for Chunks<'a> {
pub struct HighlightedChunks<'a> {
transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>,
- buffer_chunks: buffer::HighlightedChunks<'a>,
+ buffer_chunks: language::HighlightedChunks<'a>,
buffer_chunk: Option<(usize, &'a str, HighlightId)>,
buffer_offset: usize,
}
@@ -1331,7 +1331,7 @@ mod tests {
snapshot_edits.extend(map.randomly_mutate(&mut rng, cx.as_ref()));
}
_ => {
- let edits = buffer.update(cx, |buffer, cx| {
+ let edits = buffer.update(cx, |buffer, _| {
let start_version = buffer.version.clone();
let edit_count = rng.gen_range(1..=5);
buffer.randomly_edit(&mut rng, edit_count);
@@ -1,5 +1,5 @@
use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
-use buffer::{rope, HighlightId};
+use language::{rope, HighlightId};
use parking_lot::Mutex;
use std::{mem, ops::Range};
use sum_tree::Bias;
@@ -2,8 +2,8 @@ use super::{
fold_map,
tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary},
};
-use buffer::{HighlightId, Point};
use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task};
+use language::{HighlightId, Point};
use lazy_static::lazy_static;
use smol::future::yield_now;
use std::{collections::VecDeque, ops::Range, time::Duration};
@@ -899,7 +899,7 @@ mod tests {
display_map::{fold_map::FoldMap, tab_map::TabMap},
test::Observer,
};
- use buffer::{Buffer, RandomCharIter};
+ use language::{Buffer, RandomCharIter};
use rand::prelude::*;
use std::env;
@@ -990,7 +990,7 @@ mod tests {
}
}
_ => {
- buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng));
+ buffer.update(&mut cx, |buffer, _| buffer.randomly_mutate(&mut rng));
}
}
@@ -2,7 +2,6 @@ use super::{
DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
SelectPhase, Snapshot, MAX_LINE_LEN,
};
-use buffer::HighlightId;
use clock::ReplicaId;
use gpui::{
color::Color,
@@ -18,6 +17,7 @@ use gpui::{
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
};
use json::json;
+use language::HighlightId;
use smallvec::SmallVec;
use std::{
cmp::{self, Ordering},
@@ -1043,7 +1043,7 @@ mod tests {
test::sample_text,
{Editor, EditorSettings},
};
- use buffer::Buffer;
+ use language::Buffer;
#[gpui::test]
fn test_layout_line_numbers(cx: &mut gpui::MutableAppContext) {
@@ -5,7 +5,6 @@ pub mod movement;
#[cfg(test)]
mod test;
-use buffer::*;
use clock::ReplicaId;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -15,6 +14,7 @@ use gpui::{
text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle,
};
+use language::*;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use smol::Timer;
@@ -2661,17 +2661,17 @@ impl Editor {
fn on_buffer_event(
&mut self,
_: ModelHandle<Buffer>,
- event: &buffer::Event,
+ event: &language::Event,
cx: &mut ViewContext<Self>,
) {
match event {
- buffer::Event::Edited => cx.emit(Event::Edited),
- buffer::Event::Dirtied => cx.emit(Event::Dirtied),
- buffer::Event::Saved => cx.emit(Event::Saved),
- buffer::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged),
- buffer::Event::Reloaded => cx.emit(Event::FileHandleChanged),
- buffer::Event::Closed => cx.emit(Event::Closed),
- buffer::Event::Reparsed => {}
+ language::Event::Edited => cx.emit(Event::Edited),
+ language::Event::Dirtied => cx.emit(Event::Dirtied),
+ language::Event::Saved => cx.emit(Event::Saved),
+ language::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged),
+ language::Event::Reloaded => cx.emit(Event::FileHandleChanged),
+ language::Event::Closed => cx.emit(Event::Closed),
+ language::Event::Reparsed => {}
}
}
@@ -0,0 +1,35 @@
+[package]
+name = "language"
+version = "0.1.0"
+edition = "2018"
+
+[features]
+test-support = ["rand"]
+
+[dependencies]
+buffer = { path = "../buffer" }
+clock = { path = "../clock" }
+gpui = { path = "../gpui" }
+rpc = { path = "../rpc" }
+sum_tree = { path = "../sum_tree" }
+theme = { path = "../theme" }
+anyhow = "1.0.38"
+arrayvec = "0.7.1"
+lazy_static = "1.4"
+log = "0.4"
+parking_lot = "0.11.1"
+rand = { version = "0.8.3", optional = true }
+seahash = "4.1"
+serde = { version = "1", features = ["derive"] }
+similar = "1.3"
+smallvec = { version = "1.6", features = ["union"] }
+smol = "1.2"
+tree-sitter = "0.19.5"
+
+[dev-dependencies]
+buffer = { path = "../buffer", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+
+rand = "0.8.3"
+tree-sitter-rust = "0.19.0"
+unindent = "0.1.7"
@@ -0,0 +1,1471 @@
+mod highlight_map;
+mod language;
+#[cfg(test)]
+mod tests;
+
+pub use self::{
+ highlight_map::{HighlightId, HighlightMap},
+ language::{BracketPair, Language, LanguageConfig, LanguageRegistry},
+};
+use anyhow::{anyhow, Result};
+pub use buffer::*;
+use clock::ReplicaId;
+use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task};
+use lazy_static::lazy_static;
+use parking_lot::Mutex;
+use rpc::proto;
+use similar::{ChangeTag, TextDiff};
+use smol::future::yield_now;
+use std::{
+ any::Any,
+ cell::RefCell,
+ cmp,
+ collections::{BTreeMap, HashMap, HashSet},
+ ffi::OsString,
+ future::Future,
+ iter::Iterator,
+ ops::{Deref, DerefMut, Range},
+ path::{Path, PathBuf},
+ str,
+ sync::Arc,
+ time::{Duration, Instant, SystemTime, UNIX_EPOCH},
+};
+use sum_tree::Bias;
+use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};
+
+thread_local! {
+ static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
+}
+
+lazy_static! {
+ static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
+}
+
+// TODO - Make this configurable
+const INDENT_SIZE: u32 = 4;
+
+pub struct Buffer {
+ buffer: TextBuffer,
+ file: Option<Box<dyn File>>,
+ saved_version: clock::Global,
+ saved_mtime: SystemTime,
+ language: Option<Arc<Language>>,
+ autoindent_requests: Vec<Arc<AutoindentRequest>>,
+ pending_autoindent: Option<Task<()>>,
+ sync_parse_timeout: Duration,
+ syntax_tree: Mutex<Option<SyntaxTree>>,
+ parsing_in_background: bool,
+ parse_count: usize,
+ #[cfg(test)]
+ operations: Vec<Operation>,
+}
+
+pub struct Snapshot {
+ text: buffer::Snapshot,
+ tree: Option<Tree>,
+ is_parsing: bool,
+ language: Option<Arc<Language>>,
+ query_cursor: QueryCursorHandle,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Event {
+ Edited,
+ Dirtied,
+ Saved,
+ FileHandleChanged,
+ Reloaded,
+ Reparsed,
+ Closed,
+}
+
+pub trait File {
+ fn worktree_id(&self) -> usize;
+
+ fn entry_id(&self) -> Option<usize>;
+
+ fn set_entry_id(&mut self, entry_id: Option<usize>);
+
+ fn mtime(&self) -> SystemTime;
+
+ fn set_mtime(&mut self, mtime: SystemTime);
+
+ fn path(&self) -> &Arc<Path>;
+
+ fn set_path(&mut self, path: Arc<Path>);
+
+ fn full_path(&self, cx: &AppContext) -> PathBuf;
+
+ /// Returns the last component of this handle's absolute path. If this handle refers to the root
+ /// of its worktree, then this method will return the name of the worktree itself.
+ fn file_name<'a>(&'a self, cx: &'a AppContext) -> Option<OsString>;
+
+ fn is_deleted(&self) -> bool;
+
+ fn save(
+ &self,
+ buffer_id: u64,
+ text: Rope,
+ version: clock::Global,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<(clock::Global, SystemTime)>>;
+
+ fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
+
+ fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
+
+ fn boxed_clone(&self) -> Box<dyn File>;
+
+ fn as_any(&self) -> &dyn Any;
+}
+
+struct QueryCursorHandle(Option<QueryCursor>);
+
+#[derive(Clone)]
+struct SyntaxTree {
+ tree: Tree,
+ version: clock::Global,
+}
+
+#[derive(Clone)]
+struct AutoindentRequest {
+ selection_set_ids: HashSet<SelectionSetId>,
+ before_edit: Snapshot,
+ edited: AnchorSet,
+ inserted: Option<AnchorRangeSet>,
+}
+
+#[derive(Debug)]
+struct IndentSuggestion {
+ basis_row: u32,
+ indent: bool,
+}
+
+struct TextProvider<'a>(&'a Rope);
+
+struct Highlights<'a> {
+ captures: tree_sitter::QueryCaptures<'a, 'a, TextProvider<'a>>,
+ next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>,
+ stack: Vec<(usize, HighlightId)>,
+ highlight_map: HighlightMap,
+}
+
+pub struct HighlightedChunks<'a> {
+ range: Range<usize>,
+ chunks: Chunks<'a>,
+ highlights: Option<Highlights<'a>>,
+}
+
+struct Diff {
+ base_version: clock::Global,
+ new_text: Arc<str>,
+ changes: Vec<(ChangeTag, usize)>,
+}
+
+impl Buffer {
+ pub fn new<T: Into<Arc<str>>>(
+ replica_id: ReplicaId,
+ base_text: T,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ Self::build(
+ TextBuffer::new(
+ replica_id,
+ cx.model_id() as u64,
+ History::new(base_text.into()),
+ ),
+ None,
+ None,
+ cx,
+ )
+ }
+
+ pub fn from_history(
+ replica_id: ReplicaId,
+ history: History,
+ file: Option<Box<dyn File>>,
+ language: Option<Arc<Language>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ Self::build(
+ TextBuffer::new(replica_id, cx.model_id() as u64, history),
+ file,
+ language,
+ cx,
+ )
+ }
+
+ pub fn from_proto(
+ replica_id: ReplicaId,
+ message: proto::Buffer,
+ file: Option<Box<dyn File>>,
+ language: Option<Arc<Language>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<Self> {
+ Ok(Self::build(
+ TextBuffer::from_proto(replica_id, message)?,
+ file,
+ language,
+ cx,
+ ))
+ }
+
+ fn build(
+ buffer: TextBuffer,
+ file: Option<Box<dyn File>>,
+ language: Option<Arc<Language>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Self {
+ let saved_mtime;
+ if let Some(file) = file.as_ref() {
+ saved_mtime = file.mtime();
+ } else {
+ saved_mtime = UNIX_EPOCH;
+ }
+
+ let mut result = Self {
+ buffer,
+ saved_mtime,
+ saved_version: clock::Global::new(),
+ file,
+ syntax_tree: Mutex::new(None),
+ parsing_in_background: false,
+ parse_count: 0,
+ sync_parse_timeout: Duration::from_millis(1),
+ autoindent_requests: Default::default(),
+ pending_autoindent: Default::default(),
+ language,
+
+ #[cfg(test)]
+ operations: Default::default(),
+ };
+ result.reparse(cx);
+ result
+ }
+
+ pub fn snapshot(&self) -> Snapshot {
+ Snapshot {
+ text: self.buffer.snapshot(),
+ tree: self.syntax_tree(),
+ is_parsing: self.parsing_in_background,
+ language: self.language.clone(),
+ query_cursor: QueryCursorHandle::new(),
+ }
+ }
+
+ pub fn file(&self) -> Option<&dyn File> {
+ self.file.as_deref()
+ }
+
+ pub fn file_mut(&mut self) -> Option<&mut dyn File> {
+ self.file.as_mut().map(|f| f.deref_mut() as &mut dyn File)
+ }
+
+ pub fn save(
+ &mut self,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
+ let file = self
+ .file
+ .as_ref()
+ .ok_or_else(|| anyhow!("buffer has no file"))?;
+ let text = self.as_rope().clone();
+ let version = self.version.clone();
+ let save = file.save(self.remote_id(), text, version, cx.as_mut());
+ Ok(cx.spawn(|this, mut cx| async move {
+ let (version, mtime) = save.await?;
+ this.update(&mut cx, |this, cx| {
+ this.did_save(version.clone(), mtime, None, cx);
+ });
+ Ok((version, mtime))
+ }))
+ }
+
+ pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
+ self.language = language;
+ self.reparse(cx);
+ }
+
+ pub fn did_save(
+ &mut self,
+ version: clock::Global,
+ mtime: SystemTime,
+ new_file: Option<Box<dyn File>>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ self.saved_mtime = mtime;
+ self.saved_version = version;
+ if let Some(new_file) = new_file {
+ self.file = Some(new_file);
+ }
+ cx.emit(Event::Saved);
+ }
+
+ pub fn file_updated(
+ &mut self,
+ path: Arc<Path>,
+ mtime: SystemTime,
+ new_text: Option<String>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ let file = self.file.as_mut().unwrap();
+ let mut changed = false;
+ if path != *file.path() {
+ file.set_path(path);
+ changed = true;
+ }
+
+ if mtime != file.mtime() {
+ file.set_mtime(mtime);
+ changed = true;
+ if let Some(new_text) = new_text {
+ if self.version == self.saved_version {
+ cx.spawn(|this, mut cx| async move {
+ let diff = this
+ .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
+ .await;
+ this.update(&mut cx, |this, cx| {
+ if this.apply_diff(diff, cx) {
+ this.saved_version = this.version.clone();
+ this.saved_mtime = mtime;
+ cx.emit(Event::Reloaded);
+ }
+ });
+ })
+ .detach();
+ }
+ }
+ }
+
+ if changed {
+ cx.emit(Event::FileHandleChanged);
+ }
+ }
+
+ pub fn file_deleted(&mut self, cx: &mut ModelContext<Self>) {
+ if self.version == self.saved_version {
+ cx.emit(Event::Dirtied);
+ }
+ cx.emit(Event::FileHandleChanged);
+ }
+
+ pub fn close(&mut self, cx: &mut ModelContext<Self>) {
+ cx.emit(Event::Closed);
+ }
+
+ pub fn language(&self) -> Option<&Arc<Language>> {
+ self.language.as_ref()
+ }
+
+ pub fn parse_count(&self) -> usize {
+ self.parse_count
+ }
+
+ fn syntax_tree(&self) -> Option<Tree> {
+ if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() {
+ self.interpolate_tree(syntax_tree);
+ Some(syntax_tree.tree.clone())
+ } else {
+ None
+ }
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn is_parsing(&self) -> bool {
+ self.parsing_in_background
+ }
+
+ #[cfg(test)]
+ pub fn set_sync_parse_timeout(&mut self, timeout: Duration) {
+ self.sync_parse_timeout = timeout;
+ }
+
+ fn reparse(&mut self, cx: &mut ModelContext<Self>) -> bool {
+ if self.parsing_in_background {
+ return false;
+ }
+
+ if let Some(language) = self.language.clone() {
+ let old_tree = self.syntax_tree();
+ let text = self.as_rope().clone();
+ let parsed_version = self.version();
+ let parse_task = cx.background().spawn({
+ let language = language.clone();
+ async move { Self::parse_text(&text, old_tree, &language) }
+ });
+
+ match cx
+ .background()
+ .block_with_timeout(self.sync_parse_timeout, parse_task)
+ {
+ Ok(new_tree) => {
+ self.did_finish_parsing(new_tree, parsed_version, cx);
+ return true;
+ }
+ Err(parse_task) => {
+ self.parsing_in_background = true;
+ cx.spawn(move |this, mut cx| async move {
+ let new_tree = parse_task.await;
+ this.update(&mut cx, move |this, cx| {
+ let language_changed =
+ this.language.as_ref().map_or(true, |curr_language| {
+ !Arc::ptr_eq(curr_language, &language)
+ });
+ let parse_again = this.version > parsed_version || language_changed;
+ this.parsing_in_background = false;
+ this.did_finish_parsing(new_tree, parsed_version, cx);
+
+ if parse_again && this.reparse(cx) {
+ return;
+ }
+ });
+ })
+ .detach();
+ }
+ }
+ }
+ false
+ }
+
+ fn parse_text(text: &Rope, old_tree: Option<Tree>, language: &Language) -> Tree {
+ PARSER.with(|parser| {
+ let mut parser = parser.borrow_mut();
+ parser
+ .set_language(language.grammar)
+ .expect("incompatible grammar");
+ let mut chunks = text.chunks_in_range(0..text.len());
+ let tree = parser
+ .parse_with(
+ &mut move |offset, _| {
+ chunks.seek(offset);
+ chunks.next().unwrap_or("").as_bytes()
+ },
+ old_tree.as_ref(),
+ )
+ .unwrap();
+ tree
+ })
+ }
+
+ fn interpolate_tree(&self, tree: &mut SyntaxTree) {
+ let mut delta = 0_isize;
+ for edit in self.edits_since(tree.version.clone()) {
+ let start_offset = (edit.old_bytes.start as isize + delta) as usize;
+ let start_point = self.as_rope().to_point(start_offset);
+ tree.tree.edit(&InputEdit {
+ start_byte: start_offset,
+ old_end_byte: start_offset + edit.deleted_bytes(),
+ new_end_byte: start_offset + edit.inserted_bytes(),
+ start_position: start_point.into(),
+ old_end_position: (start_point + edit.deleted_lines()).into(),
+ new_end_position: self
+ .as_rope()
+ .to_point(start_offset + edit.inserted_bytes())
+ .into(),
+ });
+ delta += edit.inserted_bytes() as isize - edit.deleted_bytes() as isize;
+ }
+ tree.version = self.version();
+ }
+
+ fn did_finish_parsing(
+ &mut self,
+ tree: Tree,
+ version: clock::Global,
+ cx: &mut ModelContext<Self>,
+ ) {
+ self.parse_count += 1;
+ *self.syntax_tree.lock() = Some(SyntaxTree { tree, version });
+ self.request_autoindent(cx);
+ cx.emit(Event::Reparsed);
+ cx.notify();
+ }
+
+ 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);
+ match cx
+ .background()
+ .block_with_timeout(Duration::from_micros(500), indent_columns)
+ {
+ Ok(indent_columns) => self.apply_autoindents(indent_columns, cx),
+ Err(indent_columns) => {
+ self.pending_autoindent = Some(cx.spawn(|this, mut cx| async move {
+ let indent_columns = indent_columns.await;
+ this.update(&mut cx, |this, cx| {
+ this.apply_autoindents(indent_columns, cx);
+ });
+ }));
+ }
+ }
+ }
+ }
+
+ fn compute_autoindents(&self) -> Option<impl Future<Output = BTreeMap<u32, u32>>> {
+ let max_rows_between_yields = 100;
+ let snapshot = self.snapshot();
+ if snapshot.language.is_none()
+ || snapshot.tree.is_none()
+ || self.autoindent_requests.is_empty()
+ {
+ return None;
+ }
+
+ let autoindent_requests = self.autoindent_requests.clone();
+ Some(async move {
+ let mut indent_columns = BTreeMap::new();
+ for request in autoindent_requests {
+ let old_to_new_rows = request
+ .edited
+ .to_points(&request.before_edit)
+ .map(|point| point.row)
+ .zip(request.edited.to_points(&snapshot).map(|point| point.row))
+ .collect::<BTreeMap<u32, u32>>();
+
+ let mut old_suggestions = HashMap::<u32, u32>::default();
+ let old_edited_ranges =
+ contiguous_ranges(old_to_new_rows.keys().copied(), max_rows_between_yields);
+ for old_edited_range in old_edited_ranges {
+ let suggestions = request
+ .before_edit
+ .suggest_autoindents(old_edited_range.clone())
+ .into_iter()
+ .flatten();
+ for (old_row, suggestion) in old_edited_range.zip(suggestions) {
+ let indentation_basis = old_to_new_rows
+ .get(&suggestion.basis_row)
+ .and_then(|from_row| old_suggestions.get(from_row).copied())
+ .unwrap_or_else(|| {
+ request
+ .before_edit
+ .indent_column_for_line(suggestion.basis_row)
+ });
+ let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+ old_suggestions.insert(
+ *old_to_new_rows.get(&old_row).unwrap(),
+ indentation_basis + delta,
+ );
+ }
+ yield_now().await;
+ }
+
+ // At this point, old_suggestions contains the suggested indentation for all edited lines with respect to the state of the
+ // buffer before the edit, but keyed by the row for these lines after the edits were applied.
+ let new_edited_row_ranges =
+ contiguous_ranges(old_to_new_rows.values().copied(), max_rows_between_yields);
+ for new_edited_row_range in new_edited_row_ranges {
+ let suggestions = snapshot
+ .suggest_autoindents(new_edited_row_range.clone())
+ .into_iter()
+ .flatten();
+ for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
+ let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+ let new_indentation = indent_columns
+ .get(&suggestion.basis_row)
+ .copied()
+ .unwrap_or_else(|| {
+ snapshot.indent_column_for_line(suggestion.basis_row)
+ })
+ + delta;
+ if old_suggestions
+ .get(&new_row)
+ .map_or(true, |old_indentation| new_indentation != *old_indentation)
+ {
+ indent_columns.insert(new_row, new_indentation);
+ }
+ }
+ yield_now().await;
+ }
+
+ if let Some(inserted) = request.inserted.as_ref() {
+ let inserted_row_ranges = contiguous_ranges(
+ inserted
+ .to_point_ranges(&snapshot)
+ .flat_map(|range| range.start.row..range.end.row + 1),
+ max_rows_between_yields,
+ );
+ for inserted_row_range in inserted_row_ranges {
+ let suggestions = snapshot
+ .suggest_autoindents(inserted_row_range.clone())
+ .into_iter()
+ .flatten();
+ for (row, suggestion) in inserted_row_range.zip(suggestions) {
+ let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
+ let new_indentation = indent_columns
+ .get(&suggestion.basis_row)
+ .copied()
+ .unwrap_or_else(|| {
+ snapshot.indent_column_for_line(suggestion.basis_row)
+ })
+ + delta;
+ indent_columns.insert(row, new_indentation);
+ }
+ yield_now().await;
+ }
+ }
+ }
+ indent_columns
+ })
+ }
+
+ fn apply_autoindents(
+ &mut self,
+ indent_columns: BTreeMap<u32, u32>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ let selection_set_ids = self
+ .autoindent_requests
+ .drain(..)
+ .flat_map(|req| req.selection_set_ids.clone())
+ .collect::<HashSet<_>>();
+
+ self.start_transaction(selection_set_ids.iter().copied())
+ .unwrap();
+ for (row, indent_column) in &indent_columns {
+ self.set_indent_column_for_line(*row, *indent_column, cx);
+ }
+
+ for selection_set_id in &selection_set_ids {
+ if let Ok(set) = self.selection_set(*selection_set_id) {
+ let new_selections = set
+ .selections
+ .iter()
+ .map(|selection| {
+ let start_point = selection.start.to_point(&self.buffer);
+ if start_point.column == 0 {
+ let end_point = selection.end.to_point(&self.buffer);
+ let delta = Point::new(
+ 0,
+ indent_columns.get(&start_point.row).copied().unwrap_or(0),
+ );
+ if delta.column > 0 {
+ return Selection {
+ id: selection.id,
+ goal: selection.goal,
+ reversed: selection.reversed,
+ start: self
+ .anchor_at(start_point + delta, selection.start.bias),
+ end: self.anchor_at(end_point + delta, selection.end.bias),
+ };
+ }
+ }
+ selection.clone()
+ })
+ .collect::<Arc<[_]>>();
+ self.update_selection_set(*selection_set_id, new_selections, cx)
+ .unwrap();
+ }
+ }
+
+ self.end_transaction(selection_set_ids.iter().copied(), cx)
+ .unwrap();
+ }
+
+ pub fn indent_column_for_line(&self, row: u32) -> u32 {
+ self.content().indent_column_for_line(row)
+ }
+
+ fn set_indent_column_for_line(&mut self, row: u32, column: u32, cx: &mut ModelContext<Self>) {
+ let current_column = self.indent_column_for_line(row);
+ if column > current_column {
+ let offset = Point::new(row, 0).to_offset(&*self);
+ self.edit(
+ [offset..offset],
+ " ".repeat((column - current_column) as usize),
+ cx,
+ );
+ } else if column < current_column {
+ self.edit(
+ [Point::new(row, 0)..Point::new(row, current_column - column)],
+ "",
+ cx,
+ );
+ }
+ }
+
+ pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
+ if let Some(tree) = self.syntax_tree() {
+ let root = tree.root_node();
+ let range = range.start.to_offset(self)..range.end.to_offset(self);
+ let mut node = root.descendant_for_byte_range(range.start, range.end);
+ while node.map_or(false, |n| n.byte_range() == range) {
+ node = node.unwrap().parent();
+ }
+ node.map(|n| n.byte_range())
+ } else {
+ None
+ }
+ }
+
+ pub fn enclosing_bracket_ranges<T: ToOffset>(
+ &self,
+ range: Range<T>,
+ ) -> Option<(Range<usize>, Range<usize>)> {
+ let (lang, tree) = self.language.as_ref().zip(self.syntax_tree())?;
+ let open_capture_ix = lang.brackets_query.capture_index_for_name("open")?;
+ let close_capture_ix = lang.brackets_query.capture_index_for_name("close")?;
+
+ // Find bracket pairs that *inclusively* contain the given range.
+ let range = range.start.to_offset(self).saturating_sub(1)..range.end.to_offset(self) + 1;
+ let mut cursor = QueryCursorHandle::new();
+ let matches = cursor.set_byte_range(range).matches(
+ &lang.brackets_query,
+ tree.root_node(),
+ TextProvider(self.as_rope()),
+ );
+
+ // Get the ranges of the innermost pair of brackets.
+ matches
+ .filter_map(|mat| {
+ let open = mat.nodes_for_capture_index(open_capture_ix).next()?;
+ let close = mat.nodes_for_capture_index(close_capture_ix).next()?;
+ Some((open.byte_range(), close.byte_range()))
+ })
+ .min_by_key(|(open_range, close_range)| close_range.end - open_range.start)
+ }
+
+ fn diff(&self, new_text: Arc<str>, cx: &AppContext) -> Task<Diff> {
+ // TODO: it would be nice to not allocate here.
+ let old_text = self.text();
+ let base_version = self.version();
+ cx.background().spawn(async move {
+ let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
+ .iter_all_changes()
+ .map(|c| (c.tag(), c.value().len()))
+ .collect::<Vec<_>>();
+ Diff {
+ base_version,
+ new_text,
+ changes,
+ }
+ })
+ }
+
+ pub fn set_text_from_disk(&self, new_text: Arc<str>, cx: &mut ModelContext<Self>) -> Task<()> {
+ cx.spawn(|this, mut cx| async move {
+ let diff = this
+ .read_with(&cx, |this, cx| this.diff(new_text, cx))
+ .await;
+
+ this.update(&mut cx, |this, cx| {
+ if this.apply_diff(diff, cx) {
+ this.saved_version = this.version.clone();
+ }
+ });
+ })
+ }
+
+ fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
+ if self.version == diff.base_version {
+ self.start_transaction(None).unwrap();
+ let mut offset = 0;
+ for (tag, len) in diff.changes {
+ let range = offset..(offset + len);
+ match tag {
+ ChangeTag::Equal => offset += len,
+ ChangeTag::Delete => self.edit(Some(range), "", cx),
+ ChangeTag::Insert => {
+ self.edit(Some(offset..offset), &diff.new_text[range], cx);
+ offset += len;
+ }
+ }
+ }
+ self.end_transaction(None, cx).unwrap();
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_dirty(&self) -> bool {
+ self.version > self.saved_version
+ || self.file.as_ref().map_or(false, |file| file.is_deleted())
+ }
+
+ pub fn has_conflict(&self) -> bool {
+ self.version > self.saved_version
+ && self
+ .file
+ .as_ref()
+ .map_or(false, |file| file.mtime() > self.saved_mtime)
+ }
+
+ pub fn start_transaction(
+ &mut self,
+ selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+ ) -> Result<()> {
+ self.start_transaction_at(selection_set_ids, Instant::now())
+ }
+
+ fn start_transaction_at(
+ &mut self,
+ selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+ now: Instant,
+ ) -> Result<()> {
+ self.buffer.start_transaction_at(selection_set_ids, now)
+ }
+
+ pub fn end_transaction(
+ &mut self,
+ selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ self.end_transaction_at(selection_set_ids, Instant::now(), cx)
+ }
+
+ fn end_transaction_at(
+ &mut self,
+ selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
+ now: Instant,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ if let Some(start_version) = self.buffer.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);
+ }
+ }
+ Ok(())
+ }
+
+ pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
+ where
+ I: IntoIterator<Item = Range<S>>,
+ S: ToOffset,
+ T: Into<String>,
+ {
+ self.edit_internal(ranges_iter, new_text, false, cx)
+ }
+
+ pub fn edit_with_autoindent<I, S, T>(
+ &mut self,
+ ranges_iter: I,
+ new_text: T,
+ cx: &mut ModelContext<Self>,
+ ) where
+ I: IntoIterator<Item = Range<S>>,
+ S: ToOffset,
+ T: Into<String>,
+ {
+ self.edit_internal(ranges_iter, new_text, true, cx)
+ }
+
+ pub fn edit_internal<I, S, T>(
+ &mut self,
+ ranges_iter: I,
+ new_text: T,
+ autoindent: bool,
+ cx: &mut ModelContext<Self>,
+ ) where
+ I: IntoIterator<Item = Range<S>>,
+ S: ToOffset,
+ T: Into<String>,
+ {
+ let new_text = new_text.into();
+
+ // Skip invalid ranges and coalesce contiguous ones.
+ let mut ranges: Vec<Range<usize>> = Vec::new();
+ for range in ranges_iter {
+ let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
+ if !new_text.is_empty() || !range.is_empty() {
+ if let Some(prev_range) = ranges.last_mut() {
+ if prev_range.end >= range.start {
+ prev_range.end = cmp::max(prev_range.end, range.end);
+ } else {
+ ranges.push(range);
+ }
+ } else {
+ ranges.push(range);
+ }
+ }
+ }
+ if ranges.is_empty() {
+ return;
+ }
+
+ self.start_transaction(None).unwrap();
+ self.pending_autoindent.take();
+ let autoindent_request = if autoindent && self.language.is_some() {
+ let before_edit = self.snapshot();
+ let edited = self.content().anchor_set(ranges.iter().filter_map(|range| {
+ let start = range.start.to_point(&*self);
+ if new_text.starts_with('\n') && start.column == self.line_len(start.row) {
+ None
+ } else {
+ Some((range.start, Bias::Left))
+ }
+ }));
+ Some((before_edit, edited))
+ } else {
+ None
+ };
+
+ let first_newline_ix = new_text.find('\n');
+ let new_text_len = new_text.len();
+
+ let edit = self.buffer.edit(ranges.iter().cloned(), new_text);
+
+ if let Some((before_edit, edited)) = autoindent_request {
+ let mut inserted = None;
+ if let Some(first_newline_ix) = first_newline_ix {
+ let mut delta = 0isize;
+ inserted = Some(self.content().anchor_range_set(ranges.iter().map(|range| {
+ let start = (delta + range.start as isize) as usize + first_newline_ix + 1;
+ let end = (delta + range.start as isize) as usize + new_text_len;
+ delta += (range.end as isize - range.start as isize) + new_text_len as isize;
+ (start, Bias::Left)..(end, Bias::Right)
+ })));
+ }
+
+ let selection_set_ids = self
+ .buffer
+ .peek_undo_stack()
+ .unwrap()
+ .starting_selection_set_ids()
+ .collect();
+ self.autoindent_requests.push(Arc::new(AutoindentRequest {
+ selection_set_ids,
+ before_edit,
+ edited,
+ inserted,
+ }));
+ }
+
+ self.end_transaction(None, cx).unwrap();
+ self.send_operation(Operation::Edit(edit), cx);
+ }
+
+ fn did_edit(&self, was_dirty: bool, cx: &mut ModelContext<Self>) {
+ cx.emit(Event::Edited);
+ if !was_dirty {
+ cx.emit(Event::Dirtied);
+ }
+ }
+
+ pub fn add_selection_set(
+ &mut self,
+ selections: impl Into<Arc<[Selection]>>,
+ cx: &mut ModelContext<Self>,
+ ) -> SelectionSetId {
+ let operation = self.buffer.add_selection_set(selections);
+ if let Operation::UpdateSelections { set_id, .. } = &operation {
+ let set_id = *set_id;
+ cx.notify();
+ self.send_operation(operation, cx);
+ set_id
+ } else {
+ unreachable!()
+ }
+ }
+
+ pub fn update_selection_set(
+ &mut self,
+ set_id: SelectionSetId,
+ selections: impl Into<Arc<[Selection]>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let operation = self.buffer.update_selection_set(set_id, selections)?;
+ cx.notify();
+ self.send_operation(operation, cx);
+ Ok(())
+ }
+
+ pub fn set_active_selection_set(
+ &mut self,
+ set_id: Option<SelectionSetId>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let operation = self.buffer.set_active_selection_set(set_id)?;
+ self.send_operation(operation, cx);
+ Ok(())
+ }
+
+ pub fn remove_selection_set(
+ &mut self,
+ set_id: SelectionSetId,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let operation = self.buffer.remove_selection_set(set_id)?;
+ cx.notify();
+ self.send_operation(operation, cx);
+ Ok(())
+ }
+
+ pub fn apply_ops<I: IntoIterator<Item = Operation>>(
+ &mut self,
+ ops: I,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ self.pending_autoindent.take();
+
+ let was_dirty = self.is_dirty();
+ let old_version = self.version.clone();
+
+ self.buffer.apply_ops(ops)?;
+
+ cx.notify();
+ if self.edits_since(old_version).next().is_some() {
+ self.did_edit(was_dirty, cx);
+ self.reparse(cx);
+ }
+
+ Ok(())
+ }
+
+ #[cfg(not(test))]
+ pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext<Self>) {
+ if let Some(file) = &self.file {
+ file.buffer_updated(self.remote_id(), operation, cx.as_mut());
+ }
+ }
+
+ #[cfg(test)]
+ pub fn send_operation(&mut self, operation: Operation, _: &mut ModelContext<Self>) {
+ self.operations.push(operation);
+ }
+
+ pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
+ self.buffer.remove_peer(replica_id);
+ cx.notify();
+ }
+
+ pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
+ let was_dirty = self.is_dirty();
+ let old_version = self.version.clone();
+
+ for operation in self.buffer.undo() {
+ 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);
+ }
+ }
+
+ pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
+ let was_dirty = self.is_dirty();
+ let old_version = self.version.clone();
+
+ for operation in self.buffer.redo() {
+ 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);
+ }
+ }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+impl Buffer {
+ pub fn randomly_edit<T>(&mut self, rng: &mut T, old_range_count: usize)
+ where
+ T: rand::Rng,
+ {
+ self.buffer.randomly_edit(rng, old_range_count);
+ }
+
+ pub fn randomly_mutate<T>(&mut self, rng: &mut T)
+ where
+ T: rand::Rng,
+ {
+ self.buffer.randomly_mutate(rng);
+ }
+}
+
+impl Entity for Buffer {
+ type Event = Event;
+
+ fn release(&mut self, cx: &mut gpui::MutableAppContext) {
+ if let Some(file) = self.file.as_ref() {
+ file.buffer_removed(self.remote_id(), cx);
+ }
+ }
+}
+
+impl Clone for Buffer {
+ fn clone(&self) -> Self {
+ Self {
+ buffer: self.buffer.clone(),
+ saved_version: self.saved_version.clone(),
+ saved_mtime: self.saved_mtime,
+ file: self.file.as_ref().map(|f| f.boxed_clone()),
+ language: self.language.clone(),
+ syntax_tree: Mutex::new(self.syntax_tree.lock().clone()),
+ parsing_in_background: false,
+ sync_parse_timeout: self.sync_parse_timeout,
+ parse_count: self.parse_count,
+ autoindent_requests: Default::default(),
+ pending_autoindent: Default::default(),
+
+ #[cfg(test)]
+ operations: self.operations.clone(),
+ }
+ }
+}
+
+impl Deref for Buffer {
+ type Target = TextBuffer;
+
+ fn deref(&self) -> &Self::Target {
+ &self.buffer
+ }
+}
+
+impl<'a> From<&'a Buffer> for Content<'a> {
+ fn from(buffer: &'a Buffer) -> Self {
+ Self::from(&buffer.buffer)
+ }
+}
+
+impl<'a> From<&'a mut Buffer> for Content<'a> {
+ fn from(buffer: &'a mut Buffer) -> Self {
+ Self::from(&buffer.buffer)
+ }
+}
+
+impl<'a> From<&'a Snapshot> for Content<'a> {
+ fn from(snapshot: &'a Snapshot) -> Self {
+ Self::from(&snapshot.text)
+ }
+}
+
+impl Snapshot {
+ fn suggest_autoindents<'a>(
+ &'a self,
+ row_range: Range<u32>,
+ ) -> Option<impl Iterator<Item = IndentSuggestion> + 'a> {
+ let mut query_cursor = QueryCursorHandle::new();
+ if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
+ let prev_non_blank_row = self.prev_non_blank_row(row_range.start);
+
+ // Get the "indentation ranges" that intersect this row range.
+ let indent_capture_ix = language.indents_query.capture_index_for_name("indent");
+ let end_capture_ix = language.indents_query.capture_index_for_name("end");
+ query_cursor.set_point_range(
+ Point::new(prev_non_blank_row.unwrap_or(row_range.start), 0).into()
+ ..Point::new(row_range.end, 0).into(),
+ );
+ let mut indentation_ranges = Vec::<(Range<Point>, &'static str)>::new();
+ for mat in query_cursor.matches(
+ &language.indents_query,
+ tree.root_node(),
+ TextProvider(self.as_rope()),
+ ) {
+ let mut node_kind = "";
+ let mut start: Option<Point> = None;
+ let mut end: Option<Point> = None;
+ for capture in mat.captures {
+ if Some(capture.index) == indent_capture_ix {
+ node_kind = capture.node.kind();
+ start.get_or_insert(capture.node.start_position().into());
+ end.get_or_insert(capture.node.end_position().into());
+ } else if Some(capture.index) == end_capture_ix {
+ end = Some(capture.node.start_position().into());
+ }
+ }
+
+ if let Some((start, end)) = start.zip(end) {
+ if start.row == end.row {
+ continue;
+ }
+
+ let range = start..end;
+ match indentation_ranges.binary_search_by_key(&range.start, |r| r.0.start) {
+ Err(ix) => indentation_ranges.insert(ix, (range, node_kind)),
+ Ok(ix) => {
+ let prev_range = &mut indentation_ranges[ix];
+ prev_range.0.end = prev_range.0.end.max(range.end);
+ }
+ }
+ }
+ }
+
+ let mut prev_row = prev_non_blank_row.unwrap_or(0);
+ Some(row_range.map(move |row| {
+ let row_start = Point::new(row, self.indent_column_for_line(row));
+
+ let mut indent_from_prev_row = false;
+ let mut outdent_to_row = u32::MAX;
+ for (range, _node_kind) in &indentation_ranges {
+ if range.start.row >= row {
+ break;
+ }
+
+ if range.start.row == prev_row && range.end > row_start {
+ indent_from_prev_row = true;
+ }
+ if range.end.row >= prev_row && range.end <= row_start {
+ outdent_to_row = outdent_to_row.min(range.start.row);
+ }
+ }
+
+ let suggestion = if outdent_to_row == prev_row {
+ IndentSuggestion {
+ basis_row: prev_row,
+ indent: false,
+ }
+ } else if indent_from_prev_row {
+ IndentSuggestion {
+ basis_row: prev_row,
+ indent: true,
+ }
+ } else if outdent_to_row < prev_row {
+ IndentSuggestion {
+ basis_row: outdent_to_row,
+ indent: false,
+ }
+ } else {
+ IndentSuggestion {
+ basis_row: prev_row,
+ indent: false,
+ }
+ };
+
+ prev_row = row;
+ suggestion
+ }))
+ } else {
+ None
+ }
+ }
+
+ fn prev_non_blank_row(&self, mut row: u32) -> Option<u32> {
+ while row > 0 {
+ row -= 1;
+ if !self.is_line_blank(row) {
+ return Some(row);
+ }
+ }
+ None
+ }
+
+ fn is_line_blank(&self, row: u32) -> bool {
+ self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
+ .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
+ }
+
+ pub fn highlighted_text_for_range<T: ToOffset>(
+ &mut self,
+ range: Range<T>,
+ ) -> HighlightedChunks {
+ let range = range.start.to_offset(&*self)..range.end.to_offset(&*self);
+ let chunks = self.text.as_rope().chunks_in_range(range.clone());
+ if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) {
+ let captures = self.query_cursor.set_byte_range(range.clone()).captures(
+ &language.highlights_query,
+ tree.root_node(),
+ TextProvider(self.text.as_rope()),
+ );
+
+ HighlightedChunks {
+ range,
+ chunks,
+ highlights: Some(Highlights {
+ captures,
+ next_capture: None,
+ stack: Default::default(),
+ highlight_map: language.highlight_map(),
+ }),
+ }
+ } else {
+ HighlightedChunks {
+ range,
+ chunks,
+ highlights: None,
+ }
+ }
+ }
+}
+
+impl Clone for Snapshot {
+ fn clone(&self) -> Self {
+ Self {
+ text: self.text.clone(),
+ tree: self.tree.clone(),
+ is_parsing: self.is_parsing,
+ language: self.language.clone(),
+ query_cursor: QueryCursorHandle::new(),
+ }
+ }
+}
+
+impl Deref for Snapshot {
+ type Target = buffer::Snapshot;
+
+ fn deref(&self) -> &Self::Target {
+ &self.text
+ }
+}
+
+impl<'a> tree_sitter::TextProvider<'a> for TextProvider<'a> {
+ type I = ByteChunks<'a>;
+
+ fn text(&mut self, node: tree_sitter::Node) -> Self::I {
+ ByteChunks(self.0.chunks_in_range(node.byte_range()))
+ }
+}
+
+struct ByteChunks<'a>(rope::Chunks<'a>);
+
+impl<'a> Iterator for ByteChunks<'a> {
+ type Item = &'a [u8];
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(str::as_bytes)
+ }
+}
+
+impl<'a> HighlightedChunks<'a> {
+ pub fn seek(&mut self, offset: usize) {
+ self.range.start = offset;
+ self.chunks.seek(self.range.start);
+ if let Some(highlights) = self.highlights.as_mut() {
+ highlights
+ .stack
+ .retain(|(end_offset, _)| *end_offset > offset);
+ if let Some((mat, capture_ix)) = &highlights.next_capture {
+ let capture = mat.captures[*capture_ix as usize];
+ if offset >= capture.node.start_byte() {
+ let next_capture_end = capture.node.end_byte();
+ if offset < next_capture_end {
+ highlights.stack.push((
+ next_capture_end,
+ highlights.highlight_map.get(capture.index),
+ ));
+ }
+ highlights.next_capture.take();
+ }
+ }
+ highlights.captures.set_byte_range(self.range.clone());
+ }
+ }
+
+ pub fn offset(&self) -> usize {
+ self.range.start
+ }
+}
+
+impl<'a> Iterator for HighlightedChunks<'a> {
+ type Item = (&'a str, HighlightId);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut next_capture_start = usize::MAX;
+
+ if let Some(highlights) = self.highlights.as_mut() {
+ while let Some((parent_capture_end, _)) = highlights.stack.last() {
+ if *parent_capture_end <= self.range.start {
+ highlights.stack.pop();
+ } else {
+ break;
+ }
+ }
+
+ if highlights.next_capture.is_none() {
+ highlights.next_capture = highlights.captures.next();
+ }
+
+ while let Some((mat, capture_ix)) = highlights.next_capture.as_ref() {
+ let capture = mat.captures[*capture_ix as usize];
+ if self.range.start < capture.node.start_byte() {
+ next_capture_start = capture.node.start_byte();
+ break;
+ } else {
+ let style_id = highlights.highlight_map.get(capture.index);
+ highlights.stack.push((capture.node.end_byte(), style_id));
+ highlights.next_capture = highlights.captures.next();
+ }
+ }
+ }
+
+ if let Some(chunk) = self.chunks.peek() {
+ let chunk_start = self.range.start;
+ let mut chunk_end = (self.chunks.offset() + chunk.len()).min(next_capture_start);
+ let mut style_id = HighlightId::default();
+ if let Some((parent_capture_end, parent_style_id)) =
+ self.highlights.as_ref().and_then(|h| h.stack.last())
+ {
+ chunk_end = chunk_end.min(*parent_capture_end);
+ style_id = *parent_style_id;
+ }
+
+ let slice =
+ &chunk[chunk_start - self.chunks.offset()..chunk_end - self.chunks.offset()];
+ self.range.start = chunk_end;
+ if self.range.start == self.chunks.offset() + chunk.len() {
+ self.chunks.next().unwrap();
+ }
+
+ Some((slice, style_id))
+ } else {
+ None
+ }
+ }
+}
+
+impl QueryCursorHandle {
+ fn new() -> Self {
+ QueryCursorHandle(Some(
+ QUERY_CURSORS
+ .lock()
+ .pop()
+ .unwrap_or_else(|| QueryCursor::new()),
+ ))
+ }
+}
+
+impl Deref for QueryCursorHandle {
+ type Target = QueryCursor;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_ref().unwrap()
+ }
+}
+
+impl DerefMut for QueryCursorHandle {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.as_mut().unwrap()
+ }
+}
+
+impl Drop for QueryCursorHandle {
+ fn drop(&mut self) {
+ let mut cursor = self.0.take().unwrap();
+ cursor.set_byte_range(0..usize::MAX);
+ cursor.set_point_range(Point::zero().into()..Point::MAX.into());
+ QUERY_CURSORS.lock().push(cursor)
+ }
+}
+
+fn contiguous_ranges(
+ values: impl IntoIterator<Item = u32>,
+ max_len: usize,
+) -> impl Iterator<Item = Range<u32>> {
+ let mut values = values.into_iter();
+ let mut current_range: Option<Range<u32>> = None;
+ std::iter::from_fn(move || loop {
+ if let Some(value) = values.next() {
+ if let Some(range) = &mut current_range {
+ if value == range.end && range.len() < max_len {
+ range.end += 1;
+ continue;
+ }
+ }
+
+ let prev_range = current_range.clone();
+ current_range = Some(value..(value + 1));
+ if prev_range.is_some() {
+ return prev_range;
+ }
+ } else {
+ return current_range.take();
+ }
+ })
+}
@@ -1,7 +1,81 @@
-use crate::*;
+use super::*;
use gpui::{ModelHandle, MutableAppContext};
+use std::rc::Rc;
use unindent::Unindent as _;
+#[gpui::test]
+fn test_edit_events(cx: &mut gpui::MutableAppContext) {
+ let mut now = Instant::now();
+ let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
+ let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
+
+ let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
+ let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
+ let buffer_ops = buffer1.update(cx, |buffer, cx| {
+ let buffer_1_events = buffer_1_events.clone();
+ cx.subscribe(&buffer1, move |_, _, event, _| {
+ buffer_1_events.borrow_mut().push(event.clone())
+ })
+ .detach();
+ let buffer_2_events = buffer_2_events.clone();
+ cx.subscribe(&buffer2, move |_, _, event, _| {
+ buffer_2_events.borrow_mut().push(event.clone())
+ })
+ .detach();
+
+ // An edit emits an edited event, followed by a dirtied event,
+ // since the buffer was previously in a clean state.
+ buffer.edit(Some(2..4), "XYZ", cx);
+
+ // An empty transaction does not emit any events.
+ buffer.start_transaction(None).unwrap();
+ buffer.end_transaction(None, cx).unwrap();
+
+ // A transaction containing two edits emits one edited event.
+ now += Duration::from_secs(1);
+ buffer.start_transaction_at(None, now).unwrap();
+ buffer.edit(Some(5..5), "u", cx);
+ buffer.edit(Some(6..6), "w", cx);
+ buffer.end_transaction_at(None, now, cx).unwrap();
+
+ // Undoing a transaction emits one edited event.
+ buffer.undo(cx);
+
+ buffer.operations.clone()
+ });
+
+ // Incorporating a set of remote ops emits a single edited event,
+ // followed by a dirtied event.
+ buffer2.update(cx, |buffer, cx| {
+ buffer.apply_ops(buffer_ops, cx).unwrap();
+ });
+
+ let buffer_1_events = buffer_1_events.borrow();
+ assert_eq!(
+ *buffer_1_events,
+ vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
+ );
+
+ let buffer_2_events = buffer_2_events.borrow();
+ assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
+}
+
+#[gpui::test]
+async fn test_apply_diff(mut cx: gpui::TestAppContext) {
+ let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
+ let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
+
+ let text = "a\nccc\ndddd\nffffff\n";
+ let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
+ buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
+ cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
+
+ let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
+ let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
+ buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
+ cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
+}
+
#[gpui::test]
async fn test_reparse(mut cx: gpui::TestAppContext) {
let buffer = cx.add_model(|cx| {
@@ -12,6 +12,7 @@ clock = { path = "../clock" }
fsevent = { path = "../fsevent" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
+language = { path = "../language" }
client = { path = "../client" }
sum_tree = { path = "../sum_tree" }
util = { path = "../util" }
@@ -33,6 +34,7 @@ toml = "0.5"
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
@@ -3,11 +3,11 @@ mod ignore;
mod worktree;
use anyhow::Result;
-use buffer::LanguageRegistry;
use client::Client;
use futures::Future;
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
+use language::LanguageRegistry;
use std::{
path::Path,
sync::{atomic::AtomicBool, Arc},
@@ -302,9 +302,9 @@ impl Entity for Project {
#[cfg(test)]
mod tests {
use super::*;
- use buffer::LanguageRegistry;
use fs::RealFs;
use gpui::TestAppContext;
+ use language::LanguageRegistry;
use serde_json::json;
use std::{os::unix, path::PathBuf};
use util::test::temp_tree;
@@ -4,7 +4,6 @@ use super::{
};
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
use anyhow::{anyhow, Result};
-use buffer::{Buffer, History, LanguageRegistry, Operation, Rope};
use client::{proto, Client, PeerId, TypedEnvelope};
use clock::ReplicaId;
use futures::{Stream, StreamExt};
@@ -13,6 +12,7 @@ use gpui::{
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
Task, UpgradeModelHandle, WeakModelHandle,
};
+use language::{Buffer, History, LanguageRegistry, Operation, Rope};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use postage::{
@@ -627,14 +627,14 @@ impl Worktree {
file_changed = true;
} else if !file.is_deleted() {
if buffer_is_clean {
- cx.emit(buffer::Event::Dirtied);
+ cx.emit(language::Event::Dirtied);
}
file.set_entry_id(None);
file_changed = true;
}
if file_changed {
- cx.emit(buffer::Event::FileHandleChanged);
+ cx.emit(language::Event::FileHandleChanged);
}
}
});
@@ -862,7 +862,7 @@ impl LocalWorktree {
.update(&mut cx, |this, cx| this.as_local().unwrap().load(&path, cx))
.await?;
let language = this.read_with(&cx, |this, cx| {
- use buffer::File;
+ use language::File;
this.languages()
.select_language(file.full_path(cx))
@@ -909,7 +909,7 @@ impl LocalWorktree {
.insert(buffer.id() as u64, buffer.clone());
Ok(proto::OpenBufferResponse {
- buffer: Some(buffer.update(cx.as_mut(), |buffer, cx| buffer.to_proto(cx))),
+ buffer: Some(buffer.update(cx.as_mut(), |buffer, _| buffer.to_proto())),
})
})
})
@@ -1279,7 +1279,7 @@ impl RemoteWorktree {
.ok_or_else(|| anyhow!("worktree was closed"))?;
let file = File::new(entry.id, this.clone(), entry.path, entry.mtime);
let language = this.read_with(&cx, |this, cx| {
- use buffer::File;
+ use language::File;
this.languages()
.select_language(file.full_path(cx))
@@ -1790,7 +1790,7 @@ impl File {
}
}
-impl buffer::File for File {
+impl language::File for File {
fn worktree_id(&self) -> usize {
self.worktree.id()
}
@@ -1942,7 +1942,7 @@ impl buffer::File for File {
});
}
- fn boxed_clone(&self) -> Box<dyn buffer::File> {
+ fn boxed_clone(&self) -> Box<dyn language::File> {
Box::new(self.clone())
}
@@ -3268,7 +3268,7 @@ mod tests {
assert!(buffer.is_dirty());
assert_eq!(
*events.borrow(),
- &[buffer::Event::Edited, buffer::Event::Dirtied]
+ &[language::Event::Edited, language::Event::Dirtied]
);
events.borrow_mut().clear();
buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
@@ -3277,7 +3277,7 @@ mod tests {
// after saving, the buffer is not dirty, and emits a saved event.
buffer1.update(&mut cx, |buffer, cx| {
assert!(!buffer.is_dirty());
- assert_eq!(*events.borrow(), &[buffer::Event::Saved]);
+ assert_eq!(*events.borrow(), &[language::Event::Saved]);
events.borrow_mut().clear();
buffer.edit(vec![1..1], "B", cx);
@@ -3291,9 +3291,9 @@ mod tests {
assert_eq!(
*events.borrow(),
&[
- buffer::Event::Edited,
- buffer::Event::Dirtied,
- buffer::Event::Edited
+ language::Event::Edited,
+ language::Event::Dirtied,
+ language::Event::Edited
],
);
events.borrow_mut().clear();
@@ -3305,7 +3305,7 @@ mod tests {
assert!(buffer.is_dirty());
});
- assert_eq!(*events.borrow(), &[buffer::Event::Edited]);
+ assert_eq!(*events.borrow(), &[language::Event::Edited]);
// When a file is deleted, the buffer is considered dirty.
let events = Rc::new(RefCell::new(Vec::new()));
@@ -3325,7 +3325,7 @@ mod tests {
buffer2.condition(&cx, |b, _| b.is_dirty()).await;
assert_eq!(
*events.borrow(),
- &[buffer::Event::Dirtied, buffer::Event::FileHandleChanged]
+ &[language::Event::Dirtied, language::Event::FileHandleChanged]
);
// When a file is already dirty when deleted, we don't emit a Dirtied event.
@@ -3351,7 +3351,7 @@ mod tests {
buffer3
.condition(&cx, |_, _| !events.borrow().is_empty())
.await;
- assert_eq!(*events.borrow(), &[buffer::Event::FileHandleChanged]);
+ assert_eq!(*events.borrow(), &[language::Event::FileHandleChanged]);
cx.read(|cx| assert!(buffer3.read(cx).is_dirty()));
}
@@ -976,13 +976,13 @@ mod tests {
time::Duration,
};
use zed::{
- buffer::LanguageRegistry,
client::{
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
EstablishConnectionError, UserStore,
},
editor::{Editor, EditorSettings, Input},
fs::{FakeFs, Fs as _},
+ language::LanguageRegistry,
people_panel::JoinWorktree,
project::{ProjectPath, Worktree},
workspace::{Workspace, WorkspaceParams},
@@ -8,7 +8,7 @@ test-support = [
"client/test-support",
"project/test-support",
"tree-sitter",
- "tree-sitter-rust"
+ "tree-sitter-rust",
]
[dependencies]
@@ -16,6 +16,7 @@ buffer = { path = "../buffer" }
client = { path = "../client" }
editor = { path = "../editor" }
gpui = { path = "../gpui" }
+language = { path = "../language" }
project = { path = "../project" }
theme = { path = "../theme" }
anyhow = "1.0.38"
@@ -1,9 +1,9 @@
use super::{Item, ItemView};
use crate::Settings;
use anyhow::Result;
-use buffer::{Buffer, File as _};
use editor::{Editor, EditorSettings, Event};
use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
+use language::{Buffer, File as _};
use postage::watch;
use project::{ProjectPath, Worktree};
use std::path::Path;
@@ -5,7 +5,7 @@ pub mod settings;
pub mod sidebar;
use anyhow::Result;
-use buffer::{Buffer, LanguageRegistry};
+use language::{Buffer, LanguageRegistry};
use client::{Authenticate, ChannelList, Client, UserStore};
use gpui::{
action, elements::*, json::to_string_pretty, keymap::Binding, platform::CursorStyle,
@@ -271,8 +271,8 @@ impl WorkspaceParams {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut MutableAppContext) -> Self {
let mut languages = LanguageRegistry::new();
- languages.add(Arc::new(buffer::Language::new(
- buffer::LanguageConfig {
+ languages.add(Arc::new(language::Language::new(
+ language::LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
..Default::default()
@@ -18,6 +18,7 @@ test-support = [
"buffer/test-support",
"client/test-support",
"gpui/test-support",
+ "language/test-support",
"project/test-support",
"rpc/test-support",
"tempdir",
@@ -33,6 +34,7 @@ fuzzy = { path = "../fuzzy" }
editor = { path = "../editor" }
file_finder = { path = "../file_finder" }
gpui = { path = "../gpui" }
+language = { path = "../language" }
people_panel = { path = "../people_panel" }
project = { path = "../project" }
project_panel = { path = "../project_panel" }
@@ -85,6 +87,7 @@ url = "2.2"
buffer = { path = "../buffer", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
client = { path = "../client", features = ["test-support"] }
@@ -1,4 +1,4 @@
-use buffer::{Language, LanguageRegistry};
+pub use language::{Language, LanguageRegistry};
use rust_embed::RustEmbed;
use std::borrow::Cow;
use std::{str, sync::Arc};
@@ -4,8 +4,7 @@ pub mod menus;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
-pub use buffer;
-use buffer::LanguageRegistry;
+use self::language::LanguageRegistry;
use chat_panel::ChatPanel;
pub use client;
pub use editor;
@@ -1,7 +1,7 @@
use crate::{assets::Assets, AppState};
-use buffer::LanguageRegistry;
use client::{http::ServerResponse, test::FakeHttpClient, ChannelList, Client, UserStore};
use gpui::{AssetSource, MutableAppContext};
+use language::LanguageRegistry;
use parking_lot::Mutex;
use postage::watch;
use project::fs::FakeFs;