mirror of
https://github.com/nushell/nushell.git
synced 2025-05-02 09:04:30 +02:00
This PR sets the current working directory to the location of the Nushell executable at startup, using `std::env::set_current_dir()`. This is desirable because after PR https://github.com/nushell/nushell/pull/12922, we no longer change our current working directory even after `cd` is executed, and some OS might lock the directory where Nushell started. The location of the Nushell executable is chosen because it cannot be removed while Nushell is running anyways, so we don't have to worry about OS locking it. This PR has the side effect that it breaks buggy command even harder. I'll keep this PR as a draft until these commands are fixed, but it might be helpful to pull this PR if you're working on fixing one of those bugs. --------- Co-authored-by: Devyn Cairns <devyn.cairns@gmail.com> Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
190 lines
6.7 KiB
Rust
190 lines
6.7 KiB
Rust
use itertools::Itertools;
|
|
use nu_engine::{command_prelude::*, env_to_strings};
|
|
use nu_path::canonicalize_with;
|
|
use std::{
|
|
ffi::{OsStr, OsString},
|
|
path::Path,
|
|
process::Stdio,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Start;
|
|
|
|
impl Command for Start {
|
|
fn name(&self) -> &str {
|
|
"start"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Open a folder, file or website in the default application or viewer."
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["load", "folder", "directory", "run", "open"]
|
|
}
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
Signature::build("start")
|
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
|
.required("path", SyntaxShape::String, "Path to open.")
|
|
.category(Category::FileSystem)
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
_input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
let path = call.req::<Spanned<String>>(engine_state, stack, 0)?;
|
|
let path = Spanned {
|
|
item: nu_utils::strip_ansi_string_unlikely(path.item),
|
|
span: path.span,
|
|
};
|
|
let path_no_whitespace = &path.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
|
// only check if file exists in current current directory
|
|
let file_path = Path::new(path_no_whitespace);
|
|
if file_path.exists() {
|
|
open_path(path_no_whitespace, engine_state, stack, path.span)?;
|
|
} else if file_path.starts_with("https://") || file_path.starts_with("http://") {
|
|
let url = url::Url::parse(&path.item).map_err(|_| ShellError::GenericError {
|
|
error: format!("Cannot parse url: {}", &path.item),
|
|
msg: "".to_string(),
|
|
span: Some(path.span),
|
|
help: Some("cannot parse".to_string()),
|
|
inner: vec![],
|
|
})?;
|
|
open_path(url.as_str(), engine_state, stack, path.span)?;
|
|
} else {
|
|
// try to distinguish between file not found and opening url without prefix
|
|
let cwd = engine_state.cwd(Some(stack))?;
|
|
if let Ok(canon_path) = canonicalize_with(path_no_whitespace, cwd) {
|
|
open_path(canon_path, engine_state, stack, path.span)?;
|
|
} else {
|
|
// open crate does not allow opening URL without prefix
|
|
let path_with_prefix = Path::new("https://").join(&path.item);
|
|
let common_domains = ["com", "net", "org", "edu", "sh"];
|
|
if let Some(url) = path_with_prefix.to_str() {
|
|
let url = url::Url::parse(url).map_err(|_| ShellError::GenericError {
|
|
error: format!("Cannot parse url: {}", &path.item),
|
|
msg: "".into(),
|
|
span: Some(path.span),
|
|
help: Some("cannot parse".into()),
|
|
inner: vec![],
|
|
})?;
|
|
if let Some(domain) = url.host() {
|
|
let domain = domain.to_string();
|
|
let ext = Path::new(&domain).extension().and_then(|s| s.to_str());
|
|
if let Some(url_ext) = ext {
|
|
if common_domains.contains(&url_ext) {
|
|
open_path(url.as_str(), engine_state, stack, path.span)?;
|
|
}
|
|
}
|
|
}
|
|
return Err(ShellError::GenericError {
|
|
error: format!("Cannot find file or url: {}", &path.item),
|
|
msg: "".into(),
|
|
span: Some(path.span),
|
|
help: Some("Use prefix https:// to disambiguate URLs from files".into()),
|
|
inner: vec![],
|
|
});
|
|
}
|
|
};
|
|
}
|
|
Ok(PipelineData::Empty)
|
|
}
|
|
|
|
fn examples(&self) -> Vec<nu_protocol::Example> {
|
|
vec![
|
|
Example {
|
|
description: "Open a text file with the default text editor",
|
|
example: "start file.txt",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open an image with the default image viewer",
|
|
example: "start file.jpg",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open the current directory with the default file manager",
|
|
example: "start .",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open a pdf with the default pdf viewer",
|
|
example: "start file.pdf",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Open a website with default browser",
|
|
example: "start https://www.nushell.sh",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
fn open_path(
|
|
path: impl AsRef<OsStr>,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
span: Span,
|
|
) -> Result<(), ShellError> {
|
|
try_commands(open::commands(path), engine_state, stack, span)
|
|
}
|
|
|
|
fn try_commands(
|
|
commands: Vec<std::process::Command>,
|
|
engine_state: &EngineState,
|
|
stack: &Stack,
|
|
span: Span,
|
|
) -> Result<(), ShellError> {
|
|
let env_vars_str = env_to_strings(engine_state, stack)?;
|
|
let cmd_run_result = commands.into_iter().map(|mut cmd| {
|
|
let status = cmd
|
|
.envs(&env_vars_str)
|
|
.stdin(Stdio::null())
|
|
.stdout(Stdio::null())
|
|
.stderr(Stdio::null())
|
|
.status();
|
|
match status {
|
|
Ok(status) if status.success() => Ok(()),
|
|
Ok(status) => Err(format!(
|
|
"\nCommand `{}` failed with {}",
|
|
format_command(&cmd),
|
|
status
|
|
)),
|
|
Err(err) => Err(format!(
|
|
"\nCommand `{}` failed with {}",
|
|
format_command(&cmd),
|
|
err
|
|
)),
|
|
}
|
|
});
|
|
|
|
for one_result in cmd_run_result {
|
|
if let Err(err_msg) = one_result {
|
|
return Err(ShellError::ExternalCommand {
|
|
label: "No command found to start with this path".to_string(),
|
|
help: "Try different path or install appropriate command\n".to_string() + &err_msg,
|
|
span,
|
|
});
|
|
} else if one_result.is_ok() {
|
|
break;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn format_command(command: &std::process::Command) -> String {
|
|
let parts_iter = std::iter::repeat(command.get_program())
|
|
.take(1)
|
|
.chain(command.get_args());
|
|
Itertools::intersperse(parts_iter, " ".as_ref())
|
|
.collect::<OsString>()
|
|
.to_string_lossy()
|
|
.into_owned()
|
|
}
|