class FindReplace(QWidget): """Find widget""" STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} visibility_changed = Signal(bool) def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton( self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect(lambda state: self.find( changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowUp")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowDown")) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton( self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [ replace_with, self.replace_text, self.replace_button, self.all_check ] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = create_shortcut(self.find_next, context='Editor', name='Find next', parent=parent) findprev = create_shortcut(self.find_previous, context='Editor', name='Find previous', parent=parent) togglefind = create_shortcut(self.show, context='Editor', name='Find text', parent=parent) togglereplace = create_shortcut(self.toggle_replace_widgets, context='Editor', name='Replace text', parent=parent) # Fixed new_shortcut("Escape", self, self.hide) return [findnext, findprev, togglefind, togglereplace] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) if self.editor is not None: text = self.editor.get_selected_text() if len(text) > 0: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self): """Find next occurence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self): """Find previous occurence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if self.is_code_editor and found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found
class FindReplace(QWidget): """Find widget""" STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} visibility_changed = Signal(bool) def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=ima.icon('DialogCloseButton')) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.search_text.valid.connect( lambda state: self.find(changed=False, forward=True, rehighlight=False)) self.search_text.lineEdit().textEdited.connect( self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=ima.icon('ArrowUp')) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=ima.icon('ArrowDown')) self.next_button.clicked.connect(self.update_search_combo) self.previous_button.clicked.connect(self.update_search_combo) self.re_button = create_toolbutton(self, icon=ima.icon('advanced'), tip=_("Regular expression")) self.re_button.setCheckable(True) self.re_button.toggled.connect(lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.case_button.toggled.connect(lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.words_button.toggled.connect(lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.highlight_button.toggled.connect(self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_('Replace string')) self.replace_button = create_toolbutton(self, text=_('Replace/find'), icon=ima.icon('DialogApplyButton'), triggered=self.replace_find, text_beside_icon=True) self.replace_button.clicked.connect(self.update_replace_combo) self.replace_button.clicked.connect(self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.shortcuts = self.create_shortcuts(parent) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.highlight_timer.timeout.connect(self.highlight_matches) def create_shortcuts(self, parent): """Create shortcuts for this widget""" # Configurable findnext = create_shortcut(self.find_next, context='Editor', name='Find next', parent=parent) findprev = create_shortcut(self.find_previous, context='Editor', name='Find previous', parent=parent) togglefind = create_shortcut(self.show, context='Editor', name='Find text', parent=parent) togglereplace = create_shortcut(self.toggle_replace_widgets, context='Editor', name='Replace text', parent=parent) # Fixed new_shortcut("Escape", self, self.hide) return [findnext, findprev, togglefind, togglereplace] def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [sc.data for sc in self.shortcuts] def update_search_combo(self): self.search_text.lineEdit().returnPressed.emit() def update_replace_combo(self): self.replace_text.lineEdit().returnPressed.emit() def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() @Slot(bool) def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.visibility_changed.emit(True) if self.editor is not None: text = self.editor.get_selected_text() # If no text is highlighted for search, use whatever word is under the cursor if not text: cursor = self.editor.textCursor() cursor.select(QTextCursor.WordUnderCursor) text = to_text_string(cursor.selectedText()) # Now that text value is sorted out, use it for the search if text: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() @Slot() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.visibility_changed.emit(False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() @Slot() def find_next(self): """Find next occurrence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state @Slot() def find_previous(self): """Find previous occurrence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if self.is_code_editor and found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found @Slot() def replace_find(self): """Replace and find""" if (self.editor is not None): replace_text = to_text_string(self.replace_text.currentText()) search_text = to_text_string(self.search_text.currentText()) pattern = search_text if self.re_button.isChecked() else None case = self.case_button.isChecked() first = True cursor = None while True: if first: # First found seltxt = to_text_string(self.editor.get_selected_text()) cmptxt1 = search_text if case else search_text.lower() cmptxt2 = seltxt if case else seltxt.lower() if self.editor.has_selected_text() and cmptxt1 == cmptxt2: # Text was already found, do nothing pass else: if not self.find(changed=False, forward=True, rehighlight=False): break first = False wrapped = False position = self.editor.get_position('cursor') position0 = position cursor = self.editor.textCursor() cursor.beginEditBlock() else: position1 = self.editor.get_position('cursor') if is_position_inf(position1, position0 + len(replace_text) - len(search_text) + 1): # Identify wrapping even when the replace string # includes part of the search string wrapped = True if wrapped: if position1 == position or \ is_position_sup(position1, position): # Avoid infinite loop: replace string includes # part of the search string break if position1 == position0: # Avoid infinite loop: single found occurrence break position0 = position1 if pattern is None: cursor.removeSelectedText() cursor.insertText(replace_text) else: seltxt = to_text_string(cursor.selectedText()) cursor.removeSelectedText() cursor.insertText(re.sub(pattern, replace_text, seltxt)) if self.find_next(): found_cursor = self.editor.textCursor() cursor.setPosition(found_cursor.selectionStart(), QTextCursor.MoveAnchor) cursor.setPosition(found_cursor.selectionEnd(), QTextCursor.KeepAnchor) else: break if not self.all_check.isChecked(): break self.all_check.setCheckState(Qt.Unchecked) if cursor is not None: cursor.endEditBlock()
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) plugin.run() except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.received.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.ids = dict() self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True method = 'get_%s' % info.name value = info.serialize() self.ids = dict() for plugin in plugins: request_id = plugin.request(method, value) self.ids[request_id] = plugin.name self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) def validate(self): for plugin in self.plugins.values(): plugin.request('validate') def handle_response(self, response): name = self.ids.get(response['request_id'], None) if not name: return if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): [plugin.close() for plugin in self.plugins] def _finalize(self, response): self.waiting = False self.pending = None if self.info: delta = time.time() - self._start_time debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['name'], str(response['result'])[:100], delta)) response['info'] = self.info self.introspection_complete.emit(response) self.info = None if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired')
class PluginManager(QObject): introspection_complete = Signal(object) def __init__(self, executable): super(PluginManager, self).__init__() plugins = OrderedDict() for name in PLUGINS: try: plugin = PluginClient(name, executable) except Exception as e: debug_print('Introspection Plugin Failed: %s' % name) debug_print(str(e)) continue debug_print('Introspection Plugin Loaded: %s' % name) plugins[name] = plugin plugin.request_handled.connect(self.handle_response) self.plugins = plugins self.timer = QTimer() self.desired = [] self.info = None self.request = None self.pending = None self.pending_request = None self.waiting = False def send_request(self, info): """Handle an incoming request from the user.""" if self.waiting: if info.serialize() != self.info.serialize(): self.pending_request = info else: debug_print('skipping duplicate request') return debug_print('%s request' % info.name) desired = None self.info = info editor = info.editor if (info.name == 'completion' and 'jedi' not in self.plugins and info.line.lstrip().startswith(('import ', 'from '))): desired = 'fallback' if ((not editor.is_python_like()) or sourcecode.is_keyword(info.obj) or (editor.in_comment_or_string() and info.name != 'info')): desired = 'fallback' plugins = self.plugins.values() if desired: plugins = [self.plugins[desired]] self.desired = [desired] elif (info.name == 'definition' and not info.editor.is_python() or info.name == 'info'): self.desired = list(self.plugins.keys()) else: # Use all but the fallback plugins = list(self.plugins.values())[:-1] self.desired = list(self.plugins.keys())[:-1] self._start_time = time.time() self.waiting = True request = dict(method='get_%s' % info.name, args=[info.serialize()], request_id=id(info)) for plugin in plugins: plugin.send(request) debug_print('sending %s' % (plugin.plugin_name)) self.timer.stop() self.timer.singleShot(LEAD_TIME_SEC * 1000, self._handle_timeout) self.request = request def validate(self): message = dict(method='validate') for plugin in self.plugins.values(): plugin.send(message) def handle_response(self, response): if (self.request is None): return if (response.get('request_id', None) != id(self.info)): return name = response['plugin_name'] response['info'] = self.info if response.get('error', None): debug_print('Response error:', response['error']) return if name == self.desired[0] or not self.waiting: if response.get('result', None): self._finalize(response) else: self.pending = response def close(self): [plugin.close() for plugin in self.plugins] def _finalize(self, response): self.waiting = False self.pending = None delta = time.time() - self._start_time debug_print(str(response.keys())) debug_print('%s request from %s finished: "%s" in %.1f sec' % (self.info.name, response['plugin_name'], str(response['result'])[:100], delta)) self.introspection_complete.emit(response) self.info = None if self.pending_request: info = self.pending_request self.pending_request = None self.send_request(info) def _handle_timeout(self): self.waiting = False if self.pending: self._finalize(self.pending) else: debug_print('No valid responses acquired', self.request['method'])
class FindReplace(QWidget): """ Find widget Signals: visibility_changed(bool) """ STYLE = {False: "background-color:rgb(255, 175, 90);", True: ""} def __init__(self, parent, enable_replace=False): QWidget.__init__(self, parent) self.enable_replace = enable_replace self.editor = None self.is_code_editor = None glayout = QGridLayout() glayout.setContentsMargins(0, 0, 0, 0) self.setLayout(glayout) self.close_button = create_toolbutton(self, triggered=self.hide, icon=get_std_icon("DialogCloseButton")) glayout.addWidget(self.close_button, 0, 0) # Find layout self.search_text = PatternComboBox(self, tip=_("Search string"), adjust_to_minimum=False) self.connect(self.search_text.lineEdit(), SIGNAL("textEdited(QString)"), self.text_has_been_edited) self.previous_button = create_toolbutton(self, triggered=self.find_previous, icon=get_std_icon("ArrowBack")) self.next_button = create_toolbutton(self, triggered=self.find_next, icon=get_std_icon("ArrowForward")) self.connect(self.next_button, SIGNAL("clicked()"), self.update_search_combo) self.connect(self.previous_button, SIGNAL("clicked()"), self.update_search_combo) self.re_button = create_toolbutton(self, icon=get_icon("advanced.png"), tip=_("Regular expression")) self.re_button.setCheckable(True) self.connect(self.re_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.case_button = create_toolbutton(self, icon=get_icon("upper_lower.png"), tip=_("Case Sensitive")) self.case_button.setCheckable(True) self.connect(self.case_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.words_button = create_toolbutton(self, icon=get_icon("whole_words.png"), tip=_("Whole words")) self.words_button.setCheckable(True) self.connect(self.words_button, SIGNAL("toggled(bool)"), lambda state: self.find()) self.highlight_button = create_toolbutton(self, icon=get_icon("highlight.png"), tip=_("Highlight matches")) self.highlight_button.setCheckable(True) self.connect(self.highlight_button, SIGNAL("toggled(bool)"), self.toggle_highlighting) hlayout = QHBoxLayout() self.widgets = [ self.close_button, self.search_text, self.previous_button, self.next_button, self.re_button, self.case_button, self.words_button, self.highlight_button, ] for widget in self.widgets[1:]: hlayout.addWidget(widget) glayout.addLayout(hlayout, 0, 1) # Replace layout replace_with = QLabel(_("Replace with:")) self.replace_text = PatternComboBox(self, adjust_to_minimum=False, tip=_("Replace string")) self.replace_button = create_toolbutton( self, text=_("Replace/find"), icon=get_std_icon("DialogApplyButton"), triggered=self.replace_find, text_beside_icon=True, ) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_replace_combo) self.connect(self.replace_button, SIGNAL("clicked()"), self.update_search_combo) self.all_check = QCheckBox(_("Replace all")) self.replace_layout = QHBoxLayout() widgets = [replace_with, self.replace_text, self.replace_button, self.all_check] for widget in widgets: self.replace_layout.addWidget(widget) glayout.addLayout(self.replace_layout, 1, 1) self.widgets.extend(widgets) self.replace_widgets = widgets self.hide_replace() self.search_text.setTabOrder(self.search_text, self.replace_text) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.findnext_sc = QShortcut(QKeySequence("F3"), parent, self.find_next) self.findnext_sc.setContext(Qt.WidgetWithChildrenShortcut) self.findprev_sc = QShortcut(QKeySequence("Shift+F3"), parent, self.find_previous) self.findprev_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglefind_sc = QShortcut(QKeySequence("Ctrl+F"), parent, self.show) self.togglefind_sc.setContext(Qt.WidgetWithChildrenShortcut) self.togglereplace_sc = QShortcut(QKeySequence("Ctrl+H"), parent, self.toggle_replace_widgets) self.togglereplace_sc.setContext(Qt.WidgetWithChildrenShortcut) escape_sc = QShortcut(QKeySequence("Escape"), parent, self.hide) escape_sc.setContext(Qt.WidgetWithChildrenShortcut) self.highlight_timer = QTimer(self) self.highlight_timer.setSingleShot(True) self.highlight_timer.setInterval(1000) self.connect(self.highlight_timer, SIGNAL("timeout()"), self.highlight_matches) def get_shortcut_data(self): """ Returns shortcut data, a list of tuples (shortcut, text, default) shortcut (QShortcut or QAction instance) text (string): action/shortcut description default (string): default key sequence """ return [ (self.findnext_sc, "Find next", "F3"), (self.findprev_sc, "Find previous", "Shift+F3"), (self.togglefind_sc, "Find text", "Ctrl+F"), (self.togglereplace_sc, "Replace text", "Ctrl+H"), ] def update_search_combo(self): self.search_text.lineEdit().emit(SIGNAL("returnPressed()")) def update_replace_combo(self): self.replace_text.lineEdit().emit(SIGNAL("returnPressed()")) def toggle_replace_widgets(self): if self.enable_replace: # Toggle replace widgets if self.replace_widgets[0].isVisible(): self.hide_replace() self.hide() else: self.show_replace() self.replace_text.setFocus() def toggle_highlighting(self, state): """Toggle the 'highlight all results' feature""" if self.editor is not None: if state: self.highlight_matches() else: self.clear_matches() def show(self): """Overrides Qt Method""" QWidget.show(self) self.emit(SIGNAL("visibility_changed(bool)"), True) if self.editor is not None: text = self.editor.get_selected_text() if len(text) > 0: self.search_text.setEditText(text) self.search_text.lineEdit().selectAll() self.refresh() else: self.search_text.lineEdit().selectAll() self.search_text.setFocus() def hide(self): """Overrides Qt Method""" for widget in self.replace_widgets: widget.hide() QWidget.hide(self) self.emit(SIGNAL("visibility_changed(bool)"), False) if self.editor is not None: self.editor.setFocus() self.clear_matches() def show_replace(self): """Show replace widgets""" self.show() for widget in self.replace_widgets: widget.show() def hide_replace(self): """Hide replace widgets""" for widget in self.replace_widgets: widget.hide() def refresh(self): """Refresh widget""" if self.isHidden(): if self.editor is not None: self.clear_matches() return state = self.editor is not None for widget in self.widgets: widget.setEnabled(state) if state: self.find() def set_editor(self, editor, refresh=True): """ Set associated editor/web page: codeeditor.base.TextEditBaseWidget browser.WebView """ self.editor = editor from spyderlib.qt.QtWebKit import QWebView self.words_button.setVisible(not isinstance(editor, QWebView)) self.re_button.setVisible(not isinstance(editor, QWebView)) from spyderlib.widgets.sourcecode.codeeditor import CodeEditor self.is_code_editor = isinstance(editor, CodeEditor) self.highlight_button.setVisible(self.is_code_editor) if refresh: self.refresh() if self.isHidden() and editor is not None: self.clear_matches() def find_next(self): """Find next occurence""" state = self.find(changed=False, forward=True, rehighlight=False) self.editor.setFocus() self.search_text.add_current_text() return state def find_previous(self): """Find previous occurence""" state = self.find(changed=False, forward=False, rehighlight=False) self.editor.setFocus() return state def text_has_been_edited(self, text): """Find text has been edited (this slot won't be triggered when setting the search pattern combo box text programmatically""" self.find(changed=True, forward=True, start_highlight_timer=True) def highlight_matches(self): """Highlight found results""" if self.is_code_editor and self.highlight_button.isChecked(): text = self.search_text.currentText() words = self.words_button.isChecked() regexp = self.re_button.isChecked() self.editor.highlight_found_results(text, words=words, regexp=regexp) def clear_matches(self): """Clear all highlighted matches""" if self.is_code_editor: self.editor.clear_found_results() def find(self, changed=True, forward=True, rehighlight=True, start_highlight_timer=False): """Call the find function""" text = self.search_text.currentText() if len(text) == 0: self.search_text.lineEdit().setStyleSheet("") return None else: case = self.case_button.isChecked() words = self.words_button.isChecked() regexp = self.re_button.isChecked() found = self.editor.find_text(text, changed, forward, case=case, words=words, regexp=regexp) self.search_text.lineEdit().setStyleSheet(self.STYLE[found]) if found: if rehighlight or not self.editor.found_results: self.highlight_timer.stop() if start_highlight_timer: self.highlight_timer.start() else: self.highlight_matches() else: self.clear_matches() return found def replace_find(self): """Replace and find""" if self.editor is not None: replace_text = self.replace_text.currentText() search_text = self.search_text.currentText() pattern = search_text if self.re_button.isChecked() else None first = True while True: if first: # First found if self.editor.has_selected_text() and self.editor.get_selected_text() == unicode(search_text): # Text was already found, do nothing pass else: if not self.find(changed=False, forward=True, rehighlight=False): break first = False wrapped = False position = self.editor.get_position("cursor") position0 = position else: position1 = self.editor.get_position("cursor") if wrapped: if position1 == position or self.editor.is_position_sup(position1, position): # Avoid infinite loop: replace string includes # part of the search string break if position1 == position0: # Avoid infinite loop: single found occurence break if self.editor.is_position_inf(position1, position0): wrapped = True position0 = position1 self.editor.replace(replace_text, pattern=pattern) if not self.find_next(): break if not self.all_check.isChecked(): break self.all_check.setCheckState(Qt.Unchecked)
class AsyncClient(QObject): """ A class which handles a connection to a client through a QProcess. """ # Emitted when the client has initialized. initialized = Signal() # Emitted when the client errors. errored = Signal() # Emitted when a request response is received. received = Signal(object) def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) self.timer.start(HEARTBEAT) def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received) def request(self, func_name, *args, **kwargs): """Send a request to the server. The response will be a dictionary the 'request_id' and the 'func_name' as well as a 'result' field with the object returned by the function call or or an 'error' field with a traceback. """ if not self.is_initialized: return request_id = uuid.uuid4().hex request = dict(func_name=func_name, args=args, kwargs=kwargs, request_id=request_id) try: self.socket.send_pyobj(request) except zmq.ZMQError: pass return request_id def close(self): """Cleanly close the connection to the server. """ self.closing = True self.timer.stop() self.request('server_quit') self.process.waitForFinished(1000) self.context.destroy(0) def _on_finished(self): """Handle a finished signal from the process. """ if self.closing: return if self.is_initialized: debug_print('Restarting %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.is_initialized = False self.notifier.setEnabled(False) self.run() else: debug_print('Errored %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.errored.emit() def _on_msg_received(self): """Handle a message trigger from the socket. """ self.notifier.setEnabled(False) while 1: try: resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK) except zmq.ZMQError: self.notifier.setEnabled(True) return if not self.is_initialized: self.is_initialized = True debug_print('Initialized %s' % self.name) self.initialized.emit() continue resp['name'] = self.name self.received.emit(resp) def _heartbeat(self): """Send a heartbeat to keep the server alive. """ if not self.is_initialized: return self.socket.send_pyobj(dict(func_name='server_heartbeat'))
class AsyncClient(QObject): """ A class which handles a connection to a client through a QProcess. """ # Emitted when the client has initialized. initialized = Signal() # Emitted when the client errors. errored = Signal() # Emitted when a request response is received. received = Signal(object) def __init__(self, target, executable=None, name=None, extra_args=None, libs=None, cwd=None, env=None): super(AsyncClient, self).__init__() self.executable = executable or sys.executable self.extra_args = extra_args self.target = target self.name = name or self self.libs = libs self.cwd = cwd self.env = env self.is_initialized = False self.closing = False self.context = zmq.Context() QApplication.instance().aboutToQuit.connect(self.close) # Set up the heartbeat timer. self.timer = QTimer(self) self.timer.timeout.connect(self._heartbeat) self.timer.start(HEARTBEAT) def run(self): """Handle the connection with the server. """ # Set up the zmq port. self.socket = self.context.socket(zmq.PAIR) self.port = self.socket.bind_to_random_port('tcp://*') # Set up the process. self.process = QProcess(self) if self.cwd: self.process.setWorkingDirectory(self.cwd) p_args = ['-u', self.target, str(self.port)] if self.extra_args is not None: p_args += self.extra_args # Set up environment variables. processEnvironment = QProcessEnvironment() env = self.process.systemEnvironment() if (self.env and 'PYTHONPATH' not in self.env) or DEV: python_path = osp.dirname(get_module_path('spyderlib')) # Add the libs to the python path. for lib in self.libs: try: path = osp.dirname(imp.find_module(lib)[1]) python_path = osp.pathsep.join([python_path, path]) except ImportError: pass env.append("PYTHONPATH=%s" % python_path) if self.env: env.update(self.env) for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) # Start the process and wait for started. self.process.start(self.executable, p_args) self.process.finished.connect(self._on_finished) running = self.process.waitForStarted() if not running: raise IOError('Could not start %s' % self) # Set up the socket notifer. fid = self.socket.getsockopt(zmq.FD) self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) self.notifier.activated.connect(self._on_msg_received) def request(self, func_name, *args, **kwargs): """Send a request to the server. The response will be a dictionary the 'request_id' and the 'func_name' as well as a 'result' field with the object returned by the function call or or an 'error' field with a traceback. """ if not self.is_initialized: return request_id = uuid.uuid4().hex request = dict(func_name=func_name, args=args, kwargs=kwargs, request_id=request_id) try: self.socket.send_pyobj(request) except zmq.ZMQError: pass return request_id def close(self): """Cleanly close the connection to the server. """ self.closing = True self.timer.stop() self.request('server_quit') self.process.waitForFinished(1000) self.context.destroy(0) def _on_finished(self): """Handle a finished signal from the process. """ if self.closing: return if self.is_initialized: debug_print('Restarting %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.is_initialized = False self.notifier.setEnabled(False) self.run() else: debug_print('Errored %s' % self.name) debug_print(self.process.readAllStandardOutput()) debug_print(self.process.readAllStandardError()) self.errored.emit() def _on_msg_received(self): """Handle a message trigger from the socket. """ self.notifier.setEnabled(False) while 1: try: resp = self.socket.recv_pyobj(flags=zmq.NOBLOCK) except zmq.ZMQError: self.notifier.setEnabled(True) return if not self.is_initialized: self.is_initialized = True debug_print('Initialized %s' % self.name) self.initialized.emit() continue resp['name'] = self.name self.received.emit(resp) def _heartbeat(self): """Send a heartbeat to keep the server alive. """ if not self.is_initialized: return self.socket.send_pyobj(dict(func_name='server_heartbeat'))