Example #1
0
class Help(SpyderPluginWidget):
    """
    Docstrings viewer widget
    """
    CONF_SECTION = 'help'
    CONFIGWIDGET_CLASS = HelpConfigPage
    LOG_PATH = get_conf_path(CONF_SECTION)
    focus_changed = Signal()

    def __init__(self, parent):
        if PYQT5:
            SpyderPluginWidget.__init__(self, parent, main = parent)
        else:
            SpyderPluginWidget.__init__(self, parent)

        self.internal_shell = None

        # Initialize plugin
        self.initialize_plugin()

        self.no_doc_string = _("No further documentation available")

        self._last_console_cb = None
        self._last_editor_cb = None

        self.set_default_color_scheme()

        self.plain_text = PlainText(self)
        self.rich_text = RichText(self)

        color_scheme = get_color_scheme(self.get_option('color_scheme_name'))
        self.set_plain_text_font(self.get_plugin_font(), color_scheme)
        self.plain_text.editor.toggle_wrap_mode(self.get_option('wrap'))

        # Add entries to read-only editor context-menu
        font_action = create_action(self, _("&Font..."), None,
                                    ima.icon('font'), _("Set font style"),
                                    triggered=self.change_font)
        self.wrap_action = create_action(self, _("Wrap lines"),
                                         toggled=self.toggle_wrap_mode)
        self.wrap_action.setChecked(self.get_option('wrap'))
        self.plain_text.editor.readonly_menu.addSeparator()
        add_actions(self.plain_text.editor.readonly_menu,
                    (font_action, self.wrap_action))

        self.set_rich_text_font(self.get_plugin_font('rich_text'))

        self.shell = None

        self.external_console = None

        # locked = disable link with Console
        self.locked = False
        self._last_texts = [None, None]
        self._last_editor_doc = None

        # Object name
        layout_edit = QHBoxLayout()
        layout_edit.setContentsMargins(0, 0, 0, 0)
        txt = _("Source")
        if sys.platform == 'darwin':
            source_label = QLabel("  " + txt)
        else:
            source_label = QLabel(txt)
        layout_edit.addWidget(source_label)
        self.source_combo = QComboBox(self)
        self.source_combo.addItems([_("Console"), _("Editor")])
        self.source_combo.currentIndexChanged.connect(self.source_changed)
        if (not programs.is_module_installed('rope') and
                not programs.is_module_installed('jedi', '>=0.8.1')):
            self.source_combo.hide()
            source_label.hide()
        layout_edit.addWidget(self.source_combo)
        layout_edit.addSpacing(10)
        layout_edit.addWidget(QLabel(_("Object")))
        self.combo = ObjectComboBox(self)
        layout_edit.addWidget(self.combo)
        self.object_edit = QLineEdit(self)
        self.object_edit.setReadOnly(True)
        layout_edit.addWidget(self.object_edit)
        self.combo.setMaxCount(self.get_option('max_history_entries'))
        self.combo.addItems( self.load_history() )
        self.combo.setItemText(0, '')
        self.combo.valid.connect(lambda valid: self.force_refresh())

        # Plain text docstring option
        self.docstring = True
        self.rich_help = self.get_option('rich_mode', True)
        self.plain_text_action = create_action(self, _("Plain Text"),
                                               toggled=self.toggle_plain_text)

        # Source code option
        self.show_source_action = create_action(self, _("Show Source"),
                                                toggled=self.toggle_show_source)

        # Rich text option
        self.rich_text_action = create_action(self, _("Rich Text"),
                                         toggled=self.toggle_rich_text)

        # Add the help actions to an exclusive QActionGroup
        help_actions = QActionGroup(self)
        help_actions.setExclusive(True)
        help_actions.addAction(self.plain_text_action)
        help_actions.addAction(self.rich_text_action)

        # Automatic import option
        self.auto_import_action = create_action(self, _("Automatic import"),
                                                toggled=self.toggle_auto_import)
        auto_import_state = self.get_option('automatic_import')
        self.auto_import_action.setChecked(auto_import_state)

        # Lock checkbox
        self.locked_button = create_toolbutton(self,
                                               triggered=self.toggle_locked)
        layout_edit.addWidget(self.locked_button)
        self._update_lock_icon()

        # Option menu
        options_button = create_toolbutton(self, text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, [self.rich_text_action, self.plain_text_action,
                           self.show_source_action, None,
                           self.auto_import_action])
        options_button.setMenu(menu)
        layout_edit.addWidget(options_button)

        if self.rich_help:
            self.switch_to_rich_text()
        else:
            self.switch_to_plain_text()
        self.plain_text_action.setChecked(not self.rich_help)
        self.rich_text_action.setChecked(self.rich_help)
        self.source_changed()

        # Main layout
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addLayout(layout_edit)
        layout.addWidget(self.plain_text)
        layout.addWidget(self.rich_text)
        self.setLayout(layout)

        # Add worker thread for handling rich text rendering
        self._sphinx_thread = SphinxThread(
                                  html_text_no_doc=warning(self.no_doc_string))
        self._sphinx_thread.html_ready.connect(
                                             self._on_sphinx_thread_html_ready)
        self._sphinx_thread.error_msg.connect(self._on_sphinx_thread_error_msg)

        # Render internal links
        view = self.rich_text.webview
        view.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        view.linkClicked.connect(self.handle_link_clicks)

        self._starting_up = True

    #------ SpyderPluginWidget API ---------------------------------------------
    def on_first_registration(self):
        """Action to be performed on first plugin registration"""
        self.main.tabify_plugins(self.main.variableexplorer, self)

    def get_plugin_title(self):
        """Return widget title"""
        return _('Help')

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

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

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

    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_help(self)
        self.internal_shell = self.main.console.shell

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

    def refresh_plugin(self):
        """Refresh widget"""
        if self._starting_up:
            self._starting_up = False
            self.switch_to_rich_text()
            self.show_intro_message()

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        color_scheme_n = 'color_scheme_name'
        color_scheme_o = get_color_scheme(self.get_option(color_scheme_n))
        font_n = 'plugin_font'
        font_o = self.get_plugin_font()
        connect_n = 'connect_to_oi'
        rich_font_n = 'rich_text'
        rich_font_o = self.get_plugin_font('rich_text')
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        math_n = 'math'
        math_o = self.get_option(math_n)

        if font_n in options:
            scs = color_scheme_o if color_scheme_n in options else None
            self.set_plain_text_font(font_o, color_scheme=scs)
        if rich_font_n in options:
            self.set_rich_text_font(rich_font_o)
        elif color_scheme_n in options:
            self.set_plain_text_color_scheme(color_scheme_o)
        if wrap_n in options:
            self.toggle_wrap_mode(wrap_o)
        if math_n in options:
            self.toggle_math_mode(math_o)

        # To make auto-connection changes take place instantly
        self.main.editor.apply_plugin_settings(options=[connect_n])
        self.main.extconsole.apply_plugin_settings(options=[connect_n])
        if self.main.ipyconsole is not None:
            self.main.ipyconsole.apply_plugin_settings(options=[connect_n])

    #------ Public API (related to Help's source) -------------------------
    def source_is_console(self):
        """Return True if source is Console"""
        return self.source_combo.currentIndex() == 0

    def switch_to_editor_source(self):
        self.source_combo.setCurrentIndex(1)

    def switch_to_console_source(self):
        self.source_combo.setCurrentIndex(0)

    def source_changed(self, index=None):
        if self.source_is_console():
            # Console
            self.combo.show()
            self.object_edit.hide()
            self.show_source_action.setEnabled(True)
            self.auto_import_action.setEnabled(True)
        else:
            # Editor
            self.combo.hide()
            self.object_edit.show()
            self.show_source_action.setDisabled(True)
            self.auto_import_action.setDisabled(True)
        self.restore_text()

    def save_text(self, callback):
        if self.source_is_console():
            self._last_console_cb = callback
        else:
            self._last_editor_cb = callback

    def restore_text(self):
        if self.source_is_console():
            cb = self._last_console_cb
        else:
            cb = self._last_editor_cb
        if cb is None:
            if self.is_plain_text_mode():
                self.plain_text.clear()
            else:
                self.rich_text.clear()
        else:
            func = cb[0]
            args = cb[1:]
            func(*args)
            if get_meth_class_inst(func) is self.rich_text:
                self.switch_to_rich_text()
            else:
                self.switch_to_plain_text()

    #------ Public API (related to rich/plain text widgets) --------------------
    @property
    def find_widget(self):
        if self.plain_text.isVisible():
            return self.plain_text.find_widget
        else:
            return self.rich_text.find_widget

    def set_rich_text_font(self, font):
        """Set rich text mode font"""
        self.rich_text.set_font(font, fixed_font=self.get_plugin_font())

    def set_plain_text_font(self, font, color_scheme=None):
        """Set plain text mode font"""
        self.plain_text.set_font(font, color_scheme=color_scheme)

    def set_plain_text_color_scheme(self, color_scheme):
        """Set plain text mode color scheme"""
        self.plain_text.set_color_scheme(color_scheme)

    @Slot()
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.CONF_SECTION), self,
                                      _("Select a new font"))
        if valid:
            self.set_plain_text_font(font)
            set_font(font, self.CONF_SECTION)

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

    def toggle_math_mode(self, checked):
        """Toggle math mode"""
        self.set_option('math', checked)

    def is_plain_text_mode(self):
        """Return True if plain text mode is active"""
        return self.plain_text.isVisible()

    def is_rich_text_mode(self):
        """Return True if rich text mode is active"""
        return self.rich_text.isVisible()

    def switch_to_plain_text(self):
        """Switch to plain text mode"""
        self.rich_help = False
        self.plain_text.show()
        self.rich_text.hide()
        self.plain_text_action.setChecked(True)

    def switch_to_rich_text(self):
        """Switch to rich text mode"""
        self.rich_help = True
        self.plain_text.hide()
        self.rich_text.show()
        self.rich_text_action.setChecked(True)
        self.show_source_action.setChecked(False)

    def set_plain_text(self, text, is_code):
        """Set plain text docs"""

        # text is coming from utils.dochelpers.getdoc
        if type(text) is dict:
            name = text['name']
            if name:
                rst_title = ''.join(['='*len(name), '\n', name, '\n',
                                    '='*len(name), '\n\n'])
            else:
                rst_title = ''

            if text['argspec']:
                definition = ''.join(['Definition: ', name, text['argspec'],
                                      '\n'])
            else:
                definition = ''

            if text['note']:
                note = ''.join(['Type: ', text['note'], '\n\n----\n\n'])
            else:
                note = ''

            full_text = ''.join([rst_title, definition, note,
                                 text['docstring']])
        else:
            full_text = text

        self.plain_text.set_text(full_text, is_code)
        self.save_text([self.plain_text.set_text, full_text, is_code])

    def set_rich_text_html(self, html_text, base_url):
        """Set rich text"""
        self.rich_text.set_html(html_text, base_url)
        self.save_text([self.rich_text.set_html, html_text, base_url])

    def show_intro_message(self):
        intro_message = _("Here you can get help of any object by pressing "
                          "%s in front of it, either on the Editor or the "
                          "Console.%s"
                          "Help can also be shown automatically after writing "
                          "a left parenthesis next to an object. You can "
                          "activate this behavior in %s.")
        prefs = _("Preferences > Help")
        if sys.platform == 'darwin':
            shortcut = "Cmd+I"
        else:
            shortcut = "Ctrl+I"

        if self.is_rich_text_mode():
            title = _("Usage")
            tutorial_message = _("New to Spyder? Read our")
            tutorial = _("tutorial")
            intro_message = intro_message % ("<b>"+shortcut+"</b>", "<br><br>",
                                             "<i>"+prefs+"</i>")
            self.set_rich_text_html(usage(title, intro_message,
                                          tutorial_message, tutorial),
                                    QUrl.fromLocalFile(CSS_PATH))
        else:
            install_sphinx = "\n\n%s" % _("Please consider installing Sphinx "
                                          "to get documentation rendered in "
                                          "rich text.")
            intro_message = intro_message % (shortcut, "\n\n", prefs)
            intro_message += install_sphinx
            self.set_plain_text(intro_message, is_code=False)

    def show_rich_text(self, text, collapse=False, img_path=''):
        """Show text in rich mode"""
        self.visibility_changed(True)
        self.raise_()
        self.switch_to_rich_text()
        context = generate_context(collapse=collapse, img_path=img_path)
        self.render_sphinx_doc(text, context)

    def show_plain_text(self, text):
        """Show text in plain mode"""
        self.visibility_changed(True)
        self.raise_()
        self.switch_to_plain_text()
        self.set_plain_text(text, is_code=False)

    @Slot()
    def show_tutorial(self):
        tutorial_path = get_module_source_path('spyderlib.utils.help')
        img_path = osp.join(tutorial_path, 'static', 'images')
        tutorial = osp.join(tutorial_path, 'tutorial.rst')
        text = open(tutorial).read()
        self.show_rich_text(text, collapse=True, img_path=img_path)

    def handle_link_clicks(self, url):
        url = to_text_string(url.toString())
        if url == "spy://tutorial":
            self.show_tutorial()
        elif url.startswith('http'):
            programs.start_file(url)
        else:
            self.rich_text.webview.load(QUrl(url))

    #------ Public API ---------------------------------------------------------
    def set_external_console(self, external_console):
        self.external_console = external_console

    def force_refresh(self):
        if self.source_is_console():
            self.set_object_text(None, force_refresh=True)
        elif self._last_editor_doc is not None:
            self.set_editor_doc(self._last_editor_doc, force_refresh=True)

    def set_object_text(self, text, force_refresh=False, ignore_unknown=False):
        """Set object analyzed by Help"""
        if (self.locked and not force_refresh):
            return
        self.switch_to_console_source()

        add_to_combo = True
        if text is None:
            text = to_text_string(self.combo.currentText())
            add_to_combo = False

        found = self.show_help(text, ignore_unknown=ignore_unknown)
        if ignore_unknown and not found:
            return

        if add_to_combo:
            self.combo.add_text(text)
        if found:
            self.save_history()

        if self.dockwidget is not None:
            self.dockwidget.blockSignals(True)
        self.__eventually_raise_help(text, force=force_refresh)
        if self.dockwidget is not None:
            self.dockwidget.blockSignals(False)

    def set_editor_doc(self, doc, force_refresh=False):
        """
        Use the help plugin to show docstring dictionary computed
        with introspection plugin from the Editor plugin
        """
        if (self.locked and not force_refresh):
            return
        self.switch_to_editor_source()
        self._last_editor_doc = doc
        self.object_edit.setText(doc['obj_text'])

        if self.rich_help:
            self.render_sphinx_doc(doc)
        else:
            self.set_plain_text(doc, is_code=False)

        if self.dockwidget is not None:
            self.dockwidget.blockSignals(True)
        self.__eventually_raise_help(doc['docstring'], force=force_refresh)
        if self.dockwidget is not None:
            self.dockwidget.blockSignals(False)

    def __eventually_raise_help(self, text, force=False):
        index = self.source_combo.currentIndex()
        if hasattr(self.main, 'tabifiedDockWidgets'):
            # 'QMainWindow.tabifiedDockWidgets' was introduced in PyQt 4.5
            if self.dockwidget and (force or self.dockwidget.isVisible()) \
               and not self.ismaximized \
               and (force or text != self._last_texts[index]):
                dockwidgets = self.main.tabifiedDockWidgets(self.dockwidget)
                if self.main.console.dockwidget not in dockwidgets and \
                   (hasattr(self.main, 'extconsole') and \
                    self.main.extconsole.dockwidget not in dockwidgets):
                    self.dockwidget.show()
                    self.dockwidget.raise_()
        self._last_texts[index] = text

    def load_history(self, obj=None):
        """Load history from a text file in user home directory"""
        if osp.isfile(self.LOG_PATH):
            history = [line.replace('\n', '')
                       for line in open(self.LOG_PATH, 'r').readlines()]
        else:
            history = []
        return history

    def save_history(self):
        """Save history to a text file in user home directory"""
        open(self.LOG_PATH, 'w').write("\n".join( \
                [to_text_string(self.combo.itemText(index))
                 for index in range(self.combo.count())] ))

    @Slot(bool)
    def toggle_plain_text(self, checked):
        """Toggle plain text docstring"""
        if checked:
            self.docstring = checked
            self.switch_to_plain_text()
            self.force_refresh()
        self.set_option('rich_mode', not checked)

    @Slot(bool)
    def toggle_show_source(self, checked):
        """Toggle show source code"""
        if checked:
            self.switch_to_plain_text()
        self.docstring = not checked
        self.force_refresh()
        self.set_option('rich_mode', not checked)

    @Slot(bool)
    def toggle_rich_text(self, checked):
        """Toggle between sphinxified docstrings or plain ones"""
        if checked:
            self.docstring = not checked
            self.switch_to_rich_text()
        self.set_option('rich_mode', checked)

    @Slot(bool)
    def toggle_auto_import(self, checked):
        """Toggle automatic import feature"""
        self.combo.validate_current_text()
        self.set_option('automatic_import', checked)
        self.force_refresh()

    @Slot()
    def toggle_locked(self):
        """
        Toggle locked state
        locked = disable link with Console
        """
        self.locked = not self.locked
        self._update_lock_icon()

    def _update_lock_icon(self):
        """Update locked state icon"""
        icon = ima.icon('lock') if self.locked else ima.icon('lock_open')
        self.locked_button.setIcon(icon)
        tip = _("Unlock") if self.locked else _("Lock")
        self.locked_button.setToolTip(tip)

    def set_shell(self, shell):
        """Bind to shell"""
        if IPythonControlWidget is not None:
            # XXX(anatoli): hack to make Spyder run on systems without IPython
            #               there should be a better way
            if isinstance(shell, IPythonControlWidget):
                # XXX: this ignores passed argument completely
                self.shell = self.external_console.get_current_shell()
        else:
            self.shell = shell

    def get_shell(self):
        """Return shell which is currently bound to Help,
        or another running shell if it has been terminated"""
        if not isinstance(self.shell, ExtPythonShellWidget) \
           or not self.shell.externalshell.is_running():
            self.shell = None
            if self.external_console is not None:
                self.shell = self.external_console.get_running_python_shell()
            if self.shell is None:
                self.shell = self.internal_shell
        return self.shell

    def render_sphinx_doc(self, doc, context=None):
        """Transform doc string dictionary to HTML and show it"""
        # Math rendering option could have changed
        fname = self.parent().parent().editor.get_current_filename()
        dname = osp.dirname(fname)
        self._sphinx_thread.render(doc, context, self.get_option('math'),
                                   dname)

    def _on_sphinx_thread_html_ready(self, html_text):
        """Set our sphinx documentation based on thread result"""
        self._sphinx_thread.wait()
        self.set_rich_text_html(html_text, QUrl.fromLocalFile(CSS_PATH))

    def _on_sphinx_thread_error_msg(self, error_msg):
        """ Display error message on Sphinx rich text failure"""
        self._sphinx_thread.wait()
        self.plain_text_action.setChecked(True)
        sphinx_ver = programs.get_module_version('sphinx')
        QMessageBox.critical(self,
                    _('Help'),
                    _("The following error occured when calling "
                      "<b>Sphinx %s</b>. <br>Incompatible Sphinx "
                      "version or doc string decoding failed."
                      "<br><br>Error message:<br>%s"
                      ) % (sphinx_ver, error_msg))

    def show_help(self, obj_text, ignore_unknown=False):
        """Show help"""
        shell = self.get_shell()
        if shell is None:
            return
        obj_text = to_text_string(obj_text)

        if not shell.is_defined(obj_text):
            if self.get_option('automatic_import') and\
               self.internal_shell.is_defined(obj_text, force_import=True):
                shell = self.internal_shell
            else:
                shell = None
                doc = None
                source_text = None

        if shell is not None:
            doc = shell.get_doc(obj_text)
            source_text = shell.get_source(obj_text)

        is_code = False

        if self.rich_help:
            self.render_sphinx_doc(doc)
            return doc is not None
        elif self.docstring:
            hlp_text = doc
            if hlp_text is None:
                hlp_text = source_text
                if hlp_text is None:
                    hlp_text = self.no_doc_string
                    if ignore_unknown:
                        return False
        else:
            hlp_text = source_text
            if hlp_text is None:
                hlp_text = doc
                if hlp_text is None:
                    hlp_text = _("No source code available.")
                    if ignore_unknown:
                        return False
            else:
                is_code = True
        self.set_plain_text(hlp_text, is_code=is_code)
        return True
