Blame plugins/commander/modules/find/finder.py

Packit 6978fb
# -*- coding: utf-8 -*-
Packit 6978fb
#
Packit 6978fb
#  finder.py - finder commander module
Packit 6978fb
#
Packit 6978fb
#  Copyright (C) 2010 - Jesse van den Kieboom
Packit 6978fb
#
Packit 6978fb
#  This program is free software; you can redistribute it and/or modify
Packit 6978fb
#  it under the terms of the GNU General Public License as published by
Packit 6978fb
#  the Free Software Foundation; either version 2 of the License, or
Packit 6978fb
#  (at your option) any later version.
Packit 6978fb
#
Packit 6978fb
#  This program is distributed in the hope that it will be useful,
Packit 6978fb
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6978fb
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 6978fb
#  GNU General Public License for more details.
Packit 6978fb
#
Packit 6978fb
#  You should have received a copy of the GNU General Public License
Packit 6978fb
#  along with this program; if not, write to the Free Software
Packit 6978fb
#  Foundation, Inc., 51 Franklin Street, Fifth Floor,
Packit 6978fb
#  Boston, MA 02110-1301, USA.
Packit 6978fb
Packit 6978fb
from xml.sax import saxutils
Packit 6978fb
import commander.commands.result
Packit 6978fb
import commander.utils as utils
Packit 6978fb
from gi.repository import Gdk, Gtk
Packit 6978fb
Packit 6978fb
class Finder:
Packit 6978fb
    FIND_STARTMARK = 'gedit-commander-find-startmark'
Packit 6978fb
    FIND_ENDMARK = 'gedit-commander-find-endmark'
Packit 6978fb
Packit 6978fb
    FIND_RESULT_STARTMARK = 'gedit-commander-find-result-startmark'
Packit 6978fb
    FIND_RESULT_ENDMARK = 'gedit-commander-find-result-endmark'
Packit 6978fb
Packit 6978fb
    def __init__(self, entry):
Packit 6978fb
        self.entry = entry
Packit 6978fb
        self.view = entry.view()
Packit 6978fb
Packit 6978fb
        self.findstr = None
Packit 6978fb
        self.replacestr = None
Packit 6978fb
Packit 6978fb
        self.search_boundaries = utils.Struct({'start': None, 'end': None})
Packit 6978fb
        self.find_result = utils.Struct({'start': None, 'end': None})
Packit 6978fb
Packit 6978fb
        self.unescapes = [
Packit 6978fb
            ['\\n', '\n'],
Packit 6978fb
            ['\\r', '\r'],
Packit 6978fb
            ['\\t', '\t']
Packit 6978fb
        ]
Packit 6978fb
Packit 6978fb
        self.from_start = False
Packit 6978fb
        self.search_start_mark = None
Packit 6978fb
Packit 6978fb
    def unescape(self, s):
Packit 6978fb
        for esc in self.unescapes:
Packit 6978fb
            s = s.replace(esc[0], esc[1])
Packit 6978fb
Packit 6978fb
        return s
Packit 6978fb
Packit 6978fb
    def do_find(self, bounds):
Packit 6978fb
        return None
Packit 6978fb
Packit 6978fb
    def get_replace(self, text):
Packit 6978fb
        return self.replacestr
Packit 6978fb
Packit 6978fb
    def set_replace(self, replacestr):
Packit 6978fb
        self.replacestr = self.unescape(replacestr)
Packit 6978fb
Packit 6978fb
    def set_find(self, findstr):
Packit 6978fb
        self.findstr = self.unescape(findstr)
Packit 6978fb
Packit 6978fb
    def get_find(self):
Packit 6978fb
        return self.findstr
Packit 6978fb
Packit 6978fb
    def select_last_result(self):
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        startiter = buf.get_iter_at_mark(self.find_result.start)
Packit 6978fb
        enditer = buf.get_iter_at_mark(self.find_result.end)
Packit 6978fb
Packit 6978fb
        buf.select_range(startiter, enditer)
Packit 6978fb
Packit 6978fb
        visible = self.view.get_visible_rect()
