diff --git a/spyder/config/main.py b/spyder/config/main.py index e396d87ec24..f775873f1f1 100755 --- a/spyder/config/main.py +++ b/spyder/config/main.py @@ -137,27 +137,6 @@ 'umr/verbose': True, 'umr/namelist': [], }), - ('console', - { - 'max_line_count': 500, - 'wrap': True, - 'single_tab': True, - 'calltips': True, - 'codecompletion/auto': True, - 'codecompletion/enter_key': True, - 'codecompletion/case_sensitive': True, - 'show_elapsed_time': False, - 'show_icontext': False, - 'monitor/enabled': True, - 'qt/api': 'default', - 'matplotlib/backend/value': 0, - 'light_background': True, - 'merge_output_channels': os.name != 'nt', - 'colorize_sys_stderr': os.name != 'nt', - 'pythonstartup/default': True, - 'pythonstartup/custom': False, - 'ets_backend': 'qt4' - }), ('ipython_console', { 'show_banner': True, @@ -659,7 +638,7 @@ # or if you want to *rename* options, then you need to do a MAJOR update in # version, e.g. from 3.0.0 to 4.0.0 # 3. You don't need to touch this value if you're just adding a new option -CONF_VERSION = '37.2.0' +CONF_VERSION = '38.0.0' # Main configuration instance try: diff --git a/spyder/plugins/externalconsole.py b/spyder/plugins/externalconsole.py deleted file mode 100644 index f16a2bb3e6d..00000000000 --- a/spyder/plugins/externalconsole.py +++ /dev/null @@ -1,949 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""External Console plugin""" - -# pylint: disable=C0103 -# pylint: disable=R0903 -# pylint: disable=R0911 -# pylint: disable=R0201 - -# Standard library imports -import os -import os.path as osp -import sys - -# Third party imports -from qtpy.compat import getopenfilename -from qtpy.QtCore import Qt, Signal, Slot -from qtpy.QtWidgets import (QButtonGroup, QGroupBox, QHBoxLayout, QLabel, - QMessageBox, QTabWidget, QVBoxLayout, QWidget) - -# Local imports -from spyder import dependencies -from spyder.config.base import _, SCIENTIFIC_STARTUP -from spyder.config.main import CONF -from spyder.utils import encoding, programs, sourcecode -from spyder.utils import icon_manager as ima -from spyder.utils.misc import (get_error_match, get_python_executable, - is_python_script, remove_backslashes) -from spyder.utils.qthelpers import create_action, mimedata2url -from spyder.api.plugins import SpyderPluginWidget -from spyder.api.preferences import PluginConfigPage -from spyder.plugins.runconfig import get_run_configuration -from spyder.py3compat import to_text_string, is_text_string, getcwd -from spyder.widgets.externalshell.pythonshell import ExternalPythonShell -from spyder.widgets.externalshell.systemshell import ExternalSystemShell -from spyder.widgets.findreplace import FindReplace -from spyder.widgets.tabs import Tabs - - -MPL_REQVER = '>=1.0' -dependencies.add("matplotlib", _("Interactive data plotting in the consoles"), - required_version=MPL_REQVER, optional=True) - - -class ExternalConsoleConfigPage(PluginConfigPage): - - def __init__(self, plugin, parent): - PluginConfigPage.__init__(self, plugin, parent) - self.get_name = lambda: _("Python console") - - def setup_page(self): - interface_group = QGroupBox(_("Interface")) - newcb = self.create_checkbox - singletab_box = newcb(_("One tab per script"), 'single_tab') - showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time') - icontext_box = newcb(_("Show icons and text"), 'show_icontext') - - # Interface Group - interface_layout = QVBoxLayout() - interface_layout.addWidget(singletab_box) - interface_layout.addWidget(showtime_box) - interface_layout.addWidget(icontext_box) - interface_group.setLayout(interface_layout) - - # Source Code Group - display_group = QGroupBox(_("Source code")) - buffer_spin = self.create_spinbox( - _("Buffer: "), _(" lines"), - 'max_line_count', min_=0, max_=1000000, step=100, - tip=_("Set maximum line count")) - wrap_mode_box = newcb(_("Wrap lines"), 'wrap') - merge_channels_box = newcb( - _("Merge process standard output/error channels"), - 'merge_output_channels', - tip=_("Merging the output channels of the process means that\n" - "the standard error won't be written in red anymore,\n" - "but this has the effect of speeding up display.")) - colorize_sys_stderr_box = newcb( - _("Colorize standard error channel using ANSI escape codes"), - 'colorize_sys_stderr', - tip=_("This method is the only way to have colorized standard\n" - "error channel when the output channels have been " - "merged.")) - merge_channels_box.toggled.connect(colorize_sys_stderr_box.setEnabled) - merge_channels_box.toggled.connect(colorize_sys_stderr_box.setChecked) - colorize_sys_stderr_box.setEnabled( - self.get_option('merge_output_channels')) - - display_layout = QVBoxLayout() - display_layout.addWidget(buffer_spin) - display_layout.addWidget(wrap_mode_box) - display_layout.addWidget(merge_channels_box) - display_layout.addWidget(colorize_sys_stderr_box) - display_group.setLayout(display_layout) - - # Background Color Group - bg_group = QGroupBox(_("Background color")) - bg_label = QLabel(_("This option will be applied the next time " - "a Python console or a terminal is opened.")) - bg_label.setWordWrap(True) - lightbg_box = newcb(_("Light background (white color)"), - 'light_background') - bg_layout = QVBoxLayout() - bg_layout.addWidget(bg_label) - bg_layout.addWidget(lightbg_box) - bg_group.setLayout(bg_layout) - - # Advanced settings - source_group = QGroupBox(_("Source code")) - completion_box = newcb(_("Automatic code completion"), - 'codecompletion/auto') - case_comp_box = newcb(_("Case sensitive code completion"), - 'codecompletion/case_sensitive') - comp_enter_box = newcb(_("Enter key selects completion"), - 'codecompletion/enter_key') - calltips_box = newcb(_("Display balloon tips"), 'calltips') - - source_layout = QVBoxLayout() - source_layout.addWidget(completion_box) - source_layout.addWidget(case_comp_box) - source_layout.addWidget(comp_enter_box) - source_layout.addWidget(calltips_box) - source_group.setLayout(source_layout) - - # PYTHONSTARTUP replacement - pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement")) - pystartup_bg = QButtonGroup(pystartup_group) - pystartup_label = QLabel(_("This option will override the " - "PYTHONSTARTUP environment variable which\n" - "defines the script to be executed during " - "the Python console startup.")) - def_startup_radio = self.create_radiobutton( - _("Default PYTHONSTARTUP script"), - 'pythonstartup/default', - button_group=pystartup_bg) - cus_startup_radio = self.create_radiobutton( - _("Use the following startup script:"), - 'pythonstartup/custom', - button_group=pystartup_bg) - pystartup_file = self.create_browsefile('', 'pythonstartup', '', - filters=_("Python scripts")+\ - " (*.py)") - def_startup_radio.toggled.connect(pystartup_file.setDisabled) - cus_startup_radio.toggled.connect(pystartup_file.setEnabled) - - pystartup_layout = QVBoxLayout() - pystartup_layout.addWidget(pystartup_label) - pystartup_layout.addWidget(def_startup_radio) - pystartup_layout.addWidget(cus_startup_radio) - pystartup_layout.addWidget(pystartup_file) - pystartup_group.setLayout(pystartup_layout) - - # Monitor Group - monitor_group = QGroupBox(_("Monitor")) - monitor_label = QLabel(_("The monitor provides introspection " - "features to console: code completion, " - "calltips and variable explorer. " - "Because it relies on several modules, " - "disabling the monitor may be useful " - "to accelerate console startup.")) - monitor_label.setWordWrap(True) - monitor_box = newcb(_("Enable monitor"), 'monitor/enabled') - for obj in (completion_box, case_comp_box, comp_enter_box, - calltips_box): - monitor_box.toggled.connect(obj.setEnabled) - obj.setEnabled(self.get_option('monitor/enabled')) - - monitor_layout = QVBoxLayout() - monitor_layout.addWidget(monitor_label) - monitor_layout.addWidget(monitor_box) - monitor_group.setLayout(monitor_layout) - - # Qt Group - opts = [ - (_("Default library"), 'default'), - ('PyQt5', 'pyqt5'), - ('PyQt4', 'pyqt'), - ('PySide', 'pyside'), - ] - qt_group = QGroupBox(_("Qt-Python Bindings")) - qt_setapi_box = self.create_combobox( - _("Library:") + " ", opts, - 'qt/api', default='default', - tip=_("This option will act on
" - "libraries such as Matplotlib, guidata " - "or ETS")) - - qt_layout = QVBoxLayout() - qt_layout.addWidget(qt_setapi_box) - qt_group.setLayout(qt_layout) - - # Matplotlib Group - mpl_group = QGroupBox(_("Graphics")) - mpl_label = QLabel(_("Decide which backend to use to display graphics. " - "If unsure, please select the Automatic " - "backend.

" - "Note: We support a very limited number " - "of backends in our Python consoles. If you " - "prefer to work with a different one, please use " - "an IPython console.")) - mpl_label.setWordWrap(True) - - backends = [(_("Automatic"), 0), (_("None"), 1)] - if not os.name == 'nt' and programs.is_module_installed('_tkinter'): - backends.append( ("Tkinter", 2) ) - backends = tuple(backends) - - mpl_backend_box = self.create_combobox( _("Backend:")+" ", backends, - 'matplotlib/backend/value', - tip=_("This option will be applied the " - "next time a console is opened.")) - - mpl_installed = programs.is_module_installed('matplotlib') - mpl_layout = QVBoxLayout() - mpl_layout.addWidget(mpl_label) - mpl_layout.addWidget(mpl_backend_box) - mpl_group.setLayout(mpl_layout) - mpl_group.setEnabled(mpl_installed) - - # ETS Group - ets_group = QGroupBox(_("Enthought Tool Suite")) - ets_label = QLabel(_("Enthought Tool Suite (ETS) supports " - "PyQt4 (qt4) and wxPython (wx) graphical " - "user interfaces.")) - ets_label.setWordWrap(True) - ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend', - alignment=Qt.Horizontal) - - ets_layout = QVBoxLayout() - ets_layout.addWidget(ets_label) - ets_layout.addWidget(ets_edit) - ets_group.setLayout(ets_layout) - - if CONF.get('main_interpreter','default'): - interpreter = get_python_executable() - else: - interpreter = CONF.get('main_interpreter', 'executable') - ets_group.setEnabled(programs.is_module_installed( - "enthought.etsconfig.api", - interpreter=interpreter)) - - tabs = QTabWidget() - tabs.addTab(self.create_tab(interface_group, display_group, - bg_group), - _("Display")) - tabs.addTab(self.create_tab(monitor_group, source_group), - _("Introspection")) - tabs.addTab(self.create_tab(pystartup_group), _("Advanced settings")) - tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group), - _("External modules")) - - vlayout = QVBoxLayout() - vlayout.addWidget(tabs) - self.setLayout(vlayout) - - -class ExternalConsole(SpyderPluginWidget): - """ - Console widget - """ - CONF_SECTION = 'console' - CONFIGWIDGET_CLASS = ExternalConsoleConfigPage - DISABLE_ACTIONS_WHEN_HIDDEN = False - - edit_goto = Signal((str, int, str), (str, int, str, bool)) - focus_changed = Signal() - redirect_stdio = Signal(bool) - - def __init__(self, parent): - SpyderPluginWidget.__init__(self, parent) - - self.tabwidget = None - self.menu_actions = None - - self.historylog = None # History log plugin - - self.python_count = 0 - self.terminal_count = 0 - - # Python startup file selection - if not osp.isfile(self.get_option('pythonstartup', '')): - self.set_option('pythonstartup', SCIENTIFIC_STARTUP) - # default/custom settings are mutually exclusive: - self.set_option('pythonstartup/custom', - not self.get_option('pythonstartup/default')) - - self.shellwidgets = [] - self.filenames = [] - self.icons = [] - self.runfile_args = "" - - # Initialize plugin - self.initialize_plugin() - - layout = QVBoxLayout() - self.tabwidget = Tabs(self, self.menu_actions) - if hasattr(self.tabwidget, 'setDocumentMode')\ - and not sys.platform == 'darwin': - # Don't set document mode to true on OSX because it generates - # a crash when the console is detached from the main window - # Fixes Issue 561 - self.tabwidget.setDocumentMode(True) - self.tabwidget.currentChanged.connect(self.refresh_plugin) - self.tabwidget.move_data.connect(self.move_tab) - self.main.sig_pythonpath_changed.connect(self.set_path) - - self.tabwidget.set_close_function(self.close_console) - - if sys.platform == 'darwin': - tab_container = QWidget() - tab_container.setObjectName('tab-container') - tab_layout = QHBoxLayout(tab_container) - tab_layout.setContentsMargins(0, 0, 0, 0) - tab_layout.addWidget(self.tabwidget) - layout.addWidget(tab_container) - else: - layout.addWidget(self.tabwidget) - - # Find/replace widget - self.find_widget = FindReplace(self) - self.find_widget.hide() - self.register_widget_shortcuts(self.find_widget) - - layout.addWidget(self.find_widget) - - self.setLayout(layout) - - # Accepting drops - self.setAcceptDrops(True) - - def move_tab(self, index_from, index_to): - """ - Move tab (tabs themselves have already been moved by the tabwidget) - """ - filename = self.filenames.pop(index_from) - shell = self.shellwidgets.pop(index_from) - icons = self.icons.pop(index_from) - - self.filenames.insert(index_to, filename) - self.shellwidgets.insert(index_to, shell) - self.icons.insert(index_to, icons) - self.sig_update_plugin_title.emit() - - def get_shell_index_from_id(self, shell_id): - """Return shellwidget index from id""" - for index, shell in enumerate(self.shellwidgets): - if id(shell) == shell_id: - return index - - def close_console(self, index=None): - """Close console tab from index or widget (or close current tab)""" - # Get tab index - if not self.tabwidget.count(): - return - if index is None: - index = self.tabwidget.currentIndex() - - # Closing logic - self.tabwidget.widget(index).close() - self.tabwidget.removeTab(index) - self.filenames.pop(index) - self.shellwidgets.pop(index) - self.icons.pop(index) - self.sig_update_plugin_title.emit() - self.update_tabs_text() - - def set_path(self): - """Set consoles PYTHONPATH if changed by the user""" - from spyder.widgets.externalshell import pythonshell - for sw in self.shellwidgets: - if isinstance(sw, pythonshell.ExternalPythonShell): - if sw.is_interpreter and sw.is_running(): - sw.path = self.main.get_spyder_pythonpath() - sw.shell.path = sw.path - - def __find_python_shell(self, interpreter_only=False): - current_index = self.tabwidget.currentIndex() - if current_index == -1: - return - from spyder.widgets.externalshell import pythonshell - for index in [current_index]+list(range(self.tabwidget.count())): - shellwidget = self.tabwidget.widget(index) - if isinstance(shellwidget, pythonshell.ExternalPythonShell): - if interpreter_only and not shellwidget.is_interpreter: - continue - elif not shellwidget.is_running(): - continue - else: - self.tabwidget.setCurrentIndex(index) - return shellwidget - - def get_current_shell(self): - """ - Called by Help to retrieve the current shell instance - """ - shellwidget = self.__find_python_shell() - return shellwidget.shell - - def get_running_python_shell(self): - """ - Called by Help to retrieve a running Python shell instance - """ - current_index = self.tabwidget.currentIndex() - if current_index == -1: - return - from spyder.widgets.externalshell import pythonshell - shellwidgets = [self.tabwidget.widget(index) - for index in range(self.tabwidget.count())] - shellwidgets = [_w for _w in shellwidgets - if isinstance(_w, pythonshell.ExternalPythonShell) \ - and _w.is_running()] - if shellwidgets: - # First, iterate on interpreters only: - for shellwidget in shellwidgets: - if shellwidget.is_interpreter: - return shellwidget.shell - else: - return shellwidgets[0].shell - - def run_script_in_current_shell(self, filename, wdir, args, debug, - post_mortem): - """Run script in current shell, if any""" - norm = lambda text: remove_backslashes(to_text_string(text)) - line = "%s('%s'" % ('debugfile' if debug else 'runfile', - norm(filename)) - if args: - line += ", args='%s'" % norm(args) - if wdir: - line += ", wdir='%s'" % norm(wdir) - if post_mortem: - line += ', post_mortem=True' - line += ")" - if not self.execute_code(line, interpreter_only=True): - QMessageBox.warning(self, _('Warning'), - _("No Python console is currently selected to run %s." - "

Please select or open a new Python console " - "and try again." - ) % osp.basename(norm(filename)), QMessageBox.Ok) - else: - self.visibility_changed(True) - self.raise_() - - def set_current_shell_working_directory(self, directory): - """Set current shell working directory""" - shellwidget = self.__find_python_shell() - if shellwidget is not None: - directory = encoding.to_unicode_from_fs(directory) - shellwidget.shell.set_cwd(directory) - - def execute_code(self, lines, interpreter_only=False): - """Execute code in an already opened Python interpreter""" - shellwidget = self.__find_python_shell( - interpreter_only=interpreter_only) - if shellwidget is not None: - shellwidget.shell.execute_lines(to_text_string(lines)) - self.activateWindow() - shellwidget.shell.setFocus() - return True - else: - return False - - def pdb_has_stopped(self, fname, lineno, shellwidget): - """Python debugger has just stopped at frame (fname, lineno)""" - # This is a unique form of the edit_goto signal that is intended to - # prevent keyboard input from accidentally entering the editor - # during repeated, rapid entry of debugging commands. - self.edit_goto[str, int, str, bool].emit(fname, lineno, '', False) - self.activateWindow() - shellwidget.shell.setFocus() - - def set_spyder_breakpoints(self): - """Set all Spyder breakpoints into all shells""" - for shellwidget in self.shellwidgets: - shellwidget.shell.set_spyder_breakpoints() - - def start(self, fname, wdir=None, args='', interact=False, debug=False, - python=True, python_args='', post_mortem=True): - """ - Start new console - - fname: - string: filename of script to run - None: open an interpreter - wdir: working directory - args: command line options of the Python script - interact: inspect script interactively after its execution - debug: run pdb - python: True: Python interpreter, False: terminal - python_args: additionnal Python interpreter command line options - (option "-u" is mandatory, see widgets.externalshell package) - """ - # Note: fname is None <=> Python interpreter - if fname is not None and not is_text_string(fname): - fname = to_text_string(fname) - if wdir is not None and not is_text_string(wdir): - wdir = to_text_string(wdir) - - if fname is not None and fname in self.filenames: - index = self.filenames.index(fname) - if self.get_option('single_tab'): - old_shell = self.shellwidgets[index] - if old_shell.is_running(): - runconfig = get_run_configuration(fname) - if runconfig is None or runconfig.show_kill_warning: - if PYQT5: - answer = QMessageBox.question(self, self.get_plugin_title(), - _("%s is already running in a separate process.\n" - "Do you want to kill the process before starting " - "a new one?") % osp.basename(fname), - QMessageBox.Yes | QMessageBox.Cancel) - else: - mb = QMessageBox(self) - answer = mb.question(mb, self.get_plugin_title(), - _("%s is already running in a separate process.\n" - "Do you want to kill the process before starting " - "a new one?") % osp.basename(fname), - QMessageBox.Yes | QMessageBox.Cancel) - else: - answer = QMessageBox.Yes - - if answer == QMessageBox.Yes: - old_shell.process.kill() - old_shell.process.waitForFinished() - else: - return - self.close_console(index) - else: - index = self.tabwidget.count() - - # Creating a new external shell - pythonpath = self.main.get_spyder_pythonpath() - light_background = self.get_option('light_background') - show_elapsed_time = self.get_option('show_elapsed_time') - if python: - if CONF.get('main_interpreter', 'default'): - pythonexecutable = get_python_executable() - external_interpreter = False - else: - pythonexecutable = CONF.get('main_interpreter', 'executable') - external_interpreter = True - if self.get_option('pythonstartup/default'): - pythonstartup = None - else: - pythonstartup = self.get_option('pythonstartup', None) - monitor_enabled = self.get_option('monitor/enabled') - mpl_backend = self.get_option('matplotlib/backend/value') - ets_backend = self.get_option('ets_backend') - qt_api = self.get_option('qt/api') - if qt_api not in ('pyqt', 'pyside', 'pyqt5'): - qt_api = None - merge_output_channels = self.get_option('merge_output_channels') - colorize_sys_stderr = self.get_option('colorize_sys_stderr') - umr_enabled = CONF.get('main_interpreter', 'umr/enabled') - umr_namelist = CONF.get('main_interpreter', 'umr/namelist') - umr_verbose = CONF.get('main_interpreter', 'umr/verbose') - - sa_settings = None - shellwidget = ExternalPythonShell(self, fname, wdir, - interact, debug, post_mortem=post_mortem, - path=pythonpath, - python_args=python_args, - arguments=args, stand_alone=sa_settings, - pythonstartup=pythonstartup, - pythonexecutable=pythonexecutable, - external_interpreter=external_interpreter, - umr_enabled=umr_enabled, umr_namelist=umr_namelist, - umr_verbose=umr_verbose, ets_backend=ets_backend, - monitor_enabled=monitor_enabled, - mpl_backend=mpl_backend, qt_api=qt_api, - merge_output_channels=merge_output_channels, - colorize_sys_stderr=colorize_sys_stderr, - light_background=light_background, - menu_actions=self.menu_actions, - show_buttons_inside=False, - show_elapsed_time=show_elapsed_time) - shellwidget.sig_pdb.connect( - lambda fname, lineno, shellwidget=shellwidget: - self.pdb_has_stopped(fname, lineno, shellwidget)) - self.register_widget_shortcuts(shellwidget.shell) - else: - if os.name == 'posix': - cmd = 'gnome-terminal' - args = [] - if programs.is_program_installed(cmd): - if wdir: - args.extend(['--working-directory=%s' % wdir]) - programs.run_program(cmd, args) - return - cmd = 'konsole' - if programs.is_program_installed(cmd): - if wdir: - args.extend(['--workdir', wdir]) - programs.run_program(cmd, args) - return - shellwidget = ExternalSystemShell(self, wdir, path=pythonpath, - light_background=light_background, - menu_actions=self.menu_actions, - show_buttons_inside=False, - show_elapsed_time=show_elapsed_time) - - # Code completion / calltips - shellwidget.shell.setMaximumBlockCount( - self.get_option('max_line_count') ) - shellwidget.shell.set_font( self.get_plugin_font() ) - shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') ) - shellwidget.shell.set_calltips( self.get_option('calltips') ) - shellwidget.shell.set_codecompletion_auto( - self.get_option('codecompletion/auto') ) - shellwidget.shell.set_codecompletion_case( - self.get_option('codecompletion/case_sensitive') ) - shellwidget.shell.set_codecompletion_enter( - self.get_option('codecompletion/enter_key') ) - if self.historylog is not None: - self.historylog.add_history(shellwidget.shell.history_filename) - shellwidget.shell.append_to_history.connect( - self.historylog.append_to_history) - shellwidget.shell.go_to_error.connect(self.go_to_error) - shellwidget.shell.focus_changed.connect( - lambda: self.focus_changed.emit()) - if python: - if self.main.editor is not None: - shellwidget.open_file.connect(self.open_file_in_spyder) - if fname is None: - self.python_count += 1 - tab_name = "Python %d" % self.python_count - tab_icon1 = ima.icon('python') - tab_icon2 = ima.icon('python_t') - self.filenames.insert(index, fname) - else: - self.filenames.insert(index, fname) - tab_name = self.get_tab_text(fname) - self.update_tabs_text() - tab_icon1 = ima.icon('run') - tab_icon2 = ima.icon('terminated') - else: - fname = id(shellwidget) - if os.name == 'nt': - tab_name = _("Command Window") - else: - tab_name = _("Terminal") - self.terminal_count += 1 - tab_name += (" %d" % self.terminal_count) - tab_icon1 = ima.icon('cmdprompt') - tab_icon2 = ima.icon('cmdprompt_t') - self.filenames.insert(index, fname) - self.shellwidgets.insert(index, shellwidget) - self.icons.insert(index, (tab_icon1, tab_icon2)) - if index is None: - index = self.tabwidget.addTab(shellwidget, tab_name) - else: - self.tabwidget.insertTab(index, shellwidget, tab_name) - - shellwidget.started.connect( - lambda sid=id(shellwidget): self.process_started(sid)) - shellwidget.sig_finished.connect( - lambda sid=id(shellwidget): self.process_finished(sid)) - self.find_widget.set_editor(shellwidget.shell) - self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir) - self.tabwidget.setCurrentIndex(index) - if self.dockwidget and not self.ismaximized: - self.dockwidget.setVisible(True) - self.dockwidget.raise_() - - shellwidget.set_icontext_visible(self.get_option('show_icontext')) - - # Start process and give focus to console - shellwidget.start_shell() - - def open_file_in_spyder(self, fname, lineno): - """Open file in Spyder's editor from remote process""" - self.main.editor.activateWindow() - self.main.editor.raise_() - self.main.editor.load(fname, lineno) - - def get_tab_text(self, fname): - """Get tab text without ambiguation.""" - files_path_list = [filename for filename in self.filenames - if filename is not None] - return sourcecode.get_file_title(files_path_list, fname) - - def update_tabs_text(self): - """Update the text from the tabs.""" - for index, fname in enumerate(self.filenames): - if fname is not None: - self.tabwidget.setTabText(index, self.get_tab_text(fname)) - - #------ Private API ------------------------------------------------------- - def process_started(self, shell_id): - index = self.get_shell_index_from_id(shell_id) - shell = self.shellwidgets[index] - icon, _icon = self.icons[index] - self.tabwidget.setTabIcon(index, icon) - - def process_finished(self, shell_id): - index = self.get_shell_index_from_id(shell_id) - if index is not None: - # Not sure why it happens, but sometimes the shellwidget has - # already been removed, so that's not bad if we can't change - # the tab icon... - _icon, icon = self.icons[index] - self.tabwidget.setTabIcon(index, icon) - - #------ SpyderPluginWidget API -------------------------------------------- - def get_plugin_title(self): - """Return widget title""" - title = _('Python console') - return title - - def get_plugin_icon(self): - """Return widget icon.""" - return ima.icon('console') - - def get_focus_widget(self): - """Return the widget to give focus to.""" - return self.tabwidget.currentWidget() - - def get_plugin_actions(self): - """Return a list of actions related to plugin""" - interpreter_action = create_action(self, - _("Open a &Python console"), None, - ima.icon('python'), - triggered=self.open_interpreter) - if os.name == 'nt': - text = _("Open &command prompt") - tip = _("Open a Windows command prompt") - else: - text = _("Open a &terminal") - tip = _("Open a terminal window") - terminal_action = create_action(self, text, None, None, tip, - triggered=self.open_terminal) - run_action = create_action(self, - _("&Run..."), None, - ima.icon('run_small'), _("Run a Python script"), - triggered=self.run_script) - - consoles_menu_actions = [interpreter_action] - tools_menu_actions = [terminal_action] - self.menu_actions = [interpreter_action, terminal_action, run_action] - - self.main.consoles_menu_actions += consoles_menu_actions - self.main.tools_menu_actions += tools_menu_actions - - return self.menu_actions+consoles_menu_actions+tools_menu_actions - - def register_plugin(self): - """Register plugin in Spyder's main window""" - self.main.add_dockwidget(self) - self.historylog = self.main.historylog - self.edit_goto.connect(self.main.editor.load) - self.edit_goto[str, int, str, bool].connect( - lambda fname, lineno, word, processevents: - self.main.editor.load(fname, lineno, word, - processevents=processevents)) - self.main.editor.run_in_current_extconsole.connect( - self.run_script_in_current_shell) - self.main.editor.breakpoints_saved.connect( - self.set_spyder_breakpoints) - self.main.editor.open_dir.connect( - self.set_current_shell_working_directory) - self.main.workingdirectory.set_current_console_wd.connect( - self.set_current_shell_working_directory) - self.focus_changed.connect( - self.main.plugin_focus_changed) - self.redirect_stdio.connect( - self.main.redirect_internalshell_stdio) - - def closing_plugin(self, cancelable=False): - """Perform actions before parent main window is closed.""" - for shellwidget in self.shellwidgets: - shellwidget.close() - return True - - def restart(self): - """ - Restart the console. - - This is needed when we switch project to update PYTHONPATH - and the selected interpreter - """ - self.python_count = 0 - for i in range(len(self.shellwidgets)): - self.close_console() - self.open_interpreter() - - def refresh_plugin(self): - """Refresh tabwidget""" - shellwidget = None - if self.tabwidget.count(): - shellwidget = self.tabwidget.currentWidget() - editor = shellwidget.shell - editor.setFocus() - widgets = [shellwidget.create_time_label(), 5 - ]+shellwidget.get_toolbar_buttons()+[5] - else: - editor = None - widgets = [] - self.find_widget.set_editor(editor) - self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets}) - if shellwidget: - shellwidget.update_time_label_visibility() - self.main.last_console_plugin_focus_was_python = True - self.sig_update_plugin_title.emit() - - def update_font(self): - """Update font from Preferences""" - font = self.get_plugin_font() - for shellwidget in self.shellwidgets: - shellwidget.shell.set_font(font) - completion_size = CONF.get('main', 'completion/size') - comp_widget = shellwidget.shell.completion_widget - comp_widget.setup_appearance(completion_size, font) - - def apply_plugin_settings(self, options): - """Apply configuration file's plugin settings""" - showtime_n = 'show_elapsed_time' - showtime_o = self.get_option(showtime_n) - icontext_n = 'show_icontext' - icontext_o = self.get_option(icontext_n) - calltips_n = 'calltips' - calltips_o = self.get_option(calltips_n) - wrap_n = 'wrap' - wrap_o = self.get_option(wrap_n) - compauto_n = 'codecompletion/auto' - compauto_o = self.get_option(compauto_n) - case_comp_n = 'codecompletion/case_sensitive' - case_comp_o = self.get_option(case_comp_n) - compenter_n = 'codecompletion/enter_key' - compenter_o = self.get_option(compenter_n) - mlc_n = 'max_line_count' - mlc_o = self.get_option(mlc_n) - for shellwidget in self.shellwidgets: - if showtime_n in options: - shellwidget.set_elapsed_time_visible(showtime_o) - if icontext_n in options: - shellwidget.set_icontext_visible(icontext_o) - if calltips_n in options: - shellwidget.shell.set_calltips(calltips_o) - if wrap_n in options: - shellwidget.shell.toggle_wrap_mode(wrap_o) - if compauto_n in options: - shellwidget.shell.set_codecompletion_auto(compauto_o) - if case_comp_n in options: - shellwidget.shell.set_codecompletion_case(case_comp_o) - if compenter_n in options: - shellwidget.shell.set_codecompletion_enter(compenter_o) - if mlc_n in options: - shellwidget.shell.setMaximumBlockCount(mlc_o) - - #------ SpyderPluginMixin API --------------------------------------------- - def toggle_view(self, checked): - """Toggle view""" - if checked: - self.dockwidget.show() - self.dockwidget.raise_() - # Start a console in case there are none shown - from spyder.widgets.externalshell import pythonshell - consoles = None - for sw in self.shellwidgets: - if isinstance(sw, pythonshell.ExternalPythonShell): - consoles = True - break - if not consoles: - self.open_interpreter() - else: - self.dockwidget.hide() - - #------ Public API --------------------------------------------------------- - @Slot(bool) - @Slot(str) - def open_interpreter(self, wdir=None): - """Open interpreter""" - if not wdir: - wdir = getcwd() - self.visibility_changed(True) - self.start(fname=None, wdir=to_text_string(wdir), args='', - interact=True, debug=False, python=True) - - @Slot(bool) - @Slot(str) - def open_terminal(self, wdir=None): - """Open terminal""" - if not wdir: - wdir = getcwd() - self.start(fname=None, wdir=to_text_string(wdir), args='', - interact=True, debug=False, python=False) - - @Slot() - def run_script(self): - """Run a Python script""" - self.redirect_stdio.emit(False) - filename, _selfilter = getopenfilename(self, _("Run Python script"), - getcwd(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)") - self.redirect_stdio.emit(True) - if filename: - self.start(fname=filename, wdir=None, args='', - interact=False, debug=False) - - def go_to_error(self, text): - """Go to error if relevant""" - match = get_error_match(to_text_string(text)) - if match: - fname, lnb = match.groups() - self.edit_goto.emit(osp.abspath(fname), int(lnb), '') - - #----Drag and drop - def dragEnterEvent(self, event): - """Reimplement Qt method - Inform Qt about the types of data that the widget accepts""" - source = event.mimeData() - if source.hasUrls(): - if mimedata2url(source): - pathlist = mimedata2url(source) - shellwidget = self.tabwidget.currentWidget() - if all([is_python_script(to_text_string(qstr)) - for qstr in pathlist]): - event.acceptProposedAction() - elif shellwidget is None or not shellwidget.is_running(): - event.ignore() - else: - event.acceptProposedAction() - else: - event.ignore() - elif source.hasText(): - event.acceptProposedAction() - - def dropEvent(self, event): - """Reimplement Qt method - Unpack dropped data and handle it""" - source = event.mimeData() - shellwidget = self.tabwidget.currentWidget() - if source.hasText(): - qstr = source.text() - if is_python_script(to_text_string(qstr)): - self.start(qstr) - elif shellwidget: - shellwidget.shell.insert_text(qstr) - elif source.hasUrls(): - pathlist = mimedata2url(source) - if all([is_python_script(to_text_string(qstr)) - for qstr in pathlist]): - for fname in pathlist: - self.start(fname) - elif shellwidget: - shellwidget.shell.drop_pathlist(pathlist) - event.acceptProposedAction() diff --git a/spyder/utils/inputhooks.py b/spyder/utils/inputhooks.py deleted file mode 100644 index c2fddc3b2ac..00000000000 --- a/spyder/utils/inputhooks.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Inputhook management for GUI event loop integration - -Copyright (C) The IPython Development Team -Distributed under the terms of the modified BSD license -""" - -# Stdlib imports -import ctypes -import os -import sys - -QT_API = os.environ["QT_API"] - -# Qt imports -if QT_API == 'pyqt5': - from PyQt5 import QtCore -elif QT_API == 'pyqt': - from PyQt4 import QtCore -elif QT_API == 'pyside': - from PySide import QtCore - - -#----------------------------------------------------------------------------- -# Utilities -#----------------------------------------------------------------------------- -def _stdin_ready_posix(): - """Return True if there's something to read on stdin (posix version).""" - infds, outfds, erfds = select.select([sys.stdin],[],[],0) - return bool(infds) - -def _stdin_ready_nt(): - """Return True if there's something to read on stdin (nt version).""" - return msvcrt.kbhit() - -def _stdin_ready_other(): - """Return True, assuming there's something to read on stdin.""" - return True - - -def _ignore_CTRL_C_posix(): - """Ignore CTRL+C (SIGINT).""" - signal.signal(signal.SIGINT, signal.SIG_IGN) - -def _allow_CTRL_C_posix(): - """Take CTRL+C into account (SIGINT).""" - signal.signal(signal.SIGINT, signal.default_int_handler) - -def _ignore_CTRL_C_other(): - """Ignore CTRL+C (not implemented).""" - pass - -def _allow_CTRL_C_other(): - """Take CTRL+C into account (not implemented).""" - pass - - -if os.name == 'posix': - import select - import signal - stdin_ready = _stdin_ready_posix - ignore_CTRL_C = _ignore_CTRL_C_posix - allow_CTRL_C = _allow_CTRL_C_posix -elif os.name == 'nt': - import msvcrt - stdin_ready = _stdin_ready_nt - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other -else: - stdin_ready = _stdin_ready_other - ignore_CTRL_C = _ignore_CTRL_C_other - allow_CTRL_C = _allow_CTRL_C_other - - -def clear_inputhook(): - """Set PyOS_InputHook to NULL and return the previous one""" - pyos_inputhook_ptr = ctypes.c_void_p.in_dll(ctypes.pythonapi, - "PyOS_InputHook") - pyos_inputhook_ptr.value = ctypes.c_void_p(None).value - allow_CTRL_C() - -def get_pyos_inputhook(): - """Return the current PyOS_InputHook as a ctypes.c_void_p.""" - return ctypes.c_void_p.in_dll(ctypes.pythonapi, "PyOS_InputHook") - -def set_pyft_callback(callback): - callback = ctypes.PYFUNCTYPE(ctypes.c_int)(callback) - return callback - -def remove_pyqt_inputhook(): - if QT_API == 'pyqt' or QT_API == 'pyqt5': - QtCore.pyqtRemoveInputHook() - else: - pass - - -#------------------------------------------------------------------------------ -# Input hooks -#------------------------------------------------------------------------------ -def qt4(): - """PyOS_InputHook python hook for Qt4. - - Process pending Qt events and if there's no pending keyboard - input, spend a short slice of time (50ms) running the Qt event - loop. - - As a Python ctypes callback can't raise an exception, we catch - the KeyboardInterrupt and temporarily deactivate the hook, - which will let a *second* CTRL+C be processed normally and go - back to a clean prompt line. - """ - try: - allow_CTRL_C() - app = QtCore.QCoreApplication.instance() - if not app: - return 0 - app.processEvents(QtCore.QEventLoop.AllEvents, 300) - if not stdin_ready(): - # Generally a program would run QCoreApplication::exec() - # from main() to enter and process the Qt event loop until - # quit() or exit() is called and the program terminates. - # - # For our input hook integration, we need to repeatedly - # enter and process the Qt event loop for only a short - # amount of time (say 50ms) to ensure that Python stays - # responsive to other user inputs. - # - # A naive approach would be to repeatedly call - # QCoreApplication::exec(), using a timer to quit after a - # short amount of time. Unfortunately, QCoreApplication - # emits an aboutToQuit signal before stopping, which has - # the undesirable effect of closing all modal windows. - # - # To work around this problem, we instead create a - # QEventLoop and call QEventLoop::exec(). Other than - # setting some state variables which do not seem to be - # used anywhere, the only thing QCoreApplication adds is - # the aboutToQuit signal which is precisely what we are - # trying to avoid. - timer = QtCore.QTimer() - event_loop = QtCore.QEventLoop() - timer.timeout.connect(event_loop.quit) - while not stdin_ready(): - timer.start(50) - event_loop.exec_() - timer.stop() - except KeyboardInterrupt: - print("\nKeyboardInterrupt - Press Enter for new prompt") # spyder: test-skip - except: # NO exceptions are allowed to escape from a ctypes callback - ignore_CTRL_C() - from traceback import print_exc - print_exc() - print("Got exception from inputhook, unregistering.") # spyder: test-skip - clear_inputhook() - finally: - allow_CTRL_C() - return 0 diff --git a/spyder/utils/ipython/kernelspec.py b/spyder/utils/ipython/kernelspec.py index 517727306e3..e244bc2970a 100644 --- a/spyder/utils/ipython/kernelspec.py +++ b/spyder/utils/ipython/kernelspec.py @@ -97,7 +97,6 @@ def env(self): CONF.set('main_interpreter', 'umr/namelist', umr_namelist) env_vars = { - 'IPYTHON_KERNEL': 'True', 'EXTERNAL_INTERPRETER': not default_interpreter, 'UMR_ENABLED': CONF.get('main_interpreter', 'umr/enabled'), 'UMR_VERBOSE': CONF.get('main_interpreter', 'umr/verbose'), diff --git a/spyder/utils/site/sitecustomize.py b/spyder/utils/site/sitecustomize.py index b028146da5c..6f5cdf18717 100644 --- a/spyder/utils/site/sitecustomize.py +++ b/spyder/utils/site/sitecustomize.py @@ -17,7 +17,6 @@ import shlex import sys import time -import traceback PY2 = sys.version[0] == '2' @@ -35,7 +34,6 @@ #============================================================================== # Main constants #============================================================================== -IS_IPYKERNEL = os.environ.get("IPYTHON_KERNEL", "").lower() == "true" IS_EXT_INTERPRETER = os.environ.get('EXTERNAL_INTERPRETER', '').lower() == "true" @@ -103,40 +101,6 @@ def execfile(filename, namespace): exec(compile(f.read(), filename, 'exec'), namespace) -#============================================================================== -# Colorization of sys.stderr (standard Python interpreter) -#============================================================================== -if os.environ.get("COLORIZE_SYS_STDERR", "").lower() == "true": - class StderrProxy(object): - """Proxy to sys.stderr file object overriding only the `write` method - to provide red colorization for the whole stream, and blue-underlined - for traceback file links""" - def __init__(self): - self.old_stderr = sys.stderr - self.__buffer = '' - sys.stderr = self - - def __getattr__(self, name): - return getattr(self.old_stderr, name) - - def write(self, text): - if os.name == 'nt' and '\n' not in text: - self.__buffer += text - return - for text in (self.__buffer+text).splitlines(True): - if text.startswith(' File') \ - and not text.startswith(' File "<'): - # Show error links in blue underlined text - colored_text = ' '+'\x1b[4;34m'+text[2:]+'\x1b[0m' - else: - # Show error messages in red - colored_text = '\x1b[31m'+text+'\x1b[0m' - self.old_stderr.write(colored_text) - self.__buffer = '' - - stderrproxy = StderrProxy() - - #============================================================================== # Prepending this spyder package's path to sys.path to be sure # that another version of spyder won't be imported instead: @@ -176,6 +140,7 @@ def write(self, text): #============================================================================== # Settings for our MacOs X app #============================================================================== +# FIXME: If/when we create new apps we need to revisit this! if sys.platform == 'darwin': from spyder.config.base import MAC_APP_NAME if MAC_APP_NAME in __file__: @@ -264,7 +229,6 @@ def _getfilesystemencoding_wrapper(): #============================================================================== -# Importing matplotlib before creating the monitor. # This prevents a kernel crash with the inline backend in our IPython # consoles on Linux and Python 3 (Fixes Issue 2257) #============================================================================== @@ -274,234 +238,33 @@ def _getfilesystemencoding_wrapper(): matplotlib = None # analysis:ignore -#============================================================================== -# Monitor-based functionality -#============================================================================== -if IS_IPYKERNEL or os.environ.get('SPYDER_SHELL_ID') is None: - monitor = None -else: - from spyder.widgets.externalshell.monitor import Monitor - monitor = Monitor("127.0.0.1", - int(os.environ['SPYDER_I_PORT']), - int(os.environ['SPYDER_N_PORT']), - os.environ['SPYDER_SHELL_ID']) - monitor.start() - - def open_in_spyder(source, lineno=1): - """ - Open a source file in Spyder's editor (it could be a filename or a - Python module/package). - - If you want to use IPython's %edit use %ed instead - """ - try: - source = sys.modules[source] - except KeyError: - source = source - if not isinstance(source, basestring): - try: - source = source.__file__ - except AttributeError: - raise ValueError("source argument must be either " - "a string or a module object") - if source.endswith('.pyc'): - source = source[:-1] - source = osp.abspath(source) - if osp.exists(source): - monitor.notify_open_file(source, lineno=lineno) - else: - _print("Can't open file %s" % source, file=sys.stderr) - builtins.open_in_spyder = open_in_spyder - - # Our own input hook, monitor based and for Windows only - if os.name == 'nt' and matplotlib and not IS_IPYKERNEL: - # Qt imports - if os.environ["QT_API"] == 'pyqt5': - from PyQt5 import QtCore - from PyQt5 import QtWidgets - elif os.environ["QT_API"] == 'pyqt': - from PyQt4 import QtCore # analysis:ignore - from PyQt4 import QtGui as QtWidgets - elif os.environ["QT_API"] == 'pyside': - from PySide import QtCore # analysis:ignore - from PySide import QtGui as QtWidgets - - def qt_nt_inputhook(): - """Qt input hook for Windows - - This input hook wait for available stdin data (notified by - ExternalPythonShell through the monitor's inputhook_flag - attribute), and in the meantime it processes Qt events. - """ - # Refreshing variable explorer, except on first input hook call: - # (otherwise, on slow machines, this may freeze Spyder) - monitor.refresh_from_inputhook() - - # NOTE: This is making the inputhoook to fail completely!! - # That's why it's commented. - #try: - # This call fails for Python without readline support - # (or on Windows platforms) when PyOS_InputHook is called - # for the second consecutive time, because the 100-bytes - # stdin buffer is full. - # For more details, see the `PyOS_StdioReadline` function - # in Python source code (Parser/myreadline.c) - # sys.stdin.tell() - #except IOError: - # return 0 - - # Input hook - app = QtCore.QCoreApplication.instance() - if app is None: - app = QtWidgets.QApplication([" "]) - if app and app.thread() is QtCore.QThread.currentThread(): - try: - timer = QtCore.QTimer() - timer.timeout.connect(app.quit) - monitor.toggle_inputhook_flag(False) - while not monitor.inputhook_flag: - timer.start(50) - QtCore.QCoreApplication.exec_() - timer.stop() - except KeyboardInterrupt: - _print("\nKeyboardInterrupt - Press Enter for new prompt") - - # Socket-based alternative: - #socket = QtNetwork.QLocalSocket() - #socket.connectToServer(os.environ['SPYDER_SHELL_ID']) - #socket.waitForConnected(-1) - #while not socket.waitForReadyRead(10): - # timer.start(50) - # QtCore.QCoreApplication.exec_() - # timer.stop() - #socket.read(3) - #socket.disconnectFromServer() - return 0 - - #============================================================================== # Matplotlib settings #============================================================================== if matplotlib is not None: - if not IS_IPYKERNEL: - mpl_backend = os.environ.get("SPY_MPL_BACKEND", "") - mpl_ion = os.environ.get("MATPLOTLIB_ION", "") - - # Setting no backend if the user asks for it - if not mpl_backend or mpl_backend.lower() == 'none': - mpl_backend = "" - - # Set backend automatically - if mpl_backend.lower() == 'automatic': - if not IS_EXT_INTERPRETER: - if os.environ["QT_API"] == 'pyqt5': - mpl_backend = 'Qt5Agg' - else: - mpl_backend = 'Qt4Agg' - else: - # Test for backend libraries on external interpreters - def set_mpl_backend(backend): - mod, bend, qt_api = backend - try: - if mod: - __import__(mod) - if qt_api and (os.environ["QT_API"] != qt_api): - return None - else: - matplotlib.use(bend) - return bend - except (ImportError, ValueError): - return None - - backends = [('PyQt5', 'Qt5Agg', 'pyqt5'), - ('PyQt4', 'Qt4Agg', 'pyqt'), - ('PySide', 'Qt4Agg', 'pyqt')] - if not os.name == 'nt': - backends.append( ('_tkinter', 'TkAgg', None) ) - - for b in backends: - mpl_backend = set_mpl_backend(b) - if mpl_backend: - break - - if not mpl_backend: - _print("NOTE: No suitable Matplotlib backend was found!\n" - " You won't be able to create plots\n") - - # To have mpl docstrings as rst - matplotlib.rcParams['docstring.hardcopy'] = True - - # Activate interactive mode when needed - if mpl_ion.lower() == "true": - matplotlib.rcParams['interactive'] = True - - from spyder.utils import inputhooks - if mpl_backend: - import ctypes - - # Grab QT_API - qt_api = os.environ["QT_API"] - - # Setting the user defined backend - if not IS_EXT_INTERPRETER: - matplotlib.use(mpl_backend) - - # Setting the right input hook according to mpl_backend, - # IMPORTANT NOTE: Don't try to abstract the steps to set a PyOS - # input hook callback in a function. It will **crash** the - # interpreter!! - if (mpl_backend == "Qt4Agg" or mpl_backend == "Qt5Agg") and \ - os.name == 'nt' and monitor is not None: - # Removing PyQt4 input hook which is not working well on - # Windows since opening a subprocess does not attach a real - # console to it (with keyboard events...) - if qt_api == 'pyqt' or qt_api == 'pyqt5': - inputhooks.remove_pyqt_inputhook() - # Using our own input hook - # NOTE: it's not working correctly for some configurations - # (See issue 1831) - callback = inputhooks.set_pyft_callback(qt_nt_inputhook) - pyos_ih = inputhooks.get_pyos_inputhook() - pyos_ih.value = ctypes.cast(callback, ctypes.c_void_p).value - elif mpl_backend == "Qt4Agg" and qt_api == 'pyside': - # PySide doesn't have an input hook, so we need to install one - # to be able to show plots - # Note: This only works well for Posix systems - callback = inputhooks.set_pyft_callback(inputhooks.qt4) - pyos_ih = inputhooks.get_pyos_inputhook() - pyos_ih.value = ctypes.cast(callback, ctypes.c_void_p).value - elif (mpl_backend != "Qt4Agg" and qt_api == 'pyqt') \ - or (mpl_backend != "Qt5Agg" and qt_api == 'pyqt5'): - # Matplotlib backends install their own input hooks, so we - # need to remove the PyQt one to make them work - inputhooks.remove_pyqt_inputhook() - else: - inputhooks.remove_pyqt_inputhook() - else: - # To have mpl docstrings as rst - matplotlib.rcParams['docstring.hardcopy'] = True + # To have mpl docstrings as rst + matplotlib.rcParams['docstring.hardcopy'] = True #============================================================================== # IPython kernel adjustments #============================================================================== -if IS_IPYKERNEL: - # Use ipydb as the debugger to patch on IPython consoles - from IPython.core.debugger import Pdb as ipyPdb - pdb.Pdb = ipyPdb - - # Patch unittest.main so that errors are printed directly in the console. - # See http://comments.gmane.org/gmane.comp.python.ipython.devel/10557 - # Fixes Issue 1370 - import unittest - from unittest import TestProgram - class IPyTesProgram(TestProgram): - def __init__(self, *args, **kwargs): - test_runner = unittest.TextTestRunner(stream=sys.stderr) - kwargs['testRunner'] = kwargs.pop('testRunner', test_runner) - kwargs['exit'] = False - TestProgram.__init__(self, *args, **kwargs) - unittest.main = IPyTesProgram +# Use ipydb as the debugger to patch on IPython consoles +from IPython.core.debugger import Pdb as ipyPdb +pdb.Pdb = ipyPdb + +# Patch unittest.main so that errors are printed directly in the console. +# See http://comments.gmane.org/gmane.comp.python.ipython.devel/10557 +# Fixes Issue 1370 +import unittest +from unittest import TestProgram +class IPyTesProgram(TestProgram): + def __init__(self, *args, **kwargs): + test_runner = unittest.TextTestRunner(stream=sys.stderr) + kwargs['testRunner'] = kwargs.pop('testRunner', test_runner) + kwargs['exit'] = False + TestProgram.__init__(self, *args, **kwargs) +unittest.main = IPyTesProgram #============================================================================== @@ -568,11 +331,8 @@ def notify_spyder(self, frame): if not frame: return - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - else: - ipython_shell = None + from IPython.core.getipython import get_ipython + ipython_shell = get_ipython() # Get filename and line number of the current frame fname = self.canonic(frame.f_code.co_filename) @@ -589,9 +349,6 @@ def notify_spyder(self, frame): if osp.isfile(fname): if ipython_shell: step = dict(fname=fname, lineno=lineno) - elif monitor is not None: - monitor.notify_pdb_step(fname, lineno) - time.sleep(0.1) # Publish Pdb state so we can update the Variable Explorer # and the Editor on the Spyder side @@ -684,13 +441,12 @@ def _cmdloop(self): @monkeypatch_method(pdb.Pdb, 'Pdb') def reset(self): self._old_Pdb_reset() - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - if ipython_shell: - ipython_shell.kernel._register_pdb_session(self) - elif monitor is not None: - monitor.register_pdb_session(self) + + from IPython.core.getipython import get_ipython + ipython_shell = get_ipython() + if ipython_shell: + ipython_shell.kernel._register_pdb_session(self) + self.set_spyder_breakpoints() @@ -742,8 +498,7 @@ def break_here(self, frame): # 'site-packages' directory from sys.path! #============================================================================== try: - sys.path.remove(osp.join(spyder_path, "spyder", "widgets", - "externalshell")) + sys.path.remove(osp.join(spyder_path, "spyder", "utils", "site")) except ValueError: pass @@ -815,13 +570,10 @@ def clear_post_mortem(): """ Remove the post mortem excepthook and replace with a standard one. """ - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - if ipython_shell: - ipython_shell.set_custom_exc((), None) - else: - sys.excepthook = sys.__excepthook__ + from IPython.core.getipython import get_ipython + ipython_shell = get_ipython() + if ipython_shell: + ipython_shell.set_custom_exc((), None) def post_mortem_excepthook(type, value, tb): @@ -830,14 +582,11 @@ def post_mortem_excepthook(type, value, tb): mortem debugging. """ clear_post_mortem() - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - ipython_shell.showtraceback((type, value, tb)) - p = pdb.Pdb(ipython_shell.colors) - else: - traceback.print_exception(type, value, tb, file=sys.stderr) - p = pdb.Pdb() + + from IPython.core.getipython import get_ipython + ipython_shell = get_ipython() + ipython_shell.showtraceback((type, value, tb)) + p = pdb.Pdb(ipython_shell.colors) if not type == SyntaxError: # wait for stderr to print (stderr.flush does not work in this case) @@ -863,15 +612,12 @@ def set_post_mortem(): """ Enable the post mortem debugging excepthook. """ - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - def ipython_post_mortem_debug(shell, etype, evalue, tb, - tb_offset=None): - post_mortem_excepthook(etype, evalue, tb) - ipython_shell = get_ipython() - ipython_shell.set_custom_exc((Exception,), ipython_post_mortem_debug) - else: - sys.excepthook = post_mortem_excepthook + from IPython.core.getipython import get_ipython + def ipython_post_mortem_debug(shell, etype, evalue, tb, + tb_offset=None): + post_mortem_excepthook(etype, evalue, tb) + ipython_shell = get_ipython() + ipython_shell.set_custom_exc((Exception,), ipython_post_mortem_debug) # Add post mortem debugging if requested and in a dedicated interpreter # existing interpreters use "runfile" below @@ -940,10 +686,9 @@ def runfile(filename, args=None, wdir=None, namespace=None, post_mortem=False): if HAS_CYTHON and os.path.splitext(filename)[1].lower() == '.pyx': # Cython files with io.open(filename, encoding='utf-8') as f: - if IS_IPYKERNEL: - from IPython.core.getipython import get_ipython - ipython_shell = get_ipython() - ipython_shell.run_cell_magic('cython', '', f.read()) + from IPython.core.getipython import get_ipython + ipython_shell = get_ipython() + ipython_shell.run_cell_magic('cython', '', f.read()) else: execfile(filename, namespace) @@ -973,52 +718,6 @@ def debugfile(filename, args=None, wdir=None, post_mortem=False): builtins.debugfile = debugfile -#============================================================================== -# Evaluate external commands -#============================================================================== -def evalsc(command): - """Evaluate special commands - (analog to IPython's magic commands but far less powerful/complete)""" - assert command.startswith('%') - from spyder.utils import programs - - namespace = _get_globals() - command = command[1:].strip() # Remove leading % - - import re - clear_match = re.match(r"^clear ([a-zA-Z0-9_, ]+)", command) - cd_match = re.match(r"^cd \"?\'?([a-zA-Z0-9_\ \:\\\/\.]+)", command) - - if cd_match: - os.chdir(eval('r"%s"' % cd_match.groups()[0].strip())) - elif clear_match: - varnames = clear_match.groups()[0].replace(' ', '').split(',') - for varname in varnames: - try: - namespace.pop(varname) - except KeyError: - pass - elif command in ('cd', 'pwd'): - try: - _print(os.getcwdu()) - except AttributeError: - _print(os.getcwd()) - elif command == 'ls': - if os.name == 'nt': - programs.run_shell_command('dir') - _print('\n') - else: - programs.run_shell_command('ls') - _print('\n') - elif command == 'scientific': - from spyder.config import base - execfile(base.SCIENTIFIC_STARTUP, namespace) - else: - raise NotImplementedError("Unsupported command: '%s'" % command) - -builtins.evalsc = evalsc - - #============================================================================== # Restoring original PYTHONPATH #============================================================================== diff --git a/spyder/widgets/externalshell/__init__.py b/spyder/widgets/externalshell/__init__.py deleted file mode 100644 index 042ce5d4b1b..00000000000 --- a/spyder/widgets/externalshell/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -""" -spyder.widgets.externalshell -============================ - -External Shell widget: execute Python script/terminal in a separate process -""" diff --git a/spyder/widgets/externalshell/baseshell.py b/spyder/widgets/externalshell/baseshell.py deleted file mode 100644 index 6dd7869d01c..00000000000 --- a/spyder/widgets/externalshell/baseshell.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -# pylint: disable=C0103 -# pylint: disable=R0903 -# pylint: disable=R0911 -# pylint: disable=R0201 - -# Standard library imports -from time import time, strftime, gmtime -import os.path as osp - -# Third party imports -from qtpy.QtCore import (QByteArray, QProcess, Qt, QTextCodec, QTimer, - Signal, Slot, QMutex) -from qtpy.QtWidgets import (QHBoxLayout, QInputDialog, QLabel, QLineEdit, - QMenu, QToolButton, QVBoxLayout, QWidget) - -# Local imports -from spyder.config.base import _, get_conf_path -from spyder.py3compat import to_text_string -from spyder.utils import icon_manager as ima -from spyder.utils.qthelpers import (add_actions, create_action, - create_toolbutton) - - -LOCALE_CODEC = QTextCodec.codecForLocale() - - -#TODO: code refactoring/cleaning (together with systemshell.py and pythonshell.py) -class ExternalShellBase(QWidget): - """External Shell widget: execute Python script in a separate process""" - SHELL_CLASS = None - redirect_stdio = Signal(bool) - sig_finished = Signal() - - def __init__(self, parent=None, fname=None, wdir=None, - history_filename=None, show_icontext=True, - light_background=True, menu_actions=None, - show_buttons_inside=True, show_elapsed_time=True): - QWidget.__init__(self, parent) - - self.menu_actions = menu_actions - self.write_lock = QMutex() - self.buffer_lock = QMutex() - self.buffer = [] - - self.run_button = None - self.kill_button = None - self.options_button = None - self.icontext_action = None - - self.show_elapsed_time = show_elapsed_time - - self.fname = fname - if wdir is None: - wdir = osp.dirname(osp.abspath(fname)) - self.wdir = wdir if osp.isdir(wdir) else None - self.arguments = "" - - self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename)) - self.shell.set_light_background(light_background) - self.shell.execute.connect(self.send_to_process) - self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt) - # Redirecting some SIGNALs: - self.shell.redirect_stdio.connect( - lambda state: self.redirect_stdio.emit(state)) - - self.state_label = None - self.time_label = None - - vlayout = QVBoxLayout() - toolbar_buttons = self.get_toolbar_buttons() - if show_buttons_inside: - self.state_label = QLabel() - hlayout = QHBoxLayout() - hlayout.addWidget(self.state_label) - hlayout.addStretch(0) - hlayout.addWidget(self.create_time_label()) - hlayout.addStretch(0) - for button in toolbar_buttons: - hlayout.addWidget(button) - vlayout.addLayout(hlayout) - else: - vlayout.setContentsMargins(0, 0, 0, 0) - vlayout.addWidget(self.get_shell_widget()) - self.setLayout(vlayout) - self.resize(640, 480) - if parent is None: - self.setWindowIcon(self.get_icon()) - self.setWindowTitle(_("Console")) - - self.t0 = None - self.timer = QTimer(self) - - self.process = None - - self.is_closing = False - - if show_buttons_inside: - self.update_time_label_visibility() - - @Slot(bool) - def set_elapsed_time_visible(self, state): - self.show_elapsed_time = state - if self.time_label is not None: - self.time_label.setVisible(state) - - def create_time_label(self): - """Create elapsed time label widget (if necessary) and return it""" - if self.time_label is None: - self.time_label = QLabel() - return self.time_label - - def update_time_label_visibility(self): - self.time_label.setVisible(self.show_elapsed_time) - - def is_running(self): - if self.process is not None: - return self.process.state() == QProcess.Running - - def get_toolbar_buttons(self): - if self.run_button is None: - self.run_button = create_toolbutton(self, text=_("Run"), - icon=ima.icon('run'), - tip=_("Run again this program"), - triggered=self.start_shell) - if self.kill_button is None: - self.kill_button = create_toolbutton(self, text=_("Kill"), - icon=ima.icon('kill'), - tip=_("Kills the current process, " - "causing it to exit immediately")) - buttons = [self.run_button] - if self.options_button is None: - options = self.get_options_menu() - if options: - self.options_button = create_toolbutton(self, text=_('Options'), - icon=ima.icon('tooloptions')) - self.options_button.setPopupMode(QToolButton.InstantPopup) - menu = QMenu(self) - add_actions(menu, options) - self.options_button.setMenu(menu) - if self.options_button is not None: - buttons.append(self.options_button) - buttons.append(self.kill_button) - return buttons - - def set_icontext_visible(self, state): - """Set icon text visibility""" - for widget in self.get_toolbar_buttons(): - if state: - widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) - else: - widget.setToolButtonStyle(Qt.ToolButtonIconOnly) - - def get_options_menu(self): - self.show_time_action = create_action(self, _("Show elapsed time"), - toggled=self.set_elapsed_time_visible) - self.show_time_action.setChecked(self.show_elapsed_time) - actions = [self.show_time_action] - if self.menu_actions is not None: - actions += [None]+self.menu_actions - return actions - - def get_shell_widget(self): - return self.shell - - def get_icon(self): - raise NotImplementedError - - def show_time(self, end=False): - if self.time_label is None: - return - elapsed_time = time()-self.t0 - if elapsed_time > 24*3600: # More than a day...! - format = "%d %H:%M:%S" - else: - format = "%H:%M:%S" - if end: - color = "#AAAAAA" - else: - color = "#AA6655" - text = "%s" \ - "" % (color, strftime(format, gmtime(elapsed_time))) - self.time_label.setText(text) - - def closeEvent(self, event): - if self.process is not None: - self.is_closing = True - self.process.kill() - self.process.waitForFinished(100) - - try: - self.timer.timeout.disconnect(self.show_time) - except (RuntimeError, TypeError): - pass - - def set_running_state(self, state=True): - self.set_buttons_runnning_state(state) - self.shell.setReadOnly(not state) - if state: - if self.state_label is not None: - self.state_label.setText(_( - "Running...")) - self.t0 = time() - self.timer.timeout.connect(self.show_time) - self.timer.start(1000) - else: - if self.state_label is not None: - self.state_label.setText(_('Terminated.')) - try: - self.timer.timeout.disconnect(self.show_time) - except (RuntimeError, TypeError): - pass - - def set_buttons_runnning_state(self, state): - self.run_button.setVisible(not state) - self.kill_button.setVisible(state) - - @Slot(bool) - def start_shell(self, ask_for_arguments=False): - """Start shell""" - if ask_for_arguments and not self.get_arguments(): - self.set_running_state(False) - return - try: - self.terminate_button.clicked.disconnect(self.process.terminate) - self.kill_button.clicked.disconnect(self.process.terminate) - except (AttributeError, RuntimeError, TypeError): - pass - self.create_process() - - @Slot() - def get_arguments(self): - arguments, valid = QInputDialog.getText(self, _('Arguments'), - _('Command line arguments:'), - QLineEdit.Normal, - self.arguments) - if valid: - self.arguments = to_text_string(arguments) - return valid - - def create_process(self): - raise NotImplementedError - - def finished(self, exit_code, exit_status): - self.shell.flush() - self.sig_finished.emit() - if self.is_closing: - return - self.set_running_state(False) - self.show_time(end=True) - -#=============================================================================== -# Input/Output -#=============================================================================== - def transcode(self, qba): - try: - return to_text_string(qba.data(), 'utf8') - except UnicodeDecodeError: - return qba.data() - - def get_stdout(self): - self.process.setReadChannel(QProcess.StandardOutput) - qba = QByteArray() - while self.process.bytesAvailable(): - qba += self.process.readAllStandardOutput() - return self.transcode(qba) - - def get_stderr(self): - self.process.setReadChannel(QProcess.StandardError) - qba = QByteArray() - while self.process.bytesAvailable(): - qba += self.process.readAllStandardError() - return self.transcode(qba) - - def write_output(self): - # if we are already writing something else, - # store the present message in a buffer - if not self.write_lock.tryLock(): - self.buffer_lock.lock() - self.buffer.append(self.get_stdout()) - self.buffer_lock.unlock() - - if not self.write_lock.tryLock(): - return - - self.shell.write(self.get_stdout(), flush=True) - - while True: - self.buffer_lock.lock() - messages = self.buffer - self.buffer = [] - - if not messages: - self.write_lock.unlock() - self.buffer_lock.unlock() - return - - self.buffer_lock.unlock() - self.shell.write("\n".join(messages), flush=True) - - def send_to_process(self, qstr): - raise NotImplementedError - - def send_ctrl_to_process(self, letter): - char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1) - byte_array = QByteArray() - byte_array.append(char) - self.process.write(byte_array) - self.process.waitForBytesWritten(-1) - self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True) - - def keyboard_interrupt(self): - raise NotImplementedError diff --git a/spyder/widgets/externalshell/introspection.py b/spyder/widgets/externalshell/introspection.py deleted file mode 100644 index 2705331971b..00000000000 --- a/spyder/widgets/externalshell/introspection.py +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""External shell's introspection and notification servers""" - -# Standard library imports -import errno -import os -import socket -import threading - -# Third party imports -from qtpy.QtCore import QThread, Signal - -# Local imports -from spyder.config.base import get_conf_path, DEBUG -from spyder.utils.debug import log_last_error -from spyder.utils.bsdsocket import read_packet, write_packet -from spyder.utils.misc import select_port - - -LOG_FILENAME = get_conf_path('introspection.log') - -DEBUG_INTROSPECTION = DEBUG >= 2 - -if DEBUG_INTROSPECTION: - import logging - logging.basicConfig(filename=get_conf_path('introspection_debug.log'), - level=logging.DEBUG) - -SPYDER_PORT = 20128 - - -class IntrospectionServer(threading.Thread): - """Introspection server""" - def __init__(self): - threading.Thread.__init__(self) - self.shells = {} - self.setDaemon(True) - global SPYDER_PORT - self.port = SPYDER_PORT = select_port(default_port=SPYDER_PORT) - SPYDER_PORT += 1 - - def register(self, shell): - """Register introspection server - See notification server below""" - shell_id = str(id(shell)) - self.shells[shell_id] = shell - - def send_socket(self, shell_id, sock): - """Send socket to the appropriate object for later communication""" - shell = self.shells[shell_id] - shell.set_introspection_socket(sock) - if DEBUG_INTROSPECTION: - logging.debug('Introspection server: shell [%r] port [%r]' - % (shell, self.port)) - - def run(self): - """Start server""" - sock = socket.socket(socket.AF_INET) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind( ("127.0.0.1", self.port) ) - - while True: - sock.listen(2) - try: - conn, _addr = sock.accept() - except socket.error as e: - # See Issue 1275 for details on why errno EINTR is - # silently ignored here. - eintr = errno.WSAEINTR if os.name == 'nt' else errno.EINTR - if e.args[0] == eintr: - continue - raise - shell_id = read_packet(conn) - if shell_id is not None: - self.send_socket(shell_id, conn) - -class NotificationServer(IntrospectionServer): - """Notification server""" - def __init__(self): - IntrospectionServer.__init__(self) - self.notification_threads = {} - - def register(self, shell): - """Register notification server - See pythonshell.ExternalPythonShell.create_process""" - IntrospectionServer.register(self, shell) - shell_id = str(id(shell)) - n_thread = self.notification_threads[shell_id] = NotificationThread() - return n_thread - - def send_socket(self, shell_id, sock): - """Send socket to the appropriate object for later communication""" - n_thread = self.notification_threads[shell_id] - n_thread.set_notify_socket(sock) - n_thread.start() - if DEBUG_INTROSPECTION: - logging.debug('Notification server: shell [%r] port [%r]' - % (self.shells[shell_id], self.port)) - -INTROSPECTION_SERVER = None - -def start_introspection_server(): - """ - Start introspection server (only one time) - This server is dedicated to introspection features, i.e. Spyder is calling - it to retrieve informations on remote objects - """ - global INTROSPECTION_SERVER - if INTROSPECTION_SERVER is None: - if DEBUG_INTROSPECTION: - import time - time_str = "Logging time: %s" % time.ctime(time.time()) - logging.debug("="*len(time_str)) - logging.debug(time_str) - logging.debug("="*len(time_str)) - INTROSPECTION_SERVER = IntrospectionServer() - INTROSPECTION_SERVER.start() - return INTROSPECTION_SERVER - -NOTIFICATION_SERVER = None - -def start_notification_server(): - """ - Start notify server (only one time) - This server is dedicated to notification features, i.e. remote objects - are notifying Spyder about anything relevant like debugging data (pdb) - or "this is the right moment to refresh variable explorer" (syshook) - """ - global NOTIFICATION_SERVER - if NOTIFICATION_SERVER is None: - NOTIFICATION_SERVER = NotificationServer() - NOTIFICATION_SERVER.start() - return NOTIFICATION_SERVER - - -class NotificationThread(QThread): - """Notification thread""" - sig_process_remote_view = Signal(object) - sig_pdb = Signal(str, int) - open_file = Signal(str, int) - refresh_namespace_browser = Signal() - - def __init__(self): - QThread.__init__(self) - self.notify_socket = None - - def set_notify_socket(self, notify_socket): - """Set the notification socket""" - self.notify_socket = notify_socket - - def run(self): - """Start notification thread""" - while True: - if self.notify_socket is None: - continue - output = None - try: - try: - cdict = read_packet(self.notify_socket) - except: - # This except statement is intended to handle a struct.error - # (but when writing 'except struct.error', it doesn't work) - # Note: struct.error is raised when the communication has - # been interrupted and the received data is not a string - # of length 8 as required by struct.unpack (see read_packet) - break - if cdict is None: - # Another notification thread has just terminated and - # then wrote 'None' in the notification socket - # (see the 'finally' statement below) - continue - if not isinstance(cdict, dict): - raise TypeError("Invalid data type: %r" % cdict) - command = cdict['command'] - data = cdict.get('data') - if command == 'pdb_step': - fname, lineno = data - self.sig_pdb.emit(fname, lineno) - self.refresh_namespace_browser.emit() - elif command == 'refresh': - self.refresh_namespace_browser.emit() - elif command == 'remote_view': - self.sig_process_remote_view.emit(data) - elif command == 'open_file': - fname, lineno = data - self.open_file.emit(fname, lineno) - else: - raise RuntimeError('Unsupported command: %r' % command) - if DEBUG_INTROSPECTION: - logging.debug("received command: %r" % command) - except: - log_last_error(LOG_FILENAME, "notification thread") - finally: - try: - write_packet(self.notify_socket, output) - except: - # The only reason why it should fail is that Spyder is - # closing while this thread is still alive - break diff --git a/spyder/widgets/externalshell/monitor.py b/spyder/widgets/externalshell/monitor.py deleted file mode 100644 index 99114c20323..00000000000 --- a/spyder/widgets/externalshell/monitor.py +++ /dev/null @@ -1,521 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""External shell's monitor""" - -#TODO: The "disable auto-refresh when variable explorer is hidden" feature -# broken since we removed the "shell" widget reference from notification -# thread. We must find another mechanism to avoid refreshing systematically -# remote views for all consoles...! - -import os -import socket -import struct -import threading - -# Local imports -from spyder.config.base import get_conf_path, DEBUG -from spyder.py3compat import getcwd, is_text_string, pickle, _thread -from spyder.utils.misc import fix_reference_name -from spyder.utils.debug import log_last_error -from spyder.utils.dochelpers import (getargtxt, getdoc, getsource, - getobjdir, isdefined) -from spyder.utils.bsdsocket import (communicate, read_packet, write_packet, - PACKET_NOT_RECEIVED, PICKLE_HIGHEST_PROTOCOL) -from spyder.utils.introspection.module_completion import module_completion -from spyder.widgets.variableexplorer.utils import (get_remote_data, - make_remote_view) - - -LOG_FILENAME = get_conf_path('monitor.log') -DEBUG_MONITOR = DEBUG >= 2 -if DEBUG_MONITOR: - import logging - logging.basicConfig(filename=get_conf_path('monitor_debug.log'), - level=logging.DEBUG) - - -def monitor_save_globals(sock, settings, filename): - """Save globals() to file""" - return communicate(sock, '__save_globals__()', - settings=[settings, filename]) - -def monitor_load_globals(sock, filename, ext): - """Load globals() from file""" - return communicate(sock, '__load_globals__()', settings=[filename, ext]) - -def monitor_get_global(sock, name): - """Get global variable *name* value""" - return communicate(sock, '__get_global__("%s")' % name) - -def monitor_set_global(sock, name, value): - """Set global variable *name* value to *value*""" - return communicate(sock, '__set_global__("%s")' % name, - settings=[value]) - -def monitor_del_global(sock, name): - """Del global variable *name*""" - return communicate(sock, '__del_global__("%s")' % name) - -def monitor_copy_global(sock, orig_name, new_name): - """Copy global variable *orig_name* to *new_name*""" - return communicate(sock, '__copy_global__("%s", "%s")' \ - % (orig_name, new_name)) - -def _getcdlistdir(): - """Return current directory list dir""" - return os.listdir(getcwd()) - - -class Monitor(threading.Thread): - """Monitor server""" - def __init__(self, host, introspection_port, notification_port, - shell_id, timeout=2000, auto_refresh=False): - threading.Thread.__init__(self) - self.setDaemon(True) - - self.pdb_obj = None - - self.timeout = None - self.set_timeout(timeout) - self.auto_refresh = auto_refresh - self.refresh_after_eval = False - self.remote_view_settings = None - - self.inputhook_flag = False - self.first_inputhook_call = True - - # Connecting to introspection server - self.i_request = socket.socket( socket.AF_INET ) - self.i_request.connect( (host, introspection_port) ) - write_packet(self.i_request, shell_id) - - # Connecting to notification server - self.n_request = socket.socket( socket.AF_INET ) - self.n_request.connect( (host, notification_port) ) - write_packet(self.n_request, shell_id) - - self._mlocals = { - "refresh": self.enable_refresh_after_eval, - "setlocal": self.setlocal, - "is_array": self.is_array, - "is_image": self.is_image, - "get_globals_keys": self.get_globals_keys, - "getmodcomplist": self.getmodcomplist, - "getcdlistdir": _getcdlistdir, - "getcwd": self.getcwd, - "setcwd": self.setcwd, - "getsyspath": self.getsyspath, - "getenv": self.getenv, - "setenv": self.setenv, - "isdefined": self.isdefined, - "thread": _thread, - "toggle_inputhook_flag": self.toggle_inputhook_flag, - "set_monitor_timeout": self.set_timeout, - "set_monitor_auto_refresh": self.set_auto_refresh, - "set_remote_view_settings": - self.set_remote_view_settings, - "set_spyder_breakpoints": self.set_spyder_breakpoints, - "__get_dir__": self.get_dir, - "__iscallable__": self.iscallable, - "__get_arglist__": self.get_arglist, - "__get__doc____": self.get__doc__, - "__get_doc__": self.get_doc, - "__get_source__": self.get_source, - "__get_global__": self.getglobal, - "__set_global__": self.setglobal, - "__del_global__": self.delglobal, - "__copy_global__": self.copyglobal, - "__save_globals__": self.saveglobals, - "__load_globals__": self.loadglobals, - "_" : None} - self._mglobals = None - - @property - def pdb_frame(self): - """Return current Pdb frame if there is any""" - if self.pdb_obj is not None and self.pdb_obj.curframe is not None: - return self.pdb_obj.curframe - - @property - def pdb_locals(self): - """Return current Pdb frame locals if available - Otherwise return an empty dictionary""" - if self.pdb_frame: - return self.pdb_obj.curframe_locals - else: - return {} - - def mlocals(self): - """Return current locals -- handles Pdb frames""" - ns = {} - ns.update(self._mlocals) - ns.update(self.pdb_locals) - return ns - - def mglobals(self): - """Return current globals -- handles Pdb frames""" - if self.pdb_frame is not None: - return self.pdb_frame.f_globals - else: - if self._mglobals is None: - from __main__ import __dict__ as glbs - self._mglobals = glbs - else: - glbs = self._mglobals - self._mglobals = glbs - return glbs - - def get_current_namespace(self): - """Return current namespace, i.e. globals() if not debugging, - or a dictionary containing both locals() and globals() - for current frame when debugging""" - ns = {} - glbs = self.mglobals() - - if self.pdb_frame is None: - ns.update(glbs) - else: - ns.update(glbs) - ns.update(self.pdb_locals) - - return ns - - def get_reference_namespace(self, name): - """Return namespace where reference name is defined, - eventually returns the globals() if reference has not yet been defined""" - glbs = self.mglobals() - if self.pdb_frame is None: - return glbs - else: - lcls = self.pdb_locals - if name in lcls: - return lcls - else: - return glbs - - def get_globals_keys(self): - """Return globals() keys or globals() and locals() keys if debugging""" - ns = self.get_current_namespace() - return list(ns.keys()) - - def isdefined(self, obj, force_import=False): - """Return True if object is defined in current namespace""" - ns = self.get_current_namespace() - return isdefined(obj, force_import=force_import, namespace=ns) - - def toggle_inputhook_flag(self, state): - """Toggle the input hook flag - - The only purpose of this flag is to unblock the PyOS_InputHook - callback when text is available in stdin (see sitecustomize.py)""" - self.inputhook_flag = state - - def set_timeout(self, timeout): - """Set monitor timeout (in milliseconds!)""" - self.timeout = float(timeout)/1000. - - def set_auto_refresh(self, state): - """Enable/disable namespace browser auto refresh feature""" - self.auto_refresh = state - - def enable_refresh_after_eval(self): - self.refresh_after_eval = True - - #------ Notifications - def refresh(self): - """Refresh variable explorer in ExternalPythonShell""" - communicate(self.n_request, dict(command="refresh")) - - def refresh_from_inputhook(self): - """Refresh variable explorer from the PyOS_InputHook. - See sitecustomize.py""" - # Refreshing variable explorer, except on first input hook call - # (otherwise, on slow machines, this may freeze Spyder) - if self.first_inputhook_call: - self.first_inputhook_call = False - else: - self.refresh() - - def register_pdb_session(self, pdb_obj): - self.pdb_obj = pdb_obj - - def notify_pdb_step(self, fname, lineno): - """Notify the ExternalPythonShell regarding pdb current frame""" - communicate(self.n_request, - dict(command="pdb_step", data=(fname, lineno))) - - def set_spyder_breakpoints(self): - """Set all Spyder breakpoints in active pdb session""" - if not self.pdb_obj: - return - self.pdb_obj.set_spyder_breakpoints() - - def notify_open_file(self, fname, lineno=1): - """Open file in Spyder's editor""" - communicate(self.n_request, - dict(command="open_file", data=(fname, lineno))) - - #------ Code completion / Calltips - def _eval(self, text): - """ - Evaluate text and return (obj, valid) - where *obj* is the object represented by *text* - and *valid* is True if object evaluation did not raise any exception - """ - assert is_text_string(text) - ns = self.get_current_namespace() - try: - return eval(text, ns), True - except: - return None, False - - def get_dir(self, objtxt): - """Return dir(object)""" - obj, valid = self._eval(objtxt) - if valid: - return getobjdir(obj) - - def iscallable(self, objtxt): - """Is object callable?""" - obj, valid = self._eval(objtxt) - if valid: - return callable(obj) - - def get_arglist(self, objtxt): - """Get func/method argument list""" - obj, valid = self._eval(objtxt) - if valid: - return getargtxt(obj) - - def get__doc__(self, objtxt): - """Get object __doc__""" - obj, valid = self._eval(objtxt) - if valid: - return obj.__doc__ - - def get_doc(self, objtxt): - """Get object documentation dictionary""" - obj, valid = self._eval(objtxt) - if valid: - return getdoc(obj) - - def get_source(self, objtxt): - """Get object source""" - obj, valid = self._eval(objtxt) - if valid: - return getsource(obj) - - def getmodcomplist(self, name, path): - """Return module completion list for object named *name*""" - return module_completion(name, path) - - #------ Other - def is_array(self, name): - """Return True if object is an instance of class numpy.ndarray""" - ns = self.get_current_namespace() - try: - import numpy - return isinstance(ns[name], numpy.ndarray) - except ImportError: - return False - - def is_image(self, name): - """Return True if object is an instance of class PIL.Image.Image""" - ns = self.get_current_namespace() - try: - from spyder.pil_patch import Image - return isinstance(ns[name], Image.Image) - except ImportError: - return False - - def getcwd(self): - """Return current working directory""" - return getcwd() - - def setcwd(self, dirname): - """Set current working directory""" - return os.chdir(dirname) - - def getenv(self): - """Return os.environ""" - return os.environ.copy() - - def setenv(self): - """Set os.environ""" - env = read_packet(self.i_request) - os.environ = env - - def getsyspath(self): - """Return sys.path[:]""" - import sys - return sys.path[:] - - def setlocal(self, name, value): - """ - Set local reference value - Not used right now - could be useful in the future - """ - self._mlocals[name] = value - - def set_remote_view_settings(self): - """ - Set the namespace remote view settings - (see the namespace browser widget) - """ - self.remote_view_settings = read_packet(self.i_request) - self.enable_refresh_after_eval() - - def update_remote_view(self): - """ - Return remote view of globals() - """ - settings = self.remote_view_settings - if settings: - ns = self.get_current_namespace() - remote_view = make_remote_view(ns, settings) - communicate(self.n_request, - dict(command="remote_view", data=remote_view)) - - def saveglobals(self): - """Save globals() into filename""" - ns = self.get_current_namespace() - from spyder.utils.iofuncs import iofunctions - settings = read_packet(self.i_request) - filename = read_packet(self.i_request) - data = get_remote_data(ns, settings, mode='picklable').copy() - return iofunctions.save(data, filename) - - def loadglobals(self): - """Load globals() from filename""" - glbs = self.mglobals() - from spyder.utils.iofuncs import iofunctions - filename = read_packet(self.i_request) - ext = read_packet(self.i_request) - load_func = iofunctions.load_funcs[ext] - data, error_message = load_func(filename) - if error_message: - return error_message - for key in list(data.keys()): - new_key = fix_reference_name(key, blacklist=list(glbs.keys())) - if new_key != key: - data[new_key] = data.pop(key) - try: - glbs.update(data) - except Exception as error: - return str(error) - self.refresh_after_eval = True - - def getglobal(self, name): - """ - Get global reference value - """ - ns = self.get_current_namespace() - return ns[name] - - def setglobal(self, name): - """ - Set global reference value - """ - ns = self.get_reference_namespace(name) - ns[name] = read_packet(self.i_request) - self.refresh_after_eval = True - - def delglobal(self, name): - """ - Del global reference - """ - ns = self.get_reference_namespace(name) - ns.pop(name) - self.refresh_after_eval = True - - def copyglobal(self, orig_name, new_name): - """ - Copy global reference - """ - ns = self.get_reference_namespace(orig_name) - ns[new_name] = ns[orig_name] - self.refresh_after_eval = True - - def run(self): - while True: - output = pickle.dumps(None, PICKLE_HIGHEST_PROTOCOL) - glbs = self.mglobals() - try: - if DEBUG_MONITOR: - logging.debug("****** Introspection request /Begin ******") - command = PACKET_NOT_RECEIVED - try: - timeout = self.timeout if self.auto_refresh else None - command = read_packet(self.i_request, timeout=timeout) - if command is None: - continue - timed_out = False - except socket.timeout: - timed_out = True - except struct.error: - # This should mean that Spyder GUI has crashed - if DEBUG_MONITOR: - logging.debug("struct.error -> quitting monitor") - break - if timed_out: - if DEBUG_MONITOR: - logging.debug("connection timed out -> updating remote view") - self.update_remote_view() - if DEBUG_MONITOR: - logging.debug("****** Introspection request /End ******") - continue - if DEBUG_MONITOR: - logging.debug("command: %r" % command) - lcls = self.mlocals() - result = eval(command, glbs, lcls) - if DEBUG_MONITOR: - logging.debug(" result: %r" % result) - if self.pdb_obj is None: - lcls["_"] = result - # old com implementation: (see solution (1) in Issue 434) - output = pickle.dumps(result, PICKLE_HIGHEST_PROTOCOL) -# # new com implementation: (see solution (2) in Issue 434) -# output = pickle.dumps((command, result), -# PICKLE_HIGHEST_PROTOCOL) - except SystemExit: - break - except: - if DEBUG_MONITOR: - logging.debug("error!") - log_last_error(LOG_FILENAME, command) - finally: - try: - if DEBUG_MONITOR: - logging.debug("updating remote view") - if self.refresh_after_eval: - self.update_remote_view() - self.refresh_after_eval = False - if DEBUG_MONITOR: - logging.debug("sending result") - logging.debug("****** Introspection request /End ******") - if command is not PACKET_NOT_RECEIVED: - if write_packet is None: - # This may happen during interpreter shutdown - break - else: - write_packet(self.i_request, output, - already_pickled=True) - except AttributeError as error: - if "'NoneType' object has no attribute" in str(error): - # This may happen during interpreter shutdown - break - else: - raise - except TypeError as error: - if "'NoneType' object is not subscriptable" in str(error): - # This may happen during interpreter shutdown - break - else: - raise - - self.i_request.close() - self.n_request.close() diff --git a/spyder/widgets/externalshell/pythonshell.py b/spyder/widgets/externalshell/pythonshell.py deleted file mode 100644 index b78f70a01b0..00000000000 --- a/spyder/widgets/externalshell/pythonshell.py +++ /dev/null @@ -1,657 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""External Python Shell widget: execute Python script in a separate process""" - -# Standard library imports -import os -import os.path as osp -import socket -import sys - -# Third party imports -from qtpy.compat import getexistingdirectory -from qtpy.QtCore import QProcess, QProcessEnvironment, Qt, Signal, Slot -from qtpy.QtWidgets import QApplication, QMenu, QMessageBox, QSplitter - -# Local imports -from spyder.config.base import (_, DEBUG, get_module_source_path, - MAC_APP_NAME, running_in_mac_app) -from spyder.py3compat import (is_text_string, to_binary_string, - to_text_string) -from spyder.utils import icon_manager as ima -from spyder.utils.bsdsocket import communicate, write_packet -from spyder.utils.environ import RemoteEnvDialog -from spyder.utils.misc import add_pathlist_to_PYTHONPATH, get_python_executable -from spyder.utils.programs import get_python_args -from spyder.utils.qthelpers import (add_actions, create_action, - create_toolbutton, DialogManager) -from spyder.widgets.externalshell.baseshell import ExternalShellBase -from spyder.widgets.shell import PythonShellWidget -from spyder.widgets.variableexplorer.namespacebrowser import NamespaceBrowser -from spyder.widgets.variableexplorer.collectionseditor import CollectionsEditor - - -class ExtPythonShellWidget(PythonShellWidget): - - wait_for_ready_read = Signal() - go_to_error = Signal(str) - focus_changed = Signal() - - def __init__(self, parent, history_filename, profile=False): - initial_message = _("NOTE: The Python console is going to " - "be REMOVED in Spyder 3.2. Please start " - "to migrate your work to the " - "IPython console instead.\n\n") - PythonShellWidget.__init__(self, parent, history_filename, profile, - initial_message=initial_message) - self.path = [] - - def set_externalshell(self, externalshell): - # ExternalShellBase instance: - self.externalshell = externalshell - - def clear_terminal(self): - """Reimplement ShellBaseWidget method""" - self.clear() - self.execute.emit("\n") - - def execute_lines(self, lines): - """ - Execute a set of lines as multiple command - lines: multiple lines of text to be executed as single commands - """ - for line in lines.splitlines(): - stripped_line = line.strip() - if stripped_line.startswith('#'): - continue - self.write(line+os.linesep, flush=True) - self.execute_command(line) - # Workaround for Issue 502 - # Emmiting wait_for_ready_read was making the console hang - # in Mac OS X - if sys.platform.startswith("darwin"): - import time - time.sleep(0.025) - else: - self.wait_for_ready_read.emit() - self.flush() - - #------ Code completion / Calltips - def ask_monitor(self, command, settings=[]): - sock = self.externalshell.introspection_socket - if sock is None: - return - try: - return communicate(sock, command, settings=settings) - except socket.error: - # Process was just closed - pass - except MemoryError: - # Happens when monitor is not ready on slow machines - pass - - def get_dir(self, objtxt): - """Return dir(object)""" - return self.ask_monitor("__get_dir__('%s')" % objtxt) - - def get_globals_keys(self): - """Return shell globals() keys""" - return self.ask_monitor("get_globals_keys()") - - def get_cdlistdir(self): - """Return shell current directory list dir""" - return self.ask_monitor("getcdlistdir()") - - def iscallable(self, objtxt): - """Is object callable?""" - return self.ask_monitor("__iscallable__('%s')" % objtxt) - - def get_arglist(self, objtxt): - """Get func/method argument list""" - return self.ask_monitor("__get_arglist__('%s')" % objtxt) - - def get__doc__(self, objtxt): - """Get object __doc__""" - return self.ask_monitor("__get__doc____('%s')" % objtxt) - - def get_doc(self, objtxt): - """Get object documentation dictionary""" - return self.ask_monitor("__get_doc__('%s')" % objtxt) - - def get_source(self, objtxt): - """Get object source""" - return self.ask_monitor("__get_source__('%s')" % objtxt) - - def is_defined(self, objtxt, force_import=False): - """Return True if object is defined""" - return self.ask_monitor("isdefined('%s', force_import=%s)" - % (objtxt, force_import)) - - def get_module_completion(self, objtxt): - """Return module completion list associated to object name""" - return self.ask_monitor("getmodcomplist('%s', %s)" % \ - (objtxt, self.path)) - - def get_cwd(self): - """Return shell current working directory""" - return self.ask_monitor("getcwd()") - - def set_cwd(self, dirname): - """Set shell current working directory""" - return self.ask_monitor("setcwd(r'%s')" % dirname) - - def get_env(self): - """Return environment variables: os.environ""" - return self.ask_monitor("getenv()") - - def set_env(self, env): - """Set environment variables via os.environ""" - return self.ask_monitor('setenv()', settings=[env]) - - def get_syspath(self): - """Return sys.path[:]""" - return self.ask_monitor("getsyspath()") - - def set_spyder_breakpoints(self): - """Set Spyder breakpoints into debugging session""" - return self.ask_monitor("set_spyder_breakpoints()") - - def is_running(self): - """Check if parent is running""" - return self.parent().is_running() - - -class ExternalPythonShell(ExternalShellBase): - """External Shell widget: execute Python script in a separate process""" - SHELL_CLASS = ExtPythonShellWidget - sig_pdb = Signal(str, int) - open_file = Signal(str, int) - started = Signal() - sig_finished = Signal() - - def __init__(self, parent=None, fname=None, wdir=None, - interact=False, debug=False, post_mortem=False, - path=[], python_args='', - arguments='', stand_alone=None, - umr_enabled=True, umr_namelist=[], umr_verbose=True, - pythonstartup=None, pythonexecutable=None, - external_interpreter=False, - monitor_enabled=True, mpl_backend=None, ets_backend='qt4', - qt_api=None, merge_output_channels=False, - colorize_sys_stderr=False, - light_background=True, - menu_actions=None, show_buttons_inside=True, - show_elapsed_time=True): - - assert qt_api in (None, 'pyqt', 'pyside', 'pyqt5') - - self.namespacebrowser = None # namespace browser widget! - self.dialog_manager = DialogManager() - - self.stand_alone = stand_alone # stand alone settings (None: plugin) - self.interact = interact - self.pythonstartup = pythonstartup - self.pythonexecutable = pythonexecutable - self.external_interpreter = external_interpreter - self.monitor_enabled = monitor_enabled - self.mpl_backend = mpl_backend - self.ets_backend = ets_backend - self.qt_api = qt_api - self.merge_output_channels = merge_output_channels - self.colorize_sys_stderr = colorize_sys_stderr - self.umr_enabled = umr_enabled - self.umr_namelist = umr_namelist - self.umr_verbose = umr_verbose - - self.namespacebrowser_button = None - self.cwd_button = None - self.env_button = None - self.syspath_button = None - self.terminate_button = None - - self.notification_thread = None - - ExternalShellBase.__init__(self, parent=parent, fname=fname, wdir=wdir, - history_filename='history.py', - light_background=light_background, - menu_actions=menu_actions, - show_buttons_inside=show_buttons_inside, - show_elapsed_time=show_elapsed_time) - - if self.pythonexecutable is None: - self.pythonexecutable = get_python_executable() - - self.python_args = None - if python_args: - assert is_text_string(python_args) - self.python_args = python_args - - assert is_text_string(arguments) - self.arguments = arguments - - self.connection_file = None - self.shell.set_externalshell(self) - - self.toggle_globals_explorer(False) - self.interact_action.setChecked(self.interact) - self.debug_action.setChecked(debug) - - - self.introspection_socket = None - self.is_interpreter = fname is None - - if self.is_interpreter: - self.terminate_button.hide() - - self.post_mortem_action.setChecked(post_mortem and not self.is_interpreter) - - # Additional python path list - self.path = path - self.shell.path = path - - def set_introspection_socket(self, introspection_socket): - self.introspection_socket = introspection_socket - if self.namespacebrowser is not None: - settings = self.namespacebrowser.get_view_settings() - communicate(introspection_socket, - 'set_remote_view_settings()', settings=[settings]) - - def closeEvent(self, event): - self.quit_monitor() - ExternalShellBase.closeEvent(self, event) - - def get_toolbar_buttons(self): - ExternalShellBase.get_toolbar_buttons(self) - if self.namespacebrowser_button is None \ - and self.stand_alone is not None: - self.namespacebrowser_button = create_toolbutton(self, - text=_("Variables"), icon=ima.icon('dictedit'), - tip=_("Show/hide global variables explorer"), - toggled=self.toggle_globals_explorer, text_beside_icon=True) - if self.terminate_button is None: - self.terminate_button = create_toolbutton(self, - text=_("Terminate"), icon=ima.icon('stop'), - tip=_("Attempts to stop the process. The process\n" - "may not exit as a result of clicking this\n" - "button (it is given the chance to prompt\n" - "the user for any unsaved files, etc).")) - buttons = [] - if self.namespacebrowser_button is not None: - buttons.append(self.namespacebrowser_button) - buttons += [self.run_button, self.terminate_button, self.kill_button, - self.options_button] - return buttons - - def get_options_menu(self): - ExternalShellBase.get_options_menu(self) - self.interact_action = create_action(self, _("Interact")) - self.interact_action.setCheckable(True) - self.debug_action = create_action(self, _("Debug")) - self.debug_action.setCheckable(True) - self.args_action = create_action(self, _("Arguments..."), - triggered=self.get_arguments) - self.post_mortem_action = create_action(self, _("Post Mortem Debug")) - self.post_mortem_action.setCheckable(True) - run_settings_menu = QMenu(_("Run settings"), self) - add_actions(run_settings_menu, - (self.interact_action, self.debug_action, self.args_action, - self.post_mortem_action)) - self.cwd_button = create_action(self, _("Working directory"), - icon=ima.icon('DirOpenIcon'), - tip=_("Set current working directory"), - triggered=self.set_current_working_directory) - self.env_button = create_action(self, _("Environment variables"), - icon=ima.icon('environ'), - triggered=self.show_env) - self.syspath_button = create_action(self, - _("Show sys.path contents"), - icon=ima.icon('syspath'), - triggered=self.show_syspath) - actions = [run_settings_menu, self.show_time_action, None, - self.cwd_button, self.env_button, self.syspath_button] - if self.menu_actions is not None: - actions += [None]+self.menu_actions - return actions - - def is_interpreter(self): - """Return True if shellwidget is a Python interpreter""" - return self.is_interpreter - - def get_shell_widget(self): - if self.stand_alone is None: - return self.shell - else: - self.namespacebrowser = NamespaceBrowser(self) - settings = self.stand_alone - self.namespacebrowser.set_shellwidget(self) - self.namespacebrowser.setup(**settings) - self.namespacebrowser.sig_collapse.connect( - lambda: self.toggle_globals_explorer(False)) - # Shell splitter - self.splitter = splitter = QSplitter(Qt.Vertical, self) - self.splitter.splitterMoved.connect(self.splitter_moved) - splitter.addWidget(self.shell) - splitter.setCollapsible(0, False) - splitter.addWidget(self.namespacebrowser) - splitter.setStretchFactor(0, 1) - splitter.setStretchFactor(1, 0) - splitter.setHandleWidth(5) - splitter.setSizes([2, 1]) - return splitter - - def get_icon(self): - return ima.icon('python') - - def set_buttons_runnning_state(self, state): - ExternalShellBase.set_buttons_runnning_state(self, state) - self.interact_action.setEnabled(not state and not self.is_interpreter) - self.debug_action.setEnabled(not state and not self.is_interpreter) - self.args_action.setEnabled(not state and not self.is_interpreter) - self.post_mortem_action.setEnabled(not state and not self.is_interpreter) - if state: - if self.arguments: - argstr = _("Arguments: %s") % self.arguments - else: - argstr = _("No argument") - else: - argstr = _("Arguments...") - self.args_action.setText(argstr) - self.terminate_button.setVisible(not self.is_interpreter and state) - if not state: - self.toggle_globals_explorer(False) - for btn in (self.cwd_button, self.env_button, self.syspath_button): - btn.setEnabled(state and self.monitor_enabled) - if self.namespacebrowser_button is not None: - self.namespacebrowser_button.setEnabled(state) - - def set_namespacebrowser(self, namespacebrowser): - """ - Set namespace browser *widget* - Note: this method is not used in stand alone mode - """ - self.namespacebrowser = namespacebrowser - self.configure_namespacebrowser() - - def configure_namespacebrowser(self): - """Connect the namespace browser to the notification thread""" - if self.notification_thread is not None: - self.notification_thread.refresh_namespace_browser.connect( - self.namespacebrowser.refresh_table) - signal = self.notification_thread.sig_process_remote_view - signal.connect(lambda data: - self.namespacebrowser.process_remote_view(data)) - - def create_process(self): - self.shell.clear() - - self.process = QProcess(self) - if self.merge_output_channels: - self.process.setProcessChannelMode(QProcess.MergedChannels) - else: - self.process.setProcessChannelMode(QProcess.SeparateChannels) - self.shell.wait_for_ready_read.connect( - lambda: self.process.waitForReadyRead(250)) - - # Working directory - if self.wdir is not None: - self.process.setWorkingDirectory(self.wdir) - - #-------------------------Python specific------------------------------ - # Python arguments - p_args = ['-u'] - if DEBUG >= 3: - p_args += ['-v'] - p_args += get_python_args(self.fname, self.python_args, - self.interact_action.isChecked(), - self.debug_action.isChecked(), - self.arguments) - - env = [to_text_string(_path) - for _path in self.process.systemEnvironment()] - if self.pythonstartup: - env.append('PYTHONSTARTUP=%s' % self.pythonstartup) - - #-------------------------Python specific------------------------------- - # Post mortem debugging - if self.post_mortem_action.isChecked(): - env.append('SPYDER_EXCEPTHOOK=True') - - # Set standard input/output encoding for Python consoles - # See http://stackoverflow.com/q/26312400/438386, specifically - # the comments of Martijn Pieters - env.append('PYTHONIOENCODING=UTF-8') - - # Monitor - if self.monitor_enabled: - env.append('SPYDER_SHELL_ID=%s' % id(self)) - from spyder.widgets.externalshell import introspection - introspection_server = introspection.start_introspection_server() - introspection_server.register(self) - notification_server = introspection.start_notification_server() - self.notification_thread = notification_server.register(self) - self.notification_thread.sig_pdb.connect( - lambda fname, lineno: - self.sig_pdb.emit(fname, lineno)) - self.notification_thread.open_file.connect( - lambda fname, lineno: - self.open_file.emit(fname, lineno)) - if self.namespacebrowser is not None: - self.configure_namespacebrowser() - env.append('SPYDER_I_PORT=%d' % introspection_server.port) - env.append('SPYDER_N_PORT=%d' % notification_server.port) - - # External modules options - env.append('ETS_TOOLKIT=%s' % self.ets_backend) - if self.mpl_backend is not None: - backends = {0: 'Automatic', 1: 'None', 2: 'TkAgg'} - env.append('SPY_MPL_BACKEND=%s' % backends[self.mpl_backend]) - if self.qt_api: - env.append('QT_API=%s' % self.qt_api) - env.append('COLORIZE_SYS_STDERR=%s' % self.colorize_sys_stderr) -# # Socket-based alternative (see input hook in sitecustomize.py): -# if self.install_qt_inputhook: -# from PyQt4.QtNetwork import QLocalServer -# self.local_server = QLocalServer() -# self.local_server.listen(str(id(self))) - - # User Module Deleter - if self.is_interpreter: - env.append('UMR_ENABLED=%r' % self.umr_enabled) - env.append('UMR_NAMELIST=%s' % ','.join(self.umr_namelist)) - env.append('UMR_VERBOSE=%r' % self.umr_verbose) - env.append('MATPLOTLIB_ION=True') - else: - if self.interact: - env.append('MATPLOTLIB_ION=True') - else: - env.append('MATPLOTLIB_ION=False') - - # External interpreter - env.append('EXTERNAL_INTERPRETER=%r' % self.external_interpreter) - - # Add sitecustomize path to path list - pathlist = [] - spy_path = get_module_source_path('spyder') - sc_path = osp.join(spy_path, 'utils', 'site') - pathlist.append(sc_path) - - # Adding Spyder path - pathlist += self.path - - # Adding path list to PYTHONPATH environment variable - add_pathlist_to_PYTHONPATH(env, pathlist) - - #-------------------------Python specific------------------------------ - - self.process.readyReadStandardOutput.connect(self.write_output) - self.process.readyReadStandardError.connect(self.write_error) - self.process.finished.connect(lambda ec, es=QProcess.ExitStatus: - self.finished(ec, es)) - self.sig_finished.connect(self.dialog_manager.close_all) - self.terminate_button.clicked.connect(self.process.terminate) - self.kill_button.clicked.connect(self.process.kill) - - #-------------------------Python specific------------------------------ - # Fixes for our Mac app: - # 1. PYTHONPATH and PYTHONHOME are set while bootstrapping the app, - # but their values are messing sys.path for external interpreters - # (e.g. EPD) so we need to remove them from the environment. - # 2. Set PYTHONPATH again but without grabbing entries defined in the - # environment (Fixes Issue 1321) - # 3. Remove PYTHONOPTIMIZE from env so that we can have assert - # statements working with our interpreters (See Issue 1281) - if running_in_mac_app(): - if MAC_APP_NAME not in self.pythonexecutable: - env = [p for p in env if not (p.startswith('PYTHONPATH') or \ - p.startswith('PYTHONHOME'))] # 1. - - add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=True) # 2. - env = [p for p in env if not p.startswith('PYTHONOPTIMIZE')] # 3. - - processEnvironment = QProcessEnvironment() - for envItem in env: - envName, separator, envValue = envItem.partition('=') - processEnvironment.insert(envName, envValue) - self.process.setProcessEnvironment(processEnvironment) - self.process.start(self.pythonexecutable, p_args) - #-------------------------Python specific------------------------------ - - running = self.process.waitForStarted(3000) - self.set_running_state(running) - if not running: - QMessageBox.critical(self, _("Error"), - _("A Python console failed to start!")) - else: - self.shell.setFocus() - self.started.emit() - return self.process - - def finished(self, exit_code, exit_status): - """Reimplement ExternalShellBase method""" - ExternalShellBase.finished(self, exit_code, exit_status) - self.introspection_socket = None - - -#============================================================================== -# Input/Output -#============================================================================== - def write_error(self): - if os.name == 'nt': - #---This is apparently necessary only on Windows (not sure though): - # emptying standard output buffer before writing error output - self.process.setReadChannel(QProcess.StandardOutput) - if self.process.waitForReadyRead(1): - self.write_output() - self.shell.write_error(self.get_stderr()) - QApplication.processEvents() - - def send_to_process(self, text): - if not self.is_running(): - return - if not is_text_string(text): - text = to_text_string(text) - if self.mpl_backend == 0 and os.name == 'nt' and \ - self.introspection_socket is not None: - communicate(self.introspection_socket, "toggle_inputhook_flag(True)") -# # Socket-based alternative (see input hook in sitecustomize.py): -# while self.local_server.hasPendingConnections(): -# self.local_server.nextPendingConnection().write('go!') - if any([text == cmd for cmd in ['%ls', '%pwd', '%scientific']]) or \ - any([text.startswith(cmd) for cmd in ['%cd ', '%clear ']]): - text = 'evalsc(r"%s")\n' % text - if not text.endswith('\n'): - text += '\n' - self.process.write(to_binary_string(text, 'utf8')) - self.process.waitForBytesWritten(-1) - - # Eventually write prompt faster (when hitting Enter continuously) - # -- necessary/working on Windows only: - if os.name == 'nt': - self.write_error() - - def keyboard_interrupt(self): - if self.introspection_socket is not None: - communicate(self.introspection_socket, "thread.interrupt_main()") - - def quit_monitor(self): - if self.introspection_socket is not None: - try: - write_packet(self.introspection_socket, "thread.exit()") - except socket.error: - pass - -#============================================================================== -# Globals explorer -#============================================================================== - @Slot(bool) - def toggle_globals_explorer(self, state): - if self.stand_alone is not None: - self.splitter.setSizes([1, 1 if state else 0]) - self.namespacebrowser_button.setChecked(state) - if state and self.namespacebrowser is not None: - self.namespacebrowser.refresh_table() - - def splitter_moved(self, pos, index): - self.namespacebrowser_button.setChecked( self.splitter.sizes()[1] ) - -#============================================================================== -# Misc. -#============================================================================== - @Slot() - def set_current_working_directory(self): - """Set current working directory""" - cwd = self.shell.get_cwd() - self.redirect_stdio.emit(False) - directory = getexistingdirectory(self, _("Select directory"), cwd) - if directory: - self.shell.set_cwd(directory) - self.redirect_stdio.emit(True) - - @Slot() - def show_env(self): - """Show environment variables""" - get_func = self.shell.get_env - set_func = self.shell.set_env - self.dialog_manager.show(RemoteEnvDialog(get_func, set_func)) - - @Slot() - def show_syspath(self): - """Show sys.path contents""" - editor = CollectionsEditor() - editor.setup(self.shell.get_syspath(), title="sys.path", readonly=True, - width=600, icon=ima.icon('syspath')) - self.dialog_manager.show(editor) - - -def test(): - from spyder.utils.qthelpers import qapplication - app = qapplication() - - from spyder.plugins.variableexplorer import VariableExplorer - - settings = VariableExplorer(None).get_settings() - - shell = ExternalPythonShell(pythonexecutable=sys.executable, - interact=True, - stand_alone=settings, - wdir=osp.dirname(__file__), - mpl_backend=0, - light_background=False) - - from spyder.config.gui import get_font - - font = get_font() - shell.shell.set_font(font) - - shell.shell.toggle_wrap_mode(True) - shell.start_shell(False) - shell.show() - sys.exit(app.exec_()) - - -if __name__ == "__main__": - test() diff --git a/spyder/widgets/externalshell/systemshell.py b/spyder/widgets/externalshell/systemshell.py deleted file mode 100644 index 838a50fa435..00000000000 --- a/spyder/widgets/externalshell/systemshell.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright © Spyder Project Contributors -# Licensed under the terms of the MIT License -# (see spyder/__init__.py for details) - -"""External System Shell widget: execute terminal in a separate process""" - -# Standard library imports -import os -import sys - -# Third party imports -from qtpy.QtCore import QProcess, QProcessEnvironment, QTextCodec, Signal -from qtpy.QtWidgets import QMessageBox - -# Local imports -from spyder.config.base import _ -from spyder.py3compat import is_text_string, to_text_string -from spyder.utils import icon_manager as ima -from spyder.utils.misc import add_pathlist_to_PYTHONPATH -from spyder.widgets.externalshell.baseshell import ExternalShellBase -from spyder.widgets.shell import TerminalWidget - - -LOCALE_CODEC = QTextCodec.codecForLocale() -CP850_CODEC = QTextCodec.codecForName('cp850') - - -class ExternalSystemShell(ExternalShellBase): - """External Shell widget: execute Python script in a separate process""" - SHELL_CLASS = TerminalWidget - started = Signal() - - def __init__(self, parent=None, wdir=None, path=[], light_background=True, - menu_actions=None, show_buttons_inside=True, - show_elapsed_time=True): - ExternalShellBase.__init__(self, parent=parent, fname=None, wdir=wdir, - history_filename='.history', - light_background=light_background, - menu_actions=menu_actions, - show_buttons_inside=show_buttons_inside, - show_elapsed_time=show_elapsed_time) - - # Additional python path list - self.path = path - - # For compatibility with the other shells that can live in the external - # console - self.connection_file = None - - def get_icon(self): - return ima.icon('cmdprompt') - - def finish_process(self): - while not self.process.waitForFinished(100): - self.process.kill(); - - def create_process(self): - self.shell.clear() - - self.process = QProcess(self) - self.process.setProcessChannelMode(QProcess.MergedChannels) - - # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe) - env = [to_text_string(_path) - for _path in self.process.systemEnvironment()] - - processEnvironment = QProcessEnvironment() - for envItem in env: - envName, separator, envValue = envItem.partition('=') - processEnvironment.insert(envName, envValue) - - add_pathlist_to_PYTHONPATH(env, self.path) - self.process.setProcessEnvironment(processEnvironment) - - - # Working directory - if self.wdir is not None: - self.process.setWorkingDirectory(self.wdir) - - # Shell arguments - if os.name == 'nt': - p_args = ['/Q'] - else: - p_args = ['-i'] - - if self.arguments: - p_args.extend( shell_split(self.arguments) ) - - self.process.readyReadStandardOutput.connect(self.write_output) - self.process.finished.connect(self.finished) - self.kill_button.clicked.connect(self.process.kill) - - if os.name == 'nt': - self.process.start('cmd.exe', p_args) - else: - # Using bash: - self.process.start('bash', p_args) - self.send_to_process('PS1="\\u@\\h:\\w> "\n') - - running = self.process.waitForStarted() - self.set_running_state(running) - if not running: - QMessageBox.critical(self, _("Error"), - _("Process failed to start")) - else: - self.shell.setFocus() - self.started.emit() - - return self.process - -#=============================================================================== -# Input/Output -#=============================================================================== - def transcode(self, qba): - if os.name == 'nt': - return to_text_string( CP850_CODEC.toUnicode(qba.data()) ) - else: - return ExternalShellBase.transcode(self, qba) - - def send_to_process(self, text): - if not is_text_string(text): - text = to_text_string(text) - if text[:-1] in ["clear", "cls", "CLS"]: - self.shell.clear() - self.send_to_process(os.linesep) - return - if not text.endswith('\n'): - text += '\n' - if os.name == 'nt': - self.process.write(text.encode('cp850')) - else: - self.process.write(LOCALE_CODEC.fromUnicode(text)) - self.process.waitForBytesWritten(-1) - - def keyboard_interrupt(self): - # This does not work on Windows: - # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe) - self.send_ctrl_to_process('c') - -# # The following code will soon be removed: -# # (last attempt to send a Ctrl+C on Windows) -# if os.name == 'nt': -# pid = int(self.process.pid()) -# import ctypes, win32api, win32con -# class _PROCESS_INFORMATION(ctypes.Structure): -# _fields_ = [("hProcess", ctypes.c_int), -# ("hThread", ctypes.c_int), -# ("dwProcessID", ctypes.c_int), -# ("dwThreadID", ctypes.c_int)] -# x = ctypes.cast( ctypes.c_void_p(pid), -# ctypes.POINTER(_PROCESS_INFORMATION) ) -# win32api.GenerateConsoleCtrlEvent(win32con.CTRL_C_EVENT, -# x.dwProcessID) -# else: -# self.send_ctrl_to_process('c') - - -#============================================================================== -# Tests -#============================================================================== -def test(): - import os.path as osp - from spyder.utils.qthelpers import qapplication - app = qapplication(test_time=5) - shell = ExternalSystemShell(wdir=osp.dirname(__file__), - light_background=False) - - app.aboutToQuit.connect(shell.finish_process) - - from qtpy.QtGui import QFont - font = QFont() - font.setPointSize(10) - shell.shell.set_font(font) - - shell.shell.toggle_wrap_mode(True) - shell.start_shell(False) - shell.show() - sys.exit(app.exec_()) - - -if __name__ == "__main__": - test() \ No newline at end of file