1use rmut_core::command;
11use rmut_core::config::Config;
12
13use crate::{ComposeBase, ComposeKind, Request, Session};
14
15#[derive(Default)]
17pub struct CommandRun {
18 pub reports: Vec<String>,
21 pub warnings: Vec<String>,
23}
24
25impl Session {
26 pub fn run_command_line(&mut self, line: &str) -> CommandRun {
33 let mut run = CommandRun::default();
34 let commands = match command::parse(line) {
35 Ok(commands) => commands,
36 Err(err) => {
37 self.error(err);
38 return run;
39 }
40 };
41 if commands.is_empty() {
42 return run;
43 }
44 let sort_before = (
47 self.config.index.sort.clone(),
48 self.config.index.sort_aux.clone(),
49 self.config.index.strict_threads,
50 self.config.index.sort_re,
51 self.config.mail.reply_regexp.clone(),
52 );
53 for cmd in commands {
54 let outcome = match &cmd {
55 command::Command::Bind { .. }
56 | command::Command::Macro { .. }
57 | command::Command::Push(_)
58 | command::Command::Exec(_) => {
59 self.requests.push(Request::Command(cmd));
61 continue;
62 }
63 command::Command::Alias { nick, expansion } => self.alias_command(nick, expansion),
64 config_command => command::apply(&mut self.config, config_command),
65 };
66 match outcome {
67 Ok(Some(text)) => run.reports.push(text),
68 Ok(None) => {}
69 Err(err) => {
70 self.error(err);
71 return run;
72 }
73 }
74 }
75 self.config.expand_folders();
77 run.warnings = self.recompile();
78 if sort_before
80 != (
81 self.config.index.sort.clone(),
82 self.config.index.sort_aux.clone(),
83 self.config.index.strict_threads,
84 self.config.index.sort_re,
85 self.config.mail.reply_regexp.clone(),
86 )
87 {
88 if let Some(spec) = self.config.index.sort.clone()
89 && let Some((sort, rev)) = crate::parse_sort(&spec)
90 {
91 self.sort = sort;
92 self.sort_rev = rev;
93 }
94 self.apply_sort();
95 }
96 self.requests.push(Request::ConfigChanged);
97 run
98 }
99
100 fn alias_command(&mut self, nick: &str, expansion: &str) -> Result<Option<String>, String> {
102 if nick.contains(char::is_whitespace) {
103 return Err("the alias nick must be one word".into());
104 }
105 match rmut_core::alias::append_to(self.config.mail.alias_file.as_deref(), nick, expansion) {
106 Ok(_) => Ok(Some(format!("added: alias {nick} {expansion}"))),
107 Err(err) => Err(format!("cannot save the alias: {err:#}")),
108 }
109 }
110
111 fn run_hook(&mut self, what: &str, line: &str) {
114 self.clear_notice();
115 let run = self.run_command_line(line);
116 if let Some(err) = self.notice().filter(|n| n.is_error()).map(|n| n.text()) {
117 self.error(format!("{what}: {err}"));
118 } else if !run.warnings.is_empty() {
119 self.error(format!("{what}: {}", run.warnings.join("; ")));
120 }
121 }
122
123 pub fn run_folder_hooks(&mut self) {
126 if self.config.folder_hooks.is_empty() {
127 return;
128 }
129 let title = self.title.clone();
130 let lines: Vec<String> = self
131 .config
132 .folder_hooks
133 .iter()
134 .filter(|h| rmut_core::config::glob_match(&h.folder, &title))
135 .map(|h| h.command.clone())
136 .collect();
137 for line in lines {
138 self.run_hook("folder-hook", &line);
139 }
140 }
141
142 pub fn sync_message_hooks(&mut self) {
147 if self.message_hooks.is_empty() && self.active_message_hooks.is_empty() {
148 return;
149 }
150 let matching = self.matching_message_hooks();
151 if matching == self.active_message_hooks {
152 return;
153 }
154 self.active_message_hooks = matching.clone();
155 self.restore_hook_base();
158 if matching.is_empty() {
159 return;
160 }
161 self.hook_base = Some(Box::new(self.config.clone()));
162 for i in matching {
163 let Some(line) = self.message_hooks.get(i).map(|h| h.value.clone()) else {
164 continue;
165 };
166 self.run_hook("message-hook", &line);
167 }
168 }
169
170 pub fn clear_message_hooks(&mut self) {
173 self.active_message_hooks.clear();
174 self.restore_hook_base();
175 }
176
177 fn restore_hook_base(&mut self) {
178 let Some(base) = self.hook_base.take() else {
179 return;
180 };
181 self.config = *base;
182 let warnings = self.recompile();
183 if !warnings.is_empty() {
184 self.error(warnings.join("; "));
185 }
186 self.requests.push(Request::ConfigChanged);
187 }
188
189 pub fn apply_reply_hooks(
194 &mut self,
195 base: Option<&ComposeBase>,
196 kind: ComposeKind,
197 ) -> Option<Box<Config>> {
198 if self.reply_hooks.is_empty()
199 || !matches!(
200 kind,
201 ComposeKind::Reply | ComposeKind::GroupReply | ComposeKind::ListReply
202 )
203 {
204 return None;
205 }
206 let lines = self.reply_hook_lines(&base?.path);
207 if lines.is_empty() {
208 return None;
209 }
210 let saved = Box::new(self.config.clone());
211 for line in lines {
212 self.run_hook("reply-hook", &line);
213 }
214 Some(saved)
215 }
216
217 pub fn restore_after_reply_hooks(&mut self, saved: Option<Box<Config>>) {
219 let Some(saved) = saved else { return };
220 self.config = *saved;
221 let warnings = self.recompile();
222 if !warnings.is_empty() {
223 self.error(warnings.join("; "));
224 }
225 self.requests.push(Request::ConfigChanged);
226 }
227}