New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for Go plugin integration #1136

Closed
myitcv opened this Issue Sep 4, 2014 · 31 comments

Comments

Projects
None yet
5 participants
@myitcv
Copy link
Member

myitcv commented Sep 4, 2014

Hi all,

On the back of #1133, I would like to propose a means by which Go plugins can be integrated into Neovim, in a similar vein to the integration of Python plugins

It's worth reminding ourselves as a starting point that Go is a compiled language; all plugins will therefore be binaries that we fork from the main Neovim process in some way.

Use of the phrase 'Plugins' hereafter will mean Go plugins; others, e.g. Python, will be referred to explicitly.

Plugins will take two forms:

  1. Those that at some point call vim_register_provider; such a plugin will need to service requests made by Neovim, i.e. the plugin will act as a MSGPACK-RPC server
  2. Those that don't call vim_register_provider

Either form of plugin may vim_subscribe and make calls to the other API methods.

Bootstrapping

My current thinking is that Go plugins will live in a directory below a runtime directory, e.g. ~/.vim/plugins/go. Unlike the python-client, there will not be a controlling process that discovers other plugins. Discovery will simply be the existence of a binary in the special path. Each plugin will be forked with stdin/stdout suitably connected as the transport mechanism to Neovim.

We could alternatively define a variable which points to a series of Go plugin directories, a variable which when set effectively enables Go plugins. e.g.:

" ~/.nvimrc

if has('neovim')
  let &initgo = '~/.vim/plugins/go;/another/path/to/plugins'
endif

Changes required

The python-client provides four methods:

  • python_execute
  • python_execute_file
  • python_do_range
  • python_eval

Because there is no 'controlling' Go plugin, it doesn't make sense to expose similar methods. Each plugin will call vim_register_provider as it wishes.

Instead I propose we expose three ex commands:

  • ex_script_host_execute
  • ex_script_host_execute_file
  • ex_script_host_do_range

These will be similar to the Python commands defined here, but not Go/language specific.

The first argument to each command will be the name of the method that needs to be called, with remaining arguments then passed to the RPC method call.

And I propose one function:

  • f_script_host_eval

This will be similar to the Python definition here, but again not Go/language specific.

Again, the first argument will be the name of the method, with remaining arguments then passed to the RPC method call.

Structure of a plugin

Each plugin will likely have imported the neovim package, although this isn't a requirement (strictly speaking all the binary needs to do is speak MSGPACK-RPC; anyone can do this). By definition, as each plugin is a binary, we will have a main entry point for each.

stdin and stdout will be set by the controlling Neovim process, and the plugin will use this as the transport mechanism for MSGPACK-RPC communication.

Either form of plugin (1 and 2 above) will be expected to behave according to its form when run. Plugin form 1 will be expected to handle MSGPACK-RPC requests on its stdin; both forms of plugin will be expected to handle notifications on stdin. Either can, as required, make requests on its stdout.

Closing stdin will terminate the plugin.

Example: syntax-based folding

One of the first examples I intend to implement once this setup is complete is syntax-based folding.

neovim-go is my current, canonical example of a Go Neovim plugin. It currently provides on-the-fly syntax highlighting based on a go/parser generated AST, by listening for changes in the buffer.

Syntax-based folding would leverage this same AST but would instead be invoked as a blocking call from Neovim. For example, given the following Go code, with the cursor at the position ^ shown:

package main

func main() {
    fmt.Println("This")
    fmt.Println("is")^
    fmt.Println("a")
    fmt.Println("test")
}

we might want to fold the main function. As an example, an extended neovim-go might provide (via vim_register_provider) a method fold_function. When invoked from Neovim, if the cursor position is currently within a function definition, neovim-go will create a range-based fold (via vim_command) and then return.

Feedback and implementation

Feedback on this proposal gratefully received. My understanding of the Neovim code base is very limited, so any guidance where I have misspoken/misunderstood, again, gratefully received.

My limited understanding of the Neovim code base is surpassed only by my limited ability at writing C code. Again, any help putting the above into action gratefully received.


Want to back this issue? Place a bounty on it! We accept bounties via Bountysource.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

My current thinking is that Go plugins will live in a directory below a runtime directory, e.g. ~/.vim/plugins/go.

I believe ~/.vim/go/ would be better, to keep symmetry with the current python convention (.vim/pythonx).

Unlike the python-client, there will not be a controlling process that discovers other plugins. Discovery will simply be the existence of a binary in the special path.

Wouldn't this be applicable to any sort of binary plugin that implements the msgpack-rpc api? Your considerations in "Changes required" do consider this, so perhaps this proposal should be generalized?

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 4, 2014

@fmoralesc

Unlike the python-client, there will not be a controlling process that discovers other plugins. Discovery will simply be the existence of a binary in the special path.

Wouldn't this be applicable to any sort of binary plugin that implements the msgpack-rpc api? Your considerations in "Changes required" do consider this, so perhaps this proposal should be generalized?

I agree this proposal isn't specific to Go. I don't think it's even specific to binary plugins.

Worth considering whether it becomes a more general proposal. At least we can try it with Go plugins.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

BTW, there's already an implementation of what you are proposing about the ex commands, see here. These functions basically call the functions registered by the providers.

What I think is needed is a way to allow the providers define ex commands. ex_python() and similar should be created by the providers themselves, not hard coded. The definition for ex_go() should obviously be

void ex_go(exarg_T *eap)
{
    script_host_execute("go_execute", eap);
}

Now, in a dynamic language this would be very easy to implement, but I'm not sure how hard would it be to do in neovim's C codebase (the ex commands and functions tables are static).

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 4, 2014

@fmoralesc - my mistake for not being clear.

My proposal is to have ex commands in this form:

void ex_script_host_execute(exarg_T *eap)
{
    char *methodName
    exarg_T *remainingArgs

    // extract method from from *eap into methodName
    // leaving the remainder in remainingArgs

    script_host_execute(methodName, remainingArgs);
}

We don't, in my opinion, need ex_go as you have it defined. The above is language independent (as you observed)

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

I see. You are right.

My point still remains for interfaces like :python (possibly :ruby, :mzscheme, etc). Now, if the number of these is small, it shouldn't be a worry whether they are hardcoded or dynamically created.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

What is your proposal for the ex commands interface? Something like

:scriptcall fold_go blah
:scriptfile fold.go
:12,13scriptdo indent_go

(nevermind the script prefix, I find it awkward myself - a good prefix is needed, though).

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 4, 2014

What is your proposal for the ex commands interface? Something like

:scriptcall fold_go blah
:scriptfile fold.go
:12,13scriptdo indent_go

(nevermind the script prefix, I find it awkward myself - a good prefix is
needed, though).

@fmoralesc - open to suggestions.

That said, I don't think the names need to be overly readable. Largely
because all calls will almost certainly be wrapped in some way (for example
via nnoremap) by the plugin writer so that they can cleanly be used by the
end user for key bindings etc

fmoralesc added a commit to fmoralesc/neovim that referenced this issue Sep 4, 2014

implement ex_script_host_execute()
as described in proposal for issue neovim/neovim:neovim#1136.
@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

@myitcv I implemented ex_script_host_execute() as described above in fmoralesc@c9d9e9e. The rest of the functions seem easy enough to implement.

fmoralesc added a commit to fmoralesc/neovim that referenced this issue Sep 4, 2014

implement :scriptdo and :scriptfile
as described in proposal in issue neovim/neovim:neovim#1136

fmoralesc added a commit to fmoralesc/neovim that referenced this issue Sep 4, 2014

implement ex_script_host_execute()
as described in proposal for issue neovim/neovim:neovim#1136.

fmoralesc added a commit to fmoralesc/neovim that referenced this issue Sep 4, 2014

implement :scriptdo and :scriptfile
as described in proposal in issue neovim/neovim:neovim#1136
@tarruda

This comment has been minimized.

Copy link
Member

tarruda commented Sep 4, 2014

@myitcv Good proposal, thanks for this comprehensive documentation. I did not consider that golang still has no mechanism for dynamically loading code and so the "plugin host" concept is not applicable. This might be doable in the future if someone writes a dynamic loader for go code.

I'm not sure if I follow your reasoning of exposing the ex_script_host_* functions to vimscript. Could you show an example of how the calls would be made in vimscript? BTW, are you aware of the send_call/send_event vimscript functions? (If not, it's my fault for not writing any documentation yet) They can be used to send arbitrary msgpack-RPC requests and notifications to channels. For example, python import vim would be equivalent to call send_call(g:python_host_channel_id, 'python_execute', 'import vim').

With that said, I also have plans to simplify the task of adding extension languages to Neovim in a way that is backwards compatible with Vim. @mikaelj first dicussed the possibility with this question and I replied here. As I have explained #895, here are the necessary changes to make it happen:

  • Expose channel_from_job to vimscript. This is easy to do but I've been delaying because of more urgent tasks(Anyone is free to send a PR though)
  • Change vimscript parsing code to enable definition lowercase ex commands that can receive 'heredocs'(the << EOF block) and lowercase functions. @ZyX-I argued that the problem with this is that it may be impossible to maintain compatibility with Vim(ref).

Right now, exposing the channel_from_job function should be enough to add a lot of msgpack-RPC power to vimscript. If/when the second change is made, we can add vimscript files that will be responsible for adding extension languages to Neovim(in a way that is compatible with Vim extension languages). For example, here's a rough idea of how python support may be added:

let s:python_host_channel_id = 0

function s:BootstrapPythonHost()
  if s:python_host_channel_id
    return
  endif
  let s:python_host_channel_id = channel_start('python', ['-c', 'import neovim; neovim.start_host()'])
endfunction

function pyeval(expr)
  call BootstrapPythonHost()
  return send_call(s:python_host_channel_id, 'python_eval', a:expr)
endfunction

" Assume the `-heredoc` flag enables the command argument to be passed as a heredoc string
command! -heredoc -nargs=? python call channel_send_call(s:python_host_channel_id, "python_execute", <q-args>)
command! -nargs=? pyfile call channel_send_call(s:python_host_channel_id, "python_execute_file", <q-args>)
command! -nargs=? pydo call channel_send_call(s:python_host_channel_id, "python_do_range", <q-args>)

It would be nice to have extension language support added in pure vimscript, but that could bring the problem @ZyX-I described: Possibility of breaking compatibility with vim in the future. For example, say we add support for node.extensibility like this:

let s:nodejs_host_channel_id = 0

function s:BootstrapNodejsHost()
  if s:nodejs_host_channel_id
    return
  endif
  let s:nodejs_host_channel_id = channel_start('node_bootstrap.sh', [])
endfunction

command! -heredoc -nargs=? nodejs call channel_send_call(s:nodejs_host_channel_id, "nodejs_execute", <q-args>)

This would enable plugins to use the node.js platform to extend nvim, eg:

nodejs << EOF
console.log('Hello from node.js!');
EOF

This is all well and good, but if/when upstream Vim receives a patch with a nodejs ex command, compatibility between Vim and Neovim would decrease even more

I also plan to refactor the provider.c module to make it use vimscript functions to customize core functionality such as clipboard. This is more flexible than relying directly on RPC functions because as shown before, vimscript can be used to perform RPC calls.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

I'm not sure if I follow your reasoning of exposing the ex_script_host_* functions to vimscript. Could you show an example of how the calls would be made in vimscript?

@tarruda It would look like this (using the syntax of my implementation). Suppose we have a provider that has registered go_func. Then we could map that function using

:noremap <C-f> :scriptcall go_func args

The :python command could be created using

:command -heredoc -nargs=? python scriptcall python_execute <q:args>

I'm not sure this is any benefit from the API you have just described.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

One thing to consider in @myitcv's proposal:

:scriptfile requires passing the name of the method to execute the file with. This means the arguments signature for it cannot be like that of :pyfile. It should be used like this:

  :scriptfile execute_go_file test.go

Anyway, the use of this command wouldn't be equivalent to that of :pyfile. For some cases, it is probably useless, because it can be emulated with :scriptcall (or the equivalent), :! or job_start(). It depends on what the provided method is supposed to do with the file.

@justinmk

This comment has been minimized.

Copy link
Member

justinmk commented Sep 4, 2014

simplify the task of adding extension languages to Neovim

👍 Will this also avoid plugin hosts needing to crawl for plugins?

