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 = (
45 self.config.index.sort.clone(),
46 self.config.index.sort_aux.clone(),
47 );
48 for cmd in commands {
49 let outcome = match &cmd {
50 command::Command::Bind { .. }
51 | command::Command::Macro { .. }
52 | command::Command::Push(_)
53 | command::Command::Exec(_) => {
54 self.requests.push(Request::Command(cmd));
56 continue;
57 }
58 command::Command::Alias { nick, expansion } => self.alias_command(nick, expansion),
59 command::Command::Unalias(nicks) => self.unalias_command(nicks),
60 command::Command::Unmailboxes(_) => {
61 let outcome = command::apply(&mut self.config, &cmd);
62 self.requests.push(Request::MailboxesChanged);
64 outcome
65 }
66 config_command => command::apply(&mut self.config, config_command),
67 };
68 match outcome {
69 Ok(Some(text)) => run.reports.push(text),
70 Ok(None) => {}
71 Err(err) => {
72 self.error(err);
73 return run;
74 }
75 }
76 }
77 self.config.expand_folders();
79 run.warnings = self.recompile();
80 if sort_before
82 != (
83 self.config.index.sort.clone(),
84 self.config.index.sort_aux.clone(),
85 )
86 {
87 if let Some(spec) = self.config.index.sort.clone()
88 && let Some((sort, rev)) = crate::parse_sort(&spec)
89 {
90 self.sort = sort;
91 self.sort_rev = rev;
92 }
93 self.apply_sort();
94 }
95 self.requests.push(Request::ConfigChanged);
96 run
97 }
98
99 fn alias_command(&mut self, nick: &str, expansion: &str) -> Result<Option<String>, String> {
101 if nick.contains(char::is_whitespace) {
102 return Err("the alias nick must be one word".into());
103 }
104 match rmut_core::alias::append_to(self.config.mail.alias_file.as_deref(), nick, expansion) {
105 Ok(_) => Ok(Some(format!("added: alias {nick} {expansion}"))),
106 Err(err) => Err(format!("cannot save the alias: {err:#}")),
107 }
108 }
109
110 fn unalias_command(&mut self, nicks: &[String]) -> Result<Option<String>, String> {
112 match rmut_core::alias::remove_from(self.config.mail.alias_file.as_deref(), nicks) {
113 Ok(0) => Ok(Some("no such alias".into())),
114 Ok(n) => Ok(Some(format!("removed {n} alias(es)"))),
115 Err(err) => Err(format!("cannot rewrite the alias file: {err:#}")),
116 }
117 }
118
119 fn run_hook(&mut self, what: &str, line: &str) {
122 self.clear_notice();
123 let run = self.run_command_line(line);
124 if let Some(err) = self.notice().filter(|n| n.is_error()).map(|n| n.text()) {
125 self.error(format!("{what}: {err}"));
126 } else if !run.warnings.is_empty() {
127 self.error(format!("{what}: {}", run.warnings.join("; ")));
128 }
129 }
130
131 pub fn run_folder_hooks(&mut self) {
134 if self.config.folder_hooks.is_empty() {
135 return;
136 }
137 let title = self.title.clone();
138 let lines: Vec<String> = self
139 .config
140 .folder_hooks
141 .iter()
142 .filter(|h| rmut_core::config::glob_match(&h.folder, &title))
143 .map(|h| h.command.clone())
144 .collect();
145 for line in lines {
146 self.run_hook("folder-hook", &line);
147 }
148 }
149
150 pub fn sync_message_hooks(&mut self) {
155 if self.message_hooks.is_empty() && self.active_message_hooks.is_empty() {
156 return;
157 }
158 let matching = self.matching_message_hooks();
159 if matching == self.active_message_hooks {
160 return;
161 }
162 self.active_message_hooks = matching.clone();
163 self.restore_hook_base();
166 if matching.is_empty() {
167 return;
168 }
169 self.hook_base = Some(Box::new(self.config.clone()));
170 for i in matching {
171 let Some(line) = self.message_hooks.get(i).map(|h| h.value.clone()) else {
172 continue;
173 };
174 self.run_hook("message-hook", &line);
175 }
176 }
177
178 pub fn clear_message_hooks(&mut self) {
181 self.active_message_hooks.clear();
182 self.restore_hook_base();
183 }
184
185 fn restore_hook_base(&mut self) {
186 let Some(base) = self.hook_base.take() else {
187 return;
188 };
189 self.config = *base;
190 let warnings = self.recompile();
191 if !warnings.is_empty() {
192 self.error(warnings.join("; "));
193 }
194 self.requests.push(Request::ConfigChanged);
195 }
196
197 pub fn apply_reply_hooks(
202 &mut self,
203 base: Option<&ComposeBase>,
204 kind: ComposeKind,
205 ) -> Option<Box<Config>> {
206 if self.reply_hooks.is_empty()
207 || !matches!(
208 kind,
209 ComposeKind::Reply | ComposeKind::GroupReply | ComposeKind::ListReply
210 )
211 {
212 return None;
213 }
214 let lines = self.reply_hook_lines(&base?.path);
215 if lines.is_empty() {
216 return None;
217 }
218 let saved = Box::new(self.config.clone());
219 for line in lines {
220 self.run_hook("reply-hook", &line);
221 }
222 Some(saved)
223 }
224
225 pub fn restore_after_reply_hooks(&mut self, saved: Option<Box<Config>>) {
227 let Some(saved) = saved else { return };
228 self.config = *saved;
229 let warnings = self.recompile();
230 if !warnings.is_empty() {
231 self.error(warnings.join("; "));
232 }
233 self.requests.push(Request::ConfigChanged);
234 }
235}