Пример #1
0
class RichText(QWidget):
    """
    WebView widget with find dialog
    """
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, fixed_font=None):
        """Set font"""
        self.webview.set_font(font, fixed_font=fixed_font)

    def set_html(self, html_text, base_url):
        """Set html text"""
        self.webview.setHtml(html_text, base_url)

    def clear(self):
        self.set_html('', self.webview.url())
Пример #2
0
class RichText(QWidget):
    """
    WebView widget with find dialog
    """
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        if WEBENGINE:
            self.webview.web_widget.page().setBackgroundColor(
                QColor(MAIN_BG_COLOR))
        else:
            self.webview.web_widget.setStyleSheet(
                "background:{}".format(MAIN_BG_COLOR))
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, fixed_font=None):
        """Set font"""
        self.webview.set_font(font, fixed_font=fixed_font)

    def set_html(self, html_text, base_url):
        """Set html text"""
        self.webview.setHtml(html_text, base_url)

    def clear(self):
        self.set_html('', self.webview.url())
Пример #3
0
def editor_find_replace_bot(base_editor_bot):
    editor_stack, qtbot = base_editor_bot
    text = ('spam bacon\n'
            'spam sausage\n'
            'spam egg')
    finfo = editor_stack.new('spam.py', 'utf-8', text)
    find_replace = FindReplace(None, enable_replace=True)
    editor_stack.set_find_widget(find_replace)
    find_replace.set_editor(finfo.editor)
    qtbot.addWidget(editor_stack)
    return editor_stack, finfo.editor, find_replace, qtbot
Пример #4
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #5
0
class PlainText(QWidget):
    """
    Read-only editor widget with find dialog
    """
    # Signals
    focus_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False,
                                 language='py',
                                 scrollflagarea=False,
                                 edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, color_scheme=None):
        """Set font"""
        self.editor.set_font(font, color_scheme=color_scheme)

    def set_color_scheme(self, color_scheme):
        """Set color scheme"""
        self.editor.set_color_scheme(color_scheme)

    def set_text(self, text, is_code):
        self.editor.set_highlight_current_line(is_code)
        self.editor.set_occurrence_highlighting(is_code)
        if is_code:
            self.editor.set_language('py')
        else:
            self.editor.set_language(None)
        self.editor.set_text(text)
        self.editor.set_cursor_position('sof')

    def clear(self):
        self.editor.clear()
Пример #6
0
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        SpyderPluginWidget.__init__(self, parent)

        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state:
                                          self.redirect_stdio.emit(state))
        
        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts(self.find_widget)

        # Main layout
        btn_layout = QHBoxLayout()
        btn_layout.setAlignment(Qt.AlignLeft)
        btn_layout.addStretch()
        btn_layout.addWidget(self.options_button, Qt.AlignRight)
        layout = create_plugin_layout(btn_layout)
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)

        # Traceback MessageBox
        self.error_dlg = None
        self.error_traceback = ""
        self.dimiss_error = False
Пример #7
0
class RichText(QWidget):
    """
    WebView widget with find dialog
    """
    sig_link_clicked = Signal(QUrl)

    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        if WEBENGINE:
            self.webview.web_widget.page().setBackgroundColor(
                QColor(MAIN_BG_COLOR))
        else:
            self.webview.web_widget.setStyleSheet(
                "background:{}".format(MAIN_BG_COLOR))
            self.viewview.page().setLinkDelegationPolicy(
                QWebEnginePage.DelegateAllLinks)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        # Layout
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.webview.linkClicked.connect(self.sig_link_clicked)

    def set_font(self, font, fixed_font=None):
        """Set font"""
        self.webview.set_font(font, fixed_font=fixed_font)

    def set_html(self, html_text, base_url):
        """Set html text"""
        self.webview.setHtml(html_text, base_url)

    def load_url(self, url):
        if isinstance(url, QUrl):
            qurl = url
        else:
            qurl = QUrl(url)

        self.load(qurl)

    def clear(self):
        self.set_html('', self.webview.url())
Пример #8
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None

        self.editors = []
        self.filenames = []
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # 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)
Пример #9
0
class PlainText(QWidget):
    """
    Read-only editor widget with find dialog
    """
    # Signals
    focus_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def set_font(self, font, color_scheme=None):
        """Set font"""
        self.editor.set_font(font, color_scheme=color_scheme)

    def set_color_scheme(self, color_scheme):
        """Set color scheme"""
        self.editor.set_color_scheme(color_scheme)

    def set_text(self, text, is_code):
        self.editor.set_highlight_current_line(is_code)
        self.editor.set_occurrence_highlighting(is_code)
        if is_code:
            self.editor.set_language('py')
        else:
            self.editor.set_language(None)
        self.editor.set_text(text)
        self.editor.set_cursor_position('sof')

    def clear(self):
        self.editor.clear()
Пример #10
0
    def __init__(self,
                 name=None,
                 plugin=None,
                 parent=None,
                 options=DEFAULT_OPTIONS):
        super().__init__(name, plugin, parent=parent, options=options)

        self._is_running = False
        self.home_url = None
        self.server = None

        # Widgets
        self.label = QLabel(_("Package:"))
        self.url_combo = UrlComboBox(self)
        self.webview = WebView(self,
                               handle_links=self.get_option('handle_links'))
        self.find_widget = FindReplace(self)

        # Setup
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()
        self.url_combo.setMaxCount(self.get_option('max_history_entries'))
        tip = _('Write a package name here')
        self.url_combo.lineEdit().setPlaceholderText(tip)
        self.url_combo.lineEdit().setToolTip(tip)
        self.webview.setup()
        self.webview.set_zoom_factor(self.get_option('zoom_factor'))

        # Layout
        spacing = 10
        layout = QVBoxLayout()
        layout.addWidget(self.webview)
        layout.addSpacing(spacing)
        layout.addWidget(self.find_widget)
        layout.addSpacing(int(spacing / 2))
        self.setLayout(layout)

        # Signals
        self.url_combo.valid.connect(
            lambda x: self._handle_url_combo_activation())
        self.webview.loadStarted.connect(self._start)
        self.webview.loadFinished.connect(self._finish)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self._change_url)

        if not WEBENGINE:
            self.webview.iconChanged.connect(self._handle_icon_change)
Пример #11
0
    def __init__(self, name, plugin, parent=None):
        super().__init__(name, plugin, parent)

        logger.info("Initializing...")

        # Traceback MessageBox
        self.error_traceback = ''
        self.dismiss_error = False

        # Widgets
        self.dialog_manager = DialogManager()
        self.error_dlg = None
        self.shell = InternalShell(  # TODO: Move to use SpyderWidgetMixin?
            parent=parent,
            namespace=self.get_conf('namespace', {}),
            commands=self.get_conf('commands', []),
            message=self.get_conf('message', ''),
            max_line_count=self.get_conf('max_line_count'),
            profile=self.get_conf('profile', False),
            multithreaded=self.get_conf('multithreaded', False),
        )
        self.find_widget = FindReplace(self)

        # Setup
        self.setAcceptDrops(True)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.shell.toggle_wrap_mode(self.get_conf('wrap'))

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.shell.sig_help_requested.connect(self.sig_help_requested)
        self.shell.sig_exception_occurred.connect(self.handle_exception)
        self.shell.sig_focus_changed.connect(self.sig_focus_changed)
        self.shell.sig_go_to_error_requested.connect(self.go_to_error)
        self.shell.sig_redirect_stdio_requested.connect(
            self.sig_redirect_stdio_requested)
        self.shell.sig_refreshed.connect(self.sig_refreshed)
        self.shell.sig_show_status_requested.connect(
            lambda msg: self.sig_show_status_message.emit(msg, 0))
Пример #12
0
    def __init__(self, parent, filename, actions=None, ini_message=None):
        """
        Constructor.

        Parameters
        ----------
        parent : QWidget
            Parent of the widget under construction.
        filename : str
            File name of the notebook.
        actions : list of (QAction or QMenu or None) or None, optional
            Actions to be added to the context menu of the widget under
            construction. The default is None, meaning that no actions
            will be added.
        ini_message : str or None, optional
            HTML to be initially displayed in the widget. The default is
            None, meaning that an empty page is displayed initially.
        """
        super().__init__(parent)

        if os.name == 'nt':
            filename = filename.replace('/', '\\')
        self.filename = filename

        self.file_url = None
        self.server_url = None
        self.path = None

        self.notebookwidget = NotebookWidget(self, actions)
        if ini_message:
            self.notebookwidget.show_message(ini_message)
            self.static = True
        else:
            self.notebookwidget.show_blank()
            self.static = False

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.notebookwidget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.notebookwidget)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #13
0
def editor_folding_bot(base_editor_bot):
    """
    Setup CodeEditor with some text useful for folding related tests.
    """
    editor_stack, qtbot = base_editor_bot
    text = ('# dummy test file\n'
            'class a():\n'  # fold-block level-0
            '    self.b = 1\n'
            '    print(self.b)\n'
            '    \n'
            )
    finfo = editor_stack.new('foo.py', 'utf-8', text)

    find_replace = FindReplace(None, enable_replace=True)
    editor_stack.set_find_widget(find_replace)
    find_replace.set_editor(finfo.editor)
    qtbot.addWidget(editor_stack)
    return editor_stack, finfo.editor, find_replace, qtbot
Пример #14
0
def editor_folding_bot(base_editor_bot):
    """
    Setup CodeEditor with some text useful for folding related tests.
    """
    editor_stack, qtbot = base_editor_bot
    text = (
        '# dummy test file\n'
        'class a():\n'  # fold-block level-0
        '    self.b = 1\n'
        '    print(self.b)\n'
        '    \n')
    finfo = editor_stack.new('foo.py', 'utf-8', text)

    find_replace = FindReplace(None, enable_replace=True)
    editor_stack.set_find_widget(find_replace)
    find_replace.set_editor(finfo.editor)
    qtbot.addWidget(editor_stack)
    return editor_stack, finfo.editor, find_replace, qtbot
