上一篇

CH31 Basic Regular Expressions

  1. /?,前者向后找,后者向前找

CH32 Case Study: Grep Operator, Part One

  1. :nnoremap <leader>g :grep -R <cWORD> .<cr>可以搜索<cWORD>,表示光标下的单词(包括连字符,比<cword>更大),之后可以用:cwindow查看quickfix窗口
  2. 以上还有一点要修改,如果光标在一个foo;ls下,使用后实际会执行ls命令,原理和SQL注入类似,所以需要用单引号保证字面值,:nnoremap <leader>g :grep -R '<cWORD>' .<cr>
  3. 但上面对于光标有单引号的不启作用,用:echom shellescape(expand("<cWORD>"))可以显示shellescape后的值
  4. 最终版本:nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr> ,前面加个:silent可以防止搜索时的输出,最后再:copen打开结果页

CH33 Case Study: Grep Operator, Part Two

进入.vim/plugin/下,新建一个文件”grep-operator.vim”,写上nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@function! GrepOperator(type) | echom "Test" | endfunction,这时用<leader>giw会发现打出了Test

  1. 这里函数名后面有个g@,表示这是个operatorfunc,就是说后面可以跟动作,比如iw表示光标所在的单词,i(表示光标所在的圆括号,g@后可以跟line,char,block表示动作类型,这个也体现在上面函数的参数type
  2. 再给visual模式加个命令,加上vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>,解释下这个<c-u>,和shell中的<c-u>一样删除光标前内容直到行首,如果进入选择模式后,这时按:,会发现命令行自带了:'<,'>,这时如果按<c-u>就会“删掉从光标位置到开头的内容”,只剩:,正是我们想要的,这里的参数visualmode()是表示进的哪种visual模式,比如字节型就是v,行选择就是V,块选择就是<c-v>
  3. 上面把函数体改成echom a:type后,会有几种情况,viw<leader>g显示vvjj<leader>g显示V<leader>gviw显示char<leader>gG显示line

CH34 Case Study: Grep Operator, Part Three

  1. 一个可用的思路是,将选中的内容复制到临时寄存器中,但记得最后还原,如
     let saved_unnamed_register = @@
     # do something
     let @@ = saved_unnamed_register
    
  2. 当你真正要写一个vim脚本时,最好使用命名空间,之前的函数改为nnoremap <leader>g :set operatorfunc=<SID>GrepOperator<cr>g@,这时函数名前加了<SID>,同时,函数定义改为function! s:GrepOperator(type)
  3. <SID>应该相当于是Script ID,加上这个会在执行的时候替换为<SNR>12_func这种形式,另外在脚本内部使用时,前面加s:即可,但如果是一个map外部调用,应该使用<SID>

CH35 Lists

  1. vim的list下标是从0开始,用-1表示最后一个元素,-2表示倒数第二个
  2. 使用l[0:2]表示切片,这里注意是表示从第0个到第2个元素(闭区间),支持[-2:-1]写法,如果下界大于上界则显示空list
  3. 字符串也可以进行list的操作,但要注意"abcd"[-2]这种只用一个负数的操作会显示为空字符串,而用"abcd"[-2:]才能显示为”cd”
  4. add(['a'], 'b')b插入末尾,注意这些命令在normal模式下用要写成:call add(xxx)
  5. extend(list, [1,2])将两个list合成一个
  6. len(list)求长度
  7. get(list, 0, 'default')取list[0],如果不存在则返回”default”
  8. index(list, 'a')返回a在list中第一次出现的位置,没有返回-1
  9. join(list, glue)类似php的implode,将list用glue连接成字符串
  10. reverse(list)就地倒排list
  11. unlet list[3:]删除list的3到最后
  12. :let l = remove(list, 3, -1)返回删完的list
  13. filter(list, 'v:val !~ "x"')移除元素中带x的,后面表达式为真将被留下
  14. list用+号连接,也可以+=
  15. 直接写b = a相当于b是a的引用,对a操作同时也会作用于b,复制可以用b = copy(a),或者b = a[:],但是上面这个只对一维有效,如果a[0][1] = 'aa',这时会发现b[0]也变了,深度复制用b = deepcopy(a)(最多能复制100层)
  16. :echo a is b来判断是不是同一个list,而a == b是从值上比较的,有个例外是,:echo 4 == "4"显示是1,而:echo [4] == ["4"]显示是0
  17. 支持分别赋值,如:let [var1, var2] = list,如果只需要前几个值,可以这样:let [var1, var2; rest] = mylist,这里rest一定要有,否则会报错
  18. 使用for item in mylist进行循环,如果是多层,则用for [lnum, col] in [[1, 3], [2, 8], [3, 0]],后面的list必须每项个数一致

CH36 Looping

  1. for循环像这样
     :for i in [1,2,3,4]
     :  let c += i
     :endfor
    
  2. while循环像这样
     :while c <= 4
     :  let total += c
     :  let c += 1
     :endwhile
    
  3. 有点要注意的是,当for i in mylist时,在对当前元素执行命令前,vim会保存下一个的引用,所以这时即使删除mylist的当前元素也是OK的,但删除之后的元素会导致vim找不到它

CH37 Dictionaries

  1. :echo {'a': 1, 100: 'foo',}这是定义一个字典,同时最好保留后面的逗号,防止在多行时出错
  2. vim的字典只能是字符串,写成数字也会被转成字符串
  3. 还可以用:echo {}.a的形式
  4. remove(dict, 'a')unlet(dict.b)来删除元素,十分推荐用remove,因为它的作用比unlet
  5. 可以用:for key in keys(mydict)来循环得到key值,同理,:for v in values(mydict)循环value,还可以:for [key, value] in items(mydict)两个都循环
  6. 可以用has_key()验证一个key是不是在字典中
  7. 字典的函数和上面list的函数类似

CH38 Toggling

  1. 之前学过用:setlocal nu!是可以开头某个布尔值的,下面来看下对于非布尔怎么做
  2. 比如可以检测值满足某个条件A时,设为x,满足条件B时,设为y这样,例如
     nnoremap <leader>f :call FoldColumnToggle()<cr>
     function! FoldColumnToggle()
         if &foldcolumn
             setlocal foldcolumn=0
         else
             setlocal foldcolumn=4
         endif
     endfunction
    
  3. 或者打开或关闭某个窗口,如:copen:cclose,这时可以用全局变量标识
     nnoremap <leader>q :call QuickfixToggle()<cr>
     function! QuickfixToggle()
         if g:quickfix_is_open
             cclose
             let g:quickfix_is_open = 0
         else
             copen
             let g:quickfix_is_open = 1
         endif
     endfunction
    

    这里只有一点不太完美,如果用户手动打开了窗口,则全局变量是不会更新的,但这里要花大工夫来完成,得不偿失

  4. 这里还有一点是在打开多个文件再使用<leader>q时,如果在quick-fix窗口中使用,则会返回上一个split的窗口,而不是之前copen时所在的窗口,所以要做以下修改,同时这也是个约定俗成的写法
     nnoremap <leader>q :call QuickfixToggle()<cr>
     let g:quickfix_is_open = 0
     function! QuickfixToggle()
         if g:quickfix_is_open
             cclose
             let g:quickfix_is_open = 0
             execute g:quickfix_return_to_window . "wincmd w"
         else
             let g:quickfix_return_to_window = winnr()
             copen
             let g:quickfix_is_open = 1
         endif
     endfunction
    

CH39 Functional Programming

  1. 就是你可以把变量赋成函数,然后变量名就相当于函数名去调用,这里要注意一点是,变量可以不像函数名那样遵守首字母大写的约定,如
     :let funcs = [function("Append"), function("Pop")]
     :echo funcs[1](['a', 'b', 'c'], 1)
    
  2. 再来感受一个类似py中的map的例子
     function! Mapped(fn, l)
         let new_list = deepcopy(a:l)
         call map(new_list, string(a:fn) . '(v:val)')
         return new_list
     endfunction
    

    相当于对l数组中的所有元素过一遍函数fn

CH40 Paths

  1. 为啥这章和上章函数式编程没有一点关系了……
  2. 获取绝对路径,:echom expand('%:p'):echom fnamemodify('foo.txt', ':p'),但第二个必须手写foo.txt(可以是个不存在的文件)
  3. 列出当前文件夹下的文件:echo globpath('.', '*'),还可以递归地显示文件:echo split(globpath('.', '**'), '\n'),递归地查找某个文件:echo split(globpath('.', '**/*.py'), '\n')
  4. expand(expr[, nosuf[, list]]),expr可以以
    • % 当前文件
    • # 另一个文件
    • #n 另n个文件

    后面还可以接修饰符

    • :p 扩展成全路径
    • :h 会把最后一部分去掉(这个可以接在一些修饰符后面,比如’:p:h’就相当于全路径的文件夹名,如果只用’:h’可能只显示’.’)
    • :t 只保留路径最后一部分
    • :r 移除扩展名的全路径
    • :e 文件扩展名
  5. simplify(filename) 尽可能简化路径,会把.././/这种简化掉,快捷方式或链接不解析
  6. resolve(filename) 可以解析快捷方式或链接,并返回简化后的全路径
  7. 关于通配符,可以有?,*,**,[abc],这里注意在win下比较蛋疼,如果要实际匹配字面值path[abc]path\[abc]会认为反斜杠是目录分隔符,避免这样,可以写成path\[[]abc]

看到这你一定觉得这破文章可算完了,其实你错了,如果看了原文你会发现居然还有10+章,所以预告下期会是一个番外,可以说是一次 vim script 的最佳实践,是时候检验学习成果了!