Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tokenize command arguments in $NEOVIM_BIN (fix #2060) #2063

Merged
merged 3 commits into from Oct 8, 2023

Conversation

rhysd
Copy link
Contributor

@rhysd rhysd commented Oct 5, 2023

What kind of change does this PR introduce?

Did this PR introduce a breaking change?

A breaking change includes anything that breaks backwards compatibility either at compile or run time.

  • No

I confirmed this fix on Ubuntu 22.04 and macOS 13.6.

src/bridge/command.rs Outdated Show resolved Hide resolved
Copy link
Contributor Author

@rhysd rhysd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fredizzimo Thanks for your review. I simplified my changes and removed 4 redundant clones. Would you review again?

src/bridge/command.rs Outdated Show resolved Hide resolved
src/bridge/command.rs Show resolved Hide resolved
src/bridge/command.rs Show resolved Hide resolved
src/bridge/command.rs Show resolved Hide resolved
src/bridge/command.rs Show resolved Hide resolved
@rhysd
Copy link
Contributor Author

rhysd commented Oct 5, 2023

@fredizzimo BTW I got interested in why it wraps the command execution with a shell on macOS though it doesn't do it on Linux. Does executing the command directly on macOS cause any issue?

#[cfg(target_os = "macos")]
fn nvim_cmd_impl(bin: &str, args: &[String]) -> TokioCommand {
let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut cmd = TokioCommand::new(shell);
let args_str = args
.iter()
.map(|arg| shlex::quote(arg))
.collect::<Vec<_>>()
.join(" ");
if env::var_os("TERM").is_none() {
cmd.arg("-l");
}
cmd.arg("-c");
cmd.arg(&format!("{} {}", bin, args_str));
cmd
}

@rhysd
Copy link
Contributor Author

rhysd commented Oct 5, 2023

@fredizzimo I have another idea to split off the logic to lex Neovim command and args into a new function. Thanks to early returns with ?, it can avoid nested if blocks and the repeat of neovim_ok calls. Let me know if you prefer this.

diff --git a/src/bridge/command.rs b/src/bridge/command.rs
index 0ec0cff..38996a8 100644
--- a/src/bridge/command.rs
+++ b/src/bridge/command.rs
@@ -32,21 +32,8 @@ fn set_windows_creation_flags(cmd: &mut TokioCommand) {
 
 fn build_nvim_cmd() -> TokioCommand {
     if let Some(cmdline) = SETTINGS.get::<CmdLineSettings>().neovim_bin {
-        if let Some((bin, args)) = shlex::split(&cmdline)
-            .filter(|tokens| !tokens.is_empty())
-            .map(|mut tokens| (tokens.remove(0), tokens))
-        {
-            // if neovim_bin contains a path separator, then try to launch it directly
-            // otherwise use which to find the fully path
-            if bin.contains('/') || bin.contains('\\') {
-                if neovim_ok(&bin, &args) {
-                    return build_nvim_cmd_with_args(bin, args);
-                }
-            } else if let Some(bin) = platform_which(&bin) {
-                if neovim_ok(&bin, &args) {
-                    return build_nvim_cmd_with_args(bin, args);
-                }
-            }
+        if let Some((bin, args)) = lex_nvim_cmdline(&cmdline) {
+            return build_nvim_cmd_with_args(bin, args);
         }
 
         eprintln!("ERROR: NEOVIM_BIN='{}' was not found.", cmdline);
@@ -127,6 +114,19 @@ fn neovim_ok(bin: &str, args: &[String]) -> bool {
     false
 }
 
+fn lex_nvim_cmdline(cmdline: &str) -> Option<(String, Vec<String>)> {
+    let mut tokens = shlex::split(cmdline).filter(|t| !t.is_empty())?;
+    let (mut bin, args) = (tokens.remove(0), tokens);
+
+    // if neovim_bin contains a path separator, then try to launch it directly
+    // otherwise use which to find the fully path
+    if !bin.contains('/') && !bin.contains('\\') {
+        bin = platform_which(&bin)?;
+    }
+
+    neovim_ok(&bin, &args).then_some((bin, args))
+}
+
 fn platform_which(bin: &str) -> Option<String> {
     let is_wsl = SETTINGS.get::<CmdLineSettings>().wsl;
 

Copy link
Member

@fredizzimo fredizzimo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with the code now, but I have not tested it yet, so I'm not merging until that's done.

src/bridge/command.rs Outdated Show resolved Hide resolved
@fredizzimo
Copy link
Member

fredizzimo commented Oct 6, 2023

@rhysd

@fredizzimo BTW I got interested in why it wraps the command execution with a shell on macOS though it doesn't do it on Linux. Does executing the command directly on macOS cause any issue?

#[cfg(target_os = "macos")]
fn nvim_cmd_impl(bin: &str, args: &[String]) -> TokioCommand {
let shell = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let mut cmd = TokioCommand::new(shell);
let args_str = args
.iter()
.map(|arg| shlex::quote(arg))
.collect::<Vec<_>>()
.join(" ");
if env::var_os("TERM").is_none() {
cmd.arg("-l");
}
cmd.arg("-c");
cmd.arg(&format!("{} {}", bin, args_str));
cmd
}

macOS does some strange things when not launched from a shell. and that were multiple attempts to fix it, but I don't think it's enough, because people still report problems like #1859. We need a better solution for this, but unfortunately, I don't own a mac, so I have no idea how it's supposed to work, and what's the correct way to fix it is.

Here are some of those attempts:

@rhysd
Copy link
Contributor Author

rhysd commented Oct 6, 2023

@fredizzimo Thank you for the answer. I understood the reason.

When starting an application by clicking Foo.app in Finder or clicking an application icon in LaunchPad, it is intentional that no environment variable is set. This is the same as MacVim.app (by opening MacVim from LaunchPad and checking :echo $PATH).

Users need to set $PATH in their config files (e.g. .gvimrc). However this workaround would be reasonable as well for usability.

@rhysd
Copy link
Contributor Author

rhysd commented Oct 6, 2023

@fredizzimo I applied the patch at c4b8eea and confirmed the behavior manually.

  • OS
    • macOS 13.6
    • Ubuntu 22.04

For each above OS, I confirmed the following cases:

  • NEOVIM_BIN
    • Not set → Start with no error
    • Command "nvim" → Start with no error
    • Full path "/usr/local/bin/nvim" → Start with no error
    • Command with argument "env nvim" → Start with no error
    • Full path with argument "/usr/bin/env nvim" → Start with no error
    • Empty ""ERROR: NEOVIM_BIN='' was not found.
    • Invalid command "echo"ERROR: Unexpected output from neovim binary:
    • Invalid quote "'foo"ERROR: NEOVIM_BIN='"foo' was not found.
    • Empty quoted command "''"ERROR: NEOVIM_BIN='''' was not found.

@fredizzimo fredizzimo merged commit 690c076 into neovide:main Oct 8, 2023
2 checks passed
@fredizzimo
Copy link
Member

Thank you. I did some additional tests with WSL on Windows and everything seems to work fine.

@rhysd
Copy link
Contributor Author

rhysd commented Oct 8, 2023

@fredizzimo Thank you. It's very helpful since I don't have those environments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ERROR: NEOVIM_BIN was not found.
2 participants