feat(overlay): add active column to overlay list (#16125)

# Description

This adds a column to overlay list which indicates whether given overlay
is active or not
```nu
overlay new spam
overlay  hide spam
overlay list 
 0   spam
```


# User-Facing Changes
## Release notes summary
`overlay list` now returns table instead of list of overlays. Before,
only active overlays were included in `overlay list`. Now, hidden
overlays will be included as well, and there is a column indicating
whether a given overlay is hidden or not.

> TODO(release-notes): make sure this call out is formatted correctly
for the blog

> [!TIP]
> The ordering of `overlay list` is still preserved. If you run `overlay
list | last`, you will still get the most recently activated overlay.
>
> For migrating to the new behavior, you can update any usages of
`overlay list | last` to `overlay list | last | get name`.

# Tests + Formatting

# After Submitting
This commit is contained in:
hardfault
2025-08-25 19:15:47 +05:30
committed by GitHub
parent ba953a7e8b
commit ca0e9614ad
4 changed files with 87 additions and 33 deletions

View File

@@ -16,7 +16,7 @@ impl Command for Hide {
.optional(
"members",
SyntaxShape::Any,
"Which members of the module to import.",
"Which members of the module to hide.",
)
.category(Category::Core)
}

View File

@@ -9,42 +9,96 @@ impl Command for OverlayList {
}
fn description(&self) -> &str {
"List all active overlays."
"List all overlays with their active status."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("overlay list")
.category(Category::Core)
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
.input_output_types(vec![(
Type::Nothing,
Type::Table(
vec![
("name".to_string(), Type::String),
("active".to_string(), Type::Bool),
]
.into(),
),
)])
}
fn extra_description(&self) -> &str {
"The overlays are listed in the order they were activated."
"The overlays are listed in the order they were activated. Hidden overlays are listed first, followed by active overlays listed in the order that they were activated. `last` command will always give the top active overlay"
}
fn run(
&self,
_engine_state: &EngineState,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let active_overlays_engine: Vec<Value> = stack
// get active overlay iterator
let active_overlays = stack
.active_overlays
.iter()
.map(|s| Value::string(s, call.head))
.map(|overlay| (overlay.clone(), true));
// Get all overlay names from engine state
let output_rows: Vec<Value> = engine_state
.scope
.overlays
.iter()
.filter_map(|(name, _)| {
let name = String::from_utf8_lossy(name).to_string();
if stack
.active_overlays
.iter()
.any(|active_name| active_name == &name)
{
None
} else {
Some((name, false))
}
})
.chain(active_overlays)
.map(|(name, active)| {
Value::record(
record! {
"name" => Value::string(name.to_owned(), call.head),
"active" => Value::bool(active, call.head),
},
call.head,
)
})
.collect();
Ok(Value::list(active_overlays_engine, call.head).into_pipeline_data())
Ok(Value::list(output_rows, 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" } }
vec![
Example {
description: "List all overlays with their active status",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
overlay list | last"#,
result: Some(Value::test_string("spam")),
}]
overlay list"#,
result: Some(Value::test_list(vec![Value::test_record(record! {
"name" => Value::test_string("spam"),
"active" => Value::test_bool(true),
})])),
},
Example {
description: "Get overlay status after hiding",
example: r#"module spam { export def foo [] { "foo" } }
overlay use spam
overlay hide spam
overlay list | where name == "spam""#,
result: Some(Value::test_list(vec![Value::test_record(record! {
"name" => Value::test_string("spam"),
"active" => Value::test_bool(false),
})])),
},
]
}
}

View File