Example #2
0
class NamespaceBrowser(QWidget):
    """Namespace browser (global variables explorer widget)"""
    sig_option_changed = Signal(str, object)
    sig_collapse = Signal()

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

        self.shellwidget = None
        self.is_internal_shell = None
        self.ipyclient = None
        self.is_ipykernel = None

        self.is_visible = True  # Do not modify: light mode won't work!

        self.setup_in_progress = None

        # Remote dict editor settings
        self.check_all = None
        self.exclude_private = None
        self.exclude_uppercase = None
        self.exclude_capitalized = None
        self.exclude_unsupported = None
        self.excluded_names = None
        self.truncate = None
        self.minmax = None
        self.remote_editing = None
        self.autorefresh = None

        self.editor = None
        self.exclude_private_action = None
        self.exclude_uppercase_action = None
        self.exclude_capitalized_action = None
        self.exclude_unsupported_action = None

        self.filename = None

    def setup(self,
              check_all=None,
              exclude_private=None,
              exclude_uppercase=None,
              exclude_capitalized=None,
              exclude_unsupported=None,
              excluded_names=None,
              truncate=None,
              minmax=None,
              remote_editing=None,
              autorefresh=None):
        """Setup the namespace browser"""
        assert self.shellwidget is not None

        self.check_all = check_all
        self.exclude_private = exclude_private
        self.exclude_uppercase = exclude_uppercase
        self.exclude_capitalized = exclude_capitalized
        self.exclude_unsupported = exclude_unsupported
        self.excluded_names = excluded_names
        self.truncate = truncate
        self.minmax = minmax
        self.remote_editing = remote_editing
        self.autorefresh = autorefresh

        if self.editor is not None:
            self.editor.setup_menu(truncate, minmax)
            self.exclude_private_action.setChecked(exclude_private)
            self.exclude_uppercase_action.setChecked(exclude_uppercase)
            self.exclude_capitalized_action.setChecked(exclude_capitalized)
            self.exclude_unsupported_action.setChecked(exclude_unsupported)
            # Don't turn autorefresh on for IPython kernels
            # See Issue 1450
            if not self.is_ipykernel:
                self.auto_refresh_button.setChecked(autorefresh)
            self.refresh_table()
            return

        # Dict editor:
        if self.is_internal_shell:
            self.editor = DictEditorTableView(self,
                                              None,
                                              truncate=truncate,
                                              minmax=minmax)
        else:
            self.editor = RemoteDictEditorTableView(
                self,
                None,
                truncate=truncate,
                minmax=minmax,
                remote_editing=remote_editing,
                get_value_func=self.get_value,
                set_value_func=self.set_value,
                new_value_func=self.set_value,
                remove_values_func=self.remove_values,
                copy_value_func=self.copy_value,
                is_list_func=self.is_list,
                get_len_func=self.get_len,
                is_array_func=self.is_array,
                is_image_func=self.is_image,
                is_dict_func=self.is_dict,
                is_data_frame_func=self.is_data_frame,
                is_time_series_func=self.is_time_series,
                get_array_shape_func=self.get_array_shape,
                get_array_ndim_func=self.get_array_ndim,
                oedit_func=self.oedit,
                plot_func=self.plot,
                imshow_func=self.imshow,
                show_image_func=self.show_image)
        self.editor.sig_option_changed.connect(self.sig_option_changed.emit)
        self.editor.sig_files_dropped.connect(self.import_data)

        # Menu
        options_button = create_toolbutton(self,
                                           text=_('Options'),
                                           icon=ima.icon('tooloptions'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        editor = self.editor
        actions = [
            self.exclude_private_action, self.exclude_uppercase_action,
            self.exclude_capitalized_action, self.exclude_unsupported_action,
            None, editor.truncate_action
        ]
        if is_module_installed('numpy'):
            actions.append(editor.minmax_action)
        add_actions(menu, actions)
        options_button.setMenu(menu)

        # Setup layout
        layout = QVBoxLayout()
        blayout = QHBoxLayout()
        toolbar = self.setup_toolbar(exclude_private, exclude_uppercase,
                                     exclude_capitalized, exclude_unsupported,
                                     autorefresh)
        for widget in toolbar:
            blayout.addWidget(widget)
        blayout.addStretch()
        blayout.addWidget(options_button)
        layout.addLayout(blayout)
        layout.addWidget(self.editor)
        self.setLayout(layout)
        layout.setContentsMargins(0, 0, 0, 0)

        self.sig_option_changed.connect(self.option_changed)

    def set_shellwidget(self, shellwidget):
        """Bind shellwidget instance to namespace browser"""
        self.shellwidget = shellwidget
        from spyderlib.widgets import internalshell
        self.is_internal_shell = isinstance(self.shellwidget,
                                            internalshell.InternalShell)
        self.is_ipykernel = self.shellwidget.is_ipykernel
        if not self.is_internal_shell:
            shellwidget.set_namespacebrowser(self)

    def set_ipyclient(self, ipyclient):
        """Bind ipyclient instance to namespace browser"""
        self.ipyclient = ipyclient

    def setup_toolbar(self, exclude_private, exclude_uppercase,
                      exclude_capitalized, exclude_unsupported, autorefresh):
        """Setup toolbar"""
        self.setup_in_progress = True

        toolbar = []

        refresh_button = create_toolbutton(self,
                                           text=_('Refresh'),
                                           icon=ima.icon('reload'),
                                           triggered=self.refresh_table)
        self.auto_refresh_button = create_toolbutton(
            self,
            text=_('Refresh periodically'),
            icon=ima.icon('auto_reload'),
            toggled=self.toggle_auto_refresh)
        self.auto_refresh_button.setChecked(autorefresh)
        load_button = create_toolbutton(self,
                                        text=_('Import data'),
                                        icon=ima.icon('fileimport'),
                                        triggered=self.import_data)
        self.save_button = create_toolbutton(
            self,
            text=_("Save data"),
            icon=ima.icon('filesave'),
            triggered=lambda: self.save_data(self.filename))
        self.save_button.setEnabled(False)
        save_as_button = create_toolbutton(self,
                                           text=_("Save data as..."),
                                           icon=ima.icon('filesaveas'),
                                           triggered=self.save_data)
        toolbar += [
            refresh_button, self.auto_refresh_button, load_button,
            self.save_button, save_as_button
        ]

        self.exclude_private_action = create_action(
            self,
            _("Exclude private references"),
            tip=_("Exclude references which name starts"
                  " with an underscore"),
            toggled=lambda state: self.sig_option_changed.emit(
                'exclude_private', state))
        self.exclude_private_action.setChecked(exclude_private)

        self.exclude_uppercase_action = create_action(
            self,
            _("Exclude all-uppercase references"),
            tip=_("Exclude references which name is uppercase"),
            toggled=lambda state: self.sig_option_changed.emit(
                'exclude_uppercase', state))
        self.exclude_uppercase_action.setChecked(exclude_uppercase)

        self.exclude_capitalized_action = create_action(
            self,
            _("Exclude capitalized references"),
            tip=_("Exclude references which name starts with an "
                  "uppercase character"),
            toggled=lambda state: self.sig_option_changed.emit(
                'exclude_capitalized', state))
        self.exclude_capitalized_action.setChecked(exclude_capitalized)

        self.exclude_unsupported_action = create_action(
            self,
            _("Exclude unsupported data types"),
            tip=_("Exclude references to unsupported data types"
                  " (i.e. which won't be handled/saved correctly)"),
            toggled=lambda state: self.sig_option_changed.emit(
                'exclude_unsupported', state))
        self.exclude_unsupported_action.setChecked(exclude_unsupported)

        self.setup_in_progress = False

        return toolbar

    def option_changed(self, option, value):
        """Option has changed"""
        setattr(self, to_text_string(option), value)
        if not self.is_internal_shell:
            settings = self.get_view_settings()
            communicate(self._get_sock(),
                        'set_remote_view_settings()',
                        settings=[settings])

    def visibility_changed(self, enable):
        """Notify the widget whether its container (the namespace browser 
        plugin is visible or not"""
        self.is_visible = enable
        if enable:
            self.refresh_table()

    @Slot(bool)
    def toggle_auto_refresh(self, state):
        """Toggle auto refresh state"""
        self.autorefresh = state
        if not self.setup_in_progress and not self.is_internal_shell:
            communicate(self._get_sock(),
                        "set_monitor_auto_refresh(%r)" % state)

    def _get_sock(self):
        """Return socket connection"""
        return self.shellwidget.introspection_socket

    def get_internal_shell_filter(self, mode, check_all=None):
        """
        Return internal shell data types filter:
            * check_all: check all elements data types for sequences
              (dict, list, tuple)
            * mode (string): 'editable' or 'picklable'
        """
        assert mode in list(SUPPORTED_TYPES.keys())
        if check_all is None:
            check_all = self.check_all

        def wsfilter(input_dict,
                     check_all=check_all,
                     filters=tuple(SUPPORTED_TYPES[mode])):
            """Keep only objects that can be pickled"""
            return globalsfilter(input_dict,
                                 check_all=check_all,
                                 filters=filters,
                                 exclude_private=self.exclude_private,
                                 exclude_uppercase=self.exclude_uppercase,
                                 exclude_capitalized=self.exclude_capitalized,
                                 exclude_unsupported=self.exclude_unsupported,
                                 excluded_names=self.excluded_names)

        return wsfilter

    def get_view_settings(self):
        """Return dict editor view settings"""
        settings = {}
        for name in REMOTE_SETTINGS:
            settings[name] = getattr(self, name)
        return settings

    @Slot()
    def refresh_table(self):
        """Refresh variable table"""
        if self.is_visible and self.isVisible():
            if self.is_internal_shell:
                # Internal shell
                wsfilter = self.get_internal_shell_filter('editable')
                self.editor.set_filter(wsfilter)
                interpreter = self.shellwidget.interpreter
                if interpreter is not None:
                    self.editor.set_data(interpreter.namespace)
                    self.editor.adjust_columns()
            elif self.shellwidget.is_running():
                #            import time; print >>STDOUT, time.ctime(time.time()), "Refreshing namespace browser"
                sock = self._get_sock()
                if sock is None:
                    return
                try:
                    communicate(sock, "refresh()")
                except socket.error:
                    # Process was terminated before calling this method
                    pass

    def process_remote_view(self, remote_view):
        """Process remote view"""
        if remote_view is not None:
            self.set_data(remote_view)

    #------ Remote Python process commands ------------------------------------
    def get_value(self, name):
        value = monitor_get_global(self._get_sock(), name)
        if value is None:
            if communicate(self._get_sock(), '%s is not None' % name):
                import pickle
                msg = to_text_string(
                    _("Object <b>%s</b> is not picklable") % name)
                raise pickle.PicklingError(msg)
        return value

    def set_value(self, name, value):
        monitor_set_global(self._get_sock(), name, value)
        self.refresh_table()

    def remove_values(self, names):
        for name in names:
            monitor_del_global(self._get_sock(), name)
        self.refresh_table()

    def copy_value(self, orig_name, new_name):
        monitor_copy_global(self._get_sock(), orig_name, new_name)
        self.refresh_table()

    def is_list(self, name):
        """Return True if variable is a list or a tuple"""
        return communicate(self._get_sock(),
                           'isinstance(%s, (tuple, list))' % name)

    def is_dict(self, name):
        """Return True if variable is a dictionary"""
        return communicate(self._get_sock(), 'isinstance(%s, dict)' % name)

    def get_len(self, name):
        """Return sequence length"""
        return communicate(self._get_sock(), "len(%s)" % name)

    def is_array(self, name):
        """Return True if variable is a NumPy array"""
        return communicate(self._get_sock(), 'is_array("%s")' % name)

    def is_image(self, name):
        """Return True if variable is a PIL.Image image"""
        return communicate(self._get_sock(), 'is_image("%s")' % name)

    def is_data_frame(self, name):
        """Return True if variable is a data_frame"""
        return communicate(self._get_sock(),
                           "isinstance(globals()['%s'], DataFrame)" % name)

    def is_time_series(self, name):
        """Return True if variable is a data_frame"""
        return communicate(self._get_sock(),
                           "isinstance(globals()['%s'], TimeSeries)" % name)

    def get_array_shape(self, name):
        """Return array's shape"""
        return communicate(self._get_sock(), "%s.shape" % name)

    def get_array_ndim(self, name):
        """Return array's ndim"""
        return communicate(self._get_sock(), "%s.ndim" % name)

    def plot(self, name, funcname):
        command = "import spyderlib.pyplot; "\
                  "__fig__ = spyderlib.pyplot.figure(); "\
                  "__items__ = getattr(spyderlib.pyplot, '%s')(%s); "\
                  "spyderlib.pyplot.show(); "\
                  "del __fig__, __items__;" % (funcname, name)
        if self.is_ipykernel:
            self.ipyclient.shellwidget.execute("%%varexp --%s %s" %
                                               (funcname, name))
        else:
            self.shellwidget.send_to_process(command)

    def imshow(self, name):
        command = "import spyderlib.pyplot; " \
                  "__fig__ = spyderlib.pyplot.figure(); " \
                  "__items__ = spyderlib.pyplot.imshow(%s); " \
                  "spyderlib.pyplot.show(); del __fig__, __items__;" % name
        if self.is_ipykernel:
            self.ipyclient.shellwidget.execute("%%varexp --imshow %s" % name)
        else:
            self.shellwidget.send_to_process(command)

    def show_image(self, name):
        command = "%s.show()" % name
        if self.is_ipykernel:
            self.ipyclient.shellwidget.execute(command)
        else:
            self.shellwidget.send_to_process(command)

    def oedit(self, name):
        command = "from spyderlib.widgets.objecteditor import oedit; " \
                  "oedit('%s', modal=False, namespace=locals());" % name
        self.shellwidget.send_to_process(command)

    #------ Set, load and save data -------------------------------------------
    def set_data(self, data):
        """Set data"""
        if data != self.editor.model.get_data():
            self.editor.set_data(data)
            self.editor.adjust_columns()

    def collapse(self):
        """Collapse"""
        self.sig_collapse.emit()

    # FIXME: This method raises an error when called from the button in the UI
    @Slot(list)
    def import_data(self, filenames=None):
        """Import data from text file"""
        title = _("Import data")
        if filenames is None:
            if self.filename is None:
                basedir = getcwd()
            else:
                basedir = osp.dirname(self.filename)
            filenames, _selfilter = getopenfilenames(self, title, basedir,
                                                     iofunctions.load_filters)
            if not filenames:
                return
        elif is_text_string(filenames):
            filenames = [filenames]

        for filename in filenames:
            self.filename = to_text_string(filename)
            ext = osp.splitext(self.filename)[1].lower()

            if ext not in iofunctions.load_funcs:
                buttons = QMessageBox.Yes | QMessageBox.Cancel
                answer = QMessageBox.question(
                    self, title,
                    _("<b>Unsupported file extension '%s'</b><br><br>"
                      "Would you like to import it anyway "
                      "(by selecting a known file format)?") % ext, buttons)
                if answer == QMessageBox.Cancel:
                    return
                formats = list(iofunctions.load_extensions.keys())
                item, ok = QInputDialog.getItem(self, title,
                                                _('Open file as:'), formats, 0,
                                                False)
                if ok:
                    ext = iofunctions.load_extensions[to_text_string(item)]
                else:
                    return

            load_func = iofunctions.load_funcs[ext]

            # 'import_wizard' (self.setup_io)
            if is_text_string(load_func):
                # Import data with import wizard
                error_message = None
                try:
                    text, _encoding = encoding.read(self.filename)
                    if self.is_internal_shell:
                        self.editor.import_from_string(text)
                    else:
                        base_name = osp.basename(self.filename)
                        editor = ImportWizard(
                            self,
                            text,
                            title=base_name,
                            varname=fix_reference_name(base_name))
                        if editor.exec_():
                            var_name, clip_data = editor.get_data()
                            monitor_set_global(self._get_sock(), var_name,
                                               clip_data)
                except Exception as error:
                    error_message = str(error)
            else:
                QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
                QApplication.processEvents()
                if self.is_internal_shell:
                    namespace, error_message = load_func(self.filename)
                    interpreter = self.shellwidget.interpreter
                    for key in list(namespace.keys()):
                        new_key = fix_reference_name(
                            key, blacklist=list(interpreter.namespace.keys()))
                        if new_key != key:
                            namespace[new_key] = namespace.pop(key)
                    if error_message is None:
                        interpreter.namespace.update(namespace)
                else:
                    error_message = monitor_load_globals(
                        self._get_sock(), self.filename, ext)
                QApplication.restoreOverrideCursor()
                QApplication.processEvents()

            if error_message is not None:
                QMessageBox.critical(
                    self, title,
                    _("<b>Unable to load '%s'</b>"
                      "<br><br>Error message:<br>%s") %
                    (self.filename, error_message))
            self.refresh_table()

    @Slot()
    def save_data(self, filename=None):
        """Save data"""
        if filename is None:
            filename = self.filename
            if filename is None:
                filename = getcwd()
            filename, _selfilter = getsavefilename(self, _("Save data"),
                                                   filename,
                                                   iofunctions.save_filters)
            if filename:
                self.filename = filename
            else:
                return False
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()
        if self.is_internal_shell:
            wsfilter = self.get_internal_shell_filter('picklable',
                                                      check_all=True)
            namespace = wsfilter(self.shellwidget.interpreter.namespace).copy()
            error_message = iofunctions.save(namespace, filename)
        else:
            settings = self.get_view_settings()
            error_message = monitor_save_globals(self._get_sock(), settings,
                                                 filename)
        QApplication.restoreOverrideCursor()
        QApplication.processEvents()
        if error_message is not None:
            QMessageBox.critical(
                self, _("Save data"),
                _("<b>Unable to save current workspace</b>"
                  "<br><br>Error message:<br>%s") % error_message)
        self.save_button.setEnabled(self.filename is not None)
Example #3
0
class Profiler(ProfilerWidget, SpyderPluginMixin):
    """Profiler (after python's profile and pstats)"""
    CONF_SECTION = 'profiler'
    CONFIGWIDGET_CLASS = ProfilerConfigPage
    edit_goto = Signal(str, int, str)

    def __init__(self, parent=None):
        ProfilerWidget.__init__(self,
                                parent=parent,
                                max_entries=self.get_option('max_entries', 50))
        SpyderPluginMixin.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

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

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

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

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

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

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.edit_goto.connect(self.main.editor.load)
        self.redirect_stdio.connect(self.main.redirect_internalshell_stdio)
        self.main.add_dockwidget(self)

        profiler_act = create_action(self,
                                     _("Profile"),
                                     icon=ima.icon('profiler'),
                                     triggered=self.run_profiler)
        profiler_act.setEnabled(is_profiler_installed())
        self.register_shortcut(profiler_act,
                               context="Profiler",
                               name="Run profiler")

        self.main.run_menu_actions += [profiler_act]
        self.main.editor.pythonfile_dependent_actions += [profiler_act]

    def refresh_plugin(self):
        """Refresh profiler widget"""
        #self.remove_obsolete_items()  # FIXME: not implemented yet

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

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        # The history depth option will be applied at
        # next Spyder startup, which is soon enough
        pass

    #------ Public API ---------------------------------------------------------
    def run_profiler(self):
        """Run profiler"""
        self.analyze(self.main.editor.get_current_filename())

    def analyze(self, filename):
        """Reimplement analyze method"""
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.setFocus()
            self.dockwidget.raise_()
        pythonpath = self.main.get_spyder_pythonpath()
        runconf = runconfig.get_run_configuration(filename)
        wdir, args = None, []
        if runconf is not None:
            if runconf.wdir_enabled:
                wdir = runconf.wdir
            if runconf.args_enabled:
                args = runconf.args
        ProfilerWidget.analyze(self,
                               filename,
                               wdir=wdir,
                               args=args,
                               pythonpath=pythonpath)
Example #4
0
class PathManager(QDialog):
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent=None, pathlist=None, ro_pathlist=None, sync=True):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        assert isinstance(pathlist, list)
        self.pathlist = pathlist
        if ro_pathlist is None:
            ro_pathlist = []
        self.ro_pathlist = ro_pathlist
        
        self.last_path = getcwd()
        
        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)
        
        self.selection_widgets = []
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        top_layout = QHBoxLayout()
        layout.addLayout(top_layout)
        self.toolbar_widgets1 = self.setup_top_toolbar(top_layout)

        self.listwidget = QListWidget(self)
        self.listwidget.currentRowChanged.connect(self.refresh)
        layout.addWidget(self.listwidget)

        bottom_layout = QHBoxLayout()
        layout.addLayout(bottom_layout)
        self.sync_button = None
        self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync)        
        
        # Buttons configuration
        bbox = QDialogButtonBox(QDialogButtonBox.Close)
        bbox.rejected.connect(self.reject)
        bottom_layout.addWidget(bbox)
        
        self.update_list()
        self.refresh()
        
    def _add_widgets_to_layout(self, layout, widgets):
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            layout.addWidget(widget)
        
    def setup_top_toolbar(self, layout):
        toolbar = []
        movetop_button = create_toolbutton(self,
                                    text=_("Move to top"),
                                    icon=ima.icon('2uparrow'),
                                    triggered=lambda: self.move_to(absolute=0),
                                    text_beside_icon=True)
        toolbar.append(movetop_button)
        moveup_button = create_toolbutton(self,
                                    text=_("Move up"),
                                    icon=ima.icon('1uparrow'),
                                    triggered=lambda: self.move_to(relative=-1),
                                    text_beside_icon=True)
        toolbar.append(moveup_button)
        movedown_button = create_toolbutton(self,
                                    text=_("Move down"),
                                    icon=ima.icon('1downarrow'),
                                    triggered=lambda: self.move_to(relative=1),
                                    text_beside_icon=True)
        toolbar.append(movedown_button)
        movebottom_button = create_toolbutton(self,
                                    text=_("Move to bottom"),
                                    icon=ima.icon('2downarrow'),
                                    triggered=lambda: self.move_to(absolute=1),
                                    text_beside_icon=True)
        toolbar.append(movebottom_button)
        self.selection_widgets.extend(toolbar)
        self._add_widgets_to_layout(layout, toolbar)
        return toolbar
    
    def setup_bottom_toolbar(self, layout, sync=True):
        toolbar = []
        add_button = create_toolbutton(self, text=_('Add path'),
                                       icon=ima.icon('edit_add'),
                                       triggered=self.add_path,
                                       text_beside_icon=True)
        toolbar.append(add_button)
        remove_button = create_toolbutton(self, text=_('Remove path'),
                                          icon=ima.icon('edit_remove'),
                                          triggered=self.remove_path,
                                          text_beside_icon=True)
        toolbar.append(remove_button)
        self.selection_widgets.append(remove_button)
        self._add_widgets_to_layout(layout, toolbar)
        layout.addStretch(1)
        if os.name == 'nt' and sync:
            self.sync_button = create_toolbutton(self,
                  text=_("Synchronize..."),
                  icon=ima.icon('fileimport'), triggered=self.synchronize,
                  tip=_("Synchronize Spyder's path list with PYTHONPATH "
                              "environment variable"),
                  text_beside_icon=True)
            layout.addWidget(self.sync_button)
        return toolbar

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms
        """
        answer = QMessageBox.question(self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
                    "<b>PYTHONPATH</b> environment variable for current user, "
                    "allowing you to run your Python modules outside Spyder "
                    "without having to configure sys.path. "
                    "<br>Do you want to clear contents of PYTHONPATH before "
                    "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False
        from spyderlib.utils.environ import (get_user_env, set_user_env,
                                             listdict2envdict)
        env = get_user_env()
        if remove:
            ppath = self.pathlist+self.ro_pathlist
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]
            ppath = [path for path in ppath
                     if path not in (self.pathlist+self.ro_pathlist)]
            ppath.extend(self.pathlist+self.ro_pathlist)
        env['PYTHONPATH'] = ppath
        set_user_env( listdict2envdict(env), parent=self )
        
    def get_path_list(self):
        """Return path list (does not include the read-only path list)"""
        return self.pathlist
        
    def update_list(self):
        """Update path list"""
        self.listwidget.clear()
        for name in self.pathlist+self.ro_pathlist:
            item = QListWidgetItem(name)
            item.setIcon(ima.icon('DirClosedIcon'))
            if name in self.ro_pathlist:
                item.setFlags(Qt.NoItemFlags)
            self.listwidget.addItem(item)
        self.refresh()
        
    def refresh(self, row=None):
        """Refresh widget"""
        for widget in self.selection_widgets:
            widget.setEnabled(self.listwidget.currentItem() is not None)
        not_empty = self.listwidget.count() > 0
        if self.sync_button is not None:
            self.sync_button.setEnabled(not_empty)
    
    def move_to(self, absolute=None, relative=None):
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = len(self.pathlist)-1
            else:
                new_index = 0
        else:
            new_index = index + relative        
        new_index = max(0, min(len(self.pathlist)-1, new_index))
        path = self.pathlist.pop(index)
        self.pathlist.insert(new_index, path)
        self.update_list()
        self.listwidget.setCurrentRow(new_index)

    @Slot()
    def remove_path(self):
        answer = QMessageBox.warning(self, _("Remove path"),
            _("Do you really want to remove selected path?"),
            QMessageBox.Yes | QMessageBox.No)
        if answer == QMessageBox.Yes:
            self.pathlist.pop(self.listwidget.currentRow())
            self.update_list()

    @Slot()
    def add_path(self):
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.last_path)
        self.redirect_stdio.emit(True)
        if directory:
            directory = osp.abspath(directory)
            self.last_path = directory
            if directory in self.pathlist:
                answer = QMessageBox.question(self, _("Add path"),
                    _("This directory is already included in Spyder path "
                            "list.<br>Do you want to move it to the top of "
                            "the list?"),
                    QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.pathlist.remove(directory)
                else:
                    return
            self.pathlist.insert(0, directory)
            self.update_list()
Example #5
0
class Pylint(PylintWidget, SpyderPluginMixin):
    """Python source code analysis based on pylint"""
    CONF_SECTION = 'pylint'
    CONFIGWIDGET_CLASS = PylintConfigPage
    edit_goto = Signal(str, int, str)

    def __init__(self, parent=None):
        PylintWidget.__init__(self,
                              parent=parent,
                              max_entries=self.get_option('max_entries', 50))
        SpyderPluginMixin.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

    #------ SpyderPluginWidget API --------------------------------------------
    def get_plugin_title(self):
        """Return widget title"""
        return _("Static code analysis")

    def get_plugin_icon(self):
        """Return widget icon"""
        path = osp.join(self.PLUGIN_PATH, self.IMG_PATH)
        return ima.icon('pylint', icon_path=path)

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

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        # Font
        history_action = create_action(self,
                                       _("History..."),
                                       None,
                                       ima.icon('history'),
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        self.treewidget.common_actions += (None, history_action)
        return []

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

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.edit_goto.connect(self.main.editor.load)
        self.redirect_stdio.connect(self.main.redirect_internalshell_stdio)
        self.main.add_dockwidget(self)

        pylint_act = create_action(self,
                                   _("Run static code analysis"),
                                   triggered=self.run_pylint)
        pylint_act.setEnabled(PYLINT_PATH is not None)
        self.register_shortcut(pylint_act,
                               context="Pylint",
                               name="Run analysis")

        self.main.source_menu_actions += [None, pylint_act]
        self.main.editor.pythonfile_dependent_actions += [pylint_act]

    def refresh_plugin(self):
        """Refresh pylint widget"""
        self.remove_obsolete_items()

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

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        # The history depth option will be applied at
        # next Spyder startup, which is soon enough
        pass

    #------ Public API --------------------------------------------------------
    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInteger(self, _('History'),
                                               _('Maximum entries'),
                                               self.get_option('max_entries'),
                                               10, 10000)
        if valid:
            self.set_option('max_entries', depth)

    def run_pylint(self):
        """Run pylint code analysis"""
        if self.get_option('save_before', True)\
           and not self.main.editor.save():
            return
        self.analyze(self.main.editor.get_current_filename())

    def analyze(self, filename):
        """Reimplement analyze method"""
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.setFocus()
            self.dockwidget.raise_()
        PylintWidget.analyze(self, filename)
Example #6
0
class BaseTabs(QTabWidget):
    """TabWidget with context menu and corner widgets"""
    sig_close_tab = Signal(int)

    def __init__(self,
                 parent,
                 actions=None,
                 menu=None,
                 corner_widgets=None,
                 menu_use_tooltips=False):
        QTabWidget.__init__(self, parent)

        self.setUsesScrollButtons(True)

        self.corner_widgets = {}
        self.menu_use_tooltips = menu_use_tooltips

        if menu is None:
            self.menu = QMenu(self)
            if actions:
                add_actions(self.menu, actions)
        else:
            self.menu = menu

        # Corner widgets
        if corner_widgets is None:
            corner_widgets = {}
        corner_widgets.setdefault(Qt.TopLeftCorner, [])
        corner_widgets.setdefault(Qt.TopRightCorner, [])
        self.browse_button = create_toolbutton(self,
                                               icon=get_icon("browse_tab.png"),
                                               tip=_("Browse tabs"))
        self.browse_tabs_menu = QMenu(self)
        self.browse_button.setMenu(self.browse_tabs_menu)
        self.browse_button.setPopupMode(self.browse_button.InstantPopup)
        self.browse_tabs_menu.aboutToShow.connect(self.update_browse_tabs_menu)
        corner_widgets[Qt.TopLeftCorner] += [self.browse_button]

        self.set_corner_widgets(corner_widgets)

    def update_browse_tabs_menu(self):
        """Update browse tabs menu"""
        self.browse_tabs_menu.clear()
        names = []
        dirnames = []
        for index in range(self.count()):
            if self.menu_use_tooltips:
                text = to_text_string(self.tabToolTip(index))
            else:
                text = to_text_string(self.tabText(index))
            names.append(text)
            if osp.isfile(text):
                # Testing if tab names are filenames
                dirnames.append(osp.dirname(text))
        offset = None

        # If tab names are all filenames, removing common path:
        if len(names) == len(dirnames):
            common = get_common_path(dirnames)
            if common is None:
                offset = None
            else:
                offset = len(common) + 1
                if offset <= 3:
                    # Common path is not a path but a drive letter...
                    offset = None

        for index, text in enumerate(names):
            tab_action = create_action(
                self,
                text[offset:],
                icon=self.tabIcon(index),
                toggled=lambda state, index=index: self.setCurrentIndex(index),
                tip=self.tabToolTip(index))
            tab_action.setChecked(index == self.currentIndex())
            self.browse_tabs_menu.addAction(tab_action)

    def set_corner_widgets(self, corner_widgets):
        """
        Set tabs corner widgets
        corner_widgets: dictionary of (corner, widgets)
        corner: Qt.TopLeftCorner or Qt.TopRightCorner
        widgets: list of widgets (may contains integers to add spacings)
        """
        assert isinstance(corner_widgets, dict)
        assert all(key in (Qt.TopLeftCorner, Qt.TopRightCorner)
                   for key in corner_widgets)
        self.corner_widgets.update(corner_widgets)
        for corner, widgets in list(self.corner_widgets.items()):
            cwidget = QWidget()
            cwidget.hide()
            prev_widget = self.cornerWidget(corner)
            if prev_widget:
                prev_widget.close()
            self.setCornerWidget(cwidget, corner)
            clayout = QHBoxLayout()
            clayout.setContentsMargins(0, 0, 0, 0)
            for widget in widgets:
                if isinstance(widget, int):
                    clayout.addSpacing(widget)
                else:
                    clayout.addWidget(widget)
            cwidget.setLayout(clayout)
            cwidget.show()

    def add_corner_widgets(self, widgets, corner=Qt.TopRightCorner):
        self.set_corner_widgets(
            {corner: self.corner_widgets.get(corner, []) + widgets})

    def contextMenuEvent(self, event):
        """Override Qt method"""
        if self.menu:
            self.menu.popup(event.globalPos())

    def mousePressEvent(self, event):
        """Override Qt method"""
        if event.button() == Qt.MidButton:
            index = self.tabBar().tabAt(event.pos())
            if index >= 0:
                self.sig_close_tab.emit(index)
                event.accept()
                return
        QTabWidget.mousePressEvent(self, event)

    def keyPressEvent(self, event):
        """Override Qt method"""
        ctrl = event.modifiers() & Qt.ControlModifier
        key = event.key()
        handled = False
        if ctrl and self.count() > 0:
            index = self.currentIndex()
            if key == Qt.Key_PageUp and index > 0:
                self.setCurrentIndex(index - 1)
                handled = True
            elif key == Qt.Key_PageDown and index < self.count() - 1:
                self.setCurrentIndex(index + 1)
                handled = True
        if not handled:
            QTabWidget.keyPressEvent(self, event)

    def set_close_function(self, func):
        """Setting Tabs close function
        None -> tabs are not closable"""
        state = func is not None
        if state:
            self.sig_close_tab.connect(func)
        try:
            # Assuming Qt >= 4.5
            QTabWidget.setTabsClosable(self, state)
            self.tabCloseRequested.connect(func)
        except AttributeError:
            # Workaround for Qt < 4.5
            close_button = create_toolbutton(self,
                                             triggered=func,
                                             icon=get_icon("fileclose.png"),
                                             tip=_("Close current tab"))
            self.setCornerWidget(close_button if state else None)
Example #7
0
class TabBar(QTabBar):
    """Tabs base class with drag and drop support"""
    sig_move_tab = Signal((int, int), (str, int, int))

    def __init__(self, parent, ancestor):
        QTabBar.__init__(self, parent)
        self.ancestor = ancestor

        # Dragging tabs
        self.__drag_start_pos = QPoint()
        self.setAcceptDrops(True)

    def mousePressEvent(self, event):
        """Reimplement Qt method"""
        if event.button() == Qt.LeftButton:
            self.__drag_start_pos = QPoint(event.pos())
        QTabBar.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        """Override Qt method"""
        if event.buttons() == Qt.MouseButtons(Qt.LeftButton) and \
           (event.pos() - self.__drag_start_pos).manhattanLength() > \
                QApplication.startDragDistance():
            drag = QDrag(self)
            mimeData = QMimeData()
            # Converting id's to long to avoid an OverflowError with PySide
            if PY2:
                ancestor_id = long(id(self.ancestor))
                parent_widget_id = long(id(self.parentWidget()))
                self_id = long(id(self))
            else:
                ancestor_id = id(self.ancestor)
                parent_widget_id = id(self.parentWidget())
                self_id = id(self)
            mimeData.setData("parent-id", QByteArray.number(ancestor_id))
            mimeData.setData("tabwidget-id",
                             QByteArray.number(parent_widget_id))
            mimeData.setData("tabbar-id", QByteArray.number(self_id))
            mimeData.setData(
                "source-index",
                QByteArray.number(self.tabAt(self.__drag_start_pos)))
            drag.setMimeData(mimeData)
            drag.exec_()
        QTabBar.mouseMoveEvent(self, event)

    def dragEnterEvent(self, event):
        """Override Qt method"""
        mimeData = event.mimeData()
        formats = list(mimeData.formats())
        if "parent-id" in formats and \
           mimeData.data("parent-id").toLong()[0] == id(self.ancestor):
            event.acceptProposedAction()
        QTabBar.dragEnterEvent(self, event)

    def dropEvent(self, event):
        """Override Qt method"""
        mimeData = event.mimeData()
        index_from = mimeData.data("source-index").toInt()[0]
        index_to = self.tabAt(event.pos())
        if index_to == -1:
            index_to = self.count()
        if mimeData.data("tabbar-id").toLong()[0] != id(self):
            tabwidget_from = str(mimeData.data("tabwidget-id").toLong()[0])

            # We pass self object ID as a QString, because otherwise it would
            # depend on the platform: long for 64bit, int for 32bit. Replacing
            # by long all the time is not working on some 32bit platforms
            # (see Issue 1094, Issue 1098)
            self.sig_move_tab[(str, int, int)].emit(tabwidget_from, index_from,
                                                    index_to)

            event.acceptProposedAction()
        elif index_from != index_to:
            self.sig_move_tab.emit(index_from, index_to)
            event.acceptProposedAction()
        QTabBar.dropEvent(self, event)
Example #8
0
class FormWidget(QWidget):
    update_buttons = Signal()

    def __init__(self, data, comment="", parent=None):
        QWidget.__init__(self, parent)
        from copy import deepcopy
        self.data = deepcopy(data)
        self.widgets = []
        self.formlayout = QFormLayout(self)
        if comment:
            self.formlayout.addRow(QLabel(comment))
            self.formlayout.addRow(QLabel(" "))
        if DEBUG_FORMLAYOUT:
            print("\n" + ("*" * 80))
            print("DATA:", self.data)
            print("*" * 80)
            print("COMMENT:", comment)
            print("*" * 80)

    def get_dialog(self):
        """Return FormDialog instance"""
        dialog = self.parent()
        while not isinstance(dialog, QDialog):
            dialog = dialog.parent()
        return dialog

    def setup(self):
        for label, value in self.data:
            if DEBUG_FORMLAYOUT:
                print("value:", value)
            if label is None and value is None:
                # Separator: (None, None)
                self.formlayout.addRow(QLabel(" "), QLabel(" "))
                self.widgets.append(None)
                continue
            elif label is None:
                # Comment
                self.formlayout.addRow(QLabel(value))
                self.widgets.append(None)
                continue
            elif tuple_to_qfont(value) is not None:
                field = FontLayout(value, self)
            elif text_to_qcolor(value).isValid():
                field = ColorLayout(QColor(value), self)
            elif is_text_string(value):
                if '\n' in value:
                    for linesep in (os.linesep, '\n'):
                        if linesep in value:
                            value = value.replace(linesep, u"\u2029")
                    field = QTextEdit(value, self)
                else:
                    field = QLineEdit(value, self)
            elif isinstance(value, (list, tuple)):
                value = list(value)  # in case this is a tuple
                selindex = value.pop(0)
                field = QComboBox(self)
                if isinstance(value[0], (list, tuple)):
                    keys = [key for key, _val in value]
                    value = [val for _key, val in value]
                else:
                    keys = value
                field.addItems(value)
                if selindex in value:
                    selindex = value.index(selindex)
                elif selindex in keys:
                    selindex = keys.index(selindex)
                elif not isinstance(selindex, int):
                    print("Warning: '%s' index is invalid (label: "\
                          "%s, value: %s)" % (selindex, label, value),
                          file=STDERR)
                    selindex = 0
                field.setCurrentIndex(selindex)
            elif isinstance(value, bool):
                field = QCheckBox(self)
                field.setCheckState(Qt.Checked if value else Qt.Unchecked)
            elif isinstance(value, float):
                field = QLineEdit(repr(value), self)
                field.setValidator(QDoubleValidator(field))
                dialog = self.get_dialog()
                dialog.register_float_field(field)
                field.textChanged.connect(lambda text: dialog.update_buttons())
            elif isinstance(value, int):
                field = QSpinBox(self)
                field.setRange(-1e9, 1e9)
                field.setValue(value)
            elif isinstance(value, datetime.datetime):
                field = QDateTimeEdit(self)
                field.setDateTime(value)
            elif isinstance(value, datetime.date):
                field = QDateEdit(self)
                field.setDate(value)
            else:
                field = QLineEdit(repr(value), self)
            self.formlayout.addRow(label, field)
            self.widgets.append(field)

    def get(self):
        valuelist = []
        for index, (label, value) in enumerate(self.data):
            field = self.widgets[index]
            if label is None:
                # Separator / Comment
                continue
            elif tuple_to_qfont(value) is not None:
                value = field.get_font()
            elif is_text_string(value):
                if isinstance(field, QTextEdit):
                    value = to_text_string(field.toPlainText()).replace(
                        u"\u2029", os.linesep)
                else:
                    value = to_text_string(field.text())
            elif isinstance(value, (list, tuple)):
                index = int(field.currentIndex())
                if isinstance(value[0], int):
                    # Return an int index, if initialization was an int
                    value = index
                else:
                    value = value[index + 1]
                    if isinstance(value, (list, tuple)):
                        value = value[0]
            elif isinstance(value, bool):
                value = field.checkState() == Qt.Checked
            elif isinstance(value, float):
                value = float(field.text())
            elif isinstance(value, int):
                value = int(field.value())
            elif isinstance(value, datetime.datetime):
                value = field.dateTime()
                try:
                    value = value.toPyDateTime()  # PyQt
                except AttributeError:
                    value = value.toPython()  # PySide
            elif isinstance(value, datetime.date):
                value = field.date()
                try:
                    value = value.toPyDate()  # PyQt
                except AttributeError:
                    value = value.toPython()  # PySide
            else:
                value = eval(str(field.text()))
            valuelist.append(value)
        return valuelist
Example #9
0
class VariableExplorer(QStackedWidget, SpyderPluginMixin):
    """
    Variable Explorer Plugin
    """
    CONF_SECTION = 'variable_explorer'
    CONFIGWIDGET_CLASS = VariableExplorerConfigPage
    sig_option_changed = Signal(str, object)

    def __init__(self, parent):
        QStackedWidget.__init__(self, parent)
        SpyderPluginMixin.__init__(self, parent)
        self.shellwidgets = {}

        # Initialize plugin
        self.initialize_plugin()

    @staticmethod
    def get_settings():
        """
        Return Variable Explorer settings dictionary
        (i.e. namespace browser settings according to Spyder's configuration file)
        """
        settings = {}
        #        CONF.load_from_ini() # necessary only when called from another process
        for name in REMOTE_SETTINGS:
            settings[name] = CONF.get(VariableExplorer.CONF_SECTION, name)
        return settings

    #------ Public API ---------------------------------------------------------
    def add_shellwidget(self, shellwidget):
        shellwidget_id = id(shellwidget)
        # Add shell only once: this method may be called two times in a row
        # by the External console plugin (dev. convenience)
        from spyderlib.widgets.externalshell import systemshell
        if isinstance(shellwidget, systemshell.ExternalSystemShell):
            return
        if shellwidget_id not in self.shellwidgets:
            nsb = NamespaceBrowser(self)
            nsb.set_shellwidget(shellwidget)
            nsb.setup(**VariableExplorer.get_settings())
            nsb.sig_option_changed.connect(self.sig_option_changed.emit)
            self.addWidget(nsb)
            self.shellwidgets[shellwidget_id] = nsb
            self.set_shellwidget_from_id(shellwidget_id)
            return nsb

    def remove_shellwidget(self, shellwidget_id):
        # If shellwidget_id is not in self.shellwidgets, it simply means
        # that shell was not a Python-based console (it was a terminal)
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets.pop(shellwidget_id)
            self.removeWidget(nsb)
            nsb.close()

    def set_shellwidget_from_id(self, shellwidget_id):
        if shellwidget_id in self.shellwidgets:
            nsb = self.shellwidgets[shellwidget_id]
            self.setCurrentWidget(nsb)
            if self.isvisible:
                nsb.visibility_changed(True)

    def import_data(self, fname):
        """Import data in current namespace"""
        if self.count():
            nsb = self.currentWidget()
            nsb.refresh_table()
            nsb.import_data(fname)
            if self.dockwidget and not self.ismaximized:
                self.dockwidget.setVisible(True)
                self.dockwidget.raise_()

    #------ SpyderPluginMixin API ---------------------------------------------
    def visibility_changed(self, enable):
        """DockWidget visibility has changed"""
        SpyderPluginMixin.visibility_changed(self, enable)
        for nsb in list(self.shellwidgets.values()):
            nsb.visibility_changed(enable and nsb is self.currentWidget())

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

    def get_plugin_icon(self):
        """Return plugin icon"""
        return get_icon('dictedit.png')

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

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

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.extconsole.set_variableexplorer(self)
        self.main.add_dockwidget(self)

    def apply_plugin_settings(self, options):
        """Apply configuration file's plugin settings"""
        for nsb in list(self.shellwidgets.values()):
            nsb.setup(**VariableExplorer.get_settings())
        ar_timeout = self.get_option('autorefresh/timeout')
        for shellwidget in self.main.extconsole.shellwidgets:
            shellwidget.set_autorefresh_timeout(ar_timeout)
Example #10
0
class PythonShellWidget(TracebackLinksMixin, ShellBaseWidget,
                        InspectObjectMixin):
    """Python shell widget"""
    QT_CLASS = ShellBaseWidget
    INITHISTORY = ['# -*- coding: utf-8 -*-',
                   '# *** Spyder Python Console History Log ***',]
    SEPARATOR = '%s##---(%s)---' % (os.linesep*2, time.ctime())
    go_to_error = Signal(str)
    
    def __init__(self, parent, history_filename, profile=False):
        ShellBaseWidget.__init__(self, parent, history_filename, profile)
        TracebackLinksMixin.__init__(self)
        InspectObjectMixin.__init__(self)

        # Local shortcuts
        self.shortcuts = self.create_shortcuts()
    
    def create_shortcuts(self):
        new_shortcut(SHORTCUT_INLINE, self, lambda: self.enter_array_inline())
        new_shortcut(SHORTCUT_TABLE, self, lambda: self.enter_array_table())        
        inspectsc = create_shortcut(self.inspect_current_object,
                                    context='Console',
                                    name='Inspect current object',
                                    parent=self)
        return [inspectsc]
        
    def get_shortcut_data(self):
        """
        Returns shortcut data, a list of tuples (shortcut, text, default)
        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]

    #------ Context menu
    def setup_context_menu(self):
        """Reimplements ShellBaseWidget method"""
        ShellBaseWidget.setup_context_menu(self)
        self.copy_without_prompts_action = create_action(self,
                                     _("Copy without prompts"),
                                     icon=ima.icon('copywop'),
                                     triggered=self.copy_without_prompts)
        clear_line_action = create_action(self, _("Clear line"),
                                     QKeySequence(get_shortcut('console',
                                                               'Clear line')),
                                     icon=ima.icon('editdelete'),
                                     tip=_("Clear line"),
                                     triggered=self.clear_line)
        clear_action = create_action(self, _("Clear shell"),
                                     QKeySequence(get_shortcut('console',
                                                               'Clear shell')),
                                     icon=ima.icon('editclear'),
                                     tip=_("Clear shell contents "
                                           "('cls' command)"),
                                     triggered=self.clear_terminal)
        add_actions(self.menu, (self.copy_without_prompts_action,
                    clear_line_action, clear_action))
          
    def contextMenuEvent(self, event):
        """Reimplements ShellBaseWidget method"""
        state = self.has_selected_text()
        self.copy_without_prompts_action.setEnabled(state)
        ShellBaseWidget.contextMenuEvent(self, event)

    @Slot()
    def copy_without_prompts(self):
        """Copy text to clipboard without prompts"""
        text = self.get_selected_text()
        lines = text.split(os.linesep)
        for index, line in enumerate(lines):
            if line.startswith('>>> ') or line.startswith('... '):
                lines[index] = line[4:]
        text = os.linesep.join(lines)
        QApplication.clipboard().setText(text)
    
    
    #------ Key handlers
    def postprocess_keyevent(self, event):
        """Process keypress event"""
        ShellBaseWidget.postprocess_keyevent(self, event)
        if QToolTip.isVisible():
            _event, _text, key, _ctrl, _shift = restore_keyevent(event)
            self.hide_tooltip_if_necessary(key)
                
    def _key_other(self, text):
        """1 character key"""
        if self.is_completion_widget_visible():
            self.completion_text += text
                
    def _key_backspace(self, cursor_position):
        """Action for Backspace key"""
        if self.has_selected_text():
            self.check_selection()
            self.remove_selected_text()
        elif self.current_prompt_pos == cursor_position:
            # Avoid deleting prompt
            return
        elif self.is_cursor_on_last_line():
            self.stdkey_backspace()
            if self.is_completion_widget_visible():
                # Removing only last character because if there was a selection
                # the completion widget would have been canceled
                self.completion_text = self.completion_text[:-1]
                
    def _key_tab(self):
        """Action for TAB key"""
        if self.is_cursor_on_last_line():
            empty_line = not self.get_current_line_to_cursor().strip()
            if empty_line:
                self.stdkey_tab()
            else:
                self.show_code_completion(automatic=False)
                
    def _key_ctrl_space(self):
        """Action for Ctrl+Space"""
        if not self.is_completion_widget_visible():
            self.show_code_completion(automatic=False)
                
    def _key_pageup(self):
        """Action for PageUp key"""
        pass
    
    def _key_pagedown(self):
        """Action for PageDown key"""
        pass
                
    def _key_escape(self):
        """Action for ESCAPE key"""
        if self.is_completion_widget_visible():
            self.hide_completion_widget()

    def _key_question(self, text):
        """Action for '?'"""
        if self.get_current_line_to_cursor():
            last_obj = self.get_last_obj()
            if last_obj and not last_obj.isdigit():
                self.show_object_info(last_obj)
        self.insert_text(text)
        # In case calltip and completion are shown at the same time:
        if self.is_completion_widget_visible():
            self.completion_text += '?'
                
    def _key_parenleft(self, text):
        """Action for '('"""
        self.hide_completion_widget()
        if self.get_current_line_to_cursor():
            last_obj = self.get_last_obj()
            if last_obj and not last_obj.isdigit():
                self.insert_text(text)
                self.show_object_info(last_obj, call=True)
                return
        self.insert_text(text)
        
    def _key_period(self, text):
        """Action for '.'"""
        self.insert_text(text)
        if self.codecompletion_auto:
            # Enable auto-completion only if last token isn't a float
            last_obj = self.get_last_obj()
            if last_obj and not last_obj.isdigit():
                self.show_code_completion(automatic=True)


    #------ Paste
    def paste(self):
        """Reimplemented slot to handle multiline paste action"""
        text = to_text_string(QApplication.clipboard().text())
        if len(text.splitlines()) > 1:
            # Multiline paste
            if self.new_input_line:
                self.on_new_line()
            self.remove_selected_text() # Remove selection, eventually
            end = self.get_current_line_from_cursor()
            lines = self.get_current_line_to_cursor() + text + end
            self.clear_line()
            self.execute_lines(lines)
            self.move_cursor(-len(end))
        else:
            # Standard paste
            ShellBaseWidget.paste(self)
    
  
    #------ Code Completion / Calltips        
    # Methods implemented in child class:
    # (e.g. InternalShell)
    def get_dir(self, objtxt):
        """Return dir(object)"""
        raise NotImplementedError
    def get_module_completion(self, objtxt):
        """Return module completion list associated to object name"""
        pass
    def get_globals_keys(self):
        """Return shell globals() keys"""
        raise NotImplementedError
    def get_cdlistdir(self):
        """Return shell current directory list dir"""
        raise NotImplementedError
    def iscallable(self, objtxt):
        """Is object callable?"""
        raise NotImplementedError
    def get_arglist(self, objtxt):
        """Get func/method argument list"""
        raise NotImplementedError
    def get__doc__(self, objtxt):
        """Get object __doc__"""
        raise NotImplementedError
    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        raise NotImplementedError
    def get_source(self, objtxt):
        """Get object source"""
        raise NotImplementedError
    def is_defined(self, objtxt, force_import=False):
        """Return True if object is defined"""
        raise NotImplementedError
        
    def show_code_completion(self, automatic):
        """Display a completion list based on the current line"""
        # Note: unicode conversion is needed only for ExternalShellBase
        text = to_text_string(self.get_current_line_to_cursor())
        last_obj = self.get_last_obj()
        
        if not text:
            return

        if text.startswith('import '):
            obj_list = self.get_module_completion(text)
            words = text.split(' ')
            if ',' in words[-1]:
                words = words[-1].split(',')
            self.show_completion_list(obj_list, completion_text=words[-1],
                                      automatic=automatic)
            return
            
        elif text.startswith('from '):
            obj_list = self.get_module_completion(text)
            if obj_list is None:
                return
            words = text.split(' ')
            if '(' in words[-1]:
                words = words[:-2] + words[-1].split('(')
            if ',' in words[-1]:
                words = words[:-2] + words[-1].split(',')
            self.show_completion_list(obj_list, completion_text=words[-1],
                                      automatic=automatic)
            return
        
        obj_dir = self.get_dir(last_obj)
        if last_obj and obj_dir and text.endswith('.'):
            self.show_completion_list(obj_dir, automatic=automatic)
            return
        
        # Builtins and globals
        if not text.endswith('.') and last_obj \
           and re.match(r'[a-zA-Z_0-9]*$', last_obj):
            b_k_g = dir(builtins)+self.get_globals_keys()+keyword.kwlist
            for objname in b_k_g:
                if objname.startswith(last_obj) and objname != last_obj:
                    self.show_completion_list(b_k_g, completion_text=last_obj,
                                              automatic=automatic)
                    return
            else:
                return
        
        # Looking for an incomplete completion
        if last_obj is None:
            last_obj = text
        dot_pos = last_obj.rfind('.')
        if dot_pos != -1:
            if dot_pos == len(last_obj)-1:
                completion_text = ""
            else:
                completion_text = last_obj[dot_pos+1:]
                last_obj = last_obj[:dot_pos]
            completions = self.get_dir(last_obj)
            if completions is not None:
                self.show_completion_list(completions,
                                          completion_text=completion_text,
                                          automatic=automatic)
                return
        
        # Looking for ' or ": filename completion
        q_pos = max([text.rfind("'"), text.rfind('"')])
        if q_pos != -1:
            completions = self.get_cdlistdir()
            if completions:
                self.show_completion_list(completions,
                                          completion_text=text[q_pos+1:],
                                          automatic=automatic)
            return
            
    #------ Drag'n Drop
    def drop_pathlist(self, pathlist):
        """Drop path list"""
        if pathlist:
            files = ["r'%s'" % path for path in pathlist]
            if len(files) == 1:
                text = files[0]
            else:
                text = "[" + ", ".join(files) + "]"
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text)
            self.setFocus()
Example #11
0
class TerminalWidget(ShellBaseWidget):
    """
    Terminal widget
    """
    COM = 'rem' if os.name == 'nt' else '#'
    INITHISTORY = ['%s *** Spyder Terminal History Log ***' % COM, COM,]
    SEPARATOR = '%s%s ---(%s)---' % (os.linesep*2, COM, time.ctime())
    go_to_error = Signal(str)
    
    def __init__(self, parent, history_filename, profile=False):
        ShellBaseWidget.__init__(self, parent, history_filename, profile)
        
    #------ Key handlers
    def _key_other(self, text):
        """1 character key"""
        pass
                
    def _key_backspace(self, cursor_position):
        """Action for Backspace key"""
        if self.has_selected_text():
            self.check_selection()
            self.remove_selected_text()
        elif self.current_prompt_pos == cursor_position:
            # Avoid deleting prompt
            return
        elif self.is_cursor_on_last_line():
            self.stdkey_backspace()
                
    def _key_tab(self):
        """Action for TAB key"""
        if self.is_cursor_on_last_line():
            self.stdkey_tab()
                
    def _key_ctrl_space(self):
        """Action for Ctrl+Space"""
        pass
                
    def _key_escape(self):
        """Action for ESCAPE key"""
        self.clear_line()
                
    def _key_question(self, text):
        """Action for '?'"""
        self.insert_text(text)
                
    def _key_parenleft(self, text):
        """Action for '('"""
        self.insert_text(text)
        
    def _key_period(self, text):
        """Action for '.'"""
        self.insert_text(text)
            
            
    #------ Drag'n Drop
    def drop_pathlist(self, pathlist):
        """Drop path list"""
        if pathlist:
            files = ['"%s"' % path for path in pathlist]
            if len(files) == 1:
                text = files[0]
            else:
                text = " ".join(files)
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text)
            self.setFocus()
Example #12
0
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin):
    """
    Shell base widget
    """
    
    redirect_stdio = Signal(bool)
    sig_keyboard_interrupt = Signal()
    execute = Signal(str)
    append_to_history = Signal(str, str)
    
    def __init__(self, parent, history_filename, profile=False):
        """
        parent : specifies the parent widget
        """
        ConsoleBaseWidget.__init__(self, parent)
        SaveHistoryMixin.__init__(self)
                
        # Prompt position: tuple (line, index)
        self.current_prompt_pos = None
        self.new_input_line = True
        
        # History
        self.histidx = None
        self.hist_wholeline = False
        assert is_text_string(history_filename)
        self.history_filename = history_filename
        self.history = self.load_history()
        
        # Session
        self.historylog_filename = CONF.get('main', 'historylog_filename',
                                            get_conf_path('history.log'))
        
        # Context menu
        self.menu = None
        self.setup_context_menu()

        # Simple profiling test
        self.profile = profile
        
        # Buffer to increase performance of write/flush operations
        self.__buffer = []
        self.__timestamp = 0.0
        self.__flushtimer = QTimer(self)
        self.__flushtimer.setSingleShot(True)
        self.__flushtimer.timeout.connect(self.flush)

        # Give focus to widget
        self.setFocus()
        
        # Completion
        completion_size = CONF.get('shell_appearance', 'completion/size')
        completion_font = get_font('console')
        self.completion_widget.setup_appearance(completion_size,
                                                completion_font)
        # Cursor width
        self.setCursorWidth( CONF.get('shell_appearance', 'cursor/width') )
        
    def toggle_wrap_mode(self, enable):
        """Enable/disable wrap mode"""
        self.set_wrap_mode('character' if enable else None)

    def set_font(self, font):
        """Set shell styles font"""
        self.setFont(font)
        self.set_pythonshell_font(font)
        cursor = self.textCursor()
        cursor.select(QTextCursor.Document)
        charformat = QTextCharFormat()
        charformat.setFontFamily(font.family())
        charformat.setFontPointSize(font.pointSize())
        cursor.mergeCharFormat(charformat)


    #------ Context menu
    def setup_context_menu(self):
        """Setup shell context menu"""
        self.menu = QMenu(self)
        self.cut_action = create_action(self, _("Cut"),
                                        shortcut=keybinding('Cut'),
                                        icon=ima.icon('editcut'),
                                        triggered=self.cut)
        self.copy_action = create_action(self, _("Copy"),
                                         shortcut=keybinding('Copy'),
                                         icon=ima.icon('editcopy'),
                                         triggered=self.copy)
        paste_action = create_action(self, _("Paste"),
                                     shortcut=keybinding('Paste'),
                                     icon=ima.icon('editpaste'),
                                     triggered=self.paste)
        save_action = create_action(self, _("Save history log..."),
                                    icon=ima.icon('filesave'),
                                    tip=_("Save current history log (i.e. all "
                                          "inputs and outputs) in a text file"),
                                    triggered=self.save_historylog)
        self.delete_action = create_action(self, _("Delete"),
                                    shortcut=keybinding('Delete'),
                                    icon=ima.icon('editdelete'),
                                    triggered=self.delete)
        selectall_action = create_action(self, _("Select All"),
                                    shortcut=keybinding('SelectAll'),
                                    icon=ima.icon('selectall'),
                                    triggered=self.selectAll)
        add_actions(self.menu, (self.cut_action, self.copy_action,
                                paste_action, self.delete_action, None,
                                selectall_action, None, save_action) )
          
    def contextMenuEvent(self, event):
        """Reimplement Qt method"""
        state = self.has_selected_text()
        self.copy_action.setEnabled(state)
        self.cut_action.setEnabled(state)
        self.delete_action.setEnabled(state)
        self.menu.popup(event.globalPos())
        event.accept()        
        
        
    #------ Input buffer
    def get_current_line_from_cursor(self):
        return self.get_text('cursor', 'eof')
    
    def _select_input(self):
        """Select current line (without selecting console prompt)"""
        line, index = self.get_position('eof')
        if self.current_prompt_pos is None:
            pline, pindex = line, index
        else:
            pline, pindex = self.current_prompt_pos
        self.setSelection(pline, pindex, line, index)

    @Slot()
    def clear_line(self):
        """Clear current line (without clearing console prompt)"""
        if self.current_prompt_pos is not None:
            self.remove_text(self.current_prompt_pos, 'eof')

    @Slot()
    def clear_terminal(self):
        """
        Clear terminal window
        Child classes reimplement this method to write prompt
        """
        self.clear()

    # The buffer being edited
    def _set_input_buffer(self, text):
        """Set input buffer"""
        if self.current_prompt_pos is not None:
            self.replace_text(self.current_prompt_pos, 'eol', text)
        else:
            self.insert(text)
        self.set_cursor_position('eof')

    def _get_input_buffer(self):
        """Return input buffer"""
        input_buffer = ''
        if self.current_prompt_pos is not None:
            input_buffer = self.get_text(self.current_prompt_pos, 'eol')
            input_buffer = input_buffer.replace(os.linesep, '\n')
        return input_buffer

    input_buffer = Property("QString", _get_input_buffer, _set_input_buffer)
        
        
    #------ Prompt
    def new_prompt(self, prompt):
        """
        Print a new prompt and save its (line, index) position
        """
        if self.get_cursor_line_column()[1] != 0:
            self.write('\n')
        self.write(prompt, prompt=True)
        # now we update our cursor giving end of prompt
        self.current_prompt_pos = self.get_position('cursor')
        self.ensureCursorVisible()
        self.new_input_line = False
        
    def check_selection(self):
        """
        Check if selected text is r/w,
        otherwise remove read-only parts of selection
        """
        if self.current_prompt_pos is None:
            self.set_cursor_position('eof')
        else:
            self.truncate_selection(self.current_prompt_pos)
        
        
    #------ Copy / Keyboard interrupt
    @Slot()
    def copy(self):
        """Copy text to clipboard... or keyboard interrupt"""
        if self.has_selected_text():
            ConsoleBaseWidget.copy(self)
        elif not sys.platform == 'darwin':
            self.interrupt()

    def interrupt(self):
        """Keyboard interrupt"""
        self.sig_keyboard_interrupt.emit()

    @Slot()
    def cut(self):
        """Cut text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.cut(self)

    @Slot()
    def delete(self):
        """Remove selected text"""
        self.check_selection()
        if self.has_selected_text():
            ConsoleBaseWidget.remove_selected_text(self)

    @Slot()
    def save_historylog(self):
        """Save current history log (all text in console)"""
        title = _("Save history log")
        self.redirect_stdio.emit(False)
        filename, _selfilter = getsavefilename(self, title,
                    self.historylog_filename, "%s (*.log)" % _("History logs"))
        self.redirect_stdio.emit(True)
        if filename:
            filename = osp.normpath(filename)
            try:
                encoding.write(to_text_string(self.get_text_with_eol()),
                               filename)
                self.historylog_filename = filename
                CONF.set('main', 'historylog_filename', filename)
            except EnvironmentError as error:
                QMessageBox.critical(self, title,
                                     _("<b>Unable to save file '%s'</b>"
                                       "<br><br>Error message:<br>%s"
                                       ) % (osp.basename(filename),
                                            to_text_string(error)))
        
        
    #------ Basic keypress event handler
    def on_enter(self, command):
        """on_enter"""
        self.execute_command(command)
        
    def execute_command(self, command):
        self.execute.emit(command)
        self.add_to_history(command)
        self.new_input_line = True
        
    def on_new_line(self):
        """On new input line"""
        self.set_cursor_position('eof')
        self.current_prompt_pos = self.get_position('cursor')
        self.new_input_line = False

    @Slot()
    def paste(self):
        """Reimplemented slot to handle multiline paste action"""
        if self.new_input_line:
            self.on_new_line()
        ConsoleBaseWidget.paste(self)
        
    def keyPressEvent(self, event):
        """
        Reimplement Qt Method
        Basic keypress event handler
        (reimplemented in InternalShell to add more sophisticated features)
        """
        if self.preprocess_keyevent(event):
            # Event was accepted in self.preprocess_keyevent
            return
        self.postprocess_keyevent(event)
        
    def preprocess_keyevent(self, event):
        """Pre-process keypress event:
        return True if event is accepted, false otherwise"""
        # Copy must be done first to be able to copy read-only text parts
        # (otherwise, right below, we would remove selection
        #  if not on current line)
        ctrl = event.modifiers() & Qt.ControlModifier
        meta = event.modifiers() & Qt.MetaModifier    # meta=ctrl in OSX
        if event.key() == Qt.Key_C and \
          ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()):
            if meta and sys.platform == 'darwin':
                self.interrupt()
            elif ctrl:
                self.copy()
            event.accept()
            return True
        
        if self.new_input_line and ( len(event.text()) or event.key() in \
           (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ):
            self.on_new_line()
        
        return False
        
    def postprocess_keyevent(self, event):
        """Post-process keypress event:
        in InternalShell, this is method is called when shell is ready"""
        event, text, key, ctrl, shift = restore_keyevent(event)
        
        # Is cursor on the last line? and after prompt?
        if len(text):
            #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ?
            if self.has_selected_text():
                self.check_selection()
            self.restrict_cursor_position(self.current_prompt_pos, 'eof')
            
        cursor_position = self.get_position('cursor')

        if key in (Qt.Key_Return, Qt.Key_Enter):
            if self.is_cursor_on_last_line():
                self._key_enter()
            # add and run selection
            else:
                self.insert_text(self.get_selected_text(), at_end=True)
            
        elif key == Qt.Key_Insert and not shift and not ctrl:
            self.setOverwriteMode(not self.overwriteMode())
            
        elif key == Qt.Key_Delete:
            if self.has_selected_text():
                self.check_selection()
                self.remove_selected_text()
            elif self.is_cursor_on_last_line():
                self.stdkey_clear()
            
        elif key == Qt.Key_Backspace:
            self._key_backspace(cursor_position)
            
        elif key == Qt.Key_Tab:
            self._key_tab()
            
        elif key == Qt.Key_Space and ctrl:
            self._key_ctrl_space()

        elif key == Qt.Key_Left:
            if self.current_prompt_pos == cursor_position:
                # Avoid moving cursor on prompt
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='left')
                
        elif key == Qt.Key_Right:
            if self.is_cursor_at_end():
                return
            method = self.extend_selection_to_next if shift \
                     else self.move_cursor_to_next
            method('word' if ctrl else 'character', direction='right')

        elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl):
            self._key_home(shift, ctrl)

        elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl):
            self._key_end(shift, ctrl)

        elif key == Qt.Key_Up:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_prompt = self.get_coordinates(self.current_prompt_pos)[1]
            if y_cursor > y_prompt:
                self.stdkey_up(shift)
            else:
                self.browse_history(backward=True)
                
        elif key == Qt.Key_Down:
            if not self.is_cursor_on_last_line():
                self.set_cursor_position('eof')
            y_cursor = self.get_coordinates(cursor_position)[1]
            y_end = self.get_coordinates('eol')[1]
            if y_cursor < y_end:
                self.stdkey_down(shift)
            else:
                self.browse_history(backward=False)
            
        elif key in (Qt.Key_PageUp, Qt.Key_PageDown):
            #XXX: Find a way to do this programmatically instead of calling
            # widget keyhandler (this won't work if the *event* is coming from
            # the event queue - i.e. if the busy buffer is ever implemented)
            ConsoleBaseWidget.keyPressEvent(self, event)

        elif key == Qt.Key_Escape and shift:
            self.clear_line()

        elif key == Qt.Key_Escape:
            self._key_escape()
                
        elif key == Qt.Key_L and ctrl:
            self.clear_terminal()
            
        elif key == Qt.Key_V and ctrl:
            self.paste()
            
        elif key == Qt.Key_X and ctrl:
            self.cut()
            
        elif key == Qt.Key_Z and ctrl:
            self.undo()
            
        elif key == Qt.Key_Y and ctrl:
            self.redo()
            
        elif key == Qt.Key_A and ctrl:
            self.selectAll()
                
        elif key == Qt.Key_Question and not self.has_selected_text():
            self._key_question(text)
            
        elif key == Qt.Key_ParenLeft and not self.has_selected_text():
            self._key_parenleft(text)
            
        elif key == Qt.Key_Period and not self.has_selected_text():
            self._key_period(text)

        elif len(text) and not self.isReadOnly():
            self.hist_wholeline = False
            self.insert_text(text)
            self._key_other(text)
                
        else:
            # Let the parent widget handle the key press event
            ConsoleBaseWidget.keyPressEvent(self, event)
            
                
    #------ Key handlers
    def _key_enter(self):
        command = self.input_buffer
        self.insert_text('\n', at_end=True)
        self.on_enter(command)
        self.flush()
    def _key_other(self, text):
        raise NotImplementedError
    def _key_backspace(self, cursor_position):
        raise NotImplementedError
    def _key_tab(self):
        raise NotImplementedError
    def _key_ctrl_space(self):
        raise NotImplementedError
    def _key_home(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_home(shift, ctrl, self.current_prompt_pos)
    def _key_end(self, shift, ctrl):
        if self.is_cursor_on_last_line():
            self.stdkey_end(shift, ctrl)
    def _key_pageup(self):
        raise NotImplementedError
    def _key_pagedown(self):
        raise NotImplementedError
    def _key_escape(self):
        raise NotImplementedError
    def _key_question(self, text):
        raise NotImplementedError
    def _key_parenleft(self, text):
        raise NotImplementedError
    def _key_period(self, text):
        raise NotImplementedError

        
    #------ History Management
    def load_history(self):
        """Load history from a .py file in user home directory"""
        if osp.isfile(self.history_filename):
            rawhistory, _ = encoding.readlines(self.history_filename)
            rawhistory = [line.replace('\n', '') for line in rawhistory]
            if rawhistory[1] != self.INITHISTORY[1]:
                rawhistory[1] = self.INITHISTORY[1]
        else:
            rawhistory = self.INITHISTORY
        history = [line for line in rawhistory \
                   if line and not line.startswith('#')]

        # Truncating history to X entries:
        while len(history) >= CONF.get('historylog', 'max_entries'):
            del history[0]
            while rawhistory[0].startswith('#'):
                del rawhistory[0]
            del rawhistory[0]
        # Saving truncated history:
        encoding.writelines(rawhistory, self.history_filename)
        return history
        
    def browse_history(self, backward):
        """Browse history"""
        if self.is_cursor_before('eol') and self.hist_wholeline:
            self.hist_wholeline = False
        tocursor = self.get_current_line_to_cursor()
        text, self.histidx = self.__find_in_history(tocursor,
                                                    self.histidx, backward)
        if text is not None:
            if self.hist_wholeline:
                self.clear_line()
                self.insert_text(text)
            else:
                cursor_position = self.get_position('cursor')
                # Removing text from cursor to the end of the line
                self.remove_text('cursor', 'eol')
                # Inserting history text
                self.insert_text(text)
                self.set_cursor_position(cursor_position)

    def __find_in_history(self, tocursor, start_idx, backward):
        """Find text 'tocursor' in history, from index 'start_idx'"""
        if start_idx is None:
            start_idx = len(self.history)
        # Finding text in history
        step = -1 if backward else 1
        idx = start_idx
        if len(tocursor) == 0 or self.hist_wholeline:
            idx += step
            if idx >= len(self.history) or len(self.history) == 0:
                return "", len(self.history)
            elif idx < 0:
                idx = 0
            self.hist_wholeline = True
            return self.history[idx], idx
        else:
            for index in range(len(self.history)):
                idx = (start_idx+step*(index+1)) % len(self.history)
                entry = self.history[idx]
                if entry.startswith(tocursor):
                    return entry[len(tocursor):], idx
            else:
                return None, start_idx
    
    
    #------ Simulation standards input/output
    def write_error(self, text):
        """Simulate stderr"""
        self.flush()
        self.write(text, flush=True, error=True)
        if DEBUG:
            STDERR.write(text)

    def write(self, text, flush=False, error=False, prompt=False):
        """Simulate stdout and stderr"""
        if prompt:
            self.flush()
        if not is_string(text):
            # This test is useful to discriminate QStrings from decoded str
            text = to_text_string(text)
        self.__buffer.append(text)
        ts = time.time()
        if flush or prompt:
            self.flush(error=error, prompt=prompt)
        elif ts - self.__timestamp > 0.05:
            self.flush(error=error)
            self.__timestamp = ts
            # Timer to flush strings cached by last write() operation in series
            self.__flushtimer.start(50)

    def flush(self, error=False, prompt=False):
        """Flush buffer, write text to console"""
        # Fix for Issue 2452 
        if PY3:
            try:
                text = "".join(self.__buffer)
            except TypeError:
                text = b"".join(self.__buffer)
                try:
                    text = text.decode( locale.getdefaultlocale()[1] )
                except:
                    pass
        else:
            text = "".join(self.__buffer)

        self.__buffer = []
        self.insert_text(text, at_end=True, error=error, prompt=prompt)
        QCoreApplication.processEvents()
        self.repaint()
        # Clear input buffer:
        self.new_input_line = True


    #------ Text Insertion
    def insert_text(self, text, at_end=False, error=False, prompt=False):
        """
        Insert text at the current cursor position
        or at the end of the command line
        """
        if at_end:
            # Insert text at the end of the command line
            self.append_text_to_shell(text, error, prompt)
        else:
            # Insert text at current cursor position
            ConsoleBaseWidget.insert_text(self, text)

            
    #------ Re-implemented Qt Methods
    def focusNextPrevChild(self, next):
        """
        Reimplemented to stop Tab moving to the next window
        """
        if next:
            return False
        return ConsoleBaseWidget.focusNextPrevChild(self, next)

    
    #------ Drag and drop
    def dragEnterEvent(self, event):
        """Drag and Drop - Enter event"""
        event.setAccepted(event.mimeData().hasFormat("text/plain"))

    def dragMoveEvent(self, event):
        """Drag and Drop - Move event"""
        if (event.mimeData().hasFormat("text/plain")):
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        """Drag and Drop - Drop event"""
        if (event.mimeData().hasFormat("text/plain")):
            text = to_text_string(event.mimeData().text())
            if self.new_input_line:
                self.on_new_line()
            self.insert_text(text, at_end=True)
            self.setFocus()
            event.setDropAction(Qt.MoveAction)
            event.accept()
        else:
            event.ignore()
            
    def drop_pathlist(self, pathlist):
        """Drop path list"""
        raise NotImplementedError
Example #13
0
class ConfigDialog(QDialog):
    """Spyder configuration ('Preferences') dialog box"""
    
    # Signals
    check_settings = Signal()
    size_change = Signal(QSize)
    
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.contents_widget = QListWidget()
        self.contents_widget.setMovement(QListView.Static)
        self.contents_widget.setSpacing(1)

        bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Apply
                                |QDialogButtonBox.Cancel)
        self.apply_btn = bbox.button(QDialogButtonBox.Apply)
        bbox.accepted.connect(self.accept)
        bbox.rejected.connect(self.reject)
        bbox.clicked.connect(self.button_clicked)
        
        self.pages_widget = QStackedWidget()
        self.pages_widget.currentChanged.connect(self.current_page_changed)

        self.contents_widget.currentRowChanged.connect(
                                             self.pages_widget.setCurrentIndex)
        self.contents_widget.setCurrentRow(0)

        hsplitter = QSplitter()
        hsplitter.addWidget(self.contents_widget)
        hsplitter.addWidget(self.pages_widget)

        btnlayout = QHBoxLayout()
        btnlayout.addStretch(1)
        btnlayout.addWidget(bbox)

        vlayout = QVBoxLayout()
        vlayout.addWidget(hsplitter)
        vlayout.addLayout(btnlayout)

        self.setLayout(vlayout)

        self.setWindowTitle(_("Preferences"))
        self.setWindowIcon(get_icon("configure.png"))
        
    def get_current_index(self):
        """Return current page index"""
        return self.contents_widget.currentRow()
        
    def set_current_index(self, index):
        """Set current page index"""
        self.contents_widget.setCurrentRow(index)
        
    def get_page(self, index=None):
        """Return page widget"""
        if index is None:
            widget = self.pages_widget.currentWidget()
        else:
            widget = self.pages_widget.widget(index)
        return widget.widget()
    
    @Slot()
    def accept(self):
        """Reimplement Qt method"""
        for index in range(self.pages_widget.count()):
            configpage = self.get_page(index)
            if not configpage.is_valid():
                return
            configpage.apply_changes()
        QDialog.accept(self)
        
    def button_clicked(self, button):
        if button is self.apply_btn:
            # Apply button was clicked
            configpage = self.get_page()
            if not configpage.is_valid():
                return
            configpage.apply_changes()
            
    def current_page_changed(self, index):
        widget = self.get_page(index)
        self.apply_btn.setVisible(widget.apply_callback is not None)
        self.apply_btn.setEnabled(widget.is_modified)
        
    def add_page(self, widget):
        self.check_settings.connect(widget.check_settings)
        widget.show_this_page.connect(lambda row=self.contents_widget.count():
                                      self.contents_widget.setCurrentRow(row))
        widget.apply_button_enabled.connect(self.apply_btn.setEnabled)
        scrollarea = QScrollArea(self)
        scrollarea.setWidgetResizable(True)
        scrollarea.setWidget(widget)
        self.pages_widget.addWidget(scrollarea)
        item = QListWidgetItem(self.contents_widget)
        item.setIcon(widget.get_icon())
        item.setText(widget.get_name())
        item.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
        item.setSizeHint(QSize(0, 25))
        
    def check_all_settings(self):
        """This method is called to check all configuration page settings
        after configuration dialog has been shown"""
        self.check_settings.emit()
    
    def resizeEvent(self, event):
        """
        Reimplement Qt method to be able to save the widget's size from the
        main application
        """
        QDialog.resizeEvent(self, event)
        self.size_change.emit(self.size())
