Vim auto-indentation: Align an array initialization which extends over multiple lines
Sometimes an array initialization in C extends over several lines, especially if the array is multidimensional. In Emacs the result of auto-indentation looks like this:
int a[N][N] = {{0, 0, 6, 7, 0, 4, 0, 2, 0},
{0, 5, 0, 6, 0, 0, 0, 0, 1},
{2, 0, 0, 0, 0, 8, 0, 0, 4},
{4, 0, 9, 5, 0, 7, 0, 0, 3},
{0, 0, 0, 0, 0, 0, 0, 0, 0},
{8, 0, 0, 2, 0, 1, 9, 0, 6},
{6, 0, 0, 1, 0, 0, 0, 0, 7},
{3, 0, 0, 0, 0, 5, 0, 6, 0},
{0, 2, 0开发者_运维知识库, 3, 0, 6, 1, 0, 0}};
In Vim the auto-indentation feature enabled by :filetype indent on
merely indents the lines by the constant shiftwidth
which leads to the following:
int a[N][N] = {{0, 0, 6, 7, 0, 4, 0, 2, 0},
{0, 5, 0, 6, 0, 0, 0, 0, 1},
{2, 0, 0, 0, 0, 8, 0, 0, 4},
{4, 0, 9, 5, 0, 7, 0, 0, 3},
{0, 0, 0, 0, 0, 0, 0, 0, 0},
{8, 0, 0, 2, 0, 1, 9, 0, 6},
{6, 0, 0, 1, 0, 0, 0, 0, 7},
{3, 0, 0, 0, 0, 5, 0, 6, 0},
{0, 2, 0, 3, 0, 6, 1, 0, 0}};
Is there a way to make Vim behave like Emacs in this particular situation?
UPDATE:
Herbert Sitz's answer was indeed very helpful (thanks!). I have slightly modified his code to look like this:
setlocal indentexpr=GetMyCIndent()
function! GetMyCIndent()
let theIndent = cindent(v:lnum)
let m = matchstr(getline(v:lnum - 1),
\ '^\s*\w\+\s\+\S\+.*=\s*{\ze[^;]*$')
if !empty(m)
let theIndent = len(m)
endif
return theIndent
endfunction
Saving this to the file ~/.vim/after/ftplugin/c.vim
solves the problem, i.e. it makes Vim align the array declaration the same way Emacs does.
What I have changed:
- Use
matchstr()
instead ofmatchlist()
to make the code easier to understand (len(m)
in place oflen(m[0])
). - Allow white spaces at the beginning of the line so that the declaration can be nested (e.g. in a function).
- Allow more than just two words before the assignment operator. This takes care of
static
declarations. - Only check for the first opening bracket (
{
) so that the expression also matches one-dimensional arrays (or structures). - Don't match expressions which contain a semicolon (
;
) because this indicates that the declaration holds in one line (i.e. the next line should not be aligned under the opening bracket). I think this is a more general approach than looking for closing brackets or commas at the end of the line.
Please let me know if I have missed something important.
Someone may know better than I do, but here's a first stab: Yes, the indenting can be customized. If your file is a recognized language "filetype" then it is being indented using rules/code in the corresponding *.vim file found in the /indent directory (e..g, vim/vim72/indent).
You would need to modify the code that's providing an indent on your multiline array, which might involve adding a new if block to the section that makes the indents, with an if expression that matches all and only the first lines of your multiline arrays. You should be able to get an idea of how things work by examining the files in the /indent directory.
UPDATE: Here's a mod to the c.vim indent file that should be close to what you want, seems to work fine for me. This is the entire file:
" Last Change: 2005 Mar 27
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
" C indenting is built-in, thus this is very simple
setlocal cindent
setlocal indentexpr=GetMyCIndent()
function! GetMyCIndent()
let theIndent = cindent(v:lnum)
let m = matchlist(getline(v:lnum - 1),'^\S\+\s\+\S\+\s*=\s*{\s*{\ze.*}[^}*]')
let m2 = matchlist(getline(v:lnum - 1),'}.*}')
if (!empty(m)) && (empty(m2))
let theIndent = len(m[0]) - 1
endif
return theIndent
endfunction
let b:undo_indent = "setl cin<"
The only problem (I think) with this code is that it will give same indent to an array of arrays intialization that is completed on one line. To avoid that the pattern needs to be modified to match only when there is one closing bracket on the line, not two. (Alternatively, you could just do a separate test.) That will take a little finagling, but shouldn't be too hard. (Also, if you do extend current pattern, you'll want to use the \ze marker in pattern to mark the end of the match that you want stored in m[0], which will be after second opening bracket that is last character in current pattern.) I REVISED CODE ABOVE TO DO SEPARATE TEST (using variable m2) THAT I THINK SOLVES THE PROBLEM. Not sure what other little details need to get taken care of.
One alternative would be to say that you want this indenting behavior whenever there are at least two opening brackets on the line and the last line char is a comma. This might actually be the best way, since it lets you have pairs, triplets, etc. of elements on a line:
let m = matchlist(getline(v:lnum - 1),'^\S\+\s\+\S\+\s*=\s*{\s*{\ze.*,\s*$')
if !empty(m)
let theIndent = len(m[0]) - 1
endif
I think you can type :set ai!
then indent your second dimension line then when you press Enter and type the third dimension line it will be indented correctly ... sorry if it is not an efficient solution.
精彩评论