-
Notifications
You must be signed in to change notification settings - Fork 38
/
local_bindings.clj
116 lines (97 loc) · 4.28 KB
/
local_bindings.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
(ns compliment.sources.local-bindings
"Completion source for local bindings introduced by defn, let and the like."
(:require [compliment.sources :refer [defsource]]
[compliment.sources.ns-mappings :refer [var-symbol? dash-matches?]]))
(def let-like-forms '#{let if-let when-let if-some when-some loop with-open
dotimes with-local-vars})
(def defn-like-forms '#{defn defn- fn defmacro defmethod})
(def doseq-like-forms '#{doseq for})
(def letfn-like-forms '#{letfn})
(def destructuring-keys #{:keys :strs :syms})
(def destructuring-key-names (into #{} (map name destructuring-keys)))
(defn parse-binding
"Given a binding node returns the list of local bindings introduced by that
node. Handles vector and map destructuring."
[binding-node]
(cond (vector? binding-node)
(mapcat parse-binding binding-node)
(map? binding-node)
(let [normal-binds (->> (keys binding-node)
(remove keyword?)
(mapcat parse-binding))
keys-binds (->> binding-node
(mapcat (fn [[k v]]
(when (or (destructuring-keys k)
(and (keyword? k) (destructuring-key-names (name k))))
v)))
(map #(if (keyword? %) (symbol (name %)) %)))
as-bind (:as binding-node)]
(cond-> (concat normal-binds keys-binds)
as-bind (conj as-bind)))
(not (#{'& '_} binding-node))
[binding-node]))
(defn parse-fn-body
"Extract function name and arglists from the function body, return list of all
completable variables."
[fn-body]
(let [fn-name (when (symbol? (first fn-body))
(first fn-body))
fn-body (if fn-name (rest fn-body) fn-body)]
(cond->
(mapcat parse-binding
(loop [[c & r] fn-body, bnodes []]
(cond (nil? c) bnodes
(list? c) (recur r (conj bnodes (first c))) ;; multi-arity case
(vector? c) c ;; single-arity case
:else (recur r bnodes))))
fn-name (conj fn-name))))
(defn extract-local-bindings
"When given a form that has a binding vector traverses that binding vector and
returns the list of all local bindings."
[form ns]
(when (list? form)
(let [sym (first form)
locals-meta (when (symbol? sym)
(:completion/locals (meta (ns-resolve ns sym))))]
(cond (or (let-like-forms sym) (= locals-meta :let))
(mapcat parse-binding (take-nth 2 (second form)))
(or (defn-like-forms sym) (= locals-meta :defn))
(parse-fn-body (rest form))
(or (letfn-like-forms sym) (= locals-meta :letfn))
(mapcat parse-fn-body (second form))
(or (doseq-like-forms sym) (= locals-meta :doseq))
(->> (partition 2 (second form))
(mapcat (fn [[left right]]
(if (= left :let)
(take-nth 2 right) [left])))
(mapcat parse-binding))
(= sym 'as->) [(nth form 2)]))))
(defn- distinct-preserve-tags
"Like `distinct` but keeps symbols that have type tag with a higher priority."
[coll]
(->> coll
(sort (fn [x y]
(let [tx (:tag (meta x))
ty (:tag (meta y))]
(cond (and tx (not ty)) -1
(and (not tx) ty) 1
:else 0))))
distinct))
(defn bindings-from-context
"Returns all local bindings that are established inside the given context."
[ctx ns]
(try (->> (mapcat #(extract-local-bindings (:form %) ns) ctx)
(filter symbol?)
distinct-preserve-tags)
(catch Exception ex ())))
(defn candidates
"Returns a list of local bindings inside the context that match prefix."
[prefix ns context]
(when (var-symbol? prefix)
(for [binding (bindings-from-context context ns)
:let [binding (name binding)]
:when (dash-matches? prefix binding)]
{:candidate binding, :type :local})))
(defsource ::local-bindings
:candidates #'candidates
:doc (constantly nil))