Example #14
0
class ExternalShellBase(QWidget):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = None
    redirect_stdio = Signal(bool)
    sig_finished = Signal()

    def __init__(self,
                 parent=None,
                 fname=None,
                 wdir=None,
                 history_filename=None,
                 show_icontext=True,
                 light_background=True,
                 menu_actions=None,
                 show_buttons_inside=True,
                 show_elapsed_time=True):
        QWidget.__init__(self, parent)

        self.menu_actions = menu_actions

        self.run_button = None
        self.kill_button = None
        self.options_button = None
        self.icontext_action = None

        self.show_elapsed_time = show_elapsed_time

        self.fname = fname
        if wdir is None:
            wdir = osp.dirname(osp.abspath(fname))
        self.wdir = wdir if osp.isdir(wdir) else None
        self.arguments = ""

        self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename))
        self.shell.set_light_background(light_background)
        self.shell.execute.connect(self.send_to_process)
        self.shell.sig_keyboard_interrupt.connect(self.keyboard_interrupt)
        # Redirecting some SIGNALs:
        self.shell.redirect_stdio.connect(
            lambda state: self.redirect_stdio.emit(state))

        self.state_label = None
        self.time_label = None

        vlayout = QVBoxLayout()
        toolbar_buttons = self.get_toolbar_buttons()
        if show_buttons_inside:
            self.state_label = QLabel()
            hlayout = QHBoxLayout()
            hlayout.addWidget(self.state_label)
            hlayout.addStretch(0)
            hlayout.addWidget(self.create_time_label())
            hlayout.addStretch(0)
            for button in toolbar_buttons:
                hlayout.addWidget(button)
            vlayout.addLayout(hlayout)
        else:
            vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addWidget(self.get_shell_widget())
        self.setLayout(vlayout)
        self.resize(640, 480)
        if parent is None:
            self.setWindowIcon(self.get_icon())
            self.setWindowTitle(_("Console"))

        self.t0 = None
        self.timer = QTimer(self)

        self.process = None

        self.is_closing = False

        if show_buttons_inside:
            self.update_time_label_visibility()

    @Slot(bool)
    def set_elapsed_time_visible(self, state):
        self.show_elapsed_time = state
        if self.time_label is not None:
            self.time_label.setVisible(state)

    def create_time_label(self):
        """Create elapsed time label widget (if necessary) and return it"""
        if self.time_label is None:
            self.time_label = QLabel()
        return self.time_label

    def update_time_label_visibility(self):
        self.time_label.setVisible(self.show_elapsed_time)

    def is_running(self):
        if self.process is not None:
            return self.process.state() == QProcess.Running

    def get_toolbar_buttons(self):
        if self.run_button is None:
            self.run_button = create_toolbutton(
                self,
                text=_("Run"),
                icon=get_icon('run.png'),
                tip=_("Run again this program"),
                triggered=self.start_shell)
        if self.kill_button is None:
            self.kill_button = create_toolbutton(
                self,
                text=_("Kill"),
                icon=get_icon('kill.png'),
                tip=_("Kills the current process, "
                      "causing it to exit immediately"))
        buttons = [self.run_button]
        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(
                    self, text=_("Options"), icon=get_icon('tooloptions.png'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)
        buttons.append(self.kill_button)
        return buttons

    def set_icontext_visible(self, state):
        """Set icon text visibility"""
        for widget in self.get_toolbar_buttons():
            if state:
                widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
            else:
                widget.setToolButtonStyle(Qt.ToolButtonIconOnly)

    def get_options_menu(self):
        self.show_time_action = create_action(
            self,
            _("Show elapsed time"),
            toggled=self.set_elapsed_time_visible)
        self.show_time_action.setChecked(self.show_elapsed_time)
        actions = [self.show_time_action]
        if self.menu_actions is not None:
            actions += [None] + self.menu_actions
        return actions

    def get_shell_widget(self):
        return self.shell

    def get_icon(self):
        raise NotImplementedError

    def show_time(self, end=False):
        if self.time_label is None:
            return
        elapsed_time = time() - self.t0
        if elapsed_time > 24 * 3600:  # More than a day...!
            format = "%d %H:%M:%S"
        else:
            format = "%H:%M:%S"
        if end:
            color = "#AAAAAA"
        else:
            color = "#AA6655"
        text = "<span style=\'color: %s\'><b>%s" \
               "</b></span>" % (color, strftime(format, gmtime(elapsed_time)))
        self.time_label.setText(text)

    def closeEvent(self, event):
        if self.process is not None:
            self.is_closing = True
            self.process.kill()
            self.process.waitForFinished(100)

        try:
            self.timer.timeout.disconnect(self.show_time)
        except RuntimeError:
            pass

    def set_running_state(self, state=True):
        self.set_buttons_runnning_state(state)
        self.shell.setReadOnly(not state)
        if state:
            if self.state_label is not None:
                self.state_label.setText(
                    _("<span style=\'color: #44AA44\'><b>Running...</b></span>"
                      ))
            self.t0 = time()
            self.timer.timeout.connect(self.show_time)
            self.timer.start(1000)
        else:
            if self.state_label is not None:
                self.state_label.setText(_('Terminated.'))
            try:
                self.timer.timeout.disconnect(self.show_time)
            except:
                pass

    def set_buttons_runnning_state(self, state):
        self.run_button.setVisible(not state and not self.is_ipykernel)
        self.kill_button.setVisible(state)

    @Slot(bool)
    def start_shell(self, ask_for_arguments=False):
        """Start shell"""
        if ask_for_arguments and not self.get_arguments():
            self.set_running_state(False)
            return
        try:
            self.terminate_button.clicked.disconnect(self.process.terminate)
            self.kill_button.clicked.disconnect(self.process.terminate)
        except:
            pass
        self.create_process()

    @Slot()
    def get_arguments(self):
        arguments, valid = QInputDialog.getText(self, _('Arguments'),
                                                _('Command line arguments:'),
                                                QLineEdit.Normal,
                                                self.arguments)
        if valid:
            self.arguments = to_text_string(arguments)
        return valid

    def create_process(self):
        raise NotImplementedError

    def finished(self, exit_code, exit_status):
        self.shell.flush()
        self.sig_finished.emit()
        if self.is_closing:
            return
        self.set_running_state(False)
        self.show_time(end=True)


#===============================================================================
#    Input/Output
#===============================================================================

    def transcode(self, qba):
        try:
            return to_text_string(qba.data(), 'utf8')
        except UnicodeDecodeError:
            return qba.data()

    def get_stdout(self):
        self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            qba += self.process.readAllStandardOutput()
        return self.transcode(qba)

    def get_stderr(self):
        self.process.setReadChannel(QProcess.StandardError)
        qba = QByteArray()
        while self.process.bytesAvailable():
            qba += self.process.readAllStandardError()
        return self.transcode(qba)

    def write_output(self):
        self.shell.write(self.get_stdout(), flush=True)
        QApplication.processEvents()

    def send_to_process(self, qstr):
        raise NotImplementedError

    def send_ctrl_to_process(self, letter):
        char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1)
        byte_array = QByteArray()
        byte_array.append(char)
        self.process.write(byte_array)
        self.process.waitForBytesWritten(-1)
        self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True)

    def keyboard_interrupt(self):
        raise NotImplementedError
