如何使用 VimL 脚本向当前 Vim 缓冲区添加一些行?

How to add some lines to the current Vim buffer by using a VimL script?

我对做一个非常简单的任务感到困惑。我需要使用 VimL 脚本向当前缓冲区添加行。

众所周知,为此有两个函数:append()setline()。它们的工作方式大致相似——第一个参数是要执行插入的行号(分别是之后或之前),第二个参数定义要添加的行。

但问题是,如果不发明一些奇怪的把戏,我就无法正确地确定这个数字。我发现的唯一标准方法是询问 line('$') 函数。因此,要在当前缓冲区的末尾添加一行,它可以是 append(line('$'), ['example'])setline(line('$')+1, 'example')。但是这种方法会根据当前缓冲区的内容给出不可预测的结果。

先考虑append(line('$'), ['example'])。如果缓冲区不为空,我得到我期望的结果:

first existing line
example

但是如果缓冲区是空的,我会得到第一行空白:


example

现在,让我们来看看setline(line('$')+1, 'example')。它的工作原理相反。如果缓冲区为空,则一切正常:

example

但是如果缓冲区不为空,尝试在最后一行之后添加“example”,我得到的文本行乱七八糟:

first existing line
example
second existing line

好吧,我可以测试一下最后一行是否为空。但是如果最后一行因为内容的原因是空的怎么办?

所以,这是我的问题。我的代码是否应该分析当前缓冲区的内容来决定它必须选择哪个函数,考虑现有的最后一行是空的,因为这是函数 line('$') 的工作方式,还是由于所需的内容而空?或者我应该使用我错过的另一种方法吗?

也许,我应该使用类似的东西:

write! buffer.tmp
exe '! printf '.join(g:my_lines_to_add, "\n")."\n >>buffer.tmp"
edit! buffer.tmp
rm -f buffer.tmp

有没有简单的标准方法来解决这个琐碎的任务?我的文字听起来可能很粗鲁,但我喜欢 Vim 并且认为自己是它的老用户。预先感谢您的帮助。

你在这里有几个相互矛盾的问题。

正如您已经注意到的,您使用 append()(或者借用短语 from this answer、“appendline()”)或 setline()。这两个都采用行号和字符串或数组,然后由函数适当处理。

the first argument is the number of the line to perform insertion at (after or before, respectively).

严格来说,没有。转到任何文件,然后转到第一行。 运行:echo line('.')。它会 return 1. 这是我最讨厌 Vim/Vimscript 的部分:line()getline() 是 1 索引的。但是,如果您使用由 getline() 编辑的数组 return,您将得到一个索引为 0 的数组。因此:

append() 在第一个参数 之后添加多行 。它不影响 idx 处的行。 setline() 处的行 更改为 idxlen(text)。这意味着函数分别在之后或之时执行它们的操作,而不是之前。

Should my code analyze the content of the current buffer to decide which of the functions it must choose, considering whether the existing last line is empty because this is how the function line('$') works or it empty due to the desired content?

我不能肯定地回答是或否,因为这是你想做什么的问题。根据您要寻找的最终结果以及您可以使用多少脚本,有不同的方法。

您有时可能需要进行比较,但通常不需要将其弄得那么复杂。在您的特定情况下,正如我稍后将演示的那样,您可以检查行数,然后确保唯一的行不为空。写起来有点乱,但是可以解决问题。还可以做各种对比。

考虑append(line('$') - 1, ["example"]):在空缓冲区中,这给出

example

... 并注意文件末尾的空行。第 1 行仍然保留,因为追加 不会覆盖行 。可悲的是,空白行被认为是一行,所以没有特别简单的方法可以使用内置函数来完成它,因为没有任何东西可以做到这一点AFAIK.

那么,让我们看看您的选择:

单行setline()

最短的单行选项是:

call setline(1, add(line('$') == 1 && col('$') == 1 ? [] : getline(0, '$'), 'example'))

这里发生了很多事情,所以让我们分解一下:

setline(                            We use setline(), because it can override existing lines.
        1,                          Start at line 1. 
        add(                        add() appends to an array.
                                    Then we have a condition using ternary.
            line('$') == 1          If the last line is line 1...
            && col('$') == 1        and the last column is 1...
            ? []                    then we use an empty array.
            : getline(0, '$'),      Otherwise, we get the contents of the buffer
            'example')              and add 'example' to the array
        )

请注意,如果要添加多个项目,您需要使用 extend(),而不是插入!参见 :h insert():h extend()

单行append()

如果你觉得文件末尾有一个空行表示空文件没问题,你也可以使用append:

call append(line('$') == 1 && col('$') == 1 ? 0 : line('$'), ["example"])

同样,append() 无法覆盖现有行,因此您在某处被空白行卡住了。这导致我们...

如果接近

这可以简化为一个语句,我将包含它以用于复制面食目的,但如果您正在编写脚本,更好地使用它,并且不只是将其用作命令:

if line('$') == 1 && col('$') == 1 | call setline(1, 'example') | else | call append(line('$'), "example") | endif

或者,在其扩展的、脚本友好的形式中:

if line('$') == 1 && col('$') == 1
    call setline(1, 'example')
else
    call append(line('$'), "example")
endif

...这就是你问的间接问题。

在某些方面,这是第一个选项的更易于阅读的变体,至少对于脚本使用而言。如果你有一个脚本,你实际上可以只用 append.

黑客注意事项

通过使用 append 方法,您可以将确定行的条件移动到变量中,并将其与 normal! "Gdd" 结合使用(Goto 最后一行,ddelete line) 去掉空白行。如果您检测到一个空文件,您可以使用等效的方法。

长话短说:

But the problem is that I cannot determine the number properly without inventing weird tricks.

对于这个特定的用例来说并不是特别奇怪,至少当你确切地知道它意味着什么时。这绝对是一个 hack none 少,但在这种特殊情况下没什么可做的。

Should my code analyze the content of the current buffer to decide which of the functions it must choose,

在这种情况下,line('$') == 1 && col('$') == 1 就足够了。 (据我所知,we don't have a better approach,但这确实避免了字符串操作)。

considering whether the existing last line is empty because this is how the function line('$') works

line('$')是这里的受害者。空行仍然被认为是行,这也适用于所有其他功能。因此,append() 不想覆盖它。该行是否为空,line('$') == 1 如果缓冲区中只有一行。够烦人的,line('.') == 1,但是 line('^') == 0。我无法解释这一点,但是无论所述行的内容如何,​​这种情况都会发生在单行缓冲区中。

or it empty due to the desired content?

由于有创意的引用,现在有点断章取义,但是根据函数的工作方式,其中一些组合出现的空白行是不可避免的事实。您需要一个不同的索引来处理这些情况,但这实际上取决于您首先要做什么。可能有更好的 and/or 更短的替代方案,但这需要我对您的代码做出假设。

Or should I use another approach that I have missed?

这个答案中已经有一些备选方案,可以根据需要进行扩展。有很多方法可以解决这个特定问题,所以可能有。不过,我可能也错过了一些方法,因为它们的价值很小。

正在寻找一款精美的单衬垫?

let foo =<< END
Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
END

:call execute(["$append"] + foo)

你让事情听起来比实际更难。

您可以检查当前缓冲区是否为空:

wordcount().bytes == 0

你可以忘记 append() 因为 setline() 可以完美地完成工作 只要你给它正确的行 :

if wordcount().bytes == 0
    " buffer is empty so our target is the last line itself
    call setline('$', ['foo', 'bar'])
else
    " buffer is not empty so our target is the line after the last line:
    call setline(line('$') + 1, ['foo', 'bar'])
endif

从那里,您可以通过向条件语句的第二个分支添加更多检查来微调处理“最后一行为空白或空行”情况的方式……

  • 如果给定行为空:

    getline(<line spec>) =~ '^$'
    
  • 如果给定行非空且仅由空格组成:

    getline(<line spec>) !~ '^$' && getline(<line spec>) !~ '\S'
    
  • 等等

感谢大家,尤其是 Oliviaromainl

文件可能有也可能没有零行,但 Vim 缓冲区总是至少有一行,即使它实际上是空的。因此,在不了解缓冲区现有内容的情况下,无法确定新内容添加后的行号。

我们可以使用两种主要方法:

  • 不分析内容,不担心多余的空行;
  • 分析内容以避免出现不需要的空行;

在第一种情况下,我们必须始终使用append(line('$'), ...),而不是像我在对Olivia的回答中所说的那样append(line('$')-1, ...)。我错了,因为如果缓冲区的内容末尾有空行(例如,分隔段落),使用 line('$')-1 会导致文本结构丢失。但是使用 append(line('$'), ... 导致第一行总是空白。

第二种方法更有趣,因为它意味着内容分析。这并不像想象的那么难。 romainl建议使用wordcount()函数,Olivia建议测试行号和列号。

我没有分析代码。但是,我敢肯定,在大文本上,Olivia 的建议更有效,因为知道行数和计算文本中的字节数是两种复杂性截然不同的工作。

所以,解决方案如下。 (代码还没有测试,这是一个概念)

function Append(lines)
    " Add the given lines to the end of the current buffer.
    "
    " Arguments:
    "     ‛lines’
    "        a list of text lines to add to the buffer.
    "
    let index = (line('$') == 1) && (col('$') == 1) ? 1 : line('$') + 1
    call setline(index, a:lines)
endfunction

再次感谢大家!问题已关闭。