Commit d1c2a814 authored by AdminXVII's avatar AdminXVII Committed by Michael Aaron Murphy
Browse files

fix!: Make auto-cd an interactive feature, rather than a library feature

Breaking Change: auto-cd will no longer wrk in scripts. However, it was
never intended to work in scripts to begin with, so this is actually an
improvement.

If a command fails, try to cd before outputing the error.

Issues fixed by this:
 - Trailing slashes are no longer necessary
 - If a binary and a folder conflicts, the binary has priority
 - Typing `-` cd to the old pwd

Fix #892
parent 5a3dbece
......@@ -20,6 +20,7 @@ use std::{
cell::{Cell, RefCell},
fs::{self, OpenOptions},
io::{self, Write},
os::unix::io::{AsRawFd, IntoRawFd},
path::Path,
rc::Rc,
};
......@@ -308,19 +309,37 @@ impl<'a> InteractiveShell<'a> {
Err(IonError::PipelineExecutionError(
PipelineError::CommandNotFound(command),
)) => {
if let Some(Value::Function(func)) =
shell.variables().get("COMMAND_NOT_FOUND").cloned()
if Self::try_cd(&command, &mut shell)
.ok()
.map_or(false, |res| res.is_failure())
{
if let Err(why) =
shell.execute_function(&func, &["ion", &command])
if let Some(Value::Function(func)) =
shell.variables().get("COMMAND_NOT_FOUND").cloned()
{
eprintln!("ion: command not found handler: {}", why);
if let Err(why) =
shell.execute_function(&func, &["ion", &command])
{
eprintln!("ion: command not found handler: {}", why);
}
} else {
eprintln!("ion: command not found: {}", command);
}
} else {
eprintln!("ion: command not found: {}", command);
}
// Status::COULD_NOT_EXEC
}
Err(IonError::PipelineExecutionError(
PipelineError::CommandExecError(ref err, ref command),
)) if err.kind() == io::ErrorKind::PermissionDenied
&& command.len() == 1 =>
{
if Self::try_cd(&command[0], &mut shell)
.ok()
.map_or(false, |res| res.is_failure())
{
eprintln!("ion: {}", err);
shell.reset_flow();
}
}
Err(err) => {
eprintln!("ion: {}", err);
shell.reset_flow();
......@@ -334,6 +353,26 @@ impl<'a> InteractiveShell<'a> {
}
}
/// Try to cd if the command failed
fn try_cd(dir: &str, shell: &mut Shell<'_>) -> nix::Result<Status> {
// Gag the cd output
let null = OpenOptions::new()
.write(true)
.open(if cfg!(target_os = "redox") { "null:" } else { "/dev/null" })
.map_err(|err| {
nix::Error::from_errno(nix::errno::Errno::from_i32(err.raw_os_error().unwrap()))
})?
.into_raw_fd();
let fd = io::stderr().as_raw_fd();
let fd_dup = nix::unistd::dup(fd)?;
nix::unistd::dup2(null, fd)?;
let out = ion_shell::builtins::builtin_cd(&["cd".into(), dir.into()], shell);
nix::unistd::dup2(fd_dup, fd)?;
nix::unistd::close(fd_dup)?;
Ok(out)
}
/// Set the keybindings of the underlying liner context
pub fn set_keybindings(&mut self, key_bindings: KeyBindings) {
self.context.borrow_mut().key_bindings = key_bindings;
......
use super::{IonError, Shell};
use crate::{
builtins::{self, BuiltinFunction},
builtins::BuiltinFunction,
expansion::{self, pipelines::RedirectFrom, Expander},
types, Value,
};
use std::{fmt, fs::File, iter, path::Path, str};
use std::{fmt, fs::File, str};
#[derive(Clone)]
pub struct Job<'a> {
......@@ -13,17 +13,6 @@ pub struct Job<'a> {
pub builtin: Option<BuiltinFunction<'a>>,
}
/// Determines if the supplied command implicitly defines to change the directory.
///
/// This is detected by first checking if the argument starts with a '.' or an '/', or ends
/// with a '/'. If that validates, then it will check if the supplied argument is a valid
/// directory path.
#[inline(always)]
fn is_implicit_cd(argument: &str) -> bool {
(argument.starts_with('.') || argument.starts_with('/') || argument.ends_with('/'))
&& Path::new(argument).is_dir()
}
impl<'a> Job<'a> {
/// Get the job command (its first arg)
pub fn command(&self) -> &types::Str { &self.args[0] }
......@@ -36,13 +25,7 @@ impl<'a> Job<'a> {
args.extend(expand_arg(arg, shell)?);
}
Ok(if is_implicit_cd(&args[0]) {
RefinedJob::builtin(
&builtins::builtin_cd,
iter::once("cd".into()).chain(args).collect(),
self.redirection,
)
} else if let Some(Value::Function(_)) = shell.variables.get(&self.args[0]) {
Ok(if let Some(Value::Function(_)) = shell.variables.get(&self.args[0]) {
RefinedJob::function(self.args.clone(), self.redirection)
} else if let Some(builtin) = self.builtin {
RefinedJob::builtin(builtin, args, self.redirection)
......
......@@ -85,7 +85,7 @@ pub enum PipelineError {
TerminateJobsError(#[error(cause)] nix::Error),
/// Could not execute the command
#[error(display = "command exec error: {}", _0)]
CommandExecError(#[error(cause)] io::Error),
CommandExecError(#[error(cause)] io::Error, types::Args),
/// Could not expand the alias
#[error(display = "unable to pipe outputs of alias: '{} = {}'", _0, _1)]
InvalidAlias(String, String),
......@@ -106,7 +106,7 @@ pub enum PipelineError {
/// A command could not be found in the pipeline
#[error(display = "command not found: {}", _0)]
CommandNotFound(String),
CommandNotFound(types::Str),
/// Failed to grab the tty
#[error(display = "could not grab the terminal: {}", _0)]
......@@ -508,7 +508,7 @@ fn spawn_proc(
current_pid: &mut Pid,
group: &mut Option<Pid>,
) -> Result<(), PipelineError> {
let RefinedJob { mut var, args, stdin, stdout, stderr, redirection } = cmd;
let RefinedJob { mut var, mut args, stdin, stdout, stderr, redirection } = cmd;
let pid = match var {
Variant::External => {
let mut command = Command::new(&args[0].as_str());
......@@ -529,9 +529,9 @@ fn spawn_proc(
Ok(child) => Ok(Pid::from_raw(child.id() as i32)),
Err(err) => {
if err.kind() == io::ErrorKind::NotFound {
Err(PipelineError::CommandNotFound(args[0].to_string()))
Err(PipelineError::CommandNotFound(args.swap_remove(0)))
} else {
Err(PipelineError::CommandExecError(err))
Err(PipelineError::CommandExecError(err, args))
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment