原创 使用VIM开发软件项目 - (14) 指随意动,移动如飞 (二)收藏

新一篇: 使用cygwin X server实现Linux远程桌面 (for windows) | 旧一篇: 使用VIM开发软件项目 - (14) 指随意动,移动如飞 (一)

本节所用命令的帮助入口:

 

:help usr_03.txt
:help motion.txt
:help usr_29.txt
:help scroll.txt
:help folding

上一篇文章中我们介绍了一些常用的移动命令,本篇将继续介绍更多的命令,使你在文档中自由穿梭。

 

[ 利用跳转表 ]

 

VIM中,很多命令可以引起跳转,VIM会记住把跳转前光标的位置记录到跳转表中,并提供了一些命令来根据跳转表进行跳转。要知道哪些命令引起跳转,参见“:help jump-motions”。

使用命令“''”(两个单引号)和“``(两个反引号,在键盘上和“~”共用一个键)可以返回到最新的跳转位置。例如,当前光标位于文件中第1234行,然后我使用“G”命令跳转到第4321行;这时如果我按“''”或"``",就会跳回到1234行。

因为这两个命令也属于跳转命令,所以第4321行也被记入跳转表,如果你再次使用这两个命令,就会发现自己又跳回第4321行了。

这两个命令有一点不同,“``”在跳转时会精确到列,而“''”不会回到跳转时光标所在的那一列,而是把光标放在第一个非空白字符上。

如果想回到更老的跳转位置,使用命令“CTRL-O”;与它相对应的,是“CTRL-I”,它跳转到更新的跳转位置(:help CTRL-O:help CTRL-I)。这两个命令前面可以加数字来表示倍数。

使用命令“:jumps”可以查看跳转表(:help :jumps)

 

 [ 使用标记 ]

 

标记(mark)VIM提供的精确定位技术,其功能大概相当于GPS技术,只要你知道标记的名字,就可以使用命令直接跳转到该标记所在的位置。

VIM中的标记都有一个名字,这个名字用单一的字符表示。大写和小写字母(A-Za-z)都可以做为标记的名字,这些标志的位置可以由用户来设置;而数字标记0-9,以及一些标点符号标记,用户不能进行设置,由VIM来自动设置。

我们主要讲述字母标记的使用,对于数字标记和标点符号标记,请自行参阅帮助手册(:help mark-motions)

小写字母标记局限于缓冲区,也就是说,每个缓冲区都可以定义自己的小写字母标记,各缓冲区间的小写字母标记彼此不干扰。如果我在文件A中设置一个标记t,然后在文件B中也可以设置一个标记t。那么在文件A中,可以用“'t”命令跳到文件A的标记t位置 ;在文件B中,可以用“'t”命令跳到文件B的标记t位置。如果文件在缓冲区列表中被删除,小写字母标记就丢失了。

大写字母标记是全局的,它在文件间都有效。如果在文件A中定义一个标记T,那么当使用命令“'T”时,就会跳到文件A的标记T位置,不管你当前处于哪个文件中。

设定一个标记很简单,使用命令“m{a-zA-Z}”就可以了。例如,命令“mt”在把当前光标位置设定为标记t;命令“mT”把当前光标位置设定为标记T(:help m)

要跳转到指定的标记,使用命令“'{a-zA-Z}”或“`{a-zA-Z}”。例如,命令“'t”会跳转到标记t;命令“'T”会跳转到标记T( :help ' )

