forked from extern/nushell
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
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"
|
||||
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]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
@ -1890,6 +1899,7 @@ dependencies = [
|
||||
name = "nu-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"is_executable",
|
||||
"log",
|
||||
"miette",
|
||||
"nu-ansi-term",
|
||||
@ -1937,6 +1947,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lscolors",
|
||||
"md-5",
|
||||
"meval",
|
||||
@ -1980,6 +1991,7 @@ dependencies = [
|
||||
"url",
|
||||
"users",
|
||||
"uuid",
|
||||
"which",
|
||||
"zip",
|
||||
]
|
||||
|
||||
|
@ -63,6 +63,7 @@ default = [
|
||||
"plugin",
|
||||
"inc",
|
||||
"example",
|
||||
"which"
|
||||
]
|
||||
|
||||
stable = ["default"]
|
||||
@ -80,6 +81,8 @@ wasi = ["inc"]
|
||||
inc = ["nu_plugin_inc"]
|
||||
example = ["nu_plugin_example"]
|
||||
|
||||
which = ["nu-command/which"]
|
||||
|
||||
# Extra
|
||||
gstat = ["nu_plugin_gstat"]
|
||||
|
||||
|
@ -16,3 +16,4 @@ miette = { version = "3.0.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
reedline = { git = "https://github.com/nushell/reedline", branch = "main" }
|
||||
log = "0.4"
|
||||
is_executable = "1.0.1"
|
@ -19,6 +19,170 @@ impl NuCompleter {
|
||||
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)> {
|
||||
let mut working_set = StateWorkingSet::new(&self.engine_state);
|
||||
let offset = working_set.next_span_start();
|
||||
@ -28,35 +192,20 @@ impl NuCompleter {
|
||||
for stmt in output.stmts.into_iter() {
|
||||
if let Statement::Pipeline(pipeline) = stmt {
|
||||
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);
|
||||
for flat in flattened {
|
||||
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 {
|
||||
nu_parser::FlatShape::Custom(custom_completion) => {
|
||||
let prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
@ -81,8 +230,8 @@ impl NuCompleter {
|
||||
.into_iter()
|
||||
.map(move |x| {
|
||||
let s = x.as_string().expect(
|
||||
"FIXME: better error handling for custom completions",
|
||||
);
|
||||
"FIXME: better error handling for custom completions",
|
||||
);
|
||||
|
||||
(
|
||||
reedline::Span {
|
||||
@ -102,44 +251,11 @@ impl NuCompleter {
|
||||
nu_parser::FlatShape::External
|
||||
| nu_parser::FlatShape::InternalCall
|
||||
| nu_parser::FlatShape::String => {
|
||||
let prefix = working_set.get_span_contents(flat.0);
|
||||
let results = working_set.find_commands_by_prefix(prefix);
|
||||
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 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();
|
||||
return self.complete_filepath_and_commands(
|
||||
&working_set,
|
||||
flat.0,
|
||||
offset,
|
||||
);
|
||||
}
|
||||
nu_parser::FlatShape::Filepath
|
||||
| nu_parser::FlatShape::GlobPattern
|
||||
@ -171,41 +287,7 @@ impl NuCompleter {
|
||||
})
|
||||
.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 }
|
||||
reqwest = {version = "0.11", features = ["blocking"] }
|
||||
mime = "0.3.16"
|
||||
log = "0.4.14"
|
||||
which = { version = "4.2.2", optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
umask = "1.0.0"
|
||||
|
@ -112,6 +112,9 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
|
||||
Sys,
|
||||
};
|
||||
|
||||
#[cfg(feature = "which")]
|
||||
bind_command! { Which };
|
||||
|
||||
// Strings
|
||||
bind_command! {
|
||||
BuildString,
|
||||
|
@ -2,8 +2,10 @@ mod benchmark;
|
||||
mod ps;
|
||||
mod run_external;
|
||||
mod sys;
|
||||
mod which_;
|
||||
|
||||
pub use benchmark::Benchmark;
|
||||
pub use ps::Ps;
|
||||
pub use run_external::{External, ExternalCommand};
|
||||
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> {
|
||||
let mut visibility: Visibility = Visibility::new();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user