@@ -66,8 +66,8 @@ impl Command for OverlayUse {
return Ok(PipelineData::empty());
}
let mut name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
name_arg.item = trim_quotes_str(&name_arg.item).to_string();
let name_arg: Spanned<String> = call.req(engine_state, caller_stack, 0)?;
let name_arg_item = trim_quotes_str(&name_arg.item);
let maybe_origin_module_id: Option<ModuleId> =
if let Some(overlay_expr) = call.get_parser_info(caller_stack, "overlay_expr") {
@@ -91,11 +91,11 @@ impl Command for OverlayUse {
let overlay_name = if let Some(name) = call.opt(engine_state, caller_stack, 1)? {
name
} else if engine_state
.find_overlay(name_arg.item.as_bytes())
.find_overlay(name_arg_item.as_bytes())
.is_some()
{
name_arg.item.clone()
} else if let Some(os_str) = Path::new(&name_arg.item).file_stem() {
name_arg_item.to_string()
} else if let Some(os_str) = Path::new(name_arg_item).file_stem() {
if let Some(name) = os_str.to_str() {
name.to_string()
} else {
@@ -105,7 +105,7 @@ impl Command for OverlayUse {
}
} else {
return Err(ShellError::OverlayNotFoundAtRuntime {
overlay_name: name_arg.item,
overlay_name: (name_arg_item.to_string()),
span: name_arg.span,
});
};
@@ -122,7 +122,7 @@ impl Command for OverlayUse {
// Evaluate the export-env block (if any) and keep its environment
if let Some(block_id) = module.env_block {
let maybe_file_path_or_dir = find_in_dirs_env(
&name_arg.item,
name_arg_item,
engine_state,
caller_stack,
get_dirs_var_from_call(caller_stack, call),

View File

@@ -198,7 +198,7 @@ fn new_overlay_from_const_name() {
let inp = &[
"const mod = 'spam'",
"overlay new $mod",
"overlay list | last",
"overlay list | last | get name",
];
let actual = nu!(&inp.join("; "));
@@ -212,7 +212,7 @@ fn hide_overlay_from_const_name() {
"const mod = 'spam'",
"overlay new $mod",
"overlay hide $mod",
"overlay list | str join ' '",
"overlay list | where active == true | get name | str join ' '",
];
let actual = nu!(&inp.join("; "));
@@ -410,7 +410,7 @@ fn hide_overlay_scoped_env() {
#[test]
fn list_default_overlay() {
let inp = &["overlay list | last"];
let inp = &["overlay list | last | get name"];
let actual = nu!(&inp.join("; "));
let actual_repl = nu!(nu_repl_code(inp));
@@ -424,7 +424,7 @@ fn list_last_overlay() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
"overlay use spam",
"overlay list | last",
"overlay list | last | get name",
];
let actual = nu!(&inp.join("; "));
@@ -439,7 +439,7 @@ fn list_overlay_scoped() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
"overlay use spam",
"do { overlay list | last }",
"do { overlay list | last | get name }",
];
let actual = nu!(&inp.join("; "));
@@ -695,7 +695,7 @@ fn reset_overrides() {
#[test]
fn overlay_new() {
let inp = &["overlay new spam", "overlay list | last"];
let inp = &["overlay new spam", "overlay list | last | get name"];
let actual = nu!(&inp.join("; "));
let actual_repl = nu!(nu_repl_code(inp));
@@ -1080,7 +1080,7 @@ fn overlay_use_find_scoped_module() {
module spam { }
overlay use spam
overlay list | last
overlay list | last | get name
}
";
@@ -1196,7 +1196,7 @@ fn overlay_trim_single_quote() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
"overlay use 'spam'",
"overlay list | last ",
"overlay list | last | get name",
];
let actual = nu!(&inp.join("; "));
@@ -1227,7 +1227,7 @@ fn overlay_trim_double_quote() {
let inp = &[
r#"module spam { export def foo [] { "foo" } }"#,
r#"overlay use "spam" "#,
"overlay list | last ",
"overlay list | last | get name",
];
let actual = nu!(&inp.join("; "));
@@ -1428,7 +1428,7 @@ fn alias_overlay_use_2() {
"module spam { export alias b = overlay use inner }",
"use spam",
"spam b",
"overlay list | get 1",
"overlay list | get 1.name",
];
let actual = nu!(&inp.join("; "));
@@ -1447,7 +1447,7 @@ fn alias_overlay_use_3() {
"module spam { export alias b = overlay use inner }",
"use spam b",
"b",
"overlay list | get 1",
"overlay list | get 1.name",
];
let actual = nu!(&inp.join("; "));
@@ -1465,7 +1465,7 @@ fn alias_overlay_new() {
"alias on = overlay new",
"on spam",
"on eggs",
"overlay list | last",
"overlay list | last | get name",
];
let actual = nu!(&inp.join("; "));