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