Packit 6978fb
        loc = self.view.get_iter_location(startiter)
Packit 6978fb
Packit 6978fb
        # Scroll there if needed
Packit 6978fb
        if loc.y + loc.height < visible.y or loc.y > visible.y + visible.height:
Packit 6978fb
            self.view.scroll_to_iter(startiter, 0.2, True, 0, 0.5)
Packit 6978fb
Packit 6978fb
    def find_next(self, select=False):
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        # Search from the end of the last result to the end of the search boundary
Packit 6978fb
        bounds = [buf.get_iter_at_mark(self.find_result.end),
Packit 6978fb
                  buf.get_iter_at_mark(self.search_boundaries.end)]
Packit 6978fb
Packit 6978fb
        ret = self.do_find(bounds)
Packit 6978fb
Packit 6978fb
        # Check if we need to wrap around if nothing is found
Packit 6978fb
        if self.search_start_mark:
Packit 6978fb
            startiter = buf.get_iter_at_mark(self.search_start_mark)
Packit 6978fb
        else:
Packit 6978fb
            startiter = None
Packit 6978fb
Packit 6978fb
        startbound = buf.get_iter_at_mark(self.search_boundaries.start)
Packit 6978fb
Packit 6978fb
        if not ret and not self.from_start and (startiter and not startiter.equal(startbound)):
Packit 6978fb
            self.from_start = True
Packit 6978fb
Packit 6978fb
            # Try from beginning
Packit 6978fb
            bounds[0] = buf.get_start_iter()
Packit 6978fb
            bounds[1] = startiter
Packit 6978fb
Packit 6978fb
            # Make sure to just stop at the start of the previous
Packit 6978fb
            self.search_boundaries.end = self.search_start_mark
Packit 6978fb
Packit 6978fb
            ret = self.do_find(bounds)
Packit 6978fb
Packit 6978fb
        if not ret:
Packit 6978fb
            return False
Packit 6978fb
        else:
Packit 6978fb
            # Mark find result
Packit 6978fb
            buf.move_mark(self.find_result.start, ret[0])
Packit 6978fb
            buf.move_mark(self.find_result.end, ret[1])
Packit 6978fb
Packit 6978fb
            if select:
Packit 6978fb
                self.select_last_result()
Packit 6978fb
Packit 6978fb
            return True
Packit 6978fb
Packit 6978fb
    def _create_or_move(self, markname, piter, left_gravity):
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
        mark = buf.get_mark(markname)
Packit 6978fb
Packit 6978fb
        if not mark:
Packit 6978fb
            mark = buf.create_mark(markname, piter, left_gravity)
Packit 6978fb
        else:
Packit 6978fb
            buf.move_mark(mark, piter)
Packit 6978fb
Packit 6978fb
        return mark
Packit 6978fb
Packit 6978fb
    def find_first(self, doend=True, select=False):
Packit 6978fb
        words = []
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        while not self.findstr:
Packit 6978fb
            fstr, words, modifier = (yield commander.commands.result.Prompt('Find:'))
Packit 6978fb
Packit 6978fb
            if fstr:
Packit 6978fb
                self.set_find(fstr)
Packit 6978fb
Packit 6978fb
        # Determine search area
Packit 6978fb
        bounds = list(buf.get_selection_bounds())
Packit 6978fb
Packit 6978fb
        if self.search_start_mark:
Packit 6978fb
            buf.delete_mark(self.search_start_mark)
Packit 6978fb
            self.search_start_mark = None
Packit 6978fb
Packit 6978fb
        if not bounds:
Packit 6978fb
            # Search in the whole buffer, from the current cursor position on to the
Packit 6978fb
            # end, and then continue to start from the beginning of the buffer if needed
Packit 6978fb
            bounds = list(buf.get_bounds())
Packit 6978fb
            self.search_start_mark = buf.create_mark(None, buf.get_iter_at_mark(buf.get_insert()), True)
Packit 6978fb
            selection = False
Packit 6978fb
        else:
Packit 6978fb
            selection = True