Example #15
0
class OnlineHelp(PydocBrowser, SpyderPluginMixin):
    """
    Online Help Plugin
    """
    sig_option_changed = Signal(str, object)
    CONF_SECTION = 'onlinehelp'
    LOG_PATH = get_conf_path(CONF_SECTION)

    def __init__(self, parent):
        self.main = parent
        PydocBrowser.__init__(self, parent)
        SpyderPluginMixin.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        self.register_widget_shortcuts("Editor", self.find_widget)

        self.webview.set_zoom_factor(self.get_option('zoom_factor'))
        self.url_combo.setMaxCount(self.get_option('max_history_entries'))
        self.url_combo.addItems(self.load_history())

    #------ Public API ---------------------------------------------------------
    def load_history(self, obj=None):
        """Load history from a text file in user home directory"""
        if osp.isfile(self.LOG_PATH):
            history = [
                line.replace('\n', '')
                for line in open(self.LOG_PATH, 'r').readlines()
            ]
        else:
            history = []
        return history

    def save_history(self):
        """Save history to a text file in user home directory"""
        open(self.LOG_PATH, 'w').write("\n".join( \
                [to_text_string(self.url_combo.itemText(index))
                 for index in range(self.url_combo.count())] ))

    #------ SpyderPluginMixin API ---------------------------------------------
    def visibility_changed(self, enable):
        """DockWidget visibility has changed"""
        SpyderPluginMixin.visibility_changed(self, enable)
        if enable and not self.is_server_running():
            self.initialize()

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

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

    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.save_history()
        self.set_option('zoom_factor', self.webview.get_zoom_factor())
        return True

    def refresh_plugin(self):
        """Refresh widget"""
        pass

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []

    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.main.add_dockwidget(self)
