nrepl.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import sublime, sublime_plugin
  2. import re, socket
  3. arcadia = __import__("arcadia-repl")
  4. bencode = arcadia.bencode
  5. UDP_IP = "127.0.0.1"
  6. UDP_PORT = 3722
  7. sock = None
  8. G = {"prompt": 10, "hist": 0, "namespace": " unknown=>", "active": False}
  9. history = []
  10. print("arcadia-nrepl loaded!")
  11. print("active", G["active"])
  12. def create_repl(window):
  13. current_view = window.active_view()
  14. (group, index) = window.get_view_index(current_view)
  15. repl = window.new_file()
  16. repl.set_name("*arcadia-repl*")
  17. repl.settings().set("arcadia_repl", True)
  18. repl.settings().set("scope_name", "source.arcadia")
  19. repl.settings().set("word_wrap", True)
  20. repl.settings().set("line_numbers", False)
  21. repl.settings().set("gutter", False)
  22. repl.settings().set("word_wrap", False)
  23. repl.set_scratch(True)
  24. repl.set_syntax_file("Packages/arcadia-repl/Clojure.tmLanguage")
  25. window.set_view_index(repl, group + 1, len(window.views_in_group(group + 1)))
  26. window.focus_view(current_view)
  27. send_repl(":ok", False)
  28. history = []
  29. return repl
  30. def get_repl(window):
  31. for v in window.views():
  32. if v.name() == "*arcadia-repl*":
  33. return v
  34. for w in sublime.windows():
  35. for v in w.views():
  36. if v.name() == "*arcadia-repl*":
  37. return v
  38. def view_ns(view):
  39. namespacedecl = view.find(r"^[^;]*?\(", 0)
  40. namespacedecl = view.extract_scope(namespacedecl.end()-1)
  41. pos = namespacedecl.begin() + 3
  42. while pos < namespacedecl.end():
  43. namespace = view.find(r"[\}\s][A-Za-z\_!\?\*\+\-][\w!\?\*\+\-:]*(\.[\w!\?\*\+\-:]+)*", pos)
  44. nsn = view.substr(namespace)[1:]
  45. return nsn
  46. return False
  47. def entered_text(view): return view.substr(sublime.Region(G["prompt"], view.size()))
  48. def _bencode_connect(url, port):
  49. s = socket.create_connection((url, port))
  50. s.setblocking(False)
  51. f = s.makefile('rw')
  52. return bencode.BencodeIO(f, on_close=s.close)
  53. def init_repl():
  54. global G
  55. global sock
  56. G.pop('session', None)
  57. sock = _bencode_connect(UDP_IP, UDP_PORT)
  58. def nrepl_format(s):
  59. #s = s.replace("\n", "\\n")
  60. if 'session' in G:
  61. return {"session": G['session'], "id": "sublime", "op": "eval", "code": s}
  62. else:
  63. return {"id": "sublime", "op": "eval", "code": s}
  64. def send_repl(text, manual):
  65. special = re.search(r'\*[1-9][0-9]*', text)
  66. empty = re.match(r'^\s*$', text)
  67. if empty != None:
  68. text = 'nil'
  69. if text == "":
  70. text = " "
  71. history.append(text)
  72. G["hist"] = 0
  73. elif special:
  74. n = int(special.group()[1:])
  75. if (len(history) >= n):
  76. text = history[len(history) - n]
  77. G["hist"] = 0
  78. elif manual:
  79. history.append(text)
  80. G["hist"] = 0
  81. try:
  82. sock.write(nrepl_format(text))
  83. except ConnectionAbortedError as e:
  84. init_repl()
  85. sock.write(nrepl_format(text))
  86. def update(window):
  87. repl = get_repl(window)
  88. try:
  89. raw = sock.read()
  90. if isinstance(raw, dict):
  91. if 'value' in raw:
  92. v = raw['value']
  93. ns = raw['ns']
  94. G["namespace"] = ns+"=>"
  95. G["session"] = raw['session']
  96. repl.run_command("arcadia_repl_insert", {"data":"\n"+v+"\n"+G["namespace"]})
  97. elif 'err' in raw:
  98. v = raw['err']
  99. G["session"] = raw['session']
  100. repl.run_command("arcadia_repl_insert", {"data":"\n"+v+"\n"+G["namespace"]})
  101. except: None
  102. sublime.set_timeout(lambda: update(window), 100)
  103. def place_cursor_at_end(view):
  104. view.sel().clear()
  105. end_region = sublime.Region(view.size(),view.size())
  106. view.sel().add(end_region)
  107. G["prompt"] = view.size()
  108. view.show(end_region)
  109. class StartNreplReplCommand(sublime_plugin.TextCommand):
  110. def run(self, edit):
  111. G["active"] = True
  112. init_repl()
  113. window = self.view.window()
  114. repl = get_repl(window) or create_repl(window)
  115. window.focus_view(get_repl(window))
  116. update(window)
  117. class PluginEventListener(sublime_plugin.EventListener):
  118. def on_query_context(self, view, key, operator, operand, match_all):
  119. if key == "arcadia_nrepl":
  120. return G["active"] or False
  121. class ArcadiaReplEnterCommand(sublime_plugin.TextCommand):
  122. def run(self, edit):
  123. if not G["active"]: pass
  124. send_repl(entered_text(self.view), True)
  125. class ArcadiaReplInsertCommand(sublime_plugin.TextCommand):
  126. def run(self, edit, data):
  127. if not G["active"]: pass
  128. repl = get_repl(self.view.window())
  129. repl.insert(edit,repl.size(), data)
  130. place_cursor_at_end(repl)
  131. class ArcadiaReplClearCommand(sublime_plugin.TextCommand):
  132. def run(self, edit):
  133. if not G["active"]: pass
  134. repl = get_repl(self.view.window())
  135. repl.replace(edit,sublime.Region(0, repl.size()), G["namespace"])
  136. place_cursor_at_end(repl)
  137. class ArcadiaReplHistoryCommand(sublime_plugin.TextCommand):
  138. def run(self, edit, i=0):
  139. if not G["active"]: pass
  140. repl = get_repl(self.view.window())
  141. if (len(history) * -1) <= G["hist"] + i < 0:
  142. G["hist"] += i
  143. repl.replace(edit,sublime.Region(G["prompt"], repl.size()), history[G["hist"]])
  144. class ArcadiaReplRequireCommand(sublime_plugin.TextCommand):
  145. def run(self, edit):
  146. if not G["active"]: pass
  147. repl = get_repl(self.view.window())
  148. if self.view == repl: return False
  149. nsn = view_ns(self.view)
  150. if nsn:
  151. send_repl("(do (require '"+nsn+ " :reload) (find-ns '"+nsn+ "))", False)
  152. def format_transfered_text(view, text):
  153. if view == get_repl(view.window()):
  154. return text
  155. nsn = view_ns(view)
  156. if nsn:
  157. return "(do (if-not (find-ns '" + nsn + ") (try (require '" + nsn + " :reload) (catch Exception e (ns " + nsn + " )))) (in-ns '" + nsn + ") " + text + ")"
  158. return text
  159. def transfer_naked(view):
  160. view.run_command("expand_selection", {"to": "brackets"})
  161. if view.substr(view.sel()[0]) == "":
  162. _s = view.sel()[0]
  163. view.run_command("expand_selection", {"to": "line"})
  164. send_repl(format_transfered_text(view, view.substr(view.sel()[0])), False)
  165. view.sel().clear()
  166. view.sel().add(_s)
  167. return True
  168. return False
  169. class ArcadiaReplTransferCommand(sublime_plugin.TextCommand):
  170. def run(self, edit, scope="block"):
  171. if not G["active"]: pass
  172. repl = get_repl(self.view.window())
  173. regions, sel = [],[]
  174. for region in self.view.sel(): sel.append(region)
  175. if scope == "file":
  176. send_repl("(do " + self.view.substr(sublime.Region(0, self.view.size())) + ")", False)
  177. return True
  178. if scope == "selection":
  179. for s in sel:
  180. send_repl(format_transfered_text(self.view, self.view.substr(s)), False)
  181. return True
  182. if scope == "block":
  183. if transfer_naked(self.view): return True
  184. if scope == "top-form":
  185. if transfer_naked(self.view): return True
  186. #expand until no change, also make sure sel end is not going backwards
  187. la = self.view.sel()[0].a
  188. lb = self.view.sel()[0].b
  189. for i in range(40):
  190. self.view.run_command("expand_selection", {"to": "brackets"})
  191. if (self.view.sel()[0].b < lb):
  192. self.view.sel().clear()
  193. self.view.sel().add(sublime.Region(la, lb))
  194. break
  195. if (self.view.sel()[0].a == la) & (self.view.sel()[0].b == lb):
  196. break
  197. la = self.view.sel()[0].a
  198. lb = self.view.sel()[0].b
  199. send_repl(format_transfered_text(self.view, self.view.substr(self.view.sel()[0])), False)
  200. self.view.sel().clear()
  201. for region in sel: self.view.sel().add(region)
  202. return True
  203. for idx in range(len(self.view.sel())):
  204. if scope == "block":
  205. regions.append(sublime.Region(self.view.sel()[idx].a - 1, self.view.sel()[idx].b + 1))
  206. for pair in regions:
  207. for text in find_blocks(self.view, pair.a, pair.b):
  208. send_repl(format_transfered_text(self.view, text), False)
  209. self.view.sel().clear()
  210. for region in sel: self.view.sel().add(region)
  211. def find_blocks(view, start, end):
  212. idx, depth = start, 0
  213. regions, res = [], []
  214. while idx < end:
  215. enclosure = view.find(r"\"[^\"]*\"|;[^\n]*\n|\(|\[|\{|\)|\]|\}", idx)
  216. if not enclosure:
  217. break
  218. if view.substr(enclosure) in ("(", "[", "{", "("):
  219. if depth == 0:
  220. if view.substr(sublime.Region(enclosure.a-1, enclosure.a)) == "'":
  221. res.append(enclosure.a-1)
  222. else: res.append(enclosure.a)
  223. depth += 1
  224. elif view.substr(enclosure) in (")", "]", "}"):
  225. depth -= 1
  226. if depth == 0: regions.append(view.substr(sublime.Region(res.pop(), enclosure.b)))
  227. idx = enclosure.end()
  228. return regions