mod anchor; pub use anchor::{Anchor, AnchorRangeExt};use anyhow::{anyhow, Result};use clock::ReplicaId;use collections::{BTreeMap, Bound, HashMap, HashSet};use futures::{channel::mpsc, SinkExt};use git::diff::DiffHunk;use gpui::{AppContext, EventEmitter, Model, ModelContext};pub use language::Completion;use language::{ char_kind, language_settings::{language_settings, LanguageSettings}, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,};use std::{ borrow::Cow, cell::{Ref, RefCell}, cmp, fmt, future::Future, io, iter::{self, FromIterator}, mem, ops::{Range, RangeBounds, Sub}, str, sync::Arc, time::{Duration, Instant},};use sum_tree::{Bias, Cursor, SumTree};use text::{ locator::Locator, subscription::{Subscription, Topic}, BufferId, Edit, TextSummary,};use theme::SyntaxTheme; use util::post_inc; #[cfg(any(test, feature = "test-support"))]use gpui::Context; const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]pub struct ExcerptId(usize); /// One or more [`Buffers`](Buffer) being edited in a single view.////// See <https://zed.dev/features#multi-buffers>pub struct MultiBuffer { /// A snapshot of the [`Excerpt`]s in the MultiBuffer. /// Use [`MultiBuffer::snapshot`] to get a up-to-date snapshot. snapshot: RefCell<MultiBufferSnapshot>, /// Contains the state of the buffers being edited buffers: RefCell<HashMap<BufferId, BufferState>>, subscriptions: Topic, /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`] singleton: bool, replica_id: ReplicaId, history: History, title: Option<String>, capability: Capability,} #[derive(Clone, Debug, PartialEq, Eq)]pub enum Event { ExcerptsAdded { buffer: Model<Buffer>, predecessor: ExcerptId, excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>, }, ExcerptsRemoved { ids: Vec<ExcerptId>, }, ExcerptsEdited { ids: Vec<ExcerptId>, }, Edited { singleton_buffer_edited: bool, }, TransactionUndone { transaction_id: TransactionId, }, Reloaded, DiffBaseChanged, LanguageChanged, CapabilityChanged, Reparsed, Saved, FileHandleChanged, Closed, DirtyChanged, DiagnosticsUpdated,} #[derive(Clone)]struct History { next_transaction_id: TransactionId, undo_stack: Vec<Transaction>, redo_stack: Vec<Transaction>, transaction_depth: usize, group_interval: Duration,} #[derive(Clone)]struct Transaction { id: TransactionId, buffer_transactions: HashMap<BufferId, text::TransactionId>, first_edit_at: Instant, last_edit_at: Instant, suppress_grouping: bool,} pub trait ToOffset: 'static + fmt::Debug { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;} pub trait ToOffsetUtf16: 'static + fmt::Debug { fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;} pub trait ToPoint: 'static + fmt::Debug { fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;} pub trait ToPointUtf16: 'static + fmt::Debug { fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16;} struct BufferState { buffer: Model<Buffer>, last_version: clock::Global, last_parse_count: usize, last_selections_update_count: usize, last_diagnostics_update_count: usize, last_file_update_count: usize, last_git_diff_update_count: usize, excerpts: Vec<Locator>, _subscriptions: [gpui::Subscription; 2],} /// The contents of a [`MultiBuffer`] at a single point in time.#[derive(Clone, Default)]pub struct MultiBufferSnapshot { singleton: bool, excerpts: SumTree<Excerpt>, excerpt_ids: SumTree<ExcerptIdMapping>, parse_count: usize, diagnostics_update_count: usize, trailing_excerpt_update_count: usize, git_diff_update_count: usize, edit_count: usize, is_dirty: bool, has_conflict: bool,} /// A boundary between [`Excerpt`]s in a [`MultiBuffer`]pub struct ExcerptBoundary { pub id: ExcerptId, /// The row in the `MultiBuffer` where the boundary is located pub row: u32, pub buffer: BufferSnapshot, pub range: ExcerptRange<text::Anchor>, /// It's possible to have multiple excerpts in the same buffer, /// and they are rendered together without a new File header. /// /// This flag indicates that the excerpt is the first one in the buffer. pub starts_new_buffer: bool,} /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].#[derive(Clone)]struct Excerpt { /// The unique identifier for this excerpt id: ExcerptId, /// The location of the excerpt in the [`MultiBuffer`] locator: Locator, /// The buffer being excerpted buffer_id: BufferId, /// A snapshot of the buffer being excerpted buffer: BufferSnapshot, /// The range of the buffer to be shown in the excerpt range: ExcerptRange<text::Anchor>, /// The last row in the excerpted slice of the buffer max_buffer_row: u32, /// A summary of the text in the excerpt text_summary: TextSummary, has_trailing_newline: bool,} /// A public view into an [`Excerpt`] in a [`MultiBuffer`].////// Contains methods for getting the [`Buffer`] of the excerpt,/// as well as mapping offsets to/from buffer and multibuffer coordinates.#[derive(Clone)]pub struct MultiBufferExcerpt<'a> { excerpt: &'a Excerpt, excerpt_offset: usize,} #[derive(Clone, Debug)]struct ExcerptIdMapping { id: ExcerptId, locator: Locator,} /// A range of text from a single [`Buffer`], to be shown as an [`Excerpt`]./// These ranges are relative to the buffer itself#[derive(Clone, Debug, Eq, PartialEq)]pub struct ExcerptRange<T> { /// The full range of text to be shown in the excerpt. pub context: Range<T>, /// The primary range of text to be highlighted in the excerpt. /// In a multi-buffer search, this would be the text that matched the search pub primary: Option<Range<T>>,} #[derive(Clone, Debug, Default)]struct ExcerptSummary { excerpt_id: ExcerptId, /// The location of the last [`Excerpt`] being summarized excerpt_locator: Locator, /// The maximum row of the [`Excerpt`]s being summarized max_buffer_row: u32, text: TextSummary,} #[derive(Clone)]pub struct MultiBufferRows<'a> { buffer_row_range: Range<u32>, excerpts: Cursor<'a, Excerpt, Point>,} pub struct MultiBufferChunks<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_chunks: Option<ExcerptChunks<'a>>, language_aware: bool,} pub struct MultiBufferBytes<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_bytes: Option<ExcerptBytes<'a>>, chunk: &'a [u8],} pub struct ReversedMultiBufferBytes<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_bytes: Option<ExcerptBytes<'a>>, chunk: &'a [u8],} struct ExcerptChunks<'a> { content_chunks: BufferChunks<'a>, footer_height: usize,} struct ExcerptBytes<'a> { content_bytes: text::Bytes<'a>, footer_height: usize,} impl MultiBuffer { pub fn new(replica_id: ReplicaId, capability: Capability) -> Self { Self { snapshot: Default::default(), buffers: Default::default(), subscriptions: Default::default(), singleton: false, capability, replica_id, history: History { next_transaction_id: Default::default(), undo_stack: Default::default(), redo_stack: Default::default(), transaction_depth: 0, group_interval: Duration::from_millis(300), }, title: Default::default(), } } pub fn clone(&self, new_cx: &mut ModelContext<Self>) -> Self { let mut buffers = HashMap::default(); for (buffer_id, buffer_state) in self.buffers.borrow().iter() { buffers.insert( *buffer_id, BufferState { buffer: buffer_state.buffer.clone(), last_version: buffer_state.last_version.clone(), last_parse_count: buffer_state.last_parse_count, last_selections_update_count: buffer_state.last_selections_update_count, last_diagnostics_update_count: buffer_state.last_diagnostics_update_count, last_file_update_count: buffer_state.last_file_update_count, last_git_diff_update_count: buffer_state.last_git_diff_update_count, excerpts: buffer_state.excerpts.clone(), _subscriptions: [ new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()), new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event), ], }, ); } Self { snapshot: RefCell::new(self.snapshot.borrow().clone()), buffers: RefCell::new(buffers), subscriptions: Default::default(), singleton: self.singleton, capability: self.capability, replica_id: self.replica_id, history: self.history.clone(), title: self.title.clone(), } } pub fn with_title(mut self, title: String) -> Self { self.title = Some(title); self } pub fn read_only(&self) -> bool { self.capability == Capability::ReadOnly } pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self { let mut this = Self::new(buffer.read(cx).replica_id(), buffer.read(cx).capability()); this.singleton = true; this.push_excerpts( buffer, [ExcerptRange { context: text::Anchor::MIN..text::Anchor::MAX, primary: None, }], cx, ); this.snapshot.borrow_mut().singleton = true; this } pub fn replica_id(&self) -> ReplicaId { self.replica_id } /// Returns an up-to-date snapshot of the MultiBuffer. pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot { self.sync(cx); self.snapshot.borrow().clone() } pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> { self.sync(cx); self.snapshot.borrow() } pub fn as_singleton(&self) -> Option<Model<Buffer>> { if self.singleton { return Some( self.buffers .borrow() .values() .next() .unwrap() .buffer .clone(), ); } else { None } } pub fn is_singleton(&self) -> bool { self.singleton } pub fn subscribe(&mut self) -> Subscription { self.subscriptions.subscribe() } pub fn is_dirty(&self, cx: &AppContext) -> bool { self.read(cx).is_dirty() } pub fn has_conflict(&self, cx: &AppContext) -> bool { self.read(cx).has_conflict() } // The `is_empty` signature doesn't match what clippy expects #[allow(clippy::len_without_is_empty)] pub fn len(&self, cx: &AppContext) -> usize { self.read(cx).len() } pub fn is_empty(&self, cx: &AppContext) -> bool { self.len(cx) != 0 } pub fn symbols_containing<T: ToOffset>( &self, offset: T, theme: Option<&SyntaxTheme>, cx: &AppContext, ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> { self.read(cx).symbols_containing(offset, theme) } pub fn edit<I, S, T>( &mut self, edits: I, mut autoindent_mode: Option<AutoindentMode>, cx: &mut ModelContext<Self>, ) where I: IntoIterator<Item = (Range<S>, T)>, S: ToOffset, T: Into<Arc<str>>, { if self.buffers.borrow().is_empty() { return; } let snapshot = self.read(cx); let edits = edits.into_iter().map(|(range, new_text)| { let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); if range.start > range.end { mem::swap(&mut range.start, &mut range.end); } (range, new_text) }); if let Some(buffer) = self.as_singleton() { return buffer.update(cx, |buffer, cx| { buffer.edit(edits, autoindent_mode, cx); }); } let original_indent_columns = match &mut autoindent_mode { Some(AutoindentMode::Block { original_indent_columns, }) => mem::take(original_indent_columns), _ => Default::default(), }; struct BufferEdit { range: Range<usize>, new_text: Arc<str>, is_insertion: bool, original_indent_column: u32, } let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default(); let mut edited_excerpt_ids = Vec::new(); let mut cursor = snapshot.excerpts.cursor::<usize>(); for (ix, (range, new_text)) in edits.enumerate() { let new_text: Arc<str> = new_text.into(); let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0); cursor.seek(&range.start, Bias::Right, &()); if cursor.item().is_none() && range.start == *cursor.start() { cursor.prev(&()); } let start_excerpt = cursor.item().expect("start offset out of bounds"); let start_overshoot = range.start - cursor.start(); let buffer_start = start_excerpt .range .context .start .to_offset(&start_excerpt.buffer) + start_overshoot; edited_excerpt_ids.push(start_excerpt.id); cursor.seek(&range.end, Bias::Right, &()); if cursor.item().is_none() && range.end == *cursor.start() { cursor.prev(&()); } let end_excerpt = cursor.item().expect("end offset out of bounds"); let end_overshoot = range.end - cursor.start(); let buffer_end = end_excerpt .range .context .start .to_offset(&end_excerpt.buffer) + end_overshoot; if start_excerpt.id == end_excerpt.id { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) .push(BufferEdit { range: buffer_start..buffer_end, new_text, is_insertion: true, original_indent_column, }); } else { edited_excerpt_ids.push(end_excerpt.id); let start_excerpt_range = buffer_start ..start_excerpt .rangeLines 1–500 of 5621. Load more
mod anchor; pub use anchor::{Anchor, AnchorRangeExt}; use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; use futures::{channel::mpsc, SinkExt}; use git::diff::DiffHunk; use gpui::{AppContext, EventEmitter, Model, ModelContext}; pub use language::Completion; use language::{ char_kind, language_settings::{language_settings, LanguageSettings}, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape, DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use std::{ borrow::Cow, cell::{Ref, RefCell}, cmp, fmt, future::Future, io, iter::{self, FromIterator}, mem, ops::{Range, RangeBounds, Sub}, str, sync::Arc, time::{Duration, Instant}, }; use sum_tree::{Bias, Cursor, SumTree}; use text::{ locator::Locator, subscription::{Subscription, Topic}, BufferId, Edit, TextSummary, }; use theme::SyntaxTheme; use util::post_inc; #[cfg(any(test, feature = "test-support"))] use gpui::Context; const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ExcerptId(usize); /// One or more [`Buffers`](Buffer) being edited in a single view. /// /// See <https://zed.dev/features#multi-buffers> pub struct MultiBuffer { /// A snapshot of the [`Excerpt`]s in the MultiBuffer. /// Use [`MultiBuffer::snapshot`] to get a up-to-date snapshot. snapshot: RefCell<MultiBufferSnapshot>, /// Contains the state of the buffers being edited buffers: RefCell<HashMap<BufferId, BufferState>>, subscriptions: Topic, /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`] singleton: bool, replica_id: ReplicaId, history: History, title: Option<String>, capability: Capability, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { ExcerptsAdded { buffer: Model<Buffer>, predecessor: ExcerptId, excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>, }, ExcerptsRemoved { ids: Vec<ExcerptId>, }, ExcerptsEdited { ids: Vec<ExcerptId>, }, Edited { singleton_buffer_edited: bool, }, TransactionUndone { transaction_id: TransactionId, }, Reloaded, DiffBaseChanged, LanguageChanged, CapabilityChanged, Reparsed, Saved, FileHandleChanged, Closed, DirtyChanged, DiagnosticsUpdated, } #[derive(Clone)] struct History { next_transaction_id: TransactionId, undo_stack: Vec<Transaction>, redo_stack: Vec<Transaction>, transaction_depth: usize, group_interval: Duration, } #[derive(Clone)] struct Transaction { id: TransactionId, buffer_transactions: HashMap<BufferId, text::TransactionId>, first_edit_at: Instant, last_edit_at: Instant, suppress_grouping: bool, } pub trait ToOffset: 'static + fmt::Debug { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; } pub trait ToOffsetUtf16: 'static + fmt::Debug { fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16; } pub trait ToPoint: 'static + fmt::Debug { fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point; } pub trait ToPointUtf16: 'static + fmt::Debug { fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16; } struct BufferState { buffer: Model<Buffer>, last_version: clock::Global, last_parse_count: usize, last_selections_update_count: usize, last_diagnostics_update_count: usize, last_file_update_count: usize, last_git_diff_update_count: usize, excerpts: Vec<Locator>, _subscriptions: [gpui::Subscription; 2], } /// The contents of a [`MultiBuffer`] at a single point in time. #[derive(Clone, Default)] pub struct MultiBufferSnapshot { singleton: bool, excerpts: SumTree<Excerpt>, excerpt_ids: SumTree<ExcerptIdMapping>, parse_count: usize, diagnostics_update_count: usize, trailing_excerpt_update_count: usize, git_diff_update_count: usize, edit_count: usize, is_dirty: bool, has_conflict: bool, } /// A boundary between [`Excerpt`]s in a [`MultiBuffer`] pub struct ExcerptBoundary { pub id: ExcerptId, /// The row in the `MultiBuffer` where the boundary is located pub row: u32, pub buffer: BufferSnapshot, pub range: ExcerptRange<text::Anchor>, /// It's possible to have multiple excerpts in the same buffer, /// and they are rendered together without a new File header. /// /// This flag indicates that the excerpt is the first one in the buffer. pub starts_new_buffer: bool, } /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`]. #[derive(Clone)] struct Excerpt { /// The unique identifier for this excerpt id: ExcerptId, /// The location of the excerpt in the [`MultiBuffer`] locator: Locator, /// The buffer being excerpted buffer_id: BufferId, /// A snapshot of the buffer being excerpted buffer: BufferSnapshot, /// The range of the buffer to be shown in the excerpt range: ExcerptRange<text::Anchor>, /// The last row in the excerpted slice of the buffer max_buffer_row: u32, /// A summary of the text in the excerpt text_summary: TextSummary, has_trailing_newline: bool, } /// A public view into an [`Excerpt`] in a [`MultiBuffer`]. /// /// Contains methods for getting the [`Buffer`] of the excerpt, /// as well as mapping offsets to/from buffer and multibuffer coordinates. #[derive(Clone)] pub struct MultiBufferExcerpt<'a> { excerpt: &'a Excerpt, excerpt_offset: usize, } #[derive(Clone, Debug)] struct ExcerptIdMapping { id: ExcerptId, locator: Locator, } /// A range of text from a single [`Buffer`], to be shown as an [`Excerpt`]. /// These ranges are relative to the buffer itself #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExcerptRange<T> { /// The full range of text to be shown in the excerpt. pub context: Range<T>, /// The primary range of text to be highlighted in the excerpt. /// In a multi-buffer search, this would be the text that matched the search pub primary: Option<Range<T>>, } #[derive(Clone, Debug, Default)] struct ExcerptSummary { excerpt_id: ExcerptId, /// The location of the last [`Excerpt`] being summarized excerpt_locator: Locator, /// The maximum row of the [`Excerpt`]s being summarized max_buffer_row: u32, text: TextSummary, } #[derive(Clone)] pub struct MultiBufferRows<'a> { buffer_row_range: Range<u32>, excerpts: Cursor<'a, Excerpt, Point>, } pub struct MultiBufferChunks<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_chunks: Option<ExcerptChunks<'a>>, language_aware: bool, } pub struct MultiBufferBytes<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_bytes: Option<ExcerptBytes<'a>>, chunk: &'a [u8], } pub struct ReversedMultiBufferBytes<'a> { range: Range<usize>, excerpts: Cursor<'a, Excerpt, usize>, excerpt_bytes: Option<ExcerptBytes<'a>>, chunk: &'a [u8], } struct ExcerptChunks<'a> { content_chunks: BufferChunks<'a>, footer_height: usize, } struct ExcerptBytes<'a> { content_bytes: text::Bytes<'a>, footer_height: usize, } impl MultiBuffer { pub fn new(replica_id: ReplicaId, capability: Capability) -> Self { Self { snapshot: Default::default(), buffers: Default::default(), subscriptions: Default::default(), singleton: false, capability, replica_id, history: History { next_transaction_id: Default::default(), undo_stack: Default::default(), redo_stack: Default::default(), transaction_depth: 0, group_interval: Duration::from_millis(300), }, title: Default::default(), } } pub fn clone(&self, new_cx: &mut ModelContext<Self>) -> Self { let mut buffers = HashMap::default(); for (buffer_id, buffer_state) in self.buffers.borrow().iter() { buffers.insert( *buffer_id, BufferState { buffer: buffer_state.buffer.clone(), last_version: buffer_state.last_version.clone(), last_parse_count: buffer_state.last_parse_count, last_selections_update_count: buffer_state.last_selections_update_count, last_diagnostics_update_count: buffer_state.last_diagnostics_update_count, last_file_update_count: buffer_state.last_file_update_count, last_git_diff_update_count: buffer_state.last_git_diff_update_count, excerpts: buffer_state.excerpts.clone(), _subscriptions: [ new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()), new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event), ], }, ); } Self { snapshot: RefCell::new(self.snapshot.borrow().clone()), buffers: RefCell::new(buffers), subscriptions: Default::default(), singleton: self.singleton, capability: self.capability, replica_id: self.replica_id, history: self.history.clone(), title: self.title.clone(), } } pub fn with_title(mut self, title: String) -> Self { self.title = Some(title); self } pub fn read_only(&self) -> bool { self.capability == Capability::ReadOnly } pub fn singleton(buffer: Model<Buffer>, cx: &mut ModelContext<Self>) -> Self { let mut this = Self::new(buffer.read(cx).replica_id(), buffer.read(cx).capability()); this.singleton = true; this.push_excerpts( buffer, [ExcerptRange { context: text::Anchor::MIN..text::Anchor::MAX, primary: None, }], cx, ); this.snapshot.borrow_mut().singleton = true; this } pub fn replica_id(&self) -> ReplicaId { self.replica_id } /// Returns an up-to-date snapshot of the MultiBuffer. pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot { self.sync(cx); self.snapshot.borrow().clone() } pub fn read(&self, cx: &AppContext) -> Ref<MultiBufferSnapshot> { self.sync(cx); self.snapshot.borrow() } pub fn as_singleton(&self) -> Option<Model<Buffer>> { if self.singleton { return Some( self.buffers .borrow() .values() .next() .unwrap() .buffer .clone(), ); } else { None } } pub fn is_singleton(&self) -> bool { self.singleton } pub fn subscribe(&mut self) -> Subscription { self.subscriptions.subscribe() } pub fn is_dirty(&self, cx: &AppContext) -> bool { self.read(cx).is_dirty() } pub fn has_conflict(&self, cx: &AppContext) -> bool { self.read(cx).has_conflict() } // The `is_empty` signature doesn't match what clippy expects #[allow(clippy::len_without_is_empty)] pub fn len(&self, cx: &AppContext) -> usize { self.read(cx).len() } pub fn is_empty(&self, cx: &AppContext) -> bool { self.len(cx) != 0 } pub fn symbols_containing<T: ToOffset>( &self, offset: T, theme: Option<&SyntaxTheme>, cx: &AppContext, ) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> { self.read(cx).symbols_containing(offset, theme) } pub fn edit<I, S, T>( &mut self, edits: I, mut autoindent_mode: Option<AutoindentMode>, cx: &mut ModelContext<Self>, ) where I: IntoIterator<Item = (Range<S>, T)>, S: ToOffset, T: Into<Arc<str>>, { if self.buffers.borrow().is_empty() { return; } let snapshot = self.read(cx); let edits = edits.into_iter().map(|(range, new_text)| { let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); if range.start > range.end { mem::swap(&mut range.start, &mut range.end); } (range, new_text) }); if let Some(buffer) = self.as_singleton() { return buffer.update(cx, |buffer, cx| { buffer.edit(edits, autoindent_mode, cx); }); } let original_indent_columns = match &mut autoindent_mode { Some(AutoindentMode::Block { original_indent_columns, }) => mem::take(original_indent_columns), _ => Default::default(), }; struct BufferEdit { range: Range<usize>, new_text: Arc<str>, is_insertion: bool, original_indent_column: u32, } let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default(); let mut edited_excerpt_ids = Vec::new(); let mut cursor = snapshot.excerpts.cursor::<usize>(); for (ix, (range, new_text)) in edits.enumerate() { let new_text: Arc<str> = new_text.into(); let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0); cursor.seek(&range.start, Bias::Right, &()); if cursor.item().is_none() && range.start == *cursor.start() { cursor.prev(&()); } let start_excerpt = cursor.item().expect("start offset out of bounds"); let start_overshoot = range.start - cursor.start(); let buffer_start = start_excerpt .range .context .start .to_offset(&start_excerpt.buffer) + start_overshoot; edited_excerpt_ids.push(start_excerpt.id); cursor.seek(&range.end, Bias::Right, &()); if cursor.item().is_none() && range.end == *cursor.start() { cursor.prev(&()); } let end_excerpt = cursor.item().expect("end offset out of bounds"); let end_overshoot = range.end - cursor.start(); let buffer_end = end_excerpt .range .context .start .to_offset(&end_excerpt.buffer) + end_overshoot; if start_excerpt.id == end_excerpt.id { buffer_edits .entry(start_excerpt.buffer_id) .or_insert(Vec::new()) .push(BufferEdit { range: buffer_start..buffer_end, new_text, is_insertion: true, original_indent_column, }); } else { edited_excerpt_ids.push(end_excerpt.id); let start_excerpt_range = buffer_start ..start_excerpt .range