Vim自定义配置(二):定义ag搜索操作符

通过自定义ag操作符实现快速搜索

vim内置的grep命令会搜索一个正则表达式,并将结果输出到quickfix。

我目前使用的插件里有leaderf,这个插件的使用方式比较像quickfix,不过支持的是文件搜索(mru,buffer等)、函数列表、tag搜索。要在源代码里跳转的话,我设置了gtags和global,通过vim的cscope支持可以实现高效跳转。不过有些时候用不到tags,或者我想使用正则表达式在全代码库里搜索的情况是用不了tags的。这个时候还是需要grep的帮忙,当然,我们把grep换成了ag。

版本一

首先是改一些设定(vim的quickfix只有cscope、make、grep和cfile能写入,其实挺有局限的,否则我更倾向于调用外部的ag,毕竟ag和grep的参数又不完全兼容)

1
2
" program used when running :grep
set grepprg=ag\ --vimgrep

接着可以按照老套路写一个vim操作符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
" grep operator
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    let saved_unnamed_register = @@

    if a:type ==# 'v'
        normal! `<v`>y
    elseif a:type ==# 'char'
        normal! `[v`]y
    else
        "ignore multiline mode,just because it's not useful
        return
    endif

    silent! execute "grep! " . shellescape(@@)

    copen
    let @@ = saved_unnamed_register
endfunction

很简单的代码,应该不难懂。不过功能还是不太完善,基本上我用到ag的情况,都是要对整个代码库进行搜索,但是这只对当前目录进行了搜索,所以需要一个向上查找根目录的脚本

版本二

一个简单的bash脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#! /bin/bash
# a small script to find root
# the root is defind as .git or .svn or .root
# by linusboyle

found="false"

ls -1a |egrep "^(.root|.git|.svn)+$" >/dev/null 2>&1 && found="true"

while [ $found == "false" ] && [ $(pwd) != "/" ]
do
    cd ..
    ls -1a |egrep "^(.root|.git|.svn)+$" >/dev/null 2>&1 && found="true"
done

if [ $found == "true" ];then
    #absolute path
    pwd
else
    echo "not found"
fi

为什么不用vimscript写?因为我不会……我所能想到的只有通过不断调用cd和ls和egrep,或者用find?那这样不如一次性解决,性能可能还要好些。

然后我们修改这个操作符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
" grep operator
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    if executable('find_root')
        let saved_unnamed_register = @@
        let project_root=systemlist('find_root')[0]

        if a:type ==# 'v'
            normal! `<v`>y
        elseif a:type ==# 'char'
            normal! `[v`]y
        else
            "ignore multiline mode,just because it's not useful
            return
        endif

        if l:project_root ==# 'not found'
            silent! execute "grep! " . shellescape(@@)
        else
            silent! execute "grep! " . shellescape(@@) . " ". project_root
        endif

        copen
        let @@ = saved_unnamed_register
    else
        echo "missing find_root script,u reinstall the system again?"
        return 
    endif
endfunction

那句很皮的话是用来提醒我这个记性差的人的,忽略之:)

使用systemlist而不是system是因为后者会输出一个空行,既然我已经知道脚本一定会有标准输出,那么用列表的方式就是安全的,直接取数组首位得到没有空行的字符串。

看上去没什么问题,实战用起来也不错,不过还是有一些瑕疵,现在的搜索是基于vim的pwd,而不是文件所在位置,像我这种人经常在vim里多开项目。这也简单,修改脚本,接受一个起始目录即可

版本三

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#! /bin/bash
# a small script to find root
# the root is defind as .git or .svn or .root
# by linusboyle

if [ $# == 1 ];then
    if test -f $1;then
        base_dir=$(dirname $1)
    elif test -d $1;then
        base_dir=$1
    else
        exit 1
    fi
else
    base_dir=$PWD
fi

cd $base_dir

found="false"

function test_root(){
    ls -1a |egrep "^(.root|.git|.svn)+$" >/dev/null 2>&1 && found="true"
}

test_root

while [ $found == "false" ] && [ $PWD != "/" ]
do
    cd ..
    test_root
done

if [ $found == "true" ];then
    #absolute path
    pwd
else
    echo "not found"
fi

最终版:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
" grep operator
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    if executable('find_root')
        let saved_unnamed_register = @@
        let project_root=systemlist('find_root '.expand('%'))[0]

        if a:type ==# 'v'
            normal! `<v`>y
        elseif a:type ==# 'char'
            normal! `[v`]y
        else
            "ignore multiline mode,just because it's not useful
            return
        endif

        if l:project_root ==# 'not found'
            "search in current dir
            silent! execute "grep! " . shellescape(@@)
        else
            "else search in root dir
            silent! execute "grep! " . shellescape(@@) . " ". project_root
        endif

        copen
        let @@ = saved_unnamed_register
    else
        echo "missing find_root script,u reinstall the system again?"
        return 
    endif
endfunction

版本四:纯vimscrippt

ok,我总算知道怎么用vimscript找项目根目录了,现在替换成纯vimscript写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function! Find_project_root()

    let l:path = simplify(expand("%:p:h"))
    let l:previous_path = ""
    let l:markers = ['.root','.git','.svn']

    while l:path != l:previous_path
        for root in l:markers
            if !empty(globpath(l:path, root, 1))
                let l:proj_dir = simplify(fnamemodify(l:path, ':p'))
                if l:proj_dir == '/'
                    return ""
                endif
                return l:proj_dir
            endif
        endfor
        let l:previous_path = l:path
        let l:path = fnamemodify(l:path, ':h')
    endwhile
    return ""
endfunction

" grep operator
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>

function! GrepOperator(type)
    let saved_unnamed_register = @@
    let project_root=Find_project_root()

    if a:type ==# 'v'
        normal! `<v`>y
    elseif a:type ==# 'char'
        normal! `[v`]y
    else
        "ignore multiline mode,just because it's not useful
        return
    endif

    if empty(l:project_root)
        "search in current dir
        silent! execute "grep! " . shellescape(@@)
    else
        "else search in root dir
        silent! execute "grep! " . shellescape(@@) . " ". project_root
    endif

    let @@ = saved_unnamed_register
endfunction
发表于 2018-07-22
JS
Arrow Up