on master help sign in
add move_prev_long_word_end and extend_prev_long_word_end (#6905)
352d157vwkd
helix-core/src/movement.rs+104−3
@@ -177,6 +177,10 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran
177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
178}
178}
179
179
180+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
181+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
182+}
183+
180pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
184pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
181 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
185 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
182}
186}
@@ -189,8 +193,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
189 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
193 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
190}
194}
191
195
192−pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
196+pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
193− word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
197+ word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
194}
198}
195
199
196fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
200fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -199,6 +203,7 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
199 WordMotionTarget::PrevWordStart
203 WordMotionTarget::PrevWordStart
200 | WordMotionTarget::PrevLongWordStart
204 | WordMotionTarget::PrevLongWordStart
201 | WordMotionTarget::PrevWordEnd
205 | WordMotionTarget::PrevWordEnd
206+ | WordMotionTarget::PrevLongWordEnd
202 );
207 );
203
208
204 // Special-case early-out.
209 // Special-case early-out.
@@ -377,6 +382,7 @@ pub enum WordMotionTarget {
377 NextLongWordStart,
382 NextLongWordStart,
378 NextLongWordEnd,
383 NextLongWordEnd,
379 PrevLongWordStart,
384 PrevLongWordStart,
385+ PrevLongWordEnd,
380}
386}
381
387
382pub trait CharHelpers {
388pub trait CharHelpers {
@@ -393,6 +399,7 @@ impl CharHelpers for Chars<'_> {
393 WordMotionTarget::PrevWordStart
399 WordMotionTarget::PrevWordStart
394 | WordMotionTarget::PrevLongWordStart
400 | WordMotionTarget::PrevLongWordStart
395 | WordMotionTarget::PrevWordEnd
401 | WordMotionTarget::PrevWordEnd
402+ | WordMotionTarget::PrevLongWordEnd
396 );
403 );
397
404
398 // Reverse the iterator if needed for the motion direction.
405 // Reverse the iterator if needed for the motion direction.
@@ -479,7 +486,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
479 is_word_boundary(prev_ch, next_ch)
486 is_word_boundary(prev_ch, next_ch)
480 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
487 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
481 }
488 }
482− WordMotionTarget::NextLongWordStart => {
489+ WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
483 is_long_word_boundary(prev_ch, next_ch)
490 is_long_word_boundary(prev_ch, next_ch)
484 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
491 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
485 }
492 }
@@ -1445,6 +1452,100 @@ mod test {
1445 }
1452 }
1446 }
1453 }
1447
1454
1455+ #[test]
1456+ fn test_behaviour_when_moving_to_end_of_prev_long_words() {
1457+ let tests = [
1458+ (
1459+ "Basic backward motion from the middle of a word",
1460+ vec![(1, Range::new(3, 3), Range::new(4, 0))],
1461+ ),
1462+ ("Starting from after boundary retreats the anchor",
1463+ vec![(1, Range::new(0, 9), Range::new(8, 0))],
1464+ ),
1465+ (
1466+ "Jump to end of a word succeeded by whitespace",
1467+ vec![(1, Range::new(10, 10), Range::new(10, 4))],
1468+ ),
1469+ (
1470+ " Jump to start of line from end of word preceded by whitespace",
1471+ vec![(1, Range::new(3, 4), Range::new(4, 0))],
1472+ ),
1473+ ("Previous anchor is irrelevant for backward motions",
1474+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1475+ (
1476+ " Starting from whitespace moves to first space in sequence",
1477+ vec![(1, Range::new(0, 4), Range::new(4, 0))],
1478+ ),
1479+ ("Identifiers_with_underscores are considered a single word",
1480+ vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1481+ (
1482+ "Jumping\n \nback through a newline selects whitespace",
1483+ vec![(1, Range::new(0, 13), Range::new(12, 8))],
1484+ ),
1485+ (
1486+ "Jumping to start of word from the end selects the word",
1487+ vec![(1, Range::new(6, 7), Range::new(7, 0))],
1488+ ),
1489+ (
1490+ "alphanumeric.!,and.?=punctuation are treated exactly the same",
1491+ vec![(1, Range::new(29, 30), Range::new(30, 0))],
1492+ ),
1493+ (
1494+ "... ... punctuation and spaces behave as expected",
1495+ vec![
1496+ (1, Range::new(0, 10), Range::new(9, 3)),
1497+ (1, Range::new(10, 6), Range::new(7, 3)),
1498+ ],
1499+ ),
1500+ (".._.._ punctuation is joined by underscores into a single block",
1501+ vec![(1, Range::new(0, 6), Range::new(6, 0))]),
1502+ (
1503+ "Newlines\n\nare bridged seamlessly.",
1504+ vec![(1, Range::new(0, 10), Range::new(8, 0))],
1505+ ),
1506+ (
1507+ "Jumping \n\n\n\n\nback from within a newline group selects previous block",
1508+ vec![(1, Range::new(0, 13), Range::new(11, 7))],
1509+ ),
1510+ (
1511+ "Failed motions do not modify the range",
1512+ vec![(0, Range::new(3, 0), Range::new(3, 0))],
1513+ ),
1514+ (
1515+ "Multiple motions at once resolve correctly",
1516+ vec![(3, Range::new(19, 19), Range::new(8, 0))],
1517+ ),
1518+ (
1519+ "Excessive motions are performed partially",
1520+ vec![(999, Range::new(40, 40), Range::new(9, 0))],
1521+ ),
1522+ (
1523+ "", // Edge case of moving backwards in empty string
1524+ vec![(1, Range::new(0, 0), Range::new(0, 0))],
1525+ ),
1526+ (
1527+ "\n\n\n\n\n", // Edge case of moving backwards in all newlines
1528+ vec![(1, Range::new(5, 5), Range::new(0, 0))],
1529+ ),
1530+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1531+ vec![
1532+ (1, Range::new(0, 8), Range::new(7, 4)),
1533+ (1, Range::new(7, 4), Range::new(3, 0)),
1534+ ]),
1535+ ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
1536+ vec![
1537+ (1, Range::new(0, 8), Range::new(7, 0)),
1538+ ]),
1539+ ];
1540+
1541+ for (sample, scenario) in tests {
1542+ for (count, begin, expected_end) in scenario.into_iter() {
1543+ let range = move_prev_long_word_end(Rope::from(sample).slice(..), begin, count);
1544+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1545+ }
1546+ }
1547+ }
1548+
1448 #[test]
1549 #[test]
1449 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1550 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1450 let tests = [
1551 let tests = [
helix-term/src/commands.rs+10
@@ -247,6 +247,7 @@ impl MappableCommand {
247 move_next_long_word_start, "Move to start of next long word",
247 move_next_long_word_start, "Move to start of next long word",
248 move_prev_long_word_start, "Move to start of previous long word",
248 move_prev_long_word_start, "Move to start of previous long word",
249 move_next_long_word_end, "Move to end of next long word",
249 move_next_long_word_end, "Move to end of next long word",
250+ move_prev_long_word_end, "Move to end of previous long word",
250 extend_next_word_start, "Extend to start of next word",
251 extend_next_word_start, "Extend to start of next word",
251 extend_prev_word_start, "Extend to start of previous word",
252 extend_prev_word_start, "Extend to start of previous word",
252 extend_next_word_end, "Extend to end of next word",
253 extend_next_word_end, "Extend to end of next word",
@@ -254,6 +255,7 @@ impl MappableCommand {
254 extend_next_long_word_start, "Extend to start of next long word",
255 extend_next_long_word_start, "Extend to start of next long word",
255 extend_prev_long_word_start, "Extend to start of previous long word",
256 extend_prev_long_word_start, "Extend to start of previous long word",
256 extend_next_long_word_end, "Extend to end of next long word",
257 extend_next_long_word_end, "Extend to end of next long word",
258+ extend_prev_long_word_end, "Extend to end of prev long word",
257 find_till_char, "Move till next occurrence of char",
259 find_till_char, "Move till next occurrence of char",
258 find_next_char, "Move to next occurrence of char",
260 find_next_char, "Move to next occurrence of char",
259 extend_till_char, "Extend till next occurrence of char",
261 extend_till_char, "Extend till next occurrence of char",
@@ -1067,6 +1069,10 @@ fn move_prev_long_word_start(cx: &mut Context) {
1067 move_word_impl(cx, movement::move_prev_long_word_start)
1069 move_word_impl(cx, movement::move_prev_long_word_start)
1068}
1070}
1069
1071
1072+fn move_prev_long_word_end(cx: &mut Context) {
1073+ move_word_impl(cx, movement::move_prev_long_word_end)
1074+}
1075+
1070fn move_next_long_word_end(cx: &mut Context) {
1076fn move_next_long_word_end(cx: &mut Context) {
1071 move_word_impl(cx, movement::move_next_long_word_end)
1077 move_word_impl(cx, movement::move_next_long_word_end)
1072}
1078}
@@ -1224,6 +1230,10 @@ fn extend_prev_long_word_start(cx: &mut Context) {
1224 extend_word_impl(cx, movement::move_prev_long_word_start)
1230 extend_word_impl(cx, movement::move_prev_long_word_start)
1225}
1231}
1226
1232
1233+fn extend_prev_long_word_end(cx: &mut Context) {
1234+ extend_word_impl(cx, movement::move_prev_long_word_end)
1235+}
1236+
1227fn extend_next_long_word_end(cx: &mut Context) {
1237fn extend_next_long_word_end(cx: &mut Context) {
1228 extend_word_impl(cx, movement::move_next_long_word_end)
1238 extend_word_impl(cx, movement::move_next_long_word_end)
1229}
1239}