supertab是vim的一个出名的插件, 相信会vim的人没几个不知道的, 我在之前的<<vim之补全1>>中首先说明的也是它, supertab实现的功能简单的说就是用tab来来调用vim的补全功能, 这和linux的终端操作习惯完全一致, 并且方便而合理. supertab是我最早接触的vim插件, 保留至今而没有抛弃, 当然很多人可能已经不再使用这个插件, 因为更加自动化的nerocachecomplete和youcompleteme插件完全可以取而代之, 不过个人保留supertab的理由是用它来实现就近补全, 也就是vim自带的ctrl+p补全, 关于为何这么做我已经在<<vim之补全1>>中用相当多的话解释过. 有兴趣和耐心的读者可以移步阅读.

  这篇文章主要是说明个人supertab功能上的一些改造. 首先说明为何要对supertab动手术, vim在编辑上的高效性源自纯键盘式的操作模式和高度定制能力. 这种能力同时也给我们带来了很多负面影响, vim不像virsual Studio那个拥有完整的方案体系, VS完整到我们甚至不用去了解它的任何实现机制, VS给我们的任务是要求我们学会按照它的方式来使用, vim给我们更大的一个任务往往是要求我们学会让vim按照我们的希望的方式来使用. 这种让用户主宰一切的能力对很多喜欢自由和设计的程序员有着巨大的吸引力. 可惜的凡事都有好的一面也有坏的一面, vim就像是一匹健壮的野马, 想要让他带你跑的更快之前你必须付出相当大的精力来驯化它. 好了, 回归正题, 首先说一下supertab本身的特点: supertab在默认的配置下基本上可以和ctrl+p补全划上等号, 当前我们可以通过complete这个vim参数来配置ctrl+p的补全索引范围. 为了提高速度和精确度, 个人的配置中complete只保留了对当前缓冲,后台缓冲和其他窗口的搜索. 也就是基本上是一个标准的就近补全功能. supertab在插入模式下光标前面如果有单词时通过<tab>触发, 触发后弹出补全列表, 补全列表中的关键词是按照距离光标远近排列的,因此越近的单词出现在补全列表越靠前的位置, 但问题出现vim的作者为了实现这种就近补全, 使用了一个很巧妙的方法:将对上下文正向索引建立的补全列表从后往前倒着选择. 也也就是指ctrl+p补全的第一个选项是正向列表的最后一个. 这个方式可以大大简化和统一vim的补全列表显示算法, supertab遵循了bram的本意, 默认的tab触发后也会倒着选择, 并且在补全列表出现后通过tab键我们可以继续倒着向上选择. 可是在我个人常用的补全并不是只有ctrl+p一种, 为了最大限度的提高的补全的精确度和效率, 同时使用的又常用到还有字典补全(ctrl+x ctrl+k触发, 个人映射到"jj"双键上), tag补全(ctrl+x ctrl+]触发, 个人映射在"kk"上)等, 这些补全在触发后补全列表并不会像ctrl+p那样倒着显示, 首选项也落在了顺数第一项上, 这个时候如果我们再次使用tab键, 由于supertab的影响, 选项依然会倒着选择, 也就是会立刻从顺数第一项跳转到倒数第一项, 然后往回选择. 我们本能的使用tab键是为了能选择顺数第二个选项, 结果这个时候tab确是倒序选择, 这就是supertab最大的问题所在, 实践也证明我们习惯性操作被这种一个键在两种情形下功能不一致所打乱是一件非常恼人的事情.

  为了解决这个问题, 最初的想法是通过修改supertab的源码脚本来达到tab在选择上的功能更加合理化. 可惜的是在花掉了近半天的时间琢磨supertab的脚本后依然一无所获, 由于个人对vim脚本的熟悉程度上的有限, 即便是在我读懂了大多数supertab脚本的前提下还是不知道从怎么修改. 好在花费的时间总是有所收获的, 在慢慢知道了supertab的工作原理后, 我开始想别的办法, 首先意识到的是supertab实现的功能要比我现在使用到的要多的多, 我只是把supertab当成了ctrl+p的一个更加便捷的一个映射来使用. 其他supertab的特性几乎全部可以忽略. 由于需求上的简单让我想到了试着自己实现一个最简单的supertab, 在试着自己实现supertab最基本的ctrl+p功能的同时我也找到了可以让tab转换选择方向的方法. 事实上我们的tab只需要在ctrl+p补全中倒叙选择, 其他地方几乎统一的需要它正向选择, 而问题出在找不到一个合理的机制来精确标记从ctrl+p触发到一次补全结束这个过程.  在不精通vim的脚本的前提下我想不到办法来标识ctrl+p补全的结束. 后来想想虽然找不到ctrl+p的结束位置, 但ctrl+p补全的开始位置和其他补全的开始位置都是可以精确的被找到, 因此, 可以通过一个全局标志位来实现在在需要切换选择方向的补全开始处切换tab功能, 具体实现如下:

在vim的运行时目录中任意建立一个.vim结尾的vim脚本, 个人是删除了原来的supertab.vim在~/.vim/vunder/supertab/plugin/目录下新建了一个mysupertab.vim文件, 在文件中写入如下内容:

func! MySuperTab(cmd)  "cmd 参数标识是tab 还是shift+tab
   if pumvisible()   pumvisible函数在vim存在补全列表窗口时返回真
      if g:issupertab == 0 "全局变量issupertab标识是否是ctrl+p补全, 它会在.vimrc中初始化为0, 在第一次触发ctrl+p补全时被置1
         return a:cmd == 'n' ? "\<c-n>" : "\<c-p>" "如果cmd是n表示tab映射, issupertab == 0表示现在是其他补全, 因此返回<c-n>来正向选择
      else
         return a:cmd == 'n' ? "\<c-p>" : "\<c-n>" "issupertab == 1会触发这里, 说明现在是ctrl+p补全, 因此'n'参数将返回<c-p>来实现逆向选择
      endif
   endif
   if strpart(getline("."), col(".") - 2, 1) == ' ' || col(".") == 1 "如果光标前面的字符是空格或者光标在行首, tab和shifttab都返回真正的tab
      return "\<tab>"   "因此如果希望输入真实的tab需要在行首或者先输入一个空格再按下tab, 当然我们最好开启vim的expandtab属性, vim对tab前有空格
   else                             "存在时插入tab的处理相当的智能和方便, 你可以自己的试着测试一下, 不过, 我个人的tab长度被设置成了奇葩的3
      let g:issupertab = 1 "如果不存在弹出列表, 又不存在前导空格或行首标识,这里将触发ctrl+p补全, 因此我们将issupertab置1来标识之后的补全全部是ctrl+p补全, 这个标识会一直存在并且影响tab的行为是倒序选择, 因此, 任何需要正向选择的列表功能在触发时需要将issupertab置0
      return a:cmd == 'n' ? "\<c-p>" : "\<c-n>"
   endif
endfunc
" 下面的函数是其他不同补全的入口函数, 这些入口函数只是在返回正常触发组合键之前将issupertab置0以便让之后的tab选择功能是正向的,这些置0操作同样是具有粘滞性
func! Mycxck() "字典补全
   let g:issupertab = 0
   return "\<c-x>\<c-k>"
endfunc

func! Mycxct() "tag补全
   let g:issupertab = 0
   return "\<c-x>\<c-]>"
endfunc

func! Mycxcf() "文件路径补全
   let g:issupertab = 0
   return "\<c-x>\<c-f>"
endfunc

func! Mycxci() "包含文件补全
   let g:issupertab = 0
   return "\<c-x>\<c-i>"
endfunc

func! Mycxcu() "用户自定义补全
   let g:issupertab = 0
   return "\<c-x>\<c-u>"
endfunc

在.vimrc需要做的相关配置如下:

set expandtab

ino jj <c-r>=Mycxck()<cr>
ino kk <c-r>=Mycxct()<cr>
ino JJ <c-r>=Mycxcf()<cr>
ino KK <c-r>=Mycxci()<cr>
ino JK <c-r>=Mycxcu()<cr>

