使用脚本编写 Vim 编辑器,第 2 部分: 用户定义函数_vimscript function!-程序员宅基地

技术标签: 脚本  newline  search  function  正则表达式  VIM  vim  

原文: http://www.ibm.com/developerworks/cn/linux/l-vim-script-2/

用户定义函数

Haskell 或 Scheme 程序员会告诉您,函数对于任何严肃的编程语言来说都是最重要的特性。对于 C 或 Perl 程序员,他们也会告诉您完全相同的观点。

函数为严肃的程序员提供了两个基本优势:

  1. 它们能够将复杂的计算任务细分为足够小的部分,从而能够容易地被人类理解。
  2. 它们允许这些细分后的部分具有逻辑的和可理解的名称,这样就十分适合由人类处理。

Vimscript 是一种严肃的编程语言,因此它天生就支持创建用户定义函数。事实上,它确实提供了比 Scheme、C 或 Perl 更加优秀的用户定义函数支持。本文探究了 Vimscript 函数的各种特性,并展示了如何使用这些函数以可维护的方式增强并扩展 Vim 的内置函数。

声明函数

Vimscript 中的函数使用 function 关键字定义,后跟函数名,然后是参数列表(这是强制的,即使该函数没有参数)。函数体然后从下一行开始,一直连续下去,直到遇到一个匹配的 endfunction 关键字。例如:


清单 1. 具有正确结构的函数
				
				function
				ExpurgateText (text)
    let expurgated_text = a:text

    for expletive in [ 'cagal', 'frak', 'gorram', 'mebs', 'zarking']
        let expurgated_text
        \   = substitute(expurgated_text, expletive, '[DELETED]', 'g')
    endfor

    return expurgated_text
endfunction
			

函数返回值使用 return 语句指定。可以根据需要指定任意数量的单独 return 语句。如果函数被用作一个过程,并且没有任何有用的返回值,那么可以不包含 return 语句。然而,Vimscript 函数始终 返回一个值,因此如果没有指定任何 return,那么函数将自动返回 0。

Vimscript 中的函数名必须以大写字母开头:


清单 2. 以大写字母开头的函数名
				
function SaveBackup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

nmap <silent>  <C-B>  :call SaveBackup()<CR>

这个例子定义了一个函数,它将递增当前缓冲区的 b:backup_count 变量的值(或初始化为 1,如果尚不存在的话)。函数随后获取当前文件(getline(1,'$'))中的每一行并调用内置的 writefile() 函数来将它们写入到磁盘中。writefile() 的第二个参数是将要写入的新文件的名称;在本例中,为当前文件(bufname('%'))的名称附加上计数器的新值。返回的值为对 writefile() 调用的 success/failure 值。最后,nmap 设置 CTRL-B 以调用函数来创建对当前文件的有限备份。

Vimscript 函数没有使用前导大写字母,相反,可以使用显式的范围前缀声明函数(类似变量,如 第 1 部分 所述)。最常见的选择是s:,它表示函数对于当前脚本文件是本地函数。如果函数使用这种方式确定范围,那么它的名称就不需要以大写开头;它可以是任意有效标识符。然而,显式确定范围的函数必须始终使用范围前缀进行调用。比如:


清单 3. 使用范围前缀调用函数 
				
				" Function scoped to current script file...
function s:save_backup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

nmap <silent>  <C-B>  :call s:save_backup()<CR>

可重新声明的函数

Vimscript 中的函数声明为运行时语句,因此如果一个脚本被加载两次,那么该脚本中的任何函数声明都将被执行两次,因此将重新创建相应的函数。

重新声明函数被看作一种致命的错误(这样做是为了防止发生两个不同脚本同时声明函数的冲突)。这使得很难在需要反复加载的脚本中创建函数,比如自定义的语法突出显示脚本。

因此 Vimscript 提供了一个关键字修饰符(function!),允许在需要时指出某个函数声明可以被安全地重载:


清单 4. 表示某个函数声明可以被安全地重载
				
				function! s:save_backup ()
    let b:backup_count = exists('b:backup_count') ? b:backup_count+1 : 1
    return writefile(getline(1,'$'), bufname('%') . '_' . b:backup_count)
endfunction

对于使用这个修饰过的关键字定义的函数,没有执行任何重新声明检查,因此非常适合用于显式确定范围的函数(在这种情况下,范围已经确保函数不会和其他脚本中的函数发生冲突)。

调用函数

