Source code for envisage.plugins.python_shell.view.python_shell_view
# (C) Copyright 2007-2024 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" A view containing an interactive Python shell. """
# Standard library imports.
import logging
import sys
from pyface.api import PythonShell
from pyface.workbench.api import View
from traits.api import Any, Dict, Event, Instance, Property, provides, Str
# Enthought library imports.
from envisage.api import ExtensionPoint, IExtensionRegistry
from envisage.plugins.python_shell.api import IPythonShell
# Setup a logger for this module.
logger = logging.getLogger(__name__)
[docs]class PseudoFile(object):
"""Simulates a normal File object."""
def __init__(self, write):
self.write = write
[docs] def readline(self):
pass
[docs] def writelines(self, lines):
for line in lines:
self.write(line)
[docs] def isatty(self):
return 1
[docs]@provides(IPythonShell)
class PythonShellView(View):
"""A view containing an interactive Python shell."""
#### 'IView' interface ####################################################
# The part's globally unique identifier.
id = "envisage.plugins.python_shell_view"
# The part's name (displayed to the user).
name = "Python"
# The default position of the view relative to the item specified in the
# 'relative_to' trait.
position = "bottom"
#### 'PythonShellView' interface ##########################################
# The interpreter's namespace.
namespace = Property(Dict(Str, Any))
# The names bound in the interpreter's namespace.
names = Property
# Original value for 'sys.stdout':
original_stdout = Any
# Stdout text is posted to this event
stdout_text = Event
#### 'IExtensionPointUser' interface ######################################
# The extension registry that the object's extension points are stored in.
extension_registry = Property(Instance(IExtensionRegistry))
#### Private interface ####################################################
# Bindings.
_bindings = ExtensionPoint(id="envisage.plugins.python_shell.bindings")
# Commands.
_commands = ExtensionPoint(id="envisage.plugins.python_shell.commands")
###########################################################################
# 'IExtensionPointUser' interface.
###########################################################################
def _get_extension_registry(self):
"""Trait property getter."""
return self.window.application
###########################################################################
# 'View' interface.
###########################################################################
[docs] def create_control(self, parent):
"""Creates the toolkit-specific control that represents the view."""
self.shell = shell = PythonShell(parent)
shell.on_trait_change(self._on_key_pressed, "key_pressed")
shell.on_trait_change(self._on_command_executed, "command_executed")
# Write application standard out to this shell instead of to DOS window
self.on_trait_change(
self._on_write_stdout, "stdout_text", dispatch="ui"
)
self.original_stdout = sys.stdout
sys.stdout = PseudoFile(self._write_stdout)
# Namespace contributions.
for bindings in self._bindings:
for name, value in bindings.items():
self.bind(name, value)
for command in self._commands:
self.execute_command(command)
# We take note of the starting set of names and types bound in the
# interpreter's namespace so that we can show the user what they have
# added or removed in the namespace view.
self._namespace_types = set(
(name, type(value)) for name, value in self.namespace.items()
)
# Register the view as a service.
app = self.window.application
self._service_id = app.register_service(IPythonShell, self)
return self.shell.control
[docs] def destroy_control(self):
"""Destroys the toolkit-specific control that represents the view."""
super().destroy_control()
# Unregister the view as a service.
self.window.application.unregister_service(self._service_id)
# Remove the sys.stdout handlers.
self.on_trait_change(self._on_write_stdout, "stdout_text", remove=True)
# Restore the original stdout.
sys.stdout = self.original_stdout
###########################################################################
# 'PythonShellView' interface.
###########################################################################
#### Properties ###########################################################
def _get_namespace(self):
"""Property getter."""
return self.shell.interpreter().locals
def _get_names(self):
"""Property getter."""
return list(self.shell.interpreter().locals.keys())
#### Methods ##############################################################
[docs] def bind(self, name, value):
"""Binds a name to a value in the interpreter's namespace."""
self.shell.bind(name, value)
[docs] def execute_command(self, command, hidden=True):
"""Execute a command in the interpreter."""
return self.shell.execute_command(command, hidden)
[docs] def execute_file(self, path, hidden=True):
"""Execute a command in the interpreter."""
return self.shell.execute_file(path, hidden)
[docs] def lookup(self, name):
"""Returns the value bound to a name in the interpreter's namespace."""
return self.shell.interpreter().locals[name]
###########################################################################
# Private interface.
###########################################################################
def _write_stdout(self, text):
"""Handles text written to stdout."""
self.stdout_text = text
#### Trait change handlers ################################################
def _on_command_executed(self, shell):
"""Dynamic trait change handler."""
if self.control is not None:
# Get the set of tuples of names and types in the current
# namespace.
namespace_types = set(
(name, type(value)) for name, value in self.namespace.items()
)
# Figure out the changes in the namespace, if any.
added = namespace_types.difference(self._namespace_types)
removed = self._namespace_types.difference(namespace_types)
# Cache the new list, to use for comparison next time.
self._namespace_types = namespace_types
# Fire events if there are change.
if len(added) > 0 or len(removed) > 0:
self.trait_property_changed("namespace", {}, self.namespace)
self.trait_property_changed("names", [], self.names)
def _on_key_pressed(self, event):
"""Dynamic trait change handler."""
if event.alt_down and event.key_code == 317:
zoom = self.shell.control.GetZoom()
if zoom != 20:
self.shell.control.SetZoom(zoom + 1)
elif event.alt_down and event.key_code == 319:
zoom = self.shell.control.GetZoom()
if zoom != -10:
self.shell.control.SetZoom(zoom - 1)
def _on_write_stdout(self, text):
"""Dynamic trait change handler."""
self.shell.control.write(text)