开发者

modifying a vim function constantly giving me: "Not enough arguments for function"

开发者 https://www.devze.com 2023-02-27 10:49 出处:网络
I have this function: function! Find(name) let l:list=system(\"find . -name \'\".a:name.\"\' | perl -ne \'print \\\"$.\\\\t$_\\\"\'\")

I have this function:

function! Find(name)
  let l:list=system("find . -name '".a:name."' | perl -ne 'print \"$.\\t$_\"'")
  let l:num=strlen(substitute(l:list, "[^\n]", "", "g"))
  if l:num < 1
    echo "'".a:name."' not found"
    return
  endif
  if l:num != 1
    echo l:list
    let l:input=input("Which ? (CR=nothing)\n")
    if strlen(l:input)==0
      return
    endif
    if strlen(substitute(l:input, "[0-9]", "", "g"))>0
      echo "Not a number"
      return
    endif
    if l:input<1 || l:input>l:num
      echo "Out of range"
      return
    endif
    let l:line=matchstr("\n".l:list, "\n".l:input."\t[^\n]*")
  else
    let l:line=l:list
  endif
  let l:line=substitute(l:line, "^[^\t]*\t./", "", "")
  execute ":e ".l:line
endfunction
command! -nargs=1 Find :call Find("<args>")

When I try adding a parameter, so the declaration becomes function! Find(name, search_dir), it always tells me i don't have enough parameters when I call the funct开发者_运维百科ion in vim using :Find x y(where as Find: x would work when ther was only 1 parameter in the function declaration.

Any idea how I can add multiple parameters?

The end goal is to have a Find function that finds in a specified subdirectory.


An addition to @Peter Rincker answer: you should never use "<args>" if you want to pass parameter to a function as there is already a builtins <q-args> and <f-args> which do not need additional quoting and do not introduce a possibility of code injection (try Find ".string(g:)." with your code). First will pass all parameters as one item, second will produce a list of parameters suitable for a function call:

command -nargs=+ Find :call Find(<f-args>)

Another things to consider:

  1. (system() call) Never pass user input to shell as-is, use shellescape(str, 1). You may have unexpected problems here.
  2. strlen(substitute(l:input, "[0-9]", "", "g"))>0 condition is equivalent to input=~#'\D', but is much bigger.
  3. You don't need to specify l:: it is the default scope inside functions.
  4. There is a built-in glob() function: the whole system() line can be replaced with

    join(map(split(glob('./*'.escape(a:name, '\`*[]?').'*'), "\n"), 'v:key."\t".v:val'), "\n")
    
  5. Don't forget to escape everything you execute: execute ":e ".l:line should be written as execute "e" fnameescape(line) (it is the third place where code injection is possible in such a simple code snippet!).
  6. It is better to use lists here, in this case you don't need to use something to add line numbers:

    function s:Find(name)
        let list=split(glob('./*'.fnameescape(a:name).'*'), "\n")
        if empty(list)
            echo string(a:name) "not found"
            return
        endif
        let num=len(list)
        if num>1
            echo map(copy(list), 'v:key."\t".v:val')
            let input=input("Which?")
            if empty(input)
                return
            elseif input=~#'\D'
                echo "Not a number"
                return
            elseif input<1 || input>num
                echo "Out of range"
                return
            endif
            let line=list[input]
         else
            let line=list[0]
         endif
         execute "e" fnameescape(line)
     endfunction
     command! -nargs=1 Find :call Find(<f-args)
    
  7. Neither you nor me handle the situation where filename that matches pattern contains a newline. I know how to handle this (you can see my vim-fileutils plugin (deprecated) or os.listdir function of os module of frawor plugin (in alpha stage, not posted to vim.org)). I don't think such situation is likely, so just remember that it is possible.


You need to change -nargs=1 to -nargs=+. This will mean you have to have arguments but does not specify a number. I suggest you change your Find function to Find(...) and use a:0 get the number of arguments to error out if an invalid number of arguments are used.

Example function and command with multiple parameters:

command! -nargs=+ -complete=dir Find call Find(<f-args>)
fun! Find(name, ...)
  let dir = getcwd()
  if a:0 == 1
    let dir = getcwd() . '/' . (a:1 =~ '[/\\]$' ? a:1 : a:1 . '/')
  elseif a:0 != 0
    echohl ErrorMsg
    echo "Must supply 1 or 2 arguments"
    echohl NONE
  endif
  let &efm = "%f"
  cexpr []
  caddexpr split(glob(dir . '**/*' . escape(a:name, '\`*[]?') . '*'), '\n')
  copen
  aug Find
    au!
    au BufLeave <buffer> ccl|aug! Find
  aug END
endfun

For more help

:h :command-nargs
:h ...

Edit Added example of function that excepts multiple parameters as suggest by @ZyX.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号