class RichText(QWidget): """ WebView widget with find dialog """ def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def set_font(self, font, fixed_font=None): """Set font""" self.webview.set_font(font, fixed_font=fixed_font) def set_html(self, html_text, base_url): """Set html text""" self.webview.setHtml(html_text, base_url) def clear(self): self.set_html('', self.webview.url())
class RichText(QWidget): """ WebView widget with find dialog """ def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) if WEBENGINE: self.webview.web_widget.page().setBackgroundColor( QColor(MAIN_BG_COLOR)) else: self.webview.web_widget.setStyleSheet( "background:{}".format(MAIN_BG_COLOR)) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def set_font(self, font, fixed_font=None): """Set font""" self.webview.set_font(font, fixed_font=fixed_font) def set_html(self, html_text, base_url): """Set html text""" self.webview.setHtml(html_text, base_url) def clear(self): self.set_html('', self.webview.url())
def editor_find_replace_bot(base_editor_bot): editor_stack, qtbot = base_editor_bot text = ('spam bacon\n' 'spam sausage\n' 'spam egg') finfo = editor_stack.new('spam.py', 'utf-8', text) find_replace = FindReplace(None, enable_replace=True) editor_stack.set_find_widget(find_replace) find_replace.set_editor(finfo.editor) qtbot.addWidget(editor_stack) return editor_stack, finfo.editor, find_replace, qtbot
def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout)
class PlainText(QWidget): """ Read-only editor widget with find dialog """ # Signals focus_changed = Signal() def __init__(self, parent): QWidget.__init__(self, parent) self.editor = None # Read-only editor self.editor = codeeditor.CodeEditor(self) self.editor.setup_editor(linenumbers=False, language='py', scrollflagarea=False, edge_line=False) self.editor.focus_changed.connect(lambda: self.focus_changed.emit()) self.editor.setReadOnly(True) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.editor) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.editor) layout.addWidget(self.find_widget) self.setLayout(layout) def set_font(self, font, color_scheme=None): """Set font""" self.editor.set_font(font, color_scheme=color_scheme) def set_color_scheme(self, color_scheme): """Set color scheme""" self.editor.set_color_scheme(color_scheme) def set_text(self, text, is_code): self.editor.set_highlight_current_line(is_code) self.editor.set_occurrence_highlighting(is_code) if is_code: self.editor.set_language('py') else: self.editor.set_language(None) self.editor.set_text(text) self.editor.set_cursor_position('sof') def clear(self): self.editor.clear()
def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dimiss_error = False
class RichText(QWidget): """ WebView widget with find dialog """ sig_link_clicked = Signal(QUrl) def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) if WEBENGINE: self.webview.web_widget.page().setBackgroundColor( QColor(MAIN_BG_COLOR)) else: self.webview.web_widget.setStyleSheet( "background:{}".format(MAIN_BG_COLOR)) self.viewview.page().setLinkDelegationPolicy( QWebEnginePage.DelegateAllLinks) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() # Layout layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.webview.linkClicked.connect(self.sig_link_clicked) def set_font(self, font, fixed_font=None): """Set font""" self.webview.set_font(font, fixed_font=fixed_font) def set_html(self, html_text, base_url): """Set html text""" self.webview.setHtml(html_text, base_url) def load_url(self, url): if isinstance(url, QUrl): qurl = url else: qurl = QUrl(url) self.load(qurl) def clear(self): self.set_html('', self.webview.url())
def __init__(self, parent): self.tabwidget = None self.menu_actions = None self.dockviewer = None self.wrap_action = None self.editors = [] self.filenames = [] if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, self.menu_actions) options_button.setMenu(menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, name=None, plugin=None, parent=None, options=DEFAULT_OPTIONS): super().__init__(name, plugin, parent=parent, options=options) self._is_running = False self.home_url = None self.server = None # Widgets self.label = QLabel(_("Package:")) self.url_combo = UrlComboBox(self) self.webview = WebView(self, handle_links=self.get_option('handle_links')) self.find_widget = FindReplace(self) # Setup self.find_widget.set_editor(self.webview) self.find_widget.hide() self.url_combo.setMaxCount(self.get_option('max_history_entries')) tip = _('Write a package name here') self.url_combo.lineEdit().setPlaceholderText(tip) self.url_combo.lineEdit().setToolTip(tip) self.webview.setup() self.webview.set_zoom_factor(self.get_option('zoom_factor')) # Layout spacing = 10 layout = QVBoxLayout() layout.addWidget(self.webview) layout.addSpacing(spacing) layout.addWidget(self.find_widget) layout.addSpacing(int(spacing / 2)) self.setLayout(layout) # Signals self.url_combo.valid.connect( lambda x: self._handle_url_combo_activation()) self.webview.loadStarted.connect(self._start) self.webview.loadFinished.connect(self._finish) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self._change_url) if not WEBENGINE: self.webview.iconChanged.connect(self._handle_icon_change)
def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) logger.info("Initializing...") # Traceback MessageBox self.error_traceback = '' self.dismiss_error = False # Widgets self.dialog_manager = DialogManager() self.error_dlg = None self.shell = InternalShell( # TODO: Move to use SpyderWidgetMixin? parent=parent, namespace=self.get_conf('namespace', {}), commands=self.get_conf('commands', []), message=self.get_conf('message', ''), max_line_count=self.get_conf('max_line_count'), profile=self.get_conf('profile', False), multithreaded=self.get_conf('multithreaded', False), ) self.find_widget = FindReplace(self) # Setup self.setAcceptDrops(True) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.shell.toggle_wrap_mode(self.get_conf('wrap')) # Layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.shell.sig_help_requested.connect(self.sig_help_requested) self.shell.sig_exception_occurred.connect(self.handle_exception) self.shell.sig_focus_changed.connect(self.sig_focus_changed) self.shell.sig_go_to_error_requested.connect(self.go_to_error) self.shell.sig_redirect_stdio_requested.connect( self.sig_redirect_stdio_requested) self.shell.sig_refreshed.connect(self.sig_refreshed) self.shell.sig_show_status_requested.connect( lambda msg: self.sig_show_status_message.emit(msg, 0))
def __init__(self, parent, filename, actions=None, ini_message=None): """ Constructor. Parameters ---------- parent : QWidget Parent of the widget under construction. filename : str File name of the notebook. actions : list of (QAction or QMenu or None) or None, optional Actions to be added to the context menu of the widget under construction. The default is None, meaning that no actions will be added. ini_message : str or None, optional HTML to be initially displayed in the widget. The default is None, meaning that an empty page is displayed initially. """ super().__init__(parent) if os.name == 'nt': filename = filename.replace('/', '\\') self.filename = filename self.file_url = None self.server_url = None self.path = None self.notebookwidget = NotebookWidget(self, actions) if ini_message: self.notebookwidget.show_message(ini_message) self.static = True else: self.notebookwidget.show_blank() self.static = False self.find_widget = FindReplace(self) self.find_widget.set_editor(self.notebookwidget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.notebookwidget) layout.addWidget(self.find_widget) self.setLayout(layout)
def editor_folding_bot(base_editor_bot): """ Setup CodeEditor with some text useful for folding related tests. """ editor_stack, qtbot = base_editor_bot text = ('# dummy test file\n' 'class a():\n' # fold-block level-0 ' self.b = 1\n' ' print(self.b)\n' ' \n' ) finfo = editor_stack.new('foo.py', 'utf-8', text) find_replace = FindReplace(None, enable_replace=True) editor_stack.set_find_widget(find_replace) find_replace.set_editor(finfo.editor) qtbot.addWidget(editor_stack) return editor_stack, finfo.editor, find_replace, qtbot
def editor_folding_bot(base_editor_bot): """ Setup CodeEditor with some text useful for folding related tests. """ editor_stack, qtbot = base_editor_bot text = ( '# dummy test file\n' 'class a():\n' # fold-block level-0 ' self.b = 1\n' ' print(self.b)\n' ' \n') finfo = editor_stack.new('foo.py', 'utf-8', text) find_replace = FindReplace(None, enable_replace=True) editor_stack.set_find_widget(find_replace) find_replace.set_editor(finfo.editor) qtbot.addWidget(editor_stack) return editor_stack, finfo.editor, find_replace, qtbot
def __init__(self, parent): QWidget.__init__(self, parent) self.webview = FrameWebView(self) if WEBENGINE: self.webview.web_widget.page().setBackgroundColor( QColor(MAIN_BG_COLOR)) else: self.webview.web_widget.setStyleSheet( "background:{}".format(MAIN_BG_COLOR)) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview.web_widget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent=parent) self._is_running = False self.home_url = None self.server = None # Widgets self.label = QLabel(_("Package:")) self.label.ID = PydocBrowserToolbarItems.PackageLabel self.url_combo = UrlComboBox(self, id_=PydocBrowserToolbarItems.UrlCombo) # Setup web view frame self.webview = FrameWebView(self, handle_links=self.get_conf('handle_links')) self.webview.setup() self.webview.set_zoom_factor(self.get_conf('zoom_factor')) self.webview.loadStarted.connect(self._start) self.webview.loadFinished.connect(self._finish) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self._change_url) if not WEBENGINE: self.webview.iconChanged.connect(self._handle_icon_change) # Setup find widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() self.url_combo.setMaxCount(self.get_conf('max_history_entries')) tip = _('Write a package name here, e.g. pandas') self.url_combo.lineEdit().setPlaceholderText(tip) self.url_combo.lineEdit().setToolTip(tip) self.url_combo.valid.connect( lambda x: self._handle_url_combo_activation()) # Layout layout = QVBoxLayout() layout.addWidget(self.webview) layout.addSpacing(1) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, name, plugin, parent): super().__init__(name, plugin, parent) # Attributes self.editors = [] self.filenames = [] self.tabwidget = None self.dockviewer = None self.wrap_action = None self.linenumbers_action = None self.editors = [] self.filenames = [] self.font = None # Widgets self.tabwidget = Tabs(self) self.find_widget = FindReplace(self) # Setup self.tabwidget.setStyleSheet(self._tabs_stylesheet) self.find_widget.hide() # Layout layout = QVBoxLayout() # TODO: Move this to the tab container directly if sys.platform == 'darwin': tab_container = QWidget(self) tab_container.setObjectName('tab-container') tab_layout = QVBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.tabwidget.currentChanged.connect(self.refresh) self.tabwidget.move_data.connect(self.move_tab)
def __init__(self, parent): """Initialize widget and create layout.""" QWidget.__init__(self, parent) self.editors = [] self.filenames = [] self.tabwidget = None self.menu_actions = None layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) self.menu = QMenu(self) options_button.setMenu(self.menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, parent): """Initialize plugin and create History main widget.""" SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.dockviewer = None self.wrap_action = None self.linenumbers_action = None self.editors = [] self.filenames = [] # Initialize plugin actions, toolbutton and general signals self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.plugin_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget self.tabwidget.setCornerWidget(self.options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, parent): QWidget.__init__(self, parent) self.editor = None # Read-only editor self.editor = codeeditor.CodeEditor(self) self.editor.setup_editor(linenumbers=False, language='py', scrollflagarea=False, edge_line=False) self.editor.focus_changed.connect(lambda: self.focus_changed.emit()) self.editor.setReadOnly(True) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.editor) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.editor) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__( self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False ): if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option("light_background") self.shell = InternalShell( parent, namespace, commands, message, self.get_option("max_line_count"), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background, ) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout layout = QVBoxLayout() layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option("wrap")) # Accepting drops self.setAcceptDrops(True)
def editor_factory(new_file=True, text=None): editorstack = EditorStack(None, []) editorstack.set_find_widget(FindReplace(editorstack)) editorstack.set_io_actions(Mock(), Mock(), Mock(), Mock()) if new_file: if not text: text = ('a = 1\n' 'print(a)\n' '\n' 'x = 2') # a newline is added at end finfo = editorstack.new('foo.py', 'utf-8', text) return editorstack, finfo.editor return editorstack, None
def setup_editor(qtbot): """ Set up EditorStack with CodeEditor containing some Python code. The cursor is at the empty line below the code. Returns tuple with EditorStack and CodeEditor. """ text = ('a = 1\n' 'print(a)\n' '\n' 'x = 2') # a newline is added at end editorStack = EditorStack(None, []) editorStack.set_find_widget(FindReplace(editorStack)) editorStack.set_io_actions(Mock(), Mock(), Mock(), Mock()) finfo = editorStack.new('foo.py', 'utf-8', text) qtbot.addWidget(editorStack) return editorStack, finfo.editor
def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False
def editor_cells_bot(base_editor_bot, qtbot): editor_stack = base_editor_bot text = ('# %%\n' '# 1 cell\n' '# print(1)\n' '# %%\n' '# 2 cell\n' '# print(2)\n' '# %%\n' '# 3 cell\n' '# print(3)\n') finfo = editor_stack.new('cells.py', 'utf-8', text) find_replace = FindReplace(None, enable_replace=True) qtbot.addWidget(editor_stack) return editor_stack, finfo.editor
def __init__(self, parent): self.tabwidget = None self.menu_actions = None self.dockviewer = None self.wrap_action = None self.editors = [] self.filenames = [] if PYQT5: SpyderPluginWidget.__init__(self, parent, main = parent) else: SpyderPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, self.menu_actions) options_button.setMenu(menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, parent=None): Qt.QSplitter.__init__(self, parent) self.editorstacks = [] self.editorwindows = [] self.menu_actions, self.io_actions = self.createMenuActions() self.find_widget = FindReplace(self, enable_replace=True) self.outlineexplorer = OutlineExplorerWidget(self, show_fullpath=False, show_all_files=False) self.outlineexplorer.edit_goto.connect(self.go_to_file) self.editor_splitter = EditorSplitter(self, self, self.menu_actions, first=True) editor_widgets = Qt.QWidget(self) editor_layout = Qt.QVBoxLayout() editor_layout.setContentsMargins(0, 0, 0, 0) editor_widgets.setLayout(editor_layout) editor_layout.addWidget(self.editor_splitter) editor_layout.addWidget(self.find_widget) self.setContentsMargins(0, 0, 0, 0) self.addWidget(editor_widgets) self.addWidget(self.outlineexplorer) self.setStretchFactor(0, 5) self.setStretchFactor(1, 1) self.toolbar_list = None self.menu_list = None self.setup_window([], []) try: # spyder v3 from spyder.utils.introspection.manager import IntrospectionManager # Set introspector introspector = IntrospectionManager() editorstack = self.editor_splitter.editorstack editorstack.set_introspector(introspector) introspector.set_editor_widget(editorstack) except ImportError: pass # TODO: support introspection with spyder v4
class WebBrowser(QWidget): """ Web browser widget. """ def __init__(self, parent=None, options_button=None, handle_links=True): QWidget.__init__(self, parent) self.home_url = None self.webview = WebView(self, handle_links=handle_links) self.webview.setup() self.webview.loadFinished.connect(self.load_finished) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self.url_changed) home_button = create_toolbutton(self, icon=ima.icon('home'), tip=_("Home"), triggered=self.go_home) zoom_out_button = action2button(self.webview.zoom_out_action) zoom_in_button = action2button(self.webview.zoom_in_action) def pageact2btn(prop, icon=None): return action2button(self.webview.pageAction(prop), parent=self.webview, icon=icon) refresh_button = pageact2btn(QWebEnginePage.Reload, icon=ima.icon('refresh')) stop_button = pageact2btn(QWebEnginePage.Stop, icon=ima.icon('stop')) previous_button = pageact2btn(QWebEnginePage.Back, icon=ima.icon('previous')) next_button = pageact2btn(QWebEnginePage.Forward, icon=ima.icon('next')) stop_button.setEnabled(False) self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True)) self.webview.loadFinished.connect( lambda: stop_button.setEnabled(False)) progressbar = QProgressBar(self) progressbar.setTextVisible(False) progressbar.hide() self.webview.loadStarted.connect(progressbar.show) self.webview.loadProgress.connect(progressbar.setValue) self.webview.loadFinished.connect(lambda _state: progressbar.hide()) label = QLabel(self.get_label()) self.url_combo = UrlComboBox(self) self.url_combo.valid.connect(self.url_combo_activated) if not WEBENGINE: self.webview.iconChanged.connect(self.icon_changed) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() find_button = create_toolbutton(self, icon=ima.icon('find'), tip=_("Find text"), toggled=self.toggle_find_widget) self.find_widget.visibility_changed.connect(find_button.setChecked) hlayout = QHBoxLayout() for widget in (previous_button, next_button, home_button, find_button, label, self.url_combo, zoom_out_button, zoom_in_button, refresh_button, progressbar, stop_button): hlayout.addWidget(widget) if options_button: hlayout.addWidget(options_button) layout = create_plugin_layout(hlayout) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def get_label(self): """Return address label text""" return _("Address:") def set_home_url(self, text): """Set home URL""" self.home_url = QUrl(text) def set_url(self, url): """Set current URL""" self.url_changed(url) self.go_to(url) def go_to(self, url_or_text): """Go to page *address*""" if is_text_string(url_or_text): url = QUrl(url_or_text) else: url = url_or_text self.webview.load(url) @Slot() def go_home(self): """Go to home page""" if self.home_url is not None: self.set_url(self.home_url) def text_to_url(self, text): """Convert text address into QUrl object""" return QUrl(text) def url_combo_activated(self, valid): """Load URL from combo box first item""" text = to_text_string(self.url_combo.currentText()) self.go_to(self.text_to_url(text)) def load_finished(self, ok): if not ok: self.webview.setHtml(_("Unable to load page")) def url_to_text(self, url): """Convert QUrl object to displayed text in combo box""" return url.toString() def url_changed(self, url): """Displayed URL has changed -> updating URL combo box""" self.url_combo.add_text(self.url_to_text(url)) def icon_changed(self): self.url_combo.setItemIcon(self.url_combo.currentIndex(), self.webview.icon()) self.setWindowIcon(self.webview.icon()) @Slot(bool) def toggle_find_widget(self, state): if state: self.find_widget.show() else: self.find_widget.hide()
def __init__(self, parent=None, options_button=None, handle_links=True): QWidget.__init__(self, parent) self.home_url = None self.webview = WebView(self, handle_links=handle_links) self.webview.setup() self.webview.loadFinished.connect(self.load_finished) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self.url_changed) home_button = create_toolbutton(self, icon=ima.icon('home'), tip=_("Home"), triggered=self.go_home) zoom_out_button = action2button(self.webview.zoom_out_action) zoom_in_button = action2button(self.webview.zoom_in_action) def pageact2btn(prop, icon=None): return action2button(self.webview.pageAction(prop), parent=self.webview, icon=icon) refresh_button = pageact2btn(QWebEnginePage.Reload, icon=ima.icon('refresh')) stop_button = pageact2btn(QWebEnginePage.Stop, icon=ima.icon('stop')) previous_button = pageact2btn(QWebEnginePage.Back, icon=ima.icon('previous')) next_button = pageact2btn(QWebEnginePage.Forward, icon=ima.icon('next')) stop_button.setEnabled(False) self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True)) self.webview.loadFinished.connect( lambda: stop_button.setEnabled(False)) progressbar = QProgressBar(self) progressbar.setTextVisible(False) progressbar.hide() self.webview.loadStarted.connect(progressbar.show) self.webview.loadProgress.connect(progressbar.setValue) self.webview.loadFinished.connect(lambda _state: progressbar.hide()) label = QLabel(self.get_label()) self.url_combo = UrlComboBox(self) self.url_combo.valid.connect(self.url_combo_activated) if not WEBENGINE: self.webview.iconChanged.connect(self.icon_changed) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() find_button = create_toolbutton(self, icon=ima.icon('find'), tip=_("Find text"), toggled=self.toggle_find_widget) self.find_widget.visibility_changed.connect(find_button.setChecked) hlayout = QHBoxLayout() for widget in (previous_button, next_button, home_button, find_button, label, self.url_combo, zoom_out_button, zoom_in_button, refresh_button, progressbar, stop_button): hlayout.addWidget(widget) if options_button: hlayout.addWidget(options_button) layout = create_plugin_layout(hlayout) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout)
class ExternalConsole(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'console' CONFIGWIDGET_CLASS = ExternalConsoleConfigPage DISABLE_ACTIONS_WHEN_HIDDEN = False edit_goto = Signal((str, int, str), (str, int, str, bool)) focus_changed = Signal() redirect_stdio = Signal(bool) def __init__(self, parent): if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None self.help = None # Help plugin self.historylog = None # History log plugin self.variableexplorer = None # Variable explorer plugin self.python_count = 0 self.terminal_count = 0 # Python startup file selection if not osp.isfile(self.get_option('pythonstartup', '')): self.set_option('pythonstartup', SCIENTIFIC_STARTUP) # default/custom settings are mutually exclusive: self.set_option('pythonstartup/custom', not self.get_option('pythonstartup/default')) self.shellwidgets = [] self.filenames = [] self.icons = [] self.runfile_args = "" # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) if hasattr(self.tabwidget, 'setDocumentMode')\ and not sys.platform == 'darwin': # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.main.sig_pythonpath_changed.connect(self.set_path) self.tabwidget.set_close_function(self.close_console) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # Accepting drops self.setAcceptDrops(True) def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) shell = self.shellwidgets.pop(index_from) icons = self.icons.pop(index_from) self.filenames.insert(index_to, filename) self.shellwidgets.insert(index_to, shell) self.icons.insert(index_to, icons) self.update_plugin_title.emit() def get_shell_index_from_id(self, shell_id): """Return shellwidget index from id""" for index, shell in enumerate(self.shellwidgets): if id(shell) == shell_id: return index def close_console(self, index=None): """Close console tab from index or widget (or close current tab)""" # Get tab index if not self.tabwidget.count(): return if index is None: index = self.tabwidget.currentIndex() # Closing logic self.tabwidget.widget(index).close() self.tabwidget.removeTab(index) self.filenames.pop(index) self.shellwidgets.pop(index) self.icons.pop(index) self.update_plugin_title.emit() def set_variableexplorer(self, variableexplorer): """Set variable explorer plugin""" self.variableexplorer = variableexplorer def set_path(self): """Set consoles PYTHONPATH if changed by the user""" from spyder.widgets.externalshell import pythonshell for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): if sw.is_interpreter and sw.is_running(): sw.path = self.main.get_spyder_pythonpath() sw.shell.path = sw.path def __find_python_shell(self, interpreter_only=False): current_index = self.tabwidget.currentIndex() if current_index == -1: return from spyder.widgets.externalshell import pythonshell for index in [current_index] + list(range(self.tabwidget.count())): shellwidget = self.tabwidget.widget(index) if isinstance(shellwidget, pythonshell.ExternalPythonShell): if interpreter_only and not shellwidget.is_interpreter: continue elif not shellwidget.is_running(): continue else: self.tabwidget.setCurrentIndex(index) return shellwidget def get_current_shell(self): """ Called by Help to retrieve the current shell instance """ shellwidget = self.__find_python_shell() return shellwidget.shell def get_running_python_shell(self): """ Called by Help to retrieve a running Python shell instance """ current_index = self.tabwidget.currentIndex() if current_index == -1: return from spyder.widgets.externalshell import pythonshell shellwidgets = [ self.tabwidget.widget(index) for index in range(self.tabwidget.count()) ] shellwidgets = [_w for _w in shellwidgets if isinstance(_w, pythonshell.ExternalPythonShell) \ and _w.is_running()] if shellwidgets: # First, iterate on interpreters only: for shellwidget in shellwidgets: if shellwidget.is_interpreter: return shellwidget.shell else: return shellwidgets[0].shell def run_script_in_current_shell(self, filename, wdir, args, debug, post_mortem): """Run script in current shell, if any""" norm = lambda text: remove_backslashes(to_text_string(text)) line = "%s('%s'" % ('debugfile' if debug else 'runfile', norm(filename)) if args: line += ", args='%s'" % norm(args) if wdir: line += ", wdir='%s'" % norm(wdir) if post_mortem: line += ', post_mortem=True' line += ")" if not self.execute_code(line, interpreter_only=True): QMessageBox.warning( self, _('Warning'), _("No Python console is currently selected to run <b>%s</b>." "<br><br>Please select or open a new Python console " "and try again.") % osp.basename(norm(filename)), QMessageBox.Ok) else: self.visibility_changed(True) self.raise_() def set_current_shell_working_directory(self, directory): """Set current shell working directory""" shellwidget = self.__find_python_shell() if shellwidget is not None: directory = encoding.to_unicode_from_fs(directory) shellwidget.shell.set_cwd(directory) def execute_code(self, lines, interpreter_only=False): """Execute code in an already opened Python interpreter""" shellwidget = self.__find_python_shell( interpreter_only=interpreter_only) if shellwidget is not None: shellwidget.shell.execute_lines(to_text_string(lines)) self.activateWindow() shellwidget.shell.setFocus() return True else: return False def pdb_has_stopped(self, fname, lineno, shellwidget): """Python debugger has just stopped at frame (fname, lineno)""" # This is a unique form of the edit_goto signal that is intended to # prevent keyboard input from accidentally entering the editor # during repeated, rapid entry of debugging commands. self.edit_goto[str, int, str, bool].emit(fname, lineno, '', False) self.activateWindow() shellwidget.shell.setFocus() def set_spyder_breakpoints(self): """Set all Spyder breakpoints into all shells""" for shellwidget in self.shellwidgets: shellwidget.shell.set_spyder_breakpoints() def start(self, fname, wdir=None, args='', interact=False, debug=False, python=True, python_args='', post_mortem=True): """ Start new console fname: string: filename of script to run None: open an interpreter wdir: working directory args: command line options of the Python script interact: inspect script interactively after its execution debug: run pdb python: True: Python interpreter, False: terminal python_args: additionnal Python interpreter command line options (option "-u" is mandatory, see widgets.externalshell package) """ # Note: fname is None <=> Python interpreter if fname is not None and not is_text_string(fname): fname = to_text_string(fname) if wdir is not None and not is_text_string(wdir): wdir = to_text_string(wdir) if fname is not None and fname in self.filenames: index = self.filenames.index(fname) if self.get_option('single_tab'): old_shell = self.shellwidgets[index] if old_shell.is_running(): runconfig = get_run_configuration(fname) if runconfig is None or runconfig.show_kill_warning: answer = QMessageBox.question( self, self.get_plugin_title(), _("%s is already running in a separate process.\n" "Do you want to kill the process before starting " "a new one?") % osp.basename(fname), QMessageBox.Yes | QMessageBox.Cancel) else: answer = QMessageBox.Yes if answer == QMessageBox.Yes: old_shell.process.kill() old_shell.process.waitForFinished() else: return self.close_console(index) else: index = self.tabwidget.count() # Creating a new external shell pythonpath = self.main.get_spyder_pythonpath() light_background = self.get_option('light_background') show_elapsed_time = self.get_option('show_elapsed_time') if python: if CONF.get('main_interpreter', 'default'): pythonexecutable = get_python_executable() external_interpreter = False else: pythonexecutable = CONF.get('main_interpreter', 'executable') external_interpreter = True if self.get_option('pythonstartup/default'): pythonstartup = None else: pythonstartup = self.get_option('pythonstartup', None) monitor_enabled = self.get_option('monitor/enabled') mpl_backend = self.get_option('matplotlib/backend/value') ets_backend = self.get_option('ets_backend') qt_api = self.get_option('qt/api') if qt_api not in ('pyqt', 'pyside', 'pyqt5'): qt_api = None merge_output_channels = self.get_option('merge_output_channels') colorize_sys_stderr = self.get_option('colorize_sys_stderr') umr_enabled = CONF.get('main_interpreter', 'umr/enabled') umr_namelist = CONF.get('main_interpreter', 'umr/namelist') umr_verbose = CONF.get('main_interpreter', 'umr/verbose') ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout') ar_state = CONF.get('variable_explorer', 'autorefresh') sa_settings = None shellwidget = ExternalPythonShell( self, fname, wdir, interact, debug, post_mortem=post_mortem, path=pythonpath, python_args=python_args, arguments=args, stand_alone=sa_settings, pythonstartup=pythonstartup, pythonexecutable=pythonexecutable, external_interpreter=external_interpreter, umr_enabled=umr_enabled, umr_namelist=umr_namelist, umr_verbose=umr_verbose, ets_backend=ets_backend, monitor_enabled=monitor_enabled, mpl_backend=mpl_backend, qt_api=qt_api, merge_output_channels=merge_output_channels, colorize_sys_stderr=colorize_sys_stderr, autorefresh_timeout=ar_timeout, autorefresh_state=ar_state, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) shellwidget.sig_pdb.connect( lambda fname, lineno, shellwidget=shellwidget: self. pdb_has_stopped(fname, lineno, shellwidget)) self.register_widget_shortcuts(shellwidget.shell) else: if os.name == 'posix': cmd = 'gnome-terminal' args = [] if programs.is_program_installed(cmd): if wdir: args.extend(['--working-directory=%s' % wdir]) programs.run_program(cmd, args) return cmd = 'konsole' if programs.is_program_installed(cmd): if wdir: args.extend(['--workdir', wdir]) programs.run_program(cmd, args) return shellwidget = ExternalSystemShell( self, wdir, path=pythonpath, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) # Code completion / calltips shellwidget.shell.setMaximumBlockCount( self.get_option('max_line_count')) shellwidget.shell.set_font(self.get_plugin_font()) shellwidget.shell.toggle_wrap_mode(self.get_option('wrap')) shellwidget.shell.set_calltips(self.get_option('calltips')) shellwidget.shell.set_codecompletion_auto( self.get_option('codecompletion/auto')) shellwidget.shell.set_codecompletion_case( self.get_option('codecompletion/case_sensitive')) shellwidget.shell.set_codecompletion_enter( self.get_option('codecompletion/enter_key')) if python and self.help is not None: shellwidget.shell.set_help(self.help) shellwidget.shell.set_help_enabled( CONF.get('help', 'connect/python_console')) if self.historylog is not None: self.historylog.add_history(shellwidget.shell.history_filename) shellwidget.shell.append_to_history.connect( self.historylog.append_to_history) shellwidget.shell.go_to_error.connect(self.go_to_error) shellwidget.shell.focus_changed.connect( lambda: self.focus_changed.emit()) if python: if self.main.editor is not None: shellwidget.open_file.connect(self.open_file_in_spyder) if fname is None: self.python_count += 1 tab_name = "Python %d" % self.python_count tab_icon1 = ima.icon('python') tab_icon2 = ima.icon('python_t') else: tab_name = osp.basename(fname) tab_icon1 = ima.icon('run') tab_icon2 = ima.icon('terminated') else: fname = id(shellwidget) if os.name == 'nt': tab_name = _("Command Window") else: tab_name = _("Terminal") self.terminal_count += 1 tab_name += (" %d" % self.terminal_count) tab_icon1 = ima.icon('cmdprompt') tab_icon2 = ima.icon('cmdprompt_t') self.shellwidgets.insert(index, shellwidget) self.filenames.insert(index, fname) self.icons.insert(index, (tab_icon1, tab_icon2)) if index is None: index = self.tabwidget.addTab(shellwidget, tab_name) else: self.tabwidget.insertTab(index, shellwidget, tab_name) shellwidget.started.connect( lambda sid=id(shellwidget): self.process_started(sid)) shellwidget.sig_finished.connect( lambda sid=id(shellwidget): self.process_finished(sid)) self.find_widget.set_editor(shellwidget.shell) self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir) self.tabwidget.setCurrentIndex(index) if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() shellwidget.set_icontext_visible(self.get_option('show_icontext')) # Start process and give focus to console shellwidget.start_shell() def open_file_in_spyder(self, fname, lineno): """Open file in Spyder's editor from remote process""" self.main.editor.activateWindow() self.main.editor.raise_() self.main.editor.load(fname, lineno) #------ Private API ------------------------------------------------------- def process_started(self, shell_id): index = self.get_shell_index_from_id(shell_id) shell = self.shellwidgets[index] icon, _icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.help is not None: self.help.set_shell(shell.shell) if self.variableexplorer is not None: self.variableexplorer.add_shellwidget(shell) def process_finished(self, shell_id): index = self.get_shell_index_from_id(shell_id) if index is not None: # Not sure why it happens, but sometimes the shellwidget has # already been removed, so that's not bad if we can't change # the tab icon... _icon, icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.variableexplorer is not None: self.variableexplorer.remove_shellwidget(shell_id) #------ SpyderPluginWidget API -------------------------------------------- def get_plugin_title(self): """Return widget title""" title = _('Python console') if self.filenames: index = self.tabwidget.currentIndex() fname = self.filenames[index] if fname: title += ' - ' + to_text_string(fname) return title def get_plugin_icon(self): """Return widget icon""" return ima.icon('console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def get_plugin_actions(self): """Return a list of actions related to plugin""" interpreter_action = create_action(self, _("Open a &Python console"), None, ima.icon('python'), triggered=self.open_interpreter) if os.name == 'nt': text = _("Open &command prompt") tip = _("Open a Windows command prompt") else: text = _("Open a &terminal") tip = _("Open a terminal window") terminal_action = create_action(self, text, None, None, tip, triggered=self.open_terminal) run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) consoles_menu_actions = [interpreter_action] tools_menu_actions = [terminal_action] self.menu_actions = [interpreter_action, terminal_action, run_action] self.main.consoles_menu_actions += consoles_menu_actions self.main.tools_menu_actions += tools_menu_actions return self.menu_actions + consoles_menu_actions + tools_menu_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.main.add_dockwidget(self) self.help = self.main.help self.historylog = self.main.historylog self.edit_goto.connect(self.main.editor.load) self.edit_goto[str, int, str, bool].connect( lambda fname, lineno, word, processevents: self.main.editor.load( fname, lineno, word, processevents=processevents)) self.main.editor.run_in_current_extconsole.connect( self.run_script_in_current_shell) self.main.editor.breakpoints_saved.connect(self.set_spyder_breakpoints) self.main.editor.open_dir.connect( self.set_current_shell_working_directory) self.main.workingdirectory.set_current_console_wd.connect( self.set_current_shell_working_directory) self.focus_changed.connect(self.main.plugin_focus_changed) self.redirect_stdio.connect(self.main.redirect_internalshell_stdio) expl = self.main.explorer if expl is not None: expl.open_terminal.connect(self.open_terminal) expl.open_interpreter.connect(self.open_interpreter) pexpl = self.main.projects if pexpl is not None: pexpl.open_terminal.connect(self.open_terminal) pexpl.open_interpreter.connect(self.open_interpreter) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" for shellwidget in self.shellwidgets: shellwidget.close() return True def restart(self): """ Restart the console This is needed when we switch project to update PYTHONPATH and the selected interpreter """ self.python_count = 0 for i in range(len(self.shellwidgets)): self.close_console() self.open_interpreter() def refresh_plugin(self): """Refresh tabwidget""" shellwidget = None if self.tabwidget.count(): shellwidget = self.tabwidget.currentWidget() editor = shellwidget.shell editor.setFocus() widgets = [shellwidget.create_time_label(), 5 ] + shellwidget.get_toolbar_buttons() + [5] else: editor = None widgets = [] self.find_widget.set_editor(editor) self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets}) if shellwidget: shellwidget.update_time_label_visibility() self.variableexplorer.set_shellwidget_from_id(id(shellwidget)) self.help.set_shell(shellwidget.shell) self.main.last_console_plugin_focus_was_python = True self.update_plugin_title.emit() def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() for shellwidget in self.shellwidgets: shellwidget.shell.set_font(font) completion_size = CONF.get('main', 'completion/size') comp_widget = shellwidget.shell.completion_widget comp_widget.setup_appearance(completion_size, font) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" showtime_n = 'show_elapsed_time' showtime_o = self.get_option(showtime_n) icontext_n = 'show_icontext' icontext_o = self.get_option(icontext_n) calltips_n = 'calltips' calltips_o = self.get_option(calltips_n) help_n = 'connect_to_oi' help_o = CONF.get('help', 'connect/python_console') wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) compauto_n = 'codecompletion/auto' compauto_o = self.get_option(compauto_n) case_comp_n = 'codecompletion/case_sensitive' case_comp_o = self.get_option(case_comp_n) compenter_n = 'codecompletion/enter_key' compenter_o = self.get_option(compenter_n) mlc_n = 'max_line_count' mlc_o = self.get_option(mlc_n) for shellwidget in self.shellwidgets: if showtime_n in options: shellwidget.set_elapsed_time_visible(showtime_o) if icontext_n in options: shellwidget.set_icontext_visible(icontext_o) if calltips_n in options: shellwidget.shell.set_calltips(calltips_o) if help_n in options: if isinstance(shellwidget, ExternalPythonShell): shellwidget.shell.set_help_enabled(help_o) if wrap_n in options: shellwidget.shell.toggle_wrap_mode(wrap_o) if compauto_n in options: shellwidget.shell.set_codecompletion_auto(compauto_o) if case_comp_n in options: shellwidget.shell.set_codecompletion_case(case_comp_o) if compenter_n in options: shellwidget.shell.set_codecompletion_enter(compenter_o) if mlc_n in options: shellwidget.shell.setMaximumBlockCount(mlc_o) #------ SpyderPluginMixin API --------------------------------------------- def toggle_view(self, checked): """Toggle view""" if checked: self.dockwidget.show() self.dockwidget.raise_() # Start a console in case there are none shown from spyder.widgets.externalshell import pythonshell consoles = None for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): consoles = True break if not consoles: self.open_interpreter() else: self.dockwidget.hide() #------ Public API --------------------------------------------------------- @Slot(bool) @Slot(str) def open_interpreter(self, wdir=None): """Open interpreter""" if not wdir: wdir = getcwd() self.visibility_changed(True) self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=True) @Slot(bool) @Slot(str) def open_terminal(self, wdir=None): """Open terminal""" if not wdir: wdir = getcwd() self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=False) @Slot() def run_script(self): """Run a Python script""" self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)") self.redirect_stdio.emit(True) if filename: self.start(fname=filename, wdir=None, args='', interact=False, debug=False) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_goto.emit(osp.abspath(fname), int(lnb), '') #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): pathlist = mimedata2url(source) shellwidget = self.tabwidget.currentWidget() if all([ is_python_script(to_text_string(qstr)) for qstr in pathlist ]): event.acceptProposedAction() elif shellwidget is None or not shellwidget.is_running(): event.ignore() else: event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() shellwidget = self.tabwidget.currentWidget() if source.hasText(): qstr = source.text() if is_python_script(to_text_string(qstr)): self.start(qstr) elif shellwidget: shellwidget.shell.insert_text(qstr) elif source.hasUrls(): pathlist = mimedata2url(source) if all( [is_python_script(to_text_string(qstr)) for qstr in pathlist]): for fname in pathlist: self.start(fname) elif shellwidget: shellwidget.shell.drop_pathlist(pathlist) event.acceptProposedAction()
def __init__(self, parent): if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None self.help = None # Help plugin self.historylog = None # History log plugin self.variableexplorer = None # Variable explorer plugin self.python_count = 0 self.terminal_count = 0 # Python startup file selection if not osp.isfile(self.get_option('pythonstartup', '')): self.set_option('pythonstartup', SCIENTIFIC_STARTUP) # default/custom settings are mutually exclusive: self.set_option('pythonstartup/custom', not self.get_option('pythonstartup/default')) self.shellwidgets = [] self.filenames = [] self.icons = [] self.runfile_args = "" # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) if hasattr(self.tabwidget, 'setDocumentMode')\ and not sys.platform == 'darwin': # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.main.sig_pythonpath_changed.connect(self.set_path) self.tabwidget.set_close_function(self.close_console) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # Accepting drops self.setAcceptDrops(True)
class HistoryLog(SpyderPluginWidget): """ History log widget """ CONF_SECTION = 'historylog' CONFIGWIDGET_CLASS = HistoryConfigPage focus_changed = Signal() def __init__(self, parent): self.tabwidget = None self.menu_actions = None self.dockviewer = None self.wrap_action = None self.editors = [] self.filenames = [] if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, self.menu_actions) options_button.setMenu(menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('History log') def get_plugin_icon(self): """Return widget icon""" return ima.icon('history') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" return True def refresh_plugin(self): """Refresh tabwidget""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) def get_plugin_actions(self): """Return a list of actions related to plugin""" history_action = create_action(self, _("History..."), None, ima.icon('history'), _("Set history maximum entries"), triggered=self.change_history_depth) self.wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) self.wrap_action.setChecked(self.get_option('wrap')) self.menu_actions = [history_action, self.wrap_action] return self.menu_actions def on_first_registration(self): """Action to be performed on first plugin registration""" self.main.tabify_plugins(self.main.ipyconsole, self) def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # self.main.console.set_historylog(self) self.main.console.shell.refresh.connect(self.refresh_plugin) def update_font(self): """Update font from Preferences""" color_scheme = self.get_color_scheme() font = self.get_plugin_font() for editor in self.editors: editor.set_font(font, color_scheme) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" color_scheme_n = 'color_scheme_name' color_scheme_o = self.get_color_scheme() font_n = 'plugin_font' font_o = self.get_plugin_font() wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) self.wrap_action.setChecked(wrap_o) for editor in self.editors: if font_n in options: scs = color_scheme_o if color_scheme_n in options else None editor.set_font(font_o, scs) elif color_scheme_n in options: editor.set_color_scheme(color_scheme_o) if wrap_n in options: editor.toggle_wrap_mode(wrap_o) #------ Private API -------------------------------------------------------- def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) #------ Public API --------------------------------------------------------- def add_history(self, filename): """ Add new history tab Slot for add_history signal emitted by shell instance """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return editor = codeeditor.CodeEditor(self) if osp.splitext(filename)[1] == '.py': language = 'py' else: language = 'bat' editor.setup_editor(linenumbers=False, language=language, scrollflagarea=False) editor.focus_changed.connect(lambda: self.focus_changed.emit()) editor.setReadOnly(True) color_scheme = self.get_color_scheme() editor.set_font(self.get_plugin_font(), color_scheme) editor.toggle_wrap_mode(self.get_option('wrap')) text, _ = encoding.read(filename) editor.set_text(text) editor.set_cursor_position('eof') self.editors.append(editor) self.filenames.append(filename) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.find_widget.set_editor(editor) self.tabwidget.setTabToolTip(index, filename) self.tabwidget.setCurrentIndex(index) def append_to_history(self, filename, command): """ Append an entry to history filename Slot for append_to_history signal emitted by shell instance """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), 'utf-8') command = to_text_string(command) index = self.filenames.index(filename) self.editors[index].append(command) if self.get_option('go_to_eof'): self.editors[index].set_cursor_position('eof') self.tabwidget.setCurrentIndex(index) @Slot() def change_history_depth(self): "Change history max entries" "" depth, valid = QInputDialog.getInt(self, _('History'), _('Maximum entries'), self.get_option('max_entries'), 10, 10000) if valid: self.set_option('max_entries', depth) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" if self.tabwidget is None: return for editor in self.editors: editor.toggle_wrap_mode(checked) self.set_option('wrap', checked)
class ExternalConsole(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'console' CONFIGWIDGET_CLASS = ExternalConsoleConfigPage DISABLE_ACTIONS_WHEN_HIDDEN = False edit_goto = Signal((str, int, str), (str, int, str, bool)) focus_changed = Signal() redirect_stdio = Signal(bool) def __init__(self, parent): if PYQT5: SpyderPluginWidget.__init__(self, parent, main = parent) else: SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None self.help = None # Help plugin self.historylog = None # History log plugin self.variableexplorer = None # Variable explorer plugin self.python_count = 0 self.terminal_count = 0 # Python startup file selection if not osp.isfile(self.get_option('pythonstartup', '')): self.set_option('pythonstartup', SCIENTIFIC_STARTUP) # default/custom settings are mutually exclusive: self.set_option('pythonstartup/custom', not self.get_option('pythonstartup/default')) self.shellwidgets = [] self.filenames = [] self.icons = [] self.runfile_args = "" # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) if hasattr(self.tabwidget, 'setDocumentMode')\ and not sys.platform == 'darwin': # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.main.sig_pythonpath_changed.connect(self.set_path) self.tabwidget.set_close_function(self.close_console) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # Accepting drops self.setAcceptDrops(True) def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) shell = self.shellwidgets.pop(index_from) icons = self.icons.pop(index_from) self.filenames.insert(index_to, filename) self.shellwidgets.insert(index_to, shell) self.icons.insert(index_to, icons) self.update_plugin_title.emit() def get_shell_index_from_id(self, shell_id): """Return shellwidget index from id""" for index, shell in enumerate(self.shellwidgets): if id(shell) == shell_id: return index def close_console(self, index=None): """Close console tab from index or widget (or close current tab)""" # Get tab index if not self.tabwidget.count(): return if index is None: index = self.tabwidget.currentIndex() # Closing logic self.tabwidget.widget(index).close() self.tabwidget.removeTab(index) self.filenames.pop(index) self.shellwidgets.pop(index) self.icons.pop(index) self.update_plugin_title.emit() def set_variableexplorer(self, variableexplorer): """Set variable explorer plugin""" self.variableexplorer = variableexplorer def set_path(self): """Set consoles PYTHONPATH if changed by the user""" from spyder.widgets.externalshell import pythonshell for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): if sw.is_interpreter and sw.is_running(): sw.path = self.main.get_spyder_pythonpath() sw.shell.path = sw.path def __find_python_shell(self, interpreter_only=False): current_index = self.tabwidget.currentIndex() if current_index == -1: return from spyder.widgets.externalshell import pythonshell for index in [current_index]+list(range(self.tabwidget.count())): shellwidget = self.tabwidget.widget(index) if isinstance(shellwidget, pythonshell.ExternalPythonShell): if interpreter_only and not shellwidget.is_interpreter: continue elif not shellwidget.is_running(): continue else: self.tabwidget.setCurrentIndex(index) return shellwidget def get_current_shell(self): """ Called by Help to retrieve the current shell instance """ shellwidget = self.__find_python_shell() return shellwidget.shell def get_running_python_shell(self): """ Called by Help to retrieve a running Python shell instance """ current_index = self.tabwidget.currentIndex() if current_index == -1: return from spyder.widgets.externalshell import pythonshell shellwidgets = [self.tabwidget.widget(index) for index in range(self.tabwidget.count())] shellwidgets = [_w for _w in shellwidgets if isinstance(_w, pythonshell.ExternalPythonShell) \ and _w.is_running()] if shellwidgets: # First, iterate on interpreters only: for shellwidget in shellwidgets: if shellwidget.is_interpreter: return shellwidget.shell else: return shellwidgets[0].shell def run_script_in_current_shell(self, filename, wdir, args, debug, post_mortem): """Run script in current shell, if any""" norm = lambda text: remove_backslashes(to_text_string(text)) line = "%s('%s'" % ('debugfile' if debug else 'runfile', norm(filename)) if args: line += ", args='%s'" % norm(args) if wdir: line += ", wdir='%s'" % norm(wdir) if post_mortem: line += ', post_mortem=True' line += ")" if not self.execute_code(line, interpreter_only=True): QMessageBox.warning(self, _('Warning'), _("No Python console is currently selected to run <b>%s</b>." "<br><br>Please select or open a new Python console " "and try again." ) % osp.basename(norm(filename)), QMessageBox.Ok) else: self.visibility_changed(True) self.raise_() def set_current_shell_working_directory(self, directory): """Set current shell working directory""" shellwidget = self.__find_python_shell() if shellwidget is not None: directory = encoding.to_unicode_from_fs(directory) shellwidget.shell.set_cwd(directory) def execute_code(self, lines, interpreter_only=False): """Execute code in an already opened Python interpreter""" shellwidget = self.__find_python_shell( interpreter_only=interpreter_only) if shellwidget is not None: shellwidget.shell.execute_lines(to_text_string(lines)) self.activateWindow() shellwidget.shell.setFocus() return True else: return False def pdb_has_stopped(self, fname, lineno, shellwidget): """Python debugger has just stopped at frame (fname, lineno)""" # This is a unique form of the edit_goto signal that is intended to # prevent keyboard input from accidentally entering the editor # during repeated, rapid entry of debugging commands. self.edit_goto[str, int, str, bool].emit(fname, lineno, '', False) self.activateWindow() shellwidget.shell.setFocus() def set_spyder_breakpoints(self): """Set all Spyder breakpoints into all shells""" for shellwidget in self.shellwidgets: shellwidget.shell.set_spyder_breakpoints() def start(self, fname, wdir=None, args='', interact=False, debug=False, python=True, python_args='', post_mortem=True): """ Start new console fname: string: filename of script to run None: open an interpreter wdir: working directory args: command line options of the Python script interact: inspect script interactively after its execution debug: run pdb python: True: Python interpreter, False: terminal python_args: additionnal Python interpreter command line options (option "-u" is mandatory, see widgets.externalshell package) """ # Note: fname is None <=> Python interpreter if fname is not None and not is_text_string(fname): fname = to_text_string(fname) if wdir is not None and not is_text_string(wdir): wdir = to_text_string(wdir) if fname is not None and fname in self.filenames: index = self.filenames.index(fname) if self.get_option('single_tab'): old_shell = self.shellwidgets[index] if old_shell.is_running(): runconfig = get_run_configuration(fname) if runconfig is None or runconfig.show_kill_warning: answer = QMessageBox.question(self, self.get_plugin_title(), _("%s is already running in a separate process.\n" "Do you want to kill the process before starting " "a new one?") % osp.basename(fname), QMessageBox.Yes | QMessageBox.Cancel) else: answer = QMessageBox.Yes if answer == QMessageBox.Yes: old_shell.process.kill() old_shell.process.waitForFinished() else: return self.close_console(index) else: index = self.tabwidget.count() # Creating a new external shell pythonpath = self.main.get_spyder_pythonpath() light_background = self.get_option('light_background') show_elapsed_time = self.get_option('show_elapsed_time') if python: if CONF.get('main_interpreter', 'default'): pythonexecutable = get_python_executable() external_interpreter = False else: pythonexecutable = CONF.get('main_interpreter', 'executable') external_interpreter = True if self.get_option('pythonstartup/default'): pythonstartup = None else: pythonstartup = self.get_option('pythonstartup', None) monitor_enabled = self.get_option('monitor/enabled') mpl_backend = self.get_option('matplotlib/backend/value') ets_backend = self.get_option('ets_backend') qt_api = self.get_option('qt/api') if qt_api not in ('pyqt', 'pyside', 'pyqt5'): qt_api = None merge_output_channels = self.get_option('merge_output_channels') colorize_sys_stderr = self.get_option('colorize_sys_stderr') umr_enabled = CONF.get('main_interpreter', 'umr/enabled') umr_namelist = CONF.get('main_interpreter', 'umr/namelist') umr_verbose = CONF.get('main_interpreter', 'umr/verbose') ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout') ar_state = CONF.get('variable_explorer', 'autorefresh') sa_settings = None shellwidget = ExternalPythonShell(self, fname, wdir, interact, debug, post_mortem=post_mortem, path=pythonpath, python_args=python_args, arguments=args, stand_alone=sa_settings, pythonstartup=pythonstartup, pythonexecutable=pythonexecutable, external_interpreter=external_interpreter, umr_enabled=umr_enabled, umr_namelist=umr_namelist, umr_verbose=umr_verbose, ets_backend=ets_backend, monitor_enabled=monitor_enabled, mpl_backend=mpl_backend, qt_api=qt_api, merge_output_channels=merge_output_channels, colorize_sys_stderr=colorize_sys_stderr, autorefresh_timeout=ar_timeout, autorefresh_state=ar_state, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) shellwidget.sig_pdb.connect( lambda fname, lineno, shellwidget=shellwidget: self.pdb_has_stopped(fname, lineno, shellwidget)) self.register_widget_shortcuts(shellwidget.shell) else: if os.name == 'posix': cmd = 'gnome-terminal' args = [] if programs.is_program_installed(cmd): if wdir: args.extend(['--working-directory=%s' % wdir]) programs.run_program(cmd, args) return cmd = 'konsole' if programs.is_program_installed(cmd): if wdir: args.extend(['--workdir', wdir]) programs.run_program(cmd, args) return shellwidget = ExternalSystemShell(self, wdir, path=pythonpath, light_background=light_background, menu_actions=self.menu_actions, show_buttons_inside=False, show_elapsed_time=show_elapsed_time) # Code completion / calltips shellwidget.shell.setMaximumBlockCount( self.get_option('max_line_count') ) shellwidget.shell.set_font( self.get_plugin_font() ) shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') ) shellwidget.shell.set_calltips( self.get_option('calltips') ) shellwidget.shell.set_codecompletion_auto( self.get_option('codecompletion/auto') ) shellwidget.shell.set_codecompletion_case( self.get_option('codecompletion/case_sensitive') ) shellwidget.shell.set_codecompletion_enter( self.get_option('codecompletion/enter_key') ) if python and self.help is not None: shellwidget.shell.set_help(self.help) shellwidget.shell.set_help_enabled( CONF.get('help', 'connect/python_console')) if self.historylog is not None: self.historylog.add_history(shellwidget.shell.history_filename) shellwidget.shell.append_to_history.connect( self.historylog.append_to_history) shellwidget.shell.go_to_error.connect(self.go_to_error) shellwidget.shell.focus_changed.connect( lambda: self.focus_changed.emit()) if python: if self.main.editor is not None: shellwidget.open_file.connect(self.open_file_in_spyder) if fname is None: self.python_count += 1 tab_name = "Python %d" % self.python_count tab_icon1 = ima.icon('python') tab_icon2 = ima.icon('python_t') else: tab_name = osp.basename(fname) tab_icon1 = ima.icon('run') tab_icon2 = ima.icon('terminated') else: fname = id(shellwidget) if os.name == 'nt': tab_name = _("Command Window") else: tab_name = _("Terminal") self.terminal_count += 1 tab_name += (" %d" % self.terminal_count) tab_icon1 = ima.icon('cmdprompt') tab_icon2 = ima.icon('cmdprompt_t') self.shellwidgets.insert(index, shellwidget) self.filenames.insert(index, fname) self.icons.insert(index, (tab_icon1, tab_icon2)) if index is None: index = self.tabwidget.addTab(shellwidget, tab_name) else: self.tabwidget.insertTab(index, shellwidget, tab_name) shellwidget.started.connect( lambda sid=id(shellwidget): self.process_started(sid)) shellwidget.sig_finished.connect( lambda sid=id(shellwidget): self.process_finished(sid)) self.find_widget.set_editor(shellwidget.shell) self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir) self.tabwidget.setCurrentIndex(index) if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() shellwidget.set_icontext_visible(self.get_option('show_icontext')) # Start process and give focus to console shellwidget.start_shell() def open_file_in_spyder(self, fname, lineno): """Open file in Spyder's editor from remote process""" self.main.editor.activateWindow() self.main.editor.raise_() self.main.editor.load(fname, lineno) #------ Private API ------------------------------------------------------- def process_started(self, shell_id): index = self.get_shell_index_from_id(shell_id) shell = self.shellwidgets[index] icon, _icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.help is not None: self.help.set_shell(shell.shell) if self.variableexplorer is not None: self.variableexplorer.add_shellwidget(shell) def process_finished(self, shell_id): index = self.get_shell_index_from_id(shell_id) if index is not None: # Not sure why it happens, but sometimes the shellwidget has # already been removed, so that's not bad if we can't change # the tab icon... _icon, icon = self.icons[index] self.tabwidget.setTabIcon(index, icon) if self.variableexplorer is not None: self.variableexplorer.remove_shellwidget(shell_id) #------ SpyderPluginWidget API -------------------------------------------- def get_plugin_title(self): """Return widget title""" title = _('Python console') if self.filenames: index = self.tabwidget.currentIndex() fname = self.filenames[index] if fname: title += ' - ' + to_text_string(fname) return title def get_plugin_icon(self): """Return widget icon""" return ima.icon('console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def get_plugin_actions(self): """Return a list of actions related to plugin""" interpreter_action = create_action(self, _("Open a &Python console"), None, ima.icon('python'), triggered=self.open_interpreter) if os.name == 'nt': text = _("Open &command prompt") tip = _("Open a Windows command prompt") else: text = _("Open a &terminal") tip = _("Open a terminal window") terminal_action = create_action(self, text, None, None, tip, triggered=self.open_terminal) run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) consoles_menu_actions = [interpreter_action] tools_menu_actions = [terminal_action] self.menu_actions = [interpreter_action, terminal_action, run_action] self.main.consoles_menu_actions += consoles_menu_actions self.main.tools_menu_actions += tools_menu_actions return self.menu_actions+consoles_menu_actions+tools_menu_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.main.add_dockwidget(self) self.help = self.main.help self.historylog = self.main.historylog self.edit_goto.connect(self.main.editor.load) self.edit_goto[str, int, str, bool].connect( lambda fname, lineno, word, processevents: self.main.editor.load(fname, lineno, word, processevents=processevents)) self.main.editor.run_in_current_extconsole.connect( self.run_script_in_current_shell) self.main.editor.breakpoints_saved.connect( self.set_spyder_breakpoints) self.main.editor.open_dir.connect( self.set_current_shell_working_directory) self.main.workingdirectory.set_current_console_wd.connect( self.set_current_shell_working_directory) self.focus_changed.connect( self.main.plugin_focus_changed) self.redirect_stdio.connect( self.main.redirect_internalshell_stdio) expl = self.main.explorer if expl is not None: expl.open_terminal.connect(self.open_terminal) expl.open_interpreter.connect(self.open_interpreter) pexpl = self.main.projects if pexpl is not None: pexpl.open_terminal.connect(self.open_terminal) pexpl.open_interpreter.connect(self.open_interpreter) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" for shellwidget in self.shellwidgets: shellwidget.close() return True def restart(self): """ Restart the console This is needed when we switch project to update PYTHONPATH and the selected interpreter """ self.python_count = 0 for i in range(len(self.shellwidgets)): self.close_console() self.open_interpreter() def refresh_plugin(self): """Refresh tabwidget""" shellwidget = None if self.tabwidget.count(): shellwidget = self.tabwidget.currentWidget() editor = shellwidget.shell editor.setFocus() widgets = [shellwidget.create_time_label(), 5 ]+shellwidget.get_toolbar_buttons()+[5] else: editor = None widgets = [] self.find_widget.set_editor(editor) self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets}) if shellwidget: shellwidget.update_time_label_visibility() self.variableexplorer.set_shellwidget_from_id(id(shellwidget)) self.help.set_shell(shellwidget.shell) self.main.last_console_plugin_focus_was_python = True self.update_plugin_title.emit() def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() for shellwidget in self.shellwidgets: shellwidget.shell.set_font(font) completion_size = CONF.get('main', 'completion/size') comp_widget = shellwidget.shell.completion_widget comp_widget.setup_appearance(completion_size, font) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" showtime_n = 'show_elapsed_time' showtime_o = self.get_option(showtime_n) icontext_n = 'show_icontext' icontext_o = self.get_option(icontext_n) calltips_n = 'calltips' calltips_o = self.get_option(calltips_n) help_n = 'connect_to_oi' help_o = CONF.get('help', 'connect/python_console') wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) compauto_n = 'codecompletion/auto' compauto_o = self.get_option(compauto_n) case_comp_n = 'codecompletion/case_sensitive' case_comp_o = self.get_option(case_comp_n) compenter_n = 'codecompletion/enter_key' compenter_o = self.get_option(compenter_n) mlc_n = 'max_line_count' mlc_o = self.get_option(mlc_n) for shellwidget in self.shellwidgets: if showtime_n in options: shellwidget.set_elapsed_time_visible(showtime_o) if icontext_n in options: shellwidget.set_icontext_visible(icontext_o) if calltips_n in options: shellwidget.shell.set_calltips(calltips_o) if help_n in options: if isinstance(shellwidget, ExternalPythonShell): shellwidget.shell.set_help_enabled(help_o) if wrap_n in options: shellwidget.shell.toggle_wrap_mode(wrap_o) if compauto_n in options: shellwidget.shell.set_codecompletion_auto(compauto_o) if case_comp_n in options: shellwidget.shell.set_codecompletion_case(case_comp_o) if compenter_n in options: shellwidget.shell.set_codecompletion_enter(compenter_o) if mlc_n in options: shellwidget.shell.setMaximumBlockCount(mlc_o) #------ SpyderPluginMixin API --------------------------------------------- def toggle_view(self, checked): """Toggle view""" if checked: self.dockwidget.show() self.dockwidget.raise_() # Start a console in case there are none shown from spyder.widgets.externalshell import pythonshell consoles = None for sw in self.shellwidgets: if isinstance(sw, pythonshell.ExternalPythonShell): consoles = True break if not consoles: self.open_interpreter() else: self.dockwidget.hide() #------ Public API --------------------------------------------------------- @Slot(bool) @Slot(str) def open_interpreter(self, wdir=None): """Open interpreter""" if not wdir: wdir = getcwd() self.visibility_changed(True) self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=True) @Slot(bool) @Slot(str) def open_terminal(self, wdir=None): """Open terminal""" if not wdir: wdir = getcwd() self.start(fname=None, wdir=to_text_string(wdir), args='', interact=True, debug=False, python=False) @Slot() def run_script(self): """Run a Python script""" self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename(self, _("Run Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)") self.redirect_stdio.emit(True) if filename: self.start(fname=filename, wdir=None, args='', interact=False, debug=False) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_goto.emit(osp.abspath(fname), int(lnb), '') #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): pathlist = mimedata2url(source) shellwidget = self.tabwidget.currentWidget() if all([is_python_script(to_text_string(qstr)) for qstr in pathlist]): event.acceptProposedAction() elif shellwidget is None or not shellwidget.is_running(): event.ignore() else: event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() shellwidget = self.tabwidget.currentWidget() if source.hasText(): qstr = source.text() if is_python_script(to_text_string(qstr)): self.start(qstr) elif shellwidget: shellwidget.shell.insert_text(qstr) elif source.hasUrls(): pathlist = mimedata2url(source) if all([is_python_script(to_text_string(qstr)) for qstr in pathlist]): for fname in pathlist: self.start(fname) elif shellwidget: shellwidget.shell.drop_pathlist(pathlist) event.acceptProposedAction()
class PydocBrowser(PluginMainWidget): """PyDoc browser widget.""" ENABLE_SPINNER = True # --- Signals # ------------------------------------------------------------------------ sig_load_finished = Signal() """ This signal is emitted to indicate the help page has finished loading. """ def __init__(self, name=None, plugin=None, parent=None): super().__init__(name, plugin, parent=parent) self._is_running = False self.home_url = None self.server = None # Widgets self.label = QLabel(_("Package:")) self.url_combo = UrlComboBox(self) self.webview = FrameWebView(self, handle_links=self.get_conf('handle_links')) self.find_widget = FindReplace(self) # Setup self.find_widget.set_editor(self.webview) self.find_widget.hide() self.url_combo.setMaxCount(self.get_conf('max_history_entries')) tip = _('Write a package name here, e.g. pandas') self.url_combo.lineEdit().setPlaceholderText(tip) self.url_combo.lineEdit().setToolTip(tip) self.webview.setup() self.webview.set_zoom_factor(self.get_conf('zoom_factor')) # Layout layout = QVBoxLayout() layout.addWidget(self.webview) layout.addSpacing(1) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.url_combo.valid.connect( lambda x: self._handle_url_combo_activation()) self.webview.loadStarted.connect(self._start) self.webview.loadFinished.connect(self._finish) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self._change_url) if not WEBENGINE: self.webview.iconChanged.connect(self._handle_icon_change) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('Online help') def get_focus_widget(self): self.url_combo.lineEdit().selectAll() return self.url_combo def setup(self): # Actions home_action = self.create_action( PydocBrowserActions.Home, text=_("Home"), tip=_("Home"), icon=self.create_icon('home'), triggered=self.go_home, ) find_action = self.create_action( PydocBrowserActions.Find, text=_("Find"), tip=_("Find text"), icon=self.create_icon('find'), toggled=self.toggle_find_widget, initial=False, ) stop_action = self.get_action(WebViewActions.Stop) refresh_action = self.get_action(WebViewActions.Refresh) # Toolbar toolbar = self.get_main_toolbar() for item in [ self.get_action(WebViewActions.Back), self.get_action(WebViewActions.Forward), refresh_action, stop_action, home_action, self.label, self.url_combo, self.get_action(WebViewActions.ZoomIn), self.get_action(WebViewActions.ZoomOut), find_action, ]: self.add_item_to_toolbar( item, toolbar=toolbar, section=PydocBrowserMainToolbarSections.Main, ) # Signals self.find_widget.visibility_changed.connect(find_action.setChecked) for __, action in self.get_actions().items(): if action: # IMPORTANT: Since we are defining the main actions in here # and the context is WidgetWithChildrenShortcut we need to # assign the same actions to the children widgets in order # for shortcuts to work self.webview.addAction(action) self.sig_toggle_view_changed.connect(self.initialize) def update_actions(self): stop_action = self.get_action(WebViewActions.Stop) refresh_action = self.get_action(WebViewActions.Refresh) refresh_action.setVisible(not self._is_running) stop_action.setVisible(self._is_running) # --- Private API # ------------------------------------------------------------------------ def _start(self): """Webview load started.""" self._is_running = True self.start_spinner() self.update_actions() def _finish(self, code): """Webview load finished.""" self._is_running = False self.stop_spinner() self.update_actions() self.sig_load_finished.emit() def _continue_initialization(self): """Load home page.""" self.go_home() QApplication.restoreOverrideCursor() def _handle_url_combo_activation(self): """Load URL from combo box first item.""" if not self._is_running: text = str(self.url_combo.currentText()) self.go_to(self.text_to_url(text)) else: self.get_action(WebViewActions.Stop).trigger() self.get_focus_widget().setFocus() def _change_url(self, url): """ Displayed URL has changed -> updating URL combo box. """ self.url_combo.add_text(self.url_to_text(url)) def _handle_icon_change(self): """ Handle icon changes. """ self.url_combo.setItemIcon(self.url_combo.currentIndex(), self.webview.icon()) self.setWindowIcon(self.webview.icon()) # --- Qt overrides # ------------------------------------------------------------------------ def closeEvent(self, event): self.server.quit_server() event.accept() # --- Public API # ------------------------------------------------------------------------ def load_history(self, history): """ Load history. Parameters ---------- history: list List of searched items. """ self.url_combo.addItems(history) @Slot(bool) def initialize(self, checked=True): """ Start pydoc server. Parameters ---------- checked: bool, optional This method is connected to the `sig_toggle_view_changed` signal, so that the first time the widget is made visible it will start the server. Default is True. """ if checked and self.server is None: self.sig_toggle_view_changed.disconnect(self.initialize) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.processEvents() self.start_server() def is_server_running(self): """Return True if pydoc server is already running.""" return self.server is not None def start_server(self): """Start pydoc server.""" if self.server is None: self.set_home_url('http://127.0.0.1:{}/'.format(PORT)) elif self.server.isRunning(): self.server.sig_server_started.disconnect( self._continue_initialization) self.server.quit() self.server = PydocServer(port=PORT) self.server.sig_server_started.connect(self._continue_initialization) self.server.start() def quit_server(self): """Quit the server.""" if self.server is None: return if self.server.is_running(): self.server.sig_server_started.disconnect( self._continue_initialization) self.server.quit_server() self.server.quit() def get_label(self): """Return address label text""" return _("Package:") def reload(self): """Reload page.""" if self.server: self.webview.reload() def text_to_url(self, text): """ Convert text address into QUrl object. Parameters ---------- text: str Url address. """ if text != 'about:blank': text += '.html' if text.startswith('/'): text = text[1:] return QUrl(self.home_url.toString() + text) def url_to_text(self, url): """ Convert QUrl object to displayed text in combo box. Parameters ---------- url: QUrl Url address. """ string_url = url.toString() if 'about:blank' in string_url: return 'about:blank' elif 'get?key=' in string_url or 'search?key=' in string_url: return url.toString().split('=')[-1] return osp.splitext(str(url.path()))[0][1:] def set_home_url(self, text): """ Set home URL. Parameters ---------- text: str Home url address. """ self.home_url = QUrl(text) def set_url(self, url): """ Set current URL. Parameters ---------- url: QUrl or str Url address. """ self._change_url(url) self.go_to(url) def go_to(self, url_or_text): """ Go to page URL. """ if isinstance(url_or_text, str): url = QUrl(url_or_text) else: url = url_or_text self.webview.load(url) @Slot() def go_home(self): """ Go to home page. """ if self.home_url is not None: self.set_url(self.home_url) def get_zoom_factor(self): """ Get the current zoom factor. Returns ------- int Zoom factor. """ return self.webview.get_zoom_factor() def get_history(self): """ Return the list of history items in the combobox. Returns ------- list List of strings. """ history = [] for index in range(self.url_combo.count()): history.append(str(self.url_combo.itemText(index))) return history @Slot(bool) def toggle_find_widget(self, state): """ Show/hide the find widget. Parameters ---------- state: bool True to show and False to hide the find widget. """ if state: self.find_widget.show() else: self.find_widget.hide()
class NotebookClient(QWidget): """ Notebook client for Spyder. This is a widget composed of a NotebookWidget and a find dialog to render notebooks. """ def __init__(self, plugin, filename, ini_message=None): """Constructor.""" super(NotebookClient, self).__init__(plugin) if os.name == 'nt': filename = filename.replace('/', '\\') self.filename = filename self.file_url = None self.server_url = None self.path = None self.plugin_actions = plugin.get_plugin_actions() self.notebookwidget = NotebookWidget(self) if WEBENGINE: self.notebookwidget.loadFinished.connect(self.hide_header) else: self.notebookwidget.selectionChanged.connect(self.hide_header) self.notebookwidget.urlChanged.connect(self.hide_header) if ini_message: self.notebookwidget.show_message(ini_message) else: self.notebookwidget.show_blank() self.find_widget = FindReplace(self) self.find_widget.set_editor(self.notebookwidget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.notebookwidget) layout.addWidget(self.find_widget) self.setLayout(layout) def add_token(self, url): """Add notebook token to a given url.""" token_url = url + '?token={}'.format(self.token) return token_url def register(self, server_info): """Register attributes that can be computed with the server info.""" # Path relative to the server directory self.path = os.path.relpath(self.filename, start=server_info['notebook_dir']) # Replace backslashes on Windows if os.name == 'nt': self.path = self.path.replace('\\', '/') # Server url to send requests to self.server_url = server_info['url'] # Server token self.token = server_info['token'] url = url_path_join(self.server_url, 'notebooks', url_escape(self.path)) # Set file url to load this notebook self.file_url = self.add_token(url) def go_to(self, url_or_text): """Go to page utl.""" if is_text_string(url_or_text): url = QUrl(url_or_text) else: url = url_or_text self.notebookwidget.load(url) def load_notebook(self): """Load the associated notebook.""" self.go_to(self.file_url) def hide_header(self): """Hide the header of the notebook.""" self.notebookwidget.set_class_value("#header-container", "hidden") def get_filename(self): """Get notebook's filename.""" return self.filename def get_short_name(self): """Get a short name for the notebook.""" sname = osp.splitext(osp.basename(self.filename))[0] if len(sname) > 20: fm = QFontMetrics(QFont()) sname = fm.elidedText(sname, Qt.ElideRight, 110) return sname def save(self): """Save current notebook.""" self.notebookwidget.click("#save-notbook button") def get_session_url(self): """Get the kernel sessions url of the client.""" return self.add_token(url_path_join(self.server_url, 'api/sessions')) def get_kernel_id(self): """ Get the kernel id of the client. Return a str with the kernel id or None. """ sessions_url = self.get_session_url() sessions_req = requests.get(sessions_url).content.decode() sessions = json.loads(sessions_req) if os.name == 'nt': path = self.path.replace('\\', '/') else: path = self.path for session in sessions: notebook_path = session.get('notebook', {}).get('path') if notebook_path is not None and notebook_path == path: kernel_id = session['kernel']['id'] return kernel_id def shutdown_kernel(self): """Shutdown the kernel of the client.""" kernel_id = self.get_kernel_id() if kernel_id: delete_url = self.add_token( url_path_join(self.server_url, 'api/kernels/', kernel_id)) delete_req = requests.delete(delete_url) if delete_req.status_code != 204: QMessageBox.warning( self, _("Server error"), _("The Jupyter Notebook server " "failed to shutdown the kernel " "associated with this notebook. " "If you want to shut it down, " "you'll have to close Spyder."))
class Console(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) logger.info("Initializing...") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect(lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.error_dlg = None self.error_traceback = "" self.dismiss_error = False #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def refresh_plugin(self): pass def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action(self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action(self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) calltips_action = create_action(self, _("Display balloon tips"), toggled=self.toggle_calltips) calltips_action.setChecked(self.get_option('calltips')) codecompletion_action = create_action(self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked(self.get_option('codecompletion/auto')) codecompenter_action = create_action(self, _("Enter key selects completion"), toggled=self.toggle_codecompletion_enter) codecompenter_action.setChecked(self.get_option( 'codecompletion/enter_key')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions(option_menu, (buffer_action, wrap_action, calltips_action, codecompletion_action, codecompenter_action, exteditor_action)) plugin_actions = [None, run_action, environ_action, syspath_action, option_menu, MENU_SEPARATOR, quit_action, self.undock_action] return plugin_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # Connecting the following signal once the dockwidget has been created: self.shell.exception_occurred.connect(self.exception_occurred) def exception_occurred(self, text, is_traceback): """ Exception ocurred in the internal console. Show a QDialog or the internal console to warn the user. """ # Skip errors without traceback or dismiss if (not is_traceback and self.error_dlg is None) or self.dismiss_error: return if CONF.get('main', 'show_internal_errors'): if self.error_dlg is None: self.error_dlg = SpyderErrorDialog(self) self.error_dlg.close_btn.clicked.connect(self.close_error_dlg) self.error_dlg.rejected.connect(self.remove_error_dlg) self.error_dlg.details.go_to_error.connect(self.go_to_error) self.error_dlg.show() self.error_dlg.append_traceback(text) elif DEV or get_debug_level(): self.dockwidget.show() self.dockwidget.raise_() def close_error_dlg(self): """Close error dialog.""" if self.error_dlg.dismiss_box.isChecked(): self.dismiss_error = True self.error_dlg.reject() def remove_error_dlg(self): """Remove error dialog.""" self.error_dlg = None #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog()) @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor() editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir( osp.dirname(filename) ) filename = osp.basename(filename) else: return logger.debug("Running script with %s", args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.shell.write(command+'\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count""" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText(self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_calltips(self, checked): """Toggle calltips""" self.shell.set_calltips(checked) self.set_option('calltips', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) @Slot(bool) def toggle_codecompletion_enter(self, checked): """Toggle Enter key for code completion""" self.shell.set_codecompletion_enter(checked) self.set_option('codecompletion/enter_key', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
class HistoryLog(SpyderPluginWidget): """History log plugin.""" CONF_SECTION = 'historylog' CONFIGWIDGET_CLASS = HistoryConfigPage focus_changed = Signal() def __init__(self, parent): """Initialize plugin and create History main widget.""" SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.dockviewer = None self.wrap_action = None self.linenumbers_action = None self.editors = [] self.filenames = [] # Initialize plugin actions, toolbutton and general signals self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.plugin_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget self.tabwidget.setCornerWidget(self.options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title.""" return _('History log') def get_plugin_icon(self): """Return widget icon.""" return ima.icon('history') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" return True def refresh_plugin(self): """Refresh tabwidget""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) def get_plugin_actions(self): """Return a list of actions related to plugin""" self.history_action = create_action(self, _("History..."), None, ima.icon('history'), _("Set history maximum entries"), triggered=self.change_history_depth) self.wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) self.wrap_action.setChecked( self.get_option('wrap') ) self.linenumbers_action = create_action( self, _("Show line numbers"), toggled=self.toggle_line_numbers) self.linenumbers_action.setChecked(self.get_option('line_numbers')) menu_actions = [self.history_action, self.wrap_action, self.linenumbers_action] return menu_actions def on_first_registration(self): """Action to be performed on first plugin registration""" self.main.tabify_plugins(self.main.ipyconsole, self) def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # self.main.console.set_historylog(self) self.main.console.shell.refresh.connect(self.refresh_plugin) def update_font(self): """Update font from Preferences""" color_scheme = self.get_color_scheme() font = self.get_plugin_font() for editor in self.editors: editor.set_font(font, color_scheme) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" color_scheme_n = 'color_scheme_name' color_scheme_o = self.get_color_scheme() font_n = 'plugin_font' font_o = self.get_plugin_font() wrap_n = 'wrap' wrap_o = self.get_option(wrap_n) self.wrap_action.setChecked(wrap_o) linenb_n = 'line_numbers' linenb_o = self.get_option(linenb_n) for editor in self.editors: if font_n in options: scs = color_scheme_o if color_scheme_n in options else None editor.set_font(font_o, scs) elif color_scheme_n in options: editor.set_color_scheme(color_scheme_o) if wrap_n in options: editor.toggle_wrap_mode(wrap_o) if linenb_n in options: editor.toggle_line_numbers(linenumbers=linenb_o, markers=False) #------ Private API -------------------------------------------------------- def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) #------ Public API --------------------------------------------------------- def add_history(self, filename): """ Add new history tab Slot for add_history signal emitted by shell instance """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return editor = codeeditor.CodeEditor(self) if osp.splitext(filename)[1] == '.py': language = 'py' else: language = 'bat' editor.setup_editor(linenumbers=self.get_option('line_numbers'), language=language, scrollflagarea=False) editor.focus_changed.connect(lambda: self.focus_changed.emit()) editor.setReadOnly(True) color_scheme = self.get_color_scheme() editor.set_font( self.get_plugin_font(), color_scheme ) editor.toggle_wrap_mode( self.get_option('wrap') ) text, _ = encoding.read(filename) text = normalize_eols(text) linebreaks = [m.start() for m in re.finditer('\n', text)] maxNline = self.get_option('max_entries') if len(linebreaks) > maxNline: text = text[linebreaks[-maxNline - 1] + 1:] encoding.write(text, filename) editor.set_text(text) editor.set_cursor_position('eof') self.editors.append(editor) self.filenames.append(filename) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.find_widget.set_editor(editor) self.tabwidget.setTabToolTip(index, filename) self.tabwidget.setCurrentIndex(index) @Slot(str, str) def append_to_history(self, filename, command): """ Append an entry to history filename Slot for append_to_history signal emitted by shell instance """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), 'utf-8') command = to_text_string(command) index = self.filenames.index(filename) self.editors[index].append(command) if self.get_option('go_to_eof'): self.editors[index].set_cursor_position('eof') self.tabwidget.setCurrentIndex(index) @Slot() def change_history_depth(self): "Change history max entries""" depth, valid = QInputDialog.getInt(self, _('History'), _('Maximum entries'), self.get_option('max_entries'), 10, 10000) if valid: self.set_option('max_entries', depth) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" if self.tabwidget is None: return for editor in self.editors: editor.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_line_numbers(self, checked): """Toggle line numbers.""" if self.tabwidget is None: return for editor in self.editors: editor.toggle_line_numbers(linenumbers=checked, markers=False) self.set_option('line_numbers', checked)
class PlainText(QWidget): """ Read-only editor widget with find dialog """ # Signals focus_changed = Signal() sig_custom_context_menu_requested = Signal(QPoint) def __init__(self, parent): QWidget.__init__(self, parent) self.editor = None # Read-only simple code editor self.editor = SimpleCodeEditor(self) self.editor.setup_editor( language='py', highlight_current_line=False, linenumbers=False, ) self.editor.sig_focus_changed.connect(self.focus_changed) self.editor.setReadOnly(True) self.editor.setContextMenuPolicy(Qt.CustomContextMenu) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.editor) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.editor) layout.addWidget(self.find_widget) self.setLayout(layout) self.editor.customContextMenuRequested.connect( self.sig_custom_context_menu_requested) def set_font(self, font, color_scheme=None): """Set font""" self.editor.set_color_scheme(color_scheme) self.editor.set_font(font) def set_color_scheme(self, color_scheme): """Set color scheme""" self.editor.set_color_scheme(color_scheme) def set_text(self, text, is_code): if is_code: self.editor.set_language('py') else: self.editor.set_language(None) self.editor.set_text(text) self.editor.set_cursor_position('sof') def clear(self): self.editor.clear() def set_wrap_mode(self, value): self.editor.toggle_wrap_mode(value) def copy(self): self.editor.copy() def select_all(self): self.editor.selectAll()
class HistoryWidget(PluginMainWidget): """ History plugin main widget. """ # Signals sig_focus_changed = Signal() """ This signal is emitted when the focus of the code editor storing history changes. """ def __init__(self, name, plugin, parent): super().__init__(name, plugin, parent) # Attributes self.editors = [] self.filenames = [] self.tabwidget = None self.dockviewer = None self.wrap_action = None self.linenumbers_action = None self.filenames = [] self.font = None # Widgets self.tabwidget = Tabs(self) self.find_widget = FindReplace(self) # Setup self.tabwidget.setStyleSheet(self._tabs_stylesheet) self.find_widget.hide() # Layout layout = QVBoxLayout() # TODO: Move this to the tab container directly if sys.platform == 'darwin': tab_container = QWidget(self) tab_container.setObjectName('tab-container') tab_layout = QVBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) layout.addWidget(self.find_widget) self.setLayout(layout) # Signals self.tabwidget.currentChanged.connect(self.refresh) self.tabwidget.move_data.connect(self.move_tab) # --- PluginMainWidget API # ------------------------------------------------------------------------ def get_title(self): return _('History') def get_focus_widget(self): return self.tabwidget.currentWidget() def setup(self): # Actions self.wrap_action = self.create_action( HistoryWidgetActions.ToggleWrap, text=_("Wrap lines"), toggled=True, initial=self.get_conf('wrap'), option='wrap' ) self.linenumbers_action = self.create_action( HistoryWidgetActions.ToggleLineNumbers, text=_("Show line numbers"), toggled=True, initial=self.get_conf('line_numbers'), option='line_numbers' ) # Menu menu = self.get_options_menu() for item in [self.wrap_action, self.linenumbers_action]: self.add_item_to_menu( item, menu=menu, section=HistoryWidgetOptionsMenuSections.Main, ) def update_actions(self): pass @on_conf_change(option='wrap') def on_wrap_update(self, value): for editor in self.editors: editor.toggle_wrap_mode(value) @on_conf_change(option='line_numbers') def on_line_numbers_update(self, value): for editor in self.editors: editor.toggle_line_numbers(value) @on_conf_change(option='selected', section='appearance') def on_color_scheme_change(self, value): for editor in self.editors: editor.set_color_scheme(value) # --- Public API # ------------------------------------------------------------------------ def update_font(self, font, color_scheme): """ Update font of the code editor. Parameters ---------- font: QFont Font object. color_scheme: str Name of the color scheme to use. """ self.color_scheme = color_scheme self.font = font for editor in self.editors: editor.set_font(font) editor.set_color_scheme(color_scheme) def move_tab(self, index_from, index_to): """ Move tab. Parameters ---------- index_from: int Move tab from this index. index_to: int Move tab to this index. Notes ----- Tabs themselves have already been moved by the history.tabwidget. """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) def get_filename_text(self, filename): """ Read and return content from filename. Parameters ---------- filename: str The file path to read. Returns ------- str Content of the filename. """ # Avoid a possible error when reading the history file try: text, _ = encoding.read(filename) except (IOError, OSError): text = "# Previous history could not be read from disk, sorry\n\n" text = normalize_eols(text) linebreaks = [m.start() for m in re.finditer('\n', text)] if len(linebreaks) > MAX_LINES: text = text[linebreaks[-MAX_LINES - 1] + 1:] # Avoid an error when trying to write the trimmed text to disk. # See spyder-ide/spyder#9093. try: encoding.write(text, filename) except (IOError, OSError): pass return text def add_history(self, filename): """ Create a history tab for `filename`. Parameters ---------- filename: str History filename. """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return # Widgets editor = SimpleCodeEditor(self) # Setup language = 'py' if osp.splitext(filename)[1] == '.py' else 'bat' editor.setup_editor( linenumbers=self.get_conf('line_numbers'), language=language, color_scheme=self.get_conf('selected', section='appearance'), font=self.font, wrap=self.get_conf('wrap'), ) editor.setReadOnly(True) editor.set_text(self.get_filename_text(filename)) editor.set_cursor_position('eof') self.find_widget.set_editor(editor) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.filenames.append(filename) self.editors.append(editor) self.tabwidget.setCurrentIndex(index) self.tabwidget.setTabToolTip(index, filename) # Signals editor.sig_focus_changed.connect(lambda: self.sig_focus_changed.emit()) @Slot(str, str) def append_to_history(self, filename, command): """ Append command to history tab. Parameters ---------- filename: str History file. command: str Command to append to history file. """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), 'utf-8') index = self.filenames.index(filename) command = to_text_string(command) self.editors[index].append(command) if self.get_conf('go_to_eof'): self.editors[index].set_cursor_position('eof') self.tabwidget.setCurrentIndex(index) def refresh(self): """Refresh widget and update find widget on current editor.""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) @property def _tabs_stylesheet(self): """ Change style of tabs because we don't have a close button here. """ tabs_stylesheet = PANES_TABBAR_STYLESHEET.get_copy() css = tabs_stylesheet.get_stylesheet() css['QTabBar::tab'].setValues( marginTop='1.0em', padding='4px' ) css['QTabWidget::left-corner'].setValues( left='0px', ) css['QTabWidget::right-corner'].setValues( right='0px' ) return str(tabs_stylesheet)
class NotebookClient(QWidget): """ Notebook client for Spyder. This is a widget composed of a NotebookWidget and a find dialog to render notebooks. """ def __init__(self, parent, filename, actions=None, ini_message=None): """ Constructor. Parameters ---------- parent : QWidget Parent of the widget under construction. filename : str File name of the notebook. actions : list of (QAction or QMenu or None) or None, optional Actions to be added to the context menu of the widget under construction. The default is None, meaning that no actions will be added. ini_message : str or None, optional HTML to be initially displayed in the widget. The default is None, meaning that an empty page is displayed initially. """ super().__init__(parent) if os.name == 'nt': filename = filename.replace('/', '\\') self.filename = filename self.file_url = None self.server_url = None self.path = None self.notebookwidget = NotebookWidget(self, actions) if ini_message: self.notebookwidget.show_message(ini_message) self.static = True else: self.notebookwidget.show_blank() self.static = False self.find_widget = FindReplace(self) self.find_widget.set_editor(self.notebookwidget) self.find_widget.hide() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.notebookwidget) layout.addWidget(self.find_widget) self.setLayout(layout) def add_token(self, url): """Add notebook token to a given url.""" token_url = url + '?token={}'.format(self.token) return token_url def register(self, server_info): """Register attributes that can be computed with the server info.""" # Path relative to the server directory self.path = os.path.relpath(self.filename, start=server_info['notebook_dir']) # Replace backslashes on Windows if os.name == 'nt': self.path = self.path.replace('\\', '/') # Server url to send requests to self.server_url = server_info['url'] # Server token self.token = server_info['token'] url = url_path_join(self.server_url, 'notebook', url_escape(self.path)) # Set file url to load this notebook self.file_url = self.add_token(url) def go_to(self, url_or_text): """Go to page utl.""" if isinstance(url_or_text, str): url = QUrl(url_or_text) else: url = url_or_text self.notebookwidget.load(url) def load_notebook(self): """Load the associated notebook.""" self.go_to(self.file_url) def get_filename(self): """Get notebook's filename.""" return self.filename def get_short_name(self): """Get a short name for the notebook.""" sname = osp.splitext(osp.basename(self.filename))[0] if len(sname) > 20: fm = QFontMetrics(QFont()) sname = fm.elidedText(sname, Qt.ElideRight, 110) return sname def save(self): """ Save current notebook. This function saves the current notebook by simulating a click on the Save button in the notebook. The Save button is found by selecting the first element of class `jp-ToolbarButtonComponent` whose `title` attribute begins with the string "Save". NB: I am not sure whether the save is performed before the function returns. """ self.notebookwidget.mousedown( '.jp-ToolbarButtonComponent[title^="Save"]') def get_session_url(self): """Get the kernel sessions url of the client.""" return self.add_token(url_path_join(self.server_url, 'api/sessions')) def get_kernel_id(self): """ Get the kernel id of the client. Return a str with the kernel id or None. On error, display a dialog box and return None. """ sessions_url = self.get_session_url() try: sessions_response = requests.get(sessions_url) except requests.exceptions.RequestException as exception: msg = _('Spyder could not get a list of sessions ' 'from the Jupyter Notebook server. ' 'Message: {}').format(exception) QMessageBox.warning(self, _('Server error'), msg) return None if sessions_response.status_code != requests.codes.ok: msg = _('Spyder could not get a list of sessions ' 'from the Jupyter Notebook server. ' 'Status code: {}').format(sessions_response.status_code) QMessageBox.warning(self, _('Server error'), msg) return None if os.name == 'nt': path = self.path.replace('\\', '/') else: path = self.path sessions = json.loads(sessions_response.content.decode()) for session in sessions: notebook_path = session.get('notebook', {}).get('path') if notebook_path is not None and notebook_path == path: kernel_id = session['kernel']['id'] return kernel_id def shutdown_kernel(self): """Shutdown the kernel of the client.""" kernel_id = self.get_kernel_id() if kernel_id: delete_url = self.add_token( url_path_join(self.server_url, 'api/kernels/', kernel_id)) delete_req = requests.delete(delete_url) if delete_req.status_code != 204: QMessageBox.warning( self, _("Server error"), _("The Jupyter Notebook server " "failed to shutdown the kernel " "associated with this notebook. " "If you want to shut it down, " "you'll have to close Spyder."))
class WebBrowser(QWidget): """ Web browser widget """ def __init__(self, parent=None): QWidget.__init__(self, parent) self.home_url = None self.webview = WebView(self) self.webview.loadFinished.connect(self.load_finished) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self.url_changed) home_button = create_toolbutton(self, icon=ima.icon('home'), tip=_("Home"), triggered=self.go_home) zoom_out_button = action2button(self.webview.zoom_out_action) zoom_in_button = action2button(self.webview.zoom_in_action) pageact2btn = lambda prop: action2button(self.webview.pageAction(prop), parent=self.webview) refresh_button = pageact2btn(QWebEnginePage.Reload) stop_button = pageact2btn(QWebEnginePage.Stop) previous_button = pageact2btn(QWebEnginePage.Back) next_button = pageact2btn(QWebEnginePage.Forward) stop_button.setEnabled(False) self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True)) self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False)) progressbar = QProgressBar(self) progressbar.setTextVisible(False) progressbar.hide() self.webview.loadStarted.connect(progressbar.show) self.webview.loadProgress.connect(progressbar.setValue) self.webview.loadFinished.connect(lambda _state: progressbar.hide()) label = QLabel(self.get_label()) self.url_combo = UrlComboBox(self) self.url_combo.valid.connect(self.url_combo_activated) if not WEBENGINE: self.webview.iconChanged.connect(self.icon_changed) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() find_button = create_toolbutton(self, icon=ima.icon('find'), tip=_("Find text"), toggled=self.toggle_find_widget) self.find_widget.visibility_changed.connect(find_button.setChecked) hlayout = QHBoxLayout() for widget in (previous_button, next_button, home_button, find_button, label, self.url_combo, zoom_out_button, zoom_in_button, refresh_button, progressbar, stop_button): hlayout.addWidget(widget) layout = QVBoxLayout() layout.addLayout(hlayout) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout) def get_label(self): """Return address label text""" return _("Address:") def set_home_url(self, text): """Set home URL""" self.home_url = QUrl(text) def set_url(self, url): """Set current URL""" self.url_changed(url) self.go_to(url) def go_to(self, url_or_text): """Go to page *address*""" if is_text_string(url_or_text): url = QUrl(url_or_text) else: url = url_or_text self.webview.load(url) @Slot() def go_home(self): """Go to home page""" if self.home_url is not None: self.set_url(self.home_url) def text_to_url(self, text): """Convert text address into QUrl object""" return QUrl(text) def url_combo_activated(self, valid): """Load URL from combo box first item""" text = to_text_string(self.url_combo.currentText()) self.go_to(self.text_to_url(text)) def load_finished(self, ok): if not ok: self.webview.setHtml(_("Unable to load page")) def url_to_text(self, url): """Convert QUrl object to displayed text in combo box""" return url.toString() def url_changed(self, url): """Displayed URL has changed -> updating URL combo box""" self.url_combo.add_text(self.url_to_text(url)) def icon_changed(self): self.url_combo.setItemIcon(self.url_combo.currentIndex(), self.webview.icon()) self.setWindowIcon(self.webview.icon()) @Slot(bool) def toggle_find_widget(self, state): if state: self.find_widget.show() else: self.find_widget.hide()
class History(QWidget): """History plugin main widget.""" focus_changed = Signal() def __init__(self, parent): """Initialize widget and create layout.""" QWidget.__init__(self, parent) self.editors = [] self.filenames = [] self.tabwidget = None self.menu_actions = None layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) options_button.setPopupMode(QToolButton.InstantPopup) self.menu = QMenu(self) options_button.setMenu(self.menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() layout.addWidget(self.find_widget) self.setLayout(layout) def set_menu_actions(self, menu_actions): """Add options to corner menu.""" if self.menu_actions is not None: add_actions(self.menu, self.menu_actions) def refresh(self): """Refresh tabwidget.""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) def move_tab(self, index_from, index_to): """ Move tab. (tabs themselves have already been moved by the history.tabwidget) """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) def add_history(self, filename, color_scheme, font, wrap): """ Add new history tab. Args: filename (str): file to be loaded in a new tab. """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return editor = codeeditor.CodeEditor(self) if osp.splitext(filename)[1] == '.py': language = 'py' else: language = 'bat' editor.setup_editor(linenumbers=False, language=language, scrollflagarea=False, show_class_func_dropdown=False) editor.focus_changed.connect(lambda: self.focus_changed.emit()) editor.setReadOnly(True) editor.set_font(font, color_scheme) editor.toggle_wrap_mode(wrap) text, _ = encoding.read(filename) editor.set_text(text) editor.set_cursor_position('eof') self.editors.append(editor) self.filenames.append(filename) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.find_widget.set_editor(editor) self.tabwidget.setTabToolTip(index, filename) self.tabwidget.setCurrentIndex(index) def append_to_history(self, filename, command, go_to_eof): """ Append an entry to history filename. Args: filename (str): file to be updated in a new tab. command (str): line to be added. go_to_eof (bool): scroll to the end of file. """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), 'utf-8') command = to_text_string(command) index = self.filenames.index(filename) self.editors[index].append(command) if go_to_eof: self.editors[index].set_cursor_position('eof') self.tabwidget.setCurrentIndex(index)
def __init__(self, parent=None): QWidget.__init__(self, parent) self.home_url = None self.webview = WebView(self) self.webview.loadFinished.connect(self.load_finished) self.webview.titleChanged.connect(self.setWindowTitle) self.webview.urlChanged.connect(self.url_changed) home_button = create_toolbutton(self, icon=ima.icon('home'), tip=_("Home"), triggered=self.go_home) zoom_out_button = action2button(self.webview.zoom_out_action) zoom_in_button = action2button(self.webview.zoom_in_action) pageact2btn = lambda prop: action2button(self.webview.pageAction(prop), parent=self.webview) refresh_button = pageact2btn(QWebEnginePage.Reload) stop_button = pageact2btn(QWebEnginePage.Stop) previous_button = pageact2btn(QWebEnginePage.Back) next_button = pageact2btn(QWebEnginePage.Forward) stop_button.setEnabled(False) self.webview.loadStarted.connect(lambda: stop_button.setEnabled(True)) self.webview.loadFinished.connect(lambda: stop_button.setEnabled(False)) progressbar = QProgressBar(self) progressbar.setTextVisible(False) progressbar.hide() self.webview.loadStarted.connect(progressbar.show) self.webview.loadProgress.connect(progressbar.setValue) self.webview.loadFinished.connect(lambda _state: progressbar.hide()) label = QLabel(self.get_label()) self.url_combo = UrlComboBox(self) self.url_combo.valid.connect(self.url_combo_activated) if not WEBENGINE: self.webview.iconChanged.connect(self.icon_changed) self.find_widget = FindReplace(self) self.find_widget.set_editor(self.webview) self.find_widget.hide() find_button = create_toolbutton(self, icon=ima.icon('find'), tip=_("Find text"), toggled=self.toggle_find_widget) self.find_widget.visibility_changed.connect(find_button.setChecked) hlayout = QHBoxLayout() for widget in (previous_button, next_button, home_button, find_button, label, self.url_combo, zoom_out_button, zoom_in_button, refresh_button, progressbar, stop_button): hlayout.addWidget(widget) layout = QVBoxLayout() layout.addLayout(hlayout) layout.addWidget(self.webview) layout.addWidget(self.find_widget) self.setLayout(layout)
def __init__(self, parent): if PYQT5: SpyderPluginWidget.__init__(self, parent, main = parent) else: SpyderPluginWidget.__init__(self, parent) self.tabwidget = None self.menu_actions = None self.help = None # Help plugin self.historylog = None # History log plugin self.variableexplorer = None # Variable explorer plugin self.python_count = 0 self.terminal_count = 0 # Python startup file selection if not osp.isfile(self.get_option('pythonstartup', '')): self.set_option('pythonstartup', SCIENTIFIC_STARTUP) # default/custom settings are mutually exclusive: self.set_option('pythonstartup/custom', not self.get_option('pythonstartup/default')) self.shellwidgets = [] self.filenames = [] self.icons = [] self.runfile_args = "" # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) if hasattr(self.tabwidget, 'setDocumentMode')\ and not sys.platform == 'darwin': # Don't set document mode to true on OSX because it generates # a crash when the console is detached from the main window # Fixes Issue 561 self.tabwidget.setDocumentMode(True) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) self.main.sig_pythonpath_changed.connect(self.set_path) self.tabwidget.set_close_function(self.close_console) if sys.platform == 'darwin': tab_container = QWidget() tab_container.setObjectName('tab-container') tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # Accepting drops self.setAcceptDrops(True)
class Console(SpyderPluginWidget): """ Console widget """ CONF_SECTION = 'internal_console' focus_changed = Signal() redirect_stdio = Signal(bool) edit_goto = Signal(str, int, str) def __init__(self, parent=None, namespace=None, commands=[], message=None, exitfunc=None, profile=False, multithreaded=False): SpyderPluginWidget.__init__(self, parent) debug_print(" ..internal console: initializing") self.dialog_manager = DialogManager() # Shell light_background = self.get_option('light_background') self.shell = InternalShell(parent, namespace, commands, message, self.get_option('max_line_count'), self.get_plugin_font(), exitfunc, profile, multithreaded, light_background=light_background) self.shell.status.connect(lambda msg: self.show_message.emit(msg, 0)) self.shell.go_to_error.connect(self.go_to_error) self.shell.focus_changed.connect(lambda: self.focus_changed.emit()) # Redirecting some signals: self.shell.redirect_stdio.connect( lambda state: self.redirect_stdio.emit(state)) # Initialize plugin self.initialize_plugin() # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.set_editor(self.shell) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) # Main layout btn_layout = QHBoxLayout() btn_layout.setAlignment(Qt.AlignLeft) btn_layout.addStretch() btn_layout.addWidget(self.options_button, Qt.AlignRight) layout = create_plugin_layout(btn_layout) layout.addWidget(self.shell) layout.addWidget(self.find_widget) self.setLayout(layout) # Parameters self.shell.toggle_wrap_mode(self.get_option('wrap')) # Accepting drops self.setAcceptDrops(True) # Traceback MessageBox self.msgbox_traceback = None self.error_traceback = "" #------ Private API -------------------------------------------------------- def set_historylog(self, historylog): """Bind historylog instance to this console Not used anymore since v2.0""" historylog.add_history(self.shell.history_filename) self.shell.append_to_history.connect(historylog.append_to_history) def set_help(self, help_plugin): """Bind help instance to this console""" self.shell.help = help_plugin #------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _('Internal console') def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.shell def update_font(self): """Update font from Preferences""" font = self.get_plugin_font() self.shell.set_font(font) def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" self.dialog_manager.close_all() self.shell.exit_interpreter() return True def refresh_plugin(self): pass def get_plugin_actions(self): """Return a list of actions related to plugin""" quit_action = create_action(self, _("&Quit"), icon=ima.icon('exit'), tip=_("Quit"), triggered=self.quit) self.register_shortcut(quit_action, "_", "Quit", "Ctrl+Q") run_action = create_action(self, _("&Run..."), None, ima.icon('run_small'), _("Run a Python script"), triggered=self.run_script) environ_action = create_action( self, _("Environment variables..."), icon=ima.icon('environ'), tip=_("Show and edit environment variables" " (for current session)"), triggered=self.show_env) syspath_action = create_action(self, _("Show sys.path contents..."), icon=ima.icon('syspath'), tip=_("Show (read-only) sys.path"), triggered=self.show_syspath) buffer_action = create_action(self, _("Buffer..."), None, tip=_("Set maximum line count"), triggered=self.change_max_line_count) exteditor_action = create_action( self, _("External editor path..."), None, None, _("Set external editor executable path"), triggered=self.change_exteditor) wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) wrap_action.setChecked(self.get_option('wrap')) calltips_action = create_action(self, _("Display balloon tips"), toggled=self.toggle_calltips) calltips_action.setChecked(self.get_option('calltips')) codecompletion_action = create_action( self, _("Automatic code completion"), toggled=self.toggle_codecompletion) codecompletion_action.setChecked( self.get_option('codecompletion/auto')) codecompenter_action = create_action( self, _("Enter key selects completion"), toggled=self.toggle_codecompletion_enter) codecompenter_action.setChecked( self.get_option('codecompletion/enter_key')) option_menu = QMenu(_('Internal console settings'), self) option_menu.setIcon(ima.icon('tooloptions')) add_actions( option_menu, (buffer_action, wrap_action, calltips_action, codecompletion_action, codecompenter_action, exteditor_action)) plugin_actions = [ None, run_action, environ_action, syspath_action, option_menu, MENU_SEPARATOR, quit_action, self.undock_action ] return plugin_actions def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # Connecting the following signal once the dockwidget has been created: self.shell.exception_occurred.connect(self.exception_occurred) def exception_occurred(self, text, is_traceback): """Exception ocurred in the internal console. Show a QMessageBox or the internal console to warn the user""" # Skip errors without traceback if not is_traceback and self.msgbox_traceback is None: return if CONF.get('main', 'show_internal_console_if_traceback', False): self.dockwidget.show() self.dockwidget.raise_() else: if self.msgbox_traceback is None: self.msgbox_traceback = QMessageBox( QMessageBox.Critical, _('Error'), _("<b>Spyder has encountered a problem.</b><br>" "Sorry for the inconvenience." "<br><br>" "You can automatically submit this error to our Github " "issues tracker.<br><br>" "<i>Note:</i> You need a Github account for that."), QMessageBox.Ok, parent=self) self.submit_btn = self.msgbox_traceback.addButton( _('Submit to Github'), QMessageBox.YesRole) self.submit_btn.pressed.connect(self.press_submit_btn) self.msgbox_traceback.setWindowModality(Qt.NonModal) self.error_traceback = "" self.msgbox_traceback.show() self.msgbox_traceback.finished.connect(self.close_msg) self.msgbox_traceback.setDetailedText(' ') # open show details (iterate over all buttons and click it) for button in self.msgbox_traceback.buttons(): if (self.msgbox_traceback.buttonRole(button) == QMessageBox.ActionRole): button.click() break self.error_traceback += text self.msgbox_traceback.setDetailedText(self.error_traceback) def close_msg(self): self.msgbox_traceback = None def press_submit_btn(self): self.main.report_issue(self.error_traceback) self.msgbox_traceback = None #------ Public API --------------------------------------------------------- @Slot() def quit(self): """Quit mainwindow""" self.main.close() @Slot() def show_env(self): """Show environment variables""" self.dialog_manager.show(EnvDialog()) @Slot() def show_syspath(self): """Show sys.path""" editor = CollectionsEditor() editor.setup(sys.path, title="sys.path", readonly=True, width=600, icon=ima.icon('syspath')) self.dialog_manager.show(editor) @Slot() def run_script(self, filename=None, silent=False, set_focus=False, args=None): """Run a Python script""" if filename is None: self.shell.interpreter.restore_stds() filename, _selfilter = getopenfilename( self, _("Run Python script"), getcwd_or_home(), _("Python scripts") + " (*.py ; *.pyw ; *.ipy)") self.shell.interpreter.redirect_stds() if filename: os.chdir(osp.dirname(filename)) filename = osp.basename(filename) else: return debug_print(args) filename = osp.abspath(filename) rbs = remove_backslashes command = "runfile('%s', args='%s')" % (rbs(filename), rbs(args)) if set_focus: self.shell.setFocus() if self.dockwidget and not self.ismaximized: self.dockwidget.setVisible(True) self.dockwidget.raise_() self.shell.write(command + '\n') self.shell.run_command(command) def go_to_error(self, text): """Go to error if relevant""" match = get_error_match(to_text_string(text)) if match: fname, lnb = match.groups() self.edit_script(fname, int(lnb)) def edit_script(self, filename=None, goto=-1): """Edit script""" # Called from InternalShell if not hasattr(self, 'main') \ or not hasattr(self.main, 'editor'): self.shell.external_editor(filename, goto) return if filename is not None: self.edit_goto.emit(osp.abspath(filename), goto, '') def execute_lines(self, lines): """Execute lines and give focus to shell""" self.shell.execute_lines(to_text_string(lines)) self.shell.setFocus() @Slot() def change_max_line_count(self): "Change maximum line count" "" mlc, valid = QInputDialog.getInt(self, _('Buffer'), _('Maximum line count'), self.get_option('max_line_count'), 0, 1000000) if valid: self.shell.setMaximumBlockCount(mlc) self.set_option('max_line_count', mlc) @Slot() def change_exteditor(self): """Change external editor path""" path, valid = QInputDialog.getText( self, _('External editor'), _('External editor executable path:'), QLineEdit.Normal, self.get_option('external_editor/path')) if valid: self.set_option('external_editor/path', to_text_string(path)) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" self.shell.toggle_wrap_mode(checked) self.set_option('wrap', checked) @Slot(bool) def toggle_calltips(self, checked): """Toggle calltips""" self.shell.set_calltips(checked) self.set_option('calltips', checked) @Slot(bool) def toggle_codecompletion(self, checked): """Toggle automatic code completion""" self.shell.set_codecompletion_auto(checked) self.set_option('codecompletion/auto', checked) @Slot(bool) def toggle_codecompletion_enter(self, checked): """Toggle Enter key for code completion""" self.shell.set_codecompletion_enter(checked) self.set_option('codecompletion/enter_key', checked) #----Drag and drop def dragEnterEvent(self, event): """Reimplement Qt method Inform Qt about the types of data that the widget accepts""" source = event.mimeData() if source.hasUrls(): if mimedata2url(source): event.acceptProposedAction() else: event.ignore() elif source.hasText(): event.acceptProposedAction() def dropEvent(self, event): """Reimplement Qt method Unpack dropped data and handle it""" source = event.mimeData() if source.hasUrls(): pathlist = mimedata2url(source) self.shell.drop_pathlist(pathlist) elif source.hasText(): lines = to_text_string(source.text()) self.shell.set_cursor_position('eof') self.shell.execute_lines(lines) event.acceptProposedAction()
class HistoryLog(SpyderPluginWidget): """ History log widget """ CONF_SECTION = "historylog" CONFIGWIDGET_CLASS = HistoryConfigPage focus_changed = Signal() def __init__(self, parent): self.tabwidget = None self.menu_actions = None self.dockviewer = None self.wrap_action = None self.editors = [] self.filenames = [] if PYQT5: SpyderPluginWidget.__init__(self, parent, main=parent) else: SpyderPluginWidget.__init__(self, parent) # Initialize plugin self.initialize_plugin() layout = QVBoxLayout() self.tabwidget = Tabs(self, self.menu_actions) self.tabwidget.currentChanged.connect(self.refresh_plugin) self.tabwidget.move_data.connect(self.move_tab) if sys.platform == "darwin": tab_container = QWidget() tab_container.setObjectName("tab-container") tab_layout = QHBoxLayout(tab_container) tab_layout.setContentsMargins(0, 0, 0, 0) tab_layout.addWidget(self.tabwidget) layout.addWidget(tab_container) else: layout.addWidget(self.tabwidget) # Menu as corner widget options_button = create_toolbutton(self, text=_("Options"), icon=ima.icon("tooloptions")) options_button.setPopupMode(QToolButton.InstantPopup) menu = QMenu(self) add_actions(menu, self.menu_actions) options_button.setMenu(menu) self.tabwidget.setCornerWidget(options_button) # Find/replace widget self.find_widget = FindReplace(self) self.find_widget.hide() self.register_widget_shortcuts(self.find_widget) layout.addWidget(self.find_widget) self.setLayout(layout) # ------ SpyderPluginWidget API --------------------------------------------- def get_plugin_title(self): """Return widget title""" return _("History log") def get_plugin_icon(self): """Return widget icon""" return ima.icon("history") def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level """ return self.tabwidget.currentWidget() def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed""" return True def refresh_plugin(self): """Refresh tabwidget""" if self.tabwidget.count(): editor = self.tabwidget.currentWidget() else: editor = None self.find_widget.set_editor(editor) def get_plugin_actions(self): """Return a list of actions related to plugin""" history_action = create_action( self, _("History..."), None, ima.icon("history"), _("Set history maximum entries"), triggered=self.change_history_depth, ) self.wrap_action = create_action(self, _("Wrap lines"), toggled=self.toggle_wrap_mode) self.wrap_action.setChecked(self.get_option("wrap")) self.menu_actions = [history_action, self.wrap_action] return self.menu_actions def on_first_registration(self): """Action to be performed on first plugin registration""" self.main.tabify_plugins(self.main.extconsole, self) def register_plugin(self): """Register plugin in Spyder's main window""" self.focus_changed.connect(self.main.plugin_focus_changed) self.main.add_dockwidget(self) # self.main.console.set_historylog(self) self.main.console.shell.refresh.connect(self.refresh_plugin) def update_font(self): """Update font from Preferences""" color_scheme = self.get_color_scheme() font = self.get_plugin_font() for editor in self.editors: editor.set_font(font, color_scheme) def apply_plugin_settings(self, options): """Apply configuration file's plugin settings""" color_scheme_n = "color_scheme_name" color_scheme_o = self.get_color_scheme() font_n = "plugin_font" font_o = self.get_plugin_font() wrap_n = "wrap" wrap_o = self.get_option(wrap_n) self.wrap_action.setChecked(wrap_o) for editor in self.editors: if font_n in options: scs = color_scheme_o if color_scheme_n in options else None editor.set_font(font_o, scs) elif color_scheme_n in options: editor.set_color_scheme(color_scheme_o) if wrap_n in options: editor.toggle_wrap_mode(wrap_o) # ------ Private API -------------------------------------------------------- def move_tab(self, index_from, index_to): """ Move tab (tabs themselves have already been moved by the tabwidget) """ filename = self.filenames.pop(index_from) editor = self.editors.pop(index_from) self.filenames.insert(index_to, filename) self.editors.insert(index_to, editor) # ------ Public API --------------------------------------------------------- def add_history(self, filename): """ Add new history tab Slot for add_history signal emitted by shell instance """ filename = encoding.to_unicode_from_fs(filename) if filename in self.filenames: return editor = codeeditor.CodeEditor(self) if osp.splitext(filename)[1] == ".py": language = "py" else: language = "bat" editor.setup_editor(linenumbers=False, language=language, scrollflagarea=False) editor.focus_changed.connect(lambda: self.focus_changed.emit()) editor.setReadOnly(True) color_scheme = self.get_color_scheme() editor.set_font(self.get_plugin_font(), color_scheme) editor.toggle_wrap_mode(self.get_option("wrap")) text, _ = encoding.read(filename) editor.set_text(text) editor.set_cursor_position("eof") self.editors.append(editor) self.filenames.append(filename) index = self.tabwidget.addTab(editor, osp.basename(filename)) self.find_widget.set_editor(editor) self.tabwidget.setTabToolTip(index, filename) self.tabwidget.setCurrentIndex(index) def append_to_history(self, filename, command): """ Append an entry to history filename Slot for append_to_history signal emitted by shell instance """ if not is_text_string(filename): # filename is a QString filename = to_text_string(filename.toUtf8(), "utf-8") command = to_text_string(command) index = self.filenames.index(filename) self.editors[index].append(command) if self.get_option("go_to_eof"): self.editors[index].set_cursor_position("eof") self.tabwidget.setCurrentIndex(index) @Slot() def change_history_depth(self): "Change history max entries" "" depth, valid = QInputDialog.getInt( self, _("History"), _("Maximum entries"), self.get_option("max_entries"), 10, 10000 ) if valid: self.set_option("max_entries", depth) @Slot(bool) def toggle_wrap_mode(self, checked): """Toggle wrap mode""" if self.tabwidget is None: return for editor in self.editors: editor.toggle_wrap_mode(checked) self.set_option("wrap", checked)