use std::ops::Range; use galaxyui::{AppContext, Entity, ModelHandle}; use itertools::Itertools; use string_offset::CharOffset; use vec1::{Vec1, vec1}; use crate::content::{ anchor::{Anchor, AnchorSide, AnchorUpdate, Anchors}, buffer::{Buffer, SelectionOffsets, ToBufferPoint}, selection::{Selection, SelectionSet}, text::{BlockType, TextStylesWithMetadata}, }; /// A snapshot of the selection state. This includes all data reported by [`BufferEvent::SelectionChanged`]. #[derive(PartialEq, Eq)] pub(super) struct SelectionSnapshot { /// We must snapshot the resolved selections, rather than their anchors, because anchors are /// updated in-place and won't directly reflect the update. pub(super) selections: Vec1, pub(super) active_text_styles: TextStylesWithMetadata, pub(super) active_block_type: BlockType, } pub struct BufferSelectionModel { selections: SelectionSet, pub(crate) anchors: Anchors, buffer: ModelHandle, } impl BufferSelectionModel { pub fn new(buffer: ModelHandle) -> Self { let mut anchors = Anchors::new(); let head_anchor = anchors.create_anchor(CharOffset::from(1), AnchorSide::Right); let tail_anchor = anchors.create_anchor(CharOffset::from(1), AnchorSide::Right); let selections = SelectionSet::new(Selection::new(head_anchor, tail_anchor)); Self { selections, buffer, anchors, } } pub fn update_anchors(&mut self, anchor_updates: Vec) { for update in anchor_updates { self.anchors.update(update); } } pub(super) fn truncate(&mut self) { self.selections.truncate(); } /// Create a new anchor at the given offset. pub fn anchor(&mut self, offset: CharOffset, ctx: &AppContext) -> Anchor { self.anchors .create_anchor(offset.min(self.buffer.as_ref(ctx).len()), AnchorSide::Right) } pub(super) fn create_anchor(&mut self, offset: CharOffset, side: AnchorSide) -> Anchor { self.anchors.create_anchor(offset, side) } /// Resolve an anchor to its current offset. pub fn resolve_anchor(&self, anchor: &Anchor) -> Option { self.anchors.resolve(anchor) } /// Resolve an anchor to its 0-based line number in the buffer. pub fn line_number_from_anchor(&self, anchor: &Anchor, ctx: &AppContext) -> Option { let char_offset = self.resolve_anchor(anchor)?; Some(char_offset.to_buffer_point(self.buffer.as_ref(ctx)).row as usize) } // The following two are temporary methods until multiple selections are supported. // Todo (kc CLD-1018): This should no longer be needed once we move to multi-selection. pub fn selection(&self) -> &Selection { self.selections.first() } pub fn selections(&self) -> &SelectionSet { &self.selections } pub fn selections_mut(&mut self) -> &mut SelectionSet { &mut self.selections } /// Returns the index of all lines that have active selection. pub fn selected_lines(&self, ctx: &AppContext) -> Vec1 { Vec1::try_from_vec( self.selections .iter() .flat_map(|s| { let range = self.selection_to_offset_range(s); let start_idx = range.start.to_buffer_point(self.buffer.as_ref(ctx)).row as usize; let end_idx = ((range.end - 1).to_buffer_point(self.buffer.as_ref(ctx)).row as usize) .max(start_idx); start_idx..end_idx + 1 }) .sorted() .dedup() .collect_vec(), ) .expect("Should have more than 1 element") } pub fn selection_to_offset_range(&self, selection: &Selection) -> Range { // Selection should always be valid when we have single selections. // TODO(kevin): migrate this when moving to multi-selection. let head = self .resolve_anchor(selection.head()) .expect("anchor should exist"); let tail = self .resolve_anchor(selection.tail()) .expect("anchor should exist"); if head >= tail { tail..head } else { head..tail } } // Todo (kc CLD-1018): This should no longer be needed once we move to multi-selection. pub fn selection_to_first_offset_range(&self) -> Range { self.selection_to_offset_range(self.selection()) } pub fn selections_to_offset_ranges(&self) -> Vec1> { self.selections .selection_map(|s| self.selection_to_offset_range(s)) } fn create_selection(&mut self, head: CharOffset, tail: CharOffset) -> Selection { Selection::new( self.create_anchor(head, AnchorSide::Right), self.create_anchor(tail, AnchorSide::Right), ) } pub fn set_selections(&mut self, selections: SelectionSet) { self.selections = selections; } /// Checks for overlapping selections. If any are found, we merge them together. /// /// Before: /// | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | /// | S1 | | S3 | /// | S2 | /// | S4 | /// /// After: /// | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | /// | S1 | /// | S2 | pub fn merge_overlapping_selections(&mut self) { let (overlap_indices, new_ranges) = Buffer::overlapping_ranges(self.selections_to_offset_ranges().to_vec()); // Return early if there are no overlapping selections. if overlap_indices.is_empty() { return; } // To determine whether the head of new selections should be before or after the tail, // we check whether overlapping selections have the head before the tail. // This seems to work in the common cases. let forwards_selection = self .selections .iter() .enumerate() .filter(|(i, _)| overlap_indices.contains(i)) .any(|(_, selection)| self.selection_head(selection) > self.selection_tail(selection)); // Keep any non-overlapping selections. let mut new_selections: Vec = self .selections .iter() .enumerate() .flat_map(|(i, selection)| { if !overlap_indices.contains(&i) { Some(selection.clone()) } else { None } }) .collect(); // Create any new selections from the overlapping selections. for range in new_ranges { let new_selection = if forwards_selection { self.create_selection(range.end, range.start) } else { self.create_selection(range.start, range.end) }; new_selections.push(new_selection); } match SelectionSet::try_from(new_selections) { Ok(selections) => self.selections = selections, Err(_) => { log::error!("After removing overlapping selections, there were no selections left!") } } } // Todo (kc CLD-1018): This should no longer be needed once we move to multi-selection. pub fn first_selection_head(&self) -> CharOffset { self.resolve_anchor(self.selections.first().head()) .expect("anchor should exist") } /// The current location of the selection head. pub fn selection_head(&self, selection: &Selection) -> CharOffset { self.resolve_anchor(selection.head()) .expect("anchor should exist") } pub fn selection_heads(&self) -> Vec1 { self.selections .selection_map(|s| self.resolve_anchor(s.head()).expect("anchor should exist")) } // Todo (kc CLD-1018): This should no longer be needed once we move to multi-selection. pub fn first_selection_tail(&self) -> CharOffset { self.resolve_anchor(self.selection().tail()) .expect("anchor should exist") } /// The current location of the selection tail. pub fn selection_tail(&self, selection: &Selection) -> CharOffset { self.resolve_anchor(selection.tail()) .expect("anchor should exist") } /// Return all selected ranges. pub fn selection_offsets(&self) -> Vec1 { self.selections.selection_map(|s| SelectionOffsets { head: self.resolve_anchor(s.head()).expect("anchor should exist"), tail: self.resolve_anchor(s.tail()).expect("anchor should exist"), }) } pub fn is_single_selection(&self) -> bool { self.selections.len() == 1 } /// Query for the set of text styles that are fully active over the current /// selection. pub fn selection_text_styles(&self, ctx: &AppContext) -> TextStylesWithMetadata { self.selections_to_offset_ranges() .into_iter() .map(|range| self.buffer.as_ref(ctx).range_text_styles(range)) .reduce(|style1, style2| style1.mutual_styles(style2)) .expect("At least one selection style should exist") } pub fn active_block_type_at_selection( &self, selection: &Selection, ctx: &AppContext, ) -> BlockType { let range = self.selection_to_offset_range(selection); self.buffer.as_ref(ctx).block_type_at_point(range.start) } /// Check whether the block type for every selection allow formatting. pub fn all_selections_allow_formatting(&self, ctx: &AppContext) -> bool { self.selections.iter().all(|selection| { match self.active_block_type_at_selection(selection, ctx) { BlockType::Text(block_style) => block_style.allows_formatting(), // If pasting content directly after a rich block item, we should keep the content's original // styling. BlockType::Item(_) => true, } }) } // Todo: kc (CLD1018) Temporary until multiselect is implemented. pub fn first_selection_is_single_cursor(&self) -> bool { // Selection should always be valid when we have single selections. // TODO(kevin): migrate this when moving to multi-selection. self.first_selection_head() == self.first_selection_tail() } pub fn selection_is_single_cursor(&self, selection: &Selection) -> bool { self.selection_head(selection) == self.selection_tail(selection) } /// Return true if all selections are single cursors. pub fn all_single_cursors(&self) -> bool { self.selections .iter() .all(|s| self.selection_is_single_cursor(s)) } pub fn cursors_at_line_start(&self, ctx: &AppContext) -> bool { self.all_single_cursors(); for selection in self.selections.iter() { let cursor_offset = self.selection_head(selection); if cursor_offset != self.buffer.as_ref(ctx).containing_line_start(cursor_offset) { return false; } } true } /// Validate the buffer content with this selection model's anchors. pub fn validate_buffer(&self, ctx: &impl galaxyui::ModelAsRef) { self.buffer.as_ref(ctx).validate(&self.anchors); } pub(super) fn shift_selections_after_offset(&mut self, offset: CharOffset, delta: usize) { for selection in self.selections.iter_mut() { let head_offset = self .anchors .resolve(selection.head()) .expect("Anchor should be valid"); if head_offset >= offset && head_offset < offset + delta { selection.set_head(&mut self.anchors, offset + delta); } let tail_offset = self .anchors .resolve(selection.tail()) .expect("Anchor should be valid"); if tail_offset >= offset && tail_offset < offset + delta { selection.set_tail(&mut self.anchors, offset + delta); } } } pub(super) fn set_single_cursor(&mut self, offset: CharOffset) { self.set_selection_offsets(vec1![SelectionOffsets { head: offset, tail: offset }]); } pub(super) fn set_clamped_selection_head( &mut self, selection: &mut Selection, offset: CharOffset, ) { selection.set_head(&mut self.anchors, offset); } pub(super) fn set_clamped_selection_tail( &mut self, selection: &mut Selection, offset: CharOffset, ) { selection.set_tail(&mut self.anchors, offset); } pub(super) fn update_selection_offsets(&mut self, clamped_selections: Vec1) { debug_assert!( clamped_selections.len() == self.selections().len(), "To update selection offsets, you must provide the same number of offset pairs as there are currently active selections" ); for (selection, offsets) in self.selections.iter_mut().zip(clamped_selections.iter()) { selection.set_head(&mut self.anchors, offsets.head); selection.set_tail(&mut self.anchors, offsets.tail); } } /// Clear the current selections and replace with this new set of selections. /// All biases will be cleared. pub fn set_selection_offsets(&mut self, selections: Vec1) { let new_selections = selections.mapped(|offsets| { let head_anchor = self.create_anchor(offsets.head, AnchorSide::Right); let tail_anchor = self.create_anchor(offsets.tail, AnchorSide::Right); Selection::new(head_anchor, tail_anchor) }); self.set_selections(new_selections.into()); } } impl Entity for BufferSelectionModel { type Event = (); }