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 - st.scr.row_of(l)))
1295 });
1296 st.scr.set_cols(cols);
1297 if let Some((Some(l), sub)) = anchor {
1298 let rows = st.scr.line(l).map_or(1, |x| line_rows(x.cells, cols));
1299 st.top = st.scr.row_of(l) + sub.min(rows - 1);
1300 }
1301 st.cols = cols.min(65535) as u16;
1302 st.rows = vrows.min(65535) as u16;
1303 evs.push(TermEv::Resize(st.cols, st.rows));
1304 }
1305 if self.wp.ctx.focus == id {
1307 self.wp.ctx.focus = sid;
1308 }
1309 let fids = sid..=id.wrapping_add(6);
1310 let active_win = self.uiw_cur == self.m.win.active;
1311 let focused = active_win && fids.contains(&self.wp.ctx.focus);
1312 let on_input = active_win && self.wp.ctx.focus == sid;
1313 let on_find = active_win && self.wp.ctx.focus == find_id;
1314 let line_mode = matches!(st.mode, TM_LINE | TM_SECRET) && flags & TF_NO_INPUT == 0;
1315 let waiting = st.pager_waiting();
1316 let show_input = line_mode && !waiting && st.mode == TM_LINE;
1317
1318 let mut help_rest: Option<Vec<u32>> = None;
1320 if focused {
1321 self.term_keys_common(st, &mut evs, id, on_find);
1322 }
1323 if on_input {
1324 if waiting {
1325 self.term_keys_pager(st, &mut evs);
1326 } else if flags & TF_NO_INPUT != 0 {
1327 let _ = self.text_take(usize::MAX);
1328 } else {
1329 match st.mode {
1330 TM_RAW => self.term_keys_raw(st, &mut evs),
1331 TM_BUSY | TM_SECRET => self.term_keys_typed(st, &mut evs, flags),
1332 _ => help_rest = self.term_keys_line(st, &mut evs, sid, flags),
1333 }
1334 }
1335 }
1336 let due = st.ta_at.is_none_or(|t| self.now_ms.wrapping_sub(t) >= 16);
1339 if line_mode && !st.pager_waiting() && due && st.ta_release(&mut evs) {
1340 st.ta_at = Some(self.now_ms);
1341 self.repass();
1342 }
1343 if line_mode && !st.pager_waiting() && !st.ta.is_empty() {
1344 self.timer(UIT_TERM, 16);
1345 }
1346
1347 let waiting = st.pager_waiting();
1349 let show_input = show_input && !st.pager_waiting();
1350 let mut tail: Vec<Cell> = st.scr.cur.clone();
1351 let mut p0 = tail.len(); if waiting {
1353 tail.extend(st.pager_text.chars().map(|ch| Cell { ch, a: Attr { fg: DEF, bg: DEF, fl: A_INV } }));
1354 } else if line_mode {
1355 if st.prompt_cells.as_ref().is_none_or(|p| p.0 != st.prompt) {
1356 let mut s = Screen::new(100);
1357 s.write(st.prompt.as_bytes());
1358 st.prompt_cells = Some((st.prompt.clone(), s.cur));
1359 }
1360 if let Some((_, pc)) = &st.prompt_cells {
1361 tail.extend_from_slice(pc);
1362 }
1363 p0 = tail.len();
1364 if st.mode == TM_LINE {
1365 tail.extend(st.input.chars().map(|ch| Cell { ch, a: ATTR0 }));
1366 }
1367 }
1368 let n_in = if show_input { st.input.chars().count() } else { 0 };
1369 let no_cursor = waiting || flags & TF_NO_INPUT != 0;
1371 let cursor_max = if no_cursor {
1372 0
1373 } else if line_mode {
1374 p0 + n_in + 1
1375 } else {
1376 st.scr.col + 1
1377 };
1378 let tail_cells = tail.len().max(cursor_max).max(1);
1379 let tail_rows = tail_cells.div_ceil(cols) as u64;
1380
1381 let first = st.scr.first_row();
1383 let end_all = st.scr.row_end + tail_rows;
1384 let floor_row = if st.scr.floor >= st.scr.end() { st.scr.row_end } else { st.scr.row_of(st.scr.floor) };
1385 let bottom = end_all.saturating_sub(vrows as u64).max(floor_row).max(first);
1386 let clamp_top = |t: i64| -> u64 { (t.max(first as i64) as u64).min(bottom) };
1387 let mut top = if st.at_bottom { bottom } else { st.top.clamp(first, bottom) };
1388 if st.scroll_req != 0 {
1389 top = clamp_top(top as i64 + st.scroll_req);
1390 st.scroll_req = 0;
1391 }
1392
1393 let ev = self.hit_rect(id, r[0], r[1], r[2], r[3], CUR_IBEAM << HB_CURSOR_SHIFT);
1395 if on_input && !show_input && flags & TF_NO_INPUT == 0 {
1399 let _ = self.hit_rect(sid, r[0], r[1], 0, 0, HB_TEXT);
1400 }
1401 let (mx, my) = (self.rtl_map(self.wp.ctx.mx, tx, cols as i32 * m.cw), self.wp.ctx.my);
1403 let hit_pos = |st: &TermState, top: u64| -> Option<(u64, u32)> {
1404 let v = (my - ty).div_euclid(m.lh);
1405 let row = (top as i64 + v as i64).max(first as i64) as u64;
1406 match st.scr.line_at_row(row) {
1407 Some(l) => {
1408 let line = st.scr.line(l)?;
1409 let sub = row - st.scr.row_of(l);
1410 let c = ((mx - tx + m.cw / 2).div_euclid(m.cw)).clamp(0, cols as i32) as u64;
1411 Some((l, (sub * cols as u64 + c).min(line.cells as u64) as u32))
1412 }
1413 None => {
1414 let l = st.scr.end().checked_sub(1)?;
1415 Some((l, st.scr.line(l)?.cells))
1416 }
1417 }
1418 };
1419 let c = &self.wp.ctx;
1420 if c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == id {
1421 self.wp.ctx.focus = sid; self.wp.ctx.focus_visible = false;
1423 if let Some(p) = hit_pos(st, top) {
1424 if ev & EV_DOUBLE != 0 {
1425 st.sel = st.word_at(p);
1426 st.sel_drag = false;
1427 } else if self.mods & MOD_SHIFT != 0 && st.sel.is_some() {
1428 st.sel = st.sel.map(|(a, _)| (a, p));
1429 st.sel_drag = true;
1430 } else {
1431 st.sel = Some((p, p));
1432 st.sel_drag = true;
1433 }
1434 }
1435 self.repass();
1436 } else if ev & EV_HELD != 0 && st.sel_drag {
1437 if my < ty {
1438 top = clamp_top(top as i64 - 1);
1439 } else if my >= ty + vrows as i32 * m.lh {
1440 top = clamp_top(top as i64 + 1);
1441 }
1442 if let Some(p) = hit_pos(st, top) {
1443 st.sel = st.sel.map(|(a, _)| (a, p));
1444 }
1445 } else if st.sel_drag {
1446 st.sel_drag = false;
1447 if st.sel_range().is_none() {
1448 st.sel = None;
1449 }
1450 }
1451 let wy = self.wp.ctx.wheel_y;
1452 if (ev & EV_HOVER != 0 || self.wp.ctx.hot == sid) && wy != 0 {
1453 let t = clamp_top(top as i64 - (wy as i64 * 3) / 120);
1454 if t != top {
1455 top = t;
1456 self.wp.ctx.wheel_y = 0;
1457 }
1458 }
1459 let lh = m.lh.max(1) as i64;
1461 let (_, pan) = self.pan_take(id);
1462 if pan != 0 {
1463 let p = (top - first) as i64 * lh + st.pan_rem as i64 + pan as i64;
1464 let t = clamp_top(first as i64 + p.div_euclid(lh));
1465 st.pan_rem = if t >= bottom || t <= first && p < 0 { 0 } else { p.rem_euclid(lh) as i32 };
1466 top = t;
1467 }
1468 let hit_end = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1469 let (pos, max) = ((top - first) as i64 * lh + st.pan_rem as i64, (bottom - first) as i64 * lh);
1470 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]);
1471 st.at_bottom = top >= bottom;
1472 st.top = top;
1473 if st.at_bottom {
1474 st.unseen = false;
1475 st.seen_added = st.scr.added;
1476 } else if st.scr.added != st.seen_added {
1477 st.unseen = true;
1478 }
1479
1480 let pal = self.term_palette();
1482 if frame {
1483 let stroke = self.color(if focused { T_ACCENT } else { T_CTRL_STROKE });
1484 self.cmd_rrect(r[0], r[1], r[2], r[3], self.dp(4), hair, pal.bg, stroke, stroke);
1485 } else {
1486 self.draw_color(pal.bg);
1487 self.draw_rect(r[0], r[1], r[2], r[3]);
1488 }
1489 self.clip_push(r[0] + hair, r[1] + hair, r[2] - hair * 2, r[3] - hair * 2);
1490 self.ltr_begin(tx, cols as i32 * m.cw);
1491 self.wp.ctx.last_state = 0;
1492 self.sem(K_CANVAS, id, None, st.scr.lines.len() as i32);
1493
1494 if st.find_open {
1496 st.find_update();
1497 }
1498 let sel = st.sel_range();
1499 let body_vis = (st.scr.row_end.saturating_sub(top) as usize).min(vrows);
1500 let mut rows: Vec<Vec<Run>> = Vec::with_capacity(body_vis);
1501 let mut sems: Vec<(i32, i32, String)> = Vec::new();
1502 let mut row = top;
1503 while rows.len() < body_vis {
1504 let Some(l) = st.scr.line_at_row(row) else { break };
1505 let Some(line) = st.scr.line(l) else { break };
1506 let r0 = st.scr.row_of(l);
1507 let sub0 = (row - r0) as usize;
1508 let nrows = line_rows(line.cells, cols) as usize;
1509 let sub_end = nrows.min(sub0 + body_vis - rows.len());
1510 let mut rbs: Vec<Rb> = (sub0..sub_end).map(|_| Rb::default()).collect();
1511 let hits_from = st.find_hits.partition_point(|h| h.0 < l);
1512 let hits = st.find_hits[hits_from..].iter().take_while(|h| h.0 == l);
1513 let cur_hit = st.find_hits.get(st.find_idx).copied();
1514 let mut sp = line.spans.iter().peekable();
1515 let mut a = ATTR0;
1516 for (k, ch) in line.text.chars().enumerate() {
1517 while let Some(&&(c, at)) = sp.peek() {
1518 if c as usize > k {
1519 break;
1520 }
1521 a = at;
1522 sp.next();
1523 }
1524 let rr = k / cols;
1525 if rr < sub0 {
1526 continue;
1527 }
1528 if rr >= sub_end {
1529 break;
1530 }
1531 let mut col = pal.cell(a);
1532 let kk = k as u32;
1533 if sel.is_some_and(|(s, e)| (l, kk) >= s && (l, kk) < e) {
1534 col = (pal.sel_fg, pal.sel, col.2);
1535 } else if let Some(h) = hits.clone().find(|h| h.1 <= kk && kk < h.2) {
1536 col = if Some(*h) == cur_hit { (pal.bg, pal.hit_cur, col.2) } else { (pal.fg, pal.hit, col.2) };
1537 }
1538 rbs[rr - sub0].push((k % cols) as u32, ch, col);
1539 }
1540 let y = ty + rows.len() as i32 * m.lh;
1541 sems.push((y, (sub_end - sub0) as i32 * m.lh, line.text.clone()));
1542 rows.extend(rbs.into_iter().map(|rb| rb.runs));
1543 row = r0 + sub_end as u64;
1544 }
1545 let snap = |rows: Vec<Vec<Run>>| Snap {
1546 font: m.font,
1547 size: m.size,
1548 cw: m.cw,
1549 lh: m.lh,
1550 base: m.base,
1551 ul: (m.base + 1 + (m.size >> 8) / 12, ((m.size >> 8) / 14).max(1)),
1552 bold_dx: (m.size >> 8) / 16 + 1,
1553 per_cell: m.per_cell,
1554 rows,
1555 };
1556 let w = cols as i32 * m.cw;
1557 if body_vis > 0 {
1558 let (d, ver) = term_cached(&mut st.body, snap(rows));
1559 self.cmd_custom(tx, ty, w, body_vis as i32 * m.lh, Some(d), 0, ver);
1560 }
1561 for (y, h, label) in sems.into_iter().filter(|s| !s.2.is_empty()) {
1562 self.wp.ctx.last_rect = [tx, y, w, h];
1563 self.sem(K_TEXT, 0, Some(&label), 0);
1564 }
1565
1566 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);
1570 if t_first < t_end {
1571 let mut rbs: Vec<Rb> = (t_first..t_end).map(|_| Rb::default()).collect();
1572 for (k, cell) in tail.iter().enumerate() {
1573 let rr = k / cols;
1574 if rr >= t_first && rr < t_end {
1575 rbs[rr - t_first].push((k % cols) as u32, cell.ch, pal.cell(cell.a));
1576 }
1577 }
1578 let y = tail_y0 + t_first as i32 * m.lh;
1579 let (d, ver) = term_cached(&mut st.tail, snap(rbs.into_iter().map(|rb| rb.runs).collect()));
1580 self.cmd_custom(tx, y, w, (t_end - t_first) as i32 * m.lh, Some(d), 0, ver);
1581 let label: String = tail.iter().map(|c| c.ch).collect();
1582 if !label.is_empty() {
1583 self.wp.ctx.last_rect = [tx, y, w, (t_end - t_first) as i32 * m.lh];
1584 self.sem(K_TEXT, 0, Some(&label), 0);
1585 }
1586 }
1587
1588 if show_input {
1590 if let Some(c) = st.caret_want {
1591 if self.textbox_select(sid, &st.input, c, c) {
1592 st.caret_want = None;
1593 }
1594 }
1595 let starts: Vec<usize> = st.input.char_indices().map(|(i, _)| i).chain(core::iter::once(st.input.len())).collect();
1596 let mut lines = Vec::new();
1597 let (r_first, r_last) = (p0 / cols, (p0 + n_in) / cols);
1598 for rr in r_first..=r_last {
1599 let i0 = (rr * cols).saturating_sub(p0).min(n_in);
1600 let i1 = ((rr + 1) * cols).saturating_sub(p0).min(n_in);
1601 let y = tail_y0 + rr as i32 * m.lh;
1602 let carets = (i0..=i1).map(|i| (starts[i], tx + ((p0 + i) - rr * cols) as i32 * m.cw)).collect();
1603 lines.push(TextSurfaceLine { start: starts[i0], end: starts[i1], rect: [tx, y, w.max(1), m.lh], carets });
1604 }
1605 let before = st.input.clone();
1606 self.textbox_surface(sid, &mut st.input, INPUT_CAP, &lines);
1607 if st.input != before {
1608 st.at_bottom = true;
1609 st.hist_pos = st.history.len();
1610 }
1611 if let Some(c) = st.caret_want.take() {
1612 self.textbox_select(sid, &st.input, c.min(st.input.len()), c.min(st.input.len()));
1613 }
1614 if let Some(i) = st.input.find('\n') {
1616 let rest = String::from(&st.input[i + 1..]);
1617 let line = String::from(&st.input[..i]);
1618 st.input.clear();
1619 st.ta.insert_str(0, &rest);
1620 st.submit(line, &mut evs);
1621 st.ta_at = Some(self.now_ms);
1622 self.timer(UIT_TERM, 16);
1623 self.repass();
1624 }
1625 if let Some(rest) = help_rest {
1627 let caret = self.textbox_selection(sid).map_or(st.input.len(), |s| s.1).min(st.input.len());
1628 let line = String::from(&st.input[..caret]);
1629 st.echo(&line, "?");
1630 st.ta.insert_str(0, &rest.iter().filter_map(|&c| char::from_u32(c)).collect::<String>());
1631 st.at_bottom = true;
1632 evs.push(TermEv::Help(line));
1633 }
1634 }
1635
1636 let sel_in = self.textbox_selection(sid);
1638 let cursor = if no_cursor || sel_in.is_some_and(|s| s.0 != s.1) {
1639 None
1640 } else if show_input {
1641 let caret = sel_in.map_or(st.input.len(), |s| s.1).min(st.input.len());
1642 Some(p0 + st.input[..caret].chars().count().min(n_in))
1643 } else if line_mode {
1644 Some(p0)
1645 } else {
1646 Some(st.scr.col)
1647 };
1648 if let Some(k) = cursor {
1649 let rr = k / cols;
1650 if rr >= t_first && rr < t_end {
1651 let (x, y) = (tx + (k % cols) as i32 * m.cw, tail_y0 + rr as i32 * m.lh);
1652 if on_input {
1653 self.draw_color(pal.fg);
1654 self.draw_rect(x, y, m.cw, m.lh);
1655 if let Some(cell) = tail.get(k).filter(|c| c.ch != ' ') {
1656 let mut b = [0u8; 4];
1657 let s = cell.ch.encode_utf8(&mut b);
1658 self.cmd_text(x, y + m.base, s.as_bytes(), m.font, m.size, pal.bg, 0);
1659 }
1660 } else {
1661 self.draw_color(color_mix(pal.bg, pal.fg, 160));
1662 self.draw_frame(x, y, m.cw, m.lh, 0, hair);
1663 }
1664 }
1665 }
1666 self.ltr_end();
1667
1668 let mut pos = (top - first).min(i32::MAX as u64) as i32;
1670 let content = (end_all.max(bottom + vrows as u64) - first).min(i32::MAX as u64) as i32;
1672 self.next_rect_px(r[0] + r[2] - sbw - hair, r[1] + hair, sbw, r[3] - hair * 2);
1673 if self.scrollbar(bar_id, &mut pos, content, vrows as i32, true) & EV_ACT != 0 {
1674 let t = (first + pos as u64).min(bottom);
1675 st.top = t;
1676 st.at_bottom = t >= bottom;
1677 }
1678 if st.unseen && !st.at_bottom {
1679 let label = crate::i18n::tr("Neue Ausgabe");
1680 self.ts_font(TS_BODY);
1681 let bw = self.text.width(label) + self.dp(48);
1682 let bh = self.dp(30);
1683 self.next_rect_px(r[0] + (r[2] - bw) / 2, r[1] + r[3] - bh - self.dp(10), bw, bh);
1684 self.next_icon(IC_DOWN);
1685 if self.accent_button(more_id, label) & EV_ACT != 0 {
1686 st.at_bottom = true;
1687 self.wp.ctx.focus = sid;
1688 self.wp.ctx.focus_visible = false;
1689 }
1690 }
1691 if st.find_open {
1692 self.term_find_bar(id, st, r, sbw);
1693 }
1694 if st.scr.bell {
1695 st.scr.bell = false;
1696 st.bell_until = self.now_ms.wrapping_add(BELL_MS);
1697 self.timer(UIT_TERM_BELL, BELL_MS + 1);
1698 }
1699 if (st.bell_until.wrapping_sub(self.now_ms) as i32) > 0 {
1700 self.draw_color((self.color(T_ACCENT) & 0x00FF_FFFF) | 0x30 << 24);
1701 self.draw_rect(r[0], r[1], r[2], r[3]);
1702 }
1703 self.clip_pop();
1704 self.wp.ctx.last_rect = r;
1705 if !evs.is_empty() {
1706 self.repass();
1707 }
1708 evs
1709 }
1710
1711 fn term_keys_common(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, id: u32, on_find: bool) {
1713 let page = (st.rows.max(2) - 1) as i64;
1714 for (vk, mods, d) in [
1715 (VK_PRIOR, 0, -page),
1716 (VK_NEXT, 0, page),
1717 (VK_PRIOR, MOD_SHIFT, -page),
1718 (VK_NEXT, MOD_SHIFT, page),
1719 (VK_UP, MOD_CTRL, -1),
1720 (VK_DOWN, MOD_CTRL, 1),
1721 (VK_HOME, MOD_CTRL, i64::MIN / 2),
1722 (VK_END, MOD_CTRL, i64::MAX / 2),
1723 ] {
1724 if st.mode == TM_RAW && mods == 0 {
1725 continue; }
1727 if self.term_key(vk, mods) {
1728 st.scroll_req = st.scroll_req.saturating_add(d);
1729 }
1730 }
1731 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)) {
1732 let s = st.sel_text();
1733 self.clip_set(CLIP_TEXT, s.as_bytes());
1734 }
1735 if (st.mode != TM_RAW || on_find) && self.term_key(0x46, MOD_CTRL) {
1736 st.find_open = true;
1738 self.focus(id.wrapping_add(3));
1739 }
1740 if st.find_open {
1741 let older = self.term_key(VK_F3, 0) || (on_find && self.term_key(VK_RETURN, 0));
1742 let newer = self.term_key(VK_F3, MOD_SHIFT) || (on_find && self.term_key(VK_RETURN, MOD_SHIFT));
1743 if older || newer {
1744 st.find_update();
1745 st.find_step(older);
1746 self.repass();
1747 }
1748 if on_find && self.term_key(VK_ESCAPE, 0) {
1749 st.find_open = false;
1750 self.wp.ctx.focus = id.wrapping_add(1);
1751 self.wp.ctx.focus_visible = false;
1752 self.repass();
1753 }
1754 }
1755 if !on_find && (st.find_open || st.sel.is_some() || st.pager_waiting()) && self.term_key(VK_ESCAPE, 0) {
1758 if st.find_open {
1759 st.find_open = false;
1760 } else if st.sel.is_some() {
1761 st.sel = None;
1762 } else {
1763 st.pg_quit_now(evs);
1764 }
1765 self.repass();
1766 }
1767 }
1768
1769 fn term_keys_pager(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>) {
1771 let _ = self.text_take(usize::MAX); self.term_undo_keys();
1773 let page = st.page();
1774 if self.term_key(VK_SPACE, 0) {
1775 st.pg_release(page);
1776 } else if self.term_key(VK_RETURN, 0) {
1777 st.pg_release(1);
1778 } else if self.term_key(0x51, 0) || self.term_key(0x51, MOD_SHIFT) || self.term_key(VK_C, MOD_CTRL) {
1779 st.pg_quit_now(evs);
1780 } else {
1781 return;
1782 }
1783 self.repass();
1784 }
1785
1786 fn term_keys_raw(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>) {
1788 let mut out: Vec<u32> = self.text_take(usize::MAX).into_iter().filter(|&c| c >= 32 && c != 127).collect();
1789 if self.term_key(0x56, MOD_CTRL | MOD_SHIFT) {
1790 if let Some(clip) = self.clip_get(CLIP_TEXT) {
1791 out.extend(String::from_utf8_lossy(&clip).chars().map(|c| if c == '\n' { 13 } else { c as u32 }).filter(|&c| c >= 32 || c == 13));
1792 }
1793 }
1794 while let Some(k) = self.term_key_next() {
1795 let vk = k & 0xFFFF;
1796 let mods = k >> 16 & (MOD_SHIFT | MOD_CTRL | MOD_ALT);
1797 let ctrl = mods & (MOD_CTRL | MOD_ALT) == MOD_CTRL && k >> 16 & MOD_ALTGR == 0;
1798 let typing = vk == VK_SPACE || (0x30..=0x5A).contains(&vk) || (0x60..=0x6F).contains(&vk) || (0xBA..=0xE2).contains(&vk);
1799 out.push(match vk {
1800 VK_RETURN => 13,
1801 VK_TAB if mods == 0 => 9,
1802 VK_BACK => 127,
1803 VK_ESCAPE => 27,
1804 0x41..=0x5A if ctrl => vk - 0x40, _ if typing && mods & (MOD_CTRL | MOD_ALT) == 0 => continue, VK_SHIFT | VK_CONTROL | VK_MENU => continue,
1807 _ => TK_KEY | k & 0x7FFF_FFFF,
1808 });
1809 }
1810 if !out.is_empty() {
1811 st.at_bottom = true;
1812 evs.push(TermEv::Raw(out));
1813 }
1814 }
1815
1816 fn term_keys_typed(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, flags: u32) {
1818 let secret = st.mode == TM_SECRET;
1819 let buf = if secret { &mut st.input } else { &mut st.ta };
1820 for c in self.text_take(usize::MAX) {
1821 if c >= 32 && c != 127 {
1822 buf.push(char::from_u32(c).unwrap_or('\u{FFFD}'));
1823 }
1824 }
1825 if self.term_key(VK_BACK, 0) && !buf.ends_with('\n') {
1826 buf.pop();
1827 }
1828 if self.term_key(VK_RETURN, 0) || self.term_key(VK_RETURN, MOD_SHIFT) {
1829 if secret {
1830 let line = core::mem::take(&mut st.input);
1831 st.submit(line, evs);
1832 } else {
1833 st.ta.push('\n');
1834 }
1835 }
1836 let ctrl_c = self.term_key(VK_C, MOD_CTRL);
1837 if ctrl_c || (flags & TF_CTRL_SHIFT6 != 0 && (self.term_key(0x36, MOD_CTRL | MOD_SHIFT) || self.term_key(0x36, MOD_CTRL))) {
1838 st.ta.clear();
1839 if secret {
1840 st.input.clear();
1841 st.echo("", "^C");
1842 } else if ctrl_c {
1843 st.scr.write(b"^C"); }
1845 evs.push(TermEv::Interrupt);
1846 st.at_bottom = true;
1847 }
1848 let empty = if secret { st.input.is_empty() } else { st.ta.is_empty() || st.ta.ends_with('\n') };
1849 if empty && self.term_key(0x44, MOD_CTRL) {
1850 evs.push(TermEv::Eof);
1851 }
1852 if (empty || !secret) && self.term_key(0x5A, MOD_CTRL) {
1853 evs.push(TermEv::Chord(0x5A | MOD_CTRL << 16));
1854 }
1855 self.term_undo_keys();
1856 }
1857
1858 fn term_undo_keys(&mut self) {
1861 for (vk, mods) in [(0x5A, MOD_CTRL), (0x59, MOD_CTRL), (0x5A, MOD_CTRL | MOD_SHIFT)] {
1862 let _ = self.term_key(vk, mods);
1863 }
1864 }
1865
1866 fn term_keys_line(&mut self, st: &mut TermState, evs: &mut Vec<TermEv>, sid: u32, flags: u32) -> Option<Vec<u32>> {
1869 let caret = self.textbox_selection(sid).map_or(st.input.len(), |s| s.1).min(st.input.len());
1870 let has_sel = self.textbox_selection(sid).is_some_and(|s| s.0 != s.1);
1871 if !self.wp.ctx.textq.is_empty() {
1872 st.at_bottom = true;
1873 }
1874 if self.term_key(VK_RETURN, 0) || self.term_key(VK_RETURN, MOD_SHIFT) || self.term_key(VK_RETURN, MOD_CTRL) {
1875 let line = core::mem::take(&mut st.input);
1876 st.submit(line, evs);
1877 }
1878 if self.term_key(VK_TAB, 0) {
1879 st.at_bottom = true;
1880 evs.push(TermEv::Complete { line: st.input.clone(), caret });
1881 }
1882 if self.term_key(VK_UP, 0) {
1883 st.hist_move(true);
1884 }
1885 if self.term_key(VK_DOWN, 0) {
1886 st.hist_move(false);
1887 }
1888 let _ = self.term_key(VK_UP, MOD_SHIFT) || self.term_key(VK_DOWN, MOD_SHIFT);
1889 if self.term_key(VK_HOME, 0) {
1890 st.caret_want = Some(0);
1891 }
1892 if self.term_key(VK_END, 0) {
1893 st.caret_want = Some(st.input.len());
1894 }
1895 if !has_sel && self.term_key(VK_C, MOD_CTRL) {
1896 let line = core::mem::take(&mut st.input);
1897 st.echo(&line, "^C");
1898 st.at_bottom = true;
1899 evs.push(TermEv::Interrupt);
1900 }
1901 if flags & TF_CTRL_SHIFT6 != 0 && (self.term_key(0x36, MOD_CTRL | MOD_SHIFT) || self.term_key(0x36, MOD_CTRL)) {
1902 evs.push(TermEv::Interrupt);
1903 }
1904 if st.input.is_empty() {
1905 if self.term_key(0x44, MOD_CTRL) {
1906 evs.push(TermEv::Eof);
1907 }
1908 if self.term_key(0x5A, MOD_CTRL) {
1909 evs.push(TermEv::Chord(0x5A | MOD_CTRL << 16));
1910 }
1911 }
1912 if self.term_key(0x4C, MOD_CTRL) {
1913 evs.push(TermEv::Chord(0x4C | MOD_CTRL << 16));
1914 }
1915 if self.term_key(0x55, MOD_CTRL) {
1916 let mut caret = caret.min(st.input.len());
1919 while !st.input.is_char_boundary(caret) {
1920 caret -= 1;
1921 }
1922 st.input.drain(..caret);
1923 st.caret_want = Some(0);
1924 }
1925 if flags & TF_QHELP != 0 {
1926 let q = &mut self.wp.ctx.textq;
1927 if let Some(k) = q.iter().position(|&c| c == '?' as u32) {
1928 let rest = q.split_off(k);
1929 return Some(rest[1..].to_vec());
1930 }
1931 }
1932 None
1933 }
1934
1935 fn term_find_bar(&mut self, id: u32, st: &mut TermState, r: [i32; 4], sbw: i32) {
1937 let (fid, up_id, down_id, close_id) = (id.wrapping_add(3), id.wrapping_add(4), id.wrapping_add(5), id.wrapping_add(6));
1938 let h = self.dp(30);
1939 let g = self.dp(4);
1940 let tb_w = self.dp(160);
1941 let count = if st.find_hits.is_empty() {
1942 String::from(if st.find_text.is_empty() { "" } else { "0/0" })
1943 } else {
1944 alloc::format!("{}/{}", st.find_idx + 1, st.find_hits.len())
1945 };
1946 self.ts_font(TS_CAPTION);
1947 let cw = self.text.width(&count).max(self.dp(36));
1948 let pw = g + tb_w + g + cw + g + h * 3 + g;
1949 let ph = h + g * 2;
1950 let (px, py) = (r[0] + r[2] - sbw - pw - self.dp(8), r[1] + self.dp(6));
1951 let (fill, stroke) = (self.color(T_FLYOUT), self.color(T_FLYOUT_STROKE));
1952 self.cmd_rrect(px, py, pw, ph, self.dp(6), self.hair(), fill | 0xFF00_0000, stroke, stroke);
1953 self.wp.ctx.last_rect = [px, py, pw, ph];
1954 self.hit(0, HB_BLOCK); let mut x = px + g;
1956 self.next_rect_px(x, py + g, tb_w, h);
1957 let before = st.find_text.clone();
1958 self.textbox(fid, Some(crate::i18n::tr("Suchen")), &mut st.find_text, 256);
1959 if st.find_text != before {
1960 st.find_update();
1961 self.repass();
1962 }
1963 x += tb_w + g;
1964 let base = self.ts_base(TS_CAPTION);
1965 let line = self.ts_line(TS_CAPTION);
1966 let c = self.color(T_TEXT2);
1967 let tw = self.text.width(&count);
1968 self.emit_text(x + (cw - tw) / 2, py + g + (h - line) / 2 + base, &count, c);
1969 if !count.is_empty() {
1970 self.wp.ctx.last_rect = [x, py + g, cw, h];
1971 self.sem(K_TEXT, 0, Some(&count), 0);
1972 }
1973 x += cw + g;
1974 for (bid, icon, tip, older) in [
1975 (up_id, IC_UP, "Vorheriger Treffer", Some(true)),
1976 (down_id, IC_DOWN, "Nächster Treffer", Some(false)),
1977 (close_id, IC_CHROME_CLOSE, "Schließen", None),
1978 ] {
1979 self.next_rect_px(x, py + g, h, h);
1980 self.next_flags(ST_SUBTLE | ST_ICON_ONLY);
1981 if self.icon_button(bid, icon, crate::i18n::tr(tip)) & EV_ACT != 0 {
1982 match older {
1983 Some(o) => {
1984 st.find_update();
1985 st.find_step(o);
1986 }
1987 None => {
1988 st.find_open = false;
1989 self.wp.ctx.focus = id.wrapping_add(1);
1990 self.wp.ctx.focus_visible = false;
1991 }
1992 }
1993 self.repass();
1994 }
1995 x += h;
1996 }
1997 }
1998}
1999
2000fn term_cached(slot: &mut Option<(Rc<Snap>, u32)>, snap: Snap) -> (DrawRef, u32) {
2002 if let Some((d, ver)) = slot {
2003 if **d == snap {
2004 let r: DrawRef = d.clone();
2005 return (r, *ver);
2006 }
2007 }
2008 let ver = slot.as_ref().map_or(1, |s| s.1.wrapping_add(1));
2009 let d = Rc::new(snap);
2010 *slot = Some((d.clone(), ver));
2011 let r: DrawRef = d;
2012 (r, ver)
2013}