要调用函数并使用它的返回值作为语言表达式的一部分,只需要命名它并附加一个使用圆括号括起的参数列表:


清单 5. 使用函数的返回值
				
				"Clean up the current line...
let success = setline('.', ExpurgateText(getline('.')) )

但是要注意,与 C 或 Perl 不同,Vimscript 并不 允许您在未使用的情况下抛出函数的返回值。因此,如果打算使用函数作为过程或子例程并忽略它的返回值,那么必须使用 call 命令为调用添加前缀:


清单 6. 在未使用返回值的情况下使用函数
				
"Checkpoint the text...
call SaveBackup()

否则,Vimscript 将假设该函数调用实际上是一个内置的 Vim 命令,并且很可能会发出报警,指出并不存在这类命令。我们将在本系列的后续文章中查看函数和命令之间的区别。

参数列表

Vimscript 允许您定义显式参数 和可变参数列表,甚至可以将两者结合起来。

在声明了子例程的名称后,您可以立即指定最多 20 个显式命名的参数。指定参数后,通过将 a: 前缀添加到参数名,可以在函数内部访问当前调用的相应参数值:


清单 7. 在函数内部访问参数值
				
function PrintDetails(name, title, email)
    echo 'Name:   '  a:title  a:name
    echo 'Contact:'  a:email
endfunction

如果您不清楚一个函数具有多少个参数,那么可以指定一个可变的参数列表,使用省略号(...)而不是命名参数。在本例中,函数可以使用任意数量的参数调用,并且这些值被收集到一个单一变量中:一个名为 a:000 的数组。为单个参数也提供了位置参数名:a:1a:2a:3,等等。参数的数量可以是 a:0。例如:


清单 8. 指定并使用一个可变的参数列表
				
function Average(...)
    let sum = 0.0

    for nextval in a:000
				"a:000 is the list of arguments
        let sum += nextval
    endfor

    return sum / a:0
				"a:0 is the number of arguments
endfunction

注意,在本例中,sum 必须被初始化为一个显式的浮点值;否则,所有后续计算都将使用整数运算计算。

结合命名参数和可变参数

可以在同一个函数中同时使用命名参数和可变参数,只需要将可变参数的省略号放在命名参数列表之后

例如,假设您希望创建一个 CommentBlock() 函数,它将接收一个字符串并针对不同的编程语言将其格式化为相应的注释块。这类函数始终需要调用者为其提供一个字符串来进行格式化,因此应当使用命名参数。但是,您可能希望注释导入器(introducer)、“boxing” 字符和注释的宽度全部是可选的(在被省略时具有合理的默认值)。那么应当像下面这样调用:


清单 9. 一个简单的 CommentBlock 函数调用
				
call CommentBlock("This is a comment")

并且将返回一个多行字符串包含:


清单 10. CommentBlock 返回
				
//*******************
// This is a comment
//*******************

然而,如果提供额外的参数,那么将为注释导入器、“boxing” 字符和注释宽度指定非默认值。因此这个调用将为:


清单 11. 更加复杂的 CommentBlock 函数调用
				
call CommentBlock("This is a comment", '#', '=', 40)

would return the string:


清单 12. CommentBlock 返回
				
#========================================
# This is a comment
#========================================

这类函数的可能的实现方式为:


清单 13. CommentBlock 实现
				
function CommentBlock(comment, ...)
    "If 1 or more optional args, first optional arg is introducer...
    let introducer =  a:0 >= 1  ?  a:1  :  "//"

    "If 2 or more optional args, second optional arg is boxing character...
    let box_char   =  a:0 >= 2  ?  a:2  :  "*"

    "If 3 or more optional args, third optional arg is comment width...
    let width      =  a:0 >= 3  ?  a:3  :  strlen(a:comment) + 2

    " Build the comment box and put the comment inside it...
    return introducer . repeat(box_char,width) . "\<CR>"
    \    . introducer . " " . a:comment        . "\<CR>"
    \    . introducer . repeat(box_char,width) . "\<CR>"
endfunction

如果至少有一个可选参数(a:0 >= 1),那么导入器参数将指定给第一个选项(即 a:1);否则,将指定一个默认值 "//"。类似地,如果有两个或多个可选参数(a:0 >= 2),那么 box_char 变量被分配给第二个选项(a:2),或一个默认值 "*"。如果提供了三个或多个可选参数,那么第三个选项被分配给 width 变量。如果没有提供宽度参数,那么将自动根据注释参数本身计算相应的宽度(strlen(a:comment)+2)。

