neovim-elixir


Ⅰ. 插件描述

Neovim host plugin for Elixir (use https://github.com/awetzel/elixir.nvim for a packaged version)

Ⅱ. 基本信息

创建日期:  2014-12-03
使用用户:  2
Github星:  61
插件作者:  kbrw

Ⅲ. 安装方法

使用Vundle管理器安装

在你的.vimrc下添加:
Plugin 'awetzel/neovim-elixir'
… 然后在Vim中运行以下命令:
:source %
:PluginInstall

对于Vundle版本 < 0.10.2,请用上面的Bundle替换Plugin。

使用NeoBundle管理器安装

在你的.vimrc下添加:
NeoBundle 'awetzel/neovim-elixir'
… 然后在Vim中运行以下命令:
:source %
:NeoBundleInstall

使用VimPlug管理器安装

在你的.vimrc下添加:
Plug 'awetzel/neovim-elixir'
… 然后在Vim中运行以下命令:
:source %
:PlugInstall

使用Pathogen管理器安装

在终端中运行以下命令:
cd ~/.vim/bundle
git clone https://github.com/awetzel/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=nvim

INSTALL 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.ex
defmodule 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
end

And 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 the nvim_specs function,
      it is called the plugin module
    • an archive someapp.ez in RUNTIMEPATH/rplugin/elixir
      containing an otp app, inside it there must be one and only one
      module implementing the nvim_specs function, it is called the
      plugin module
  • The plugin module must implement child_spec/0 returning 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/0 returning 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 a start_link
      function starting it registered with the plugin module name.
    • define a default but overridable child_spec launching only
      this GenServer.
    • define at the end of the module the nvim_specs function returning @specs
    • import macrosdeffunc,defcommand,defautocmd which

      • adds a nvim specification to @spec
      • defines a def handle_call but rearranging parameters and
        wrapping response to make it easier to understand and makes

      its definition closer to the corresponding vim definition.

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 add
handle_info, handle_cast or even additional handle_call if

  1. You can customize the child_spec in 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.Logger takes the first line of a log and echo it to vim.
  • NVim.DebugLogger append log to a "./nvim_debug" file (configurable with :debug_logger_file env)

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_ADDRESS

By 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" nvim

Auto 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

添加新评论