Пример #15
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        if WEBENGINE:
            self.webview.web_widget.page().setBackgroundColor(
                QColor(MAIN_BG_COLOR))
        else:
            self.webview.web_widget.setStyleSheet(
                "background:{}".format(MAIN_BG_COLOR))
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #16
0
    def __init__(self, name=None, plugin=None, parent=None):
        super().__init__(name, plugin, parent=parent)

        self._is_running = False
        self.home_url = None
        self.server = None

        # Widgets
        self.label = QLabel(_("Package:"))
        self.label.ID = PydocBrowserToolbarItems.PackageLabel

        self.url_combo = UrlComboBox(self,
                                     id_=PydocBrowserToolbarItems.UrlCombo)

        # Setup web view frame
        self.webview = FrameWebView(self,
                                    handle_links=self.get_conf('handle_links'))
        self.webview.setup()
        self.webview.set_zoom_factor(self.get_conf('zoom_factor'))
        self.webview.loadStarted.connect(self._start)
        self.webview.loadFinished.connect(self._finish)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self._change_url)
        if not WEBENGINE:
            self.webview.iconChanged.connect(self._handle_icon_change)

        # Setup find widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()
        self.url_combo.setMaxCount(self.get_conf('max_history_entries'))
        tip = _('Write a package name here, e.g. pandas')
        self.url_combo.lineEdit().setPlaceholderText(tip)
        self.url_combo.lineEdit().setToolTip(tip)
        self.url_combo.valid.connect(
            lambda x: self._handle_url_combo_activation())

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.webview)
        layout.addSpacing(1)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #17
0
    def __init__(self, name, plugin, parent):
        super().__init__(name, plugin, parent)

        # Attributes
        self.editors = []
        self.filenames = []
        self.tabwidget = None
        self.dockviewer = None
        self.wrap_action = None
        self.linenumbers_action = None
        self.editors = []
        self.filenames = []
        self.font = None

        # Widgets
        self.tabwidget = Tabs(self)
        self.find_widget = FindReplace(self)

        # Setup
        self.tabwidget.setStyleSheet(self._tabs_stylesheet)
        self.find_widget.hide()

        # Layout
        layout = QVBoxLayout()

        # TODO: Move this to the tab container directly
        if sys.platform == 'darwin':
            tab_container = QWidget(self)
            tab_container.setObjectName('tab-container')
            tab_layout = QVBoxLayout(tab_container)
            tab_layout.setContentsMargins(0, 0, 0, 0)
            tab_layout.addWidget(self.tabwidget)
            layout.addWidget(tab_container)
        else:
            layout.addWidget(self.tabwidget)

        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.tabwidget.currentChanged.connect(self.refresh)
        self.tabwidget.move_data.connect(self.move_tab)
Пример #18
0
    def __init__(self, parent):
        """Initialize widget and create layout."""
        QWidget.__init__(self, parent)

        self.editors = []
        self.filenames = []

        self.tabwidget = None
        self.menu_actions = None

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        self.menu = QMenu(self)

        options_button.setMenu(self.menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()

        layout.addWidget(self.find_widget)

        self.setLayout(layout)
Пример #19
0
    def __init__(self, parent):
        """Initialize plugin and create History main widget."""
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.dockviewer = None
        self.wrap_action = None
        self.linenumbers_action = None

        self.editors = []
        self.filenames = []

        # Initialize plugin actions, toolbutton and general signals
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.plugin_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        self.tabwidget.setCornerWidget(self.options_button)

        # 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)
Пример #20
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #21
0
    def __init__(
        self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False
    ):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option("light_background")
        self.shell = InternalShell(
            parent,
            namespace,
            commands,
            message,
            self.get_option("max_line_count"),
            self.get_plugin_font(),
            exitfunc,
            profile,
            multithreaded,
            light_background=light_background,
        )
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state))

        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts(self.find_widget)

        # Main layout
        layout = QVBoxLayout()
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Parameters
        self.shell.toggle_wrap_mode(self.get_option("wrap"))

        # Accepting drops
        self.setAcceptDrops(True)
Пример #22
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #23
0
def editor_factory(new_file=True, text=None):
    editorstack = EditorStack(None, [])
    editorstack.set_find_widget(FindReplace(editorstack))
    editorstack.set_io_actions(Mock(), Mock(), Mock(), Mock())
    if new_file:
        if not text:
            text = ('a = 1\n'
                    'print(a)\n'
                    '\n'
                    'x = 2')  # a newline is added at end
        finfo = editorstack.new('foo.py', 'utf-8', text)
        return editorstack, finfo.editor
    return editorstack, None
Пример #24
0
def setup_editor(qtbot):
    """
    Set up EditorStack with CodeEditor containing some Python code.
    The cursor is at the empty line below the code.
    Returns tuple with EditorStack and CodeEditor.
    """
    text = ('a = 1\n' 'print(a)\n' '\n' 'x = 2')  # a newline is added at end
    editorStack = EditorStack(None, [])
    editorStack.set_find_widget(FindReplace(editorStack))
    editorStack.set_io_actions(Mock(), Mock(), Mock(), Mock())
    finfo = editorStack.new('foo.py', 'utf-8', text)
    qtbot.addWidget(editorStack)
    return editorStack, finfo.editor
Пример #25
0
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        SpyderPluginWidget.__init__(self, parent)

        logger.info("Initializing...")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state:
                                          self.redirect_stdio.emit(state))
        
        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts(self.find_widget)

        # Main layout
        btn_layout = QHBoxLayout()
        btn_layout.setAlignment(Qt.AlignLeft)
        btn_layout.addStretch()
        btn_layout.addWidget(self.options_button, Qt.AlignRight)
        layout = create_plugin_layout(btn_layout)
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)

        # Traceback MessageBox
        self.error_dlg = None
        self.error_traceback = ""
        self.dismiss_error = False
Пример #26
0
def editor_cells_bot(base_editor_bot, qtbot):
    editor_stack = base_editor_bot
    text = ('# %%\n'
            '# 1 cell\n'
            '# print(1)\n'
            '# %%\n'
            '# 2 cell\n'
            '# print(2)\n'
            '# %%\n'
            '# 3 cell\n'
            '# print(3)\n')
    finfo = editor_stack.new('cells.py', 'utf-8', text)
    find_replace = FindReplace(None, enable_replace=True)
    qtbot.addWidget(editor_stack)
    return editor_stack, finfo.editor
Пример #27
0
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None
        
        self.editors = []
        self.filenames = []
        if PYQT5:        
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # 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)
Пример #28
0
    def __init__(self, parent=None):
        Qt.QSplitter.__init__(self, parent)

        self.editorstacks = []
        self.editorwindows = []

        self.menu_actions, self.io_actions = self.createMenuActions()

        self.find_widget = FindReplace(self, enable_replace=True)
        self.outlineexplorer = OutlineExplorerWidget(self, show_fullpath=False,
                                                     show_all_files=False)
        self.outlineexplorer.edit_goto.connect(self.go_to_file)
        self.editor_splitter = EditorSplitter(self, self, self.menu_actions,
                                              first=True)

        editor_widgets = Qt.QWidget(self)
        editor_layout = Qt.QVBoxLayout()
        editor_layout.setContentsMargins(0, 0, 0, 0)
        editor_widgets.setLayout(editor_layout)
        editor_layout.addWidget(self.editor_splitter)
        editor_layout.addWidget(self.find_widget)

        self.setContentsMargins(0, 0, 0, 0)
        self.addWidget(editor_widgets)
        self.addWidget(self.outlineexplorer)

        self.setStretchFactor(0, 5)
        self.setStretchFactor(1, 1)

        self.toolbar_list = None
        self.menu_list = None
        self.setup_window([], [])

        try:
            # spyder v3
            from spyder.utils.introspection.manager import IntrospectionManager
            # Set introspector
            introspector = IntrospectionManager()
            editorstack = self.editor_splitter.editorstack
            editorstack.set_introspector(introspector)
            introspector.set_editor_widget(editorstack)
        except ImportError:
            pass  # TODO: support introspection with spyder v4
Пример #29
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)

        self.webview = FrameWebView(self)
        if WEBENGINE:
            self.webview.web_widget.page().setBackgroundColor(
                QColor(MAIN_BG_COLOR))
        else:
            self.webview.web_widget.setStyleSheet(
                "background:{}".format(MAIN_BG_COLOR))
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview.web_widget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #30
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only editor
        self.editor = codeeditor.CodeEditor(self)
        self.editor.setup_editor(linenumbers=False, language='py',
                                 scrollflagarea=False, edge_line=False)
        self.editor.focus_changed.connect(lambda: self.focus_changed.emit())
        self.editor.setReadOnly(True)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #31
0
class WebBrowser(QWidget):
    """
    Web browser widget.
    """
    def __init__(self, parent=None, options_button=None, handle_links=True):
        QWidget.__init__(self, parent)

        self.home_url = None

        self.webview = WebView(self, handle_links=handle_links)
        self.webview.setup()
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)

        home_button = create_toolbutton(self,
                                        icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)

        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)

        def pageact2btn(prop, icon=None):
            return action2button(self.webview.pageAction(prop),
                                 parent=self.webview,
                                 icon=icon)

        refresh_button = pageact2btn(QWebEnginePage.Reload,
                                     icon=ima.icon('refresh'))
        stop_button = pageact2btn(QWebEnginePage.Stop, icon=ima.icon('stop'))
        previous_button = pageact2btn(QWebEnginePage.Back,
                                      icon=ima.icon('previous'))
        next_button = pageact2btn(QWebEnginePage.Forward,
                                  icon=ima.icon('next'))

        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(
            lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        if not WEBENGINE:
            self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self,
                                        icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)

        if options_button:
            hlayout.addWidget(options_button)

        layout = create_plugin_layout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def get_label(self):
        """Return address label text"""
        return _("Address:")

    def set_home_url(self, text):
        """Set home URL"""
        self.home_url = QUrl(text)

    def set_url(self, url):
        """Set current URL"""
        self.url_changed(url)
        self.go_to(url)

    def go_to(self, url_or_text):
        """Go to page *address*"""
        if is_text_string(url_or_text):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.webview.load(url)

    @Slot()
    def go_home(self):
        """Go to home page"""
        if self.home_url is not None:
            self.set_url(self.home_url)

    def text_to_url(self, text):
        """Convert text address into QUrl object"""
        return QUrl(text)

    def url_combo_activated(self, valid):
        """Load URL from combo box first item"""
        text = to_text_string(self.url_combo.currentText())
        self.go_to(self.text_to_url(text))

    def load_finished(self, ok):
        if not ok:
            self.webview.setHtml(_("Unable to load page"))

    def url_to_text(self, url):
        """Convert QUrl object to displayed text in combo box"""
        return url.toString()

    def url_changed(self, url):
        """Displayed URL has changed -> updating URL combo box"""
        self.url_combo.add_text(self.url_to_text(url))

    def icon_changed(self):
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    @Slot(bool)
    def toggle_find_widget(self, state):
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #32
0
    def __init__(self, parent=None, options_button=None, handle_links=True):
        QWidget.__init__(self, parent)

        self.home_url = None

        self.webview = WebView(self, handle_links=handle_links)
        self.webview.setup()
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)

        home_button = create_toolbutton(self,
                                        icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)

        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)

        def pageact2btn(prop, icon=None):
            return action2button(self.webview.pageAction(prop),
                                 parent=self.webview,
                                 icon=icon)

        refresh_button = pageact2btn(QWebEnginePage.Reload,
                                     icon=ima.icon('refresh'))
        stop_button = pageact2btn(QWebEnginePage.Stop, icon=ima.icon('stop'))
        previous_button = pageact2btn(QWebEnginePage.Back,
                                      icon=ima.icon('previous'))
        next_button = pageact2btn(QWebEnginePage.Forward,
                                  icon=ima.icon('next'))

        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(
            lambda: stop_button.setEnabled(False))

        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        if not WEBENGINE:
            self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self,
                                        icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)

        if options_button:
            hlayout.addWidget(options_button)

        layout = create_plugin_layout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #33
