Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep plugins persistently running in the background #12064

Merged
merged 15 commits into from Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions crates/nu-cmd-lang/src/core_commands/mod.rs
Expand Up @@ -71,8 +71,13 @@ pub use try_::Try;
pub use use_::Use;
pub use version::Version;
pub use while_::While;
//#[cfg(feature = "plugin")]

mod plugin;
mod plugin_list;
mod plugin_stop;
mod register;

//#[cfg(feature = "plugin")]
pub use plugin::PluginCommand;
pub use plugin_list::PluginList;
pub use plugin_stop::PluginStop;
pub use register::Register;
64 changes: 64 additions & 0 deletions crates/nu-cmd-lang/src/core_commands/plugin.rs
@@ -0,0 +1,64 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
};

#[derive(Clone)]
pub struct PluginCommand;

impl Command for PluginCommand {
fn name(&self) -> &str {
"plugin"
}

fn signature(&self) -> Signature {
Signature::build("plugin")
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
.category(Category::Core)
}

fn usage(&self) -> &str {
"Commands for managing plugins."
}

fn extra_usage(&self) -> &str {
"To load a plugin, see `register`."
}

fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::string(
get_full_help(
&PluginCommand.signature(),
&PluginCommand.examples(),
engine_state,
stack,
self.is_parser_keyword(),
),
call.head,
)
.into_pipeline_data())
}

fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "plugin list",
description: "List installed plugins",
result: None,
},
Example {
example: "plugin stop inc",
description: "Stop the plugin named `inc`.",
result: None,
},
]
}
}
101 changes: 101 additions & 0 deletions crates/nu-cmd-lang/src/core_commands/plugin_list.rs
@@ -0,0 +1,101 @@
use itertools::Itertools;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature,
Type, Value,
};

#[derive(Clone)]
pub struct PluginList;

impl Command for PluginList {
fn name(&self) -> &str {
"plugin list"
}

fn signature(&self) -> Signature {
Signature::build("plugin list")
.input_output_type(
Type::Nothing,
Type::Table(vec![
("name".into(), Type::String),
("is_running".into(), Type::Bool),
("pid".into(), Type::Int),
("filename".into(), Type::String),
("shell".into(), Type::String),
("commands".into(), Type::List(Type::String.into())),
]),
)
.category(Category::Core)
}

fn usage(&self) -> &str {
"List installed plugins."
}

fn examples(&self) -> Vec<nu_protocol::Example> {
vec![
Example {
example: "plugin list",
description: "List installed plugins.",
result: Some(Value::test_list(vec![Value::test_record(record! {
"name" => Value::test_string("inc"),
"is_running" => Value::test_bool(true),
"pid" => Value::test_int(106480),
"filename" => if cfg!(windows) {
Value::test_string(r"C:\nu\plugins\nu_plugin_inc.exe")
} else {
Value::test_string("/opt/nu/plugins/nu_plugin_inc")
},
"shell" => Value::test_nothing(),
"commands" => Value::test_list(vec![Value::test_string("inc")]),
})])),
},
Example {
example: "ps | where pid in (plugin list).pid",
description: "Get process information for running plugins.",
result: None,
},
]
}

fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.span();
// Group plugin decls by plugin identity
let decls = engine_state.plugin_decls().into_group_map_by(|decl| {
decl.plugin_identity()
.expect("plugin decl should have identity")
});
// Build plugins list
let list = engine_state.plugins().iter().map(|plugin| {
// Find commands that belong to the plugin
let commands = decls.get(plugin.identity())
.into_iter()
.flat_map(|decls| {
decls.iter().map(|decl| Value::string(decl.name(), span))
})
.collect();

Value::record(record! {
"name" => Value::string(plugin.identity().name(), span),
"is_running" => Value::bool(plugin.is_running(), span),
"pid" => plugin.pid()
.map(|p| Value::int(p as i64, span))
.unwrap_or(Value::nothing(span)),
"filename" => Value::string(plugin.identity().filename().to_string_lossy(), span),
"shell" => plugin.identity().shell()
.map(|s| Value::string(s.to_string_lossy(), span))
.unwrap_or(Value::nothing(span)),
"commands" => Value::list(commands, span),
}, span)
}).collect::<Vec<Value>>();
Ok(list.into_pipeline_data(engine_state.ctrlc.clone()))
}
}
75 changes: 75 additions & 0 deletions crates/nu-cmd-lang/src/core_commands/plugin_stop.rs
@@ -0,0 +1,75 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
};

#[derive(Clone)]
pub struct PluginStop;

impl Command for PluginStop {
fn name(&self) -> &str {
"plugin stop"
}

fn signature(&self) -> Signature {
Signature::build("plugin stop")
.input_output_type(Type::Nothing, Type::Nothing)
.required(
"name",
SyntaxShape::String,
"The name of the plugin to stop.",
)
.category(Category::Core)
}

fn usage(&self) -> &str {
"Stop an installed plugin if it was running."
}

fn examples(&self) -> Vec<nu_protocol::Example> {
vec![
Example {
example: "plugin stop inc",
description: "Stop the plugin named `inc`.",
result: None,
},
Example {
example: "plugin list | each { |p| plugin stop $p.name }",
description: "Stop all plugins.",
result: None,
},
]
}

fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;

let mut found = false;
for plugin in engine_state.plugins() {
if plugin.identity().name() == name.item {
plugin.stop()?;
found = true;
}
}

if found {
Ok(PipelineData::Empty)
} else {
Err(ShellError::GenericError {
error: format!("Failed to stop the `{}` plugin", name.item),
msg: "couldn't find a plugin with this name".into(),
span: Some(name.span),
help: Some("you may need to `register` the plugin first".into()),
inner: vec![],
})
}
}
}
8 changes: 4 additions & 4 deletions crates/nu-cmd-lang/src/core_commands/version.rs
Expand Up @@ -130,11 +130,11 @@ pub fn version(engine_state: &EngineState, call: &Call) -> Result<PipelineData,
Value::string(features_enabled().join(", "), call.head),
);

// Get a list of command names and check for plugins
// Get a list of plugin names
let installed_plugins = engine_state
.plugin_decls()
.filter(|x| x.is_plugin().is_some())
.map(|x| x.name())
.plugins()
.iter()
.map(|x| x.identity().name())
.collect::<Vec<_>>();

record.push(
Expand Down
2 changes: 1 addition & 1 deletion crates/nu-cmd-lang/src/default_context.rs
Expand Up @@ -65,7 +65,7 @@ pub fn create_default_context() -> EngineState {
};

//#[cfg(feature = "plugin")]
bind_command!(Register);
bind_command!(PluginCommand, PluginList, PluginStop, Register,);

working_set.render()
};
Expand Down
2 changes: 1 addition & 1 deletion crates/nu-engine/src/scope.rs
Expand Up @@ -115,7 +115,7 @@ impl<'e, 's> ScopeData<'e, 's> {
// we can only be a is_builtin or is_custom, not both
"is_builtin" => Value::bool(!decl.is_custom_command(), span),
"is_sub" => Value::bool(decl.is_sub(), span),
"is_plugin" => Value::bool(decl.is_plugin().is_some(), span),
"is_plugin" => Value::bool(decl.is_plugin(), span),
"is_custom" => Value::bool(decl.is_custom_command(), span),
"is_keyword" => Value::bool(decl.is_parser_keyword(), span),
"is_extern" => Value::bool(decl.is_known_external(), span),
Expand Down