Skip to main content

nu_plugin_engine/
context.rs

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
17/// Object safe trait for abstracting operations required of the plugin context.
18pub trait PluginExecutionContext: Send + Sync {
19    /// A span pointing to the command being executed
20    fn span(&self) -> Span;
21    /// The [`Signals`] struct, if present
22    fn signals(&self) -> &Signals;
23    /// The pipeline externals state, for tracking the foreground process group, if present
24    fn pipeline_externals_state(&self) -> Option<&Arc<(AtomicU32, AtomicU32)>>;
25    /// Get engine configuration
26    fn get_config(&self) -> Result<Arc<Config>, ShellError>;
27    /// Get plugin configuration
28    fn get_plugin_config(&self) -> Result<Option<Value>, ShellError>;
29    /// Get an environment variable from `$env`
30    fn get_env_var(&self, name: &str) -> Result<Option<&Value>, ShellError>;
31    /// Get all environment variables
32    fn get_env_vars(&self) -> Result<HashMap<String, Value>, ShellError>;
33    /// Get current working directory
34    fn get_current_dir(&self) -> Result<Spanned<String>, ShellError>;
35    /// Set an environment variable
36    fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
37    /// Get help for the current command
38    fn get_help(&self) -> Result<Spanned<String>, ShellError>;
39    /// Get the contents of a [`Span`]
40    fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
41    /// Evaluate a closure passed to the plugin
42    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    /// Find a declaration by name
51    fn find_decl(&self, name: &str) -> Result<Option<DeclId>, ShellError>;
52    /// Get the compiled IR for a block
53    fn get_block_ir(&self, block_id: BlockId) -> Result<IrBlock, ShellError>;
54    /// Call a declaration with arguments and input
55    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    /// Create an owned version of the context with `'static` lifetime
64    fn boxed(&self) -> Box<dyn PluginExecutionContext>;
65}
66
67/// The execution context of a plugin command. Can be borrowed.
68pub 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        // The span is not really used, so just give it call.head
129        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        // Set up the positional arguments
190        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
288// Fetch the configuration for a plugin
289//
290// The `plugin` must match the registered name of a plugin.  For `plugin add nu_plugin_example` the
291// plugin config lookup uses `"example"`
292fn 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
311/// The execution context of plugin completion.
312///
313/// This supports limited interactions suitable for generating completions from nushell
314/// configuration, the environment, current working directory, or existing help.
315pub 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        // The span is not really used, so just give it call.head
376        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/// A bogus execution context for testing that doesn't really implement anything properly
454#[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}