* WIP: Start laying overlays

* Rename Overlay->Module; Start adding overlay

* Revamp adding overlay

* Add overlay add tests; Disable debug print

* Fix overlay add; Add overlay remove

* Add overlay remove tests

* Add missing overlay remove file

* Add overlay list command

* (WIP?) Enable overlays for env vars

* Move OverlayFrames to ScopeFrames

* (WIP) Move everything to overlays only

ScopeFrame contains nothing but overlays now

* Fix predecls

* Fix wrong overlay id translation and aliases

* Fix broken env lookup logic

* Remove TODOs

* Add overlay add + remove for environment

* Add a few overlay tests; Fix overlay add name

* Some cleanup; Fix overlay add/remove names

* Clippy

* Fmt

* Remove walls of comments

* List overlays from stack; Add debugging flag

Currently, the engine state ordering is somehow broken.

* Fix (?) overlay list test

* Fix tests on Windows

* Fix activated overlay ordering

* Check for active overlays equality in overlay list

This removes the -p flag: Either both parser and engine will have the
same overlays, or the command will fail.

* Add merging on overlay remove

* Change help message and comment

* Add some remove-merge/discard tests

* (WIP) Track removed overlays properly

* Clippy; Fmt

* Fix getting last overlay; Fix predecls in overlays

* Remove merging; Fix re-add overwriting stuff

Also some error message tweaks.

* Fix overlay error in the engine

* Update variable_completions.rs

* Adds flags and optional arguments to view-source (#5446)

* added flags and optional arguments to view-source

* removed redundant code

* removed redundant code

* fmt

* fix bug in shell_integration (#5450)

* fix bug in shell_integration

* add some comments

* enable cd to work with directory abbreviations (#5452)

* enable cd to work with abbreviations

* add abbreviation example

* fix tests

* make it configurable

* make cd recornize symblic link (#5454)

* implement seq char command to generate single character sequence (#5453)

* add tmp code

* add seq char command

* Add split number flag in `split row` (#5434)

Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com>

* Add two more overlay tests

* Add ModuleId to OverlayFrame

* Fix env conversion accidentally activating overlay

It activated overlay from permanent state prematurely which would
cause `overlay add` to misbehave.

* Remove unused parameter; Add overlay list test

* Remove added traces

* Add overlay commands examples

* Modify TODO

* Fix $nu.scope iteration

* Disallow removing default overlay

* Refactor some parser errors

* Remove last overlay if no argument

* Diversify overlay examples

* Make it possible to update overlay's module

In case the origin module updates, the overlay add loads the new module,
makes it overlay's origin and applies the changes. Before, it was
impossible to update the overlay if the module changed.

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: WindSoilder <WindSoilder@outlook.com>
Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
This commit is contained in:
Jakub Žádník
2022-05-07 22:39:22 +03:00
committed by GitHub
parent 1cb449b2d1
commit 9b99b2f6ac
46 changed files with 2638 additions and 607 deletions

View File

@ -63,23 +63,23 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) {
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
// The first word is a module
let overlay = engine_state.get_overlay(overlay_id);
let module = engine_state.get_module(module_id);
let env_vars_to_hide = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name)
module.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(),
ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) {
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
@ -93,10 +93,10 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
for (name, span) in names {
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) {
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,

View File

@ -22,6 +22,7 @@ mod ignore;
mod let_;
mod metadata;
mod module;
pub(crate) mod overlay;
mod source;
mod tutor;
mod use_;
@ -51,6 +52,7 @@ pub use ignore::Ignore;
pub use let_::Let;
pub use metadata::Metadata;
pub use module::Module;
pub use overlay::*;
pub use source::Source;
pub use tutor::Tutor;
pub use use_::Use;

View File

@ -0,0 +1,139 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
use std::path::Path;
#[derive(Clone)]
pub struct OverlayAdd;
impl Command for OverlayAdd {
fn name(&self) -> &str {
"overlay add"
}
fn usage(&self) -> &str {
"Add definitions from a module as an overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay add")
.required(
"name",
SyntaxShape::String,
"Module name to create overlay for",
)
// TODO:
// .switch(
// "prefix",
// "Prepend module name to the imported symbols",
// Some('p'),
// )
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name_arg: Spanned<String> = call.req(engine_state, stack, 0)?;
// TODO: This logic is duplicated in the parser.
if stack.has_env_overlay(&name_arg.item, engine_state) {
stack.add_overlay(name_arg.item);
} else {
let (overlay_name, module) =
if let Some(module_id) = engine_state.find_module(name_arg.item.as_bytes(), &[]) {
(name_arg.item, engine_state.get_module(module_id))
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
let name = if let Some(s) = os_str.to_str() {
s.to_string()
} else {
return Err(ShellError::NonUtf8(name_arg.span));
};
if let Some(module_id) = engine_state.find_module(name.as_bytes(), &[]) {
(name, engine_state.get_module(module_id))
} else {
return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime(
name_arg.item,
name_arg.span,
));
}
} else {
return Err(ShellError::ModuleOrOverlayNotFoundAtRuntime(
name_arg.item,
name_arg.span,
));
};
stack.add_overlay(overlay_name);
for (name, block_id) in module.env_vars() {
let name = if let Ok(s) = String::from_utf8(name.clone()) {
s
} else {
return Err(ShellError::NonUtf8(name_arg.span));
};
let block = engine_state.get_block(block_id);
let val = eval_block(
engine_state,
stack,
block,
PipelineData::new(call.head),
false,
true,
)?
.into_value(call.head);
stack.add_env_var(name, val);
}
}
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create an overlay from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay add spam"#,
result: None,
},
Example {
description: "Create an overlay from a file",
example: r#"echo 'export env FOO { "foo" }' | save spam.nu
overlay add spam.nu"#,
result: None,
},
]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(OverlayAdd {})
}
}

View File

@ -0,0 +1,58 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct Overlay;
impl Command for Overlay {
fn name(&self) -> &str {
"overlay"
}
fn signature(&self) -> Signature {
Signature::build("overlay").category(Category::Core)
}
fn usage(&self) -> &str {
"Commands for manipulating overlays."
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Overlay.signature(), &[], engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Overlay {})
}
}

View File

@ -0,0 +1,85 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
};
use log::trace;
#[derive(Clone)]
pub struct OverlayList;
impl Command for OverlayList {
fn name(&self) -> &str {
"overlay list"
}
fn usage(&self) -> &str {
"List all active overlays"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay list").category(Category::Core)
}
fn extra_usage(&self) -> &str {
"The overlays are listed in the order they were activated."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let active_overlays_parser: Vec<Value> = engine_state
.active_overlay_names(&[])
.iter()
.map(|s| Value::string(String::from_utf8_lossy(s), call.head))
.collect();
let active_overlays_engine: Vec<Value> = stack
.active_overlays
.iter()
.map(|s| Value::string(s, call.head))
.collect();
// Check if the overlays in the engine match the overlays in the parser
if (active_overlays_parser.len() != active_overlays_engine.len())
|| active_overlays_parser
.iter()
.zip(active_overlays_engine.iter())
.any(|(op, oe)| op != oe)
{
trace!("parser overlays: {:?}", active_overlays_parser);
trace!("engine overlays: {:?}", active_overlays_engine);
return Err(ShellError::NushellFailedSpannedHelp(
"Overlay mismatch".into(),
"Active overlays do not match between the engine and the parser.".into(),
call.head,
"Run Nushell with --log-level=trace to see what went wrong.".into(),
));
}
Ok(Value::List {
vals: active_overlays_engine,
span: call.head,
}
.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the last activated overlay",
example: r#"module spam { export def foo [] { "foo" } }
overlay add spam
overlay list | last"#,
result: Some(Value::String {
val: "spam".to_string(),
span: Span::test_data(),
}),
}]
}
}

View File

@ -0,0 +1,9 @@
mod add;
mod command;
mod list;
mod remove;
pub use add::OverlayAdd;
pub use command::Overlay;
pub use list::OverlayList;
pub use remove::OverlayRemove;

View File

@ -0,0 +1,82 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, Spanned, SyntaxShape};
#[derive(Clone)]
pub struct OverlayRemove;
impl Command for OverlayRemove {
fn name(&self) -> &str {
"overlay remove"
}
fn usage(&self) -> &str {
"Remove an active overlay"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay remove")
.optional("name", SyntaxShape::String, "Overlay to remove")
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
// let module_name: Spanned<String> = call.req(engine_state, stack, 0)?;
let module_name: Spanned<String> = if let Some(name) = call.opt(engine_state, stack, 0)? {
name
} else {
Spanned {
item: stack.last_overlay_name()?,
span: call.head,
}
};
// TODO: Add env merging
stack.remove_overlay(&module_name.item, &module_name.span)?;
Ok(PipelineData::new(call.head))
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove an overlay created from a module",
example: r#"module spam { export def foo [] { "foo" } }
overlay add spam
overlay remove spam"#,
result: None,
},
Example {
description: "Remove an overlay created from a file",
example: r#"echo 'export alias f = "foo"' | save spam.nu
overlay add spam.nu
overlay remove spam"#,
result: None,
},
Example {
description: "Remove the last activated overlay",
example: r#"module spam { export env FOO { "foo" } }
overlay add spam
overlay remove"#,
result: None,
},
]
}
}

View File

@ -424,7 +424,7 @@ fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span
code_mode = false;
//TODO: support no-color mode
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") {
if let Some(highlighter) = engine_state.find_decl(b"nu-highlight", &[]) {
let decl = engine_state.get_decl(highlighter);
if let Ok(output) = decl.run(

View File

@ -55,20 +55,20 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
));
};
if let Some(overlay_id) = import_pattern.head.id {
let overlay = engine_state.get_overlay(overlay_id);
if let Some(module_id) = import_pattern.head.id {
let module = engine_state.get_module(module_id);
let env_vars_to_use = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name)
module.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(),
ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some(id) = overlay.get_env_var_id(name) {
if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !overlay.has_decl(name) && !overlay.has_alias(name) {
} else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
@ -81,9 +81,9 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
let mut output = vec![];
for (name, span) in names {
if let Some(id) = overlay.get_env_var_id(name) {
if let Some(id) = module.get_env_var_id(name) {
output.push((name.clone(), id));
} else if !overlay.has_decl(name) && !overlay.has_alias(name) {
} else if !module.has_decl(name) && !module.has_alias(name) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
@ -105,8 +105,6 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
let block = engine_state.get_block(block_id);
// TODO: Add string conversions (e.g. int to string)
// TODO: Later expand env to take all Values
let val = eval_block(
engine_state,
stack,

View File

@ -38,25 +38,19 @@ impl Command for ListDF {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let vals = engine_state
.scope
.iter()
.flat_map(|frame| {
frame
.vars
.iter()
.filter_map(|var| {
let value = stack.get_var(*var.1, call.head);
match value {
Ok(value) => {
let name = String::from_utf8_lossy(var.0).to_string();
Some((name, value))
}
Err(_) => None,
}
})
.collect::<Vec<(String, Value)>>()
})
let mut vals: Vec<(String, Value)> = vec![];
for overlay_frame in engine_state.active_overlays(&[]) {
for var in &overlay_frame.vars {
if let Ok(value) = stack.get_var(*var.1, call.head) {
let name = String::from_utf8_lossy(var.0).to_string();
vals.push((name, value));
}
}
}
let vals = vals
.into_iter()
.filter_map(|(name, value)| match NuDataFrame::try_from_value(value) {
Ok(df) => Some((name, df)),
Err(_) => None,

View File

@ -52,6 +52,10 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
History,
If,
Ignore,
Overlay,
OverlayAdd,
OverlayList,
OverlayRemove,
Let,
Metadata,
Module,

View File

@ -47,7 +47,7 @@ impl Command for ViewSource {
}
}
Value::String { val, .. } => {
if let Some(decl_id) = engine_state.find_decl(val.as_bytes()) {
if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
// arg is a command
let decl = engine_state.get_decl(decl_id);
let sig = decl.signature();
@ -115,11 +115,11 @@ impl Command for ViewSource {
Vec::new(),
))
}
} else if let Some(overlay_id) = engine_state.find_overlay(val.as_bytes()) {
} else if let Some(module_id) = engine_state.find_module(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);
let module = engine_state.get_module(module_id);
if let Some(module_span) = module.span {
let contents = engine_state.get_span_contents(&module_span);
Ok(Value::string(String::from_utf8_lossy(contents), call.head)
.into_pipeline_data())
} else {

View File

@ -151,7 +151,7 @@ impl Command for Open {
};
if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) {
match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => {
let decl = engine_state.get_decl(converter_id);
if let Some(block_id) = decl.get_block_id() {

View File

@ -82,7 +82,7 @@ impl Command for Save {
};
if let Some(ext) = ext {
let output = match engine_state.find_decl(format!("to {}", ext).as_bytes()) {
let output = match engine_state.find_decl(format!("to {}", ext).as_bytes(), &[]) {
Some(converter_id) => {
let output = engine_state.get_decl(converter_id).run(
engine_state,

View File

@ -294,7 +294,7 @@ fn helper(
}
if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) {
match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => engine_state.get_decl(converter_id).run(
engine_state,
stack,

View File

@ -351,7 +351,7 @@ fn helper(
return Ok(output);
}
if let Some(ext) = ext {
match engine_state.find_decl(format!("from {}", ext).as_bytes()) {
match engine_state.find_decl(format!("from {}", ext).as_bytes(), &[]) {
Some(converter_id) => engine_state.get_decl(converter_id).run(
engine_state,
stack,

View File

@ -69,7 +69,7 @@ fn entry(arg: impl Into<String>, path: impl Into<String>, builtin: bool, span: S
}
fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
if let Some(alias_id) = engine_state.find_alias(name.as_bytes()) {
if let Some(alias_id) = engine_state.find_alias(name.as_bytes(), &[]) {
let alias = engine_state.get_alias(alias_id);
let alias_str = alias
.iter()
@ -90,7 +90,7 @@ fn get_entry_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> O
}
fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
if let Some(decl_id) = engine_state.find_decl(name.as_bytes()) {
if let Some(decl_id) = engine_state.find_decl(name.as_bytes(), &[]) {
let (msg, is_builtin) = if engine_state.get_decl(decl_id).get_block_id().is_some() {
("Nushell custom command", false)
} else {