Example #16
0
class ExplorerWidget(QWidget):
    """Explorer widget"""
    sig_option_changed = Signal(str, object)
    sig_open_file = Signal(str)
    sig_new_file = Signal(str)
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent=None, name_filters=['*.py', '*.pyw'],
                 show_all=False, show_cd_only=None, show_icontext=True):
        QWidget.__init__(self, parent)
        
        self.treewidget = ExplorerTreeWidget(self, show_cd_only=show_cd_only)
        self.treewidget.setup(name_filters=name_filters, show_all=show_all)
        self.treewidget.chdir(getcwd())
        
        icontext_action = create_action(self, _("Show icons and text"),
                                        toggled=self.toggle_icontext)
        self.treewidget.common_actions += [None, icontext_action]
        
        # Setup toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setIconSize(QSize(16, 16))
        
        self.previous_action = create_action(self, text=_("Previous"),
                            icon=get_std_icon("ArrowBack"),
                            triggered=self.treewidget.go_to_previous_directory)
        self.toolbar.addAction(self.previous_action)
        self.previous_action.setEnabled(False)
        self.treewidget.set_previous_enabled.connect(
                                               self.previous_action.setEnabled)
        
        self.next_action = create_action(self, text=_("Next"),
                            icon=get_std_icon("ArrowForward"),
                            triggered=self.treewidget.go_to_next_directory)
        self.toolbar.addAction(self.next_action)
        self.next_action.setEnabled(False)
        self.treewidget.set_next_enabled.connect(self.next_action.setEnabled)
        
        parent_action = create_action(self, text=_("Parent"),
                            icon=get_std_icon("ArrowUp"),
                            triggered=self.treewidget.go_to_parent_directory)
        self.toolbar.addAction(parent_action)
        self.toolbar.addSeparator()

        options_action = create_action(self, text='', tip=_("Options"),
                                       icon=get_icon('tooloptions.png'))
        self.toolbar.addAction(options_action)
        widget = self.toolbar.widgetForAction(options_action)
        widget.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.treewidget.common_actions)
        options_action.setMenu(menu)
            
        icontext_action.setChecked(show_icontext)
        self.toggle_icontext(show_icontext)     
        
        vlayout = QVBoxLayout()
        vlayout.addWidget(self.toolbar)
        vlayout.addWidget(self.treewidget)
        self.setLayout(vlayout)

    @Slot(bool)
    def toggle_icontext(self, state):
        """Toggle icon text"""
        self.sig_option_changed.emit('show_icontext', state)
        for action in self.toolbar.actions():
            if not action.isSeparator():
                widget = self.toolbar.widgetForAction(action)
                if state:
                    widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
                else:
                    widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
Example #17
0
class BreakpointTableView(QTableView):
    edit_goto = Signal(str, int, str)
    clear_breakpoint = Signal(str, int)
    clear_all_breakpoints = Signal()
    set_or_edit_conditional_breakpoint = Signal()

    def __init__(self, parent, data):
        QTableView.__init__(self, parent)
        self.model = BreakpointTableModel(self, data)
        self.setModel(self.model)
        self.delegate = BreakpointDelegate(self)
        self.setItemDelegate(self.delegate)

        self.setup_table()

    def setup_table(self):
        """Setup table"""
        self.horizontalHeader().setStretchLastSection(True)
        self.adjust_columns()
        self.columnAt(0)
        # Sorting columns
        self.setSortingEnabled(False)
        self.sortByColumn(0, Qt.DescendingOrder)

    def adjust_columns(self):
        """Resize three first columns to contents"""
        for col in range(3):
            self.resizeColumnToContents(col)

    def mouseDoubleClickEvent(self, event):
        """Reimplement Qt method"""
        index_clicked = self.indexAt(event.pos())
        if self.model.breakpoints:
            filename = self.model.breakpoints[index_clicked.row()][0]
            line_number_str = self.model.breakpoints[index_clicked.row()][1]
            self.edit_goto.emit(filename, int(line_number_str), '')
        if index_clicked.column() == 2:
            self.set_or_edit_conditional_breakpoint.emit()

    def contextMenuEvent(self, event):
        index_clicked = self.indexAt(event.pos())
        actions = []
        self.popup_menu = QMenu(self)
        clear_all_breakpoints_action = create_action(
            self,
            _("Clear breakpoints in all files"),
            triggered=lambda: self.clear_all_breakpoints.emit())
        actions.append(clear_all_breakpoints_action)
        if self.model.breakpoints:
            filename = self.model.breakpoints[index_clicked.row()][0]
            lineno = int(self.model.breakpoints[index_clicked.row()][1])
            clear_breakpoint_action = create_action(self,
                    _("Clear this breakpoint"),
                    triggered=lambda filename=filename, lineno=lineno: \
                    self.clear_breakpoint.emit(filename, lineno))
            actions.insert(0, clear_breakpoint_action)

            edit_breakpoint_action = create_action(self,
                    _("Edit this breakpoint"),
                    triggered=lambda filename=filename, lineno=lineno: \
                    (self.edit_goto.emit(filename, lineno, ''),
                     self.set_or_edit_conditional_breakpoint.emit())
                    )
            actions.append(edit_breakpoint_action)
        add_actions(self.popup_menu, actions)
        self.popup_menu.popup(event.globalPos())
        event.accept()
