From b2c76dc627d1cabe83b69a025fccb8ea8259d2a5 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Mon, 26 Jul 2021 18:04:03 -0700 Subject: [PATCH] Improve `Range` documentation and organization. --- helix-core/src/selection.rs | 99 +++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 247a69fe4..ec2094245 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -1,5 +1,5 @@ -//! Selections are the primary editing construct. Even a single cursor is -//! defined as a single empty or 1-wide selection range. +//! Selections are the primary editing construct. Even cursors are +//! defined as a selection range. //! //! All positioning is done via `char` offsets into the buffer. use crate::{ @@ -14,17 +14,20 @@ use std::borrow::Cow; /// 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 -/// directly extending the selection. The head and anchor -/// can be in any order: either can precede or follow the -/// other in the text, and they can share the same position -/// for a zero-width range. +/// directly extending a selection. The head and anchor +/// can be in any order, or even share the same position. +/// +/// 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 /// illustrate. The anchor and head indices are show as /// "(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. /// - (3, 0): ]Som[e text. @@ -37,6 +40,12 @@ use std::borrow::Cow; /// are directly adjecent, sharing an edge, do not overlap. /// However, a zero-width range will overlap with the shared /// 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)] pub struct Range { /// 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) } - /// 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. #[inline] #[must_use] @@ -184,32 +186,16 @@ 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] + // groupAt + #[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 - } + pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> { + text.slice(self.from()..self.to()).into() } + //-------------------------------- + // Alignment methods. + /// Compute a possibly new range from this range, with its ends /// 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. #[must_use] #[inline] @@ -284,11 +299,11 @@ impl Range { } } - // groupAt - + /// The line number that the block-cursor is on. #[inline] - pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> { - text.slice(self.from()..self.to()).into() + #[must_use] + pub fn cursor_line(&self, text: RopeSlice) -> usize { + text.char_to_line(self.cursor(text)) } }