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