mirror of
https://github.com/sstent/sublime-text-3.git
synced 2026-01-25 22:51:41 +00:00
184 lines
7.1 KiB
Python
184 lines
7.1 KiB
Python
from __future__ import absolute_import, unicode_literals, print_function, division
|
|
|
|
import re
|
|
import sublime_plugin
|
|
import sublime
|
|
from collections import defaultdict
|
|
import tempfile
|
|
import binascii
|
|
|
|
try:
|
|
from .sublimerepl import manager, SETTINGS_FILE
|
|
except (ImportError, ValueError):
|
|
from sublimerepl import manager, SETTINGS_FILE
|
|
|
|
|
|
def default_sender(repl, text, view=None):
|
|
repl.write(text)
|
|
|
|
"""Senders is a dict of functions used to transfer text to repl as a repl
|
|
specific load_file action"""
|
|
SENDERS = defaultdict(lambda: default_sender)
|
|
|
|
|
|
def sender(external_id,):
|
|
def wrap(func):
|
|
SENDERS[external_id] = func
|
|
return wrap
|
|
|
|
|
|
@sender("coffee")
|
|
def coffee(repl, text, view=None):
|
|
"""
|
|
use CoffeeScript multiline hack
|
|
http://coffeescript.org/documentation/docs/repl.html
|
|
"""
|
|
default_sender(repl, text.replace("\n", u'\uFF00') + "\n", view)
|
|
|
|
@sender("python")
|
|
def python_sender(repl, text, view=None):
|
|
text_wo_encoding = re.sub(
|
|
pattern=r"#.*coding[:=]\s*([-\w.]+)",
|
|
repl="# <SublimeREPL: encoding comment removed>",
|
|
string=text,
|
|
count=1)
|
|
code = binascii.hexlify(text_wo_encoding.encode("utf-8"))
|
|
execute = ''.join([
|
|
'from binascii import unhexlify as __un; exec(compile(__un("',
|
|
str(code.decode('ascii')),
|
|
'").decode("utf-8"), "<string>", "exec"))\n'
|
|
])
|
|
return default_sender(repl, execute, view)
|
|
|
|
|
|
@sender("ruby")
|
|
def ruby_sender(repl, text, view=None):
|
|
code = binascii.b2a_base64(text.encode("utf-8"))
|
|
payload = "begin require 'base64'; eval(Base64.decode64('%s'), binding=TOPLEVEL_BINDING) end\n" % (code.decode("ascii"),)
|
|
return default_sender(repl, payload, view)
|
|
|
|
|
|
# custom clojure sender that makes sure that all selections are
|
|
# evaluated in the namespace declared by the file they are in
|
|
@sender("clojure")
|
|
def clojure_sender(repl, text, view):
|
|
# call (load-string) instead of just writing the string so
|
|
# that syntax errors are caught and thrown back immediately
|
|
text = '(load-string "' + text.strip().replace('"', r'\"') + '")'
|
|
|
|
# find the first non-commented statement from the start of the file
|
|
namespacedecl = view.find(r"^[^;]*?\(", 0)
|
|
|
|
# if it's a namespace declaration, go search for the namespace name
|
|
if namespacedecl and view.scope_name(namespacedecl.end()-1).startswith("source.clojure meta.function.namespace.clojure"):
|
|
namespacedecl = view.extract_scope(namespacedecl.end()-1)
|
|
|
|
# we're looking for the first symbol within the declaration that
|
|
# looks like a namespace and isn't metadata, a comment, etc.
|
|
pos = namespacedecl.begin() + 3
|
|
while pos < namespacedecl.end():
|
|
# see http://clojure.org/reader for a description of valid
|
|
# namespace names. the inital } or whitespace make sure we're
|
|
# not matching on keywords etc.
|
|
namespace = view.find(r"[\}\s][A-Za-z\_!\?\*\+\-][\w!\?\*\+\-:]*(\.[\w!\?\*\+\-:]+)*", pos)
|
|
|
|
if not namespace:
|
|
# couldn't find the namespace name within the declaration. suspicious.
|
|
break
|
|
elif view.scope_name(namespace.begin() + 1).startswith("source.clojure meta.function.namespace.clojure entity.name.namespace.clojure"):
|
|
# looks alright, we've got our namespace!
|
|
# switch to namespace before executing command
|
|
|
|
# we could do this explicitly by calling (ns), (in-ns) etc:
|
|
# text = "(ns " + view.substr(namespace)[1:] + ") " + text
|
|
# but this would not only result in an extra return value
|
|
# printed to the user, the repl would also remain in that
|
|
# namespace after execution, so instead we do the same thing
|
|
# that swank-clojure does:
|
|
text = "(binding [*ns* (or (find-ns '" + view.substr(namespace)[1:] + ") (find-ns 'user))] " + text + ')'
|
|
# i.e. we temporarily switch to the namespace if it has already
|
|
# been created, otherwise we execute it in 'user. the most
|
|
# elegant option for this would probably be:
|
|
# text = "(binding [*ns* (create-ns '" + view.substr(namespace)[1:] + ")] " + text + ')'
|
|
# but this can lead to problems because of newly created
|
|
# namespaces not automatically referring to clojure.core
|
|
# (see https://groups.google.com/forum/?fromgroups=#!topic/clojure/Th-Bqq68hfo)
|
|
break
|
|
else:
|
|
# false alarm (metadata or a comment), keep looking
|
|
pos = namespace.end()
|
|
return default_sender(repl, text + repl.cmd_postfix, view)
|
|
|
|
class ReplViewWrite(sublime_plugin.TextCommand):
|
|
def run(self, edit, external_id, text):
|
|
for rv in manager.find_repl(external_id):
|
|
rv.append_input_text(text)
|
|
break # send to first repl found
|
|
else:
|
|
sublime.error_message("Cannot find REPL for '{0}'".format(external_id))
|
|
|
|
|
|
class ReplSend(sublime_plugin.TextCommand):
|
|
def run(self, edit, external_id, text, with_auto_postfix=True):
|
|
for rv in manager.find_repl(external_id):
|
|
if with_auto_postfix:
|
|
text += rv.repl.cmd_postfix
|
|
if sublime.load_settings(SETTINGS_FILE).get('show_transferred_text'):
|
|
rv.append_input_text(text)
|
|
rv.adjust_end()
|
|
SENDERS[external_id](rv.repl, text, self.view)
|
|
break
|
|
else:
|
|
sublime.error_message("Cannot find REPL for '{}'".format(external_id))
|
|
|
|
|
|
class ReplTransferCurrent(sublime_plugin.TextCommand):
|
|
def run(self, edit, scope="selection", action="send"):
|
|
text = ""
|
|
if scope == "selection":
|
|
text = self.selected_text()
|
|
elif scope == "lines":
|
|
text = self.selected_lines()
|
|
elif scope == "function":
|
|
text = self.selected_functions()
|
|
elif scope == "block":
|
|
text = self.selected_blocks()
|
|
elif scope == "file":
|
|
text = self.selected_file()
|
|
cmd = "repl_" + action
|
|
self.view.window().run_command(cmd, {"external_id": self.repl_external_id(), "text": text})
|
|
|
|
def repl_external_id(self):
|
|
return self.view.scope_name(0).split(" ")[0].split(".", 1)[1]
|
|
|
|
def selected_text(self):
|
|
v = self.view
|
|
parts = [v.substr(region) for region in v.sel()]
|
|
return "".join(parts)
|
|
|
|
def selected_blocks(self):
|
|
# TODO: Clojure only for now
|
|
v = self.view
|
|
strs = []
|
|
old_sel = list(v.sel())
|
|
v.run_command("expand_selection", {"to": "brackets"})
|
|
v.run_command("expand_selection", {"to": "brackets"})
|
|
for s in v.sel():
|
|
strs.append(v.substr(s))
|
|
v.sel().clear()
|
|
for s in old_sel:
|
|
v.sel().add(s)
|
|
return "\n\n".join(strs)
|
|
|
|
def selected_lines(self):
|
|
v = self.view
|
|
parts = []
|
|
for sel in v.sel():
|
|
for line in v.lines(sel):
|
|
parts.append(v.substr(line))
|
|
return "\n".join(parts)
|
|
|
|
def selected_file(self):
|
|
v = self.view
|
|
return v.substr(sublime.Region(0, v.size()))
|