but if/when upstream Vim receives a patch with a nodejs ex command

I do not think we should be saddled with worrying about future compatibility (except where reasonable, e.g., a new option that is likely to be added to Vim): it will paralyze us.

We can note Neovim-specific features using a {Neovim} tag in the help docs, and auto-generate a list from those tags. At some point (say, first release) we must admit that Neovim adds value and let users choose to use it instead of Vim. I think our goal should be to reach a stable cross-platform release that allows users to painlessly switch to Neovim with existing Vim plugins. But thereafter there should be no reason to switch back to Vim.

fmoralesc added a commit to fmoralesc/neovim that referenced this issue Sep 4, 2014

new viml function: scriptval()
As described in proposal issue neovim/neovim:neovim#1136
@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 4, 2014

@myitcv I have implemented the :scriptcall, :scriptfile and :scriptdo commands and the scripteval() function in my providers feature-branch, basing them in the python interfaces. I haven't tested them intensively, but this, at least, works:

 :scriptcall python_execute print 1+1
 :echo scripteval('python_eval', '1+1')
 :`<,`>scriptdo python_do_range return int(line)+1

scriptfile and scriptdo are is failing.

EDIT: Actually, scriptfile works, but it hangs if the path includes ~. This doesn't happen with :pyfile, and I'm not sure what is the difference.

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 5, 2014

@fmoralesc - fantastic start, thanks. Will take a look at this today

@tarruda

This comment has been minimized.

Copy link
Member

tarruda commented Sep 5, 2014

Will this also avoid plugin hosts needing to crawl for plugins?

This can't be avoided because it's a language-specific matter. @myitcv proposal illustrates this: each golang plugin requires its own dedicated process since there's no support for dynamic golang code loading yet(btw @myitcv, would it be viable to build a single executable with all .go files discovered in the run time directories when nvim starts? This would require the presence of the go compiler but could be more efficient than the process-per-plugin model)

I do not think we should be saddled with worrying about future compatibility (except where reasonable, e.g., a new option that is likely to be added to Vim): it will paralyze us

👍

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 5, 2014

@tarruda - thanks for the feedback.

I did not consider that golang still has no mechanism for dynamically loading code and so the "plugin host" concept is not applicable. This might be doable in the future if someone writes a dynamic loader for go code

Discussions are afoot, but nothing guaranteed and hence no timeline.

That said, I don't think a plugin-host architecture is necessary, given the alternative approach I described to bootstrapping binaries (which could of course be broadened to executables in general, thereby including scripts with interpreters she-bang defined). Is there a reason to prefer the plugin-host approach?

I'm not sure if I follow your reasoning of exposing the ex_script_host_* functions to vimscript.

I didn't do a good job of presenting that rationale. Let me try again (see below).

BTW, are you aware of the send_call/send_event vimscript functions?

I was aware of send_event, but not its sibling, send_call. That might obviate the need for exposing ex_script_host*, but there's one question I've got which I will also touch on below in the course of explaining the rationale behind exposing ex_script_host*.

Rationale for exposing ex_script_host_*

First expanding on the bootstrapping section in my original post. Assuming &initgo set in .nvimrc as shown, the following sequence is followed:

  • User launches Neovim
  • .nvimrc sourced; &initgo set. &initgo is a list of directories that contain binaries/executables that are Neovim plugins. Any number, any language, all they need to do is speak MSGPACK-RPC over stdin/stdout
  • For each entry (plugin) in the directories listed in &initgo, Neovim uses channel_from_job to launch the plugin

That's the bootstrapping done. Each plugin is now free to do what it likes. It can be of either form described in my original post, form 1 or 2. And can be in any language. No plugin-host architecture strictly required.

Form 2 plugins will typically use a combination of events and requests. In its current form, neovim-go does just this; it listens for buffer changes, then gets the current buffer contents, does some parsing before making a number of matchadd callbacks

Form 1 plugins will need to call vim_register_provider to tell Neovim about the methods it, the plugin in question, exposes. There could be N such plugins, again in any language, not necessarily dependent on any plugin-host. The example I provided transforms neovim-go to become a form 1 plugin (need to come up with a better name than form 1/2) so let me expand on that below:

  • Assume plugin has been bootstrapped as described
  • Plugin calls vim_register_provider with fold_function as an argument
  • Let us assume that, in neovim-go, fold_function itself requires no arguments and instead will use the current cursor position (determined via requests from the plugin -> Neovim)

At this point it's worth summarising what knows what:

  • Neovim knows that, amongst the many plugins it has loaded, the plugin with channel ID X provides fold_function. That is to say, Neovim holds some mapping from methods to channels, fold_function is one such mapping
  • The plugin that provides fold_function, does not know its channel ID (nor should it in my opinion)
  • The end user does not know which channel provides fold_function; again, nor should she/he

Hence the exposing of ex_script_host_* is really a means to allow a user or plugin to ask Neovim, "I know the method fold_function is provided by a plugin you have loaded, please make the call on the appropriate channel".

As @fmoralesc explained, this would then allow the user (in their .nvimrc) to define key bindings:

nnoremap <silent> <C-f> :<C-u>scriptcall "fold_function"<CR>

Or more likely, the plugin (neovim-go in this example) would expose the slightly more readable:

nnoremap <silent> <Plug>(go-fold-function) :<C-u>scriptcall "fold_function"<CR>

allowing the user (in their .nvimrc) to define the more readable:

nnoremap <silent> <C-f> <Plug>(go-fold-function)

Key mappings for illustrative purposes only... not tested

Back to your response

Given the rationale behind exposing ex_script_host_*, I don't think we need to expose channel_from_job as you described. Doing so would essentially break the very neat abstraction whereby only Neovim need know and talk channel IDs.

With ex_script_host_* exposed, can you see any need for a user/plugin to know about/store a channel ID?

Taking a look at your example, which is looking at some code in a user's .nvimrc, we could transform it to:

" a Python equivalent to &initgo; indeed we could generalise this to something
" like &pluginpath to be language agnostic
let &initpy="~/plugins/pythonx"

" assume ~/plugins/pythonx contains an executable python script that has a main 
" entry point that calls neovim.start_host(). Bootstrapping will launch this
" plugin

