Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Dreams come true. Slim and slick python repl

  • Loading branch information...
commit e2b385dd4e0333425aa6efd628c54995d60cd275 1 parent ea15ce7
@baverman baverman authored
View
29 snaked/core/editor.py
@@ -1,12 +1,12 @@
import os.path
import weakref
-import gtk, pango
+import gtk
import gtksourceview2
from uxie.utils import idle
-from .prefs import add_option
+from .prefs import add_option, update_view_preferences
from ..util import save_file, get_project_root
from ..signals import SignalManager, Signal, connect_all, connect_external, weak_connect
@@ -279,30 +279,7 @@ def on_close(self):
self.view.destroy()
def update_view_preferences(self):
- # Try to fix screen flickering
- # No hope, should mail bug to upstream
- #text_style = style_scheme.get_style('text')
- #if text_style and editor.view.window:
- # color = editor.view.get_colormap().alloc_color(text_style.props.background)
- # editor.view.modify_bg(gtk.STATE_NORMAL, color)
-
- pref = self.buffer.config
-
- font = pango.FontDescription(pref['font'])
- self.view.modify_font(font)
-
- self.view.set_auto_indent(pref['auto-indent'])
- self.view.set_indent_on_tab(pref['indent-on-tab'])
- self.view.set_insert_spaces_instead_of_tabs(not pref['use-tabs'])
- self.view.set_smart_home_end(pref['smart-home-end'])
- self.view.set_highlight_current_line(pref['highlight-current-line'])
- self.view.set_show_line_numbers(pref['show-line-numbers'])
- self.view.set_tab_width(pref['tab-width'])
- self.view.set_draw_spaces(pref['show-whitespace'])
- self.view.set_right_margin_position(pref['right-margin'])
- self.view.set_show_right_margin(pref['show-right-margin'])
- self.view.set_wrap_mode(gtk.WRAP_WORD if pref['wrap-text'] else gtk.WRAP_NONE)
- self.view.set_pixels_above_lines(pref['line-spacing'])
+ update_view_preferences(self.view, self.buffer)
@property
def window(self):
View
30 snaked/core/prefs.py
@@ -4,6 +4,8 @@
from itertools import chain
from inspect import cleandoc
+import gtk, pango
+
from uxie.utils import make_missing_dirs, join_to_settings_dir
def init(injector):
@@ -120,6 +122,32 @@ def on_context_saved(editor):
}
}
+def update_view_preferences(view, buf):
+ # Try to fix screen flickering
+ # No hope, should mail bug to upstream
+ #text_style = style_scheme.get_style('text')
+ #if text_style and editor.view.window:
+ # color = editor.view.get_colormap().alloc_color(text_style.props.background)
+ # editor.view.modify_bg(gtk.STATE_NORMAL, color)
+
+ pref = buf.config
+
+ font = pango.FontDescription(pref['font'])
+ view.modify_font(font)
+
+ view.set_auto_indent(pref['auto-indent'])
+ view.set_indent_on_tab(pref['indent-on-tab'])
+ view.set_insert_spaces_instead_of_tabs(not pref['use-tabs'])
+ view.set_smart_home_end(pref['smart-home-end'])
+ view.set_highlight_current_line(pref['highlight-current-line'])
+ view.set_show_line_numbers(pref['show-line-numbers'])
+ view.set_tab_width(pref['tab-width'])
+ view.set_draw_spaces(pref['show-whitespace'])
+ view.set_right_margin_position(pref['right-margin'])
+ view.set_show_right_margin(pref['show-right-margin'])
+ view.set_wrap_mode(gtk.WRAP_WORD if pref['wrap-text'] else gtk.WRAP_NONE)
+ view.set_pixels_above_lines(pref['line-spacing'])
+
def load_json_settings(name, default=None):
filename = get_settings_path(name)
try:
@@ -163,7 +191,7 @@ def add_editor_preferences(on_dialog_created, on_pref_refresh, default_values):
class CompositePreferences(object):
def __init__(self, *prefs):
- self.prefs = prefs
+ self.prefs = list(prefs)
def __getitem__(self, key):
for p in self.prefs:
View
2  snaked/core/window.py
@@ -20,6 +20,8 @@ def init(injector):
injector.add_context('editor-active', 'editor', lambda e: e if e.view.is_focus() else None)
injector.add_context('editor-with-selection', 'editor-active',
lambda e: e if e.buffer.get_has_selection() else None)
+ injector.add_context('textview-active', 'window',
+ lambda w: w.get_focus() if isinstance(w.get_focus(), gtk.TextView) else None)
with injector.on('window', 'editor') as ctx:
ctx.bind_accel('save', 'File/_Save#20', '<ctrl>s', Window.save_editor)
View
150 snaked/plugins/python_repl/__init__.py
@@ -1,50 +1,138 @@
-import gtk
+author = 'Anton Bobrov<bobrov@vl.ru>'
+name = 'Python REPL'
+desc = 'Slim and slick python console'
-class Escape(object): pass
-repl_widget = []
+import os.path
+import gtk.gdk
+import gtksourceview2
+from cPickle import dumps
-def init(manager):
- manager.add_shortcut('python-repl', '<alt>2', 'Window',
- 'Toggle python interactive repl', toggle_repl)
+from snaked.core.prefs import update_view_preferences
+def init(injector):
+ injector.add_context('python-repl', 'editor',
+ lambda e: get_repl_widget(e) if get_repl_widget(e).view.is_focus() else None)
+
+ injector.add_context('python-repl-result-chunk', 'python-repl',
+ lambda p: p if cursor_in_result_chunk(p) else None)
+
+ injector.bind_accel('editor', 'python-repl', 'View/Python console', '<alt>2', toggle_repl)
+ injector.bind_accel(('editor', 'python-repl'), 'python-repl-exec', 'Python/_Execute',
+ '<ctrl>Return', exec_code, 1)
+
+ injector.bind_accel('python-repl-result-chunk', 'python-repl-squash-result-chunk',
+ 'Python/S_quash result chunk', '<ctrl>d', squash_result_chunk)
+
+repl_widget = None
def get_repl_widget(editor):
- try:
- return repl_widget[0]
- except IndexError:
- pass
+ global repl_widget
+ if repl_widget:
+ return repl_widget
- w = create_repl_widget()
- repl_widget.append(w)
+ repl_widget = create_repl_widget(editor)
- editor.add_widget_to_stack(w, on_repl_popup)
- return w
+ editor.window.append_panel(repl_widget).on_activate(lambda p: p.view.grab_focus())
+ return repl_widget
-def create_repl_widget():
+def create_repl_widget(editor):
panel = gtk.ScrolledWindow()
- panel.set_border_width(5)
+ #panel.set_border_width(5)
panel.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- panel.view = gtk.TextView()
- panel.view.set_buffer(gtk.TextBuffer())
+ panel.view = gtksourceview2.View()
+ panel.buffer = gtksourceview2.Buffer()
+ panel.view.set_buffer(panel.buffer)
panel.add(panel.view)
- panel.view.show()
+ panel.view.show_all()
+
+ editor.window.manager.set_buffer_prefs(panel.buffer, '', 'python')
+ panel.buffer.config.prefs.insert(0, {'show-line-numbers':False, 'highlight-current-line':False})
+ update_view_preferences(panel.view, panel.buffer)
+
+ style = panel.buffer.get_style_scheme().get_style('text')
+
+ color = gtk.gdk.color_parse(style.props.background)
+ mul = 1.4 if color.value < 0.5 else 1/1.4
+ color = str(gtk.gdk.color_from_hsv(color.hue, color.saturation, color.value * mul))
+
+ panel.buffer.create_tag('exec-result', editable=False, scale=0.9, indent=20,
+ foreground=style.props.foreground, background=color, background_full_height=True,
+ paragraph_background=color)
return panel
def toggle_repl(editor):
repl = get_repl_widget(editor)
+ editor.window.popup_panel(repl)
+
+server = None
+def get_server_conn(editor):
+ global server
+ if not server:
+ from .executor import run_server
+ from ..python.utils import get_executable
+ root = editor.project_root
+ if not root:
+ root = os.path.dirname(editor.uri)
+ server = run_server(root, get_executable(editor.conf))
+
+ return server
+
+def cursor_in_result_chunk(panel):
+ buf = panel.buffer
+ tag = buf.get_tag_table().lookup('exec-result')
+ cursor = buf.get_iter_at_mark(buf.get_insert())
+ return cursor.has_tag(tag)
+
+def squash_result_chunk(panel):
+ buf = panel.buffer
+ tag = buf.get_tag_table().lookup('exec-result')
+ cursor = buf.get_iter_at_mark(buf.get_insert())
+
+ start = cursor.copy()
+ if not start.toggles_tag(tag):
+ start.backward_to_tag_toggle(tag)
+
+ end = cursor
+ end.forward_to_tag_toggle(tag)
+ buf.begin_user_action()
+ buf.delete(start, end)
+ buf.end_user_action()
+
+def exec_code(editor, panel):
+ buf = panel.buffer
+ tag = buf.get_tag_table().lookup('exec-result')
+
+ cursor = buf.get_iter_at_mark(buf.get_insert())
+ if cursor.has_tag(tag):
+ editor.window.message('You are at result chunk. Nothing to exec', 'warn', parent=panel.view)
+ return True
+
+ start = cursor.copy()
+ if not start.toggles_tag(tag):
+ start.backward_to_tag_toggle(tag)
+
+ end = cursor
+ end.forward_to_tag_toggle(tag)
+
+ source = buf.get_text(start, end).decode('utf-8')
+
+ _, conn = get_server_conn(editor)
+ conn.send_bytes(dumps(('run', source, start.get_line() + 1), 2))
+ result = conn.recv()
+
+ start = end
+ end = start.copy()
+ end.forward_to_tag_toggle(tag)
+
+ buf.begin_user_action()
+ buf.delete(start, end)
- if repl.get_focus_child():
- repl.hide()
- editor.view.grab_focus()
- else:
- editor.popup_widget(repl)
+ if not start.starts_line():
+ buf.insert(start, '\n')
-def hide(editor, widget, escape):
- widget.hide()
- editor.view.grab_focus()
+ if not result.endswith('\n'):
+ result += '\n'
-def on_repl_popup(widget, editor):
- widget.escape = Escape()
- editor.push_escape(hide, widget, widget.escape)
- widget.view.grab_focus()
+ buf.insert_with_tags_by_name(start, result, 'exec-result')
+ buf.end_user_action()
View
136 snaked/plugins/python_repl/executor.py
@@ -1,8 +1,65 @@
import sys
import gc
-
import ast
import types
+import code
+import StringIO
+
+from pickle import dumps
+
+
+class Namespace(object):
+ def __init__(self):
+ self.data = {}
+
+ def __getitem__(self, name):
+ return self.data[name]
+
+ def __setitem__(self, name, value):
+ self.data[name] = value
+
+
+class Interpreter(code.InteractiveInterpreter):
+ def __init__(self):
+ code.InteractiveInterpreter.__init__(self)
+ self.buffer = StringIO.StringIO()
+
+ def execute(self, source, lineno=None):
+ oldstdout = sys.stdout
+ oldstderr = sys.stderr
+ sys.stdout = self.buffer
+ sys.stderr = self.buffer
+ try:
+ tree = ast.parse(source, '<repl>', 'exec')
+ if lineno:
+ ast.increment_lineno(tree, lineno-1)
+
+ for node, etype in break_ast_to_exec_parts(tree):
+ result = eval(compile(node, '<repl>', etype), {}, self.locals)
+ if etype == 'eval':
+ if result is not None:
+ self.write(repr(result) + '\n')
+ self.locals['___'] = self.locals.get('__', None)
+ self.locals['__'] = self.locals.get('_', None)
+ self.locals['_'] = result
+
+ except SystemExit:
+ raise
+ except SyntaxError:
+ self.showsyntaxerror()
+ except:
+ self.showtraceback()
+ finally:
+ sys.stdout = oldstdout
+ sys.stderr = oldstderr
+
+ def write(self, data):
+ self.buffer.write(data)
+
+ def get_buffer(self):
+ data = self.buffer.getvalue()
+ self.buffer = StringIO.StringIO()
+ return data
def execute(code, globals=None, locals=None):
root = ast.parse(code, '<repl>', 'exec')
@@ -13,9 +70,9 @@ def break_ast_to_exec_parts(root):
result = []
for node in root.body:
if isinstance(node, ast.Expr):
- n = ast.Interactive()
- n.body = [node]
- result.append((n, 'single'))
+ n = ast.Expression()
+ n.body = node.value
+ result.append((n, 'eval'))
else:
n = ast.Module()
n.body = [node]
@@ -77,9 +134,76 @@ def get_consts(func, name, result):
result[name + '.' + c.co_name] = c
def trace(obj, level):
- print ' ' * level, obj.__code__.co_name, obj, obj.__module__
+ #print ' ' * level, obj.__code__.co_name, obj, obj.__module__
if obj.__closure__:
for f in obj.__closure__:
f = f.cell_contents
if isinstance(f, types.FunctionType):
- trace(f, level + 1)
+ trace(f, level + 1)
+
+
+def run_server(project_dir, executable=None, env=None):
+ import os.path, time
+ from subprocess import Popen
+ from multiprocessing.connection import Client, arbitrary_address
+
+ addr = arbitrary_address('AF_UNIX')
+
+ executable = executable or sys.executable
+ args = [executable, __file__, addr]
+
+ environ = None
+ if env:
+ environ = os.environ.copy()
+ environ.update(env)
+
+ proc = Popen(args, cwd=project_dir, env=environ)
+ start = time.time()
+ while not os.path.exists(addr):
+ if time.time() - start > 5:
+ raise Exception('py.test launching timeout exceed')
+ time.sleep(0.01)
+
+ conn = Client(addr)
+
+ return proc, conn
+
+
+class Server(object):
+ def __init__(self, conn):
+ self.conn = conn
+ self.interpreter = Interpreter()
+
+ def run(self):
+ conn = self.conn
+ while True:
+ if conn.poll(1):
+ try:
+ args = conn.recv()
+ except EOFError:
+ break
+ except Exception:
+ import traceback
+ traceback.print_exc()
+ break
+
+ if args[0] == 'close':
+ conn.close()
+ break
+ else:
+ self.interpreter.execute(*args[1:])
+ result = self.interpreter.get_buffer()
+ try:
+ self.conn.send_bytes(dumps(result, 2))
+ except:
+ import traceback
+ traceback.print_exc()
+
+
+if __name__ == '__main__':
+ from multiprocessing.connection import Listener
+
+ listener = Listener(sys.argv[1])
+ conn = listener.accept()
+ server = Server(conn)
+ server.run()
View
2  snaked/plugins/search/__init__.py
@@ -20,8 +20,6 @@ def __init__(self, search):
self.search = search
def init(injector):
- injector.add_context('textview-active', 'window',
- lambda w: w.get_focus() if isinstance(w.get_focus(), gtk.TextView) else None)
injector.add_context('search', 'textview-active',
lambda t: t if t in active_widgets or search_selections else None)
View
14 tests/test_python_repl.py
@@ -1,6 +1,7 @@
import sys
+from inspect import cleandoc
-from snaked.plugins.python_repl.executor import execute, patch
+from snaked.plugins.python_repl.executor import execute, patch, Interpreter
def remove_test_modules():
try:
@@ -91,3 +92,14 @@ def inner(x):
result = another_decorated_func(3)
assert result == 36
+
+def test_interpreter():
+ e = Interpreter()
+ e.execute('a = 10; a')
+ e.execute('a + 10')
+ result = e.get_buffer()
+ assert result == '10\n20\n'
+
+ e.execute('a + 20')
+ result = e.get_buffer()
+ assert result == '30\n'
Please sign in to comment.
Something went wrong with that request. Please try again.