Example #18
0
class ExplorerTreeWidget(DirView):
    """File/directory explorer tree widget
    show_cd_only: Show current directory only
    (True/False: enable/disable the option
     None: enable the option and do not allow the user to disable it)"""
    set_previous_enabled = Signal(bool)
    set_next_enabled = Signal(bool)
    
    def __init__(self, parent=None, show_cd_only=None):
        DirView.__init__(self, parent)
                
        self.history = []
        self.histindex = None

        self.show_cd_only = show_cd_only
        self.__original_root_index = None
        self.__last_folder = None

        self.menu = None
        self.common_actions = None
        
        # Enable drag events
        self.setDragEnabled(True)
        
    #---- Context menu
    def setup_common_actions(self):
        """Setup context menu common actions"""
        actions = super(ExplorerTreeWidget, self).setup_common_actions()
        if self.show_cd_only is None:
            # Enabling the 'show current directory only' option but do not
            # allow the user to disable it
            self.show_cd_only = True
        else:
            # Show current directory only
            cd_only_action = create_action(self,
                                           _("Show current directory only"),
                                           toggled=self.toggle_show_cd_only)
            cd_only_action.setChecked(self.show_cd_only)
            self.toggle_show_cd_only(self.show_cd_only)
            actions.append(cd_only_action)
        return actions

    @Slot(bool)
    def toggle_show_cd_only(self, checked):
        """Toggle show current directory only mode"""
        self.parent_widget.sig_option_changed.emit('show_cd_only', checked)
        self.show_cd_only = checked
        if checked:
            if self.__last_folder is not None:
                self.set_current_folder(self.__last_folder)
        elif self.__original_root_index is not None:
            self.setRootIndex(self.__original_root_index)
        
    #---- Refreshing widget
    def set_current_folder(self, folder):
        """Set current folder and return associated model index"""
        index = self.fsmodel.setRootPath(folder)
        self.__last_folder = folder
        if self.show_cd_only:
            if self.__original_root_index is None:
                self.__original_root_index = self.rootIndex()
            self.setRootIndex(index)
        return index
        
    def refresh(self, new_path=None, force_current=False):
        """Refresh widget
        force=False: won't refresh widget if path has not changed"""
        if new_path is None:
            new_path = getcwd()
        if force_current:
            index = self.set_current_folder(new_path)
            self.expand(index)
            self.setCurrentIndex(index)
        self.set_previous_enabled.emit(
                             self.histindex is not None and self.histindex > 0)
        self.set_next_enabled.emit(self.histindex is not None and \
                                   self.histindex < len(self.history)-1)
            
    #---- Events
    def directory_clicked(self, dirname):
        """Directory was just clicked"""
        self.chdir(directory=dirname)
        
    #---- Files/Directories Actions
    @Slot()
    def go_to_parent_directory(self):
        """Go to parent directory"""
        self.chdir( osp.abspath(osp.join(getcwd(), os.pardir)) )

    @Slot()
    def go_to_previous_directory(self):
        """Back to previous directory"""
        self.histindex -= 1
        self.chdir(browsing_history=True)

    @Slot()
    def go_to_next_directory(self):
        """Return to next directory"""
        self.histindex += 1
        self.chdir(browsing_history=True)
        
    def update_history(self, directory):
        """Update browse history"""
        directory = osp.abspath(to_text_string(directory))
        if directory in self.history:
            self.histindex = self.history.index(directory)
        
    def chdir(self, directory=None, browsing_history=False):
        """Set directory as working directory"""
        if directory is not None:
            directory = osp.abspath(to_text_string(directory))
        if browsing_history:
            directory = self.history[self.histindex]
        elif directory in self.history:
            self.histindex = self.history.index(directory)
        else:
            if self.histindex is None:
                self.history = []
            else:
                self.history = self.history[:self.histindex+1]
            if len(self.history) == 0 or \
               (self.history and self.history[-1] != directory):
                self.history.append(directory)
            self.histindex = len(self.history)-1
        directory = to_text_string(directory)
        if PY2:
            PermissionError = OSError
        try:
            os.chdir(directory)
            self.parent_widget.open_dir.emit(directory)
            self.refresh(new_path=directory, force_current=True)
        except PermissionError:
            QMessageBox.critical(self.parent_widget, "Error",
                                 _("You don't have the right permissions to "
                                   "open this directory"))
Example #19
0
class Tabs(BaseTabs):
    """BaseTabs widget with movable tabs and tab navigation shortcuts"""
    # Signals
    move_data = Signal(int, int)
    move_tab_finished = Signal()
    sig_move_tab = Signal(str, str, int, int)

    def __init__(self,
                 parent,
                 actions=None,
                 menu=None,
                 corner_widgets=None,
                 menu_use_tooltips=False):
        BaseTabs.__init__(self, parent, actions, menu, corner_widgets,
                          menu_use_tooltips)
        tab_bar = TabBar(self, parent)
        tab_bar.sig_move_tab.connect(self.move_tab)
        tab_bar.sig_move_tab[(str, int, int)].connect(
            self.move_tab_from_another_tabwidget)
        self.setTabBar(tab_bar)

        new_shortcut("Ctrl+Tab", parent, lambda: self.tab_navigate(1))
        new_shortcut("Shift+Ctrl+Tab", parent, lambda: self.tab_navigate(-1))
        new_shortcut("Ctrl+W", parent,
                     lambda: self.sig_close_tab.emit(self.currentIndex()))
        new_shortcut("Ctrl+F4", parent,
                     lambda: self.sig_close_tab.emit(self.currentIndex()))

    def tab_navigate(self, delta=1):
        """Ctrl+Tab"""
        if delta > 0 and self.currentIndex() == self.count() - 1:
            index = delta - 1
        elif delta < 0 and self.currentIndex() == 0:
            index = self.count() + delta
        else:
            index = self.currentIndex() + delta
        self.setCurrentIndex(index)

    def move_tab(self, index_from, index_to):
        """Move tab inside a tabwidget"""
        self.move_data.emit(index_from, index_to)

        tip, text = self.tabToolTip(index_from), self.tabText(index_from)
        icon, widget = self.tabIcon(index_from), self.widget(index_from)
        current_widget = self.currentWidget()

        self.removeTab(index_from)
        self.insertTab(index_to, widget, icon, text)
        self.setTabToolTip(index_to, tip)

        self.setCurrentWidget(current_widget)
        self.move_tab_finished.emit()

    def move_tab_from_another_tabwidget(self, tabwidget_from, index_from,
                                        index_to):
        """Move tab from a tabwidget to another"""

        # We pass self object IDs as QString objs, because otherwise it would
        # depend on the platform: long for 64bit, int for 32bit. Replacing
        # by long all the time is not working on some 32bit platforms
        # (see Issue 1094, Issue 1098)
        self.sig_move_tab.emit(tabwidget_from, str(id(self)), index_from,
                               index_to)
Example #20
0
class ExplorerWidget(QWidget):
    """Explorer widget"""
    sig_option_changed = Signal(str, object)
    sig_open_file = Signal(str)
    sig_new_file = Signal(str)
    redirect_stdio = Signal(bool)
    open_dir = Signal(str)

    def __init__(self, parent=None, name_filters=['*.py', '*.pyw'],
                 show_all=False, show_cd_only=None, show_icontext=True):
        QWidget.__init__(self, parent)

        # Widgets
        self.treewidget = ExplorerTreeWidget(self, show_cd_only=show_cd_only)
        button_previous = QToolButton(self)
        button_next = QToolButton(self)
        button_parent = QToolButton(self)
        self.button_menu = QToolButton(self)
        menu = QMenu(self)

        self.action_widgets = [button_previous, button_next, button_parent,
                               self.button_menu]

        # Actions
        icontext_action = create_action(self, _("Show icons and text"),
                                        toggled=self.toggle_icontext)
        previous_action = create_action(self, text=_("Previous"),
                            icon=ima.icon('ArrowBack'),
                            triggered=self.treewidget.go_to_previous_directory)
        next_action = create_action(self, text=_("Next"),
                            icon=ima.icon('ArrowForward'),
                            triggered=self.treewidget.go_to_next_directory)
        parent_action = create_action(self, text=_("Parent"),
                            icon=ima.icon('ArrowUp'),
                            triggered=self.treewidget.go_to_parent_directory)
        options_action = create_action(self, text='', tip=_('Options'))

        # Setup widgets
        self.treewidget.setup(name_filters=name_filters, show_all=show_all)
        self.treewidget.chdir(getcwd())
        self.treewidget.common_actions += [None, icontext_action]

        button_previous.setDefaultAction(previous_action)
        previous_action.setEnabled(False)

        button_next.setDefaultAction(next_action)
        next_action.setEnabled(False)

        button_parent.setDefaultAction(parent_action)

        self.button_menu.setIcon(ima.icon('tooloptions'))
        self.button_menu.setPopupMode(QToolButton.InstantPopup)
        self.button_menu.setMenu(menu)
        add_actions(menu, self.treewidget.common_actions)
        options_action.setMenu(menu)

        self.toggle_icontext(show_icontext)
        icontext_action.setChecked(show_icontext)

        for widget in self.action_widgets:
            widget.setAutoRaise(True)
            widget.setIconSize(QSize(16, 16))

        # Layouts
        blayout = QHBoxLayout()
        blayout.addWidget(button_previous)
        blayout.addWidget(button_next)
        blayout.addWidget(button_parent)
        blayout.addStretch()
        blayout.addWidget(self.button_menu)

        layout = QVBoxLayout()
        layout.addLayout(blayout)
        layout.addWidget(self.treewidget)
        self.setLayout(layout)

        # Signals and slots
        self.treewidget.set_previous_enabled.connect(
                                               previous_action.setEnabled)
        self.treewidget.set_next_enabled.connect(next_action.setEnabled)

    @Slot(bool)
    def toggle_icontext(self, state):
        """Toggle icon text"""
        self.sig_option_changed.emit('show_icontext', state)
        for widget in self.action_widgets:
            if widget is not self.button_menu:
                if state:
                    widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
                else:
                    widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
Example #21
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):
        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("Editor", 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)

    #------ 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_inspector(self, inspector):
        """Bind inspector instance to this console"""
        self.shell.inspector = inspector

    #------ 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 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)
        font_action = create_action(self,
                                    _("&Font..."),
                                    None,
                                    ima.icon('font'),
                                    _("Set shell font style"),
                                    triggered=self.change_font)
        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, font_action, wrap_action, calltips_action,
             codecompletion_action, codecompenter_action, exteditor_action))

        plugin_actions = [
            None, run_action, environ_action, syspath_action, option_menu,
            None, quit_action
        ]

        # Add actions to context menu
        add_actions(self.shell.menu, plugin_actions)

        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.traceback_available.connect(self.traceback_available)

    def traceback_available(self):
        """Traceback is available in the internal console: showing the 
        internal console automatically to warn the user"""
        if CONF.get('main', 'show_internal_console_if_traceback', False):
            self.dockwidget.show()
            self.dockwidget.raise_()

    #------ 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 = DictEditor()
        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(),
                _("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_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(), self,
                                          _("Select a new font"))
        if valid:
            self.shell.set_font(font)
            self.set_plugin_font(font)

    @Slot()
    def change_max_line_count(self):
        "Change maximum line count" ""
        mlc, valid = QInputDialog.getInteger(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()
Example #22
0
class ProfilerWidget(QWidget):
    """
    Profiler widget
    """
    DATAPATH = get_conf_path('profiler.results')
    VERSION = '0.0.1'
    redirect_stdio = Signal(bool)

    def __init__(self, parent, max_entries=100):
        QWidget.__init__(self, parent)

        self.setWindowTitle("Profiler")

        self.output = None
        self.error_output = None

        self._last_wdir = None
        self._last_args = None
        self._last_pythonpath = None

        self.filecombo = PythonModulesComboBox(self)

        self.start_button = create_toolbutton(self,
                                              icon=ima.icon('run'),
                                              text=_("Profile"),
                                              tip=_("Run profiler"),
                                              triggered=lambda: self.start(),
                                              text_beside_icon=True)
        self.stop_button = create_toolbutton(self,
                                             icon=ima.icon('stop'),
                                             text=_("Stop"),
                                             tip=_("Stop current profiling"),
                                             text_beside_icon=True)
        self.filecombo.valid.connect(self.start_button.setEnabled)
        #self.connect(self.filecombo, SIGNAL('valid(bool)'), self.show_data)
        # FIXME: The combobox emits this signal on almost any event
        #        triggering show_data() too early, too often.

        browse_button = create_toolbutton(self,
                                          icon=ima.icon('fileopen'),
                                          tip=_('Select Python script'),
                                          triggered=self.select_file)

        self.datelabel = QLabel()

        self.log_button = create_toolbutton(self,
                                            icon=ima.icon('log'),
                                            text=_("Output"),
                                            text_beside_icon=True,
                                            tip=_("Show program's output"),
                                            triggered=self.show_log)

        self.datatree = ProfilerDataTree(self)

        self.collapse_button = create_toolbutton(
            self,
            icon=ima.icon('collapse'),
            triggered=lambda dD=-1: self.datatree.change_view(dD),
            tip=_('Collapse one level up'))
        self.expand_button = create_toolbutton(
            self,
            icon=ima.icon('expand'),
            triggered=lambda dD=1: self.datatree.change_view(dD),
            tip=_('Expand one level down'))

        self.save_button = create_toolbutton(self,
                                             text_beside_icon=True,
                                             text=_("Save data"),
                                             icon=ima.icon('filesave'),
                                             triggered=self.save_data,
                                             tip=_('Save profiling data'))
        self.load_button = create_toolbutton(
            self,
            text_beside_icon=True,
            text=_("Load data"),
            icon=ima.icon('fileimport'),
            triggered=self.compare,
            tip=_('Load profiling data for comparison'))
        self.clear_button = create_toolbutton(self,
                                              text_beside_icon=True,
                                              text=_("Clear comparison"),
                                              icon=ima.icon('editdelete'),
                                              triggered=self.clear)

        hlayout1 = QHBoxLayout()
        hlayout1.addWidget(self.filecombo)
        hlayout1.addWidget(browse_button)
        hlayout1.addWidget(self.start_button)
        hlayout1.addWidget(self.stop_button)

        hlayout2 = QHBoxLayout()
        hlayout2.addWidget(self.collapse_button)
        hlayout2.addWidget(self.expand_button)
        hlayout2.addStretch()
        hlayout2.addWidget(self.datelabel)
        hlayout2.addStretch()
        hlayout2.addWidget(self.log_button)
        hlayout2.addWidget(self.save_button)
        hlayout2.addWidget(self.load_button)
        hlayout2.addWidget(self.clear_button)

        layout = QVBoxLayout()
        layout.addLayout(hlayout1)
        layout.addLayout(hlayout2)
        layout.addWidget(self.datatree)
        self.setLayout(layout)

        self.process = None
        self.set_running_state(False)
        self.start_button.setEnabled(False)
        self.clear_button.setEnabled(False)

        if not is_profiler_installed():
            # This should happen only on certain GNU/Linux distributions
            # or when this a home-made Python build because the Python
            # profilers are included in the Python standard library
            for widget in (self.datatree, self.filecombo, self.start_button,
                           self.stop_button):
                widget.setDisabled(True)
            url = 'http://docs.python.org/library/profile.html'
            text = '%s <a href=%s>%s</a>' % (_('Please install'), url,
                                             _("the Python profiler modules"))
            self.datelabel.setText(text)
        else:
            pass  # self.show_data()

    def save_data(self):
        """Save data"""
        title = _("Save profiler result")
        filename, _selfilter = getsavefilename(
            self, title, getcwd(),
            _("Profiler result") + " (*.Result)")
        if filename:
            self.datatree.save_data(filename)

    def compare(self):
        filename, _selfilter = getopenfilename(
            self, _("Select script to compare"), getcwd(),
            _("Profiler result") + " (*.Result)")
        if filename:
            self.datatree.compare(filename)
            self.show_data()
            self.clear_button.setEnabled(True)

    def clear(self):
        self.datatree.compare(None)
        self.datatree.hide_diff_cols(True)
        self.show_data()
        self.clear_button.setEnabled(False)

    def analyze(self, filename, wdir=None, args=None, pythonpath=None):
        if not is_profiler_installed():
            return
        self.kill_if_running()
        #index, _data = self.get_data(filename)
        index = None  # FIXME: storing data is not implemented yet
        if index is None:
            self.filecombo.addItem(filename)
            self.filecombo.setCurrentIndex(self.filecombo.count() - 1)
        else:
            self.filecombo.setCurrentIndex(self.filecombo.findText(filename))
        self.filecombo.selected()
        if self.filecombo.is_valid():
            if wdir is None:
                wdir = osp.dirname(filename)
            self.start(wdir, args, pythonpath)

    def select_file(self):
        self.redirect_stdio.emit(False)
        filename, _selfilter = getopenfilename(
            self, _("Select Python script"), getcwd(),
            _("Python scripts") + " (*.py ; *.pyw)")
        self.redirect_stdio.emit(True)
        if filename:
            self.analyze(filename)

    def show_log(self):
        if self.output:
            TextEditor(self.output,
                       title=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def show_errorlog(self):
        if self.error_output:
            TextEditor(self.error_output,
                       title=_("Profiler output"),
                       readonly=True,
                       size=(700, 500)).exec_()

    def start(self, wdir=None, args=None, pythonpath=None):
        filename = to_text_string(self.filecombo.currentText())
        if wdir is None:
            wdir = self._last_wdir
            if wdir is None:
                wdir = osp.basename(filename)
        if args is None:
            args = self._last_args
            if args is None:
                args = []
        if pythonpath is None:
            pythonpath = self._last_pythonpath
        self._last_wdir = wdir
        self._last_args = args
        self._last_pythonpath = pythonpath

        self.datelabel.setText(_('Profiling, please wait...'))

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.SeparateChannels)
        self.process.setWorkingDirectory(wdir)
        self.process.readyReadStandardOutput.connect(self.read_output)
        self.process.readyReadStandardError.connect(
            lambda: self.read_output(error=True))
        self.process.finished.connect(
            lambda ec, es=QProcess.ExitStatus: self.finished(ec, es))
        self.stop_button.clicked.connect(self.process.kill)

        if pythonpath is not None:
            env = [
                to_text_string(_pth)
                for _pth in self.process.systemEnvironment()
            ]
            baseshell.add_pathlist_to_PYTHONPATH(env, pythonpath)
            processEnvironment = QProcessEnvironment()
            for envItem in env:
                envName, separator, envValue = envItem.partition('=')
                processEnvironment.insert(envName, envValue)
            self.process.setProcessEnvironment(processEnvironment)

        self.output = ''
        self.error_output = ''

        p_args = ['-m', 'cProfile', '-o', self.DATAPATH]
        if os.name == 'nt':
            # On Windows, one has to replace backslashes by slashes to avoid
            # confusion with escape characters (otherwise, for example, '\t'
            # will be interpreted as a tabulation):
            p_args.append(osp.normpath(filename).replace(os.sep, '/'))
        else:
            p_args.append(filename)
        if args:
            p_args.extend(shell_split(args))
        executable = sys.executable
        if executable.endswith("spyder.exe"):
            # py2exe distribution
            executable = "python.exe"
        self.process.start(executable, p_args)

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))

    def set_running_state(self, state=True):
        self.start_button.setEnabled(not state)
        self.stop_button.setEnabled(state)

    def read_output(self, error=False):
        if error:
            self.process.setReadChannel(QProcess.StandardError)
        else:
            self.process.setReadChannel(QProcess.StandardOutput)
        qba = QByteArray()
        while self.process.bytesAvailable():
            if error:
                qba += self.process.readAllStandardError()
            else:
                qba += self.process.readAllStandardOutput()
        text = to_text_string(locale_codec.toUnicode(qba.data()))
        if error:
            self.error_output += text
        else:
            self.output += text

    def finished(self, exit_code, exit_status):
        self.set_running_state(False)
        self.show_errorlog()  # If errors occurred, show them.
        self.output = self.error_output + self.output
        # FIXME: figure out if show_data should be called here or
        #        as a signal from the combobox
        self.show_data(justanalyzed=True)

    def kill_if_running(self):
        if self.process is not None:
            if self.process.state() == QProcess.Running:
                self.process.kill()
                self.process.waitForFinished()

    def show_data(self, justanalyzed=False):
        if not justanalyzed:
            self.output = None
        self.log_button.setEnabled(self.output is not None \
                                   and len(self.output) > 0)
        self.kill_if_running()
        filename = to_text_string(self.filecombo.currentText())
        if not filename:
            return

        self.datelabel.setText(_('Sorting data, please wait...'))
        QApplication.processEvents()

        self.datatree.load_data(self.DATAPATH)
        self.datatree.show_tree()

        text_style = "<span style=\'color: #444444\'><b>%s </b></span>"
        date_text = text_style % time.strftime("%d %b %Y %H:%M",
                                               time.localtime())
        self.datelabel.setText(date_text)
Example #23
0
class PluginManager(QObject):

    send_to_inspector = Signal(str, str, str, str, bool)
    edit_goto = Signal(str, int, str)

    def __init__(self, editor_widget):
        super(PluginManager, self).__init__()
        self.editor_widget = editor_widget
        self.pending = None
        self.busy = False
        self.load_plugins()
        self._submods_thread = GetSubmodulesThread()
        self._submods_thread.finished.connect(self._update_extension_modules)
        self._submods_thread.start()

    def load_plugins(self):
        """Get and load a plugin, checking in order of PLUGINS"""
        plugins = OrderedDict()
        for plugin_name in PLUGINS:
            mod_name = plugin_name + '_plugin'
            try:
                mod = __import__('spyderlib.utils.introspection.' + mod_name,
                                 fromlist=[mod_name])
                cls = getattr(mod, '%sPlugin' % plugin_name.capitalize())
                plugin = cls()
                plugin.load_plugin()
            except Exception as e:
                debug_print(e)
                if DEBUG_EDITOR:
                    log_last_error(LOG_FILENAME)
            else:
                plugins[plugin_name] = plugin
                debug_print('Instropection Plugin Loaded: %s' % plugin.name)
        self.plugins = plugins
        debug_print('Plugins loaded: %s' % self.plugins.keys())
        return plugins

    def _get_code_info(self, name, position=None, **kwargs):

        editor = self.editor_widget.get_current_editor()
        finfo = self.editor_widget.get_current_finfo()
        in_comment_or_string = editor.in_comment_or_string()

        if position is None:
            position = editor.get_position('cursor')

        kwargs['editor'] = editor
        kwargs['finfo'] = finfo
        kwargs['editor_widget'] = self.editor_widget

        return CodeInfo(name, finfo.get_source_code(), position,
                        finfo.filename, editor.is_python_like,
                        in_comment_or_string, **kwargs)

    def get_completions(self, automatic):
        """Get code completion"""
        info = self._get_code_info('completions', automatic=automatic)

        if 'jedi' in self.plugins and not self.plugins['jedi'].busy:
            self._handle_request(info)

        elif info.line.lstrip().startswith(('import ', 'from ')):
            self._handle_request(info, 'fallback')

        else:
            self._handle_request(info)

    def go_to_definition(self, position):
        """Go to definition"""
        info = self._get_code_info('definition', position)

        self._handle_request(info)

    def show_object_info(self, position, auto=True):
        """Show signature calltip and/or docstring in the Object Inspector"""
        # auto is True means that this method was called automatically,
        # i.e. the user has just entered an opening parenthesis -- in that
        # case, we don't want to force the object inspector to be visible,
        # to avoid polluting the window layout
        info = self._get_code_info('info', position, auto=auto)
        self._handle_request(info)

    def validate(self):
        """Validate the plugins"""
        if not self.busy:
            for plugin in self.plugins.values():
                plugin.validate()

    def is_editor_ready(self):
        """Check if the main app is starting up"""
        if self.editor_widget:
            window = self.editor_widget.window()
            if hasattr(window, 'is_starting_up') and not window.is_starting_up:
                return True

    def _handle_request(self, info, desired=None):
        """Handle an incoming request from the user."""
        debug_print('%s request' % info.name)

        editor = info.editor
        if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj)
                or (editor.in_comment_or_string() and info.name != 'info')):
            desired = 'fallback'

        self.pending = (info, desired)
        if not self.busy:
            self._handle_pending()

    def _handle_pending(self):
        """Handle any pending requests, sending them to the correct plugin."""
        if not self.pending:
            self._post_message('')
            return
        info, desired = self.pending
        if desired and self.plugins[desired].busy:
            return
        self.busy = True

        if desired:
            plugins = [self.plugins[desired]]
        elif (info.name == 'definition' and not info.editor.is_python()
              or info.name == 'info'):
            plugins = [p for p in self.plugins.values() if not p.busy]
        else:
            # use all but the fallback
            plugins = [
                p for p in list(self.plugins.values())[:-1] if not p.busy
            ]

        self.request = RequestHandler(info, plugins)
        self.request.introspection_complete.connect(
            self._introspection_complete)
        self.pending = None

    def _introspection_complete(self):
        """
        Handle an introspection response from the thread.

        Route the response to the correct handler, and then handle
        any pending requests.
        """
        self.busy = False
        result = self.request.result
        info = self.request.info
        current = self._get_code_info('current')

        if result and current.filename == info.filename:
            func = getattr(self, '_handle_%s_response' % info.name)
            try:
                func(result, current, info)
            except Exception as e:
                debug_print(e)
        elif current.filename == info.filename and info.name == 'definition':
            result = self.plugins['fallback'].get_definition(info)

        if info == self.pending:
            self.pending = None

        self._handle_pending()

    def _handle_completions_response(self, comp_list, info, prev_info):
        """
        Handle a `completions` response.

        Only handle the response if we are on the same line of text and
        on the same `obj` as the original request.
        """
        if info.line_num != prev_info.line_num:
            return
        completion_text = info.obj
        prev_text = prev_info.obj

        if prev_info.obj is None:
            completion_text = ''
            prev_text = ''

        if not completion_text.startswith(prev_text):
            return

        if info.full_obj and len(info.full_obj) > len(info.obj):
            new_list = [c for c in comp_list if c.startswith(info.full_obj)]
            if new_list:
                pos = info.editor.get_position('cursor')
                new_pos = pos + len(info.full_obj) - len(info.obj)
                info.editor.set_cursor_position(new_pos)
                completion_text = info.full_obj
                comp_list = new_list

        if '.' in completion_text:
            completion_text = completion_text.split('.')[-1]

        comp_list = [c.split('.')[-1] for c in comp_list]
        comp_list = [c for c in comp_list if c.startswith(completion_text)]

        info.editor.show_completion_list(comp_list, completion_text,
                                         prev_info.automatic)

    def _handle_info_response(self, resp, info, prev_info):
        """
        Handle an `info` response, triggering a calltip and/or docstring.

        Only handle the response if we are on the same line of text as
        when the request was initiated.
        """
        if info.line_num != prev_info.line_num:
            return

        if resp['calltip']:
            info.editor.show_calltip('Arguments',
                                     resp['calltip'],
                                     signature=True,
                                     at_position=prev_info.position)

        if resp['name']:
            self.send_to_inspector.emit(resp['name'], resp['argspec'],
                                        resp['note'], resp['docstring'],
                                        not prev_info.auto)

    def _handle_definition_response(self, resp, info, prev_info):
        """Handle a `definition` response"""
        fname, lineno = resp
        self.edit_goto.emit(fname, lineno, "")

    def _update_extension_modules(self):
        """Set the extension_modules after submods thread finishes"""
        for plugin in self.plugins.values():
            plugin.set_pref('extension_modules', self._submods_thread.submods)

    def _post_message(self, message, timeout=60000):
        """
        Post a message to the main window status bar with a timeout in ms
        """
        if self.editor_widget:
            statusbar = self.editor_widget.window().statusBar()
            statusbar.showMessage(message, timeout)
            QApplication.processEvents()
