diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 18ac7e1748..a20f13b864 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -330,6 +330,11 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Base64, }; + // Experimental + bind_command! { + ViewSource, + }; + #[cfg(feature = "plugin")] bind_command!(Register); diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index b90a5fd0dd..6f3c70ea5d 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -1,7 +1,9 @@ mod git; mod git_checkout; mod list_git_branches; +mod view_source; pub use git::Git; pub use git_checkout::GitCheckout; pub use list_git_branches::ListGitBranches; +pub use view_source::ViewSource; diff --git a/crates/nu-command/src/experimental/view_source.rs b/crates/nu-command/src/experimental/view_source.rs new file mode 100644 index 0000000000..8958cb2f55 --- /dev/null +++ b/crates/nu-command/src/experimental/view_source.rs @@ -0,0 +1,98 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct ViewSource; + +impl Command for ViewSource { + fn name(&self) -> &str { + "view-source" + } + + fn usage(&self) -> &str { + "View a block, module, or a definition" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("view-source") + .desc(self.usage()) + .required("item", SyntaxShape::Any, "name or block to view") + .category(Category::Core) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let arg: Value = call.req(engine_state, stack, 0)?; + let arg_span = arg.span()?; + + match arg { + Value::Block { span, .. } => { + let contents = engine_state.get_span_contents(&span); + Ok( + Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data(), + ) + } + Value::String { val, .. } => { + if let Some(decl_id) = engine_state.find_decl(val.as_bytes()) { + // arg is a command + let decl = engine_state.get_decl(decl_id); + if let Some(block_id) = decl.get_block_id() { + let block = engine_state.get_block(block_id); + if let Some(block_span) = block.span { + let contents = engine_state.get_span_contents(&block_span); + Ok(Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data()) + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the command does not have a viewable block".to_string(), + arg_span, + )) + } + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the command does not have a viewable block".to_string(), + arg_span, + )) + } + } else if let Some(overlay_id) = engine_state.find_overlay(val.as_bytes()) { + // arg is a module + let overlay = engine_state.get_overlay(overlay_id); + if let Some(overlay_span) = overlay.span { + let contents = engine_state.get_span_contents(&overlay_span); + Ok(Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data()) + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the module does not have a viewable block".to_string(), + arg_span, + )) + } + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "this name does not correspond to a viewable value".to_string(), + arg_span, + )) + } + } + _ => Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "this value cannot be viewed".to_string(), + arg_span, + )), + } + } +} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 158ef92957..aff3e2251e 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -761,7 +761,7 @@ pub fn parse_module_block( } } - let mut overlay = Overlay::new(); + let mut overlay = Overlay::from_span(span); let block: Block = output .block diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 83bc5b187c..e9a710496d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2956,6 +2956,7 @@ pub fn parse_block_expression( let captures = find_captures_in_block(working_set, &output, &mut seen, &mut seen_decls); output.captures = captures; + output.span = Some(span); working_set.exit_scope(); diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index ac8163b9ef..79a1f150ce 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,6 +1,6 @@ use std::ops::{Index, IndexMut}; -use crate::{Signature, VarId}; +use crate::{Signature, Span, VarId}; use super::Statement; @@ -10,6 +10,7 @@ pub struct Block { pub stmts: Vec, pub captures: Vec, pub redirect_env: bool, + pub span: Option, // None option encodes no span to avoid using test_span() } impl Block { @@ -49,6 +50,7 @@ impl Block { stmts: vec![], captures: vec![], redirect_env: false, + span: None, } } } @@ -63,6 +65,7 @@ where stmts: stmts.collect(), captures: vec![], redirect_env: false, + span: None, } } } diff --git a/crates/nu-protocol/src/overlay.rs b/crates/nu-protocol/src/overlay.rs index cde25187bb..81b7816113 100644 --- a/crates/nu-protocol/src/overlay.rs +++ b/crates/nu-protocol/src/overlay.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, DeclId}; +use crate::{BlockId, DeclId, Span}; use indexmap::IndexMap; @@ -10,6 +10,7 @@ use indexmap::IndexMap; pub struct Overlay { pub decls: IndexMap, DeclId>, pub env_vars: IndexMap, BlockId>, + pub span: Option, } impl Overlay { @@ -17,6 +18,15 @@ impl Overlay { Overlay { decls: IndexMap::new(), env_vars: IndexMap::new(), + span: None, + } + } + + pub fn from_span(span: Span) -> Self { + Overlay { + decls: IndexMap::new(), + env_vars: IndexMap::new(), + span: Some(span), } }