views:

180

answers:

2

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 of matchlist() to make the code easier to understand (len(m) in place of len(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.

A: 

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.

Sam
Thanks, but (as far as I understand) this would be manual indenting since the changes would be gone after auto-reindenting the whole file with `gg=G`.
qfab
+4  A: 

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
Herbert Sitz
Thank you for your answer. The file $VIMRUNTIME/indent/c.vim just says *"C indenting is built-in, thus this is very simple"* and uses `setlocal cindent`. This is not very helpful. I know that the built-in C indentation can be customized via the `cinoptions` variable but I haven't found anything useful in the documentation. The indentation of continuation lines (which is exactly my request) can be adjusted with `cinoptions=+N` but N must be a constant so this does not solve the problem.
qfab
@qfab -- You may want to take a look at the indent file java.vim. Java is one of the languages that starts by using cindent, the built-in c indent, and adds exceptions to override the c behavior. There are lots of built-in options to change the cindent behavior without writing any new code, but I don't think there's one that corresponds to your precise situation. But basically you just want to write an if-statement that matches first line of your array and sets the indent for next line the way you want. (Lines after second line use previous indent by default.)
Herbert Sitz
@Herbert Sitz: Thank you very much for your help! I apologize for taking so long to reply but I first had to read a lot of documentation in order to completely understand your code (I'm new to Vimscript). I have modified it slightly to make it more general (see my update). It seems to work as intended so far. I hope that I have not overlooked something important.
qfab
@qfab -- Very nice. I'm not real new to Vimscript, but I didn't have any understanding of how custom indenting worked until I tried to answer your question. I also finally grokked what the \ze pattern marker is used for and now I don't need to use matchlist all the time. Thanks. Cheers, Herb
Herbert Sitz