Exemple #1
0
    def _onSearchInDirectoryStartPressed(self, regExp, mask, path):
        """Handler for 'search in directory' action
        """
        self._widget.updateComboBoxes()

        if self._dock is None:
            self._createDockWidget()

        from threads import SearchThread
        self._searchThread = SearchThread()
        self._searchThread.progressChanged.connect(
            self._widget.onSearchProgressChanged)
        self._searchThread.resultsAvailable.connect(self._dock.appendResults)
        self._searchThread.finished.connect(self._onSearchThreadFinished)
        self._searchThread.error.connect(self._onThreadError)

        inOpenedFiles = self._mode in (
            MODE_SEARCH_OPENED_FILES,
            MODE_REPLACE_OPENED_FILES,
        )

        self._widget.setSearchInProgress(True)
        self._dock.clear()
        self._searchThread.search(regExp, mask, inOpenedFiles, path)
Exemple #2
0
 def _onSearchInDirectoryStartPressed(self, regExp, mask, path):
     """Handler for 'search in directory' action
     """
     self._widget.updateComboBoxes()
     
     if self._dock is None:
         self._createDockWidget()
     
     from threads import SearchThread
     self._searchThread = SearchThread()
     self._searchThread.progressChanged.connect(self._widget.onSearchProgressChanged)
     self._searchThread.resultsAvailable.connect(self._dock.appendResults)
     self._searchThread.finished.connect(self._onSearchThreadFinished)
     
     inOpenedFiles = self._mode in (ModeSearchOpenedFiles, ModeReplaceOpenedFiles,)
     
     self._widget.setSearchInProgress(True)
     self._dock.clear()
     self._searchThread.search( regExp,
                                mask,
                                inOpenedFiles,
                                path)
