first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -2,11 +2,9 @@ use anyhow::{anyhow, Result};
|
||||
use itertools::Itertools;
|
||||
use string_offset::{ByteOffset, CharCounter, CharOffset};
|
||||
|
||||
use crate::event::ModifiersState;
|
||||
|
||||
use self::point::Point;
|
||||
|
||||
use self::word_boundaries::WordBoundaries;
|
||||
use self::word_boundaries::{WordBoundaries, WordBoundariesPolicy};
|
||||
use crate::event::ModifiersState;
|
||||
|
||||
pub mod header;
|
||||
pub mod point;
|
||||
@@ -189,6 +187,59 @@ pub trait TextBuffer {
|
||||
self,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the target [`Point`] for expanding a semantic (double-click) selection from
|
||||
/// `position` in the given `direction`, respecting the word-boundary `policy`.
|
||||
fn semantic_expansion_target<T: BufferIndex>(
|
||||
&self,
|
||||
position: T,
|
||||
direction: SelectionDirection,
|
||||
policy: &WordBoundariesPolicy,
|
||||
) -> Result<Point> {
|
||||
let offset = position.to_char_offset(self)?;
|
||||
|
||||
let target = match direction {
|
||||
SelectionDirection::Forward => {
|
||||
let mut chars = self.chars_at(offset)?;
|
||||
let mut end = offset;
|
||||
// Include the character under the cursor (if any).
|
||||
if chars.next().is_some() {
|
||||
end += 1;
|
||||
// Extend through the following word only if the very next character is part
|
||||
// of a word; a boundary char here stops us (no trailing whitespace, no
|
||||
// crossing into the next word past a separator run).
|
||||
if chars.next().is_some_and(|c| !policy.is_word_boundary(c)) {
|
||||
end += 1;
|
||||
for c in chars {
|
||||
if policy.is_word_boundary(c) {
|
||||
break;
|
||||
}
|
||||
end += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
}
|
||||
SelectionDirection::Backward => {
|
||||
let mut chars = self.chars_rev_at(offset)?;
|
||||
let mut start = offset;
|
||||
// Extend left through a preceding word only if the character immediately before
|
||||
// the cursor is part of a word.
|
||||
if chars.next().is_some_and(|c| !policy.is_word_boundary(c)) {
|
||||
start -= 1;
|
||||
for c in chars {
|
||||
if policy.is_word_boundary(c) {
|
||||
break;
|
||||
}
|
||||
start -= 1;
|
||||
}
|
||||
}
|
||||
start
|
||||
}
|
||||
};
|
||||
|
||||
self.to_point(target)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type which can index into a text buffer.
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
use super::{
|
||||
count_chars_up_to_byte,
|
||||
point::Point,
|
||||
{char_slice, BufferIndex, TextBuffer},
|
||||
};
|
||||
|
||||
use super::str_to_byte_vec;
|
||||
use super::point::Point;
|
||||
use super::{char_slice, count_chars_up_to_byte, str_to_byte_vec, BufferIndex, TextBuffer};
|
||||
|
||||
#[test]
|
||||
fn test_str_to_byte_vec() {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{Add, AddAssign, Sub},
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Add, AddAssign, Sub};
|
||||
|
||||
/// A point within a document. The exact coordinate system (whether or not rows
|
||||
/// are soft-wrapped, what the unit for characters is) is unspecified.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::iter::Peekable;
|
||||
use std::{borrow::Cow, collections::HashSet};
|
||||
|
||||
use itertools::Either;
|
||||
use string_offset::CharOffset;
|
||||
|
||||
use super::point::Point;
|
||||
|
||||
use super::words::is_default_word_boundary;
|
||||
use super::TextBuffer;
|
||||
|
||||
@@ -20,6 +20,19 @@ pub enum WordBoundariesPolicy {
|
||||
OnlyWhitespace,
|
||||
}
|
||||
|
||||
impl WordBoundariesPolicy {
|
||||
/// Returns whether `c` is a word-boundary (separator) character under this policy.
|
||||
pub fn is_word_boundary(&self, c: char) -> bool {
|
||||
match self {
|
||||
WordBoundariesPolicy::Default => is_default_word_boundary(c),
|
||||
WordBoundariesPolicy::Custom(boundary_chars) => {
|
||||
c.is_whitespace() || boundary_chars.contains(&c)
|
||||
}
|
||||
WordBoundariesPolicy::OnlyWhitespace => c.is_whitespace(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum WordBoundariesApproach {
|
||||
ForwardWordStarts,
|
||||
@@ -150,13 +163,7 @@ impl<'a, T: TextBuffer + ?Sized> WordBoundaries<'a, T> {
|
||||
}
|
||||
|
||||
fn is_word_boundary(&self, c: char) -> bool {
|
||||
match self.policy.as_ref() {
|
||||
WordBoundariesPolicy::Default => is_default_word_boundary(c),
|
||||
WordBoundariesPolicy::Custom(boundary_chars) => {
|
||||
c.is_whitespace() || boundary_chars.contains(&c)
|
||||
}
|
||||
WordBoundariesPolicy::OnlyWhitespace => c.is_whitespace(),
|
||||
}
|
||||
self.policy.is_word_boundary(c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,71 @@
|
||||
use super::*;
|
||||
use crate::text::SelectionDirection;
|
||||
|
||||
/// Convenience wrapper for the forward exclusive-end target under the default policy.
|
||||
fn fwd(buffer: &str, offset: usize) -> Point {
|
||||
buffer
|
||||
.semantic_expansion_target(
|
||||
CharOffset::from(offset),
|
||||
SelectionDirection::Forward,
|
||||
&WordBoundariesPolicy::Default,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Convenience wrapper for the backward inclusive-start target under the default policy.
|
||||
fn bwd(buffer: &str, offset: usize) -> Point {
|
||||
buffer
|
||||
.semantic_expansion_target(
|
||||
CharOffset::from(offset),
|
||||
SelectionDirection::Backward,
|
||||
&WordBoundariesPolicy::Default,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_semantic_expansion_matches_block_list() {
|
||||
// semantic_expansion_target must behave exactly like the terminal grid's
|
||||
// semantic_search_left/right, which crucially never includes trailing whitespace when the
|
||||
// tail sits on a non-word character.
|
||||
|
||||
// Char offsets: f0 o1 o2 .3 .4 .5 <space>6 b7 a8 r9 <space>10 b11 a12 z13
|
||||
let dots = "foo... bar baz";
|
||||
|
||||
// Forward, tail on the LAST char of a punctuation run: the run is included up to that char,
|
||||
// but the trailing space is NOT (exclusive end 6 = "foo...", not 7 which would add the space).
|
||||
assert_eq!(
|
||||
fwd(dots, 5),
|
||||
Point::new(0, 6),
|
||||
"trailing space must be excluded"
|
||||
);
|
||||
// Forward, tail on an earlier punctuation char: stops right after that char (next is a
|
||||
// boundary), per the grid's per-cell behavior.
|
||||
assert_eq!(fwd(dots, 3), Point::new(0, 4));
|
||||
assert_eq!(fwd(dots, 4), Point::new(0, 5));
|
||||
// Forward, tail on a word char: ordinary word selection (unchanged).
|
||||
assert_eq!(fwd(dots, 1), Point::new(0, 3));
|
||||
assert_eq!(fwd(dots, 0), Point::new(0, 3));
|
||||
// Forward, tail directly on whitespace whose next char is a word: extends through that word
|
||||
// (matches the grid, where a space's right neighbor pulls in the word).
|
||||
assert_eq!(fwd(dots, 6), Point::new(0, 10));
|
||||
|
||||
// Backward, tail on a punctuation char whose left neighbor is also a boundary: stays put.
|
||||
assert_eq!(bwd(dots, 5), Point::new(0, 5));
|
||||
assert_eq!(bwd(dots, 4), Point::new(0, 4));
|
||||
// Backward, tail on a word char: ordinary word selection (start of "bar").
|
||||
assert_eq!(bwd(dots, 8), Point::new(0, 7));
|
||||
|
||||
// A comma followed by a space: forward from the comma includes the comma but excludes the
|
||||
// following space (the reporter's exact case).
|
||||
// Char offsets: f0 o1 o2 ,3 <space>4 b5 a6 r7
|
||||
let comma = "foo, bar";
|
||||
assert_eq!(
|
||||
fwd(comma, 3),
|
||||
Point::new(0, 4),
|
||||
"comma included, trailing space excluded",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_word_boundaries() {
|
||||
|
||||
Reference in New Issue
Block a user