[meld] meldbuffer: Add a line cache to our Bufferlines helper class (#564)
- From: Kai Willadsen <kaiw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [meld] meldbuffer: Add a line cache to our Bufferlines helper class (#564)
- Date: Sat, 7 Aug 2021 23:17:16 +0000 (UTC)
commit a15dbdfce29bd30f76883ff55c41eef19777b323
Author: Kai Willadsen <kai willadsen gmail com>
Date: Sun Aug 8 08:20:52 2021 +1000
meldbuffer: Add a line cache to our Bufferlines helper class (#564)
In the 3.21.x series, we changed this class so that instead of fetching
a text range and splitting it in Python, we instead manually fetched
individual lines using GTK logic. We did this because we have a long
history of GTK and Python disagreeing about what constitutes a line
break. Since the base GtkTextBuffer will always use its own definition,
we have no realistic choice but to go with what it wants.
The problem here is that it turns out that between GTK not really
exposing fast line-oriented APIs, and the number of Python -> GTK
calls we were having to make to get the line data out, this turns out
to be *very* slow.
The solution we've gone with here is to add a Python-side cache on top
of our line accessing logic. This is not entirely dissimilar to our
existing logic that invalidates diff chunks when the underlying buffer
changes.
meld/meldbuffer.py | 65 +++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 47 insertions(+), 18 deletions(-)
---
diff --git a/meld/meldbuffer.py b/meld/meldbuffer.py
index 958c99dc..fba5675d 100644
--- a/meld/meldbuffer.py
+++ b/meld/meldbuffer.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
+from typing import List, Optional
from gi.repository import Gio, GLib, GObject, GtkSource
@@ -216,6 +217,11 @@ class BufferLines:
Gtk.TextBuffer is used.
"""
+ #: Cached copy of the (possibly filtered) text in a single line,
+ #: where an entry of None indicates that there is no cached result
+ #: available.
+ lines: List[Optional[str]]
+
def __init__(self, buf, textfilter=None):
self.buf = buf
if textfilter is not None:
@@ -223,33 +229,56 @@ class BufferLines:
else:
self.textfilter = lambda x, buf, start_iter, end_iter: x
+ self.lines = [None] * self.buf.get_line_count()
+ self.mark = buf.create_mark(
+ "bufferlines-insert", buf.get_start_iter(), True,
+ )
+
+ buf.connect("insert-text", self.on_insert_text),
+ buf.connect("delete-range", self.on_delete_range),
+ buf.connect_after("insert-text", self.after_insert_text),
+
+ def on_insert_text(self, buf, it, text, textlen):
+ buf.move_mark(self.mark, it)
+
+ def after_insert_text(self, buf, it, newtext, textlen):
+ start_idx = buf.get_iter_at_mark(self.mark).get_line()
+ end_idx = it.get_line() + 1
+ # Replace the insertion-point cache line with a list of empty
+ # lines. In the single-line case this will be a single element
+ # substitution; for multi-line inserts, we will replace the
+ # single insertion point line with several empty cache lines.
+ self.lines[start_idx:start_idx + 1] = [None] * (end_idx - start_idx)
+
+ def on_delete_range(self, buf, it0, it1):
+ start_idx = it0.get_line()
+ end_idx = it1.get_line() + 1
+ self.lines[start_idx:end_idx] = [None]
+
def __getitem__(self, key):
if isinstance(key, slice):
lo, hi, _ = key.indices(self.buf.get_line_count())
- line_start = self.buf.get_iter_at_line_or_eof(lo)
- end = self.buf.get_iter_at_line_or_eof(hi)
- lines = []
- while line_start.compare(end) < 0:
- line_end = line_start.copy()
- if not line_end.ends_line():
- line_end.forward_to_line_end()
- txt = self.buf.get_text(line_start, line_end, False)
- filter_txt = self.textfilter(txt, self.buf, line_start, end)
- lines.append(filter_txt)
- line_start.forward_visible_line()
+ for idx in range(lo, hi):
+ if self.lines[idx] is None:
+ self.lines[idx] = self[idx]
- return lines
+ return self.lines[lo:hi]
elif isinstance(key, int):
if key >= len(self):
raise IndexError
- line_start = self.buf.get_iter_at_line_or_eof(key)
- line_end = line_start.copy()
- if not line_end.ends_line():
- line_end.forward_to_line_end()
- txt = self.buf.get_text(line_start, line_end, False)
- return self.textfilter(txt, self.buf, line_start, line_end)
+
+ if self.lines[key] is None:
+ line_start = self.buf.get_iter_at_line_or_eof(key)
+ line_end = line_start.copy()
+ if not line_end.ends_line():
+ line_end.forward_to_line_end()
+ txt = self.buf.get_text(line_start, line_end, False)
+ txt = self.textfilter(txt, self.buf, line_start, line_end)
+ self.lines[key] = txt
+
+ return self.lines[key]
def __len__(self):
return self.buf.get_line_count()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]