-
Notifications
You must be signed in to change notification settings - Fork 722
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
Pipe handles newlines improperly #3669
Comments
Related: #1515 |
Are there many apps that misbehave if stdin reaches EOF without a trailing newline? |
@Screwtapello Commands like I definitely understand why some whitespace manipulation happens for general usability's sake, but this breaks my primary usecase for the piping feature. An option or method for disabling that manipulation behavior would be greatly appreciated. |
I agree this needs to be fixed somehow, the general issue is that many commands, like Maybe the solution is to simplify the existing logic not-to add a newline when its missing, but still remove a newline from the output if the original text did not end with one ? I wonder what we would break if we stopped doing that, here is a diff that does that: diff --git a/src/normal.cc b/src/normal.cc
index beb880f9..33256c0b 100644
--- a/src/normal.cc
+++ b/src/normal.cc
@@ -579,9 +579,7 @@ void pipe(Context& context, NormalParams)
const auto end = changes_tracker.get_new_coord_tolerant(sel.max());
String in = buffer.string(beg, buffer.char_next(end));
- const bool insert_eol = in.back() != '\n';
- if (insert_eol)
- in += '\n';
+ const bool ends_with_eol = in.back() == '\n';
// Needed in case we read selections inside the cmdline
context.selections_write_only().set({keep_direction(Selection{beg, end}, sel)}, 0);
@@ -590,12 +588,9 @@ void pipe(Context& context, NormalParams)
cmdline, context, in,
ShellManager::Flags::WaitForStdout).first;
- if (insert_eol)
- {
- in.resize(in.length()-1, 0);
- if (not out.empty() and out.back() == '\n')
- out.resize(out.length()-1, 0);
- }
+ if (not ends_with_eol and not out.empty() and out.back() == '\n')
+ out.resize(out.length()-1, 0);
+
auto new_end = apply_diff(buffer, beg, in, out);
if (new_end != beg)
new_sels.push_back(keep_direction({beg, buffer.char_prev(new_end), std::move(sel.captures())}, sel)); I'll run with that for a while see if I can spot any issue. |
So, one issue I got so far with that patch is with
For some reason it really wants an end-of-line, however seems |
According to my
I guess users who know about that could pipe But to play the advocate of the conservative side here, why does the editor have to pick one way or the other? I understand we're trying to make the default behaviour as convenient as possible to everybody, but since there are cases where a trailing newline is important to the shell program, why make the editor take the decision whether to include/strip it arbitrarily? Could we not let the |
Thats what I'd like to get to here, stop inserting that extra newline, reading the bc grammar at https://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html however makes it quite clear that the newline is required, so at least for this use case, it would mean that writing a mathematical expression, selecting it and piping to bc would now fail due to the missing newline. I think we still want to strip the output trailing newline if the input did not have one, because almost every filter will output a final newline. |
Hmmm... On one hand, I like @lenormf's idea that So what about just making it more clear to the user what is happening with trailing newlines and offer them some kind of alternative? Maybe something like If we can make it sufficiently clear to the user which command has which behavior, I think that would work. |
The issue is that we already have I am leaning towards not adding the trailing newline and break GNU bc, this can be addressed by piping into something else, say |
Fair point. It would soon get to the point where a user would rather use the most versatile implementation and manually manage the alternate behavior externally, (similar to your example with On another note, I'm not as worried about stripping newlines when output is inserted into the editor, since that behavior is more easily visible to the user. However we do run the risk of losing new lines mysteriously if the newline is an important part of the output, but I don't think that's particularly common. Again, But as I said though, this issue doesn't worry me as much. Thoughts, @mawww? |
Would it be possible to the no-add-newline behaviour as default, and have a configuration setting that allows certain command (i.e. first word after | ) that allows adding and/or stripping of the newline, with some explicit setting for doubling or not if the original buffer ends in a newline? That way you could configure using I was testing out |
It occurs to me that if Kakoune always piped data exactly as-is, it would be easy to write wrapper scripts to automatically add/strip a trailing newline where needed. However, since Kakoune does the fix-ups itself, it's not possible for wrapper scripts to restore the original content. |
I wonder if it is possible to leverage the hook system to enable both use cases nicely... Here's how I figure: Usecase 1:
|
Kakoune still eats the newline character after piping, e.g. |
On Tue, Feb 08, 2022 at 02:46:05AM -0800, Taupiqueur wrote:
Kakoune still eats the newline character after piping, e.g. `|printf 'foo'<ret>` and `|printf 'foo\n'<ret>` give the same result.
That's expected. This issue is only about not corrupting pipe *input*.
Kakoune trims a single newline from pipe output if the input selection does not end in a newline.
This works pretty well in practice for things like `|grep`, that print a newline even if the input doesn't have one.
If you really need this newline, you can add an extra one, or use a sentinel character.
|
This will unfortunately break some use case which will require using wrapper scripts to add the necessary newline. It is however harder to do the contrary, and it makes a lot of other use case possible, such as checksuming. Fixes mawww#3669
Steps
Write some string into the buffer. Select the entire string, but do not include the newline character at the end of it. Use
|
in normal mode to pipe the selection into an external program;base64
works well here.Issue 1: Copy that entire string into some separate base64 decoder like CyberChef and decode it.
Issue 2: Back in Kakoune, select your string again, (make sure to not include a new line). Pipe your selection through
base64 --decode
.Outcome
With Issue 1, it is clear to see that there is an extra new line included. This is not expected.
With Issue 2, while it is clear that the encoded string contains a newline (as per Issue 1), no new line is included in the decoded string.
Expected
With Issue 1, I would expect that no newline character would be appended to my selection string, and it should string should be exactly passed to the shell command's input stream.
With Issue 2, I would expect that any newline characters included at the end of the output stream to be accounted for
and recorded in the buffer.
To add some color to this, I was using Kakoune to edit some k8s resources, some of which were secrets. In k8s, the string data of a secret is encoded with base64. For example:
Withing Kakoune, I decoded a string to edit it, and then reencoded it; this resulted in a sneaky and unexpected newline character being included with my secret's value. I spent a good deal of time running around trying to figure out why the secret was not what it was showing up to be in my editor, and eventually found this issue.
The text was updated successfully, but these errors were encountered: