@@ -1,5 +1,5 @@
1−//! Selections are the primary editing construct. Even a single cursor is
1+//! Selections are the primary editing construct. Even cursors are
2−//! defined as a single empty or 1-wide selection range.
2+//! defined as a selection range.
4//! All positioning is done via `char` offsets into the buffer.
4//! All positioning is done via `char` offsets into the buffer.
5use crate::{
5use crate::{
@@ -14,17 +14,20 @@ use std::borrow::Cow;
15/// A single selection range.
15/// A single selection range.
17−/// The range consists of an "anchor" and "head" position in
17+/// A range consists of an "anchor" and "head" position in
18/// the text. The head is the part that the user moves when
18/// the text. The head is the part that the user moves when
19−/// directly extending the selection. The head and anchor
19+/// directly extending a selection. The head and anchor
20−/// can be in any order: either can precede or follow the
21−/// other in the text, and they can share the same position
20+/// can be in any order, or even share the same position.
22−/// for a zero-width range.
21+///
22+/// The anchor and head positions use gap indexing, meaning
23+/// that their indices represent the the gaps *between* `char`s
24+/// rather than the `char`s themselves. For example, 1
25+/// represents the position between the first and second `char`.
24/// Below are some example `Range` configurations to better
27/// Below are some example `Range` configurations to better
25/// illustrate. The anchor and head indices are show as
28/// illustrate. The anchor and head indices are show as
26/// "(anchor, head)", followed by example text with "[" and "]"
29/// "(anchor, head)", followed by example text with "[" and "]"
27−/// inserted to visually represent the anchor and head positions:
30+/// inserted to represent the anchor and head positions:
29/// - (0, 3): [Som]e text.
32/// - (0, 3): [Som]e text.
30/// - (3, 0): ]Som[e text.
33/// - (3, 0): ]Som[e text.
@@ -37,6 +40,12 @@ use std::borrow::Cow;
37/// are directly adjecent, sharing an edge, do not overlap.
40/// are directly adjecent, sharing an edge, do not overlap.
38/// However, a zero-width range will overlap with the shared
41/// However, a zero-width range will overlap with the shared
39/// left-edge of another range.
42/// left-edge of another range.
44+/// By convention, user-facing ranges are considered to have
45+/// a block cursor on the head-side of the range that spans a
46+/// single grapheme inward from the range's edge. There are a
47+/// variety of helper methods on `Range` for working in terms of
48+/// that block cursor, all of which have `cursor` in their name.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Range {
50pub struct Range {
42 /// The anchor of the range: the side that doesn't move when extending.
51 /// The anchor of the range: the side that doesn't move when extending.
@@ -73,13 +82,6 @@ impl Range {
73 std::cmp::max(self.anchor, self.head)
82 std::cmp::max(self.anchor, self.head)
76− /// The line number that the block-cursor is on.
79− pub fn cursor_line(&self, text: RopeSlice) -> usize {
80− text.char_to_line(self.cursor(text))
83 /// The (inclusive) range of lines that the range overlaps.
85 /// The (inclusive) range of lines that the range overlaps.
85 #[must_use]
87 #[must_use]
@@ -184,32 +186,16 @@ impl Range {
187− /// Compute a possibly new range from this range, attempting to ensure
189+ // groupAt
188− /// a minimum range width of 1 char by shifting the head in the forward
190+
189− /// direction as needed.
191− /// This method will never shift the anchor, and will only shift the
192− /// head in the forward direction. Therefore, this method can fail
193− /// at ensuring the minimum width if and only if the passed range is
194− /// both zero-width and at the end of the `RopeSlice`.
196− /// If the input range is grapheme-boundary aligned, the returned range
197− /// will also be. Specifically, if the head needs to shift to achieve
198− /// the minimum width, it will shift to the next grapheme boundary.
200 #[inline]
191 #[inline]
201− pub fn min_width_1(&self, slice: RopeSlice) -> Self {
192+ pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
202− if self.anchor == self.head {
193+ text.slice(self.from()..self.to()).into()
204− anchor: self.anchor,
205− head: next_grapheme_boundary(slice, self.head),
196+ //--------------------------------
197+ // Alignment methods.
213 /// Compute a possibly new range from this range, with its ends
199 /// Compute a possibly new range from this range, with its ends
214 /// shifted as needed to align with grapheme boundaries.
200 /// shifted as needed to align with grapheme boundaries.
@@ -243,6 +229,35 @@ impl Range {
232+ /// Compute a possibly new range from this range, attempting to ensure
233+ /// a minimum range width of 1 char by shifting the head in the forward
234+ /// direction as needed.
236+ /// This method will never shift the anchor, and will only shift the
237+ /// head in the forward direction. Therefore, this method can fail
238+ /// at ensuring the minimum width if and only if the passed range is
239+ /// both zero-width and at the end of the `RopeSlice`.
241+ /// If the input range is grapheme-boundary aligned, the returned range
242+ /// will also be. Specifically, if the head needs to shift to achieve
243+ /// the minimum width, it will shift to the next grapheme boundary.
246+ pub fn min_width_1(&self, slice: RopeSlice) -> Self {
247+ if self.anchor == self.head {
249+ anchor: self.anchor,
250+ head: next_grapheme_boundary(slice, self.head),
258+ //--------------------------------
259+ // Block-cursor methods.
246 /// Gets the left-side position of the block cursor.
261 /// Gets the left-side position of the block cursor.
247 #[must_use]
262 #[must_use]
248 #[inline]
263 #[inline]
@@ -284,11 +299,11 @@ impl Range {
287− // groupAt
302+ /// The line number that the block-cursor is on.
289 #[inline]
303 #[inline]
290− pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
305+ pub fn cursor_line(&self, text: RopeSlice) -> usize {
291− text.slice(self.from()..self.to()).into()
306+ text.char_to_line(self.cursor(text))