|
| 1 | +import uuid |
| 2 | +import threading |
| 3 | +# Adds a git submodule to the import path |
| 4 | +import sys |
| 5 | +import os |
| 6 | +basedir = os.path.dirname(os.path.realpath(__file__)) |
| 7 | +sys.path.append(os.path.join(basedir, "vim_nrepl_python_client/")) |
| 8 | +sys.path.append(os.path.join(basedir, "../../acid")) |
| 9 | + |
| 10 | +try: |
| 11 | + from acid.nvim import localhost, path_to_ns |
| 12 | + from acid.session import SessionHandler, send |
| 13 | + loaded = True |
| 14 | +except: |
| 15 | + loaded = False |
| 16 | + |
| 17 | +from .base import Base # NOQA |
| 18 | +import nrepl # NOQA |
| 19 | + |
| 20 | + |
| 21 | +short_types = { |
| 22 | + "function": "f", |
| 23 | + "macro": "m", |
| 24 | + "var": "v", |
| 25 | + "special-form": "s", |
| 26 | + "class": "c", |
| 27 | + "keyword": "k", |
| 28 | + "local": "l", |
| 29 | + "namespace": "n", |
| 30 | + "field": "i", |
| 31 | + "method": "f", |
| 32 | + "static-field": "i", |
| 33 | + "static-method": "f", |
| 34 | + "resource": "r" |
| 35 | +} |
| 36 | + |
| 37 | + |
| 38 | +def candidate(val): |
| 39 | + arglists = val.get("arglists") |
| 40 | + type = val.get("type") |
| 41 | + return { |
| 42 | + "word": val.get("candidate"), |
| 43 | + "kind": short_types.get(type, type), |
| 44 | + "info": val.get("doc", ""), |
| 45 | + "menu": " ".join(arglists) if arglists else "" |
| 46 | + } |
| 47 | + |
| 48 | + |
| 49 | +def completion_callback(event): |
| 50 | + def handlecompletion(msg, wc, key): |
| 51 | + pass |
| 52 | + return handlecompletion |
| 53 | + |
| 54 | + |
| 55 | +class Source(Base): |
| 56 | + def __init__(self, vim): |
| 57 | + Base.__init__(self, vim) |
| 58 | + self.name = "acid" |
| 59 | + self.mark = "[acid]" |
| 60 | + self.filetypes = ['clojure'] |
| 61 | + self.rank = 200 |
| 62 | + self.__conns = {} |
| 63 | + |
| 64 | + def on_init(self, context): |
| 65 | + if loaded: |
| 66 | + self.acid_sessions = SessionHandler() |
| 67 | + else: |
| 68 | + self.vim.call('echom "Acid.nvim not found. Please install it."') |
| 69 | + self.sessions = {} |
| 70 | + |
| 71 | + def get_wc(self, url): |
| 72 | + return self.acid_sessions.get_or_create(url) |
| 73 | + |
| 74 | + def get_session(self, url, wc): |
| 75 | + if url in self.sessions: |
| 76 | + return self.sessions[url] |
| 77 | + |
| 78 | + session_event = threading.Event() |
| 79 | + |
| 80 | + def clone_handler(msg, wc, key): |
| 81 | + wc.unwatch(key) |
| 82 | + self.sessions[url] = msg['new-session'] |
| 83 | + session_event.set() |
| 84 | + |
| 85 | + wc.watch('dyn-session', {'new-session': None}, clone_handler) |
| 86 | + wc.send({'op': 'clone'}) |
| 87 | + session_event.wait(0.5) |
| 88 | + |
| 89 | + return self.sessions[url] |
| 90 | + |
| 91 | + def gather_candidates(self, context): |
| 92 | + if not loaded: |
| 93 | + return [] |
| 94 | + |
| 95 | + address = localhost(self.vim) |
| 96 | + if address is None: |
| 97 | + return [] |
| 98 | + url = "nrepl://{}:{}".format(*address) |
| 99 | + wc = self.get_wc(url) |
| 100 | + session = self.get_session(url, wc) |
| 101 | + ns = path_to_ns(self.vim) |
| 102 | + |
| 103 | + def global_watch(cmsg, cwc, ckey): |
| 104 | + self.debug("Received message for {}".format(url)) |
| 105 | + self.debug(cmsg) |
| 106 | + |
| 107 | + wc.watch('global_watch', {}, global_watch) |
| 108 | + |
| 109 | + # Should be unique for EVERY message |
| 110 | + msgid = uuid.uuid4().hex |
| 111 | + |
| 112 | + # Perform completion |
| 113 | + completion_event = threading.Event() |
| 114 | + response = None |
| 115 | + |
| 116 | + def completion_callback(cmsg, cwc, ckey): |
| 117 | + nonlocal response |
| 118 | + response = cmsg |
| 119 | + self.debug("Got response {}".format(str(cmsg))) |
| 120 | + completion_event.set() |
| 121 | + |
| 122 | + self.debug("Adding completion watch") |
| 123 | + watcher_key = "{}-completion".format(msgid), |
| 124 | + wc.watch(watcher_key, {"id": msgid}, completion_callback) |
| 125 | + |
| 126 | + # TODO: context for context aware completions |
| 127 | + self.debug("Sending completion op") |
| 128 | + try: |
| 129 | + payload = {"id": msgid, |
| 130 | + "op": "complete", |
| 131 | + "symbol": context["complete_str"], |
| 132 | + "session": session, |
| 133 | + "extra-metadata": ["arglists", "doc"], |
| 134 | + "ns": ns} |
| 135 | + self.debug('Sending payload {}'.format(str(payload))) |
| 136 | + wc.send(payload) |
| 137 | + except BrokenPipeError: |
| 138 | + self.debug("Connection died. Removing the connection.") |
| 139 | + wc.close() # Try and cancel the hanging connection |
| 140 | + del self.acid_sessions.sessions[url] |
| 141 | + |
| 142 | + self.debug("Waiting for completion") |
| 143 | + completion_event.wait(0.5) |
| 144 | + self.debug("Completion event is done!") |
| 145 | + wc.unwatch(watcher_key) |
| 146 | + # Bencode read can return None, e.g. when and empty byte is read |
| 147 | + # from connection. |
| 148 | + if response: |
| 149 | + return [candidate(x) for x in response.get("completions", [])] |
| 150 | + |
| 151 | + self.debug('No answer.') |
| 152 | + return [] |
0 commit comments