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, ''') . '\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.