Vim 自动缩进:对齐跨多行的数组初始化

发布于 2024-09-25 21:07:37 字数 1832 浏览 1 评论 0原文

有时,C 中的数组初始化会跨越多行,尤其是当数组是多维时。在 Emacs 中,自动缩进的结果如下所示:

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}};

在 Vim 中,由 :filetype indent on 启用的自动缩进功能仅将行缩进常量 shiftwidth,这会导致以下内容:

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}};

有没有办法让 Vim 在这种特殊情况下表现得像 Emacs 一样?

更新:

赫伯特·西茨的回答确实非常有帮助(谢谢!)。我稍微修改了他的代码,如下所示:

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

将其保存到文件 ~/.vim/after/ftplugin/c.vim 解决了问题,即它使 Vim 以相同的方式对齐数组声明Emacs 确实如此。

我所做的更改:

  • 使用 matchstr() 而不是 matchlist() 使代码更易于理解(len(m) 代替len(m[0]))。
  • 在行的开头允许有空格,以便声明可以嵌套(例如在函数中)。
  • 赋值运算符前允许有两个以上的单词。这负责静态声明。
  • 仅检查第一个左括号 ({),以便表达式也匹配一维数组(或结构)。
  • 不要匹配包含分号 (;) 的表达式,因为这表明声明保留在一行中(即下一行不应在左括号下对齐)。我认为这是比在行尾查找右括号或逗号更通用的方法。

如果我错过了一些重要的事情,请告诉我。

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[^;]*

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.

) 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

悲歌长辞 2024-10-02 21:07:38

有人可能比我更了解,但这是第一次尝试:是的,缩进可以自定义。如果您的文件是可识别的语言“文件类型”,则将使用 /indent 目录中找到的相应 *.vim 文件中的规则/代码(例如 vim/vim72/indent)对其进行缩进。

您需要修改在多行数组上提供缩进的代码,这可能涉及向进行缩进的部分添加一个新的 if 块,其中 if 表达式匹配多行数组的所有且仅第一行。您应该能够通过检查 /indent 目录中的文件来了解事情是如何工作的。

更新:这是 c.vim 缩进文件的一个模块,应该接近你想要的,似乎对我来说工作得很好。这是整个文件:(

" 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<"

我认为)这段代码的唯一问题是,它会给数组数组的初始化提供与在一行上完成的相同的缩进。为了避免这种情况,需要修改模式以仅当该行中有一个右括号时才匹配,而不是两个。 (或者,您可以进行单独的测试。)这需要一些技巧,但应该不会太难。 (此外,如果您确实扩展当前模式,则需要在模式中使用 \ze 标记来标记要存储在 m[0] 中的匹配的结尾,该结尾将位于第二个左括号(即最后一个字符)之后在当前模式中。)我修改了上面的代码以进行单独的测试(使用变量 m2),我认为可以解决问题。不确定还需要注意哪些其他小细节。

一种替代方法是,只要行上至少有两个左括号并且最后一个行字符是逗号,您就希望这种缩进行为。这实际上可能是最好的方法,因为它可以让你在一行上有成对、三元组等元素:

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

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
云巢 2024-10-02 21:07:38

我认为您可以输入 :set ai! 然后缩进第二个尺寸线,然后当您按 Enter 并输入第三个尺寸线时,它将正确缩进...如果这不是一个有效的解决方案,抱歉。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文