Discovering the cause of Vim exit status - vim

On running:
vim /tmp/blah
:q
echo $?
I get an exit status of 1. This is breaking various things including Git. If I run vim without my vimrc:
vim -u NONE /tmp/blah
:q
echo $?
I get an exit status of 0. I use Pathogen so this also effectively disables plugins. Does anyone have a suggestion for efficiently determining the cause of the exit status? I'm aware of running Vim verbosely and logging to a file. Should I be looking for something specific in this file?
If there is a method of finding the exact line that determines the exit status I would love to know of it as searching around didn't turn much up.

Finally found this command in help: :cq[uit]. So after you do verbose logging, search for \<cq\%[uit]\>.
Update: There are also methods to alter the exit status using vim compiled with some interpreters support: at least, the following works:
python import sys
python sys.exit(1)
" (same for python3)
perl exit 1
I do not know other languages enough to write here examples of code that would quit vim with different exit status. Note also that such commands inside files sourced using :pyfile, :rubyfile and other :*file should also work, as well as this code in a modules not distributed with plugin.
I think the most efficient way here at this point is disabling plugins until you find the source of the problem.

Related

How can I force vi/vim to exit cleanly with status 0

When editing with vim, the normal exit code is 0. For example:
vi; echo $?
will return 0 if you just do :q immediately. However, it will return 1 (or other error codes) if you ever enter a bad command. For example, typing :aaa in vim will give the error E492: Not an editor command: aaa in vim. After that point, no matter what I do, vim will subsequently exit with status 1.
This is a problem when editing git commit messages, because if you ever make a mistake while typing (easy to do in vim), the subsequent commit message will be thrown away. (Yes, I know there are ways to retrieve it - I don't want that hassle every time I know I made a mistake.)
The question: is there a vim command to "reset" the exit code to 0?
There is a similar question here: can i force vim to exit with 0 status. However, that one is due to using vi instead of vim, which (apparently) exits with non-zero if you search for a non-existent string. This question is specifically about vim.
Edit: Thanks for the answers here! Some additional details:
this is on MacOS 11.2.1.
I have no ~/.vimrc so I'm just falling back to the Mac-provided /usr/share/vim/vimrc, which seems pretty basic.
calling vi -u NONE still causes the behavior described above.
(most interesting) calling vim fixes the problem. Yes, I know I mentioned above that the other answer was using vi instead of vim, but I was thrown off by this:
$ which vi
/usr/bin/vi
$ which vim
/usr/bin/vim
$ ls -l /usr/bin/vi
lrwxr-xr-x 1 root wheel 3 Jan 1 2020 /usr/bin/vi -> vim
And when I run either vi --version or vim --version, I get identically the same output. Does vim change its behavior depending on the calling executable name? And if so, is there something I can stick in my .vimrc to override whatever that switch is? I'd love to know. In the meantime, I can fix this with export EDITOR="vim".
By default, Vim (and at least some other implementations of vi, such as nvi), return 0 if a "normal" error occurs, such as an invalid command or another normal user input error. It is the behavior of ed to exit nonzero in such a case, and as you've found, that behavior is generally undesirable because humans are imperfect and make many mistakes, which is why Vim doesn't do that.
Vim should exit nonzero if you use :cq, which has this behavior intentionally, as well as if certain error conditions are met (e.g., you run vim -y but the display cannot be started.
It is possible you have a configuration setting or plugin that causes this behavior. You can try running with vim -u NONE -U NONE to verify this, and then isolate the problem by commenting out portions of your .vimrc. It is also possible that your Vim distributor thought this would be a desirable feature to add and patched it in for you, but you haven't mentioned your OS or source of Vim packages, so it's hard to say.
To always force a zero exit, you can try :cq 0, but that is no more likely to work than a standard :q, since they both call the exact same function (getout) with the exact same value (0).
I'm seeing this behavior as well on macOS 11.6, and it only happens when invoked as vi – not vim.
Why does this happen?
If we look at vim --version:
> vim --version
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Aug 30 2021 06:51:40)
macOS version
Included patches: 1-2029
Compiled by root#apple.com
The macOS version here let's us know that something might be up.
Looking at vim's main.c in the Apple's open source repository (https://opensource.apple.com/source/vim/vim-91/src/main.c.auto.html), we find this inside the int main() function:
Unix2003_compat = 0;
if (strcmp(base,"vi")==0) {
Unix2003_compat = COMPAT_MODE("bin/vi", "Unix2003");
} else if (strcmp(base,"ex")==0) {
Unix2003_compat = COMPAT_MODE("bin/ex", "Unix2003");
}
and then in void getout(int exitval):
if (exmode_active || Unix2003_compat )
exitval += ex_exitval;
The Unix2003_compat parts here are not present in the official Vim sources.
So on the macOS version of vim, if invoked as vi or ex you'll get this error preserving behavior.
Note: The Apple URL is confusing – vim-91 really corresponds to vim 8.2, which can be checked by looking at version.h in the same repo.
Solution
Invoke vim as vim instead of as vi. In the case of git set the EDITOR environment variable to vim in your .bashrc/.zshrc/config.fish.
Alternately, install the official vim through something like Homebrew.

Can I react on entered command in bash?

I would like to configure my bash in a way so that I react on the event that the user enters a command. The moment they press Enter I would like my bash to run a script I installed first (analog to any PROMPT_COMMAND which is run each time a prompt is given out). This script should be able to
see what was entered,
maybe change it,
maybe even make the shell ignore it (i. e. make it not execute the line),
decide on whether the text shall be inserted in the history or not,
and maybe similar things.
I have not found a proper way to do this. My current implementations are all flawed and use things like debug traps to intervene before executing a command or (HISTTIMEFORMAT='%s '; history 1) to ask the history after the command execution is complete about things when the command was started etc (but that is only hindsight which is not really what I want).
I'd expect something like a COMMAND_INTERCEPTION variable which would work similar to PROMPT_COMMAND but I'm not able to find anything like it.
I also considered to use command line completion to achieve my goal but wasn't able to find anything about reacting on sending a finished command in this, but maybe I just didn't find it.
Any help appreciated :)
You can use the DEBUG trap and the extdebug feature, and peek into BASH_COMMAND from the trap handler to see the running command. (Though as noted in comments, the debug trap is sprung on every simple command, not every command line. Also subshells elude it.)
The debug handler can prevent the command from running, but can't change it directly. Though of course you could run any command inside the debugger, possibly using BASH_COMMAND and eval to build it and then tell the shell to ignore the original command.
This would prevent running anything starting with ls:
$ preventls() { case "$BASH_COMMAND" in ls*) echo "no!"; return 1 ;; esac; }
$ shopt -s extdebug
$ trap preventls DEBUG
$ ls -l
no!
Use trap - DEBUG to remove the trap. Tested on Bash 4.3.30.

