-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Fix powershell escaping #2647
Fix powershell escaping #2647
Conversation
@@ -37,7 +37,7 @@ func quoteEntry(entry string) string { | |||
return "^" + match | |||
}) | |||
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") { | |||
escaped := strings.Replace(entry, `"`, `""`, -1) | |||
escaped := strings.Replace(entry, `"`, `\"`, -1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this needed (perhaps add a test showing why)? Would it be an option to escape with a backtick instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was actually motivated by your PR (#2438) itself.
I have tried escaping quotes for powershell commands, using backticks in cmd shell. It didn't work for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. My PR was more a POC actually and the quoting wasn't well-researched nor well-tested. Thoughts for now (I might be missing something though, didn't look into this in detail):
- escaping depends on which type of string is used in PS. So
echo '\"'
yields\"
whereasecho "\""
is not a complete command - when you enter
echo 'bar"'
in PS it's going to showbar"
and that is exactly as expected. If I get it correctly this change would mean that if somehow fzf encouters'bar"'
it is going to change that into'bar\"'
(?) but that is really a different string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
escaping depends on which type of string is used in PS. So echo '"' yields " whereas echo """ is not a complete command
Right, this is because \
is not an escape character in PS,
when you enter echo 'bar"' in PS it's going to show bar" and that is exactly as expected. If I get it correctly this change would mean that if somehow fzf encouters 'bar"' it is going to change that into 'bar"' (?) but that is really a different string.
Hmm, yes. Would you recommend removing the escaped := strings.Replace(entry,
",
", -1)
statement altogether?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fzf for windows has two layers of escaping. First layer of escaping is done for shells, i.e. ^
for cmd, ""
for powershell, *nix gets by with its single quotes just fine. After the first layer, you are basically done and you could call it a day. However, some applications that are commonly used with fzf require some characters to be escaped with backslash. Hence there is second layer of application specific backslash-escaping, i.e. \"
. This is usability enhancement.
@stinos Here is a test case for backslash-escaping in cmd:
Lines 314 to 315 in edac982
// example of mandatorily escaped double quote in the output, otherwise `rg -- ""C:\\test.txt""` is not matching for the double quotes around the path | |
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}}, |
if somehow fzf encouters 'bar"' it is going to change that into 'bar"' (?) but that is really a different string
Yeah. You would see this output when you run fzf --preview "rg {}"
and the {}
string of value bar"
would get escaped to bar\"
. Note that the backslash is required for the ripgrep to work as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vovcacik right, thanks for the explanation, I didn't realize that. It does ring a bell though and probably this is what cause me problems in the past; I think I was trying to use something like Read-History | fzf --bind=enter:execute({})
so like plain Ctrl-R but allowing to execute lines at will but that never really worked out as expected, didn't look into it because IIRC dlv.exe gave up on me somehow.
Anyway if I understand it correctly fzf will never pass the {}
for execute/preview as-is then, right? Or is there a placeholder flag or similar which means 'just pass as-is to execute/preview/...' ?
I get that this is for convenience, but it's also a bit counterintuitive; I mean when I'm in PS I know I have to use git grep 'bar\"'
(or 'bar"""'
or probably some other forms work as well) because that is just how PS executes external commands.
Also '
does not need being doubled then probably?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway if I understand it correctly fzf will never pass the {} for execute/preview as-is then, right?
Nope, it will aggressively escape the {}
contents to play it safe. @junegunn usually recommends the use of {f}
which will create a temp file (script) that will contain the {}
value as-is as you say. The full example would then be Read-History | fzf --bind=enter:execute(cmd /c {f})
(but it is broken at the moment, the file is missing .bat
extension).
Also ' does not need being doubled then probably?
Not sure about that. But I didn't have any problems with single quotes when writing tests for this PR (rashil2000#1)
It is needed, and it works fine in this PR (see c7473f8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The full example would then be Read-History | fzf --bind=enter:execute(cmd /c {f})
Ok that's useful. It would rather be Read-History | fzf --bind=enter:execute(Get-Content {f} | Invoke-Expression)
though, to execute PS history via PS, not cmd.
It is needed
Oh right because the complete command is wrapped in '
, missed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stinos, @rashil2000 the current implementation is actually what you guys want. The consistency with ripgrep (from cmd) would require additional level of escaping (as pointed out in #2647 (comment) and vovcacik@5e774bc)
To settle down this PR, I have written tests for it. The tests are not exhaustive, but it will make the PR consistent with how fzf works in cmd console. Maybe we should have write the tests first 😅 |
Probably to late to revert this decision, but my 2 cents again: this is Powershell, not cmd, they're very different so the more I think about it, the more it feels wrong to try and make them consistent. I get that for cmd the quoting gets applied because it's needed like 99% of the time anyway. But that isn't the case in PS: it has a ton of builtin commands and for those the quoting of Anyway, I feel like this should at least be documented. Have the manual state explicitly that on Windows fzf is always going to insert |
@janlazo Do you remember why vim-plug escapes |
From Neovim's
|
@janlazo Thanks! That explains the implementation of Is there any scenario that would cause issues when the I am asking for this because @stinos is proposing to use powershell's native quoting rules and if that would not break any workflows I think @junegunn could actually like it more (the rules are closer to how fzf escapes strings on *nix). Footnotes |
Any Windows program that executes external commands is subject to CommandLineToArgW. Command passed to jobstart must be escaped. Editor plugin doesn't have to escape commands that fzf (binary) will execute internally. When executing the expanded command, inner Backtick escaping seems to specific to powershell. cmd.exe doesn't special-case backticks. It can work for the value passed to |
@vovcacik Thanks for the test cases! |
Parsers included: - go parser (well, this is easily dealt with using `` strings) - win32 (shell-api) parser - powershell parser (for powershell commands) - powershell parsing rules for calling native commands - internal parsers of select regex applications (like grep)
c5016bf
to
121f5c8
Compare
Rebased to tidy up the commits. |
Merged, thanks! |
Fix what was left in #2641