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
427struct LineEdit {
428 first: usize,
429 old: Vec<String>,
430 new: Vec<String>,
431 sel0: TextSel,
432 sel1: TextSel,
433 label: String,
434 kind: u8,
435}
436
437impl Edit<CodeBody> for LineEdit {
438 fn apply(&mut self, s: &mut CodeBody) {
439 s.splice(self.first, self.old.len(), &self.new);
440 s.sel = self.sel1;
441 }
442 fn revert(&mut self, s: &mut CodeBody) {
443 s.splice(self.first, self.new.len(), &self.old);
444 s.sel = self.sel0;
445 }
446 fn label(&self) -> &str {
447 &self.label
448 }
449 fn merge(&mut self, next: &mut dyn Edit<CodeBody>) -> bool {
450 if self.kind == CK_NONE {
451 return false;
452 }
453 match next.value().and_then(|v| v.downcast_ref::<LineEdit>()) {
454 Some(n) if n.kind == self.kind && n.first == self.first && n.sel0 == self.sel1 && n.old == self.new => {
455 self.new = n.new.clone();
456 self.sel1 = n.sel1;
457 true
458 }
459 _ => false,
460 }
461 }
462 fn value(&self) -> Option<&dyn core::any::Any> {
463 Some(self)
464 }
465}
466
467pub struct CodeDoc {
469 body: CodeBody,
470 hist: History<CodeBody>,
471 uid: u32,
473 pub tab_width: u8,
475 pub use_tabs: bool,
477}
478
479impl Default for CodeDoc {
480 fn default() -> CodeDoc {
481 CodeDoc::new()
482 }
483}
484
485pub(crate) fn is_word(c: char) -> bool {
487 c.is_alphanumeric() || c == '_'
488}
489
490pub(crate) fn fold(c: char) -> char {
492 if c.is_ascii() {
493 c.to_ascii_lowercase()
494 } else {
495 c.to_lowercase().next().unwrap_or(c)
496 }
497}
498
499pub(crate) fn char_cols(c: char) -> usize {
502 if segment::char_props(c) & (1 << 16) != 0 { 2 } else { 1 }
504}
505
506impl CodeDoc {
507 pub fn new() -> CodeDoc {
509 CodeDoc::from_lines(Vec::new())
510 }
511
512 pub fn from_text(s: &str) -> CodeDoc {
514 CodeDoc::from_lines(lines_of(s).map(String::from).collect())
515 }
516
517 pub fn from_lines(mut lines: Vec<String>) -> CodeDoc {
519 if lines.is_empty() {
520 lines.push(String::new());
521 }
522 for l in lines.iter_mut() {
523 if l.contains(['\n', '\r']) {
524 *l = l.replace(['\n', '\r'], "");
525 }
526 }
527 static NEXT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
528 let uid = NEXT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
529 let body = CodeBody {
530 lines,
531 sel: TextSel::default(),
532 rev: 0,
533 journal: Vec::new(),
534 marks: Vec::new(),
535 span: 0,
536 counts: [0; 3],
537 bps: Vec::new(),
538 exec: None,
539 folds: Vec::new(),
540 hidden: Vec::new(),
541 hidden_n: 0,
542 };
543 CodeDoc { body, hist: History::new(1000), uid, tab_width: 4, use_tabs: false }
544 }
545
546 pub fn text(&self) -> String {
548 let n: usize = self.body.lines.iter().map(|l| l.len() + 1).sum();
549 let mut s = String::with_capacity(n);
550 for (i, l) in self.body.lines.iter().enumerate() {
551 if i > 0 {
552 s.push('\n');
553 }
554 s.push_str(l);
555 }
556 s
557 }
558
559 pub fn line_count(&self) -> usize {
561 self.body.lines.len()
562 }
563
564 pub fn line(&self, i: usize) -> &str {
566 &self.body.lines[i.min(self.body.lines.len() - 1)]
567 }
568
569 pub fn lines(&self) -> &[String] {
571 &self.body.lines
572 }
573
574 pub fn revision(&self) -> u64 {
576 self.body.rev
577 }
578
579 pub(crate) fn uid(&self) -> u32 {
581 self.uid
582 }
583
584 pub fn changes_since(&self, rev: u64) -> Option<&[LineChange]> {
587 if rev == self.body.rev {
588 return Some(&[]);
589 }
590 let j = &self.body.journal;
591 let i = j.iter().position(|c| c.rev > rev)?;
592 (j[i].rev == rev + 1).then_some(&j[i..])
593 }
594
595 pub fn selection(&self) -> TextSel {
597 self.body.sel
598 }
599
600 pub fn set_selection(&mut self, sel: TextSel) {
602 self.body.sel = TextSel::new(self.clamp(sel.anchor), self.clamp(sel.caret));
603 }
604
605 pub fn clamp(&self, p: TextPos) -> TextPos {
607 let line = p.line.min(self.body.lines.len() - 1);
608 let t = &self.body.lines[line];
609 let mut b = p.byte.min(t.len());
610 while !t.is_char_boundary(b) {
611 b -= 1;
612 }
613 TextPos::new(line, b)
614 }
615
616 pub fn end(&self) -> TextPos {
618 let l = self.body.lines.len() - 1;
619 TextPos::new(l, self.body.lines[l].len())
620 }
621
622 pub fn text_range(&self, a: TextPos, b: TextPos) -> String {
624 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
625 let mut s = String::new();
626 for l in a.line..=b.line {
627 let t = &self.body.lines[l];
628 if l > a.line {
629 s.push('\n');
630 }
631 s.push_str(&t[if l == a.line { a.byte } else { 0 }..if l == b.line { b.byte } else { t.len() }]);
632 }
633 s
634 }
635
636 pub fn selected_text(&self) -> String {
638 let (a, b) = self.body.sel.range();
639 self.text_range(a, b)
640 }
641
642 pub fn flat(&self, p: TextPos) -> usize {
644 let p = self.clamp(p);
645 self.body.lines[..p.line].iter().map(|l| l.len() + 1).sum::<usize>() + p.byte
646 }
647
648 pub fn unflat(&self, f: usize) -> TextPos {
650 let mut f = f;
651 for (i, l) in self.body.lines.iter().enumerate() {
652 if f <= l.len() {
653 return self.clamp(TextPos::new(i, f));
654 }
655 f -= l.len() + 1;
656 }
657 self.end()
658 }
659
660 pub fn col_of(&self, line: usize, byte: usize) -> usize {
664 col_in(self.line(line), byte, self.tab_width as usize)
665 }
666
667 pub fn byte_at_col(&self, line: usize, col: usize) -> usize {
669 byte_in(self.line(line), col, self.tab_width as usize)
670 }
671
672 pub fn can_undo(&self) -> bool {
676 self.hist.can_undo()
677 }
678 pub fn can_redo(&self) -> bool {
680 self.hist.can_redo()
681 }
682 pub fn undo_label(&self) -> Option<&str> {
684 self.hist.undo_label()
685 }
686 pub fn redo_label(&self) -> Option<&str> {
688 self.hist.redo_label()
689 }
690 pub fn undo(&mut self) -> bool {
692 self.hist.undo(&mut self.body)
693 }
694 pub fn redo(&mut self) -> bool {
696 self.hist.redo(&mut self.body)
697 }
698 pub fn is_dirty(&self) -> bool {
700 self.hist.dirty()
701 }
702 pub fn mark_saved(&mut self) {
704 self.hist.mark_saved();
705 }
706 pub fn clear_history(&mut self) {
708 self.hist.clear();
709 }
710
711 fn edit(&mut self, first: usize, n: usize, mut new: Vec<String>, sel: TextSel, label: &str, kind: u8) -> bool {
713 if new.is_empty() && n == self.body.lines.len() {
714 new.push(String::new());
715 }
716 if self.body.lines[first..first + n] == new[..] {
717 self.body.sel = sel;
718 return false;
719 }
720 let old = self.body.lines[first..first + n].to_vec();
721 let e = LineEdit { first, old, new, sel0: self.body.sel, sel1: sel, label: String::from(label), kind };
722 self.hist.apply(&mut self.body, Box::new(e));
723 true
724 }
725
726 fn spliced(&self, a: TextPos, b: TextPos, s: &str) -> (Vec<String>, TextPos) {
728 let head = &self.body.lines[a.line][..a.byte];
729 let tail = &self.body.lines[b.line][b.byte..];
730 let mut out: Vec<String> = Vec::new();
731 let mut first = true;
732 for l in lines_of(s) {
733 if first {
734 let mut h = String::with_capacity(head.len() + l.len() + tail.len());
735 h.push_str(head);
736 h.push_str(l);
737 out.push(h);
738 first = false;
739 } else {
740 out.push(String::from(l));
741 }
742 }
743 let k = out.len() - 1;
744 let end = TextPos::new(a.line + k, out[k].len());
745 out[k].push_str(tail);
746 (out, end)
747 }
748
749 pub fn replace(&mut self, a: TextPos, b: TextPos, s: &str) -> TextPos {
753 self.replace_kind(a, b, s, CK_NONE, tr("Ersetzen"))
754 }
755
756 pub(crate) fn replace_kind(&mut self, a: TextPos, b: TextPos, s: &str, kind: u8, label: &str) -> TextPos {
757 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
758 let (new, end) = self.spliced(a, b, s);
759 self.edit(a.line, b.line - a.line + 1, new, TextSel::at(end), label, kind);
760 end
761 }
762
763 pub fn insert(&mut self, s: &str) -> TextPos {
765 let (a, b) = self.body.sel.range();
766 self.replace_kind(a, b, s, CK_NONE, tr("Einfügen"))
767 }
768
769 pub fn delete(&mut self, a: TextPos, b: TextPos) -> bool {
771 self.delete_kind(a, b, CK_NONE)
772 }
773
774 pub(crate) fn delete_kind(&mut self, a: TextPos, b: TextPos, kind: u8) -> bool {
775 let (a, b) = (self.clamp(a.min(b)), self.clamp(a.max(b)));
776 if a == b {
777 return false;
778 }
779 let rev = self.body.rev;
780 self.replace_kind(a, b, "", kind, tr("Löschen"));
781 self.body.rev != rev
782 }
783
784 pub fn indentation(&self, line: usize) -> &str {
786 let t = self.line(line);
787 &t[..t.len() - t.trim_start_matches([' ', '\t']).len()]
788 }
789
790 pub fn indent_unit(&self) -> String {
792 if self.use_tabs { String::from("\t") } else { " ".repeat(self.tab_width.clamp(1, 16) as usize) }
793 }
794
795 pub fn type_text(&mut self, s: &str, closers: &str) -> bool {
798 let (a, b) = self.body.sel.range();
799 let rev = self.body.rev;
800 let line = self.line(a.line);
801 let mut chars = s.chars();
802 let one = chars.next().filter(|_| chars.next().is_none());
803 if let Some(c) = one.filter(|c| closers.contains(*c)) {
804 if a.line == b.line && line[..a.byte].trim().is_empty() && !line[..a.byte].is_empty() {
805 let ind = &line[..a.byte];
807 let w = col_in(ind, ind.len(), self.tab_width as usize);
808 let tw = self.tab_width.max(1) as usize;
809 let target = if w % tw == 0 { w.saturating_sub(tw) } else { w - w % tw };
810 let keep = byte_in(ind, target, self.tab_width as usize);
811 let mut rep = String::from(&ind[..keep]);
812 rep.push(c);
813 self.replace_kind(TextPos::new(a.line, 0), b, &rep, CK_NONE, tr("Schreiben"));
814 return self.body.rev != rev;
815 }
816 }
817 self.replace_kind(a, b, s, CK_TYPE, tr("Schreiben"));
818 self.body.rev != rev
819 }
820
821 pub fn newline(&mut self, openers: &str) -> bool {
825 let (a, b) = self.body.sel.range();
826 let rev = self.body.rev;
827 let line = self.line(a.line);
828 let ind = String::from(self.indentation(a.line).get(..a.byte.min(self.indentation(a.line).len())).unwrap_or(""));
829 let before = line[..a.byte].trim_end();
830 let after = self.line(b.line)[b.byte..].trim_start();
831 let opener = before.chars().last().filter(|c| openers.contains(*c));
832 let mut s = String::from("\n");
833 s.push_str(&ind);
834 let mut caret_off = s.len();
835 if let Some(o) = opener {
836 s.push_str(&self.indent_unit());
837 caret_off = s.len();
838 let close = match o {
839 '{' => Some('}'),
840 '[' => Some(']'),
841 '(' => Some(')'),
842 _ => None,
843 };
844 if close.is_some() && after.starts_with(close.unwrap()) {
845 s.push('\n');
846 s.push_str(&ind);
847 }
848 }
849 let trail = self.line(b.line)[b.byte..].len() - after.len();
851 let b2 = TextPos::new(b.line, b.byte + trail);
852 let (new, _) = self.spliced(a, b2, &s);
853 let caret = TextPos::new(a.line + 1, caret_off - 1);
854 self.edit(a.line, b2.line - a.line + 1, new, TextSel::at(caret), tr("Zeilenumbruch"), CK_NONE);
855 self.body.rev != rev
856 }
857
858 pub fn tab(&mut self) -> bool {
861 let (a, b) = self.body.sel.range();
862 if a.line != b.line {
863 return self.indent(1);
864 }
865 let rev = self.body.rev;
866 let s = if self.use_tabs {
867 String::from("\t")
868 } else {
869 let col = self.col_of(a.line, a.byte);
870 let tw = self.tab_width.clamp(1, 16) as usize;
871 " ".repeat(tw - col % tw)
872 };
873 self.replace_kind(a, b, &s, CK_TYPE, tr("Einzug"));
874 self.body.rev != rev
875 }
876
877 pub fn back_tab(&mut self) -> bool {
879 self.indent(-1)
880 }
881
882 pub fn indent(&mut self, delta: i32) -> bool {
885 let sel = self.body.sel;
886 let (a, b) = sel.range();
887 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
888 let unit = self.indent_unit();
889 let tw = self.tab_width.clamp(1, 16) as usize;
890 let mut new = Vec::with_capacity(last - a.line + 1);
891 let mut shift: Vec<isize> = Vec::with_capacity(last - a.line + 1);
892 for l in a.line..=last {
893 let t = &self.body.lines[l];
894 if delta > 0 {
895 if t.trim().is_empty() && last > a.line {
896 new.push(t.clone());
897 shift.push(0);
898 } else {
899 let mut s = unit.clone();
900 s.push_str(t);
901 new.push(s);
902 shift.push(unit.len() as isize);
903 }
904 } else {
905 let n = if t.starts_with('\t') { 1 } else { t.bytes().take(tw).take_while(|&c| c == b' ').count() };
907 new.push(String::from(&t[n..]));
908 shift.push(-(n as isize));
909 }
910 }
911 let mv = |p: TextPos| -> TextPos {
913 if p.line < a.line || p.line > last {
914 return p;
915 }
916 let d = shift[p.line - a.line];
917 if d >= 0 {
918 if p.byte == 0 && !sel.is_empty() { p } else { TextPos::new(p.line, p.byte + d as usize) }
919 } else {
920 TextPos::new(p.line, p.byte.saturating_sub((-d) as usize))
921 }
922 };
923 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
924 self.edit(a.line, last - a.line + 1, new, to, tr("Einzug"), CK_NONE)
925 }
926
927 pub fn toggle_comment(&mut self, prefix: &str) -> bool {
929 if prefix.is_empty() {
930 return false;
931 }
932 let sel = self.body.sel;
933 let (a, b) = sel.range();
934 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
935 let lines = &self.body.lines[a.line..=last];
936 let all = lines.iter().filter(|t| !t.trim().is_empty()).all(|t| t.trim_start().starts_with(prefix));
937 let min_ind = lines.iter().filter(|t| !t.trim().is_empty()).map(|t| t.len() - t.trim_start().len()).min().unwrap_or(0);
938 let mut new = Vec::new();
939 let mut shift = Vec::new();
940 for t in lines {
941 if t.trim().is_empty() {
942 new.push(t.clone());
943 shift.push((0usize, 0isize));
944 continue;
945 }
946 if all {
947 let i = t.len() - t.trim_start().len();
948 let mut n = prefix.len();
949 if t[i + n..].starts_with(' ') {
950 n += 1;
951 }
952 let mut s = String::from(&t[..i]);
953 s.push_str(&t[i + n..]);
954 new.push(s);
955 shift.push((i, -(n as isize)));
956 } else {
957 let mut s = String::from(&t[..min_ind]);
958 s.push_str(prefix);
959 s.push(' ');
960 s.push_str(&t[min_ind..]);
961 new.push(s);
962 shift.push((min_ind, prefix.len() as isize + 1));
963 }
964 }
965 let mv = |p: TextPos| -> TextPos {
966 if p.line < a.line || p.line > last {
967 return p;
968 }
969 let (at, d) = shift[p.line - a.line];
970 if p.byte < at {
971 return p;
972 }
973 TextPos::new(p.line, if d >= 0 { p.byte + d as usize } else { p.byte.saturating_sub((-d) as usize).max(at) })
974 };
975 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
976 self.edit(a.line, last - a.line + 1, new, to, tr("Kommentar"), CK_NONE)
977 }
978
979 pub fn move_lines(&mut self, delta: i32) -> bool {
981 let sel = self.body.sel;
982 let (a, b) = sel.range();
983 let last = if b.line > a.line && b.byte == 0 { b.line - 1 } else { b.line };
984 let n = self.body.lines.len();
985 if (delta < 0 && a.line == 0) || (delta > 0 && last + 1 >= n) {
986 return false;
987 }
988 let (first, cnt) = if delta < 0 { (a.line - 1, last - a.line + 2) } else { (a.line, last - a.line + 2) };
989 let mut new: Vec<String> = self.body.lines[first..first + cnt].to_vec();
990 if delta < 0 {
991 new.rotate_left(1);
992 } else {
993 new.rotate_right(1);
994 }
995 let d = if delta < 0 { -1isize } else { 1 };
996 let mv = |p: TextPos| TextPos::new((p.line as isize + d) as usize, p.byte);
997 let to = TextSel::new(mv(sel.anchor), mv(sel.caret));
998 self.edit(first, cnt, new, to, tr("Zeilen verschieben"), CK_NONE)
999 }
1000
1001 pub fn find_all(&self, q: &str, opts: u8) -> Vec<(TextPos, TextPos)> {
1005 let mut out = Vec::new();
1006 if q.is_empty() || q.contains('\n') {
1007 return out;
1008 }
1009 for (l, t) in self.body.lines.iter().enumerate() {
1010 find_in(t, q, opts, &mut |a, b| {
1011 out.push((TextPos::new(l, a), TextPos::new(l, b)));
1012 out.len() < FIND_MAX
1013 });
1014 if out.len() >= FIND_MAX {
1015 break;
1016 }
1017 }
1018 out
1019 }
1020
1021 pub fn find_lines(&self, q: &str, opts: u8, first: usize, n: usize) -> Vec<(TextPos, TextPos)> {
1023 let mut out = Vec::new();
1024 if q.is_empty() || q.contains('\n') {
1025 return out;
1026 }
1027 let end = (first + n).min(self.body.lines.len());
1028 for l in first.min(end)..end {
1029 find_in(&self.body.lines[l], q, opts, &mut |a, b| {
1030 out.push((TextPos::new(l, a), TextPos::new(l, b)));
1031 true
1032 });
1033 }
1034 out
1035 }
1036
1037 pub fn find_next(&self, from: TextPos, q: &str, opts: u8, backwards: bool) -> Option<(TextPos, TextPos)> {
1039 if q.is_empty() || q.contains('\n') {
1040 return None;
1041 }
1042 let n = self.body.lines.len();
1043 let from = self.clamp(from);
1044 for k in 0..=n {
1045 let l = if backwards { (from.line + n * 2 - k) % n } else { (from.line + k) % n };
1046 let mut hits: Vec<(usize, usize)> = Vec::new();
1047 find_in(&self.body.lines[l], q, opts, &mut |a, b| {
1048 hits.push((a, b));
1049 true
1050 });
1051 let pick = if backwards {
1052 hits.into_iter().rev().find(|h| k > 0 || h.0 < from.byte)
1053 } else {
1054 hits.into_iter().find(|h| k > 0 || h.0 >= from.byte)
1055 };
1056 if let Some((a, b)) = pick {
1057 return Some((TextPos::new(l, a), TextPos::new(l, b)));
1058 }
1059 }
1060 None
1061 }
1062
1063 pub fn sel_is_match(&self, q: &str, opts: u8) -> bool {
1065 let (a, b) = self.body.sel.range();
1066 if a.line != b.line || a == b {
1067 return false;
1068 }
1069 let mut ok = false;
1070 find_in(&self.body.lines[a.line][..], q, opts, &mut |x, y| {
1071 if x == a.byte && y == b.byte {
1072 ok = true;
1073 return false;
1074 }
1075 true
1076 });
1077 ok
1078 }
1079
1080 pub fn replace_next(&mut self, q: &str, rep: &str, opts: u8) -> bool {
1082 let mut did = false;
1083 if self.sel_is_match(q, opts) {
1084 let (a, b) = self.body.sel.range();
1085 self.replace_kind(a, b, rep, CK_NONE, tr("Ersetzen"));
1086 did = true;
1087 }
1088 let from = self.body.sel.end();
1089 if let Some((a, b)) = self.find_next(from, q, opts, false) {
1090 self.body.sel = TextSel::new(a, b);
1091 }
1092 did
1093 }
1094
1095 pub fn replace_all(&mut self, q: &str, rep: &str, opts: u8) -> usize {
1097 if q.is_empty() || q.contains('\n') {
1098 return 0;
1099 }
1100 let mut first = usize::MAX;
1101 let mut last = 0;
1102 let mut count = 0;
1103 let mut new_lines: Vec<(usize, String)> = Vec::new();
1104 for (l, t) in self.body.lines.iter().enumerate() {
1105 let mut hits = Vec::new();
1106 find_in(t, q, opts, &mut |a, b| {
1107 hits.push((a, b));
1108 true
1109 });
1110 if hits.is_empty() {
1111 continue;
1112 }
1113 let mut s = String::with_capacity(t.len());
1114 let mut at = 0;
1115 for (a, b) in &hits {
1116 s.push_str(&t[at..*a]);
1117 s.push_str(rep);
1118 at = *b;
1119 }
1120 s.push_str(&t[at..]);
1121 count += hits.len();
1122 first = first.min(l);
1123 last = l;
1124 new_lines.push((l, s));
1125 }
1126 if count == 0 {
1127 return 0;
1128 }
1129 let mut new: Vec<String> = self.body.lines[first..=last].to_vec();
1130 for (l, s) in new_lines {
1131 new[l - first] = s;
1132 }
1133 let sel = TextSel::at(self.clamp(self.body.sel.caret));
1134 let label = tr("Alle ersetzen");
1135 self.edit(first, last - first + 1, new, sel, label, CK_NONE);
1136 let c = self.clamp(self.body.sel.caret);
1137 self.body.sel = TextSel::at(c);
1138 count
1139 }
1140
1141 pub fn set_diagnostics(&mut self, d: &[Diag]) {
1146 let lines = &self.body.lines;
1147 let pos = |line: usize, col: usize| -> TextPos {
1148 match lines.get(line.max(1) - 1) {
1149 Some(t) => TextPos::new(line.max(1) - 1, byte_of_char(t, col.max(1) - 1)),
1150 None => TextPos::new(lines.len() - 1, lines[lines.len() - 1].len()),
1151 }
1152 };
1153 let mut v: Vec<Mark> = d
1154 .iter()
1155 .map(|x| {
1156 let a = pos(x.line, x.col);
1157 Mark { a, b: pos(x.end_line, x.end_col).max(a), sev: x.severity.min(DIAG_INFO), msg: x.msg.clone() }
1158 })
1159 .collect();
1160 v.sort_by(|x, y| (x.a, x.b).cmp(&(y.a, y.b)));
1161 self.body.marks = v;
1162 self.body.recount();
1163 }
1164
1165 pub fn clear_diagnostics(&mut self) {
1167 self.body.marks.clear();
1168 self.body.recount();
1169 }
1170
1171 pub fn diagnostics(&self) -> Vec<Diag> {
1173 self.body.marks.iter().map(|m| self.diag_of(m)).collect()
1174 }
1175
1176 pub fn diagnostic_count(&self) -> usize {
1178 self.body.marks.len()
1179 }
1180
1181 pub fn diagnostic(&self, i: usize) -> Option<Diag> {
1183 self.body.marks.get(i).map(|m| self.diag_of(m))
1184 }
1185
1186 pub fn diagnostic_counts(&self) -> [usize; 3] {
1188 self.body.counts
1189 }
1190
1191 pub fn diagnostics_at(&self, p: TextPos) -> Vec<Diag> {
1194 self.marks_at(p).into_iter().map(|i| self.diag_of(&self.body.marks[i])).collect()
1195 }
1196
1197 pub fn next_diagnostic(&self, p: TextPos, back: bool) -> Option<TextPos> {
1200 let m = &self.body.marks;
1201 let last = m.last()?;
1202 Some(if back {
1203 let k = m.partition_point(|x| x.a < p);
1204 if k == 0 { last.a } else { m[k - 1].a }
1205 } else {
1206 m.get(m.partition_point(|x| x.a <= p)).map_or(m[0].a, |x| x.a)
1207 })
1208 }
1209
1210 pub fn set_breakpoints(&mut self, bps: &[(usize, u8)]) {
1216 let n = self.body.lines.len();
1217 let mut v: Vec<(usize, u8)> = bps.iter().copied().filter(|b| b.0 < n).collect();
1218 v.sort_by_key(|b| b.0);
1219 v.dedup_by_key(|b| b.0);
1220 self.body.bps = v;
1221 }
1222
1223 pub fn breakpoints(&self) -> &[(usize, u8)] {
1225 &self.body.bps
1226 }
1227
1228 pub fn breakpoint(&self, line: usize) -> Option<u8> {
1230 self.body.bps.binary_search_by_key(&line, |b| b.0).ok().map(|i| self.body.bps[i].1)
1231 }
1232
1233 pub fn set_exec_line(&mut self, line: Option<usize>) {
1236 self.body.exec = line.filter(|&l| l < self.body.lines.len());
1237 }
1238
1239 pub fn exec_line(&self) -> Option<usize> {
1241 self.body.exec
1242 }
1243
1244 pub fn set_fold_ranges(&mut self, ranges: &[(usize, usize)]) {
1249 let n = self.body.lines.len();
1250 let old = core::mem::take(&mut self.body.folds);
1251 let mut v: Vec<FoldRange> = ranges
1252 .iter()
1253 .filter(|r| r.0 < r.1 && r.0 < n)
1254 .map(|&(a, z)| FoldRange { first: a, last: z.min(n - 1), folded: old.iter().any(|o| o.first == a && o.folded) })
1255 .filter(|r| r.last > r.first)
1256 .collect();
1257 v.sort_by_key(|r| r.first);
1258 v.dedup_by_key(|r| r.first);
1259 self.body.folds = v;
1260 self.body.rehide();
1261 }
1262
1263 pub fn fold_count(&self) -> usize {
1265 self.body.folds.len()
1266 }
1267
1268 pub fn fold_ranges(&self) -> Vec<(usize, usize, bool)> {
1270 self.body.folds.iter().map(|r| (r.first, r.last, r.folded)).collect()
1271 }
1272
1273 pub fn fold_at(&self, line: usize) -> Option<(usize, bool)> {
1275 let f = &self.body.folds;
1276 f.binary_search_by_key(&line, |r| r.first).ok().map(|i| (f[i].last, f[i].folded))
1277 }
1278
1279 pub fn set_folded(&mut self, line: usize, folded: bool) -> bool {
1281 let Ok(i) = self.body.folds.binary_search_by_key(&line, |r| r.first) else { return false };
1282 if self.body.folds[i].folded == folded {
1283 return false;
1284 }
1285 self.body.folds[i].folded = folded;
1286 self.body.rehide();
1287 true
1288 }
1289
1290 pub fn toggle_fold(&mut self, line: usize) -> bool {
1292 match self.fold_at(line) {
1293 Some((_, f)) => self.set_folded(line, !f),
1294 None => false,
1295 }
1296 }
1297
1298 pub fn fold_all(&mut self, folded: bool) {
1300 for r in self.body.folds.iter_mut() {
1301 r.folded = folded;
1302 }
1303 self.body.rehide();
1304 }
1305
1306 pub fn fold_around(&self, line: usize) -> Option<(usize, usize)> {
1308 self.body.folds.iter().filter(|r| r.first <= line && line <= r.last).max_by_key(|r| r.first).map(|r| (r.first, r.last))
1309 }
1310
1311 pub fn is_hidden(&self, line: usize) -> bool {
1313 let h = &self.body.hidden;
1314 let k = h.partition_point(|x| x.1 < line);
1315 h.get(k).is_some_and(|x| x.0 <= line)
1316 }
1317
1318 pub fn reveal(&mut self, line: usize) -> bool {
1320 if !self.is_hidden(line) {
1321 return false;
1322 }
1323 for r in self.body.folds.iter_mut() {
1324 if r.folded && r.first < line && line <= r.last {
1325 r.folded = false;
1326 }
1327 }
1328 self.body.rehide();
1329 true
1330 }
1331
1332 pub fn visible_lines(&self) -> usize {
1334 self.body.lines.len() - self.body.hidden_n
1335 }
1336
1337 pub fn row_of_line(&self, line: usize) -> usize {
1340 let mut r = line;
1341 for &(a, z) in &self.body.hidden {
1342 if z < line {
1343 r -= z - a + 1;
1344 } else {
1345 if a <= line {
1346 r -= line - a + 1;
1347 }
1348 break;
1349 }
1350 }
1351 r
1352 }
1353
1354 pub fn line_of_row(&self, row: usize) -> usize {
1356 let mut l = row;
1357 for &(a, z) in &self.body.hidden {
1358 if a <= l {
1359 l += z - a + 1;
1360 } else {
1361 break;
1362 }
1363 }
1364 l.min(self.body.lines.len() - 1)
1365 }
1366
1367 pub fn visible_line(&self, line: usize, down: bool) -> usize {
1369 let h = &self.body.hidden;
1370 let k = h.partition_point(|x| x.1 < line);
1371 match h.get(k) {
1372 Some(&(a, z)) if a <= line => {
1373 if down && z + 1 < self.body.lines.len() {
1374 z + 1
1375 } else {
1376 a - 1
1377 }
1378 }
1379 _ => line,
1380 }
1381 }
1382
1383 pub(crate) fn hidden(&self) -> &[(usize, usize)] {
1385 &self.body.hidden
1386 }
1387
1388 pub fn apply_edits(&mut self, edits: &[TextEdit], label: &str) -> bool {
1395 let mut ed: Vec<TextEdit> = edits
1396 .iter()
1397 .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") })
1398 .collect();
1399 ed.sort_by(|x, y| (x.a, x.b).cmp(&(y.a, y.b)));
1400 if ed.is_empty() || ed.windows(2).any(|w| w[1].a < w[0].b) {
1401 return false;
1402 }
1403 let first = ed[0].a.line;
1404 let last = ed.iter().map(|e| e.b.line).max().unwrap_or(first);
1405 let block = self.text_range(TextPos::new(first, 0), TextPos::new(last, self.body.lines[last].len()));
1406 let base = self.flat(TextPos::new(first, 0));
1407 let mut out = String::with_capacity(block.len() + 16);
1408 let mut at = 0;
1409 let mut map: Vec<(usize, usize, usize)> = Vec::with_capacity(ed.len());
1411 for e in &ed {
1412 let (a, b) = (self.flat(e.a) - base, self.flat(e.b) - base);
1413 out.push_str(&block[at..a]);
1414 out.push_str(&e.text);
1415 map.push((a, b, out.len()));
1416 at = b;
1417 }
1418 out.push_str(&block[at..]);
1419 let new: Vec<String> = out.split('\n').map(String::from).collect();
1420 let dl = new.len() as isize - (last - first + 1) as isize;
1421 let mv = |p: TextPos| -> TextPos {
1422 if p.line < first {
1423 return p;
1424 }
1425 if p.line > last {
1426 return TextPos::new((p.line as isize + dl) as usize, p.byte);
1427 }
1428 let o = self.flat(p) - base;
1429 let mut n = o;
1430 for &(a, b, ne) in &map {
1431 if o >= b {
1432 n = ne + (o - b);
1433 } else {
1434 if o > a {
1435 n = ne;
1436 }
1437 break;
1438 }
1439 }
1440 let line = out[..n].matches('\n').count();
1441 let ls = out[..n].rfind('\n').map_or(0, |k| k + 1);
1442 TextPos::new(first + line, n - ls)
1443 };
1444 let s = self.body.sel;
1445 let sel = TextSel::new(mv(s.anchor), mv(s.caret));
1446 self.edit(first, last - first + 1, new, sel, label, CK_NONE)
1447 }
1448
1449 pub fn replace_text(&mut self, text: &str, label: &str) -> bool {
1454 let new: Vec<String> = lines_of(text).map(String::from).collect();
1455 let old = &self.body.lines;
1456 let pre = old.iter().zip(&new).take_while(|(a, b)| a == b).count();
1457 if pre == old.len() && pre == new.len() {
1458 return false;
1459 }
1460 let room = old.len().min(new.len()) - pre;
1461 let post = old.iter().rev().zip(new.iter().rev()).take(room).take_while(|(a, b)| a == b).count();
1462 let n_old = old.len() - pre - post;
1463 let mid: Vec<String> = new[pre..new.len() - post].to_vec();
1464 let mv = |p: TextPos| -> TextPos {
1465 if p.line < pre {
1466 return p;
1467 }
1468 if p.line >= pre + n_old {
1469 return TextPos::new(p.line - n_old + mid.len(), p.byte);
1470 }
1471 let ink = |c: char| !c.is_whitespace() && c != ';';
1474 let line = &old[p.line][..p.byte.min(old[p.line].len())];
1475 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();
1476 let after = line.chars().next_back().is_some_and(ink);
1477 let want = if after { k - 1 } else { k };
1478 let mut seen = 0;
1479 let mut end = TextPos::new(pre, 0);
1480 for (i, l) in mid.iter().enumerate() {
1481 for (b, c) in l.char_indices() {
1482 if !ink(c) {
1483 continue;
1484 }
1485 if seen == want {
1486 return TextPos::new(pre + i, if after { b + c.len_utf8() } else { b });
1487 }
1488 seen += 1;
1489 end = TextPos::new(pre + i, b + c.len_utf8());
1490 }
1491 }
1492 end
1493 };
1494 let s = self.body.sel;
1495 let clamp = |p: TextPos| -> TextPos {
1496 let line = p.line.min(new.len() - 1);
1497 let t = &new[line];
1498 let mut b = p.byte.min(t.len());
1499 while !t.is_char_boundary(b) {
1500 b -= 1;
1501 }
1502 TextPos::new(line, b)
1503 };
1504 let sel = TextSel::new(clamp(mv(s.anchor)), clamp(mv(s.caret)));
1505 self.edit(pre, n_old, mid, sel, label, CK_NONE)
1506 }
1507
1508 pub(crate) fn marks(&self) -> &[Mark] {
1510 &self.body.marks
1511 }
1512
1513 pub(crate) fn mark_range(&self, first: usize, last: usize) -> Range<usize> {
1515 self.body.mark_range(first, last)
1516 }
1517
1518 pub(crate) fn marks_at(&self, p: TextPos) -> Vec<usize> {
1520 let m = &self.body.marks;
1521 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();
1522 v.sort_by_key(|&i| m[i].sev);
1523 v
1524 }
1525
1526 pub(crate) fn diag_of(&self, m: &Mark) -> Diag {
1528 let col = |p: TextPos| {
1529 let p = self.clamp(p);
1530 self.body.lines[p.line][..p.byte].chars().count() + 1
1531 };
1532 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() }
1533 }
1534}
1535
1536pub(crate) fn find_in(t: &str, q: &str, opts: u8, found: &mut dyn FnMut(usize, usize) -> bool) {
1538 if q.is_empty() {
1539 return;
1540 }
1541 let case = opts & FIND_CASE != 0;
1542 let word = opts & FIND_WORD != 0;
1543 let qc: Vec<char> = q.chars().map(|c| if case { c } else { fold(c) }).collect();
1544 let tb = t.as_bytes();
1545 let first_ascii = q.as_bytes()[0];
1546 let mut i = 0;
1547 while i < tb.len() {
1548 if first_ascii < 0x80 {
1550 let b = tb[i];
1551 let ok = if case { b == first_ascii } else { b.to_ascii_lowercase() == first_ascii.to_ascii_lowercase() };
1552 if !ok {
1553 i += 1;
1554 continue;
1555 }
1556 } else if tb[i] & 0xC0 == 0x80 {
1557 i += 1;
1558 continue;
1559 }
1560 let mut j = i;
1562 let mut ok = true;
1563 for &c in &qc {
1564 let Some(tc) = t[j..].chars().next() else {
1565 ok = false;
1566 break;
1567 };
1568 let tc2 = if case { tc } else { fold(tc) };
1569 if tc2 != c {
1570 ok = false;
1571 break;
1572 }
1573 j += tc.len_utf8();
1574 }
1575 if ok && word {
1576 let before = t[..i].chars().next_back().is_some_and(is_word);
1577 let after = t[j..].chars().next().is_some_and(is_word);
1578 ok = !before && !after;
1579 }
1580 if ok {
1581 if !found(i, j) {
1582 return;
1583 }
1584 i = j.max(i + 1);
1585 } else {
1586 i += 1;
1587 }
1588 while i < tb.len() && tb[i] & 0xC0 == 0x80 {
1589 i += 1;
1590 }
1591 }
1592}
1593
1594pub(crate) fn col_in(t: &str, byte: usize, tw: usize) -> usize {
1596 col_from(t, 0, 0, byte, tw)
1597}
1598
1599pub(crate) fn byte_in(t: &str, col: usize, tw: usize) -> usize {
1601 byte_from(t, 0, 0, col, tw)
1602}
1603
1604pub(crate) fn ascii_run(b: &[u8], i: usize, lim: usize) -> usize {
1607 let mut j = i;
1608 while j < lim && (0x20..0x7F).contains(&b[j]) {
1609 j += 1;
1610 }
1611 if j > i && b.get(j).is_some_and(|&c| c >= 0x80) {
1612 j -= 1;
1613 }
1614 j - i
1615}
1616
1617pub(crate) fn cell(t: &str, i: usize, col: usize, tw: usize) -> (usize, usize) {
1619 let n = segment::cluster_end(t, i);
1620 let c = t[i..].chars().next().unwrap_or(' ');
1621 (n, if c == '\t' { tw - col % tw } else { char_cols(c) })
1622}
1623
1624pub(crate) fn col_from(t: &str, i: usize, col: usize, byte: usize, tw: usize) -> usize {
1626 let tw = tw.clamp(1, 16);
1627 let (b, byte) = (t.as_bytes(), byte.min(t.len()));
1628 let (mut i, mut col) = (i, col);
1629 while i < byte {
1630 let k = ascii_run(b, i, byte);
1631 if k > 0 {
1632 (i, col) = (i + k, col + k);
1633 continue;
1634 }
1635 let (n, w) = cell(t, i, col, tw);
1636 (i, col) = (n, col + w);
1637 }
1638 col
1639}
1640
1641pub(crate) fn byte_from(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> usize {
1643 let (i, c0, n, w) = cell_at(t, i, c0, col, tw);
1644 if i < t.len() && (col - c0) * 2 >= w { n } else { i }
1646}
1647
1648pub(crate) fn cell_floor(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> (usize, usize) {
1651 let (i, c0, _, _) = cell_at(t, i, c0, col, tw);
1652 (i, c0)
1653}
1654
1655fn cell_at(t: &str, i: usize, c0: usize, col: usize, tw: usize) -> (usize, usize, usize, usize) {
1658 let tw = tw.clamp(1, 16);
1659 let b = t.as_bytes();
1660 let (mut i, mut c0) = (i, c0);
1661 while i < t.len() {
1662 let k = ascii_run(b, i, t.len().min(i + (col - c0) + 1));
1664 if k > 0 {
1665 if col < c0 + k {
1666 let j = i + (col - c0);
1667 return (j, col, j + 1, 1);
1668 }
1669 (i, c0) = (i + k, c0 + k);
1670 continue;
1671 }
1672 let (n, w) = cell(t, i, c0, tw);
1673 if col < c0 + w {
1674 return (i, c0, n, w);
1675 }
1676 (i, c0) = (n, c0 + w);
1677 }
1678 (t.len(), c0, t.len(), 0)
1679}
1680