1use std::{ops::Range, sync::LazyLock};
76
77use duat::prelude::*;
78
79#[derive(Default)]
81pub struct Hop;
82
83impl Plugin for Hop {
84 fn plug(self, _: &Plugins) {
85 mode::map::<mode::User>("w", Hopper::word());
86 mode::map::<mode::User>("l", Hopper::line());
87
88 form::set_weak("hop", "accent.info");
89 form::set_weak("hop.char2", "hop.char1");
90 }
91}
92
93#[derive(Clone)]
94pub struct Hopper {
95 regex: &'static str,
96 ranges: Vec<Range<usize>>,
97 seq: String,
98}
99
100impl Hopper {
101 pub fn word() -> Self {
104 Self {
105 regex: "[^\n\\s]+",
106 ranges: Vec::new(),
107 seq: String::new(),
108 }
109 }
110
111 pub fn line() -> Self {
113 Self { regex: "[^\n\\s][^\n]+", ..Self::word() }
114 }
115
116 pub fn with_regex(regex: &'static str) -> Self {
118 Self { regex, ..Self::word() }
119 }
120}
121
122impl Mode for Hopper {
123 type Widget = Buffer;
124
125 fn on_switch(&mut self, pa: &mut Pass, handle: Handle) {
126 let (file, area) = handle.write_with_area(pa);
127
128 let opts = file.opts;
129 let text = file.text_mut();
130
131 let id = form::id_of!("cloak");
132 text.insert_tag(*CLOAK_TAGGER, .., id.to_tag(101));
133
134 let start = area.start_points(text, opts).real;
135 let end = area.end_points(text, opts).real;
136
137 self.ranges = text.search_fwd(self.regex, start..end).unwrap().collect();
138
139 let seqs = key_seqs(self.ranges.len());
140
141 for (seq, r) in seqs.iter().zip(&self.ranges) {
142 let ghost = if seq.len() == 1 {
143 Ghost(txt!("[hop.one_char:102]{seq}"))
144 } else {
145 let mut chars = seq.chars();
146 Ghost(txt!(
147 "[hop.char1:102]{}[hop.char2:102]{}",
148 chars.next().unwrap(),
149 chars.next().unwrap()
150 ))
151 };
152
153 text.insert_tag(*TAGGER, r.start, ghost);
154
155 let seq_end = if r.end == r.start + 1
156 && let Some('\n') = text.char_at(r.end)
157 {
158 r.end
159 } else {
160 let chars = text.strs(r.start..).unwrap().chars().map(|c| c.len_utf8());
161 r.start + chars.take(seq.len()).sum::<usize>()
162 };
163
164 text.insert_tag(*TAGGER, r.start..seq_end, Conceal);
165 }
166 }
167
168 fn send_key(&mut self, pa: &mut Pass, key_event: KeyEvent, handle: Handle) {
169 let char = match key_event {
170 event!(KeyCode::Char(c)) => c,
171 _ => {
172 context::error!("Invalid label input");
173 mode::reset::<Buffer>();
174 return;
175 }
176 };
177
178 self.seq.push(char);
179
180 handle.write(pa).selections_mut().remove_extras();
181
182 let seqs = key_seqs(self.ranges.len());
183 for (seq, r) in seqs.iter().zip(&self.ranges) {
184 if *seq == self.seq {
185 handle.edit_main(pa, |mut e| e.move_to(r.clone()));
186 mode::reset::<Buffer>();
187 } else if seq.starts_with(&self.seq) {
188 continue;
189 }
190 handle.write(pa).text_mut().remove_tags(*TAGGER, r.start);
192 }
193
194 if self.seq.chars().count() == 2 || !LETTERS.contains(char) {
195 mode::reset::<Buffer>();
196 }
197 }
198
199 fn before_exit(&mut self, pa: &mut Pass, handle: Handle<Self::Widget>) {
200 handle
201 .write(pa)
202 .text_mut()
203 .remove_tags([*TAGGER, *CLOAK_TAGGER], ..)
204 }
205}
206
207fn key_seqs(len: usize) -> Vec<String> {
208 let double = len / LETTERS.len();
209 let mut seqs = Vec::new();
210
211 seqs.extend(LETTERS.chars().skip(double).map(char::into));
212 let chars = LETTERS.chars().take(double);
213 seqs.extend(chars.flat_map(|c1| LETTERS.chars().map(move |c2| format!("{c1}{c2}"))));
214
215 seqs
216}
217
218static LETTERS: &str = "abcdefghijklmnopqrstuvwxyz";
219static TAGGER: LazyLock<Tagger> = Tagger::new_static();
220static CLOAK_TAGGER: LazyLock<Tagger> = Tagger::new_static();