hjkl_buffer/motion.rs
1//! Motion vocabulary helpers.
2//!
3//! Patch C (0.0.30) relocated the 24 inherent vim motion helpers
4//! that lived here onto [`hjkl_engine::motions`] free functions
5//! over `&mut hjkl_buffer::Buffer`. Motions don't belong on `Buffer`
6//! — they're computed over the buffer, not delegated to it; the
7//! relocation is a step toward 0.1.0's full motion-as-trait-bound
8//! generic-ification.
9//!
10//! What stays in this module: [`is_keyword_char`] — the
11//! `iskeyword`-spec parser. Keyword classification is data over the
12//! `iskeyword` string and a single `char`; it has no buffer
13//! dependency, so the engine motions module re-exports it from here.
14
15/// Match `c` against a vim-style `iskeyword` spec. Tokens are
16/// comma-separated; understood forms: `@` (any alphabetic),
17/// `_` (literal underscore), `N-M` (decimal char-code range, inclusive),
18/// bare integer `N` (single char code), single ASCII punctuation char
19/// (literal). Unknown tokens are ignored.
20pub fn is_keyword_char(c: char, spec: &str) -> bool {
21 for raw in spec.split(',') {
22 let token = raw.trim();
23 if token.is_empty() {
24 continue;
25 }
26 if token == "@" {
27 if c.is_alphabetic() {
28 return true;
29 }
30 continue;
31 }
32 if let Some((lo, hi)) = token.split_once('-')
33 && let (Ok(lo), Ok(hi)) = (lo.parse::<u32>(), hi.parse::<u32>())
34 {
35 if (lo..=hi).contains(&(c as u32)) {
36 return true;
37 }
38 continue;
39 }
40 if let Ok(n) = token.parse::<u32>() {
41 if c as u32 == n {
42 return true;
43 }
44 continue;
45 }
46 let mut chars = token.chars();
47 if let (Some(only), None) = (chars.next(), chars.next())
48 && c == only
49 {
50 return true;
51 }
52 }
53 false
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn iskeyword_alphabetic_via_at() {
62 assert!(is_keyword_char('a', "@"));
63 assert!(is_keyword_char('Z', "@"));
64 assert!(!is_keyword_char('1', "@"));
65 }
66
67 #[test]
68 fn iskeyword_numeric_range() {
69 assert!(is_keyword_char('0', "48-57"));
70 assert!(is_keyword_char('9', "48-57"));
71 assert!(!is_keyword_char('a', "48-57"));
72 }
73
74 #[test]
75 fn iskeyword_literal_punctuation() {
76 assert!(is_keyword_char('_', "_"));
77 assert!(!is_keyword_char('.', "_"));
78 }
79
80 #[test]
81 fn iskeyword_default_spec() {
82 // Matches vim default `@,48-57,_,192-255` and engine's
83 // `Settings::default()`.
84 let spec = "@,48-57,_,192-255";
85 assert!(is_keyword_char('a', spec));
86 assert!(is_keyword_char('5', spec));
87 assert!(is_keyword_char('_', spec));
88 assert!(!is_keyword_char(' ', spec));
89 assert!(!is_keyword_char('.', spec));
90 }
91}