Exemple #3
0
class Controller(QObject):
    """S&R module business logic
    """
    def __init__(self):
        QObject.__init__(self)
        self._mode = None
        self._searchThread = None
        self._replaceThread = None
        self._widget = None
        self._dock = None
        self._searchInFileStartPoint = None
        self._searchInFileLastCursorPos = None

        # all matches cache
        self._cachedRegExp = None
        self._cachedText = None
        self._catchedMatches = None

        self._createActions()

        core.workspace().currentDocumentChanged.connect(self._resetSearchInFileStartPoint)
        QApplication.instance().focusChanged.connect(self._resetSearchInFileStartPoint)

    def del_(self):
        """Explicitly called destructor
        """
        if self._searchThread is not None:
            self._searchThread.stop()
        if self._replaceThread is not None:
            self._replaceThread.stop()

        for action in self._createdActions:
            core.actionManager().removeAction(action)
        self._menuSeparator.parent().removeAction(self._menuSeparator)

        if self._dock is not None:
            self._dock.del_()

    def _createActions(self):
        """Create main menu actions
        """
        actManager = core.actionManager()

        self._createdActions = []

        menu = 'mNavigation/mSearchReplace'

        def createAction(path, text, icon, shortcut, tooltip, slot, data, enabled=True):  # pylint: disable=R0913
            """Create action object
            """
            actObject = core.actionManager().addAction( menu + '/' + path,
                                                        self.tr(text),
                                                        QIcon(':/enkiicons/' + icon),
                                                        shortcut)
            actObject.setToolTip(self.tr(tooltip))
            if slot:
                actObject.triggered.connect(slot)
            actObject.setData(data)
            actObject.setEnabled(enabled)
            self._createdActions.append(actObject)

        if sys.platform == 'darwin':
            # Ctrl+, conflicts with "Open preferences"
            searchWordBackwardShortcut, searchWordForwardShortcut = 'Meta+,', 'Meta+.'
        else:
            searchWordBackwardShortcut, searchWordForwardShortcut = 'Ctrl+,', 'Ctrl+.'

        # List if search actions.
        # First acition created by MainWindow, so, do not fill text
        createAction("aSearchFile", "&Search...",
                      "search.png", "Ctrl+F",
                      "Search in the current file...",
                      self._onModeSwitchTriggered, MODE_SEARCH)
        createAction("aSearchPrevious", "Search &Previous",
                      "previous.png", "Shift+F3",
                      "Search previous occurrence",
                      self._onSearchPrevious, None,
                      False)  # will be connected to search widget, when it is created
        createAction("aSearchNext", "Search &Next",
                      "next.png", "F3",
                      "Search next occurrence",
                      self._onSearchNext, None,
                      False)  # will be connected to search widget, when it is created
        createAction("aReplaceFile", "&Replace...",
                      "replace.png", "Ctrl+R",
                      "Replace in the current file...",
                      self._onModeSwitchTriggered, MODE_REPLACE)
        createAction("aSearchWordBackward", "Search word under cursor backward",
                      "less.png", searchWordBackwardShortcut,
                      "",
                      self._onSearchCurrentWordBackward, None)
        createAction("aSearchWordForward", "Search word under cursor forward",
                      "bigger.png", searchWordForwardShortcut,
                      "",
                      self._onSearchCurrentWordForward, None)
        self._menuSeparator = core.actionManager().menu(menu).addSeparator()
        createAction("aSearchDirectory", "Search in &Directory...",
                      "search-replace-directory.png", "Ctrl+Shift+F",
                      "Search in directory...",
                      self._onModeSwitchTriggered, MODE_SEARCH_DIRECTORY)
        createAction("aReplaceDirectory", "Replace in Director&y...",
                      "search-replace-directory.png", "Ctrl+Shift+R",
                      "Replace in directory...",
                      self._onModeSwitchTriggered, MODE_REPLACE_DIRECTORY)
        createAction("aSearchOpenedFiles", "Search in &Opened Files...",
                      "search-replace-opened-files.png",
                      "Ctrl+Alt+F", "Search in opened files...",
                      self._onModeSwitchTriggered, MODE_SEARCH_OPENED_FILES)
        createAction("aReplaceOpenedFiles", "Replace in Open&ed Files...",
                      "search-replace-opened-files.png", "Ctrl+Alt+R",
                      "Replace in opened files...",
                      self._onModeSwitchTriggered, MODE_REPLACE_OPENED_FILES)

        am = core.actionManager()
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchOpenedFiles").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceOpenedFiles").setEnabled(new is not None))

    def _createSearchWidget(self):
        """ Create search widget. Called only when user requested it first time
        """
        import searchwidget
        self._widget = searchwidget.SearchWidget( self )
        self._widget.searchInDirectoryStartPressed.connect(self._onSearchInDirectoryStartPressed)
        self._widget.searchInDirectoryStopPressed.connect(self._onSearchInDirectoryStopPressed)
        self._widget.replaceCheckedStartPressed.connect(self._onReplaceCheckedStartPressed)
        self._widget.replaceCheckedStopPressed.connect(self._onReplaceCheckedStopPressed)
        self._widget.visibilityChanged.connect(self._updateSearchWidgetFoundItemsHighlighting)

        self._widget.searchRegExpChanged.connect(self._updateFileActionsState)
        self._widget.searchRegExpChanged.connect(self._onRegExpChanged)
        self._widget.searchRegExpChanged.connect(self._updateSearchWidgetFoundItemsHighlighting)

        self._widget.searchNext.connect(self._onSearchNext)
        self._widget.searchPrevious.connect(self._onSearchPrevious)

        self._widget.replaceFileOne.connect(self._onReplaceFileOne)
        self._widget.replaceFileAll.connect(self._onReplaceFileAll)

        core.workspace().currentDocumentChanged.connect(self._updateFileActionsState)  # always disabled, if no widget
        core.workspace().currentDocumentChanged.connect(self._onCurrentDocumentChanged)
        core.workspace().textChanged.connect(self._updateSearchWidgetFoundItemsHighlighting)

        core.mainWindow().centralLayout().addWidget( self._widget )
        self._widget.setVisible( False )
        self._updateFileActionsState()

    def _createDockWidget(self):
        """Create dock widget, which displays search results.
        Called only when search in direcory process starts
        """
        import searchresultsdock
        self._dock = searchresultsdock.SearchResultsDock(core.mainWindow())

        core.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self._dock)
        self._dock.setVisible( False )
        self._dock.setReplaceMode(self._mode == MODE_REPLACE_DIRECTORY or \
                                  self._mode == MODE_REPLACE_OPENED_FILES)

    def _onModeSwitchTriggered(self):
        """Changing mode, i.e. from "Search file" to "Replace file"
        """
        if not self._widget:
            self._createSearchWidget()

        newMode = self.sender().data()

        if newMode & MODE_FLAG_FILES and \
           not core.workspace().documents():
            return

        self._widget.setMode(newMode)

        if self._searchThread is not None:
            self._searchThread.stop()
        if self._replaceThread is not None:
            self._replaceThread.stop()

        self._mode = newMode

        if self._dock is not None:
            self._dock.setReplaceMode(self._mode == MODE_REPLACE_DIRECTORY or \
                                      self._mode == MODE_REPLACE_OPENED_FILES)

    #
    # Highlight found items with yellow
    #
    def _findAllMatches(self, text, regExp):
        """Find all matces of regExp in text
        This method caches found items for last text and regExp
        """
        if self._cachedText != text or \
           self._cachedRegExp != regExp:
            self._catchedMatches = [match for match in regExp.finditer(text)]
            self._cachedText = text
            self._cachedRegExp = regExp

        return self._catchedMatches

    def _updateSearchWidgetFoundItemsHighlighting(self):
        document = core.workspace().currentDocument()
        if document is None:
            return

        if not self._widget.isVisible() or \
           not self._widget.isSearchRegExpValid()[0] or \
           not self._widget.getRegExp().pattern:
            document.qutepart.setExtraSelections([])
            return

        return self._updateFoundItemsHighlighting(self._widget.getRegExp())

    def _updateFoundItemsHighlighting(self, regExp):
        """(Re)highlight found items with yellow color
        Called by _updateSearchWidgetFoundItemsHighlighting and by word search highlighting
        """
        document = core.workspace().currentDocument()

        matches = self._findAllMatches(document.qutepart.text, regExp)

        if len(matches) <= MAX_EXTRA_SELECTIONS_COUNT:
            selections = [ (match.start(), len(match.group(0))) \
                                for match in matches]
        else:
            selections = []

        document.qutepart.setExtraSelections(selections)

    def _onCurrentDocumentChanged(self, old, new):
        """Current document changed. Clear highlighted items
        """
        if old is not None:
            old.qutepart.setExtraSelections([])

    def _searchInText(self, regExp, text, startPoint, forward):
        """Search in text and return tuple (nearest match, all matches)
        (None, None) if not found
        """
        matches = self._findAllMatches(text, regExp)
        if matches:
            if forward:
                for match in matches:
                    if match.start() >= startPoint:
                        break
                else:  # wrap, search from start
                    match = matches[0]
            else:  # reverse search
                for match in matches[::-1]:
                    if match.start() < startPoint:
                        break
                else:  # wrap, search from end
                    match = matches[-1]
            return match, matches
        else:
            return None, None

    #
    # Search word under cursor
    #
    def _onSearchCurrentWordBackward(self):
        """Search current word backward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=False)

    def _onSearchCurrentWordForward(self):
        """Search current word forward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=True)

    def _searchWord(self, forward):
        """Do search in file operation. Will select next found item
        if updateWidget is True, search widget line edit will color will be set according to result
        """
        document = core.workspace().currentDocument()

        cursor = document.qutepart.textCursor()
        if not cursor.hasSelection():
            cursor.select(cursor.WordUnderCursor)
        word = cursor.selectedText()
        wordStartAbsPos = cursor.anchor()
        wordEndAbsPos = cursor.position()

        if not word:
            return

        regExp = re.compile('\\b%s\\b' % re.escape(word))
        text = document.qutepart.text

        # avoid matching word under cursor
        if forward:
            startPoint = wordEndAbsPos
        else:
            startPoint = wordStartAbsPos

        self._updateFoundItemsHighlighting(regExp)

        match, matches = self._searchInText(regExp, document.qutepart.text, startPoint, forward)
        if match is not None:
            document.qutepart.absSelectedPosition = (match.start(), match.start() + len(match.group(0)))
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            core.workspace().currentDocument().qutepart.resetSelection()

    #
    # Search and replace in file
    #

    def _resetSearchInFileStartPoint(self):
        """Reset the start point.
        Something changed, restart the search process
        """
        self._searchInFileStartPoint = None

    def _updateFileActionsState(self):
        """Update actions enabled/disabled state.
        Called if current document had been changed or if reg exp had been changed
        """
        valid, error = self._widget.isSearchRegExpValid()
        valid = valid and len(self._widget.getRegExp().pattern) > 0  # valid and not empty
        searchAvailable = valid

        haveDocument = core.workspace().currentDocument() is not None
        searchInFileAvailable = valid and haveDocument

        self._widget.setSearchInFileActionsEnabled(searchInFileAvailable)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchNext").setEnabled(searchInFileAvailable)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchPrevious").setEnabled(searchInFileAvailable)

        core.actionManager().action("mNavigation/mSearchReplace/aSearchWordBackward").setEnabled(haveDocument)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchWordForward").setEnabled(haveDocument)

    def _onRegExpChanged(self, regExp):
        """Search regExp changed. Do incremental search
        """
        if self._mode in (MODE_SEARCH, MODE_REPLACE) and \
           core.workspace().currentDocument() is not None:
            if regExp.pattern:
                self._searchFile(forward=True, incremental=True )
            else:  # Clear selection
                core.workspace().currentDocument().qutepart.resetSelection()

    def _onSearchNext(self):
        """Search Next clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=True, incremental=False )

    def _onSearchPrevious(self):
        """Search Previous clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=False, incremental=False )

    def _searchFile(self, forward=True, incremental=False):
        """Do search in file operation. Will select next found item
        """
        qutepart = core.workspace().currentDocument().qutepart

        regExp = self._widget.getRegExp()

        if qutepart.absCursorPosition != self._searchInFileLastCursorPos:
            self._searchInFileStartPoint = None

        if self._searchInFileStartPoint is None or not incremental:
            # get cursor position
            cursor = qutepart.textCursor()

            if forward:
                if  incremental :
                    self._searchInFileStartPoint = cursor.selectionStart()
                else:
                    self._searchInFileStartPoint = cursor.selectionEnd()
            else:
                self._searchInFileStartPoint = cursor.selectionStart()

        match, matches = self._searchInText(regExp, qutepart.text, self._searchInFileStartPoint, forward)
        if match:
            selectionStart, selectionEnd = match.start(), match.start() + len(match.group(0))
            qutepart.absSelectedPosition = (selectionStart, selectionEnd)
            self._searchInFileLastCursorPos = selectionEnd
            self._widget.setState(self._widget.Good)  # change background acording to result
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            self._widget.setState(self._widget.Bad)
            qutepart.resetSelection()

    def _onReplaceFileOne(self, replaceText):
        """Do one replacement in the file
        """
        self._widget.updateComboBoxes()

        qpart = core.workspace().currentDocument().qutepart
        regExp = self._widget.getRegExp()

        start, end = qpart.absSelectedPosition

        match = regExp.search(qpart.text, start)

        if match is None:
            match = regExp.search(qpart.text, 0)

        if match is not None:
            replaceTextSubed = substitutions.makeSubstitutions(replaceText, match)
            qpart.replaceText(match.start(), len(match.group(0)), replaceTextSubed)
            # move cursor to the end of replaced text
            qpart.absCursorPosition = match.start() + len(replaceTextSubed)
            # move selection to the next item
            self._searchFile(forward=True, incremental=False )
        else:
            self._widget.setState(self._widget.Bad)

    def _onReplaceFileAll(self, replaceText):
        """Do all replacements in the file
        """
        self._widget.updateComboBoxes()

        qpart = core.workspace().currentDocument().qutepart
        regExp = self._widget.getRegExp()

        matches = self._findAllMatches(qpart.text, regExp)
        with qpart:
            for match in matches[::-1]:  # reverse order, because replacement may move indexes
                replaceTextSubed = substitutions.makeSubstitutions(replaceText, match)
                qpart.replaceText(match.start(), len(match.group(0)), replaceTextSubed)

        core.mainWindow().statusBar().showMessage( self.tr( "%d match(es) replaced." % len(matches) ), 3000 )

    #
    # Search in directory (with thread)
    #

    def _onSearchInDirectoryStartPressed(self, regExp, mask, path):
        """Handler for 'search in directory' action
        """
        self._widget.updateComboBoxes()

        if self._dock is None:
            self._createDockWidget()

        from threads import SearchThread
        self._searchThread = SearchThread()
        self._searchThread.progressChanged.connect(self._widget.onSearchProgressChanged)
        self._searchThread.resultsAvailable.connect(self._dock.appendResults)
        self._searchThread.finished.connect(self._onSearchThreadFinished)
        self._searchThread.error.connect(self._onThreadError)

        inOpenedFiles = self._mode in (MODE_SEARCH_OPENED_FILES, MODE_REPLACE_OPENED_FILES,)

        self._widget.setSearchInProgress(True)
        self._dock.clear()
        self._searchThread.search( regExp,
                                   mask,
                                   inOpenedFiles,
                                   path)

    def _onSearchInDirectoryStopPressed(self):
        """Handler for 'search in directory' action
        """
        if self._searchThread is not None:
            self._searchThread.stop()

    def _onSearchThreadFinished(self):
        """Handler for search in directory finished signal
        """
        self._widget.setSearchInProgress(False)
        matchesCount = self._dock.matchesCount()
        if matchesCount:
            core.mainWindow().statusBar().showMessage('%d matches ' % matchesCount, 3000)
        else:
            core.mainWindow().statusBar().showMessage('Nothing found', 3000)

    #
    # Replace in directory (with thread)
    #

    def _onReplaceCheckedStartPressed(self, replaceText):
        """Handler for 'replace checked' action
        """
        self._widget.updateComboBoxes()

        if self._dock is None:  # no any results
            return

        from threads import ReplaceThread
        self._replaceThread = ReplaceThread()
        self._replaceThread.resultsHandled.connect(self._dock.onResultsHandledByReplaceThread)
        self._replaceThread.error.connect(self._onThreadError)
        self._replaceThread.finalStatus.connect(self._onReplaceThreadFinalStatus)

        self._replaceThread.replace( self._dock.getCheckedItems(),
                                     replaceText)

    def _onReplaceCheckedStopPressed(self):
        """Handler for 'stop replacing checked' action
        """
        if self._replaceThread is not None:
            self._replaceThread.stop()

    def _onThreadError(self, error ):
        """Error message from the replace thread
        """
        core.mainWindow().appendMessage( error )

    def _onReplaceThreadFinished(self):
        """Handler for replace in directory finished event
        """
        self._widget.setReplaceInProgress(False)

    def _onReplaceThreadFinalStatus(self, message):
        """Show replace thread status on status bar
        """
        core.mainWindow().statusBar().showMessage(message, 3000)
