I was playing around to try and allow vim access to tab-completions and 
documentation from the julia REPL. I managed to get something that seems to 
work, so I thought I would share it and see if anyone has comments. This is 
a hack, and I'm not a julia, vimscript, or 0mq expert, so I'm sure there 
are better ways to do things. I attached a file of what it looks like in 
action.

The following seems to work. Include this code in the julia REPL:

using ZMQ,JSON

#sends  REPL completions to vim using ZMQ.jl
function zmq_sendcompl(str,zmq_ctx,zmq_s,max_docstring,max_results)
    compl = Base.REPLCompletions.completions(str,length(str))[1]

    Nresults = min(length(compl),max_results)
    #see :help complete-functions in vim for the information that can be 
passed to vim's completion functions
    compldict = Array{Dict{String, String},1}(Nresults)
    for di in 1:Nresults
        if contains(str,".")
            #add text before the ".", otherwise vim will overwrite it
            entry = str[1:rsearchindex(str,".")]*compl[di]
        else
            entry = compl[di]
        end
        if di <= max_docstring
            try
                entrydoc = string(eval(parse("@doc "*entry)))
                compldict[di] =  Dict("word" => entry, "info" => entrydoc)
            catch
                compldict[di] =  Dict("word" => entry, "info" => " ")
            end
        else
            compldict[di] =  Dict("word" => entry, "info" => " ")
        end
    end

    compljson = JSON.json(compldict)
    ZMQ.send(zmq_s,compljson)
end

max_docstring = 50 #maximum number of docstring entries to return (set to 0 
for no docstrings)
max_results = 200 #maximum number of completion results to return
zmq_portnum = 5557

zmq_ctx = Context()
zmq_s = Socket(zmq_ctx, REP)
@async begin
    try
        ZMQ.bind(zmq_s, "tcp://*:" * string(zmq_portnum))

        while true
            msg = ZMQ.recv(zmq_s)
            out = convert(IOStream, msg)
            str = takebuf_string(out)
            zmq_sendcompl(str,zmq_ctx,zmq_s,max_docstring,max_results)
        end
    catch
        warn("ZMQ connection to port ",zmq_portnum," failed")
    end
    ZMQ.close(zmq_s)
    ZMQ.close(zmq_ctx)
end

The code uses ZMQ.jl to listen for a string that represents something that 
vim is trying to complete. It then uses Base.REPLCompletions to get the 
completions. It also gets the docstrings and passes everything back as a 
JSON string. Is there a better way to get the docstrings?

On the vim side, place this code in ~/.vim/ftplugin/julia.vim. This 
requires a couple things:
1) A command to make the 0mq request. The simplest one I could find was 
here: https://github.com/plq/zmq, the zmq executable needs to be in your 
PATH.
2) I used a simple call to netstat to make sure that julia is listening, 
but I'm sure there's a better way to do it. And I'm sure the code for 
finding the beginning of the word could be better.
3) If you want to include local completions (strings in the current file) 
alongside those from the REPL, you need 
https://github.com/vim-scripts/CompleteHelper (which requires 
https://github.com/vim-scripts/ingo-library). To include them, uncomment 
the line that calls CompleteHelper.

let g:zmq_portnum = 5557
>
> if exists('g:JLcompletefunc_loaded')
>     finish
> endif
> let g:JLcompletefunc_loaded = 1
>
> function JLcompletefunc(findstart,base)
>     if a:findstart
>         "find current word
>         let cursorcol = col('.')
>
>         if cursorcol == 1
>             return 0
>         endif
>
>         "get current line
>         let line = getline('.')
>         "only keep up to the cursor position
>         let line = strpart(line,0,cursorcol-1)
>         "replace everything except alphanumeric, underscores, and periods 
> with whitespace
>         let line = substitute(line,"[^.A-za-z0-9]",' ','g')
>         let line = substitute(line,'\^\|\[\|\]',' ','g')
>         let prevchar = matchstr(line, '\%' . (cursorcol-1) . 'c.')
>         if prevchar == ' '
>             return cursorcol
>         end
>
>         let wordstart = cursorcol
>         let linesplit = split(line)
>         if len(linesplit) > 0
>             let curword = linesplit[-1]
>             let wordstart = cursorcol-strlen(curword)-1
>         endif
>
>         if wordstart < 0
>             wordstart = 0
>         endif
>
>         return wordstart
>     else
>         "remove everything except alphanumeric, underscores, and periods
>         let newbase = substitute(a:base,"[^.A-za-z0-9]",'','g')
>         let newbase = substitute(newbase,'\^\|\[\|\]','','g')
>         if (strlen(newbase) == 0) || (newbase == ' ')
>             return ''
>         endif
>
>         "get list of keyword completions from the current file using 
> CompleteHelper (comment out if not desired)
>         let keywordcompl = []
>         "call CompleteHelper#FindMatches(keywordcompl,'\V\<' . 
> escape(newbase, '&#039') . '\k\+',{'complete': '.'})
>
>         "check if ZMQ.jl is listening on g:zmq_portnum, if so request 
> completions
>         let netstatoutput = system('netstat -l | grep ' . g:zmq_portnum)
>         if strlen(netstatoutput) > 0
>             let compljson = system('zmq REQ tcp://localhost:' . 
> g:zmq_portnum .' "' . newbase .'"')
>             let compljson = substitute(compljson,'\n','','')
>             let replcompl = eval(compljson)
>             return replcompl + keywordcompl
>         else
>             return keywordcompl
>         endif
>     endif
> endfunction
>
> setlocal completefunc=JLcompletefunc
>

vim's eval() function works for turning the JSON string into an array of 
dicts.

If everything works you should see completions pop up when you type 
<C-x><C-u> in vim. You can make these available to whatever other 
completion plugins you use (I use supertab). Currently only one julia REPL 
can provide completions at a time, but this could be changed by writing 
some logic to set zmq_portnum in julia and vim.

Here are a couple vimrc options that are helpful:

"close preview window after selecting completion
autocmd CompleteDone * pclose
"hide line numbers in preview window and use github style markdown
"highlighting (requires https://github.com/jtratner/vim-flavored-markdown)
autocmd WinEnter *
  \ if &previewwindow |
  \   set nonumber |
  \   set syntax=ghmarkdown |
  \ endif
"preview window appears on bottom
set splitbelow

Let me know what you think.


Reply via email to