最后,将所有参数值解析后,将构建注释框的顶部和底部行:首先是一个注释导入器,后跟 boxing 字符的重复次数(repeat(box_char,width)),在这两者之间是注释文本本身。

当然,要使用这个函数,需要以某种方式调用它。最理想的方法可能是使用一个插入映射:


清单 14. 使用一个插入映射调用函数
				
				"C++/Java/PHP comment...
imap <silent>  ///  <C-R>=CommentBlock(input("Enter comment: "))<CR>

"Ada/Applescript/Eiffel comment...
imap <silent>  ---  <C-R>=CommentBlock(input("Enter comment: "),'--')<CR>

"Perl/Python/Shell comment...
imap <silent>  ###  <C-R>=CommentBlock(input("Enter comment: "),'#','#')<CR>

对于每一个映射,将首先调用内置的 input() 函数来请求注释文本中的用户类型。CommentBlock() 函数随后被调用,以将文本转换为一个注释块。最后,前导 <C-R>= 插入结果字符串。

注意,第一个映射仅仅传递一个单一参数,因此默认使用 // 作为其注释标记。第二个和第三个映射传递第二个参数来指定 # 或 -- 作为它们各自的注释导入器。最后一个映射传递第三个参数,使得 “boxing” 字符匹配它的注释导入器。

函数和行范围

可以使用一个初始的行范围调用任何标准的 Vim 命令(包括 call),这将针对范围中的每一行重复一次命令:

"Delete every line from the current line (.) to the end-of-file ($)...
:.,$delete

"Replace "foo" with "bar" everywhere in lines 1 to 10
:1,10s/foo/bar/

"Center every line from five above the current line to five below it...
:-5,+5center

可以在任何 Vim 会话中输入 :help cmdline-ranges 来了解更多有关此工具的内容。

对于 call 命令,指定范围将致使所请求的函数被反复调用:对范围中的每一行调用一次。要了解这样做的原因,考虑一下如何编写一个函数来将当前行中的任何 “原始的” & 符号转换为适当的 XML &amp; 实体,但是这样做也足够灵巧,可以忽略任何已经存在于其他实体中的 & 符号。这个功能的实现方式类似如下所示:


清单 15. 转换 & 符号的函数 
				
function DeAmperfy()
    "Get current line...
    let curr_line   = getline('.')

    "Replace raw ampersands...
    let replacement = substitute(curr_line,'&\(\w\+;\)\@!','&amp;','g')

    "Update current line...
    call setline('.', replacement)
endfunction

DeAmperfy() 中的第一行代码从编辑器缓冲区获取当前行(getline('.'))。第二行代码从当前行中查找其后 跟随标识符和冒号的 &,使用了否定先行(negative lookahead)模式 '&\(\w\+;\)\@!'(参见 :help \@! 获得更多细节)。substitute() 调用随后使用 XML&amp; 实体替换所有此类 “原始” & 符号。最后,DeAmperfy() 中的第三行代码使用修改后的文本更新当前行。

如果从命令行调用该函数:

:call DeAmperfy()

将只对当前行执行替换。但是如果在 call 之前指定了一个范围:

:1,$call DeAmperfy()

那么将针对范围内的每一行调用一次函数(在本例中,指的是文件中的每一行)。

内部化函数行范围

这种针对每一行反复调用函数 的行为是一种方便的默认行为。然而,有时希望指定一个范围,但是只调用一次函数,然后在函数内部处理范围语义。这对于 Vimscript 也很简单。只需要将一个特殊修饰符(range)附加到函数声明之后:


清单 16. 函数内部的范围语义
				
function DeAmperfyAll() range
				"Step through each line in the range...
    for linenum in range(a:firstline, a:lastline)
        "Replace loose ampersands (as in DeAmperfy())...
        let curr_line   = getline(linenum)
        let replacement = substitute(curr_line,'&\(\w\+;\)\@!','&amp;','g')
        call setline(linenum, replacement)
    endfor

    "Report what was done...
    if a:lastline > a:firstline
        echo "DeAmperfied" (a:lastline - a:firstline + 1) "lines"
    endif
endfunction

在参数列表之后指定了 range 修饰符后,使用如下范围调用 DeAmperfyAll() 时:

:1,$call DeAmperfyAll()

将只对函数执行一次调用,而两个特殊参数 a:firstline 和 a:lastline 被设置为范围的第一个行号和最后一个行号。如果未指定任何范围,那么 a:firstline 和 a:lastline 都将被设置为当前行号。