Exemple #4
0
 def on_main_search_button_clicked(self, button):
     if self.search_entry.get_text() != '':
         search_thread = SearchThread(self, self.search_entry.get_text(), 'song')
         search_thread.start()
Exemple #5
0
 def on_main_search_button_clicked(self, button):
     if self.search_entry.get_text() != '':
         search_thread = SearchThread(self, self.search_entry.get_text(),
                                      'song')
         search_thread.start()
Exemple #6
0
class Controller(QObject):
    """S&R module business logic
    """
    def __init__(self):
        QObject.__init__(self)
        self._mode = None
        self._searchThread = None
        self._replaceThread = None
        self._widget = None
        self._dock = None
        self._searchInFileStartPoint = None
        self._searchInFileLastCursorPos = None

        # all matches cache
        self._cachedRegExp = None
        self._cachedText = None
        self._catchedMatches = None

        self._createActions()

        core.workspace().currentDocumentChanged.connect(
            self._resetSearchInFileStartPoint)
        QApplication.instance().focusChanged.connect(
            self._resetSearchInFileStartPoint)

    def del_(self):
        """Explicitly called destructor
        """
        if self._searchThread is not None:
            self._searchThread.stop()
        if self._replaceThread is not None:
            self._replaceThread.stop()

        for action in self._createdActions:
            core.actionManager().removeAction(action)
        self._menuSeparator.parent().removeAction(self._menuSeparator)

        if self._dock is not None:
            self._dock.del_()

    def _createActions(self):
        """Create main menu actions
        """
        actManager = core.actionManager()

        self._createdActions = []

        menu = 'mNavigation/mSearchReplace'

        def createAction(path,
                         text,
                         icon,
                         shortcut,
                         tooltip,
                         slot,
                         data,
                         enabled=True):  # pylint: disable=R0913
            """Create action object
            """
            actObject = core.actionManager().addAction(
                menu + '/' + path, self.tr(text), QIcon(':/enkiicons/' + icon),
                shortcut)
            actObject.setToolTip(self.tr(tooltip))
            if slot:
                actObject.triggered.connect(slot)
            actObject.setData(data)
            actObject.setEnabled(enabled)
            self._createdActions.append(actObject)

        if sys.platform == 'darwin':
            # Ctrl+, conflicts with "Open preferences"
            searchWordBackwardShortcut, searchWordForwardShortcut = 'Meta+,', 'Meta+.'
        else:
            searchWordBackwardShortcut, searchWordForwardShortcut = 'Ctrl+,', 'Ctrl+.'

        # List if search actions.
        # First acition created by MainWindow, so, do not fill text
        createAction("aSearchFile", "&Search...", "search.png", "Ctrl+F",
                     "Search in the current file...",
                     self._onModeSwitchTriggered, MODE_SEARCH)
        createAction(
            "aSearchPrevious", "Search &Previous", "previous.png", "Shift+F3",
            "Search previous occurrence", self._onSearchPrevious, None,
            False)  # will be connected to search widget, when it is created
        createAction(
            "aSearchNext", "Search &Next", "next.png", "F3",
            "Search next occurrence", self._onSearchNext, None,
            False)  # will be connected to search widget, when it is created
        createAction("aReplaceFile", "&Replace...", "replace.png", "Ctrl+R",
                     "Replace in the current file...",
                     self._onModeSwitchTriggered, MODE_REPLACE)
        createAction("aSearchWordBackward",
                     "Search word under cursor backward", "less.png",
                     searchWordBackwardShortcut, "",
                     self._onSearchCurrentWordBackward, None)
        createAction("aSearchWordForward", "Search word under cursor forward",
                     "bigger.png", searchWordForwardShortcut, "",
                     self._onSearchCurrentWordForward, None)
        self._menuSeparator = core.actionManager().menu(menu).addSeparator()
        createAction("aSearchDirectory", "Search in &Directory...",
                     "search-replace-directory.png", "Ctrl+Shift+F",
                     "Search in directory...", self._onModeSwitchTriggered,
                     MODE_SEARCH_DIRECTORY)
        createAction("aReplaceDirectory", "Replace in Director&y...",
                     "search-replace-directory.png", "Ctrl+Shift+R",
                     "Replace in directory...", self._onModeSwitchTriggered,
                     MODE_REPLACE_DIRECTORY)
        createAction("aSearchOpenedFiles", "Search in &Opened Files...",
                     "search-replace-opened-files.png", "Ctrl+Alt+Meta+F",
                     "Search in opened files...", self._onModeSwitchTriggered,
                     MODE_SEARCH_OPENED_FILES)
        createAction("aReplaceOpenedFiles", "Replace in Open&ed Files...",
                     "search-replace-opened-files.png", "Ctrl+Alt+Meta+R",
                     "Replace in opened files...", self._onModeSwitchTriggered,
                     MODE_REPLACE_OPENED_FILES)

        am = core.actionManager()
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchOpenedFiles").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceOpenedFiles").setEnabled(new is not None))

    def _createSearchWidget(self):
        """ Create search widget. Called only when user requested it first time
        """
        import searchwidget
        self._widget = searchwidget.SearchWidget(self)
        self._widget.searchInDirectoryStartPressed.connect(
            self._onSearchInDirectoryStartPressed)
        self._widget.searchInDirectoryStopPressed.connect(
            self._onSearchInDirectoryStopPressed)
        self._widget.replaceCheckedStartPressed.connect(
            self._onReplaceCheckedStartPressed)
        self._widget.replaceCheckedStopPressed.connect(
            self._onReplaceCheckedStopPressed)
        self._widget.visibilityChanged.connect(
            self._updateSearchWidgetFoundItemsHighlighting)

        self._widget.searchRegExpChanged.connect(self._updateFileActionsState)
        self._widget.searchRegExpChanged.connect(self._onRegExpChanged)
        self._widget.searchRegExpChanged.connect(
            self._updateSearchWidgetFoundItemsHighlighting)

        self._widget.searchNext.connect(self._onSearchNext)
        self._widget.searchPrevious.connect(self._onSearchPrevious)

        self._widget.replaceFileOne.connect(self._onReplaceFileOne)
        self._widget.replaceFileAll.connect(self._onReplaceFileAll)

        core.workspace().currentDocumentChanged.connect(
            self._updateFileActionsState)  # always disabled, if no widget
        core.workspace().currentDocumentChanged.connect(
            self._onCurrentDocumentChanged)
        core.workspace().textChanged.connect(
            self._updateSearchWidgetFoundItemsHighlighting)

        core.mainWindow().centralLayout().addWidget(self._widget)
        self._widget.setVisible(False)
        self._updateFileActionsState()

    def _createDockWidget(self):
        """Create dock widget, which displays search results.
        Called only when search in direcory process starts
        """
        import searchresultsdock
        self._dock = searchresultsdock.SearchResultsDock(core.mainWindow())

        core.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self._dock)
        self._dock.setVisible(False)
        self._dock.setReplaceMode(self._mode == MODE_REPLACE_DIRECTORY or \
                                  self._mode == MODE_REPLACE_OPENED_FILES)

    def _onModeSwitchTriggered(self):
        """Changing mode, i.e. from "Search file" to "Replace file"
        """
        if not self._widget:
            self._createSearchWidget()

        newMode = self.sender().data().toInt()[0]

        if newMode & MODE_FLAG_FILES and \
           not core.workspace().documents():
            return

        self._widget.setMode(newMode)

        if self._searchThread is not None:
            self._searchThread.stop()
        if self._replaceThread is not None:
            self._replaceThread.stop()

        self._mode = newMode

        if self._dock is not None:
            self._dock.setReplaceMode(self._mode == MODE_REPLACE_DIRECTORY or \
                                      self._mode == MODE_REPLACE_OPENED_FILES)

    #
    # Highlight found items with yellow
    #
    def _findAllMatches(self, text, regExp):
        """Find all matces of regExp in text
        This method caches found items for last text and regExp
        """
        if self._cachedText != text or \
           self._cachedRegExp != regExp:
            self._catchedMatches = [match for match in regExp.finditer(text)]
            self._cachedText = text
            self._cachedRegExp = regExp

        return self._catchedMatches

    def _updateSearchWidgetFoundItemsHighlighting(self):
        document = core.workspace().currentDocument()
        if document is None:
            return

        if not self._widget.isVisible() or \
           not self._widget.isSearchRegExpValid()[0] or \
           not self._widget.getRegExp().pattern:
            document.qutepart.setExtraSelections([])
            return

        return self._updateFoundItemsHighlighting(self._widget.getRegExp())

    def _updateFoundItemsHighlighting(self, regExp):
        """(Re)highlight found items with yellow color
        Called by _updateSearchWidgetFoundItemsHighlighting and by word search highlighting
        """
        document = core.workspace().currentDocument()

        matches = self._findAllMatches(document.qutepart.text, regExp)

        if len(matches) <= MAX_EXTRA_SELECTIONS_COUNT:
            selections = [ (match.start(), len(match.group(0))) \
                                for match in matches]
        else:
            selections = []

        document.qutepart.setExtraSelections(selections)

    def _onCurrentDocumentChanged(self, old, new):
        """Current document changed. Clear highlighted items
        """
        if old is not None:
            old.qutepart.setExtraSelections([])

    def _searchInText(self, regExp, text, startPoint, forward):
        """Search in text and return tuple (nearest match, all matches)
        (None, None) if not found
        """
        matches = self._findAllMatches(text, regExp)
        if matches:
            if forward:
                for match in matches:
                    if match.start() >= startPoint:
                        break
                else:  # wrap, search from start
                    match = matches[0]
            else:  # reverse search
                for match in matches[::-1]:
                    if match.start() < startPoint:
                        break
                else:  # wrap, search from end
                    match = matches[-1]
            return match, matches
        else:
            return None, None

    #
    # Search word under cursor
    #
    def _onSearchCurrentWordBackward(self):
        """Search current word backward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=False)

    def _onSearchCurrentWordForward(self):
        """Search current word forward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=True)

    def _searchWord(self, forward):
        """Do search in file operation. Will select next found item
        if updateWidget is True, search widget line edit will color will be set according to result
        """
        document = core.workspace().currentDocument()

        cursor = document.qutepart.textCursor()
        if not cursor.hasSelection():
            cursor.select(cursor.WordUnderCursor)
        word = cursor.selectedText()
        wordStartAbsPos = cursor.anchor()
        wordEndAbsPos = cursor.position()

        if not word:
            return

        regExp = re.compile('\\b%s\\b' % re.escape(word))
        text = document.qutepart.text

        # avoid matching word under cursor
        if forward:
            startPoint = wordEndAbsPos
        else:
            startPoint = wordStartAbsPos

        self._updateFoundItemsHighlighting(regExp)

        match, matches = self._searchInText(regExp, document.qutepart.text,
                                            startPoint, forward)
        if match is not None:
            document.qutepart.absSelectedPosition = (match.start(),
                                                     match.start() +
                                                     len(match.group(0)))
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            core.workspace().currentDocument().qutepart.resetSelection()

    #
    # Search and replace in file
    #

    def _resetSearchInFileStartPoint(self):
        """Reset the start point.
        Something changed, restart the search process
        """
        self._searchInFileStartPoint = None

    def _updateFileActionsState(self):
        """Update actions enabled/disabled state.
        Called if current document had been changed or if reg exp had been changed
        """
        valid, error = self._widget.isSearchRegExpValid()
        valid = valid and len(
            self._widget.getRegExp().pattern) > 0  # valid and not empty
        searchAvailable = valid

        haveDocument = core.workspace().currentDocument() is not None
        searchInFileAvailable = valid and haveDocument

        self._widget.setSearchInFileActionsEnabled(searchInFileAvailable)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchNext"
                                    ).setEnabled(searchInFileAvailable)
        core.actionManager().action(
            "mNavigation/mSearchReplace/aSearchPrevious").setEnabled(
                searchInFileAvailable)

        core.actionManager().action(
            "mNavigation/mSearchReplace/aSearchWordBackward").setEnabled(
                haveDocument)
        core.actionManager().action(
            "mNavigation/mSearchReplace/aSearchWordForward").setEnabled(
                haveDocument)

    def _onRegExpChanged(self, regExp):
        """Search regExp changed. Do incremental search
        """
        if self._mode in (MODE_SEARCH, MODE_REPLACE) and \
           core.workspace().currentDocument() is not None:
            if regExp.pattern:
                self._searchFile(forward=True, incremental=True)
            else:  # Clear selection
                core.workspace().currentDocument().qutepart.resetSelection()

    def _onSearchNext(self):
        """Search Next clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=True, incremental=False)

    def _onSearchPrevious(self):
        """Search Previous clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=False, incremental=False)

    def _searchFile(self, forward=True, incremental=False):
        """Do search in file operation. Will select next found item
        """
        qutepart = core.workspace().currentDocument().qutepart

        regExp = self._widget.getRegExp()

        if qutepart.absCursorPosition != self._searchInFileLastCursorPos:
            self._searchInFileStartPoint = None

        if self._searchInFileStartPoint is None or not incremental:
            # get cursor position
            cursor = qutepart.textCursor()

            if forward:
                if incremental:
                    self._searchInFileStartPoint = cursor.selectionStart()
                else:
                    self._searchInFileStartPoint = cursor.selectionEnd()
            else:
                self._searchInFileStartPoint = cursor.selectionStart()

        match, matches = self._searchInText(regExp, qutepart.text,
                                            self._searchInFileStartPoint,
                                            forward)
        if match:
            selectionStart, selectionEnd = match.start(), match.start() + len(
                match.group(0))
            qutepart.absSelectedPosition = (selectionStart, selectionEnd)
            self._searchInFileLastCursorPos = selectionEnd
            self._widget.setState(
                self._widget.Good)  # change background acording to result
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            self._widget.setState(self._widget.Bad)
            qutepart.resetSelection()

    def _onReplaceFileOne(self, replaceText):
        """Do one replacement in the file
        """
        self._widget.updateComboBoxes()

        qpart = core.workspace().currentDocument().qutepart
        regExp = self._widget.getRegExp()

        start, end = qpart.absSelectedPosition

        match = regExp.search(qpart.text, start)

        if match is None:
            match = regExp.search(qpart.text, 0)

        if match is not None:
            replaceTextSubed = substitutions.makeSubstitutions(
                replaceText, match)
            qpart.replaceText(match.start(), len(match.group(0)),
                              replaceTextSubed)
            # move cursor to the end of replaced text
            qpart.absCursorPosition = match.start() + len(replaceTextSubed)
            # move selection to the next item
            self._searchFile(forward=True, incremental=False)
        else:
            self._widget.setState(self._widget.Bad)

    def _onReplaceFileAll(self, replaceText):
        """Do all replacements in the file
        """
        self._widget.updateComboBoxes()

        qpart = core.workspace().currentDocument().qutepart
        regExp = self._widget.getRegExp()

        matches = self._findAllMatches(qpart.text, regExp)
        with qpart:
            for match in matches[::
                                 -1]:  # reverse order, because replacement may move indexes
                replaceTextSubed = substitutions.makeSubstitutions(
                    replaceText, match)
                qpart.replaceText(match.start(), len(match.group(0)),
                                  replaceTextSubed)

        core.mainWindow().statusBar().showMessage(
            self.tr("%d match(es) replaced." % len(matches)), 3000)

    #
    # Search in directory (with thread)
    #

    def _onSearchInDirectoryStartPressed(self, regExp, mask, path):
        """Handler for 'search in directory' action
        """
        self._widget.updateComboBoxes()

        if self._dock is None:
            self._createDockWidget()

        from threads import SearchThread
        self._searchThread = SearchThread()
        self._searchThread.progressChanged.connect(
            self._widget.onSearchProgressChanged)
        self._searchThread.resultsAvailable.connect(self._dock.appendResults)
        self._searchThread.finished.connect(self._onSearchThreadFinished)
        self._searchThread.error.connect(self._onThreadError)

        inOpenedFiles = self._mode in (
            MODE_SEARCH_OPENED_FILES,
            MODE_REPLACE_OPENED_FILES,
        )

        self._widget.setSearchInProgress(True)
        self._dock.clear()
        self._searchThread.search(regExp, mask, inOpenedFiles, path)

    def _onSearchInDirectoryStopPressed(self):
        """Handler for 'search in directory' action
        """
        if self._searchThread is not None:
            self._searchThread.stop()

    def _onSearchThreadFinished(self):
        """Handler for search in directory finished signal
        """
        self._widget.setSearchInProgress(False)
        matchesCount = self._dock.matchesCount()
        if matchesCount:
            core.mainWindow().statusBar().showMessage(
                '%d matches ' % matchesCount, 3000)
        else:
            core.mainWindow().statusBar().showMessage('Nothing found', 3000)

    #
    # Replace in directory (with thread)
    #

    def _onReplaceCheckedStartPressed(self, replaceText):
        """Handler for 'replace checked' action
        """
        self._widget.updateComboBoxes()

        if self._dock is None:  # no any results
            return

        from threads import ReplaceThread
        self._replaceThread = ReplaceThread()
        self._replaceThread.resultsHandled.connect(
            self._dock.onResultsHandledByReplaceThread)
        self._replaceThread.error.connect(self._onThreadError)
        self._replaceThread.finalStatus.connect(
            self._onReplaceThreadFinalStatus)

        self._replaceThread.replace(self._dock.getCheckedItems(), replaceText)

    def _onReplaceCheckedStopPressed(self):
        """Handler for 'stop replacing checked' action
        """
        if self._replaceThread is not None:
            self._replaceThread.stop()

    def _onThreadError(self, error):
        """Error message from the replace thread
        """
        core.mainWindow().appendMessage(error)

    def _onReplaceThreadFinished(self):
        """Handler for replace in directory finished event
        """
        self._widget.setReplaceInProgress(False)

    def _onReplaceThreadFinalStatus(self, message):
        """Show replace thread status on status bar
        """
        core.mainWindow().statusBar().showMessage(message, 3000)
