LeaderF icon indicating copy to clipboard operation
LeaderF copied to clipboard

使用vimL来写扩展

Open skywind3000 opened this issue 6 years ago • 71 comments

新命令:

:LeaderfAny[!] 

可以提供一个 list 列表,和一个 callback 函数,用 Leaderf 的搜索方式提供搜索,关闭 leaderf 窗口后,调用一下用户提供的回调,告知选择结果,null 代表用户没做选择。

如果还能提供个额外的语法文件支持就太好了啊。

skywind3000 avatar Apr 30 '18 06:04 skywind3000

哎,有点小忙,我对这个插件也只是fix一些bug,好久没有增加新的feature了。

我也一直想实现这个功能,这个功能就类似于fzf了,可以自己随便自定义要搜索的内容。 要不然还是你帮忙来实现,有问题我们一起讨论?

Yggdroot avatar Apr 30 '18 09:04 Yggdroot

好吧,我先研究下 denite 去,先把命令接口讨论清楚了,我再照着 LeaderfFunction 改改。 让用户提供 source,preview function 和一个 callback 即可。

skywind3000 avatar Apr 30 '18 14:04 skywind3000

写好了 ??

skywind3000 avatar May 21 '18 13:05 skywind3000

以下内容已经过时,请参考 https://github.com/Yggdroot/LeaderF/issues/144#issuecomment-540008950

功能大致实现了,还有些细节问题。 在any branch。

let g:Lf_Extensions = {
    \ "apple": {
    \       "source": [], "grep -r '%s' *", funcref (...) 
    \       "format": funcref ([], ...),
    \       "accept": funcref (line, ...),
    \       "options": [],
    \       "preview": funcref,
    \       "supports_name_only": 0,
    \       "get_digest": funcref,
    \       "before_enter": funcref (...),
    \       "after_enter": funcref (orig_buf_nr, orig_cursor, ...),
    \       "bang_enter": funcref (orig_buf_nr, orig_cursor, ...),
    \       "before_exit": funcref (orig_buf_nr, orig_cursor, ...),
    \       "after_exit": funcref (...),
    \       "highlights_def": {
    \               "Lf_hl_apple": '^\s*\zs\d\+',
    \               "Lf_hl_appleId": '\d\+$',
    \       },
    \       "highlights_cmd": [
    \               "hi Lf_hl_apple guifg=red",
    \               "hi Lf_hl_appleId guifg=green",
    \       ],
    \       "supports_multi": 0,
    \       "supports_refine": 0,
    \ },
    \ "orange": {}
\}

字典中只有sourceaccept是mandatory的, 其他都是optional的。

  1. source 可以是个list,也可以是个字符串,如果是字符串就是个命令,还可以是个Funcref。如果是Funcref,函数签名是不定参数(...),会接受Leaderf test --a --b --c命令中的“--a”, "--b", "--c", 可以根据参数自来决定不同的返回值,返回类型是个list
  2. format是个Funcref,作用是对source的结果进行格式化,比如source是个ctags命令, 但是想对ctags的输出进行调整,就用format函数修改一下,返回值是个list。format函数的签名是([], ...), list是source的返回值,...跟source中一样。
  3. accept是个Funcref,签名是 (line, ...),是响应回调,...同上,也许会根据某个参数做不同的响应。
  4. options指定可以有哪些选项,补全时可以用到,是个list。
  5. preview是个Funcref,还没实现,具体没想好。
  6. supports_name_only是bool类型,如果搜索内容是类似文件路径这样的,就跟LeaderfFile一样。
  7. get_digest,是个Funcref,签名是(line, mode),返回值类型是个list,[不同模式下要匹配的字符串,对应的起始位置(in bytes)]
        specify what part in the line to be processed and highlighted
        Args:
            mode:
                  0, return the full path和full path的起始位置
                  1, return the name only和name only的起始位置
                  2, return the directory name和directory name的起始位置
  1. before_enter, after_enter, before_exit, after_exit 分别是进入LeaderF窗口前,进入后,退出前,退出后的回调,签名在上面。
  2. bang_enter是在bang模式下刚进入时的回调,签名是(orig_buf_nr, orig_cursor, ...),orig_buf_nr是原来buffer的number,orig_cursor是原来buffer光标的位置[行,列],行列都是从1开始。...同上。它的作用就像你上次实现的_relocateCursor
  3. highlights_defhighlights_cmd可以定义高亮。Note, pattern部分最好用单引号引起来,不然反斜杠要用两个。
  4. supports_multi是bool类型,指定是否支持多选,缺省不支持。比如当前LeaderfFile就支持多选,shift+鼠标可以选择多行。
  5. supports_refine是bool类型,指定是否支持refine,缺省不支持。refine就是LeaderfFile在nameOnly模式下,有多个同名的,输入分号,然后再匹配路径。

目前主要功能已经差不多了,已经支持选项"--top", "--bottom", "--left", "--right", "--belowright", "--aboveleft", "--fullScreen"--cword(当前光标下的单词直接作为模式字符串进行匹配), 例如: Leaderf test --right --cword 这是我的测试例子:

function! Source(...)
    return ["aaabcaabcabc 23333333333", "bbaabbccdbb 345555533333", "caaaabbbbcccc 453333333"]
