1use alloc::collections::BTreeMap;
84use alloc::format;
85use alloc::rc::Rc;
86use alloc::string::String;
87use alloc::vec::Vec;
88
89use gfx::{CAP_ROUND, JOIN_ROUND};
90use text::segment;
91use text::FS_SYN_ITALIC;
92
93use crate::cmd::{Draw, DrawRef, Gfx};
94use crate::code_assist::*;
95use crate::code_doc::*;
96use crate::code_long::{Longs, COL_LONG};
97use crate::consts::*;
98use crate::ed_core::*;
99use crate::highlight::*;
100use crate::i18n::tr;
101use crate::keys::*;
102use crate::{Sp, Ui};
103
104pub const CE_READONLY: u32 = 1;
106pub const CE_NO_GUTTER: u32 = 2;
108pub const CE_NO_FRAME: u32 = 4;
110pub const CE_NO_FIND: u32 = 8;
112pub const CE_NO_LINE: u32 = 16;
114pub const CE_MARKS: u32 = 32;
116pub const CE_BREAKPOINTS: u32 = 64;
118pub const CE_FOLDS: u32 = 128;
120
121const NAT_W: i32 = 560;
122const NAT_H: i32 = 320;
123const LABEL_MAX: usize = 64 * 1024; const BRACKET_LINES: usize = 3000; const UNKNOWN: u32 = u32::MAX;
126
127#[derive(Clone, Copy, Debug, Default, PartialEq)]
129pub struct CodeStatus {
130 pub line: usize,
132 pub col: usize,
133 pub matches: usize,
135 pub current: usize,
136 pub hl_lines: usize,
139 pub lines: usize,
141 pub diags: [usize; 3],
143 pub diag: Option<u8>,
146 pub popup: usize,
148 pub popup_sel: usize,
149 pub sig: bool,
151 pub hidden: usize,
153}
154
155#[derive(Default)]
157struct Hl {
158 grammar: usize,
159 uid: u32,
160 rev: u64,
161 states: Vec<u32>,
162 valid: usize,
165 known: usize,
166 dirty: usize,
167 count: usize,
169}
170
171#[derive(Default)]
172struct Find {
173 open: bool,
174 replace: bool,
175 query: String,
176 rep: String,
177 case: i32,
178 word: i32,
179 matches: Vec<(TextPos, TextPos)>,
180 key: (String, u8, u64, u32),
182 current: Option<usize>,
183 from: TextPos,
185}
186
187impl Find {
188 fn opts(&self) -> u8 {
189 (if self.case != 0 { FIND_CASE } else { 0 }) | (if self.word != 0 { FIND_WORD } else { 0 })
190 }
191}
192
193#[derive(Default)]
194struct View {
195 seen: u32,
196 sx: i32,
197 sy: i32,
198 t0: u32,
199 prefx: i32,
200 drag: u8,
202 word: (TextPos, TextPos),
203 held: Option<(usize, usize)>,
204 label: (u32, u64, usize, usize, String),
206 last_sel: TextSel,
207 last_rev: u64,
208 hl: Hl,
209 find: Find,
210 max_cols: (u32, u64, usize, usize),
212 bracket: Option<(u32, u64, TextPos, Option<(TextPos, TextPos)>)>,
213 status: CodeStatus,
214 hl_last: usize,
216 caret_diag: Option<Diag>,
218 long: Longs,
220 pop: Pop,
222 sig: Sig,
223 hov: Hov,
224 events: Vec<CodeEv>,
225 show_caret: bool,
227}
228
229#[derive(Default)]
231pub(crate) struct S {
232 views: BTreeMap<u32, View>,
233 theme: Option<CodeTheme>,
234 size_pt: f32,
235 find_req: Option<(u32, String, bool)>,
237 reveal_req: BTreeMap<u32, usize>,
239 waves: Vec<(u32, u32, DrawRef)>,
241}
242
243struct Wave {
245 color: u32,
246 hp: i32,
248 amp: i32,
249 width: i32,
250}
251
252impl Draw for Wave {
253 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
254 let pad = (self.width >> 9) + 1;
255 let (x0, x1, top) = (r[0] + pad, r[0] + r[2] - pad, r[1] + pad);
256 let (c0, c1) = (g.dc.clip[0] - g.dc.ox, g.dc.clip[2] - g.dc.ox);
258 let k0 = ((c0 - x0) / self.hp - 2).max(0);
259 let y = |k: i32| ((top + (k & 1) * self.amp) << 8) + 128;
260 let sh = g.shapes();
261 sh.path.begin(sh.dc);
262 let mut k = k0;
263 let mut x = x0 + k * self.hp;
264 sh.path.move_fx(x << 8, y(k));
265 while x < x1 && x <= c1 + self.hp * 2 {
266 x = (x + self.hp).min(x1);
267 k += 1;
268 sh.path.line_fx(x << 8, y(k));
269 }
270 sh.dc.set_color(self.color);
271 sh.path.stroke(sh.ras, sh.dc, sh.sf, self.width, CAP_ROUND << 4 | JOIN_ROUND);
272 }
273}
274
275#[derive(Clone, Copy)]
277struct Metrics {
278 font: u32,
279 italic: u32,
280 size: i32,
281 cw: i32,
282 lh: i32,
283 base: i32,
284 per_cell: bool,
285}
286
287struct CView<'a> {
289 doc: &'a CodeDoc,
290 cw: i32,
291 lh: i32,
292 tw: usize,
293 long: &'a Longs,
294}
295
296impl EdView for CView<'_> {
297 fn lines(&self) -> usize {
298 self.doc.line_count()
299 }
300 fn text(&self, line: usize) -> &str {
301 self.doc.line(line)
302 }
303 fn rows(&self, _: usize) -> usize {
304 1
305 }
306 fn row_range(&self, line: usize, _: usize) -> (usize, usize) {
307 (0, self.doc.line(line).len())
308 }
309 fn x_at(&self, line: usize, _: usize, byte: usize) -> i32 {
310 self.long.col(self.doc, line, byte, self.tw) as i32 * self.cw
311 }
312 fn hit_x(&self, line: usize, _: usize, x: i32) -> usize {
313 let col = ((x.max(0) + self.cw / 2) / self.cw.max(1)) as usize;
314 self.long.byte(self.doc, line, col, self.tw)
315 }
316 fn row_y(&self, line: usize, _: usize) -> (i32, i32) {
318 (self.doc.row_of_line(line) as i32 * self.lh, self.lh)
319 }
320 fn row_at(&self, y: i32) -> (usize, usize) {
321 (self.doc.line_of_row((y.max(0) / self.lh.max(1)) as usize), 0)
322 }
323}
324
325fn code_word(t: &str, byte: usize) -> (usize, usize) {
327 let b = byte.min(t.len());
328 let at = t[b..].chars().next().or_else(|| t[..b].chars().next_back());
329 match at {
330 Some(c) if is_word(c) => {
331 let a = t[..b].char_indices().rev().take_while(|(_, c)| is_word(*c)).last().map_or(b, |(i, _)| i);
332 let e = t[b..].char_indices().find(|(_, c)| !is_word(*c)).map_or(t.len(), |(i, _)| b + i);
333 (a, e)
334 }
335 _ => word_range(t, b),
336 }
337}
338
339impl Ui {
340 pub fn code_theme_set(&mut self, theme: Option<CodeTheme>) {
342 self.m.code.theme = theme;
343 self.repass();
344 }
345
346 pub fn code_font_size(&mut self, pt: f32) {
348 self.m.code.size_pt = pt;
349 self.repass();
350 }
351
352 pub fn code_find(&mut self, id: u32, query: &str, replace: bool) {
355 self.m.code.find_req = Some((id, String::from(query), replace));
356 self.repass();
357 }
358
359 pub fn code_reveal(&mut self, id: u32, line: usize) {
365 self.m.code.reveal_req.insert(id, line);
366 self.repass();
367 }
368
369 pub fn code_status(&self, id: u32) -> CodeStatus {
372 self.m.code.views.get(&id).map_or(CodeStatus::default(), |v| v.status)
373 }
374
375 pub fn code_diag(&self, id: u32) -> Option<Diag> {
378 self.m.code.views.get(&id).and_then(|v| v.caret_diag.clone())
379 }
380
381 pub fn code_events(&self, id: u32) -> Vec<CodeEv> {
384 self.m.code.views.get(&id).map_or(Vec::new(), |v| v.events.clone())
385 }
386
387 pub fn code_popup_item(&self, id: u32) -> Option<CompItem> {
389 let v = self.m.code.views.get(&id)?;
390 let p = &v.pop;
391 p.shown.get(p.sel).and_then(|&i| p.items.get(i)).filter(|_| p.open).cloned()
392 }
393
394 fn ce_metrics(&mut self) -> Metrics {
396 let pt = if self.m.code.size_pt > 0.0 { self.m.code.size_pt } else { 10.0 };
397 let (font, _) = self.mono_font(pt);
398 let dpi = self.wp.rt.dpi as f32;
399 let scale = self.text_scale as f32 / 100.0;
400 let mut size = ((pt.clamp(4.0, 96.0) * 4.0 / 3.0 * dpi / 96.0 * scale * 256.0) as i32).max(256);
401 let prev = self.text.get_font();
402 self.text.set_font(font, size);
403 let per_cell = self.text.measure("i") != self.text.measure("M");
404 let cw = if per_cell {
405 self.text.width("M").max(self.text.width("W")).max(1)
406 } else {
407 let adv = self.text.measure("0000000000");
408 let cw = ((adv + 1280) / 2560).max(1);
409 if adv > 0 {
410 size = (size as i64 * (cw as i64 * 2560) / adv as i64) as i32;
411 self.text.set_font(font, size);
412 }
413 cw
414 };
415 let (asc, desc) = (self.text.ascent(), self.text.descent());
416 let lh = (self.text.line_h().max(asc + desc) + self.dp(3)).max(1);
417 let base = ((lh - asc - desc) >> 1) + asc;
418 self.text.set_font(prev.0, prev.1);
419 let it = self.text.style(font, FS_SYN_ITALIC);
420 Metrics { font, italic: if it >= 0 { it as u32 } else { font }, size, cw, lh, base, per_cell }
421 }
422
423 fn ce_hl_sync(hl: &mut Hl, doc: &CodeDoc, h: &Highlighter) {
425 let g = h.grammar() as *const Grammar as usize;
426 let n = doc.line_count();
427 let changes = if hl.grammar == g && hl.uid == doc.uid() && !hl.states.is_empty() { doc.changes_since(hl.rev).map(|c| c.to_vec()) } else { None };
428 match changes {
429 Some(cs) => {
430 for c in cs {
431 if hl.dirty <= hl.valid {
432 hl.dirty = 0; }
434 let lo = (c.first + 1).min(hl.states.len());
436 let hi = (c.first + c.old).min(hl.states.len()).max(lo);
437 hl.states.splice(lo..hi, core::iter::repeat_n(UNKNOWN, c.new.saturating_sub(1)));
438 let d = c.new as isize - c.old as isize;
439 let shift = |x: usize| -> usize {
440 if x >= c.first + c.old {
441 (x as isize + d) as usize
442 } else {
443 x.min(c.first)
444 }
445 };
446 hl.known = shift(hl.known);
447 hl.dirty = if hl.dirty > c.first { shift(hl.dirty).max(c.first + c.new) } else { c.first + c.new };
448 hl.valid = hl.valid.min(c.first);
449 }
450 }
451 None => {
452 hl.states.clear();
453 hl.states.push(0);
454 hl.valid = 0;
455 hl.known = 0;
456 hl.dirty = 0;
457 hl.grammar = g;
458 hl.uid = doc.uid();
459 }
460 }
461 hl.states.resize(n + 1, UNKNOWN);
462 if hl.states[0] == UNKNOWN {
463 hl.states[0] = 0;
464 }
465 hl.known = hl.known.min(n);
466 hl.valid = hl.valid.min(n);
467 hl.rev = doc.revision();
468 }
469
470 fn ce_hl_upto(hl: &mut Hl, doc: &CodeDoc, h: &Highlighter, upto: usize, toks: &mut Vec<Token>, long: &Longs, keep: usize) {
472 let upto = upto.min(doc.line_count());
473 let tw = doc.tab_width.clamp(1, 16) as usize;
474 while hl.valid < upto {
475 let k = hl.valid;
476 let t = doc.line(k);
477 let st = if t.len() >= COL_LONG && k >= keep { long.tokens(doc, k, tw, h, hl.states[k]).0 } else { h.line(t, hl.states[k], toks) };
478 hl.count += 1;
479 let next = k + 1;
480 if next >= hl.dirty && next <= hl.known && hl.states[next] == st {
481 hl.valid = hl.known;
483 hl.dirty = 0;
484 continue;
485 }
486 hl.states[next] = st;
487 hl.valid = next;
488 if hl.valid > hl.known {
489 hl.known = hl.valid;
490 }
491 }
492 }
493
494 fn ce_bracket(v: &mut View, doc: &CodeDoc, h: &Highlighter) -> Option<(TextPos, TextPos)> {
497 let caret = doc.selection().caret;
498 if let Some((uid, rev, at, r)) = v.bracket {
499 if uid == doc.uid() && rev == doc.revision() && at == caret {
500 return r;
501 }
502 }
503 let pairs: Vec<(u8, u8)> = h.grammar().pairs.as_bytes().chunks(2).filter(|c| c.len() == 2).map(|c| (c[0], c[1])).collect();
504 let tw = doc.tab_width.clamp(1, 16) as usize;
505 let long = &v.long;
506 let mut toks = Vec::new();
507 let mut cur: Option<(usize, Rc<Vec<Token>>, usize)> = None;
509 let mut code_at = |hl: &mut Hl, l: usize, b: usize| -> bool {
510 if cur.as_ref().is_none_or(|c| c.0 != l) {
511 Ui::ce_hl_upto(hl, doc, h, l, &mut toks, long, usize::MAX);
512 let t = doc.line(l);
513 let tk = if t.len() >= COL_LONG {
514 long.tokens(doc, l, tw, h, hl.states[l]).1
515 } else {
516 h.line(t, hl.states[l], &mut toks);
517 Rc::new(toks.clone())
518 };
519 cur = Some((l, tk, 0));
520 }
521 let Some((_, tk, last)) = &mut cur else { return true };
522 let mut k = (*last).min(tk.len());
524 while k < tk.len() && tk[k].end <= b {
525 k += 1;
526 }
527 while k > 0 && tk[k - 1].end > b {
528 k -= 1;
529 }
530 *last = k;
531 !tk.get(k).is_some_and(|t| t.start <= b && matches!(t.kind, SY_STRING | SY_COMMENT | SY_CODE | SY_ESCAPE))
532 };
533 let line = doc.line(caret.line).as_bytes();
534 let mut found = None;
535 for at in [caret.byte, caret.byte.wrapping_sub(1)] {
536 let Some(&c) = line.get(at) else { continue };
537 let Some(&(o, cl)) = pairs.iter().find(|p| p.0 == c || p.1 == c) else { continue };
538 if !code_at(&mut v.hl, caret.line, at) {
539 continue;
540 }
541 let fwd = c == o;
542 let (want, other) = if fwd { (cl, o) } else { (o, cl) };
543 let mut depth = 0;
544 let mut l = caret.line;
545 let mut b = at as isize;
546 let mut steps = 0;
547 'scan: loop {
548 let t = doc.line(l).as_bytes();
549 loop {
550 let is = |x: &u8| *x == want || *x == other;
552 let next = if fwd {
553 t.get((b + 1) as usize..).and_then(|r| r.iter().position(is)).map(|k| b + 1 + k as isize)
554 } else {
555 t[..b.max(0) as usize].iter().rposition(is).map(|k| k as isize)
556 };
557 let Some(nb) = next else { break };
558 b = nb;
559 let x = t[b as usize];
560 if !code_at(&mut v.hl, l, b as usize) {
561 continue;
562 }
563 if x == other {
564 depth += 1;
565 } else if depth == 0 {
566 found = Some((TextPos::new(caret.line, at), TextPos::new(l, b as usize)));
567 break 'scan;
568 } else {
569 depth -= 1;
570 }
571 }
572 steps += 1;
573 if steps > BRACKET_LINES {
574 break;
575 }
576 if fwd {
577 l += 1;
578 if l >= doc.line_count() {
579 break;
580 }
581 b = -1;
582 } else {
583 if l == 0 {
584 break;
585 }
586 l -= 1;
587 b = doc.line(l).len() as isize;
588 }
589 }
590 if found.is_some() {
591 break;
592 }
593 }
594 v.bracket = Some((doc.uid(), doc.revision(), caret, found));
595 found
596 }
597
598 fn ce_find_update(f: &mut Find, doc: &CodeDoc) {
600 let key = (f.query.clone(), f.opts(), doc.revision(), doc.uid());
601 if f.key == key {
602 return;
603 }
604 let same_query = f.key.0 == key.0 && f.key.1 == key.1 && f.key.3 == key.3;
605 let changes = if same_query { doc.changes_since(f.key.2).map(|c| c.to_vec()) } else { None };
606 match changes {
607 Some(cs) => {
608 for c in cs {
609 let lo = f.matches.partition_point(|m| m.0.line < c.first);
610 let hi = f.matches.partition_point(|m| m.0.line < c.first + c.old);
611 let d = c.new as isize - c.old as isize;
612 for m in f.matches[hi..].iter_mut() {
613 m.0.line = (m.0.line as isize + d) as usize;
614 m.1.line = (m.1.line as isize + d) as usize;
615 }
616 let fresh = doc_find_range(doc, &f.query, f.opts(), c.first, c.new);
617 f.matches.splice(lo..hi, fresh);
618 }
619 f.matches.truncate(FIND_MAX);
620 }
621 None => f.matches = doc.find_all(&f.query, f.opts()),
622 }
623 f.key = key;
624 }
625
626 #[cfg_attr(jfgui_dev, track_caller)]
628 pub fn code_editor(&mut self, id: u32, doc: &mut CodeDoc, h: &Highlighter, flags: u32) -> u32 {
629 let _src = self.src_scope();
630 self.code_editor_with(id, doc, h, None, flags)
631 }
632
633 #[cfg_attr(jfgui_dev, track_caller)]
636 pub fn code_editor_with(&mut self, id: u32, doc: &mut CodeDoc, h: &Highlighter, mut assist: Option<&mut dyn CodeAssist>, flags: u32) -> u32 {
637 let _src = self.src_scope();
638 let ro = flags & CE_READONLY != 0;
639 let stflags = self.take_flags();
640 let r = self.place(self.dp(NAT_W), self.dp(NAT_H));
641 let hair = self.hair();
642 let st = self.style;
643 let frame = flags & CE_NO_FRAME == 0;
644 let m = self.ce_metrics();
645 let theme = self.m.code.theme.clone().unwrap_or_else(|| self.code_theme());
646 let mut v = self.m.code.views.remove(&id).unwrap_or_default();
647 v.seen = self.frame;
648 v.hl.count = 0;
649 v.events.clear();
650 let view0 = (v.sx, v.sy);
651 let rev0 = doc.revision();
652 let sel0 = doc.selection();
653 let tw = doc.tab_width.clamp(1, 16) as usize;
654 if let Some((rid, q, rep)) = self.m.code.find_req.take() {
656 if rid == id && flags & CE_NO_FIND == 0 {
657 v.find.open = true;
658 v.find.replace = rep;
659 v.find.query = q;
660 v.find.from = doc.selection().start();
661 v.find.key.2 = u64::MAX;
662 self.focus(id.wrapping_add(3));
663 } else {
664 self.m.code.find_req = Some((rid, q, rep));
665 }
666 }
667 let reveal = self.m.code.reveal_req.remove(&id).map(|l| l.min(doc.line_count().saturating_sub(1)));
669 if let Some(l) = reveal {
670 doc.reveal(l);
671 }
672 let sbw = st.scrollbar_thickness(self);
674 let n = doc.line_count();
675 let digits = format!("{}", n).len().max(2) as i32;
676 let bpw = if flags & CE_BREAKPOINTS != 0 || !doc.breakpoints().is_empty() || doc.exec_line().is_some() { m.lh } else { 0 };
679 let mk = if flags & CE_MARKS != 0 || doc.diagnostic_count() > 0 { m.lh } else { 0 };
680 let nums = flags & CE_NO_GUTTER == 0;
681 let numw = if nums { digits * m.cw + self.dp(18) } else { 0 };
682 let fw = if flags & CE_FOLDS != 0 || doc.fold_count() > 0 { (m.lh * 3 / 4).max(self.dp(12)) } else { 0 };
683 let gw = bpw + mk + numw + fw;
684 let bar_h = if v.find.open { self.dp(if v.find.replace { 76 } else { 40 }) } else { 0 };
685 let inner = [r[0] + hair, r[1] + hair, (r[2] - hair * 2).max(1), (r[3] - hair * 2).max(1)];
686 let ty = inner[1] + bar_h;
687 let tx = inner[0] + gw + self.dp(6);
688 if v.max_cols.0 != doc.uid() || v.max_cols.1 != doc.revision() || v.max_cols.2 == 0 {
689 v.max_cols = line_sizes(doc);
690 }
691 let content_w = (v.max_cols.2 as i32 + 2).saturating_mul(m.cw);
692 let tw_px0 = (inner[0] + inner[2] - sbw - tx).max(m.cw);
693 let hbar = content_w > tw_px0;
694 let th = (inner[1] + inner[3] - ty - if hbar { sbw } else { 0 }).max(m.lh);
695 let tw_px = tw_px0;
696 let total_h = doc.visible_lines() as i32 * m.lh;
697 let page = th;
698 let mut hb = HB_FOCUS | HB_TEXT | CUR_IBEAM << HB_CURSOR_SHIFT;
700 if stflags & ST_DISABLED != 0 {
701 hb |= HB_DISABLED;
702 }
703 if doc.diagnostic_count() > 0 || assist.is_some() {
704 hb |= HB_MOVES; }
706 self.wp.ctx.last_rect = [inner[0], ty, inner[2], inner[1] + inner[3] - ty];
707 let hit0 = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
708 let ev = self.behave(id, hb);
709 let disabled = self.wp.ctx.last_state == SS_DISABLED as u32;
710 let focused = !disabled && self.ed_focused(id);
711 let mut out = ev & (EV_HOVER | EV_HELD | EV_DOUBLE | EV_RCLICK | EV_DRAG);
712 if focused {
713 if let (Some(sel), Some(held)) = (self.textbox_selection(id), v.held) {
714 if sel != held {
715 let base = doc.flat(TextPos::new(v.label.2, 0));
717 let (a, c) = (doc.unflat(sel.0 + base), doc.unflat(sel.1 + base));
718 doc.set_selection(TextSel::new(a, c));
719 }
720 }
721 if let Some(t) = self.a11y_text(id) {
722 if !ro {
723 let e = doc.end();
724 doc.replace(TextPos::default(), e, &t);
725 }
726 }
727 } else if v.held.is_some() {
728 v.held = None;
729 v.drag = 0;
730 self.ed_drop(id);
731 out |= EV_COMMIT;
732 }
733 if !focused {
734 v.pop.close();
735 v.sig.open = false;
736 }
737 let (mx, my) = (self.rtl_map(self.wp.ctx.mx, inner[0], inner[2]), self.wp.ctx.my);
739 let (cx, cy) = (mx - tx + v.sx, my - ty + v.sy);
740 let in_gutter = mx < tx && gw > 0;
741 let c = &self.wp.ctx;
742 let press = c.pressed & (1 << BTN_LEFT) != 0 && ev & EV_HOVER != 0 && c.active == id;
743 if press && !disabled {
744 let view = CView { doc: &*doc, cw: m.cw, lh: m.lh, tw, long: &v.long };
745 let p = clamp_pos(&view, TextPos::new(view.row_at(cy).0, view.hit_x(view.row_at(cy).0, 0, cx)));
746 let row_line = view.row_at(cy).0;
747 v.prefx = -1;
748 v.t0 = self.now_ms;
749 v.pop.close();
750 v.sig.open = false;
751 let fold_box = doc.fold_at(row_line).is_some_and(|f| f.1) && {
753 let end = v.long.col(doc, row_line, doc.line(row_line).len(), tw) as i32 * m.cw;
754 cx >= end + m.cw / 2 && cx < end + m.cw * 4
755 };
756 if in_gutter && bpw > 0 && mx < inner[0] + bpw {
757 v.events.push(CodeEv::Breakpoint(row_line));
759 v.drag = 0;
760 } else if in_gutter && fw > 0 && mx >= inner[0] + gw - fw && doc.fold_at(row_line).is_some() {
761 doc.toggle_fold(row_line);
762 v.drag = 0;
763 self.repass();
764 } else if fold_box && !in_gutter {
765 doc.set_folded(row_line, false);
766 v.drag = 0;
767 self.repass();
768 } else if in_gutter {
769 let l = view.row_at(cy).0;
770 let e = if l + 1 < n { TextPos::new(l + 1, 0) } else { TextPos::new(l, doc.line(l).len()) };
771 v.word = (TextPos::new(l, 0), e);
772 if self.mods & MOD_SHIFT != 0 {
773 let a = doc.selection().anchor;
774 doc.set_selection(TextSel::new(a, e));
775 } else {
776 doc.set_selection(TextSel::new(v.word.0, e));
777 }
778 v.drag = 3;
779 } else if ev & EV_DOUBLE != 0 {
780 let (a, b) = code_word(doc.line(p.line), p.byte);
781 v.word = (TextPos::new(p.line, a), TextPos::new(p.line, b));
782 doc.set_selection(TextSel::new(v.word.0, v.word.1));
783 v.drag = 2;
784 } else if self.mods & MOD_SHIFT != 0 {
785 let a = doc.selection().anchor;
786 doc.set_selection(TextSel::new(a, p));
787 v.drag = 1;
788 } else {
789 doc.set_selection(TextSel::at(p));
790 v.drag = 1;
791 if self.mods & MOD_CTRL != 0 {
792 v.events.push(CodeEv::Definition(p));
794 v.drag = 0;
795 }
796 }
797 } else if ev & EV_HELD != 0 && v.drag != 0 {
798 if my < ty {
799 v.sy -= m.lh;
800 } else if my >= ty + th {
801 v.sy += m.lh;
802 }
803 if mx >= tx + tw_px {
804 v.sx += m.cw * 2;
805 } else if mx < tx && v.drag != 3 {
806 v.sx -= m.cw * 2;
807 }
808 v.sy = v.sy.clamp(0, (total_h - th).max(0));
809 v.sx = v.sx.clamp(0, (content_w - tw_px).max(0));
810 let (cx, cy) = (mx - tx + v.sx, my - ty + v.sy);
811 let view = CView { doc: &*doc, cw: m.cw, lh: m.lh, tw, long: &v.long };
812 let l = view.row_at(cy).0;
813 let p = TextPos::new(l, view.hit_x(l, 0, cx));
814 match v.drag {
815 3 => {
816 let e = if l + 1 < n { TextPos::new(l + 1, 0) } else { TextPos::new(l, doc.line(l).len()) };
817 if TextPos::new(l, 0) < v.word.0 {
818 doc.set_selection(TextSel::new(v.word.1, TextPos::new(l, 0)));
819 } else {
820 doc.set_selection(TextSel::new(v.word.0, e));
821 }
822 }
823 2 => {
824 let (a, b) = code_word(doc.line(l), p.byte);
825 let (wa, wb) = (TextPos::new(l, a), TextPos::new(l, b));
826 if wa < v.word.0 {
827 doc.set_selection(TextSel::new(v.word.1, wa));
828 } else {
829 doc.set_selection(TextSel::new(v.word.0, wb.max(v.word.1)));
830 }
831 }
832 _ => {
833 let a = doc.selection().anchor;
834 doc.set_selection(TextSel::new(a, p));
835 }
836 }
837 } else if ev & EV_HELD == 0 {
838 v.drag = 0;
839 }
840 if focused {
842 loop {
843 let k0 = self.ed_key_first();
844 if k0 == 0 {
845 break;
846 }
847 let k = self.rtl_text_key(k0);
848 if Ui::ce_assist_key(doc, &mut v, assist.as_deref_mut(), k, ro, page / m.lh.max(1)) || self.ce_key(id, doc, &mut v, h, k, flags, &m) {
849 self.ed_key_take();
850 v.t0 = self.now_ms;
851 self.repass();
852 continue;
853 }
854 let act = {
855 let view = CView { doc: &*doc, cw: m.cw, lh: m.lh, tw, long: &v.long };
856 ed_key(&view, doc.selection(), &mut v.prefx, k, page, false)
857 };
858 let Some(act) = act else { break };
859 self.ed_key_take();
860 v.t0 = self.now_ms;
861 match act {
862 EdAct::Sel(s) => {
863 let c0 = doc.selection().caret;
865 let s = if doc.hidden().is_empty() || !matches!(k & 0xFFFF, VK_UP | VK_DOWN | VK_LEFT | VK_RIGHT | VK_PRIOR | VK_NEXT) {
866 s
867 } else {
868 let view = CView { doc: &*doc, cw: m.cw, lh: m.lh, tw, long: &v.long };
869 let c = skip_hidden(&view, c0, s.caret, k & 0xFFFF, v.prefx);
870 if s.anchor == s.caret { TextSel::at(c) } else { TextSel::new(s.anchor, c) }
871 };
872 doc.set_selection(s);
873 }
874 EdAct::Del(a, b, _) if !ro => {
875 let back = k & 0xFFFF == VK_BACK;
876 let empty = doc.selection().is_empty();
877 let (a, b) = if back && empty && !doc.use_tabs { soft_back(doc, a, b) } else { (a, b) };
878 doc.delete_kind(a, b, if !empty { CK_NONE } else if back { CK_BACK } else { CK_DEL });
879 }
880 EdAct::Enter if !ro => {
881 doc.newline(h.grammar().openers);
882 }
883 EdAct::Copy => self.ce_copy(doc, false, ro),
884 EdAct::Cut => self.ce_copy(doc, !ro, ro),
885 EdAct::Paste if !ro => {
886 if let Some(t) = self.clip_get(CLIP_TEXT) {
887 let s = String::from_utf8_lossy(&t).replace('\0', "");
888 if !s.is_empty() {
889 doc.insert(&s);
890 }
891 }
892 }
893 EdAct::Undo if !ro => {
894 doc.undo();
895 }
896 EdAct::Redo if !ro => {
897 doc.redo();
898 }
899 _ => {}
900 }
901 }
902 let cps = self.text_take(super::ctx::TEXTQ_MAX);
903 if !ro {
904 let s = typed_text(&cps, false);
905 if !s.is_empty() {
906 doc.type_text(&s, h.grammar().closers);
907 v.t0 = self.now_ms;
908 v.prefx = -1;
909 if let Some(a) = assist.as_deref_mut() {
910 Ui::ce_assist_typed(doc, &mut v, a, &s);
911 }
912 }
913 }
914 }
915 if v.pop.open {
918 v.pop.filter(doc);
919 }
920 if let Some((l, a)) = v.pop.quiet {
921 let c = doc.clamp(doc.selection().caret);
922 if c.line != l || c.byte <= a || word_around(doc.line(l), c.byte).0 != a {
923 v.pop.quiet = None;
924 }
925 }
926 if let Some(a) = assist.as_deref_mut().filter(|_| v.sig.open) {
927 let key = (doc.revision(), doc.selection().caret);
928 if v.sig.key != key {
929 v.sig.key = key;
930 v.sig.help = a.signature(doc, key.1);
931 v.sig.open = v.sig.help.is_some();
932 }
933 }
934 let c = doc.clamp(doc.selection().caret);
938 if doc.is_hidden(c.line) {
939 if doc.selection() != v.last_sel || doc.revision() != v.last_rev {
940 doc.reveal(c.line);
941 } else {
942 let l = doc.visible_line(c.line, false);
943 doc.set_selection(TextSel::at(TextPos::new(l, doc.line(l).len())));
944 }
945 self.repass();
946 }
947 let changed = doc.revision() != rev0;
948 if changed {
949 out |= EV_ACT;
950 v.max_cols = line_sizes(doc);
951 }
952 if changed || doc.selection() != sel0 {
953 self.repass();
954 }
955 let n = doc.line_count();
956 let total_h = doc.visible_lines() as i32 * m.lh;
957 if v.find.open {
959 Ui::ce_find_update(&mut v.find, doc);
960 }
961 let wy = self.wp.ctx.wheel_y;
963 let wx = self.wp.ctx.wheel_x;
964 let over = ev & EV_HOVER != 0 || self.wp.ctx.hot == id.wrapping_add(1) || self.wp.ctx.hot == id.wrapping_add(2);
965 if over && (wy != 0 || wx != 0) {
966 let (dx, dy) = if self.mods & MOD_SHIFT != 0 { (wy, 0) } else { (wx, wy) };
967 let sy = (v.sy - dy * m.lh * 3 / 120).clamp(0, (total_h - th).max(0));
968 let sx = (v.sx - dx * m.cw * 6 / 120).clamp(0, (content_w - tw_px).max(0));
969 if sy != v.sy || sx != v.sx {
970 v.sy = sy;
971 v.sx = sx;
972 self.wp.ctx.wheel_y = 0;
973 self.wp.ctx.wheel_x = 0;
974 }
975 }
976 let (px, py) = self.pan_take(id);
978 v.sy = (v.sy + py).clamp(0, (total_h - th).max(0));
979 v.sx = (v.sx + px).clamp(0, (content_w - tw_px).max(0));
980 let caret = doc.clamp(doc.selection().caret);
981 let ccol = v.long.col(doc, caret.line, caret.byte, tw) as i32;
982 if (focused || v.find.open) && (doc.selection() != v.last_sel || doc.revision() != v.last_rev || core::mem::take(&mut v.show_caret)) {
983 let cy = doc.row_of_line(caret.line) as i32 * m.lh;
984 if cy < v.sy {
985 v.sy = cy;
986 }
987 if cy + m.lh > v.sy + th {
988 v.sy = cy + m.lh - th;
989 }
990 let cxp = ccol * m.cw;
991 if cxp < v.sx + m.cw * 2 {
992 v.sx = (cxp - m.cw * 4).max(0);
993 }
994 if cxp > v.sx + tw_px - m.cw * 2 {
995 v.sx = cxp - tw_px + m.cw * 4;
996 }
997 }
998 if let Some(l) = reveal {
1000 let ly = doc.row_of_line(l) as i32 * m.lh;
1001 if ly < v.sy || ly + m.lh > v.sy + th {
1002 v.sy = ly + m.lh / 2 - th / 2;
1003 }
1004 }
1005 v.last_sel = doc.selection();
1006 v.last_rev = doc.revision();
1007 v.sy = v.sy.clamp(0, (total_h - th).max(0));
1008 v.sx = v.sx.clamp(0, (content_w - tw_px).max(0));
1009 let first_row = (v.sy / m.lh) as usize;
1012 let rows_n = doc.visible_lines();
1013 let last_row = (((v.sy + th) / m.lh) as usize + 1).min(rows_n).max(first_row.min(rows_n));
1014 let mut rows: Vec<usize> = Vec::with_capacity(last_row.saturating_sub(first_row));
1015 if doc.hidden().is_empty() {
1016 rows.extend(first_row..last_row);
1017 } else if last_row > first_row {
1018 let mut l = doc.line_of_row(first_row);
1019 for _ in first_row..last_row {
1020 rows.push(l);
1021 l = doc.visible_line(l + 1, true);
1022 if l <= *rows.last().unwrap_or(&0) {
1023 break;
1024 }
1025 }
1026 }
1027 let first = rows.first().copied().unwrap_or(0).min(n);
1028 let last = rows.last().map_or(first, |&l| l + 1).min(n);
1029 let sy0 = v.sy;
1031 let lh0 = m.lh;
1032 let row_y = move |k: usize| ty + (first_row + k) as i32 * lh0 - sy0;
1033 let mut toks = Vec::new();
1034 Ui::ce_hl_sync(&mut v.hl, doc, h);
1035 Ui::ce_hl_upto(&mut v.hl, doc, h, last, &mut toks, &v.long, first);
1036 let bracket = if focused { Ui::ce_bracket(&mut v, doc, h) } else { None };
1037
1038 if frame {
1040 let mut sp = Sp { value: NAT_W, value2: m.lh, flags: 1, ..Sp::default() };
1041 let _ = st.textbox_metrics(self, &mut sp);
1042 sp.place(r);
1043 sp.ev = ev;
1044 sp.value = focused as i32;
1045 sp.value2 = disabled as i32;
1046 st.textbox_frame(self, &sp);
1047 }
1048 self.clip_push(inner[0], inner[1], inner[2], inner[3]);
1049 self.draw_color(theme.bg);
1050 self.draw_rect(inner[0], ty, inner[2], inner[1] + inner[3] - ty);
1051 if gw > 0 {
1052 self.draw_color(theme.gutter_bg);
1053 self.draw_rect(inner[0], ty, gw, inner[1] + inner[3] - ty);
1054 }
1055 self.ltr_begin(inner[0], inner[2]);
1056 let copy = total_h > th || hbar;
1058 if copy {
1059 self.scroll_copy_begin(id, (tx - v.sx) as i64, (ty - v.sy) as i64);
1060 }
1061 let (sa, sb) = doc.selection().range();
1062 let sel_col = if focused { theme.sel } else { theme.sel_inactive };
1063 let ital = |k: u8| theme.italic >> k & 1 != 0;
1064 let bold = |k: u8| theme.bold >> k & 1 != 0;
1065 let col0 = (v.sx / m.cw.max(1)) as usize;
1066 let cols = (tw_px / m.cw.max(1)) as usize + 2;
1067 let tx0 = tx - v.sx;
1068 let vis: Vec<(usize, usize)> = (first..last)
1071 .map(|l| {
1072 let len = doc.line(l).len();
1073 if len < COL_LONG {
1074 (0, len)
1075 } else {
1076 (v.long.floor(doc, l, col0.saturating_sub(4), tw).0, v.long.byte(doc, l, col0 + cols + 4, tw))
1077 }
1078 })
1079 .collect();
1080 let exec = doc.exec_line();
1082 let numx = inner[0] + bpw + mk + numw - self.dp(10);
1084 for (k, &l) in rows.iter().enumerate() {
1085 let y = row_y(k);
1086 if nums {
1087 let num = format!("{}", l + 1);
1088 let nw = num.len() as i32 * m.cw;
1089 let c = if l == caret.line { theme.gutter_cur } else { theme.gutter_fg };
1090 self.ce_cells(&m, numx - nw, y + m.base, num.as_bytes(), c, false, false);
1091 }
1092 if bpw > 0 {
1093 if let Some(b) = doc.breakpoint(l) {
1094 self.ce_breakpoint(b, inner[0], y, bpw, m.lh, &theme);
1095 }
1096 if exec == Some(l) {
1097 self.ce_exec_mark(inner[0], y, bpw, m.lh, &theme);
1098 }
1099 }
1100 if fw > 0 {
1101 if let Some((_, folded)) = doc.fold_at(l) {
1102 let px = (fw * 3 / 4).max(8);
1103 self.draw_color(if l == caret.line { theme.gutter_cur } else { theme.gutter_fg });
1104 self.draw_icon(if folded { IC_CHEV_RIGHT_SMALL } else { IC_CHEV_DOWN_SMALL }, inner[0] + gw - fw + (fw - px) / 2, y + (m.lh - px) / 2, px << 8);
1105 }
1106 }
1107 }
1108 let dvis: Vec<usize> = doc.mark_range(first, last).filter(|&i| last_line(&doc.marks()[i]) >= first).collect();
1111 if mk > 0 {
1112 let mut best: Vec<(usize, u8)> = Vec::new();
1113 for &i in &dvis {
1114 let d = &doc.marks()[i];
1115 match best.last_mut() {
1116 Some(b) if b.0 == d.a.line => b.1 = b.1.min(d.sev),
1117 _ if d.a.line >= first => best.push((d.a.line, d.sev)),
1118 _ => {}
1119 }
1120 }
1121 for (l, sev) in best {
1122 if doc.is_hidden(l) {
1123 continue;
1124 }
1125 let y = ty + doc.row_of_line(l) as i32 * m.lh - v.sy;
1126 self.ce_marker(sev, inner[0] + bpw, y, mk, m.lh, theme.diag[sev as usize]);
1127 }
1128 }
1129 let mut waves: Vec<(u8, i32, i32, i32)> = Vec::new();
1130 for &i in &dvis {
1131 let d = &doc.marks()[i];
1132 for l in d.a.line.max(first)..=d.b.line.min(last.max(1) - 1) {
1133 let (b0, b1) = vis[l - first];
1134 let (a, b) = (if d.a.line == l { d.a.byte } else { 0 }, if d.b.line == l { d.b.byte } else { usize::MAX });
1135 if !touches(d, l) || b < b0 || a > b1 || doc.is_hidden(l) {
1136 continue;
1137 }
1138 let (c0, c1) = mark_cols(d, l, doc.line(l).len(), |b| v.long.col(doc, l, b, tw));
1139 waves.push((d.sev, tx0 + c0 as i32 * m.cw, tx0 + c1 as i32 * m.cw, ty + doc.row_of_line(l) as i32 * m.lh - v.sy));
1140 }
1141 }
1142 let mut hov: Vec<usize> = Vec::new();
1144 let pointer_line = doc.line_of_row(((my - ty + v.sy).max(0) / m.lh.max(1)) as usize);
1145 if ev & EV_HOVER != 0 && v.drag == 0 && !dvis.is_empty() && my >= ty && my < ty + th {
1146 let l = pointer_line;
1147 let col = ((mx - tx0).max(0) / m.cw.max(1)) as usize;
1148 let on_mark = mk > 0 && mx >= inner[0] + bpw && mx < inner[0] + bpw + mk;
1149 let on_text = mx >= tx && mx < tx + tw_px;
1150 hov = dvis
1151 .iter()
1152 .copied()
1153 .filter(|&i| {
1154 let d = &doc.marks()[i];
1155 if on_mark {
1156 d.a.line == l
1157 } else {
1158 on_text && touches(d, l) && {
1159 let (c0, c1) = mark_cols(d, l, doc.line(l).len(), |b| v.long.col(doc, l, b, tw));
1160 col >= c0 && col < c1
1161 }
1162 }
1163 })
1164 .collect();
1165 hov.sort_by_key(|&i| doc.marks()[i].sev);
1166 }
1167 let area = [tx - self.dp(6), ty, inner[0] + inner[2] - tx + self.dp(6), th];
1168 self.clip_push(area[0], area[1], area[2], area[3]);
1169 for (ri, &l) in rows.iter().enumerate() {
1170 let y = row_y(ri);
1171 let t = doc.line(l);
1172 if exec == Some(l) {
1175 self.draw_color(theme.exec_line);
1176 self.draw_rect(area[0], y, area[2], m.lh);
1177 if theme.exec_line == theme.bg {
1178 self.draw_color(theme.exec_mark);
1179 self.draw_frame(area[0], y, area[2], m.lh, 0, hair);
1180 }
1181 } else if l == caret.line && flags & CE_NO_LINE == 0 && (focused || v.find.open) && sa == sb {
1182 self.draw_color(theme.line_hl);
1183 self.draw_rect(area[0], y, area[2], m.lh);
1184 }
1185 let (ls, le) = (TextPos::new(l, 0), TextPos::new(l, t.len()));
1186 let col_of = |b: usize| v.long.col(doc, l, b, tw) as i32 * m.cw;
1187 if sa < sb && sa <= le && sb >= ls {
1188 let (lo, hi) = (ls.max(sa), le.min(sb));
1189 let x0 = col_of(lo.byte);
1190 let x1 = col_of(hi.byte) + if sb > le { m.cw / 2 } else { 0 };
1191 if x1 > x0 {
1192 self.draw_color(sel_col);
1193 self.draw_rect(tx0 + x0, y, x1 - x0, m.lh);
1194 }
1195 }
1196 let (b0, b1) = vis[l - first];
1198 let ms = if v.find.open { &v.find.matches[..] } else { &[] };
1199 let k0 = ms.partition_point(|x| x.1 <= TextPos::new(l, b0));
1200 let k1 = ms.partition_point(|x| x.0 < TextPos::new(l, b1)).max(k0);
1201 for (k, &(a, b)) in ms[k0..k1].iter().enumerate() {
1202 let cur = Some(k0 + k) == v.find.current;
1203 let x0 = col_of(a.byte);
1204 let x1 = col_of(b.byte);
1205 self.draw_color(if cur { theme.find_cur } else { theme.find });
1206 if theme.find_frame {
1207 self.draw_frame(tx0 + x0, y, (x1 - x0).max(m.cw / 2), m.lh, 0, if cur { hair * 2 } else { hair });
1208 } else {
1209 self.draw_rect(tx0 + x0, y, (x1 - x0).max(m.cw / 2), m.lh);
1210 }
1211 }
1212 if let Some((p, q)) = bracket {
1213 for at in [p, q] {
1214 if at.line == l {
1215 let x0 = col_of(at.byte);
1216 self.draw_color(theme.bracket);
1217 self.draw_frame(tx0 + x0, y, m.cw, m.lh, 0, hair);
1218 }
1219 }
1220 }
1221 let long = t.len() >= COL_LONG;
1224 let (mut i, mut col) = if long { v.long.floor(doc, l, col0, tw) } else { (0, 0) };
1225 let kept;
1226 let tk: &[Token] = if long {
1227 kept = v.long.tokens(doc, l, tw, h, v.hl.states[l]).1;
1228 &kept
1229 } else {
1230 h.line(t, v.hl.states[l], &mut toks);
1231 &toks
1232 };
1233 let mut k = if long { tk.partition_point(|x| x.end <= i) } else { 0 };
1234 let mut at = i;
1235 let tb = t.as_bytes();
1236 loop {
1237 let (a, b, kind) = match tk.get(k) {
1239 Some(x) if x.start > at => (at, x.start, SY_TEXT),
1240 Some(x) => {
1241 k += 1;
1242 (x.start, x.end, x.kind)
1243 }
1244 None if at < t.len() => (at, t.len(), SY_TEXT),
1245 None => break,
1246 };
1247 at = b;
1248 if col > col0 + cols {
1249 break;
1250 }
1251 let inside = sa < sb && TextPos::new(l, a) >= sa && TextPos::new(l, b) <= sb;
1252 let color = if theme.sel_text != 0 && inside { theme.sel_text } else { theme.tokens[kind as usize % SY_COUNT] };
1253 i = i.max(a);
1255 while i < b && col <= col0 + cols {
1256 if tb[i] == b'\t' {
1257 col += tw - col % tw;
1258 i += 1;
1259 continue;
1260 }
1261 if tb[i] < 0x80 && !m.per_cell {
1262 let lim = b.min(i + (col0 + cols + 1 - col));
1264 let mut j = i;
1265 while j < lim && tb[j] < 0x80 && tb[j] != b'\t' {
1266 j += 1;
1267 }
1268 let s0 = i + col0.saturating_sub(col).min(j - i);
1270 let c0 = col + (s0 - i);
1271 let s1 = (s0 + (col0 + cols).saturating_sub(c0)).min(j);
1272 if s1 > s0 && tb[s0..s1].iter().any(|&c| c != b' ') {
1273 self.ce_cells(&m, tx0 + c0 as i32 * m.cw, y + m.base, &tb[s0..s1], color, bold(kind), ital(kind));
1274 }
1275 col += j - i;
1276 i = j;
1277 continue;
1278 }
1279 let e = segment::grapheme_next(t, i).min(b.max(i + 1));
1280 let ch = t[i..].chars().next().unwrap_or(' ');
1281 let w = char_cols(ch);
1282 if col + w > col0 && col < col0 + cols && ch != ' ' {
1283 self.ce_cells(&m, tx0 + col as i32 * m.cw, y + m.base, &tb[i..e], color, bold(kind), ital(kind));
1284 }
1285 col += w;
1286 i = e;
1287 }
1288 }
1289 if doc.fold_at(l).is_some_and(|f| f.1) {
1291 let end = v.long.col(doc, l, t.len(), tw) as i32 * m.cw;
1292 let (bx, bw) = (tx0 + end + m.cw / 2, m.cw * 3);
1293 self.draw_color(theme.line_hl);
1294 self.draw_rrect(bx, y + hair, bw, m.lh - hair * 2, self.dp(3));
1295 self.draw_color(theme.gutter_fg);
1296 self.draw_frame(bx, y + hair, bw, m.lh - hair * 2, self.dp(3), hair);
1297 self.ce_cells(&m, bx, y + m.base, b"...", theme.gutter_fg, false, false);
1298 }
1299 }
1300 waves.sort_by_key(|w| core::cmp::Reverse(w.0));
1302 let (lim0, lim1) = (tx - m.cw * 2, tx + tw_px + m.cw * 2);
1303 let period = self.dp(2).max(2) * 2;
1304 for (sev, x0, x1, y) in waves {
1305 let x0 = if x0 < lim0 { x0 + (lim0 - x0) / period * period } else { x0 };
1306 let x1 = x1.min(lim1);
1307 if x1 > x0 {
1308 self.ce_wave(x0, x1, y, &m, theme.diag[sev as usize]);
1309 }
1310 }
1311 self.clip_pop();
1312 let cx_px = tx0 + ccol * m.cw;
1314 let cy_px = ty + doc.row_of_line(caret.line) as i32 * m.lh - v.sy;
1315 if focused {
1316 let pe = self.ed_preedit();
1317 if !pe.text.is_empty() {
1318 self.ed_preedit_draw(&pe, cx_px, cy_px, m.lh, cy_px + m.base, (m.font, m.size), theme.fg, theme.bg);
1319 } else if doc.selection().is_empty() && self.ed_caret_on(v.t0) && cy_px + m.lh > ty && cy_px < ty + th && cx_px >= tx - 1 {
1320 self.draw_color(theme.caret);
1321 self.draw_rect(cx_px, cy_px, self.dp(2).max(1), m.lh);
1322 }
1323 plat::ime_inline(true); self.ime_pos(cx_px, cy_px, m.lh);
1325 }
1326 if copy {
1327 self.scroll_copy_end();
1328 }
1329 self.ltr_end();
1330 self.clip_pop();
1331 if total_h > th {
1333 let mut pos = v.sy;
1334 self.next_rect_px(inner[0] + inner[2] - sbw, ty, sbw, th);
1335 if self.scrollbar(id.wrapping_add(1), &mut pos, total_h, th, true) & EV_ACT != 0 {
1336 v.sy = pos.clamp(0, (total_h - th).max(0));
1337 }
1338 }
1339 if hbar {
1340 let mut pos = v.sx;
1341 self.next_rect_px(tx, ty + th, tw_px, sbw);
1342 if self.scrollbar(id.wrapping_add(2), &mut pos, content_w, tw_px, false) & EV_ACT != 0 {
1343 v.sx = pos.clamp(0, (content_w - tw_px).max(0));
1344 }
1345 }
1346 self.pan_view(id, hit0, super::touch::AX_ALL, [v.sx, v.sy], [(content_w - tw_px).max(0), (total_h - th).max(0)]);
1348 if v.find.open {
1350 self.ce_find_bar(id, doc, &mut v, [inner[0], inner[1], inner[2], bar_h], ro);
1351 }
1352 let big = v.max_cols.3 > LABEL_MAX;
1354 let (l0, l1) = if big { (first, last) } else { (0, n) };
1355 if v.label.0 != doc.uid() || v.label.1 != doc.revision() || v.label.2 != l0 || v.label.3 != l1 {
1356 let mut s = String::new();
1357 for (k, l) in doc.lines()[l0..l1].iter().enumerate() {
1358 if k > 0 {
1359 s.push('\n');
1360 }
1361 if s.len() + l.len() > LABEL_MAX {
1362 break;
1363 }
1364 s.push_str(l);
1365 }
1366 v.label = (doc.uid(), doc.revision(), l0, l1, s);
1367 }
1368 let rel = |p: TextPos| -> usize {
1369 if p.line < l0 || p.line >= l1 {
1370 0
1371 } else {
1372 doc.lines()[l0..p.line].iter().map(|l| l.len() + 1).sum::<usize>() + p.byte
1373 }
1374 };
1375 let (fa, fc) = if big { (rel(doc.selection().anchor), rel(doc.selection().caret)) } else { (doc.flat(doc.selection().anchor), doc.flat(doc.selection().caret)) };
1376 self.wp.ctx.last_rect = r;
1377 self.wp.ctx.last_state = if focused { SS_FOCUSED as u32 } else { 0 } | if disabled { SS_DISABLED as u32 } else { 0 };
1378 let label = core::mem::take(&mut v.label.4);
1379 {
1380 let kind = (TB_MULTI | if ro { TB_READONLY } else { 0 }) >> 24;
1381 let s = self.sem(K_TEXTBOX, id, Some(&label), if focused { fc as i32 } else { 0 });
1382 s.max = if focused { fa as i32 } else { 0 };
1383 s.min = kind as i32;
1384 }
1385 v.label.4 = label;
1386 let mut hover_text: Option<String> = None;
1388 let mut word_key = 0u64;
1389 if let Some(a) = assist.as_deref_mut() {
1390 if ev & EV_HOVER != 0 && v.drag == 0 && mx >= tx && mx < tx + tw_px && my >= ty && my < ty + th {
1391 let l = pointer_line;
1392 let col = ((mx - tx0).max(0) / m.cw.max(1)) as usize;
1393 let b = v.long.floor(doc, l, col, tw).0;
1394 let t = doc.line(l);
1395 if t[b.min(t.len())..].chars().next().is_some_and(is_word) {
1396 let (wa, _) = word_around(t, b);
1397 let key = (doc.uid(), doc.revision(), l, wa);
1398 if v.hov.key != key {
1399 v.hov.key = key;
1400 v.hov.text = a.hover(doc, TextPos::new(l, b));
1401 }
1402 hover_text = v.hov.text.clone();
1403 word_key = ((l as u64 + 1) << 24) ^ (wa as u64 + 1);
1404 }
1405 }
1406 }
1407 if ev & EV_HOVER != 0 && (!dvis.is_empty() || assist.is_some()) {
1411 let mut text = if hov.is_empty() { String::new() } else { diag_text(doc, &hov) };
1412 if let Some(s) = hover_text.as_deref().filter(|s| !s.is_empty()) {
1413 if !text.is_empty() {
1414 text.push_str("\n\n");
1415 }
1416 text.push_str(&wrap(s, 72));
1417 }
1418 if text.is_empty() {
1419 self.tooltip_part("", 0);
1420 } else {
1421 let dpart = if hov.is_empty() { 0 } else { 1 + hov[0] as u64 + if mx < tx { 1 << 40 } else { 0 } };
1423 let th = text.bytes().fold(0xCBF2_9CE4_8422_2325u64, |h, b| (h ^ b as u64).wrapping_mul(0x100_0000_01B3));
1424 let part = dpart.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ word_key.wrapping_mul(0xC2B2_AE3D_27D4_EB4F) ^ th;
1425 self.tooltip_part(&text, part.max(1));
1426 }
1427 }
1428 let at = doc.marks_at(caret);
1431 v.caret_diag = at.first().map(|&i| doc.diag_of(&doc.marks()[i]));
1432 if !at.is_empty() {
1433 let state = self.wp.ctx.last_state;
1434 let cy = (ty + doc.row_of_line(caret.line) as i32 * m.lh - v.sy).clamp(ty, (ty + th - m.lh).max(ty));
1435 self.wp.ctx.last_rect = [inner[0], cy, inner[2], m.lh];
1436 self.wp.ctx.last_state = 0;
1437 self.sem(K_TEXT, 0, Some(&diag_text(doc, &at)), doc.marks()[at[0]].sev as i32);
1438 self.wp.ctx.last_rect = r;
1439 self.wp.ctx.last_state = state;
1440 }
1441 if focused {
1442 self.ed_hold(id, fa, fc);
1443 v.held = Some((fa, fc));
1444 out |= EV_FOCUS;
1445 }
1446 if v.hl.count > 0 || doc.revision() != rev0 || doc.selection() != sel0 || (v.sx, v.sy) != view0 {
1448 v.hl_last = v.hl.count;
1449 }
1450 v.status = CodeStatus {
1451 line: caret.line + 1,
1452 col: ccol as usize + 1,
1453 matches: if v.find.open { v.find.matches.len() } else { 0 },
1454 current: if v.find.open { v.find.current.map_or(0, |c| c + 1) } else { 0 },
1455 hl_lines: v.hl_last,
1456 lines: n,
1457 diags: doc.diagnostic_counts(),
1458 diag: at.first().map(|&i| doc.marks()[i].sev),
1459 popup: if v.pop.open { v.pop.shown.len() } else { 0 },
1460 popup_sel: v.pop.sel,
1461 sig: v.sig.open,
1462 hidden: n - doc.visible_lines(),
1463 };
1464 if focused {
1466 let cy = ty + doc.row_of_line(caret.line) as i32 * m.lh - v.sy;
1467 let in_view = cy + m.lh > ty && cy < ty + th;
1468 let pf = PopFont { font: m.font, size: m.size, cw: m.cw, lh: m.lh, base: m.base };
1469 if v.pop.open && in_view {
1470 let scol = v.long.col(doc, v.pop.start.line, v.pop.start.byte, tw) as i32;
1471 if self.ca_complete_popup(id.wrapping_add(14), &mut v.pop, doc, tx0 + scol * m.cw, cy + m.lh, cy, &pf, &theme) {
1472 out |= EV_ACT;
1473 v.max_cols = line_sizes(doc);
1474 self.repass();
1475 }
1476 }
1477 if let Some(sh) = v.sig.help.as_ref().filter(|_| v.sig.open && in_view) {
1478 self.ca_signature_popup(sh, cx_px - m.cw * 2, cy, cy + m.lh, &pf, &theme);
1479 }
1480 }
1481 self.m.code.views.insert(id, v);
1482 let now = self.frame;
1483 if self.m.code.views.len() > 8 {
1484 self.m.code.views.retain(|_, v| now.wrapping_sub(v.seen) < 600);
1485 }
1486 self.wp.ctx.last_rect = r;
1487 out
1488 }
1489
1490 #[allow(clippy::too_many_arguments)]
1492 fn ce_cells(&mut self, m: &Metrics, x: i32, y: i32, s: &[u8], c: u32, bold: bool, italic: bool) {
1493 let font = if italic { m.italic } else { m.font };
1494 let passes = if bold { 2 } else { 1 };
1495 for p in 0..passes {
1496 let dx = p * ((m.size >> 8) / 16 + 1);
1497 if m.per_cell {
1498 let mut i = 0;
1499 let mut k = 0;
1500 while i < s.len() {
1501 let n = match s[i] {
1502 0..=0x7F => 1,
1503 0x80..=0xDF => 2,
1504 0xE0..=0xEF => 3,
1505 _ => 4,
1506 }
1507 .min(s.len() - i);
1508 if s[i] != b' ' {
1509 self.cmd_text(x + k * m.cw + dx, y, &s[i..i + n], font, m.size, c, 0);
1510 }
1511 i += n;
1512 k += 1;
1513 }
1514 } else {
1515 self.cmd_text(x + dx, y, s, font, m.size, c, 0);
1516 }
1517 }
1518 }
1519
1520 fn ce_wave(&mut self, x0: i32, x1: i32, y: i32, m: &Metrics, color: u32) {
1523 let hp = self.dp(2).max(2);
1524 let amp = self.dp(2).max(2);
1525 let width = self.dp(1).max(1) << 8;
1526 let top = (y + m.base + self.dp(1)).min(y + m.lh - amp - 1);
1527 let key = (hp as u32) << 16 | (amp as u32) << 8 | (width >> 8) as u32;
1528 let w = &mut self.m.code.waves;
1529 let d = match w.iter().find(|e| e.0 == color && e.1 == key) {
1530 Some(e) => e.2.clone(),
1531 None => {
1532 if w.len() >= 24 {
1533 w.clear();
1534 }
1535 let d: DrawRef = Rc::new(Wave { color, hp, amp, width });
1536 w.push((color, key, d.clone()));
1537 d
1538 }
1539 };
1540 let pad = (width >> 9) + 1;
1541 self.cmd_custom(x0 - pad, top - pad, x1 - x0 + pad * 2, amp + pad * 2, Some(d), key as usize, color);
1542 }
1543
1544 fn ce_marker(&mut self, sev: u8, x: i32, y: i32, cell: i32, lh: i32, color: u32) {
1547 let cp = match sev {
1548 DIAG_ERROR => IC_ERROR_FILL,
1549 DIAG_WARNING => IC_WARNING,
1550 _ => IC_INFO,
1551 };
1552 let px = (lh * 2 / 3).max(8);
1553 self.draw_color(color);
1554 self.draw_icon(cp, x + (cell - px) / 2, y + (lh - px) / 2, px << 8);
1555 }
1556
1557 fn ce_breakpoint(&mut self, kind: u8, x: i32, y: i32, cell: i32, lh: i32, t: &CodeTheme) {
1560 let r = (lh * 3 / 10).max(3);
1561 let (cx, cy) = ((x + cell / 2) << 8, (y + lh / 2) << 8);
1562 self.draw_color(t.breakpoint);
1563 match kind {
1564 BP_DISABLED => self.draw_circle(cx, cy, (r << 8) - 128, (self.dp(2).max(1) << 8) - 64),
1565 _ => self.draw_circle(cx, cy, r << 8, 0),
1566 }
1567 if kind == BP_COND {
1568 self.draw_color(t.bg);
1569 let bh = self.dp(2).max(2);
1570 self.draw_rect(x + cell / 2 - r + r / 3, y + lh / 2 - bh / 2, (r - r / 3) * 2, bh);
1571 }
1572 }
1573
1574 fn ce_exec_mark(&mut self, x: i32, y: i32, cell: i32, lh: i32, t: &CodeTheme) {
1576 let px = (lh * 3 / 4).max(8);
1577 self.draw_color(t.exec_mark);
1578 self.draw_icon(IC_CARET_RIGHT, x + (cell - px) / 2, y + (lh - px) / 2, px << 8);
1579 }
1580
1581 fn ce_assist_key(doc: &mut CodeDoc, v: &mut View, assist: Option<&mut (dyn CodeAssist + '_)>, k: u32, ro: bool, page: i32) -> bool {
1585 let vk = k & 0xFFFF;
1586 let mods = k >> 16;
1587 if mods & MOD_ALTGR != 0 || mods & (MOD_CTRL | MOD_ALT) == MOD_CTRL | MOD_ALT {
1588 return false;
1589 }
1590 let ctrl = mods & MOD_CTRL != 0;
1591 let shift = mods & MOD_SHIFT != 0;
1592 let alt = mods & MOD_ALT != 0;
1593 let caret = doc.selection().caret;
1594 if v.pop.open && !ctrl && !alt {
1595 let page = (POP_ROWS as isize - 1).min(page.max(1) as isize);
1596 match vk {
1597 VK_UP if !shift => v.pop.step(-1),
1598 VK_DOWN if !shift => v.pop.step(1),
1599 VK_PRIOR if !shift => v.pop.step(-page),
1600 VK_NEXT if !shift => v.pop.step(page),
1601 VK_RETURN if !shift && v.pop.typed_whole() => {
1603 v.pop.close();
1604 return false;
1605 }
1606 VK_RETURN | VK_TAB if !shift && !ro => {
1607 v.pop.accept(doc);
1608 v.prefx = -1;
1609 }
1610 VK_ESCAPE if !shift => {
1611 v.pop.close();
1612 let (a, _) = word_around(doc.line(caret.line), caret.byte);
1613 v.pop.quiet = Some((caret.line, a));
1614 }
1615 _ => return false,
1616 }
1617 return true;
1618 }
1619 if v.sig.open && vk == VK_ESCAPE && mods == 0 {
1620 v.sig.open = false;
1621 return true;
1622 }
1623 match vk {
1624 VK_SPACE if ctrl && !alt => {
1625 let Some(a) = assist else { return false };
1626 v.show_caret = true;
1627 if shift {
1628 v.sig.key = (doc.revision(), caret);
1629 v.sig.help = a.signature(doc, caret);
1630 v.sig.open = v.sig.help.is_some();
1631 } else if !ro {
1632 v.pop.request(doc, a, true);
1633 }
1634 true
1635 }
1636 VK_F12 if mods == 0 => {
1637 v.events.push(CodeEv::Definition(caret));
1638 true
1639 }
1640 VK_F2 if mods == 0 && !ro => {
1641 v.events.push(CodeEv::Rename(caret));
1642 true
1643 }
1644 0x46 if shift && alt && !ctrl && !ro => {
1646 v.events.push(CodeEv::Format);
1647 true
1648 }
1649 VK_OEM_4 | VK_OEM_6 if ctrl && shift && !alt => {
1650 let l = doc.clamp(caret).line;
1651 if vk == VK_OEM_4 {
1652 let r = doc.fold_ranges().into_iter().filter(|r| r.0 <= l && l <= r.1 && !r.2).max_by_key(|r| r.0);
1654 if let Some(r) = r {
1655 doc.set_folded(r.0, true);
1656 doc.set_selection(TextSel::at(TextPos::new(r.0, doc.line(r.0).len())));
1657 }
1658 } else if !doc.set_folded(l, false) {
1659 let r = doc.fold_ranges().into_iter().filter(|r| r.0 <= l && l <= r.1 && r.2).max_by_key(|r| r.0);
1660 if let Some(r) = r {
1661 doc.set_folded(r.0, false);
1662 }
1663 }
1664 true
1665 }
1666 _ => false,
1667 }
1668 }
1669
1670 fn ce_assist_typed(doc: &mut CodeDoc, v: &mut View, a: &mut (dyn CodeAssist + '_), s: &str) {
1673 let Some(c) = s.chars().next_back() else { return };
1674 let caret = doc.selection().caret;
1675 if v.pop.open {
1676 if !is_word(c) {
1677 v.pop.close();
1678 if a.triggers().contains(c) {
1679 v.pop.request(doc, a, false);
1680 }
1681 }
1682 } else if a.triggers().contains(c) {
1683 v.pop.request(doc, a, false);
1684 } else if is_word(c) {
1685 let (w0, _) = word_around(doc.line(caret.line), caret.byte);
1686 if v.pop.quiet != Some((caret.line, w0)) {
1687 v.pop.request(doc, a, false);
1688 }
1689 }
1690 if c == '(' || c == ',' || (c == ')' && v.sig.open) {
1691 v.sig.key = (doc.revision(), caret);
1692 v.sig.help = a.signature(doc, caret);
1693 v.sig.open = v.sig.help.is_some();
1694 }
1695 }
1696
1697 #[allow(clippy::too_many_arguments)]
1699 fn ce_key(&mut self, id: u32, doc: &mut CodeDoc, v: &mut View, h: &Highlighter, k: u32, flags: u32, m: &Metrics) -> bool {
1700 let ro = flags & CE_READONLY != 0;
1701 let vk = k & 0xFFFF;
1702 let mods = k >> 16;
1703 let altgr = mods & MOD_ALTGR != 0 || mods & (MOD_CTRL | MOD_ALT) == MOD_CTRL | MOD_ALT;
1704 if altgr {
1705 return false;
1706 }
1707 let ctrl = mods & MOD_CTRL != 0;
1708 let shift = mods & MOD_SHIFT != 0;
1709 let alt = mods & MOD_ALT != 0;
1710 let sel = doc.selection();
1711 if alt {
1712 return match vk {
1713 VK_UP | VK_DOWN if !ro && !ctrl && !shift => {
1714 doc.move_lines(if vk == VK_UP { -1 } else { 1 });
1715 true
1716 }
1717 _ => false,
1718 };
1719 }
1720 if ctrl {
1721 return match (vk, shift) {
1722 (0x46, false) | (0x48, false) if flags & CE_NO_FIND == 0 => {
1723 let q = if sel.is_empty() || sel.start().line != sel.end().line { v.find.query.clone() } else { doc.selected_text() };
1724 v.find.open = true;
1725 v.find.replace = vk == 0x48 && !ro;
1726 v.find.query = q;
1727 v.find.from = sel.start();
1728 v.find.key.2 = u64::MAX;
1729 self.focus(id.wrapping_add(3));
1730 true
1731 }
1732 (VK_OEM_2, false) | (VK_DIVIDE, false) => {
1733 if !ro {
1734 doc.toggle_comment(h.grammar().comment);
1735 }
1736 true
1737 }
1738 (VK_UP, false) | (VK_DOWN, false) => {
1739 v.sy += if vk == VK_UP { -m.lh } else { m.lh };
1740 v.sy = v.sy.max(0);
1741 true
1742 }
1743 _ => false,
1744 };
1745 }
1746 match vk {
1747 VK_TAB if !ro => {
1749 if shift {
1750 doc.back_tab();
1751 } else {
1752 doc.tab();
1753 }
1754 true
1755 }
1756 VK_F3 => {
1757 self.ce_find_step(doc, v, shift);
1758 true
1759 }
1760 VK_F8 if doc.diagnostic_count() > 0 => {
1762 if let Some(p) = doc.next_diagnostic(sel.caret, shift) {
1763 doc.set_selection(TextSel::at(p));
1764 }
1765 v.prefx = -1;
1766 true
1767 }
1768 VK_HOME => {
1769 let t = doc.line(sel.caret.line);
1771 let ind = t.len() - t.trim_start_matches([' ', '\t']).len();
1772 let to = if sel.caret.byte == ind { 0 } else { ind };
1773 let p = TextPos::new(sel.caret.line, to);
1774 doc.set_selection(if shift { TextSel::new(sel.anchor, p) } else { TextSel::at(p) });
1775 v.prefx = -1;
1776 true
1777 }
1778 VK_ESCAPE => {
1779 if v.find.open {
1780 v.find.open = false;
1781 } else {
1782 self.wp.ctx.focus = 0;
1783 }
1784 self.repass();
1785 true
1786 }
1787 _ => false,
1788 }
1789 }
1790
1791 fn ce_copy(&mut self, doc: &mut CodeDoc, cut: bool, ro: bool) {
1793 let sel = doc.selection();
1794 if sel.is_empty() {
1795 let l = sel.caret.line;
1796 let mut s = String::from(doc.line(l));
1797 s.push('\n');
1798 self.clip_set(CLIP_TEXT, s.as_bytes());
1799 if cut && !ro {
1800 let (a, b) = if l + 1 < doc.line_count() {
1801 (TextPos::new(l, 0), TextPos::new(l + 1, 0))
1802 } else if l > 0 {
1803 (TextPos::new(l - 1, doc.line(l - 1).len()), TextPos::new(l, doc.line(l).len()))
1804 } else {
1805 (TextPos::new(0, 0), TextPos::new(0, doc.line(0).len()))
1806 };
1807 doc.delete(a, b);
1808 }
1809 return;
1810 }
1811 let s = doc.selected_text();
1812 self.clip_set(CLIP_TEXT, s.as_bytes());
1813 if cut {
1814 doc.delete(sel.start(), sel.end());
1815 }
1816 }
1817
1818 fn ce_find_step(&mut self, doc: &mut CodeDoc, v: &mut View, back: bool) {
1820 if v.find.query.is_empty() {
1821 return;
1822 }
1823 Ui::ce_find_update(&mut v.find, doc);
1824 let sel = doc.selection();
1825 let ms = &v.find.matches;
1826 if ms.is_empty() {
1827 v.find.current = None;
1828 return;
1829 }
1830 let mut i = if back {
1831 let k = ms.partition_point(|x| x.0 < sel.start());
1832 if k == 0 { ms.len() - 1 } else { k - 1 }
1833 } else {
1834 let k = ms.partition_point(|x| x.0 < sel.end() || (x.0 == sel.start() && x.1 == sel.end() && !sel.is_empty()));
1835 if k >= ms.len() { 0 } else { k }
1836 };
1837 let n = ms.len();
1839 for _ in 0..n {
1840 if !doc.is_hidden(ms[i].0.line) {
1841 break;
1842 }
1843 i = if back { (i + n - 1) % n } else { (i + 1) % n };
1844 }
1845 if doc.is_hidden(ms[i].0.line) {
1846 v.find.current = None;
1847 return;
1848 }
1849 v.find.current = Some(i);
1850 doc.set_selection(TextSel::new(ms[i].0, ms[i].1));
1851 v.find.from = ms[i].0;
1852 self.repass();
1853 }
1854
1855 fn ce_find_bar(&mut self, id: u32, doc: &mut CodeDoc, v: &mut View, r: [i32; 4], ro: bool) {
1857 let ids = |k: u32| id.wrapping_add(k);
1858 self.draw_color(self.color(T_LAYER) | 0xFF00_0000);
1859 self.draw_rect(r[0], r[1], r[2], r[3]);
1860 self.draw_color(self.color(T_DIVIDER));
1861 self.draw_rect(r[0], r[1] + r[3] - self.hair(), r[2], self.hair());
1862 let pad = self.dp(6);
1863 let row_h = self.dp(32);
1864 let bw = self.dp(32);
1865 let (fq, fr) = (ids(3), ids(4));
1866 let in_bar = self.wp.ctx.focus == fq || self.wp.ctx.focus == fr;
1868 if in_bar {
1869 if self.ed_key_chord(VK_ESCAPE, 0) {
1870 v.find.open = false;
1871 self.ed_focus_quiet(id);
1872 return;
1873 }
1874 if self.ed_key_chord(VK_F3, 0) {
1875 self.ce_find_step(doc, v, false);
1876 }
1877 if self.ed_key_chord(VK_F3, MOD_SHIFT) {
1878 self.ce_find_step(doc, v, true);
1879 }
1880 }
1881 let shift_enter = self.wp.ctx.focus == fq && self.ed_key_chord(VK_RETURN, MOD_SHIFT);
1882 let x0 = r[0] + pad;
1883 let y0 = r[1] + (self.dp(40) - row_h) / 2;
1884 let count_w = self.dp(90);
1885 let fw = (r[2] - pad * 3 - bw * 5 - count_w).max(self.dp(80));
1886 self.next_rect_px(x0, y0, fw, row_h);
1887 let before = v.find.query.clone();
1888 let enter = self.wp.ctx.focus == fq && self.ed_key_chord(VK_RETURN, 0);
1890 self.textbox(fq, Some(tr("Suchen")), &mut v.find.query, 1024);
1891 if v.find.query != before {
1892 Ui::ce_find_update(&mut v.find, doc);
1894 let from = v.find.from;
1895 let ms = &v.find.matches;
1896 let k = ms.partition_point(|x| x.0 < from);
1897 let n = ms.len();
1899 let k = (0..n).map(|j| (k + j) % n).find(|&j| !doc.is_hidden(ms[j].0.line));
1900 v.find.current = k;
1901 if let Some(i) = v.find.current {
1902 doc.set_selection(TextSel::new(ms[i].0, ms[i].1));
1903 }
1904 self.repass();
1905 }
1906 if shift_enter {
1907 self.ce_find_step(doc, v, true);
1908 } else if enter {
1909 self.ce_find_step(doc, v, false);
1910 }
1911 let mut x = x0 + fw + pad;
1912 let tool = |ui: &mut Ui, kid: u32, x: i32, label: &str, tip: &str, on: bool| -> bool {
1913 ui.next_rect_px(x, y0, bw, row_h);
1914 ui.next_flags(ST_SUBTLE | if on { ST_CHECKED } else { 0 });
1915 let e = ui.button(kid, label);
1916 ui.tooltip(tip);
1917 e & EV_ACT != 0
1918 };
1919 if tool(self, ids(5), x, "Aa", tr("Groß-/Kleinschreibung"), v.find.case != 0) {
1920 v.find.case ^= 1;
1921 self.repass();
1922 }
1923 x += bw;
1924 if tool(self, ids(6), x, "W", tr("Ganzes Wort"), v.find.word != 0) {
1925 v.find.word ^= 1;
1926 self.repass();
1927 }
1928 x += bw;
1929 self.next_rect_px(x, y0, bw, row_h);
1930 if self.icon_button(ids(7), IC_CHEV_UP, tr("Vorheriger Treffer")) & EV_ACT != 0 {
1931 self.ce_find_step(doc, v, true);
1932 }
1933 x += bw;
1934 self.next_rect_px(x, y0, bw, row_h);
1935 if self.icon_button(ids(8), IC_CHEV_DOWN, tr("Nächster Treffer")) & EV_ACT != 0 {
1936 self.ce_find_step(doc, v, false);
1937 }
1938 x += bw;
1939 Ui::ce_find_update(&mut v.find, doc);
1940 let sel = doc.selection();
1942 v.find.current = v.find.matches.binary_search_by(|m| m.0.cmp(&sel.start()).then(m.1.cmp(&sel.end()))).ok();
1943 let count = if v.find.query.is_empty() {
1944 String::new()
1945 } else if v.find.matches.is_empty() {
1946 String::from(tr("Keine Treffer"))
1947 } else {
1948 let more = if v.find.matches.len() >= FIND_MAX { "+" } else { "" };
1949 format!("{}/{}{}", v.find.current.map_or(String::from("?"), |c| format!("{}", c + 1)), v.find.matches.len(), more)
1950 };
1951 self.next_rect_px(x + pad / 2, y0, count_w, row_h);
1952 self.ts_font(TS_CAPTION);
1953 let tc = self.color(T_TEXT2);
1954 let (f, s) = self.text.get_font();
1955 let base = self.ts_base(TS_CAPTION);
1956 let lr = self.place(count_w, row_h);
1957 self.cmd_text(lr[0], lr[1] + (row_h - self.ts_line(TS_CAPTION)) / 2 + base, count.as_bytes(), f, s, tc, count_w);
1958 self.wp.ctx.last_state = 0;
1959 self.sem(K_TEXT, 0, Some(&count), 0);
1960 x += count_w;
1961 self.next_rect_px(x, y0, bw, row_h);
1962 if self.icon_button(ids(9), IC_CANCEL, tr("Schließen")) & EV_ACT != 0 {
1963 v.find.open = false;
1964 self.ed_focus_quiet(id);
1965 }
1966 if v.find.replace && !ro {
1968 let y1 = y0 + self.dp(36);
1969 self.next_rect_px(x0, y1, fw, row_h);
1970 let renter = self.wp.ctx.focus == fr && self.ed_key_chord(VK_RETURN, 0);
1971 self.textbox(fr, Some(tr("Ersetzen")), &mut v.find.rep, 1024);
1972 let mut xr = x0 + fw + pad;
1973 let q = v.find.query.clone();
1974 let rep = v.find.rep.clone();
1975 let opts = v.find.opts();
1976 self.next_rect_px(xr, y1, self.dp(96), row_h);
1977 if self.button(ids(10), tr("Ersetzen")) & EV_ACT != 0 || renter {
1978 doc.replace_next(&q, &rep, opts);
1979 self.repass();
1980 }
1981 xr += self.dp(100);
1982 self.next_rect_px(xr, y1, self.dp(110), row_h);
1983 if self.button(ids(11), tr("Alle ersetzen")) & EV_ACT != 0 {
1984 doc.replace_all(&q, &rep, opts);
1985 }
1986 }
1987 }
1988}
1989
1990fn skip_hidden(view: &CView, c0: TextPos, c: TextPos, vk: u32, prefx: i32) -> TextPos {
1994 let doc = view.doc;
1995 if !doc.is_hidden(c.line) {
1996 return c;
1997 }
1998 let l = doc.visible_line(c.line, c > c0);
1999 match vk {
2000 VK_LEFT => TextPos::new(l, doc.line(l).len()),
2001 VK_RIGHT => TextPos::new(l, 0),
2002 _ => TextPos::new(l, view.hit_x(l, 0, prefx.max(0))),
2003 }
2004}
2005
2006fn soft_back(doc: &CodeDoc, a: TextPos, b: TextPos) -> (TextPos, TextPos) {
2008 if a.line != b.line || b.byte == 0 {
2009 return (a, b);
2010 }
2011 let t = doc.line(b.line);
2012 if !t[..b.byte].bytes().all(|c| c == b' ') {
2013 return (a, b);
2014 }
2015 let tw = doc.tab_width.clamp(1, 16) as usize;
2016 let col = b.byte;
2017 let target = (col - 1) / tw * tw;
2018 (TextPos::new(b.line, target), b)
2019}
2020
2021fn last_line(d: &Mark) -> usize {
2024 if d.b.byte == 0 && d.b.line > d.a.line { d.b.line - 1 } else { d.b.line }
2025}
2026
2027fn touches(d: &Mark, l: usize) -> bool {
2029 d.a.line <= l && l <= last_line(d)
2030}
2031
2032fn mark_cols(d: &Mark, l: usize, len: usize, col: impl Fn(usize) -> usize) -> (usize, usize) {
2035 let c0 = if d.a.line == l { col(d.a.byte) } else { 0 };
2036 let c1 = if d.b.line == l { col(d.b.byte) } else { col(len) + 1 };
2037 (c0, c1.max(c0 + 1))
2038}
2039
2040fn diag_text(doc: &CodeDoc, ix: &[usize]) -> String {
2042 let mut s = String::new();
2043 for (k, &i) in ix.iter().enumerate() {
2044 let d = &doc.marks()[i];
2045 if k > 0 {
2046 s.push_str(" · ");
2047 }
2048 s.push_str(tr(["Fehler", "Warnung", "Hinweis"][d.sev.min(DIAG_INFO) as usize]));
2049 s.push_str(": ");
2050 s.push_str(&d.msg);
2051 }
2052 s
2053}
2054
2055fn doc_find_range(doc: &CodeDoc, q: &str, opts: u8, first: usize, n: usize) -> Vec<(TextPos, TextPos)> {
2057 doc.find_lines(q, opts, first, n)
2058}
2059
2060fn line_sizes(doc: &CodeDoc) -> (u32, u64, usize, usize) {
2062 let (mut w, mut t) = (0, 0);
2063 for l in doc.lines() {
2064 w = w.max(l.len());
2065 t += l.len() + 1;
2066 }
2067 (doc.uid(), doc.revision(), w + 1, t)
2068}