lattice_compilation/
ex_commands.rs1use std::sync::Arc;
15
16use lattice_grammar::CommandRegistry;
17use lattice_grammar::app_effect::AppEffect;
18use lattice_grammar::args::{ArgKind, ArgSpec, Args};
19use lattice_grammar::command::LatencyClass;
20use lattice_grammar::effect::Effect;
21use lattice_grammar::error::CommandError;
22use lattice_grammar::registry::{ExCommandSpec, SurfaceForm};
23
24fn arg_to_cmdline(args: &Args) -> Option<String> {
27 match args {
28 Args::String(s) => Some(s.clone()),
29 _ => None,
30 }
31}
32
33pub fn register_compilation_ex_commands(registry: &mut CommandRegistry) {
35 registry.register_ex_command(
38 "compile",
39 "Run a shell command and stream its output into the *compilation* buffer.",
40 ExCommandSpec {
41 latency_class: LatencyClass::Reflex,
42 accepts_bang: false,
43 accepts_range: false,
44 parse_args: Arc::new(|s: &str, _bang: bool| {
45 let trimmed = s.trim();
46 if trimmed.is_empty() {
47 return Err(CommandError::BadArgs(":compile requires a command".into()));
48 }
49 Ok(Args::String(trimmed.to_string()))
50 }),
51 apply: Arc::new(|ctx| {
52 Ok(Effect::AppAction(AppEffect::CompileRun {
53 cmdline: arg_to_cmdline(&ctx.args),
54 }))
55 }),
56 args_schema: vec![ArgSpec::required(
57 "command",
58 ArgKind::String,
59 "shell command to run",
60 )],
61 surface_form: SurfaceForm::Keyword,
62 },
63 );
64
65 registry.register_ex_command(
67 "recompile",
68 "Re-run the last compilation command, clearing and re-streaming *compilation*.",
69 ExCommandSpec {
70 latency_class: LatencyClass::Reflex,
71 accepts_bang: false,
72 accepts_range: false,
73 parse_args: Arc::new(|s: &str, _bang: bool| {
74 if s.trim().is_empty() {
75 Ok(Args::None)
76 } else {
77 Err(CommandError::BadArgs(
78 ":recompile takes no arguments".into(),
79 ))
80 }
81 }),
82 apply: Arc::new(|_ctx| Ok(Effect::AppAction(AppEffect::CompileRun { cmdline: None }))),
83 args_schema: vec![],
84 surface_form: SurfaceForm::Keyword,
85 },
86 );
87
88 registry.register_ex_command(
91 "make",
92 "Run the build command (optionally overridden) into the *compilation* buffer.",
93 ExCommandSpec {
94 latency_class: LatencyClass::Reflex,
95 accepts_bang: false,
96 accepts_range: false,
97 parse_args: Arc::new(|s: &str, _bang: bool| {
98 let trimmed = s.trim();
99 if trimmed.is_empty() {
100 Ok(Args::None)
101 } else {
102 Ok(Args::String(trimmed.to_string()))
103 }
104 }),
105 apply: Arc::new(|ctx| {
106 Ok(Effect::AppAction(AppEffect::CompileRun {
107 cmdline: arg_to_cmdline(&ctx.args),
108 }))
109 }),
110 args_schema: vec![ArgSpec::optional(
111 "command",
112 ArgKind::String,
113 "build command override",
114 )],
115 surface_form: SurfaceForm::Keyword,
116 },
117 );
118
119 registry.register_ex_command(
122 "compilation-kill",
123 "Kill the running compilation child process.",
124 ExCommandSpec {
125 latency_class: LatencyClass::Reflex,
126 accepts_bang: false,
127 accepts_range: false,
128 parse_args: Arc::new(|_s: &str, _bang: bool| Ok(Args::None)),
129 apply: Arc::new(|_| Ok(Effect::AppAction(AppEffect::CompilationKill))),
130 args_schema: vec![],
131 surface_form: SurfaceForm::Keyword,
132 },
133 );
134}
135
136#[cfg(test)]
137mod tests {
138 #![allow(clippy::unwrap_used, clippy::panic)]
139 use super::*;
140
141 fn ex_spec<'a>(registry: &'a CommandRegistry, name: &str) -> Option<&'a ExCommandSpec> {
142 registry
143 .id_by_name(name)
144 .and_then(|id| registry.ex_command_spec(id))
145 }
146
147 #[test]
148 fn registers_all_four_commands() {
149 let mut registry = CommandRegistry::new();
150 register_compilation_ex_commands(&mut registry);
151 assert!(ex_spec(®istry, "compile").is_some());
152 assert!(ex_spec(®istry, "recompile").is_some());
153 assert!(ex_spec(®istry, "make").is_some());
154 assert!(ex_spec(®istry, "compilation-kill").is_some());
155 }
156
157 #[test]
158 fn compile_rejects_empty_and_accepts_command() {
159 let mut registry = CommandRegistry::new();
160 register_compilation_ex_commands(&mut registry);
161 let spec = ex_spec(®istry, "compile").unwrap();
162 assert!((spec.parse_args)("", false).is_err());
163 let parsed = (spec.parse_args)("cargo build", false).unwrap();
164 match parsed {
165 Args::String(s) => assert_eq!(s, "cargo build"),
166 other => panic!("expected String args, got {other:?}"),
167 }
168 }
169
170 #[test]
171 fn make_arg_is_optional() {
172 let mut registry = CommandRegistry::new();
173 register_compilation_ex_commands(&mut registry);
174 let spec = ex_spec(®istry, "make").unwrap();
175 assert!(matches!((spec.parse_args)("", false).unwrap(), Args::None));
176 assert!(matches!(
177 (spec.parse_args)("cargo test", false).unwrap(),
178 Args::String(_)
179 ));
180 }
181}