分类: LINUX
2010-04-13 21:42:37
Vimscript 是一种用于改造和扩展 Vim 编辑器的机制。使用脚本可以创建新工具、简化常见任务,甚至重新设计并取代已有的编辑特性。本文是本系列文章的第一篇,介绍了 Vimscript 编程语言的基本元素:值、变量、表达式、语句、函数和命令。将通过一系列简单的示例演示和解释这些特性。
:help
获得帮助。请查看 参考资料 小节中的链接。vim --version
:version
。如果使用的是旧版本的 Vim,强烈建议您升级到最新版本,因为早期版本并不支持我们将要讨论的大部分 Vimscript 特性。参考资料 小节提供了下载和升级 Vim 的链接。:help vim-script-intro
:source
::source /full/path/to/the/scriptfile.vim
:call MyBackupFunc(expand('%'), { 'all':1, 'save':'recent'})
:nmap ;s :source /full/path/to/the/scriptfile.vim
:nmap \b :call MyBackupFunc(expand('%'), { 'all': 1 })
;s
将执行指定的脚本文件,而 \b
序列将调用 MyBackupFunc()
函数(该函数大概也是在 .vimrc 文件的某个位置中定义的)。:syntax enable
命令启用,并使用 :syntax off
命令关闭。function! ToggleSyntax() if exists("g:syntax_on") syntax off else syntax enable endif endfunction nmap |
;s
序列将开启或关闭语法突出显示功能。让我们看看这段脚本的每个部分。ToggleSyntax()
的函数,它没有包含任何参数。这个用户定义函数首先调用名为 exists()
的 Vim 函数,为它传递一个字符串。exists()
函数确定是否定义了一个具有由字符串定义的名称的变量(在本例中为全局变量 g:syntax_on
)。if
语句将执行一个 syntax off
命令;否则它将执行 syntax enable
命令。由于 syntax enable
定义了 g:syntax_on
变量,而 syntax off
解除了变量定义,反复调用 ToggleSyntax()
函数将不停地启用和禁用语法突出显示。;s
)来调用 ToggleSyntax()
函数:nmap ;s :call ToggleSyntax()
nmap
表示 “normal-mode key mapping”。nmap
之后的
选项使得映射不能回传它所执行的任何命令,确保新的 ;s
命令默默地完成它的工作。它的职责就是执行下面的命令::call ToggleSyntax()
序列是字符 <
、C
、R
和 >
的字母序列。Vimscript 将它与回车符等同起来。事实上,Vimscript 理解非打印字符的许多其他类似的表示。例如,可以创建一个键盘映射,使空格键起到下翻键的功能(和在大多数 Web 浏览器中一样),如下所示::nmap
:help keycodes
,可以看到这些特殊符号的完整列表。ToggleSyntax()
能够直接调用内置 syntax
命令。这是因为 Vim 中的每个内置冒号命令都自动成为 Vimscript 中的一个语句。例如,要为使用 Vim 编写的文档更方便地创建中心标题,可以创建一个函数,将当前行中的每个字母大写,将整个行集中在一起,然后跳到下一行,如下所示:function! CapitalizeCenterAndMoveDown() s/\<./\u&/g "Built-in substitution capitalizes each word center "Built-in center command centers entire line +1 "Built-in relative motion (+1 line down) endfunction nmap |
call SetName( \ first_name, \ middle_initial, \ family_name \ ) |
echo "Starting..." | call Phase(1) | call Phase(2) | echo "Done"
if exists("g:syntax_on") syntax off "Not 'syntax clear' (which does something else) else syntax enable "Not 'syntax on' (which overrides colorscheme) endif |
echo "> " "Print generic prompt
echo "> " |"Print generic prompt
let
:let name = "Damian" let height = 165 let interests = [ 'Cinema', 'Literature', 'World Domination', 101 ] let phone = { 'cell':5551017346, 'home':5558038728, 'work':'?' } |
"\n"
(用于换行符)、"\t"
(用于制表符)、"\u263A"
(用于 Unicode 笑脸标志)或者 "\"
(用于转义字符)。相比之下,使用单引号分隔的字符串将包括在其中的所有内容视为文字字符 — 两个连续的单引号是一个例外,它们被单纯地当作单引号。"Damian"
或 165
['Cinema', 'Literature', 'World Domination', 101]
{'cell':5551017346, 'home':5558038728, 'work':'?'}
name
和 height
变量现在都是标量(就是说,它们现在只能存储字符串或数字),interests
现在是一个 list 变量(就是说,它只能存储列表),而 phone
现在是一个 dictionary 变量(并且只能存储字典)。变量类型一旦分配后,就会保持不变并在运行时严格遵守:let interests = 'unknown' " Error: variable type mismatch
前缀 | 含义 |
g: varname | 变量为全局变量 |
s: varname | 变量的范围为当前的脚本文件 |
w: varname | 变量的范围为当前的编辑器窗口 |
t: varname | 变量的范围为当前的编辑器选项卡 |
b: varname | 变量的范围为当前的编辑器缓冲区 |
l: varname | 变量的范围为当前的函数 |
a: varname | 变量是当前函数的一个参数 |
v: varname | 变量是 Vim 的预定义变量 |
前缀 | 含义 |
& varname | 一个 Vim 选项(如果指定的话,则为本地选项,否则为全局选项) |
&l: varname | 本地 Vim 选项 |
&g: varname | 全局 Vim 选项 |
@ varname | 一个 Vim 注册器 |
$ varname | 一个环境变量 |
nmap ]] :let &tabstop += 1
nmap [[ :let &tabstop -= &tabstop > 1 ? 1 : 0
[[
键映射使用了一个包含类似 C 的 “三元表达式” 的表达式:&tabstop > 1 ? 1 : 0
运算 | 运算符语法 |
赋值 数值相加并赋值 数值相减并赋值 字符串连接并赋值 | let var = expr let var += expr let var -= expr let var .= expr |
三元运算符 | bool ? expr-if-true : expr-if-false |
逻辑 OR | bool || bool |
逻辑 AND | bool && bool |
数值或字符串相等 数值或字符串不相等 数值或字符串大于 数值或字符串大于等于 数值或字符串小于 数值或字符串小于等于 | expr == expr expr != expr expr > expr expr >= expr expr < expr expr <= expr |
数值相加 数值相减 字符串连接 | num + num num - num str . str |
数值相乘 数值相除 数值系数 | num * num num / num num % num |
转换为数值 求负数 逻辑 NOT | + num - num ! bool |
括号优先 | ( expr ) |
let result_string = GetResult(); if !result_string echo "No result" endif |
result_string
被分配了一个空字符串后,上面的代码就不能正常工作,如果 result_string
包含诸如 "I am NOT an empty string"
之类的字符串,它仍然表示 "No result"
,因为该字符串首先将被转换为一个数字(0),然后再转换为布尔值(假)。if empty(result_string) echo "No result" endif |
let ident = 'Vim'
if ident == 0 "Always true (string 'Vim' converted to number 0)
if ident == '0' "Uses string equality if ident contains string "but numeri c equality if ident contains number |
ignorecase
选项的本地设置,但是任何字符串比较函数都可以被显式地标记为大小写敏感(通过附加一个 #
)或大小写不敏感(通过附加一个 ?
):if name ==? 'Batman' |"Equality always case insensitive echo "I'm Batman" elseif name <# 'ee cummings' |"Less-than always case sensitive echo "the sky was can dy lu minous" endif |
"Step through each file... for filenum in range(filecount) " Show progress... echo (filenum / filecount * 100) . '% done' " Make progress... call process_file(filenum) endfor |
filenum
始终小于 filecount
,整数除法 filenum/filecount
将始终生成 0,因此每次迭代循环都将生成:Now 0% done
let filecount = 234 echo filecount/100 |" echoes 2 echo filecount/100.0 |" echoes 2.34 |
;p
),能够按照下面的方式在 Vim 中显式文本(比如前一个段落):It's easy to adapt the syntax-toggling script shown earlier to create other useful tools. For example, if there is a set of words that you frequently misspell or misapply, you could add a script to your.vimrc to activate Vim's match mechanism and highlight problematic words when you're proofreading text.
"Create a text highlighting style that always stands out... highlight STANDOUT term=bold cterm=bold gui=bold "List of troublesome words... let s:words = [ \ "it's", "its", \ "your", "you're", \ "were", "we're", "where", \ "their", "they're", "there", \ "to", "too", "two" \ ] "Build a Vim command to match troublesome words... let s:words_matcher \ = 'match STANDOUT /\c\<\(' . join(s:words, '\|') . '\)\>/' "Toggle word checking on or off... function! WordCheck () "Toggle the flag (or set it if it doesn't yet exist)... let w:check_words = exists('w:check_words') ? !w:check_words : 1 "Turn match mechanism on/off, according to new state of flag... if w:check_words exec s:words_matcher else match none endif endfunction "Use ;p to toggle checking... |
w:check_words
被用作一个布尔标记,用来启用或关闭单词检查。WordCheck()
函数的第一行代码检查标记是否已经存在,如果存在的话,变量分配仅仅启用变量的布尔值:let w:check_words = exists('w:check_words') ? !w:check_words : 1
w:check_words
不存在,那么将通过向它分配值 1
来创建:let w:check_words = exists('w:check_words') ? !w:check_words : 1
w:
作为前缀,这表示标记变量对于当前窗口来说始终是本地变量。这将允许在每个编辑器窗口中独立启用单词检查(这与 match
命令的行为是一致的,后者的作用也只对当前窗口有效)。match
命令启用的。match
要求一个文本突出显示说明(本例中为 STANDOUT
),紧接着是一个正则表达式,指定要突出显示哪段文本。在本例中,正则表达式的构建方式是对脚本 s:words
list 变量中指定的所有单词执行 OR
运算(即 join(s:words, '\|')
)。这组待选对象随后使用大小写不敏感的单词边界括起(\c\<\(...\)\>
),确保只有完整的单词被匹配,而不需要关心大小写。WordCheck()
函数随后将结果字符串转换为一个 Vim 命令并执行它(exec s:words_matcher
),从而启用匹配功能。当 w:check_words
被关闭后,函数将执行 match none
命令,解除特殊匹配。imap
或 iabbrev
命令设置键映射或缩写词,可以在插入文本时使用。例如:imap =strftime("%e %b %Y")
imap =strftime("%l:%M %p")
strftime()
函数并插入生成的日期,同样,两次按下 CTRL-T 将插入当前时间。=
(告诉 Vim 插入后面内容的计算结果)和最后一个
(告诉 Vim 实际地执行前面的表达式)之间。但是,要注意,
(Vim 对 CTRL-R 使用的缩写)和
(Vim 对回车使用的缩写)是不同的。getcwd()
函数为当前的工作目录创建缩写,如下所示:iabbrev CWD =getcwd()
imap =string(eval(input("Calculate: ")))
string( eval( input("Calculate: ") ) )
input()
函数来请求用户输入他们的计算,随后 input()
作为一个字符串返回。该输入字符串被传递到内置的 eval()
中,后者将它作为一个 Vimscript 表达式进行计算并返回结果。接下来,内置 string()
函数将数值结果转换回一个字符串,键映射的 =
序列将能够插入该字符串。Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_
Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_
"Locate and return character "above" current cursor position... function! LookUpwards() "Locate current column and preceding line from which to copy... let column_num = virtcol('.') let target_pattern = '\%' . column_num . 'v.' let target_line_num = search(target_pattern . '*\S', 'bnW') "If target line found, return vertically copied character... |
LookUpwards()
函数首先判断插入点目前位于屏幕上的哪个列(即 “虚拟列”),使用内置的 virtcol()
函数可以做到这点。'.'
参数指定当前游标位置的列编号:let column_num = virtcol('.')
LookUpwards()
随后使用内置 search()
函数从当前的游标位置倒退着浏览文件:let target_pattern = '\%' . column_num . 'v.'
let target_line_num = search(target_pattern . '*\S', 'bnW')
\%column_numv.*\S
)来查找最近的一个行,这个行在游标所处的这一列(\%column_numv
)或后一列(.*
)应当具有一个非空字符(\S
)。search()
的第二个参数是配置字符串 bnW
,它告诉函数向后搜索,而不是移动游标,也不是封装搜索。如果搜索成功,search()
将返回相应行的行号;如果搜索失败,它将返回 0。if
语句将判断哪个字符 — 如果有的话 — 将被复制到插入点。如果没有找到符合条件的行,target_line_num
将被分配一个 0 值,因此,第一个返回语句被执行并返回空字符串(表示 “没有插入任何内容”)。return matchstr(getline(target_line_num), target_pattern)
search()
并成功匹配的只包含一个字符的字符串:return matchstr(getline(target_line_num), target_pattern)
LookUpwards()
内部实现了这个新的纵向复制行为后,剩下的工作就是在 Insert 模式中覆盖标准的 CTRL-Y 命令,使用下面的 imap:imap =LookUpwards()
=
调用 Vimscript 函数调用,因此本例使用 =
。使用 single-CTRL-R 表单插入后面的表达式的结果,就好象是直接输入的一样,这意味着结果中的任何特殊字符将保留它们的特殊含义和行为。另一方面,double-CTRL-R 表单将按原样逐字插入结果,不做任何处理。=
,那么从前面的行中复制字母转义字符就相当于输入字符,而这将会造成编辑器立刻退出 Insert 模式。:help functions
:help function-list