Skip to main content

nu_plugin_engine/
declaration.rs

1use nu_engine::{command_prelude::*, get_eval_expression};
2use nu_plugin_protocol::{CallInfo, EvaluatedCall, GetCompletionArgType, GetCompletionInfo};
3use nu_protocol::engine::ArgType;
4use nu_protocol::shell_error::generic::GenericError;
5use nu_protocol::{DynamicCompletionCallRef, DynamicSuggestion};
6use nu_protocol::{PluginIdentity, PluginSignature, engine::CommandType};
7use std::sync::Arc;
8
9use crate::context::PluginGetDynamicCompletionContext;
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::Generic(GenericError::new(
97                    format!("Unable to spawn plugin for `{}`", decl.name()),
98                    err.to_string(),
99                    call.head,
100                ))
101            })?;
102
103        // Create the context to execute in - this supports engine calls and custom values
104        let mut context = PluginExecutionCommandContext::new(
105            self.source.identity.clone(),
106            engine_state,
107            stack,
108            call,
109        );
110
111        plugin.run(
112            CallInfo {
113                name: self.name.clone(),
114                call: evaluated_call,
115                input,
116            },
117            &mut context,
118        )
119    }
120
121    fn command_type(&self) -> CommandType {
122        CommandType::Plugin
123    }
124
125    fn plugin_identity(&self) -> Option<&PluginIdentity> {
126        Some(&self.source.identity)
127    }
128
129    #[expect(deprecated, reason = "internal usage")]
130    fn get_dynamic_completion(
131        &self,
132        engine_state: &EngineState,
133        stack: &mut Stack,
134        call: DynamicCompletionCallRef,
135        arg_type: &ArgType,
136        _experimental: nu_protocol::engine::ExperimentalMarker,
137    ) -> Result<Option<Vec<DynamicSuggestion>>, ShellError> {
138        // Get the engine config
139        let engine_config = stack.get_config(engine_state);
140
141        // Get, or start, the plugin.
142        let plugin = self
143            .source
144            .persistent(None)
145            .and_then(|p| {
146                // Set the garbage collector config from the local config before running
147                p.set_gc_config(engine_config.plugin_gc.get(p.identity().name()));
148                p.get_plugin(Some((engine_state, stack)))
149            })
150            .map_err(|err| {
151                ShellError::Generic(GenericError::new_internal(
152                    "failed to get custom completion",
153                    err.to_string(),
154                ))
155            })?;
156
157        let arg_info = match arg_type {
158            ArgType::Flag(flag_name) => GetCompletionArgType::Flag(flag_name.to_string()),
159            ArgType::Positional(index) => GetCompletionArgType::Positional(*index),
160        };
161
162        let mut context = PluginGetDynamicCompletionContext::new(
163            self.source.identity.clone(),
164            engine_state,
165            stack,
166            &call,
167        );
168
169        plugin.get_dynamic_completion(
170            GetCompletionInfo {
171                name: self.name.clone(),
172                arg_type: arg_info,
173                call: (&call).into(),
174            },
175            &mut context,
176        )
177    }
178}