"mysupertab
let g:issupertab = 0
imap <script> <tab>   <c-r>=MySuperTab('n')<cr>
imap <script> <s-tab> <c-r>=MySuperTab('p')<cr>

实现效果:

vim刚进入时tab补全选择方向默认是正向的, 如果在插入模式下非行首和空格之后直接使用tab键将触发ctrl+p的就近补全. 此时补全列表选择方向切换到反向选择. 之后将一直保持反向,直到通过jj触发字典补全的时候tab选择列表的方式才会被切换正向补全.如果再次触发ctrl+p补全方向再次切换....

这样的切换方式基本实现了tab在补全列表上的方向的自动切换, 并且由于简化了tab的触发机制因此在vim性能上会有所提升.但存在的问题是任何需要用到vim弹出列表功能的插件为保证tab选择行为的一致性, 都需要小小的hack一下, 比如clang_complete, 默认在输入. > 和::时会触发clang_complete为保证clang_complete在弹出的补全列表上tab键的正向选择, 需要在clang_complete源码中这个三个符号的触发函数中添加let g:issupertab == 0. 又比如FuzzyFinder, 它的弹出列表我测试过, 没有修改任何代码时<tab>总是会回到第一选项, shift-tab可以正常会向上选择, 不过这里我没有修改FuzzyFinder的任何代码, 因为我已经将它的向上和向下选项选择键设置成了<ctrl+j>和<ctrl+k>, 具体是源码中下面相关的代码被修改成这样:

function s:initialize()
  "---------------------------------------------------------------------------
  call l9#defineVariableDefault('g:fuf_modesDisable'     , [ 'mrufile', 'mrucmd', ])
  call l9#defineVariableDefault('g:fuf_keyOpen'          , '<CR>')
  call l9#defineVariableDefault('g:fuf_keyOpenSplit'     , '<C-h>')
  call l9#defineVariableDefault('g:fuf_keyOpenVsplit'    , '<C-l>')
  call l9#defineVariableDefault('g:fuf_keyOpenTabpage'   , '<C-t>')
  call l9#defineVariableDefault('g:fuf_keyPreview'       , '<C-@>')
  call l9#defineVariableDefault('g:fuf_keyNextMode'      , '<C-u>')
  call l9#defineVariableDefault('g:fuf_keyPrevMode'      , '<C-y>')
  call l9#defineVariableDefault('g:fuf_keyPrevPattern'   , '<C-f>')
  call l9#defineVariableDefault('g:fuf_keyNextPattern'   , '<C-b>')
  call l9#defineVariableDefault('g:fuf_keySwitchMatching', '<C-i>')

由于上面的源码修改后的设置中没有出现ctrl+j和ctrl+k, 因此vimrc中如下映射可以正常工作:

ino <c-j> <down>
ino <c-k> <up>

因此FuzzyFinder的弹出列表可以正常使用ctrl+j和ctrl+k, 甚至如下设置也是可以正常工作的:

ino <c-r> <left><left><left><left><left><left>
ino <c-f> <right><right><right><right><right><right>

事实上, 只要插件中没有对ctrl+j和ctrl+k等做过映射, 插件的弹出列表中上面的映射总是可以使用的. How wanderful!!!

用最简单的脚本完成supertab的基本功能并实现一个更加合理的功能的更多相关文章

  1. 简单shell脚本

      简单shell脚本备忘   #!/bin/sh num= ] do table_num=`printf %03d ${num}` echo album_info_${table_num} #mys ...

  2. [shell编程]一个简单的脚本

    首先,为什么要学习shell呢?哈哈,当然不是shell能够怎样怎样然后100字. 最近看到一篇博文<开阔自己的视野,勇敢的接触新知识>,读完反思良久.常常感慨自己所会不多,对新知识又有畏 ...

  3. 查看当前支持的shell,echo -e相关转义符,一个简单shell脚本,dos2unix命令把windows格式转为Linux格式

    /etc/shells [root@localhost ~]# more /etc/shells /bin/sh /bin/bash /sbin/nologin /usr/bin/sh /usr/bi ...

  4. selenium从入门到应用 - 2,简单线性脚本的编写

    本系列所有代码 https://github.com/zhangting85/simpleWebtest 本文将介绍一个Java+TestNG+Maven+Selenium的web自动化测试脚本环境下 ...

  5. Linux学习之十七-配置Linux简单的脚本文件自启动

    配置Linux简单的脚本文件自启动 在Linux中使用shell脚本解决一些问题会比单独执行多条命令要有效率,脚本文件规定命名以.sh结尾,最基本的规则就是其内容是命令,想要脚本文件开机自启动,就需要 ...

  6. Shell基础(一):Shell基础应用、简单Shell脚本的设计、使用Shell变量、变量的扩展应用

    一.Shell基础应用 目标: 本案例要求熟悉Linux Shell环境的特点,主要练习以下操作: 1> 切换用户的Shell环境       2> 练习命令历史.命令别名       3 ...

  7. android-sdk-window的环境搭建以及appium简单录制脚本的使用

    大家好,今天给大家带来的是appium的环境搭建以及简单的录制脚本,自学的过程中入了不少坑,下面给大家开始分享! 使用Appium录制脚本必备三大金刚:Appium-desktop(至于为什么用这个, ...

  8. Linux简单Shell脚本监控MySQL、Apache Web和磁盘空间

    Linux简单Shell脚本监控MySQL.Apache Web和磁盘空间 1. 目的或任务 当MySQL数据库.Apache Web服务器停止运行时,重新启动运行,并发送邮件通知: 当服务器磁盘的空 ...

  9. Cordova webapp实战开发:(7)如何通过简单的方法做到,不重新发布APP来修复bug、增加功能、或者躲开苹果的一些严格审核?

    到<Cordova webapp实战开发:(6)如何写一个iOS下获取APP版本号的插件?>为止,我们已经大体学会了如何使用Cordova了,那些都是使用Cordova的开发者必备的技能. ...

随机推荐

  1. Android 内存管理

    1.Activity中的对象生命周期勿大于Activity的生命周期.OOM演示样例代码例如以下: private static Drawable sBackground; @Override pro ...

  2. postgresql 创建函数

    One of the most powerful features of PostgreSQL is its support for user-defined functions written in ...

  3. HDU 4849 Wow! Such City!陕西邀请赛C(最短路)

    HDU 4849 Wow! Such City! 题目链接 题意:依照题目中的公式构造出临接矩阵后.求出1到2 - n最短路%M的最小值 思路:就依据题目中方法构造矩阵,然后写一个dijkstra,利 ...

  4. webservice Connection timed out

    webservice Connection timed out,当发生webservice的链接超时错误时.我想原因无非就是webclient到webservice之间的链接通路发生了异常,那么该怎样 ...

  5. Android开发之ListView实现不同品种分类分隔栏的效果(非ExpandableListView实现)

    我们有时候会遇到这么一个情况.就是我在一个ListView里面须要显示的东西事实上是有种类之分的.比方我要分冬天,夏天.秋天.春天,然后在这每一个季节以下再去载入各自的条目数据. 还有,比方我们的通讯 ...

  6. 具体解释clone函数

    我们都知道linux中创建新进程是系统调用fork,但实际上fork是clone功能的一部分,clone和fork的主要差别是传递了几个參数.clone隶属于libc.它的意义就是实现线程. 看一下c ...

  7. Boost Replaceable by C++11 language features or libraries

    Replaceable by C++11 language features or libraries Foreach → Range-based for Functional/Forward → P ...

  8. POJ 2007 Scrambled Polygon(简单极角排序)

    水题,根本不用凸包,就是一简单的极角排序. 叉乘<0,逆时针. #include <iostream> #include <cstdio> #include <cs ...

  9. 1695 windows 2013

    1695 windows 2013  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 话说adam ...

  10. JPA学习笔记(13)——查询缓存

    使用hibernate的查询缓存 运行下面代码: String jpql = "FROM User u WHERE u.id = ?"; Query query = entityM ...