Exemple #7
0
class Controller(QObject):
    """S&R module business logic
    """
    def __init__(self):
        QObject.__init__(self)
        self._mode = None
        self._searchThread = None
        self._replaceThread = None
        self._widget = None
        self._dock = None
        self._searchInFileStartPoint = None
        self._searchInFileLastCursorPos = None
        self._createActions()
        
        core.workspace().currentDocumentChanged.connect(self._resetSearchInFileStartPoint)
        QApplication.instance().focusChanged.connect(self._resetSearchInFileStartPoint)
        # QScintilla .cursorPositionChanged is emitted with delay.

    def del_(self):
        """Explicitly called destructor
        """
        for action in self._createdActions:
            core.actionManager().removeAction(action)
        self._menuSeparator.parent().removeAction(self._menuSeparator)
        
        if self._dock is not None:
            self._dock.del_()

    def _createActions(self):
        """Create main menu actions
        """
        actManager = core.actionManager()
        
        self._createdActions = []
        
        menu = 'mNavigation/mSearchReplace'
        
        def createAction(path, text, icon, shortcut, tooltip, slot, data, enabled=True):  # pylint: disable=R0913
            """Create action object
            """
            actObject = core.actionManager().addAction( menu + '/' + path,
                                                        self.tr(text),
                                                        QIcon(':/enkiicons/' + icon),
                                                        shortcut)
            actObject.setToolTip(self.tr(tooltip))
            if slot:
                actObject.triggered.connect(slot)
            actObject.setData(data)
            actObject.setEnabled(enabled)
            self._createdActions.append(actObject)
        
        # List if search actions.
        # First acition created by MainWindow, so, do not fill text
        createAction("aSearchFile", "&Search...", 
                      "search.png", "Ctrl+F",
                      "Search in the current file...", 
                      self._onModeSwitchTriggered, ModeSearch)
        createAction("aSearchPrevious", "Search &Previous",
                      "previous.png", "Shift+F3",
                      "Search previous occurrence",
                      self._onSearchPrevious, None,
                      False)  # will be connected to search widget, when it is created
        createAction("aSearchNext", "Search &Next",
                      "next.png", "F3",
                      "Search next occurrence",
                      self._onSearchNext, None,
                      False)  # will be connected to search widget, when it is created
        createAction("aReplaceFile", "&Replace...",
                      "replace.png", "Ctrl+R",
                      "Replace in the current file...",
                      self._onModeSwitchTriggered, ModeReplace)
        createAction("aSearchWordBackward", "Search word under cursor backward",
                      "less.png", "Ctrl+,",
                      "",
                      self._onSearchCurrentWordBackward, None)
        createAction("aSearchWordForward", "Search word under cursor forward",
                      "bigger.png", "Ctrl+.",
                      "",
                      self._onSearchCurrentWordForward, None)
        self._menuSeparator = core.actionManager().menu(menu).addSeparator()
        createAction("aSearchDirectory", "Search in &Directory...", 
                      "search-replace-directory.png", "Ctrl+Shift+F", 
                      "Search in directory...",
                      self._onModeSwitchTriggered, ModeSearchDirectory)
        createAction("aReplaceDirectory", "Replace in Director&y...",
                      "search-replace-directory.png", "Ctrl+Shift+R",
                      "Replace in directory...",
                      self._onModeSwitchTriggered, ModeReplaceDirectory)
        createAction("aSearchOpenedFiles", "Search in &Opened Files...",
                      "search-replace-opened-files.png",
                      "Ctrl+Alt+Meta+F", "Search in opened files...",
                      self._onModeSwitchTriggered, ModeSearchOpenedFiles)
        createAction("aReplaceOpenedFiles", "Replace in Open&ed Files...",
                      "search-replace-opened-files.png", "Ctrl+Alt+Meta+R",
                      "Replace in opened files...",
                      self._onModeSwitchTriggered, ModeReplaceOpenedFiles)
        
        am = core.actionManager()
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceFile").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aSearchOpenedFiles").setEnabled(new is not None))
        core.workspace().currentDocumentChanged.connect( \
            lambda old, new: am.action(menu + "/aReplaceOpenedFiles").setEnabled(new is not None))

    def _createSearchWidget(self):
        """ Create search widget. Called only when user requested it first time
        """
        import searchwidget
        self._widget = searchwidget.SearchWidget( self )
        self._widget.searchInDirectoryStartPressed.connect(self._onSearchInDirectoryStartPressed)
        self._widget.searchInDirectoryStopPressed.connect(self._onSearchInDirectoryStopPressed)
        self._widget.replaceCheckedStartPressed.connect(self._onReplaceCheckedStartPressed)
        self._widget.replaceCheckedStopPressed.connect(self._onReplaceCheckedStopPressed)
        self._widget.visibilityChanged.connect(self._updateFoundItemsHighlighting)
        
        self._widget.searchRegExpChanged.connect(self._updateFileActionsState)
        self._widget.searchRegExpChanged.connect(self._onRegExpChanged)
        self._widget.searchRegExpChanged.connect(self._updateFoundItemsHighlighting)
        
        self._widget.searchNext.connect(self._onSearchNext)
        self._widget.searchPrevious.connect(self._onSearchPrevious)
        
        self._widget.replaceFileOne.connect(self._onReplaceFileOne)
        self._widget.replaceFileAll.connect(self._onReplaceFileAll)
        
        core.workspace().currentDocumentChanged.connect(self._updateFileActionsState)  # always disabled, if no widget
        core.workspace().currentDocumentChanged.connect(self._onCurrentDocumentChanged)
        core.workspace().textChanged.connect(self._updateFoundItemsHighlighting)

        core.mainWindow().centralLayout().addWidget( self._widget )
        self._widget.setVisible( False )
        self._updateFileActionsState()

    def _createDockWidget(self):
        """Create dock widget, which displays search results.
        Called only when search in direcory process starts
        """
        import searchresultsdock
        self._dock = searchresultsdock.SearchResultsDock(core.mainWindow())

        core.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self._dock)
        self._dock.setVisible( False )
        self._dock.setReplaceMode(self._mode == ModeReplaceDirectory or \
                                  self._mode == ModeReplaceOpenedFiles)

    def _onModeSwitchTriggered(self):
        """Changing mode, i.e. from "Search file" to "Replace file"
        """
        if not self._widget:
            self._createSearchWidget()
        
        newMode = self.sender().data().toInt()[0]
        
        if newMode & ModeFlagOpenedFiles and \
           not core.workspace().documents():
            return
        
        self._widget.setMode(newMode)
        
        if self._searchThread is not None:
            self._searchThread.stop()
        if self._replaceThread is not None:
            self._replaceThread.stop()

        #self._resetSearchInFileStartPoint()

        self._mode = newMode
        
        if self._dock is not None:
            self._dock.setReplaceMode(self._mode == ModeReplaceDirectory or \
                                      self._mode == ModeReplaceOpenedFiles)
    
    #
    # Highlight found items with yellow
    #
    def _updateFoundItemsHighlighting(self):
        """(Re)highlight found items with yellow color
        """
        document = core.workspace().currentDocument()
        if document is None:
            return
        
        if not self._widget.isVisible() or \
           not self._widget.isSearchRegExpValid()[0] or \
           not self._widget.getRegExp().pattern:
            document.setExtraSelections([])
            return
        
        regExp = self._widget.getRegExp()
        selections = [ (match.start(), len(match.group(0)))\
                        for match in regExp.finditer(document.text())]
        document.setExtraSelections(selections)
    
    def _onCurrentDocumentChanged(self, old, new):
        """Current document changed. Clear highlighted items
        """
        if old is not None:
            old.setExtraSelections([])
    
    @staticmethod
    def _searchInText(regExp, text, startPoint, forward):
        """Search in text and return tuple (nearest match, all matches)
        (None, None) if not found
        """
        matches = [m for m in regExp.finditer(text)]
        if matches:
            if forward:
                matchesAfter = [match for match in matches \
                                    if match.start() >= startPoint]
                if matchesAfter:
                    match = matchesAfter[0]
                else:  # wrap, search from start
                    match = matches[0]
            else:  # reverse search
                matchesBefore = [match for match in matches \
                                    if match.start() < startPoint]
                if matchesBefore:
                    match = matchesBefore[-1]
                else:  # wrap, search from end
                    match = matches[-1]
            return match, matches
        else:
            return None, None

    #
    # Search word under cursor
    #
    def _onSearchCurrentWordBackward(self):
        """Search current word backward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=False)

    def _onSearchCurrentWordForward(self):
        """Search current word forward.
        This search doesn't depend on search widget state, mode and contents
        """
        self._searchWord(forward=True)

    def _searchWord(self, forward=True):
        """Do search in file operation. Will select next found item
        if updateWidget is True, search widget line edit will color will be set according to result
        """
        document = core.workspace().currentDocument()

        word, wordStartAbsPos, wordEndAbsPos = document.wordUnderCursor()
        if word is None:
            return
        
        regExp = re.compile('\\b%s\\b' % re.escape(word))
        text = document.text()

        # avoid matching word under cursor
        if forward:
            startPoint = wordEndAbsPos
        else:
            startPoint = wordStartAbsPos
        
        match, matches = self._searchInText(regExp, document.text(), startPoint, forward)
        if match is not None:
            document.goTo(absPos = match.start(), selectionLength = len(match.group(0)))
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            self._resetSelection(core.workspace().currentDocument())

    #
    # Search and replace in file
    #
    
    def _resetSearchInFileStartPoint(self):
        """Reset the start point.
        Something changed, restart the search process
        """
        self._searchInFileStartPoint = None

    def _updateFileActionsState(self):
        """Update actions enabled/disabled state.
        Called if current document had been changed or if reg exp had been changed
        """
        valid, error = self._widget.isSearchRegExpValid()
        valid = valid and len(self._widget.getRegExp().pattern) > 0  # valid and not empty
        searchAvailable = valid
        
        haveDocument = core.workspace().currentDocument() is not None
        searchInFileAvailable = valid and haveDocument
        
        self._widget.setSearchInFileActionsEnabled(searchInFileAvailable)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchNext").setEnabled(searchInFileAvailable)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchPrevious").setEnabled(searchInFileAvailable)
        
        core.actionManager().action("mNavigation/mSearchReplace/aSearchWordBackward").setEnabled(haveDocument)
        core.actionManager().action("mNavigation/mSearchReplace/aSearchWordForward").setEnabled(haveDocument)

    @staticmethod
    def _resetSelection(document):
        """Reset selection in the document
        """
        line, column = document.cursorPosition()
        document.goTo(line=line, column=column)
    
    def _onRegExpChanged(self, regExp):
        """Search regExp changed. Do incremental search
        """
        if self._mode in (ModeSearch, ModeReplace) and \
           core.workspace().currentDocument() is not None:
            if regExp.pattern:
                self._searchFile(forward=True, incremental=True )
            else:  # Clear selection
                self._resetSelection(core.workspace().currentDocument())

    def _onSearchNext(self):
        """Search Next clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=True, incremental=False )

    def _onSearchPrevious(self):
        """Search Previous clicked
        """
        self._widget.updateComboBoxes()
        self._searchFile(forward=False, incremental=False )
    
    def _searchFile(self, forward=True, incremental=False):
        """Do search in file operation. Will select next found item
        """
        document = core.workspace().currentDocument()
        
        regExp = self._widget.getRegExp()

        if document.absCursorPosition() != self._searchInFileLastCursorPos:
            self._searchInFileStartPoint = None
        
        if self._searchInFileStartPoint is None or not incremental:
            # get cursor position        
            start, end = document.absSelection()

            if start is None:
                start = 0
                end = 0
        
            if forward:
                if  incremental :
                    self._searchInFileStartPoint = start
                else:
                    self._searchInFileStartPoint = end
            else:
                self._searchInFileStartPoint = start
        
        match, matches = self._searchInText(regExp, document.text(), self._searchInFileStartPoint, forward)
        if match:
            document.goTo(absPos = match.start(), selectionLength = len(match.group(0)))
            self._searchInFileLastCursorPos = match.start()
            self._widget.setState(self._widget.Good)  # change background acording to result
            core.mainWindow().statusBar().showMessage('Match %d of %d' % \
                                                      (matches.index(match) + 1, len(matches)), 3000)
        else:
            self._widget.setState(self._widget.Bad)
            self._resetSelection(core.workspace().currentDocument())

    def _onReplaceFileOne(self, replaceText):
        """Do one replacement in the file
        """
        self._widget.updateComboBoxes()
        
        document = core.workspace().currentDocument()
        regExp = self._widget.getRegExp()

        start, end = document.absSelection()  # pylint: disable=W0612
        if start is None:
            start = 0
        
        match = regExp.search(document.text(), start)
        
        if match is None:
            match = regExp.search(document.text(), 0)
        
        if match is not None:
            document.goTo(absPos = match.start(), selectionLength = len(match.group(0)))
            replaceTextSubed = substitutions.makeSubstitutions(replaceText, match)
            document.replaceSelectedText(replaceTextSubed)
            document.goTo(absPos = match.start() + len(replaceTextSubed))
            # move selection to the next item
            self._searchFile(forward=True, incremental=False )
        else:
            self._widget.setState(self._widget.Bad)

    def _onReplaceFileAll(self, replaceText):
        """Do all replacements in the file
        """
        self._widget.updateComboBoxes()
        
        document = core.workspace().currentDocument()
        regExp = self._widget.getRegExp()

        oldPos = document.absCursorPosition()
        
        document.beginUndoAction()
        
        pos = 0
        count = 0
        match = regExp.search(document.text(), pos)
        while match is not None:
            document.goTo(absPos = match.start(), selectionLength = len(match.group(0)))
            replaceTextSubed = substitutions.makeSubstitutions(replaceText, match)
                
            document.replaceSelectedText(replaceTextSubed)
            
            count += 1
            
            pos = match.start() + len(replaceTextSubed)
            
            if not match.group(0) and not replText:  # avoid freeze when replacing empty with empty
                pos  += 1
            if pos < len(document.text()):
                match = regExp.search(document.text(), pos)
            else:
                match = None

        document.endUndoAction()
        
        if oldPos is not None:
            document.setCursorPosition(absPos = oldPos) # restore cursor position
        core.mainWindow().statusBar().showMessage( self.tr( "%d match(es) replaced." % count ), 3000 )
    
    #
    # Search in directory (with thread)
    #

    def _onSearchInDirectoryStartPressed(self, regExp, mask, path):
        """Handler for 'search in directory' action
        """
        self._widget.updateComboBoxes()
        
        if self._dock is None:
            self._createDockWidget()
        
        from threads import SearchThread
        self._searchThread = SearchThread()
        self._searchThread.progressChanged.connect(self._widget.onSearchProgressChanged)
        self._searchThread.resultsAvailable.connect(self._dock.appendResults)
        self._searchThread.finished.connect(self._onSearchThreadFinished)
        
        inOpenedFiles = self._mode in (ModeSearchOpenedFiles, ModeReplaceOpenedFiles,)
        
        self._widget.setSearchInProgress(True)
        self._dock.clear()
        self._searchThread.search( regExp,
                                   mask,
                                   inOpenedFiles,
                                   path)

    def _onSearchInDirectoryStopPressed(self):
        """Handler for 'search in directory' action
        """
        if self._searchThread is not None:
            self._searchThread.stop()

    def _onSearchThreadFinished(self):
        """Handler for search in directory finished signal
        """
        self._widget.setSearchInProgress(False)
        matchesCount = self._dock.matchesCount()
        if matchesCount:
            core.mainWindow().statusBar().showMessage('%d matches ' % matchesCount, 3000)
        else:
            core.mainWindow().statusBar().showMessage('Nothing found', 3000)
    
    #
    # Replace in directory (with thread)
    #

    def _onReplaceCheckedStartPressed(self, replaceText):
        """Handler for 'replace checked' action
        """
        self._widget.updateComboBoxes()
        
        if self._dock is None:  # no any results
            return

        from threads import ReplaceThread 
        self._replaceThread = ReplaceThread()
        self._replaceThread.resultsHandled.connect(self._dock.onResultsHandledByReplaceThread)
        self._replaceThread.error.connect(self._onReplaceThreadError)
        self._replaceThread.finalStatus.connect(self._onReplaceThreadFinalStatus)

        self._replaceThread.replace( self._dock.getCheckedItems(),
                                     replaceText)

    def _onReplaceCheckedStopPressed(self):
        """Handler for 'stop replacing checked' action
        """
        if self._replaceThread is not None:
            self._replaceThread.stop()

    def _onReplaceThreadError(self, error ):
        """Error message from the replace thread
        """
        core.mainWindow().appendMessage( error )

    def _onReplaceThreadFinished(self):
        """Handler for replace in directory finished event
        """
        self._widget.setReplaceInProgress(False)

    def _onReplaceThreadFinalStatus(self, message):
        """Show replace thread status on status bar
        """
        core.mainWindow().statusBar().showMessage(message, 3000)