1use crate::util::MutableCow;
2use nu_engine::{ClosureEvalOnce, get_eval_block_with_early_return, get_full_help};
3use nu_plugin_protocol::{DynamicCompletionCall, EvaluatedCall};
4use nu_protocol::{
5 BlockId, Config, DeclId, DynamicCompletionCallRef, IntoSpanned, OutDest, PipelineData,
6 PluginIdentity, ShellError, Signals, Span, Spanned, Value,
7 engine::{Call, Closure, EngineState, Redirection, Stack},
8 ir::{self, IrBlock},
9 shell_error::generic::GenericError,
10};
11use std::{
12 borrow::Cow,
13 collections::HashMap,
14 sync::{Arc, atomic::AtomicU32},
15};
16
17pub trait PluginExecutionContext: Send + Sync {
19 fn span(&self) -> Span;
21 fn signals(&self) -> &Signals;
23 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>;
25 fn get_config(&self) -> Result<Arc<Config>, ShellError>;
27 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError>;
29 fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError>;
31 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError>;
33 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
35 fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
37 fn get_help(&self) -> Result<Spanned<String>, ShellError>;
39 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
41 fn eval_closure(
43 &self,
44 closure: Spanned<Closure>,
45 positional: Vec<Value>,
46 input: PipelineData,
47 redirect_stdout: bool,
48 redirect_stderr: bool,
49 ) -> Result<PipelineData, ShellError>;
50 fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
52 fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError>;
54 fn call_decl(
56 &mut self,
57 decl_id: DeclId,
58 call: EvaluatedCall,
59 input: PipelineData,
60 redirect_stdout: bool,
61 redirect_stderr: bool,
62 ) -> Result<PipelineData, ShellError>;
63 fn boxed(&self) -> Box<dyn PluginExecutionContext>;
65}
66
67pub struct PluginExecutionCommandContext<'a> {
69 identity: Arc<PluginIdentity>,
70 engine_state: Cow<'a, EngineState>,
71 stack: MutableCow<'a, Stack>,
72 call: Call<'a>,
73}
74
75impl<'a> PluginExecutionCommandContext<'a> {
76 pub fn new(
77 identity: Arc<PluginIdentity>,
78 engine_state: &'a EngineState,
79 stack: &'a mut Stack,
80 call: &'a Call<'a>,
81 ) -> PluginExecutionCommandContext<'a> {
82 PluginExecutionCommandContext {
83 identity,
84 engine_state: Cow::Borrowed(engine_state),
85 stack: MutableCow::Borrowed(stack),
86 call: call.clone(),
87 }
88 }
89}
90
91impl PluginExecutionContext for PluginExecutionCommandContext<'_> {
92 fn span(&self) -> Span {
93 self.call.head
94 }
95
96 fn signals(&self) -> &Signals {
97 self.engine_state.signals()
98 }
99
100 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
101 Some(&self.engine_state.pipeline_externals_state)
102 }
103
104 fn get_config(&self) -> Result<Arc<Config>, ShellError> {
105 Ok(self.stack.get_config(&self.engine_state))
106 }
107
108 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
109 Ok(plugin_config(
110 self.get_config()?,
111 self.identity.name(),
112 &self.engine_state,
113 &self.stack,
114 self.call.head,
115 ))
116 }
117
118 fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError> {
119 Ok(self.stack.get_env_var(&self.engine_state, name))
120 }
121
122 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
123 Ok(self.stack.get_env_vars(&self.engine_state))
124 }
125
126 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
127 let cwd = self.engine_state.cwd_as_string(Some(&self.stack))?;
128 Ok(cwd.into_spanned(self.call.head))
130 }
131
132 fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError> {
133 self.stack.add_env_var(name, value);
134 Ok(())
135 }
136
137 fn get_help(&self) -> Result<Spanned<String>, ShellError> {
138 let decl = self.engine_state.get_decl(self.call.decl_id);
139
140 Ok(get_full_help(
141 decl,
142 &self.engine_state,
143 &mut self.stack.clone(),
144 self.call.head,
145 )
146 .into_spanned(self.call.head))
147 }
148
149 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
150 Ok(self
151 .engine_state
152 .get_span_contents(span)
153 .to_vec()
154 .into_spanned(self.call.head))
155 }
156
157 fn eval_closure(
158 &self,
159 closure: Spanned<Closure>,
160 positional: Vec<Value>,
161 input: PipelineData,
162 redirect_stdout: bool,
163 redirect_stderr: bool,
164 ) -> Result<PipelineData, ShellError> {
165 let block = self
166 .engine_state
167 .try_get_block(closure.item.block_id)
168 .ok_or_else(|| {
169 ShellError::Generic(GenericError::new(
170 "Plugin misbehaving",
171 format!(
172 "Tried to evaluate unknown block id: {}",
173 closure.item.block_id.get()
174 ),
175 closure.span,
176 ))
177 })?;
178
179 let mut stack = self
180 .stack
181 .captures_to_stack(closure.item.captures)
182 .reset_pipes();
183
184 let stack = &mut stack.push_redirection(
185 redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
186 redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
187 );
188
189 for (idx, value) in positional.into_iter().enumerate() {
191 if let Some(arg) = block.signature.get_positional(idx) {
192 if let Some(var_id) = arg.var_id {
193 stack.add_var(var_id, value);
194 } else {
195 return Err(ShellError::NushellFailedSpanned {
196 msg: "Error while evaluating closure from plugin".into(),
197 label: "closure argument missing var_id".into(),
198 span: closure.span,
199 });
200 }
201 }
202 }
203
204 let eval_block_with_early_return = get_eval_block_with_early_return(&self.engine_state);
205
206 eval_block_with_early_return(&self.engine_state, stack, block, input).map(|p| p.body)
207 }
208
209 fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError> {
210 Ok(self.engine_state.find_decl(name.as_bytes(), &[]))
211 }
212
213 fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError> {
214 let block = self.engine_state.try_get_block(block_id).ok_or_else(|| {
215 ShellError::Generic(GenericError::new(
216 "Plugin misbehaving",
217 format!("Tried to get IR for unknown block id: {}", block_id.get()),
218 self.call.head,
219 ))
220 })?;
221
222 block.ir_block.clone().ok_or_else(|| {
223 ShellError::Generic(
224 GenericError::new(
225 "Block has no IR",
226 format!("Block {} was not compiled to IR", block_id.get()),
227 self.call.head,
228 )
229 .with_help(
230 "This block may be a declaration or built-in that has no IR representation",
231 ),
232 )
233 })
234 }
235
236 fn call_decl(
237 &mut self,
238 decl_id: DeclId,
239 call: EvaluatedCall,
240 input: PipelineData,
241 redirect_stdout: bool,
242 redirect_stderr: bool,
243 ) -> Result<PipelineData, ShellError> {
244 if decl_id.get() >= self.engine_state.num_decls() {
245 return Err(ShellError::Generic(GenericError::new(
246 "Plugin misbehaving",
247 format!("Tried to call unknown decl id: {}", decl_id.get()),
248 call.head,
249 )));
250 }
251
252 let decl = self.engine_state.get_decl(decl_id);
253
254 let stack = &mut self.stack.push_redirection(
255 redirect_stdout.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
256 redirect_stderr.then_some(Redirection::Pipe(OutDest::PipeSeparate)),
257 );
258
259 let mut call_builder = ir::Call::build(decl_id, call.head);
260
261 for positional in call.positional {
262 call_builder.add_positional(stack, positional.span(), positional);
263 }
264
265 for (name, value) in call.named {
266 if let Some(value) = value {
267 call_builder.add_named(stack, &name.item, "", name.span, value);
268 } else {
269 call_builder.add_flag(stack, &name.item, "", name.span);
270 }
271 }
272
273 call_builder.with(stack, |stack, call| {
274 decl.run(&self.engine_state, stack, call, input)
275 })
276 }
277
278 fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
279 Box::new(PluginExecutionCommandContext {
280 identity: self.identity.clone(),
281 engine_state: Cow::Owned(self.engine_state.clone().into_owned()),
282 stack: self.stack.owned(),
283 call: self.call.to_owned(),
284 })
285 }
286}
287
288fn plugin_config(
293 config: Arc<Config>,
294 plugin_name: &str,
295 engine_state: &EngineState,
296 stack: &Stack,
297 head: Span,
298) -> Option<Value> {
299 config.plugins.get(plugin_name).cloned().map(|value| {
300 let span = value.span();
301 match value {
302 Value::Closure { val, .. } => ClosureEvalOnce::new(engine_state, stack, *val)
303 .run_with_input(PipelineData::empty())
304 .and_then(|data| data.into_value(span))
305 .unwrap_or_else(|err| Value::error(err, head)),
306 _ => value.clone(),
307 }
308 })
309}
310
311pub struct PluginGetDynamicCompletionContext<'a> {
316 identity: Arc<PluginIdentity>,
317 engine_state: Cow<'a, EngineState>,
318 stack: MutableCow<'a, Stack>,
319 call: DynamicCompletionCall,
320}
321
322impl<'a> PluginGetDynamicCompletionContext<'a> {
323 pub fn new(
324 identity: Arc<PluginIdentity>,
325 engine_state: &'a EngineState,
326 stack: &'a mut Stack,
327 call: &DynamicCompletionCallRef<'a>,
328 ) -> Self {
329 Self {
330 identity,
331 engine_state: Cow::Borrowed(engine_state),
332 stack: MutableCow::Borrowed(stack),
333 call: call.into(),
334 }
335 }
336}
337
338impl PluginExecutionContext for PluginGetDynamicCompletionContext<'_> {
339 fn span(&self) -> Span {
340 self.call.call.head
341 }
342
343 fn signals(&self) -> &Signals {
344 &Signals::EMPTY
345 }
346
347 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
348 Some(&self.engine_state.pipeline_externals_state)
349 }
350
351 fn get_config(&self) -> Result<Arc<Config>, ShellError> {
352 Ok(self.stack.get_config(&self.engine_state))
353 }
354
355 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
356 Ok(plugin_config(
357 self.get_config()?,
358 self.identity.name(),
359 &self.engine_state,
360 &self.stack,
361 self.call.call.head,
362 ))
363 }
364
365 fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError> {
366 Ok(self.stack.get_env_var(&self.engine_state, name))
367 }
368
369 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
370 Ok(self.stack.get_env_vars(&self.engine_state))
371 }
372
373 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
374 let cwd = self.engine_state.cwd_as_string(Some(&self.stack))?;
375 Ok(cwd.into_spanned(self.call.call.head))
377 }
378
379 fn add_env_var(&mut self, _name: String, _value: Value) -> Result<(), ShellError> {
380 Err(ShellError::NushellFailed {
381 msg: "add_env_var not implemented for PluginGetDynamicCompletionContext".into(),
382 })
383 }
384
385 fn get_help(&self) -> Result<Spanned<String>, ShellError> {
386 let decl = self.engine_state.get_decl(self.call.call.decl_id);
387
388 Ok(get_full_help(
389 decl,
390 &self.engine_state,
391 &mut self.stack.clone(),
392 self.call.call.head,
393 )
394 .into_spanned(self.call.call.head))
395 }
396
397 fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
398 Ok(self
399 .engine_state
400 .get_span_contents(span)
401 .to_vec()
402 .into_spanned(self.call.call.head))
403 }
404
405 fn eval_closure(
406 &self,
407 _closure: Spanned<Closure>,
408 _positional: Vec<Value>,
409 _input: PipelineData,
410 _redirect_stdout: bool,
411 _redirect_stderr: bool,
412 ) -> Result<PipelineData, ShellError> {
413 Err(ShellError::NushellFailed {
414 msg: "eval_closure not implemented for PluginGetDynamicCompletionContext".into(),
415 })
416 }
417
418 fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
419 Err(ShellError::NushellFailed {
420 msg: "find_decl not implemented for PluginGetDynamicCompletionContext".into(),
421 })
422 }
423
424 fn get_block_ir(&self, _block_id: BlockId) -> Result<IrBlock, ShellError> {
425 Err(ShellError::NushellFailed {
426 msg: "get_block_ir not implemented for PluginGetDynamicCompletionContext".into(),
427 })
428 }
429
430 fn call_decl(
431 &mut self,
432 _decl_id: DeclId,
433 _call: EvaluatedCall,
434 _input: PipelineData,
435 _redirect_stdout: bool,
436 _redirect_stderr: bool,
437 ) -> Result<PipelineData, ShellError> {
438 Err(ShellError::NushellFailed {
439 msg: "call_decl not implemented for PluginGetDynamicCompletionContext".into(),
440 })
441 }
442
443 fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
444 Box::new(PluginGetDynamicCompletionContext {
445 identity: self.identity.clone(),
446 engine_state: Cow::Owned(self.engine_state.clone().into_owned()),
447 stack: self.stack.owned(),
448 call: self.call.to_owned(),
449 })
450 }
451}
452
453#[cfg(test)]
455pub(crate) struct PluginExecutionBogusContext;
456
457#[cfg(test)]
458impl PluginExecutionContext for PluginExecutionBogusContext {
459 fn span(&self) -> Span {
460 Span::test_data()
461 }
462
463 fn signals(&self) -> &Signals {
464 &Signals::EMPTY
465 }
466
467 fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>> {
468 None
469 }
470
471 fn get_config(&self) -> Result<Arc<Config>, ShellError> {
472 Err(ShellError::NushellFailed {
473 msg: "get_config not implemented on bogus".into(),
474 })
475 }
476
477 fn get_plugin_config(&self) -> Result<Option<Value>, ShellError> {
478 Ok(None)
479 }
480
481 fn get_env_var(&self, _name: &str) -> Result<Option<&Value>, ShellError> {
482 Err(ShellError::NushellFailed {
483 msg: "get_env_var not implemented on bogus".into(),
484 })
485 }
486
487 fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError> {
488 Err(ShellError::NushellFailed {
489 msg: "get_env_vars not implemented on bogus".into(),
490 })
491 }
492
493 fn get_current_dir(&self) -> Result<Spanned<String>, ShellError> {
494 Err(ShellError::NushellFailed {
495 msg: "get_current_dir not implemented on bogus".into(),
496 })
497 }
498
499 fn add_env_var(&mut self, _name: String, _value: Value) -> Result<(), ShellError> {
500 Err(ShellError::NushellFailed {
501 msg: "add_env_var not implemented on bogus".into(),
502 })
503 }
504
505 fn get_help(&self) -> Result<Spanned<String>, ShellError> {
506 Err(ShellError::NushellFailed {
507 msg: "get_help not implemented on bogus".into(),
508 })
509 }
510
511 fn get_span_contents(&self, _span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
512 Err(ShellError::NushellFailed {
513 msg: "get_span_contents not implemented on bogus".into(),
514 })
515 }
516
517 fn eval_closure(
518 &self,
519 _closure: Spanned<Closure>,
520 _positional: Vec<Value>,
521 _input: PipelineData,
522 _redirect_stdout: bool,
523 _redirect_stderr: bool,
524 ) -> Result<PipelineData, ShellError> {
525 Err(ShellError::NushellFailed {
526 msg: "eval_closure not implemented on bogus".into(),
527 })
528 }
529
530 fn find_decl(&self, _name: &str) -> Result<Option<DeclId>, ShellError> {
531 Err(ShellError::NushellFailed {
532 msg: "find_decl not implemented on bogus".into(),
533 })
534 }
535
536 fn get_block_ir(&self, _block_id: BlockId) -> Result<IrBlock, ShellError> {
537 Err(ShellError::NushellFailed {
538 msg: "get_block_ir not implemented on bogus".into(),
539 })
540 }
541
542 fn call_decl(
543 &mut self,
544 _decl_id: DeclId,
545 _call: EvaluatedCall,
546 _input: PipelineData,
547 _redirect_stdout: bool,
548 _redirect_stderr: bool,
549 ) -> Result<PipelineData, ShellError> {
550 Err(ShellError::NushellFailed {
551 msg: "call_decl not implemented on bogus".into(),
552 })
553 }
554
555 fn boxed(&self) -> Box<dyn PluginExecutionContext + 'static> {
556 Box::new(PluginExecutionBogusContext)
557 }
558}