Example #24
0
class FindInFiles(FindInFilesWidget, SpyderPluginMixin):
    """Find in files DockWidget"""
    CONF_SECTION = 'find_in_files'
    sig_option_changed = Signal(str, object)
    toggle_visibility = Signal(bool)
    edit_goto = Signal(str, int, str)
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent=None):
        supported_encodings = self.get_option('supported_encodings')
        
        search_path = self.get_option('search_path', None)        
        self.search_text_samples = self.get_option('search_text_samples')
        search_text = self.get_option('search_text')
        search_text = [txt for txt in search_text \
                       if txt not in self.search_text_samples]
        search_text += self.search_text_samples
        
        search_text_regexp = self.get_option('search_text_regexp')
        include = self.get_option('include')
        include_idx = self.get_option('include_idx', None)
        include_regexp = self.get_option('include_regexp')
        exclude = self.get_option('exclude')
        exclude_idx = self.get_option('exclude_idx', None)
        exclude_regexp = self.get_option('exclude_regexp')
        in_python_path = self.get_option('in_python_path')
        more_options = self.get_option('more_options')
        FindInFilesWidget.__init__(self, parent,
                                   search_text, search_text_regexp, search_path,
                                   include, include_idx, include_regexp,
                                   exclude, exclude_idx, exclude_regexp,
                                   supported_encodings,
                                   in_python_path, more_options)
        SpyderPluginMixin.__init__(self, parent)
        
        # Initialize plugin
        self.initialize_plugin()
        
        self.toggle_visibility.connect(self.toggle)
        
    def toggle(self, state):
        """Toggle widget visibility"""
        if self.dockwidget:
            self.dockwidget.setVisible(state)
    
    def refreshdir(self):
        """Refresh search directory"""
        self.find_options.set_directory(getcwd())

    @Slot()
    def findinfiles_callback(self):
        """Find in files callback"""
        widget = QApplication.focusWidget()
        if not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        text = ''
        try:
            if widget.has_selected_text():
                text = widget.get_selected_text()
        except AttributeError:
            # This is not a text widget deriving from TextEditBaseWidget
            pass
        self.set_search_text(text)
        if text:
            self.find()
        
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return _("Find in files")
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.find_options.search_text
    
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        return []
    
    def register_plugin(self):
        """Register plugin in Spyder's main window"""
        self.get_pythonpath_callback = self.main.get_spyder_pythonpath
        self.main.add_dockwidget(self)
        self.edit_goto.connect(self.main.editor.load)
        self.redirect_stdio.connect(self.main.redirect_internalshell_stdio)
        self.main.workingdirectory.refresh_findinfiles.connect(self.refreshdir)
        
        findinfiles_action = create_action(self, _("&Find in files"),
                                   icon='findf.png',
                                   triggered=self.findinfiles_callback,
                                   tip=_("Search text in multiple files"))        
        
        self.main.search_menu_actions += [None, findinfiles_action]
        self.main.search_toolbar_actions += [None, findinfiles_action]
    
    def refresh_plugin(self):
        """Refresh widget"""
        pass
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        self.closing_widget()  # stop search thread and clean-up
        options = self.find_options.get_options(all=True)
        if options is not None:
            search_text, text_re, search_path, \
            include, include_idx, include_re, \
            exclude, exclude_idx, exclude_re, \
            in_python_path, more_options = options
            hist_limit = 15
            search_text = search_text[:hist_limit]
            search_path = search_path[:hist_limit]
            include = include[:hist_limit]
            exclude = exclude[:hist_limit]
            self.set_option('search_text', search_text)
            self.set_option('search_text_regexp', text_re)
            self.set_option('search_path', search_path)
            self.set_option('include', include)
            self.set_option('include_idx', include_idx)
            self.set_option('include_regexp', include_re)
            self.set_option('exclude', exclude)
            self.set_option('exclude_idx', exclude_idx)
            self.set_option('exclude_regexp', exclude_re)
            self.set_option('in_python_path', in_python_path)
            self.set_option('more_options', more_options)
        return True
Example #25
0
class AsyncClient(QObject):
    """
    A class which handles a connection to a client through a QProcess.
    """

    # Emitted when the client has initialized.
    initialized = Signal()

    # Emitted when the client errors.
    errored = Signal()

    # Emitted when a request response is received.
    received = Signal(object)

    def __init__(self,
                 target,
                 executable=None,
                 name=None,
                 extra_args=None,
                 libs=None,
                 cwd=None,
                 env=None):
        super(AsyncClient, self).__init__()
        self.executable = executable or sys.executable
        self.extra_args = extra_args
        self.target = target
        self.name = name or self
        self.libs = libs
        self.cwd = cwd
        self.env = env
        self.is_initialized = False
        self.closing = False
        self.context = zmq.Context()
        QApplication.instance().aboutToQuit.connect(self.close)

        # Set up the heartbeat timer.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self._heartbeat)
        self.timer.start(HEARTBEAT)

    def run(self):
        """Handle the connection with the server.
        """
        # Set up the zmq port.
        self.socket = self.context.socket(zmq.PAIR)
        self.port = self.socket.bind_to_random_port('tcp://*')

        # Set up the process.
        self.process = QProcess(self)
        if self.cwd:
            self.process.setWorkingDirectory(self.cwd)
        p_args = ['-u', self.target, str(self.port)]
        if self.extra_args is not None:
            p_args += self.extra_args

        # Set up environment variables.
        processEnvironment = QProcessEnvironment()
        env = self.process.systemEnvironment()
        if (self.env and 'PYTHONPATH' not in self.env) or DEV:
            python_path = osp.dirname(get_module_path('spyderlib'))
            # Add the libs to the python path.
            for lib in self.libs:
                try:
                    path = osp.dirname(imp.find_module(lib)[1])
                    python_path = osp.pathsep.join([python_path, path])
                except ImportError:
                    pass
            env.append("PYTHONPATH=%s" % python_path)
        if self.env:
            env.update(self.env)
        for envItem in env:
            envName, separator, envValue = envItem.partition('=')
            processEnvironment.insert(envName, envValue)
        self.process.setProcessEnvironment(processEnvironment)

        # Start the process and wait for started.
        self.process.start(self.executable, p_args)
        self.process.finished.connect(self._on_finished)
        running = self.process.waitForStarted()
        if not running:
            raise IOError('Could not start %s' % self)

        # Set up the socket notifer.
        fid = self.socket.getsockopt(zmq.FD)
        self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self)
        self.notifier.activated.connect(self._on_msg_received)

    def request(self, func_name, *args, **kwargs):
        """Send a request to the server.

        The response will be a dictionary the 'request_id' and the
        'func_name' as well as a 'result' field with the object returned by
        the function call or or an 'error' field with a traceback.
        """
        if not self.is_initialized:
            return
        request_id = uuid.uuid4().hex
        request = dict(func_name=func_name,
                       args=args,
                       kwargs=kwargs,
                       request_id=request_id)
        try:
            self.socket.send_pyobj(request)
        except zmq.ZMQError:
            pass
        return request_id

    def close(self):
        """Cleanly close the connection to the server.
        """
        self.closing = True
        self.timer.stop()
        self.request('server_quit')
        self.process.waitForFinished(1000)
        self.context.destroy(0)

    def _on_finished(self):
        """Handle a finished signal from the process.
        """
        if self.closing:
            return
        if self.is_initialized:
            debug_print('Restarting %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.is_initialized = False
            self.notifier.setEnabled(False)
            self.run()
        else:
            debug_print('Errored %s' % self.name)
            debug_print(self.process.readAllStandardOutput())
            debug_print(self.process.readAllStandardError())
            self.errored.emit()

    def _on_msg_received(self):
        """Handle a message trigger from the socket.
        """
        self.notifier.setEnabled(False)
        while 1:
            try:
                resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK)
            except zmq.ZMQError:
                self.notifier.setEnabled(True)
                return
            if not self.is_initialized:
                self.is_initialized = True
                debug_print('Initialized %s' % self.name)
                self.initialized.emit()
                continue
            resp['name'] = self.name
            self.received.emit(resp)

    def _heartbeat(self):
        """Send a heartbeat to keep the server alive.
        """
        if not self.is_initialized:
            return
        self.socket.send_pyobj(dict(func_name='server_heartbeat'))
Example #26
0
class IPythonShellWidget(RichIPythonWidget):
    """
    Spyder's IPython shell widget

    This class has custom control and page_control widgets, additional methods
    to provide missing functionality and a couple more keyboard shortcuts.
    """
    focus_changed = Signal()
    new_client = Signal()

    def __init__(self, *args, **kw):
        # To override the Qt widget used by RichIPythonWidget
        self.custom_control = IPythonControlWidget
        self.custom_page_control = IPythonPageControlWidget
        super(IPythonShellWidget, self).__init__(*args, **kw)
        self.set_background_color()

        # --- Spyder variables ---
        self.ipyclient = None

        # --- Keyboard shortcuts ---
        self.shortcuts = self.create_shortcuts()

        # --- IPython variables ---
        # To send an interrupt signal to the Spyder kernel
        self.custom_interrupt = True

        # To restart the Spyder kernel in case it dies
        self.custom_restart = True

    #---- Public API ----------------------------------------------------------
    def set_ipyclient(self, ipyclient):
        """Bind this shell widget to an IPython client one"""
        self.ipyclient = ipyclient
        self.exit_requested.connect(ipyclient.exit_callback)

    def long_banner(self):
        """Banner for IPython widgets with pylab message"""
        from IPython.core.usage import default_gui_banner
        banner = default_gui_banner

        pylab_o = CONF.get('ipython_console', 'pylab', True)
        autoload_pylab_o = CONF.get('ipython_console', 'pylab/autoload', True)
        mpl_installed = programs.is_module_installed('matplotlib')
        if mpl_installed and (pylab_o and autoload_pylab_o):
            pylab_message = ("\nPopulating the interactive namespace from "
                             "numpy and matplotlib")
            banner = banner + pylab_message

        sympy_o = CONF.get('ipython_console', 'symbolic_math', True)
        if sympy_o:
            lines = """
These commands were executed:
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)
"""
            banner = banner + lines
        return banner

    def short_banner(self):
        """Short banner with Python and IPython versions"""
        from IPython.core.release import version
        py_ver = '%d.%d.%d' % (sys.version_info[0], sys.version_info[1],
                               sys.version_info[2])
        banner = 'Python %s on %s -- IPython %s' % (py_ver, sys.platform,
                                                    version)
        return banner

    def clear_console(self):
        self.execute("%clear")

    def write_to_stdin(self, line):
        """Send raw characters to the IPython kernel through stdin"""
        try:
            self.kernel_client.stdin_channel.input(line)
        except AttributeError:
            self.kernel_client.input(line)

    def set_background_color(self):
        lightbg_o = CONF.get('ipython_console', 'light_color')
        if not lightbg_o:
            self.set_default_style(colors='linux')

    def create_shortcuts(self):
        inspect = create_shortcut(self._control.inspect_current_object,
                                  context='Console',
                                  name='Inspect current object',
                                  parent=self)
        clear_console = create_shortcut(self.clear_console,
                                        context='Console',
                                        name='Clear shell',
                                        parent=self)

        # Fixed shortcuts
        new_shortcut("Ctrl+T", self, lambda: self.new_client.emit())
        new_shortcut(SHORTCUT_INLINE, self,
                     lambda: self._control.enter_array_inline())
        new_shortcut(SHORTCUT_TABLE, self,
                     lambda: self._control.enter_array_table())

        return [inspect, clear_console]

    def get_signature(self, content):
        """Get signature from inspect reply content"""
        data = content.get('data', {})
        text = data.get('text/plain', '')
        if text:
            text = ANSI_OR_SPECIAL_PATTERN.sub('', text)
            line = self._control.get_current_line_to_cursor()
            name = line[:-1].split('.')[-1]
            argspec = getargspecfromtext(text)
            if argspec:
                # This covers cases like np.abs, whose docstring is
                # the same as np.absolute and because of that a proper
                # signature can't be obtained correctly
                signature = name + argspec
            else:
                signature = getsignaturefromtext(text, name)
            return signature
        else:
            return ''

    #---- IPython private methods ---------------------------------------------
    def _context_menu_make(self, pos):
        """Reimplement the IPython context menu"""
        menu = super(IPythonShellWidget, self)._context_menu_make(pos)
        return self.ipyclient.add_actions_to_context_menu(menu)

    def _banner_default(self):
        """
        Reimplement banner creation to let the user decide if he wants a
        banner or not
        """
        banner_o = CONF.get('ipython_console', 'show_banner', True)
        if banner_o:
            return self.long_banner()
        else:
            return self.short_banner()

    def _handle_object_info_reply(self, rep):
        """
        Reimplement call tips to only show signatures, using the same style
        from our Editor and External Console too
        Note: For IPython 2-
        """
        self.log.debug("oinfo: %s", rep.get('content', ''))
        cursor = self._get_cursor()
        info = self._request_info.get('call_tip')
        if info and info.id == rep['parent_header']['msg_id'] and \
          info.pos == cursor.position():
            content = rep['content']
            if content.get('ismagic', False):
                call_info, doc = None, None
            else:
                call_info, doc = call_tip(content, format_call=True)
                if call_info is None and doc is not None:
                    name = content['name'].split('.')[-1]
                    argspec = getargspecfromtext(doc)
                    if argspec:
                        # This covers cases like np.abs, whose docstring is
                        # the same as np.absolute and because of that a proper
                        # signature can't be obtained correctly
                        call_info = name + argspec
                    else:
                        call_info = getsignaturefromtext(doc, name)
            if call_info:
                self._control.show_calltip(_("Arguments"),
                                           call_info,
                                           signature=True,
                                           color='#2D62FF')

    def _handle_inspect_reply(self, rep):
        """
        Reimplement call tips to only show signatures, using the same style
        from our Editor and External Console too
        Note: For IPython 3+
        """
        cursor = self._get_cursor()
        info = self._request_info.get('call_tip')
        if info and info.id == rep['parent_header']['msg_id'] and \
          info.pos == cursor.position():
            content = rep['content']
            if content.get('status') == 'ok' and content.get('found', False):
                signature = self.get_signature(content)
                if signature:
                    self._control.show_calltip(_("Arguments"),
                                               signature,
                                               signature=True,
                                               color='#2D62FF')

    #---- Qt methods ----------------------------------------------------------
    def focusInEvent(self, event):
        """Reimplement Qt method to send focus change notification"""
        self.focus_changed.emit()
        return super(IPythonShellWidget, self).focusInEvent(event)

    def focusOutEvent(self, event):
        """Reimplement Qt method to send focus change notification"""
        self.focus_changed.emit()
        return super(IPythonShellWidget, self).focusOutEvent(event)
Example #27
0
class ExternalSystemShell(ExternalShellBase):
    """External Shell widget: execute Python script in a separate process"""
    SHELL_CLASS = TerminalWidget
    started = Signal()

    def __init__(self,
                 parent=None,
                 wdir=None,
                 path=[],
                 light_background=True,
                 menu_actions=None,
                 show_buttons_inside=True,
                 show_elapsed_time=True):
        ExternalShellBase.__init__(self,
                                   parent=parent,
                                   fname=None,
                                   wdir=wdir,
                                   history_filename='.history',
                                   light_background=light_background,
                                   menu_actions=menu_actions,
                                   show_buttons_inside=show_buttons_inside,
                                   show_elapsed_time=show_elapsed_time)

        # Additional python path list
        self.path = path

        # For compatibility with the other shells that can live in the external
        # console
        self.is_ipykernel = False
        self.connection_file = None

    def get_icon(self):
        return ima.icon('cmdprompt')

    def create_process(self):
        self.shell.clear()

        self.process = QProcess(self)
        self.process.setProcessChannelMode(QProcess.MergedChannels)

        # PYTHONPATH (in case we use Python in this terminal, e.g. py2exe)
        env = [
            to_text_string(_path)
            for _path in self.process.systemEnvironment()
        ]

        processEnvironment = QProcessEnvironment()
        for envItem in env:
            envName, separator, envValue = envItem.partition('=')
            processEnvironment.insert(envName, envValue)

        add_pathlist_to_PYTHONPATH(env, self.path)
        self.process.setProcessEnvironment(processEnvironment)

        # Working directory
        if self.wdir is not None:
            self.process.setWorkingDirectory(self.wdir)

        # Shell arguments
        if os.name == 'nt':
            p_args = ['/Q']
        else:
            p_args = ['-i']

        if self.arguments:
            p_args.extend(shell_split(self.arguments))

        self.process.readyReadStandardOutput.connect(self.write_output)
        self.process.finished.connect(self.finished)
        self.kill_button.clicked.connect(self.process.kill)

        if os.name == 'nt':
            self.process.start('cmd.exe', p_args)
        else:
            # Using bash:
            self.process.start('bash', p_args)
            self.send_to_process('PS1="\\u@\\h:\\w> "\n')

        running = self.process.waitForStarted()
        self.set_running_state(running)
        if not running:
            QMessageBox.critical(self, _("Error"),
                                 _("Process failed to start"))
        else:
            self.shell.setFocus()
            self.started.emit()

        return self.process

#===============================================================================
#    Input/Output
#===============================================================================

    def transcode(self, qba):
        if os.name == 'nt':
            return to_text_string(CP850_CODEC.toUnicode(qba.data()))
        else:
            return ExternalShellBase.transcode(self, qba)

    def send_to_process(self, text):
        if not is_text_string(text):
            text = to_text_string(text)
        if text[:-1] in ["clear", "cls", "CLS"]:
            self.shell.clear()
            self.send_to_process(os.linesep)
            return
        if not text.endswith('\n'):
            text += '\n'
        if os.name == 'nt':
            self.process.write(text.encode('cp850'))
        else:
            self.process.write(LOCALE_CODEC.fromUnicode(text))
        self.process.waitForBytesWritten(-1)

    def keyboard_interrupt(self):
        # This does not work on Windows:
        # (unfortunately there is no easy way to send a Ctrl+C to cmd.exe)
        self.send_ctrl_to_process('c')
