3

Is it possible to create several functions with the same name whose implementation depends on the file type?

For example, I could imagine a function named CurrentFunctionName that returns the name of the function in which the cursor of the current buffer is. Since the determination of a function name depends on the file type, I need to create multiple implementations for CurrentFunctionName (Perl, C++, JavaScript etc).

This function could then be called in another (global) function with only one implementation:

fu Log(text)
   call WriteToLog(CurrentFunctionName() . ': ' . a:text)
endfu

Which CurrentFunctionName is now called within Log is then dependent on the file type of the buffer that was active when Log was called.

Is there something like that?

  • 1
    I'm not sure if it is a duplicate but I think that lcd047's answer to this question of mine could help you. – statox Feb 2 '17 at 8:00
  • Not sure if it will work, maybe try to implement each function in proper ftplugin/<filetype>.vim? – grodzik Feb 2 '17 at 8:33
4

Inside ftplugin/<filetype>.vim you could set a buffer local variable to point to the correct function:

function s:CurrentFunctionName() abort
  " Implement the function here
endfunction

let b:CurrentFunctionName = function('<SID>CurrentFunctionName')

In other scripts you can call b:CurrentFunctionName() and it will call the appropriate one based on what was set for the buffer in the ftplugin. This will fail if you try to call it when for a filetype which did not set this variable.

If you want a fallback you could define it like this (not in ftplugin):

function s:CurrentFunctionNameFallback() abort
  " Implement the fallback here
endfunction

autocmd BufRead,BufNewFile *
    \ if !exists('b:CurrentFunctionName') |
    \   let b:CurrentFunctionName = function('<SID>CurrenFunctionNameFallback') |
    \ endif
  • Nice. However, I see a little caveat: the function cannot be loaded lazily. IIRC, a way to define funcrefs over autoloaded functions have been added to Vim lately. We could improve your solution with: let b:CurrentFunctionName = function('namespace#filetype#funcname') – Luc Hermitte Feb 2 '17 at 17:45
  • Yes I think we can lazy load the funcref - that woudl be a good improvement. I kept the function immediate for brevity. – Nate Bosch Feb 3 '17 at 3:49
0

You could just define the function in a ftplugin file. For example create the two files with content below them:

:~/.vim/ftplugin/a.vim:

function! Test()
    echom 'a'
endfunction

~/.vim/ftplugin/b.vim:

function! Test()
    echom 'b'
endfunction

Now make sure you have set :filetype plugin on. If you now open a .a file (or run :setf a) the a.vim file will be sourced and the function will be defined to echo a. If you open a .b file the other file will be sourced, the function will be redefined and a call to it will echo b, demonstrating that a function can do different things depending on the filetype.

It should be noted that if you open any other filetype, the function is not redefined and will therefore be dependent on the filetype that was last loaded. As you would probably not be calling the function for a filetype that does not define it, this would probably not be a problem. If it is you could :delfunction the function when the filetype changes to something else with an autocmd.

  • 1
    This won't work correctly. If I open foo.cpp, then foo.vim, then switch back to foo.cpp, the C++ function won't be reloaded. This is not enough to guarantee to always call the right specialisation. – Luc Hermitte Feb 2 '17 at 17:40
  • @LucHermitte How to overcome this problem? This is exactly what I am facing right now. – Ankit Jain May 22 '18 at 10:01
  • 1
    @AnkitJain, we need a different, and more convoluted, workflow, see my answer for instance where I propose a way to have filetype polymorphism. – Luc Hermitte May 22 '18 at 10:48
0

I have implemented a mecanism for this in lh-dev (a library plugin dedicated to this kind of issues -- generic functions for generic plugins that can be specialized on filetype basis, which I abusively call "ft-polymorphism"). I use it in my generic refactoring plugin, and to automate the insertion of import/#include in mu-template snippets.

How does it works ?

  • I usually have a generic front-end function, let's say lh#dev#domain#verb() when I follow something like the NVI pattern (from C++), but I may also not have this function and directly call the variation point from an unrelated function as I would do with the template method design pattern.
  • I may have a default implementation (I don't remember the name I've chosen)
  • and I have a few specialisations, for instance: lh#dev#cpp#domain#verb(), lh#dev#vim#domain#verb(), and so on.

Then I call the right function, from lh#dev#domain#verb(), thank to lh#dev#options#call('domain#verb', &ft, param1, param2...) that looks like this:

" Function: lh#dev#option#call(name, filetype, [, parameters])  {{{2
" @return lh#dev#{ft}#{name}({parameters}) if it exists, or
" lh#dev#{name}({parameters}) otherwise
" If {name} is a |List|, then the function name used is: {name}[0]#{ft}#{name}[1]
function! lh#dev#option#call(name, ft, ...) abort
  if type(a:name) == type([])
    let prefix = a:name[0]
    let name   = a:name[1]
  elseif type(a:name) == type('string')
    let prefix = 'lh#dev'
    let name   = a:name
  else
    throw "Unexpected type (".type(a:name).") for name parameter"
  endif

  let fts = lh#dev#option#inherited_filetypes(a:ft)
  call map(fts, 'v:val."#"')
  let fts += ['']
  for ft in fts
    let fname = prefix.'#'.ft.name
    if !exists('*'.fname)
      let file = substitute(fname, '#', '/', 'g')
      let file = substitute(file, '.*\zs/.*', '.vim', '')
      exe 'runtime autoload/'.file
    endif
    if exists('*'.fname) | break | endif
  endfor

  call s:Verbose('Calling: %1(%2)', fname, a:000)
  if s:verbose >= 2
    debug return call (function(fname), a:000)
  else
    return call (function(fname), a:000)
  endif
endfunction

The caveat? The :for loop is a nightmare when I'm debugging.

For more explanations on the topic, see it's related documentation: https://github.com/LucHermitte/lh-dev#filetype-polymorphism . Notes: lh#def#option#get() has been moved to lh#ft#option#get() for dependency issues, it also supports my new project variables p:foobar (which act as shared b:variables).

Also note that if namespace#cpp#domain#funcname() doesn't exist, this function will try to execute namespace#c#domain#funcname(). A feature I've named filetype inheritance (I wasn't inspired ^^').

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.