1use std::{ops::Range, sync::LazyLock};
74
75use duat::prelude::*;
76
77#[derive(Default)]
79pub struct Hop;
80
81impl Plugin for Hop {
82 fn plug(self, _: &Plugins) {
83 mode::map::<mode::User>("w", Hopper::word()).doc(txt!("[mode]Hop[] to a [a]word"));
84 mode::map::<mode::User>("l", Hopper::line()).doc(txt!("[mode]Hop[] to a [a]line"));
85
86 opts::set(|opts| opts.whichkey.always_show::<Hopper>());
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 bindings() -> mode::Bindings {
126 mode::bindings!(match _ {
127 event!(KeyCode::Char(..)) => txt!("Filter hopping entries"),
128 })
129 }
130
131 fn on_switch(&mut self, pa: &mut Pass, handle: Handle) {
132 let (buffer, area) = handle.write_with_area(pa);
133
134 let opts = buffer.opts;
135 let mut text = buffer.text_mut();
136
137 let id = form::id_of!("cloak");
138 text.insert_tag(*CLOAK_TAGGER, .., id.to_tag(101));
139
140 let start = area.start_points(&text, opts).real;
141 let end = area.end_points(&text, opts).real;
142
143 self.ranges = text.search(self.regex).range(start..end).collect();
144
145 let seqs = key_seqs(self.ranges.len());
146
147 for (seq, r) in seqs.iter().zip(&self.ranges) {
148 let ghost = if seq.len() == 1 {
149 Ghost::new(txt!("[hop.one_char:102]{seq}"))
150 } else {
151 let mut chars = seq.chars();
152 Ghost::new(txt!(
153 "[hop.char1:102]{}[hop.char2:102]{}",
154 chars.next().unwrap(),
155 chars.next().unwrap()
156 ))
157 };
158
159 text.insert_tag(*TAGGER, r.start, ghost);
160
161 let seq_end = if r.end == r.start + 1
162 && let Some('\n') = text.char_at(r.end)
163 {
164 r.end
165 } else {
166 let chars = text.strs(r.start..).unwrap().chars().map(|c| c.len_utf8());
167 r.start + chars.take(seq.len()).sum::<usize>()
168 };
169
170 text.insert_tag(*TAGGER, r.start..seq_end, Conceal);
171 }
172 }
173
174 fn send_key(&mut self, pa: &mut Pass, key_event: KeyEvent, handle: Handle) {
175 let char = match key_event {
176 event!(KeyCode::Char(c)) => c,
177 _ => {
178 context::error!("Invalid label input");
179 mode::reset::<Buffer>(pa);
180 return;
181 }
182 };
183
184 self.seq.push(char);
185
186 handle.write(pa).selections_mut().remove_extras();
187
188 let seqs = key_seqs(self.ranges.len());
189 for (seq, r) in seqs.iter().zip(&self.ranges) {
190 if *seq == self.seq {
191 handle.edit_main(pa, |mut e| e.move_to(r.clone()));
192 mode::reset::<Buffer>(pa);
193 } else if seq.starts_with(&self.seq) {
194 continue;
195 }
196 handle.write(pa).text_mut().remove_tags(*TAGGER, r.start);
198 }
199
200 if self.seq.chars().count() == 2 || !LETTERS.contains(char) {
201 mode::reset::<Buffer>(pa);
202 }
203 }
204
205 fn before_exit(&mut self, pa: &mut Pass, handle: Handle<Self::Widget>) {
206 handle
207 .write(pa)
208 .text_mut()
209 .remove_tags([*TAGGER, *CLOAK_TAGGER], ..)
210 }
211}
212
213fn key_seqs(len: usize) -> Vec<String> {
214 let double = len / LETTERS.len();
215 let mut seqs = Vec::new();
216
217 seqs.extend(LETTERS.chars().skip(double).map(char::into));
218 let chars = LETTERS.chars().take(double);
219 seqs.extend(chars.flat_map(|c1| LETTERS.chars().map(move |c2| format!("{c1}{c2}"))));
220
221 seqs
222}
223
224static LETTERS: &str = "abcdefghijklmnopqrstuvwxyz";
225static TAGGER: LazyLock<Tagger> = Tagger::new_static();
226static CLOAK_TAGGER: LazyLock<Tagger> = Tagger::new_static();