Skip to main content

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