mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 13:21:43 +03:00
Merge pull request #13 from zed-industries/undo-stack
Undo/redo support
This commit is contained in:
commit
a0a004737d
@ -1,11 +1,13 @@
|
||||
mod anchor;
|
||||
mod point;
|
||||
mod selection;
|
||||
mod text;
|
||||
|
||||
pub use anchor::*;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
pub use point::*;
|
||||
use seahash::SeaHasher;
|
||||
pub use selection::*;
|
||||
pub use text::*;
|
||||
|
||||
use crate::{
|
||||
@ -20,18 +22,17 @@ use gpui::{AppContext, Entity, ModelContext};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
cmp,
|
||||
hash::BuildHasher,
|
||||
iter::{self, Iterator},
|
||||
mem,
|
||||
ops::{AddAssign, Range},
|
||||
path::PathBuf,
|
||||
str,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub type SelectionSetId = time::Lamport;
|
||||
pub type SelectionsVersion = usize;
|
||||
const UNDO_GROUP_INTERVAL: Duration = Duration::from_millis(300);
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct DeterministicState;
|
||||
@ -60,12 +61,12 @@ pub struct Buffer {
|
||||
file: Option<FileHandle>,
|
||||
fragments: SumTree<Fragment>,
|
||||
insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
|
||||
edit_ops: HashMap<time::Local, EditOperation>,
|
||||
pub version: time::Global,
|
||||
saved_version: time::Global,
|
||||
last_edit: time::Local,
|
||||
undo_map: UndoMap,
|
||||
selections: HashMap<SelectionSetId, Vec<Selection>>,
|
||||
history: History,
|
||||
selections: HashMap<SelectionSetId, Arc<[Selection]>>,
|
||||
pub selections_last_update: SelectionsVersion,
|
||||
deferred_ops: OperationQueue<Operation>,
|
||||
deferred_replicas: HashSet<ReplicaId>,
|
||||
@ -79,15 +80,126 @@ pub struct Snapshot {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct History {
|
||||
pub base_text: String,
|
||||
struct Transaction {
|
||||
start: time::Global,
|
||||
buffer_was_dirty: bool,
|
||||
edits: Vec<time::Local>,
|
||||
selections_before: Option<(SelectionSetId, Arc<[Selection]>)>,
|
||||
selections_after: Option<(SelectionSetId, Arc<[Selection]>)>,
|
||||
first_edit_at: Instant,
|
||||
last_edit_at: Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Selection {
|
||||
pub start: Anchor,
|
||||
pub end: Anchor,
|
||||
pub reversed: bool,
|
||||
#[derive(Clone)]
|
||||
pub struct History {
|
||||
pub base_text: Arc<str>,
|
||||
ops: HashMap<time::Local, EditOperation>,
|
||||
undo_stack: Vec<Transaction>,
|
||||
redo_stack: Vec<Transaction>,
|
||||
transaction_depth: usize,
|
||||
group_interval: Duration,
|
||||
}
|
||||
|
||||
impl History {
|
||||
pub fn new(base_text: Arc<str>) -> Self {
|
||||
Self {
|
||||
base_text,
|
||||
ops: Default::default(),
|
||||
undo_stack: Vec::new(),
|
||||
redo_stack: Vec::new(),
|
||||
transaction_depth: 0,
|
||||
group_interval: UNDO_GROUP_INTERVAL,
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, op: EditOperation) {
|
||||
self.ops.insert(op.id, op);
|
||||
}
|
||||
|
||||
fn start_transaction(
|
||||
&mut self,
|
||||
start: time::Global,
|
||||
buffer_was_dirty: bool,
|
||||
selections: Option<(SelectionSetId, Arc<[Selection]>)>,
|
||||
now: Instant,
|
||||
) {
|
||||
self.transaction_depth += 1;
|
||||
if self.transaction_depth == 1 {
|
||||
self.undo_stack.push(Transaction {
|
||||
start,
|
||||
buffer_was_dirty,
|
||||
edits: Vec::new(),
|
||||
selections_before: selections,
|
||||
selections_after: None,
|
||||
first_edit_at: now,
|
||||
last_edit_at: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn end_transaction(
|
||||
&mut self,
|
||||
selections: Option<(SelectionSetId, Arc<[Selection]>)>,
|
||||
now: Instant,
|
||||
) -> Option<&Transaction> {
|
||||
assert_ne!(self.transaction_depth, 0);
|
||||
self.transaction_depth -= 1;
|
||||
if self.transaction_depth == 0 {
|
||||
let transaction = self.undo_stack.last_mut().unwrap();
|
||||
transaction.selections_after = selections;
|
||||
transaction.last_edit_at = now;
|
||||
Some(transaction)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn group(&mut self) {
|
||||
let mut new_len = self.undo_stack.len();
|
||||
let mut transactions = self.undo_stack.iter_mut();
|
||||
|
||||
if let Some(mut transaction) = transactions.next_back() {
|
||||
for prev_transaction in transactions.next_back() {
|
||||
if transaction.first_edit_at - prev_transaction.last_edit_at <= self.group_interval
|
||||
{
|
||||
prev_transaction.edits.append(&mut transaction.edits);
|
||||
prev_transaction.last_edit_at = transaction.last_edit_at;
|
||||
prev_transaction.selections_after = transaction.selections_after.take();
|
||||
transaction = prev_transaction;
|
||||
new_len -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.undo_stack.truncate(new_len);
|
||||
}
|
||||
|
||||
fn push_undo(&mut self, edit_id: time::Local) {
|
||||
assert_ne!(self.transaction_depth, 0);
|
||||
self.undo_stack.last_mut().unwrap().edits.push(edit_id);
|
||||
}
|
||||
|
||||
fn pop_undo(&mut self) -> Option<&Transaction> {
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
if let Some(transaction) = self.undo_stack.pop() {
|
||||
self.redo_stack.push(transaction);
|
||||
self.redo_stack.last()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_redo(&mut self) -> Option<&Transaction> {
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
if let Some(transaction) = self.redo_stack.pop() {
|
||||
self.undo_stack.push(transaction);
|
||||
self.undo_stack.last()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
@ -217,7 +329,7 @@ pub enum Operation {
|
||||
},
|
||||
UpdateSelections {
|
||||
set_id: SelectionSetId,
|
||||
selections: Option<Vec<Selection>>,
|
||||
selections: Option<Arc<[Selection]>>,
|
||||
lamport_timestamp: time::Lamport,
|
||||
},
|
||||
}
|
||||
@ -241,15 +353,15 @@ pub struct UndoOperation {
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new<T: Into<String>>(replica_id: ReplicaId, base_text: T) -> Self {
|
||||
Self::build(replica_id, None, base_text.into())
|
||||
pub fn new<T: Into<Arc<str>>>(replica_id: ReplicaId, base_text: T) -> Self {
|
||||
Self::build(replica_id, None, History::new(base_text.into()))
|
||||
}
|
||||
|
||||
pub fn from_history(replica_id: ReplicaId, file: FileHandle, history: History) -> Self {
|
||||
Self::build(replica_id, Some(file), history.base_text)
|
||||
Self::build(replica_id, Some(file), history)
|
||||
}
|
||||
|
||||
fn build(replica_id: ReplicaId, file: Option<FileHandle>, base_text: String) -> Self {
|
||||
fn build(replica_id: ReplicaId, file: Option<FileHandle>, history: History) -> Self {
|
||||
let mut insertion_splits = HashMap::default();
|
||||
let mut fragments = SumTree::new();
|
||||
|
||||
@ -257,7 +369,7 @@ impl Buffer {
|
||||
id: time::Local::default(),
|
||||
parent_id: time::Local::default(),
|
||||
offset_in_parent: 0,
|
||||
text: base_text.into(),
|
||||
text: history.base_text.clone().into(),
|
||||
lamport_timestamp: time::Lamport::default(),
|
||||
};
|
||||
|
||||
@ -302,11 +414,11 @@ impl Buffer {
|
||||
file,
|
||||
fragments,
|
||||
insertion_splits,
|
||||
edit_ops: HashMap::default(),
|
||||
version: time::Global::new(),
|
||||
saved_version: time::Global::new(),
|
||||
last_edit: time::Local::default(),
|
||||
undo_map: Default::default(),
|
||||
history,
|
||||
selections: HashMap::default(),
|
||||
selections_last_update: 0,
|
||||
deferred_ops: OperationQueue::new(),
|
||||
@ -488,6 +600,67 @@ impl Buffer {
|
||||
self.deferred_ops.len()
|
||||
}
|
||||
|
||||
pub fn start_transaction(&mut self, set_id: Option<SelectionSetId>) -> Result<()> {
|
||||
self.start_transaction_at(set_id, Instant::now())
|
||||
}
|
||||
|
||||
fn start_transaction_at(&mut self, set_id: Option<SelectionSetId>, now: Instant) -> Result<()> {
|
||||
let selections = if let Some(set_id) = set_id {
|
||||
let selections = self
|
||||
.selections
|
||||
.get(&set_id)
|
||||
.ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?;
|
||||
Some((set_id, selections.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.history
|
||||
.start_transaction(self.version.clone(), self.is_dirty(), selections, now);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn end_transaction(
|
||||
&mut self,
|
||||
set_id: Option<SelectionSetId>,
|
||||
ctx: Option<&mut ModelContext<Self>>,
|
||||
) -> Result<()> {
|
||||
self.end_transaction_at(set_id, Instant::now(), ctx)
|
||||
}
|
||||
|
||||
fn end_transaction_at(
|
||||
&mut self,
|
||||
set_id: Option<SelectionSetId>,
|
||||
now: Instant,
|
||||
ctx: Option<&mut ModelContext<Self>>,
|
||||
) -> Result<()> {
|
||||
let selections = if let Some(set_id) = set_id {
|
||||
let selections = self
|
||||
.selections
|
||||
.get(&set_id)
|
||||
.ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?;
|
||||
Some((set_id, selections.clone()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(transaction) = self.history.end_transaction(selections, now) {
|
||||
let since = transaction.start.clone();
|
||||
let was_dirty = transaction.buffer_was_dirty;
|
||||
self.history.group();
|
||||
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
|
||||
let changes = self.edits_since(since).collect::<Vec<_>>();
|
||||
if !changes.is_empty() {
|
||||
self.did_edit(changes, was_dirty, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit<I, S, T>(
|
||||
&mut self,
|
||||
old_ranges: I,
|
||||
@ -499,6 +672,8 @@ impl Buffer {
|
||||
S: ToOffset,
|
||||
T: Into<Text>,
|
||||
{
|
||||
self.start_transaction_at(None, Instant::now())?;
|
||||
|
||||
let new_text = new_text.into();
|
||||
let new_text = if new_text.len() > 0 {
|
||||
Some(new_text)
|
||||
@ -506,8 +681,6 @@ impl Buffer {
|
||||
None
|
||||
};
|
||||
|
||||
let was_dirty = self.is_dirty();
|
||||
let old_version = self.version.clone();
|
||||
let old_ranges = old_ranges
|
||||
.into_iter()
|
||||
.map(|range| Ok(range.start.to_offset(self)?..range.end.to_offset(self)?))
|
||||
@ -522,19 +695,12 @@ impl Buffer {
|
||||
|
||||
for op in &ops {
|
||||
if let Operation::Edit { edit, .. } = op {
|
||||
self.edit_ops.insert(edit.id, edit.clone());
|
||||
self.history.push(edit.clone());
|
||||
self.history.push_undo(edit.id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(op) = ops.last() {
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
let changes = self.edits_since(old_version).collect::<Vec<_>>();
|
||||
if !changes.is_empty() {
|
||||
self.did_edit(changes, was_dirty, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Operation::Edit { edit, .. } = op {
|
||||
self.last_edit = edit.id;
|
||||
self.version.observe(edit.id);
|
||||
@ -543,6 +709,8 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
self.end_transaction_at(None, Instant::now(), ctx)?;
|
||||
|
||||
Ok(ops)
|
||||
}
|
||||
|
||||
@ -597,45 +765,47 @@ impl Buffer {
|
||||
(old_ranges, new_text, operations)
|
||||
}
|
||||
|
||||
pub fn add_selection_set<I>(&mut self, ranges: I) -> Result<(SelectionSetId, Operation)>
|
||||
where
|
||||
I: IntoIterator<Item = Range<Point>>,
|
||||
{
|
||||
let selections = self.selections_from_ranges(ranges)?;
|
||||
pub fn add_selection_set(
|
||||
&mut self,
|
||||
selections: impl Into<Arc<[Selection]>>,
|
||||
ctx: Option<&mut ModelContext<Self>>,
|
||||
) -> (SelectionSetId, Operation) {
|
||||
let selections = selections.into();
|
||||
let lamport_timestamp = self.lamport_clock.tick();
|
||||
self.selections
|
||||
.insert(lamport_timestamp, selections.clone());
|
||||
.insert(lamport_timestamp, Arc::clone(&selections));
|
||||
self.selections_last_update += 1;
|
||||
|
||||
Ok((
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
(
|
||||
lamport_timestamp,
|
||||
Operation::UpdateSelections {
|
||||
set_id: lamport_timestamp,
|
||||
selections: Some(selections),
|
||||
lamport_timestamp,
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
pub fn replace_selection_set<I>(
|
||||
pub fn update_selection_set(
|
||||
&mut self,
|
||||
set_id: SelectionSetId,
|
||||
ranges: I,
|
||||
) -> Result<Operation>
|
||||
where
|
||||
I: IntoIterator<Item = Range<Point>>,
|
||||
{
|
||||
self.selections
|
||||
.remove(&set_id)
|
||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
||||
|
||||
let mut selections = self.selections_from_ranges(ranges)?;
|
||||
self.merge_selections(&mut selections);
|
||||
selections: impl Into<Arc<[Selection]>>,
|
||||
ctx: Option<&mut ModelContext<Self>>,
|
||||
) -> Result<Operation> {
|
||||
let selections = selections.into();
|
||||
self.selections.insert(set_id, selections.clone());
|
||||
|
||||
let lamport_timestamp = self.lamport_clock.tick();
|
||||
self.selections_last_update += 1;
|
||||
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
Ok(Operation::UpdateSelections {
|
||||
set_id,
|
||||
selections: Some(selections),
|
||||
@ -643,12 +813,21 @@ impl Buffer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_selection_set(&mut self, set_id: SelectionSetId) -> Result<Operation> {
|
||||
pub fn remove_selection_set(
|
||||
&mut self,
|
||||
set_id: SelectionSetId,
|
||||
ctx: Option<&mut ModelContext<Self>>,
|
||||
) -> Result<Operation> {
|
||||
self.selections
|
||||
.remove(&set_id)
|
||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
||||
let lamport_timestamp = self.lamport_clock.tick();
|
||||
self.selections_last_update += 1;
|
||||
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
Ok(Operation::UpdateSelections {
|
||||
set_id,
|
||||
selections: None,
|
||||
@ -656,81 +835,11 @@ impl Buffer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn selection_ranges<'a>(
|
||||
&'a self,
|
||||
set_id: SelectionSetId,
|
||||
) -> Result<impl Iterator<Item = Range<Point>> + 'a> {
|
||||
let selections = self
|
||||
.selections
|
||||
.get(&set_id)
|
||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?;
|
||||
Ok(selections.iter().map(move |selection| {
|
||||
let start = selection.start.to_point(self).unwrap();
|
||||
let end = selection.end.to_point(self).unwrap();
|
||||
if selection.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn all_selections(&self) -> impl Iterator<Item = (&SelectionSetId, &Vec<Selection>)> {
|
||||
self.selections.iter()
|
||||
}
|
||||
|
||||
pub fn all_selection_ranges<'a>(
|
||||
&'a self,
|
||||
) -> impl 'a + Iterator<Item = (SelectionSetId, Vec<Range<Point>>)> {
|
||||
pub fn selections(&self, set_id: SelectionSetId) -> Result<&[Selection]> {
|
||||
self.selections
|
||||
.keys()
|
||||
.map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap().collect()))
|
||||
}
|
||||
|
||||
fn merge_selections(&mut self, selections: &mut Vec<Selection>) {
|
||||
let mut new_selections = Vec::with_capacity(selections.len());
|
||||
{
|
||||
let mut old_selections = selections.drain(..);
|
||||
if let Some(mut prev_selection) = old_selections.next() {
|
||||
for selection in old_selections {
|
||||
if prev_selection.end.cmp(&selection.start, self).unwrap() >= Ordering::Equal {
|
||||
if selection.end.cmp(&prev_selection.end, self).unwrap() > Ordering::Equal {
|
||||
prev_selection.end = selection.end;
|
||||
}
|
||||
} else {
|
||||
new_selections.push(mem::replace(&mut prev_selection, selection));
|
||||
}
|
||||
}
|
||||
new_selections.push(prev_selection);
|
||||
}
|
||||
}
|
||||
*selections = new_selections;
|
||||
}
|
||||
|
||||
fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
|
||||
where
|
||||
I: IntoIterator<Item = Range<Point>>,
|
||||
{
|
||||
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 {
|
||||
if range.start > range.end {
|
||||
selections.push(Selection {
|
||||
start: self.anchor_before(range.end)?,
|
||||
end: self.anchor_before(range.start)?,
|
||||
reversed: true,
|
||||
});
|
||||
} else {
|
||||
selections.push(Selection {
|
||||
start: self.anchor_after(range.start)?,
|
||||
end: self.anchor_before(range.end)?,
|
||||
reversed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(selections)
|
||||
.get(&set_id)
|
||||
.map(|s| s.as_ref())
|
||||
.ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))
|
||||
}
|
||||
|
||||
pub fn apply_ops<I: IntoIterator<Item = Operation>>(
|
||||
@ -783,7 +892,7 @@ impl Buffer {
|
||||
lamport_timestamp,
|
||||
)?;
|
||||
self.version.observe(edit.id);
|
||||
self.edit_ops.insert(edit.id, edit);
|
||||
self.history.push(edit);
|
||||
}
|
||||
}
|
||||
Operation::Undo {
|
||||
@ -931,6 +1040,60 @@ impl Buffer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, mut ctx: Option<&mut ModelContext<Self>>) -> Vec<Operation> {
|
||||
let was_dirty = self.is_dirty();
|
||||
let old_version = self.version.clone();
|
||||
|
||||
let mut ops = Vec::new();
|
||||
if let Some(transaction) = self.history.pop_undo() {
|
||||
let selections = transaction.selections_before.clone();
|
||||
for edit_id in transaction.edits.clone() {
|
||||
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||
}
|
||||
|
||||
if let Some((set_id, selections)) = selections {
|
||||
let _ = self.update_selection_set(set_id, selections, ctx.as_deref_mut());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
let changes = self.edits_since(old_version).collect::<Vec<_>>();
|
||||
if !changes.is_empty() {
|
||||
self.did_edit(changes, was_dirty, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
ops
|
||||
}
|
||||
|
||||
pub fn redo(&mut self, mut ctx: Option<&mut ModelContext<Self>>) -> Vec<Operation> {
|
||||
let was_dirty = self.is_dirty();
|
||||
let old_version = self.version.clone();
|
||||
|
||||
let mut ops = Vec::new();
|
||||
if let Some(transaction) = self.history.pop_redo() {
|
||||
let selections = transaction.selections_after.clone();
|
||||
for edit_id in transaction.edits.clone() {
|
||||
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||
}
|
||||
|
||||
if let Some((set_id, selections)) = selections {
|
||||
let _ = self.update_selection_set(set_id, selections, ctx.as_deref_mut());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.notify();
|
||||
let changes = self.edits_since(old_version).collect::<Vec<_>>();
|
||||
if !changes.is_empty() {
|
||||
self.did_edit(changes, was_dirty, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
ops
|
||||
}
|
||||
|
||||
fn undo_or_redo(&mut self, edit_id: time::Local) -> Result<Operation> {
|
||||
let undo = UndoOperation {
|
||||
id: self.local_clock.tick(),
|
||||
@ -950,7 +1113,7 @@ impl Buffer {
|
||||
let mut new_fragments;
|
||||
|
||||
self.undo_map.insert(undo);
|
||||
let edit = &self.edit_ops[&undo.edit_id];
|
||||
let edit = &self.history.ops[&undo.edit_id];
|
||||
let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?;
|
||||
let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?;
|
||||
let mut cursor = self.fragments.cursor::<FragmentIdRef, ()>();
|
||||
@ -1575,11 +1738,11 @@ impl Clone for Buffer {
|
||||
file: self.file.clone(),
|
||||
fragments: self.fragments.clone(),
|
||||
insertion_splits: self.insertion_splits.clone(),
|
||||
edit_ops: self.edit_ops.clone(),
|
||||
version: self.version.clone(),
|
||||
saved_version: self.saved_version.clone(),
|
||||
last_edit: self.last_edit.clone(),
|
||||
undo_map: self.undo_map.clone(),
|
||||
history: self.history.clone(),
|
||||
selections: self.selections.clone(),
|
||||
selections_last_update: self.selections_last_update.clone(),
|
||||
deferred_ops: self.deferred_ops.clone(),
|
||||
@ -1801,48 +1964,6 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> {
|
||||
// collector.into_inner().changes
|
||||
// }
|
||||
|
||||
impl Selection {
|
||||
pub fn head(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.start
|
||||
} else {
|
||||
&self.end
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_head<S>(&mut self, buffer: &Buffer, cursor: Anchor) {
|
||||
if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal {
|
||||
if !self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = true;
|
||||
}
|
||||
self.start = cursor;
|
||||
} else {
|
||||
if self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = false;
|
||||
}
|
||||
self.end = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tail(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.end
|
||||
} else {
|
||||
&self.start
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self, buffer: &Buffer) -> bool {
|
||||
self.start.to_offset(buffer).unwrap() == self.end.to_offset(buffer).unwrap()
|
||||
}
|
||||
|
||||
pub fn anchor_range(&self) -> Range<Anchor> {
|
||||
self.start.clone()..self.end.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
|
||||
struct FragmentId(Arc<[u16]>);
|
||||
|
||||
@ -2169,6 +2290,7 @@ impl ToPoint for usize {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cmp::Ordering;
|
||||
use gpui::App;
|
||||
use std::collections::BTreeMap;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@ -2194,12 +2316,14 @@ mod tests {
|
||||
#[test]
|
||||
fn test_edit_events() {
|
||||
App::test((), |app| {
|
||||
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 = app.add_model(|_| Buffer::new(0, "abcdef"));
|
||||
let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
|
||||
let ops = buffer1.update(app, |buffer, ctx| {
|
||||
let mut buffer_ops = Vec::new();
|
||||
buffer1.update(app, |buffer, ctx| {
|
||||
let buffer_1_events = buffer_1_events.clone();
|
||||
ctx.subscribe(&buffer1, move |_, event, _| {
|
||||
buffer_1_events.borrow_mut().push(event.clone())
|
||||
@ -2209,10 +2333,33 @@ mod tests {
|
||||
buffer_2_events.borrow_mut().push(event.clone())
|
||||
});
|
||||
|
||||
buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap()
|
||||
// An edit emits an edited event, followed by a dirtied event,
|
||||
// since the buffer was previously in a clean state.
|
||||
let ops = buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap();
|
||||
buffer_ops.extend_from_slice(&ops);
|
||||
|
||||
// An empty transaction does not emit any events.
|
||||
buffer.start_transaction(None).unwrap();
|
||||
buffer.end_transaction(None, Some(ctx)).unwrap();
|
||||
|
||||
// A transaction containing two edits emits one edited event.
|
||||
now += Duration::from_secs(1);
|
||||
buffer.start_transaction_at(None, now).unwrap();
|
||||
let ops = buffer.edit(Some(5..5), "u", Some(ctx)).unwrap();
|
||||
buffer_ops.extend_from_slice(&ops);
|
||||
let ops = buffer.edit(Some(6..6), "w", Some(ctx)).unwrap();
|
||||
buffer_ops.extend_from_slice(&ops);
|
||||
buffer.end_transaction_at(None, now, Some(ctx)).unwrap();
|
||||
|
||||
// Undoing a transaction emits one edited event.
|
||||
let ops = buffer.undo(Some(ctx));
|
||||
buffer_ops.extend_from_slice(&ops);
|
||||
});
|
||||
|
||||
// Incorporating a set of remote ops emits a single edited event,
|
||||
// followed by a dirtied event.
|
||||
buffer2.update(app, |buffer, ctx| {
|
||||
buffer.apply_ops(ops, Some(ctx)).unwrap();
|
||||
buffer.apply_ops(buffer_ops, Some(ctx)).unwrap();
|
||||
});
|
||||
|
||||
let buffer_1_events = buffer_1_events.borrow();
|
||||
@ -2222,8 +2369,16 @@ mod tests {
|
||||
Event::Edited(vec![Edit {
|
||||
old_range: 2..4,
|
||||
new_range: 2..5
|
||||
},]),
|
||||
Event::Dirtied
|
||||
}]),
|
||||
Event::Dirtied,
|
||||
Event::Edited(vec![Edit {
|
||||
old_range: 5..5,
|
||||
new_range: 5..7
|
||||
}]),
|
||||
Event::Edited(vec![Edit {
|
||||
old_range: 5..7,
|
||||
new_range: 5..5
|
||||
}]),
|
||||
]
|
||||
);
|
||||
|
||||
@ -2834,6 +2989,60 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history() -> Result<()> {
|
||||
let mut now = Instant::now();
|
||||
let mut buffer = Buffer::new(0, "123456");
|
||||
|
||||
let (set_id, _) =
|
||||
buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4])?, None);
|
||||
buffer.start_transaction_at(Some(set_id), now)?;
|
||||
buffer.edit(vec![2..4], "cd", None)?;
|
||||
buffer.end_transaction_at(Some(set_id), now, None)?;
|
||||
assert_eq!(buffer.text(), "12cd56");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![4..4]);
|
||||
|
||||
buffer.start_transaction_at(Some(set_id), now)?;
|
||||
buffer.update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3])?, None)?;
|
||||
buffer.edit(vec![4..5], "e", None)?;
|
||||
buffer.end_transaction_at(Some(set_id), now, None)?;
|
||||
assert_eq!(buffer.text(), "12cde6");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
|
||||
|
||||
now += UNDO_GROUP_INTERVAL + Duration::from_millis(1);
|
||||
buffer.start_transaction_at(Some(set_id), now)?;
|
||||
buffer.update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2])?, None)?;
|
||||
buffer.edit(vec![0..1], "a", None)?;
|
||||
buffer.edit(vec![1..1], "b", None)?;
|
||||
buffer.end_transaction_at(Some(set_id), now, None)?;
|
||||
assert_eq!(buffer.text(), "ab2cde6");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![3..3]);
|
||||
|
||||
// Last transaction happened past the group interval, undo it on its
|
||||
// own.
|
||||
buffer.undo(None);
|
||||
assert_eq!(buffer.text(), "12cde6");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
|
||||
|
||||
// First two transactions happened within the group interval, undo them
|
||||
// together.
|
||||
buffer.undo(None);
|
||||
assert_eq!(buffer.text(), "123456");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![4..4]);
|
||||
|
||||
// Redo the first two transactions together.
|
||||
buffer.redo(None);
|
||||
assert_eq!(buffer.text(), "12cde6");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![1..3]);
|
||||
|
||||
// Redo the last transaction on its own.
|
||||
buffer.redo(None);
|
||||
assert_eq!(buffer.text(), "ab2cde6");
|
||||
assert_eq!(buffer.selection_ranges(set_id)?, vec![3..3]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_random_concurrent_edits() {
|
||||
use crate::test::Network;
|
||||
@ -2923,22 +3132,21 @@ mod tests {
|
||||
.collect::<Vec<_>>();
|
||||
let set_id = replica_selection_sets.choose(rng);
|
||||
if set_id.is_some() && rng.gen_bool(1.0 / 6.0) {
|
||||
let op = self.remove_selection_set(*set_id.unwrap()).unwrap();
|
||||
let op = self.remove_selection_set(*set_id.unwrap(), None).unwrap();
|
||||
operations.push(op);
|
||||
} else {
|
||||
let mut ranges = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let start = rng.gen_range(0..self.len() + 1);
|
||||
let start_point = self.point_for_offset(start).unwrap();
|
||||
let end = rng.gen_range(0..self.len() + 1);
|
||||
let end_point = self.point_for_offset(end).unwrap();
|
||||
ranges.push(start_point..end_point);
|
||||
ranges.push(start..end);
|
||||
}
|
||||
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(ranges).unwrap().1
|
||||
self.add_selection_set(new_selections, None).1
|
||||
} else {
|
||||
self.replace_selection_set(*set_id.unwrap(), ranges)
|
||||
self.update_selection_set(*set_id.unwrap(), new_selections, None)
|
||||
.unwrap()
|
||||
};
|
||||
operations.push(op);
|
||||
@ -2950,12 +3158,70 @@ mod tests {
|
||||
pub fn randomly_undo_redo(&mut self, rng: &mut impl Rng) -> Vec<Operation> {
|
||||
let mut ops = Vec::new();
|
||||
for _ in 0..rng.gen_range(1..5) {
|
||||
if let Some(edit_id) = self.edit_ops.keys().choose(rng).copied() {
|
||||
if let Some(edit_id) = self.history.ops.keys().choose(rng).copied() {
|
||||
ops.push(self.undo_or_redo(edit_id).unwrap());
|
||||
}
|
||||
}
|
||||
ops
|
||||
}
|
||||
|
||||
fn selections_from_ranges<I>(&self, ranges: I) -> Result<Vec<Selection>>
|
||||
where
|
||||
I: IntoIterator<Item = Range<usize>>,
|
||||
{
|
||||
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 {
|
||||
if range.start > range.end {
|
||||
selections.push(Selection {
|
||||
start: self.anchor_before(range.end)?,
|
||||
end: self.anchor_before(range.start)?,
|
||||
reversed: true,
|
||||
goal_column: None,
|
||||
});
|
||||
} else {
|
||||
selections.push(Selection {
|
||||
start: self.anchor_after(range.start)?,
|
||||
end: self.anchor_before(range.end)?,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(selections)
|
||||
}
|
||||
|
||||
pub fn selection_ranges<'a>(&'a self, set_id: SelectionSetId) -> Result<Vec<Range<usize>>> {
|
||||
Ok(self
|
||||
.selections(set_id)?
|
||||
.iter()
|
||||
.map(move |selection| {
|
||||
let start = selection.start.to_offset(self).unwrap();
|
||||
let end = selection.end.to_offset(self).unwrap();
|
||||
if selection.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn all_selections(&self) -> impl Iterator<Item = (&SelectionSetId, &[Selection])> {
|
||||
self.selections
|
||||
.iter()
|
||||
.map(|(set_id, selections)| (set_id, selections.as_ref()))
|
||||
}
|
||||
|
||||
pub fn all_selection_ranges<'a>(
|
||||
&'a self,
|
||||
) -> impl 'a + Iterator<Item = (SelectionSetId, Vec<Range<usize>>)> {
|
||||
self.selections
|
||||
.keys()
|
||||
.map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
|
75
zed/src/editor/buffer/selection.rs
Normal file
75
zed/src/editor/buffer/selection.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::{
|
||||
editor::{
|
||||
buffer::{Anchor, Buffer, Point, ToPoint},
|
||||
display_map::DisplayMap,
|
||||
DisplayPoint,
|
||||
},
|
||||
time,
|
||||
};
|
||||
use gpui::AppContext;
|
||||
use std::{cmp::Ordering, mem, ops::Range};
|
||||
|
||||
pub type SelectionSetId = time::Lamport;
|
||||
pub type SelectionsVersion = usize;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Selection {
|
||||
pub start: Anchor,
|
||||
pub end: Anchor,
|
||||
pub reversed: bool,
|
||||
pub goal_column: Option<u32>,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn head(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.start
|
||||
} else {
|
||||
&self.end
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) {
|
||||
if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal {
|
||||
if !self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = true;
|
||||
}
|
||||
self.start = cursor;
|
||||
} else {
|
||||
if self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = false;
|
||||
}
|
||||
self.end = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tail(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.end
|
||||
} else {
|
||||
&self.start
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range(&self, buffer: &Buffer) -> Range<Point> {
|
||||
let start = self.start.to_point(buffer).unwrap();
|
||||
let end = self.end.to_point(buffer).unwrap();
|
||||
if self.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_range(&self, map: &DisplayMap, app: &AppContext) -> Range<DisplayPoint> {
|
||||
let start = self.start.to_display_point(map, app).unwrap();
|
||||
let end = self.end.to_display_point(map, app).unwrap();
|
||||
if self.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
}
|
||||
}
|
@ -117,6 +117,18 @@ pub struct Text {
|
||||
|
||||
impl From<String> for Text {
|
||||
fn from(text: String) -> Self {
|
||||
Self::from(Arc::from(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Text {
|
||||
fn from(text: &'a str) -> Self {
|
||||
Self::from(Arc::from(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<str>> for Text {
|
||||
fn from(text: Arc<str>) -> Self {
|
||||
let mut runs = Vec::new();
|
||||
|
||||
let mut chars_len = 0;
|
||||
@ -147,19 +159,13 @@ impl From<String> for Text {
|
||||
let mut tree = SumTree::new();
|
||||
tree.extend(runs);
|
||||
Text {
|
||||
text: text.into(),
|
||||
text,
|
||||
runs: tree,
|
||||
range: 0..chars_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Text {
|
||||
fn from(text: &'a str) -> Self {
|
||||
Self::from(String::from(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Text {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("Text").field(&self.as_str()).finish()
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point,
|
||||
ToOffset, ToPoint,
|
||||
Selection, SelectionSetId, ToOffset,
|
||||
};
|
||||
use crate::{settings::Settings, watch, workspace};
|
||||
use anyhow::Result;
|
||||
@ -17,7 +17,6 @@ use smol::Timer;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
fmt::Write,
|
||||
mem,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@ -29,6 +28,8 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_bindings(vec![
|
||||
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
||||
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
||||
Binding::new("cmd-z", "buffer:undo", Some("BufferView")),
|
||||
Binding::new("cmd-shift-Z", "buffer:redo", Some("BufferView")),
|
||||
Binding::new("up", "buffer:move_up", Some("BufferView")),
|
||||
Binding::new("down", "buffer:move_down", Some("BufferView")),
|
||||
Binding::new("left", "buffer:move_left", Some("BufferView")),
|
||||
@ -53,6 +54,8 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_action("buffer:insert", BufferView::insert);
|
||||
app.add_action("buffer:newline", BufferView::newline);
|
||||
app.add_action("buffer:backspace", BufferView::backspace);
|
||||
app.add_action("buffer:undo", BufferView::undo);
|
||||
app.add_action("buffer:redo", BufferView::redo);
|
||||
app.add_action("buffer:move_up", BufferView::move_up);
|
||||
app.add_action("buffer:move_down", BufferView::move_down);
|
||||
app.add_action("buffer:move_left", BufferView::move_left);
|
||||
@ -87,7 +90,7 @@ pub struct BufferView {
|
||||
handle: WeakViewHandle<Self>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
display_map: ModelHandle<DisplayMap>,
|
||||
selections: Vec<Selection>,
|
||||
selection_set_id: SelectionSetId,
|
||||
pending_selection: Option<Selection>,
|
||||
scroll_position: Mutex<Vector2F>,
|
||||
autoscroll_requested: Mutex<bool>,
|
||||
@ -125,17 +128,22 @@ impl BufferView {
|
||||
});
|
||||
ctx.observe(&display_map, Self::on_display_map_changed);
|
||||
|
||||
let buffer_ref = buffer.read(ctx);
|
||||
let (selection_set_id, _) = buffer.update(ctx, |buffer, ctx| {
|
||||
buffer.add_selection_set(
|
||||
vec![Selection {
|
||||
start: buffer.anchor_before(0).unwrap(),
|
||||
end: buffer.anchor_before(0).unwrap(),
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
}],
|
||||
Some(ctx),
|
||||
)
|
||||
});
|
||||
Self {
|
||||
handle: ctx.handle().downgrade(),
|
||||
buffer,
|
||||
display_map,
|
||||
selections: vec![Selection {
|
||||
start: buffer_ref.anchor_before(0).unwrap(),
|
||||
end: buffer_ref.anchor_before(0).unwrap(),
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
}],
|
||||
selection_set_id,
|
||||
pending_selection: None,
|
||||
scroll_position: Mutex::new(Vector2F::zero()),
|
||||
autoscroll_requested: Mutex::new(false),
|
||||
@ -191,7 +199,7 @@ impl BufferView {
|
||||
let map = self.display_map.read(app);
|
||||
let visible_lines = viewport_height / line_height;
|
||||
let first_cursor_top = self
|
||||
.selections
|
||||
.selections(app)
|
||||
.first()
|
||||
.unwrap()
|
||||
.head()
|
||||
@ -199,7 +207,7 @@ impl BufferView {
|
||||
.unwrap()
|
||||
.row() as f32;
|
||||
let last_cursor_bottom = self
|
||||
.selections
|
||||
.selections(app)
|
||||
.last()
|
||||
.unwrap()
|
||||
.head()
|
||||
@ -242,7 +250,7 @@ impl BufferView {
|
||||
|
||||
let mut target_left = std::f32::INFINITY;
|
||||
let mut target_right = 0.0_f32;
|
||||
for selection in &self.selections {
|
||||
for selection in self.selections(app) {
|
||||
let head = selection.head().to_display_point(map, app).unwrap();
|
||||
let start_column = head.column().saturating_sub(3);
|
||||
let end_column = cmp::min(map.line_len(head.row(), app).unwrap(), head.column() + 3);
|
||||
@ -299,7 +307,7 @@ impl BufferView {
|
||||
};
|
||||
|
||||
if !add {
|
||||
self.selections.clear();
|
||||
self.update_selections(Vec::new(), ctx);
|
||||
}
|
||||
self.pending_selection = Some(selection);
|
||||
|
||||
@ -330,9 +338,9 @@ impl BufferView {
|
||||
fn end_selection(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
if let Some(selection) = self.pending_selection.take() {
|
||||
let ix = self.selection_insertion_index(&selection.start, ctx.app());
|
||||
self.selections.insert(ix, selection);
|
||||
self.merge_selections(ctx.app());
|
||||
ctx.notify();
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
selections.insert(ix, selection);
|
||||
self.update_selections(selections, ctx);
|
||||
} else {
|
||||
log::error!("end_selection dispatched with no pending selection");
|
||||
}
|
||||
@ -347,7 +355,6 @@ impl BufferView {
|
||||
where
|
||||
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let map = self.display_map.read(ctx);
|
||||
let mut selections = Vec::new();
|
||||
for range in ranges {
|
||||
@ -358,53 +365,52 @@ impl BufferView {
|
||||
goal_column: None,
|
||||
});
|
||||
}
|
||||
selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap());
|
||||
self.selections = selections;
|
||||
self.merge_selections(ctx.app());
|
||||
ctx.notify();
|
||||
self.update_selections(selections, ctx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(&mut self, text: &String, ctx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let mut offset_ranges = SmallVec::<[Range<usize>; 32]>::new();
|
||||
for selection in &self.selections {
|
||||
let start = selection.start.to_offset(buffer).unwrap();
|
||||
let end = selection.end.to_offset(buffer).unwrap();
|
||||
offset_ranges.push(start..end);
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
for selection in self.selections(ctx.app()) {
|
||||
let start = selection.start.to_offset(buffer).unwrap();
|
||||
let end = selection.end.to_offset(buffer).unwrap();
|
||||
offset_ranges.push(start..end);
|
||||
}
|
||||
}
|
||||
|
||||
self.start_transaction(ctx);
|
||||
let mut new_selections = Vec::new();
|
||||
self.buffer.update(ctx, |buffer, ctx| {
|
||||
if let Err(error) = buffer.edit(offset_ranges.iter().cloned(), text.as_str(), Some(ctx))
|
||||
{
|
||||
log::error!("error inserting text: {}", error);
|
||||
};
|
||||
let char_count = text.chars().count() as isize;
|
||||
let mut delta = 0_isize;
|
||||
new_selections = offset_ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start = range.start as isize;
|
||||
let end = range.end as isize;
|
||||
let anchor = buffer
|
||||
.anchor_before((start + delta + char_count) as usize)
|
||||
.unwrap();
|
||||
let deleted_count = end - start;
|
||||
delta += char_count - deleted_count;
|
||||
Selection {
|
||||
start: anchor.clone(),
|
||||
end: anchor,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
});
|
||||
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let char_count = text.chars().count() as isize;
|
||||
let mut delta = 0_isize;
|
||||
self.selections = offset_ranges
|
||||
.into_iter()
|
||||
.map(|range| {
|
||||
let start = range.start as isize;
|
||||
let end = range.end as isize;
|
||||
let anchor = buffer
|
||||
.anchor_before((start + delta + char_count) as usize)
|
||||
.unwrap();
|
||||
let deleted_count = end - start;
|
||||
delta += char_count - deleted_count;
|
||||
Selection {
|
||||
start: anchor.clone(),
|
||||
end: anchor,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.pause_cursor_blinking(ctx);
|
||||
*self.autoscroll_requested.lock() = true;
|
||||
self.update_selections(new_selections, ctx);
|
||||
self.end_transaction(ctx);
|
||||
}
|
||||
|
||||
fn newline(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
@ -416,31 +422,49 @@ impl BufferView {
|
||||
}
|
||||
|
||||
pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let map = self.display_map.read(ctx);
|
||||
for selection in &mut self.selections {
|
||||
if selection.range(buffer).is_empty() {
|
||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||
let cursor = map
|
||||
.anchor_before(
|
||||
movement::left(map, head, ctx.app()).unwrap(),
|
||||
Bias::Left,
|
||||
ctx.app(),
|
||||
)
|
||||
.unwrap();
|
||||
selection.set_head(&buffer, cursor);
|
||||
selection.goal_column = None;
|
||||
self.start_transaction(ctx);
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let map = self.display_map.read(ctx);
|
||||
for selection in &mut selections {
|
||||
if selection.range(buffer).is_empty() {
|
||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||
let cursor = map
|
||||
.anchor_before(
|
||||
movement::left(map, head, ctx.app()).unwrap(),
|
||||
Bias::Left,
|
||||
ctx.app(),
|
||||
)
|
||||
.unwrap();
|
||||
selection.set_head(&buffer, cursor);
|
||||
selection.goal_column = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
self.insert(&String::new(), ctx);
|
||||
self.end_transaction(ctx);
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
self.buffer
|
||||
.update(ctx, |buffer, ctx| buffer.undo(Some(ctx)));
|
||||
}
|
||||
|
||||
pub fn redo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
self.buffer
|
||||
.update(ctx, |buffer, ctx| buffer.redo(Some(ctx)));
|
||||
}
|
||||
|
||||
pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let app = ctx.app();
|
||||
let mut selections = self.selections(app).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(ctx);
|
||||
for selection in &mut self.selections {
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
|
||||
@ -457,14 +481,16 @@ impl BufferView {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
|
||||
pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let map = self.display_map.read(ctx);
|
||||
for selection in &mut self.selections {
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||
let cursor = map
|
||||
.anchor_before(
|
||||
@ -477,14 +503,16 @@ impl BufferView {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
|
||||
pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut self.selections {
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
|
||||
@ -501,15 +529,17 @@ impl BufferView {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
|
||||
pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let app = ctx.app();
|
||||
let buffer = self.buffer.read(app);
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut self.selections {
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(map, ctx.app()).unwrap();
|
||||
let cursor = map
|
||||
.anchor_before(movement::right(map, head, app).unwrap(), Bias::Right, app)
|
||||
@ -518,6 +548,7 @@ impl BufferView {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
|
||||
@ -525,23 +556,27 @@ impl BufferView {
|
||||
if self.single_line {
|
||||
ctx.propagate_action();
|
||||
} else {
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut self.selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
if start != end {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
if start != end {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
|
||||
let (start, goal_column) =
|
||||
movement::up(map, start, selection.goal_column, app).unwrap();
|
||||
let cursor = map.anchor_before(start, Bias::Left, app).unwrap();
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
selection.goal_column = goal_column;
|
||||
selection.reversed = false;
|
||||
let (start, goal_column) =
|
||||
movement::up(map, start, selection.goal_column, app).unwrap();
|
||||
let cursor = map.anchor_before(start, Bias::Left, app).unwrap();
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
selection.goal_column = goal_column;
|
||||
selection.reversed = false;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
}
|
||||
@ -550,16 +585,20 @@ impl BufferView {
|
||||
if self.single_line {
|
||||
ctx.propagate_action();
|
||||
} else {
|
||||
let app = ctx.app();
|
||||
let buffer = self.buffer.read(app);
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut self.selections {
|
||||
let head = selection.head().to_display_point(map, app).unwrap();
|
||||
let (head, goal_column) =
|
||||
movement::up(map, head, selection.goal_column, app).unwrap();
|
||||
selection.set_head(&buffer, map.anchor_before(head, Bias::Left, app).unwrap());
|
||||
selection.goal_column = goal_column;
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let buffer = self.buffer.read(app);
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(map, app).unwrap();
|
||||
let (head, goal_column) =
|
||||
movement::up(map, head, selection.goal_column, app).unwrap();
|
||||
selection.set_head(&buffer, map.anchor_before(head, Bias::Left, app).unwrap());
|
||||
selection.goal_column = goal_column;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
}
|
||||
@ -568,23 +607,27 @@ impl BufferView {
|
||||
if self.single_line {
|
||||
ctx.propagate_action();
|
||||
} else {
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut self.selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
if start != end {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut selections {
|
||||
let start = selection.start.to_display_point(map, app).unwrap();
|
||||
let end = selection.end.to_display_point(map, app).unwrap();
|
||||
if start != end {
|
||||
selection.goal_column = None;
|
||||
}
|
||||
|
||||
let (start, goal_column) =
|
||||
movement::down(map, end, selection.goal_column, app).unwrap();
|
||||
let cursor = map.anchor_before(start, Bias::Right, app).unwrap();
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
selection.goal_column = goal_column;
|
||||
selection.reversed = false;
|
||||
let (start, goal_column) =
|
||||
movement::down(map, end, selection.goal_column, app).unwrap();
|
||||
let cursor = map.anchor_before(start, Bias::Right, app).unwrap();
|
||||
selection.start = cursor.clone();
|
||||
selection.end = cursor;
|
||||
selection.goal_column = goal_column;
|
||||
selection.reversed = false;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
}
|
||||
@ -593,69 +636,39 @@ impl BufferView {
|
||||
if self.single_line {
|
||||
ctx.propagate_action();
|
||||
} else {
|
||||
let app = ctx.app();
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let map = self.display_map.read(ctx);
|
||||
for selection in &mut self.selections {
|
||||
let head = selection.head().to_display_point(map, app).unwrap();
|
||||
let (head, goal_column) =
|
||||
movement::down(map, head, selection.goal_column, app).unwrap();
|
||||
selection.set_head(&buffer, map.anchor_before(head, Bias::Right, app).unwrap());
|
||||
selection.goal_column = goal_column;
|
||||
let mut selections = self.selections(ctx.app()).to_vec();
|
||||
{
|
||||
let app = ctx.app();
|
||||
let buffer = self.buffer.read(app);
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &mut selections {
|
||||
let head = selection.head().to_display_point(map, app).unwrap();
|
||||
let (head, goal_column) =
|
||||
movement::down(map, head, selection.goal_column, app).unwrap();
|
||||
selection.set_head(&buffer, map.anchor_before(head, Bias::Right, app).unwrap());
|
||||
selection.goal_column = goal_column;
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changed_selections(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
self.merge_selections(ctx.app());
|
||||
self.pause_cursor_blinking(ctx);
|
||||
*self.autoscroll_requested.lock() = true;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
fn merge_selections(&mut self, ctx: &AppContext) {
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let mut i = 1;
|
||||
while i < self.selections.len() {
|
||||
if self.selections[i - 1]
|
||||
.end
|
||||
.cmp(&self.selections[i].start, buffer)
|
||||
.unwrap()
|
||||
>= Ordering::Equal
|
||||
{
|
||||
let removed = self.selections.remove(i);
|
||||
if removed
|
||||
.start
|
||||
.cmp(&self.selections[i - 1].start, buffer)
|
||||
.unwrap()
|
||||
< Ordering::Equal
|
||||
{
|
||||
self.selections[i - 1].start = removed.start;
|
||||
}
|
||||
if removed
|
||||
.end
|
||||
.cmp(&self.selections[i - 1].end, buffer)
|
||||
.unwrap()
|
||||
> Ordering::Equal
|
||||
{
|
||||
self.selections[i - 1].end = removed.end;
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
|
||||
self.selections
|
||||
self.selections(app)
|
||||
.first()
|
||||
.unwrap()
|
||||
.display_range(self.display_map.read(app), app)
|
||||
}
|
||||
|
||||
pub fn last_selection(&self, app: &AppContext) -> Range<DisplayPoint> {
|
||||
self.selections
|
||||
self.selections(app)
|
||||
.last()
|
||||
.unwrap()
|
||||
.display_range(self.display_map.read(app), app)
|
||||
@ -678,7 +691,7 @@ impl BufferView {
|
||||
None
|
||||
}
|
||||
});
|
||||
self.selections[start_index..]
|
||||
self.selections(app)[start_index..]
|
||||
.iter()
|
||||
.map(move |s| s.display_range(map, app))
|
||||
.take_while(move |r| r.start <= range.end || r.end <= range.end)
|
||||
@ -687,16 +700,12 @@ impl BufferView {
|
||||
|
||||
fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize {
|
||||
let buffer = self.buffer.read(app);
|
||||
|
||||
match self
|
||||
.selections
|
||||
.binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap())
|
||||
{
|
||||
let selections = self.selections(app);
|
||||
match selections.binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap()) {
|
||||
Ok(index) => index,
|
||||
Err(index) => {
|
||||
if index > 0
|
||||
&& self.selections[index - 1].end.cmp(&start, buffer).unwrap()
|
||||
== Ordering::Greater
|
||||
&& selections[index - 1].end.cmp(&start, buffer).unwrap() == Ordering::Greater
|
||||
{
|
||||
index - 1
|
||||
} else {
|
||||
@ -706,6 +715,59 @@ impl BufferView {
|
||||
}
|
||||
}
|
||||
|
||||
fn selections<'a>(&self, app: &'a AppContext) -> &'a [Selection] {
|
||||
self.buffer
|
||||
.read(app)
|
||||
.selections(self.selection_set_id)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn update_selections(&self, mut selections: Vec<Selection>, ctx: &mut ViewContext<Self>) {
|
||||
// Merge overlapping selections.
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let mut i = 1;
|
||||
while i < selections.len() {
|
||||
if selections[i - 1]
|
||||
.end
|
||||
.cmp(&selections[i].start, buffer)
|
||||
.unwrap()
|
||||
>= Ordering::Equal
|
||||
{
|
||||
let removed = selections.remove(i);
|
||||
if removed.start.cmp(&selections[i - 1].start, buffer).unwrap() < Ordering::Equal {
|
||||
selections[i - 1].start = removed.start;
|
||||
}
|
||||
if removed.end.cmp(&selections[i - 1].end, buffer).unwrap() > Ordering::Equal {
|
||||
selections[i - 1].end = removed.end;
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.buffer.update(ctx, |buffer, ctx| {
|
||||
buffer
|
||||
.update_selection_set(self.selection_set_id, selections, Some(ctx))
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
fn start_transaction(&self, ctx: &mut ViewContext<Self>) {
|
||||
self.buffer.update(ctx, |buffer, _| {
|
||||
buffer
|
||||
.start_transaction(Some(self.selection_set_id))
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
fn end_transaction(&self, ctx: &mut ViewContext<Self>) {
|
||||
self.buffer.update(ctx, |buffer, ctx| {
|
||||
buffer
|
||||
.end_transaction(Some(self.selection_set_id), Some(ctx))
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn page_up(&mut self, _: &(), _: &mut ViewContext<Self>) {
|
||||
log::info!("BufferView::page_up");
|
||||
}
|
||||
@ -721,7 +783,7 @@ impl BufferView {
|
||||
|
||||
let app = ctx.app();
|
||||
let map = self.display_map.read(app);
|
||||
for selection in &self.selections {
|
||||
for selection in self.selections(app) {
|
||||
let (start, end) = selection.display_range(map, app).sorted();
|
||||
let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row;
|
||||
|
||||
@ -753,7 +815,7 @@ impl BufferView {
|
||||
let map = self.display_map.read(app);
|
||||
let buffer = self.buffer.read(app);
|
||||
let ranges = self
|
||||
.selections
|
||||
.selections(app)
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let (start, end) = s.display_range(map, app).sorted();
|
||||
@ -833,7 +895,7 @@ impl BufferView {
|
||||
self.display_map.update(ctx, |map, ctx| {
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let ranges = self
|
||||
.selections
|
||||
.selections(ctx.app())
|
||||
.iter()
|
||||
.map(|s| s.range(buffer))
|
||||
.collect::<Vec<_>>();
|
||||
@ -1086,13 +1148,6 @@ impl BufferView {
|
||||
}
|
||||
}
|
||||
|
||||
struct Selection {
|
||||
start: Anchor,
|
||||
end: Anchor,
|
||||
reversed: bool,
|
||||
goal_column: Option<u32>,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Activate,
|
||||
Edited,
|
||||
@ -1181,60 +1236,6 @@ impl workspace::ItemView for BufferView {
|
||||
}
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
fn head(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.start
|
||||
} else {
|
||||
&self.end
|
||||
}
|
||||
}
|
||||
|
||||
fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) {
|
||||
if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal {
|
||||
if !self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = true;
|
||||
}
|
||||
self.start = cursor;
|
||||
} else {
|
||||
if self.reversed {
|
||||
mem::swap(&mut self.start, &mut self.end);
|
||||
self.reversed = false;
|
||||
}
|
||||
self.end = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
fn tail(&self) -> &Anchor {
|
||||
if self.reversed {
|
||||
&self.end
|
||||
} else {
|
||||
&self.start
|
||||
}
|
||||
}
|
||||
|
||||
fn range(&self, buffer: &Buffer) -> Range<Point> {
|
||||
let start = self.start.to_point(buffer).unwrap();
|
||||
let end = self.end.to_point(buffer).unwrap();
|
||||
if self.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
}
|
||||
|
||||
fn display_range(&self, map: &DisplayMap, app: &AppContext) -> Range<DisplayPoint> {
|
||||
let start = self.start.to_display_point(map, app).unwrap();
|
||||
let end = self.end.to_display_point(map, app).unwrap();
|
||||
if self.reversed {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -1487,12 +1488,12 @@ mod tests {
|
||||
view.update(app, |view, ctx| {
|
||||
view.move_down(&(), ctx);
|
||||
assert_eq!(
|
||||
view.selections(ctx.app()),
|
||||
view.selection_ranges(ctx.app()),
|
||||
&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
|
||||
);
|
||||
view.move_right(&(), ctx);
|
||||
assert_eq!(
|
||||
view.selections(ctx.app()),
|
||||
view.selection_ranges(ctx.app()),
|
||||
&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
|
||||
);
|
||||
Ok::<(), Error>(())
|
||||
@ -1536,7 +1537,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl BufferView {
|
||||
fn selections(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
|
||||
fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
|
||||
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ impl Worktree {
|
||||
let mut file = smol::fs::File::open(&path).await?;
|
||||
let mut base_text = String::new();
|
||||
file.read_to_string(&mut base_text).await?;
|
||||
let history = History { base_text };
|
||||
let history = History::new(Arc::from(base_text));
|
||||
tree.0.write().histories.insert(entry_id, history.clone());
|
||||
Ok(history)
|
||||
}
|
||||
@ -717,7 +717,7 @@ mod test {
|
||||
.read(|ctx| tree.read(ctx).load_history(file_id))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(history.base_text, buffer.text());
|
||||
assert_eq!(history.base_text.as_ref(), buffer.text());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user