Ⅰ. 插件描述
Neovim host plugin for Elixir (use https://github.com/awetzel/elixir.nvim for a packaged version)Ⅱ. 基本信息
|
Ⅲ. 安装方法
使用Vundle管理器安装
在你的.vimrc下添加:Plugin 'kbrw/neovim-elixir-he-is-going'
… 然后在Vim中运行以下命令:
:source %
:PluginInstall
对于Vundle版本 < 0.10.2,请用上面的Bundle替换Plugin。
使用NeoBundle管理器安装
在你的.vimrc下添加:NeoBundle 'kbrw/neovim-elixir-he-is-going'
… 然后在Vim中运行以下命令:
:source %
:NeoBundleInstall
使用VimPlug管理器安装
在你的.vimrc下添加:Plug 'kbrw/neovim-elixir-he-is-going'
… 然后在Vim中运行以下命令:
:source %
:PlugInstall
使用Pathogen管理器安装
在终端中运行以下命令:cd ~/.vim/bundle
git clone https://github.com/kbrw/neovim-elixir
Ⅳ. 文档说明
Elixir host for NVim
Instead of this repository, you can directly use
https://github.com/awetzel/elixir.nvim, which packages this host,
a vim plugin with useful functions awetzel/nvim-rplugin, and
add some vim configuration.
Write your Vim plugin in Elixir : Elixir Host for NVim
Firstly, to replace your vim with nvim, not so hard :)
git clone https://github.com/neovim/neovim ; cd neovim ; sudo make install
# add-apt-repository ppa:neovim-ppa/unstable && apt-get update && apt-get install neovimapt-get install
cp -R ~/.vim ~/.config/nvim ; cp ~/.vimrc ~/.config/nvim/init.vim
alias vim=nvimINSTALL this host
Compile the Elixir Host, then copy the vim-elixir-host directory to ~/.nvim :
mix deps.get
MIX_ENV=host mix escript.build
cp -R vim-elixir-host/* ~/.nvim/
# or with pathogen cp -R vim-elixir-host ~/.nvim/bundle/That's it !
You can also use MIX_ENV=debug_host to compile a host plugin which
logs into a ./nvim_debug file and set log level to :debug (see below).
Write a vim Elixir plugin
Before going into a detail, let's see a basic usage example : add
Elixir autocompletion for module and functions, with documentation in
the preview window, in less than 40 LOC.
mkdir -p ~/.nvim/rplugin/elixir
vim ~/.nvim/rplugin/elixir/completion.exdefmodule AutoComplete do
use NVim.Plugin
deffunc elixir_complete("1",_,cursor,line,state), eval: "col('.')", eval: "getline('.')" do
cursor = cursor - 1 # because we are in insert mode
[tomatch] = Regex.run(~r"[\w\.:]*$",String.slice(line,0..cursor-1))
cursor - String.length(tomatch)
end
deffunc elixir_complete(_,base,_,_,state), eval: "col('.')", eval: "getline('.')" do
case (base |> to_char_list |> Enum.reverse |> IEx.Autocomplete.expand) do
{:no,_,_}-> [base] # no expand
{:yes,comp,[]}->["#{base}#{comp}"] #simple expand, no choices
{:yes,_,alts}-> # multiple choices
Enum.map(alts,fn comp->
{base,comp} = {String.replace(base,~r"[^.]*$",""), to_string(comp)}
case Regex.run(~r"^(.*)/([0-9]+)$",comp) do # first see if these choices are module or function
[_,function,arity]-> # it is a function completion
replace = base<>function
module = if String.last(base) == ".", do: Module.concat([String.slice(base,0..-2)]), else: Kernel
if (docs=Code.get_docs(module,:docs)) && (doc=List.keyfind(docs,{:"#{function}",elem(Integer.parse(arity),0)},0)) && (docmd=elem(doc,4)) do
%{"word"=>replace,"kind"=> if(elem(doc,2)==:def, do: "f", else: "m"), "abbr"=>comp,"info"=>docmd}
else
%{"word"=>replace,"abbr"=>comp}
end
nil-> # it is a module completion
module = base<>comp
case Code.get_docs(Module.concat([module]),:moduledoc) do
{_,moduledoc} -> %{"word"=>module,"info"=>moduledoc}
_ -> %{"word"=>module}
end
end
end)
end
end
defautocmd file_type(state), pattern: "elixir", async: true do
{:ok,nil} = NVim.vim_command("filetype plugin on")
{:ok,nil} = NVim.vim_command("set omnifunc=ElixirComplete")
state
end
endAnd then open nvim and execute :UpdateRemotePlugins to update the plugin database.
That's it, just open an elixir file and "CTRL-X CTRL-O" for completion.
Write a compiled vim Elixir plugin
Create any OTP app with a plugin_module (as described below) inside it.
Then create an erlang archive and put it into your rplugin/elixir directory.
mix new myplugin
cd myplugin
vim lib/myplugin.ex
# write your plugin module, like the AutoComplete module below
mix archive.build
cp myplugin-0.0.1.ez ~/.config/nvim/rplugin/elixir/Plugin architecture
But the integration allows much more things, lets look into
details :
A plugin is either:
- an elixir file defining modules in
RUNTIMEPATH/rplugin/elixir,
but only one module must implement thenvim_specsfunction,
it is called the plugin module - an archive
someapp.ezinRUNTIMEPATH/rplugin/elixir
containing an otp app, inside it there must be one and only one
module implementing thenvim_specsfunction, it is called the
plugin module
- an elixir file defining modules in
- The plugin module must implement
child_spec/0returning the
supervisor child specification started on the first plugin call. - The supervision tree must launch in it a GenServer registered
with the name of the _plugin module_, a vim query will trigger a
genserver call {:function|:autocmd|:command,methodname,args}
to this registered process.
- The plugin module must implement
nvim_specs/0returning the
specification of available commands, functions, autocmd with
their options, as describeb in nvim documentation, in order to
define them on the vim side as rpc calls to the host plugin :
(UpdateRemotePlugins).
The code is self explanatory, so you can look at host.ex where
you can see this architecture :
def ensure_plugin(path,plugins) do
case plugins[path] do
nil ->
plugin = case Path.extname(path) do
".ex"->
modules = Code.compile_string(File.read!(path),path) |> Enum.map(&elem(&1,0))
Enum.find(modules,&function_exported?(&1,:nvim_specs,0))
".ez"->
app_version = path |> Path.basename |> Path.rootname
app = app_version |> String.replace(~r/-([0-9]+\.?)+/,"") |> String.to_atom
Code.append_path("#{path}/#{app_version}/ebin")
res = Application.ensure_all_started(app)
{:ok,modules} = :application.get_key(app,:modules)
Enum.each(modules,&Code.ensure_loaded/1)
Application.get_env(app,:nvim_plugin) || (
{:ok,modules} = :application.get_key(app,:modules)
Enum.find(modules,&function_exported?(&1,:nvim_specs,0)))
end
{:ok,_} = Supervisor.start_child NVim.Plugin.Sup, plugin.child_spec
{plugin,Dict.put(plugins,path,plugin)}
plugin -> {plugin,plugins}
end
end
def specs(plugin), do: plugin.nvim_specs
def handle(plugin,[type|name],args), do:
GenServer.call(plugin,{:"#{type}",compose_name(name),args})Main plugin module definition facilities
Use NVim.plugin provides facilities to define the previously described module :
use NVim.plugin:- define a GenServer (
use GenServer) with astart_link
function starting it registered with the plugin module name. - define a default but overridable
child_speclaunching only
this GenServer. - define at the end of the module the
nvim_specsfunction returning@specs import macros
deffunc,defcommand,defautocmdwhich- adds a nvim specification to
@spec - defines a
def handle_callbut rearranging parameters and
wrapping response to make it easier to understand and makes
its definition closer to the corresponding vim definition.
- adds a nvim specification to
- define a GenServer (
So in the end deffunc|defcommand|defautocmd are only `def
handle_call` so you can pattern match and add guards as you want
(see the example of the completion function above). You can addhandle_info, handle_cast or even additional handle_call if
- You can customize the
child_specin order to launch
dependencies, with the only contraint that the new tree must
contains the plugin module GenServer.
Understand deffunc
Todo
Understand defcommand
Todo
Understand defautocmd
Todo
Debugging and Logger
Standard output and input of the neovim host are used to communicate with vim, so
to avoid any freeze, the erlang group_leader (pid where io outputs are send
through a protocol), is set to a sink, so all outputs are ignored.
To allow some debugging and feed back from your plugin, two Logger backends
are provided:
NVim.Loggertakes the first line of a log andechoit to vim.NVim.DebugLoggerappend log to a "./nvim_debug" file (configurable with:debug_logger_fileenv)
Control a nvim instance from Elixir
Connect to a running vim instance using :
iex -S mix nvim.attach "127.0.0.1:7777"
iex -S mix nvim.attach "[::1]:7777"
iex -S mix nvim.attach "/path/to/unix/domain/sock"The argument is where the socket of your nvim instance lies : to
find the current listening socket of your nvim instance, just
read the correct env variable :
"" in vim
:echo $NVIM_LISTEN_ADDRESSBy default this socket is a unix domain socket in a random file,
but you can customize the address at launch (tcp or unix domain socket):
NVIM_LISTEN_ADDRESS="127.0.0.1:7777" nvim
NVIM_LISTEN_ADDRESS="/tmp/mysock" nvimAuto generated API
The module NVim is automatically generated when you attach to
vim using vim_get_api_info.
{:ok,current_line} = NVim.vim_get_current_line
{:ok,current_column} = NVim.vim_eval "col('.')"
NVim.vim_command "echo 'coucou'"The help is also automatically generated
h NVim.vim_del_current_line