How to prevent saving files with certain names in Vim?
Asked Answered
W

1

17

I type really fast and sometimes accidentally save a file with the name consisting of a single ; or :. (A typo is sometimes introduced as I type the :wq command.)

Is there any way to write a macro that rejects files matching certain names from being saved?

Waters answered 2/6, 2011 at 5:51 Comment(1)
I guess you could use an autocommand with BufWritePre to warn you and not write files starting with ;*. But I'm sleepy right now and will give it a shot whenever I wake up... if it's not already answered by then.Monkshood
N
18

A simple yet effective solution would be to define an auto-command matching potentially mistyped file names, that issues a warning and terminates saving:

:autocmd BufWritePre [:;]* throw 'Forbidden file name: '..expand('<afile>')

Note that the :throw command is necessary to make Vim stop writing the contents of a buffer.

In order to avoid getting the E605 error because of an uncaught exception, one can issue an error using the :echoerr command run in the try block—:echoerr raises its error message as an exception when called from inside a try construct (see :help :echoerr).

:autocmd BufWritePre [:;]*
\   try | echoerr 'Forbidden file name: '..expand('<afile>') | endtry

If it is ever needed to save a file with a name matching the pattern used in the above auto-command, one can prepend a writing command with :noautocmd or set the eventignore option accordingly (see :help :noautocmd and :help eventignore for details), e.g.:

:noa w :ok.txt
Newish answered 2/6, 2011 at 7:3 Comment(13)
This is great (and I have a pre-save function that does something very similar), but is there are more subtle way of preventing saving during BufWritePre? Throwing an error in this way results in E605: Exception not caught. It would be nice if a message could be echoed rather than the ugly error message.Voluntary
@Prince: Unfortunately, there is no other way to terminate the action that triggers autocmd event, than throwing an exception, as far as I know. However it's possible to get rid of the E605 error by wrapping throw in try-catch—the answer is updated.Newish
I guess you need echohl ErrorMsg | echom 'Suspicious file name: '.v:exception | echohl NONE instead of echoerr: echoerr should not be used because it 1. provides not needed information about error context here 2. depending on context, it may or may not be transformed into an exception. Latter is why I hate echoerr: exceptions on their own are not good as they create additional hidden exit points to all functions in the stack until try..catch is found, but echoerr is worse as it makes number of exit points depend on context. I once had a 'good' time debugging a problem with echoerr.Electrotechnics
@ib: whoa, 7 upvotes! Guess I should've fought back some sleep and written the answer myself ;) Anyway, very nice and good explanation. +1Monkshood
@ZyX: I know well both of these caveats, however none of them is a drawback here. Regarding the first, error context information is useful, because it reports autocmd and its file name pattern that prevents saving (which is helpful if one have several autocmds of this kind). The second feature—you missed that fact—is actually a key necessity in this case. The only thing that prevents Vim from writing to file is exception handling, and the designed way for re-throwing exception without an error in Vim is :echoerr (see :help try-echoerr). Using :echom here would break the solution.Newish
@yoda: Thanks! :autocmd BufWritePre-idea is only a half of the problem. The other (funnier) one was to find a way to terminate the event handling from within the auto-command that is triggered by this event. :-)Newish
@ib: It acts like an exception and breaks writing even outside of :try block (in :catch block it should not act as exception)? It is even stranger then I thought. The help tag you refer to shows echoe inside a :catch block that is inside an outer :try block, so echoe being an exception is expected. Why then you don't use try | echoe 'Suspicious file name' expand('<afile>') | endtry without invoking additional throws?Electrotechnics
@ib: Here is response from Bram: «This indeed looks wrong.». So stop using echoe in :catch.Electrotechnics
@ZyX: Yes, unnecessary throw/catch part could be omitted here, I didn't notice that yesterday.Newish
@ZyX: The bad about :echoerr is that it tries to serve two different functions: reporting an error (and saving it to the message history), and throwing an exception. In my opinion, it should be dedicated to the first function only. However, if it have formed that way that :echoerr should also raise an exception in try context, it's logical for it to act as :throw in the whole try-catch construct so that it is synonymous to :throw between try and endtry, and is a "plain :echoerr" otherwise.Newish
@ZyX: (cont) This way it's simpler to be on the safe side: if there is any exception handling in a function or in those places where that function is called, one avoids using :echoerr completely. Frankly speaking, I try hard not to use it in relatively complex scripts after I run into a strange "bug" (as I thought that time) which was caused by :echoerr inside the function that performs some cleanup necessary when an exception is caught in the other function (so :echoerr turns up to be in the catch part of the try in the outer scope).Newish
@ib: If your plugin functionality can be reached by using a mapping accesible through source, normal, a function, a command or with doautocmd, it may be put into try block where echoerr is an exception. So there is no 'safe side': you don't know where and why user may put a call to your plugin inside a :try block and thus it should be completely avoided. On the other hand, if echoerr is definitely an exception (like here), it is 'safe': its behavior is no more determined by context that is out of your control. But I prefer to have throw in such cases: it describes what is done.Electrotechnics
@ZyX: Totally agree! :echoerr is weird by design, and even in this case I prefer the first solution that uses plain :throw. (In fact, the second one came to mind only because of attempts to avoid the error message as @Prince asked.)Newish

© 2022 - 2024 — McMap. All rights reserved.