mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
multi_buffer: Split multi_buffer into more modules (#41033)
There are a of separate APIs in this, partially interleaved making it difficult to grasp. Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
parent
d83ed4e03e
commit
6aaf19f276
12 changed files with 1215 additions and 1059 deletions
|
|
@ -140,7 +140,6 @@ use mouse_context_menu::MouseContextMenu;
|
|||
use movement::TextLayoutDetails;
|
||||
use multi_buffer::{
|
||||
ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
|
||||
ToOffsetUtf16,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use persistence::DB;
|
||||
|
|
|
|||
|
|
@ -938,8 +938,9 @@ impl Item for Editor {
|
|||
fn breadcrumbs(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let multibuffer = &self.buffer().read(cx);
|
||||
let (buffer_id, symbols) =
|
||||
multibuffer.symbols_containing(cursor, Some(variant.syntax()), cx)?;
|
||||
let (buffer_id, symbols) = multibuffer
|
||||
.read(cx)
|
||||
.symbols_containing(cursor, Some(variant.syntax()))?;
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
|
||||
let buffer = buffer.read(cx);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ use language::{
|
|||
point_to_lsp,
|
||||
};
|
||||
use lsp::{notification, request};
|
||||
use multi_buffer::ToPointUtf16;
|
||||
use project::Project;
|
||||
use smol::stream::StreamExt;
|
||||
use workspace::{AppState, Workspace, WorkspaceHandle};
|
||||
|
|
|
|||
|
|
@ -2078,12 +2078,15 @@ impl Buffer {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the change bit for all "listeners".
|
||||
fn was_changed(&mut self) {
|
||||
self.change_bits.retain(|change_bit| {
|
||||
change_bit.upgrade().is_some_and(|bit| {
|
||||
bit.replace(true);
|
||||
true
|
||||
})
|
||||
change_bit
|
||||
.upgrade()
|
||||
.inspect(|bit| {
|
||||
_ = bit.replace(true);
|
||||
})
|
||||
.is_some()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
||||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use language::{OffsetUtf16, Point, TextDimension};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
|
|
@ -185,9 +185,6 @@ impl ToOffset for Anchor {
|
|||
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffsetUtf16 for Anchor {
|
||||
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
|
|
@ -197,6 +194,9 @@ impl ToPoint for Anchor {
|
|||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
|
||||
self.summary(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnchorRangeExt {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ use parking_lot::RwLock;
|
|||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
use std::env;
|
||||
use std::time::{Duration, Instant};
|
||||
use util::RandomCharIter;
|
||||
use util::rel_path::rel_path;
|
||||
use util::test::sample_text;
|
||||
|
|
@ -2984,7 +2985,7 @@ fn test_history(cx: &mut App) {
|
|||
});
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
multibuffer.update(cx, |this, _| {
|
||||
this.history.group_interval = group_interval;
|
||||
this.set_group_interval(group_interval);
|
||||
});
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.push_excerpts(
|
||||
|
|
|
|||
417
crates/multi_buffer/src/path_key.rs
Normal file
417
crates/multi_buffer/src/path_key.rs
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
use std::{mem, ops::Range, sync::Arc};
|
||||
|
||||
use collections::HashSet;
|
||||
use gpui::{App, AppContext, Context, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use rope::Point;
|
||||
use text::{Bias, OffsetRangeExt, locator::Locator};
|
||||
use util::{post_inc, rel_path::RelPath};
|
||||
|
||||
use crate::{
|
||||
Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, build_excerpt_ranges,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
|
||||
pub struct PathKey {
|
||||
// Used by the derived PartialOrd & Ord
|
||||
pub sort_prefix: Option<u64>,
|
||||
pub path: Arc<RelPath>,
|
||||
}
|
||||
|
||||
impl PathKey {
|
||||
pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
|
||||
Self {
|
||||
sort_prefix: Some(sort_prefix),
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
|
||||
if let Some(file) = buffer.read(cx).file() {
|
||||
Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
|
||||
} else {
|
||||
Self {
|
||||
sort_prefix: None,
|
||||
path: RelPath::unix(&buffer.entity_id().to_string())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiBuffer {
|
||||
pub fn paths(&self) -> impl Iterator<Item = PathKey> + '_ {
|
||||
self.excerpts_by_path.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||
if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
|
||||
self.remove_excerpts(to_remove, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
|
||||
let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
|
||||
let snapshot = self.read(cx);
|
||||
let excerpt = snapshot.excerpt(*excerpt_id)?;
|
||||
Some(Anchor::in_buffer(
|
||||
*excerpt_id,
|
||||
excerpt.buffer_id,
|
||||
excerpt.range.context.start,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn excerpt_paths(&self) -> impl Iterator<Item = &PathKey> {
|
||||
self.excerpts_by_path.keys()
|
||||
}
|
||||
|
||||
/// Sets excerpts, returns `true` if at least one new excerpt was added.
|
||||
pub fn set_excerpts_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: impl IntoIterator<Item = Range<Point>>,
|
||||
context_line_count: u32,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
|
||||
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
self.set_merged_excerpt_ranges_for_path(
|
||||
path,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
&buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_excerpt_ranges_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
excerpt_ranges: Vec<ExcerptRange<Point>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
self.set_merged_excerpt_ranges_for_path(
|
||||
path,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_anchored_excerpts_for_path(
|
||||
&self,
|
||||
path_key: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: Vec<Range<text::Anchor>>,
|
||||
context_line_count: u32,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Vec<Range<Anchor>>> {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
cx.spawn(async move |multi_buffer, cx| {
|
||||
let snapshot = buffer_snapshot.clone();
|
||||
let (excerpt_ranges, new, counts) = cx
|
||||
.background_spawn(async move {
|
||||
let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
|
||||
let excerpt_ranges =
|
||||
build_excerpt_ranges(ranges, context_line_count, &snapshot);
|
||||
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
|
||||
(excerpt_ranges, new, counts)
|
||||
})
|
||||
.await;
|
||||
|
||||
multi_buffer
|
||||
.update(cx, move |multi_buffer, cx| {
|
||||
let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
|
||||
path_key,
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
&buffer_snapshot,
|
||||
new,
|
||||
counts,
|
||||
cx,
|
||||
);
|
||||
ranges
|
||||
})
|
||||
.ok()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn expand_excerpts_with_paths(
|
||||
&mut self,
|
||||
ids: impl IntoIterator<Item = ExcerptId>,
|
||||
line_count: u32,
|
||||
direction: ExpandExcerptDirection,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let grouped = ids
|
||||
.into_iter()
|
||||
.chunk_by(|id| self.paths_by_excerpt.get(id).cloned())
|
||||
.into_iter()
|
||||
.flat_map(|(k, v)| Some((k?, v.into_iter().collect::<Vec<_>>())))
|
||||
.collect::<Vec<_>>();
|
||||
let snapshot = self.snapshot(cx);
|
||||
|
||||
for (path, ids) in grouped.into_iter() {
|
||||
let Some(excerpt_ids) = self.excerpts_by_path.get(&path) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let ids_to_expand = HashSet::from_iter(ids);
|
||||
let expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
|
||||
let excerpt = snapshot.excerpt(*excerpt_id)?;
|
||||
|
||||
let mut context = excerpt.range.context.to_point(&excerpt.buffer);
|
||||
if ids_to_expand.contains(excerpt_id) {
|
||||
match direction {
|
||||
ExpandExcerptDirection::Up => {
|
||||
context.start.row = context.start.row.saturating_sub(line_count);
|
||||
context.start.column = 0;
|
||||
}
|
||||
ExpandExcerptDirection::Down => {
|
||||
context.end.row =
|
||||
(context.end.row + line_count).min(excerpt.buffer.max_point().row);
|
||||
context.end.column = excerpt.buffer.line_len(context.end.row);
|
||||
}
|
||||
ExpandExcerptDirection::UpAndDown => {
|
||||
context.start.row = context.start.row.saturating_sub(line_count);
|
||||
context.start.column = 0;
|
||||
context.end.row =
|
||||
(context.end.row + line_count).min(excerpt.buffer.max_point().row);
|
||||
context.end.column = excerpt.buffer.line_len(context.end.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ExcerptRange {
|
||||
context,
|
||||
primary: excerpt.range.primary.to_point(&excerpt.buffer),
|
||||
})
|
||||
});
|
||||
let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
|
||||
for range in expanded_ranges {
|
||||
if let Some(last_range) = merged_ranges.last_mut()
|
||||
&& last_range.context.end >= range.context.start
|
||||
{
|
||||
last_range.context.end = range.context.end;
|
||||
continue;
|
||||
}
|
||||
merged_ranges.push(range)
|
||||
}
|
||||
let Some(excerpt_id) = excerpt_ids.first() else {
|
||||
continue;
|
||||
};
|
||||
let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(*excerpt_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
self.update_path_excerpts(path.clone(), buffer, &buffer_snapshot, merged_ranges, cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets excerpts, returns `true` if at least one new excerpt was added.
|
||||
fn set_merged_excerpt_ranges_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: Vec<ExcerptRange<Point>>,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
counts: Vec<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
let (excerpt_ids, added_a_new_excerpt) =
|
||||
self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut ranges = ranges.into_iter();
|
||||
for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(counts.into_iter()) {
|
||||
for range in ranges.by_ref().take(range_count) {
|
||||
let range = Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
buffer_snapshot.remote_id(),
|
||||
buffer_snapshot.anchor_before(&range.primary.start)
|
||||
..buffer_snapshot.anchor_after(&range.primary.end),
|
||||
);
|
||||
result.push(range)
|
||||
}
|
||||
}
|
||||
(result, added_a_new_excerpt)
|
||||
}
|
||||
|
||||
fn update_path_excerpts(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<ExcerptId>, bool) {
|
||||
let mut insert_after = self
|
||||
.excerpts_by_path
|
||||
.range(..path.clone())
|
||||
.next_back()
|
||||
.map(|(_, value)| *value.last().unwrap())
|
||||
.unwrap_or(ExcerptId::min());
|
||||
|
||||
let existing = self
|
||||
.excerpts_by_path
|
||||
.get(&path)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut new_iter = new.into_iter().peekable();
|
||||
let mut existing_iter = existing.into_iter().peekable();
|
||||
|
||||
let mut excerpt_ids = Vec::new();
|
||||
let mut to_remove = Vec::new();
|
||||
let mut to_insert: Vec<(ExcerptId, ExcerptRange<Point>)> = Vec::new();
|
||||
let mut added_a_new_excerpt = false;
|
||||
let snapshot = self.snapshot(cx);
|
||||
|
||||
let mut next_excerpt_id =
|
||||
if let Some(last_entry) = self.snapshot.borrow().excerpt_ids.last() {
|
||||
last_entry.id.0 + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id));
|
||||
|
||||
let mut excerpts_cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
|
||||
excerpts_cursor.next();
|
||||
|
||||
loop {
|
||||
let new = new_iter.peek();
|
||||
let existing = if let Some(existing_id) = existing_iter.peek() {
|
||||
let locator = snapshot.excerpt_locator_for_id(*existing_id);
|
||||
excerpts_cursor.seek_forward(&Some(locator), Bias::Left);
|
||||
if let Some(excerpt) = excerpts_cursor.item() {
|
||||
if excerpt.buffer_id != buffer_snapshot.remote_id() {
|
||||
to_remove.push(*existing_id);
|
||||
existing_iter.next();
|
||||
continue;
|
||||
}
|
||||
Some((
|
||||
*existing_id,
|
||||
excerpt.range.context.to_point(buffer_snapshot),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some((last_id, last)) = to_insert.last_mut() {
|
||||
if let Some(new) = new
|
||||
&& last.context.end >= new.context.start
|
||||
{
|
||||
last.context.end = last.context.end.max(new.context.end);
|
||||
excerpt_ids.push(*last_id);
|
||||
new_iter.next();
|
||||
continue;
|
||||
}
|
||||
if let Some((existing_id, existing_range)) = &existing
|
||||
&& last.context.end >= existing_range.start
|
||||
{
|
||||
last.context.end = last.context.end.max(existing_range.end);
|
||||
to_remove.push(*existing_id);
|
||||
self.snapshot
|
||||
.get_mut()
|
||||
.replaced_excerpts
|
||||
.insert(*existing_id, *last_id);
|
||||
existing_iter.next();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
match (new, existing) {
|
||||
(None, None) => break,
|
||||
(None, Some((existing_id, _))) => {
|
||||
existing_iter.next();
|
||||
to_remove.push(existing_id);
|
||||
continue;
|
||||
}
|
||||
(Some(_), None) => {
|
||||
added_a_new_excerpt = true;
|
||||
let new_id = next_excerpt_id();
|
||||
excerpt_ids.push(new_id);
|
||||
to_insert.push((new_id, new_iter.next().unwrap()));
|
||||
continue;
|
||||
}
|
||||
(Some(new), Some((_, existing_range))) => {
|
||||
if existing_range.end < new.context.start {
|
||||
let existing_id = existing_iter.next().unwrap();
|
||||
to_remove.push(existing_id);
|
||||
continue;
|
||||
} else if existing_range.start > new.context.end {
|
||||
let new_id = next_excerpt_id();
|
||||
excerpt_ids.push(new_id);
|
||||
to_insert.push((new_id, new_iter.next().unwrap()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if existing_range.start == new.context.start
|
||||
&& existing_range.end == new.context.end
|
||||
{
|
||||
self.insert_excerpts_with_ids_after(
|
||||
insert_after,
|
||||
buffer.clone(),
|
||||
mem::take(&mut to_insert),
|
||||
cx,
|
||||
);
|
||||
insert_after = existing_iter.next().unwrap();
|
||||
excerpt_ids.push(insert_after);
|
||||
new_iter.next();
|
||||
} else {
|
||||
let existing_id = existing_iter.next().unwrap();
|
||||
let new_id = next_excerpt_id();
|
||||
self.snapshot
|
||||
.get_mut()
|
||||
.replaced_excerpts
|
||||
.insert(existing_id, new_id);
|
||||
to_remove.push(existing_id);
|
||||
let mut range = new_iter.next().unwrap();
|
||||
range.context.start = range.context.start.min(existing_range.start);
|
||||
range.context.end = range.context.end.max(existing_range.end);
|
||||
excerpt_ids.push(new_id);
|
||||
to_insert.push((new_id, range));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.insert_excerpts_with_ids_after(insert_after, buffer, to_insert, cx);
|
||||
self.remove_excerpts(to_remove, cx);
|
||||
if excerpt_ids.is_empty() {
|
||||
self.excerpts_by_path.remove(&path);
|
||||
} else {
|
||||
for excerpt_id in &excerpt_ids {
|
||||
self.paths_by_excerpt.insert(*excerpt_id, path.clone());
|
||||
}
|
||||
self.excerpts_by_path
|
||||
.insert(path, excerpt_ids.iter().dedup().cloned().collect());
|
||||
}
|
||||
|
||||
(excerpt_ids, added_a_new_excerpt)
|
||||
}
|
||||
}
|
||||
524
crates/multi_buffer/src/transaction.rs
Normal file
524
crates/multi_buffer/src/transaction.rs
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
use gpui::{App, Context, Entity};
|
||||
use language::{self, Buffer, TextDimension, TransactionId};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Range, Sub},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use text::BufferId;
|
||||
|
||||
use crate::BufferState;
|
||||
|
||||
use super::{Event, ExcerptSummary, MultiBuffer};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct History {
|
||||
next_transaction_id: TransactionId,
|
||||
undo_stack: Vec<Transaction>,
|
||||
redo_stack: Vec<Transaction>,
|
||||
transaction_depth: usize,
|
||||
group_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for History {
|
||||
fn default() -> Self {
|
||||
History {
|
||||
next_transaction_id: clock::Lamport::MIN,
|
||||
undo_stack: Vec::new(),
|
||||
redo_stack: Vec::new(),
|
||||
transaction_depth: 0,
|
||||
group_interval: Duration::from_millis(300),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Transaction {
|
||||
id: TransactionId,
|
||||
buffer_transactions: HashMap<BufferId, text::TransactionId>,
|
||||
first_edit_at: Instant,
|
||||
last_edit_at: Instant,
|
||||
suppress_grouping: bool,
|
||||
}
|
||||
|
||||
impl History {
|
||||
fn start_transaction(&mut self, now: Instant) -> Option<TransactionId> {
|
||||
self.transaction_depth += 1;
|
||||
if self.transaction_depth == 1 {
|
||||
let id = self.next_transaction_id.tick();
|
||||
self.undo_stack.push(Transaction {
|
||||
id,
|
||||
buffer_transactions: Default::default(),
|
||||
first_edit_at: now,
|
||||
last_edit_at: now,
|
||||
suppress_grouping: false,
|
||||
});
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn end_transaction(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
buffer_transactions: HashMap<BufferId, text::TransactionId>,
|
||||
) -> bool {
|
||||
assert_ne!(self.transaction_depth, 0);
|
||||
self.transaction_depth -= 1;
|
||||
if self.transaction_depth == 0 {
|
||||
if buffer_transactions.is_empty() {
|
||||
self.undo_stack.pop();
|
||||
false
|
||||
} else {
|
||||
self.redo_stack.clear();
|
||||
let transaction = self.undo_stack.last_mut().unwrap();
|
||||
transaction.last_edit_at = now;
|
||||
for (buffer_id, transaction_id) in buffer_transactions {
|
||||
transaction
|
||||
.buffer_transactions
|
||||
.entry(buffer_id)
|
||||
.or_insert(transaction_id);
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn push_transaction<'a, T>(
|
||||
&mut self,
|
||||
buffer_transactions: T,
|
||||
now: Instant,
|
||||
cx: &Context<MultiBuffer>,
|
||||
) where
|
||||
T: IntoIterator<Item = (&'a Entity<Buffer>, &'a language::Transaction)>,
|
||||
{
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
let transaction = Transaction {
|
||||
id: self.next_transaction_id.tick(),
|
||||
buffer_transactions: buffer_transactions
|
||||
.into_iter()
|
||||
.map(|(buffer, transaction)| (buffer.read(cx).remote_id(), transaction.id))
|
||||
.collect(),
|
||||
first_edit_at: now,
|
||||
last_edit_at: now,
|
||||
suppress_grouping: false,
|
||||
};
|
||||
if !transaction.buffer_transactions.is_empty() {
|
||||
self.undo_stack.push(transaction);
|
||||
self.redo_stack.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_last_transaction(&mut self) {
|
||||
if let Some(transaction) = self.undo_stack.last_mut() {
|
||||
transaction.suppress_grouping = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn forget(&mut self, transaction_id: TransactionId) -> Option<Transaction> {
|
||||
if let Some(ix) = self
|
||||
.undo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)
|
||||
{
|
||||
Some(self.undo_stack.remove(ix))
|
||||
} else if let Some(ix) = self
|
||||
.redo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)
|
||||
{
|
||||
Some(self.redo_stack.remove(ix))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction(&self, transaction_id: TransactionId) -> Option<&Transaction> {
|
||||
self.undo_stack
|
||||
.iter()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
.or_else(|| {
|
||||
self.redo_stack
|
||||
.iter()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
|
||||
self.undo_stack
|
||||
.iter_mut()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
.or_else(|| {
|
||||
self.redo_stack
|
||||
.iter_mut()
|
||||
.find(|transaction| transaction.id == transaction_id)
|
||||
})
|
||||
}
|
||||
|
||||
fn pop_undo(&mut self) -> Option<&mut Transaction> {
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
if let Some(transaction) = self.undo_stack.pop() {
|
||||
self.redo_stack.push(transaction);
|
||||
self.redo_stack.last_mut()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_redo(&mut self) -> Option<&mut Transaction> {
|
||||
assert_eq!(self.transaction_depth, 0);
|
||||
if let Some(transaction) = self.redo_stack.pop() {
|
||||
self.undo_stack.push(transaction);
|
||||
self.undo_stack.last_mut()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> {
|
||||
let ix = self
|
||||
.undo_stack
|
||||
.iter()
|
||||
.rposition(|transaction| transaction.id == transaction_id)?;
|
||||
let transaction = self.undo_stack.remove(ix);
|
||||
self.redo_stack.push(transaction);
|
||||
self.redo_stack.last()
|
||||
}
|
||||
|
||||
fn group(&mut self) -> Option<TransactionId> {
|
||||
let mut count = 0;
|
||||
let mut transactions = self.undo_stack.iter();
|
||||
if let Some(mut transaction) = transactions.next_back() {
|
||||
while let Some(prev_transaction) = transactions.next_back() {
|
||||
if !prev_transaction.suppress_grouping
|
||||
&& transaction.first_edit_at - prev_transaction.last_edit_at
|
||||
<= self.group_interval
|
||||
{
|
||||
transaction = prev_transaction;
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.group_trailing(count)
|
||||
}
|
||||
|
||||
fn group_until(&mut self, transaction_id: TransactionId) {
|
||||
let mut count = 0;
|
||||
for transaction in self.undo_stack.iter().rev() {
|
||||
if transaction.id == transaction_id {
|
||||
self.group_trailing(count);
|
||||
break;
|
||||
} else if transaction.suppress_grouping {
|
||||
break;
|
||||
} else {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
|
||||
let new_len = self.undo_stack.len() - n;
|
||||
let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
|
||||
if let Some(last_transaction) = transactions_to_keep.last_mut() {
|
||||
if let Some(transaction) = transactions_to_merge.last() {
|
||||
last_transaction.last_edit_at = transaction.last_edit_at;
|
||||
}
|
||||
for to_merge in transactions_to_merge {
|
||||
for (buffer_id, transaction_id) in &to_merge.buffer_transactions {
|
||||
last_transaction
|
||||
.buffer_transactions
|
||||
.entry(*buffer_id)
|
||||
.or_insert(*transaction_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.undo_stack.truncate(new_len);
|
||||
self.undo_stack.last().map(|t| t.id)
|
||||
}
|
||||
|
||||
pub(super) fn transaction_depth(&self) -> usize {
|
||||
self.transaction_depth
|
||||
}
|
||||
|
||||
pub fn set_group_interval(&mut self, group_interval: Duration) {
|
||||
self.group_interval = group_interval;
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiBuffer {
|
||||
pub fn start_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
|
||||
self.start_transaction_at(Instant::now(), cx)
|
||||
}
|
||||
|
||||
pub fn start_transaction_at(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
|
||||
}
|
||||
|
||||
for BufferState { buffer, .. } in self.buffers.values() {
|
||||
buffer.update(cx, |buffer, _| buffer.start_transaction_at(now));
|
||||
}
|
||||
self.history.start_transaction(now)
|
||||
}
|
||||
|
||||
pub fn last_transaction_id(&self, cx: &App) -> Option<TransactionId> {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer
|
||||
.read(cx)
|
||||
.peek_undo_stack()
|
||||
.map(|history_entry| history_entry.transaction_id())
|
||||
} else {
|
||||
let last_transaction = self.history.undo_stack.last()?;
|
||||
Some(last_transaction.id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_transaction(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
|
||||
self.end_transaction_at(Instant::now(), cx)
|
||||
}
|
||||
|
||||
pub fn end_transaction_at(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx));
|
||||
}
|
||||
|
||||
let mut buffer_transactions = HashMap::default();
|
||||
for BufferState { buffer, .. } in self.buffers.values() {
|
||||
if let Some(transaction_id) =
|
||||
buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
|
||||
{
|
||||
buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id);
|
||||
}
|
||||
}
|
||||
|
||||
if self.history.end_transaction(now, buffer_transactions) {
|
||||
let transaction_id = self.history.group().unwrap();
|
||||
Some(transaction_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edited_ranges_for_transaction<D>(
|
||||
&self,
|
||||
transaction_id: TransactionId,
|
||||
cx: &App,
|
||||
) -> Vec<Range<D>>
|
||||
where
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
{
|
||||
let Some(transaction) = self.history.transaction(transaction_id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut ranges = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
|
||||
|
||||
for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
|
||||
let Some(buffer_state) = self.buffers.get(buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer = buffer_state.buffer.read(cx);
|
||||
for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) {
|
||||
for excerpt_id in &buffer_state.excerpts {
|
||||
cursor.seek(excerpt_id, Bias::Left);
|
||||
if let Some(excerpt) = cursor.item()
|
||||
&& excerpt.locator == *excerpt_id
|
||||
{
|
||||
let excerpt_buffer_start = excerpt.range.context.start.summary::<D>(buffer);
|
||||
let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);
|
||||
let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
|
||||
if excerpt_range.contains(&range.start)
|
||||
&& excerpt_range.contains(&range.end)
|
||||
{
|
||||
let excerpt_start = D::from_text_summary(&cursor.start().text);
|
||||
|
||||
let mut start = excerpt_start;
|
||||
start.add_assign(&(range.start - excerpt_buffer_start));
|
||||
let mut end = excerpt_start;
|
||||
end.add_assign(&(range.end - excerpt_buffer_start));
|
||||
|
||||
ranges.push(start..end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ranges.sort_by_key(|range| range.start);
|
||||
ranges
|
||||
}
|
||||
|
||||
pub fn merge_transactions(
|
||||
&mut self,
|
||||
transaction: TransactionId,
|
||||
destination: TransactionId,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.merge_transactions(transaction, destination)
|
||||
});
|
||||
} else if let Some(transaction) = self.history.forget(transaction)
|
||||
&& let Some(destination) = self.history.transaction_mut(destination)
|
||||
{
|
||||
for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
|
||||
if let Some(destination_buffer_transaction_id) =
|
||||
destination.buffer_transactions.get(&buffer_id)
|
||||
{
|
||||
if let Some(state) = self.buffers.get(&buffer_id) {
|
||||
state.buffer.update(cx, |buffer, _| {
|
||||
buffer.merge_transactions(
|
||||
buffer_transaction_id,
|
||||
*destination_buffer_transaction_id,
|
||||
)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
destination
|
||||
.buffer_transactions
|
||||
.insert(buffer_id, buffer_transaction_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
|
||||
self.history.finalize_last_transaction();
|
||||
for BufferState { buffer, .. } in self.buffers.values() {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.finalize_last_transaction();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &Context<Self>)
|
||||
where
|
||||
T: IntoIterator<Item = (&'a Entity<Buffer>, &'a language::Transaction)>,
|
||||
{
|
||||
self.history
|
||||
.push_transaction(buffer_transactions, Instant::now(), cx);
|
||||
self.history.finalize_last_transaction();
|
||||
}
|
||||
|
||||
pub fn group_until_transaction(
|
||||
&mut self,
|
||||
transaction_id: TransactionId,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.group_until_transaction(transaction_id)
|
||||
});
|
||||
} else {
|
||||
self.history.group_until(transaction_id);
|
||||
}
|
||||
}
|
||||
pub fn undo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
|
||||
let mut transaction_id = None;
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
} else {
|
||||
while let Some(transaction) = self.history.pop_undo() {
|
||||
let mut undone = false;
|
||||
for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions {
|
||||
if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
|
||||
undone |= buffer.update(cx, |buffer, cx| {
|
||||
let undo_to = *buffer_transaction_id;
|
||||
if let Some(entry) = buffer.peek_undo_stack() {
|
||||
*buffer_transaction_id = entry.transaction_id();
|
||||
}
|
||||
buffer.undo_to_transaction(undo_to, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if undone {
|
||||
transaction_id = Some(transaction.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(transaction_id) = transaction_id {
|
||||
cx.emit(Event::TransactionUndone { transaction_id });
|
||||
}
|
||||
|
||||
transaction_id
|
||||
}
|
||||
|
||||
pub fn redo(&mut self, cx: &mut Context<Self>) -> Option<TransactionId> {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
return buffer.update(cx, |buffer, cx| buffer.redo(cx));
|
||||
}
|
||||
|
||||
while let Some(transaction) = self.history.pop_redo() {
|
||||
let mut redone = false;
|
||||
for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions.iter_mut() {
|
||||
if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
|
||||
redone |= buffer.update(cx, |buffer, cx| {
|
||||
let redo_to = *buffer_transaction_id;
|
||||
if let Some(entry) = buffer.peek_redo_stack() {
|
||||
*buffer_transaction_id = entry.transaction_id();
|
||||
}
|
||||
buffer.redo_to_transaction(redo_to, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if redone {
|
||||
return Some(transaction.id);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx));
|
||||
} else if let Some(transaction) = self.history.remove_from_undo(transaction_id) {
|
||||
for (buffer_id, transaction_id) in &transaction.buffer_transactions {
|
||||
if let Some(BufferState { buffer, .. }) = self.buffers.get(buffer_id) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.undo_transaction(*transaction_id, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forget_transaction(&mut self, transaction_id: TransactionId, cx: &mut Context<Self>) {
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
buffer.forget_transaction(transaction_id);
|
||||
});
|
||||
} else if let Some(transaction) = self.history.forget(transaction_id) {
|
||||
for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions {
|
||||
if let Some(state) = self.buffers.get_mut(&buffer_id) {
|
||||
state.buffer.update(cx, |buffer, _| {
|
||||
buffer.forget_transaction(buffer_transaction_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -448,6 +448,19 @@ impl<'a> ChunkSlice<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn point_to_offset_utf16(&self, point: Point) -> OffsetUtf16 {
|
||||
if point.row > self.lines().row {
|
||||
debug_panic!(
|
||||
"point {:?} extends beyond rows for string {:?}",
|
||||
point,
|
||||
self.text
|
||||
);
|
||||
return self.len_utf16();
|
||||
}
|
||||
self.offset_to_offset_utf16(self.point_to_offset(point))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
|
||||
let mask = (1 as Bitmap).unbounded_shl(offset as u32).wrapping_sub(1);
|
||||
|
|
|
|||
|
|
@ -440,6 +440,21 @@ impl Rope {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
||||
if point >= self.summary().lines_utf16() {
|
||||
return self.summary().lines;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<Dimensions<PointUtf16, Point>>(());
|
||||
cursor.seek(&point, Bias::Left);
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.start().1
|
||||
+ cursor.item().map_or(Point::zero(), |chunk| {
|
||||
chunk
|
||||
.as_slice()
|
||||
.offset_to_point(chunk.as_slice().point_utf16_to_offset(overshoot, false))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn point_to_offset(&self, point: Point) -> usize {
|
||||
if point >= self.summary().lines {
|
||||
return self.summary().len;
|
||||
|
|
@ -451,10 +466,27 @@ impl Rope {
|
|||
start.1 + item.map_or(0, |chunk| chunk.as_slice().point_to_offset(overshoot))
|
||||
}
|
||||
|
||||
pub fn point_to_offset_utf16(&self, point: Point) -> OffsetUtf16 {
|
||||
if point >= self.summary().lines {
|
||||
return self.summary().len_utf16;
|
||||
}
|
||||
let mut cursor = self.chunks.cursor::<Dimensions<Point, OffsetUtf16>>(());
|
||||
cursor.seek(&point, Bias::Left);
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.start().1
|
||||
+ cursor.item().map_or(OffsetUtf16(0), |chunk| {
|
||||
chunk.as_slice().point_to_offset_utf16(overshoot)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||
self.point_utf16_to_offset_impl(point, false)
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset_utf16(&self, point: PointUtf16) -> OffsetUtf16 {
|
||||
self.point_utf16_to_offset_utf16_impl(point, false)
|
||||
}
|
||||
|
||||
pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped<PointUtf16>) -> usize {
|
||||
self.point_utf16_to_offset_impl(point.0, true)
|
||||
}
|
||||
|
|
@ -473,6 +505,23 @@ impl Rope {
|
|||
})
|
||||
}
|
||||
|
||||
fn point_utf16_to_offset_utf16_impl(&self, point: PointUtf16, clip: bool) -> OffsetUtf16 {
|
||||
if point >= self.summary().lines_utf16() {
|
||||
return self.summary().len_utf16;
|
||||
}
|
||||
let mut cursor = self
|
||||
.chunks
|
||||
.cursor::<Dimensions<PointUtf16, OffsetUtf16>>(());
|
||||
cursor.seek(&point, Bias::Left);
|
||||
let overshoot = point - cursor.start().0;
|
||||
cursor.start().1
|
||||
+ cursor.item().map_or(OffsetUtf16(0), |chunk| {
|
||||
chunk
|
||||
.as_slice()
|
||||
.offset_to_offset_utf16(chunk.as_slice().point_utf16_to_offset(overshoot, clip))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
|
||||
if point.0 >= self.summary().lines_utf16() {
|
||||
return self.summary().lines;
|
||||
|
|
|
|||
|
|
@ -2051,6 +2051,14 @@ impl BufferSnapshot {
|
|||
self.visible_text.point_to_offset(point)
|
||||
}
|
||||
|
||||
pub fn point_to_offset_utf16(&self, point: Point) -> OffsetUtf16 {
|
||||
self.visible_text.point_to_offset_utf16(point)
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset_utf16(&self, point: PointUtf16) -> OffsetUtf16 {
|
||||
self.visible_text.point_utf16_to_offset_utf16(point)
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||
self.visible_text.point_utf16_to_offset(point)
|
||||
}
|
||||
|
|
@ -2083,6 +2091,10 @@ impl BufferSnapshot {
|
|||
self.visible_text.point_to_point_utf16(point)
|
||||
}
|
||||
|
||||
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
||||
self.visible_text.point_utf16_to_point(point)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &clock::Global {
|
||||
&self.version
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue