1use std::{
164 collections::HashMap,
165 fmt::Display,
166 ops::Range,
167 sync::{Arc, LazyLock},
168};
169
170use crossterm::style::Color;
171
172pub use self::{global::*, parameters::*};
173use crate::{
174 buffer::{Buffer, PathKind},
175 context::{self, Handle, sender},
176 data::{Pass, RwData},
177 form::FormId,
178 mode,
179 session::DuatEvent,
180 text::{Text, txt},
181 ui::{Node, Widget},
182};
183
184mod parameters;
185
186pub(crate) fn add_session_commands() {
188 add!("alias", |pa, alias: &str, command: Remainder| {
189 crate::cmd::alias(pa, alias, command)
190 });
191
192 add!(["write", "w"], |pa, path: Option<ValidBuffer>| {
193 let handle = context::current_buffer(pa).clone();
194 let buffer = handle.write(pa);
195
196 let (bytes, name) = if let Some(path) = path {
197 (buffer.save_to(&path)?, path)
198 } else if let Some(name) = buffer.name_set() {
199 (buffer.save()?, std::path::PathBuf::from(name))
200 } else {
201 return Err(txt!("Buffer has no name path to write to"));
202 };
203
204 match bytes {
205 Some(bytes) => Ok(Some(txt!("Wrote [a]{bytes}[] bytes to [buffer]{name}"))),
206 None => Ok(Some(txt!("Nothing to be written"))),
207 }
208 });
209
210 add!(["write-quit", "wq"], |pa, path: Option<ValidBuffer>| {
211 let handle = context::current_buffer(pa).clone();
212
213 let (bytes, name) = {
214 let buffer = handle.write(pa);
215 let bytes = if let Some(path) = path {
216 buffer.save_quit_to(path, true)?
217 } else {
218 buffer.save_quit(true)?
219 };
220 (bytes, buffer.name())
221 };
222
223 context::windows().close(pa, &handle)?;
224
225 match bytes {
226 Some(bytes) => Ok(Some(txt!(
227 "Closed [buffer]{name}[], writing [a]{bytes}[] bytes"
228 ))),
229 None => Ok(Some(txt!("Closed [buffer]{name}[]"))),
230 }
231 });
232
233 add!(["write-all", "wa"], |pa| {
234 let windows = context::windows();
235
236 let mut written = 0;
237 let handles: Vec<_> = windows
238 .buffers(pa)
239 .filter(|handle| handle.read(pa).path_set().is_some())
240 .collect();
241
242 for handle in &handles {
243 written += handle.write(pa).save().is_ok() as usize;
244 }
245
246 if written == handles.len() {
247 Ok(Some(txt!("Wrote to [a]{written}[] buffers")))
248 } else {
249 let unwritten = handles.len() - written;
250 let plural = if unwritten == 1 { "" } else { "s" };
251 Err(txt!("Failed to write to [a]{unwritten}[] buffer{plural}"))
252 }
253 });
254
255 add!(["write-all-quit", "waq"], |pa| {
256 let windows = context::windows();
257
258 let mut written = 0;
259 let handles: Vec<_> = windows
260 .buffers(pa)
261 .filter(|handle| handle.read(pa).path_set().is_some())
262 .collect();
263 for handle in &handles {
264 written += handle.write(pa).save_quit(true).is_ok() as usize;
265 }
266
267 if written == handles.len() {
268 sender().send(DuatEvent::Quit).unwrap();
269 Ok(None)
270 } else {
271 let unwritten = handles.len() - written;
272 let plural = if unwritten == 1 { "" } else { "s" };
273 Err(txt!("Failed to write to [a]{unwritten}[] buffer{plural}"))
274 }
275 });
276
277 add!(["write-all-quit!", "waq!"], |pa| {
278 let handles: Vec<_> = context::windows().buffers(pa).collect();
279
280 for handle in handles {
281 let _ = handle.write(pa).save_quit(true);
282 }
283
284 sender().send(DuatEvent::Quit).unwrap();
285 Ok(None)
286 });
287
288 add!(["quit", "q"], |pa, handle: Option<Buffer>| {
289 let handle = match handle {
290 Some(handle) => handle,
291 None => context::current_buffer(pa).clone(),
292 };
293
294 let buffer = handle.read(pa);
295 if buffer.text().has_unsaved_changes() && buffer.exists() {
296 return Err(txt!("{} has unsaved changes", buffer.name()));
297 }
298
299 context::windows().close(pa, &handle)?;
300
301 Ok(Some(txt!("Closed [buffer]{}", handle.read(pa).name())))
302 });
303
304 add!(["quit!", "q!"], |pa, handle: Option<Buffer>| {
305 let handle = match handle {
306 Some(handle) => handle,
307 None => context::current_buffer(pa).clone(),
308 };
309
310 context::windows().close(pa, &handle)?;
311
312 Ok(Some(txt!("Forcefully closed {}", handle.read(pa).name())))
313 });
314
315 add!(["quit-all", "qa"], |pa| {
316 let windows = context::windows();
317 let unwritten = windows
318 .buffers(pa)
319 .filter(|handle| {
320 let buffer = handle.read(pa);
321 buffer.text().has_unsaved_changes() && buffer.exists()
322 })
323 .count();
324
325 if unwritten == 0 {
326 sender().send(DuatEvent::Quit).unwrap();
327 Ok(None)
328 } else if unwritten == 1 {
329 Err(txt!("There is [a]1[] unsaved buffer"))
330 } else {
331 Err(txt!("There are [a]{unwritten}[] unsaved buffers"))
332 }
333 });
334
335 add!(["quit-all!", "qa!"], |_pa| {
336 sender().send(DuatEvent::Quit).unwrap();
337 Ok(None)
338 });
339
340 add!(["reload"], |_pa, flags: Flags, profile: Option<String>| {
341 sender()
342 .send(DuatEvent::RequestReload(crate::session::ReloadEvent {
343 clean: flags.word("clean"),
344 update: flags.word("update"),
345 profile: profile.unwrap_or(crate::utils::profile().to_string()),
346 }))
347 .unwrap();
348
349 #[cfg(target_os = "windows")]
354 sender().send(DuatEvent::ReloadSucceeded).unwrap();
355
356 Ok(None)
357 });
358
359 add!(["edit", "e"], |pa, arg: PathOrBufferOrCfg| {
360 let windows = context::windows();
361
362 let pk = match arg {
363 PathOrBufferOrCfg::Cfg => {
364 PathKind::from(crate::utils::crate_dir()?.join("src").join("lib.rs"))
365 }
366 PathOrBufferOrCfg::CfgManifest => {
367 PathKind::from(crate::utils::crate_dir()?.join("Cargo.toml"))
368 }
369 PathOrBufferOrCfg::Path(path) => PathKind::from(path),
370 PathOrBufferOrCfg::Buffer(handle) => {
371 mode::reset_to(handle.to_dyn());
372 return Ok(Some(txt!("Switched to {}", handle.read(pa).name())));
373 }
374 };
375
376 let buffer = Buffer::new(pk.as_path(), *crate::session::FILE_CFG.get().unwrap());
377 let handle = windows.new_buffer(pa, buffer);
378 context::set_current_node(pa, handle);
379
380 return Ok(Some(txt!("Opened {pk}")));
381 });
382
383 add!(["open", "o"], |pa, arg: PathOrBufferOrCfg| {
384 let windows = context::windows();
385
386 let (pk, msg) = match arg {
387 PathOrBufferOrCfg::Cfg => (
388 PathKind::from(crate::utils::crate_dir()?.join("src").join("lib.rs")),
389 None,
390 ),
391 PathOrBufferOrCfg::CfgManifest => (
392 PathKind::from(crate::utils::crate_dir()?.join("Cargo.toml")),
393 None,
394 ),
395 PathOrBufferOrCfg::Path(path) => (PathKind::from(path), None),
396 PathOrBufferOrCfg::Buffer(handle) => {
397 let pk = handle.read(pa).path_kind();
398 let (win, ..) = windows.buffer_entry(pa, pk.clone()).unwrap();
399 if windows.get(pa, win).unwrap().buffers(pa).len() == 1 {
400 (pk.clone(), Some(txt!("Switched to {pk}")))
401 } else {
402 (pk.clone(), Some(txt!("Moved {pk} to a new window")))
403 }
404 }
405 };
406
407 let file_cfg = *crate::session::FILE_CFG.get().unwrap();
408 let node = windows.open_or_move_to_new_window(pa, pk.clone(), file_cfg);
409
410 return Ok(msg.or_else(|| Some(txt!("Opened {pk} on new window"))));
411 });
412
413 add!(["buffer", "b"], |pa, handle: OtherBuffer| {
414 mode::reset_to(handle.to_dyn());
415 Ok(Some(txt!("Switched to [buffer]{}", handle.read(pa).name())))
416 });
417
418 add!("next-buffer", |pa, flags: Flags| {
419 let windows = context::windows();
420 let handle = context::current_buffer(pa);
421 let win = context::current_win_index(pa);
422
423 let wid = windows
424 .get(pa, win)
425 .unwrap()
426 .nodes(pa)
427 .position(|node| handle.ptr_eq(node.widget()))
428 .unwrap_or_else(|| panic!("{}, {win}", handle.read(pa).name()));
429
430 let handle = if flags.word("global") {
431 windows
432 .iter_around(pa, win, wid)
433 .find_map(as_buffer_handle)
434 .ok_or_else(|| txt!("There are no other open buffers"))?
435 } else {
436 windows
437 .iter_around(pa, win, wid)
438 .filter(|(lhs, ..)| *lhs == win)
439 .find_map(as_buffer_handle)
440 .ok_or_else(|| txt!("There are no other buffers open in this window"))?
441 };
442
443 mode::reset_to(handle.to_dyn());
444 Ok(Some(txt!("Switched to [buffer]{}", handle.read(pa).name())))
445 });
446
447 add!("prev-buffer", |pa, flags: Flags| {
448 let windows = context::windows();
449 let handle = context::current_buffer(pa);
450 let win = context::current_win_index(pa);
451
452 let wid = windows
453 .get(pa, win)
454 .unwrap()
455 .nodes(pa)
456 .position(|node| handle.ptr_eq(node.widget()))
457 .unwrap();
458
459 let handle = if flags.word("global") {
460 windows
461 .iter_around_rev(pa, win, wid)
462 .find_map(as_buffer_handle)
463 .ok_or_else(|| txt!("There are no other open buffers"))?
464 } else {
465 windows
466 .iter_around(pa, win, wid)
467 .filter(|(lhs, ..)| *lhs == win)
468 .find_map(as_buffer_handle)
469 .ok_or_else(|| txt!("There are no other buffers open in this window"))?
470 };
471
472 mode::reset_to(handle.to_dyn());
473 Ok(Some(txt!("Switched to [buffer]{}", handle.read(pa).name())))
474 });
475
476 add!("last-buffer", |pa| {
477 let handle = context::windows().last_buffer(pa)?;
478 Ok(Some(txt!("Switched to [buffer]{}", handle.read(pa).name())))
479 });
480
481 add!("swap", |pa, lhs: Buffer, rhs: Option<Buffer>| {
482 let rhs = rhs.unwrap_or_else(|| context::current_buffer(pa).clone());
483
484 context::windows().swap(pa, &lhs.to_dyn(), &rhs.to_dyn())?;
485
486 Ok(Some(txt!(
487 "Swapped {} and {}",
488 lhs.read(pa).name(),
489 rhs.read(pa).name()
490 )))
491 });
492
493 add!("colorscheme", |_pa, scheme: ColorSchemeArg| {
494 crate::form::set_colorscheme(scheme);
495 Ok(Some(txt!("Set colorscheme to [a]{scheme}[]")))
496 });
497
498 add!(
499 "set-form",
500 |_pa, name: FormName, colors: Between<0, 3, Color>| {
501 let mut form = crate::form::Form::new();
502 form.style.foreground_color = colors.first().cloned();
503 form.style.background_color = colors.get(1).cloned();
504 form.style.underline_color = colors.get(2).cloned();
505 crate::form::set(name, form);
506
507 Ok(Some(txt!("Set [a]{name}[] to a new Form")))
508 }
509 );
510}
511
512mod global {
513 use std::ops::Range;
514
515 use super::{CheckerFn, CmdFn, CmdResult, Commands};
516 #[doc(inline)]
517 pub use crate::__add__ as add;
518 use crate::{
519 context, data::Pass, form::FormId, main_thread_only::MainThreadOnly, session::DuatEvent,
520 text::Text,
521 };
522
523 static COMMANDS: MainThreadOnly<Commands> = MainThreadOnly::new(Commands::new());
524
525 #[macro_export]
568 #[doc(hidden)]
569 macro_rules! __add__ {
570 ($callers:expr, |$pa:ident $(: &mut Pass)? $(, $arg:tt: $t:ty)* $(,)?| $f:block) => {{
571 use std::{sync::Arc, cell::UnsafeCell};
572 #[allow(unused_imports)]
573 use $crate::{
574 data::{Pass, RwData},
575 cmd::{Args, Caller, CmdFn, CmdResult, Parameter, Remainder, add_inner}
576 };
577
578 #[allow(unused_variables, unused_mut)]
579 let cmd = move |pa: &mut Pass, mut args: Args| -> CmdResult {
580 $(
581 let ($arg, form): (<$t as Parameter>::Returns, _) =
582 <$t as Parameter>::new(pa, &mut args)?;
583 )*
584
585 if let Ok(arg) = args.next() {
586 return Err($crate::text::txt!("Too many arguments"));
587 }
588
589 let mut $pa = pa;
590
591 $f
592 };
593
594 #[allow(unused_variables, unused_mut)]
595 let check_args = |pa: &Pass, mut args: Args| {
596 let mut ok_ranges = Vec::new();
597
598 $(
599 let start = args.next_start();
600 let result = <$t as Parameter>::new(pa, &mut args);
601 match result {
602 Ok((_, form)) => if let Some(start) = start
603 .filter(|s| args.param_range().end > *s)
604 {
605 ok_ranges.push((start..args.param_range().end, form));
606 }
607 Err(err) => return (ok_ranges, Some((args.param_range(), err)))
608 }
609 )*
610
611 let start = args.next_start();
612 if let (Ok(_), Some(start)) = (args.next_as::<Remainder>(pa), start) {
613 let err = $crate::text::txt!("Too many arguments");
614 return (ok_ranges, Some((start..args.param_range().end, err)))
615 }
616
617 (ok_ranges, None)
618 };
619
620 let callers: Vec<String> = $callers.into_callers().map(str::to_string).collect();
621 let cmd: CmdFn = unsafe { RwData::new_unsized::<()>(Arc::new(UnsafeCell::new(cmd))) };
623
624 add_inner(callers, cmd, check_args)
625 }}
626 }
627
628 pub fn quit() {
636 queue("quit");
637 }
638
639 pub fn edit(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
648 call(pa, format!("edit {buffer}"))
649 }
650
651 pub fn buffer(pa: &mut Pass, buffer: impl std::fmt::Display) -> CmdResult {
660 call(pa, format!("buffer {buffer}"))
661 }
662
663 pub fn next_buffer(pa: &mut Pass) -> CmdResult {
671 call(pa, "next-buffer")
672 }
673
674 pub fn prev_buffer(pa: &mut Pass) -> CmdResult {
682 call(pa, "prev-buffer")
683 }
684
685 pub fn next_global_buffer(pa: &mut Pass) -> CmdResult {
693 call(pa, "next-buffer --global")
694 }
695
696 pub fn prev_global_buffer(pa: &mut Pass) -> CmdResult {
704 call(pa, "prev-buffer --global")
705 }
706
707 pub fn alias(pa: &mut Pass, alias: impl ToString, command: impl ToString) -> CmdResult {
713 unsafe { COMMANDS.get() }.alias(pa, alias, command)
715 }
716
717 pub fn call(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
751 unsafe { COMMANDS.get() }.run(pa, call)
753 }
754
755 #[allow(unused_must_use)]
757 pub fn call_notify(pa: &mut Pass, call: impl std::fmt::Display) -> CmdResult {
758 let result = unsafe { COMMANDS.get() }.run(pa, call.to_string());
760 context::logs().push_cmd_result(result.clone());
761
762 result
763 }
764
765 pub fn queue(call: impl std::fmt::Display) {
777 let call = call.to_string();
778 crate::context::sender()
779 .send(DuatEvent::QueuedFunction(Box::new(move |pa| {
780 let _ = unsafe { COMMANDS.get() }.run(pa, call);
782 })))
783 .unwrap();
784 }
785
786 pub fn queue_notify(call: impl std::fmt::Display) {
788 let call = call.to_string();
789 crate::context::sender()
790 .send(DuatEvent::QueuedFunction(Box::new(move |pa| {
791 context::logs()
792 .push_cmd_result(unsafe { COMMANDS.get() }.run(pa, call.clone()).clone());
793 })))
794 .unwrap()
795 }
796
797 pub fn queue_and(call: impl std::fmt::Display, map: impl FnOnce(CmdResult) + Send + 'static) {
799 let call = call.to_string();
800 crate::context::sender()
801 .send(DuatEvent::QueuedFunction(Box::new(move |pa| {
802 map(unsafe { COMMANDS.get() }.run(pa, call));
804 })))
805 .unwrap()
806 }
807
808 pub fn queue_notify_and(
810 call: impl std::fmt::Display,
811 map: impl FnOnce(CmdResult) + Send + 'static,
812 ) {
813 let call = call.to_string();
814 crate::context::sender()
815 .send(DuatEvent::QueuedFunction(Box::new(move |pa| {
816 let result = unsafe { COMMANDS.get() }.run(pa, call.clone());
818 context::logs().push_cmd_result(result.clone());
819
820 map(result)
821 })))
822 .unwrap()
823 }
824
825 #[doc(hidden)]
829 pub fn add_inner(callers: Vec<String>, cmd: CmdFn, check_args: CheckerFn) {
830 let mut pa = unsafe { Pass::new() };
833 unsafe { COMMANDS.get() }.add(&mut pa, callers, cmd, check_args)
834 }
835
836 pub fn check_args(
838 pa: &Pass,
839 caller: &str,
840 ) -> Option<(
841 Vec<(Range<usize>, Option<FormId>)>,
842 Option<(Range<usize>, Text)>,
843 )> {
844 unsafe { COMMANDS.get() }.check_args(pa, caller)
846 }
847}
848
849struct Commands(LazyLock<RwData<InnerCommands>>);
859
860impl Commands {
861 const fn new() -> Self {
863 Self(LazyLock::new(|| {
864 RwData::new(InnerCommands {
865 list: Vec::new(),
866 aliases: HashMap::new(),
867 })
868 }))
869 }
870
871 fn alias(&self, pa: &mut Pass, alias: impl ToString, command: impl ToString) -> CmdResult {
873 self.0
874 .write(pa)
875 .try_alias(alias.to_string(), command.to_string())
876 }
877
878 fn run(&self, pa: &mut Pass, call: impl Display) -> CmdResult {
880 let call = call.to_string();
881 let mut args = call.split_whitespace();
882 let caller = args.next().ok_or(txt!("The command is empty"))?.to_string();
883
884 let inner = self.0.read(pa);
885
886 let (command, call) = {
887 if let Some(command) = inner.aliases.get(&caller) {
888 let (command, call) = command;
889 let mut call = call.clone() + " ";
890 call.extend(args);
891
892 (command.clone(), call)
893 } else {
894 let command = inner
895 .list
896 .iter()
897 .find(|cmd| cmd.callers().contains(&caller))
898 .ok_or(txt!("[a]{caller}[]: No such command"))?;
899
900 (command.clone(), call.clone())
901 }
902 };
903
904 let args = get_args(&call);
905
906 if let (_, Some((_, err))) = (command.check_args)(pa, args.clone()) {
907 return Err(err);
908 }
909
910 let silent = call.len() > call.trim_start().len();
911 command.cmd.write(&mut unsafe { Pass::new() })(pa, args).map(|ok| ok.filter(|_| !silent))
912 }
913
914 fn add(&self, pa: &mut Pass, callers: Vec<String>, cmd: CmdFn, check_args: CheckerFn) {
916 let cmd = Command::new(callers, cmd, check_args);
917 self.0.write(pa).add(cmd)
918 }
919
920 fn check_args(
922 &self,
923 pa: &Pass,
924 call: &str,
925 ) -> Option<(
926 Vec<(Range<usize>, Option<FormId>)>,
927 Option<(Range<usize>, Text)>,
928 )> {
929 let mut args = call.split_whitespace();
930 let caller = args.next()?.to_string();
931
932 let inner = self.0.read(pa);
933 if let Some((command, _)) = inner.aliases.get(&caller) {
934 Some((command.check_args)(pa, get_args(call)))
935 } else {
936 let command = inner
937 .list
938 .iter()
939 .find(|cmd| cmd.callers().contains(&caller))?;
940
941 Some((command.check_args)(pa, get_args(call)))
942 }
943 }
944}
945
946pub type CmdResult = Result<Option<Text>, Text>;
954
955#[derive(Clone)]
957struct Command {
958 callers: Arc<[String]>,
959 cmd: CmdFn,
960 check_args: CheckerFn,
961}
962
963impl Command {
964 fn new(callers: Vec<String>, cmd: CmdFn, check_args: CheckerFn) -> Self {
966 if let Some(caller) = callers
967 .iter()
968 .find(|caller| caller.split_whitespace().count() != 1)
969 {
970 panic!("Command caller \"{caller}\" contains more than one word");
971 }
972 Self { cmd, check_args, callers: callers.into() }
973 }
974
975 fn callers(&self) -> &[String] {
977 &self.callers
978 }
979}
980
981struct InnerCommands {
982 list: Vec<Command>,
983 aliases: HashMap<String, (Command, String)>,
984}
985
986impl InnerCommands {
987 fn add(&mut self, command: Command) {
991 let mut new_callers = command.callers().iter();
992
993 self.list
994 .retain(|cmd| new_callers.all(|caller| !cmd.callers.contains(caller)));
995
996 self.list.push(command);
997 }
998
999 fn try_alias(&mut self, alias: String, call: String) -> Result<Option<Text>, Text> {
1002 if alias.split_whitespace().count() != 1 {
1003 return Err(txt!("Alias [a]{alias}[] is not a single word"));
1004 }
1005
1006 let caller = call
1007 .split_whitespace()
1008 .next()
1009 .ok_or(txt!("The command is empty"))?
1010 .to_string();
1011
1012 let mut cmds = self.list.iter();
1013
1014 if let Some(command) = cmds.find(|cmd| cmd.callers().contains(&caller)) {
1015 let entry = (command.clone(), call.clone());
1016 Ok(Some(match self.aliases.insert(alias.clone(), entry) {
1017 Some((_, prev_call)) => {
1018 txt!("Aliased [a]{alias}[] from [a]{prev_call}[] to [a]{call}")
1019 }
1020 None => txt!("Aliased [a]{alias}[] to [a]{call}"),
1021 }))
1022 } else {
1023 Err(txt!("The caller [a]{caller}[] was not found"))
1024 }
1025 }
1026}
1027
1028pub trait Caller<'a>: Sized {
1030 fn into_callers(self) -> impl Iterator<Item = &'a str>;
1032}
1033
1034impl<'a> Caller<'a> for &'a str {
1035 fn into_callers(self) -> impl Iterator<Item = &'a str> {
1036 [self].into_iter()
1037 }
1038}
1039
1040impl<'a> Caller<'a> for &'a [&'a str] {
1041 fn into_callers(self) -> impl Iterator<Item = &'a str> {
1042 self.iter().cloned()
1043 }
1044}
1045
1046impl<'a, const N: usize> Caller<'a> for [&'a str; N] {
1047 fn into_callers(self) -> impl Iterator<Item = &'a str> {
1048 self.into_iter()
1049 }
1050}
1051
1052#[doc(hidden)]
1054pub type CmdFn = RwData<dyn FnMut(&mut Pass, Args) -> CmdResult + Send + 'static>;
1055
1056#[doc(hidden)]
1058pub type CheckerFn = fn(
1059 &Pass,
1060 Args,
1061) -> (
1062 Vec<(Range<usize>, Option<FormId>)>,
1063 Option<(Range<usize>, Text)>,
1064);
1065
1066pub(crate) fn as_buffer_handle((.., node): (usize, &Node)) -> Option<Handle> {
1067 node.try_downcast()
1068}