Source code for envisage.plugins.python_shell.view.python_shell_view

# (C) Copyright 2007-2023 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 flush(self): pass
[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)