|
|
@ -1,5 +1,5 @@
|
|
|
|
//! Selections are the primary editing construct. Even a single cursor is
|
|
|
|
//! Selections are the primary editing construct. Even cursors are
|
|
|
|
//! defined as a single empty or 1-wide selection range.
|
|
|
|
//! defined as a selection range.
|
|
|
|
//!
|
|
|
|
//!
|
|
|
|
//! All positioning is done via `char` offsets into the buffer.
|
|
|
|
//! All positioning is done via `char` offsets into the buffer.
|
|
|
|
use crate::{
|
|
|
|
use crate::{
|
|
|
@ -14,17 +14,20 @@ use std::borrow::Cow;
|
|
|
|
|
|
|
|
|
|
|
|
/// A single selection range.
|
|
|
|
/// A single selection range.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// The range consists of an "anchor" and "head" position in
|
|
|
|
/// A range consists of an "anchor" and "head" position in
|
|
|
|
/// the text. The head is the part that the user moves when
|
|
|
|
/// the text. The head is the part that the user moves when
|
|
|
|
/// directly extending the selection. The head and anchor
|
|
|
|
/// directly extending a selection. The head and anchor
|
|
|
|
/// can be in any order: either can precede or follow the
|
|
|
|
/// can be in any order, or even share the same position.
|
|
|
|
/// other in the text, and they can share the same position
|
|
|
|
///
|
|
|
|
/// for a zero-width range.
|
|
|
|
/// The anchor and head positions use gap indexing, meaning
|
|
|
|
|
|
|
|
/// that their indices represent the the gaps *between* `char`s
|
|
|
|
|
|
|
|
/// rather than the `char`s themselves. For example, 1
|
|
|
|
|
|
|
|
/// represents the position between the first and second `char`.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// Below are some example `Range` configurations to better
|
|
|
|
/// Below are some example `Range` configurations to better
|
|
|
|
/// illustrate. The anchor and head indices are show as
|
|
|
|
/// illustrate. The anchor and head indices are show as
|
|
|
|
/// "(anchor, head)", followed by example text with "[" and "]"
|
|
|
|
/// "(anchor, head)", followed by example text with "[" and "]"
|
|
|
|
/// inserted to visually represent the anchor and head positions:
|
|
|
|
/// inserted to represent the anchor and head positions:
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// - (0, 3): [Som]e text.
|
|
|
|
/// - (0, 3): [Som]e text.
|
|
|
|
/// - (3, 0): ]Som[e text.
|
|
|
|
/// - (3, 0): ]Som[e text.
|
|
|
@ -37,6 +40,12 @@ use std::borrow::Cow;
|
|
|
|
/// are directly adjecent, sharing an edge, do not overlap.
|
|
|
|
/// are directly adjecent, sharing an edge, do not overlap.
|
|
|
|
/// However, a zero-width range will overlap with the shared
|
|
|
|
/// However, a zero-width range will overlap with the shared
|
|
|
|
/// left-edge of another range.
|
|
|
|
/// left-edge of another range.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// By convention, user-facing ranges are considered to have
|
|
|
|
|
|
|
|
/// a block cursor on the head-side of the range that spans a
|
|
|
|
|
|
|
|
/// single grapheme inward from the range's edge. There are a
|
|
|
|
|
|
|
|
/// variety of helper methods on `Range` for working in terms of
|
|
|
|
|
|
|
|
/// that block cursor, all of which have `cursor` in their name.
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub struct Range {
|
|
|
|
pub struct Range {
|
|
|
|
/// The anchor of the range: the side that doesn't move when extending.
|
|
|
|
/// The anchor of the range: the side that doesn't move when extending.
|
|
|
@ -73,13 +82,6 @@ impl Range {
|
|
|
|
std::cmp::max(self.anchor, self.head)
|
|
|
|
std::cmp::max(self.anchor, self.head)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// The line number that the block-cursor is on.
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
pub fn cursor_line(&self, text: RopeSlice) -> usize {
|
|
|
|
|
|
|
|
text.char_to_line(self.cursor(text))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// The (inclusive) range of lines that the range overlaps.
|
|
|
|
/// The (inclusive) range of lines that the range overlaps.
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
#[must_use]
|
|
|
@ -184,32 +186,16 @@ impl Range {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Compute a possibly new range from this range, attempting to ensure
|
|
|
|
// groupAt
|
|
|
|
/// a minimum range width of 1 char by shifting the head in the forward
|
|
|
|
|
|
|
|
/// direction as needed.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// This method will never shift the anchor, and will only shift the
|
|
|
|
|
|
|
|
/// head in the forward direction. Therefore, this method can fail
|
|
|
|
|
|
|
|
/// at ensuring the minimum width if and only if the passed range is
|
|
|
|
|
|
|
|
/// both zero-width and at the end of the `RopeSlice`.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// If the input range is grapheme-boundary aligned, the returned range
|
|
|
|
|
|
|
|
/// will also be. Specifically, if the head needs to shift to achieve
|
|
|
|
|
|
|
|
/// the minimum width, it will shift to the next grapheme boundary.
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
pub fn min_width_1(&self, slice: RopeSlice) -> Self {
|
|
|
|
pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
|
|
|
|
if self.anchor == self.head {
|
|
|
|
text.slice(self.from()..self.to()).into()
|
|
|
|
Range {
|
|
|
|
|
|
|
|
anchor: self.anchor,
|
|
|
|
|
|
|
|
head: next_grapheme_boundary(slice, self.head),
|
|
|
|
|
|
|
|
horiz: self.horiz,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
*self
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
// Alignment methods.
|
|
|
|
|
|
|
|
|
|
|
|
/// Compute a possibly new range from this range, with its ends
|
|
|
|
/// Compute a possibly new range from this range, with its ends
|
|
|
|
/// shifted as needed to align with grapheme boundaries.
|
|
|
|
/// shifted as needed to align with grapheme boundaries.
|
|
|
|
///
|
|
|
|
///
|
|
|
@ -243,6 +229,35 @@ impl Range {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Compute a possibly new range from this range, attempting to ensure
|
|
|
|
|
|
|
|
/// a minimum range width of 1 char by shifting the head in the forward
|
|
|
|
|
|
|
|
/// direction as needed.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// This method will never shift the anchor, and will only shift the
|
|
|
|
|
|
|
|
/// head in the forward direction. Therefore, this method can fail
|
|
|
|
|
|
|
|
/// at ensuring the minimum width if and only if the passed range is
|
|
|
|
|
|
|
|
/// both zero-width and at the end of the `RopeSlice`.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// If the input range is grapheme-boundary aligned, the returned range
|
|
|
|
|
|
|
|
/// will also be. Specifically, if the head needs to shift to achieve
|
|
|
|
|
|
|
|
/// the minimum width, it will shift to the next grapheme boundary.
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
pub fn min_width_1(&self, slice: RopeSlice) -> Self {
|
|
|
|
|
|
|
|
if self.anchor == self.head {
|
|
|
|
|
|
|
|
Range {
|
|
|
|
|
|
|
|
anchor: self.anchor,
|
|
|
|
|
|
|
|
head: next_grapheme_boundary(slice, self.head),
|
|
|
|
|
|
|
|
horiz: self.horiz,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
*self
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
|
|
|
// Block-cursor methods.
|
|
|
|
|
|
|
|
|
|
|
|
/// Gets the left-side position of the block cursor.
|
|
|
|
/// Gets the left-side position of the block cursor.
|
|
|
|
#[must_use]
|
|
|
|
#[must_use]
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
@ -284,11 +299,11 @@ impl Range {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// groupAt
|
|
|
|
/// The line number that the block-cursor is on.
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
|
|
|
|
#[must_use]
|
|
|
|
text.slice(self.from()..self.to()).into()
|
|
|
|
pub fn cursor_line(&self, text: RopeSlice) -> usize {
|
|
|
|
|
|
|
|
text.char_to_line(self.cursor(text))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|