endfunction
function! Get_digest(line, mode)
    if a:mode == 0
        return [a:line, 0]
    elseif a:mode == 1
        return [split(a:line)[0], 0]
    else
        return [split(a:line)[1], len(a:line) - len(split(a:line)[1])]
    endif
endfunction
function! Accept(line, ...)
    exec "edit ".split(a:line)[1]
endfunction
function! Bang(orig_buf_nr, orig_cursor, ...)
    exec "norm! 2G"
endfunction
let g:Lf_Extensions = {
            \ "apple": {
            \       "source": function("Source"),
            \       "supports_name_only": 1,
            \       "get_digest": function("Get_digest"),
            \       "accept": function("Accept"),
            \       "highlights_def": {
            \               "Lf_hl_apple": '\w\+',
            \               "Lf_hl_appleId": '\d\+',
            \       },
            \       "highlights_cmd": [
            \               "hi Lf_hl_apple guifg=red",
            \               "hi Lf_hl_appleId guifg=green",
            \       ],
            \       "bang_enter": function("Bang"),
            \       "supports_multi": 1,
            \       "supports_refine": 0,
            \ },
            \ "orange": {
            \       "source": "git ls-files",
            \       "supports_name_only": 0,
            \       "highlights_def": {
            \               "Lf_hl_bufNumber": "^\s*\zs\d\+",
            \               "Lf_hl_buf": "^\s*\zs\d\+",
            \       },
            \       "highlights_cmd": [
            \               "hi Lf_hl_bufNumber guifg=red",
            \               "hi Lf_hl_buf guifg=green",
            \       ],
            \       "supports_multi": 0,
            \       "supports_refine": 0,
            \ },
            \}

可以使用Leaderf apple --right

你看看还有什么需要注意和改进的地方。

Yggdroot avatar May 21 '18 15:05 Yggdroot

我擦,帅啊,我明天看看,初看感觉可定制度快赶上 denite 了。

skywind3000 avatar May 21 '18 15:05 skywind3000

看了一下,功能上我好像没什么补充了,补充点式样方面,如果每行内容采用 tab 分隔的话,可以被识别成不同的语法高亮,定义成:

LfColorColumn1
LfColorColumn2
LfColorColumn3
LfColorColumn4

之类,可以修改的,然后能否有一个对齐选项,就是列表里所有 tab 分隔的记录可以统计长度,进行对齐,比如 LeaderfBufTag 搜索窗口中,如果对齐的话,会好看很多,也更容易识别。

skywind3000 avatar May 23 '18 11:05 skywind3000

这个好像不太好做吧,因为source的内容不确定,不一定是分成几块的。而且直接采用tab分隔,不一定能对齐。像LeaderfBuffer,LeaderfBufTag, 我都是做了计算的,跟名字最长的那个对齐。 语法高亮可以在highlights_def里自己定义,也很方便。

Yggdroot avatar May 23 '18 12:05 Yggdroot

BufTag 经过对齐了?为啥我经常发现没对齐,比如:

align

对齐 bug ?

skywind3000 avatar May 23 '18 12:05 skywind3000

这是做了点妥协,并不是跟最长的对齐,而是平均长度的1.5倍,防止因为某一个特别的长,导致中间有很大空白,这样保证大部分是对齐的。当然还可以是平均长度的2倍或更多。

Yggdroot avatar May 23 '18 12:05 Yggdroot

其实如果宽度够,能不能不管这个妥协?现在都是 16:9的LCD了,你看我右边空那么多。

skywind3000 avatar May 23 '18 12:05 skywind3000

你有四列,如果每列最长没超过窗口宽度四分之一就不用1.5了,直接最长对齐吧。

skywind3000 avatar May 23 '18 12:05 skywind3000

这是因为是当前buffer,就没有显示文件名。如果是所有buffer,会显示文件名,有时候文件名加上路径会很长。

Yggdroot avatar May 23 '18 12:05 Yggdroot

还可以2/5,1/5,1/5,1/5 比例分配

skywind3000 avatar May 23 '18 12:05 skywind3000

或者最长长度大于平均长度的2倍就用平均长度的2倍,小于就用最长长度。我还是不打算简单的使用最长长度,因为我们公司的代码,有时候就有某个函数名特别的长。 貌似有些跑题了。

Yggdroot avatar May 23 '18 12:05 Yggdroot

对,这个方法也很好。这样就看起来比较大气了,恩回过头来吧,我貌似没什么补充了,可能要实际用一下才知道。

skywind3000 avatar May 23 '18 12:05 skywind3000

现在功能基本实现了,我在考虑怎么支持使用Python写。

Yggdroot avatar May 23 '18 12:05 Yggdroot