0
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):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        self.tabwidget = None
        self.menu_actions = None

        self.help = None  # Help plugin
        self.historylog = None  # History log plugin
        self.variableexplorer = None  # Variable explorer 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.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.update_plugin_title.emit()

    def set_variableexplorer(self, variableexplorer):
        """Set variable explorer plugin"""
        self.variableexplorer = variableexplorer

    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 <b>%s</b>."
                  "<br><br>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:
                        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:
                        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')
            ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
            ar_state = CONF.get('variable_explorer', 'autorefresh')

            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,
                autorefresh_timeout=ar_timeout,
                autorefresh_state=ar_state,
                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 python and self.help is not None:
            shellwidget.shell.set_help(self.help)
            shellwidget.shell.set_help_enabled(
                CONF.get('help', 'connect/python_console'))
        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')
            else:
                tab_name = osp.basename(fname)
                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.shellwidgets.insert(index, shellwidget)
        self.filenames.insert(index, fname)
        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)

    #------ 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)
        if self.help is not None:
            self.help.set_shell(shell.shell)
        if self.variableexplorer is not None:
            self.variableexplorer.add_shellwidget(shell)

    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)
        if self.variableexplorer is not None:
            self.variableexplorer.remove_shellwidget(shell_id)

    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        title = _('Python console')
        if self.filenames:
            index = self.tabwidget.currentIndex()
            fname = self.filenames[index]
            if fname:
                title += ' - ' + to_text_string(fname)
        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 when
        this plugin's dockwidget is raised on top-level
        """
        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.help = self.main.help
        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)
        expl = self.main.explorer
        if expl is not None:
            expl.open_terminal.connect(self.open_terminal)
            expl.open_interpreter.connect(self.open_interpreter)
        pexpl = self.main.projects
        if pexpl is not None:
            pexpl.open_terminal.connect(self.open_terminal)
            pexpl.open_interpreter.connect(self.open_interpreter)

    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.variableexplorer.set_shellwidget_from_id(id(shellwidget))
            self.help.set_shell(shellwidget.shell)
        self.main.last_console_plugin_focus_was_python = True
        self.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)
        help_n = 'connect_to_oi'
        help_o = CONF.get('help', 'connect/python_console')
        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 help_n in options:
                if isinstance(shellwidget, ExternalPythonShell):
                    shellwidget.shell.set_help_enabled(help_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()
Пример #34
0
    def __init__(self, parent):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        self.tabwidget = None
        self.menu_actions = None

        self.help = None  # Help plugin
        self.historylog = None  # History log plugin
        self.variableexplorer = None  # Variable explorer 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)
Пример #35
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage
    focus_changed = Signal()

    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None

        self.editors = []
        self.filenames = []
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self,
                                           text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # 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)

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('History log')

    def get_plugin_icon(self):
        """Return widget icon"""
        return ima.icon('history')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(self,
                                       _("History..."),
                                       None,
                                       ima.icon('history'),
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        self.wrap_action = create_action(self,
                                         _("Wrap lines"),
                                         toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked(self.get_option('wrap'))
        self.menu_actions = [history_action, self.wrap_action]
        return self.menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.ipyconsole, self)

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        #        self.main.console.set_historylog(self)
        self.main.console.shell.refresh.connect(self.refresh_plugin)

    def update_font(self):
        """Update font from Preferences"""
        color_scheme = self.get_color_scheme()
        font = self.get_plugin_font()
        for editor in self.editors:
            editor.set_font(font, color_scheme)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = 'color_scheme_name'
        color_scheme_o = self.get_color_scheme()
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)

    #------ Private API --------------------------------------------------------
    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)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for add_history signal emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
        else:
            language = 'bat'
        editor.setup_editor(linenumbers=False,
                            language=language,
                            scrollflagarea=False)
        editor.focus_changed.connect(lambda: self.focus_changed.emit())
        editor.setReadOnly(True)
        color_scheme = self.get_color_scheme()
        editor.set_font(self.get_plugin_font(), color_scheme)
        editor.toggle_wrap_mode(self.get_option('wrap'))

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')

        self.editors.append(editor)
        self.filenames.append(filename)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setCurrentIndex(index)

    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for append_to_history signal emitted by shell instance
        """
        if not is_text_string(filename):  # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option('go_to_eof'):
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)

    @Slot()
    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInt(self, _('History'),
                                           _('Maximum entries'),
                                           self.get_option('max_entries'), 10,
                                           10000)
        if valid:
            self.set_option('max_entries', depth)

    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