函数首先构建一个包含所有相关行号的列表(range(a:firstline, a:lastline))。注意,对内置 range() 函数的调用与在函数声明中使用range 修饰符一点关系也没有。range() 函数只是一个 list 构造函数,非常类似于 Python 中的 range() 函数,或者是 Haskell 或 Perl 中的 .. 运算符。

确定了将要处理的行号列表后,函数使用 for 循环来遍历每个行号:

for linenum in range(a:firstline, a:lastline)

然后相应地更新每一行(正如最初的 DeAmperfy() 所做的那样)。

最后,如果范围涵盖了多个行(即 a:lastline > a:firstline),函数将报告被更新的行的数量。

可视范围

一旦拥有了一个可以操作行范围的函数调用后,一个特别有用的技巧就是通过 Visual 模式(参见 :help Visual-mode 获得更多细节)调用该函数。

例如,如果游标位于文本块的某个位置,那么可以使用下面的代码在周围的段落中编码所有 & 号:

Vip:call DeAmperfyAll()

在 Normal 模式下输入 V 将切换到 Visual 模式。ip 随后将使 Visual 模式突出显示您正位于其中的整个段落。之后,: 将您切换到 Command 模式并自动将命令范围设置为刚刚从 Visual 模式选择的行的范围。此时,调用 DeAmperfyAll() 对所有行执行 deamperfy 操作。

注意,在这个实例中,可以使用下面的代码获得相同的效果:

Vip:call DeAmperfy()

惟一的不同之处在于 DeAmperfy() 函数将被反复调用:针对 Visual 模式下 Vip 中突出显示的每一行调用一次。

用于编码的函数

Vimscript 中的大多数用户定义函数只需要很少的参数,并且通常情况下根本不需要参数。这是因为它们常常直接从当前编辑器缓冲区和上下文信息(比如当前游标位置、当前段落大小、当前窗口大小或当前行的内容)中获得数据。

此外,如果函数通过上下文而不是参数列表包含数据,那么往往更加有用和方便。例如,维护源代码的一个常见问题就是赋值运算符在聚集起来后无法对齐,这将损害代码的可读性:


清单 16. 无法对齐的赋值运算符
				
let applicants_name = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative = 'sister'
let fathers_occupation = 'Sith'

在每次添加新语句时手动重新对齐它们将十分费力:


清单 17. 手动重新对齐赋值运算符
				
let applicants_name     = 'Luke'
let mothers_maiden_name = 'Amidala'
let closest_relative    = 'sister'
let fathers_occupation  = 'Sith'

要让日常编程任务没那么乏味,可以创建一个键映射(比如 ;=),它可以选择当前代码块、定位具有赋值运算符的任何行,并自动对齐这些运算符。如下所示:


清单 18. 对齐赋值运算符的函数
				
function AlignAssignments ()
    "Patterns needed to locate assignment operators...
    let ASSIGN_OP   = '[-+*/%|&]\?=\@<!=[=~]\@!'
    let ASSIGN_LINE = '^\(.\{-}\)\s*\(' . ASSIGN_OP . '\)'

    "Locate block of code to be considered (same indentation, no blanks)
    let indent_pat = '^' . matchstr(getline('.'), '^\s*') . '\S'
    let firstline  = search('^\%('. indent_pat . '\)\@!','bnW') + 1
    let lastline   = search('^\%('. indent_pat . '\)\@!', 'nW') - 1
    if lastline < 0
        let lastline = line('$')
    endif

    "Find the column at which the operators should be aligned...
    let max_align_col = 0
    let max_op_width  = 0
    for linetext in getline(firstline, lastline)
        "Does this line have an assignment in it?
        let left_width = match(linetext, '\s*' . ASSIGN_OP)

        "If so, track the maximal assignment column and operator width...
        if left_width >= 0
            let max_align_col = max([max_align_col, left_width])

            let op_width      = strlen(matchstr(linetext, ASSIGN_OP))
            let max_op_width  = max([max_op_width, op_width+1])
         endif
    endfor

    "Code needed to reformat lines so as to align operators...
    let FORMATTER = '\=printf("%-*s%*s", max_align_col, submatch(1),
    \                                    max_op_width,  submatch(2))'

    " Reformat lines with operators aligned in the appropriate column...
    for linenum in range(firstline, lastline)
        let oldline = getline(linenum)
        let newline = substitute(oldline, ASSIGN_LINE, FORMATTER, "")
        call setline(linenum, newline)
    endfor
