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 command::Command::Unalias(nicks) => self.unalias_command(nicks),
65 command::Command::Unmailboxes(_) => {
66 let outcome = command::apply(&mut self.config, &cmd);
67 self.requests.push(Request::MailboxesChanged);
69 outcome
70 }
71 config_command => command::apply(&mut self.config, config_command),
72 };
73 match outcome {
74 Ok(Some(text)) => run.reports.push(text),
75 Ok(None) => {}
76 Err(err) => {
77 self.error(err);
78 return run;
79 }
80 }
81 }
82 self.config.expand_folders();
84 run.warnings = self.recompile();
85 if sort_before
87 != (
88 self.config.index.sort.clone(),
89 self.config.index.sort_aux.clone(),
90 self.config.index.strict_threads,
91 self.config.index.sort_re,
92 self.config.mail.reply_regexp.clone(),
93 )
94 {
95 if let Some(spec) = self.config.index.sort.clone()
96 && let Some((sort, rev)) = crate::parse_sort(&spec)
97 {
98 self.sort = sort;
99 self.sort_rev = rev;
100 }
101 self.apply_sort();
102 }
103 self.requests.push(Request::ConfigChanged);
104 run
105 }
106
107 fn alias_command(&mut self, nick: &str, expansion: &str) -> Result<Option<String>, String> {
109 if nick.contains(char::is_whitespace) {
110 return Err("the alias nick must be one word".into());
111 }
112 match rmut_core::alias::append_to(self.config.mail.alias_file.as_deref(), nick, expansion) {
113 Ok(_) => Ok(Some(format!("added: alias {nick} {expansion}"))),
114 Err(err) => Err(format!("cannot save the alias: {err:#}")),
115 }
116 }
117
118 fn unalias_command(&mut self, nicks: &[String]) -> Result<Option<String>, String> {
120 match rmut_core::alias::remove_from(self.config.mail.alias_file.as_deref(), nicks) {
121 Ok(0) => Ok(Some("no such alias".into())),
122 Ok(n) => Ok(Some(format!("removed {n} alias(es)"))),
123 Err(err) => Err(format!("cannot rewrite the alias file: {err:#}")),
124 }
125 }
126
127 fn run_hook(&mut self, what: &str, line: &str) {
130 self.clear_notice();
131 let run = self.run_command_line(line);
132 if let Some(err) = self.notice().filter(|n| n.is_error()).map(|n| n.text()) {
133 self.error(format!("{what}: {err}"));
134 } else if !run.warnings.is_empty() {
135 self.error(format!("{what}: {}", run.warnings.join("; ")));
136 }
137 }
138
139 pub fn run_folder_hooks(&mut self) {
142 if self.config.folder_hooks.is_empty() {
143 return;
144 }
145 let title = self.title.clone();
146 let lines: Vec<String> = self
147 .config
148 .folder_hooks
149 .iter()
150 .filter(|h| rmut_core::config::glob_match(&h.folder, &title))
151 .map(|h| h.command.clone())
152 .collect();
153 for line in lines {
154 self.run_hook("folder-hook", &line);
155 }
156 }
157
158 pub fn sync_message_hooks(&mut self) {
163 if self.message_hooks.is_empty() && self.active_message_hooks.is_empty() {
164 return;
165 }
166 let matching = self.matching_message_hooks();
167 if matching == self.active_message_hooks {
168 return;
169 }
170 self.active_message_hooks = matching.clone();
171 self.restore_hook_base();
174 if matching.is_empty() {
175 return;
176 }
177 self.hook_base = Some(Box::new(self.config.clone()));
178 for i in matching {
179 let Some(line) = self.message_hooks.get(i).map(|h| h.value.clone()) else {
180 continue;
181 };
182 self.run_hook("message-hook", &line);
183 }
184 }
185
186 pub fn clear_message_hooks(&mut self) {
189 self.active_message_hooks.clear();
190 self.restore_hook_base();
191 }
192
193 fn restore_hook_base(&mut self) {
194 let Some(base) = self.hook_base.take() else {
195 return;
196 };
197 self.config = *base;
198 let warnings = self.recompile();
199 if !warnings.is_empty() {
200 self.error(warnings.join("; "));
201 }
202 self.requests.push(Request::ConfigChanged);
203 }
204
205 pub fn apply_reply_hooks(
210 &mut self,
211 base: Option<&ComposeBase>,
212 kind: ComposeKind,
213 ) -> Option<Box<Config>> {
214 if self.reply_hooks.is_empty()
215 || !matches!(
216 kind,
217 ComposeKind::Reply | ComposeKind::GroupReply | ComposeKind::ListReply
218 )
219 {
220 return None;
221 }
222 let lines = self.reply_hook_lines(&base?.path);
223 if lines.is_empty() {
224 return None;
225 }
226 let saved = Box::new(self.config.clone());
227 for line in lines {
228 self.run_hook("reply-hook", &line);
229 }
230 Some(saved)
231 }
232
233 pub fn restore_after_reply_hooks(&mut self, saved: Option<Box<Config>>) {
235 let Some(saved) = saved else { return };
236 self.config = *saved;
237 let warnings = self.recompile();
238 if !warnings.is_empty() {
239 self.error(warnings.join("; "));
240 }
241 self.requests.push(Request::ConfigChanged);
242 }
243}