PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/pyface/ui/wx/python_shell.py

https://github.com/enthought/pyface
Python | 357 lines | 220 code | 59 blank | 78 comment | 21 complexity | 2a8dfe191818c99e00f8e85ef2a8fa57 MD5 | raw file
  1. # (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
  2. # All rights reserved.
  3. #
  4. # This software is provided without warranty under the terms of the BSD
  5. # license included in LICENSE.txt and may be redistributed only under
  6. # the conditions described in the aforementioned license. The license
  7. # is also available online at http://www.enthought.com/licenses/BSD.txt
  8. #
  9. # Thanks for using Enthought open source!
  10. """ Enthought pyface package component
  11. """
  12. import builtins
  13. import os
  14. import sys
  15. import types
  16. import warnings
  17. from wx.py.shell import Shell as PyShellBase
  18. import wx
  19. from traits.api import Event, provides
  20. from traits.util.clean_strings import python_name
  21. from pyface.wx.drag_and_drop import PythonDropTarget
  22. from pyface.i_python_shell import IPythonShell, MPythonShell
  23. from pyface.key_pressed_event import KeyPressedEvent
  24. from .layout_widget import LayoutWidget
  25. @provides(IPythonShell)
  26. class PythonShell(MPythonShell, LayoutWidget):
  27. """ The toolkit specific implementation of a PythonShell. See the
  28. IPythonShell interface for the API documentation.
  29. """
  30. # 'IPythonShell' interface ---------------------------------------------
  31. command_executed = Event()
  32. key_pressed = Event(KeyPressedEvent)
  33. # ------------------------------------------------------------------------
  34. # 'object' interface.
  35. # ------------------------------------------------------------------------
  36. # FIXME v3: Either make this API consistent with other Widget sub-classes
  37. # or make it a sub-class of HasTraits.
  38. def __init__(self, parent=None, **traits):
  39. """ Creates a new pager. """
  40. create = traits.pop("create", True)
  41. # Base class constructor.
  42. super().__init__(parent=parent, **traits)
  43. if create:
  44. # Create the toolkit-specific control that represents the widget.
  45. self.create()
  46. warnings.warn(
  47. "automatic widget creation is deprecated and will be removed "
  48. "in a future Pyface version, use create=False and explicitly "
  49. "call create() for future behaviour",
  50. PendingDeprecationWarning,
  51. )
  52. # ------------------------------------------------------------------------
  53. # 'IPythonShell' interface.
  54. # ------------------------------------------------------------------------
  55. def interpreter(self):
  56. return self.control.interp
  57. def execute_command(self, command, hidden=True):
  58. if hidden:
  59. self.control.hidden_push(command)
  60. else:
  61. # Replace the edit area text with command, then run it:
  62. self.control.Execute(command)
  63. def execute_file(self, path, hidden=True):
  64. # Note: The code in this function is largely ripped from IPython's
  65. # Magic.py, FakeModule.py, and iplib.py.
  66. filename = os.path.basename(path)
  67. # Run in a fresh, empty namespace
  68. main_mod = types.ModuleType("__main__")
  69. prog_ns = main_mod.__dict__
  70. prog_ns["__file__"] = filename
  71. prog_ns["__nonzero__"] = lambda: True
  72. # Make sure that the running script gets a proper sys.argv as if it
  73. # were run from a system shell.
  74. save_argv = sys.argv
  75. sys.argv = [filename]
  76. # Make sure that the running script thinks it is the main module
  77. save_main = sys.modules["__main__"]
  78. sys.modules["__main__"] = main_mod
  79. # Redirect sys.std* to control or null
  80. old_stdin = sys.stdin
  81. old_stdout = sys.stdout
  82. old_stderr = sys.stderr
  83. if hidden:
  84. sys.stdin = sys.stdout = sys.stderr = _NullIO()
  85. else:
  86. sys.stdin = sys.stdout = sys.stderr = self.control
  87. # Execute the file
  88. try:
  89. if not hidden:
  90. self.control.clearCommand()
  91. self.control.write('# Executing "%s"\n' % path)
  92. exec(open(path).read(), prog_ns, prog_ns)
  93. if not hidden:
  94. self.control.prompt()
  95. finally:
  96. # Ensure key global stuctures are restored
  97. sys.argv = save_argv
  98. sys.modules["__main__"] = save_main
  99. sys.stdin = old_stdin
  100. sys.stdout = old_stdout
  101. sys.stderr = old_stderr
  102. # Update the interpreter with the new namespace
  103. del prog_ns["__name__"]
  104. del prog_ns["__file__"]
  105. del prog_ns["__nonzero__"]
  106. self.interpreter().locals.update(prog_ns)
  107. def get_history(self):
  108. """ Return the current command history and index.
  109. Returns
  110. -------
  111. history : list of str
  112. The list of commands in the new history.
  113. history_index : int from 0 to len(history)
  114. The current item in the command history navigation.
  115. """
  116. return self.control.history, self.control.historyIndex
  117. def set_history(self, history, history_index):
  118. """ Replace the current command history and index with new ones.
  119. Parameters
  120. ----------
  121. history : list of str
  122. The list of commands in the new history.
  123. history_index : int from 0 to len(history)
  124. The current item in the command history navigation.
  125. """
  126. if not 0 <= history_index <= len(history):
  127. history_index = len(history)
  128. self.control.history = list(history)
  129. self.control.historyIndex = history_index
  130. # ------------------------------------------------------------------------
  131. # 'IWidget' interface.
  132. # ------------------------------------------------------------------------
  133. def _create_control(self, parent):
  134. shell = PyShell(parent, -1)
  135. # Listen for key press events.
  136. shell.Bind(wx.EVT_CHAR, self._wx_on_char)
  137. # Enable the shell as a drag and drop target.
  138. shell.SetDropTarget(PythonDropTarget(self))
  139. # Set up to be notified whenever a Python statement is executed:
  140. shell.handlers.append(self._on_command_executed)
  141. return shell
  142. # ------------------------------------------------------------------------
  143. # 'PythonDropTarget' handler interface.
  144. # ------------------------------------------------------------------------
  145. def on_drop(self, x, y, obj, default_drag_result):
  146. """ Called when a drop occurs on the shell. """
  147. # If we can't create a valid Python identifier for the name of an
  148. # object we use this instead.
  149. name = "dragged"
  150. if (
  151. hasattr(obj, "name")
  152. and isinstance(obj.name, str)
  153. and len(obj.name) > 0
  154. ):
  155. py_name = python_name(obj.name)
  156. # Make sure that the name is actually a valid Python identifier.
  157. try:
  158. if eval(py_name, {py_name: True}):
  159. name = py_name
  160. except:
  161. pass
  162. self.control.interp.locals[name] = obj
  163. self.control.run(name)
  164. self.control.SetFocus()
  165. # We always copy into the shell since we don't want the data
  166. # removed from the source
  167. return wx.DragCopy
  168. def on_drag_over(self, x, y, obj, default_drag_result):
  169. """ Always returns wx.DragCopy to indicate we will be doing a copy."""
  170. return wx.DragCopy
  171. # ------------------------------------------------------------------------
  172. # Private handler interface.
  173. # ------------------------------------------------------------------------
  174. def _wx_on_char(self, event):
  175. """ Called whenever a change is made to the text of the document. """
  176. # This was originally in the python_shell plugin, but is toolkit
  177. # specific.
  178. if event.AltDown() and event.GetKeyCode() == 317:
  179. zoom = self.shell.control.GetZoom()
  180. if zoom != 20:
  181. self.control.SetZoom(zoom + 1)
  182. elif event.AltDown() and event.GetKeyCode() == 319:
  183. zoom = self.shell.control.GetZoom()
  184. if zoom != -10:
  185. self.control.SetZoom(zoom - 1)
  186. self.key_pressed = KeyPressedEvent(
  187. alt_down=event.AltDown() == 1,
  188. control_down=event.ControlDown() == 1,
  189. shift_down=event.ShiftDown() == 1,
  190. key_code=event.GetKeyCode(),
  191. event=event,
  192. )
  193. # Give other event handlers a chance.
  194. event.Skip()
  195. class PyShell(PyShellBase):
  196. def __init__(
  197. self,
  198. parent,
  199. id=-1,
  200. pos=wx.DefaultPosition,
  201. size=wx.DefaultSize,
  202. style=wx.CLIP_CHILDREN,
  203. introText="",
  204. locals=None,
  205. InterpClass=None,
  206. *args,
  207. **kwds
  208. ):
  209. self.handlers = []
  210. # save a reference to the original raw_input() function since
  211. # wx.py.shell dosent reassign it back to the original on destruction
  212. self.raw_input = input
  213. super().__init__(
  214. parent,
  215. id,
  216. pos,
  217. size,
  218. style,
  219. introText,
  220. locals,
  221. InterpClass,
  222. *args,
  223. **kwds
  224. )
  225. def hidden_push(self, command):
  226. """ Send a command to the interpreter for execution without adding
  227. output to the display.
  228. """
  229. wx.BeginBusyCursor()
  230. try:
  231. self.waiting = True
  232. self.more = self.interp.push(command)
  233. self.waiting = False
  234. if not self.more:
  235. self.addHistory(command.rstrip())
  236. for handler in self.handlers:
  237. handler()
  238. finally:
  239. # This needs to be out here to make this works with
  240. # traits.util.refresh.refresh()
  241. wx.EndBusyCursor()
  242. def push(self, command):
  243. """Send command to the interpreter for execution."""
  244. self.write(os.linesep)
  245. self.hidden_push(command)
  246. self.prompt()
  247. def Destroy(self):
  248. """Cleanup before destroying the control...namely, return std I/O and
  249. the raw_input() function back to their rightful owners!
  250. """
  251. self.redirectStdout(False)
  252. self.redirectStderr(False)
  253. self.redirectStdin(False)
  254. builtins.raw_input = self.raw_input
  255. self.destroy()
  256. super().Destroy()
  257. class _NullIO(object):
  258. """ A portable /dev/null for use with PythonShell.execute_file.
  259. """
  260. def tell(self):
  261. return 0
  262. def read(self, n=-1):
  263. return ""
  264. def readline(self, length=None):
  265. return ""
  266. def readlines(self):
  267. return []
  268. def write(self, s):
  269. pass
  270. def writelines(self, list):
  271. pass
  272. def isatty(self):
  273. return 0
  274. def flush(self):
  275. pass
  276. def close(self):
  277. pass
  278. def seek(self, pos, mode=0):
  279. pass