Пример #36
0
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):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        self.tabwidget = None
        self.menu_actions = None

        self.help = None # Help plugin
        self.historylog = None # History log plugin
        self.variableexplorer = None # Variable explorer 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.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.update_plugin_title.emit()

    def set_variableexplorer(self, variableexplorer):
        """Set variable explorer plugin"""
        self.variableexplorer = variableexplorer
    
    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 <b>%s</b>."
                  "<br><br>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:
                        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:
                        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')
            ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
            ar_state = CONF.get('variable_explorer', 'autorefresh')

            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,
                           autorefresh_timeout=ar_timeout,
                           autorefresh_state=ar_state,
                           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 python and self.help is not None:
            shellwidget.shell.set_help(self.help)
            shellwidget.shell.set_help_enabled(
                               CONF.get('help', 'connect/python_console'))
        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')
            else:
                tab_name = osp.basename(fname)
                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.shellwidgets.insert(index, shellwidget)
        self.filenames.insert(index, fname)
        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)
        
    #------ 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)
        if self.help is not None:
            self.help.set_shell(shell.shell)
        if self.variableexplorer is not None:
            self.variableexplorer.add_shellwidget(shell)

    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)
        if self.variableexplorer is not None:
            self.variableexplorer.remove_shellwidget(shell_id)
        
    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        title = _('Python console')
        if self.filenames:
            index = self.tabwidget.currentIndex()
            fname = self.filenames[index]
            if fname:
                title += ' - ' + to_text_string(fname)
        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 when
        this plugin's dockwidget is raised on top-level
        """
        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.help = self.main.help
        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)
        expl = self.main.explorer
        if expl is not None:
            expl.open_terminal.connect(self.open_terminal)
            expl.open_interpreter.connect(self.open_interpreter)
        pexpl = self.main.projects
        if pexpl is not None:
            pexpl.open_terminal.connect(self.open_terminal)
            pexpl.open_interpreter.connect(self.open_interpreter)

    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.variableexplorer.set_shellwidget_from_id(id(shellwidget))
            self.help.set_shell(shellwidget.shell)
        self.main.last_console_plugin_focus_was_python = True
        self.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)
        help_n = 'connect_to_oi'
        help_o = CONF.get('help', 'connect/python_console')
        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 help_n in options:
                if isinstance(shellwidget, ExternalPythonShell):
                    shellwidget.shell.set_help_enabled(help_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()
Пример #37
0
class PydocBrowser(PluginMainWidget):
    """PyDoc browser widget."""

    ENABLE_SPINNER = True

    # --- Signals
    # ------------------------------------------------------------------------
    sig_load_finished = Signal()
    """
    This signal is emitted to indicate the help page has finished loading.
    """
    def __init__(self, name=None, plugin=None, parent=None):
        super().__init__(name, plugin, parent=parent)

        self._is_running = False
        self.home_url = None
        self.server = None

        # Widgets
        self.label = QLabel(_("Package:"))
        self.url_combo = UrlComboBox(self)
        self.webview = FrameWebView(self,
                                    handle_links=self.get_conf('handle_links'))
        self.find_widget = FindReplace(self)

        # Setup
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()
        self.url_combo.setMaxCount(self.get_conf('max_history_entries'))
        tip = _('Write a package name here, e.g. pandas')
        self.url_combo.lineEdit().setPlaceholderText(tip)
        self.url_combo.lineEdit().setToolTip(tip)
        self.webview.setup()
        self.webview.set_zoom_factor(self.get_conf('zoom_factor'))

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.webview)
        layout.addSpacing(1)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.url_combo.valid.connect(
            lambda x: self._handle_url_combo_activation())
        self.webview.loadStarted.connect(self._start)
        self.webview.loadFinished.connect(self._finish)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self._change_url)

        if not WEBENGINE:
            self.webview.iconChanged.connect(self._handle_icon_change)

    # --- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _('Online help')

    def get_focus_widget(self):
        self.url_combo.lineEdit().selectAll()
        return self.url_combo

    def setup(self):
        # Actions
        home_action = self.create_action(
            PydocBrowserActions.Home,
            text=_("Home"),
            tip=_("Home"),
            icon=self.create_icon('home'),
            triggered=self.go_home,
        )
        find_action = self.create_action(
            PydocBrowserActions.Find,
            text=_("Find"),
            tip=_("Find text"),
            icon=self.create_icon('find'),
            toggled=self.toggle_find_widget,
            initial=False,
        )
        stop_action = self.get_action(WebViewActions.Stop)
        refresh_action = self.get_action(WebViewActions.Refresh)

        # Toolbar
        toolbar = self.get_main_toolbar()
        for item in [
                self.get_action(WebViewActions.Back),
                self.get_action(WebViewActions.Forward),
                refresh_action,
                stop_action,
                home_action,
                self.label,
                self.url_combo,
                self.get_action(WebViewActions.ZoomIn),
                self.get_action(WebViewActions.ZoomOut),
                find_action,
        ]:
            self.add_item_to_toolbar(
                item,
                toolbar=toolbar,
                section=PydocBrowserMainToolbarSections.Main,
            )

        # Signals
        self.find_widget.visibility_changed.connect(find_action.setChecked)

        for __, action in self.get_actions().items():
            if action:
                # IMPORTANT: Since we are defining the main actions in here
                # and the context is WidgetWithChildrenShortcut we need to
                # assign the same actions to the children widgets in order
                # for shortcuts to work
                self.webview.addAction(action)

        self.sig_toggle_view_changed.connect(self.initialize)

    def update_actions(self):
        stop_action = self.get_action(WebViewActions.Stop)
        refresh_action = self.get_action(WebViewActions.Refresh)

        refresh_action.setVisible(not self._is_running)
        stop_action.setVisible(self._is_running)

    # --- Private API
    # ------------------------------------------------------------------------
    def _start(self):
        """Webview load started."""
        self._is_running = True
        self.start_spinner()
        self.update_actions()

    def _finish(self, code):
        """Webview load finished."""
        self._is_running = False
        self.stop_spinner()
        self.update_actions()
        self.sig_load_finished.emit()

    def _continue_initialization(self):
        """Load home page."""
        self.go_home()
        QApplication.restoreOverrideCursor()

    def _handle_url_combo_activation(self):
        """Load URL from combo box first item."""
        if not self._is_running:
            text = str(self.url_combo.currentText())
            self.go_to(self.text_to_url(text))
        else:
            self.get_action(WebViewActions.Stop).trigger()

        self.get_focus_widget().setFocus()

    def _change_url(self, url):
        """
        Displayed URL has changed -> updating URL combo box.
        """
        self.url_combo.add_text(self.url_to_text(url))

    def _handle_icon_change(self):
        """
        Handle icon changes.
        """
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    # --- Qt overrides
    # ------------------------------------------------------------------------
    def closeEvent(self, event):
        self.server.quit_server()
        event.accept()

    # --- Public API
    # ------------------------------------------------------------------------
    def load_history(self, history):
        """
        Load history.

        Parameters
        ----------
        history: list
            List of searched items.
        """
        self.url_combo.addItems(history)

    @Slot(bool)
    def initialize(self, checked=True):
        """
        Start pydoc server.

        Parameters
        ----------
        checked: bool, optional
            This method is connected to the `sig_toggle_view_changed` signal,
            so that the first time the widget is made visible it will start
            the server. Default is True.
        """
        if checked and self.server is None:
            self.sig_toggle_view_changed.disconnect(self.initialize)
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            QApplication.processEvents()
            self.start_server()

    def is_server_running(self):
        """Return True if pydoc server is already running."""
        return self.server is not None

    def start_server(self):
        """Start pydoc server."""
        if self.server is None:
            self.set_home_url('http://127.0.0.1:{}/'.format(PORT))
        elif self.server.isRunning():
            self.server.sig_server_started.disconnect(
                self._continue_initialization)
            self.server.quit()

        self.server = PydocServer(port=PORT)
        self.server.sig_server_started.connect(self._continue_initialization)
        self.server.start()

    def quit_server(self):
        """Quit the server."""
        if self.server is None:
            return

        if self.server.is_running():
            self.server.sig_server_started.disconnect(
                self._continue_initialization)
            self.server.quit_server()
            self.server.quit()

    def get_label(self):
        """Return address label text"""
        return _("Package:")

    def reload(self):
        """Reload page."""
        if self.server:
            self.webview.reload()

    def text_to_url(self, text):
        """
        Convert text address into QUrl object.

        Parameters
        ----------
        text: str
            Url address.
        """
        if text != 'about:blank':
            text += '.html'

        if text.startswith('/'):
            text = text[1:]

        return QUrl(self.home_url.toString() + text)

    def url_to_text(self, url):
        """
        Convert QUrl object to displayed text in combo box.

        Parameters
        ----------
        url: QUrl
            Url address.
        """
        string_url = url.toString()
        if 'about:blank' in string_url:
            return 'about:blank'
        elif 'get?key=' in string_url or 'search?key=' in string_url:
            return url.toString().split('=')[-1]

        return osp.splitext(str(url.path()))[0][1:]

    def set_home_url(self, text):
        """
        Set home URL.

        Parameters
        ----------
        text: str
            Home url address.
        """
        self.home_url = QUrl(text)

    def set_url(self, url):
        """
        Set current URL.

        Parameters
        ----------
        url: QUrl or str
            Url address.
        """
        self._change_url(url)
        self.go_to(url)

    def go_to(self, url_or_text):
        """
        Go to page URL.
        """
        if isinstance(url_or_text, str):
            url = QUrl(url_or_text)
        else:
            url = url_or_text

        self.webview.load(url)

    @Slot()
    def go_home(self):
        """
        Go to home page.
        """
        if self.home_url is not None:
            self.set_url(self.home_url)

    def get_zoom_factor(self):
        """
        Get the current zoom factor.

        Returns
        -------
        int
            Zoom factor.
        """
        return self.webview.get_zoom_factor()

    def get_history(self):
        """
        Return the list of history items in the combobox.

        Returns
        -------
        list
            List of strings.
        """
        history = []
        for index in range(self.url_combo.count()):
            history.append(str(self.url_combo.itemText(index)))

        return history

    @Slot(bool)
    def toggle_find_widget(self, state):
        """
        Show/hide the find widget.

        Parameters
        ----------
        state: bool
            True to show and False to hide the find widget.
        """
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #38
0
class NotebookClient(QWidget):
    """
    Notebook client for Spyder.

    This is a widget composed of a NotebookWidget and a find dialog to
    render notebooks.
    """
    def __init__(self, plugin, filename, ini_message=None):
        """Constructor."""
        super(NotebookClient, self).__init__(plugin)

        if os.name == 'nt':
            filename = filename.replace('/', '\\')
        self.filename = filename

        self.file_url = None
        self.server_url = None
        self.path = None

        self.plugin_actions = plugin.get_plugin_actions()
        self.notebookwidget = NotebookWidget(self)
        if WEBENGINE:
            self.notebookwidget.loadFinished.connect(self.hide_header)
        else:
            self.notebookwidget.selectionChanged.connect(self.hide_header)
        self.notebookwidget.urlChanged.connect(self.hide_header)
        if ini_message:
            self.notebookwidget.show_message(ini_message)
        else:
            self.notebookwidget.show_blank()

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.notebookwidget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.notebookwidget)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def add_token(self, url):
        """Add notebook token to a given url."""
        token_url = url + '?token={}'.format(self.token)
        return token_url

    def register(self, server_info):
        """Register attributes that can be computed with the server info."""
        # Path relative to the server directory
        self.path = os.path.relpath(self.filename,
                                    start=server_info['notebook_dir'])

        # Replace backslashes on Windows
        if os.name == 'nt':
            self.path = self.path.replace('\\', '/')

        # Server url to send requests to
        self.server_url = server_info['url']

        # Server token
        self.token = server_info['token']

        url = url_path_join(self.server_url, 'notebooks',
                            url_escape(self.path))

        # Set file url to load this notebook
        self.file_url = self.add_token(url)

    def go_to(self, url_or_text):
        """Go to page utl."""
        if is_text_string(url_or_text):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.notebookwidget.load(url)

    def load_notebook(self):
        """Load the associated notebook."""
        self.go_to(self.file_url)

    def hide_header(self):
        """Hide the header of the notebook."""
        self.notebookwidget.set_class_value("#header-container", "hidden")

    def get_filename(self):
        """Get notebook's filename."""
        return self.filename

    def get_short_name(self):
        """Get a short name for the notebook."""
        sname = osp.splitext(osp.basename(self.filename))[0]
        if len(sname) > 20:
            fm = QFontMetrics(QFont())
            sname = fm.elidedText(sname, Qt.ElideRight, 110)
        return sname

    def save(self):
        """Save current notebook."""
        self.notebookwidget.click("#save-notbook button")

    def get_session_url(self):
        """Get the kernel sessions url of the client."""
        return self.add_token(url_path_join(self.server_url, 'api/sessions'))

    def get_kernel_id(self):
        """
        Get the kernel id of the client.

        Return a str with the kernel id or None.
        """
        sessions_url = self.get_session_url()
        sessions_req = requests.get(sessions_url).content.decode()
        sessions = json.loads(sessions_req)

        if os.name == 'nt':
            path = self.path.replace('\\', '/')
        else:
            path = self.path

        for session in sessions:
            notebook_path = session.get('notebook', {}).get('path')
            if notebook_path is not None and notebook_path == path:
                kernel_id = session['kernel']['id']
                return kernel_id

    def shutdown_kernel(self):
        """Shutdown the kernel of the client."""
        kernel_id = self.get_kernel_id()

        if kernel_id:
            delete_url = self.add_token(
                url_path_join(self.server_url, 'api/kernels/', kernel_id))
            delete_req = requests.delete(delete_url)
            if delete_req.status_code != 204:
                QMessageBox.warning(
                    self, _("Server error"),
                    _("The Jupyter Notebook server "
                      "failed to shutdown the kernel "
                      "associated with this notebook. "
                      "If you want to shut it down, "
                      "you'll have to close Spyder."))
