Skip to content

Commit

Permalink
WIP: Add omnicompletion support
Browse files Browse the repository at this point in the history
I debated two different approaches:

1. Inject a namespace which loads needed dependencies and uses
`clojure.repl/apropos` as a fallback
2. Use a custom classloader with the classpath of the repl project and
any dependencies

Ultimately I decided against option 2 because it has a lot more upkeep
with regards to dynamic evaluation in the repl; e.g. user evals a new
defn, that won't be picked up (I think?). The other point is that I know
too little about how classloaders work to feel comfortable using a
custom one.

Omnicomplete function requires the plugin to be able to return data to
the client. This was not possible with the async implementation of
`run-command`, so I renamed that to `run-command-async` and added
`run-command` as a sync alternative.

NOTE: This requires clojure-vim/neovim-client#4
  • Loading branch information
mdiin committed May 15, 2018
1 parent 0912bb9 commit 2eba443
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 33 deletions.
16 changes: 16 additions & 0 deletions plugin/socketrepl.vim
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,22 @@ function! ReadySwitchBufferNS()
endfunction
command! SwitchBufferNS call ReadySwitchBufferNS()

function! socketrepl#omnicomplete(findstart, base)
if a:findstart
let res = rpcrequest(g:nvim_tcp_plugin_channel, 'complete-initial', [])
return l:res
else
echo a:base
let res = rpcrequest(g:nvim_tcp_plugin_channel, 'complete-matches', a:base)
return l:res
endif
endfunction

augroup socketrepl_completion
autocmd!
autocmd FileType clojure setlocal omnifunc=socketrepl#omnicomplete
augroup END

if !exists('g:disable_socket_repl_mappings')
nnoremap K :DocCursor<cr>
nnoremap [d :SourceCursor<cr>
Expand Down
22 changes: 13 additions & 9 deletions src/socket_repl/socket_repl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@

(defn subscribe-output
"Pipes the socket repl output to `chan`"
[{:keys [output-channel]} chan]
(async/pipe output-channel chan))
[{:keys [output-mult]} chan]
(async/tap output-mult chan))

(defn connect
"Create a connection to a socket repl."
Expand Down Expand Up @@ -93,10 +93,14 @@

(defn new
[]
{:input-channel (async/chan 1024)
:output-channel (async/chan 1024 (map edn/read-string))
:connection (atom {:host nil
:port nil
:socket nil
:reader nil
:print-stream nil})})
(let [output-channel (async/chan
(async/sliding-buffer 1)
(map edn/read-string))]
{:input-channel (async/chan 1024)
:output-channel output-channel
:output-mult (async/mult output-channel)
:connection (atom {:host nil
:port nil
:socket nil
:reader nil
:print-stream nil})}))
120 changes: 96 additions & 24 deletions src/socket_repl/socket_repl_plugin.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Neovim."
(:require
[clojure.core.async :as async]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.string :as string]
[clojure.tools.namespace.find :as namespace.find]
Expand All @@ -28,7 +29,7 @@
(string/join "\n" (map str (.getStackTrace throwable)))
"\n######################\n")))

(defn run-command
(defn run-command-async
[{:keys [nvim nrepl socket-repl]} f]
(fn [msg]
(if-not (or (socket-repl/connected? socket-repl)
Expand All @@ -38,7 +39,17 @@
nvim ":echo 'Use :Connect host:port to connect to a socket repl'"))
(async/thread (f msg)))
;; Don't return an async channel, return something msg-pack can serialize.
:done))
"done"))

(defn run-command
[{:keys [nvim nrepl socket-repl]} f]
(fn [msg]
(if-not (or (socket-repl/connected? socket-repl)
(nrepl/connected? nrepl))
(async/thread
(api/command
nvim ":echo 'Use :Connect host:port to connect to a socket repl'"))
(f msg))))

(defn get-rlog-buffer
"Returns the buffer w/ b:rlog set, if one exists."
Expand Down Expand Up @@ -88,15 +99,23 @@
(async/>!! (socket-repl/input-channel internal-socket-repl)
'(do
(ns srepl.injection
(:refer-clojure :rename {eval core-eval}))
(defn eval
([form] (eval form nil))
([form options]
(let [result (core-eval form)]
(merge
#:socket-repl{:result (pr-str result)
:form (pr-str form)}
options))))))
(:require
[clojure.repl :as repl]))

(defonce available-plugins (atom {}))

