mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 09:04:18 +01:00
Add GetSpanContents
engine call (#12439)
# Description This allows plugins to view the source code of spans. Requested by @ayax79 for implementing `polars ls`. Note that this won't really help you find the location of the span. I'm planning to add another engine call that will return information more similar to what shows up in the miette diagnostics, with filename / line number / some context, but I'll want to refactor some of the existing logic to make that happen, so it was easier to just do this first. I hope this is enough to at least have something somewhat useful show up for `polars ls`. # User-Facing Changes - Example plugin: added `example view span` command # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] Add to plugin protocol reference
This commit is contained in:
parent
9a2a6ab52c
commit
00b3a07efe
@ -3,7 +3,7 @@ use nu_engine::{get_eval_block_with_early_return, get_full_help};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Closure, EngineState, Redirection, Stack},
|
||||
Config, IntoSpanned, IoStream, PipelineData, PluginIdentity, ShellError, Spanned, Value,
|
||||
Config, IntoSpanned, IoStream, PipelineData, PluginIdentity, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@ -32,6 +32,8 @@ pub trait PluginExecutionContext: Send + Sync {
|
||||
fn add_env_var(&mut self, name: String, value: Value) -> Result<(), ShellError>;
|
||||
/// Get help for the current command
|
||||
fn get_help(&self) -> Result<Spanned<String>, ShellError>;
|
||||
/// Get the contents of a [`Span`]
|
||||
fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError>;
|
||||
/// Evaluate a closure passed to the plugin
|
||||
fn eval_closure(
|
||||
&self,
|
||||
@ -150,6 +152,14 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> {
|
||||
.into_spanned(self.call.head))
|
||||
}
|
||||
|
||||
fn get_span_contents(&self, span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
|
||||
Ok(self
|
||||
.engine_state
|
||||
.get_span_contents(span)
|
||||
.to_vec()
|
||||
.into_spanned(self.call.head))
|
||||
}
|
||||
|
||||
fn eval_closure(
|
||||
&self,
|
||||
closure: Spanned<Closure>,
|
||||
@ -271,6 +281,12 @@ impl PluginExecutionContext for PluginExecutionBogusContext {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_span_contents(&self, _span: Span) -> Result<Spanned<Vec<u8>>, ShellError> {
|
||||
Err(ShellError::NushellFailed {
|
||||
msg: "get_span_contents not implemented on bogus".into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn eval_closure(
|
||||
&self,
|
||||
_closure: Spanned<Closure>,
|
||||
|
@ -11,7 +11,7 @@ use crate::protocol::{
|
||||
};
|
||||
use nu_protocol::{
|
||||
engine::Closure, Config, IntoInterruptiblePipelineData, LabeledError, ListStream, PipelineData,
|
||||
PluginSignature, ShellError, Spanned, Value,
|
||||
PluginSignature, ShellError, Span, Spanned, Value,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap, HashMap},
|
||||
@ -646,6 +646,22 @@ impl EngineInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the contents of a [`Span`] from the engine.
|
||||
///
|
||||
/// This method returns `Vec<u8>` as it's possible for the matched span to not be a valid UTF-8
|
||||
/// string, perhaps because it sliced through the middle of a UTF-8 byte sequence, as the
|
||||
/// offsets are byte-indexed. Use [`String::from_utf8_lossy()`] for display if necessary.
|
||||
pub fn get_span_contents(&self, span: Span) -> Result<Vec<u8>, ShellError> {
|
||||
match self.engine_call(EngineCall::GetSpanContents(span))? {
|
||||
EngineCallResponse::PipelineData(PipelineData::Value(Value::Binary { val, .. }, _)) => {
|
||||
Ok(val)
|
||||
}
|
||||
_ => Err(ShellError::PluginFailedToDecode {
|
||||
msg: "Received unexpected response type for EngineCall::GetSpanContents".into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ask the engine to evaluate a closure. Input to the closure is passed as a stream, and the
|
||||
/// output is available as a stream.
|
||||
///
|
||||
|
@ -978,6 +978,24 @@ fn interface_get_help() -> Result<(), ShellError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_get_span_contents() -> Result<(), ShellError> {
|
||||
let test = TestCase::new();
|
||||
let manager = test.engine();
|
||||
let interface = manager.interface_for_context(0);
|
||||
|
||||
start_fake_plugin_call_responder(manager, 1, move |_| {
|
||||
EngineCallResponse::value(Value::test_binary(b"test string"))
|
||||
});
|
||||
|
||||
let contents = interface.get_span_contents(Span::test_data())?;
|
||||
|
||||
assert_eq!(b"test string", &contents[..]);
|
||||
|
||||
assert!(test.has_unconsumed_write());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interface_eval_closure_with_stream() -> Result<(), ShellError> {
|
||||
let test = TestCase::new();
|
||||
|
@ -1180,6 +1180,13 @@ pub(crate) fn handle_engine_call(
|
||||
help.item, help.span,
|
||||
)))
|
||||
}
|
||||
EngineCall::GetSpanContents(span) => {
|
||||
let contents = context.get_span_contents(span)?;
|
||||
Ok(EngineCallResponse::value(Value::binary(
|
||||
contents.item,
|
||||
contents.span,
|
||||
)))
|
||||
}
|
||||
EngineCall::EvalClosure {
|
||||
closure,
|
||||
positional,
|
||||
|
@ -463,6 +463,8 @@ pub enum EngineCall<D> {
|
||||
AddEnvVar(String, Value),
|
||||
/// Get help for the current command
|
||||
GetHelp,
|
||||
/// Get the contents of a span. Response is a binary which may not parse to UTF-8
|
||||
GetSpanContents(Span),
|
||||
/// Evaluate a closure with stream input/output
|
||||
EvalClosure {
|
||||
/// The closure to call.
|
||||
@ -491,6 +493,7 @@ impl<D> EngineCall<D> {
|
||||
EngineCall::GetCurrentDir => "GetCurrentDir",
|
||||
EngineCall::AddEnvVar(..) => "AddEnvVar",
|
||||
EngineCall::GetHelp => "GetHelp",
|
||||
EngineCall::GetSpanContents(_) => "GetSpanContents",
|
||||
EngineCall::EvalClosure { .. } => "EvalClosure",
|
||||
}
|
||||
}
|
||||
@ -509,6 +512,7 @@ impl<D> EngineCall<D> {
|
||||
EngineCall::GetCurrentDir => EngineCall::GetCurrentDir,
|
||||
EngineCall::AddEnvVar(name, value) => EngineCall::AddEnvVar(name, value),
|
||||
EngineCall::GetHelp => EngineCall::GetHelp,
|
||||
EngineCall::GetSpanContents(span) => EngineCall::GetSpanContents(span),
|
||||
EngineCall::EvalClosure {
|
||||
closure,
|
||||
positional,
|
||||
|
@ -16,10 +16,12 @@ pub use two::Two;
|
||||
mod config;
|
||||
mod disable_gc;
|
||||
mod env;
|
||||
mod view_span;
|
||||
|
||||
pub use config::Config;
|
||||
pub use disable_gc::DisableGc;
|
||||
pub use env::Env;
|
||||
pub use view_span::ViewSpan;
|
||||
|
||||
// Stream demos
|
||||
mod collect_external;
|
||||
|
58
crates/nu_plugin_example/src/commands/view_span.rs
Normal file
58
crates/nu_plugin_example/src/commands/view_span.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
|
||||
use nu_protocol::{Category, Example, LabeledError, Signature, Type, Value};
|
||||
|
||||
use crate::ExamplePlugin;
|
||||
|
||||
/// `<value> | example view span`
|
||||
pub struct ViewSpan;
|
||||
|
||||
impl SimplePluginCommand for ViewSpan {
|
||||
type Plugin = ExamplePlugin;
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"example view span"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Example command for looking up the contents of a parser span"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Shows the original source code of the expression that generated the value passed as input."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(Type::Any, Type::String)
|
||||
.category(Category::Experimental)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["example"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "('hello ' ++ 'world') | example view span",
|
||||
description: "Show the source code of the expression that generated a value",
|
||||
result: Some(Value::test_string("'hello ' ++ 'world'")),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_plugin: &ExamplePlugin,
|
||||
engine: &EngineInterface,
|
||||
call: &EvaluatedCall,
|
||||
input: &Value,
|
||||
) -> Result<Value, LabeledError> {
|
||||
let contents = engine.get_span_contents(input.span())?;
|
||||
Ok(Value::string(String::from_utf8_lossy(&contents), call.head))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), nu_protocol::ShellError> {
|
||||
use nu_plugin_test_support::PluginTest;
|
||||
PluginTest::new("example", ExamplePlugin.into())?.test_command_examples(&ViewSpan)
|
||||
}
|
@ -21,6 +21,7 @@ impl Plugin for ExamplePlugin {
|
||||
// Engine interface demos
|
||||
Box::new(Config),
|
||||
Box::new(Env),
|
||||
Box::new(ViewSpan),
|
||||
Box::new(DisableGc),
|
||||
// Stream demos
|
||||
Box::new(CollectExternal),
|
||||
|
Loading…
Reference in New Issue
Block a user