Пример #39
0
class Console(SpyderPluginWidget):
    """
    Console widget
    """
    CONF_SECTION = 'internal_console'
    focus_changed = Signal()
    redirect_stdio = Signal(bool)
    edit_goto = Signal(str, int, str)
    
    def __init__(self, parent=None, namespace=None, commands=[], message=None,
                 exitfunc=None, profile=False, multithreaded=False):
        SpyderPluginWidget.__init__(self, parent)

        logger.info("Initializing...")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent, namespace, commands, message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(), exitfunc, profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(lambda state:
                                          self.redirect_stdio.emit(state))
        
        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts(self.find_widget)

        # Main layout
        btn_layout = QHBoxLayout()
        btn_layout.setAlignment(Qt.AlignLeft)
        btn_layout.addStretch()
        btn_layout.addWidget(self.options_button, Qt.AlignRight)
        layout = create_plugin_layout(btn_layout)
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
        
        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))
            
        # Accepting drops
        self.setAcceptDrops(True)

        # Traceback MessageBox
        self.error_dlg = None
        self.error_traceback = ""
        self.dismiss_error = False

    #------ Private API --------------------------------------------------------
    def set_historylog(self, historylog):
        """Bind historylog instance to this console
        Not used anymore since v2.0"""
        historylog.add_history(self.shell.history_filename)
        self.shell.append_to_history.connect(historylog.append_to_history)

    def set_help(self, help_plugin):
        """Bind help instance to this console"""
        self.shell.help = help_plugin

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Internal console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell

    def update_font(self):
        """Update font from Preferences"""
        font = self.get_plugin_font()
        self.shell.set_font(font)

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.dialog_manager.close_all()
        self.shell.exit_interpreter()
        return True
        
    def refresh_plugin(self):
        pass
    
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        quit_action = create_action(self, _("&Quit"),
                                    icon=ima.icon('exit'), 
                                    tip=_("Quit"),
                                    triggered=self.quit)
        self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q")
        run_action = create_action(self, _("&Run..."), None,
                            ima.icon('run_small'),
                            _("Run a Python script"),
                            triggered=self.run_script)
        environ_action = create_action(self,
                            _("Environment variables..."),
                            icon=ima.icon('environ'),
                            tip=_("Show and edit environment variables"
                                        " (for current session)"),
                            triggered=self.show_env)
        syspath_action = create_action(self,
                            _("Show sys.path contents..."),
                            icon=ima.icon('syspath'),
                            tip=_("Show (read-only) sys.path"),
                            triggered=self.show_syspath)
        buffer_action = create_action(self,
                            _("Buffer..."), None,
                            tip=_("Set maximum line count"),
                            triggered=self.change_max_line_count)
        exteditor_action = create_action(self,
                            _("External editor path..."), None, None,
                            _("Set external editor executable path"),
                            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                            _("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(self.get_option('wrap'))
        calltips_action = create_action(self, _("Display balloon tips"),
            toggled=self.toggle_calltips)
        calltips_action.setChecked(self.get_option('calltips'))
        codecompletion_action = create_action(self,
                                          _("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(self.get_option('codecompletion/auto'))
        codecompenter_action = create_action(self,
                                    _("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(self.get_option(
                                                    'codecompletion/enter_key'))
        
        option_menu = QMenu(_('Internal console settings'), self)
        option_menu.setIcon(ima.icon('tooloptions'))
        add_actions(option_menu, (buffer_action, wrap_action,
                                  calltips_action, codecompletion_action,
                                  codecompenter_action, exteditor_action))
                    
        plugin_actions = [None, run_action, environ_action, syspath_action,
                          option_menu, MENU_SEPARATOR, quit_action,
                          self.undock_action]

        return plugin_actions
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        # Connecting the following signal once the dockwidget has been created:
        self.shell.exception_occurred.connect(self.exception_occurred)
    
    def exception_occurred(self, text, is_traceback):
        """
        Exception ocurred in the internal console.

        Show a QDialog or the internal console to warn the user.
        """
        # Skip errors without traceback or dismiss
        if (not is_traceback and self.error_dlg is None) or self.dismiss_error:
            return

        if CONF.get('main', 'show_internal_errors'):
            if self.error_dlg is None:
                self.error_dlg = SpyderErrorDialog(self)
                self.error_dlg.close_btn.clicked.connect(self.close_error_dlg)
                self.error_dlg.rejected.connect(self.remove_error_dlg)
                self.error_dlg.details.go_to_error.connect(self.go_to_error)
                self.error_dlg.show()
            self.error_dlg.append_traceback(text)
        elif DEV or get_debug_level():
            self.dockwidget.show()
            self.dockwidget.raise_()

    def close_error_dlg(self):
        """Close error dialog."""
        if self.error_dlg.dismiss_box.isChecked():
            self.dismiss_error = True
        self.error_dlg.reject()

    def remove_error_dlg(self):
        """Remove error dialog."""
        self.error_dlg = None

    #------ Public API ---------------------------------------------------------
    @Slot()
    def quit(self):
        """Quit mainwindow"""
        self.main.close()
    
    @Slot()
    def show_env(self):
        """Show environment variables"""
        self.dialog_manager.show(EnvDialog())
    
    @Slot()
    def show_syspath(self):
        """Show sys.path"""
        editor = CollectionsEditor()
        editor.setup(sys.path, title="sys.path", readonly=True,
                     width=600, icon=ima.icon('syspath'))
        self.dialog_manager.show(editor)
    
    @Slot()
    def run_script(self, filename=None, silent=False, set_focus=False,
                   args=None):
        """Run a Python script"""
        if filename is None:
            self.shell.interpreter.restore_stds()
            filename, _selfilter = getopenfilename(
                    self, _("Run Python script"), getcwd_or_home(),
                    _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
            self.shell.interpreter.redirect_stds()
            if filename:
                os.chdir( osp.dirname(filename) )
                filename = osp.basename(filename)
            else:
                return
        logger.debug("Running script with %s", args)
        filename = osp.abspath(filename)
        rbs = remove_backslashes
        command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.shell.write(command+'\n')
        self.shell.run_command(command)

            
    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_script(fname, int(lnb))
            
    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InternalShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.edit_goto.emit(osp.abspath(filename), goto, '')
        
    def execute_lines(self, lines):
        """Execute lines and give focus to shell"""
        self.shell.execute_lines(to_text_string(lines))
        self.shell.setFocus()

    @Slot()
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInt(self, _('Buffer'),
                                           _('Maximum line count'),
                                           self.get_option('max_line_count'),
                                           0, 1000000)
        if valid:
            self.shell.setMaximumBlockCount(mlc)
            self.set_option('max_line_count', mlc)

    @Slot()
    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(self, _('External editor'),
                          _('External editor executable path:'),
                          QLineEdit.Normal,
                          self.get_option('external_editor/path'))
        if valid:
            self.set_option('external_editor/path', to_text_string(path))
    
    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
    
    @Slot(bool)
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        self.set_option('calltips', checked)
    
    @Slot(bool)
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        self.shell.set_codecompletion_auto(checked)
        self.set_option('codecompletion/auto', checked)
    
    @Slot(bool)
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        self.set_option('codecompletion/enter_key', checked)
                
    #----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):
                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()
        if source.hasUrls():
            pathlist = mimedata2url(source)
            self.shell.drop_pathlist(pathlist)
        elif source.hasText():
            lines = to_text_string(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #40
0
class HistoryLog(SpyderPluginWidget):
    """History log plugin."""

    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage
    focus_changed = Signal()

    def __init__(self, parent):
        """Initialize plugin and create History main widget."""
        SpyderPluginWidget.__init__(self, parent)

        self.tabwidget = None
        self.dockviewer = None
        self.wrap_action = None
        self.linenumbers_action = None

        self.editors = []
        self.filenames = []

        # Initialize plugin actions, toolbutton and general signals
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.plugin_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        self.tabwidget.setCornerWidget(self.options_button)

        # 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)

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title."""
        return _('History log')
    
    def get_plugin_icon(self):
        """Return widget icon."""
        return ima.icon('history')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        self.history_action = create_action(self, _("History..."),
                                       None, ima.icon('history'),
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        self.wrap_action = create_action(self, _("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked( self.get_option('wrap') )
        self.linenumbers_action = create_action(
                self, _("Show line numbers"), toggled=self.toggle_line_numbers)
        self.linenumbers_action.setChecked(self.get_option('line_numbers'))

        menu_actions = [self.history_action, self.wrap_action,
                        self.linenumbers_action]
        return menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.ipyconsole, self)
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
#        self.main.console.set_historylog(self)
        self.main.console.shell.refresh.connect(self.refresh_plugin)

    def update_font(self):
        """Update font from Preferences"""
        color_scheme = self.get_color_scheme()
        font = self.get_plugin_font()
        for editor in self.editors:
            editor.set_font(font, color_scheme)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = 'color_scheme_name'
        color_scheme_o = self.get_color_scheme()
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        linenb_n = 'line_numbers'
        linenb_o = self.get_option(linenb_n)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)
            if linenb_n in options:
                editor.toggle_line_numbers(linenumbers=linenb_o, markers=False)

    #------ Private API --------------------------------------------------------
    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)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for add_history signal emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
        else:
            language = 'bat'
        editor.setup_editor(linenumbers=self.get_option('line_numbers'),
                            language=language,
                            scrollflagarea=False)
        editor.focus_changed.connect(lambda: self.focus_changed.emit())
        editor.setReadOnly(True)
        color_scheme = self.get_color_scheme()
        editor.set_font( self.get_plugin_font(), color_scheme )
        editor.toggle_wrap_mode( self.get_option('wrap') )

        text, _ = encoding.read(filename)
        text = normalize_eols(text)
        linebreaks = [m.start() for m in re.finditer('\n', text)]
        maxNline = self.get_option('max_entries')
        if len(linebreaks) > maxNline:
            text = text[linebreaks[-maxNline - 1] + 1:]
            encoding.write(text, filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')

        self.editors.append(editor)
        self.filenames.append(filename)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setCurrentIndex(index)

    @Slot(str, str)
    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for append_to_history signal emitted by shell instance
        """
        if not is_text_string(filename): # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option('go_to_eof'):
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
    
    @Slot()
    def change_history_depth(self):
        "Change history max entries"""
        depth, valid = QInputDialog.getInt(self, _('History'),
                                       _('Maximum entries'),
                                       self.get_option('max_entries'),
                                       10, 10000)
        if valid:
            self.set_option('max_entries', depth)

    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)

    @Slot(bool)
    def toggle_line_numbers(self, checked):
        """Toggle line numbers."""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_line_numbers(linenumbers=checked, markers=False)
        self.set_option('line_numbers', checked)
Пример #41
0
class PlainText(QWidget):
    """
    Read-only editor widget with find dialog
    """
    # Signals
    focus_changed = Signal()

    sig_custom_context_menu_requested = Signal(QPoint)

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.editor = None

        # Read-only simple code editor
        self.editor = SimpleCodeEditor(self)
        self.editor.setup_editor(
            language='py',
            highlight_current_line=False,
            linenumbers=False,
        )
        self.editor.sig_focus_changed.connect(self.focus_changed)
        self.editor.setReadOnly(True)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.editor)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.editor)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        self.editor.customContextMenuRequested.connect(
            self.sig_custom_context_menu_requested)

    def set_font(self, font, color_scheme=None):
        """Set font"""
        self.editor.set_color_scheme(color_scheme)
        self.editor.set_font(font)

    def set_color_scheme(self, color_scheme):
        """Set color scheme"""
        self.editor.set_color_scheme(color_scheme)

    def set_text(self, text, is_code):
        if is_code:
            self.editor.set_language('py')
        else:
            self.editor.set_language(None)

        self.editor.set_text(text)
        self.editor.set_cursor_position('sof')

    def clear(self):
        self.editor.clear()

    def set_wrap_mode(self, value):
        self.editor.toggle_wrap_mode(value)

    def copy(self):
        self.editor.copy()

    def select_all(self):
        self.editor.selectAll()
Пример #42
0
class HistoryWidget(PluginMainWidget):
    """
    History plugin main widget.
    """

    # Signals
    sig_focus_changed = Signal()
    """
    This signal is emitted when the focus of the code editor storing history
    changes.
    """

    def __init__(self, name, plugin, parent):
        super().__init__(name, plugin, parent)

        # Attributes
        self.editors = []
        self.filenames = []
        self.tabwidget = None
        self.dockviewer = None
        self.wrap_action = None
        self.linenumbers_action = None
        self.filenames = []
        self.font = None

        # Widgets
        self.tabwidget = Tabs(self)
        self.find_widget = FindReplace(self)

        # Setup
        self.tabwidget.setStyleSheet(self._tabs_stylesheet)
        self.find_widget.hide()

        # Layout
        layout = QVBoxLayout()

        # TODO: Move this to the tab container directly
        if sys.platform == 'darwin':
            tab_container = QWidget(self)
            tab_container.setObjectName('tab-container')
            tab_layout = QVBoxLayout(tab_container)
            tab_layout.setContentsMargins(0, 0, 0, 0)
            tab_layout.addWidget(self.tabwidget)
            layout.addWidget(tab_container)
        else:
            layout.addWidget(self.tabwidget)

        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Signals
        self.tabwidget.currentChanged.connect(self.refresh)
        self.tabwidget.move_data.connect(self.move_tab)

    # --- PluginMainWidget API
    # ------------------------------------------------------------------------
    def get_title(self):
        return _('History')

    def get_focus_widget(self):
        return self.tabwidget.currentWidget()

    def setup(self):
        # Actions
        self.wrap_action = self.create_action(
            HistoryWidgetActions.ToggleWrap,
            text=_("Wrap lines"),
            toggled=True,
            initial=self.get_conf('wrap'),
            option='wrap'
        )
        self.linenumbers_action = self.create_action(
            HistoryWidgetActions.ToggleLineNumbers,
            text=_("Show line numbers"),
            toggled=True,
            initial=self.get_conf('line_numbers'),
            option='line_numbers'
        )

        # Menu
        menu = self.get_options_menu()
        for item in [self.wrap_action, self.linenumbers_action]:
            self.add_item_to_menu(
                item,
                menu=menu,
                section=HistoryWidgetOptionsMenuSections.Main,
            )

    def update_actions(self):
        pass

    @on_conf_change(option='wrap')
    def on_wrap_update(self, value):
        for editor in self.editors:
            editor.toggle_wrap_mode(value)

    @on_conf_change(option='line_numbers')
    def on_line_numbers_update(self, value):
        for editor in self.editors:
            editor.toggle_line_numbers(value)

    @on_conf_change(option='selected', section='appearance')
    def on_color_scheme_change(self, value):
        for editor in self.editors:
            editor.set_color_scheme(value)

    # --- Public API
    # ------------------------------------------------------------------------
    def update_font(self, font, color_scheme):
        """
        Update font of the code editor.

        Parameters
        ----------
        font: QFont
            Font object.
        color_scheme: str
            Name of the color scheme to use.
        """
        self.color_scheme = color_scheme
        self.font = font

        for editor in self.editors:
            editor.set_font(font)
            editor.set_color_scheme(color_scheme)

    def move_tab(self, index_from, index_to):
        """
        Move tab.

        Parameters
        ----------
        index_from: int
            Move tab from this index.
        index_to: int
            Move tab to this index.

        Notes
        -----
        Tabs themselves have already been moved by the history.tabwidget.
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    def get_filename_text(self, filename):
        """
        Read and return content from filename.

        Parameters
        ----------
        filename: str
            The file path to read.

        Returns
        -------
        str
            Content of the filename.
        """
        # Avoid a possible error when reading the history file
        try:
            text, _ = encoding.read(filename)
        except (IOError, OSError):
            text = "# Previous history could not be read from disk, sorry\n\n"

        text = normalize_eols(text)
        linebreaks = [m.start() for m in re.finditer('\n', text)]

        if len(linebreaks) > MAX_LINES:
            text = text[linebreaks[-MAX_LINES - 1] + 1:]
            # Avoid an error when trying to write the trimmed text to disk.
            # See spyder-ide/spyder#9093.
            try:
                encoding.write(text, filename)
            except (IOError, OSError):
                pass

        return text

    def add_history(self, filename):
        """
        Create a history tab for `filename`.

        Parameters
        ----------
        filename: str
            History filename.
        """

        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return

        # Widgets
        editor = SimpleCodeEditor(self)

        # Setup
        language = 'py' if osp.splitext(filename)[1] == '.py' else 'bat'
        editor.setup_editor(
            linenumbers=self.get_conf('line_numbers'),
            language=language,
            color_scheme=self.get_conf('selected', section='appearance'),
            font=self.font,
            wrap=self.get_conf('wrap'),
        )
        editor.setReadOnly(True)
        editor.set_text(self.get_filename_text(filename))
        editor.set_cursor_position('eof')
        self.find_widget.set_editor(editor)

        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.filenames.append(filename)
        self.editors.append(editor)
        self.tabwidget.setCurrentIndex(index)
        self.tabwidget.setTabToolTip(index, filename)

        # Signals
        editor.sig_focus_changed.connect(lambda: self.sig_focus_changed.emit())

    @Slot(str, str)
    def append_to_history(self, filename, command):
        """
        Append command to history tab.

        Parameters
        ----------
        filename: str
            History file.
        command: str
            Command to append to history file.
        """
        if not is_text_string(filename):  # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')

        index = self.filenames.index(filename)
        command = to_text_string(command)
        self.editors[index].append(command)

        if self.get_conf('go_to_eof'):
            self.editors[index].set_cursor_position('eof')

        self.tabwidget.setCurrentIndex(index)

    def refresh(self):
        """Refresh widget and update find widget on current editor."""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None

        self.find_widget.set_editor(editor)

    @property
    def _tabs_stylesheet(self):
        """
        Change style of tabs because we don't have a close button here.
        """
        tabs_stylesheet = PANES_TABBAR_STYLESHEET.get_copy()
        css = tabs_stylesheet.get_stylesheet()

        css['QTabBar::tab'].setValues(
            marginTop='1.0em',
            padding='4px'
        )

        css['QTabWidget::left-corner'].setValues(
            left='0px',
        )

        css['QTabWidget::right-corner'].setValues(
            right='0px'
        )

        return str(tabs_stylesheet)
Пример #43
0
class NotebookClient(QWidget):
    """
    Notebook client for Spyder.

    This is a widget composed of a NotebookWidget and a find dialog to
    render notebooks.
    """
    def __init__(self, parent, filename, actions=None, ini_message=None):
        """
        Constructor.

        Parameters
        ----------
        parent : QWidget
            Parent of the widget under construction.
        filename : str
            File name of the notebook.
        actions : list of (QAction or QMenu or None) or None, optional
            Actions to be added to the context menu of the widget under
            construction. The default is None, meaning that no actions
            will be added.
        ini_message : str or None, optional
            HTML to be initially displayed in the widget. The default is
            None, meaning that an empty page is displayed initially.
        """
        super().__init__(parent)

        if os.name == 'nt':
            filename = filename.replace('/', '\\')
        self.filename = filename

        self.file_url = None
        self.server_url = None
        self.path = None

        self.notebookwidget = NotebookWidget(self, actions)
        if ini_message:
            self.notebookwidget.show_message(ini_message)
            self.static = True
        else:
            self.notebookwidget.show_blank()
            self.static = False

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.notebookwidget)
        self.find_widget.hide()

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.notebookwidget)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

    def add_token(self, url):
        """Add notebook token to a given url."""
        token_url = url + '?token={}'.format(self.token)
        return token_url

    def register(self, server_info):
        """Register attributes that can be computed with the server info."""
        # Path relative to the server directory
        self.path = os.path.relpath(self.filename,
                                    start=server_info['notebook_dir'])

        # Replace backslashes on Windows
        if os.name == 'nt':
            self.path = self.path.replace('\\', '/')

        # Server url to send requests to
        self.server_url = server_info['url']

        # Server token
        self.token = server_info['token']

        url = url_path_join(self.server_url, 'notebook', url_escape(self.path))

        # Set file url to load this notebook
        self.file_url = self.add_token(url)

    def go_to(self, url_or_text):
        """Go to page utl."""
        if isinstance(url_or_text, str):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.notebookwidget.load(url)

    def load_notebook(self):
        """Load the associated notebook."""
        self.go_to(self.file_url)

    def get_filename(self):
        """Get notebook's filename."""
        return self.filename

    def get_short_name(self):
        """Get a short name for the notebook."""
        sname = osp.splitext(osp.basename(self.filename))[0]
        if len(sname) > 20:
            fm = QFontMetrics(QFont())
            sname = fm.elidedText(sname, Qt.ElideRight, 110)
        return sname

    def save(self):
        """
        Save current notebook.

        This function saves the current notebook by simulating a click on the
        Save button in the notebook. The Save button is found by selecting the
        first element of class `jp-ToolbarButtonComponent` whose `title`
        attribute begins with the string "Save".

        NB: I am not sure whether the save is performed before the function
        returns.
        """
        self.notebookwidget.mousedown(
            '.jp-ToolbarButtonComponent[title^="Save"]')

    def get_session_url(self):
        """Get the kernel sessions url of the client."""
        return self.add_token(url_path_join(self.server_url, 'api/sessions'))

    def get_kernel_id(self):
        """
        Get the kernel id of the client.

        Return a str with the kernel id or None. On error, display a dialog
        box and return None.
        """
        sessions_url = self.get_session_url()
        try:
            sessions_response = requests.get(sessions_url)
        except requests.exceptions.RequestException as exception:
            msg = _('Spyder could not get a list of sessions '
                    'from the Jupyter Notebook server. '
                    'Message: {}').format(exception)
            QMessageBox.warning(self, _('Server error'), msg)
            return None

        if sessions_response.status_code != requests.codes.ok:
            msg = _('Spyder could not get a list of sessions '
                    'from the Jupyter Notebook server. '
                    'Status code: {}').format(sessions_response.status_code)
            QMessageBox.warning(self, _('Server error'), msg)
            return None

        if os.name == 'nt':
            path = self.path.replace('\\', '/')
        else:
            path = self.path

        sessions = json.loads(sessions_response.content.decode())
        for session in sessions:
            notebook_path = session.get('notebook', {}).get('path')
            if notebook_path is not None and notebook_path == path:
                kernel_id = session['kernel']['id']
                return kernel_id

    def shutdown_kernel(self):
        """Shutdown the kernel of the client."""
        kernel_id = self.get_kernel_id()

        if kernel_id:
            delete_url = self.add_token(
                url_path_join(self.server_url, 'api/kernels/', kernel_id))
            delete_req = requests.delete(delete_url)
            if delete_req.status_code != 204:
                QMessageBox.warning(
                    self, _("Server error"),
                    _("The Jupyter Notebook server "
                      "failed to shutdown the kernel "
                      "associated with this notebook. "
                      "If you want to shut it down, "
                      "you'll have to close Spyder."))
Пример #44
0
class WebBrowser(QWidget):
    """
    Web browser widget
    """
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        
        self.home_url = None
        
        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)
                
        home_button = create_toolbutton(self, icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)
        
        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)
        
        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebEnginePage.Reload)
        stop_button = pageact2btn(QWebEnginePage.Stop)
        previous_button = pageact2btn(QWebEnginePage.Back)
        next_button = pageact2btn(QWebEnginePage.Forward)
        
        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False))
        
        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        if not WEBENGINE:
            self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self, icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
                
    def get_label(self):
        """Return address label text"""
        return _("Address:")
            
    def set_home_url(self, text):
        """Set home URL"""
        self.home_url = QUrl(text)
        
    def set_url(self, url):
        """Set current URL"""
        self.url_changed(url)
        self.go_to(url)
        
    def go_to(self, url_or_text):
        """Go to page *address*"""
        if is_text_string(url_or_text):
            url = QUrl(url_or_text)
        else:
            url = url_or_text
        self.webview.load(url)

    @Slot()
    def go_home(self):
        """Go to home page"""
        if self.home_url is not None:
            self.set_url(self.home_url)
        
    def text_to_url(self, text):
        """Convert text address into QUrl object"""
        return QUrl(text)
        
    def url_combo_activated(self, valid):
        """Load URL from combo box first item"""
        text = to_text_string(self.url_combo.currentText())
        self.go_to(self.text_to_url(text))
        
    def load_finished(self, ok):
        if not ok:
            self.webview.setHtml(_("Unable to load page"))
            
    def url_to_text(self, url):
        """Convert QUrl object to displayed text in combo box"""
        return url.toString()
            
    def url_changed(self, url):
        """Displayed URL has changed -> updating URL combo box"""
        self.url_combo.add_text(self.url_to_text(url))
            
    def icon_changed(self):
        self.url_combo.setItemIcon(self.url_combo.currentIndex(),
                                   self.webview.icon())
        self.setWindowIcon(self.webview.icon())

    @Slot(bool)
    def toggle_find_widget(self, state):
        if state:
            self.find_widget.show()
        else:
            self.find_widget.hide()