单引号和反引号的区别和上面所讲的一样,“`”在跳转时会精确到列,而“'”不会回到跳转时光标所在的那一列,而是把光标放在第一个非空白字符上。

标记也可以被删除,使用命令“:delmarks”可以删除指定标记。命令“:marks”列出所有的标记。

 

关于标记,有两个非常有用的插件,一个是ShowMarks,另外一个叫marks browser

ShowMarks是我最常用的插件之一,它使用VIM提供的sign功能以及高亮功能显示出标记的位置。这样,你在设定了一个标记后,它就会在你的VIM窗口中显示出标记的名字,并高亮这一行。

这个插件在这里下载,下载后,在你的$HOME/.vim目录把它解压:

http://www.vim.org/scripts/script.php?script_id=152

 

在我的vimrc中,对ShowMarks进行了如下配置:

   """"""""""""""""""""""""""""""
   " showmarks setting
   """"""""""""""""""""""""""""""
   " Enable ShowMarks
   let showmarks_enable = 1
   " Show which marks
   let showmarks_include = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
   " Ignore help, quickfix, non-modifiable buffers
   let showmarks_ignore_type = "hqm"
   " Hilight lower & upper marks
   let showmarks_hlline_lower = 1
   let showmarks_hlline_upper = 1

首先,使能showmarks插件,然后定义showmarks只显示全部的大写标记和小写,并高亮这两种标记;对文件类型为help, quickfix, 和不可修改的缓冲区,不显示标记的位置。

你可以定义自己的颜色来高亮标记所在的行,下面是我的定义,我把它放在我自己的colorscheme文件中:

" For showmarks plugin
hi ShowMarksHLl ctermbg=Yellow   ctermfg=Black  guibg=#FFDB72    guifg=Black
hi ShowMarksHLu ctermbg=Magenta  ctermfg=Black  guibg=#FFB3FF    guifg=Black

ShowMarks插件中已经定义了一些快捷键:


<Leader>mt   - 打开/关闭ShowMarks插件
<Leader>mo   - 强制打开ShowMarks插件
<Leader>mh   - 清除当前行的标记
<Leader>ma   - 清除当前缓冲区中所有的标记
<Leader>mm   - 在当前行打一个标记,使用下一个可用的标记名

我最常使用的是“<Leader>mm”和“<Leader>mh”,用起来非常方便。

VIM 7.0中,如果大写的标记被定义了,那么函数line()无论在哪个缓冲区里都会返回该标记的行号,导致showmarks在每个缓冲区里都会把这个大写标记显示出来。因此我为这个插件打了个补丁来修正此问题。
VIM 7.0
中也可以真正的删除一个mark标记,所以也改了showmarks插件的删除标记功能。原来的功能在删除标记时,并未真正删除它,只是把这个标记指向缓冲区的第一行;现在则是真正删除此标记。

补丁的内容如下:

--- easwy/showmarks.vim    2007-03-13 10:15:07.000000000 +0800
+++ plugin/showmarks.vim    2007-03-23 09:35:01.000000000 +0800
@@ -144,6 +144,25 @@
 hi default ShowMarksHLo ctermfg=darkblue ctermbg=blue cterm=bold guifg=blue guibg=lightblue gui=bold
 hi default ShowMarksHLm ctermfg=darkblue ctermbg=blue cterm=bold guifg=blue guibg=lightblue gui=bold
 
+" Function: GetMarkLine()
+" Authors: Easwy Yang
+" Description: This function will return the line number where the mark
+" placed. In VIM 7.0 and later, function line() always returns line number but
+" not 0 in case an uppercase mark or number mark is placed. However, in VIM 6,
+" it only returns 0 when the uppercase mark isn't placed in current file.
+fun! s:GetMarkLine(mark)
+    if v:version < 700
+        let lnum = line(a:mark)
+    else
+        let pos = getpos(a:mark)
+        let lnum = pos[1]
+        if pos[0] && bufnr("%") != pos[0]
+            let lnum = 0
+        endif
+    endif
+    return lnum
+endf
+
 " Function: IncludeMarks()
 " Description: This function returns the list of marks (in priority order) to
 " show in this buffer.  Each buffer, if not already set, inherits the global
@@ -354,7 +373,8 @@
         let c = strpart(s:IncludeMarks(), n, 1)
         let nm = s:NameOfMark(c)
         let id = n + (s:maxmarks * winbufnr(0))
-        let ln = line("'".c)
+        "let ln = line("'".c)
+        let ln = s:GetMarkLine("'".c)
 
         if ln == 0 && (exists('b:placed_'.nm) && b:placed_{nm} != ln)
             exe 'sign unplace '.id.' buffer='.winbufnr(0)
@@ -393,11 +413,18 @@
     let s:maxmarks = strlen(s:IncludeMarks())
     while n < s:maxmarks
         let c = strpart(s:IncludeMarks(), n, 1)
-        if c =~# '[a-zA-Z]' && ln == line("'".c)
+        "if c =~# '[a-zA-Z]' && ln == line("'".c)
+        if c =~# '[a-zA-Z]' && ln == s:GetMarkLine("'".c)
             let nm = s:NameOfMark(c)
             let id = n + (s:maxmarks * winbufnr(0))
             exe 'sign unplace '.id.' buffer='.winbufnr(0)
-            exe '1 mark '.c
+            " Easwy, we can really remove marks in VIM 7.0 and later
+            if v:version >= 700
+                exe 'delm '.c
+            else
+                exe '1 mark '.c
+            endif
+            " Easwy, end
             let b:placed_{nm} = 1
         endif
         let n = n + 1
@@ -417,7 +444,13 @@
             let nm = s:NameOfMark(c)
             let id = n + (s:maxmarks * winbufnr(0))
             exe 'sign unplace '.id.' buffer='.winbufnr(0)
-            exe '1 mark '.c
+            " Easwy, we can really remove marks in VIM 7.0 and later
+            if v:version >= 700
+                exe 'delm '.c
+            else
+                exe '1 mark '.c
+            endif
+            " Easwy, end
             let b:placed_{nm} = 1
         endif
         let n = n + 1
@@ -466,7 +499,8 @@
     while n < s:maxmarks
         let c = strpart(s:IncludeMarks(), n, 1)
         if c =~# '[a-z]'
-            if line("'".c) <= 1
+            "if line("'".c) <= 1
+            if s:GetMarkLine("'".c) <= 1
                 " Found an unused [a-z] mark; we're done.
                 let next_mark = n
                 break

用法:

1. 保存该patch到某一目录,例如:/tmp/showmarks.vim.patch

2. cd到你的.vim目录:cd ~/.vim

3. 运行命令:cat /tmp/showmarks.vim.patch | patch -p0

 

Marks Browser可以显示出当前缓冲区中定义的小写标记的位置,在你无法对应上标记的名字和其位置时,非常有用。这个插件在此下载:

http://www.vim.org/scripts/script.php?script_id=1706

下载后把它放到你的$HOME/.vim/plugin目录即可,我为其定义了一个快捷键:

   """"""""""""""""""""""""""""""
   " markbrowser setting
   """"""""""""""""""""""""""""""
   nmap <silent> <leader>mk :MarksBrowser<cr>

这样,直接使用“,mk”就可以打开Mark Browser窗口了。

下图显示这两个插件工作时的效果。我在文件中定义了三个标记,一个大写标记A,两个小写标记at。最上面的窗口是Mark Browser窗口,主编辑窗口中的高亮行及sign标记是ShowMarks插件放置的。

 


[ 折行 ]

 

在文件比较大时,在文件中移动也许会比较费力。这个时候,你可以根据自己的需要把暂时不会访问的文本折叠起来,既减少了对空间的占用,移动速度也会快很多。

VIM提供了多种方法来进行折叠,既可以手动折叠,也可以根据缩进、语法,或使用表达式来进行折叠。

程序文件一般都具有良好的结构,所以根据语法进行折叠是一个不错的选择。

 

要启用折叠,首先要使能'foldenable'选项,这个选项是局部于窗口的选项,因此可以为每个窗口定义不同的折叠。

接下来,设置'foldmethod'选项,对于程序,我们可以选择根据语法高亮进行折叠。需注意的,要根据语法高亮进行折叠,必须打开文件类型检测和语法高亮功能,请参见我前面的文章。

下面是我的vimrc中的设置,它使用了自动命令,如果发现文件类型为ccpp,就启用折叠功能,并按语法进行折叠:

autocmd FileType c,cpp  setl fdm=syntax | setl fen

注意,VIM的很多命令、选项名都有简写形式,在帮助手册中可以看到简写形式,也可以按简写形式来help,例如,要查看'foldmethod'选项的帮助,可以只输入“:help 'fdm'”。

 

折叠后的效果见下图:

 

图中以黑色背景显示的行就是被折叠起来的行,VIM会显示这个fold中被折叠了多少行,以及起始行的内容。留意一下左下方的“__Tag_List__”窗口,在这个窗口中也存在着折叠,我把macro, typedef, variable几项折叠起来了,而把function的折叠打开。从该窗口最左边的折叠栏(:help fold-foldcolumn)也可以看出不同:被折叠的文本前显示了"+",打开的折叠前显示的是"|"。

折叠的背景色及显示文字等都可以修改,参阅帮助手册(:help folding)

 

下面的命令用来打开和关闭折叠:

zo – 打开光标下的折叠
zO – 循环打开光标下的折叠,也就是说,如果存在多级折叠,每一级都会被打开
zc – 关闭光标下的折叠
zC – 循环关闭光标下的折叠

更多的命令,请参阅手册。

VIM提供了一些命令在折叠间快速移动:

[z – 到当前打开折叠的开始
]z – 到当前打开折叠的结束
zj – 向下移动到下一个折叠的开始处
zk – 向上移动到上一个折叠的结束处

我通常不喜欢把文本折叠起来,因为我更喜欢一目了然的看到全部文本。你可以根据自己的喜好来决定是否启用折叠。

多说一点,手动创建的折叠是可以保存在session文件,这样下次进入VIM时可以载入之前创建的折叠,参见:help 'sessionoptions'

 

[ 在程序中移动 ]

 

VIM的作者是一个程序员,这就不难理解为什么VIM提供了众多在程序中移动的命令。这里面既包括我们前面的文章中介绍过的利用tag文件及cscope在标签间跳转,也包括众多在函数、注释、预处理指令、程序段,及其它程序元素中移动的命令。

本文不再详细介绍这些命令,作为程序员,一定要熟读usr_29.txt!这些命令,可以帮助你在程序中得心应手的移动。
 

在这里介绍两个插件,增强了在程序中移动的功能,一个是a.vim,另外一个是matchit

a.vim的功能非常简单,它帮助你在源文件和头文件间进行切换,这个简单的功能,却非常实用,至少它为我节省了很多时间。

在此下载a.vim插件:

        http://www.vim.org/scripts/script.php?script_id=31

然后把它放到你的.vim/plugin目录就可以了。

假设你正在浏览C语言的源文件,这时想修改它对应的头文件,只需要输入“:A”命令,就切换到头文件了(需要源文件和头文件在同一目录中)。a.vim插件还定义了其它一些命令和快捷键,参见它的帮助。
 

VIM中,"%"命令跳转到与当前项目相匹配的项目。例如,当光标位置在“{”时,按下%,光标就跳转到对应的“}( :help %)

VIM提供的%命令,只能在括号,或者C注释的开始和结束( /*  */),或者C编译预处理指令间进行跳转。对于其它程序结构,例如HTML%命令不能从<html>标记,跳转到对应的</html>标记。

Matchit插件则扩展了%命令的功能,使%命令可以对其它程序语言的开始和结束标记间进行跳转。在此处下载matchit插件:

        http://www.vim.org/scripts/script.php?script_id=39

把这个插件放到你的.vim/plugin目录,你就可以用%在各种开始/结束标记间跳转了,目前,它可以支持Ada, ASP with VBS, Csh, DTD, Essbase, Fortran, HTML, JSP (same as HTML), LaTeX, Lua, Pascal, SGML, Shell, Tcsh, Vim, XML等语言。

 

[ 插入模式下的移动 ]

 

上面介绍的移动命令,都是在normal模式下使用的,如果想在insert模式下移动,阅读:help ins-special-special

你真的需要在插入模式下移动吗?我几乎不会!通常我会先按ESC返回Normal模式,然后再移动,当你习惯了以后,你会发现效率会更高。

 

[ 小结 ]

 

你会发现,本文的内容,和usr_03.txt帮助文档很相似。是的,只要你学会了usr_03.txt中列出的命令,你就掌握了最常用最实用的VIM移动命令。

如果你想了解更多的移动命令,请通篇阅读motion.txt,记住你最有可能用到的那些键。当你的手指能够不假思索的使用这些命令后,你在VIM中就能做到指随意动、移动如飞了。

 

 [参考文档]

1. VIM帮助文件

2. http://vimcdoc.sourceforge.net/

 

[尾记]

本文可以自由应用于非商业用途。转载请注明出处。

原文链接:http://blog.csdn.net/easwy

 

[版本]

24Sep07, easwy, v0.1, initial version

发表于 @ 2007年09月24日 09:53:00|评论(loading...)|编辑

新一篇: 使用cygwin X server实现Linux远程桌面 (for windows) | 旧一篇: 使用VIM开发软件项目 - (14) 指随意动,移动如飞 (一)

评论

#leesnow1 发表于2007-09-24 16:08:05  IP: 218.20.59.*
非常感谢 楼主 给出的这些好文章!!!!

同时希望楼主 能够给出个实际用VIM+GCC+GDB编程的例子,就是一切编程操作向IDE一样,直接在VIM中进行,特别是关于调用GDB后如何在返回VIM进行程序编辑

#easwy 发表于2007-09-25 10:19:17  IP: 213.70.90.*
好的,我调整一下提纲,下一篇先写调试相关的主题。
不过工作中我用不到GDB调试,所以只是大概介绍一下,抛砖引玉。
#leesnow1 发表于2007-09-25 11:16:22  IP: 121.32.0.*
楼主 不是在做嵌入式开发的吗 编程环境是什么 不是VIM+GCC+GDB吗 难道语义错误都是靠人工解决的 ? 我之前一直在VC环境下做开发 最近想向linux平台发展 楼主能否有好的意见
#szjso 发表于2007-09-25 11:31:36  IP: 58.62.96.*
谢谢easwy关于vim的系列文章!
我刚学vim,有些问题想向你请教一下。看过你自动补全的两篇文章之后,我觉得奇怪,我搞了函数名的前几个字母,就按<C-X><C-O>,是可以弹出菜单,但只有函数名而没有函数的参数,这个和你的截图不一样。而我安装了OmniCppComplete后,弹出菜单也只能列出一个类的成员变量,而不能列出类的成员函数。我想可能是有些设置不对,所以向你请教。望不吝赐教,先谢过了!
#easwy 发表于2007-09-25 12:25:41  IP: 213.70.90.*
我做的不是嵌入式linux的开发,以前用的是vxworks,它的调试器源于gdb,不是提供了更友好的图形前端。现在用的平台比较糟糕,只能printf调试,这比较考验人, ;-)
要向linux平台发展,最困难的是克服已有的习惯,以及真正接受linux的观念。我见过很多人,他们使用linux,但却没有体会到它的强大与便利,因为他们改变不了已经培养起来的习惯:离不开图形界面,不习惯使用命令行,懒于阅读手册,不屑于了解linux的文化传统和其内含的奉献、共享的精神......
必须承认,linux的用户界面不够友好,这阻止了很多人学习使用linux,不过,当你度过了最初的阶段,当你学会了通过手册、internet、邮件列表来学习,当你接受了它的文化,你会觉得使用linux是很愉快的事,你也会体会到它的强大、便利、优美......
当然,这一切需要付出,需要时间,你会体验到别扭、沮丧、失败,如果你能坚持下来,你就能达到
#easwy 发表于2007-09-25 12:34:43  IP: 213.70.90.*
第一个问题,要显示函数参数,要打开OmniCppComplete的OmniCpp_ShowPrototypeInAbbr选项,我没有试过,不知道是不是满足你的需求。或者你可以不更改completeopt选项,在preview窗口在查看函数原型。

第二个问题,不知道你的tag文件中有没有生成OmniCppComplete需要的额外信息。另外,似乎ctags是从cpp文件中获取成员函数信息的,你在生成tag时有没有包含cpp文件呢? (我记不清楚是不是这样,你可以试一下)
#szjso 发表于2007-09-25 14:47:56  IP: 58.62.96.*
谢谢easwy的回答!

我发现了第二个问题的原因。是我看OmniCppComplete的手册时理解错误,在用ctags时加了-I选项。结果不能列出成员函数。

至于第一个问题,我是指没有安装OmniCppComplete前,只用vim自己的自动补全功能时的情况。我看你的截图是在说明OmniCppComplete前就有函数的参数显示了。后来我设置了OmniCpp_ShowPrototypeInAbbr=1。就可以显示函数参数了。但是,显示位置和你的截图不同。截图中函数原型显示在'f'的右边,但我的显示是在'f'的左边。我想我还有设置没对。不过已经可以用了。呵呵,再次谢谢!
#easwy 发表于2007-09-26 09:33:24  IP: 213.70.90.*
关于第一个问题:
这个补全是对c文件的补全,没有使用OmniCppComplete。你可以在c文件中试一下,用的是vim自带的c文件缺省omni函数。
#szjso 发表于2007-09-27 14:26:08  IP: 59.42.189.*
谢谢你的答复!我又试了一下c文件,果然效果和你的截图一样。我之前是用cpp文件做试验的。
#szjso 发表于2007-10-10 16:55:04  IP: 121.32.186.*
学习了omnicppcomplete和vim的script一个多星期,说说自己的一个经验:如果有如下代码:
typedef struct abc
{
int a,b;
}ABC;

void test1(ABC * abc1)
{
}

void test2(ABC * const abc1)
{
}
则test1()可以完成对abc1的自动补全,但test2()就不能完成对abc1的自动补全了。原因是omnicppcomplete没有考虑到const这个关键字。如果修改文件 utils.vim 中的 omni#cpp#utils#ExtractTypeInfoFromTokens()函数中以下
一句:
elseif index(['*', '&'], token.value)<0 为
elseif index(['*', '&' , 'const'], token.value)<0

就可进行补全了。我还没有对这样修改以后进行详细测试。但总算可以解决了问题。
#王军 发表于2007-10-12 18:08:11  IP: 203.129.78.*
谢谢 Easwy, 我很受益。

#easwy 发表于2007-10-15 11:41:47  IP: 213.70.90.*
to szjso

你试试把
void test2(ABC * const abc1)
写成
void test2(const ABC * abc1)
可能这样OmniCppComplete就能正常工作了。
#szjso 发表于2007-10-16 09:03:42  IP: 121.33.12.*
谢谢easwy的回复!正如你所说,是这样的。

但是不知道我有没有理解错误。 我印象中void test2(ABC * const abc1)和 void test2(const ABC * abc1)的意义是不同的。

前者是在函数test2()中,可以改变abc1所指向的结构体的元素,但不允许改变abc1指针本身的值。
后者是在函数test2()中,不可以改变abc1所指向的结构体的元素,但允许改变abc1指针本身的值。
#easwy 发表于2007-10-16 10:12:58  IP: 213.70.90.*
你说的对,const放在这两个位置的含义是不同的
但是我认为在这里使用void test2(ABC * const abc1)是没有意义的,因为在函数调用时,abc1是值拷贝的,即使你在函数中对其进行修改,也不影响原来的指针,所以没有必要用const修饰。受保护的应该是指针所指向的内容。
发表评论  


登录
Csdn Blog version 3.1a
Copyright © Easwy