Unable to svn diff using meld

I want to use meld to view the difference between revisions. I installed meld and then executed in the project directory:
svn diff -r 2165:2182 --diff-cmd meld
but it thows up the following error:
Index: app/models/college_friends_count.rb
===================================================================
svn: E200012: Process 'meld' failed (exitwhy 2)
Can anybody tell me what is going wrong here?
I believe E200012 means the underlying process (meld) exited with a non-zero exit code. Lots of diff tools do this to indicate the result of the diff operation (0 = no difference 1 = differences, etc).
Though my version of meld doesn't appear to use non-zero exit codes, I know colordiff does, which halts SVN during a directory-crawling "svn diff", like in your example above. Try it on a file that doesn't have any changes to test.
A good fix is to to make your own diff command, let's say you call it meld_svn:
#!/bin/bash
meld "$6" "$7"
exit 0
So what we're doing is ignoring meld's exit codes, and exiting with our own (which won't stop SVN). The quotes around the arguments mean that filenames with spaces in them won't break your script.
Make it executable, then edit your ~/.subversion/config and set the diff-cmd to "meld_svn". This works great for colordiff, should fix your problem with meld if meld's indeed exiting with non-zero exit codes.
I hope that helps.
For me the problem was that by default svn passes -u as an option to the external diff command, and meld doesn't expect or that flag.
The -x flag for svn-diff allows you to to override this default flag:
svn diff -x \"\" --diff-cmd meld
This replaces -u with "" on melds command line, the escapes are required so that your shell doesn't parse the quote-marks the first time round and instead passes them to SVN, who passes it onto the meld command line.
(btw, using echo as the diff-cmd allows you to easily inspect what SVN would send to meld)

what is the load order of scripts when you start up vim?

If you start up vim with something like this:
vim -S myscript.vim file.txt
What is the load order of scripts? Does myscript.vim get loaded after or before ~/.vimrc.
If you pass in vimscript commands to vim directly on the command line, when do they get executed relative to sourced and default vimscripts?
I believe vimrc is always first. You can run :scriptnames to get a list of sourced scripts in order in which they were first sourced in your Vim instance.
The help entry is way too long to post here, but it lists the order of everything that vim does at initialization. See :help initialization.
The answer is myscript.vim gets loaded dead last.
The vim -V option is a lifesaver here. (Capital -V, because -v starts in vi mode.) Just ran across it, after searching further since although the other answers answered your question, they don't show what wasn't sourced because it wasn't found. If I could send it back in time, I'd save myself a lot of time banging my head against strace output.
This will not only show you all of the scriptnames that it did source in order, but also all of the scriptnames that it would have sourced if they existed in order. So, you can discover what files you can create to load at the appropriate time.
$ vim -V
Adding it to your vim arguments easily answers the question.
$ vim -V -S myscript.vim file.txt
It shows myscript.vim as dead last.
It prints a ton, and winds up at a "Press ENTER or type command to continue" prompt, which lets you step through Autocommands.

Getting the linux error code from make in Vim

I'm trying to get the "0 of success, nonzero if error" return code from make in Vim. Specifically, I am on Ubuntu and using v:shell_error does not work.
After digging around and looking at this question, it seems to be because of my shellpipe setting, which is
shellpipe=2>&1| tee
The tee pipes the make output back into vim. The shell is apparently returning the error code from tee to vim and not from make. How do I get make's error code instead?
You can try to make a custom function for that. E.g. using :call system("make > make.out") run make redirecting output into a file. After that load the error file using :cf make.out. Never tried that myself, though.
In the end, results of make might be also simply checked by testing whether the result is there, in the file system:
:make | if !filereadable("whatever-make-was-supposed-to-create") | throw "Make failed!!!" | endif
(Here the '|' symbol is vim's command separator.) Assigning that to a keyboard shortcut would remove the need for typing.
P.S. I usually try to make my programs to produce no warnings, so I never really came across the issue. What BTW leads to another possible solution: simply remove warnings (or simply undesired output lines) using e.g. grep -v tabooword from the make output by overriding the 'makeprg'. What is actually described in the help: :h 'makeprg'.
P.P.S. I got started on the VIM... Provided that you also use bash as a shell. Did you tried to add to the exit ${PIPESTATUS[0]} to the shellpipe? E.g.:
:set shellpipe=2>&1\ \|\ tee\ %s;exit\ \${PIPESTATUS[0]}
Just tested that on Debian and it worked for me. :h 'shellpipe' for more.
The only thing I can currently think of is creating two wrapper scripts for make and tee. I'm sure there's an easier way, but for now you might try this:
Create a make wrapper script:
#!/bin/bash
make $#
echo $? > ~/exit_code_cache
Create a tee wrapper script:
#!/bin/bash
tee $#
return `cat ~/exit_code_cache` # (or do something else with the exit code)
Use the new make :set makeprg=mymake and setup your own shellpipe that uses your tee wrapper (shellpipe=2>&1 | mytee).
It's not tested, but the idea should be clear. Hope it helps.

Resources