endfunction

nmap <silent>  ;=  :call AlignAssignments()<CR>

AlignAssignments() 函数首先创建两个正则表达式(参见 :help pattern 获得有关 Vim 正则表达式语法的必要细节):

let ASSIGN_OP   = '[-+*/%|&]\?=\@<!=[=~]\@!'
let ASSIGN_LINE = '^\(.\{-}\)\s*\(' . ASSIGN_OP . '\)'

ASSIGN_OP 中的模式匹配任何标准的赋值运算符:=+=-=*=,等等,但是注意不要匹配其他包含 = 的运算符,比如 == 和 =~。如果您喜欢的语言中包含其他赋值运算符(比如 .= 或 ||= 或 ^=),那么可以扩展 ASSIGN_OP 正则表达式来识别这些运算符。另一种选择是,可以重新定义 ASSIGN_OP 来识别其他 “可对齐的” 类型,比如注释导入器或列表及,并对齐它们。

ASSIGN_LINE 中的模式只在行的起始部分(^)开始匹配,首先匹配最小字符数(.\{-}),然后匹配任何空白(\s*),最后匹配赋值运算符。

注意,最初的 “最小字符数” 子模式和运算符子模式都在捕捉圆括号内进行了指定:\(...\)。这两个正则表达式组件捕获的子字符串稍后将通过调用内置 submatch() 函数来提取;具体来讲,通过调用 submatch(1) 来提取运算符前面的所有内容,然后调用 submatch(2)来提取运算符本身。

AlignAssignments() 随后查找它将对其进行操作的行范围:

let indent_pat = '^' . matchstr(getline('.'), '^\s*') . '\S'
let firstline  = search('^\%('. indent_pat . '\)\@!','bnW') + 1
let lastline   = search('^\%('. indent_pat . '\)\@!', 'nW') - 1
if lastline < 0
    let lastline = line('$')
endif

在此前的例子中,函数依赖于一个显式的命令范围或一个 Visual 模式选择来确定要进行处理的行,但是这个函数则直接计算它自己的行范围。具体来讲,它首先调用内置 matchstr() 函数来确定出现在当前行(getline('.'))起始部分的前导空白('^\s*')。随后在indent_pat 中构建一个新的正则表达式,精确匹配任何非空行的起始处的相同序列的空白(即拖尾 '\S')。

AlignAssignments() 随后调用内置 search() 函数向上搜索(使用标记 'bnW')并定位位于游标上方的第一个 具有相同缩进的行。向此行号加 1 将得出感兴趣的范围的起始行号,也就是说,具有相同缩进的第一个相邻行就作为当前行。

第二个 search() 调用随后向下搜索('nW')来判断 lastline:具有相同缩进的最后一个相邻行。对于这种情况,搜索可能会到达文件的末尾,并且没有找到具有不同缩进的行,这种情况下 search() 将返回 -1。要正确地处理这种情况,随后的 if 语句需要显式地将lastline 设置为文件末端的行号(即设置为由 line('$') 返回的行号)。

这两个搜索的结果将使 AlignAssignments() 知道紧邻着当前行的上方或下方、具有与当前行完全相同的缩进的完整行范围。它使用这些信息来确保只对位于同一代码块中相同范围级别的赋值语句执行对齐。当然,如果代码的缩进不能正确反映它的范围,那么这种情况下必须进行重新格式化。

AlignAssignments() 中的第一个 for 循环判断其中的赋值运算符应当对齐的列。实现方法是遍历所选范围内的行列表(由getline(firstline, lastline) 取回的行)并检查每个行是否包含赋值运算符(运算符的前面可能包含空格):

let left_width = match(linetext, '\s*' . ASSIGN_OP)

如果该行中没有运算符,那么内置 match() 函数将无法找到匹配,因此将返回 -1。对于这种情况,循环将直接跳到下一行。如果存在运算符,那么 match() 将返回在其中显示运算符的(正)指数。if 语句随后使用内置 max() 函数判断这个最近的列位置是否比此前找到的运算符更靠右,从而跟踪所需的最大列位置来对齐范围内的所有赋值运算符:

let max_align_col = max([max_align_col, left_width])

