Whats the best way to clang-format a C/C++/GLSL a buffer on save, that does nothing in the case there is no clang-format file found for a project?
1 Answer
You can add the following to your .vimrc
1. function FormatBuffer()
2. if &modified && !empty(findfile('.clang-format', expand('%:p:h') . ';'))
3. let cursor_pos = getpos('.')
4. :%!clang-format
5. call setpos('.', cursor_pos)
6. endif
7. endfunction
8.
9. autocmd BufWritePre *.h,*.hpp,*.c,*.cpp,*.vert,*.frag :call FormatBuffer()
What this does
- Defines the function
FormatBuffer
.- You can call this at any time like so
:call FormatBuffer()
- You can call this at any time like so
- Check whether the current buffer has been modified, and check if a
.clang-format
file is present in the same directory as the current file or any parent directories. - Save the current location of the cursor in the buffer.
- Pass the contents of the current buffer to clang-format and replace the buffer contents with the output.
- Note that this does mean if an error occurs during formatting, the current buffer will be replaced with the error message (e.g. if there is a syntax error in the .clang-format file). Of course the buffer contents can be recovered with a simple undo.
- Restore the previous location of the cursor within the buffer.
- Since the entire buffer gets erased and rewritten the cursor ends up at the beginning of the buffer.
- Register a command hook that:
- Is invoked just before a file is saved.
- Only applies to C, CPP and GLSL files based on the file extension.
- Calls the
FormatBuffer
function.
Sources:
boolean (default off), local to buffer
When on, the buffer is considered to be modified. This option is set when:
- A change was made to the text since it was last written. Using the undo command to go back to the original text will reset the option. But undoing changes that were made before writing the buffer will set the option again, since the text is different from when it was written.
- 'fileformat' or 'fileencoding' is different from its original value. The original value is set when the buffer is read or written. A ":set nomodified" command also resets the original values to the current values and the 'modified' option will be reset.
findfile({name} [, {path} [, {count}]])
Just like finddir(), but find a file instead of a directory. Uses 'suffixesadd'.
Example:
:echo findfile("tags.vim", ".;")
Searches from the directory of the current file upwards until it finds the file "tags.vim".
finddir({name} [, {path} [, {count}]])
Find directory {name} in {path}. Supports both downwards and upwards recursive directory searches. See
file-searching
for the syntax of {path}.Returns the path of the first found match. When the found directory is below the current directory a relative path is returned. Otherwise a full path is returned.
If {path} is omitted or empty then 'path' is used. If the optional {count} is given, find {count}'s occurrence of {name} in {path} instead of the first one. When {count} is negative return all the matches in a List.
This is quite similar to the ex-command :find. {only available when compiled with the +file_in_path feature}
2) Upward search:
Here you can give a directory and then search the directory tree upward for a file. You could give stop-directories to limit the upward search. The stop-directories are appended to the path (for the 'path' option) or to the filename (for the 'tags' option) with a ';'. If you want several stop-directories separate them with ';'. If you want no stop-directory ("search upward till the root directory) just use ';'.
/usr/include/sys;/usr
will search in:
/usr/include/sys
/usr/include
/usr
If you use a relative path the upward search is started in Vim's current directory or in the directory of the current file (if the relative path starts with './' and 'd' is not included in 'cpoptions').
If Vim's current path is
/u/user_x/work/release
and you do
:set path=include;/u/user_x
and then search for a file with gf the file is searched in:
/u/user_x/work/release/include
/u/user_x/work/include
/u/user_x/include
:_%: ::8 ::p ::. ::~ ::h ::t ::r ::e ::s ::gs
%:8 %:p %:. %:~ %:h %:t %:r %:e %:s %:gs
The file name modifiers can be used after "%", "#", "#n", "", "", "" or "". They are also used with the |fnamemodify()| function. These are not available when Vim has been compiled without the |+modify_fname| feature.
These modifiers can be given, in this order:
:p Make file name a full path. Must be the first modifier. Also changes "~/" (and "~user/" for Unix and VMS) to the path for the home directory. If the name is a directory a path separator is added at the end. For a file name that does not exist and does not have an absolute path the result is unpredictable.
:h Head of the file name (the last component and any separators removed). Cannot be used with :e, :r or :t. Can be repeated to remove several components at the end. When the file name ends in a path separator, only the path separator is removed. Thus ":p:h" on a directory name results on the directory name itself (without trailing slash).
When the file name is an absolute path (starts with "/" for Unix; "x:\" for MS-DOS, WIN32, OS/2; "drive:" for Amiga), that part is not removed. When there is no head (path is relative to current directory) the result is empty.
Examples, when the file name is "src/version.c", current dir "/home/mool/vim":
- :p /home/mool/vim/src/version.c
- :p:. src/version.c
- :p:~ ~/vim/src/version.c
- :h src
- :p:h /home/mool/vim/src
- :p:h:h /home/mool/vim
- :t version.c
- :p:t version.c
- :r src/version
- :p:r /home/mool/vim/src/version
- :t:r version
- :e c
- :s?version?main? src/main.c
- :s?version?main?:p /home/mool/vim/src/main.c
- :p:gs?/?\? \home\mool\vim\src\version.c
:au[tocmd] [group] {event} {pat} [nested] {cmd}
Add {cmd} to the list of commands that Vim will execute automatically on {event} for a file matching {pat}. Vim always adds the {cmd} after existing autocommands, so that the autocommands execute in the order in which they were given. See |autocmd-nested| for [nested].
BufWrite or BufWritePre Before writing the whole buffer to a file.
BufWritePost After writing the whole buffer to a file (should undo the commands for BufWritePre).
BufWriteCmd Before writing the whole buffer to a file. Should do the writing of the file and reset 'modified' if successful. The buffer contents should not be changed.
|Cmd-event|
:fu[nction][!] {name}([arguments]) [range] [abort]
Define a new function by the name {name}. The name must be made of alphanumeric characters and '_', and must start with a capital or "s:" (see above).
function-argument a:var
An argument can be defined by giving its name. In the function this can then be used as "a:name" ("a:" for argument). Up to 20 arguments can be given, separated by commas. Finally, an argument "..." can be specified, which means that more arguments may be following. In the function they can be used as "a:1", "a:2", etc. "a:0" is set to the number of extra arguments (which can be 0). When not using "...", the number of arguments in a function call must be equal to the number of named arguments. When using "...", the number of arguments may be larger. It is also possible to define a function without any arguments. You must still supply the () then. The body of the function follows in the next lines, until the matching |:endfunction|. It is allowed to define another function inside a function body.
E127 E122
When a function by this name already exists and [!] is not used an error message is given. When [!] is used, an existing function is silently replaced.
a:firstline a:lastline
When the [range] argument is added, the function is expected to take care of a range itself. The range is passed as "a:firstline" and "a:lastline". If [range] is excluded, ":{range}call" will call the function for each line in the range, with the cursor on the start of each line. See |function-range-example|. When the [abort] argument is added, the function will abort as soon as an error is detected. The last used search pattern and the redo command "." will not be changed by the function.
:endf[unction]
The end of a function definition. Must be on a line by its own, without other commands.
-
1The check for
.clang-format
should search all parent paths relative to the current file, similar to checks for.git
. Feb 24, 2020 at 13:39 -
1@ideasman42 Good point. I've updated the answer to do this. Feb 24, 2020 at 14:47
-
@ideasman42 Is there a reason you haven't accepted this answer? I've been using this for a few days now and it's working nicely. Feb 27, 2020 at 21:02
-
There is an error searching up the parent directories:
findfile('.clang-format', expand('%:p:h') . ';')
works. After this, there is still the problem that this is slow on large files when the content is unmodified. Feb 27, 2020 at 23:28 -
@ideasman42 you're right, I thought I tested that... well I made an edit to fix that and also added a check for whether the buffer is modified Feb 28, 2020 at 13:35