Пример #45
0
class History(QWidget):
    """History plugin main widget."""

    focus_changed = Signal()

    def __init__(self, parent):
        """Initialize widget and create layout."""
        QWidget.__init__(self, parent)

        self.editors = []
        self.filenames = []

        self.tabwidget = None
        self.menu_actions = None

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        self.menu = QMenu(self)

        options_button.setMenu(self.menu)
        self.tabwidget.setCornerWidget(options_button)

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()

        layout.addWidget(self.find_widget)

        self.setLayout(layout)

    def set_menu_actions(self, menu_actions):
        """Add options to corner menu."""
        if self.menu_actions is not None:
            add_actions(self.menu, self.menu_actions)

    def refresh(self):
        """Refresh tabwidget."""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def move_tab(self, index_from, index_to):
        """
        Move tab.

        (tabs themselves have already been moved by the history.tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    def add_history(self, filename, color_scheme, font, wrap):
        """
        Add new history tab.

        Args:
            filename (str): file to be loaded in a new tab.
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
        else:
            language = 'bat'
        editor.setup_editor(linenumbers=False,
                            language=language,
                            scrollflagarea=False,
                            show_class_func_dropdown=False)
        editor.focus_changed.connect(lambda: self.focus_changed.emit())
        editor.setReadOnly(True)

        editor.set_font(font, color_scheme)
        editor.toggle_wrap_mode(wrap)

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')

        self.editors.append(editor)
        self.filenames.append(filename)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setCurrentIndex(index)

    def append_to_history(self, filename, command, go_to_eof):
        """
        Append an entry to history filename.

        Args:
            filename (str): file to be updated in a new tab.
            command (str): line to be added.
            go_to_eof (bool): scroll to the end of file.
        """
        if not is_text_string(filename): # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if go_to_eof:
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
Пример #46
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        
        self.home_url = None
        
        self.webview = WebView(self)
        self.webview.loadFinished.connect(self.load_finished)
        self.webview.titleChanged.connect(self.setWindowTitle)
        self.webview.urlChanged.connect(self.url_changed)
                
        home_button = create_toolbutton(self, icon=ima.icon('home'),
                                        tip=_("Home"),
                                        triggered=self.go_home)
        
        zoom_out_button = action2button(self.webview.zoom_out_action)
        zoom_in_button = action2button(self.webview.zoom_in_action)
        
        pageact2btn = lambda prop: action2button(self.webview.pageAction(prop),
                                                 parent=self.webview)
        refresh_button = pageact2btn(QWebEnginePage.Reload)
        stop_button = pageact2btn(QWebEnginePage.Stop)
        previous_button = pageact2btn(QWebEnginePage.Back)
        next_button = pageact2btn(QWebEnginePage.Forward)
        
        stop_button.setEnabled(False)
        self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True))
        self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False))
        
        progressbar = QProgressBar(self)
        progressbar.setTextVisible(False)
        progressbar.hide()
        self.webview.loadStarted.connect(progressbar.show)
        self.webview.loadProgress.connect(progressbar.setValue)
        self.webview.loadFinished.connect(lambda _state: progressbar.hide())

        label = QLabel(self.get_label())

        self.url_combo = UrlComboBox(self)
        self.url_combo.valid.connect(self.url_combo_activated)
        if not WEBENGINE:
            self.webview.iconChanged.connect(self.icon_changed)

        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.webview)
        self.find_widget.hide()

        find_button = create_toolbutton(self, icon=ima.icon('find'),
                                        tip=_("Find text"),
                                        toggled=self.toggle_find_widget)
        self.find_widget.visibility_changed.connect(find_button.setChecked)

        hlayout = QHBoxLayout()
        for widget in (previous_button, next_button, home_button, find_button,
                       label, self.url_combo, zoom_out_button, zoom_in_button,
                       refresh_button, progressbar, stop_button):
            hlayout.addWidget(widget)
        
        layout = QVBoxLayout()
        layout.addLayout(hlayout)
        layout.addWidget(self.webview)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)