Packit 6978fb
Packit 6978fb
        bounds[0].order(bounds[1])
Packit 6978fb
Packit 6978fb
        # Set marks at the boundaries
Packit 6978fb
        self.search_boundaries.start = self._create_or_move(Finder.FIND_STARTMARK, bounds[0], True)
Packit 6978fb
        self.search_boundaries.end = self._create_or_move(Finder.FIND_ENDMARK, bounds[1], False)
Packit 6978fb
Packit 6978fb
        # Set the result marks so the next find will start at the correct location
Packit 6978fb
        if selection:
Packit 6978fb
            piter = bounds[0]
Packit 6978fb
        else:
Packit 6978fb
            piter = buf.get_iter_at_mark(buf.get_insert())
Packit 6978fb
Packit 6978fb
        self.find_result.start = self._create_or_move(Finder.FIND_RESULT_STARTMARK, piter, True)
Packit 6978fb
        self.find_result.end = self._create_or_move(Finder.FIND_RESULT_ENDMARK, piter, False)
Packit 6978fb
Packit 6978fb
        if not self.find_next(select=select):
Packit 6978fb
            if doend:
Packit 6978fb
                self.entry.info_show('Search hit end of the document', True)
Packit 6978fb
Packit 6978fb
            yield commander.commands.result.DONE
Packit 6978fb
        else:
Packit 6978fb
            yield True
Packit 6978fb
Packit 6978fb
    def cancel(self):
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        buf.set_search_text('', 0)
Packit 6978fb
        buf.move_mark(buf.get_selection_bound(), buf.get_iter_at_mark(buf.get_insert()))
Packit 6978fb
Packit 6978fb
        if self.search_start_mark:
Packit 6978fb
            buf.delete_mark(self.search_start_mark)
Packit 6978fb
Packit 6978fb
    def find(self, findstr):
Packit 6978fb
        if findstr:
Packit 6978fb
            self.set_find(findstr)
Packit 6978fb
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        try:
Packit 6978fb
            if (yield self.find_first(select=True)):
Packit 6978fb
                while True:
Packit 6978fb
                    argstr, words, modifier = (yield commander.commands.result.Prompt('Search next [%s]:' % (saxutils.escape(self.findstr),)))
Packit 6978fb
Packit 6978fb
                    if argstr:
Packit 6978fb
                        self.set_find(argstr)
Packit 6978fb
Packit 6978fb
                    if not self.find_next(select=True):
Packit 6978fb
                        break
Packit 6978fb
Packit 6978fb
                self.entry.info_show('Search hit end of the document', True)
Packit 6978fb
        except GeneratorExit as e:
Packit 6978fb
            self.cancel()
Packit 6978fb
            raise e
Packit 6978fb
Packit 6978fb
        self.cancel()
Packit 6978fb
        yield commander.commands.result.DONE
Packit 6978fb
Packit 6978fb
    def _restore_cursor(self, mark):
Packit 6978fb
        buf = mark.get_buffer()
Packit 6978fb
Packit 6978fb
        buf.place_cursor(buf.get_iter_at_mark(mark))
Packit 6978fb
        buf.delete_mark(mark)
Packit 6978fb
Packit 6978fb
        self.view.scroll_to_mark(buf.get_insert(), 0.2, True, 0, 0.5)
Packit 6978fb
Packit 6978fb
    def get_current_replace(self):
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
        bounds = utils.Struct({'start': buf.get_iter_at_mark(self.find_result.start),
Packit 6978fb
                               'end': buf.get_iter_at_mark(self.find_result.end)})
Packit 6978fb
Packit 6978fb
        if not bounds.start.equal(bounds.end):
Packit 6978fb
            text = bounds.start.get_text(bounds.end)
Packit 6978fb
            return self.get_replace(text)
Packit 6978fb
        else:
Packit 6978fb
            return self.replacestr
Packit 6978fb
Packit 6978fb
    def replace(self, findstr, replaceall=False, replacestr=None):
Packit 6978fb
        if findstr:
Packit 6978fb
            self.set_find(findstr)
Packit 6978fb
Packit 6978fb
        if replacestr != None:
Packit 6978fb
            self.set_replace(replacestr)
Packit 6978fb
Packit 6978fb
        # First find something
Packit 6978fb
        buf = self.view.get_buffer()
Packit 6978fb
Packit 6978fb
        if replaceall:
Packit 6978fb
            startmark = buf.create_mark(None, buf.get_iter_at_mark(buf.get_insert()), False)
Packit 6978fb
Packit 6978fb
        ret = (yield self.find_first(select=not replaceall))
Packit 6978fb
Packit 6978fb
        if not ret:
Packit 6978fb
            yield commander.commands.result.DONE
Packit 6978fb
Packit 6978fb
        self.scroll_back = False
Packit 6978fb
Packit 6978fb
        # Then ask for the replacement string
Packit 6978fb
        if not self.replacestr:
Packit 6978fb
            try:
Packit 6978fb
                if replaceall:
Packit 6978fb
                    self.scroll_back = True
Packit 6978fb
                    self.select_last_result()
Packit 6978fb
Packit 6978fb
                replacestr, words, modifier = (yield commander.commands.result.Prompt('Replace with:'))
Packit 6978fb
                self.set_replace(replacestr)
Packit 6978fb
            except GeneratorExit as e:
Packit 6978fb
                if replaceall:
Packit 6978fb
                    self._restore_cursor(startmark)
Packit 6978fb
Packit 6978fb
                self.cancel()
Packit 6978fb
                raise e
Packit 6978fb
Packit 6978fb
        # On replace all, wrap it in begin/end user action
Packit 6978fb
        if replaceall:
Packit 6978fb
            buf.begin_user_action()
Packit 6978fb
Packit 6978fb
        try:
Packit 6978fb
            while True:
Packit 6978fb
                if not replaceall:
Packit 6978fb
                    rep, words, modifier = (yield commander.commands.result.Prompt('Replace next [%s]:' % (saxutils.escape(self.get_current_replace()),)))
Packit 6978fb
Packit 6978fb
                    if rep:
Packit 6978fb
                        self.set_replace(rep)
Packit 6978fb
Packit 6978fb
                bounds = utils.Struct({'start': buf.get_iter_at_mark(self.find_result.start),
Packit 6978fb
                                       'end': buf.get_iter_at_mark(self.find_result.end)})
Packit 6978fb
Packit 6978fb
                # If there is a selection, replace it with the replacement string
Packit 6978fb
                if not bounds.start.equal(bounds.end) and (replaceall or not (modifier & Gdk.ModifierType.CONTROL_MASK)):
Packit 6978fb
                    text = bounds.start.get_text(bounds.end)
Packit 6978fb
                    repl = self.get_replace(text)
Packit 6978fb
Packit 6978fb
                    buf.begin_user_action()
Packit 6978fb
                    buf.delete(bounds.start, bounds.end)
Packit 6978fb
                    buf.insert(bounds.start, repl)
Packit 6978fb
                    buf.end_user_action()
Packit 6978fb
Packit 6978fb
                # Find next
Packit 6978fb
                if not self.find_next(select=not replaceall):
Packit 6978fb
                    if not replaceall:
Packit 6978fb
                        self.entry.info_show('Search hit end of the document', True)
Packit 6978fb
Packit 6978fb
                    break
Packit 6978fb
Packit 6978fb
        except GeneratorExit as e:
Packit 6978fb
            if replaceall:
Packit 6978fb
                self._restore_cursor(startmark)
Packit 6978fb
                buf.end_user_action()
Packit 6978fb
Packit 6978fb
            self.cancel()
Packit 6978fb
            raise e
Packit 6978fb
Packit 6978fb
        if replaceall:
Packit 6978fb
            if self.scroll_back:
Packit 6978fb
                self._restore_cursor(startmark)
Packit 6978fb
Packit 6978fb
            buf.end_user_action()
Packit 6978fb
Packit 6978fb
        self.cancel()
Packit 6978fb
        yield commander.commands.result.DONE
Packit 6978fb
Packit 6978fb
# ex:ts=4:et