Example #28
0
class IPythonClient(QWidget, SaveHistoryMixin):
    """
    IPython client or frontend for Spyder

    This is a widget composed of a shell widget (i.e. RichIPythonWidget
    + our additions = IPythonShellWidget) and an WebView info widget to 
    print kernel error and other messages.
    """

    SEPARATOR = '%s##---(%s)---' % (os.linesep * 2, time.ctime())
    append_to_history = Signal(str, str)

    def __init__(self,
                 plugin,
                 name,
                 history_filename,
                 connection_file=None,
                 hostname=None,
                 sshkey=None,
                 password=None,
                 kernel_widget_id=None,
                 menu_actions=None):
        super(IPythonClient, self).__init__(plugin)
        SaveHistoryMixin.__init__(self)
        self.options_button = None

        # stop button and icon
        self.stop_button = None
        self.stop_icon = get_icon("stop.png")

        self.connection_file = connection_file
        self.kernel_widget_id = kernel_widget_id
        self.hostname = hostname
        self.sshkey = sshkey
        self.password = password
        self.name = name
        self.get_option = plugin.get_option
        self.shellwidget = IPythonShellWidget(config=self.shellwidget_config(),
                                              local_kernel=False)
        self.shellwidget.hide()
        self.infowidget = WebView(self)
        self.menu_actions = menu_actions
        self.history_filename = get_conf_path(history_filename)
        self.history = []
        self.namespacebrowser = None

        self.set_infowidget_font()
        self.loading_page = self._create_loading_page()
        self.infowidget.setHtml(self.loading_page)

        vlayout = QVBoxLayout()
        toolbar_buttons = self.get_toolbar_buttons()
        hlayout = QHBoxLayout()
        for button in toolbar_buttons:
            hlayout.addWidget(button)
        vlayout.addLayout(hlayout)
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.addWidget(self.shellwidget)
        vlayout.addWidget(self.infowidget)
        self.setLayout(vlayout)

        self.exit_callback = lambda: plugin.close_client(client=self)

    #------ Public API --------------------------------------------------------
    def show_shellwidget(self, give_focus=True):
        """Show shellwidget and configure it"""
        self.infowidget.hide()
        self.shellwidget.show()
        self.infowidget.setHtml(BLANK)
        if give_focus:
            self.get_control().setFocus()

        # Connect shellwidget to the client
        self.shellwidget.set_ipyclient(self)

        # To save history
        self.shellwidget.executing.connect(self.add_to_history)

        # For Mayavi to run correctly
        self.shellwidget.executing.connect(self.set_backend_for_mayavi)

        # To update history after execution
        self.shellwidget.executed.connect(self.update_history)

        # To update the Variable Explorer after execution
        self.shellwidget.executed.connect(self.auto_refresh_namespacebrowser)

        # To show a stop button, when executing a process
        self.shellwidget.executing.connect(self.enable_stop_button)

        # To hide a stop button after execution stopped
        self.shellwidget.executed.connect(self.disable_stop_button)

    def enable_stop_button(self):
        self.stop_button.setEnabled(True)

    def disable_stop_button(self):
        self.stop_button.setDisabled(True)

    @Slot()
    def stop_button_click_handler(self):
        self.stop_button.setDisabled(True)
        self.interrupt_kernel()

    def show_kernel_error(self, error):
        """Show kernel initialization errors in infowidget"""
        # Remove explanation about how to kill the kernel (doesn't apply to us)
        error = error.split('issues/2049')[-1]
        # Remove unneeded blank lines at the beginning
        eol = sourcecode.get_eol_chars(error)
        if eol:
            error = error.replace(eol, '<br>')
        while error.startswith('<br>'):
            error = error[4:]
        # Remove connection message
        if error.startswith('To connect another client') or \
          error.startswith('[IPKernelApp] To connect another client'):
            error = error.split('<br>')
            error = '<br>'.join(error[2:])
        # Don't break lines in hyphens
        # From http://stackoverflow.com/q/7691569/438386
        error = error.replace('-', '&#8209')

        message = _("An error ocurred while starting the kernel")
        kernel_error_template = Template(KERNEL_ERROR)
        page = kernel_error_template.substitute(css_path=CSS_PATH,
                                                message=message,
                                                error=error)
        self.infowidget.setHtml(page)

    def show_restart_animation(self):
        self.shellwidget.hide()
        self.infowidget.setHtml(self.loading_page)
        self.infowidget.show()

    def get_name(self):
        """Return client name"""
        return ((_("Console") if self.hostname is None else self.hostname) +
                " " + self.name)

    def get_control(self):
        """Return the text widget (or similar) to give focus to"""
        # page_control is the widget used for paging
        page_control = self.shellwidget._page_control
        if page_control and page_control.isVisible():
            return page_control
        else:
            return self.shellwidget._control

    def get_options_menu(self):
        """Return options menu"""
        restart_action = create_action(self,
                                       _("Restart kernel"),
                                       shortcut=QKeySequence("Ctrl+."),
                                       icon=get_icon('restart.png'),
                                       triggered=self.restart_kernel)
        restart_action.setShortcutContext(Qt.WidgetWithChildrenShortcut)

        # Main menu
        if self.menu_actions is not None:
            actions = [restart_action, None] + self.menu_actions
        else:
            actions = [restart_action]
        return actions

    def get_toolbar_buttons(self):
        """Return toolbar buttons list"""
        #TODO: Eventually add some buttons (Empty for now)
        # (see for example: spyderlib/widgets/externalshell/baseshell.py)
        buttons = []
        # Code to add the stop button
        if self.stop_button is None:
            self.stop_button = create_toolbutton(
                self,
                text=_("Stop"),
                icon=self.stop_icon,
                tip=_("Stop the current command"))
            self.disable_stop_button()
            # set click event handler
            self.stop_button.clicked.connect(self.stop_button_click_handler)
        if self.stop_button is not None:
            buttons.append(self.stop_button)

        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(
                    self, text=_("Options"), icon=get_icon('tooloptions.png'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)

        return buttons

    def add_actions_to_context_menu(self, menu):
        """Add actions to IPython widget context menu"""
        # See spyderlib/widgets/ipython.py for more details on this method
        inspect_action = create_action(
            self,
            _("Inspect current object"),
            QKeySequence(get_shortcut('console', 'inspect current object')),
            icon=get_std_icon('MessageBoxInformation'),
            triggered=self.inspect_object)
        clear_line_action = create_action(self,
                                          _("Clear line or block"),
                                          QKeySequence("Shift+Escape"),
                                          icon=get_icon('eraser.png'),
                                          triggered=self.clear_line)
        clear_console_action = create_action(
            self,
            _("Clear console"),
            QKeySequence(get_shortcut('console', 'clear shell')),
            icon=get_icon('clear.png'),
            triggered=self.clear_console)
        quit_action = create_action(self,
                                    _("&Quit"),
                                    icon='exit.png',
                                    triggered=self.exit_callback)
        add_actions(menu, (None, inspect_action, clear_line_action,
                           clear_console_action, None, quit_action))
        return menu

    def set_font(self, font):
        """Set IPython widget's font"""
        self.shellwidget._control.setFont(font)
        self.shellwidget.font = font

    def set_infowidget_font(self):
        font = get_font('inspector', 'rich_text')
        self.infowidget.set_font(font)

    def interrupt_kernel(self):
        """Interrupt the associanted Spyder kernel if it's running"""
        self.shellwidget.request_interrupt_kernel()

    @Slot()
    def restart_kernel(self):
        """Restart the associanted Spyder kernel"""
        self.shellwidget.request_restart_kernel()

    @Slot()
    def inspect_object(self):
        """Show how to inspect an object with our object inspector"""
        self.shellwidget._control.inspect_current_object()

    @Slot()
    def clear_line(self):
        """Clear a console line"""
        self.shellwidget._keyboard_quit()

    @Slot()
    def clear_console(self):
        """Clear the whole console"""
        self.shellwidget.execute("%clear")

    def if_kernel_dies(self, t):
        """
        Show a message in the console if the kernel dies.
        t is the time in seconds between the death and showing the message.
        """
        message = _("It seems the kernel died unexpectedly. Use "
                    "'Restart kernel' to continue using this console.")
        self.shellwidget._append_plain_text(message + '\n')

    def update_history(self):
        self.history = self.shellwidget._history

    def set_backend_for_mayavi(self, command):
        calling_mayavi = False
        lines = command.splitlines()
        for l in lines:
            if not l.startswith('#'):
                if 'import mayavi' in l or 'from mayavi' in l:
                    calling_mayavi = True
                    break
        if calling_mayavi:
            message = _("Changing backend to Qt for Mayavi")
            self.shellwidget._append_plain_text(message + '\n')
            self.shellwidget.execute("%gui inline\n%gui qt")

    def interrupt_message(self):
        """
        Print an interrupt message when the client is connected to an external
        kernel
        """
        message = _("Kernel process is either remote or unspecified. "
                    "Cannot interrupt")
        QMessageBox.information(self, "IPython", message)

    def restart_message(self):
        """
        Print a restart message when the client is connected to an external
        kernel
        """
        message = _("Kernel process is either remote or unspecified. "
                    "Cannot restart.")
        QMessageBox.information(self, "IPython", message)

    def set_namespacebrowser(self, namespacebrowser):
        """Set namespace browser widget"""
        self.namespacebrowser = namespacebrowser

    def auto_refresh_namespacebrowser(self):
        """Refresh namespace browser"""
        if self.namespacebrowser:
            self.namespacebrowser.refresh_table()

    def shellwidget_config(self):
        """Generate a Config instance for shell widgets using our config
        system
        
        This lets us create each widget with its own config (as opposed to
        IPythonQtConsoleApp, where all widgets have the same config)
        """
        # ---- IPython config ----
        try:
            profile_path = osp.join(get_ipython_dir(), 'profile_default')
            full_ip_cfg = load_pyconfig_files(['ipython_qtconsole_config.py'],
                                              profile_path)

            # From the full config we only select the IPythonWidget section
            # because the others have no effect here.
            ip_cfg = Config({'IPythonWidget': full_ip_cfg.IPythonWidget})
        except:
            ip_cfg = Config()

        # ---- Spyder config ----
        spy_cfg = Config()

        # Make the pager widget a rich one (i.e a QTextEdit)
        spy_cfg.IPythonWidget.kind = 'rich'

        # Gui completion widget
        gui_comp_o = self.get_option('use_gui_completion')
        completions = {True: 'droplist', False: 'ncurses'}
        spy_cfg.IPythonWidget.gui_completion = completions[gui_comp_o]

        # Pager
        pager_o = self.get_option('use_pager')
        if pager_o:
            spy_cfg.IPythonWidget.paging = 'inside'
        else:
            spy_cfg.IPythonWidget.paging = 'none'

        # Calltips
        calltips_o = self.get_option('show_calltips')
        spy_cfg.IPythonWidget.enable_calltips = calltips_o

        # Buffer size
        buffer_size_o = self.get_option('buffer_size')
        spy_cfg.IPythonWidget.buffer_size = buffer_size_o

        # Prompts
        in_prompt_o = self.get_option('in_prompt')
        out_prompt_o = self.get_option('out_prompt')
        if in_prompt_o:
            spy_cfg.IPythonWidget.in_prompt = in_prompt_o
        if out_prompt_o:
            spy_cfg.IPythonWidget.out_prompt = out_prompt_o

        # Merge IPython and Spyder configs. Spyder prefs will have prevalence
        # over IPython ones
        ip_cfg._merge(spy_cfg)
        return ip_cfg

    #------ Private API -------------------------------------------------------
    def _create_loading_page(self):
        loading_template = Template(LOADING)
        loading_img = get_image_path('loading_sprites.png')
        if os.name == 'nt':
            loading_img = loading_img.replace('\\', '/')
        message = _("Connecting to kernel...")
        page = loading_template.substitute(css_path=CSS_PATH,
                                           loading_img=loading_img,
                                           message=message)
        return page

    #---- Qt methods ----------------------------------------------------------
    def closeEvent(self, event):
        """
        Reimplement Qt method to stop sending the custom_restart_kernel_died
        signal
        """
        kc = self.shellwidget.kernel_client
        if kc is not None:
            kc.hb_channel.pause()
Example #29
0
class InternalShell(PythonShellWidget):
    """Shell base widget: link between PythonShellWidget and Interpreter"""

    status = Signal(str)
    refresh = Signal()
    go_to_error = Signal(str)
    focus_changed = Signal()

    def __init__(self,
                 parent=None,
                 namespace=None,
                 commands=[],
                 message=None,
                 max_line_count=300,
                 font=None,
                 exitfunc=None,
                 profile=False,
                 multithreaded=True,
                 light_background=True):
        PythonShellWidget.__init__(self, parent,
                                   get_conf_path('history_internal.py'),
                                   profile)

        self.set_light_background(light_background)
        self.multithreaded = multithreaded
        self.setMaximumBlockCount(max_line_count)

        # For compatibility with ExtPythonShellWidget
        self.is_ipykernel = False

        if font is not None:
            self.set_font(font)

        # Allow raw_input support:
        self.input_loop = None
        self.input_mode = False

        # KeyboardInterrupt support
        self.interrupted = False  # used only for not-multithreaded mode
        self.sig_keyboard_interrupt.connect(self.keyboard_interrupt)

        # Code completion / calltips
        getcfg = lambda option: CONF.get('internal_console', option)
        case_sensitive = getcfg('codecompletion/case_sensitive')
        self.set_codecompletion_case(case_sensitive)

        # keyboard events management
        self.eventqueue = []

        # Init interpreter
        self.exitfunc = exitfunc
        self.commands = commands
        self.message = message
        self.interpreter = None
        self.start_interpreter(namespace)

        # Clear status bar
        self.status.emit('')

        # Embedded shell -- requires the monitor (which installs the
        # 'open_in_spyder' function in builtins)
        if hasattr(builtins, 'open_in_spyder'):
            self.go_to_error.connect(self.open_with_external_spyder)

    #------ Interpreter
    def start_interpreter(self, namespace):
        """Start Python interpreter"""
        self.clear()

        if self.interpreter is not None:
            self.interpreter.closing()
        self.interpreter = Interpreter(namespace, self.exitfunc, SysOutput,
                                       WidgetProxy, DEBUG)
        self.interpreter.stdout_write.data_avail.connect(self.stdout_avail)
        self.interpreter.stderr_write.data_avail.connect(self.stderr_avail)
        self.interpreter.widget_proxy.sig_set_readonly.connect(
            self.setReadOnly)
        self.interpreter.widget_proxy.sig_new_prompt.connect(self.new_prompt)
        self.interpreter.widget_proxy.sig_edit.connect(self.edit_script)
        self.interpreter.widget_proxy.sig_wait_input.connect(self.wait_input)
        if self.multithreaded:
            self.interpreter.start()

        # Interpreter banner
        banner = create_banner(self.message)
        self.write(banner, prompt=True)

        # Initial commands
        for cmd in self.commands:
            self.run_command(cmd, history=False, new_prompt=False)

        # First prompt
        self.new_prompt(self.interpreter.p1)
        self.refresh.emit()

        return self.interpreter

    def exit_interpreter(self):
        """Exit interpreter"""
        self.interpreter.exit_flag = True
        if self.multithreaded:
            self.interpreter.stdin_write.write('\n')
        self.interpreter.restore_stds()

    def edit_script(self, filename, external_editor):
        filename = to_text_string(filename)
        if external_editor:
            self.external_editor(filename)
        else:
            self.parent().edit_script(filename)

    def stdout_avail(self):
        """Data is available in stdout, let's empty the queue and write it!"""
        data = self.interpreter.stdout_write.empty_queue()
        if data:
            self.write(data)

    def stderr_avail(self):
        """Data is available in stderr, let's empty the queue and write it!"""
        data = self.interpreter.stderr_write.empty_queue()
        if data:
            self.write(data, error=True)
            self.flush(error=True)

    #------Raw input support
    def wait_input(self, prompt=''):
        """Wait for input (raw_input support)"""
        self.new_prompt(prompt)
        self.setFocus()
        self.input_mode = True
        self.input_loop = QEventLoop()
        self.input_loop.exec_()
        self.input_loop = None

    def end_input(self, cmd):
        """End of wait_input mode"""
        self.input_mode = False
        self.input_loop.exit()
        self.interpreter.widget_proxy.end_input(cmd)

    #----- Menus, actions, ...
    def setup_context_menu(self):
        """Reimplement PythonShellWidget method"""
        PythonShellWidget.setup_context_menu(self)
        self.help_action = create_action(self,
                                         _("Help..."),
                                         icon=get_std_icon('DialogHelpButton'),
                                         triggered=self.help)
        self.menu.addAction(self.help_action)

    @Slot()
    def help(self):
        """Help on Spyder console"""
        QMessageBox.about(
            self, _("Help"), """<b>%s</b>
                          <p><i>%s</i><br>    edit foobar.py
                          <p><i>%s</i><br>    xedit foobar.py
                          <p><i>%s</i><br>    run foobar.py
                          <p><i>%s</i><br>    clear x, y
                          <p><i>%s</i><br>    !ls
                          <p><i>%s</i><br>    object?
                          <p><i>%s</i><br>    result = oedit(object)
                          """ %
            (_('Shell special commands:'), _('Internal editor:'),
             _('External editor:'), _('Run script:'), _('Remove references:'),
             _('System commands:'), _('Python help:'), _('GUI-based editor:')))

    #------ External editing
    def open_with_external_spyder(self, text):
        """Load file in external Spyder's editor, if available
        This method is used only for embedded consoles
        (could also be useful if we ever implement the magic %edit command)"""
        match = get_error_match(to_text_string(text))
        if match:
            fname, lnb = match.groups()
            builtins.open_in_spyder(fname, int(lnb))

    def external_editor(self, filename, goto=-1):
        """Edit in an external editor
        Recommended: SciTE (e.g. to go to line where an error did occur)"""
        editor_path = CONF.get('internal_console', 'external_editor/path')
        goto_option = CONF.get('internal_console', 'external_editor/gotoline')
        try:
            if goto > 0 and goto_option:
                Popen(r'%s "%s" %s%d' %
                      (editor_path, filename, goto_option, goto))
            else:
                Popen(r'%s "%s"' % (editor_path, filename))
        except OSError:
            self.write_error("External editor was not found:"
                             " %s\n" % editor_path)

    #------ I/O
    def flush(self, error=False, prompt=False):
        """Reimplement ShellBaseWidget method"""
        PythonShellWidget.flush(self, error=error, prompt=prompt)
        if self.interrupted:
            self.interrupted = False
            raise KeyboardInterrupt

    #------ Clear terminal
    def clear_terminal(self):
        """Reimplement ShellBaseWidget method"""
        self.clear()
        self.new_prompt(self.interpreter.p2 if self.interpreter.more else self.
                        interpreter.p1)

    #------ Keyboard events
    def on_enter(self, command):
        """on_enter"""
        if self.profile:
            # Simple profiling test
            t0 = time()
            for _ in range(10):
                self.execute_command(command)
            self.insert_text(u("\n<Δt>=%dms\n") % (1e2 * (time() - t0)))
            self.new_prompt(self.interpreter.p1)
        else:
            self.execute_command(command)
        self.__flush_eventqueue()

    def keyPressEvent(self, event):
        """
        Reimplement Qt Method
        Enhanced keypress event handler
        """
        if self.preprocess_keyevent(event):
            # Event was accepted in self.preprocess_keyevent
            return
        self.postprocess_keyevent(event)

    def __flush_eventqueue(self):
        """Flush keyboard event queue"""
        while self.eventqueue:
            past_event = self.eventqueue.pop(0)
            self.postprocess_keyevent(past_event)

    #------ Command execution
    def keyboard_interrupt(self):
        """Simulate keyboard interrupt"""
        if self.multithreaded:
            self.interpreter.raise_keyboard_interrupt()
        else:
            if self.interpreter.more:
                self.write_error("\nKeyboardInterrupt\n")
                self.interpreter.more = False
                self.new_prompt(self.interpreter.p1)
                self.interpreter.resetbuffer()
            else:
                self.interrupted = True

    def execute_lines(self, lines):
        """
        Execute a set of lines as multiple command
        lines: multiple lines of text to be executed as single commands
        """
        for line in lines.splitlines():
            stripped_line = line.strip()
            if stripped_line.startswith('#'):
                continue
            self.write(line + os.linesep, flush=True)
            self.execute_command(line + "\n")
            self.flush()

    def execute_command(self, cmd):
        """
        Execute a command
        cmd: one-line command only, with '\n' at the end
        """
        if self.input_mode:
            self.end_input(cmd)
            return
        if cmd.endswith('\n'):
            cmd = cmd[:-1]
        # cls command
        if cmd == 'cls':
            self.clear_terminal()
            return
        self.run_command(cmd)

    def run_command(self, cmd, history=True, new_prompt=True):
        """Run command in interpreter"""
        if not cmd:
            cmd = ''
        else:
            if history:
                self.add_to_history(cmd)
        self.interpreter.stdin_write.write(to_binary_string(cmd + '\n'))
        if not self.multithreaded:
            self.interpreter.run_line()
            self.refresh.emit()

    #------ Code completion / Calltips
    def _eval(self, text):
        """Is text a valid object?"""
        return self.interpreter.eval(text)

    def get_dir(self, objtxt):
        """Return dir(object)"""
        obj, valid = self._eval(objtxt)
        if valid:
            return getobjdir(obj)

    def get_globals_keys(self):
        """Return shell globals() keys"""
        return list(self.interpreter.namespace.keys())

    def get_cdlistdir(self):
        """Return shell current directory list dir"""
        return os.listdir(getcwd())

    def iscallable(self, objtxt):
        """Is object callable?"""
        obj, valid = self._eval(objtxt)
        if valid:
            return callable(obj)

    def get_arglist(self, objtxt):
        """Get func/method argument list"""
        obj, valid = self._eval(objtxt)
        if valid:
            return getargtxt(obj)

    def get__doc__(self, objtxt):
        """Get object __doc__"""
        obj, valid = self._eval(objtxt)
        if valid:
            return obj.__doc__

    def get_doc(self, objtxt):
        """Get object documentation dictionary"""
        obj, valid = self._eval(objtxt)
        if valid:
            return getdoc(obj)

    def get_source(self, objtxt):
        """Get object source"""
        obj, valid = self._eval(objtxt)
        if valid:
            return getsource(obj)

    def is_defined(self, objtxt, force_import=False):
        """Return True if object is defined"""
        return self.interpreter.is_defined(objtxt, force_import)
Example #30
0
class SphinxThread(QThread):
    """
    A worker thread for handling rich text rendering.

    Parameters
    ----------
    doc : str or dict
        A string containing a raw rst text or a dict containing
        the doc string components to be rendered.
        See spyderlib.utils.dochelpers.getdoc for description.
    context : dict
        A dict containing the substitution variables for the
        layout template
    html_text_no_doc : unicode
        Text to be rendered if doc string cannot be extracted.
    math_option : bool
        Use LaTeX math rendering.

    """
    # Signals
    error_msg = Signal(str)
    html_ready = Signal(str)

    def __init__(self, html_text_no_doc=''):
        super(SphinxThread, self).__init__()
        self.doc = None
        self.context = None
        self.html_text_no_doc = html_text_no_doc
        self.math_option = False

    def render(self, doc, context=None, math_option=False, img_path=''):
        """Start thread to render a given documentation"""
        # If the thread is already running wait for it to finish before
        # starting it again.
        if self.wait():
            self.doc = doc
            self.context = context
            self.math_option = math_option
            self.img_path = img_path
            # This causes run() to be executed in separate thread
            self.start()

    def run(self):
        html_text = self.html_text_no_doc
        doc = self.doc
        if doc is not None:
            if type(doc) is dict and 'docstring' in doc.keys():
                try:
                    context = generate_context(name=doc['name'],
                                               argspec=doc['argspec'],
                                               note=doc['note'],
                                               math=self.math_option,
                                               img_path=self.img_path)
                    html_text = sphinxify(doc['docstring'], context)
                    if doc['docstring'] == '':
                        html_text += '<div class="hr"></div>'
                        html_text += self.html_text_no_doc

                except Exception as error:
                    self.error_msg.emit(to_text_string(error))
                    return
            elif self.context is not None:
                try:
                    html_text = sphinxify(doc, self.context)
                except Exception as error:
                    self.error_msg.emit(to_text_string(error))
                    return
        self.html_ready.emit(html_text)