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?

ib.
  • 27,830
  • 11
  • 80
  • 100
xjq233p_1
  • 7,810
  • 11
  • 61
  • 107
  • 3
    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. – abcd Jun 02 '11 at 06:44

1 Answers1

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
ib.
  • 27,830
  • 11
  • 80
  • 100
  • 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. – Prince Goulash Jun 02 '11 at 09:17
  • @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. – ib. Jun 02 '11 at 12:31
  • 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`. – ZyX Jun 02 '11 at 13:59
  • 1
    @ib: whoa, 7 upvotes! Guess I should've fought back some sleep and written the answer myself ;) Anyway, very nice and good explanation. +1 – abcd Jun 02 '11 at 14:12
  • @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 `autocmd`s 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. – ib. Jun 02 '11 at 14:57
  • @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. :-) – ib. Jun 02 '11 at 15:04
  • @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('') | endtry` without invoking additional `throw`s? – ZyX Jun 02 '11 at 16:20
  • 1
    @ib: Here is [response from Bram](http://groups.google.com/group/vim_dev/browse_thread/thread/77c3904a8e9529a2): «This indeed looks wrong.». So stop using `echoe` in `:catch`. – ZyX Jun 02 '11 at 21:41
  • @ZyX: Yes, unnecessary `throw`/`catch` part could be omitted here, I didn't notice that yesterday. – ib. Jun 03 '11 at 01:21
  • @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. – ib. Jun 03 '11 at 01:47
  • @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). – ib. Jun 03 '11 at 01:58
  • 1
    @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. – ZyX Jun 03 '11 at 06:03
  • @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.) – ib. Jun 03 '11 at 06:56