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
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
467/// the document of ui.code_editor (see the module header)
468pub struct CodeDoc {
469    body: CodeBody,
470    hist: History<CodeBody>,
471    // tells documents apart (editor caches)
472    uid: u32,
473    /// columns between tab stops (1..16)
474    pub tab_width: u8,
475    /// indent with tabs (else with blanks)
476    pub use_tabs: bool,
477}
478
479impl Default for CodeDoc {
480    fn default() -> CodeDoc {
481        CodeDoc::new()
482    }
483}
484
485// a character of a word (whole-word search, double click)
486pub(crate) fn is_word(c: char) -> bool {
487    c.is_alphanumeric() || c == '_'
488}
489
490// a simple case fold
491pub(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
499/// the columns a character takes: 2 for wide East Asian characters, 0 for marks inside a
500/// cluster (the caller counts clusters), else 1
501pub(crate) fn char_cols(c: char) -> usize {
502    // text::segment's packed properties: bit 16 East Asian wide / full width
503    if segment::char_props(c) & (1 << 16) != 0 { 2 } else { 1 }
504}
505
506impl CodeDoc {
507    /// one empty line
508    pub fn new() -> CodeDoc {
509        CodeDoc::from_lines(Vec::new())
510    }
511
512    /// a text: a line per '\n' ('\r\n' and '\r' too)
513    pub fn from_text(s: &str) -> CodeDoc {
514        CodeDoc::from_lines(lines_of(s).map(String::from).collect())
515    }
516
517    /// lines (an empty list: one empty line); they must not hold '\n'
518    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    /// the text: the lines joined by '\n'
547    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    /// the number of lines (at least 1)
560    pub fn line_count(&self) -> usize {
561        self.body.lines.len()
562    }
563
564    /// line i (clamped to the last one), without its '\n'
565    pub fn line(&self, i: usize) -> &str {
566        &self.body.lines[i.min(self.body.lines.len() - 1)]
567    }
568
569    /// every line
570    pub fn lines(&self) -> &[String] {
571        &self.body.lines
572    }
573
574    /// counts the content changes (edits, undo, redo)
575    pub fn revision(&self) -> u64 {
576        self.body.rev
577    }
578
579    // (code_edit.rs) a number of this document instance
580    pub(crate) fn uid(&self) -> u32 {
581        self.uid
582    }
583
584    /// the changes after revision rev, oldest first (None: the journal no longer reaches back
585    /// that far -- start again)
586    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    /// the selection (the caret: anchor == caret)
596    pub fn selection(&self) -> TextSel {
597        self.body.sel
598    }
599
600    /// move the selection (clamped, not undoable)
601    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    /// p inside the document, on a character boundary
606    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    /// the end of the document
617    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    /// the text between a and b
623    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    /// the selected text (lines joined by '\n')
637    pub fn selected_text(&self) -> String {
638        let (a, b) = self.body.sel.range();
639        self.text_range(a, b)
640    }
641
642    /// the byte offset of p in text()
643    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    /// the position of byte offset f of text()
649    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    // ------------------------------------------------------------------ columns
661
662    /// the visual column of byte in a line (tabs to the next stop, wide characters 2)
663    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    /// the byte of the cluster boundary nearest to column col
668    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    // ------------------------------------------------------------------ history
673
674    /// there is a step to undo
675    pub fn can_undo(&self) -> bool {
676        self.hist.can_undo()
677    }
678    /// there is a step to redo
679    pub fn can_redo(&self) -> bool {
680        self.hist.can_redo()
681    }
682    /// the label of the step undo takes back
683    pub fn undo_label(&self) -> Option<&str> {
684        self.hist.undo_label()
685    }
686    /// the label of the step redo does again
687    pub fn redo_label(&self) -> Option<&str> {
688        self.hist.redo_label()
689    }
690    /// take the last step back -> false: nothing to undo
691    pub fn undo(&mut self) -> bool {
692        self.hist.undo(&mut self.body)
693    }
694    /// do the last undone step again -> false: nothing to redo
695    pub fn redo(&mut self) -> bool {
696        self.hist.redo(&mut self.body)
697    }
698    /// the content differs from the state of the last mark_saved
699    pub fn is_dirty(&self) -> bool {
700        self.hist.dirty()
701    }
702    /// the content is saved now
703    pub fn mark_saved(&mut self) {
704        self.hist.mark_saved();
705    }
706    /// forget the undo steps
707    pub fn clear_history(&mut self) {
708        self.hist.clear();
709    }
710
711    // lines first .. first + n become new; the selection then sel
712    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    // the lines that replace a..b by s -> (lines, the end of s)
727    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    // ------------------------------------------------------------------ edits
750
751    /// replace a..b by s; the caret after it -> the end of s
752    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    /// replace the selection by s (a paste)
764    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    /// remove a..b
770    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    /// the leading blanks of a line
785    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    /// one level of indentation (a tab or tab_width blanks)
791    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    /// typed text replaces the selection; a closer of `closers` typed as the first character
796    /// of a blank line takes one level of indentation away
797    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                // the line is blank up to the caret: out by one level
806                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    /// Enter: a line break with the line's indentation; after an opener of `openers` (the
822    /// last non-blank character before the caret) one level more, and when its closer follows
823    /// the caret, that goes to a line of its own
824    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        // blanks right of the caret go (the new line starts at its indentation)
850        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    /// Tab: the selected lines one level in (several lines selected), else the indentation
859    /// up to the next tab stop at the caret (replacing the selection)
860    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    /// Shift+Tab: the selected lines (or the caret's) one level out
878    pub fn back_tab(&mut self) -> bool {
879        self.indent(-1)
880    }
881
882    /// the selected lines (a selection ending at a line start leaves that line out) one level
883    /// in (delta > 0) or out; the selection follows its text
884    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                // remove up to one level: a tab, or blanks up to tab_width
906                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        // (a selection of whole lines keeps its start at column 0)
912        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    /// line comments on (when not every selected non-blank line has one) or off
928    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    /// Alt+Up / Alt+Down: the selected lines move one line up (delta < 0) or down
980    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    // ------------------------------------------------------------------ find and replace
1002
1003    /// every match of q (FIND_CASE, FIND_WORD), at most FIND_MAX
1004    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    /// the matches of q in lines first .. first + n
1022    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    /// the next match after `from` (backwards: the last one before it), wrapping around
1038    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    /// the selection is a match of q
1064    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    /// replace the selection when it is a match, then select the next match -> replaced
1081    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    /// replace every match (one undo step) -> how many
1096    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    // ------------------------------------------------------------------ diagnostics
1142
1143    /// replace the diagnostics (Diag positions, clamped to the text); from now on they follow
1144    /// the edits until the next call (see the module header)
1145    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    /// no diagnostics
1166    pub fn clear_diagnostics(&mut self) {
1167        self.body.marks.clear();
1168        self.body.recount();
1169    }
1170
1171    /// the diagnostics where they are now, by position
1172    pub fn diagnostics(&self) -> Vec<Diag> {
1173        self.body.marks.iter().map(|m| self.diag_of(m)).collect()
1174    }
1175
1176    /// the number of diagnostics
1177    pub fn diagnostic_count(&self) -> usize {
1178        self.body.marks.len()
1179    }
1180
1181    /// diagnostic i (0 .. diagnostic_count(), by position) where it is now
1182    pub fn diagnostic(&self, i: usize) -> Option<Diag> {
1183        self.body.marks.get(i).map(|m| self.diag_of(m))
1184    }
1185
1186    /// the number of errors, warnings and infos (DIAG_* order)
1187    pub fn diagnostic_counts(&self) -> [usize; 3] {
1188        self.body.counts
1189    }
1190
1191    /// the diagnostics at p (their part holds p, its end included; a point: its character),
1192    /// most severe first
1193    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    /// the start of the first diagnostic after p (back: the last one before p), round the end
1198    /// of the text; None without diagnostics
1199    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    // ------------------------------------------------------------------ breakpoints, execution, folds
1211
1212    /// the breakpoints: (line from 0, BP_*), drawn in the gutter column of CE_BREAKPOINTS (the
1213    /// app keeps the real ones: a click there is CodeEv::Breakpoint); they follow the edits
1214    /// with their lines until the next call
1215    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    /// the breakpoints where they are now, by line
1224    pub fn breakpoints(&self) -> &[(usize, u8)] {
1225        &self.body.bps
1226    }
1227
1228    /// the breakpoint of a line
1229    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    /// the line a debugger stopped at (None: none), drawn marked with an arrow in the
1234    /// breakpoints' column; it follows the edits
1235    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    /// the line of set_exec_line where it is now
1240    pub fn exec_line(&self) -> Option<usize> {
1241        self.body.exec
1242    }
1243
1244    /// the folding ranges (first, last) with lines from 0 (a language's services give them):
1245    /// line first stays, first + 1 ..= last fold away.  A range that starts on the line of a
1246    /// folded one stays folded; ranges follow the edits, and an edit inside the hidden lines
1247    /// of a folded range unfolds it
1248    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    /// the number of folding ranges
1264    pub fn fold_count(&self) -> usize {
1265        self.body.folds.len()
1266    }
1267
1268    /// the folding ranges where they are now: (first, last, folded)
1269    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    /// the range that starts at line: (last, folded)
1274    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    /// fold (folded) or unfold the range that starts at line -> changed
1280    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    /// fold or unfold the range that starts at line -> changed
1291    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    /// every range folded (true) or unfolded
1299    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    /// the innermost range that holds line (its first line counts): (first, last)
1307    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    /// line is hidden in a folded range
1312    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    /// unfold every range that hides line -> changed
1319    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    /// the lines that are not hidden
1333    pub fn visible_lines(&self) -> usize {
1334        self.body.lines.len() - self.body.hidden_n
1335    }
1336
1337    /// the visible row of a line (counting only lines that are not hidden; a hidden line:
1338    /// the row of its fold's first line)
1339    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    /// the line of a visible row
1355    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    /// the nearest line that is not hidden from line on, downwards (down) or upwards
1368    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    // (code_edit.rs) the hidden lines: merged ranges (first, last), by line
1384    pub(crate) fn hidden(&self) -> &[(usize, usize)] {
1385        &self.body.hidden
1386    }
1387
1388    // ------------------------------------------------------------------ several edits as one step
1389
1390    /// several changes as one undo step named `label`: the edits (positions in the text before
1391    /// any of them, in any order, not overlapping) replace their parts; the selection follows
1392    /// the text (inside a replaced part: to its new end) -> false when nothing changed or two
1393    /// edits overlap.  Rename and format of a language service go through here
1394    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        // (old start, old end, new end) of each edit, offsets in the block
1410        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    /// the whole text becomes `text` as one undo step; only the lines that differ change (the
1450    /// diagnostics, breakpoints and folds of the others stay), and the caret keeps its place
1451    /// among the characters that are no blanks (so after a formatter it stays at its token)
1452    /// -> changed
1453    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            // the same count of ink before it (no blanks, no `;`): right after the k-th one when
1472            // it was right after one, else at the next one
1473            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    // (code_edit.rs) the diagnostics by position
1509    pub(crate) fn marks(&self) -> &[Mark] {
1510        &self.body.marks
1511    }
1512
1513    // (code_edit.rs) the indexes of the diagnostics that may touch lines first .. last
1514    pub(crate) fn mark_range(&self, first: usize, last: usize) -> Range<usize> {
1515        self.body.mark_range(first, last)
1516    }
1517
1518    // (code_edit.rs) the indexes of the diagnostics at p, most severe first
1519    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    // a mark as the app sees it
1527    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
1536/// the matches of q in t: found(a, b) for each, false stops
1537pub(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        // a quick test of the first byte for ASCII queries
1549        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        // compare the characters
1561        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
1594/// the visual column of byte in t (tab stops every tw columns)
1595pub(crate) fn col_in(t: &str, byte: usize, tw: usize) -> usize {
1596    col_from(t, 0, 0, byte, tw)
1597}
1598
1599/// the cluster boundary of t nearest to column col
1600pub(crate) fn byte_in(t: &str, col: usize, tw: usize) -> usize {
1601    byte_from(t, 0, 0, col, tw)
1602}
1603
1604// plain ASCII from i on (before lim): one cluster and one column per byte; the last one before
1605// a non-ASCII character is left out (a mark may join it)
1606pub(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
1617// the cluster of t at boundary i, at column col: (its end, its columns)
1618pub(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
1624/// the column of byte in t, counted from boundary i at column col (a checkpoint of a long line)
1625pub(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
1641/// the cluster boundary nearest to column col, counted from boundary i at column c0 <= col
1642pub(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    // nearer to the start or to the end of this cluster
1645    if i < t.len() && (col - c0) * 2 >= w { n } else { i }
1646}
1647
1648/// the cluster boundary at or before column col and its column, counted from boundary i at
1649/// column c0 <= col (where drawing a row from column col starts)
1650pub(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
1655// the cluster that holds column col, counted from boundary i at column c0 <= col -> (its start,
1656// its column, its end, its columns); past the end of t: (t.len(), the end's column, t.len(), 0)
1657fn 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        // (ASCII up to the column at most)
1663        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