1use alloc::collections::VecDeque;
60use alloc::rc::Rc;
61use alloc::string::String;
62use alloc::vec::Vec;
63
64use crate::cmd::{Draw, DrawRef, Gfx};
65use crate::consts::*;
66use crate::keys::*;
67use crate::theme::color_mix;
68use crate::{FontSel, TextSurfaceLine, Ui};
69
70pub const TM_LINE: u8 = 0;
72pub const TM_BUSY: u8 = 1;
74pub const TM_RAW: u8 = 2;
76pub const TM_SECRET: u8 = 3;
78
79pub const TF_QHELP: u32 = 1; pub const TF_CTRL_SHIFT6: u32 = 2; pub const TF_NO_INPUT: u32 = 4; pub const TF_NO_FRAME: u32 = 8; pub const TK_KEY: u32 = 0x8000_0000; const DEF: u8 = 255; const A_BOLD: u8 = 1;
88const A_DIM: u8 = 2;
89const A_UNDER: u8 = 4;
90const A_INV: u8 = 8;
91const LINE_MAX: usize = 32768; const INPUT_CAP: u32 = 0x1_0000; const HIST_MAX: usize = 500;
94const PARAMS_MAX: usize = 16;
95const UIT_TERM: u32 = 0x7FFE_0040; const UIT_TERM_BELL: u32 = 0x7FFE_0041; const BELL_MS: u32 = 150;
98
99#[derive(Clone, Debug, PartialEq)]
101#[non_exhaustive]
102pub enum TermEv {
103 Submit(String),
105 Complete { line: String, caret: usize },
107 Help(String),
109 Interrupt,
111 Eof,
113 Chord(u32),
115 PagerQuit,
117 Resize(u16, u16),
119 Raw(Vec<u32>),
122}
123
124#[derive(Clone, Copy, PartialEq, Eq, Debug)]
128struct Attr {
129 fg: u8,
130 bg: u8,
131 fl: u8,
132}
133
134const ATTR0: Attr = Attr { fg: DEF, bg: DEF, fl: 0 };
135
136#[derive(Clone, Copy, PartialEq)]
137struct Cell {
138 ch: char,
139 a: Attr,
140}
141
142struct Line {
145 text: String,
146 spans: Vec<(u32, Attr)>,
147 cells: u32,
148}
149
150impl Line {
151 fn of(cells: &[Cell]) -> Line {
152 let mut text = String::with_capacity(cells.len());
153 let mut spans = Vec::new();
154 let mut a = ATTR0;
155 for (i, c) in cells.iter().enumerate() {
156 if c.a != a {
157 spans.push((i as u32, c.a));
158 a = c.a;
159 }
160 text.push(c.ch);
161 }
162 Line { text, spans, cells: cells.len() as u32 }
163 }
164}
165
166#[derive(Clone, Copy, PartialEq)]
167enum Esc {
168 No,
169 Esc,
170 Csi,
171 Osc,
172 OscEsc,
173}
174
175fn line_rows(cells: u32, cols: usize) -> u64 {
177 if cells == 0 {
178 1
179 } else {
180 (cells as u64).div_ceil(cols.max(1) as u64)
181 }
182}
183
184struct Screen {
186 lines: VecDeque<Line>,
187 row0: VecDeque<u64>, row_end: u64, base: u64, cap: usize,
191 cols: usize,
192 cur: Vec<Cell>,
193 col: usize,
194 attr: Attr,
195 esc: Esc,
196 params: Vec<u32>,
197 param: Option<u32>,
198 utf: u32,
199 utf_need: u8,
200 utf_min: u32,
201 floor: u64, bell: bool,
203 added: u64, ver: u64, }
206
207impl Screen {
208 fn new(cap: usize) -> Screen {
209 Screen {
210 lines: VecDeque::new(),
211 row0: VecDeque::new(),
212 row_end: 0,
213 base: 0,
214 cap: cap.max(100),
215 cols: 80,
216 cur: Vec::new(),
217 col: 0,
218 attr: ATTR0,
219 esc: Esc::No,
220 params: Vec::new(),
221 param: None,
222 utf: 0,
223 utf_need: 0,
224 utf_min: 0,
225 floor: 0,
226 bell: false,
227 added: 0,
228 ver: 0,
229 }
230 }
231
232 fn first_row(&self) -> u64 {
233 self.row0.front().copied().unwrap_or(self.row_end)
234 }
235
236 fn end(&self) -> u64 {
238 self.base + self.lines.len() as u64
239 }
240
241 fn line(&self, abs: u64) -> Option<&Line> {
242 abs.checked_sub(self.base).and_then(|i| self.lines.get(i as usize))
243 }
244
245 fn row_of(&self, abs: u64) -> u64 {
247 match abs.checked_sub(self.base) {
248 None => self.first_row(),
249 Some(i) => self.row0.get(i as usize).copied().unwrap_or(self.row_end),
250 }
251 }
252
253 fn line_at_row(&self, r: u64) -> Option<u64> {
255 if r >= self.row_end || self.lines.is_empty() {
256 return None;
257 }
258 let i = self.row0.partition_point(|&x| x <= r);
259 Some(self.base + i.max(1) as u64 - 1)
260 }
261
262 fn set_cols(&mut self, cols: usize) {
264 let cols = cols.max(1);
265 if cols == self.cols {
266 return;
267 }
268 self.cols = cols;
269 let mut r = 0;
270 for (i, l) in self.lines.iter().enumerate() {
271 self.row0[i] = r;
272 r += line_rows(l.cells, cols);
273 }
274 self.row_end = r;
275 self.ver += 1;
276 }
277
278 fn push(&mut self, l: Line) {
279 self.row0.push_back(self.row_end);
280 self.row_end += line_rows(l.cells, self.cols);
281 self.lines.push_back(l);
282 self.added += 1;
283 while self.lines.len() > self.cap {
284 self.lines.pop_front();
285 self.row0.pop_front();
286 self.base += 1;
287 }
288 }
289
290 fn newline(&mut self) {
291 let cells = core::mem::take(&mut self.cur);
292 self.col = 0;
293 self.push(Line::of(&cells));
294 }
295
296 fn clear(&mut self) {
297 self.base = self.end();
298 self.lines.clear();
299 self.row0.clear();
300 self.cur.clear();
301 self.col = 0;
302 self.floor = self.base;
303 self.ver += 1;
304 }
305
306 fn write(&mut self, s: &[u8]) {
307 if s.is_empty() {
308 return;
309 }
310 for &b in s {
311 self.feed(b);
312 }
313 self.ver += 1;
314 }
315
316 fn put(&mut self, ch: char) {
317 if self.col >= LINE_MAX {
318 self.newline();
319 }
320 let cell = Cell { ch, a: self.attr };
321 if self.col < self.cur.len() {
322 self.cur[self.col] = cell;
323 } else {
324 while self.cur.len() < self.col {
325 self.cur.push(Cell { ch: ' ', a: ATTR0 });
326 }
327 self.cur.push(cell);
328 }
329 self.col += 1;
330 }
331
332 fn feed(&mut self, b: u8) {
333 match self.esc {
334 Esc::No => {}
335 Esc::Esc => {
336 self.esc = match b {
337 b'[' => {
338 self.params.clear();
339 self.param = None;
340 Esc::Csi
341 }
342 b']' => Esc::Osc,
343 _ => Esc::No, };
345 return;
346 }
347 Esc::Csi => {
348 match b {
349 b'0'..=b'9' => {
350 let v = self.param.unwrap_or(0) * 10 + (b - b'0') as u32;
351 self.param = Some(v.min(65535));
352 }
353 b';' | b':' => {
354 if self.params.len() < PARAMS_MAX {
355 self.params.push(self.param.unwrap_or(0));
356 }
357 self.param = None;
358 }
359 0x40..=0x7E => {
360 if let Some(p) = self.param.take() {
361 if self.params.len() < PARAMS_MAX {
362 self.params.push(p);
363 }
364 }
365 self.esc = Esc::No;
366 self.csi(b);
367 }
368 0x1B => self.esc = Esc::Esc,
369 _ => {} }
371 return;
372 }
373 Esc::Osc => {
374 if b == 7 {
375 self.esc = Esc::No;
376 } else if b == 0x1B {
377 self.esc = Esc::OscEsc;
378 }
379 return;
380 }
381 Esc::OscEsc => {
382 self.esc = if b == b'\\' { Esc::No } else { Esc::Osc };
383 return;
384 }
385 }
386 if self.utf_need != 0 {
387 if b & 0xC0 == 0x80 {
388 self.utf = self.utf << 6 | (b & 0x3F) as u32;
389 self.utf_need -= 1;
390 if self.utf_need == 0 {
391 let c = self.utf;
392 let ok = c >= self.utf_min && c <= 0x10FFFF && c & 0xFFFF_F800 != 0xD800;
393 self.put(if ok { char::from_u32(c).unwrap_or('\u{FFFD}') } else { '\u{FFFD}' });
394 }
395 return;
396 }
397 self.utf_need = 0; self.put('\u{FFFD}');
399 }
400 match b {
401 0x1B => self.esc = Esc::Esc,
402 b'\n' => self.newline(),
403 b'\r' => self.col = 0,
404 b'\t' => {
405 let to = ((self.col / 8 + 1) * 8).min(LINE_MAX);
407 while self.col < to {
408 self.put(' ');
409 }
410 }
411 8 => self.col = self.col.saturating_sub(1),
412 7 => self.bell = true,
413 0..=0x1F | 0x7F => {}
414 0x20..=0x7E => self.put(b as char),
415 0xC2..=0xDF => self.utf_begin(b & 0x1F, 1, 0x80),
416 0xE0..=0xEF => self.utf_begin(b & 0x0F, 2, 0x800),
417 0xF0..=0xF4 => self.utf_begin(b & 7, 3, 0x10000),
418 _ => self.put('\u{FFFD}'),
419 }
420 }
421
422 fn utf_begin(&mut self, bits: u8, need: u8, min: u32) {
423 self.utf = bits as u32;
424 self.utf_need = need;
425 self.utf_min = min;
426 }
427
428 fn arg(&self, i: usize, default: u32) -> u32 {
429 match self.params.get(i) {
430 Some(&v) if v != 0 => v,
431 _ => default,
432 }
433 }
434
435 fn csi(&mut self, f: u8) {
436 match f {
437 b'm' => self.sgr(),
438 b'K' => match self.params.first().copied().unwrap_or(0) {
439 0 => self.cur.truncate(self.col),
440 1 => {
441 let n = (self.col + 1).min(self.cur.len());
442 for c in &mut self.cur[..n] {
443 *c = Cell { ch: ' ', a: ATTR0 };
444 }
445 }
446 _ => self.cur.clear(),
447 },
448 b'J' => match self.params.first().copied().unwrap_or(0) {
449 2 => {
450 if !self.cur.is_empty() {
452 self.newline();
453 }
454 self.col = 0;
455 self.floor = self.end();
456 }
457 3 => self.clear(),
458 _ => {}
459 },
460 b'H' | b'f' => self.col = self.arg(1, 1) as usize - 1,
461 b'G' => self.col = self.arg(0, 1) as usize - 1,
462 b'C' => self.col = (self.col + self.arg(0, 1) as usize).min(LINE_MAX - 1),
463 b'D' => self.col = self.col.saturating_sub(self.arg(0, 1) as usize),
464 _ => {}
465 }
466 }
467
468 fn sgr(&mut self) {
469 if self.params.is_empty() {
470 self.attr = ATTR0;
471 return;
472 }
473 let p = core::mem::take(&mut self.params);
474 let mut i = 0;
475 while i < p.len() {
476 let a = &mut self.attr;
477 match p[i] {
478 0 => *a = ATTR0,
479 1 => a.fl |= A_BOLD,
480 2 => a.fl |= A_DIM,
481 4 => a.fl |= A_UNDER,
482 7 => a.fl |= A_INV,
483 22 => a.fl &= !(A_BOLD | A_DIM),
484 24 => a.fl &= !A_UNDER,
485 27 => a.fl &= !A_INV,
486 v @ 30..=37 => a.fg = (v - 30) as u8,
487 39 => a.fg = DEF,
488 v @ 40..=47 => a.bg = (v - 40) as u8,
489 49 => a.bg = DEF,
490 v @ 90..=97 => a.fg = (v - 90 + 8) as u8,
491 v @ 100..=107 => a.bg = (v - 100 + 8) as u8,
492 v @ (38 | 48) => {
493 let c = match p.get(i + 1) {
495 Some(5) => {
496 i += 2;
497 p.get(i).map(|&n| ansi256(n))
498 }
499 Some(2) => {
500 i += 4;
501 match (p.get(i - 2), p.get(i - 1), p.get(i)) {
502 (Some(&r), Some(&g), Some(&b)) => Some(rgb16(r.min(255) / 51, g.min(255) / 51, b.min(255) / 51)),
503 _ => None,
504 }
505 }
506 _ => None,
507 };
508 if let Some(c) = c {
509 if v == 38 {
510 a.fg = c;
511 } else {
512 a.bg = c;
513 }
514 }
515 }
516 _ => {}
517 }
518 i += 1;
519 }
520 self.params = p;
521 }
522}
523
524fn rgb16(r: u32, g: u32, b: u32) -> u8 {
526 let bits = (r > 2) as u8 | ((g > 2) as u8) << 1 | ((b > 2) as u8) << 2;
527 let hi = r.max(g).max(b);
528 if bits == 0 {
529 return if hi >= 2 { 8 } else { 0 };
530 }
531 if hi >= 5 { bits + 8 } else { bits }
532}
533
534fn ansi256(n: u32) -> u8 {
536 match n {
537 0..=15 => n as u8,
538 16..=231 => {
539 let v = n - 16;
540 rgb16(v / 36, v / 6 % 6, v % 6)
541 }
542 232..=243 => 8,
543 _ => 7,
544 }
545}
546
547pub struct TermState {
551 pan_rem: i32,
553 pub prompt: String,
555 pub mode: u8,
557 pub flags: u32,
559 pub font: Option<u32>,
561 pub size_pt: f32,
563 pub pager_text: String,
565 pub page_len: u16,
567 scr: Screen,
568 input: String,
569 caret_want: Option<usize>,
570 history: Vec<String>,
571 hist_pos: usize,
572 hist_draft: String,
573 ta: String, ta_at: Option<u32>, pg_open: bool,
576 pg_quit: bool,
577 pg_held: Vec<u8>,
578 pg_left: u32,
579 find_open: bool,
580 find_text: String,
581 find_hits: Vec<(u64, u32, u32)>, find_idx: usize,
583 find_key: Option<(String, u64)>, sel: Option<((u64, u32), (u64, u32))>, sel_drag: bool,
586 at_bottom: bool,
587 top: u64,
588 unseen: bool,
589 seen_added: u64,
590 cols: u16,
591 rows: u16,
592 scroll_req: i64,
593 bell_until: u32,
594 prompt_cells: Option<(String, Vec<Cell>)>,
595 body: Option<(Rc<Snap>, u32)>,
596 tail: Option<(Rc<Snap>, u32)>,
597}
598
599impl TermState {
600 pub fn new(scrollback: usize) -> TermState {
602 TermState {
603 prompt: String::new(),
604 mode: TM_LINE,
605 flags: 0,
606 font: None,
607 size_pt: 10.0,
608 pager_text: String::from(" --More-- "),
609 page_len: 0,
610 scr: Screen::new(scrollback),
611 input: String::new(),
612 caret_want: None,
613 history: Vec::new(),
614 hist_pos: 0,
615 hist_draft: String::new(),
616 ta: String::new(),
617 ta_at: None,
618 pg_open: false,
619 pg_quit: false,
620 pg_held: Vec::new(),
621 pg_left: 0,
622 find_open: false,
623 find_text: String::new(),
624 find_hits: Vec::new(),
625 find_idx: 0,
626 find_key: None,
627 sel: None,
628 sel_drag: false,
629 at_bottom: true,
630 pan_rem: 0,
631 top: 0,
632 unseen: false,
633 seen_added: 0,
634 cols: 0,
635 rows: 0,
636 scroll_req: 0,
637 bell_until: 0,
638 prompt_cells: None,
639 body: None,
640 tail: None,
641 }
642 }
643
644 pub fn write(&mut self, s: &[u8]) {
646 if self.pg_open && self.pg_quit {
647 return; }
649 if self.pg_open || !self.pg_held.is_empty() {
650 self.pg_feed(s);
651 } else {
652 self.scr.write(s);
653 }
654 }
655
656 pub fn write_str(&mut self, s: &str) {
658 self.write(s.as_bytes());
659 }
660
661 pub fn clear(&mut self) {
663 self.scr.clear();
664 self.sel = None;
665 self.at_bottom = true;
666 self.find_key = None;
667 }
668
669 pub fn set_prompt(&mut self, s: &str) {
671 if self.prompt != s {
672 self.prompt = String::from(s);
673 }
674 }
675
676 pub fn set_mode(&mut self, mode: u8) {
678 self.mode = mode;
679 }
680
681 pub fn input_set(&mut self, s: &str, caret: usize) {
683 self.input = String::from(s);
684 let mut c = caret.min(s.len());
685 while !s.is_char_boundary(c) {
686 c -= 1;
687 }
688 self.caret_want = Some(c);
689 }
690
691 pub fn input(&self) -> &str {
693 &self.input
694 }
695
696 pub fn history(&self) -> &[String] {
698 &self.history
699 }
700
701 pub fn complete_reply<S: AsRef<str>>(&mut self, line: &str, list: &[S]) {
704 if !list.is_empty() {
705 let typed = self.input.clone();
706 self.echo(&typed, "");
707 let w = list.iter().map(|s| s.as_ref().chars().count()).max().unwrap_or(0) + 2;
708 let per = (self.cols.max(1) as usize / w).max(1);
709 let mut out = String::new();
710 for (i, s) in list.iter().enumerate() {
711 let s = s.as_ref();
712 out.push_str(s);
713 if (i + 1) % per == 0 || i + 1 == list.len() {
714 out.push('\n');
715 } else {
716 for _ in s.chars().count()..w {
717 out.push(' ');
718 }
719 }
720 }
721 self.scr.write(out.as_bytes());
722 }
723 self.input_set(line, line.len());
724 self.at_bottom = true;
725 }
726
727 pub fn pager(&mut self, on: bool) {
729 if on {
730 self.pg_open = true;
731 self.pg_quit = false;
732 let held = core::mem::take(&mut self.pg_held);
733 self.scr.write(&held);
734 self.pg_left = self.page();
735 } else {
736 self.pg_open = false;
737 if self.pg_quit {
738 self.pg_held.clear();
739 self.pg_quit = false;
740 }
741 }
742 }
743
744 pub fn pager_waiting(&self) -> bool {
746 !self.pg_held.is_empty()
747 }
748
749 pub fn grid(&self) -> (u16, u16) {
751 (self.cols, self.rows)
752 }
753
754 pub fn text(&self) -> String {
756 let mut s = String::new();
757 for l in &self.scr.lines {
758 s.push_str(&l.text);
759 s.push('\n');
760 }
761 s.extend(self.scr.cur.iter().map(|c| c.ch));
762 s
763 }
764
765 pub fn line_count(&self) -> usize {
767 self.scr.lines.len()
768 }
769
770 fn page(&self) -> u32 {
772 match (self.page_len, self.rows) {
773 (0, 0) => 23,
774 (0, r) => (r.max(2) as u32 - 1).max(1),
775 (n, _) => n as u32,
776 }
777 }
778
779 fn pg_feed(&mut self, s: &[u8]) {
781 if !self.pg_held.is_empty() {
782 self.pg_held.extend_from_slice(s);
783 return;
784 }
785 let mut i = 0;
786 while i < s.len() {
787 if self.pg_left == 0 {
788 self.pg_held.extend_from_slice(&s[i..]);
789 break;
790 }
791 let end = s[i..].iter().position(|&b| b == b'\n').map_or(s.len(), |p| i + p + 1);
792 self.scr.write(&s[i..end]);
793 if s[end - 1] == b'\n' {
794 self.pg_left -= 1;
795 }
796 i = end;
797 }
798 }
799
800 fn pg_release(&mut self, n: u32) {
802 self.pg_left = n.max(1);
803 let held = core::mem::take(&mut self.pg_held);
804 self.pg_feed(&held);
805 self.at_bottom = true;
806 }
807
808 fn pg_quit_now(&mut self, evs: &mut Vec<TermEv>) {
809 self.pg_held.clear();
810 if self.pg_open {
811 self.pg_quit = true;
812 }
813 evs.push(TermEv::PagerQuit);
814 self.at_bottom = true;
815 }
816
817 fn echo(&mut self, line: &str, suffix: &str) {
820 let attr = self.scr.attr;
821 self.scr.attr = ATTR0;
822 self.scr.col = self.scr.cur.len();
823 let mut s = String::with_capacity(self.prompt.len() + line.len() + suffix.len() + 1);
824 s.push_str(&self.prompt);
825 s.push_str("\x1b[0m");
826 if self.mode != TM_SECRET {
827 s.push_str(line);
828 }
829 s.push_str(suffix);
830 s.push('\n');
831 self.scr.write(s.as_bytes());
832 self.scr.attr = attr;
833 }
834
835 fn submit(&mut self, line: String, evs: &mut Vec<TermEv>) {
836 self.echo(&line, "");
837 if self.mode != TM_SECRET && !line.trim().is_empty() && self.history.last() != Some(&line) {
838 self.history.push(line.clone());
839 if self.history.len() > HIST_MAX {
840 self.history.remove(0);
841 }
842 }
843 self.hist_pos = self.history.len();
844 self.hist_draft.clear();
845 self.at_bottom = true;
846 evs.push(TermEv::Submit(line));
847 }
848
849 fn hist_move(&mut self, older: bool) {
850 let n = self.history.len();
851 if older {
852 if self.hist_pos == 0 || n == 0 {
853 return;
854 }
855 if self.hist_pos >= n {
856 self.hist_pos = n;
857 self.hist_draft = self.input.clone();
858 }
859 self.hist_pos -= 1;
860 self.input = self.history[self.hist_pos].clone();
861 } else {
862 if self.hist_pos >= n {
863 return;
864 }
865 self.hist_pos += 1;
866 self.input = if self.hist_pos == n { self.hist_draft.clone() } else { self.history[self.hist_pos].clone() };
867 }
868 self.caret_want = Some(self.input.len());
869 self.at_bottom = true;
870 }
871
872 fn ta_release(&mut self, evs: &mut Vec<TermEv>) -> bool {
874 if self.ta.is_empty() {
875 return false;
876 }
877 match self.ta.find('\n') {
878 Some(i) => {
879 let mut line = core::mem::take(&mut self.input);
880 line.push_str(&self.ta[..i]);
881 self.ta.drain(..=i);
882 self.submit(line, evs);
883 true
884 }
885 None => {
886 let rest = core::mem::take(&mut self.ta);
887 self.input.push_str(&rest);
888 self.caret_want = Some(self.input.len());
889 false
890 }
891 }
892 }
893
894 fn sel_range(&self) -> Option<((u64, u32), (u64, u32))> {
896 let (a, b) = self.sel?;
897 let (a, b) = if a <= b { (a, b) } else { (b, a) };
898 (a != b).then_some((a, b))
899 }
900
901 fn sel_text(&self) -> String {
902 let Some((a, b)) = self.sel_range() else { return String::new() };
903 let mut out = String::new();
904 for l in a.0..=b.0 {
905 let Some(line) = self.scr.line(l) else { continue };
906 let c0 = if l == a.0 { a.1 } else { 0 } as usize;
907 let c1 = if l == b.0 { b.1 } else { line.cells } as usize;
908 let part: String = line.text.chars().skip(c0).take(c1.saturating_sub(c0)).collect();
909 if c1 >= line.cells as usize {
910 out.push_str(part.trim_end());
911 } else {
912 out.push_str(&part);
913 }
914 if l != b.0 {
915 out.push('\n');
916 }
917 }
918 out
919 }
920
921 fn word_at(&self, p: (u64, u32)) -> Option<((u64, u32), (u64, u32))> {
923 let line = self.scr.line(p.0)?;
924 let chars: Vec<char> = line.text.chars().collect();
925 if chars.is_empty() {
926 return None;
927 }
928 let k = (p.1 as usize).min(chars.len() - 1);
929 let class = |c: char| -> u8 {
930 if c.is_alphanumeric() || "_-./:@".contains(c) {
931 1
932 } else if c == ' ' {
933 0
934 } else {
935 2
936 }
937 };
938 let cl = class(chars[k]);
939 let mut s = k;
940 while s > 0 && class(chars[s - 1]) == cl {
941 s -= 1;
942 }
943 let mut e = k + 1;
944 while e < chars.len() && class(chars[e]) == cl {
945 e += 1;
946 }
947 Some(((p.0, s as u32), (p.0, e as u32)))
948 }
949
950 fn find_update(&mut self) {
952 let key = (self.find_text.clone(), self.scr.ver);
953 if self.find_key.as_ref() == Some(&key) {
954 return;
955 }
956 let fresh = self.find_key.as_ref().is_none_or(|k| k.0 != key.0);
957 self.find_key = Some(key);
958 let cur = self.find_hits.get(self.find_idx).copied();
959 self.find_hits.clear();
960 let needle: Vec<char> = self.find_text.chars().map(fold).collect();
961 if needle.is_empty() {
962 return;
963 }
964 for (i, l) in self.scr.lines.iter().enumerate() {
965 let hay: Vec<char> = l.text.chars().map(fold).collect();
966 let mut k = 0;
967 while k + needle.len() <= hay.len() {
968 if hay[k..k + needle.len()] == needle[..] {
969 self.find_hits.push((self.scr.base + i as u64, k as u32, (k + needle.len()) as u32));
970 k += needle.len();
971 } else {
972 k += 1;
973 }
974 }
975 }
976 self.find_idx = match cur {
978 Some(c) if !fresh => self.find_hits.iter().position(|h| *h == c).unwrap_or(self.find_hits.len().saturating_sub(1)),
979 _ => self.find_hits.len().saturating_sub(1),
980 };
981 if fresh {
982 self.find_show();
983 }
984 }
985
986 fn find_step(&mut self, older: bool) {
988 let n = self.find_hits.len();
989 if n == 0 {
990 return;
991 }
992 self.find_idx = if older { (self.find_idx + n - 1) % n } else { (self.find_idx + 1) % n };
993 self.find_show();
994 }
995
996 fn find_show(&mut self) {
998 let Some(&(l, c, _)) = self.find_hits.get(self.find_idx) else { return };
999 let row = self.scr.row_of(l) + c as u64 / self.scr.cols.max(1) as u64;
1000 let rows = self.rows.max(1) as u64;
1001 let top = if self.at_bottom { u64::MAX } else { self.top };
1002 if top == u64::MAX || row < top || row >= top + rows {
1003 self.at_bottom = false;
1004 self.top = row.saturating_sub(rows / 3);
1005 }
1006 }
1007}
1008
1009fn fold(c: char) -> char {
1011 c.to_lowercase().next().unwrap_or(c)
1012}
1013
1014#[derive(Clone, PartialEq)]
1018struct Run {
1019 col: u32,
1020 n: u32,
1021 fg: u32,
1022 bg: u32,
1023 fl: u8,
1024 text: String,
1025}
1026
1027#[derive(PartialEq)]
1029struct Snap {
1030 font: u32,
1031 size: i32,
1032 cw: i32,
1033 lh: i32,
1034 base: i32,
1035 ul: (i32, i32),
1036 bold_dx: i32,
1037 per_cell: bool,
1038 rows: Vec<Vec<Run>>,
1039}
1040
1041impl Draw for Snap {
1042 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
1043 g.text.set_font(self.font, self.size);
1044 for (i, row) in self.rows.iter().enumerate() {
1045 let y = r[1] + i as i32 * self.lh;
1046 for run in row.iter().filter(|run| run.bg != 0) {
1047 g.dc.set_color(run.bg);
1048 g.dc.fill(g.sf, r[0] + run.col as i32 * self.cw, y, run.n as i32 * self.cw, self.lh);
1049 }
1050 for run in row {
1051 let x0 = r[0] + run.col as i32 * self.cw;
1052 g.dc.set_color(run.fg);
1053 let passes = if run.fl & A_BOLD != 0 { 2 } else { 1 };
1054 for p in 0..passes {
1055 let dx = p * self.bold_dx;
1056 let b = run.text.as_bytes();
1057 let (mut i, mut col) = (0, 0);
1058 while i < b.len() {
1059 let s = i;
1060 if !self.per_cell && b[i] < 0x80 {
1061 while i < b.len() && b[i] < 0x80 {
1063 i += 1;
1064 }
1065 } else {
1066 i += match b[i] {
1068 0..=0x7F => 1,
1069 0x80..=0xDF => 2,
1070 0xE0..=0xEF => 3,
1071 _ => 4,
1072 };
1073 i = i.min(b.len());
1074 }
1075 if b[s..i] != *b" " {
1076 g.text.draw(&mut *g.dc, &mut *g.sf, x0 + col * self.cw + dx, y + self.base, &b[s..i]);
1077 }
1078 col += if i - s > 1 && b[s] < 0x80 { (i - s) as i32 } else { 1 };
1079 }
1080 }
1081 if run.fl & A_UNDER != 0 {
1082 g.dc.fill(g.sf, x0, y + self.ul.0, run.n as i32 * self.cw, self.ul.1);
1083 }
1084 }
1085 }
1086 }
1087}
1088
1089#[derive(Default)]
1091struct Rb {
1092 runs: Vec<Run>,
1093}
1094
1095impl Rb {
1096 fn push(&mut self, col: u32, ch: char, (fg, bg, fl): (u32, u32, u8)) {
1097 if let Some(r) = self.runs.last_mut() {
1098 if r.fg == fg && r.bg == bg && r.fl == fl && r.col + r.n == col {
1099 r.text.push(ch);
1100 r.n += 1;
1101 return;
1102 }
1103 }
1104 self.runs.push(Run { col, n: 1, fg, bg, fl, text: String::from(ch) });
1105 }
1106}
1107
1108struct Pal {
1110 c: [u32; 16],
1111 fg: u32,
1112 bg: u32,
1113 sel: u32,
1114 sel_fg: u32,
1115 hit: u32,
1116 hit_cur: u32,
1117}
1118
1119fn luma(c: u32) -> u32 {
1121 ((c >> 16 & 255) * 299 + (c >> 8 & 255) * 587 + (c & 255) * 114) / 1000
1122}
1123
1124fn over(bg: u32, c: u32) -> u32 {
1126 let a = (c >> 24) as i32;
1127 color_mix(bg | 0xFF00_0000, c | 0xFF00_0000, a + (a >> 7))
1128}
1129
1130impl Pal {
1131 fn cell(&self, a: Attr) -> (u32, u32, u8) {
1133 let mut fg = if a.fg == DEF { self.fg } else { self.c[(a.fg & 15) as usize] };
1134 let mut bg = if a.bg == DEF { 0 } else { self.c[(a.bg & 15) as usize] };
1135 if a.fg == DEF && bg != 0 && luma(fg).abs_diff(luma(bg)) < 110 {
1136 fg = self.bg; }
1138 if a.fl & A_DIM != 0 {
1139 fg = color_mix(fg, self.bg, 110);
1140 }
1141 if a.fl & A_INV != 0 {
1142 let b = if bg == 0 { self.bg } else { bg };
1143 bg = fg;
1144 fg = b;
1145 }
1146 (fg, bg, a.fl & (A_BOLD | A_UNDER))
1147 }
1148}
1149
1150#[derive(Clone, Copy)]
1152struct Metrics {
1153 font: u32,
1154 size: i32,
1155 cw: i32,
1156 lh: i32,
1157 base: i32,
1158 per_cell: bool,
1159}
1160
1161#[derive(Default)]
1163pub(crate) struct S {
1164 pub(crate) mono: Option<u32>, }
1166
1167impl Ui {
1168 pub fn mono_font_set(&mut self, font: u32) {
1170 self.m.term.mono = Some(font);
1171 }
1172
1173 pub fn mono_font(&mut self, size_pt: f32) -> (u32, i32) {
1176 let m = self.term_metrics(None, size_pt);
1177 (m.font, m.cw)
1178 }
1179
1180 fn term_metrics(&mut self, font: Option<u32>, size_pt: f32) -> Metrics {
1181 let (font, per_cell) = match font.or(self.m.term.mono) {
1182 Some(f) if self.text.valid(f) => (f, false),
1183 _ => match self.font_load(&FontSel::new("monospace", size_pt as f64)) {
1184 Some(f) => (f, false),
1185 None => (text::FONT_REG, true),
1186 },
1187 };
1188 let dpi = self.wp.rt.dpi as f32;
1189 let scale = self.text_scale as f32 / 100.0;
1190 let mut size = ((size_pt.clamp(4.0, 96.0) * 4.0 / 3.0 * dpi / 96.0 * scale * 256.0) as i32).max(256);
1191 let prev = self.text.get_font();
1192 self.text.set_font(font, size);
1193 let cw = if per_cell {
1194 self.text.width("M").max(self.text.width("W")).max(1)
1195 } else {
1196 let adv = self.text.measure("0000000000");
1198 let cw = ((adv + 1280) / 2560).max(1);
1199 if adv > 0 {
1200 size = (size as i64 * (cw as i64 * 2560) / adv as i64) as i32;
1201 self.text.set_font(font, size);
1202 }
1203 cw
1204 };
1205 let (asc, desc) = (self.text.ascent(), self.text.descent());
1206 let lh = self.text.line_h().max(asc + desc - 1).max(1);
1207 let base = ((lh - asc - desc) >> 1) + asc;
1208 self.text.set_font(prev.0, prev.1);
1209 Metrics { font, size, cw, lh, base, per_cell }
1210 }
1211
1212 fn term_palette(&self) -> Pal {
1213 let dark = self.theme_dark();
1214 let bg = over(self.color(T_BG), self.color(T_INPUT_ACTIVE));
1215 let fg = over(bg, self.color(T_TEXT));
1216 let red = over(bg, self.color(T_CRIT));
1217 let green = over(bg, self.color(T_OK));
1218 let yellow = over(bg, self.color(T_WARN));
1219 let blue = over(bg, self.color(T_INFO));
1220 let magenta = color_mix(red, blue, 128);
1221 let cyan = color_mix(green, blue, 140);
1222 let (black, white, gray) = if dark {
1223 (color_mix(bg, fg, 70), over(bg, self.color(T_TEXT2)), over(bg, self.color(T_TEXT3)))
1224 } else {
1225 (fg, color_mix(fg, bg, 150), over(bg, self.color(T_TEXT3)))
1226 };
1227 let lift = |c: u32| color_mix(c, if dark { 0xFFFF_FFFF } else { 0xFF00_0000 }, if dark { 70 } else { 40 });
1228 let base = [black, red, green, yellow, blue, magenta, cyan, white];
1229 let mut c = [0; 16];
1230 for i in 0..8 {
1231 c[i] = base[i];
1232 c[i + 8] = lift(base[i]);
1233 }
1234 c[8] = gray;
1235 c[15] = if dark { fg } else { color_mix(white, bg, 120) };
1236 Pal {
1237 c,
1238 fg,
1239 bg,
1240 sel: over(bg, self.color(T_ACCENT)),
1241 sel_fg: self.color(T_ON_ACCENT) | 0xFF00_0000,
1242 hit: over(bg, self.color(T_WARN_BG)),
1243 hit_cur: yellow,
1244 }
1245 }
1246
1247 fn term_key(&mut self, vk: u32, mods: u32) -> bool {
1250 let m = MOD_SHIFT | MOD_CTRL | MOD_ALT;
1251 let q = &mut self.wp.ctx.keyq;
1252 match q.iter().position(|&k| (k as i32) >= 0 && k & 0xFFFF == vk && (k >> 16) & m == mods && k >> 16 & MOD_ALTGR == 0) {
1253 Some(i) => {
1254 q[i] |= 0x8000_0000;
1255 true
1256 }
1257 None => false,
1258 }
1259 }
1260
1261 fn term_key_next(&mut self) -> Option<u32> {
1263 let q = &mut self.wp.ctx.keyq;
1264 let i = q.iter().position(|&k| (k as i32) >= 0)?;
1265 let k = q[i];
1266 q[i] |= 0x8000_0000;
1267 Some(k)
1268 }
1269
1270 #[cfg_attr(jfgui_dev, track_caller)]
1272 pub fn terminal(&mut self, id: u32, st: &mut TermState, flags: u32) -> Vec<TermEv> {
1273 let _src = self.src_scope();
1274 let flags = flags | st.flags;
1275 let mut evs = Vec::new();
1276 let sid = id.wrapping_add(1); let (bar_id, find_id, more_id) = (id.wrapping_add(2), id.wrapping_add(3), id.wrapping_add(7));
1278 let r = self.place(self.dp(480), self.dp(240));
1279 let m = self.term_metrics(st.font, st.size_pt);
1280 let hair = self.hair();
1281 let style = self.style;
1282 let sbw = style.scrollbar_thickness(self).max(self.dp(10));
1283 let frame = flags & TF_NO_FRAME == 0;
1284 let pad = if frame { self.dp(6) } else { self.dp(2) };
1285 let (tx, ty) = (r[0] + pad, r[1] + pad);
1286 let tw = (r[2] - pad * 2 - sbw).max(m.cw);
1287 let th = (r[3] - pad * 2).max(m.lh);
1288 let cols = (tw / m.cw).max(1) as usize;
1289 let vrows = (th / m.lh).max(1) as usize;
1290 if (cols as u16, vrows as u16) != (st.cols, st.rows) {
1291 let anchor = (!st.at_bottom).then(|| {
1293 let l = st.scr.line_at_row(st.top);
1294 (l, l.map_or(0, |l| st.top.saturating_sub(st.scr.row_of(l))))
1296 });
1297 st.scr.set_cols(cols);
1298 if let Some((Some(l), sub)) = anchor {
1299 let rows = st.scr.line(l).map_or(1, |x| line_rows(x.cells, cols));
1300 st.top = st.scr.row_of(l) + sub.min(rows - 1);
1301 }
1302 st.cols = cols.min(65535) as u16;
1303 st.rows = vrows.min(65535) as u16;
1304 evs.push(TermEv::Resize(st.cols, st.rows));
1305 }
1306 if self.wp.ctx.focus == id {
1308 self.wp.ctx.focus = sid;
1309 }
1310 let fids = sid..=id.wrapping_add(6);
1311 let active_win = self.uiw_cur == self.m.win.active;
1312 let focused = active_win && fids.contains(&self.wp.ctx.focus);
1313 let on_input = active_win && self.wp.ctx.focus == sid;
1314 let on_find = active_win && self.wp.ctx.focus == find_id;
1315 let line_mode = matches!(st.mode, TM_LINE | TM_SECRET) && flags & TF_NO_INPUT == 0;
1316 let waiting = st.pager_waiting();
1317 let show_input = line_mode && !waiting && st.mode == TM_LINE;
1318
1319 let mut help_rest: Option<Vec<u32>> = None;
1321 if focused {
1322 self.term_keys_common(st, &mut evs, id, on_find);
1323 }
1324 if on_input {
1325 if waiting {
1326 self.term_keys_pager(st, &mut evs);
1327 } else if flags & TF_NO_INPUT != 0 {
1328 let _ = self.text_take(usize::MAX);
1329 } else {
1330 match st.mode {
1331 TM_RAW => self.term_keys_raw(st, &mut evs),
1332 TM_BUSY | TM_SECRET => self.term_keys_typed(st, &mut evs, flags),
1333 _ => help_rest = self.term_keys_line(st, &mut evs, sid, flags),
1334 }
1335 }
1336 }
1337 let due = st.ta_at.is_none_or(|t| self.now_ms.wrapping_sub(t) >= 16);
1340 if line_mode && !st.pager_waiting() && due && st.ta_release(&mut evs) {
1341 st.ta_at = Some(self.now_ms);
1342 self.repass();
1343 }
1344 if line_mode && !st.pager_waiting() && !st.ta.is_empty() {
1345 self.timer(UIT_TERM, 16);
1346 }
1347
1348 let waiting = st.pager_waiting();
1350 let show_input = show_input && !st.pager_waiting();
1351 let mut tail: Vec<Cell> = st.scr.cur.clone();
1352 let mut p0 = tail.len(); if waiting {
1354 tail.extend(st.pager_text.chars().map(|ch| Cell { ch, a: Attr { fg: DEF, bg: DEF, fl: A_INV } }));
1355 } else if line_mode {
1356 if st.prompt_cells.as_ref().is_none_or(|p| p.0 != st.prompt) {
1357 let mut s = Screen::new(100);
1358 s.write(st.prompt.as_bytes());
1359 st.prompt_cells = Some((st.prompt.clone(), s.cur));
1360 }
1361 if let Some((_, pc)) = &st.prompt_cells {
1362 tail.extend_from_slice(pc);
1363 }
1364 p0 = tail.len();
1365 if st.mode == TM_LINE {
1366 tail.extend(st.input.chars().map(|ch| Cell { ch, a: ATTR0 }));
1367 }
1368 }
1369 let n_in = if show_input { st.input.chars().count() } else { 0 };
1370 let no_cursor = waiting || flags & TF_NO_INPUT != 0;
1372 let cursor_max = if no_cursor {
1373 0
1374 } else if line_mode {
1375 p0 + n_in + 1
1376 } else {
1377 st.scr.col + 1
1378 };
1379 let tail_cells = tail.len().max(cursor_max).max(1);
1380 let tail_rows = tail_cells.div_ceil(cols) as u64;
1381
1382 let first = st.scr.first_row();
1384 let end_all = st.scr.row_end + tail_rows;
1385 let floor_row = if st.scr.floor >= st.scr.end() { st.scr.row_end } else { st.scr.row_of(st.scr.floor) };
1386 let bottom = end_all.saturating_sub(vrows as u64).max(floor_row).max(first);
1387 let clamp_top = |t: i64| -> u64 { (t.max(first as i64) as u64).min(bottom) };
1388 let mut top = if st.at_bottom { bottom } else { st.top.clamp(first, bottom) };
1389 if st.scroll_req != 0 {
1390 top = clamp_top(top as i64 + st.scroll_req);
1391 st.scroll_req = 0;
1392 }
1393
1394 let ev = self.hit_rect(id, r[0], r[1], r[2], r[3], CUR_IBEAM << HB_CURSOR_SHIFT);
1396 if on_input && !show_input && flags & TF_NO_INPUT == 0 {
1400 let _ = self.hit_rect(sid, r[0], r[1], 0, 0, HB_TEXT);
1401 }
1402 let (mx, my) = (self.rtl_map(self.wp.ctx.mx, tx, cols as i32 * m.cw), self.wp.ctx.my);
1404 let hit_pos = |st: &TermState, top: u64| -> Option<(u64, u32)> {
1405 let v = (my - ty).div_euclid(m.lh);
1406 let row = (top as i64 + v as i64).max(first as i64) as u64;
1407 match st.scr.line_at_row(row) {
1408 Some(l) => {
1409 let line = st.scr.line(l)?;
1410 let sub = row - st.scr.row_of(l);
1411 let c = ((mx - tx + m.cw / 2).div_euclid(m.cw)).clamp(0, cols as i32) as u64;
1412 Some((l, (sub * cols as u64 + c).min(line.cells as u64) as u32))
1413 }
1414 None => {
1415 let l = st.scr.end().checked_sub(1)?;
1416 Some((l, st.scr.line(l)?.cells))
1417 }
1418 }
1419 };
1420 let c = &self.wp.ctx;
1421 if c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == id {
1422 self.wp.ctx.focus = sid; self.wp.ctx.focus_visible = false;
1424 if let Some(p) = hit_pos(st, top) {
1425 if ev & EV_DOUBLE != 0 {
1426 st.sel = st.word_at(p);
1427 st.sel_drag = false;
1428 } else if self.mods & MOD_SHIFT != 0 && st.sel.is_some() {
1429 st.sel = st.sel.map(|(a, _)| (a, p));
1430 st.sel_drag = true;
1431 } else {
1432 st.sel = Some((p, p));
1433 st.sel_drag = true;
1434 }
1435 }
1436 self.repass();
1437 } else if ev & EV_HELD != 0 && st.sel_drag {
1438 if my < ty {
1439 top = clamp_top(top as i64 - 1);
1440 } else if my >= ty + vrows as i32 * m.lh {
1441 top = clamp_top(top as i64 + 1);
1442 }
1443 if let Some(p) = hit_pos(st, top) {
1444 st.sel = st.sel.map(|(a, _)| (a, p));
1445 }
1446 } else if st.sel_drag {
1447 st.sel_drag = false;
1448 if st.sel_range().is_none() {
1449 st.sel = None;
1450 }
1451 }
1452 let wy = self.wp.ctx.wheel_y;
1453 if (ev & EV_HOVER != 0 || self.wp.ctx.hot == sid) && wy != 0 {
1454 let t = clamp_top(top as i64 - (wy as i64 * 3) / 120);
1455 if t != top {
1456 top = t;
1457 self.wp.ctx.wheel_y = 0;
1458 }
1459 }
1460 let lh = m.lh.max(1) as i64;
1462 let (_, pan) = self.pan_take(id);
1463 if pan != 0 {
1464 let p = (top - first) as i64 * lh + st.pan_rem as i64 + pan as i64;
1465 let t = clamp_top(first as i64 + p.div_euclid(lh));
1466 st.pan_rem = if t >= bottom || t <= first && p < 0 { 0 } else { p.rem_euclid(lh) as i32 };
1467 top = t;
1468 }
1469 let hit_end = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1470 let (pos, max) = ((top - first) as i64 * lh + st.pan_rem as i64, (bottom - first) as i64 * lh);
1471 self.pan_view(id, hit_end.saturating_sub(1), super::touch::AX_V, [0, pos.min(i32::MAX as i64) as i32], [0, max.min(i32::MAX as i64) as i32]);
1472 st.at_bottom = top >= bottom;
1473 st.top = top;
1474 if st.at_bottom {
1475 st.unseen = false;
1476 st.seen_added = st.scr.added;
1477 } else if st.scr.added != st.seen_added {
1478 st.unseen = true;
1479 }
1480
1481 let pal = self.term_palette();
1483 if frame {
1484 let stroke = self.color(if focused { T_ACCENT } else { T_CTRL_STROKE });
1485 self.cmd_rrect(r[0], r[1], r[2], r[3], self.dp(4), hair, pal.bg, stroke, stroke);
1486 } else {
1487 self.draw_color(pal.bg);
1488 self.draw_rect(r[0], r[1], r[2], r[3]);
1489 }
1490 self.clip_push(r[0] + hair, r[1] + hair, r[2] - hair * 2, r[3] - hair * 2);
1491 self.ltr_begin(tx, cols as i32 * m.cw);
1492 self.wp.ctx.last_state = 0;
1493 self.sem(K_CANVAS, id, None, st.scr.lines.len() as i32);
1494
1495 if st.find_open {
1497 st.find_update();
1498 }
1499 let sel = st.sel_range();
1500 let body_vis = (st.scr.row_end.saturating_sub(top) as usize).min(vrows);
1501 let mut rows: Vec<Vec<Run>> = Vec::with_capacity(body_vis);
1502 let mut sems: Vec<(i32, i32, String)> = Vec::new();
1503 let mut row = top;
1504 while rows.len() < body_vis {
1505 let Some(l) = st.scr.line_at_row(row) else { break };
1506 let Some(line) = st.scr.line(l) else { break };
1507 let r0 = st.scr.row_of(l);
1508 let sub0 = (row - r0) as usize;
1509 let nrows = line_rows(line.cells, cols) as usize;
1510 let sub_end = nrows.min(sub0 + body_vis - rows.len());
1511 let mut rbs: Vec<Rb> = (sub0..sub_end).map(|_| Rb::default()).collect();
1512 let hits_from = st.find_hits.partition_point(|h| h.0 < l);
1513 let hits = st.find_hits[hits_from..].iter().take_while(|h| h.0 == l);
1514 let cur_hit = st.find_hits.get(st.find_idx).copied();
1515 let mut sp = line.spans.iter().peekable();
1516 let mut a = ATTR0;
1517 for (k, ch) in line.text.chars().enumerate() {
1518 while let Some(&&(c, at)) = sp.peek() {
1519 if c as usize > k {
1520 break;
1521 }
1522 a = at;
1523 sp.next();
1524 }
1525 let rr = k / cols;
1526 if rr < sub0 {
1527 continue;
1528 }
1529 if rr >= sub_end {
1530 break;
1531 }
1532 let mut col = pal.cell(a);
1533 let kk = k as u32;
1534 if sel.is_some_and(|(s, e)| (l, kk) >= s && (l, kk) < e) {
1535 col = (pal.sel_fg, pal.sel, col.2);
1536 } else if let Some(h) = hits.clone().find(|h| h.1 <= kk && kk < h.2) {
1537 col = if Some(*h) == cur_hit { (pal.bg, pal.hit_cur, col.2) } else { (pal.fg, pal.hit, col.2) };
1538 }
1539 rbs[rr - sub0].push((k % cols) as u32, ch, col);
1540 }
1541 let y = ty + rows.len() as i32 * m.lh;
1542 sems.push((y, (sub_end - sub0) as i32 * m.lh, line.text.clone()));
1543 rows.extend(rbs.into_iter().map(|rb| rb.runs));
1544 row = r0 + sub_end as u64;
1545 }
1546 let snap = |rows: Vec<Vec<Run>>| Snap {
1547 font: m.font,
1548 size: m.size,
1549 cw: m.cw,
1550 lh: m.lh,
1551 base: m.base,
1552 ul: (m.base + 1 + (m.size >> 8) / 12, ((m.size >> 8) / 14).max(1)),
1553 bold_dx: (m.size >> 8) / 16 + 1,
1554 per_cell: m.per_cell,
1555 rows,
1556 };
1557 let w = cols as i32 * m.cw;
1558 if body_vis > 0 {
1559 let (d, ver) = term_cached(&mut st.body, snap(rows));
1560 self.cmd_custom(tx, ty, w, body_vis as i32 * m.lh, Some(d), 0, ver);
1561 }
1562 for (y, h, label) in sems.into_iter().filter(|s| !s.2.is_empty()) {
1563 self.wp.ctx.last_rect = [tx, y, w, h];
1564 self.sem(K_TEXT, 0, Some(&label), 0);
1565 }
1566
1567 let tail_y0 = ty + (st.scr.row_end as i64 - top as i64) as i32 * m.lh; let t_first = top.saturating_sub(st.scr.row_end) as usize; let t_end = ((top + vrows as u64).saturating_sub(st.scr.row_end) as usize).min(tail_rows as usize);
1571 if t_first < t_end {
1572 let mut rbs: Vec<Rb> = (t_first..t_end).map(|_| Rb::default()).collect();
1573 for (k, cell) in tail.iter().enumerate() {
1574 let rr = k / cols;
1575 if rr >= t_first && rr < t_end {
1576 rbs[rr - t_first].push((k % cols) as u32, cell.ch, pal.cell(cell.a));
1577 }
1578 }
1579 let y = tail_y0 + t_first as i32 * m.lh;
1580 let (d, ver) = term_cached(&mut st.tail, snap(rbs.into_iter().map(|rb| rb.runs).collect()));
1581 self.cmd_custom(tx, y, w, (t_end - t_first) as i32 * m.lh, Some(d), 0, ver);
1582 let label: String = tail.iter().map(|c| c.ch).collect();
1583 if !label.is_empty() {
1584 self.wp.ctx.last_rect = [tx, y, w, (t_end - t_first) as i32 * m.lh];
1585 self.sem(K_TEXT, 0, Some(&label), 0);
1586 }
1587 }
1588
1589 if show_input {
1591 if let Some(c) = st.caret_want {
1592 if self.textbox_select(sid, &st.input, c, c) {
1593 st.caret_want = None;
1594 }
1595 }
1596 let starts: Vec<usize> = st.input.char_indices().map(|(i, _)| i).chain(core::iter::once(st.input.len())).collect();
1597 let mut lines = Vec::new();
1598 let (r_first, r_last) = (p0 / cols, (p0 + n_in) / cols);
1599 for rr in r_first..=r_last {
1600 let i0 = (rr * cols).saturating_sub(p0).min(n_in);
1601 let i1 = ((rr + 1) * cols).saturating_sub(p0).min(n_in);
1602 let y = tail_y0 + rr as i32 * m.lh;
1603 let carets = (i0..=i1).map(|i| (starts[i], tx + ((p0 + i) - rr * cols) as i32 * m.cw)).collect();
1604 lines.push(TextSurfaceLine { start: starts[i0], end: starts[i1], rect: [tx, y, w.max(1), m.lh], carets });
1605 }
1606 let before = st.input.clone();
1607 self.textbox_surface(sid, &mut st.input, INPUT_CAP, &lines);
1608 if st.input != before {
1609 st.at_bottom = true;
1610 st.hist_pos = st.history.len();
1611 }
1612 if let Some(c) = st.caret_want.take() {
1613 self.textbox_select(sid, &st.input, c.min(st.input.len()), c.min(st.input.len()));
1614 }
1615 if let Some(i) = st.input.find('\n') {
1617 let rest = String::from(&st.input[i + 1..]);
1618 let line = String::from(&st.input[..i]);
1619 st.input.clear();
1620 st.ta.insert_str(0, &rest);
1621 st.submit(line, &mut evs);
1622 st.ta_at = Some(self.now_ms);
1623 self.timer(UIT_TERM, 16);
1624 self.repass();
1625 }
1626 if let Some(rest) = help_rest {
1628 let caret = self.textbox_selection(sid).map_or(st.input.len(), |s| s.1).min(st.input.len());
1629 let line = String::from(&st.input[..caret]);
1630 st.echo(&line, "?");
1631 st.ta.insert_str(0, &rest.iter().filter_map(|&c| char::from_u32(c)).collect::<String>());
1632 st.at_bottom = true;
1633 evs.push(TermEv::Help(line));
1634 }
1635 }
1636
1637 let sel_in = self.textbox_selection(sid);
1639 let cursor = if no_cursor || sel_in.is_some_and(|s| s.0 != s.1) {
1640 None
1641 } else if show_input {
1642 let caret = sel_in.map_or(st.input.len(), |s| s.1).min(st.input.len());
1643 Some(p0 + st.input[..caret].chars().count().min(n_in))
1644 } else if line_mode {
1645 Some(p0)
1646 } else {
1647 Some(st.scr.col)
1648 };
1649 if let Some(k) = cursor {
1650 let rr = k / cols;
1651 if rr >= t_first && rr < t_end {
1652 let (x, y) = (tx + (k % cols) as i32 * m.cw, tail_y0 + rr as i32 * m.lh);
1653 if on_input {
1654 self.draw_color(pal.fg);
1655 self.draw_rect(x, y, m.cw, m.lh);
1656 if let Some(cell) = tail.get(k).filter(|c| c.ch != ' ') {
1657 let mut b = [0u8; 4];
1658 let s = cell.ch.encode_utf8(&mut b);
1659 self.cmd_text(x, y + m.base, s.as_bytes(), m.font, m.size, pal.bg, 0);
1660 }
1661 } else {
1662 self.draw_color(color_mix(pal.bg, pal.fg, 160));
1663 self.draw_frame(x, y, m.cw, m.lh, 0, hair);
1664 }
1665 }
1666 }
1667 self.ltr_end();
1668
1669 let mut pos = (top - first).min(i32::MAX as u64) as i32;
1671 let content = (end_all.max(bottom + vrows as u64) - first).min(i32::MAX as u64) as i32;
1673 self.next_rect_px(r[0] + r[2] - sbw - hair, r[1] + hair, sbw, r[3] - hair * 2);
1674 if self.scrollbar(bar_id, &mut pos, content, vrows as i32, true) & EV_ACT != 0 {
1675 let t = (first + pos as u64).min(bottom);
1676 st.top = t;
1677 st.at_bottom = t >= bottom;
1678 }
1679 if st.unseen && !st.at_bottom {
1680 let label = crate::i18n::tr("Neue Ausgabe");
1681 self.ts_font(TS_BODY);
1682 let bw = self.text.width(label) + self.dp(48);
1683 let bh = self.dp(30);
1684 self.next_rect_px(r[0] + (r[2] - bw) / 2, r[1] + r[3] - bh - self.dp(10), bw, bh);
1685 self.next_icon(IC_DOWN);
1686 if self.accent_button(more_id, label) & EV_ACT != 0 {
1687 st.at_bottom = true;
1688 self.wp.ctx.focus = sid;
1689 self.wp.ctx.focus_visible = false;
1690 }
1691 }
1692 if st.find_open {
1693 self.term_find_bar(id, st, r, sbw);
1694 }
1695 if st.scr.bell {
1696 st.scr.bell = false;
1697 st.bell_until = self.now_ms.wrapping_add(BELL_MS);
1698 self.timer(UIT_TERM_BELL, BELL_MS + 1);
1699 }
1700 if (st.bell_until.wrapping_sub(self.now_ms) as i32) > 0 {
1701 self.draw_color((self.color(T_ACCENT) & 0x00FF_FFFF) | 0x30 << 24);
1702 self.draw_rect(r[0], r[1], r[2], r[3]);
1703 }
1704 self.clip_pop();
1705 self.wp.ctx.last_rect = r;
1706 if !evs.is_empty() {
1707 self.repass();
1708 }
1709 evs
1710 }
1711
1712 fn term_keys_common(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, id: u32, on_find: bool) {
1714 let page = (st.rows.max(2) - 1) as i64;
1715 for (vk, mods, d) in [
1716 (VK_PRIOR, 0, -page),
1717 (VK_NEXT, 0, page),
1718 (VK_PRIOR, MOD_SHIFT, -page),
1719 (VK_NEXT, MOD_SHIFT, page),
1720 (VK_UP, MOD_CTRL, -1),
1721 (VK_DOWN, MOD_CTRL, 1),
1722 (VK_HOME, MOD_CTRL, i64::MIN / 2),
1723 (VK_END, MOD_CTRL, i64::MAX / 2),
1724 ] {
1725 if st.mode == TM_RAW && mods == 0 {
1726 continue; }
1728 if self.term_key(vk, mods) {
1729 st.scroll_req = st.scroll_req.saturating_add(d);
1730 }
1731 }
1732 if st.sel_range().is_some() && (self.term_key(VK_C, MOD_CTRL | MOD_SHIFT) || self.term_key(VK_C, MOD_CTRL) || self.term_key(VK_INSERT, MOD_CTRL)) {
1733 let s = st.sel_text();
1734 self.clip_set(CLIP_TEXT, s.as_bytes());
1735 }
1736 if (st.mode != TM_RAW || on_find) && self.term_key(0x46, MOD_CTRL) {
1737 st.find_open = true;
1739 self.focus(id.wrapping_add(3));
1740 }
1741 if st.find_open {
1742 let older = self.term_key(VK_F3, 0) || (on_find && self.term_key(VK_RETURN, 0));
1743 let newer = self.term_key(VK_F3, MOD_SHIFT) || (on_find && self.term_key(VK_RETURN, MOD_SHIFT));
1744 if older || newer {
1745 st.find_update();
1746 st.find_step(older);
1747 self.repass();
1748 }
1749 if on_find && self.term_key(VK_ESCAPE, 0) {
1750 st.find_open = false;
1751 self.wp.ctx.focus = id.wrapping_add(1);
1752 self.wp.ctx.focus_visible = false;
1753 self.repass();
1754 }
1755 }
1756 if !on_find && (st.find_open || st.sel.is_some() || st.pager_waiting()) && self.term_key(VK_ESCAPE, 0) {
1759 if st.find_open {
1760 st.find_open = false;
1761 } else if st.sel.is_some() {
1762 st.sel = None;
1763 } else {
1764 st.pg_quit_now(evs);
1765 }
1766 self.repass();
1767 }
1768 }
1769
1770 fn term_keys_pager(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>) {
1772 let _ = self.text_take(usize::MAX); self.term_undo_keys();
1774 let page = st.page();
1775 if self.term_key(VK_SPACE, 0) {
1776 st.pg_release(page);
1777 } else if self.term_key(VK_RETURN, 0) {
1778 st.pg_release(1);
1779 } else if self.term_key(0x51, 0) || self.term_key(0x51, MOD_SHIFT) || self.term_key(VK_C, MOD_CTRL) {
1780 st.pg_quit_now(evs);
1781 } else {
1782 return;
1783 }
1784 self.repass();
1785 }
1786
1787 fn term_keys_raw(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>) {
1789 let mut out: Vec<u32> = self.text_take(usize::MAX).into_iter().filter(|&c| c >= 32 && c != 127).collect();
1790 if self.term_key(0x56, MOD_CTRL | MOD_SHIFT) {
1791 if let Some(clip) = self.clip_get(CLIP_TEXT) {
1792 out.extend(String::from_utf8_lossy(&clip).chars().map(|c| if c == '\n' { 13 } else { c as u32 }).filter(|&c| c >= 32 || c == 13));
1793 }
1794 }
1795 while let Some(k) = self.term_key_next() {
1796 let vk = k & 0xFFFF;
1797 let mods = k >> 16 & (MOD_SHIFT | MOD_CTRL | MOD_ALT);
1798 let ctrl = mods & (MOD_CTRL | MOD_ALT) == MOD_CTRL && k >> 16 & MOD_ALTGR == 0;
1799 let typing = vk == VK_SPACE || (0x30..=0x5A).contains(&vk) || (0x60..=0x6F).contains(&vk) || (0xBA..=0xE2).contains(&vk);
1800 out.push(match vk {
1801 VK_RETURN => 13,
1802 VK_TAB if mods == 0 => 9,
1803 VK_BACK => 127,
1804 VK_ESCAPE => 27,
1805 0x41..=0x5A if ctrl => vk - 0x40, _ if typing && mods & (MOD_CTRL | MOD_ALT) == 0 => continue, VK_SHIFT | VK_CONTROL | VK_MENU => continue,
1808 _ => TK_KEY | k & 0x7FFF_FFFF,
1809 });
1810 }
1811 if !out.is_empty() {
1812 st.at_bottom = true;
1813 evs.push(TermEv::Raw(out));
1814 }
1815 }
1816
1817 fn term_keys_typed(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, flags: u32) {
1819 let secret = st.mode == TM_SECRET;
1820 let buf = if secret { &mut st.input } else { &mut st.ta };
1821 for c in self.text_take(usize::MAX) {
1822 if c >= 32 && c != 127 {
1823 buf.push(char::from_u32(c).unwrap_or('\u{FFFD}'));
1824 }
1825 }
1826 if self.term_key(VK_BACK, 0) && !buf.ends_with('\n') {
1827 buf.pop();
1828 }
1829 if self.term_key(VK_RETURN, 0) || self.term_key(VK_RETURN, MOD_SHIFT) {
1830 if secret {
1831 let line = core::mem::take(&mut st.input);
1832 st.submit(line, evs);
1833 } else {
1834 st.ta.push('\n');
1835 }
1836 }
1837 let ctrl_c = self.term_key(VK_C, MOD_CTRL);
1838 if ctrl_c || (flags & TF_CTRL_SHIFT6 != 0 && (self.term_key(0x36, MOD_CTRL | MOD_SHIFT) || self.term_key(0x36, MOD_CTRL))) {
1839 st.ta.clear();
1840 if secret {
1841 st.input.clear();
1842 st.echo("", "^C");
1843 } else if ctrl_c {
1844 st.scr.write(b"^C"); }
1846 evs.push(TermEv::Interrupt);
1847 st.at_bottom = true;
1848 }
1849 let empty = if secret { st.input.is_empty() } else { st.ta.is_empty() || st.ta.ends_with('\n') };
1850 if empty && self.term_key(0x44, MOD_CTRL) {
1851 evs.push(TermEv::Eof);
1852 }
1853 if (empty || !secret) && self.term_key(0x5A, MOD_CTRL) {
1854 evs.push(TermEv::Chord(0x5A | MOD_CTRL << 16));
1855 }
1856 self.term_undo_keys();
1857 }
1858
1859 fn term_undo_keys(&mut self) {
1862 for (vk, mods) in [(0x5A, MOD_CTRL), (0x59, MOD_CTRL), (0x5A, MOD_CTRL | MOD_SHIFT)] {
1863 let _ = self.term_key(vk, mods);
1864 }
1865 }
1866
1867 fn term_keys_line(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, sid: u32, flags: u32) -> Option<Vec<u32>> {
1870 let caret = self.textbox_selection(sid).map_or(st.input.len(), |s| s.1).min(st.input.len());
1871 let has_sel = self.textbox_selection(sid).is_some_and(|s| s.0 != s.1);
1872 if !self.wp.ctx.textq.is_empty() {
1873 st.at_bottom = true;
1874 }
1875 if self.term_key(VK_RETURN, 0) || self.term_key(VK_RETURN, MOD_SHIFT) || self.term_key(VK_RETURN, MOD_CTRL) {
1876 let line = core::mem::take(&mut st.input);
1877 st.submit(line, evs);
1878 }
1879 if self.term_key(VK_TAB, 0) {
1880 st.at_bottom = true;
1881 evs.push(TermEv::Complete { line: st.input.clone(), caret });
1882 }
1883 if self.term_key(VK_UP, 0) {
1884 st.hist_move(true);
1885 }
1886 if self.term_key(VK_DOWN, 0) {
1887 st.hist_move(false);
1888 }
1889 let _ = self.term_key(VK_UP, MOD_SHIFT) || self.term_key(VK_DOWN, MOD_SHIFT);
1890 if self.term_key(VK_HOME, 0) {
1891 st.caret_want = Some(0);
1892 }
1893 if self.term_key(VK_END, 0) {
1894 st.caret_want = Some(st.input.len());
1895 }
1896 if !has_sel && self.term_key(VK_C, MOD_CTRL) {
1897 let line = core::mem::take(&mut st.input);
1898 st.echo(&line, "^C");
1899 st.at_bottom = true;
1900 evs.push(TermEv::Interrupt);
1901 }
1902 if flags & TF_CTRL_SHIFT6 != 0 && (self.term_key(0x36, MOD_CTRL | MOD_SHIFT) || self.term_key(0x36, MOD_CTRL)) {
1903 evs.push(TermEv::Interrupt);
1904 }
1905 if st.input.is_empty() {
1906 if self.term_key(0x44, MOD_CTRL) {
1907 evs.push(TermEv::Eof);
1908 }
1909 if self.term_key(0x5A, MOD_CTRL) {
1910 evs.push(TermEv::Chord(0x5A | MOD_CTRL << 16));
1911 }
1912 }
1913 if self.term_key(0x4C, MOD_CTRL) {
1914 evs.push(TermEv::Chord(0x4C | MOD_CTRL << 16));
1915 }
1916 if self.term_key(0x55, MOD_CTRL) {
1917 let mut caret = caret.min(st.input.len());
1920 while !st.input.is_char_boundary(caret) {
1921 caret -= 1;
1922 }
1923 st.input.drain(..caret);
1924 st.caret_want = Some(0);
1925 }
1926 if flags & TF_QHELP != 0 {
1927 let q = &mut self.wp.ctx.textq;
1928 if let Some(k) = q.iter().position(|&c| c == '?' as u32) {
1929 let rest = q.split_off(k);
1930 return Some(rest[1..].to_vec());
1931 }
1932 }
1933 None
1934 }
1935
1936 fn term_find_bar(&mut self, id: u32, st: &mut TermState, r: [i32; 4], sbw: i32) {
1938 let (fid, up_id, down_id, close_id) = (id.wrapping_add(3), id.wrapping_add(4), id.wrapping_add(5), id.wrapping_add(6));
1939 let h = self.dp(30);
1940 let g = self.dp(4);
1941 let tb_w = self.dp(160);
1942 let count = if st.find_hits.is_empty() {
1943 String::from(if st.find_text.is_empty() { "" } else { "0/0" })
1944 } else {
1945 alloc::format!("{}/{}", st.find_idx + 1, st.find_hits.len())
1946 };
1947 self.ts_font(TS_CAPTION);
1948 let cw = self.text.width(&count).max(self.dp(36));
1949 let pw = g + tb_w + g + cw + g + h * 3 + g;
1950 let ph = h + g * 2;
1951 let (px, py) = (r[0] + r[2] - sbw - pw - self.dp(8), r[1] + self.dp(6));
1952 let (fill, stroke) = (self.color(T_FLYOUT), self.color(T_FLYOUT_STROKE));
1953 self.cmd_rrect(px, py, pw, ph, self.dp(6), self.hair(), fill | 0xFF00_0000, stroke, stroke);
1954 self.wp.ctx.last_rect = [px, py, pw, ph];
1955 self.hit(0, HB_BLOCK); let mut x = px + g;
1957 self.next_rect_px(x, py + g, tb_w, h);
1958 let before = st.find_text.clone();
1959 self.textbox(fid, Some(crate::i18n::tr("Suchen")), &mut st.find_text, 256);
1960 if st.find_text != before {
1961 st.find_update();
1962 self.repass();
1963 }
1964 x += tb_w + g;
1965 let base = self.ts_base(TS_CAPTION);
1966 let line = self.ts_line(TS_CAPTION);
1967 let c = self.color(T_TEXT2);
1968 let tw = self.text.width(&count);
1969 self.emit_text(x + (cw - tw) / 2, py + g + (h - line) / 2 + base, &count, c);
1970 if !count.is_empty() {
1971 self.wp.ctx.last_rect = [x, py + g, cw, h];
1972 self.sem(K_TEXT, 0, Some(&count), 0);
1973 }
1974 x += cw + g;
1975 for (bid, icon, tip, older) in [
1976 (up_id, IC_UP, "Vorheriger Treffer", Some(true)),
1977 (down_id, IC_DOWN, "Nächster Treffer", Some(false)),
1978 (close_id, IC_CHROME_CLOSE, "Schließen", None),
1979 ] {
1980 self.next_rect_px(x, py + g, h, h);
1981 self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
1982 if self.icon_button(bid, icon, crate::i18n::tr(tip)) & EV_ACT != 0 {
1983 match older {
1984 Some(o) => {
1985 st.find_update();
1986 st.find_step(o);
1987 }
1988 None => {
1989 st.find_open = false;
1990 self.wp.ctx.focus = id.wrapping_add(1);
1991 self.wp.ctx.focus_visible = false;
1992 }
1993 }
1994 self.repass();
1995 }
1996 x += h;
1997 }
1998 }
1999}
2000
2001fn term_cached(slot: &mut Option<(Rc<Snap>, u32)>, snap: Snap) -> (DrawRef, u32) {
2003 if let Some((d, ver)) = slot {
2004 if **d == snap {
2005 let r: DrawRef = d.clone();
2006 return (r, *ver);
2007 }
2008 }
2009 let ver = slot.as_ref().map_or(1, |s| s.1.wrapping_add(1));
2010 let d = Rc::new(snap);
2011 *slot = Some((d.clone(), ver));
2012 let r: DrawRef = d;
2013 (r, ver)
2014}