forked from Mirrors/apostrophe
Fix search and replace
TextIter must not be reused in between buffer changes. This resulted in unpredictable behavior when using search and replace. For instance, in the following string: This _is_ a _test_ of _search_ and _replace_ Searching for "_" and replacing with "**" sequentially would: [0] This **is_ a _test_ [1] This ****is a _test_ [2] This ****is a **test_ [2] This ****is a ****test Replace had similar results.ft.font-size^2
parent
ac7e18b0b3
commit
5be563ccc7
|
@ -35,6 +35,9 @@ class SearchAndReplace:
|
||||||
|
|
||||||
def __init__(self, parentwindow, textview):
|
def __init__(self, parentwindow, textview):
|
||||||
self.parentwindow = parentwindow
|
self.parentwindow = parentwindow
|
||||||
|
self.textview = textview
|
||||||
|
self.textbuffer = textview.get_buffer()
|
||||||
|
|
||||||
self.box = parentwindow.builder.get_object("searchbar_placeholder")
|
self.box = parentwindow.builder.get_object("searchbar_placeholder")
|
||||||
self.box.set_reveal_child(False)
|
self.box.set_reveal_child(False)
|
||||||
self.searchbar = parentwindow.builder.get_object("searchbar")
|
self.searchbar = parentwindow.builder.get_object("searchbar")
|
||||||
|
@ -46,9 +49,6 @@ class SearchAndReplace:
|
||||||
self.open_replace_button = parentwindow.builder.get_object("replace")
|
self.open_replace_button = parentwindow.builder.get_object("replace")
|
||||||
self.open_replace_button.connect("toggled", self.toggle_replace)
|
self.open_replace_button.connect("toggled", self.toggle_replace)
|
||||||
|
|
||||||
self.textview = textview
|
|
||||||
self.textbuffer = textview.get_buffer()
|
|
||||||
|
|
||||||
self.nextbutton = parentwindow.builder.get_object("next_result")
|
self.nextbutton = parentwindow.builder.get_object("next_result")
|
||||||
self.prevbutton = parentwindow.builder.get_object("previous_result")
|
self.prevbutton = parentwindow.builder.get_object("previous_result")
|
||||||
self.regexbutton = parentwindow.builder.get_object("regex")
|
self.regexbutton = parentwindow.builder.get_object("regex")
|
||||||
|
@ -68,11 +68,13 @@ class SearchAndReplace:
|
||||||
self.prevbutton.connect('clicked', self.scrolltoprev)
|
self.prevbutton.connect('clicked', self.scrolltoprev)
|
||||||
self.regexbutton.connect('toggled', self.search)
|
self.regexbutton.connect('toggled', self.search)
|
||||||
self.casesensitivebutton.connect('toggled', self.search)
|
self.casesensitivebutton.connect('toggled', self.search)
|
||||||
self.highlight = self.textbuffer.create_tag('search_highlight',
|
self.highlight = self.textbuffer.create_tag('search_highlight', background="yellow")
|
||||||
background="yellow")
|
|
||||||
|
|
||||||
self.textview.connect("focus-in-event", self.focused_texteditor)
|
self.textview.connect("focus-in-event", self.focused_texteditor)
|
||||||
|
|
||||||
|
self.matches = []
|
||||||
|
self.active = 0
|
||||||
|
|
||||||
def toggle_replace(self, widget, _data=None):
|
def toggle_replace(self, widget, _data=None):
|
||||||
"""toggle the replace box
|
"""toggle the replace box
|
||||||
"""
|
"""
|
||||||
|
@ -107,10 +109,9 @@ class SearchAndReplace:
|
||||||
|
|
||||||
def search(self, _widget=None, _data=None, scroll=True):
|
def search(self, _widget=None, _data=None, scroll=True):
|
||||||
searchtext = self.searchentry.get_text()
|
searchtext = self.searchentry.get_text()
|
||||||
buf = self.textbuffer
|
context_start = self.textbuffer.get_start_iter()
|
||||||
context_start = buf.get_start_iter()
|
context_end = self.textbuffer.get_end_iter()
|
||||||
context_end = buf.get_end_iter()
|
text = self.textbuffer.get_slice(context_start, context_end, False)
|
||||||
text = buf.get_slice(context_start, context_end, False)
|
|
||||||
|
|
||||||
self.textbuffer.remove_tag(self.highlight, context_start, context_end)
|
self.textbuffer.remove_tag(self.highlight, context_start, context_end)
|
||||||
|
|
||||||
|
@ -125,12 +126,12 @@ class SearchAndReplace:
|
||||||
|
|
||||||
matches = re.finditer(searchtext, text, flags)
|
matches = re.finditer(searchtext, text, flags)
|
||||||
|
|
||||||
self.matchiters = []
|
self.matches = []
|
||||||
self.active = 0
|
self.active = 0
|
||||||
for match in matches:
|
for match in matches:
|
||||||
start_iter = buf.get_iter_at_offset(match.start())
|
self.matches.append((match.start(), match.end()))
|
||||||
end_iter = buf.get_iter_at_offset(match.end())
|
start_iter = self.textbuffer.get_iter_at_offset(match.start())
|
||||||
self.matchiters.append((start_iter, end_iter))
|
end_iter = self.textbuffer.get_iter_at_offset(match.end())
|
||||||
self.textbuffer.apply_tag(self.highlight, start_iter, end_iter)
|
self.textbuffer.apply_tag(self.highlight, start_iter, end_iter)
|
||||||
if scroll:
|
if scroll:
|
||||||
self.scrollto(self.active)
|
self.scrollto(self.active)
|
||||||
|
@ -143,17 +144,17 @@ class SearchAndReplace:
|
||||||
self.scrollto(self.active - 1)
|
self.scrollto(self.active - 1)
|
||||||
|
|
||||||
def scrollto(self, index):
|
def scrollto(self, index):
|
||||||
if not self.matchiters:
|
if not self.matches:
|
||||||
return
|
return
|
||||||
if index < len(self.matchiters):
|
if index < len(self.matches):
|
||||||
self.active = index
|
self.active = index
|
||||||
else:
|
else:
|
||||||
self.active = 0
|
self.active = 0
|
||||||
|
|
||||||
matchiter = self.matchiters[self.active]
|
match = self.matches[self.active]
|
||||||
self.textview.get_buffer().select_range(matchiter[0], matchiter[1])
|
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||||
|
end_iter = self.textbuffer.get_iter_at_offset(match[1])
|
||||||
# self.texteditor.scroll_to_iter(matchiter[0], 0.0, True, 0.0, 0.5)
|
self.textbuffer.select_range(start_iter, end_iter)
|
||||||
|
|
||||||
def hide(self):
|
def hide(self):
|
||||||
self.replacebox.set_reveal_child(False)
|
self.replacebox.set_reveal_child(False)
|
||||||
|
@ -167,17 +168,20 @@ class SearchAndReplace:
|
||||||
self.replace(self.active)
|
self.replace(self.active)
|
||||||
|
|
||||||
def replace_all(self, _widget=None, _data=None):
|
def replace_all(self, _widget=None, _data=None):
|
||||||
while self.matchiters:
|
for match in reversed(self.matches):
|
||||||
match = self.matchiters[0]
|
self.do_replace(match)
|
||||||
self.textbuffer.delete(match[0], match[1])
|
self.search(scroll=False)
|
||||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
|
||||||
self.search(scroll=False)
|
|
||||||
|
|
||||||
def replace(self, searchindex, _inloop=False):
|
def replace(self, searchindex, _inloop=False):
|
||||||
match = self.matchiters[searchindex]
|
self.do_replace(self.matches[searchindex])
|
||||||
self.textbuffer.delete(match[0], match[1])
|
|
||||||
self.textbuffer.insert(match[0], self.replaceentry.get_text())
|
|
||||||
active = self.active
|
active = self.active
|
||||||
self.search(scroll=False)
|
self.search(scroll=False)
|
||||||
self.active = active
|
self.active = active
|
||||||
self.scrollto(self.active)
|
self.scrollto(self.active)
|
||||||
|
|
||||||
|
def do_replace(self, match):
|
||||||
|
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||||
|
end_iter = self.textbuffer.get_iter_at_offset(match[1])
|
||||||
|
self.textbuffer.delete(start_iter, end_iter)
|
||||||
|
start_iter = self.textbuffer.get_iter_at_offset(match[0])
|
||||||
|
self.textbuffer.insert(start_iter, self.replaceentry.get_text())
|
||||||
|
|
Loading…
Reference in New Issue