tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 614Markdown 274TOML 149RON 310606 Definiciones

⎇ main widgets/text-editor-core/src/bracket.rs Rust · 166 líneas · 4.9 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1//! Matching de paréntesis/corchetes/llaves bajo el cursor.
2//!
3//! Si el carácter inmediatamente *antes* o *en* el caret es un bracket
4//! abridor o cerrador, busca su par contando profundidad y devuelve las
5//! dos posiciones. Útil para el visor (resaltar ambas).
6//!
7//! Restricciones del PMV: no diferencia brackets dentro de strings ni
8//! comentarios — el tokenizer del bloque de highlight (tree-sitter) lo
9//! resolverá mejor en una pasada futura. Para WAT/JSON/Lisp esto basta.
10
11
12use crate::buffer::Buffer;
13use crate::cursor::{Cursor, Pos};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Direction {
17    Forward,
18    Backward,
19}
20
21/// Pares reconocidos.
22const PAIRS: &[(char, char)] = &[('(', ')'), ('[', ']'), ('{', '}')];
23
24fn pair_of(c: char) -> Option<(char, char, Direction)> {
25    for &(o, cl) in PAIRS {
26        if c == o {
27            return Some((o, cl, Direction::Forward));
28        }
29        if c == cl {
30            return Some((o, cl, Direction::Backward));
31        }
32    }
33    None
34}
35
36/// Si el caret toca un bracket, devuelve `(pos_del_bracket, pos_del_par)`.
37pub fn find_bracket_pair(buf: &Buffer, cursor: &Cursor) -> Option<(Pos, Pos)> {
38    let caret_off = buf.pos_to_offset(cursor.caret.line, cursor.caret.col);
39
40    // Probamos en `caret` y `caret-1` — un caret "entre" dos chars puede
41    // tocar al de la izquierda visualmente.
42    let candidates: [Option<usize>; 2] = [
43        Some(caret_off).filter(|&o| o < buf.len_chars()),
44        caret_off.checked_sub(1),
45    ];
46
47    for opt in candidates {
48        let Some(off) = opt else { continue };
49        let Some(ch) = buf.char_at(off) else { continue };
50        let Some((open, close, dir)) = pair_of(ch) else { continue };
51        let mate = match dir {
52            Direction::Forward => find_forward(buf, off + 1, open, close),
53            Direction::Backward => find_backward(buf, off, open, close),
54        };
55        if let Some(mate_off) = mate {
56            let a = buf.offset_to_pos(off);
57            let b = buf.offset_to_pos(mate_off);
58            return Some((Pos::new(a.0, a.1), Pos::new(b.0, b.1)));
59        }
60    }
61    None
62}
63
64fn find_forward(buf: &Buffer, from: usize, open: char, close: char) -> Option<usize> {
65    let mut depth = 1usize;
66    let mut off = from;
67    let len = buf.len_chars();
68    while off < len {
69        match buf.char_at(off) {
70            Some(c) if c == open => depth += 1,
71            Some(c) if c == close => {
72                depth -= 1;
73                if depth == 0 {
74                    return Some(off);
75                }
76            }
77            _ => {}
78        }
79        off += 1;
80    }
81    None
82}
83
84fn find_backward(buf: &Buffer, before: usize, open: char, close: char) -> Option<usize> {
85    if before == 0 {
86        return None;
87    }
88    let mut depth = 1usize;
89    let mut off = before;
90    while off > 0 {
91        off -= 1;
92        match buf.char_at(off) {
93            Some(c) if c == close => depth += 1,
94            Some(c) if c == open => {
95                depth -= 1;
96                if depth == 0 {
97                    return Some(off);
98                }
99            }
100            _ => {}
101        }
102    }
103    None
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn empareja_paren_simple() {
112        let b = Buffer::from_str("(a)");
113        let c = Cursor::at(0, 0); // caret antes del '('
114        let (a, m) = find_bracket_pair(&b, &c).unwrap();
115        assert_eq!(a, Pos::new(0, 0));
116        assert_eq!(m, Pos::new(0, 2));
117    }
118
119    #[test]
120    fn empareja_desde_el_lado_derecho() {
121        let b = Buffer::from_str("(a)");
122        let c = Cursor::at(0, 3); // caret después del ')'
123        let (a, m) = find_bracket_pair(&b, &c).unwrap();
124        assert_eq!(a, Pos::new(0, 2)); // ')'
125        assert_eq!(m, Pos::new(0, 0)); // '('
126    }
127
128    #[test]
129    fn anidados_respeta_profundidad() {
130        let b = Buffer::from_str("((a))");
131        let c = Cursor::at(0, 0); // primer '('
132        let (_, m) = find_bracket_pair(&b, &c).unwrap();
133        assert_eq!(m, Pos::new(0, 4)); // último ')'
134    }
135
136    #[test]
137    fn empareja_brackets_y_llaves() {
138        let b = Buffer::from_str("[a]");
139        assert!(find_bracket_pair(&b, &Cursor::at(0, 0)).is_some());
140
141        let b2 = Buffer::from_str("{a}");
142        assert!(find_bracket_pair(&b2, &Cursor::at(0, 0)).is_some());
143    }
144
145    #[test]
146    fn caret_lejos_de_bracket_devuelve_none() {
147        let b = Buffer::from_str("hola");
148        let c = Cursor::at(0, 2);
149        assert!(find_bracket_pair(&b, &c).is_none());
150    }
151
152    #[test]
153    fn bracket_sin_par_devuelve_none() {
154        let b = Buffer::from_str("(a");
155        let c = Cursor::at(0, 0);
156        assert!(find_bracket_pair(&b, &c).is_none());
157    }
158
159    #[test]
160    fn multilinea_pasa_saltos() {
161        let b = Buffer::from_str("(\n  a\n)");
162        let c = Cursor::at(0, 0);
163        let (_, m) = find_bracket_pair(&b, &c).unwrap();
164        assert_eq!(m, Pos::new(2, 0));
165    }
166}