1use once_cell::sync::Lazy;
7use regex::Regex;
8
9const KILL_RING_MAX_SIZE: usize = 10;
12
13static KILL_RING: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(Vec::new());
14static KILL_RING_INDEX: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
15static LAST_ACTION_WAS_KILL: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
16
17static LAST_YANK_START: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
19static LAST_YANK_LENGTH: std::sync::Mutex<usize> = std::sync::Mutex::new(0);
20static LAST_ACTION_WAS_YANK: std::sync::Mutex<bool> = std::sync::Mutex::new(false);
21
22pub fn push_to_kill_ring(text: &str, direction: &str) {
24 if text.is_empty() {
25 return;
26 }
27
28 let mut kill_ring = KILL_RING.lock().unwrap();
29 let mut last_action_kill = LAST_ACTION_WAS_KILL.lock().unwrap();
30
31 if *last_action_kill && !kill_ring.is_empty() {
32 if direction == "prepend" {
34 kill_ring[0] = format!("{}{}", text, kill_ring[0]);
35 } else {
36 kill_ring[0] = format!("{}{}", kill_ring[0], text);
37 }
38 } else {
39 kill_ring.insert(0, text.to_string());
41 if kill_ring.len() > KILL_RING_MAX_SIZE {
42 kill_ring.pop();
43 }
44 }
45 *last_action_kill = true;
46
47 let mut last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
49 *last_action_yank = false;
50}
51
52pub fn get_last_kill() -> String {
54 let kill_ring = KILL_RING.lock().unwrap();
55 kill_ring.first().cloned().unwrap_or_default()
56}
57
58pub fn get_kill_ring_item(index: usize) -> String {
60 let kill_ring = KILL_RING.lock().unwrap();
61 if kill_ring.is_empty() {
62 return String::new();
63 }
64 let normalized_index = ((index % kill_ring.len()) + kill_ring.len()) % kill_ring.len();
65 kill_ring.get(normalized_index).cloned().unwrap_or_default()
66}
67
68pub fn get_kill_ring_size() -> usize {
70 let kill_ring = KILL_RING.lock().unwrap();
71 kill_ring.len()
72}
73
74pub fn clear_kill_ring() {
76 let mut kill_ring = KILL_RING.lock().unwrap();
77 let mut kill_ring_index = KILL_RING_INDEX.lock().unwrap();
78 let mut last_action_kill = LAST_ACTION_WAS_KILL.lock().unwrap();
79 let mut last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
80 let mut last_yank_start = LAST_YANK_START.lock().unwrap();
81 let mut last_yank_length = LAST_YANK_LENGTH.lock().unwrap();
82
83 kill_ring.clear();
84 *kill_ring_index = 0;
85 *last_action_kill = false;
86 *last_action_yank = false;
87 *last_yank_start = 0;
88 *last_yank_length = 0;
89}
90
91pub fn reset_kill_accumulation() {
93 let mut last_action_kill = LAST_ACTION_WAS_KILL.lock().unwrap();
94 *last_action_kill = false;
95}
96
97pub fn record_yank(start: usize, length: usize) {
99 let mut last_yank_start = LAST_YANK_START.lock().unwrap();
100 let mut last_yank_length = LAST_YANK_LENGTH.lock().unwrap();
101 let mut last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
102 let mut kill_ring_index = KILL_RING_INDEX.lock().unwrap();
103
104 *last_yank_start = start;
105 *last_yank_length = length;
106 *last_action_yank = true;
107 *kill_ring_index = 0;
108}
109
110pub fn can_yank_pop() -> bool {
112 let last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
113 let kill_ring = KILL_RING.lock().unwrap();
114 *last_action_yank && kill_ring.len() > 1
115}
116
117pub fn yank_pop() -> Option<YankPopResult> {
119 let last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
120 let kill_ring = KILL_RING.lock().unwrap();
121
122 if !*last_action_yank || kill_ring.len() <= 1 {
123 return None;
124 }
125 drop(last_action_yank);
126 drop(kill_ring);
127
128 let mut kill_ring_index = KILL_RING_INDEX.lock().unwrap();
129 let last_yank_start = LAST_YANK_START.lock().unwrap();
130 let last_yank_length = LAST_YANK_LENGTH.lock().unwrap();
131
132 let kill_ring = KILL_RING.lock().unwrap();
134 *kill_ring_index = (*kill_ring_index + 1) % kill_ring.len();
135 let text = kill_ring.get(*kill_ring_index).cloned().unwrap_or_default();
136
137 Some(YankPopResult {
138 text,
139 start: *last_yank_start,
140 length: *last_yank_length,
141 })
142}
143
144pub fn update_yank_length(length: usize) {
146 let mut last_yank_length = LAST_YANK_LENGTH.lock().unwrap();
147 *last_yank_length = length;
148}
149
150pub fn reset_yank_state() {
152 let mut last_action_yank = LAST_ACTION_WAS_YANK.lock().unwrap();
153 *last_action_yank = false;
154}
155
156#[derive(Debug, Clone)]
158pub struct YankPopResult {
159 pub text: String,
160 pub start: usize,
161 pub length: usize,
162}
163
164pub static VIM_WORD_CHAR_REGEX: Lazy<Regex> =
166 Lazy::new(|| Regex::new(r"^[\p{L}\p{N}\p{M}_]$").unwrap());
167pub static WHITESPACE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s").unwrap());
168
169pub fn is_vim_word_char(ch: &str) -> bool {
171 VIM_WORD_CHAR_REGEX.is_match(ch)
172}
173
174pub fn is_vim_whitespace(ch: &str) -> bool {
176 WHITESPACE_REGEX.is_match(ch)
177}
178
179pub fn is_vim_punctuation(ch: &str) -> bool {
181 !ch.is_empty() && !is_vim_whitespace(ch) && !is_vim_word_char(ch)
182}