使用脚本编写 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

智能推荐

2022黑龙江最新建筑八大员(材料员)模拟考试试题及答案_料账的试题-程序员宅基地

文章浏览阅读529次。百分百题库提供建筑八大员(材料员)考试试题、建筑八大员(材料员)考试预测题、建筑八大员(材料员)考试真题、建筑八大员(材料员)证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。310项目经理部应编制机械设备使用计划并报()审批。A监理单位B企业C建设单位D租赁单位答案:B311对技术开发、新技术和新工艺应用等情况进行的分析和评价属于()。A人力资源管理考核B材料管理考核C机械设备管理考核D技术管理考核答案:D312建筑垃圾和渣土._料账的试题

chatgpt赋能python:Python自动打开浏览器的技巧-程序员宅基地

文章浏览阅读614次。本文由chatgpt生成,文章没有在chatgpt生成的基础上进行任何的修改。以上只是chatgpt能力的冰山一角。作为通用的Aigc大模型,只是展现它原本的实力。对于颠覆工作方式的ChatGPT,应该选择拥抱而不是抗拒,未来属于“会用”AI的人。AI职场汇报智能办公文案写作效率提升教程 专注于AI+职场+办公方向。下图是课程的整体大纲下图是AI职场汇报智能办公文案写作效率提升教程中用到的ai工具。_python自动打开浏览器

Linux中安装JDK-RPM_linux 安装jdk rpm-程序员宅基地

文章浏览阅读545次。Linux中安装JDK-RPM方式_linux 安装jdk rpm

net高校志愿者管理系统-73371,计算机毕业设计(上万套实战教程,赠送源码)-程序员宅基地

文章浏览阅读25次。免费领取项目源码,请关注赞收藏并私信博主,谢谢-高校志愿者管理系统主要功能模块包括页、个人资料(个人信息。修改密码)、公共管理(轮播图、系统公告)、用户管理(管理员、志愿用户)、信息管理(志愿资讯、资讯分类)、活动分类、志愿活动、报名信息、活动心得、留言反馈,采取面对对象的开发模式进行软件的开发和硬体的架设,能很好的满足实际使用的需求,完善了对应的软体架设以及程序编码的工作,采取SQL Server 作为后台数据的主要存储单元,采用Asp.Net技术进行业务系统的编码及其开发,实现了本系统的全部功能。

小米宣布用鸿蒙了吗,小米OV对于是否采用鸿蒙保持沉默,原因是中国制造需要它们...-程序员宅基地

文章浏览阅读122次。原标题:小米OV对于是否采用鸿蒙保持沉默,原因是中国制造需要它们目前华为已开始对鸿蒙系统大规模宣传,不过中国手机四强中的另外三家小米、OPPO、vivo对于是否采用鸿蒙系统保持沉默,甚至OPPO还因此而闹出了一些风波,对此柏铭科技认为这是因为中国制造当下需要小米OV几家继续将手机出口至海外市场。 2020年中国制造支持中国经济渡过了艰难的一年,这一年中国进出口贸易额保持稳步增长的势头,成为全球唯一..._小米宣布用鸿蒙系统

Kafka Eagle_kafka eagle git-程序员宅基地

文章浏览阅读1.3k次。1.Kafka Eagle实现kafka消息监控的代码细节是什么?2.Kafka owner的组成规则是什么?3.怎样使用SQL进行kafka数据预览?4.Kafka Eagle是否支持多集群监控?1.概述在《Kafka 消息监控 - Kafka Eagle》一文中,简单的介绍了 Kafka Eagle这款监控工具的作用,截图预览,以及使用详情。今天_kafka eagle git

随便推点

Eva.js是什么(互动小游戏开发)-程序员宅基地

文章浏览阅读1.1k次,点赞29次,收藏19次。Eva.js 是一个专注于开发互动游戏项目的前端游戏引擎。:Eva.js 提供开箱即用的游戏组件供开发人员立即使用。是的,它简单而优雅!:Eva.js 由高效的运行时和渲染管道 (Pixi.JS) 提供支持,这使得释放设备的全部潜力成为可能。:得益于 ECS(实体-组件-系统)架构,你可以通过高度可定制的 API 扩展您的需求。唯一的限制是你的想象力!_eva.js

OC学习笔记-Objective-C概述和特点_objective-c特点及应用领域-程序员宅基地

文章浏览阅读1k次。Objective-C概述Objective-C是一种面向对象的计算机语言,1980年代初布莱德.考斯特在其公司Stepstone发明Objective-C,该语言是基于SmallTalk-80。1988年NeXT公司发布了OC,他的开发环境和类库叫NEXTSTEP, 1994年NExt与Sun公司发布了标准的NEXTSTEP系统,取名openStep。1996_objective-c特点及应用领域

STM32学习笔记6:TIM基本介绍_stm32 tim寄存器详解-程序员宅基地

文章浏览阅读955次,点赞20次,收藏16次。TIM(Timer)定时器定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断16位计数器、预分频器、自动重装寄存器的时基单元,在 72MHz 计数时钟下可以实现最大 59.65s 的定时,59.65s65536×65536×172MHz59.65s65536×65536×721​MHz不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能。_stm32 tim寄存器详解

前端基础语言HTML、CSS 和 JavaScript 学习指南_艾编程学习资料-程序员宅基地

文章浏览阅读1.5k次。对于任何有兴趣学习前端 Web 开发的人来说,了解 HTML、CSS 和JavaScript 之间的区别至关重要。这三种前端语言都是您访问过的每个网站的用户界面构建块。而且,虽然每种语言都有不同的功能重点,但它们都可以共同创建令人兴奋的交互式网站,让用户保持参与。因此,您会发现学习所有三种语言都很重要。如果您有兴趣从事前端开发工作,可以通过多种方式学习这些语言——在艾编程就可以参与到学习当中来。在本文中,我们将回顾每种语言的特征、它们如何协同工作以及您可以在哪里学习它们。HTML vs C._艾编程学习资料

三维重构(10):PCL点云配准_局部点云与全局点云配准-程序员宅基地

文章浏览阅读2.8k次。点云配准主要针对点云的:不完整、旋转错位、平移错位。因此要得到完整点云就需要对局部点云进行配准。为了得到被测物体的完整数据模型,需要确定一个合适的坐标系变换,将从各个视角得到的点集合并到一个统一的坐标系下形成一个完整的数据点云,然后就可以方便地进行可视化,这就是点云数据的配准。点云配准技术通过计算机技术和统计学规律,通过计算机计算两个点云之间的错位,也就是把在不同的坐标系下的得到的点云进行坐标变..._局部点云与全局点云配准

python零基础学习书-Python零基础到进阶必读的书藉:Python学习手册pdf免费下载-程序员宅基地

文章浏览阅读273次。提取码:0oorGoogle和YouTube由于Python的高可适应性、易于维护以及适合于快速开发而采用它。如果你想要编写高质量、高效的并且易于与其他语言和工具集成的代码,《Python学习手册:第4 版》将帮助你使用Python快速实现这一点,不管你是编程新手还是Python初学者。本书是易于掌握和自学的教程,根据作者Python专家Mark Lutz的著名培训课程编写而成。《Python学习..._零基础学pythonpdf电子书

推荐文章

热门文章

相关标签