改好了(https://github.com/Yggdroot/LeaderF/commit/66fbab1b39a79bdc5e6f06d17072e5c661a3b233 ),你更新下。

Yggdroot avatar May 23 '18 13:05 Yggdroot

帅啊,thanks

skywind3000 avatar May 23 '18 14:05 skywind3000

我已经merge到master了,有空补补文档。

Yggdroot avatar May 26 '18 02:05 Yggdroot

帅啊,怎么用它写个

:LeaderfAny grep printf

skywind3000 avatar May 26 '18 13:05 skywind3000

你一下子就问到点子上,现在还不支持参数,本来想实现source可以是这样的"grep -r '%s' *",还没实现,还需要加点代码。 我在想怎么来支持呢?

Yggdroot avatar May 26 '18 13:05 Yggdroot

以下内容已经过时,请参考 https://github.com/Yggdroot/LeaderF/issues/144#issuecomment-540008950

Yggdroot avatar Jun 12 '18 06:06 Yggdroot

辛苦了,周末我研究一下。

skywind3000 avatar Jun 15 '18 15:06 skywind3000

你好反馈一个问题:

macvim的python2使能

vim的python3使能

这两个使用同一个配置以及插件目录,leaderf的其它命令都正常

但是我添加了一个扩展,在macvim下是正常的,在vim下面却出现了如下错误信息:

 searching ...
Error detected while processing function te#tools#vim_get_message[3]..leaderf#Any#start[4]..leaderf#LfPy:
line    1:
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/tracyone/.vim/bundle/LeaderF/autoload/leaderf/python/leaderf/anyExpl.py", line 499, in start
    the_args.start(arguments, *args, **kwargs)
  File "/Users/tracyone/.vim/bundle/LeaderF/autoload/leaderf/python/leaderf/anyExpl.py", line 466, in _default_action
    manager.startExplorer(win_pos[2:], *args, **kwargs)
  File "/Users/tracyone/.vim/bundle/LeaderF/autoload/leaderf/python/leaderf/anyExpl.py", line 312, in startExplorer
    super(AnyExplManager, self).startExplorer(win_pos, *args, **kwargs)
  File "/Users/tracyone/.vim/bundle/LeaderF/autoload/leaderf/python/leaderf/manager.py", line 787, in startExplorer
    if len(content[0]) == len(content[0].rstrip("\r\n")):
TypeError: a bytes-like object is required, not 'str'


Error detected while processing function youcompleteme#Enable[7]..<SNR>142_SetUpOptions[4]..<SNR>142_SetUpKeyMappings:
line   59:
E227: mapping already exists for 
let g:Lf_Extensions = {
			\ "dir": {
			\       "source": function("te#leaderf#dir#source"),
			\       "accept": function("te#leaderf#dir#accept"),
			\       "supports_name_only": 1,
			\       "supports_multi": 0,
			\ },
			\}

tracyone avatar Sep 02 '18 14:09 tracyone

@tracyone 你试一下,在dev branch,看看有没有fix。另外那个map冲突的问题应该跟LeaderF无关。

Yggdroot avatar Sep 03 '18 02:09 Yggdroot

@Yggdroot 第一次打开的时候没错误了,可是筛选的时候出现了以下错误,同样配置macvim不会出现。

screen shot 2018-09-03 at 20 00 18

tracyone avatar Sep 03 '18 12:09 tracyone

@tracyone 这是python3上的问题,单独开一个issue讨论吧。 你能给我你的扩展所有代码吗?这样我可以方便调试。只根据你图上的错误信息不太容易查。

Yggdroot avatar Sep 03 '18 12:09 Yggdroot

hi,我想问下,accept的时候如何得知当前用户的动作呢?是按回车还是准备标签页打开呢?

可能,我描述的不清楚:

就是通常在浏览文件的时候,按回车是直接当前buffer打开,而按ctrl-t的时候标签页打开。

tracyone avatar Sep 05 '18 12:09 tracyone

目前没法知道。除非修改代码。

Yggdroot avatar Sep 05 '18 12:09 Yggdroot

我最近也再给leaderf定义一些自定义source,遇到一个问题,比如我需要指定输入,目前仅支持 --cword ,我希望可以更加智能一点比如 --input=hello 起到一个类似于打开leaderf后并默认输入hello的效果,并且这个hello在leaderf打开后是可以修改的。比如启动后按下 <BS> 他就变成 hell

wsdjeg avatar Sep 06 '18 01:09 wsdjeg

@wsdjeg 已经支持了,readme里有说。

Yggdroot avatar Sep 06 '18 02:09 Yggdroot

neovim中不支持这样来写扩展,因为neovim不再支持vim.bindeval,我不知道neovim怎么来使用Python获取vimscript写的Funcref,谁如果知道,望告知。

Yggdroot avatar Sep 06 '18 06:09 Yggdroot

补个需求,搜出来的结果不自动换行要如何处理?另外感觉速度比 vim-grepper 慢好多。

hanxi avatar Dec 06 '18 10:12 hanxi

补个需求,搜出来的结果不自动换行要如何处理?另外感觉速度比 vim-grepper 慢好多。

不自动换行是什么意思? 上面只是个demo,离实用还有点距离。你可以使用:Leaderf rg, 然后跟vim-grepper 比比。

Yggdroot avatar Dec 06 '18 12:12 Yggdroot

@Yggdroot 上面的是用 :Leader grep Build搜出来的,搜索结果自动换行了。下面的是用 vim-grepper 搜出来的,结果一行超出屏幕宽度没有自动换行。 image

image

hanxi avatar Dec 06 '18 13:12 hanxi

@hanxi 这是因为set wrap了,就是为了看整行的,如果不这样就要拉滚动条了。

Yggdroot avatar Dec 06 '18 14:12 Yggdroot

@Yggdroot 不喜欢 set wrap ,因为我这工程目录下有一个自动生成的协议文件一行代码很长很长,一搜到它就占用了四五行。

你这个支持 fuzzy 再次搜索匹配的结果很好用,如果以后这个 grep 功能完善了,我就可以把 vim-grepper 扔了。

hanxi avatar Dec 06 '18 15:12 hanxi

@Yggdroot 不喜欢 set wrap ,因为我这工程目录下有一个自动生成的协议文件一行代码很长很长,一搜到它就占用了四五行。

增加了一个--nowrap选项,在命令里加上它就可以了。

你这个支持 fuzzy 再次搜索匹配的结果很好用,如果以后这个 grep 功能完善了,我就可以把 vim-grepper 扔了。

已经有Leaderf rg了呀。

Yggdroot avatar Dec 07 '18 02:12 Yggdroot

@Yggdroot 抱歉,昨天没看明白 Leaderf rg 是啥意思,今天看了 README 才发现有这么个命令。赞一个!

hanxi avatar Dec 07 '18 04:12 hanxi

Leaderf rg 太好用了,按 tab 或 i 对搜索结果进行二次搜索。

hanxi avatar Dec 07 '18 06:12 hanxi

neovim 还是不支持这个功能么?我刚刚在mac里的neovim试了试 得到下面这个错误

function! Data()
	return ['a', 'b', 'c', 'd']
endfunction

function! AcceptEdit(line, ...)
	echom "start"
	echom line
endfunction

let g:Lf_Extensions = {
    \ "apple": {
    \       "source": function("Data")(),
    \       "accept": function("AcceptEdit")
    \ }
\}

去掉function("Data")后面的括号也不行

错误如下 screenshot_2019-01-22 16 21 38_bocpi5

目前主要的搜索都已经转移到Leaderf 不能加扩展是我还保留Denite的唯一原因了。。。惆怅啊

yongwangd avatar Jan 22 '19 21:01 yongwangd

@yongwangd 写扩展可以用Python写呀,可以参考 https://github.com/Yggdroot/LeaderF-marks 。

Yggdroot avatar Jan 23 '19 02:01 Yggdroot

@Yggdroot 請問Leaderf rg 可以加入--heading的功能嗎?

thlineric avatar Mar 04 '19 11:03 thlineric

@thlineric 加了就没法模糊匹配了,模糊匹配后跳转需要知道文件信息才能跳过去。

Yggdroot avatar Mar 04 '19 12:03 Yggdroot

@thlineric 加了就没法模糊匹配了,模糊匹配后跳转需要知道文件信息才能跳过去。

@Yggdroot 我之前有嘗試把python的--no-heading拿掉...結果就是無法跳過去Orz

thlineric avatar Mar 04 '19 12:03 thlineric

@Yggdroot 前面提到的bufTag对齐问题。我希望添加个配置来控制是否要尽量对齐。我调试了下,下面是我修改的接口。如果可以的话,希望能合到master。 我个人是希望全部对齐的,可能有些人不喜欢这样(前面提到的有些函数名过长)。因此加个配置比较友好。

    def _formatResult(self, buffer, result):
        if not buffer.name or lfEval("bufloaded(%d)" % buffer.number) == '0':
            return []

        # a list of [tag, file, line, kind, scope]
        output = [line.split('\t') for line in result if line is not None]
        if not output:
            return []

        if len(output[0]) < 4:
            lfCmd("echoerr '%s'" % escQuote(str(output[0])))
            return []

        tag_total_len = 0
        max_kind_len = 0
        max_tag_len = 0
        max_scope_len = 0
        for _, item  in enumerate(output):
            tag_len = len(item[0])
            tag_total_len += tag_len
            if tag_len > max_tag_len:
                max_tag_len = tag_len
            kind_len = len(item[3])
            if kind_len > max_kind_len:
                max_kind_len = kind_len

            scope_len = len(item[4] if len(item) > 4 else "Global")
            if max_scope_len < scope_len:
                max_scope_len = scope_len

        ave_taglen = tag_total_len // len(output)
        tag_len = min(max_tag_len, ave_taglen * 2)

        tab_len = buffer.options["shiftwidth"]
        std_tag_kind_len = tag_len // tab_len * tab_len + tab_len + max_kind_len

        tag_list = []
        almost_align = int(lfEval("g:Lf_AlmostAlign"))
        for _, item  in enumerate(output):
            scope = item[4] if len(item) > 4 else "Global"
            bufname = buffer.name if vim.options["autochdir"] else lfRelpath(buffer.name)
            if almost_align == 0:
                line = "{:<{l1}s}\t{:<{l2}s}\t{:<{l3}s}\t{}:{}\t{}".format(item[0],
                                                                           item[3],
                                                                           scope,
                                                                           bufname,        # file
                                                                           item[2][:-2],   # line
                                                                           buffer.number,
                                                                           l1=max_tag_len,
                                                                           l2=max_kind_len,
                                                                           l3=max_scope_len)
            else:
                tag_kind = "{:{taglen}s}\t{}".format(item[0],   # tag
                                                     item[3],   # kind
                                                     taglen=tag_len
                                                     )
                tag_kind_len = int(lfEval("strdisplaywidth('%s')" % escQuote(tag_kind)))
                num = std_tag_kind_len - tag_kind_len
                space_num = num if num > 0 else 0
                line = "{}{}\t{}\t{:2s}{}:{}\t{}".format(tag_kind,
                                                         ' ' * space_num,
                                                         scope,          # scope
                                                         ' ',
                                                         bufname,        # file
                                                         item[2][:-2],   # line
                                                         buffer.number
                                                         )

            tag_list.append(line)
            if self._supports_preview:
                # code = "{:{taglen}s}\t{}".format(' ' * len(item[0]),
                #                                  buffer[int(item[2][:-2]) - 1].lstrip(),
                #                                  taglen=tag_len
                #                                  )
                code = "\t\t{}".format(buffer[int(item[2][:-2]) - 1].lstrip())
                tag_list.append(code)

        self._tag_list[buffer.number] = tag_list

        return tag_list

liubaohai avatar Jul 13 '19 17:07 liubaohai

又做了一下更新,可以同时给vim和neovim写扩展了。@wsdjeg

现在应该还没有人用vimL给LeaderF写扩展,所以做了些改变,跟前面不兼容了:原来每个回调函数是个Funcref对象,现在改为函数名了。

let g:Lf_Extensions = {
    \ "apple": {
    \       "source": [], "grep -r '%s' *", funcref (arguments), {"command": "ls" or funcref(arguments)}
    \       "arguments": [
    \           { "name": ["--foo", "-f"], "nargs": n or "?" or "*" or "+", help: "hehe"},
    \           { "name": ["bar"], "nargs": n or "?" or "*" or "+" }
    \       ],
    \       "format_line": funcref (line, arguments),
    \       "format_list": funcref ([], arguments),
    \       "accept": funcref (line, arguments),
    \       "preview": funcref (orig_buf_nr, orig_cursor, line, arguments),
    \       "supports_name_only": 0,
    \       "get_digest": funcref (line, mode),
    \       "before_enter": funcref (arguments),
    \       "after_enter": funcref (orig_buf_nr, orig_cursor, arguments),
    \       "bang_enter": funcref (orig_buf_nr, orig_cursor, arguments),
    \       "before_exit": funcref (orig_buf_nr, orig_cursor, arguments),
    \       "after_exit": funcref (arguments),
    \       "highlights_def": {
    \               "Lf_hl_apple": '^\s*\zs\d\+',
    \               "Lf_hl_appleId": '\d\+$',
    \       },
    \       "highlights_cmd": [
    \               "hi Lf_hl_apple guifg=red",
    \               "hi Lf_hl_appleId guifg=green",
    \       ],
    \       "highlight": funcref (arguments),
    \       "need_exit": funcref (line, arguments),
    \       "supports_multi": 0,
    \       "supports_refine": 0,
    \ },
    \ "orange": {}
\}

字典中只有source和accept是mandatory的, 其他都是optional的。

  1. source 可以是以下几种类型
  • list 例如:["aaa", "bbbb", "cccc"]
  • string 是函数名,签名是(arguments)arguments是个字典,会在下面详细说明。 返回值是个list
  • dict({"command": string or Funcref}) dict只有一个元素, "command"的值可以是个string,例如:git ls-files;如果想传参数给这个命令,在命令里使用"%s", 例如:grep -r '%s' %s,如果命令中有“%s", 下面说的位置参数必须与"%s"相对应(个数,顺序);也可以是个Funcref,签名是(arguments),返回值是个命令字符串。
  1. arguments用来指定命令的参数,参数分为两种类型:
  • 位置参数 前面不带--或者-的是位置参数,位置参数在命令行中一般是必填项(当然也可以是可选),如果缺失会报错。而且位置参数之间的顺序跟定义时要一致,顺序乱了也会出错。

  • 可选参数 前面带--或者-的是可选参数。可选参数,顾名思义就是optional的。 arguments的格式如下:

    "arguments": [
       { "name": ["--foo", "-f"], "nargs": n or "?" or "*" or "+", "help": "hehe"},
       { "name": ["bar"], "nargs": n or "?" or "*" or "+" },
       [ 
         { "name": ["--big"], "nargs": n or "?" or "*" or "+" },
         { "name": ["--small"], "nargs": n or "?" or "*" or "+" }
       ]
    ]
    

    "name"是个list,可选参数一般可以有长名和短名。"nargs"指定参数的个数,值可以是个整数N(0,1,2 ...),也可以是"?"(0个或1个),"*"(0个或多个),"+"(1个或多个)。不管"nargs"指定的个数是0、1,还是多个,命令行都会把参数放到一个list中,例如:

    "arguments": [
      { "name": ["--foo", "-f"], "nargs": 0, "help": "hehe"},
      { "name": ["--bar"], "nargs": 1, "help": "hehe"},
      { "name": ["--name"], "nargs": "+", "help": "hehe"},
    ]
    

    则在解析命令Leaderf grep --name aaa bbb ccc --foo --bar hello 时,会形成一个这样的dict作为上面提到的函数签名中的arguments

    {
         "--foo":  [],
         "--bar":  ["hello"],
         "--name": ["aaa", "bbb", "ccc"]
    }
    

    "help"是可选的,主要是使用打印帮助时用的,例如使用:Leaderf file -h就可以查看Leaderf file的帮助。 对于一些参数是互斥的,需要再放到一层list中去,例如上面的"--big"和"--small"。

  1. format_line类型是string, 指向一个函数名,函数作用是对source结果中的行进行格式化,签名是(line, arguments),返回格式化后的行。

  2. format_list类型是string, 指向一个函数名,函数作用是对source的结果进行格式化,返回值是个list。函数的签名是([], arguments), list是source的返回值,返回格式化后的list。

  3. accept类型是string, 指向一个函数名,函数作用是对当前选中的行做操作,当按下回车时调用这个函数。函数签名是 (line, arguments),line是当前选中的行,arguments同上,也许会根据某个参数做不同的响应。

  4. preview类型是string, 指向一个函数名,函数签名是(orig_buf_nr, orig_cursor, line, arguments)。orig_buf_nr是原来buffer的number,orig_cursor是原来buffer光标的位置[行,列],行列都是从1开始,line是当前选中的行, arguments同上。返回值可以是[], 表示preview什么都不做,或者返回[filename, line_num, jump_cmd], filename 是预览的文件名,line_num是预览buffer里的行号,jump_cmd 是个vim里的一个命令,用来跳转到具体某一行,比如当获取不到行号时可以用,它可以是空的,如果不空,就用它跳转,否则用行号跳转。

  5. supports_name_only是bool类型,如果搜索内容是类似文件路径这样的,就跟LeaderfFile一样。

  6. get_digest,类型是string, 指向一个函数名,函数签名是(line, mode),返回值类型是个list,[不同模式下要匹配的字符串,对应的起始位置(in bytes)]

     specify what part in the line to be processed and highlighted
         Args:
             mode:
                   0, return the full path和full path的起始位置
                   1, return the name only和name only的起始位置
                   2, return the directory name和directory name的起始位置
    
  7. before_enter, after_enter, before_exit, after_exit 类型是string, 分别是进入LeaderF窗口前,进入后,退出前,退出后的回调,函数签名(orig_buf_nr, orig_cursor, arguments)。

  8. bang_enter类型是string,是在bang模式下刚进入时的回调,签名是(orig_buf_nr, orig_cursor, arguments)。

  9. highlights_defhighlights_cmd可以定义高亮。Note, pattern部分最好用单引号引起来,不然反斜杠要用两个。 highlights_def类型是个dict,highlights_cmd类型是哦list highlight类型是string, 指向一个函数名,函数签名是(arguments),有时需要根据arguments来设置高亮,可以用它。返回一个list,list内容是matchadd()的返回id

  10. need_exit类型是string, 指向一个函数名,函数签名是(line, arguments)。用来指定按回车后,LeaderF窗口是否需要退出,例如,如果当前行是路径,就不退出,return 0,是文件名,就退出 return 1

  11. supports_multi是bool类型,指定是否支持多选,缺省不支持。比如当前LeaderfFile就支持多选,shift+鼠标可以选择多行。

  12. supports_refine是bool类型,指定是否支持refine,缺省不支持。refine就是LeaderfFile在nameOnly模式下,有多个同名的,输入分号,然后再匹配路径。

这是我写的一个demo:

function! Grep(args)
    let ignorecase = ""
    if has_key(a:args, "--ignore-case")
        let ignorecase = "-i"
    endif

    let whole_word = ""
    if has_key(a:args, "-w")
        let whole_word = "-w"
    endif

    return printf("grep -n -I -r %s %s '%s' .", ignorecase, whole_word, get(a:args, "pattern", [""])[0])
endfunction

function! FormatLine(line, args)
    let line = substitute(a:line, '^[^:]*\zs:', '\t|', '')
    let line = substitute(line, '|\d\+\zs:', '|\t', '')
    return line
endfunction

function! Accept(line, args)
    let items = split(a:line, '\t')
    let file = items[0]
    let line = items[1][1:-2]
    exec "edit +".line." ".file
    norm! zz
    setlocal cursorline!
    redraw
    sleep 100m
    setlocal cursorline!
endfunction

function! Preview(orig_buf_nr, orig_cursor, line, args)
    let items = split(a:line, '\t')
    let file = items[0]
    let line_num = items[1][1:-2]
    return [file, line_num, ""]
endfunction

function! Highlight(args)
    highlight Grep_Pattern guifg=Black guibg=lightgreen ctermfg=16 ctermbg=120

    " suppose that pattern is not a regex
    if has_key(a:args, "-w")
        let pattern = '\<' . get(a:args, "pattern", [""])[0] . '\>'
    else
        let pattern = get(a:args, "pattern", [""])[0]
    endif

    if has_key(a:args, "--ignore-case")
        let pattern = '\c' . pattern
    endif

    let ids = []
    call add(ids, matchadd("Grep_Pattern", pattern, 20))
    return ids
endfunction

function! Get_digest(line, mode)
    " full path, i.e, the whole line
    if a:mode == 0
        return [a:line, 0]
    " name only, i.e, the part of file name
    elseif a:mode == 1
        return [split(a:line)[0], 0]
    " directory, i.e, the part of greped line
    else
        let items = split(a:line, '\t')
        return [items[2], len(a:line) - len(items[2])]
    endif
endfunction

function! Do_nothing(orig_buf_nr, orig_cursor, args)
endfunction

let g:Lf_Extensions = {
            \ "grep": {
            \       "source": {"command": function("Grep")},
            \       "arguments": [
            \           { "name": ["pattern"], "nargs": 1 },
            \           { "name": ["--ignore-case", "-i"], "nargs": 0 },
            \           { "name": ["-w"], "nargs": 0, "help": "Select  only  those lines containing matches that form whole words." },
            \       ],
            \       "format_line": "FormatLine",
            \       "accept": "Accept",
            \       "preview": "Preview",
            \       "supports_name_only": 1,
            \       "get_digest": "Get_digest",
            \       "highlights_def": {
            \               "Lf_hl_grep_file": '^.\{-}\ze\t',
            \               "Lf_hl_grep_line": '\t|\zs\d\+\ze|\t',
            \       },
            \       "highlights_cmd": [
            \               "hi Lf_hl_grep_file guifg=red ctermfg=196",
            \               "hi Lf_hl_grep_line guifg=green ctermfg=120",
            \       ],
            \       "highlight": "Highlight",
            \       "bang_enter": "Do_nothing",
            \       "after_enter": "",
            \       "before_exit": "",
            \       "supports_multi": 0,
            \ },
            \}

用法可以: :Leaderf grep -h

NOTE: 上面的函数都是全局函数,如果想用脚本局部函数s:FunctionName,需要这样用: 例如, "source": string(function("s:FunctionName"))[10:-3]

Yggdroot avatar Oct 09 '19 13:10 Yggdroot

neovim下不能使用

Error invoking 'python_execute' on channel 4 (python3-script-host):
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "plugged/LeaderF/autoload/leaderf/python/leaderf/anyExpl.py", line
715, in start
    the_args.start(arguments, *args, **kwargs)
  File "plugged/LeaderF/autoload/leaderf/python/leaderf/anyExpl.py", line
600, in _default_action
    config[k] = vim.bindeval("g:Lf_Extensions['%s']['%s']" % (category, k))
AttributeError: 'LegacyVim' object has no attribute 'bindeval'

hilarryxu avatar Oct 12 '19 04:10 hilarryxu

@hilarryxu 你用的不是最新的代码,请更新一下插件。

Yggdroot avatar Oct 12 '19 04:10 Yggdroot

更新后可以了 谢谢 @Yggdroot

hilarryxu avatar Oct 12 '19 05:10 hilarryxu

Leaderf 就差 POPUP 就圆满啦,Normal 模式能不能用 vim 的 popup 模拟?有个work around,sendkey 之类的,我见人说过。或者直接重新从键盘命令开始模拟,其实也没几个,主要就是光标上下移动和翻页。

skywind3000 avatar Oct 12 '19 06:10 skywind3000

@skywind3000 这样以后看吧,还是感觉popup不支持focus比neovim的float window差了些。

Yggdroot avatar Oct 12 '19 08:10 Yggdroot

嗯,可以参考下:

https://github.com/liuchengxu/vim-clap

这个 fuzzy finder,它使用 popup,同时支持 vim/neovim

skywind3000 avatar Oct 12 '19 08:10 skywind3000

@skywind3000 我早就想实现使用popup了,后来发现它不支持focus,就放下了。

Yggdroot avatar Oct 12 '19 09:10 Yggdroot

好吧。

skywind3000 avatar Oct 12 '19 09:10 skywind3000

@Yggdroot 我测试了了一下,完全不好用,不知道具体原因。代码非常简单:

fu! test#test(...)
  retun ['sss', 'ssss']
endf

fu! test#echo(line)
   echo a:line
endf
let g:Lf_Extensions.menu =
      \ {
      \       "source": function("test#test"),
      \       "arguments": [
      \           { "name": ["--name"], "nargs": 1, "help": "Use leaderf show unite menu"},
      \       ],
      \       "accept": function("test#echo"),
      \ }

wsdjeg avatar Oct 16 '19 14:10 wsdjeg

@Yggdroot 我测试了了一下,完全不好用,不知道具体原因。代码非常简单:

fu! test#test(...)
  retun ['sss', 'ssss']
endf

fu! test#echo(line)
   echo a:line
endf
let g:Lf_Extensions.menu =
      \ {
      \       "source": function("test#test"),
      \       "arguments": [
      \           { "name": ["--name"], "nargs": 1, "help": "Use leaderf show unite menu"},
      \       ],
      \       "accept": function("test#echo"),
      \ }

早期可以这样,现在需要把function("test#test")改成"test#test",把function("test#echo")改成"test#echo"。 原来每个回调函数是个Funcref对象,现在改为函数名了。 有一个例外,source里面如果是个dict,还是Funcref

  • dict({"command": string or Funcref}) dict只有一个元素, "command"的值可以是个string,例如:git ls-files;如果想传参数给这个命令,在命令里使用"%s", 例如:grep -r '%s' %s,如果命令中有“%s", 下面说的位置参数必须与"%s"相对应(个数,顺序);也可以是个Funcref,签名是(arguments),返回值是个命令字符串。

Yggdroot avatar Oct 16 '19 15:10 Yggdroot

扩展的时候,如果source是个函数名,那么会无限循环把list重复,查找到的计数一直增加。 source函数只是简单返回包含两个元素的list。如果直接把这个list放到source则没问题。。

tracyone avatar Nov 21 '19 13:11 tracyone

更新了代码后,变成下面这样

Screen Shot 2019-11-23 at 10 20 47

tracyone avatar Nov 23 '19 02:11 tracyone

更新了代码后,变成下面这样

你是怎么用的?

Yggdroot avatar Nov 23 '19 02:11 Yggdroot

let g:Lf_Extensions = {
            \ 'dir': {
            \       'source': 'te#leaderf#dir#source',
            \       'accept': 'te#leaderf#dir#accept',
            \ 'need_exit': 'te#leaderf#dir#needExit',
            \       'supports_name_only': 1,
            \       'supports_multi': 0,
            \ },
            \ 'feat': {
            \       'source': "te#leaderf#feat#source",
            \       'accept': 'te#leaderf#feat#accept',
            \       'supports_name_only': 1,
            \       'supports_multi': 0,
            \ },
            \}

什么都没动,更新前还能弹出悬浮框,只不过计数一直增加,更新后就成这样着

tracyone avatar Nov 23 '19 08:11 tracyone

let g:Lf_Extensions = {
            \ 'dir': {
            \       'source': 'te#leaderf#dir#source',
            \       'accept': 'te#leaderf#dir#accept',
            \ 'need_exit': 'te#leaderf#dir#needExit',
            \       'supports_name_only': 1,
            \       'supports_multi': 0,
            \ },
            \ 'feat': {
            \       'source': "te#leaderf#feat#source",
            \       'accept': 'te#leaderf#feat#accept',
            \       'supports_name_only': 1,
            \       'supports_multi': 0,
            \ },
            \}

什么都没动,更新前还能弹出悬浮框,只不过计数一直增加,更新后就成这样着

你敲的什么命令? 错误说识别不了-e。

Yggdroot avatar Nov 23 '19 08:11 Yggdroot

最近跟新解决了下面这个问题,非常感谢。

“什么都没动,更新前还能弹出悬浮框,只不过计数一直增加,更新后就成这样着”

tracyone avatar Jan 13 '20 06:01 tracyone

@Yggdroot 将supports_multi设置为1 在多选模式下按回车会调用LeaderfFile的Accept 不会调用自己编写的Accept

linjiX avatar Jul 15 '20 05:07 linjiX

@Yggdroot 将supports_multi设置为1 在多选模式下按回车会调用LeaderfFile的Accept 不会调用自己编写的Accept

感觉应该不会,你怎么写的

是有问题

Yggdroot avatar Jul 15 '20 07:07 Yggdroot

@Yggdroot 将supports_multi设置为1 在多选模式下按回车会调用LeaderfFile的Accept 不会调用自己编写的Accept

改好了

Yggdroot avatar Jul 15 '20 07:07 Yggdroot

@Yggdroot 将supports_multi设置为1 在多选模式下按回车会调用LeaderfFile的Accept 不会调用自己编写的Accept

改好了

确认修复,非常感谢~

linjiX avatar Jul 15 '20 08:07 linjiX

请问一下,用vimL 写扩展如何把多个函数绑定到多个快捷键上呢?比如

let g:Lf_Extensions.calibre = {
			\ 'source': 'leaderf#calibre#source',
			\ 'accept': 'leaderf#calibre#accept',
			\ }

默认把回车键绑定到leaderf#calibre#accept这个函数上,现在希望按不同的快捷键触发不同的函数该怎么办呢?换言之,vim里有没有像python用的ExplManager的方法呢?

Screenshot from 2020-09-12 17-23-50

Freed-Wu avatar Sep 12 '20 09:09 Freed-Wu

请问一下,用vimL 写扩展如何把多个函数绑定到多个快捷键上呢?比如

LeaderF 不支持这样。只支持一个accept函数

Yggdroot avatar Sep 24 '20 01:09 Yggdroot

请问一下,用vimL 写扩展如何把多个函数绑定到多个快捷键上呢?比如

LeaderF 不支持这样。只支持一个accept函数

多谢!既然VimL不支持,那我去看一下Python写扩展~

Freed-Wu avatar Sep 24 '20 03:09 Freed-Wu

试着写了个fuzzy spell check的拓展, 比原来用fzf实现的快多了 :)))

function! LfSpellSink(line,...)
  exe 'normal! "_ciw'.a:line
endfunction

function! LfSpell(args)
  return spellsuggest(expand(get(a:args, "pattern",[""])[0]))
endfunction

let g:Lf_Extensions = {
    \ "spell": {
    \       "source": "LfSpell",
    \       "arguments" : [
    \       {"name":["pattern"], "nargs":1 },
    \       ],
    \       "accept": "LfSpellSink",
    \ }
    \} 
nnoremap z= :Leaderf spell <cword> <CR>

nash-yzhang avatar Nov 26 '20 14:11 nash-yzhang