Skip to main content

rgx/
app.rs

1use crate::engine::{self, CompiledRegex, EngineFlags, EngineKind, RegexEngine};
2use crate::explain::{self, ExplainNode};
3use crate::input::editor::Editor;
4
5pub struct App {
6    pub regex_editor: Editor,
7    pub test_editor: Editor,
8    pub replace_editor: Editor,
9    pub focused_panel: u8, // 0=regex, 1=test, 2=replace, 3=matches, 4=explanation
10    pub engine_kind: EngineKind,
11    pub flags: EngineFlags,
12    pub matches: Vec<engine::Match>,
13    pub replace_result: Option<engine::ReplaceResult>,
14    pub explanation: Vec<ExplainNode>,
15    pub error: Option<String>,
16    pub show_help: bool,
17    pub should_quit: bool,
18    pub match_scroll: u16,
19    pub replace_scroll: u16,
20    pub explain_scroll: u16,
21    engine: Box<dyn RegexEngine>,
22    compiled: Option<Box<dyn CompiledRegex>>,
23}
24
25impl App {
26    pub fn new(engine_kind: EngineKind, flags: EngineFlags) -> Self {
27        let engine = engine::create_engine(engine_kind);
28        Self {
29            regex_editor: Editor::new(),
30            test_editor: Editor::new(),
31            replace_editor: Editor::new(),
32            focused_panel: 0,
33            engine_kind,
34            flags,
35            matches: Vec::new(),
36            replace_result: None,
37            explanation: Vec::new(),
38            error: None,
39            show_help: false,
40            should_quit: false,
41            match_scroll: 0,
42            replace_scroll: 0,
43            explain_scroll: 0,
44            engine,
45            compiled: None,
46        }
47    }
48
49    pub fn set_replacement(&mut self, text: &str) {
50        self.replace_editor = Editor::with_content(text.to_string());
51        self.rereplace();
52    }
53
54    pub fn scroll_replace_up(&mut self) {
55        self.replace_scroll = self.replace_scroll.saturating_sub(1);
56    }
57
58    pub fn scroll_replace_down(&mut self) {
59        self.replace_scroll = self.replace_scroll.saturating_add(1);
60    }
61
62    pub fn rereplace(&mut self) {
63        let template = self.replace_editor.content().to_string();
64        if template.is_empty() || self.matches.is_empty() {
65            self.replace_result = None;
66            return;
67        }
68        let text = self.test_editor.content().to_string();
69        self.replace_result = Some(engine::replace_all(&text, &self.matches, &template));
70    }
71
72    pub fn set_pattern(&mut self, pattern: &str) {
73        self.regex_editor = Editor::with_content(pattern.to_string());
74        self.recompute();
75    }
76
77    pub fn set_test_string(&mut self, text: &str) {
78        self.test_editor = Editor::with_content(text.to_string());
79        self.rematch();
80    }
81
82    pub fn switch_engine(&mut self) {
83        self.engine_kind = self.engine_kind.next();
84        self.engine = engine::create_engine(self.engine_kind);
85        self.recompute();
86    }
87
88    pub fn scroll_match_up(&mut self) {
89        self.match_scroll = self.match_scroll.saturating_sub(1);
90    }
91
92    pub fn scroll_match_down(&mut self) {
93        self.match_scroll = self.match_scroll.saturating_add(1);
94    }
95
96    pub fn scroll_explain_up(&mut self) {
97        self.explain_scroll = self.explain_scroll.saturating_sub(1);
98    }
99
100    pub fn scroll_explain_down(&mut self) {
101        self.explain_scroll = self.explain_scroll.saturating_add(1);
102    }
103
104    pub fn recompute(&mut self) {
105        let pattern = self.regex_editor.content().to_string();
106        self.match_scroll = 0;
107        self.explain_scroll = 0;
108
109        if pattern.is_empty() {
110            self.compiled = None;
111            self.matches.clear();
112            self.explanation.clear();
113            self.error = None;
114            return;
115        }
116
117        // Compile
118        match self.engine.compile(&pattern, &self.flags) {
119            Ok(compiled) => {
120                self.compiled = Some(compiled);
121                self.error = None;
122            }
123            Err(e) => {
124                self.compiled = None;
125                self.matches.clear();
126                self.error = Some(e.to_string());
127            }
128        }
129
130        // Explain (uses regex-syntax, independent of engine)
131        match explain::explain(&pattern) {
132            Ok(nodes) => self.explanation = nodes,
133            Err(e) => {
134                self.explanation.clear();
135                if self.error.is_none() {
136                    self.error = Some(e);
137                }
138            }
139        }
140
141        // Match
142        self.rematch();
143    }
144
145    pub fn rematch(&mut self) {
146        self.match_scroll = 0;
147        if let Some(compiled) = &self.compiled {
148            let text = self.test_editor.content().to_string();
149            if text.is_empty() {
150                self.matches.clear();
151                self.replace_result = None;
152                return;
153            }
154            match compiled.find_matches(&text) {
155                Ok(m) => self.matches = m,
156                Err(e) => {
157                    self.matches.clear();
158                    self.error = Some(e.to_string());
159                }
160            }
161        } else {
162            self.matches.clear();
163        }
164        self.rereplace();
165    }
166}