if 中剩下的两行代码使用内置 matchstr() 函数检索实际的运算符,然后使用内置 strlen() 函数判断行的长度("=" 的长度为 1,'+=''-=' 的长度为 2,等等)。max_op_width 变量随后被用来跟踪所需的最大宽度,以对范围内的各种运算符执行对齐:

let op_width     = strlen(matchstr(linetext, ASSIGN_OP))
let max_op_width = max([max_op_width, op_width+1])

一旦确定了赋值区域的位置和宽度,剩下的就是遍历范围中的行并相应地执行重新格式化。要执行重新格式化,函数将使用内置的printf() 函数。这个函数十分有用,但是它的命名非常糟糕。它与 C、Perl 或 PHP 中的 printf 函数不同。实际上,它类似于以上这些语言中的 sprintf 函数。也就是说,在 Vimscript 中,printf 并不会输出其数据参数列表的格式化后的版本;它会返回一个字符串,其中包含了数据参数列表的格式化后的版本。

理想情况下,要重新格式化每一行,AlignAssignments() 将使用内置的 substitute() 函数,并使用经过 printf 重新整理后的文本替换运算符之前的所有内容。不幸的是,substitute() 要求使用固定的字符串作为它的替代值,而不是一个函数调用。

因此,要使用 printf() 来重新格式化每个替换文本,需要使用特殊的嵌入式替换形式:"\=expr"。替换字符串中的前导 \= 要求substitute() 对随后的表达式求值并使用结果作为替换文本。注意,这类似于 Insert 模式下的 <C-R>= 机制,惟一不同的是这种奇妙的行为只针对内置 substitute() 函数的替换字符串(或在标准 :s/.../.../ Vim 命令中)。

在本例中,特殊替换形式对于每一行来说都将是相同的 printf ,因此它将在第二个 for 循环开始之前被预先存储到 FORMATTER 变量中:

let FORMATTER = '\=printf("%-*s%*s", max_align_col, submatch(1),
\                                    max_op_width,  submatch(2))'

当最终被 substitute() 调用时,这个内嵌的 printf() 将把运算符左侧的所有内容(submatch(1))靠左对齐(使用 %-*s 占位符)并将结果放到字符宽度为 max_align_col 的字段中。随后将运算符本身(submatch(2))右对齐(使用 %*s)到第二个字段,其字符宽度为max_op_width。参考 :help printf(),了解 - 和 * 选项如何修改这里使用的两个 %s 格式说明符(specifier)。

有了这个格式化程序后,第二个 for 循环就可以遍历完整的行号范围,每次取回一行相应的文本缓冲内容:

for linenum in range(firstline, lastline)
    let oldline = getline(linenum)

循环随后使用 substitute() 来转换这些内容,方法是匹配位于任何赋值运算符之前并包括运算符在内的所有内容(使用 ASSIGN_LINE中的模式)并使用 printf() 调用的结果替换文本(如 FORMATTER 中指定的那样):

    let newline = substitute(oldline, ASSIGN_LINE, FORMATTER, "")
    call setline(linenum, newline)
endfor

当 for 循环遍历了所有行之后,这些行中的赋值运算符将被正确对齐。剩余的工作是创建一个键映射来调用 AlignAssignments(),如下所示:

nmap <silent>  ;=  :call AlignAssignments()<CR>

结束语

为了处理真实 Vim 编程任务的复杂性,需要将应用程序分解为正确的、可维护的组件,而函数是实现这个过程的基本工具。

Vimscript 允许您使用固定的或可变的参数列表来定义函数,并使它们通过自动的或用户控制的方式与编辑器的文本缓冲中的行范围进行交互。函数可以回调到 Vim 的内置特性(比如,回调到 search() 或 substitute() 文本),并且它们可以直接访问编辑器状态信息(比如通过 line('.') 确定游标所在的当前行)或者与当前进行编辑的任何文本缓冲进行交互(通过 getline() 和 setline())。

这无疑提供了非常强大的功能,但是通过编程的方式操作状态和内容始终受限于数据表示的整洁性和准确性,我们的代码将对这些数据进行处理。到目前为止,该 系列文章 一直关注单个标量函数(数值、字符串和布尔值)的使用。在接下来两篇文章中,我们将探讨更强大、更方便的数据结构的应用:有序列表和随机访问字典。


参考资料

学习

获得产品和技术

讨论

  • 加入 My developerWorks 社区;您可以通过个人档案和定制主页获得符合自己的兴趣的 developerWorks 文章,并与其他 developerWorks 用户进行交流。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/MDL13412ZhuanZai/article/details/8093459

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签