8

I'm working on a plugin that allows users to create custom operators. The operators would apply a VimL expression to the motion or text object that the operator moves over.

Keeping a clean user interface

I think the cleanest interface for defining the custom operators would be to use a command. I've defined a command :MapExpress that would be invoked something like this:

:MapExpress cd '/* ' . v:val . ' */'

This would create a Normal mode operator and a Visual mode mapping for cd that would surround the motion or selection in C-style comment delimiters.

Of course, here lies a problem. You can't call a command that's defined in a plugin from your .vimrc file.

Less-than-satisfying workarounds

I've come up with a few workarounds that I'm not entirely happy with.

User uses autocmd VimEnter * to call the command

While this would work, it adds a lot of "mental overhead". I would guess that a lot of Vim users don't have a solid grasp on how autocmd works.

User creates a file in ~/.vim/after/plugin/ which calls the command

Again, this would work, but it has the drawback that this piece of configuration is off in its own file, easily lost and forgotten.

I move the command definitions into my plugin's autoload/ directory, and the user calls some method that triggers the file to be loaded, and thus the commands to be defined

This would look like this:

call express#init()
MapExpress cd '/* ' . v:val . ' */'

Slightly better, but this would cause confusion about whether the express#init() method is necessary for the plugin to work at all.

Alternatives to using a command

I also considered some alternatives to using a command to define the operator, but each has its caveats.

User calls a function to define the operators

This would look something like this:

call express#operator('cd', '"/* ".v:val." */"')

This is not terrible, but it has the disadvantage of requiring a quoted expression. That can be annoying when you want to use quotes in your expression.

User uses an <expr> mapping

Like this:

nmap <expr> cd express#nmap('"/* ".v:val." */"')
xmap <expr> cd express#xmap('"/* ".v:val." */"')

This has the same tedious quoted expression requirement, and also violates DRY unless you introduce a variable (not ideal).

Okay, so what?

Here are all of my ideas and why I don't like any of them. Am I being too fussy? Is there some better solution I haven't thought of?

3
  • 2
    The autoload solution doesn't sound too bad to me, especially if you use a function name that's slightly more specific to what it's doing. It's hard to come up with a good example without knowing what other functionality your plugin had that the autoload init is not required for, but maybe something like call express#initMapCommands()? Anything that requires extra quoting, though, is a super bad idea.
    – Rich
    Mar 31, 2015 at 6:30
  • This is what I'm leaning towards, I think.
    – tommcdo
    Mar 31, 2015 at 10:36
  • @tommcdo and today I learned how <q-args> really works. Thanks
    – D. Ben Knoble
    Feb 27, 2019 at 20:11

2 Answers 2

5

It seems to me you are looking for :runtime. You can perfectly run commands defined in plugins from your .vimrc, but you have to know the plugin name (that defines it) before that.

" 1- Prepare the plugin-suite if it's managed through VAM/Vundle/NeoBundle/...
ActivateAddons FooBar

" 2- Be sure the plugin is loaded
runtime plugin/foobar.vim

" 3- And finally run (defensively) the command
if !exists(':FooBar')
   echo "Sorry some initializations will be ignored as foobar is nowhere to be found"
else
   FooBar abc
   FooBar 135468
endif

Note: unlike the autoload solution, this approach won't crash .vimrc sourcing. If an autoload plugin cannot be found (through the execution of a function), vim throws an error. :runtime stays silent if no plugin can be loaded.

5
  • 1
    This is a lot of work for the end user. As a plugin developer, I'm trying to minimize the effort required to use my plugin.
    – tommcdo
    Mar 31, 2015 at 9:25
  • This solution is for a .vimrc that you may deploy where your plugin isn't installed. For a simplified installation guide, it's just :runtime. Otherwise use an option as romainl suggested. Mar 31, 2015 at 9:43
  • Even still, that requires the user to know where the file is. If they use a plugin manager, it could be somewhere less obvious than plugin/.
    – tommcdo
    Mar 31, 2015 at 9:50
  • If they use a plugin manager, it will always be in plugin/ -- if you organize your "project"/repository this way. Plugin managers update automatically the 'runtimepath' option. Mar 31, 2015 at 9:54
  • Oh, good point.
    – tommcdo
    Mar 31, 2015 at 9:56
2

A list would IMO be the easiest solution:

let g:pluginname_my_operators = [
    ['cd', '/* ' . v:val . ' */'],
    ['cm', '<em>' . v:val . '</em>']
]

Check for g:pluginname_my_operators when you initialize your plugin and run :MapExpress on each item if applicable while still allowing your users to use the command for on-the-fly definition.

1
  • 2
    This would actually need more quoting... v:val must be part of the string, because it is evaluated when the map internals are invoked. I think any solution involving quoting is less than ideal.
    – tommcdo
    Mar 31, 2015 at 9:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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