mirror of
https://github.com/nushell/nushell.git
synced 2025-08-16 19:30:59 +02:00
For example, when running the following: crates/nu-cli/src nushell currently parses this as an external command. Before running the command, we check to see if it's a directory. If it is, we "auto cd" into that directory, otherwise we go through normal external processing. If we put a trailing slash on it though, shells typically interpret that as "user is explicitly referencing directory". So crates/nu-cli/src/ should not be interpreted as "run an external command". We intercept a trailing slash in the head position of a command in a pipeline as such, and inject a `cd` internal command.
This commit is contained in:
@ -1,7 +1,16 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CdArgs {
|
||||
pub(crate) path: Option<Tagged<PathBuf>>,
|
||||
}
|
||||
|
||||
pub struct Cd;
|
||||
|
||||
@ -27,12 +36,10 @@ impl WholeStreamCommand for Cd {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
cd(args, registry)
|
||||
args.process(registry, cd)?.run()
|
||||
}
|
||||
}
|
||||
|
||||
fn cd(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let shell_manager = args.shell_manager.clone();
|
||||
let args = args.evaluate_once(registry)?;
|
||||
shell_manager.cd(args)
|
||||
fn cd(args: CdArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
|
||||
context.shell_manager.cd(args, &context)
|
||||
}
|
||||
|
@ -1,29 +1,39 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::classified::external;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
|
||||
use derive_new::new;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{Expression, ExternalArgs, ExternalCommand, Literal, SpannedExpression};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RunExternalArgs {}
|
||||
|
||||
#[derive(new)]
|
||||
pub struct RunExternalCommand;
|
||||
pub struct RunExternalCommand {
|
||||
/// Whether or not nushell is being used in an interactive context
|
||||
pub(crate) interactive: bool,
|
||||
}
|
||||
|
||||
fn spanned_expression_to_string(expr: SpannedExpression) -> String {
|
||||
fn spanned_expression_to_string(expr: SpannedExpression) -> Result<String, ShellError> {
|
||||
if let SpannedExpression {
|
||||
expr: Expression::Literal(Literal::String(s)),
|
||||
..
|
||||
} = expr
|
||||
{
|
||||
s
|
||||
Ok(s)
|
||||
} else {
|
||||
"notacommand!!!".to_string()
|
||||
Err(ShellError::labeled_error(
|
||||
"Expected string for command name",
|
||||
"expected string",
|
||||
expr.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +55,7 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let positionals = args.call_info.args.positional.ok_or_else(|| {
|
||||
let positionals = args.call_info.args.positional.clone().ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error("positional arguments unexpectedly empty")
|
||||
})?;
|
||||
|
||||
@ -53,49 +63,89 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
|
||||
let name = positionals
|
||||
.next()
|
||||
.map(spanned_expression_to_string)
|
||||
.ok_or_else(|| {
|
||||
ShellError::untagged_runtime_error(
|
||||
"run_external unexpectedly missing external name positional arg",
|
||||
)
|
||||
})?;
|
||||
ShellError::untagged_runtime_error("run_external called with no arguments")
|
||||
})
|
||||
.and_then(spanned_expression_to_string)?;
|
||||
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
name_tag: args.call_info.name_tag.clone(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
let mut external_context = {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(
|
||||
std::collections::HashMap::new(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut external_context;
|
||||
#[cfg(windows)]
|
||||
{
|
||||
external_context = Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
};
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
external_context = Context {
|
||||
registry: registry.clone(),
|
||||
host: args.host.clone(),
|
||||
shell_manager: args.shell_manager.clone(),
|
||||
ctrl_c: args.ctrl_c.clone(),
|
||||
current_errors: Arc::new(Mutex::new(vec![])),
|
||||
};
|
||||
}
|
||||
let scope = args.call_info.scope.clone();
|
||||
let is_interactive = self.interactive;
|
||||
|
||||
let is_last = args.call_info.args.is_last;
|
||||
let input = args.input;
|
||||
let stream = async_stream! {
|
||||
let command = ExternalCommand {
|
||||
name,
|
||||
name_tag: args.call_info.name_tag.clone(),
|
||||
args: ExternalArgs {
|
||||
list: positionals.collect(),
|
||||
span: args.call_info.args.span,
|
||||
},
|
||||
};
|
||||
|
||||
// If we're in interactive mode, we will "auto cd". That is, instead of interpreting
|
||||
// this as an external command, we will see it as a path and `cd` into it.
|
||||
if is_interactive {
|
||||
if let Some(path) = maybe_autocd_dir(&command, &mut external_context).await {
|
||||
let cd_args = CdArgs {
|
||||
path: Some(Tagged {
|
||||
item: PathBuf::from(path),
|
||||
tag: args.call_info.name_tag.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
let context = RunnableContext {
|
||||
input: InputStream::empty(),
|
||||
shell_manager: external_context.shell_manager.clone(),
|
||||
host: external_context.host.clone(),
|
||||
ctrl_c: external_context.ctrl_c.clone(),
|
||||
registry: external_context.registry.clone(),
|
||||
name: args.call_info.name_tag.clone(),
|
||||
};
|
||||
|
||||
let result = external_context.shell_manager.cd(cd_args, &context);
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(value) = stream.next().await {
|
||||
yield value;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
yield Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let scope = args.call_info.scope.clone();
|
||||
let is_last = args.call_info.args.is_last;
|
||||
let input = args.input;
|
||||
let result = external::run_external_command(
|
||||
command,
|
||||
&mut external_context,
|
||||
@ -120,3 +170,54 @@ impl WholeStreamCommand for RunExternalCommand {
|
||||
Ok(stream.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
async fn maybe_autocd_dir<'a>(cmd: &ExternalCommand, ctx: &mut Context) -> Option<String> {
|
||||
// We will "auto cd" if
|
||||
// - the command name ends in a path separator, or
|
||||
// - it's not a command on the path and no arguments were given.
|
||||
let name = &cmd.name;
|
||||
let path_name = if name.ends_with(std::path::MAIN_SEPARATOR)
|
||||
|| (cmd.args.is_empty()
|
||||
&& PathBuf::from(name).is_dir()
|
||||
&& dunce::canonicalize(name).is_ok()
|
||||
&& ichwh::which(&name).await.unwrap_or(None).is_none())
|
||||
{
|
||||
Some(name)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
path_name.map(|name| {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if name.ends_with(':') {
|
||||
// This looks like a drive shortcut. We need to a) switch drives and b) go back to the previous directory we were viewing on that drive
|
||||
// But first, we need to save where we are now
|
||||
let current_path = ctx.shell_manager.path();
|
||||
|
||||
let split_path: Vec<_> = current_path.split(':').collect();
|
||||
if split_path.len() > 1 {
|
||||
ctx.windows_drives_previous_cwd
|
||||
.lock()
|
||||
.insert(split_path[0].to_string(), current_path);
|
||||
}
|
||||
|
||||
let name = name.to_uppercase();
|
||||
let new_drive: Vec<_> = name.split(':').collect();
|
||||
|
||||
if let Some(val) = ctx.windows_drives_previous_cwd.lock().get(new_drive[0]) {
|
||||
val.to_string()
|
||||
} else {
|
||||
name
|
||||
}
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
name.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user