Skip to main content

ui/
code_doc.rs

1// code_doc -- the document of ui.code_editor: lines of text, the selection, undo (ui::History),
2// the indentation settings and the edits of a code editor
3//
4//   CodeDoc::new() / from_text(s)       ('\r\n', '\r' and '\n' end lines)
5//   doc.text() / line_count() / line(i) / lines() / selection() / set_selection(sel)
6//   doc.revision()                      counts content changes (edits, undo, redo)
7//   edits (each one undoable step; typing and repeated Backspace / Delete merge):
8//     replace(a, b, s) / insert(s) / delete(a, b) / newline(openers) (auto-indent: the line's
9//     indentation, one level more after an opener, a closer right of the caret on a line of
10//     its own) / type_text(s) (a closer typed on a blank line goes one level out) / indent(delta)
11//     (block indent / unindent of the selected lines) / tab() / back_tab() /
12//     toggle_comment(prefix) / replace_all(q, rep, FIND_*) / replace_next(q, rep, FIND_*)
13//   find: find_all(q, FIND_*) / find_next(from, q, FIND_*, backwards) (FIND_CASE matches the
14//     case, FIND_WORD whole words)
15//   doc.undo() / redo() / can_undo() / can_redo() / undo_label() / is_dirty() / mark_saved()
16//   tab_width (1..16, default 4), use_tabs (indent with tabs instead of blanks)
17//   col_of(line, byte) / byte_at_col(line, col)   visual columns (tabs to the next stop,
18//   wide East Asian characters two)
19//   diagnostics (a checker's or compiler's messages, drawn by the editor):
20//     set_diagnostics(&[Diag]) / clear_diagnostics() / diagnostics() -> Vec<Diag> /
21//     diagnostic(i) / diagnostic_count() / diagnostic_counts() -> [errors, warnings, infos] /
22//     diagnostics_at(pos) (most severe first) / next_diagnostic(pos, back) (wrapping)
23//   breakpoints and a debugger's current line (the gutter column of CE_BREAKPOINTS; lines
24//     from 0): set_breakpoints(&[(line, BP_ON | BP_DISABLED | BP_COND)]) / breakpoints() /
25//     breakpoint(line) / set_exec_line(Some(line)) / exec_line()
26//   folding (ranges from a language's services; lines from 0): set_fold_ranges(&[(first,
27//     last)]) / fold_ranges() / fold_at(line) / set_folded(line, on) / toggle_fold(line) /
28//     fold_all(on) / fold_around(line) / is_hidden(line) / reveal(line) / visible_lines() /
29//     row_of_line(line) / line_of_row(row) / visible_line(line, down)
30//   several changes as one undo step: apply_edits(&[TextEdit], label) (a rename's edits),
31//     replace_text(text, label) (a formatter's result: only the lines that differ change, the
32//     caret keeps its place among the non-blank characters)
33//
34// An edit replaces a range of whole lines (LineEdit: old and new lines, the selection before
35// and after); each change is also kept in a short journal (changes_since) that the editor's
36// highlighting states and the find results follow without starting again from the top.
37//
38// Diagnostics follow every change of the text (edits, undo, redo) until the app sets new
39// ones: when lines change, the old and new lines are compared -- the same number of lines
40// line by line (one line moved by Alt+Up / Down: as a move), else as one replaced part from
41// the first to the last difference.  Text before a change keeps its positions, text after it
42// shifts; a diagnostic whose text was replaced shrinks to what is left of it and is dropped
43// when nothing is (one that marks a point stays at the change).  Typed text at a diagnostic's
44// edge stays outside it.  So squiggles stay under their words while the user types, until
45// the next check replaces them.  Breakpoints, the execution line and folding ranges follow
46// the same way by their lines (a line counts as the point at its start); an edit inside the
47// hidden lines of a folded range unfolds it.  None of them is part of the undo history.
48
49use 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
61/// find: match the case
62pub const FIND_CASE: u8 = 1;
63/// find: whole words only
64pub const FIND_WORD: u8 = 2;
65/// find_all stops after this many matches
66pub const FIND_MAX: usize = 100_000;
67
68// merge classes of LineEdit
69pub(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/// a change of the lines: first .. first + old became first .. first + new (at revision rev)
75#[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
85/// Diag::severity: an error
86pub const DIAG_ERROR: u8 = 0;
87/// a warning
88pub const DIAG_WARNING: u8 = 1;
89/// a note, a hint (higher values count as this too)
90pub const DIAG_INFO: u8 = 2;
91
92/// a diagnostic of a code editor: a part of the text in lines and columns from 1 as compilers
93/// report them (columns count characters, a tab is one; the end is exclusive, an end at or
94/// before the start marks the one character at the start, past a line's end the end of the
95/// line), a severity DIAG_* and the message (see the module header)
96#[derive(Clone, Debug, Default, PartialEq, Eq)]
97pub struct Diag {
98    /// where it starts (from 1)
99    pub line: usize,
100    pub col: usize,
101    /// where it ends (exclusive)
102    pub end_line: usize,
103    pub end_col: usize,
104    /// DIAG_ERROR, DIAG_WARNING, DIAG_INFO
105    pub severity: u8,
106    pub msg: String,
107}
108
109impl Diag {
110    /// a part of one line: line:col .. line:end_col
111    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    /// line:col .. end_line:end_col
116    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// a diagnostic in the document: a .. b (a == b: the character at a), severity, message
122#[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
130/// a breakpoint's look (CodeDoc::set_breakpoints): set
131pub const BP_ON: u8 = 0;
132/// a breakpoint that is there but off (a ring)
133pub const BP_DISABLED: u8 = 1;
134/// a breakpoint with a condition (a bar across it)
135pub const BP_COND: u8 = 2;
136
137/// a change of a CodeDoc's text: a .. b becomes text (CodeDoc::apply_edits)
138#[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// a folding range: line `first` stays, first + 1 ..= last hide while folded
152#[derive(Clone, Copy, Debug, PartialEq)]
153struct FoldRange {
154    first: usize,
155    last: usize,
156    folded: bool,
157}
158
159/// the content an edit changes: the lines, the selection, the change journal and the
160/// diagnostics, breakpoints, execution line and folding ranges that follow the changes
161pub(crate) struct CodeBody {
162    lines: Vec<String>,
163    sel: TextSel,
164    rev: u64,
165    journal: Vec<LineChange>,
166    // sorted by position; the most lines one of them spans; how many per severity
167    marks: Vec<Mark>,
168    span: usize,
169    counts: [usize; 3],
170    // (line, BP_*) by line; the line of the current execution
171    bps: Vec<(usize, u8)>,
172    exec: Option<usize>,
173    // by first line; the hidden lines of the folded ones (merged, inclusive), how many
174    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    // the diagnostics follow lines first .. first + old becoming `new` (the lines still hold
196    // the old text; see the module header)
197    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                        // (no lines on one side) the block's diagnostics go, later ones move
225                        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        // breakpoints, the execution line and the folds by their lines (a line as the point at
241        // its start: Enter at a line's start takes it down with its text)
242        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            // an edit in the hidden lines of a fold opens it
259            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    // the hidden lines after the folds changed
275    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    // the counts and the longest span after the diagnostics changed
291    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    // the marks that may touch lines first .. last: those starting from first - span on
301    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
308// what a position of a diagnostic is when it follows a change: its start, its end, a point
309const AT_START: u8 = 0;
310const AT_END: u8 = 1;
311const AT_POINT: u8 = 2;
312
313// a change of a block of lines as the positions see it (block-relative: line 0 is the first
314// changed line)
315enum Follow {
316    // one replaced part
317    One(Moved),
318    // as many lines as before, each changed by itself (None: unchanged)
319    Lines(Vec<Option<Moved>>),
320    // the lines turned by one (Alt+Up / Down): the last to the front (true), the first to the end
321    Turn(bool),
322}
323
324impl Follow {
325    // where position q (absolute) goes; c: the lines of the block before
326    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// one replaced part: old [p, os) became new [p, ns) (block-relative)
347#[derive(Clone, Copy)]
348struct Moved {
349    p: TextPos,
350    os: TextPos,
351    ns: TextPos,
352}
353
354impl Moved {
355    // before the part a position stays, after it it shifts; inside it a start goes behind the
356    // new text, an end and a point to its front
357    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    // q at or after the old part's end
369    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
378// lines o became lines n: one replaced part from the first difference to the last (the common
379// end does not reach into the common start)
380fn 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
404// the bytes a and b start with alike (up to a character boundary)
405fn 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
413// the bytes a and b end with alike, at most max (from a character boundary)
414fn 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
422// the byte of character column c (from 0) of t; past its end the end
423fn byte_of_char(t: &str, c: usize) -> usize {
424    t.char_indices().nth(c).map_or(t.len(), |(i, _)| i)
425}
426
427// p as a position of lines: on a line there is, at most at its end and at a character boundary
428fn 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
439// a selection of lines (pos_in for both ends)
440fn 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    // (the selection a step leaves is one of the text it leaves: a caller that measured it in
456    // the text before the step would otherwise hand redo a position past a line's end or
457    // inside a character, and the next edit slices there)
458    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
489/// the document of ui.code_editor (see the module header)
490pub struct CodeDoc {
491    body: CodeBody,
492    hist: History<CodeBody>,
493    // tells documents apart (editor caches)
494    uid: u32,
495    /// columns between tab stops (1..16)
496    pub tab_width: u8,
497    /// indent with tabs (else with blanks)
498    pub use_tabs: bool,
499}
500
501impl Default for CodeDoc {
502    fn default() -> CodeDoc {
503        CodeDoc::new()
504    }
505}
506
507// a character of a word (whole-word search, double click)
508pub(crate) fn is_word(c: char) -> bool {
509    c.is_alphanumeric() || c == '_'
510}
511
512// a simple case fold
513pub(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
521/// the columns a character takes: 2 for wide East Asian characters, 0 for marks inside a
522/// cluster (the caller counts clusters), else 1
523pub(crate) fn char_cols(c: char) -> usize {
524    // text::segment's packed properties: bit 16 East Asian wide / full width
525    if segment::char_props(c) & (1 << 16) != 0 { 2 } else { 1 }
526}
527
528impl CodeDoc {
529    /// one empty line
530    pub fn new() -> CodeDoc {
531        CodeDoc::from_lines(Vec::new())
532    }
533
534    /// a text: a line per '\n' ('\r\n' and '\r' too)
535    pub fn from_text(s: &str) -> CodeDoc {
536        CodeDoc::from_lines(lines_of(s).map(String::from).collect())
537    }
538
539    /// lines (an empty list: one empty line); they must not hold '\n'
540    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    /// the text: the lines joined by '\n'
569    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    /// the number of lines (at least 1)
582    pub fn line_count(&self) -> usize {
583        self.body.lines.len()
584    }
585
586    /// line i (clamped to the last one), without its '\n'
587    pub fn line(&self, i: usize) -> &str {
588        &self.body.lines[i.min(self.body.lines.len() - 1)]
589    }
590
591    /// every line
592    pub fn lines(&self) -> &[String] {
593        &self.body.lines
594    }
595
596    /// counts the content changes (edits, undo, redo)
597    pub fn revision(&self) -> u64 {
598        self.body.rev
599    }
600
601    // (code_edit.rs) a number of this document instance
602    pub(crate) fn uid(&self) -> u32 {
603        self.uid
604    }
605
606    /// the changes after revision rev, oldest first (None: the journal no longer reaches back
607    /// that far -- start again)
608    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    /// the selection (the caret: anchor == caret)
618    pub fn selection(&self) -> TextSel {
619        self.body.sel
620    }
621
622    /// move the selection (clamped, not undoable)
623    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    /// p inside the document, on a character boundary
628    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    /// the end of the document
639    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    /// the text between a and b
645    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    /// the selected text (lines joined by '\n')
659    pub fn selected_text(&self) -> String {
660        let (a, b) = self.body.sel.range();
661        self.text_range(a, b)
662    }
663
664    /// the byte offset of p in text()
665    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    /// the position of byte offset f of text()
671    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    // ------------------------------------------------------------------ columns
683
684    /// the visual column of byte in a line (tabs to the next stop, wide characters 2)
685    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    /// the byte of the cluster boundary nearest to column col
690    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    // ------------------------------------------------------------------ history
695
696    /// there is a step to undo
697    pub fn can_undo(&self) -> bool {
698        self.hist.can_undo()
699    }
700    /// there is a step to redo
701    pub fn can_redo(&self) -> bool {
702        self.hist.can_redo()
703    }
704    /// the label of the step undo takes back
705    pub fn undo_label(&self) -> Option<&str> {
706        self.hist.undo_label()
707    }
708    /// the label of the step redo does again
709    pub fn redo_label(&self) -> Option<&str> {
710        self.hist.redo_label()
711    }
712    /// take the last step back -> false: nothing to undo
713    pub fn undo(&mut self) -> bool {
714        self.hist.undo(&mut self.body)
715    }
716    /// do the last undone step again -> false: nothing to redo
717    pub fn redo(&mut self) -> bool {
718        self.hist.redo(&mut self.body)
719    }
720    /// the content differs from the state of the last mark_saved
721    pub fn is_dirty(&self) -> bool {
722        self.hist.dirty()
723    }
724    /// the content is saved now
725    pub fn mark_saved(&mut self) {
726        self.hist.mark_saved();
727    }
728    /// forget the undo steps
729    pub fn clear_history(&mut self) {
730        self.hist.clear();
731    }
732
733    // lines first .. first + n become new; the selection then sel
734    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    // the lines that replace a..b by s -> (lines, the end of s)
749    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    // ------------------------------------------------------------------ edits
772
773    /// replace a..b by s; the caret after it -> the end of s
774    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    /// replace the selection by s (a paste)
786    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    /// remove a..b
792    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    /// the leading blanks of a line
807    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    /// one level of indentation (a tab or tab_width blanks)
813    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    /// typed text replaces the selection; a closer of `closers` typed as the first character
818    /// of a blank line takes one level of indentation away
819    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                // the line is blank up to the caret: out by one level
828                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    /// Enter: a line break with the line's indentation; after an opener of `openers` (the
844    /// last non-blank character before the caret) one level more, and when its closer follows
845    /// the caret, that goes to a line of its own
846    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        // blanks right of the caret go (the new line starts at its indentation)
872        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    /// Tab: the selected lines one level in (several lines selected), else the indentation
881    /// up to the next tab stop at the caret (replacing the selection)
882    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    /// Shift+Tab: the selected lines (or the caret's) one level out
900    pub fn back_tab(&mut self) -> bool {
901        self.indent(-1)
902    }
903
904    /// the selected lines (a selection ending at a line start leaves that line out) one level
905    /// in (delta > 0) or out; the selection follows its text
906    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                // remove up to one level: a tab, or blanks up to tab_width
928                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        // (a selection of whole lines keeps its start at column 0)
934        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    /// line comments on (when not every selected non-blank line has one) or off
950    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        // the indentation the lines share: blanks and tabs only (one byte each, so the cut below
960        // is at a character boundary of every line; a no-break space is 2 bytes, an ideographic
961        // space 3, and the smallest count of another line can end inside one)
962        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    /// Alt+Up / Alt+Down: the selected lines move one line up (delta < 0) or down
1006    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        // (a selection of whole lines ends at the start of the line below them; moved down to
1023        // the end of the text there is no such line: it ends at the end of the last one)
1024        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    // ------------------------------------------------------------------ find and replace
1033
1034    /// every match of q (FIND_CASE, FIND_WORD), at most FIND_MAX
1035    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    /// the matches of q in lines first .. first + n
1053    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    /// the next match after `from` (backwards: the last one before it), wrapping around
1069    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    /// the selection is a match of q
1095    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    /// replace the selection when it is a match, then select the next match -> replaced
1112    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    /// replace every match (one undo step) -> how many
1127    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    // ------------------------------------------------------------------ diagnostics
1173
1174    /// replace the diagnostics (Diag positions, clamped to the text); from now on they follow
1175    /// the edits until the next call (see the module header)
1176    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    /// no diagnostics
1197    pub fn clear_diagnostics(&mut self) {
1198        self.body.marks.clear();
1199        self.body.recount();
1200    }
1201
1202    /// the diagnostics where they are now, by position
1203    pub fn diagnostics(&self) -> Vec<Diag> {
1204        self.body.marks.iter().map(|m| self.diag_of(m)).collect()
1205    }
1206
1207    /// the number of diagnostics
1208    pub fn diagnostic_count(&self) -> usize {
1209        self.body.marks.len()
1210    }
1211
1212    /// diagnostic i (0 .. diagnostic_count(), by position) where it is now
1213    pub fn diagnostic(&self, i: usize) -> Option<Diag> {
1214        self.body.marks.get(i).map(|m| self.diag_of(m))
1215    }
1216
1217    /// the number of errors, warnings and infos (DIAG_* order)
1218    pub fn diagnostic_counts(&self) -> [usize; 3] {
1219        self.body.counts
1220    }
1221
1222    /// the diagnostics at p (their part holds p, its end included; a point: its character),
1223    /// most severe first
1224    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    /// the start of the first diagnostic after p (back: the last one before p), round the end
1229    /// of the text; None without diagnostics
1230    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    // ------------------------------------------------------------------ breakpoints, execution, folds
1242
1243    /// the breakpoints: (line from 0, BP_*), drawn in the gutter column of CE_BREAKPOINTS (the
1244    /// app keeps the real ones: a click there is CodeEv::Breakpoint); they follow the edits
1245    /// with their lines until the next call
1246    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    /// the breakpoints where they are now, by line
1255    pub fn breakpoints(&self) -> &[(usize, u8)] {
1256        &self.body.bps
1257    }
1258
1259    /// the breakpoint of a line
1260    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    /// the line a debugger stopped at (None: none), drawn marked with an arrow in the
1265    /// breakpoints' column; it follows the edits
1266    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    /// the line of set_exec_line where it is now
1271    pub fn exec_line(&self) -> Option<usize> {
1272        self.body.exec
1273    }
1274
1275    /// the folding ranges (first, last) with lines from 0 (a language's services give them):
1276    /// line first stays, first + 1 ..= last fold away.  A range that starts on the line of a
1277    /// folded one stays folded; ranges follow the edits, and an edit inside the hidden lines
1278    /// of a folded range unfolds it
1279    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    /// the number of folding ranges
1295    pub fn fold_count(&self) -> usize {
1296        self.body.folds.len()
1297    }
1298
1299    /// the folding ranges where they are now: (first, last, folded)
1300    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    /// the range that starts at line: (last, folded)
1305    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    /// fold (folded) or unfold the range that starts at line -> changed
1311    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    /// fold or unfold the range that starts at line -> changed
1322    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    /// every range folded (true) or unfolded
1330    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    /// the innermost range that holds line (its first line counts): (first, last)
1338    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    /// line is hidden in a folded range
1343    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    /// unfold every range that hides line -> changed
1350    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    /// the lines that are not hidden
1364    pub fn visible_lines(&self) -> usize {
1365        self.body.lines.len() - self.body.hidden_n
1366    }
1367
1368    /// the visible row of a line (counting only lines that are not hidden; a hidden line:
1369    /// the row of its fold's first line)
1370    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    /// the line of a visible row
1386    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    /// the nearest line that is not hidden from line on, downwards (down) or upwards
1399    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    // (code_edit.rs) the hidden lines: merged ranges (first, last), by line
1415    pub(crate) fn hidden(&self) -> &[(usize, usize)] {
1416        &self.body.hidden
1417    }
1418
1419    // ------------------------------------------------------------------ several edits as one step
1420
1421    /// several changes as one undo step named `label`: the edits (positions in the text before
1422    /// any of them, in any order, not overlapping) replace their parts; the selection follows
1423    /// the text (inside a replaced part: to its new end) -> false when nothing changed or two
1424    /// edits overlap.  Rename and format of a language service go through here
1425    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        // (old start, old end, new end) of each edit, offsets in the block
1441        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    /// the whole text becomes `text` as one undo step; only the lines that differ change (the
1481    /// diagnostics, breakpoints and folds of the others stay), and the caret keeps its place
1482    /// among the characters that are no blanks (so after a formatter it stays at its token)
1483    /// -> changed
1484    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            // the same count of ink before it (no blanks, no `;`): right after the k-th one when
1503            // it was right after one, else at the next one
1504            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    // (code_edit.rs) the diagnostics by position
1540    pub(crate) fn marks(&self) -> &[Mark] {
1541        &self.body.marks
1542    }
1543
1544    // (code_edit.rs) the indexes of the diagnostics that may touch lines first .. last
1545    pub(crate) fn mark_range(&self, first: usize, last: usize) -> Range<usize> {
1546        self.body.mark_range(first, last)
1547    }
1548
1549    // (code_edit.rs) the indexes of the diagnostics at p, most severe first
1550    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    // a mark as the app sees it
1558    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
1567/// the matches of q in t: found(a, b) for each, false stops
1568pub(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        // a quick test of the first byte for ASCII queries
1580        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        // compare the characters
1592        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
1625/// the visual column of byte in t (tab stops every tw columns)
1626pub(crate) fn col_in(t: &str, byte: usize, tw: usize) -> usize {
1627    col_from(t, 0, 0, byte, tw)
1628}
1629
1630/// the cluster boundary of t nearest to column col
1631pub(crate) fn byte_in(t: &str, col: usize, tw: usize) -> usize {
1632    byte_from(t, 0, 0, col, tw)
1633}
1634
1635// plain ASCII from i on (before lim): one cluster and one column per byte; the last one before
1636// a non-ASCII character is left out (a mark may join it)
1637pub(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
1648// the cluster of t at boundary i, at column col: (its end, its columns)
1649pub(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
1655/// the column of byte in t, counted from boundary i at column col (a checkpoint of a long line)
1656pub(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
1672/// the cluster boundary nearest to column col, counted from boundary i at column c0 <= col
1673pub(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    // nearer to the start or to the end of this cluster
1676    if i < t.len() && (col - c0) * 2 >= w { n } else { i }
1677}
1678
1679/// the cluster boundary at or before column col and its column, counted from boundary i at
1680/// column c0 <= col (where drawing a row from column col starts)
1681pub(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
1686// the cluster that holds column col, counted from boundary i at column c0 <= col -> (its start,
1687// its column, its end, its columns); past the end of t: (t.len(), the end's column, t.len(), 0)
1688fn 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        // (ASCII up to the column at most)
1694        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