Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vim auto-indentation: Align an array initialization which extends over multiple lines

Tags:

c

vim

indentation

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.

like image 204
qfab Avatar asked Oct 04 '10 15:10

qfab


People also ask

How do I indent multiple lines in Vim?

To tab or add the indentation at multiple lines, try “shift+dot” i.e., “.” Shortcut once. You will see it will add an indentation of one character at each selected line from the start. If you want to add indentation without stopping, then you have to try the “.” Key from the keyword after using “shift+.”.

What Vim command would you use to automatically re indent all of the code inside the set of curly braces which the cursor is in?

To mark a block of lines and indent it, V j j > to indent three lines (Vim only). To indent a curly-braces block, put your cursor on one of the curly braces and use > % or from anywhere inside block use > i B .

What is smart indent in Vim?

autoindent essentially tells vim to apply the indentation of the current line to the next (created by pressing enter in insert mode or with O or o in normal mode. smartindent reacts to the syntax/style of the code you are editing (especially for C). When having it on you also should have autoindent on.


1 Answers

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
like image 186
Herbert Sitz Avatar answered Oct 20 '22 02:10

Herbert Sitz