diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 2f62a160fc..114803bf8d 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -270,6 +270,14 @@ impl ExternalCommand { "'{}' was not found, did you mean '{s}'?", self.name.item ) + } else if self.name.item == s { + let sugg = engine_state.which_module_has_decl(s.as_bytes()); + if let Some(sugg) = sugg { + let sugg = String::from_utf8_lossy(sugg); + format!("command '{s}' was not found but it exists in module '{sugg}'; try using `{sugg} {s}`") + } else { + format!("did you mean '{s}'?") + } } else { format!("did you mean '{s}'?") } diff --git a/crates/nu-command/tests/commands/use_.rs b/crates/nu-command/tests/commands/use_.rs index 8ea5fdfd0a..a2ff62cbfd 100644 --- a/crates/nu-command/tests/commands/use_.rs +++ b/crates/nu-command/tests/commands/use_.rs @@ -183,3 +183,17 @@ fn use_export_env_combined() { assert_eq!(actual.out, "foo"); }) } + +#[test] +fn use_module_creates_accurate_did_you_mean() { + let actual = nu!( + cwd: ".", pipeline( + r#" + module spam { export def foo [] { "foo" } }; use spam; foo + "# + ) + ); + assert!(actual.err.contains( + "command 'foo' was not found but it exists in module 'spam'; try using `spam foo`" + )); +} diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index d8020900ae..7a5ceae8b9 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -546,6 +546,26 @@ impl EngineState { None } + pub fn which_module_has_decl(&self, name: &[u8]) -> Option<&[u8]> { + for (module_id, m) in self.modules.iter().enumerate() { + if m.has_decl(name) { + for overlay_frame in self.active_overlays(&[]).iter() { + let module_name = overlay_frame.modules.iter().find_map(|(key, &val)| { + if val == module_id { + Some(key) + } else { + None + } + }); + if let Some(final_name) = module_name { + return Some(&final_name[..]); + } + } + } + } + None + } + pub fn find_overlay(&self, name: &[u8]) -> Option { self.scope.find_overlay(name) }