1use text::segment;
30
31use crate::consts::*;
32use crate::keys::*;
33use crate::Ui;
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct TextPos {
39 pub line: usize,
40 pub byte: usize,
41}
42
43impl TextPos {
44 pub const fn new(line: usize, byte: usize) -> TextPos {
46 TextPos { line, byte }
47 }
48}
49
50#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub struct TextSel {
53 pub anchor: TextPos,
54 pub caret: TextPos,
55}
56
57impl TextSel {
58 pub const fn at(p: TextPos) -> TextSel {
60 TextSel { anchor: p, caret: p }
61 }
62 pub const fn new(anchor: TextPos, caret: TextPos) -> TextSel {
64 TextSel { anchor, caret }
65 }
66 pub fn is_empty(&self) -> bool {
68 self.anchor == self.caret
69 }
70 pub fn start(&self) -> TextPos {
72 self.anchor.min(self.caret)
73 }
74 pub fn end(&self) -> TextPos {
76 self.anchor.max(self.caret)
77 }
78 pub fn range(&self) -> (TextPos, TextPos) {
80 (self.start(), self.end())
81 }
82}
83
84pub(crate) trait EdView {
87 fn lines(&self) -> usize;
88 fn text(&self, line: usize) -> &str;
89 fn rows(&self, line: usize) -> usize;
91 fn row_range(&self, line: usize, row: usize) -> (usize, usize);
93 fn x_at(&self, line: usize, row: usize, byte: usize) -> i32;
95 fn hit_x(&self, line: usize, row: usize, x: i32) -> usize;
97 fn row_y(&self, line: usize, row: usize) -> (i32, i32);
99 fn row_at(&self, y: i32) -> (usize, usize);
101}
102
103pub(crate) fn row_of(v: &dyn EdView, p: TextPos) -> usize {
105 let n = v.rows(p.line);
106 let mut r = n.saturating_sub(1);
107 while r > 0 && v.row_range(p.line, r).0 > p.byte {
108 r -= 1;
109 }
110 r
111}
112
113pub(crate) fn x_of(v: &dyn EdView, p: TextPos) -> i32 {
115 v.x_at(p.line, row_of(v, p), p.byte)
116}
117
118pub(crate) fn doc_end(v: &dyn EdView) -> TextPos {
120 let l = v.lines().saturating_sub(1);
121 TextPos::new(l, v.text(l).len())
122}
123
124pub(crate) fn clamp_pos(v: &dyn EdView, p: TextPos) -> TextPos {
126 let n = v.lines();
127 if n == 0 {
128 return TextPos::default();
129 }
130 let line = p.line.min(n - 1);
131 let t = v.text(line);
132 let mut b = p.byte.min(t.len());
133 while !t.is_char_boundary(b) {
134 b -= 1;
135 }
136 TextPos::new(line, b)
137}
138
139pub(crate) fn prev_pos(v: &dyn EdView, p: TextPos) -> TextPos {
141 if p.byte == 0 {
142 if p.line == 0 {
143 return p;
144 }
145 return TextPos::new(p.line - 1, v.text(p.line - 1).len());
146 }
147 TextPos::new(p.line, segment::grapheme_prev(v.text(p.line), p.byte))
148}
149
150pub(crate) fn next_pos(v: &dyn EdView, p: TextPos) -> TextPos {
152 let t = v.text(p.line);
153 if p.byte >= t.len() {
154 if p.line + 1 >= v.lines() {
155 return p;
156 }
157 return TextPos::new(p.line + 1, 0);
158 }
159 TextPos::new(p.line, segment::grapheme_next(t, p.byte))
160}
161
162pub(crate) fn word_left(v: &dyn EdView, p: TextPos) -> TextPos {
164 if p.byte == 0 {
165 return prev_pos(v, p);
166 }
167 TextPos::new(p.line, segment::word_prev(v.text(p.line), p.byte))
168}
169
170pub(crate) fn word_right(v: &dyn EdView, p: TextPos) -> TextPos {
172 let t = v.text(p.line);
173 if p.byte >= t.len() {
174 return next_pos(v, p);
175 }
176 TextPos::new(p.line, segment::word_next(t, p.byte))
177}
178
179fn row_step(v: &dyn EdView, line: usize, row: usize, delta: i32) -> Option<(usize, usize)> {
181 if delta > 0 {
182 if row + 1 < v.rows(line) {
183 Some((line, row + 1))
184 } else if line + 1 < v.lines() {
185 Some((line + 1, 0))
186 } else {
187 None
188 }
189 } else if row > 0 {
190 Some((line, row - 1))
191 } else if line > 0 {
192 Some((line - 1, v.rows(line - 1) - 1))
193 } else {
194 None
195 }
196}
197
198pub(crate) fn vmove(v: &dyn EdView, p: TextPos, prefx: &mut i32, delta: i32) -> TextPos {
201 let x = if *prefx < 0 { x_of(v, p) } else { *prefx };
202 *prefx = x.max(0);
203 let (mut line, mut row) = (p.line, row_of(v, p));
204 for _ in 0..delta.unsigned_abs() {
205 match row_step(v, line, row, delta.signum()) {
206 Some(r) => (line, row) = r,
207 None => return if delta < 0 { TextPos::default() } else { doc_end(v) },
208 }
209 }
210 TextPos::new(line, v.hit_x(line, row, x))
211}
212
213pub(crate) fn page_move(v: &dyn EdView, p: TextPos, prefx: &mut i32, dy: i32) -> TextPos {
215 let x = if *prefx < 0 { x_of(v, p) } else { *prefx };
216 *prefx = x.max(0);
217 let row = row_of(v, p);
218 let (y, h) = v.row_y(p.line, row);
219 let target = y + h / 2 + dy;
220 if target < 0 {
221 return TextPos::default();
222 }
223 let (line, r) = v.row_at(target);
224 if (line, r) == (p.line, row) {
225 return vmove(v, p, prefx, dy.signum());
227 }
228 TextPos::new(line, v.hit_x(line, r, x))
229}
230
231pub(crate) fn row_home(v: &dyn EdView, p: TextPos) -> TextPos {
233 TextPos::new(p.line, v.row_range(p.line, row_of(v, p)).0)
234}
235
236pub(crate) fn row_end(v: &dyn EdView, p: TextPos) -> TextPos {
239 let row = row_of(v, p);
240 let (a, b) = v.row_range(p.line, row);
241 if row + 1 < v.rows(p.line) && b > a {
242 return TextPos::new(p.line, segment::grapheme_prev(v.text(p.line), b).max(a));
243 }
244 TextPos::new(p.line, b)
245}
246
247pub(crate) fn back_range(v: &dyn EdView, p: TextPos, ctrl: bool) -> (TextPos, TextPos) {
250 if p.byte == 0 {
251 return (prev_pos(v, p), p);
252 }
253 let t = v.text(p.line);
254 let a = if ctrl {
255 segment::word_prev(t, p.byte)
256 } else {
257 let g = segment::grapheme_prev(t, p.byte);
258 if segment::pictographic_cluster(t, g, p.byte) {
259 g
260 } else {
261 let mut c = p.byte - 1;
262 while !t.is_char_boundary(c) {
263 c -= 1;
264 }
265 c.max(g)
266 }
267 };
268 (TextPos::new(p.line, a), p)
269}
270
271pub(crate) fn del_range(v: &dyn EdView, p: TextPos, ctrl: bool) -> (TextPos, TextPos) {
273 let t = v.text(p.line);
274 if p.byte >= t.len() {
275 return (p, next_pos(v, p));
276 }
277 let b = if ctrl { segment::word_next(t, p.byte) } else { segment::grapheme_next(t, p.byte) };
278 (p, TextPos::new(p.line, b))
279}
280
281pub(crate) fn word_range(t: &str, byte: usize) -> (usize, usize) {
283 if t.is_empty() {
284 return (0, 0);
285 }
286 let (mut a, mut b) = segment::word_at(t, byte.min(t.len()));
287 let kind = segment::word_kind(t, a, b);
288 if kind != segment::WK_WORD {
289 while a > 0 {
290 let (pa, pb) = segment::word_at(t, a - 1);
291 if segment::word_kind(t, pa, pb) != kind {
292 break;
293 }
294 a = pa;
295 }
296 while b < t.len() {
297 let (na, nb) = segment::word_at(t, b);
298 if segment::word_kind(t, na, nb) != kind {
299 break;
300 }
301 b = nb;
302 }
303 }
304 (a, b)
305}
306
307#[derive(Clone, Copy, Debug, PartialEq)]
309pub(crate) enum EdAct {
310 Sel(TextSel),
312 Del(TextPos, TextPos, bool),
314 Enter,
316 Tab(bool),
318 Copy,
319 Cut,
320 Paste,
321 Undo,
322 Redo,
323 Char,
325}
326
327pub(crate) fn ed_key(v: &dyn EdView, sel: TextSel, prefx: &mut i32, key: u32, page_h: i32, tab: bool) -> Option<EdAct> {
331 let vk = key & 0xFFFF;
332 let m = key >> 16;
333 let shift = m & MOD_SHIFT != 0;
334 let altgr = m & MOD_ALTGR != 0 || m & (MOD_CTRL | MOD_ALT) == MOD_CTRL | MOD_ALT;
335 let ctrl = !altgr && m & MOD_CTRL != 0;
336 let alt = !altgr && m & MOD_ALT != 0;
337 if alt {
338 return None;
339 }
340 let keep = *prefx;
341 *prefx = -1;
342 let to = |p: TextPos| {
343 let p = clamp_pos(v, p);
344 Some(EdAct::Sel(if shift { TextSel::new(sel.anchor, p) } else { TextSel::at(p) }))
345 };
346 let c = sel.caret;
347 match vk {
348 VK_LEFT => {
349 if ctrl {
350 to(word_left(v, c))
351 } else if !shift && !sel.is_empty() {
352 to(sel.start())
353 } else {
354 to(prev_pos(v, c))
355 }
356 }
357 VK_RIGHT => {
358 if ctrl {
359 to(word_right(v, c))
360 } else if !shift && !sel.is_empty() {
361 to(sel.end())
362 } else {
363 to(next_pos(v, c))
364 }
365 }
366 VK_UP | VK_DOWN if !ctrl => {
367 *prefx = keep;
368 let p = vmove(v, c, prefx, if vk == VK_UP { -1 } else { 1 });
369 let px = *prefx;
370 let r = to(p);
371 *prefx = px;
372 r
373 }
374 VK_PRIOR | VK_NEXT if !ctrl => {
375 *prefx = keep;
376 let h = page_h.max(1);
377 let p = page_move(v, c, prefx, if vk == VK_PRIOR { -h } else { h });
378 let px = *prefx;
379 let r = to(p);
380 *prefx = px;
381 r
382 }
383 VK_HOME => to(if ctrl { TextPos::default() } else { row_home(v, c) }),
384 VK_END => to(if ctrl { doc_end(v) } else { row_end(v, c) }),
385 VK_BACK if !shift || !ctrl => {
386 if !sel.is_empty() {
387 Some(EdAct::Del(sel.start(), sel.end(), false))
388 } else {
389 let (a, b) = back_range(v, c, ctrl);
390 Some(EdAct::Del(a, b, ctrl))
391 }
392 }
393 VK_DELETE => {
394 if shift && !ctrl {
395 Some(EdAct::Cut)
396 } else if !sel.is_empty() {
397 Some(EdAct::Del(sel.start(), sel.end(), false))
398 } else {
399 let (a, b) = del_range(v, c, ctrl);
400 Some(EdAct::Del(a, b, ctrl))
401 }
402 }
403 VK_INSERT => {
404 if ctrl {
405 Some(EdAct::Copy)
406 } else if shift {
407 Some(EdAct::Paste)
408 } else {
409 Some(EdAct::Char)
410 }
411 }
412 VK_RETURN if !ctrl => Some(EdAct::Enter),
413 VK_TAB if tab && !ctrl => Some(EdAct::Tab(shift)),
414 _ if ctrl => match vk {
415 0x41 if !shift => Some(EdAct::Sel(TextSel::new(TextPos::default(), doc_end(v)))),
416 0x43 if !shift => Some(EdAct::Copy),
417 0x58 if !shift => Some(EdAct::Cut),
418 0x56 if !shift => Some(EdAct::Paste),
419 0x5A => Some(if shift { EdAct::Redo } else { EdAct::Undo }),
420 0x59 if !shift => Some(EdAct::Redo),
421 _ => None,
422 },
423 _ => {
424 let own = vk.wrapping_sub(VK_SHIFT) <= VK_CAPITAL - VK_SHIFT
426 || vk.wrapping_sub(VK_LWIN) <= VK_APPS - VK_LWIN
427 || vk.wrapping_sub(VK_F1) <= 23
428 || vk.wrapping_sub(VK_NUMLOCK) <= 1
429 || vk.wrapping_sub(VK_LSHIFT) <= VK_RMENU - VK_LSHIFT
430 || matches!(vk, VK_ESCAPE | VK_TAB | VK_UP | VK_DOWN | VK_PRIOR | VK_NEXT | VK_RETURN);
431 if own {
432 *prefx = keep;
433 None
434 } else {
435 *prefx = keep;
436 Some(EdAct::Char)
437 }
438 }
439 }
440}
441
442pub(crate) fn typed_text(cps: &[u32], tab: bool) -> alloc::string::String {
445 let mut s = alloc::string::String::new();
446 for &c in cps {
447 if (c < 32 && !(tab && c == 9)) || c == 127 {
448 continue;
449 }
450 if let Some(ch) = char::from_u32(c) {
451 s.push(ch);
452 }
453 }
454 s
455}
456
457impl Ui {
458 pub(crate) fn ed_key_first(&self) -> u32 {
461 self.wp.ctx.keyq.iter().copied().find(|&k| (k as i32) >= 0).unwrap_or(0)
462 }
463
464 pub(crate) fn ed_key_take(&mut self) {
466 let q = &mut self.wp.ctx.keyq;
467 if let Some(i) = q.iter().position(|&k| (k as i32) >= 0) {
468 q[i] |= 0x8000_0000;
469 }
470 }
471
472 pub(crate) fn ed_key_chord(&mut self, vk: u32, mods: u32) -> bool {
475 let m = MOD_SHIFT | MOD_CTRL | MOD_ALT;
476 let q = &mut self.wp.ctx.keyq;
477 match q.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & m == mods && k >> 16 & MOD_ALTGR == 0) {
478 Some(i) => {
479 q[i] |= 0x8000_0000;
480 true
481 }
482 None => false,
483 }
484 }
485
486 pub(crate) fn ed_caret_on(&mut self, t0: u32) -> bool {
489 if self.headless || self.blink_ms == 0 {
490 return true;
491 }
492 let blink = self.blink_ms;
493 let t = self.now_ms.wrapping_sub(t0);
494 self.timer(UIT_CARET, blink - t % blink);
495 (t / blink) & 1 == 0
496 }
497
498 pub(crate) fn ed_focused(&self, id: u32) -> bool {
500 self.wp.ctx.focus == id && self.uiw_cur == self.m.win.active
501 }
502
503 pub(crate) fn ed_preedit(&self) -> plat::Preedit {
506 let pw = if self.headless { self.uiw_cur as i32 } else { self.uiw_pw(self.uiw_cur) };
507 if pw < 0 {
508 return plat::Preedit::default();
509 }
510 plat::ime_preedit(pw as u8)
511 }
512
513 #[allow(clippy::too_many_arguments)]
516 pub(crate) fn ed_preedit_draw(&mut self, pe: &plat::Preedit, x: i32, top: i32, h: i32, base: i32, font: (u32, i32), fg: u32, bg: u32) {
517 let prev = self.text.get_font();
518 self.text.set_font(font.0, font.1);
519 let w = self.text.width(&pe.text).max(1);
520 let cx = self.text.caret_x(pe.text.as_bytes(), pe.cursor.min(pe.text.len()));
521 self.text.set_font(prev.0, prev.1);
522 let hair = self.hair().max(1);
523 self.draw_color(bg | 0xFF00_0000);
524 self.draw_rect(x, top, w + hair, h);
525 self.cmd_text(x, base, pe.text.as_bytes(), font.0, font.1, fg, 0);
526 self.draw_color(fg);
527 self.draw_rect(x, base + hair.max((font.1 >> 8) / 10), w, hair);
528 self.draw_rect(x + cx, top, hair, h);
529 }
530
531 pub(crate) fn ed_focus_quiet(&mut self, id: u32) {
533 if self.wp.ctx.focus != id {
534 self.wp.ctx.focus = id;
535 self.wp.ctx.focus_visible = false;
536 self.repass();
537 }
538 }
539}