(try
(require '[compliment.core :as compliment])
(swap! available-plugins assoc :compliment true)
(catch Exception e
nil))

(defn completions
[prefix options]
(if
(:compliment @available-plugins)
(compliment/completions prefix options)
(repl/apropos prefix)))))
(catch Throwable t
(log/error t "Error connecting to socket repl")
(async/thread (api/command
Expand Down Expand Up @@ -125,7 +144,7 @@
(nvim/register-method!
nvim
"eval"
(run-command
(run-command-async
plugin
(fn [msg]
(try
Expand All @@ -142,7 +161,7 @@
(nvim/register-method!
nvim
"eval-form"
(run-command
(run-command-async
plugin
(fn [msg]
(let [[row col] (api-ext/get-cursor-location nvim)
Expand All @@ -157,7 +176,7 @@
(nvim/register-method!
nvim
"eval-buffer"
(run-command
(run-command-async
plugin
(fn [msg]
(let [buffer (api/get-current-buf nvim)]
Expand All @@ -169,7 +188,7 @@
(nvim/register-method!
nvim
"doc"
(run-command
(run-command-async
plugin
(fn [msg]
(let [code (format "(clojure.repl/doc %s)" (-> msg
Expand All @@ -180,7 +199,7 @@
(nvim/register-method!
nvim
"doc-cursor"
(run-command
(run-command-async
plugin
(fn [msg]
(api-ext/get-current-word-async
Expand All @@ -192,7 +211,7 @@
(nvim/register-method!
nvim
"source"
(run-command
(run-command-async
plugin
(fn [msg]
(let [code (format "(clojure.repl/source %s)" (-> msg
Expand All @@ -203,7 +222,7 @@
(nvim/register-method!
nvim
"source-cursor"
(run-command
(run-command-async
plugin
(fn [msg]
(api-ext/get-current-word-async
Expand All @@ -214,8 +233,60 @@

(nvim/register-method!
nvim
"cp"
"complete-initial"
(run-command
plugin
(fn [msg]
(let [line (api/get-current-line nvim)
[cursor-row cursor-col] (api-ext/get-cursor-location nvim)]
(let [start-col (- cursor-col
(->> line
(take cursor-col)
(reverse)
(take-while #(not (#{\ \(} %)))
(count)))]
start-col)))))

(nvim/register-method!
nvim
"complete-matches"
(run-command
plugin
(fn [msg]
(let [word (first (message/params msg))
code-form (str "(srepl.injection/completions "
"\"" word "\" "
"{:ns *ns*})")
res-chan (async/chan 1 (filter #(= (:form %)
code-form)))]
(try
(socket-repl/subscribe-output internal-socket-repl res-chan)
(async/>!! (socket-repl/input-channel internal-socket-repl) code-form)
(let [matches (async/<!! res-chan)
r (map (fn [{:keys [candidate type ns] :as match}]
(log/info (str "Match: " match))
{"word" candidate
"menu" ns
"kind" (case type
:function "f"
:special-form "d"
:class "t"
:local "v"
:keyword "v"
:resource "t"
:namespace "t"
:method "f"
:static-field "m"
"")})
(edn/read-string (:val matches)))]
r)
(finally
(async/close! res-chan)))))))

(nvim/register-method!
nvim
"cp"
(run-command-async
plugin
(fn [msg]
(let [code-form "(map #(.getAbsolutePath %) (clojure.java.classpath/classpath))"]
Expand All @@ -230,12 +301,12 @@
(log/info (:ms res))
(log/info (:val res)))
(finally
(.close res-chan)))))))))
(async/close! res-chan)))))))))

(nvim/register-method!
nvim
"switch-buffer-ns"
(run-command
(run-command-async
plugin
(fn [msg]
(let [buffer-name (api/get-current-buf nvim)
Expand All @@ -246,12 +317,13 @@
code-form (if eval-entire-declaration?
namespace-declaration
`(clojure.core/in-ns '~(second namespace-declaration)))]
(async/>!! (socket-repl/input-channel socket-repl) code-form)))))
(async/>!! (socket-repl/input-channel socket-repl) code-form)
(async/>!! (socket-repl/input-channel internal-socket-repl) code-form)))))

(nvim/register-method!
nvim
"show-log"
(run-command
(run-command-async
plugin
(fn [msg]
(let [file (-> repl-log repl-log/file .getAbsolutePath)]
Expand All @@ -272,7 +344,7 @@
(nvim/register-method!
nvim
"dismiss-log"
(run-command
(run-command-async
plugin
(fn [msg]
(api/command
Expand Down

0 comments on commit 2eba443

Please sign in to comment.