nu_plugin_engine/
declaration.rs

1use nu_engine::{command_prelude::*, get_eval_expression};
2use nu_plugin_protocol::{
3    CallInfo, DynamicCompletionCall, EvaluatedCall, GetCompletionArgType, GetCompletionInfo,
4};
5use nu_protocol::engine::ArgType;
6use nu_protocol::{DynamicCompletionCallRef, DynamicSuggestion};
7use nu_protocol::{PluginIdentity, PluginSignature, engine::CommandType};
8use std::sync::Arc;
9
10use crate::{GetPlugin, PluginExecutionCommandContext, PluginSource};
11
12/// The command declaration proxy used within the engine for all plugin commands.
13#[derive(Clone)]
14pub struct PluginDeclaration {
15    name: String,
16    signature: PluginSignature,
17    source: PluginSource,
18}
19
20impl PluginDeclaration {
21    pub fn new(plugin: Arc<dyn GetPlugin>, signature: PluginSignature) -> Self {
22        Self {
23            name: signature.sig.name.clone(),
24            signature,
25            source: PluginSource::new(plugin),
26        }
27    }
28}
29
30impl Command for PluginDeclaration {
31    fn name(&self) -> &str {
32        &self.name
33    }
34
35    fn signature(&self) -> Signature {
36        self.signature.sig.clone()
37    }
38
39    fn description(&self) -> &str {
40        self.signature.sig.description.as_str()
41    }
42
43    fn extra_description(&self) -> &str {
44        self.signature.sig.extra_description.as_str()
45    }
46
47    fn search_terms(&self) -> Vec<&str> {
48        self.signature
49            .sig
50            .search_terms
51            .iter()
52            .map(|term| term.as_str())
53            .collect()
54    }
55
56    fn examples(&self) -> Vec<Example<'_>> {
57        let mut res = vec![];
58        for e in self.signature.examples.iter() {
59            res.push(Example {
60                example: &e.example,
61                description: &e.description,
62                result: e.result.clone(),
63            })
64        }
65        res
66    }
67
68    fn run(
69        &self,
70        engine_state: &EngineState,
71        stack: &mut Stack,
72        call: &Call,
73        input: PipelineData,
74    ) -> Result<PipelineData, ShellError> {
75        let eval_expression = get_eval_expression(engine_state);
76
77        // Create the EvaluatedCall to send to the plugin first - it's best for this to fail early,
78        // before we actually try to run the plugin command
79        let evaluated_call =
80            EvaluatedCall::try_from_call(call, engine_state, stack, eval_expression)?;
81
82        // Get the engine config
83        let engine_config = stack.get_config(engine_state);
84
85        // Get, or start, the plugin.
86        let plugin = self
87            .source
88            .persistent(None)
89            .and_then(|p| {
90                // Set the garbage collector config from the local config before running
91                p.set_gc_config(engine_config.plugin_gc.get(p.identity().name()));
92                p.get_plugin(Some((engine_state, stack)))
93            })
94            .map_err(|err| {
95                let decl = engine_state.get_decl(call.decl_id);
96                ShellError::GenericError {
97                    error: format!("Unable to spawn plugin for `{}`", decl.name()),
98                    msg: err.to_string(),
99                    span: Some(call.head),
100                    help: None,
101                    inner: vec![],
102                }
103            })?;
104
105        // Create the context to execute in - this supports engine calls and custom values
106        let mut context = PluginExecutionCommandContext::new(
107            self.source.identity.clone(),
108            engine_state,
109            stack,
110            call,
111        );
112
113        plugin.run(
114            CallInfo {
115                name: self.name.clone(),
116                call: evaluated_call,
117                input,
118            },
119            &mut context,
120        )
121    }
122
123    fn command_type(&self) -> CommandType {
124        CommandType::Plugin
125    }
126
127    fn plugin_identity(&self) -> Option<&PluginIdentity> {
128        Some(&self.source.identity)
129    }
130
131    #[expect(deprecated, reason = "internal usage")]
132    fn get_dynamic_completion(
133        &self,
134        engine_state: &EngineState,
135        stack: &mut Stack,
136        call: DynamicCompletionCallRef,
137        arg_type: &ArgType,
138        _experimental: nu_protocol::engine::ExperimentalMarker,
139    ) -> Result<Option<Vec<DynamicSuggestion>>, ShellError> {
140        // Get the engine config
141        let engine_config = stack.get_config(engine_state);
142
143        // Get, or start, the plugin.
144        let plugin = self
145            .source
146            .persistent(None)
147            .and_then(|p| {
148                // Set the garbage collector config from the local config before running
149                p.set_gc_config(engine_config.plugin_gc.get(p.identity().name()));
150                p.get_plugin(Some((engine_state, stack)))
151            })
152            .map_err(|err| ShellError::GenericError {
153                error: "failed to get custom completion".to_string(),
154                msg: err.to_string(),
155                span: None,
156                help: None,
157                inner: vec![],
158            })?;
159
160        let arg_info = match arg_type {
161            ArgType::Flag(flag_name) => GetCompletionArgType::Flag(flag_name.to_string()),
162            ArgType::Positional(index) => GetCompletionArgType::Positional(*index),
163        };
164        plugin.get_dynamic_completion(GetCompletionInfo {
165            name: self.name.clone(),
166            arg_type: arg_info,
167            call: DynamicCompletionCall {
168                call: call.call.clone(),
169                strip: call.strip,
170                pos: call.pos,
171            },
172        })
173    }
174}