command! -nargs=? Py scriptcall "python_execute", <q-args>
" this is probably invalid Vimscript, but hopefully you get the idea

Notice, no mention of channel IDs, and no need for the user to call channel_from_job either.

This is all well and good, but if/when upstream Vim receives a patch with a nodejs ex command, compatibility between Vim and Neovim would decrease even more

I'm not sure I follow how this breaks things. The potential clash I see is multiple plugins trying to vim_register_provider with the same method name argument. My proposal doesn't deal with this eventuality (and probably should in some way).

The potential clash of a plugin (or user for that matter) defining an ex command that clashes with a Neovim-provided ex command has always existed has it not?

Will this also avoid plugin hosts needing to crawl for plugins?

This can't be avoided because it's a language-specific matter.

As I've questioned above, I'm unclear on why a plugin-host architecture is required/preferable.

Would it be viable to build a single executable with all .go files discovered in the run time directories when nvim starts?

My proposal, as far as Go is concerned, has missed the obvious step on "how do I install a Go plugin?" Your question touches on that so let me give it a go here.

An initial implementation would require that a plugin be compiled on the user's machine, hence requiring a Go compiler. Future implementations could support the fetching of pre-compiled binaries, but I won't elaborate on that here.

Let's continue with the example of neovim-go. Assuming a GOPATH of ~/go, the steps required to install the plugin would be:

  • go get github.com/myitcv/neovim-go
  • mkdir -p ~/.vim/plugins/go (if it doesn't already exist)
  • cp ~/go/bin/neovim-go ~/.vim/plugins/go (go get builds the binary)
  • Ensure ~/.vim/plugins/go is in &initgo, as shown before

This could be wrapped up in some way to simplify things for the end user. But the important thing here is that we are using the existing Go toolchain.

So to answer your question more directly, .go files will not be in &initgo directories; the binaries themselves will be.

This would require the presence of the go compiler but could be more efficient than the process-per-plugin model

Again, I'm unclear on why you think a process-per-plugin model is not preferable. Under the plugin-host architecture, if N plugins in M different languages are loaded (therefore requiring M channels/processes), is this not equivalent to a process-per-plugin model where I load M plugins (in whatever number of languages)? Granted there is probably good efficiency gains for languages like interpreted languages like Python, but I suspect the overhead for languages like Go is minimal (said without any numbers to back that up) because they are precompiled.

But the great thing about the architecture you have is that we can have the best of both worlds, i.e. we don't need to choose between one or the other.

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 5, 2014

@fmoralesc - thanks again, useful to have this as a prototype.

I have this working locally via scriptcall:

func (t *NeovimTest) TestRegisterProvider(c *C) {
    _ = t.client.RegisterProvider("my_first_method")
    _ = t.client.Command("scriptcall my_first_method")
}

Will do some more testing over the weekend.

@tarruda

This comment has been minimized.

Copy link
Member

tarruda commented Sep 5, 2014

Is there a reason to prefer the plugin-host approach?

It has been brought to my attention that processes are very expensive to start on Windows(in the order of 100x slower than on unixes). Since Windows will be one of your supported platforms, that alone would be a good reason for trying to reduce the number of started processes using the plugin host approach(How slow would Neovim be at startup the user has a hundred of plugins, for example). I also consider these as reasons for using plugin hosts:

  • For python it is necessary as a compatibility layer for Vim-python plugins(where all plugin code is executed in the same VM/namespace)
  • It provides an abstraction layer for plugins, eg it reduces the need for plugins having knowledge about connections or subscribing to RPC events/calls, using language-specific idioms for performing these tasks(define classes/methods, for example).
  • Considering that each plugin requires 3 additional open file descriptors, a plugin junkie(eg: 100 plugins) could potentially reach that limit on some platforms(the truth is that this can be tuned on most OSes, but I don't think asking the user to tune OS parameters in order to use Neovim is a good idea).
  • Even on unixes, I can't imagine that starting a hundred processes will be fast enough to be unnoticeable by the user.
  • Even after startup, a hundred processes might take a considerable toll on OS resources(scheduler, memory, etc). For example, right now I have 87 processes running on my VM. If I had 100 plugins that would increase by more than 100% the number of running processes in a plugin-per-process model, and that for a single nvim instance.
  • We would be assuming/depending too much on OS-specific behavior/features, that is simply not good for a program that needs to be usable from so many platforms.

Given the rationale behind exposing ex_script_host_*, I don't think we need to expose channel_from_job as you described. Doing so would essentially break the very neat abstraction whereby only Neovim need know and talk channel IDs.

With ex_script_host_* exposed, can you see any need for a user/plugin to know about/store a channel ID?

Taking a look at your example, which is looking at some code in a user's .nvimrc, we could transform it to:

That example was showing something that would be distributed as a runtime file or as a vimscript plugin(one that starts a platform for plugins written in another language), not something the end user will have on their .nvimrc. In that case, the channel id is private and the user only interacts with the defined ex commands/functions.

Now let me expose some other ideas I had for implementing a plugin host for golang, but first let's consider what a plugin is from the perspective of the python host:

  • Each plugin is a class that has a name starting with 'Nvim', defined in a module with name prefixed by 'nvim_'
  • It can provide implementation for some editor functionality(eg: clipboard or spell checking)
  • It can listen/react to one or more nvim events.

Here's a dummy python plugin that listens for a "loaded" event and provides a "greet" function to Neovim:

class NvimPlugin(object):
    def __init__(self, vim):
        self.vim = vim
        self.provides = ['greet']

    def greet(self, subject):
        print 'hello %s' % subject

    def on_loaded(self):
        print 'Loaded!'

Some things to notice about this design:

  • It doesn't perform explicit API calls(has no knowledge of a 'vim' object)
  • It simply declares interest in some event(the plugin host scans methods starting with 'on_' and automatically subscribe those as handlers using vim_subscribe). The plugin host could be extended to automatically register on_ method as handlers for autocommands.
  • It provides an implementation for a nvim function by using a declarative style(the self.provides part could be improved by automatically scanning for method names matching known provider names)
  • No setup needs to be done by plugin, discovery/registration is automatically done by the plugin host
  • It uses a python idiom(classes/methods) to define plugins

This code is loaded like this:

  • Nvim starts the python interpreter with the required arguments
  • The python interpreter:
    • Discovers API
    • Creates a vim object with method wrappers
    • Discovers/initializes plugins, registering handlers if necessary passing the vim object(which can be seen as a connection to nvim) to each plugin

It might be possible to adapt this scheme to golang like this(I'm still a golang beginner, so correct if I say something that doesnt make sense):

  • Nvim starts a go program that will be connected via stdin/stdout
  • The go program:
    • Discovers *.go files in certain runtime directories. These files can define types with methods that will handle nvim events(example below)
    • Use the discovered go files to produce a binary containing the plugin host(Not 100% sure about this, but it seems the golang compiler & tools are exposed as libraries to go programs)
    • Executes the plugin host and forwards it's stdin/stdout to Neovim(or simply returns the path and exits, so Neovim can start the plugin host itself)

Analogous golang plugin:

type NvimPlugin struct {
  vim *Vim
  provides []string
}

func (p *NvimPlugin) Init(vim *Vim) {
  p.vim = vim
  p.provides = append(p.provides, "greet")
}

func (p *NvimPlugin) Greet(subject string) {
  print("hello", subject, "!")
}

func (p *NvimPlugin) OnLoaded() {
  print("Loaded!")
}

The main difference between the python host is that plugins are collected in a separate build step. This also has these advantages:

  • Go plugins can be distributed as source files
  • The plugin host is automatically recompiled when necessary(it can check if any of the *.go files changed and rebuild if necessary)

But the great thing about the architecture you have is that we can have the best of both worlds, i.e. we don't need to choose between one or the other.

Indeed, but for both resource usage sake and simplicity of writing and distributing plugins, I think a plugin-host approach is better as the default(Most plugins won't need the isolated environment provided by a separate process and can happily live with other plugins).

We need a way to reduce the amount of code necessary for implementing plugin hosts as much as possible, and moving the channel/rpc infrastructure setup to vimscript might make things easier.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 5, 2014

@myitcv Glad the prototype was useful.

Indeed, but for both resource usage sake and simplicity of writing and distributing plugins, I think a plugin-host approach is better as the default(Most plugins won't need the isolated environment provided by a separate process and can happily live with other plugins).

@tarruda I think both models need to be supported.

  1. As a plugin junkie myself of sorts, I am not concerned about the case where I have 100 processes providing functions for neovim.
  • It is doubtful that many plugins will take the approach described here anyway, so the 100 processes is unrealistic. Most current plugins are either vimscript based or python based and I believe neovim supporting "non-host plugins" won't change this, specially because vim won't support them, and many plugin writers care a great deal about vim compatibillity. Go (and other) plugins using the msgpack-rpc approach are not compatible anyway.

  • As for the performance/resources issue, I believe that to keep things sane is not the editor's responsibility, but the users. Neovim can (and possibly should) strive to launch as few processes as possible by default, but additional stuff falls within the user preferences. Letting the users shoot themselves in the foot every once in while is OK, as long as they provide their own weapons.

  • There could be a lazy execution model for non-host processes, so they are only launched when/if the methods they expose are called. This falls into package/plugin management, though, and a convention should be created for this purpose. But, for example, the hypothetical plugin directory could contain files like this:

     { 
      name: "neovim-go",
      exec: "plugins/go/neovim-go",
      methods: ["go_fold", "go_highlight"]          
    }
    

    On startup, neovim could register that those methods should start a neovim-go instance if none exists (we could make the plugins register themselves as active on intialization). These plugins should be provided by plugin authors, but could be autogenerated. These metadata files could also define whether the plugins should be executed on certain events or for certain filetypes (so the first time the methods are called performance is not pesimized). Just a few ideas.

  1. Supporting the approach described by @myitcv doesn't preclude developing what you have described (which is very cool and neat, btw).

  2. Not every language one would want to write plugins in will support the host approach as neatly as python (or dynamic languages in general). What if someone wants to write a C plugin (which, admittedly, would be insane, but insane is the name of the game for some plugin writers, even for viml)?

A question about your scheme: the bootstrapping go program and the go host would be separate, or the same? How would the binary be updated? If it is reexec'd, both programs could (theoretically) be the same.

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 5, 2014

But the great thing about the architecture you have is that we can have the best of both worlds, i.e. we don't need to choose between one or the other.

Indeed, but for both resource usage sake and simplicity of writing and distributing plugins, I think a plugin-host approach is better as the default(Most plugins won't need the isolated environment provided by a separate process and can happily live with other plugins).

@tarruda - I think you've convinced me. Whilst from a Go perspective, at least, the simplicity of writing a plugin is probably about equal either way, your point about resource usage has won me over (I hadn't considered the Windows angle amongst others). Why would we start with a suboptimal setup, when (again from a Go perspective at least) the effort required to move to a more optimal setup (the plugin-host approach) is minimal? Thanks for putting the effort into your detailed responses.

Therefore I envisage us creating Go versions (in some way) of these functions and this function. At least until your thoughts on another approach become more concrete.

As a follow up to your outline of a plugin-host-based scheme for Go plugins, let me flesh out my thoughts. The example will again draw on my canonical example, neovim-go.

For the purposes of this example, just imagine that I have adapted the code you see here. I will do the actual conversion on a separate branch later this evening/tomorrow. Also, please bear with me as I continue (for consistency's sake) to imagine that Go plugins will live in ~/.vim/plugins/go

As an overview of the architecture I have in mind (see later notes for thoughts on a small toolset that will wrap this architecture and make it more user friendly):

  • A list of installed plugins will be maintained; e.g. in ~/.vim/plugins/go/plugins.json
  • Go plugins will be written and distributed as packages, e.g. github.com/myitcv/neovim-go
  • Plugins packages will ultimately be fetched using the Go tool chain: go get github.com/myitcv/neovim-go. See note below, however, for further thoughts on plugin discovery and installation
  • The Go neovim package will be imported by all Go plugins. It will define a new neovim.Plugin interface:
// from the neovim package

type Plugin interface {
    Init(*Client) error
    Shutdown() error
}
  • A plugin package will define a number of exported types that implement the Plugin interface (remember, implementing an interface is implicit in Go)
  • A list of all types implementing the Plugin interface from the list of installed plugin packages will be collated
  • A Go plugin-host that references these plugin packages and calls Init on the list of collated types will be code generated. e.g. something like:
package main

import (
    "io"
    "log"

    "github.com/myitcv/neovim"
    "github.com/myitcv/neovim_go"
    "github.com/myitcv/neovim_myitcv"

    // list continues...
)

func main() {
    var transport io.ReadWriteCloser
    client, err := neovim.NewClient(transport)
    if err != nil {
        log.Fatalf("Could not connect to Neovim: %v\n", err)
    }

    // list of types implementing neovim.Plugin

    var p1 neovim.Plugin
    p1 = &neovim_go.NeovimGo{}
    err = p1.Init(client)
    if err != nil {
        log.Fatalf("Could not Init neovim_go.NeovimGo: %v\n", err)
    }

    var p2 neovim.Plugin
    p2 = &neovim_myitcv.NeovimMyitcv{} // see below
    err = p2.Init(client)
    if err != nil {
        log.Fatalf("Could not Init neovim_go.NeovimMyitcv: %v\n", err)
    }

    // list continues...

    for {
        // read from stdin; handle events, requests and responses
        // ...
    }
}
  • The code-generated plugin-host will then be built; I'm still thinking about how we could invoke any tests from the plugin package at this stage
  • If the build succeeds, the binary is copied into place: ~/.vim/plugins/go/plugins_host
  • When launched, Neovim will start ~/.vim/plugins/go/plugins_host with stdin and stdout suitably connected

Now turning to how the plugins themselves will be structured. For this section I will write a new Go plugin, neovim_myitcv. This very basic plugin will make an API request, expose an RPC method and handle events on a topic from Neovim:

package neovim_myitcv

import (
    "fmt"
    "os"

    // import the neovim package
    "github.com/myitcv/neovim"
)

type NeovimMyitcv struct {
    client *neovim.Client
}

func (n *NeovimMyitcv) Init(c *neovim.Client) error {
    n.client = c

    // Tell Neovim to broadcast TextChanged*
    topic := "text_changed"
    com := fmt.Sprintf(`au TextChanged,TextChangedI <buffer> call send_event(0, "%v", [])`, topic)
    _ = c.Command(com)

    // Setup a subscription
    sub, _ := c.Subscribe(topic)
    go n.subLoop(sub.Events)

    // Handle an RPC request from Neovim
    n.client.RegisterProvider("get_a_number", n.getANumber)

    return nil
}

func (n *NeovimMyitcv) getANumber(args []interface{}) ([]interface{}, error) {
    return []interface{}{42}, nil
}

func (n *NeovimMyitcv) subLoop(events chan *neovim.SubscriptionEvent) {
    for {
        select {
        case <-n.client.KillChannel:
            return
        case <-events:
            // Make an API request
            cb, _ := n.client.GetCurrentBuffer()
            bc, _ := cb.GetSlice(0, -1, true, true)

            // in practice we would use bc to do something useful
            // just log for now
            fmt.Fprintf(os.Stderr, "Buffer is: %v\n", bc)
        }
    }
}

func (n *NeovimMyitcv) Shutdown() error {
    return nil
}

To contrast with the Python approach: the main difference is that the API exposed to Go plugin writers is almost identical to that exposed by Neovim, with a few exceptions: calls to vim_subscribe, vim_unsubscribe and vim_register_provider are proxied by the plugin-host. This allows the plugin-host to act as a subscription manager (e.g. if multiple Plugin types subscribe to topic abc, only one call is made to Neovim) and also an MSGPACK-RPC server (handling requests, and routing the requests, in the example above to NeovimMyitcv.getANumber). This keeps things clean and simple.

Discovering and Installing plugins

The various steps I outlined above clearly need to be wrapped up in a toolset of some sort. There are two possible ways of doing this to my mind:

  1. A separate Go (or other) binary
  2. A flag to nvim itself, e.g. nvim --install-plugin ....

Which brings me onto another area I think we need to consider. Are we envisaging a plugin registry of some sort, similar to godoc or Bower (to list just two examples)?

If at least initially we are not, then for simplicity (again initially) I would favour option 1. Roughly then this would look like:

  • One-off install of Go plugin manager: go get github.com/myitcv/neovim-go-plugin-manager
  • Install a plugin: neovim-go-plugin-manager install github.com/myitcv/neovim-go
  • ...
  • Launch Neovim

Implicit in this suggestion: I consider that plugin installation and upgrading needs to happen separately to launching a Neovim instance. Not only will this keep things simple to start with, it will also allow us to be more explicit in case the installation of a plugin fails because of compile errors etc

Plugin dependencies

We haven't dealt with this.... but theoretically one plugin in language X could rely on a plugin written in language Y. Indeed, one Go-based plugins could rely on another in some way. Just sowing this seed of thought for now. Details should probably be handled on another issue.

Plugins and events

You will see something rather ugly in the code above, in the implementation of our new plugin neovim_myitcv:

    topic := "text_changed"
    com := fmt.Sprintf(`au TextChanged,TextChangedI <buffer> call send_event(0, "%v", [])`, topic)
    _ = c.Command(com)

This is the plugin asking Neovim to send_event on TextChanged*. The problem however is that I've had to use channel 0, or broadcast, in my call to send_event. Because, as I alluded to in a previous comment as a plugin I don't know what channel I'm on.

@tarruda - thoughts on how we handle this?

Testing of plugins

With my plugin-per-process approach, writing tests for a Go plugin is quite simple. go test involves an initialisation step that forks a Neovim instance with --embedded-mode. However, if we are moving to a plugin-host architecture, this approach will not work. Indeed I think we will need some changes on the Neovim side to extend --embedded-mode in some way (or maybe we have a different mode altogether). Either way, Neovim needs to be launched in this special mode such that no other plugins are loaded and the plugin communicating over the forked process' stdin/stdout is assumed to be a Go plugin. More thought needed on this... feels like a separate issue.

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 5, 2014

Therefore I envisage us creating Go versions (in some way) of these functions and this function. At least until your thoughts on another approach become more concrete.

The prototype I made can be used for this. As my tests showed, the python commands can be emulated with them. The Go plugin-host would simply have to export equivalent functions (go_execute, go_execute_file, go_do_range, goeval).

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 5, 2014

@fmoralesc - yes indeed. That's how I plan to test this tomorrow.

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 6, 2014

@tarruda - I'm making good process implementing the outline above, but I've come across a couple issues:

  1. Initially Neovim would not talk to me plugin host. Then I told my plugin host to read from stdout and write to the stdin and everything started working. Should these not be the other way round from the plugin writer's perspective?
  2. With point 1 fixed, I can now successfully make a call via my plugin host using scriptcall provided by @fmoralesc (thanks again). The first call appears to succeed (both from a Neovim perspective and also the logs of my plugin-host) but then if I attempt to make the call a second time I get:
Provider for "_init" is not available

Looking at a stack trace of my plugin host, it's ready and waiting for another request/event. So it's not blocked. Which makes me think that the response I sent back to Neovim on the first call was badly formatted in some way, i.e. Neovim is still waiting for me to send more in response to the first call.

The spec is a bit vague on this point so let me summarise what I am sending back. The method in question has a void return type. I therefore send back an array of length 4 that is composed of:

  • The response type: 1
  • The request ID from Neovim:
  • The error value: nil (no error)
  • The result: nil (in this case)

Is Neovim expecting something else?

Indeed any other thoughts on how to debug this?

Thanks

@tarruda

This comment has been minimized.

Copy link
Member

tarruda commented Sep 6, 2014

@fmoralesc

@tarruda I think both models need to be supported.

They will be. It's important to understand that all nvim does is talk with processes that understand msgpack-rpc via stdin/stdout, it has no idea of what plugin hosts are. I'm only arguing that language-specific hosts will let us have a more flexible/scalable/reliable cross-language plugin model.

A question about your scheme: the bootstrapping go program and the go host would be separate, or the same?

Here's how the go bootstrapper might work after channel_from_job is exposed via the API/vimscript:

  • Build the executable containing all discovered plugins
  • Call the exposed channel_from_job function to start a new a new job/channel passing the path of built executable
  • Exit

If it is reexec'd, both programs could (theoretically) be the same.

True but that would probably only work on unix. As far as I know, Windows has no system call for replacing the current process's program image by another

The prototype I made can be used for this. As my tests showed, the python commands can be emulated with them.

It still not clear to me how those ex commands can be used to emulate the classic python ex commands. Remember that vimscript code cannot define commands with starting lowercase letter or that receive heredocs.

@myitcv

Therefore I envisage us creating Go versions (in some way) of these functions and this function. At least until your thoughts on another approach become more concrete.

For now, I'm pretty sure I will expose the channel_from_job function which coupled with send_call/send_event might be all you need to start using go plugins(It will be enough to implement the scheme you suggested of discovering executables that talk via msgpack-rpc)

This is the plugin asking Neovim to send_event on TextChanged*. The problem however is that I've had to use channel 0, or broadcast, in my call to send_event. Because, as I alluded to in a previous comment as a plugin I don't know what channel I'm on.

@tarruda - thoughts on how we handle this?

I considered shipping some vimscript code to take care common support code for event handling via msgpack-rpc, but the idea is not concrete yet. First let me start by clarifying that every plugin can discover it's channel number via the get_api_metadata function(after #1130), and that events via msgpack-rpc may be propagated in one of two ways:

  • Via the send_call function, which blocks nvim until the remote handler has finished executing. This is required to emulate classic synchronous autocommands.
  • Via the send_event function, which does not block or expects a response from clients

So we might have the following vimscript functions defined somewhere in the runtime paths(note that there may vimscript errors, I have not tested):

let s:sync_handlers_for = {}
let s:async_handlers_for = {}

function AutocmdSubscribeSync(name, channel_id)
  if !has_key(s:sync_handlers_for, a:name)
    " If no channel subscribed to this event, setup an autocommand that will
    " take care of propagating the event to all channels in the list
    let s:sync_handlers_for[a:name] = []
    let ausetup = 'au '.name.' for id in s:sync_handlers_for["'.a:name.'"] | call send_call(id, "SyncHandle'.a:name.'") | endfor'
    exe ausetup
  endif
  " Add the channel to the list
  call add(s:sync_handlers_for[a:name], s:channel_id)
endfunction

" Same as above, but for asynchronous event handling
function AutocmdSubscribeAsync(name, channel_id)
  if !has_key(s:async_handlers_for, a:name)
    let s:async_handlers_for[a:name] = []
    let ausetup = 'au '.name.' for id in s:async_handlers_for["'.a:name.'"] | call send_event(id, "AsyncHandle'.a:name.'") | endfor'
    exe ausetup
  endif
  call add(s:async_handlers_for[a:name], s:channel_id)
endfunction

These two functions exist to support subscription of events in the sync/async models across msgpack-rpc. Then, the python plugin host might subscribe it's plugins like this:

# Synchronous event handling
vim.eval('AutocmdSubscribeSync("%s", %d' % ("VimLeavePre", vim.channel_id)))
# After the above, we will receive a msgpack-RPC request for "SyncHandleVimLeavePre" when "VimLeavePre" is triggered.
# Asynchronous event handling
vim.eval('AutocmdSubscribeAsync("%s", %d' % ("VimLeavePre", vim.channel_id)))
# After the above, we will receive a msgpack-RPC notifications for "AsyncHandleVimLeavePre" when "VimLeavePre" is triggered.

If the above wasn't clear let me know so I can try to provide a better explanation. The basic idea is to use vimscript(or even C API functions) to provide common code for subscribing for events. Please note that for the above to work, plugins don't need to know their channel id(only the host does)

@tarruda

This comment has been minimized.

Copy link
Member

tarruda commented Sep 6, 2014

Initially Neovim would not talk to me plugin host. Then I told my plugin host to read from stdout and write to the stdin and everything started working. Should these not be the other way round from the plugin writer's perspective?

I will try to finish #1130 today and expose channel_from_job to the API, after that you can try again with the scheme I explained above:

Here's how the go bootstrapper might work after channel_from_job is exposed via the API/vimscript:

Build the executable containing all discovered plugins
Call the exposed channel_from_job function to start a new a new job/channel passing the path of built executable
Exit

With point 1 fixed, I can now successfully make a call via my plugin host using scriptcall provided by @fmoralesc (thanks again). The first call appears to succeed (both from a Neovim perspective and also the logs of my plugin-host) but then if I attempt to make the call a second time I get:

After I expose channel_from_job(I will squeeze a commit in #1130) , you will be able to use send_call directly instead of scriptcall

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 6, 2014

@tarruda - thanks for explanation.

I can imagine that most (all?) plugins (host-based or otherwise), when first 'connected' to Neovim, will likely want to know their channel ID.

Could we arrive at a protocol whereby once Neovim has 'attached' a plugin, Neovim sends the channel ID to the plugin(-host)?

This could also act as a signal for "ready"

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 6, 2014

It still not clear to me how those ex commands can be used to emulate the classic python ex commands. Remember that vimscript code cannot define commands with starting lowercase letter or that receive heredocs.

@tarruda The ex_script_host_* functions are just a generalization of ex_py*, so they can be used to emulate them (that's how I tested them).

I was not thinking of allowing vimscript defining the :py* commands (or their equivalents), but making neovim create them dynamically on startup/plugin-host registration, for which it might use the ex_script_host functions. I believe this would require some non-trivial changes in the codebase (the assumption of the ex commands being defined statically seems very strong), so I'm not really pushing that idea that hard, but it seems like it would be more elegant than hardcoding commands for especific languages, if they are all supposed to provide the same interface anyway (or a subset of it). A hackier, but potentially simpler way of doing this would be to allow neovim to define commands starting with lowercase internally using ex_command(), or allowing plugin-hosts to do so on initialization (a way to let know neovim it is talking with a plugin-host would be convenient for this).

@fmoralesc

This comment has been minimized.

Copy link
Contributor

fmoralesc commented Sep 6, 2014

After I expose channel_from_job(I will squeeze a commit in #1130) , you will be able to use send_call directly instead of scriptcall

BTW, I would push for the py* and equivalent commands being defined dynamically even it the ex_script_host_* are not used. I'm not lobbying for them ;)

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Sep 6, 2014

@tarruda - here's a quick way to reproduce problem 2 described in this comment

x=`mktemp -d`
export GOPATH=$x
go get github.com/myitcv/neovim
cd $x/src/github.com/myitcv/neovim
git checkout plugin-host
cd _cmd/plugin_host
go build
echo "let &initgo = '$x/src/github.com/myitcv/neovim/_cmd/plugin_host/plugin_host'" >> ~/.nvimrc
  • Now launch Neovim and run:
:scriptcall go_init

This should run with no output and no error. You can check /tmp/neovim_go_plugin_host_PID to verify the Go plugin host at least started. Output should be similar to this

Running the command a second time produces the error (in Neovim):

Provider for "go_init" is not available

Just for reference (fairly certain it won't make a difference):

$ go version
go version go1.3.1 linux/amd64
$ uname -a
Linux myitcv-virtual-machine 3.13.0-24-generic #47-Ubuntu SMP Fri May 2 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
@mikaelj

This comment has been minimized.

Copy link

mikaelj commented Sep 6, 2014

It would (or should!) be easy enough to support plugins that expose a set
of functions according to a well-defined interface using "C" calling
conventions. Just compile your plugin and drop the resulting binary in
~/.nvim/plugins

-Micke

On Fri, Sep 5, 2014 at 5:12 PM, Felipe Morales notifications@github.com
wrote:

@myitcv https://github.com/myitcv Glad the prototype was useful.

Indeed, but for both resource usage sake and simplicity of writing and
distributing plugins, I think a plugin-host approach is better as the
default(Most plugins won't need the isolated environment provided by a
separate process and can happily live with other plugins).

@tarruda https://github.com/tarruda I think both models need to be
supported.

  1. As a plugin junkie myself of sorts, I am not concerned about the case
    where I have 100 processes providing functions for neovim.
  • It is doubtful that many plugins will take the approach described here anyway, so the 100 processes is unrealistic. Most current plugins are either vimscript based or python based and I believe neovim supporting "non-host plugins" won't change this, specially because vim won't support them, and many plugin writers care a great deal about vim compatibillity. Go (and other) plugins using the msgpack-rpc approach are not compatible anyway.

    • As for the performance/resources issue, I believe that to keep
      things sane is not the editor's responsibility, but the users. Neovim can
      (and possibly should) strive to launch as few processes as possible by
      default, but additional stuff falls within the user preferences. Letting
      the users shoot themselves in the foot every once in while is OK, as long
      as they provide their own weapons.

    There could be a lazy execution model for non-host processes, so they
    are only launched when/if the methods they expose are called. This falls
    into package/plugin management, though, and a convention should be created
    for this purpose. But, for example, the hypothetical plugin directory
    could contain files like this:

    {
    name: "neovim-go",
    exec: "plugins/go/neovim-go",
    methods: ["go_fold", "go_highlight"]
    }

    On startup, neovim could register that those methods should start a
    neovim-go instance if none exists (we could make the plugins register
    themselves as active on intialization). These plugins should be provided by
    plugin authors, but could be autogenerated. These metadata files could also
    define whether the plugins should be executed on certain events or for
    certain filetypes (so the first time the methods are called performance is
    not pesimized). Just a few ideas.

  1. Supporting the approach described by @myitcv
    https://github.com/myitcv doesn't preclude developing what you have
    described (which is very cool and neat, btw).

  2. Not every language one would want to write plugins in will support the
    host approach as neatly as python (or dynamic languages in general). What
    if someone wants to write a C plugin (which, admittedly, would be insane,
    but insane is the name of the game for some plugin writers, even for viml)?

A question about your scheme: the bootstrapping go program and the go host
would be separate, or the same? How would the binary be updated? If it is
reexec'd, both programs could (theoretically) be the same.


Reply to this email directly or view it on GitHub
#1136 (comment).

@myitcv

This comment has been minimized.

Copy link
Member

myitcv commented Aug 25, 2015

@tarruda I think this can be closed now. Lots has moved on since this conversation and now that I've picked up the Go host again I will use all of the work on the Python client as a starting point. Agree?

@myitcv myitcv referenced this issue Aug 25, 2015

Open

Complete implementation of plugin host #10

0 of 2 tasks complete

@justinmk justinmk closed this Aug 25, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment