Skip to content

Commit 2eba443

Browse files
committed
WIP: Add omnicompletion support
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
1 parent 0912bb9 commit 2eba443

File tree

3 files changed

+125
-33
lines changed

3 files changed

+125
-33
lines changed

plugin/socketrepl.vim

+16
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,22 @@ function! ReadySwitchBufferNS()
191191
endfunction
192192
command! SwitchBufferNS call ReadySwitchBufferNS()
193193

194+
function! socketrepl#omnicomplete(findstart, base)
195+
if a:findstart
196+
let res = rpcrequest(g:nvim_tcp_plugin_channel, 'complete-initial', [])
197+
return l:res
198+
else
199+
echo a:base
200+
let res = rpcrequest(g:nvim_tcp_plugin_channel, 'complete-matches', a:base)
201+
return l:res
202+
endif
203+
endfunction
204+
205+
augroup socketrepl_completion
206+
autocmd!
207+
autocmd FileType clojure setlocal omnifunc=socketrepl#omnicomplete
208+
augroup END
209+
194210
if !exists('g:disable_socket_repl_mappings')
195211
nnoremap K :DocCursor<cr>
196212
nnoremap [d :SourceCursor<cr>

src/socket_repl/socket_repl.clj

+13-9
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

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

2525
(defn connect
2626
"Create a connection to a socket repl."
@@ -93,10 +93,14 @@
9393

9494
(defn new
9595
[]
96-
{:input-channel (async/chan 1024)
97-
:output-channel (async/chan 1024 (map edn/read-string))
98-
:connection (atom {:host nil
99-
:port nil
100-
:socket nil
101-
:reader nil
102-
:print-stream nil})})
96+
(let [output-channel (async/chan
97+
(async/sliding-buffer 1)
98+
(map edn/read-string))]
99+
{:input-channel (async/chan 1024)
100+
:output-channel output-channel
101+
:output-mult (async/mult output-channel)
102+
:connection (atom {:host nil
103+
:port nil
104+
:socket nil
105+
:reader nil
106+
:print-stream nil})}))

src/socket_repl/socket_repl_plugin.clj

+96-24
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Neovim."
44
(:require
55
[clojure.core.async :as async]
6+
[clojure.edn :as edn]
67
[clojure.java.io :as io]
78
[clojure.string :as string]
89
[clojure.tools.namespace.find :as namespace.find]
@@ -28,7 +29,7 @@
2829
(string/join "\n" (map str (.getStackTrace throwable)))
2930
"\n######################\n")))
3031

31-
(defn run-command
32+
(defn run-command-async
3233
[{:keys [nvim nrepl socket-repl]} f]
3334
(fn [msg]
3435
(if-not (or (socket-repl/connected? socket-repl)
@@ -38,7 +39,17 @@
3839
nvim ":echo 'Use :Connect host:port to connect to a socket repl'"))
3940
(async/thread (f msg)))
4041
;; Don't return an async channel, return something msg-pack can serialize.
41-
:done))
42+
"done"))
43+
44+
(defn run-command
45+
[{:keys [nvim nrepl socket-repl]} f]
46+
(fn [msg]
47+
(if-not (or (socket-repl/connected? socket-repl)
48+
(nrepl/connected? nrepl))
49+
(async/thread
50+
(api/command
51+
nvim ":echo 'Use :Connect host:port to connect to a socket repl'"))
52+
(f msg))))
4253

4354
(defn get-rlog-buffer
4455
"Returns the buffer w/ b:rlog set, if one exists."
@@ -88,15 +99,23 @@
8899
(async/>!! (socket-repl/input-channel internal-socket-repl)
89100
'(do
90101
(ns srepl.injection
91-
(:refer-clojure :rename {eval core-eval}))
92-
(defn eval
93-
([form] (eval form nil))
94-
([form options]
95-
(let [result (core-eval form)]
96-
(merge
97-
#:socket-repl{:result (pr-str result)
98-
:form (pr-str form)}
99-
options))))))
102+
(:require
103+
[clojure.repl :as repl]))
104+
105+
(defonce available-plugins (atom {}))
106+
107+
(try
108+
(require '[compliment.core :as compliment])
109+
(swap! available-plugins assoc :compliment true)
110+
(catch Exception e
111+
nil))
112+
113+
(defn completions
114+
[prefix options]
115+
(if
116+
(:compliment @available-plugins)
117+
(compliment/completions prefix options)
118+
(repl/apropos prefix)))))
100119
(catch Throwable t
101120
(log/error t "Error connecting to socket repl")
102121
(async/thread (api/command
@@ -125,7 +144,7 @@
125144
(nvim/register-method!
126145
nvim
127146
"eval"
128-
(run-command
147+
(run-command-async
129148
plugin
130149
(fn [msg]
131150
(try
@@ -142,7 +161,7 @@
142161
(nvim/register-method!
143162
nvim
144163
"eval-form"
145-
(run-command
164+
(run-command-async
146165
plugin
147166
(fn [msg]
148167
(let [[row col] (api-ext/get-cursor-location nvim)
@@ -157,7 +176,7 @@
157176
(nvim/register-method!
158177
nvim
159178
"eval-buffer"
160-
(run-command
179+
(run-command-async
161180
plugin
162181
(fn [msg]
163182
(let [buffer (api/get-current-buf nvim)]
@@ -169,7 +188,7 @@
169188
(nvim/register-method!
170189
nvim
171190
"doc"
172-
(run-command
191+
(run-command-async
173192
plugin
174193
(fn [msg]
175194
(let [code (format "(clojure.repl/doc %s)" (-> msg
@@ -180,7 +199,7 @@
180199
(nvim/register-method!
181200
nvim
182201
"doc-cursor"
183-
(run-command
202+
(run-command-async
184203
plugin
185204
(fn [msg]
186205
(api-ext/get-current-word-async
@@ -192,7 +211,7 @@
192211
(nvim/register-method!
193212
nvim
194213
"source"
195-
(run-command
214+
(run-command-async
196215
plugin
197216
(fn [msg]
198217
(let [code (format "(clojure.repl/source %s)" (-> msg
@@ -203,7 +222,7 @@
203222
(nvim/register-method!
204223
nvim
205224
"source-cursor"
206-
(run-command
225+
(run-command-async
207226
plugin
208227
(fn [msg]
209228
(api-ext/get-current-word-async
@@ -214,8 +233,60 @@
214233

215234
(nvim/register-method!
216235
nvim
217-
"cp"
236+
"complete-initial"
218237
(run-command
238+
plugin
239+
(fn [msg]
240+
(let [line (api/get-current-line nvim)
241+
[cursor-row cursor-col] (api-ext/get-cursor-location nvim)]
242+
(let [start-col (- cursor-col
243+
(->> line
244+
(take cursor-col)
245+
(reverse)
246+
(take-while #(not (#{\ \(} %)))
247+
(count)))]
248+
start-col)))))
249+
250+
(nvim/register-method!
251+
nvim
252+
"complete-matches"
253+
(run-command
254+
plugin
255+
(fn [msg]
256+
(let [word (first (message/params msg))
257+
code-form (str "(srepl.injection/completions "
258+
"\"" word "\" "
259+
"{:ns *ns*})")
260+
res-chan (async/chan 1 (filter #(= (:form %)
261+
code-form)))]
262+
(try
263+
(socket-repl/subscribe-output internal-socket-repl res-chan)
264+
(async/>!! (socket-repl/input-channel internal-socket-repl) code-form)
265+
(let [matches (async/<!! res-chan)
266+
r (map (fn [{:keys [candidate type ns] :as match}]
267+
(log/info (str "Match: " match))
268+
{"word" candidate
269+
"menu" ns
270+
"kind" (case type
271+
:function "f"
272+
:special-form "d"
273+
:class "t"
274+
:local "v"
275+
:keyword "v"
276+
:resource "t"
277+
:namespace "t"
278+
:method "f"
279+
:static-field "m"
280+
"")})
281+
(edn/read-string (:val matches)))]
282+
r)
283+
(finally
284+
(async/close! res-chan)))))))
285+
286+
(nvim/register-method!
287+
nvim
288+
"cp"
289+
(run-command-async
219290
plugin
220291
(fn [msg]
221292
(let [code-form "(map #(.getAbsolutePath %) (clojure.java.classpath/classpath))"]
@@ -230,12 +301,12 @@
230301
(log/info (:ms res))
231302
(log/info (:val res)))
232303
(finally
233-
(.close res-chan)))))))))
304+
(async/close! res-chan)))))))))
234305

235306
(nvim/register-method!
236307
nvim
237308
"switch-buffer-ns"
238-
(run-command
309+
(run-command-async
239310
plugin
240311
(fn [msg]
241312
(let [buffer-name (api/get-current-buf nvim)
@@ -246,12 +317,13 @@
246317
code-form (if eval-entire-declaration?
247318
namespace-declaration
248319
`(clojure.core/in-ns '~(second namespace-declaration)))]
249-
(async/>!! (socket-repl/input-channel socket-repl) code-form)))))
320+
(async/>!! (socket-repl/input-channel socket-repl) code-form)
321+
(async/>!! (socket-repl/input-channel internal-socket-repl) code-form)))))
250322

251323
(nvim/register-method!
252324
nvim
253325
"show-log"
254-
(run-command
326+
(run-command-async
255327
plugin
256328
(fn [msg]
257329
(let [file (-> repl-log repl-log/file .getAbsolutePath)]
@@ -272,7 +344,7 @@
272344
(nvim/register-method!
273345
nvim
274346
"dismiss-log"
275-
(run-command
347+
(run-command-async
276348
plugin
277349
(fn [msg]
278350
(api/command

0 commit comments

Comments
 (0)