Пример #47
0
    def __init__(self, parent):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)
        self.tabwidget = None
        self.menu_actions = None

        self.help = None # Help plugin
        self.historylog = None # History log plugin
        self.variableexplorer = None # Variable explorer 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)
Пример #48
0
class Console(SpyderPluginWidget):
    """
    Console widget
    """
    CONF_SECTION = 'internal_console'
    focus_changed = Signal()
    redirect_stdio = Signal(bool)
    edit_goto = Signal(str, int, str)

    def __init__(self,
                 parent=None,
                 namespace=None,
                 commands=[],
                 message=None,
                 exitfunc=None,
                 profile=False,
                 multithreaded=False):
        SpyderPluginWidget.__init__(self, parent)

        debug_print("    ..internal console: initializing")
        self.dialog_manager = DialogManager()

        # Shell
        light_background = self.get_option('light_background')
        self.shell = InternalShell(parent,
                                   namespace,
                                   commands,
                                   message,
                                   self.get_option('max_line_count'),
                                   self.get_plugin_font(),
                                   exitfunc,
                                   profile,
                                   multithreaded,
                                   light_background=light_background)
        self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0))
        self.shell.go_to_error.connect(self.go_to_error)
        self.shell.focus_changed.connect(lambda: self.focus_changed.emit())

        # Redirecting some signals:
        self.shell.redirect_stdio.connect(
            lambda state: self.redirect_stdio.emit(state))

        # Initialize plugin
        self.initialize_plugin()

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.set_editor(self.shell)
        self.find_widget.hide()
        self.register_widget_shortcuts(self.find_widget)

        # Main layout
        btn_layout = QHBoxLayout()
        btn_layout.setAlignment(Qt.AlignLeft)
        btn_layout.addStretch()
        btn_layout.addWidget(self.options_button, Qt.AlignRight)
        layout = create_plugin_layout(btn_layout)
        layout.addWidget(self.shell)
        layout.addWidget(self.find_widget)
        self.setLayout(layout)

        # Parameters
        self.shell.toggle_wrap_mode(self.get_option('wrap'))

        # Accepting drops
        self.setAcceptDrops(True)

        # Traceback MessageBox
        self.msgbox_traceback = None
        self.error_traceback = ""

    #------ Private API --------------------------------------------------------
    def set_historylog(self, historylog):
        """Bind historylog instance to this console
        Not used anymore since v2.0"""
        historylog.add_history(self.shell.history_filename)
        self.shell.append_to_history.connect(historylog.append_to_history)

    def set_help(self, help_plugin):
        """Bind help instance to this console"""
        self.shell.help = help_plugin

    #------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _('Internal console')

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.shell

    def update_font(self):
        """Update font from Preferences"""
        font = self.get_plugin_font()
        self.shell.set_font(font)

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.dialog_manager.close_all()
        self.shell.exit_interpreter()
        return True

    def refresh_plugin(self):
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        quit_action = create_action(self,
                                    _("&Quit"),
                                    icon=ima.icon('exit'),
                                    tip=_("Quit"),
                                    triggered=self.quit)
        self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q")
        run_action = create_action(self,
                                   _("&Run..."),
                                   None,
                                   ima.icon('run_small'),
                                   _("Run a Python script"),
                                   triggered=self.run_script)
        environ_action = create_action(
            self,
            _("Environment variables..."),
            icon=ima.icon('environ'),
            tip=_("Show and edit environment variables"
                  " (for current session)"),
            triggered=self.show_env)
        syspath_action = create_action(self,
                                       _("Show sys.path contents..."),
                                       icon=ima.icon('syspath'),
                                       tip=_("Show (read-only) sys.path"),
                                       triggered=self.show_syspath)
        buffer_action = create_action(self,
                                      _("Buffer..."),
                                      None,
                                      tip=_("Set maximum line count"),
                                      triggered=self.change_max_line_count)
        exteditor_action = create_action(
            self,
            _("External editor path..."),
            None,
            None,
            _("Set external editor executable path"),
            triggered=self.change_exteditor)
        wrap_action = create_action(self,
                                    _("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(self.get_option('wrap'))
        calltips_action = create_action(self,
                                        _("Display balloon tips"),
                                        toggled=self.toggle_calltips)
        calltips_action.setChecked(self.get_option('calltips'))
        codecompletion_action = create_action(
            self,
            _("Automatic code completion"),
            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(
            self.get_option('codecompletion/auto'))
        codecompenter_action = create_action(
            self,
            _("Enter key selects completion"),
            toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(
            self.get_option('codecompletion/enter_key'))

        option_menu = QMenu(_('Internal console settings'), self)
        option_menu.setIcon(ima.icon('tooloptions'))
        add_actions(
            option_menu,
            (buffer_action, wrap_action, calltips_action,
             codecompletion_action, codecompenter_action, exteditor_action))

        plugin_actions = [
            None, run_action, environ_action, syspath_action, option_menu,
            MENU_SEPARATOR, quit_action, self.undock_action
        ]

        return plugin_actions

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        # Connecting the following signal once the dockwidget has been created:
        self.shell.exception_occurred.connect(self.exception_occurred)

    def exception_occurred(self, text, is_traceback):
        """Exception ocurred in the internal console.
        Show a QMessageBox or the internal console to warn the user"""
        # Skip errors without traceback
        if not is_traceback and self.msgbox_traceback is None:
            return

        if CONF.get('main', 'show_internal_console_if_traceback', False):
            self.dockwidget.show()
            self.dockwidget.raise_()
        else:
            if self.msgbox_traceback is None:
                self.msgbox_traceback = QMessageBox(
                    QMessageBox.Critical,
                    _('Error'),
                    _("<b>Spyder has encountered a problem.</b><br>"
                      "Sorry for the inconvenience."
                      "<br><br>"
                      "You can automatically submit this error to our Github "
                      "issues tracker.<br><br>"
                      "<i>Note:</i> You need a Github account for that."),
                    QMessageBox.Ok,
                    parent=self)

                self.submit_btn = self.msgbox_traceback.addButton(
                    _('Submit to Github'), QMessageBox.YesRole)
                self.submit_btn.pressed.connect(self.press_submit_btn)

                self.msgbox_traceback.setWindowModality(Qt.NonModal)
                self.error_traceback = ""
                self.msgbox_traceback.show()
                self.msgbox_traceback.finished.connect(self.close_msg)
                self.msgbox_traceback.setDetailedText(' ')

                # open show details (iterate over all buttons and click it)
                for button in self.msgbox_traceback.buttons():
                    if (self.msgbox_traceback.buttonRole(button) ==
                            QMessageBox.ActionRole):
                        button.click()
                        break

            self.error_traceback += text
            self.msgbox_traceback.setDetailedText(self.error_traceback)

    def close_msg(self):
        self.msgbox_traceback = None

    def press_submit_btn(self):
        self.main.report_issue(self.error_traceback)
        self.msgbox_traceback = None

    #------ Public API ---------------------------------------------------------
    @Slot()
    def quit(self):
        """Quit mainwindow"""
        self.main.close()

    @Slot()
    def show_env(self):
        """Show environment variables"""
        self.dialog_manager.show(EnvDialog())

    @Slot()
    def show_syspath(self):
        """Show sys.path"""
        editor = CollectionsEditor()
        editor.setup(sys.path,
                     title="sys.path",
                     readonly=True,
                     width=600,
                     icon=ima.icon('syspath'))
        self.dialog_manager.show(editor)

    @Slot()
    def run_script(self,
                   filename=None,
                   silent=False,
                   set_focus=False,
                   args=None):
        """Run a Python script"""
        if filename is None:
            self.shell.interpreter.restore_stds()
            filename, _selfilter = getopenfilename(
                self, _("Run Python script"), getcwd_or_home(),
                _("Python scripts") + " (*.py ; *.pyw ; *.ipy)")
            self.shell.interpreter.redirect_stds()
            if filename:
                os.chdir(osp.dirname(filename))
                filename = osp.basename(filename)
            else:
                return
        debug_print(args)
        filename = osp.abspath(filename)
        rbs = remove_backslashes
        command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args))
        if set_focus:
            self.shell.setFocus()
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        self.shell.write(command + '\n')
        self.shell.run_command(command)

    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_script(fname, int(lnb))

    def edit_script(self, filename=None, goto=-1):
        """Edit script"""
        # Called from InternalShell
        if not hasattr(self, 'main') \
           or not hasattr(self.main, 'editor'):
            self.shell.external_editor(filename, goto)
            return
        if filename is not None:
            self.edit_goto.emit(osp.abspath(filename), goto, '')

    def execute_lines(self, lines):
        """Execute lines and give focus to shell"""
        self.shell.execute_lines(to_text_string(lines))
        self.shell.setFocus()

    @Slot()
    def change_max_line_count(self):
        "Change maximum line count" ""
        mlc, valid = QInputDialog.getInt(self, _('Buffer'),
                                         _('Maximum line count'),
                                         self.get_option('max_line_count'), 0,
                                         1000000)
        if valid:
            self.shell.setMaximumBlockCount(mlc)
            self.set_option('max_line_count', mlc)

    @Slot()
    def change_exteditor(self):
        """Change external editor path"""
        path, valid = QInputDialog.getText(
            self, _('External editor'), _('External editor executable path:'),
            QLineEdit.Normal, self.get_option('external_editor/path'))
        if valid:
            self.set_option('external_editor/path', to_text_string(path))

    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        self.shell.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)

    @Slot(bool)
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        self.shell.set_calltips(checked)
        self.set_option('calltips', checked)

    @Slot(bool)
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        self.shell.set_codecompletion_auto(checked)
        self.set_option('codecompletion/auto', checked)

    @Slot(bool)
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        self.shell.set_codecompletion_enter(checked)
        self.set_option('codecompletion/enter_key', checked)

    #----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):
                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()
        if source.hasUrls():
            pathlist = mimedata2url(source)
            self.shell.drop_pathlist(pathlist)
        elif source.hasText():
            lines = to_text_string(source.text())
            self.shell.set_cursor_position('eof')
            self.shell.execute_lines(lines)
        event.acceptProposedAction()
Пример #49
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """

    CONF_SECTION = "historylog"
    CONFIGWIDGET_CLASS = HistoryConfigPage
    focus_changed = Signal()

    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None

        self.editors = []
        self.filenames = []
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main=parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.tabwidget.currentChanged.connect(self.refresh_plugin)
        self.tabwidget.move_data.connect(self.move_tab)

        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)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_("Options"), icon=ima.icon("tooloptions"))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)

        # 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)

    # ------ SpyderPluginWidget API ---------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _("History log")

    def get_plugin_icon(self):
        """Return widget icon"""
        return ima.icon("history")

    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True

    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(
            self,
            _("History..."),
            None,
            ima.icon("history"),
            _("Set history maximum entries"),
            triggered=self.change_history_depth,
        )
        self.wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked(self.get_option("wrap"))
        self.menu_actions = [history_action, self.wrap_action]
        return self.menu_actions

    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.extconsole, self)

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.focus_changed.connect(self.main.plugin_focus_changed)
        self.main.add_dockwidget(self)
        #        self.main.console.set_historylog(self)
        self.main.console.shell.refresh.connect(self.refresh_plugin)

    def update_font(self):
        """Update font from Preferences"""
        color_scheme = self.get_color_scheme()
        font = self.get_plugin_font()
        for editor in self.editors:
            editor.set_font(font, color_scheme)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = "color_scheme_name"
        color_scheme_o = self.get_color_scheme()
        font_n = "plugin_font"
        font_o = self.get_plugin_font()
        wrap_n = "wrap"
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)

    # ------ Private API --------------------------------------------------------
    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)
        editor = self.editors.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)

    # ------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for add_history signal emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == ".py":
            language = "py"
        else:
            language = "bat"
        editor.setup_editor(linenumbers=False, language=language, scrollflagarea=False)
        editor.focus_changed.connect(lambda: self.focus_changed.emit())
        editor.setReadOnly(True)
        color_scheme = self.get_color_scheme()
        editor.set_font(self.get_plugin_font(), color_scheme)
        editor.toggle_wrap_mode(self.get_option("wrap"))

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position("eof")

        self.editors.append(editor)
        self.filenames.append(filename)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setCurrentIndex(index)

    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for append_to_history signal emitted by shell instance
        """
        if not is_text_string(filename):  # filename is a QString
            filename = to_text_string(filename.toUtf8(), "utf-8")
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option("go_to_eof"):
            self.editors[index].set_cursor_position("eof")
        self.tabwidget.setCurrentIndex(index)

    @Slot()
    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInt(
            self, _("History"), _("Maximum entries"), self.get_option("max_entries"), 10, 10000
        )
        if valid:
            self.set_option("max_entries", depth)

    @Slot(bool)
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option("wrap", checked)