class ExternalConsole(PluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    location = Qt.RightDockWidgetArea
    def __init__(self, parent, commands=None):
        self.commands = commands
        self.tabwidget = None
        self.menu_actions = None
        self.docviewer = None
        self.historylog = None
        
        self.shells = []
        self.filenames = []
        self.icons = []
        
        PluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        self.close_button = create_toolbutton(self.tabwidget,
                                          icon=get_icon("fileclose.png"),
                                          triggered=self.close_console,
                                          tip=self.tr("Close current console"))
        self.tabwidget.setCornerWidget(self.close_button)
        layout.addWidget(self.tabwidget)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icon)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)
        
    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog
        
    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.docviewer = docviewer
        
    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        from spyderlib.widgets.externalshell.pythonshell import ExtPyQsciShell
        def execute(index):
            shell = self.tabwidget.widget(index).shell
            if isinstance(shell, ExtPyQsciShell):
                self.tabwidget.setCurrentIndex(index)
                shell.execute_lines(unicode(lines))
                shell.setFocus()
                return True
        # Find the Python shell, starting with current widget:
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            # No shell!
            return
        if not execute(current_index):
            for index in self.tabwidget.count():
                execute(index)
        
    def start(self, fname, wdir=None, ask_for_arguments=False,
              interact=False, debug=False, python=True):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(self, self.get_widget_title(),
                        self.tr("%1 is already running in a separate process.\n"
                                "Do you want to kill the process before starting "
                                "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        if python:
            shell = ExternalPythonShell(self, fname, wdir, self.commands,
                                        interact, debug, path=self.main.path)
        else:
            shell = ExternalSystemShell(self, wdir)
        shell.shell.set_font( get_font(self.ID) )
        shell.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        shell.shell.set_calltips( CONF.get(self.ID, 'calltips') )
        shell.shell.set_codecompletion( CONF.get(self.ID,
                                                 'autocompletion/enabled') )
        shell.shell.set_codecompletion_enter(CONF.get(self.ID,
                                                 'autocompletion/enter-key'))
        if python:
            shell.shell.set_docviewer(self.docviewer)
        self.historylog.add_history(shell.shell.history_filename)
        self.connect(shell.shell, SIGNAL('append_to_history(QString,QString)'),
                     self.historylog.append_to_history)
        self.connect(shell.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shell.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                name = "Python"
                icon = get_icon('python.png')
            else:
                name = osp.basename(fname)
                icon = get_icon('run.png')
        else:
            name = "Command Window"
            icon = get_icon('cmdprompt.png')
        self.shells.insert(index, shell)
        self.filenames.insert(index, fname)
        self.icons.insert(index, icon)
        if index is None:
            index = self.tabwidget.addTab(shell, name)
        else:
            self.tabwidget.insertTab(index, shell, name)
        
        self.connect(shell, SIGNAL("started()"),
                     lambda sid=id(shell): self.process_started(sid))
        self.connect(shell, SIGNAL("finished()"),
                     lambda sid=id(shell): self.process_finished(sid))
        self.find_widget.set_editor(shell.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        
        # Start process and give focus to console
        shell.start(ask_for_arguments)
        shell.shell.setFocus()
        
    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, self.icons[index])
        
    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, get_icon('terminated.png'))
        
    def get_widget_title(self):
        """Return widget title"""
        return self.tr('External console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def set_actions(self):
        """Setup actions"""
        interpreter_action = create_action(self,
                            self.tr("Open &interpreter"), None,
                            'python.png', self.tr("Open a Python interpreter"),
                            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &command shell")
            tip = self.tr("Open a shell window inside Spyder")
        console_action = create_action(self, text, None, 'cmdprompt.png', tip,
                            triggered=self.open_console)
        run_action = create_action(self,
                            self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
                            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self, self.tr("Code completion"),
                            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'autocompletion/enabled') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                  'autocompletion/enter-key') )
        singletab_action = create_action(self,
                            self.tr("One tab per script"),
                            toggled=self.toggle_singletab)
        singletab_action.setChecked( CONF.get(self.ID, 'single_tab') )
        self.menu_actions = [interpreter_action, run_action, None,
                             font_action, wrap_action, calltips_action,
                             codecompletion_action, codecompenter_action,
                             singletab_action]
        if console_action:
            self.menu_actions.insert(1, console_action)
        return (self.menu_actions, None)
        
    def open_interpreter(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False)
        
    def open_console(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False, python=False)
        
    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      self.tr("Run Python script"), os.getcwdu(),
                      self.tr("Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(unicode(filename), None, False, False, False)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)
        
    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)
        
    def closing(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget().shell
            editor.setFocus()
        else:
            editor = None
        self.find_widget.set_editor(editor)
    
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int)"),
                      osp.abspath(fname), int(lnb))
            
            
    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )
        
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or \
           ( source.hasText() and self.__is_python_script(source.text()) ):
            event.acceptProposedAction()            
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasText():
            self.start(source.text())
        elif source.hasUrls():
            files = mimedata2url(source)
            for fname in files:
                if self.__is_python_script(fname):
                    self.start(fname)
        event.acceptProposedAction()
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage

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

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

        SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()

        self.set_default_color_scheme()

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

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

        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)

        layout.addWidget(self.find_widget)

        self.setLayout(layout)

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

    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('history.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.tabwidget.currentWidget()

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

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

    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(self,
                                       _("History..."),
                                       None,
                                       'history.png',
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self,
                                    _("&Font..."),
                                    None,
                                    'font.png',
                                    _("Set shell 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.menu_actions = [history_action, font_action, self.wrap_action]
        return self.menu_actions

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

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

    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()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)

    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)

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

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

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

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

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

    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 change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(), self,
                                          _("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            self.set_plugin_font(font)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
Exemple #3
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    CONF_SECTION = 'historylog'
    CONFIGWIDGET_CLASS = HistoryConfigPage
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        self.wrap_action = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)

        # Initialize plugin
        self.initialize_plugin()
        
        self.set_default_color_scheme()
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=_("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        self.register_widget_shortcuts("Editor", self.find_widget)
        
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return _('History log')
    
    def get_plugin_icon(self):
        """Return widget icon"""
        return get_icon('history.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.tabwidget.currentWidget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
        
    def get_plugin_actions(self):
        """Return a list of actions related to plugin"""
        history_action = create_action(self, _("History..."),
                                       None, 'history.png',
                                       _("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self, _("&Font..."), None,
                                    'font.png', _("Set shell 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.menu_actions = [history_action, font_action, self.wrap_action]
        return self.menu_actions

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

    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()
        wrap_n = 'wrap'
        wrap_o = self.get_option(wrap_n)
        self.wrap_action.setChecked(wrap_o)
        for editor in self.editors:
            if font_n in options:
                scs = color_scheme_o if color_scheme_n in options else None
                editor.set_font(font_o, scs)
            elif color_scheme_n in options:
                editor.set_color_scheme(color_scheme_o)
            if wrap_n in options:
                editor.toggle_wrap_mode(wrap_o)
        
    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)
        
    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode_from_fs(filename)
        if filename in self.filenames:
            return
        editor = codeeditor.CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False, language=language,
                            scrollflagarea=False)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        color_scheme = get_color_scheme(self.get_option('color_scheme_name'))
        editor.set_font( self.get_plugin_font(), color_scheme )
        editor.toggle_wrap_mode( self.get_option('wrap') )

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')
        
        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)
        
    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        if not is_text_string(filename): # filename is a QString
            filename = to_text_string(filename.toUtf8(), 'utf-8')
        command = to_text_string(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        if self.get_option('go_to_eof'):
            self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
        
    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 change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(self.get_plugin_font(),
                       self, _("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            self.set_plugin_font(font)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        self.set_option('wrap', checked)
Exemple #4
0
class HistoryLog(PluginWidget):
    """
    History log widget
    """
    ID = 'historylog'
    location = Qt.RightDockWidgetArea

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

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

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

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

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

        self.setLayout(layout)

    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)

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

    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode(filename)
        if filename in self.filenames:
            return
        editor = QsciEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False,
                            language=language,
                            code_folding=True)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        editor.set_font(get_font(self.ID))
        editor.toggle_wrap_mode(CONF.get(self.ID, 'wrap'))

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

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

    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        filename, command = encoding.to_unicode(filename), unicode(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)

    def get_widget_title(self):
        """Return widget title"""
        return self.tr('History log')

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

    def set_actions(self):
        """Setup actions"""
        history_action = create_action(self,
                                       self.tr("History..."),
                                       None,
                                       'history.png',
                                       self.tr("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self,
                                    self.tr("&Font..."),
                                    None,
                                    'font.png',
                                    self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self,
                                    self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(CONF.get(self.ID, 'wrap'))
        self.menu_actions = [history_action, font_action, wrap_action]
        return (self.menu_actions, None)

    def change_history_depth(self):
        "Change history max entries" ""
        depth, valid = QInputDialog.getInteger(
            self, self.tr('History'), self.tr('Maximum entries'),
            CONF.get(self.ID, 'max_entries'), 10, 10000)
        if valid:
            CONF.set(self.ID, 'max_entries', depth)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                          self.tr("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            set_font(font, self.ID)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)

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

    def refresh(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
Exemple #5
0
class HistoryLog(SpyderPluginWidget):
    """
    History log widget
    """
    ID = 'historylog'
    def __init__(self, parent):
        self.tabwidget = None
        self.menu_actions = None
        self.dockviewer = None
        
        self.editors = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        layout.addWidget(self.tabwidget)

        # Menu as corner widget
        options_button = create_toolbutton(self, text=self.tr("Options"),
                                           icon=get_icon('tooloptions.png'))
        options_button.setPopupMode(QToolButton.InstantPopup)
        menu = QMenu(self)
        add_actions(menu, self.menu_actions)
        options_button.setMenu(menu)
        self.tabwidget.setCornerWidget(options_button)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return self.tr('History log')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget()
        else:
            editor = None
        self.find_widget.set_editor(editor)
        
    def get_plugin_actions(self):
        """Setup actions"""
        history_action = create_action(self, self.tr("History..."),
                                       None, 'history.png',
                                       self.tr("Set history maximum entries"),
                                       triggered=self.change_history_depth)
        font_action = create_action(self, self.tr("&Font..."), None,
                                    'font.png', self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self, self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        self.menu_actions = [history_action, font_action, wrap_action]
        return (self.menu_actions, None)
        
    #------ Private API --------------------------------------------------------
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        editor = self.editors.pop(index_from)
        icon = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.editors.insert(index_to, editor)
        self.icons.insert(index_to, icon)
        
    #------ Public API ---------------------------------------------------------
    def add_history(self, filename):
        """
        Add new history tab
        Slot for SIGNAL('add_history(QString)') emitted by shell instance
        """
        filename = encoding.to_unicode(filename)
        if filename in self.filenames:
            return
        editor = CodeEditor(self)
        if osp.splitext(filename)[1] == '.py':
            language = 'py'
            icon = get_icon('python.png')
        else:
            language = 'bat'
            icon = get_icon('cmdprompt.png')
        editor.setup_editor(linenumbers=False, language=language,
                            code_folding=True, scrollflagarea=False)
        self.connect(editor, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        editor.setReadOnly(True)
        editor.set_font( get_font(self.ID) )
        editor.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )

        text, _ = encoding.read(filename)
        editor.set_text(text)
        editor.set_cursor_position('eof')
        
        self.editors.append(editor)
        self.filenames.append(filename)
        self.icons.append(icon)
        index = self.tabwidget.addTab(editor, osp.basename(filename))
        self.find_widget.set_editor(editor)
        self.tabwidget.setTabToolTip(index, filename)
        self.tabwidget.setTabIcon(index, icon)
        self.tabwidget.setCurrentIndex(index)
        
    def append_to_history(self, filename, command):
        """
        Append an entry to history filename
        Slot for SIGNAL('append_to_history(QString,QString)')
        emitted by shell instance
        """
        filename, command = encoding.to_unicode(filename), unicode(command)
        index = self.filenames.index(filename)
        self.editors[index].append(command)
        self.editors[index].set_cursor_position('eof')
        self.tabwidget.setCurrentIndex(index)
        
    def change_history_depth(self):
        "Change history max entries"""
        depth, valid = QInputDialog.getInteger(self, self.tr('History'),
                                       self.tr('Maximum entries'),
                                       CONF.get(self.ID, 'max_entries'),
                                       10, 10000)
        if valid:
            CONF.set(self.ID, 'max_entries', depth)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for editor in self.editors:
                editor.set_font(font)
            set_font(font, self.ID)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for editor in self.editors:
            editor.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
class ExternalConsole(SpyderPluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    def __init__(self, parent, light_mode):
        self.light_mode = light_mode
        self.commands = []
        self.tabwidget = None
        self.menu_actions = None
        self.inspector = None
        self.historylog = None
        self.variableexplorer = None # variable explorer plugin
        
        self.ipython_count = 0
        self.python_count = 0
        self.terminal_count = 0
        
        if CONF.get(self.ID, 'ipython_options', None) is None:
            CONF.set(self.ID, 'ipython_options',
                     self.get_default_ipython_options())
        
        self.shells = []
        self.filenames = []
        self.icons = []
        
        SpyderPluginWidget.__init__(self, parent)
        
        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh_plugin)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
                     
        self.tabwidget.set_close_function(self.close_console)

        layout.addWidget(self.tabwidget)
        
        # Find/replace widget
        self.find_widget = FindReplace(self)
        self.find_widget.hide()
        layout.addWidget(self.find_widget)
        
        self.setLayout(layout)
            
        # Accepting drops
        self.setAcceptDrops(True)
        
    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icons = self.icons.pop(index_from)
        
        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icons)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)
        
    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog
        
    def set_inspector(self, inspector):
        """Bind inspector instance to this console"""
        self.inspector = inspector
        inspector.set_external_console(self)
        
    def set_variableexplorer(self, variableexplorer):
        """Set variable explorer plugin"""
        self.variableexplorer = variableexplorer
        
    def __find_python_shell(self):
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            return
        from spyderlib.widgets.externalshell import pythonshell
        for index in [current_index]+range(self.tabwidget.count()):
            shellwidget = self.tabwidget.widget(index)
            if isinstance(shellwidget, pythonshell.ExternalPythonShell):
                self.tabwidget.setCurrentIndex(index)
                return shellwidget
                
    def get_running_python_shell(self):
        """
        Called by object inspector to retrieve a running Python shell instance
        """
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            return
        from spyderlib.widgets.externalshell import pythonshell
        shellwidgets = [self.tabwidget.widget(index)
                        for index in range(self.tabwidget.count())]
        shellwidgets = [_w for _w in shellwidgets
                        if isinstance(_w, pythonshell.ExternalPythonShell) \
                        and _w.is_running()]
        if shellwidgets:
            # First, iterate on interpreters only:
            for shellwidget in shellwidgets:
                if shellwidget.is_interpreter():
                    return shellwidget.shell
            else:
                return shellwidgets[0].shell
        
    def run_script_in_current_shell(self, filename):
        """Run script in current shell, if any"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None and shellwidget.is_running():
            line = "runfile(r'%s')" % unicode(filename)
            shellwidget.shell.execute_lines(line)
            shellwidget.shell.setFocus()
            
    def set_current_shell_working_directory(self, directory):
        """Set current shell working directory"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None and shellwidget.is_running():
            shellwidget.shell.set_cwd(unicode(directory))
        
    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        shellwidget = self.__find_python_shell()
        if shellwidget is not None:
            shellwidget.shell.execute_lines(unicode(lines))
            shellwidget.shell.setFocus()
        
    def start(self, fname, wdir=None, ask_for_arguments=False,
              interact=False, debug=False, python=True,
              ipython=False, arguments=None, current=False):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(self, self.get_plugin_title(),
                        self.tr("%1 is already running in a separate process.\n"
                                "Do you want to kill the process before starting "
                                "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        pythonpath = self.main.get_spyder_pythonpath()
        if python:
            umd_enabled = CONF.get(self.ID, 'umd/enabled')
            umd_namelist = CONF.get(self.ID, 'umd/namelist')
            umd_verbose = CONF.get(self.ID, 'umd/verbose')
            shellwidget = ExternalPythonShell(self, fname, wdir, self.commands,
                           interact, debug, path=pythonpath, ipython=ipython,
                           arguments=arguments, stand_alone=self.light_mode,
                           umd_enabled=umd_enabled, umd_namelist=umd_namelist,
                           umd_verbose=umd_verbose)
            if self.variableexplorer is not None:
                self.variableexplorer.add_shellwidget(shellwidget)
        else:
            shellwidget = ExternalSystemShell(self, wdir, path=pythonpath)
        
        # Code completion / calltips
        case_sensitive = CONF.get(self.ID, 'codecompletion/case-sensitivity')
        show_single = CONF.get(self.ID, 'codecompletion/select-single')
        from_document = CONF.get(self.ID, 'codecompletion/from-document')
        shellwidget.shell.setup_code_completion(case_sensitive, show_single,
                                                 from_document)
        
        shellwidget.shell.setMaximumBlockCount( CONF.get(self.ID,
                                                          'max_line_count') )
        shellwidget.shell.set_font( get_font(self.ID) )
        shellwidget.shell.toggle_wrap_mode( CONF.get(self.ID, 'wrap') )
        shellwidget.shell.set_calltips( CONF.get(self.ID, 'calltips') )
        shellwidget.shell.set_codecompletion_auto( CONF.get(self.ID,
                                                 'codecompletion/auto') )
        shellwidget.shell.set_codecompletion_enter(CONF.get(self.ID,
                                                 'codecompletion/enter-key'))
        if python and self.inspector is not None:
            shellwidget.shell.set_inspector(self.inspector)
        if self.historylog is not None:
            self.historylog.add_history(shellwidget.shell.history_filename)
            self.connect(shellwidget.shell,
                         SIGNAL('append_to_history(QString,QString)'),
                         self.historylog.append_to_history)
        self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                if ipython:
                    self.ipython_count += 1
                    tab_name = "IPython %d" % self.ipython_count
                    tab_icon1 = get_icon('ipython.png')
                    tab_icon2 = get_icon('ipython_t.png')
                else:
                    self.python_count += 1
                    tab_name = "Python %d" % self.python_count
                    tab_icon1 = get_icon('python.png')
                    tab_icon2 = get_icon('python_t.png')
            else:
                tab_name = osp.basename(fname)
                tab_icon1 = get_icon('run.png')
                tab_icon2 = get_icon('terminated.png')
        else:
            fname = id(shellwidget)
            if os.name == 'nt':
                tab_name = self.tr("Command Window")
            else:
                tab_name = self.tr("Terminal")
            self.terminal_count += 1
            tab_name += (" %d" % self.terminal_count)
            tab_icon1 = get_icon('cmdprompt.png')
            tab_icon2 = get_icon('cmdprompt_t.png')
        self.shells.insert(index, shellwidget)
        self.filenames.insert(index, fname)
        self.icons.insert(index, (tab_icon1, tab_icon2))
        if index is None:
            index = self.tabwidget.addTab(shellwidget, tab_name)
        else:
            self.tabwidget.insertTab(index, shellwidget, tab_name)
        
        self.connect(shellwidget, SIGNAL("started()"),
                     lambda sid=id(shellwidget): self.process_started(sid))
        self.connect(shellwidget, SIGNAL("finished()"),
                     lambda sid=id(shellwidget): self.process_finished(sid))
        self.find_widget.set_editor(shellwidget.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()
        
        self.toggle_icontext(CONF.get(self.ID, 'show_icontext'))
        
        # Start process and give focus to console
        shellwidget.start(ask_for_arguments)
        shellwidget.shell.setFocus()
        
    #------ Private API --------------------------------------------------------
    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                icon, _icon = self.icons[index]
                self.tabwidget.setTabIcon(index, icon)
                if self.inspector is not None:
                    self.inspector.set_shell(shell.shell)
                if self.variableexplorer is not None:
                    self.variableexplorer.add_shellwidget(shell)
        
    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                _icon, icon = self.icons[index]
                self.tabwidget.setTabIcon(index, icon)
                if self.inspector is not None:
                    self.inspector.shell_terminated(shell.shell)
        if self.variableexplorer is not None:
            self.variableexplorer.remove_shellwidget(shell_id)
        
    #------ SpyderPluginWidget API ---------------------------------------------    
    def get_plugin_title(self):
        """Return widget title"""
        return self.tr('Console')
    
    def get_focus_widget(self):
        """
        Return the widget to give focus to when
        this plugin's dockwidget is raised on top-level
        """
        return self.tabwidget.currentWidget()
        
    def get_plugin_actions(self):
        """Setup actions"""
        interpreter_action = create_action(self,
                            self.tr("Open &interpreter"), None,
                            'python.png', self.tr("Open a Python interpreter"),
                            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &terminal")
            tip = self.tr("Open a terminal window inside Spyder")
        console_action = create_action(self, text, None, 'cmdprompt.png', tip,
                            triggered=self.open_terminal)
        run_action = create_action(self,
                            self.tr("&Run..."), None,
                            'run_small.png', self.tr("Run a Python script"),
                            triggered=self.run_script)
        umd_action = create_action(self,
                self.tr("Force modules to be completely reloaded"),
                tip=self.tr("Force Python to reload modules imported when "
                            "executing a script in the external console "
                            "with the 'runfile' function (UMD: User Module "
                            "Deleter)"),
                toggled=self.toggle_umd)
        umd_action.setChecked( CONF.get(self.ID, 'umd/enabled') )
        
        python_startup = CONF.get(self.ID, 'open_python_at_startup', None)
        ipython_startup = CONF.get(self.ID, 'open_ipython_at_startup', None)
        if ipython_startup is None:
            ipython_startup = programs.is_module_installed("IPython")
            CONF.set(self.ID, 'open_ipython_at_startup', ipython_startup)
        if python_startup is None:
            python_startup = not ipython_startup
            CONF.set(self.ID, 'open_python_at_startup', python_startup)
        python_startup_action = create_action(self,
                self.tr("Open a Python interpreter at startup"),
                toggled=lambda checked:
                CONF.set(self.ID, 'open_python_at_startup', checked))
        python_startup_action.setChecked(python_startup)
        ipython_startup_action = create_action(self,
                self.tr("Open a IPython interpreter at startup"),
                toggled=lambda checked:
                CONF.set(self.ID, 'open_ipython_at_startup', checked))
        ipython_startup_action.setChecked(ipython_startup)
        
        buffer_action = create_action(self,
                            self.tr("Buffer..."), None,
                            tip=self.tr("Set maximum line count"),
                            triggered=self.change_max_line_count)
        font_action = create_action(self,
                            self.tr("&Font..."), None,
                            'font.png', self.tr("Set shell font style"),
                            triggered=self.change_font)
        wrap_action = create_action(self,
                            self.tr("Wrap lines"),
                            toggled=self.toggle_wrap_mode)
        wrap_action.setChecked( CONF.get(self.ID, 'wrap') )
        calltips_action = create_action(self, self.tr("Balloon tips"),
                            toggled=self.toggle_calltips)
        calltips_action.setChecked( CONF.get(self.ID, 'calltips') )
        codecompletion_action = create_action(self,
                                          self.tr("Automatic code completion"),
                                          toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked( CONF.get(self.ID,
                                                   'codecompletion/auto') )
        codecompenter_action = create_action(self,
                                    self.tr("Enter key selects completion"),
                                    toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked( CONF.get(self.ID,
                                                  'codecompletion/enter-key') )
        singletab_action = create_action(self,
                            self.tr("One tab per script"),
                            toggled=self.toggle_singletab)
        singletab_action.setChecked( CONF.get(self.ID, 'single_tab') )
        icontext_action = create_action(self, self.tr("Show icons and text"),
                                        toggled=self.toggle_icontext)
        icontext_action.setChecked( CONF.get(self.ID, 'show_icontext') )
        
        self.menu_actions = [interpreter_action, console_action, run_action,
                             umd_action, python_startup_action,
                             ipython_startup_action, None,
                             buffer_action, font_action, wrap_action,
                             calltips_action, codecompletion_action,
                             codecompenter_action, singletab_action,
                             icontext_action]
        
        ipython_action = create_action(self,
                            self.tr("Open IPython interpreter"), None,
                            'ipython.png',
                            self.tr("Open an IPython interpreter"),
                            triggered=self.open_ipython)
        ipython_options_action = create_action(self,
                            self.tr("IPython interpreter options..."), None,
                            tip=self.tr("Set IPython interpreter "
                                        "command line arguments"),
                            triggered=self.set_ipython_options)
        if programs.is_module_installed("IPython"):
            self.menu_actions.insert(3, ipython_options_action)
            self.menu_actions.insert(1, ipython_action)
        
        return (self.menu_actions, None)
    
    def open_interpreter_at_startup(self):
        """Open an interpreter at startup, IPython if module is available"""
        if CONF.get(self.ID, 'open_ipython_at_startup') \
           and programs.is_module_installed("IPython"):
            self.open_ipython()
        if CONF.get(self.ID, 'open_python_at_startup'):
            self.open_interpreter()
        
    def closing_plugin(self, cancelable=False):
        """Perform actions before parent main window is closed"""
        for shell in self.shells:
            shell.process.kill()
        return True
    
    def refresh_plugin(self):
        """Refresh tabwidget"""
        if self.tabwidget.count():
            editor = self.tabwidget.currentWidget().shell
            editor.setFocus()
        else:
            editor = None
        self.find_widget.set_editor(editor)
    
    #------ Public API ---------------------------------------------------------
    def open_interpreter(self, wdir=None):
        """Open interpreter"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=True)
        
    def get_default_ipython_options(self):
        """Return default ipython command line arguments"""
        default_options = []
        if programs.is_module_installed('matplotlib'):
            default_options.append("-pylab")
        else:
            default_options.append("-q4thread")
        default_options.append("-colors LightBG")
        default_options.append("-xmode Plain")
        for editor_name in ("scite", "gedit"):
            real_name = programs.get_nt_program_name(editor_name)
            if programs.is_program_installed(real_name):
                default_options.append("-editor "+real_name)
                break
        return " ".join(default_options)
        
    def open_ipython(self, wdir=None):
        """Open IPython"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=True, ipython=True,
                   arguments=CONF.get(self.ID, 'ipython_options', ""))
        
    def open_terminal(self, wdir=None):
        """Open terminal"""
        if wdir is None:
            wdir = os.getcwdu()
        self.start(fname=None, wdir=unicode(wdir), ask_for_arguments=False,
                   interact=True, debug=False, python=False)
        
    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(self,
                      self.tr("Run Python script"), os.getcwdu(),
                      self.tr("Python scripts")+" (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(fname=unicode(filename), wdir=None,
                       ask_for_arguments=False, interact=False, debug=False)
        
    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID),
                       self, self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)
        
    def change_max_line_count(self):
        "Change maximum line count"""
        mlc, valid = QInputDialog.getInteger(self, self.tr('Buffer'),
                                           self.tr('Maximum line count'),
                                           CONF.get(self.ID, 'max_line_count'),
                                           10, 1000000)
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.setMaximumBlockCount(mlc)
            CONF.set(self.ID, 'max_line_count', mlc)
            
    def set_ipython_options(self):
        """Set IPython interpreter arguments"""
        arguments, valid = QInputDialog.getText(self,
                      self.tr('IPython'),
                      self.tr('IPython command line options:\n'
                              '(Qt4 support: -q4thread)\n'
                              '(Qt4 and matplotlib support: -q4thread -pylab)'),
                      QLineEdit.Normal, CONF.get(self.ID, 'ipython_options'))
        if valid:
            CONF.set(self.ID, 'ipython_options', unicode(arguments))
        
    def toggle_umd(self, checked):
        """Toggle UMD"""
        CONF.set(self.ID, 'umd/enabled', checked)
        if checked and self.isVisible():
            QMessageBox.warning(self, self.get_plugin_title(),
                self.tr("This option will enable the User Module Deleter (UMD) "
                        "in Python/IPython interpreters. UMD forces Python to "
                        "reload deeply modules during import when running a "
                        "Python script using the Spyder's builtin function "
                        "<b>runfile</b>."
                        "<br><br><b>1.</b> UMD may require to restart the "
                        "Python interpreter in which it will be called "
                        "(otherwise only newly imported modules will be "
                        "reloaded when executing scripts)."
                        "<br><br><b>2.</b> If errors occur when re-running a "
                        "PyQt-based program, please check that the Qt objects "
                        "are properly destroyed (e.g. you may have to use the "
                        "attribute <b>Qt.WA_DeleteOnClose</b> on your main "
                        "window, using the <b>setAttribute</b> method)"),
                QMessageBox.Ok)
            
    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)
            
    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)
            
    def toggle_codecompletion(self, checked):
        """Toggle automatic code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_auto(checked)
        CONF.set(self.ID, 'codecompletion/auto', checked)
            
    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'codecompletion/enter-key', checked)
        
    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)

    def toggle_icontext(self, checked):
        """Toggle icon text"""
        CONF.set(self.ID, 'show_icontext', checked)
        if self.tabwidget is None:
            return
        for index in range(self.tabwidget.count()):
            for widget in self.tabwidget.widget(index).get_toolbar_buttons():
                if checked:
                    widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
                else:
                    widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
                
    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
                      osp.abspath(fname), int(lnb), '')
            
    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )
        
    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls():
            if mimedata2url(source):
                pathlist = mimedata2url(source)
                shellwidget = self.tabwidget.currentWidget()
                if all([self.__is_python_script(qstr) for qstr in pathlist]):
                    event.acceptProposedAction()
                elif shellwidget is None or not shellwidget.is_running():
                    event.ignore()
                else:
                    event.acceptProposedAction()
            else:
                event.ignore()
        elif source.hasText():
            event.acceptProposedAction()            
            
    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        shellwidget = self.tabwidget.currentWidget()
        if source.hasText():
            qstr = source.text()
            if self.__is_python_script(qstr):
                self.start(qstr, ask_for_arguments=True)
            elif shellwidget:
                shellwidget.shell.insert_text(qstr)
        elif source.hasUrls():
            pathlist = mimedata2url(source)
            if all([self.__is_python_script(qstr) for qstr in pathlist]):
                for fname in pathlist:
                    self.start(fname, ask_for_arguments=True)
            elif shellwidget:
                shellwidget.shell.drop_pathlist(pathlist)
        event.acceptProposedAction()
Exemple #7
0
class ExternalConsole(PluginWidget):
    """
    Console widget
    """
    ID = 'external_shell'
    location = Qt.RightDockWidgetArea

    def __init__(self, parent, commands=None):
        self.commands = commands
        self.tabwidget = None
        self.menu_actions = None
        self.docviewer = None
        self.historylog = None

        self.shells = []
        self.filenames = []
        self.icons = []

        PluginWidget.__init__(self, parent)

        layout = QVBoxLayout()
        self.tabwidget = Tabs(self, self.menu_actions)
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
                     self.refresh)
        self.connect(self.tabwidget, SIGNAL("close_tab(int)"),
                     self.tabwidget.removeTab)
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
                     self.move_tab)
        self.close_button = create_toolbutton(
            self.tabwidget,
            icon=get_icon("fileclose.png"),
            triggered=self.close_console,
            tip=self.tr("Close current console"))
        self.tabwidget.setCornerWidget(self.close_button)
        layout.addWidget(self.tabwidget)

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

        self.setLayout(layout)

        # Accepting drops
        self.setAcceptDrops(True)

    def move_tab(self, index_from, index_to):
        """
        Move tab (tabs themselves have already been moved by the tabwidget)
        """
        filename = self.filenames.pop(index_from)
        shell = self.shells.pop(index_from)
        icon = self.icons.pop(index_from)

        self.filenames.insert(index_to, filename)
        self.shells.insert(index_to, shell)
        self.icons.insert(index_to, icon)

    def close_console(self, index=None):
        if not self.tabwidget.count():
            return
        if index is None:
            index = self.tabwidget.currentIndex()
        self.tabwidget.widget(index).close()
        self.tabwidget.removeTab(index)
        self.filenames.pop(index)
        self.shells.pop(index)
        self.icons.pop(index)

    def set_historylog(self, historylog):
        """Bind historylog instance to this console"""
        self.historylog = historylog

    def set_docviewer(self, docviewer):
        """Bind docviewer instance to this console"""
        self.docviewer = docviewer

    def execute_python_code(self, lines):
        """Execute Python code in an already opened Python interpreter"""
        from spyderlib.widgets.externalshell.pythonshell import ExtPyQsciShell

        def execute(index):
            shell = self.tabwidget.widget(index).shell
            if isinstance(shell, ExtPyQsciShell):
                self.tabwidget.setCurrentIndex(index)
                shell.execute_lines(unicode(lines))
                shell.setFocus()
                return True

        # Find the Python shell, starting with current widget:
        current_index = self.tabwidget.currentIndex()
        if current_index == -1:
            # No shell!
            return
        if not execute(current_index):
            for index in self.tabwidget.count():
                execute(index)

    def start(self,
              fname,
              wdir=None,
              ask_for_arguments=False,
              interact=False,
              debug=False,
              python=True):
        """Start new console"""
        # Note: fname is None <=> Python interpreter
        fname = unicode(fname) if isinstance(fname, QString) else fname
        wdir = unicode(wdir) if isinstance(wdir, QString) else wdir

        if fname is not None and fname in self.filenames:
            index = self.filenames.index(fname)
            if CONF.get(self.ID, 'single_tab'):
                old_shell = self.shells[index]
                if old_shell.is_running():
                    answer = QMessageBox.question(
                        self, self.get_widget_title(),
                        self.tr(
                            "%1 is already running in a separate process.\n"
                            "Do you want to kill the process before starting "
                            "a new one?").arg(osp.basename(fname)),
                        QMessageBox.Yes | QMessageBox.Cancel)
                    if answer == QMessageBox.Yes:
                        old_shell.process.kill()
                        old_shell.process.waitForFinished()
                    else:
                        return
                self.close_console(index)
        else:
            index = 0

        # Creating a new external shell
        if python:
            shell = ExternalPythonShell(self,
                                        fname,
                                        wdir,
                                        self.commands,
                                        interact,
                                        debug,
                                        path=self.main.path)
        else:
            shell = ExternalSystemShell(self, wdir)
        shell.shell.set_font(get_font(self.ID))
        shell.shell.toggle_wrap_mode(CONF.get(self.ID, 'wrap'))
        shell.shell.set_calltips(CONF.get(self.ID, 'calltips'))
        shell.shell.set_codecompletion(
            CONF.get(self.ID, 'autocompletion/enabled'))
        shell.shell.set_codecompletion_enter(
            CONF.get(self.ID, 'autocompletion/enter-key'))
        if python:
            shell.shell.set_docviewer(self.docviewer)
        self.historylog.add_history(shell.shell.history_filename)
        self.connect(shell.shell, SIGNAL('append_to_history(QString,QString)'),
                     self.historylog.append_to_history)
        self.connect(shell.shell, SIGNAL("go_to_error(QString)"),
                     self.go_to_error)
        self.connect(shell.shell, SIGNAL("focus_changed()"),
                     lambda: self.emit(SIGNAL("focus_changed()")))
        if python:
            if fname is None:
                name = "Python"
                icon = get_icon('python.png')
            else:
                name = osp.basename(fname)
                icon = get_icon('run.png')
        else:
            name = "Command Window"
            icon = get_icon('cmdprompt.png')
        self.shells.insert(index, shell)
        self.filenames.insert(index, fname)
        self.icons.insert(index, icon)
        if index is None:
            index = self.tabwidget.addTab(shell, name)
        else:
            self.tabwidget.insertTab(index, shell, name)

        self.connect(shell,
                     SIGNAL("started()"),
                     lambda sid=id(shell): self.process_started(sid))
        self.connect(shell,
                     SIGNAL("finished()"),
                     lambda sid=id(shell): self.process_finished(sid))
        self.find_widget.set_editor(shell.shell)
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
        self.tabwidget.setCurrentIndex(index)
        if self.dockwidget and not self.ismaximized:
            self.dockwidget.setVisible(True)
            self.dockwidget.raise_()

        # Start process and give focus to console
        shell.start(ask_for_arguments)
        shell.shell.setFocus()

    def process_started(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, self.icons[index])

    def process_finished(self, shell_id):
        for index, shell in enumerate(self.shells):
            if id(shell) == shell_id:
                self.tabwidget.setTabIcon(index, get_icon('terminated.png'))

    def get_widget_title(self):
        """Return widget title"""
        return self.tr('External console')

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

    def set_actions(self):
        """Setup actions"""
        interpreter_action = create_action(
            self,
            self.tr("Open &interpreter"),
            None,
            'python.png',
            self.tr("Open a Python interpreter"),
            triggered=self.open_interpreter)
        if os.name == 'nt':
            text = self.tr("Open &command prompt")
            tip = self.tr("Open a Windows command prompt")
        else:
            text = self.tr("Open &command shell")
            tip = self.tr("Open a shell window inside Spyder")
        console_action = create_action(self,
                                       text,
                                       None,
                                       'cmdprompt.png',
                                       tip,
                                       triggered=self.open_console)
        run_action = create_action(self,
                                   self.tr("&Run..."),
                                   None,
                                   'run_small.png',
                                   self.tr("Run a Python script"),
                                   triggered=self.run_script)
        font_action = create_action(self,
                                    self.tr("&Font..."),
                                    None,
                                    'font.png',
                                    self.tr("Set shell font style"),
                                    triggered=self.change_font)
        wrap_action = create_action(self,
                                    self.tr("Wrap lines"),
                                    toggled=self.toggle_wrap_mode)
        wrap_action.setChecked(CONF.get(self.ID, 'wrap'))
        calltips_action = create_action(self,
                                        self.tr("Balloon tips"),
                                        toggled=self.toggle_calltips)
        calltips_action.setChecked(CONF.get(self.ID, 'calltips'))
        codecompletion_action = create_action(
            self,
            self.tr("Code completion"),
            toggled=self.toggle_codecompletion)
        codecompletion_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enabled'))
        codecompenter_action = create_action(
            self,
            self.tr("Enter key selects completion"),
            toggled=self.toggle_codecompletion_enter)
        codecompenter_action.setChecked(
            CONF.get(self.ID, 'autocompletion/enter-key'))
        singletab_action = create_action(self,
                                         self.tr("One tab per script"),
                                         toggled=self.toggle_singletab)
        singletab_action.setChecked(CONF.get(self.ID, 'single_tab'))
        self.menu_actions = [
            interpreter_action, run_action, None, font_action, wrap_action,
            calltips_action, codecompletion_action, codecompenter_action,
            singletab_action
        ]
        if console_action:
            self.menu_actions.insert(1, console_action)
        return (self.menu_actions, None)

    def open_interpreter(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False)

    def open_console(self):
        """Open interpreter"""
        self.start(None, os.getcwdu(), False, True, False, python=False)

    def run_script(self):
        """Run a Python script"""
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
        filename = QFileDialog.getOpenFileName(
            self, self.tr("Run Python script"), os.getcwdu(),
            self.tr("Python scripts") + " (*.py ; *.pyw)")
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
        if filename:
            self.start(unicode(filename), None, False, False, False)

    def change_font(self):
        """Change console font"""
        font, valid = QFontDialog.getFont(get_font(self.ID), self,
                                          self.tr("Select a new font"))
        if valid:
            for index in range(self.tabwidget.count()):
                self.tabwidget.widget(index).shell.set_font(font)
            set_font(font, self.ID)

    def toggle_wrap_mode(self, checked):
        """Toggle wrap mode"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.toggle_wrap_mode(checked)
        CONF.set(self.ID, 'wrap', checked)

    def toggle_calltips(self, checked):
        """Toggle calltips"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_calltips(checked)
        CONF.set(self.ID, 'calltips', checked)

    def toggle_codecompletion(self, checked):
        """Toggle code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion(checked)
        CONF.set(self.ID, 'autocompletion/enabled', checked)

    def toggle_codecompletion_enter(self, checked):
        """Toggle Enter key for code completion"""
        if self.tabwidget is None:
            return
        for shell in self.shells:
            shell.shell.set_codecompletion_enter(checked)
        CONF.set(self.ID, 'autocompletion/enter-key', checked)

    def toggle_singletab(self, checked):
        """Toggle single tab mode"""
        CONF.set(self.ID, 'single_tab', checked)

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

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

    def go_to_error(self, text):
        """Go to error if relevant"""
        match = get_error_match(unicode(text))
        if match:
            fname, lnb = match.groups()
            self.emit(SIGNAL("edit_goto(QString,int)"), osp.abspath(fname),
                      int(lnb))

    #----Drag and drop
    def __is_python_script(self, qstr):
        """Is it a valid Python script?"""
        fname = unicode(qstr)
        return osp.isfile(fname) and \
               ( fname.endswith('.py') or fname.endswith('.pyw') )

    def dragEnterEvent(self, event):
        """Reimplement Qt method
        Inform Qt about the types of data that the widget accepts"""
        source = event.mimeData()
        if source.hasUrls() or \
           ( source.hasText() and self.__is_python_script(source.text()) ):
            event.acceptProposedAction()

    def dropEvent(self, event):
        """Reimplement Qt method
        Unpack dropped data and handle it"""
        source = event.mimeData()
        if source.hasText():
            self.start(source.text())
        elif source.hasUrls():
            files = mimedata2url(source)
            for fname in files:
                if self.__is_python_script(fname):
                    self.start(fname)
        event.acceptProposedAction()