1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! ZLE word operations
//!
//! Direct port from zsh/Src/Zle/zle_word.c
use super::main::{Zle, ZleChar};
/// Word style for movement
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WordStyle {
/// Emacs-style words (alphanumeric + underscore)
Emacs,
/// Vi-style words (separated by whitespace and punctuation)
Vi,
/// Shell words (quoted strings, etc.)
Shell,
/// Whitespace-separated "WORDS"
BlankDelimited,
}
impl Zle {
/// Find the start of the current/previous word
pub fn find_word_start(&self, style: WordStyle) -> usize {
let mut pos = self.zlecs;
match style {
WordStyle::Emacs => {
// Skip non-word characters
while pos > 0 && !is_emacs_word_char(self.zleline[pos - 1]) {
pos -= 1;
}
// Skip word characters
while pos > 0 && is_emacs_word_char(self.zleline[pos - 1]) {
pos -= 1;
}
}
WordStyle::Vi => {
// Skip whitespace
while pos > 0 && self.zleline[pos - 1].is_whitespace() {
pos -= 1;
}
if pos > 0 {
let is_word = is_vi_word_char(self.zleline[pos - 1]);
// Skip same class of characters
while pos > 0 {
let c = self.zleline[pos - 1];
if c.is_whitespace() || (is_vi_word_char(c) != is_word) {
break;
}
pos -= 1;
}
}
}
WordStyle::Shell => {
// TODO: implement shell word boundaries
while pos > 0 && !self.zleline[pos - 1].is_whitespace() {
pos -= 1;
}
}
WordStyle::BlankDelimited => {
// Skip whitespace
while pos > 0 && self.zleline[pos - 1].is_whitespace() {
pos -= 1;
}
// Skip non-whitespace
while pos > 0 && !self.zleline[pos - 1].is_whitespace() {
pos -= 1;
}
}
}
pos
}
/// Find the end of the current/next word
pub fn find_word_end(&self, style: WordStyle) -> usize {
let mut pos = self.zlecs;
match style {
WordStyle::Emacs => {
// Skip non-word characters
while pos < self.zlell && !is_emacs_word_char(self.zleline[pos]) {
pos += 1;
}
// Skip word characters
while pos < self.zlell && is_emacs_word_char(self.zleline[pos]) {
pos += 1;
}
}
WordStyle::Vi => {
if pos < self.zlell {
let is_word = is_vi_word_char(self.zleline[pos]);
// Skip same class of characters
while pos < self.zlell {
let c = self.zleline[pos];
if c.is_whitespace() || (is_vi_word_char(c) != is_word) {
break;
}
pos += 1;
}
// Skip whitespace
while pos < self.zlell && self.zleline[pos].is_whitespace() {
pos += 1;
}
}
}
WordStyle::Shell => {
// TODO: implement shell word boundaries
while pos < self.zlell && !self.zleline[pos].is_whitespace() {
pos += 1;
}
while pos < self.zlell && self.zleline[pos].is_whitespace() {
pos += 1;
}
}
WordStyle::BlankDelimited => {
// Skip non-whitespace
while pos < self.zlell && !self.zleline[pos].is_whitespace() {
pos += 1;
}
// Skip whitespace
while pos < self.zlell && self.zleline[pos].is_whitespace() {
pos += 1;
}
}
}
pos
}
/// Get the current word
pub fn get_current_word(&self, style: WordStyle) -> &[ZleChar] {
let start = self.find_word_start(style);
let end = self.find_word_end(style);
&self.zleline[start..end]
}
}
/// Check if character is an emacs word character
fn is_emacs_word_char(c: ZleChar) -> bool {
c.is_alphanumeric() || c == '_'
}
/// Check if character is a vi word character (alphanumeric)
fn is_vi_word_char(c: ZleChar) -> bool {
c.is_alphanumeric() || c == '_'
}