mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Add which
command, add external completions, and builtin var completions (#782)
* Add which and external completions * WIP * Finish up external and var completions * fix windows
This commit is contained in:
parent
d4b6b4b09a
commit
33ffb2c39a
9 changed files with 490 additions and 101 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -1430,6 +1430,15 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89"
|
checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_executable"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
|
@ -1890,6 +1899,7 @@ dependencies = [
|
||||||
name = "nu-cli"
|
name = "nu-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"is_executable",
|
||||||
"log",
|
"log",
|
||||||
"miette",
|
"miette",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
@ -1937,6 +1947,7 @@ dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"md-5",
|
"md-5",
|
||||||
"meval",
|
"meval",
|
||||||
|
@ -1980,6 +1991,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"users",
|
"users",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"which",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ default = [
|
||||||
"plugin",
|
"plugin",
|
||||||
"inc",
|
"inc",
|
||||||
"example",
|
"example",
|
||||||
|
"which"
|
||||||
]
|
]
|
||||||
|
|
||||||
stable = ["default"]
|
stable = ["default"]
|
||||||
|
@ -80,6 +81,8 @@ wasi = ["inc"]
|
||||||
inc = ["nu_plugin_inc"]
|
inc = ["nu_plugin_inc"]
|
||||||
example = ["nu_plugin_example"]
|
example = ["nu_plugin_example"]
|
||||||
|
|
||||||
|
which = ["nu-command/which"]
|
||||||
|
|
||||||
# Extra
|
# Extra
|
||||||
gstat = ["nu_plugin_gstat"]
|
gstat = ["nu_plugin_gstat"]
|
||||||
|
|
||||||
|
|
|
@ -16,3 +16,4 @@ miette = { version = "3.0.0", features = ["fancy"] }
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
is_executable = "1.0.1"
|
|
@ -19,6 +19,170 @@ impl NuCompleter {
|
||||||
Self { engine_state }
|
Self { engine_state }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn external_command_completion(&self, prefix: &str) -> Vec<String> {
|
||||||
|
let mut executables = vec![];
|
||||||
|
|
||||||
|
let paths;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
paths = self.engine_state.env_vars.get("Path");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
paths = self.engine_state.env_vars.get("PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(paths) = paths {
|
||||||
|
if let Ok(paths) = paths.as_list() {
|
||||||
|
for path in paths {
|
||||||
|
let path = path.as_string().unwrap_or_default();
|
||||||
|
|
||||||
|
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||||
|
while let Some(Ok(item)) = contents.next() {
|
||||||
|
if !executables.contains(
|
||||||
|
&item
|
||||||
|
.path()
|
||||||
|
.file_name()
|
||||||
|
.map(|x| x.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
) && matches!(
|
||||||
|
item.path()
|
||||||
|
.file_name()
|
||||||
|
.map(|x| x.to_string_lossy().starts_with(prefix)),
|
||||||
|
Some(true)
|
||||||
|
) && is_executable::is_executable(&item.path())
|
||||||
|
{
|
||||||
|
if let Ok(name) = item.file_name().into_string() {
|
||||||
|
executables.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executables
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_variables(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
prefix: &[u8],
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> Vec<(reedline::Span, String)> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
let builtins = ["$nu", "$scope", "$in", "$config", "$env"];
|
||||||
|
|
||||||
|
for builtin in builtins {
|
||||||
|
if builtin.as_bytes().starts_with(prefix) {
|
||||||
|
output.push((
|
||||||
|
reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
builtin.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for scope in &working_set.delta.scope {
|
||||||
|
for v in &scope.vars {
|
||||||
|
if v.0.starts_with(prefix) {
|
||||||
|
output.push((
|
||||||
|
reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
String::from_utf8_lossy(v.0).to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for scope in &self.engine_state.scope {
|
||||||
|
for v in &scope.vars {
|
||||||
|
if v.0.starts_with(prefix) {
|
||||||
|
output.push((
|
||||||
|
reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
String::from_utf8_lossy(v.0).to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.dedup();
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_filepath_and_commands(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
span: Span,
|
||||||
|
offset: usize,
|
||||||
|
) -> Vec<(reedline::Span, String)> {
|
||||||
|
let prefix = working_set.get_span_contents(span);
|
||||||
|
|
||||||
|
let results = working_set
|
||||||
|
.find_commands_by_prefix(prefix)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| {
|
||||||
|
(
|
||||||
|
reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
String::from_utf8_lossy(&x).to_string(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
|
||||||
|
match d.as_string() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => "".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let prefix = String::from_utf8_lossy(prefix).to_string();
|
||||||
|
let results_paths = file_path_completion(span, &prefix, &cwd)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| {
|
||||||
|
(
|
||||||
|
reedline::Span {
|
||||||
|
start: x.0.start - offset,
|
||||||
|
end: x.0.end - offset,
|
||||||
|
},
|
||||||
|
x.1,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let results_external =
|
||||||
|
self.external_command_completion(&prefix)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| {
|
||||||
|
(
|
||||||
|
reedline::Span {
|
||||||
|
start: span.start - offset,
|
||||||
|
end: span.end - offset,
|
||||||
|
},
|
||||||
|
x,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
results
|
||||||
|
.chain(results_paths.into_iter())
|
||||||
|
.chain(results_external.into_iter())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
|
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
|
||||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
@ -28,35 +192,20 @@ impl NuCompleter {
|
||||||
for stmt in output.stmts.into_iter() {
|
for stmt in output.stmts.into_iter() {
|
||||||
if let Statement::Pipeline(pipeline) = stmt {
|
if let Statement::Pipeline(pipeline) = stmt {
|
||||||
for expr in pipeline.expressions {
|
for expr in pipeline.expressions {
|
||||||
if pos >= expr.span.start
|
|
||||||
&& (pos <= (line.len() + offset) || pos <= expr.span.end)
|
|
||||||
{
|
|
||||||
let possible_cmd = working_set.get_span_contents(Span {
|
|
||||||
start: expr.span.start,
|
|
||||||
end: pos,
|
|
||||||
});
|
|
||||||
|
|
||||||
let results = working_set.find_commands_by_prefix(possible_cmd);
|
|
||||||
|
|
||||||
if !results.is_empty() {
|
|
||||||
return results
|
|
||||||
.into_iter()
|
|
||||||
.map(move |x| {
|
|
||||||
(
|
|
||||||
reedline::Span {
|
|
||||||
start: expr.span.start - offset,
|
|
||||||
end: pos - offset,
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(&x).to_string(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let flattened = flatten_expression(&working_set, &expr);
|
let flattened = flatten_expression(&working_set, &expr);
|
||||||
for flat in flattened {
|
for flat in flattened {
|
||||||
if pos >= flat.0.start && pos <= flat.0.end {
|
if pos >= flat.0.start && pos <= flat.0.end {
|
||||||
|
let prefix = working_set.get_span_contents(flat.0);
|
||||||
|
|
||||||
|
if prefix.starts_with(b"$") {
|
||||||
|
return self.complete_variables(
|
||||||
|
&working_set,
|
||||||
|
prefix,
|
||||||
|
flat.0,
|
||||||
|
offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
match &flat.1 {
|
match &flat.1 {
|
||||||
nu_parser::FlatShape::Custom(custom_completion) => {
|
nu_parser::FlatShape::Custom(custom_completion) => {
|
||||||
let prefix = working_set.get_span_contents(flat.0).to_vec();
|
let prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||||
|
@ -102,44 +251,11 @@ impl NuCompleter {
|
||||||
nu_parser::FlatShape::External
|
nu_parser::FlatShape::External
|
||||||
| nu_parser::FlatShape::InternalCall
|
| nu_parser::FlatShape::InternalCall
|
||||||
| nu_parser::FlatShape::String => {
|
| nu_parser::FlatShape::String => {
|
||||||
let prefix = working_set.get_span_contents(flat.0);
|
return self.complete_filepath_and_commands(
|
||||||
let results = working_set.find_commands_by_prefix(prefix);
|
&working_set,
|
||||||
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
|
flat.0,
|
||||||
{
|
offset,
|
||||||
match d.as_string() {
|
);
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => "".to_string(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let prefix = String::from_utf8_lossy(prefix).to_string();
|
|
||||||
let results2 = file_path_completion(flat.0, &prefix, &cwd)
|
|
||||||
.into_iter()
|
|
||||||
.map(move |x| {
|
|
||||||
(
|
|
||||||
reedline::Span {
|
|
||||||
start: x.0.start - offset,
|
|
||||||
end: x.0.end - offset,
|
|
||||||
},
|
|
||||||
x.1,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
return results
|
|
||||||
.into_iter()
|
|
||||||
.map(move |x| {
|
|
||||||
(
|
|
||||||
reedline::Span {
|
|
||||||
start: flat.0.start - offset,
|
|
||||||
end: flat.0.end - offset,
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(&x).to_string(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.chain(results2.into_iter())
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
nu_parser::FlatShape::Filepath
|
nu_parser::FlatShape::Filepath
|
||||||
| nu_parser::FlatShape::GlobPattern
|
| nu_parser::FlatShape::GlobPattern
|
||||||
|
@ -171,41 +287,7 @@ impl NuCompleter {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {}
|
||||||
let prefix = working_set.get_span_contents(flat.0);
|
|
||||||
|
|
||||||
if prefix.starts_with(b"$") {
|
|
||||||
let mut output = vec![];
|
|
||||||
|
|
||||||
for scope in &working_set.delta.scope {
|
|
||||||
for v in &scope.vars {
|
|
||||||
if v.0.starts_with(prefix) {
|
|
||||||
output.push((
|
|
||||||
reedline::Span {
|
|
||||||
start: flat.0.start - offset,
|
|
||||||
end: flat.0.end - offset,
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(v.0).to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for scope in &self.engine_state.scope {
|
|
||||||
for v in &scope.vars {
|
|
||||||
if v.0.starts_with(prefix) {
|
|
||||||
output.push((
|
|
||||||
reedline::Span {
|
|
||||||
start: flat.0.start - offset,
|
|
||||||
end: flat.0.end - offset,
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(v.0).to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,8 @@ encoding_rs = "0.8.30"
|
||||||
num = { version = "0.4.0", optional = true }
|
num = { version = "0.4.0", optional = true }
|
||||||
reqwest = {version = "0.11", features = ["blocking"] }
|
reqwest = {version = "0.11", features = ["blocking"] }
|
||||||
mime = "0.3.16"
|
mime = "0.3.16"
|
||||||
|
log = "0.4.14"
|
||||||
|
which = { version = "4.2.2", optional = true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
umask = "1.0.0"
|
umask = "1.0.0"
|
||||||
|
|
|
@ -112,6 +112,9 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
||||||
Sys,
|
Sys,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
bind_command! { Which };
|
||||||
|
|
||||||
// Strings
|
// Strings
|
||||||
bind_command! {
|
bind_command! {
|
||||||
BuildString,
|
BuildString,
|
||||||
|
|
|
@ -2,8 +2,10 @@ mod benchmark;
|
||||||
mod ps;
|
mod ps;
|
||||||
mod run_external;
|
mod run_external;
|
||||||
mod sys;
|
mod sys;
|
||||||
|
mod which_;
|
||||||
|
|
||||||
pub use benchmark::Benchmark;
|
pub use benchmark::Benchmark;
|
||||||
pub use ps::Ps;
|
pub use ps::Ps;
|
||||||
pub use run_external::{External, ExternalCommand};
|
pub use run_external::{External, ExternalCommand};
|
||||||
pub use sys::Sys;
|
pub use sys::Sys;
|
||||||
|
pub use which_::Which;
|
||||||
|
|
256
crates/nu-command/src/system/which_.rs
Normal file
256
crates/nu-command/src/system/which_.rs
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::trace;
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Which;
|
||||||
|
|
||||||
|
impl Command for Which {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"which"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("which")
|
||||||
|
.required("application", SyntaxShape::String, "application")
|
||||||
|
.rest("rest", SyntaxShape::String, "additional applications")
|
||||||
|
.switch("all", "list all executables", Some('a'))
|
||||||
|
.category(Category::System)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Finds a program file, alias or custom command."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
which(engine_state, stack, call)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Find if the 'myapp' application is available",
|
||||||
|
example: "which myapp",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shortcuts for creating an entry to the output table
|
||||||
|
fn entry(arg: impl Into<String>, path: Value, builtin: bool, span: Span) -> Value {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
cols.push("arg".to_string());
|
||||||
|
vals.push(Value::string(arg.into(), span));
|
||||||
|
|
||||||
|
cols.push("path".to_string());
|
||||||
|
vals.push(path);
|
||||||
|
|
||||||
|
cols.push("builtin".to_string());
|
||||||
|
vals.push(Value::Bool { val: builtin, span });
|
||||||
|
|
||||||
|
Value::Record { cols, vals, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! create_entry {
|
||||||
|
($arg:expr, $path:expr, $span:expr, $is_builtin:expr) => {
|
||||||
|
entry(
|
||||||
|
$arg.clone(),
|
||||||
|
Value::string($path.to_string(), $span),
|
||||||
|
$is_builtin,
|
||||||
|
$span,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entries_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Vec<Value> {
|
||||||
|
let aliases = engine_state.find_aliases(name);
|
||||||
|
|
||||||
|
let aliases = aliases
|
||||||
|
.into_iter()
|
||||||
|
.map(|spans| {
|
||||||
|
spans
|
||||||
|
.into_iter()
|
||||||
|
.map(|span| {
|
||||||
|
String::from_utf8_lossy(engine_state.get_span_contents(&span)).to_string()
|
||||||
|
})
|
||||||
|
.join(" ")
|
||||||
|
})
|
||||||
|
.map(|alias| create_entry!(name, format!("Nushell alias: {}", alias), span, false))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
trace!("Found {} aliases", aliases.len());
|
||||||
|
aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entries_in_custom_command(engine_state: &EngineState, name: &str, span: Span) -> Vec<Value> {
|
||||||
|
let custom_commands = engine_state.find_custom_commands(name);
|
||||||
|
|
||||||
|
custom_commands
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| create_entry!(name, "Nushell custom command", span, false))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option<Value> {
|
||||||
|
if engine_state.find_decl(name.as_bytes()).is_some() {
|
||||||
|
Some(create_entry!(name, "Nushell built-in command", span, true))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entries_in_nu(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
name: &str,
|
||||||
|
span: Span,
|
||||||
|
skip_after_first_found: bool,
|
||||||
|
) -> Vec<Value> {
|
||||||
|
let mut all_entries = vec![];
|
||||||
|
|
||||||
|
all_entries.extend(get_entries_in_aliases(engine_state, name, span));
|
||||||
|
if !all_entries.is_empty() && skip_after_first_found {
|
||||||
|
return all_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
all_entries.extend(get_entries_in_custom_command(engine_state, name, span));
|
||||||
|
if !all_entries.is_empty() && skip_after_first_found {
|
||||||
|
return all_entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(entry) = get_entry_in_commands(engine_state, name, span) {
|
||||||
|
all_entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
all_entries
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
macro_rules! entry_path {
|
||||||
|
($arg:expr, $path:expr, $span:expr) => {
|
||||||
|
entry($arg.clone(), Value::string($path, $span), false, $span)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
fn get_first_entry_in_path(item: &str, span: Span) -> Option<Value> {
|
||||||
|
which::which(item)
|
||||||
|
.map(|path| entry_path!(item, path.to_string_lossy().to_string(), span))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "which"))]
|
||||||
|
fn get_first_entry_in_path(_: &str, _: Span) -> Option<Value> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "which")]
|
||||||
|
fn get_all_entries_in_path(item: &str, span: Span) -> Vec<Value> {
|
||||||
|
which::which_all(&item)
|
||||||
|
.map(|iter| {
|
||||||
|
iter.map(|path| entry_path!(item, path.to_string_lossy().to_string(), span))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "which"))]
|
||||||
|
fn get_all_entries_in_path(_: &str, _: Span) -> Vec<Value> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WhichArgs {
|
||||||
|
applications: Vec<Spanned<String>>,
|
||||||
|
all: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn which_single(application: Spanned<String>, all: bool, engine_state: &EngineState) -> Vec<Value> {
|
||||||
|
let (external, prog_name) = if application.item.starts_with('^') {
|
||||||
|
(true, application.item[1..].to_string())
|
||||||
|
} else {
|
||||||
|
(false, application.item.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
//If prog_name is an external command, don't search for nu-specific programs
|
||||||
|
//If all is false, we can save some time by only searching for the first matching
|
||||||
|
//program
|
||||||
|
//This match handles all different cases
|
||||||
|
match (all, external) {
|
||||||
|
(true, true) => get_all_entries_in_path(&prog_name, application.span),
|
||||||
|
(true, false) => {
|
||||||
|
let mut output: Vec<Value> = vec![];
|
||||||
|
output.extend(get_entries_in_nu(
|
||||||
|
engine_state,
|
||||||
|
&prog_name,
|
||||||
|
application.span,
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
output.extend(get_all_entries_in_path(&prog_name, application.span));
|
||||||
|
output
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
if let Some(entry) = get_first_entry_in_path(&prog_name, application.span) {
|
||||||
|
return vec![entry];
|
||||||
|
}
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
(false, false) => {
|
||||||
|
let nu_entries = get_entries_in_nu(engine_state, &prog_name, application.span, true);
|
||||||
|
if !nu_entries.is_empty() {
|
||||||
|
return vec![nu_entries[0].clone()];
|
||||||
|
} else if let Some(entry) = get_first_entry_in_path(&prog_name, application.span) {
|
||||||
|
return vec![entry];
|
||||||
|
}
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn which(
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let which_args = WhichArgs {
|
||||||
|
applications: call.rest(engine_state, stack, 0)?,
|
||||||
|
all: call.has_flag("all"),
|
||||||
|
};
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
|
if which_args.applications.is_empty() {
|
||||||
|
return Err(ShellError::MissingParameter(
|
||||||
|
"application".into(),
|
||||||
|
call.head,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for app in which_args.applications {
|
||||||
|
let values = which_single(app, which_args.all, engine_state);
|
||||||
|
output.extend(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output.into_iter().into_pipeline_data(ctrlc))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
crate::test_examples(Which)
|
||||||
|
}
|
||||||
|
}
|
|
@ -344,6 +344,34 @@ impl EngineState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_aliases(&self, name: &str) -> Vec<Vec<Span>> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for frame in &self.scope {
|
||||||
|
if let Some(alias) = frame.aliases.get(name.as_bytes()) {
|
||||||
|
output.push(alias.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_custom_commands(&self, name: &str) -> Vec<Block> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for frame in &self.scope {
|
||||||
|
if let Some(decl_id) = frame.decls.get(name.as_bytes()) {
|
||||||
|
let decl = self.get_decl(*decl_id);
|
||||||
|
|
||||||
|
if let Some(block_id) = decl.get_block_id() {
|
||||||
|
output.push(self.get_block(block_id).clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
||||||
let mut visibility: Visibility = Visibility::new();
|
let mut visibility: Visibility = Visibility::new();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue