1use alloc::boxed::Box;
50use alloc::string::String;
51use alloc::vec::Vec;
52use core::ops::Range;
53
54use text::segment;
55
56use crate::ed_core::{TextPos, TextSel};
57use crate::history::{Edit, History};
58use crate::i18n::tr;
59use crate::rich_doc::lines_of;
60
61pub const FIND_CASE: u8 = 1;
63pub const FIND_WORD: u8 = 2;
65pub const FIND_MAX: usize = 100_000;
67
68pub(crate) const CK_NONE: u8 = 0;
70pub(crate) const CK_TYPE: u8 = 1;
71pub(crate) const CK_BACK: u8 = 2;
72pub(crate) const CK_DEL: u8 = 3;
73
74#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct LineChange {
77 pub rev: u64,
78 pub first: usize,
79 pub old: usize,
80 pub new: usize,
81}
82
83const JOURNAL_MAX: usize = 256;
84
85pub const DIAG_ERROR: u8 = 0;
87pub const DIAG_WARNING: u8 = 1;
89pub const DIAG_INFO: u8 = 2;
91
92#[derive(Clone, Debug, Default, PartialEq, Eq)]
97pub struct Diag {
98 pub line: usize,
100 pub col: usize,
101 pub end_line: usize,
103 pub end_col: usize,
104 pub severity: u8,
106 pub msg: String,
107}
108
109impl Diag {
110 pub fn new(line: usize, col: usize, end_col: usize, severity: u8, msg: &str) -> Diag {
112 Diag { line, col, end_line: line, end_col, severity, msg: String::from(msg) }
113 }
114
115 pub fn span(line: usize, col: usize, end_line: usize, end_col: usize, severity: u8, msg: &str) -> Diag {
117 Diag { line, col, end_line, end_col, severity, msg: String::from(msg) }
118 }
119}
120
121#[derive(Clone, Debug)]
123pub(crate) struct Mark {
124 pub(crate) a: TextPos,
125 pub(crate) b: TextPos,
126 pub(crate) sev: u8,
127 pub(crate) msg: String,
128}
129
130pub const BP_ON: u8 = 0;
132pub const BP_DISABLED: u8 = 1;
134pub const BP_COND: u8 = 2;
136
137#[derive(Clone, Debug, Default, PartialEq, Eq)]
139pub struct TextEdit {
140 pub a: TextPos,
141 pub b: TextPos,
142 pub text: String,
143}
144
145impl TextEdit {
146 pub fn new(a: TextPos, b: TextPos, text: &str) -> TextEdit {
147 TextEdit { a, b, text: String::from(text) }
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq)]
153struct FoldRange {
154 first: usize,
155 last: usize,
156 folded: bool,
157}
158
159pub(crate) struct CodeBody {
162 lines: Vec<String>,
163 sel: TextSel,
164 rev: u64,
165 journal: Vec<LineChange>,
166 marks: Vec<Mark>,
168 span: usize,
169 counts: [usize; 3],
170 bps: Vec<(usize, u8)>,
172 exec: Option<usize>,
173 folds: Vec<FoldRange>,
175 hidden: Vec<(usize, usize)>,
176 hidden_n: usize,
177}
178
179impl CodeBody {
180 fn splice(&mut self, first: usize, old: usize, new: &[String]) {
181 if !self.marks.is_empty() || !self.bps.is_empty() || self.exec.is_some() || !self.folds.is_empty() {
182 self.follow(first, old, new);
183 }
184 self.lines.splice(first..first + old, new.iter().cloned());
185 if self.lines.is_empty() {
186 self.lines.push(String::new());
187 }
188 self.rev += 1;
189 if self.journal.len() >= JOURNAL_MAX {
190 self.journal.drain(..JOURNAL_MAX / 2);
191 }
192 self.journal.push(LineChange { rev: self.rev, first, old, new: new.len() });
193 }
194
195 fn follow(&mut self, first: usize, old: usize, new: &[String]) {
198 let o = &self.lines[first..first + old];
199 let c = o.len();
200 let f = if o.is_empty() || new.is_empty() {
201 None
202 } else if c == new.len() && c > 1 && new[..c - 1] == o[1..] && new[c - 1] == o[0] {
203 Some(Follow::Turn(false))
204 } else if c == new.len() && c > 1 && new[1..] == o[..c - 1] && new[0] == o[c - 1] {
205 Some(Follow::Turn(true))
206 } else if c == new.len() {
207 let each = o.iter().zip(new).map(|(a, b)| (a != b).then(|| moved(core::slice::from_ref(a), core::slice::from_ref(b))));
208 Some(Follow::Lines(each.collect()))
209 } else {
210 Some(Follow::One(moved(o, new)))
211 };
212 let d = new.len() as isize - c as isize;
213 let marks = core::mem::take(&mut self.marks);
214 self.marks = marks
215 .into_iter()
216 .filter_map(|mut m| {
217 let point = m.a == m.b;
218 match &f {
219 Some(f) => {
220 m.a = f.pos(first, c, m.a, if point { AT_POINT } else { AT_START });
221 m.b = if point { m.a } else { f.pos(first, c, m.b, AT_END) };
222 }
223 None => {
224 if [m.a, m.b].iter().any(|p| p.line >= first && p.line < first + c) {
226 return None;
227 }
228 for p in [&mut m.a, &mut m.b] {
229 if p.line >= first + c {
230 p.line = (p.line as isize + d) as usize;
231 }
232 }
233 }
234 }
235 (point || m.a < m.b).then_some(m)
236 })
237 .collect();
238 self.marks.sort_by(|x, y| (x.a, x.b).cmp(&(y.a, y.b)));
239 self.recount();
240 let line_of = |l: usize| -> Option<usize> {
243 match &f {
244 Some(f) => Some(f.pos(first, c, TextPos::new(l, 0), AT_POINT).line),
245 None if l >= first + c => Some((l as isize + d) as usize),
246 None if l >= first => None,
247 None => Some(l),
248 }
249 };
250 if !self.bps.is_empty() {
251 let mut v: Vec<(usize, u8)> = self.bps.iter().filter_map(|&(l, k)| line_of(l).map(|l| (l, k))).collect();
252 v.sort_by_key(|x| x.0);
253 v.dedup_by_key(|x| x.0);
254 self.bps = v;
255 }
256 self.exec = self.exec.and_then(line_of);
257 if !self.folds.is_empty() {
258 let folds = core::mem::take(&mut self.folds);
260 self.folds = folds
261 .into_iter()
262 .filter_map(|r| {
263 let inside = first + c > r.first + 1 && first <= r.last;
264 let (a, z) = (line_of(r.first)?, line_of(r.last)?);
265 (z > a).then_some(FoldRange { first: a, last: z, folded: r.folded && !inside })
266 })
267 .collect();
268 self.folds.sort_by_key(|r| r.first);
269 self.folds.dedup_by_key(|r| r.first);
270 self.rehide();
271 }
272 }
273
274 fn rehide(&mut self) {
276 self.hidden.clear();
277 for r in self.folds.iter().filter(|r| r.folded) {
278 let (a, z) = (r.first + 1, r.last.min(self.lines.len().saturating_sub(1)));
279 if z < a {
280 continue;
281 }
282 match self.hidden.last_mut() {
283 Some(h) if a <= h.1 + 1 => h.1 = h.1.max(z),
284 _ => self.hidden.push((a, z)),
285 }
286 }
287 self.hidden_n = self.hidden.iter().map(|h| h.1 - h.0 + 1).sum();
288 }
289
290 fn recount(&mut self) {
292 self.counts = [0; 3];
293 self.span = 0;
294 for m in &self.marks {
295 self.counts[m.sev.min(DIAG_INFO) as usize] += 1;
296 self.span = self.span.max(m.b.line - m.a.line);
297 }
298 }
299
300 fn mark_range(&self, first: usize, last: usize) -> Range<usize> {
302 let lo = self.marks.partition_point(|m| m.a.line + self.span < first);
303 let hi = self.marks.partition_point(|m| m.a.line < last);
304 lo..hi.max(lo)
305 }
306}
307
308const AT_START: u8 = 0;
310const AT_END: u8 = 1;
311const AT_POINT: u8 = 2;
312
313enum Follow {
316 One(Moved),
318 Lines(Vec<Option<Moved>>),
320 Turn(bool),
322}
323
324impl Follow {
325 fn pos(&self, first: usize, c: usize, q: TextPos, at: u8) -> TextPos {
327 if q.line < first {
328 return q;
329 }
330 let k = q.line - first;
331 match self {
332 Follow::One(m) => {
333 let r = m.at(TextPos::new(k, q.byte), at);
334 TextPos::new(r.line + first, r.byte)
335 }
336 Follow::Lines(v) => match v.get(k) {
337 Some(Some(m)) => TextPos::new(q.line, m.at(TextPos::new(0, q.byte), at).byte),
338 _ => q,
339 },
340 Follow::Turn(down) if k < c => TextPos::new(first + if *down { (k + 1) % c } else { (k + c - 1) % c }, q.byte),
341 Follow::Turn(_) => q,
342 }
343 }
344}
345
346#[derive(Clone, Copy)]
348struct Moved {
349 p: TextPos,
350 os: TextPos,
351 ns: TextPos,
352}
353
354impl Moved {
355 fn at(&self, q: TextPos, at: u8) -> TextPos {
358 match at {
359 AT_END if q <= self.p => q,
360 AT_END if q <= self.os => self.p,
361 _ if q < self.p => q,
362 AT_START if q < self.os => self.ns,
363 AT_POINT if q < self.os => self.p,
364 _ => self.shift(q),
365 }
366 }
367
368 fn shift(&self, q: TextPos) -> TextPos {
370 if q.line == self.os.line {
371 TextPos::new(self.ns.line, self.ns.byte + (q.byte - self.os.byte))
372 } else {
373 TextPos::new(q.line - self.os.line + self.ns.line, q.byte)
374 }
375 }
376}
377
378fn moved(o: &[String], n: &[String]) -> Moved {
381 let m = o.len().min(n.len());
382 let mut k = 0;
383 while k < m && o[k] == n[k] {
384 k += 1;
385 }
386 let p = if k < m {
387 TextPos::new(k, common_prefix(&o[k], &n[k]))
388 } else if k > 0 {
389 TextPos::new(k - 1, o[k - 1].len())
390 } else {
391 TextPos::new(0, 0)
392 };
393 let (mut ol, mut nl) = (o.len() - 1, n.len() - 1);
394 while ol > p.line && nl > p.line && o[ol] == n[nl] {
395 ol -= 1;
396 nl -= 1;
397 }
398 let lo = if ol == p.line { p.byte } else { 0 };
399 let ln = if nl == p.line { p.byte } else { 0 };
400 let s = common_suffix(&o[ol], &n[nl], (o[ol].len() - lo).min(n[nl].len() - ln));
401 Moved { p, os: TextPos::new(ol, o[ol].len() - s), ns: TextPos::new(nl, n[nl].len() - s) }
402}
403
404fn common_prefix(a: &str, b: &str) -> usize {
406 let mut k = a.bytes().zip(b.bytes()).take_while(|(x, y)| x == y).count();
407 while !(a.is_char_boundary(k) && b.is_char_boundary(k)) {
408 k -= 1;
409 }
410 k
411}
412
413fn common_suffix(a: &str, b: &str, max: usize) -> usize {
415 let mut k = a.bytes().rev().zip(b.bytes().rev()).take(max).take_while(|(x, y)| x == y).count();
416 while !(a.is_char_boundary(a.len() - k) && b.is_char_boundary(b.len() - k)) {
417 k -= 1;
418 }
419 k
420}
421
422fn byte_of_char(t: &str, c: usize) -> usize {
424 t.char_indices().nth(c).map_or(t.len(), |(i, _)| i)
425}
426
427fn pos_in(lines: &[String], p: TextPos) -> TextPos {
429 let Some(last) = lines.len().checked_sub(1) else { return TextPos::new(0, 0) };
430 let line = p.line.min(last);
431 let t = &lines[line];
432 let mut b = p.byte.min(t.len());
433 while !t.is_char_boundary(b) {
434 b -= 1;
435 }
436 TextPos::new(line, b)
437}
438
439fn sel_in(lines: &[String], s: TextSel) -> TextSel {
441 TextSel::new(pos_in(lines, s.anchor), pos_in(lines, s.caret))
442}
443
444struct LineEdit {
445 first: usize,
446 old: Vec<String>,
447 new: Vec<String>,
448 sel0: TextSel,
449 sel1: TextSel,
450 label: String,
451 kind: u8,
452}
453
454impl Edit<CodeBody> for LineEdit {
455 fn apply(&mut self, s: &mut CodeBody) {
459 s.splice(self.first, self.old.len(), &self.new);
460 self.sel1 = sel_in(&s.lines, self.sel1);
461 s.sel = self.sel1;
462 }
463 fn revert(&mut self, s: &mut CodeBody) {
464 s.splice(self.first, self.new.len(), &self.old);
465 self.sel0 = sel_in(&s.lines, self.sel0);
466 s.sel = self.sel0;
467 }
468 fn label(&self) -> &str {
469 &self.label
470 }
471 fn merge(&mut self, next: &mut dyn Edit<CodeBody>) -> bool {
472 if self.kind == CK_NONE {
473 return false;
474 }
475 match next.value().and_then(|v| v.downcast_ref::<LineEdit>()) {
476 Some(n) if n.kind == self.kind && n.first == self.first && n.sel0 == self.sel1 && n.old == self.new => {
477 self.new = n.new.clone();
478 self.sel1 = n.sel1;
479 true
480 }
481 _ => false,
482 }
483 }
484 fn value(&self) -> Option<&dyn core::any::Any> {
485 Some(self)
486 }
487}
488
489pub struct CodeDoc {
491 body: CodeBody,
492 hist: History<CodeBody>,
493 uid: u32,
495 pub tab_width: u8,
497 pub use_tabs: bool,
499}
500
501impl Default for CodeDoc {
502 fn default() -> CodeDoc {
503 CodeDoc::new()
504 }
505}
506
507pub(crate) fn is_word(c: char) -> bool {
509 c.is_alphanumeric() || c == '_'
510}
511
512pub(crate) fn fold(c: char) -> char {
514 if c.is_ascii() {
515 c.to_ascii_lowercase()
516 } else {
517 c.to_lowercase().next().unwrap_or(c)
518 }
519}
520
521pub(crate) fn char_cols(c: char) -> usize {
524 if segment::char_props(c) & (1 << 16) != 0 { 2 } else { 1 }
526}
527
528impl CodeDoc {
529 pub fn new() -> CodeDoc {
531 CodeDoc::from_lines(Vec::new())
532 }
533
534 pub fn from_text(s: &str) -> CodeDoc {
536 CodeDoc::from_lines(lines_of(s).map(String::from).collect())
537 }
538
539 pub fn from_lines(mut lines: Vec<String>) -> CodeDoc {
541 if lines.is_empty() {
542 lines.push(String::new());
543 }
544 for l in lines.iter_mut() {
545 if l.contains(['\n', '\r']) {
546 *l = l.replace(['\n', '\r'], "");
547 }
548 }
549 static NEXT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
550 let uid = NEXT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
551 let body = CodeBody {
552 lines,
553 sel: TextSel::default(),
554 rev: 0,
555 journal: Vec::new(),
556 marks: Vec::new(),
557 span: 0,
558 counts: [0; 3],
559 bps: Vec::new(),
560 exec: None,
561 folds: Vec::new(),
562 hidden: Vec::new(),
563 hidden_n: 0,
564 };
565 CodeDoc { body, hist: History::new(1000), uid, tab_width: 4, use_tabs: false }
566 }
567
568 pub fn text(&self) -> String {
570 let n: usize = self.body.lines.iter().map(|l| l.len() + 1).sum();
571 let mut s = String::with_capacity(n);
572 for (i, l) in self.body.lines.iter().enumerate() {
573 if i > 0 {
574 s.push('\n');
575 }
576 s.push_str(l);
577 }
578 s
579 }
580
581 pub fn line_count(&self) -> usize {
583 self.body.lines.len()
584 }
585
586 pub fn line(&self, i: usize) -> &str {
588 &self.body.lines[i.min(self.body.lines.len() - 1)]
589 }
590
591 pub fn lines(&self) -> &[String] {
593 &self.body.lines
594 }
595
596 pub fn revision(&self) -> u64 {
598 self.body.rev
599 }
600
601 pub(crate) fn uid(&self) -> u32 {
603 self.uid
604 }
605
606 pub fn changes_since(&self, rev: u64) -> Option<&[LineChange]> {
609 if rev == self.body.rev {
610 return Some(&[]);
611 }
612 let j = &self.body.journal;
613 let i = j.iter().position(|c| c.rev > rev)?;
614 (j[i].rev == rev + 1).then_some(&j[i..])
615 }
616
617 pub fn selection(&self) -> TextSel {
619 self.body.sel
620 }
621
622 pub fn set_selection(&mut self, sel: TextSel) {
624 self.body.sel = TextSel::new(self.clamp(sel.anchor), self.clamp(sel.caret));
625 }
626
627 pub fn clamp(&self, p: TextPos) -> TextPos {
629 let line = p.line.min(self.body.lines.len() - 1);
630 let t = &self.body.lines[line];
631 let mut b = p.byte.min(t.len());
632 while !t.is_char_boundary(b) {
633 b -= 1;
634 }
635 TextPos::new(line, b)
636 }
637
638 pub fn end(&self) -> TextPos {
640 let l = self.body.lines.len() - 1;
641 TextPos::new(l, self.body.lines[l].len())
642 }
643
644 pub fn text_range(&self, a: TextPos, b: TextPos) -> String {
646 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
647 let mut s = String::new();
648 for l in a.line..=b.line {
649 let t = &self.body.lines[l];
650 if l > a.line {
651 s.push('\n');
652 }
653 s.push_str(&t[if l == a.line { a.byte } else { 0 }..if l == b.line { b.byte } else { t.len() }]);
654 }
655 s
656 }
657
658 pub fn selected_text(&self) -> String {
660 let (a, b) = self.body.sel.range();
661 self.text_range(a, b)
662 }
663
664 pub fn flat(&self, p: TextPos) -> usize {
666 let p = self.clamp(p);
667 self.body.lines[..p.line].iter().map(|l| l.len() + 1).sum::<usize>() + p.byte
668 }
669
670 pub fn unflat(&self, f: usize) -> TextPos {
672 let mut f = f;
673 for (i, l) in self.body.lines.iter().enumerate() {
674 if f <= l.len() {
675 return self.clamp(TextPos::new(i, f));
676 }
677 f -= l.len() + 1;
678 }
679 self.end()
680 }
681
682 pub fn col_of(&self, line: usize, byte: usize) -> usize {
686 col_in(self.line(line), byte, self.tab_width as usize)
687 }
688
689 pub fn byte_at_col(&self, line: usize, col: usize) -> usize {
691 byte_in(self.line(line), col, self.tab_width as usize)
692 }
693
694 pub fn can_undo(&self) -> bool {
698 self.hist.can_undo()
699 }
700 pub fn can_redo(&self) -> bool {
702 self.hist.can_redo()
703 }
704 pub fn undo_label(&self) -> Option<&str> {
706 self.hist.undo_label()
707 }
708 pub fn redo_label(&self) -> Option<&str> {
710 self.hist.redo_label()
711 }
712 pub fn undo(&mut self) -> bool {
714 self.hist.undo(&mut self.body)
715 }
716 pub fn redo(&mut self) -> bool {
718 self.hist.redo(&mut self.body)
719 }
720 pub fn is_dirty(&self) -> bool {
722 self.hist.dirty()
723 }
724 pub fn mark_saved(&mut self) {
726 self.hist.mark_saved();
727 }
728 pub fn clear_history(&mut self) {
730 self.hist.clear();
731 }
732
733 fn edit(&mut self, first: usize, n: usize, mut new: Vec<String>, sel: TextSel, label: &str, kind: u8) -> bool {
735 if new.is_empty() && n == self.body.lines.len() {
736 new.push(String::new());
737 }
738 if self.body.lines[first..first + n] == new[..] {
739 self.body.sel = sel_in(&self.body.lines, sel);
740 return false;
741 }
742 let old = self.body.lines[first..first + n].to_vec();
743 let e = LineEdit { first, old, new, sel0: self.body.sel, sel1: sel, label: String::from(label), kind };
744 self.hist.apply(&mut self.body, Box::new(e));
745 true
746 }
747
748 fn spliced(&self, a: TextPos, b: TextPos, s: &str) -> (Vec<String>, TextPos) {
750 let head = &self.body.lines[a.line][..a.byte];
751 let tail = &self.body.lines[b.line][b.byte..];
752 let mut out: Vec<String> = Vec::new();
753 let mut first = true;
754 for l in lines_of(s) {
755 if first {
756 let mut h = String::with_capacity(head.len() + l.len() + tail.len());
757 h.push_str(head);
758 h.push_str(l);
759 out.push(h);
760 first = false;
761 } else {
762 out.push(String::from(l));
763 }
764 }
765 let k = out.len() - 1;
766 let end = TextPos::new(a.line + k, out[k].len());
767 out[k].push_str(tail);
768 (out, end)
769 }
770
771 pub fn replace(&mut self, a: TextPos, b: TextPos, s: &str) -> TextPos {
775 self.replace_kind(a, b, s, CK_NONE, tr("Ersetzen"))
776 }
777
778 pub(crate) fn replace_kind(&mut self, a: TextPos, b: TextPos, s: &str, kind: u8, label: &str) -> TextPos {
779 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
780 let (new, end) = self.spliced(a, b, s);
781 self.edit(a.line, b.line - a.line + 1, new, TextSel::at(end), label, kind);
782 end
783 }
784
785 pub fn insert(&mut self, s: &str) -> TextPos {
787 let (a, b) = self.body.sel.range();
788 self.replace_kind(a, b, s, CK_NONE, tr("Einfügen"))
789 }
790
791 pub fn delete(&mut self, a: TextPos, b: TextPos) -> bool {
793 self.delete_kind(a, b, CK_NONE)
794 }
795
796 pub(crate) fn delete_kind(&mut self, a: TextPos, b: TextPos, kind: u8) -> bool {
797 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
798 if a == b {
799 return false;
800 }
801 let rev = self.body.rev;
802 self.replace_kind(a, b, "", kind, tr("Löschen"));
803 self.body.rev != rev
804 }
805
806 pub fn indentation(&self, line: usize) -> &str {
808 let t = self.line(line);
809 &t[..t.len() - t.trim_start_matches([' ', '\t']).len()]
810 }
811
812 pub fn indent_unit(&self) -> String {
814 if self.use_tabs { String::from("\t") } else { " ".repeat(self.tab_width.clamp(1, 16) as usize) }
815 }
816
817 pub fn type_text(&mut self, s: &str, closers: &str) -> bool {
820 let (a, b) = self.body.sel.range();
821 let rev = self.body.rev;
822 let line = self.line(a.line);
823 let mut chars = s.chars();
824 let one = chars.next().filter(|_| chars.next().is_none());
825 if let Some(c) = one.filter(|c| closers.contains(*c)) {
826 if a.line == b.line && line[..a.byte].trim().is_empty() && !line[..a.byte].is_empty() {
827 let ind = &line[..a.byte];
829 let w = col_in(ind, ind.len(), self.tab_width as usize);
830 let tw = self.tab_width.max(1) as usize;
831 let target = if w % tw == 0 { w.saturating_sub(tw) } else { w - w % tw };
832 let keep = byte_in(ind, target, self.tab_width as usize);
833 let mut rep = String::from(&ind[..keep]);
834 rep.push(c);
835 self.replace_kind(TextPos::new(a.line, 0), b, &rep, CK_NONE, tr("Schreiben"));
836 return self.body.rev != rev;
837 }
838 }
839 self.replace_kind(a, b, s, CK_TYPE, tr("Schreiben"));
840 self.body.rev != rev
841 }
842
843 pub fn newline(&mut self, openers: &str) -> bool {
847 let (a, b) = self.body.sel.range();
848 let rev = self.body.rev;
849 let line = self.line(a.line);
850 let ind = String::from(self.indentation(a.line).get(..a.byte.min(self.indentation(a.line).len())).unwrap_or(""));
851 let before = line[..a.byte].trim_end();
852 let after = self.line(b.line)[b.byte..].trim_start();
853 let opener = before.chars().last().filter(|c| openers.contains(*c));
854 let mut s = String::from("\n");
855 s.push_str(&ind);
856 let mut caret_off = s.len();
857 if let Some(o) = opener {
858 s.push_str(&self.indent_unit());
859 caret_off = s.len();
860 let close = match o {
861 '{' => Some('}'),
862 '[' => Some(']'),
863 '(' => Some(')'),
864 _ => None,
865 };
866 if close.is_some() && after.starts_with(close.unwrap()) {
867 s.push('\n');
868 s.push_str(&ind);
869 }
870 }
871 let trail = self.line(b.line)[b.byte..].len() - after.len();
873 let b2 = TextPos::new(b.line, b.byte + trail);
874 let (new, _) = self.spliced(a, b2, &s);
875 let caret = TextPos::new(a.line + 1, caret_off - 1);
876 self.edit(a.line, b2.line - a.line + 1, new, TextSel::at(caret), tr("Zeilenumbruch"), CK_NONE);
877 self.body.rev != rev
878 }
879
880 pub fn tab(&mut self) -> bool {
883 let (a, b) = self.body.sel.range();
884 if a.line != b.line {
885 return self.indent(1);
886 }
887 let rev = self.body.rev;
888 let s = if self.use_tabs {
889 String::from("\t")
890 } else {
891 let col = self.col_of(a.line, a.byte);
892 let tw = self.tab_width.clamp(1, 16) as usize;
893 " ".repeat(tw - col % tw)
894 };
895 self.replace_kind(a, b, &s, CK_TYPE, tr("Einzug"));
896 self.body.rev != rev
897 }
898
899 pub fn back_tab(&mut self) -> bool {
901 self.indent(-1)
902 }
903
904 pub fn indent(&mut self, delta: i32) -> bool {
907 let sel = self.body.sel;
908 let (a, b) = sel.range();
909 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
910 let unit = self.indent_unit();
911 let tw = self.tab_width.clamp(1, 16) as usize;
912 let mut new = Vec::with_capacity(last - a.line + 1);
913 let mut shift: Vec<isize> = Vec::with_capacity(last - a.line + 1);
914 for l in a.line..=last {
915 let t = &self.body.lines[l];
916 if delta > 0 {
917 if t.trim().is_empty() && last > a.line {
918 new.push(t.clone());
919 shift.push(0);
920 } else {
921 let mut s = unit.clone();
922 s.push_str(t);
923 new.push(s);
924 shift.push(unit.len() as isize);
925 }
926 } else {
927 let n = if t.starts_with('\t') { 1 } else { t.bytes().take(tw).take_while(|&c| c == b' ').count() };
929 new.push(String::from(&t[n..]));
930 shift.push(-(n as isize));
931 }
932 }
933 let mv = |p: TextPos| -> TextPos {
935 if p.line < a.line || p.line > last {
936 return p;
937 }
938 let d = shift[p.line - a.line];
939 if d >= 0 {
940 if p.byte == 0 && !sel.is_empty() { p } else { TextPos::new(p.line, p.byte + d as usize) }
941 } else {
942 TextPos::new(p.line, p.byte.saturating_sub((-d) as usize))
943 }
944 };
945 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
946 self.edit(a.line, last - a.line + 1, new, to, tr("Einzug"), CK_NONE)
947 }
948
949 pub fn toggle_comment(&mut self, prefix: &str) -> bool {
951 if prefix.is_empty() {
952 return false;
953 }
954 let sel = self.body.sel;
955 let (a, b) = sel.range();
956 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
957 let lines = &self.body.lines[a.line..=last];
958 let all = lines.iter().filter(|t| !t.trim().is_empty()).all(|t| t.trim_start().starts_with(prefix));
959 let ind = |t: &str| t.bytes().take_while(|&c| c == b' ' || c == b'\t').count();
963 let min_ind = lines.iter().filter(|t| !t.trim().is_empty()).map(|t| ind(t)).min().unwrap_or(0);
964 let mut new = Vec::new();
965 let mut shift = Vec::new();
966 for t in lines {
967 if t.trim().is_empty() {
968 new.push(t.clone());
969 shift.push((0usize, 0isize));
970 continue;
971 }
972 if all {
973 let i = t.len() - t.trim_start().len();
974 let mut n = prefix.len();
975 if t[i + n..].starts_with(' ') {
976 n += 1;
977 }
978 let mut s = String::from(&t[..i]);
979 s.push_str(&t[i + n..]);
980 new.push(s);
981 shift.push((i, -(n as isize)));
982 } else {
983 let mut s = String::from(&t[..min_ind]);
984 s.push_str(prefix);
985 s.push(' ');
986 s.push_str(&t[min_ind..]);
987 new.push(s);
988 shift.push((min_ind, prefix.len() as isize + 1));
989 }
990 }
991 let mv = |p: TextPos| -> TextPos {
992 if p.line < a.line || p.line > last {
993 return p;
994 }
995 let (at, d) = shift[p.line - a.line];
996 if p.byte < at {
997 return p;
998 }
999 TextPos::new(p.line, if d >= 0 { p.byte + d as usize } else { p.byte.saturating_sub((-d) as usize).max(at) })
1000 };
1001 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
1002 self.edit(a.line, last - a.line + 1, new, to, tr("Kommentar"), CK_NONE)
1003 }
1004
1005 pub fn move_lines(&mut self, delta: i32) -> bool {
1007 let sel = self.body.sel;
1008 let (a, b) = sel.range();
1009 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
1010 let n = self.body.lines.len();
1011 if (delta < 0 && a.line == 0) || (delta > 0 && last + 1 >= n) {
1012 return false;
1013 }
1014 let (first, cnt) = if delta < 0 { (a.line - 1, last - a.line + 2) } else { (a.line, last - a.line + 2) };
1015 let mut new: Vec<String> = self.body.lines[first..first + cnt].to_vec();
1016 if delta < 0 {
1017 new.rotate_left(1);
1018 } else {
1019 new.rotate_right(1);
1020 }
1021 let d = if delta < 0 { -1isize } else { 1 };
1022 let mv = |p: TextPos| {
1025 let l = (p.line as isize + d) as usize;
1026 if l >= n { TextPos::new(n - 1, new[cnt - 1].len()) } else { TextPos::new(l, p.byte) }
1027 };
1028 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
1029 self.edit(first, cnt, new, to, tr("Zeilen verschieben"), CK_NONE)
1030 }
1031
1032 pub fn find_all(&self, q: &str, opts: u8) -> Vec<(TextPos, TextPos)> {
1036 let mut out = Vec::new();
1037 if q.is_empty() || q.contains('\n') {
1038 return out;
1039 }
1040 for (l, t) in self.body.lines.iter().enumerate() {
1041 find_in(t, q, opts, &mut |a, b| {
1042 out.push((TextPos::new(l, a), TextPos::new(l, b)));
1043 out.len() < FIND_MAX
1044 });
1045 if out.len() >= FIND_MAX {
1046 break;
1047 }
1048 }
1049 out
1050 }
1051
1052 pub fn find_lines(&self, q: &str, opts: u8, first: usize, n: usize) -> Vec<(TextPos, TextPos)> {
1054 let mut out = Vec::new();
1055 if q.is_empty() || q.contains('\n') {
1056 return out;
1057 }
1058 let end = (first + n).min(self.body.lines.len());
1059 for l in first.min(end)..end {
1060 find_in(&self.body.lines[l], q, opts, &mut |a, b| {
1061 out.push((TextPos::new(l, a), TextPos::new(l, b)));
1062 true
1063 });
1064 }
1065 out
1066 }
1067
1068 pub fn find_next(&self, from: TextPos, q: &str, opts: u8, backwards: bool) -> Option<(TextPos, TextPos)> {
1070 if q.is_empty() || q.contains('\n') {
1071 return None;
1072 }
1073 let n = self.body.lines.len();
1074 let from = self.clamp(from);
1075 for k in 0..=n {
1076 let l = if backwards { (from.line + n * 2 - k) % n } else { (from.line + k) % n };
1077 let mut hits: Vec<(usize, usize)> = Vec::new();
1078 find_in(&self.body.lines[l], q, opts, &mut |a, b| {
1079 hits.push((a, b));
1080 true
1081 });
1082 let pick = if backwards {
1083 hits.into_iter().rev().find(|h| k > 0 || h.0 < from.byte)
1084 } else {
1085 hits.into_iter().find(|h| k > 0 || h.0 >= from.byte)
1086 };
1087 if let Some((a, b)) = pick {
1088 return Some((TextPos::new(l, a), TextPos::new(l, b)));
1089 }
1090 }
1091 None
1092 }
1093
1094 pub fn sel_is_match(&self, q: &str, opts: u8) -> bool {
1096 let (a, b) = self.body.sel.range();
1097 if a.line != b.line || a == b {
1098 return false;
1099 }
1100 let mut ok = false;
1101 find_in(&self.body.lines[a.line][..], q, opts, &mut |x, y| {
1102 if x == a.byte && y == b.byte {
1103 ok = true;
1104 return false;
1105 }
1106 true
1107 });
1108 ok
1109 }
1110
1111 pub fn replace_next(&mut self, q: &str, rep: &str, opts: u8) -> bool {
1113 let mut did = false;
1114 if self.sel_is_match(q, opts) {
1115 let (a, b) = self.body.sel.range();
1116 self.replace_kind(a, b, rep, CK_NONE, tr("Ersetzen"));
1117 did = true;
1118 }
1119 let from = self.body.sel.end();
1120 if let Some((a, b)) = self.find_next(from, q, opts, false) {
1121 self.body.sel = TextSel::new(a, b);
1122 }
1123 did
1124 }
1125
1126 pub fn replace_all(&mut self, q: &str, rep: &str, opts: u8) -> usize {
1128 if q.is_empty() || q.contains('\n') {
1129 return 0;
1130 }
1131 let mut first = usize::MAX;
1132 let mut last = 0;
1133 let mut count = 0;
1134 let mut new_lines: Vec<(usize, String)> = Vec::new();
1135 for (l, t) in self.body.lines.iter().enumerate() {
1136 let mut hits = Vec::new();
1137 find_in(t, q, opts, &mut |a, b| {
1138 hits.push((a, b));
1139 true
1140 });
1141 if hits.is_empty() {
1142 continue;
1143 }
1144 let mut s = String::with_capacity(t.len());
1145 let mut at = 0;
1146 for (a, b) in &hits {
1147 s.push_str(&t[at..*a]);
1148 s.push_str(rep);
1149 at = *b;
1150 }
1151 s.push_str(&t[at..]);
1152 count += hits.len();
1153 first = first.min(l);
1154 last = l;
1155 new_lines.push((l, s));
1156 }
1157 if count == 0 {
1158 return 0;
1159 }
1160 let mut new: Vec<String> = self.body.lines[first..=last].to_vec();
1161 for (l, s) in new_lines {
1162 new[l - first] = s;
1163 }
1164 let sel = TextSel::at(self.clamp(self.body.sel.caret));
1165 let label = tr("Alle ersetzen");
1166 self.edit(first, last - first + 1, new, sel, label, CK_NONE);
1167 let c = self.clamp(self.body.sel.caret);
1168 self.body.sel = TextSel::at(c);
1169 count
1170 }
1171
1172 pub fn set_diagnostics(&mut self, d: &[Diag]) {
1177 let lines = &self.body.lines;
1178 let pos = |line: usize, col: usize| -> TextPos {
1179 match lines.get(line.max(1) - 1) {
1180 Some(t) => TextPos::new(line.max(1) - 1, byte_of_char(t, col.max(1) - 1)),
1181 None => TextPos::new(lines.len() - 1, lines[lines.len() - 1].len()),
1182 }
1183 };
1184 let mut v: Vec<Mark> = d
1185 .iter()
1186 .map(|x| {
1187 let a = pos(x.line, x.col);
1188 Mark { a, b: pos(x.end_line, x.end_col).max(a), sev: x.severity.min(DIAG_INFO), msg: x.msg.clone() }
1189 })
1190 .collect();
1191 v.sort_by(|x, y| (x.a, x.b).cmp(&(y.a, y.b)));
1192 self.body.marks = v;
1193 self.body.recount();
1194 }
1195
1196 pub fn clear_diagnostics(&mut self) {
1198 self.body.marks.clear();
1199 self.body.recount();
1200 }
1201
1202 pub fn diagnostics(&self) -> Vec<Diag> {
1204 self.body.marks.iter().map(|m| self.diag_of(m)).collect()
1205 }
1206
1207 pub fn diagnostic_count(&self) -> usize {
1209 self.body.marks.len()
1210 }
1211
1212 pub fn diagnostic(&self, i: usize) -> Option<Diag> {
1214 self.body.marks.get(i).map(|m| self.diag_of(m))
1215 }
1216
1217 pub fn diagnostic_counts(&self) -> [usize; 3] {
1219 self.body.counts
1220 }
1221
1222 pub fn diagnostics_at(&self, p: TextPos) -> Vec<Diag> {
1225 self.marks_at(p).into_iter().map(|i| self.diag_of(&self.body.marks[i])).collect()
1226 }
1227
1228 pub fn next_diagnostic(&self, p: TextPos, back: bool) -> Option<TextPos> {
1231 let m = &self.body.marks;
1232 let last = m.last()?;
1233 Some(if back {
1234 let k = m.partition_point(|x| x.a < p);
1235 if k == 0 { last.a } else { m[k - 1].a }
1236 } else {
1237 m.get(m.partition_point(|x| x.a <= p)).map_or(m[0].a, |x| x.a)
1238 })
1239 }
1240
1241 pub fn set_breakpoints(&mut self, bps: &[(usize, u8)]) {
1247 let n = self.body.lines.len();
1248 let mut v: Vec<(usize, u8)> = bps.iter().copied().filter(|b| b.0 < n).collect();
1249 v.sort_by_key(|b| b.0);
1250 v.dedup_by_key(|b| b.0);
1251 self.body.bps = v;
1252 }
1253
1254 pub fn breakpoints(&self) -> &[(usize, u8)] {
1256 &self.body.bps
1257 }
1258
1259 pub fn breakpoint(&self, line: usize) -> Option<u8> {
1261 self.body.bps.binary_search_by_key(&line, |b| b.0).ok().map(|i| self.body.bps[i].1)
1262 }
1263
1264 pub fn set_exec_line(&mut self, line: Option<usize>) {
1267 self.body.exec = line.filter(|&l| l < self.body.lines.len());
1268 }
1269
1270 pub fn exec_line(&self) -> Option<usize> {
1272 self.body.exec
1273 }
1274
1275 pub fn set_fold_ranges(&mut self, ranges: &[(usize, usize)]) {
1280 let n = self.body.lines.len();
1281 let old = core::mem::take(&mut self.body.folds);
1282 let mut v: Vec<FoldRange> = ranges
1283 .iter()
1284 .filter(|r| r.0 < r.1 && r.0 < n)
1285 .map(|&(a, z)| FoldRange { first: a, last: z.min(n - 1), folded: old.iter().any(|o| o.first == a && o.folded) })
1286 .filter(|r| r.last > r.first)
1287 .collect();
1288 v.sort_by_key(|r| r.first);
1289 v.dedup_by_key(|r| r.first);
1290 self.body.folds = v;
1291 self.body.rehide();
1292 }
1293
1294 pub fn fold_count(&self) -> usize {
1296 self.body.folds.len()
1297 }
1298
1299 pub fn fold_ranges(&self) -> Vec<(usize, usize, bool)> {
1301 self.body.folds.iter().map(|r| (r.first, r.last, r.folded)).collect()
1302 }
1303
1304 pub fn fold_at(&self, line: usize) -> Option<(usize, bool)> {
1306 let f = &self.body.folds;
1307 f.binary_search_by_key(&line, |r| r.first).ok().map(|i| (f[i].last, f[i].folded))
1308 }
1309
1310 pub fn set_folded(&mut self, line: usize, folded: bool) -> bool {
1312 let Ok(i) = self.body.folds.binary_search_by_key(&line, |r| r.first) else { return false };
1313 if self.body.folds[i].folded == folded {
1314 return false;
1315 }
1316 self.body.folds[i].folded = folded;
1317 self.body.rehide();
1318 true
1319 }
1320
1321 pub fn toggle_fold(&mut self, line: usize) -> bool {
1323 match self.fold_at(line) {
1324 Some((_, f)) => self.set_folded(line, !f),
1325 None => false,
1326 }
1327 }
1328
1329 pub fn fold_all(&mut self, folded: bool) {
1331 for r in self.body.folds.iter_mut() {
1332 r.folded = folded;
1333 }
1334 self.body.rehide();
1335 }
1336
1337 pub fn fold_around(&self, line: usize) -> Option<(usize, usize)> {
1339 self.body.folds.iter().filter(|r| r.first <= line && line <= r.last).max_by_key(|r| r.first).map(|r| (r.first, r.last))
1340 }
1341
1342 pub fn is_hidden(&self, line: usize) -> bool {
1344 let h = &self.body.hidden;
1345 let k = h.partition_point(|x| x.1 < line);
1346 h.get(k).is_some_and(|x| x.0 <= line)
1347 }
1348
1349 pub fn reveal(&mut self, line: usize) -> bool {
1351 if !self.is_hidden(line) {
1352 return false;
1353 }
1354 for r in self.body.folds.iter_mut() {
1355 if r.folded && r.first < line && line <= r.last {
1356 r.folded = false;
1357 }
1358 }
1359 self.body.rehide();
1360 true
1361 }
1362
1363 pub fn visible_lines(&self) -> usize {
1365 self.body.lines.len() - self.body.hidden_n
1366 }
1367
1368 pub fn row_of_line(&self, line: usize) -> usize {
1371 let mut r = line;
1372 for &(a, z) in &self.body.hidden {
1373 if z < line {
1374 r -= z - a + 1;
1375 } else {
1376 if a <= line {
1377 r -= line - a + 1;
1378 }
1379 break;
1380 }
1381 }
1382 r
1383 }
1384
1385 pub fn line_of_row(&self, row: usize) -> usize {
1387 let mut l = row;
1388 for &(a, z) in &self.body.hidden {
1389 if a <= l {
1390 l += z - a + 1;
1391 } else {
1392 break;
1393 }
1394 }
1395 l.min(self.body.lines.len() - 1)
1396 }
1397
1398 pub fn visible_line(&self, line: usize, down: bool) -> usize {
1400 let h = &self.body.hidden;
1401 let k = h.partition_point(|x| x.1 < line);
1402 match h.get(k) {
1403 Some(&(a, z)) if a <= line => {
1404 if down && z + 1 < self.body.lines.len() {
1405 z + 1
1406 } else {
1407 a - 1
1408 }
1409 }
1410 _ => line,
1411 }
1412 }
1413
1414 pub(crate) fn hidden(&self) -> &[(usize, usize)] {
1416 &self.body.hidden
1417 }
1418
1419 pub fn apply_edits(&mut self, edits: &[TextEdit], label: &str) -> bool {
1426 let mut ed: Vec<TextEdit> = edits
1427 .iter()
1428 .map(|e| TextEdit { a: self.clamp(e.a.min(e.b)), b: self.clamp(e.a.max(e.b)), text: e.text.replace("\r\n", "\n").replace('\r', "\n") })
1429 .collect();
1430 ed.sort_by(|x, y| (x.a, x.b).cmp(&(y.a, y.b)));
1431 if ed.is_empty() || ed.windows(2).any(|w| w[1].a < w[0].b) {
1432 return false;
1433 }
1434 let first = ed[0].a.line;
1435 let last = ed.iter().map(|e| e.b.line).max().unwrap_or(first);
1436 let block = self.text_range(TextPos::new(first, 0), TextPos::new(last, self.body.lines[last].len()));
1437 let base = self.flat(TextPos::new(first, 0));
1438 let mut out = String::with_capacity(block.len() + 16);
1439 let mut at = 0;
1440 let mut map: Vec<(usize, usize, usize)> = Vec::with_capacity(ed.len());
1442 for e in &ed {
1443 let (a, b) = (self.flat(e.a) - base, self.flat(e.b) - base);
1444 out.push_str(&block[at..a]);
1445 out.push_str(&e.text);
1446 map.push((a, b, out.len()));
1447 at = b;
1448 }
1449 out.push_str(&block[at..]);
1450 let new: Vec<String> = out.split('\n').map(String::from).collect();
1451 let dl = new.len() as isize - (last - first + 1) as isize;
1452 let mv = |p: TextPos| -> TextPos {
1453 if p.line < first {
1454 return p;
1455 }
1456 if p.line > last {
1457 return TextPos::new((p.line as isize + dl) as usize, p.byte);
1458 }
1459 let o = self.flat(p) - base;
1460 let mut n = o;
1461 for &(a, b, ne) in &map {
1462 if o >= b {
1463 n = ne + (o - b);
1464 } else {
1465 if o > a {
1466 n = ne;
1467 }
1468 break;
1469 }
1470 }
1471 let line = out[..n].matches('\n').count();
1472 let ls = out[..n].rfind('\n').map_or(0, |k| k + 1);
1473 TextPos::new(first + line, n - ls)
1474 };
1475 let s = self.body.sel;
1476 let sel = TextSel::new(mv(s.anchor), mv(s.caret));
1477 self.edit(first, last - first + 1, new, sel, label, CK_NONE)
1478 }
1479
1480 pub fn replace_text(&mut self, text: &str, label: &str) -> bool {
1485 let new: Vec<String> = lines_of(text).map(String::from).collect();
1486 let old = &self.body.lines;
1487 let pre = old.iter().zip(&new).take_while(|(a, b)| a == b).count();
1488 if pre == old.len() && pre == new.len() {
1489 return false;
1490 }
1491 let room = old.len().min(new.len()) - pre;
1492 let post = old.iter().rev().zip(new.iter().rev()).take(room).take_while(|(a, b)| a == b).count();
1493 let n_old = old.len() - pre - post;
1494 let mid: Vec<String> = new[pre..new.len() - post].to_vec();
1495 let mv = |p: TextPos| -> TextPos {
1496 if p.line < pre {
1497 return p;
1498 }
1499 if p.line >= pre + n_old {
1500 return TextPos::new(p.line - n_old + mid.len(), p.byte);
1501 }
1502 let ink = |c: char| !c.is_whitespace() && c != ';';
1505 let line = &old[p.line][..p.byte.min(old[p.line].len())];
1506 let k: usize = old[pre..p.line].iter().map(|l| l.chars().filter(|&c| ink(c)).count()).sum::<usize>() + line.chars().filter(|&c| ink(c)).count();
1507 let after = line.chars().next_back().is_some_and(ink);
1508 let want = if after { k - 1 } else { k };
1509 let mut seen = 0;
1510 let mut end = TextPos::new(pre, 0);
1511 for (i, l) in mid.iter().enumerate() {
1512 for (b, c) in l.char_indices() {
1513 if !ink(c) {
1514 continue;
1515 }
1516 if seen == want {
1517 return TextPos::new(pre + i, if after { b + c.len_utf8() } else { b });
1518 }
1519 seen += 1;
1520 end = TextPos::new(pre + i, b + c.len_utf8());
1521 }
1522 }
1523 end
1524 };
1525 let s = self.body.sel;
1526 let clamp = |p: TextPos| -> TextPos {
1527 let line = p.line.min(new.len() - 1);
1528 let t = &new[line];
1529 let mut b = p.byte.min(t.len());
1530 while !t.is_char_boundary(b) {
1531 b -= 1;
1532 }
1533 TextPos::new(line, b)
1534 };
1535 let sel = TextSel::new(clamp(mv(s.anchor)), clamp(mv(s.caret)));
1536 self.edit(pre, n_old, mid, sel, label, CK_NONE)
1537 }
1538
1539 pub(crate) fn marks(&self) -> &[Mark] {
1541 &self.body.marks
1542 }
1543
1544 pub(crate) fn mark_range(&self, first: usize, last: usize) -> Range<usize> {
1546 self.body.mark_range(first, last)
1547 }
1548
1549 pub(crate) fn marks_at(&self, p: TextPos) -> Vec<usize> {
1551 let m = &self.body.marks;
1552 let mut v: Vec<usize> = self.body.mark_range(p.line, p.line + 1).filter(|&i| m[i].a <= p && p <= m[i].b).collect();
1553 v.sort_by_key(|&i| m[i].sev);
1554 v
1555 }
1556
1557 pub(crate) fn diag_of(&self, m: &Mark) -> Diag {
1559 let col = |p: TextPos| {
1560 let p = self.clamp(p);
1561 self.body.lines[p.line][..p.byte].chars().count() + 1
1562 };
1563 Diag { line: m.a.line + 1, col: col(m.a), end_line: m.b.line + 1, end_col: col(m.b), severity: m.sev, msg: m.msg.clone() }
1564 }
1565}
1566
1567pub(crate) fn find_in(t: &str, q: &str, opts: u8, found: &mut dyn FnMut(usize, usize) -> bool) {
1569 if q.is_empty() {
1570 return;
1571 }
1572 let case = opts & FIND_CASE != 0;
1573 let word = opts & FIND_WORD != 0;
1574 let qc: Vec<char> = q.chars().map(|c| if case { c } else { fold(c) }).collect();
1575 let tb = t.as_bytes();
1576 let first_ascii = q.as_bytes()[0];
1577 let mut i = 0;
1578 while i < tb.len() {
1579 if first_ascii < 0x80 {
1581 let b = tb[i];
1582 let ok = if case { b == first_ascii } else { b.to_ascii_lowercase() == first_ascii.to_ascii_lowercase() };
1583 if !ok {
1584 i += 1;
1585 continue;
1586 }
1587 } else if tb[i] & 0xC0 == 0x80 {
1588 i += 1;
1589 continue;
1590 }
1591 let mut j = i;
1593 let mut ok = true;
1594 for &c in &qc {
1595 let Some(tc) = t[j..].chars().next() else {
1596 ok = false;
1597 break;
1598 };
1599 let tc2 = if case { tc } else { fold(tc) };
1600 if tc2 != c {
1601 ok = false;
1602 break;
1603 }
1604 j += tc.len_utf8();
1605 }
1606 if ok && word {
1607 let before = t[..i].chars().next_back().is_some_and(is_word);
1608 let after = t[j..].chars().next().is_some_and(is_word);
1609 ok = !before && !after;
1610 }
1611 if ok {
1612 if !found(i, j) {
1613 return;
1614 }
1615 i = j.max(i + 1);
1616 } else {
1617 i += 1;
1618 }
1619 while i < tb.len() && tb[i] & 0xC0 == 0x80 {
1620 i += 1;
1621 }
1622 }
1623}
1624
1625pub(crate) fn col_in(t: &str, byte: usize, tw: usize) -> usize {
1627 col_from(t, 0, 0, byte, tw)
1628}
1629
1630pub(crate) fn byte_in(t: &str, col: usize, tw: usize) -> usize {
1632 byte_from(t, 0, 0, col, tw)
1633}
1634
1635pub(crate) fn ascii_run(b: &[u8], i: usize, lim: usize) -> usize {
1638 let mut j = i;
1639 while j < lim && (0x20..0x7F).contains(&b[j]) {
1640 j += 1;
1641 }
1642 if j > i && b.get(j).is_some_and(|&c| c >= 0x80) {
1643 j -= 1;
1644 }
1645 j - i
1646}
1647
1648pub(crate) fn cell(t: &str, i: usize, col: usize, tw: usize) -> (usize, usize) {
1650 let n = segment::cluster_end(t, i);
1651 let c = t[i..].chars().next().unwrap_or(' ');
1652 (n, if c == '\t' { tw - col % tw } else { char_cols(c) })
1653}
1654
1655pub(crate) fn col_from(t: &str, i: usize, col: usize, byte: usize, tw: usize) -> usize {
1657 let tw = tw.clamp(1, 16);
1658 let (b, byte) = (t.as_bytes(), byte.min(t.len()));
1659 let (mut i, mut col) = (i, col);
1660 while i < byte {
1661 let k = ascii_run(b, i, byte);
1662 if k > 0 {
1663 (i, col) = (i + k, col + k);
1664 continue;
1665 }
1666 let (n, w) = cell(t, i, col, tw);
1667 (i, col) = (n, col + w);
1668 }
1669 col
1670}
1671
1672pub(crate) fn byte_from(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> usize {
1674 let (i, c0, n, w) = cell_at(t, i, c0, col, tw);
1675 if i < t.len() && (col - c0) * 2 >= w { n } else { i }
1677}
1678
1679pub(crate) fn cell_floor(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> (usize, usize) {
1682 let (i, c0, _, _) = cell_at(t, i, c0, col, tw);
1683 (i, c0)
1684}
1685
1686fn cell_at(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> (usize, usize, usize, usize) {
1689 let tw = tw.clamp(1, 16);
1690 let b = t.as_bytes();
1691 let (mut i, mut c0) = (i, c0);
1692 while i < t.len() {
1693 let k = ascii_run(b, i, t.len().min(i + (col - c0) + 1));
1695 if k > 0 {
1696 if col < c0 + k {
1697 let j = i + (col - c0);
1698 return (j, col, j + 1, 1);
1699 }
1700 (i, c0) = (i + k, c0 + k);
1701 continue;
1702 }
1703 let (n, w) = cell(t, i, c0, tw);
1704 if col < c0 + w {
1705 return (i, c0, n, w);
1706 }
1707 (i, c0) = (n, c0 + w);
1708 }
1709 (t.len(), c0, t.len(), 0)
1710}
1711