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