[meld] Add detailed cursor handling, and use for Up/Down sensitivity setting
- From: Kai Willadsen <kaiw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [meld] Add detailed cursor handling, and use for Up/Down sensitivity setting
- Date: Tue, 2 Mar 2010 04:45:00 +0000 (UTC)
commit bafa162612031596861febe99f6c5f73a9c6b481
Author: Kai Willadsen <kai willadsen gmail com>
Date: Sat Sep 26 18:10:08 2009 +1000
Add detailed cursor handling, and use for Up/Down sensitivity setting
There are several pieces of cursor-related information that we need: its
current location, which chunk (if any) it is currently in, and which chunks
are above and below it. The first is used for the status bar and for
determining the other two. The second may be used for a status-bar display
of where in the list of changes we currently are, and also for keyboard
operations (such as insert/delete/replace chunk). The third is useful in
skipping between changes, and in sensitivity setting.
This patch introduces a CursorDetails structure that stores all of the
interesting information, updates these details as necessary, and uses the
next/previous chunk information for sensitivity setting.
Differ: Add chunk by-line locator and by-index accessor for cursor handling
CursorDetails: New simple class holding cursor data
FileDiff: Use CursorDetails to keep track of cursor and its
current/previous/next diff chunks
MeldApp: Use new next-diff-changed signal to update Up/Down sensitivity
MeldDoc: Add new cursor-related signals
Closes bgo#533752 and bgo#609629
meld/diffutil.py | 71 ++++++++++++++++++++++++++++++++++++
meld/filediff.py | 106 +++++++++++++++++++++++++++++-------------------------
meld/meldapp.py | 8 ++++
meld/melddoc.py | 3 +-
4 files changed, 138 insertions(+), 50 deletions(-)
---
diff --git a/meld/diffutil.py b/meld/diffutil.py
index b1a2047..9109855 100644
--- a/meld/diffutil.py
+++ b/meld/diffutil.py
@@ -76,6 +76,7 @@ class Differ(object):
self.seqlength = [0, 0, 0]
self.diffs = [[], []]
self._merge_cache = []
+ self._line_cache = [[], [], []]
self.ignore_blanks = False
def _update_merge_cache(self, texts):
@@ -92,6 +93,49 @@ class Differ(object):
self._consume_blank_lines(c[1], texts, 1, 2))
self._merge_cache = [x for x in self._merge_cache if x != (None, None)]
+ self._update_line_cache()
+
+ def _update_line_cache(self):
+ for i, l in enumerate(self.seqlength):
+ self._line_cache[i] = [(None, None, None)] * l
+
+ last_chunk = len(self._merge_cache)
+ def find_next(diff, seq, current):
+ next_chunk = None
+ if seq == 1 and current + 1 < last_chunk:
+ next_chunk = current + 1
+ else:
+ for j in range(current + 1, last_chunk):
+ if self._merge_cache[j][diff] is not None:
+ next_chunk = j
+ break
+ return next_chunk
+
+ prev = [None, None, None]
+ next = [find_next(0, 0, -1), find_next(0, 1, -1), find_next(1, 2, -1)]
+ old_end = [0, 0, 0]
+
+ for i, c in enumerate(self._merge_cache):
+ seq_params = ((0, 0, 3, 4), (0, 1, 1, 2), (1, 2, 3, 4))
+ for (diff, seq, lo, hi) in seq_params:
+ if c[diff] is None:
+ if seq == 1:
+ diff = 1
+ else:
+ continue
+
+ start, end, last = c[diff][lo], c[diff][hi], old_end[seq]
+ if (start > last):
+ self._line_cache[seq][last:start] = [(None, prev[seq], next[seq])] * (start - last)
+
+ # For insert chunks, claim the subsequent line.
+ if start == end:
+ end += 1
+
+ next[seq] = find_next(diff, seq, i)
+ self._line_cache[seq][start:end] = [(i, prev[seq], next[seq])] * (end - start)
+ prev[seq], old_end[seq] = i, end
+
def _consume_blank_lines(self, c, texts, pane1, pane2):
if c is None:
return None
@@ -134,6 +178,33 @@ class Differ(object):
return i
return len(self.diffs[whichdiffs])
+ def get_chunk(self, index, from_pane, to_pane=None):
+ """Return the index-th change in from_pane
+
+ If to_pane is provided, then only changes between from_pane and to_pane
+ are considered, otherwise all changes starting at from_pane are used.
+ """
+ sequence = int(from_pane == 2 or to_pane == 2)
+ chunk = self._merge_cache[index][sequence]
+ if from_pane in (0, 2):
+ if chunk is None:
+ return None
+ return reverse_chunk(chunk)
+ else:
+ if to_pane is None and chunk is None:
+ chunk = self._merge_cache[index][1]
+ return chunk
+
+ def locate_chunk(self, pane, line):
+ """Find the index of the chunk which contains line."""
+ try:
+ return self._line_cache[pane][line]
+ except IndexError:
+ return (None, None, None)
+
+ def diff_count(self):
+ return len(self._merge_cache)
+
def _change_sequence(self, which, sequence, startidx, sizechange, texts):
diffs = self.diffs[which]
lines_added = [0,0,0]
diff --git a/meld/filediff.py b/meld/filediff.py
index 8a7e5ff..e8d6063 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -90,6 +90,14 @@ def insert_with_tags_by_name(buffer, line, text, tag):
text = "\n" + text
buffer.insert_with_tags_by_name(get_iter_at_line_or_eof(buffer, line), text, tag)
+class CursorDetails(object):
+ __slots__ = ("pane", "pos", "line", "offset", "chunk", "prev", "next")
+
+ def __init__(self):
+ for var in self.__slots__:
+ setattr(self, var, None)
+
+
class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
"""Two or three way diff of text files.
"""
@@ -181,6 +189,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
gobject.idle_add( lambda *args: self.load_font()) # hack around Bug 316730
gnomeglade.connect_signal_handlers(self)
self.findbar = self.findbar.get_data("pyobject")
+ self.cursor = CursorDetails()
def on_focus_change(self):
self.keymask = 0
@@ -216,36 +225,48 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
id1 = buf.connect("delete-range", self.on_text_delete_range)
id2 = buf.connect_after("insert-text", self.after_text_insert_text)
id3 = buf.connect_after("delete-range", self.after_text_delete_range)
- id4 = buf.connect("mark-set", self.on_textbuffer_mark_set)
+ id4 = buf.connect("notify::cursor-position",
+ self.on_cursor_position_changed)
buf.handlers = id0, id1, id2, id3, id4
- def _update_cursor_status(self, buf):
- def update():
- it = buf.get_iter_at_mark( buf.get_insert() )
- # Abbreviation for insert,overwrite so that it will fit in the status bar
- insert_overwrite = _("INS,OVR").split(",")[ self.textview_overwrite ]
- # Abbreviation for line, column so that it will fit in the status bar
- line_column = _("Ln %i, Col %i") % (it.get_line()+1, it.get_line_offset()+1)
- status = "%s : %s" % ( insert_overwrite, line_column )
- self.emit("status-changed", status)
- return False
- self.scheduler.add_task(update)
+ def on_cursor_position_changed(self, buf, pspec, force=False):
+ pane = self.textbuffer.index(buf)
+ pos = buf.props.cursor_position
+ if pane == self.cursor.pane and pos == self.cursor.pos and not force:
+ return
+ self.cursor.pane, self.cursor.pos = pane, pos
+
+ cursor_it = buf.get_iter_at_offset(pos)
+ offset = cursor_it.get_line_offset()
+ line = cursor_it.get_line()
+
+ # Abbreviations for insert and overwrite that fit in the status bar
+ insert_overwrite = (_("INS"), _("OVR"))[self.textview_overwrite]
+ # Abbreviation for line, column so that it will fit in the status bar
+ line_column = _("Ln %i, Col %i") % (line + 1, offset + 1)
+ status = "%s : %s" % (insert_overwrite, line_column)
+ self.emit("status-changed", status)
+
+ if line != self.cursor.line or force:
+ chunk, prev, next = self.linediffer.locate_chunk(pane, line)
+ if prev != self.cursor.prev or next != self.cursor.next:
+ self.emit("next-diff-changed", prev is not None,
+ next is not None)
+ self.cursor.chunk, self.cursor.prev, self.cursor.next = chunk, prev, next
+ self.cursor.line, self.cursor.offset = line, offset
- def on_textbuffer_mark_set(self, buffer, it, mark):
- if mark.get_name() == "insert":
- self._update_cursor_status(buffer)
def on_textview_focus_in_event(self, view, event):
self.textview_focussed = view
self.findbar.textview = view
- self._update_cursor_status(view.get_buffer())
+ self.on_cursor_position_changed(view.get_buffer(), None, True)
def _after_text_modified(self, buffer, startline, sizechange):
if self.num_panes > 1:
pane = self.textbuffer.index(buffer)
self.linediffer.change_sequence(pane, startline, sizechange, self._get_texts())
+ self.on_cursor_position_changed(buffer, None, True)
self.scheduler.add_task(self._update_highlighting().next)
self.queue_draw()
- self._update_cursor_status(buffer)
def _get_texts(self, raw=0):
class FakeText(object):
@@ -510,7 +531,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
if v != view:
v.emit("toggle-overwrite")
self.textview_overwrite_handlers = [ t.connect("toggle-overwrite", self.on_textview_toggle_overwrite) for t in self.textview ]
- self._update_cursor_status(view.get_buffer())
+ self.on_cursor_position_changed(view.get_buffer(), None, True)
#
@@ -669,7 +690,11 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
msgarea.connect("response", self.on_msgarea_identical_response)
msgarea.show_all()
- self.scheduler.add_task( lambda: self.next_diff(gdk.SCROLL_DOWN, jump_to_first=True), True )
+ chunk, prev, next = self.linediffer.locate_chunk(1, 0)
+ self.cursor.next = chunk
+ if self.cursor.next is None:
+ self.cursor.next = next
+ self.scheduler.add_task(lambda: self.next_diff(gdk.SCROLL_DOWN), True)
self.queue_draw()
self.scheduler.add_task(self._update_highlighting().next)
self._connect_buffer_handlers()
@@ -1133,29 +1158,17 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
def _pixel_to_line(self, pane, pixel ):
return self.textview[pane].get_line_at_y( pixel )[0].get_line()
- def _find_next_chunk(self, direction, curline, pane):
- c = None
- for chunk in self.linediffer.single_changes(pane):
- assert chunk[0] != "equal"
- if direction == gdk.SCROLL_DOWN:
- # Take the first chunk which is starting after curline
- if chunk[1] > curline:
- c = chunk
- break
- else: # direction == gdk.SCROLL_UP
- # Skip 'delete' blocks when we are at the warp position,
- # i.e. on the line just after the block, because that may
- # be where we ended up at the previous 'UP' button press
- if chunk[2] == chunk[1] == curline:
- continue
- # Take the last chunk which is ending before curline
- if chunk[2] - 1 < curline:
- c = chunk
- elif c:
- break
- return c
+ def _find_next_chunk(self, direction, pane):
+ if direction == gtk.gdk.SCROLL_DOWN:
+ target = self.cursor.next
+ else: # direction == gtk.gdk.SCROLL_UP
+ target = self.cursor.prev
- def next_diff(self, direction, jump_to_first=False):
+ if target is None:
+ return None
+ return self.linediffer.get_chunk(target, pane)
+
+ def next_diff(self, direction):
pane = self._get_focused_pane()
if pane == -1:
if len(self.textview) > 1:
@@ -1164,15 +1177,10 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
pane = 0
buf = self.textbuffer[pane]
- if jump_to_first:
- cursorline = -1
- else:
- cursorline = buf.get_iter_at_mark(buf.get_insert()).get_line()
-
- c = self._find_next_chunk(direction, cursorline, pane)
+ c = self._find_next_chunk(direction, pane)
if c:
# Warp the cursor to the first line of next chunk
- if cursorline != c[1]:
+ if self.cursor.line != c[1]:
buf.place_cursor(buf.get_iter_at_line(c[1]))
self.textview[pane].scroll_to_mark(buf.get_insert(), 0.1)
diff --git a/meld/meldapp.py b/meld/meldapp.py
index 81e6991..7de8ab3 100644
--- a/meld/meldapp.py
+++ b/meld/meldapp.py
@@ -500,6 +500,7 @@ class MeldApp(gnomeglade.Component):
self.widget.set_default_size(self.prefs.window_size_x, self.prefs.window_size_y)
self.ui.ensure_update()
self.widget.show()
+ self.diff_handler = None
self.widget.connect('focus_in_event', self.on_focus_change)
self.widget.connect('focus_out_event', self.on_focus_change)
@@ -590,6 +591,7 @@ class MeldApp(gnomeglade.Component):
oldidx = notebook.get_current_page()
if oldidx >= 0:
olddoc = notebook.get_nth_page(oldidx).get_data("pyobject")
+ olddoc.disconnect(self.diff_handler)
olddoc.on_container_switch_out_event(self.ui)
self.actiongroup.get_action("Undo").set_sensitive(newseq.can_undo())
self.actiongroup.get_action("Redo").set_sensitive(newseq.can_redo())
@@ -597,6 +599,8 @@ class MeldApp(gnomeglade.Component):
self.widget.set_title(nbl.get_label_text() + " - Meld")
self.statusbar.set_doc_status("")
newdoc.on_container_switch_in_event(self.ui)
+ self.diff_handler = newdoc.connect("next-diff-changed",
+ self.on_next_diff_changed)
self.scheduler.add_task( newdoc.scheduler )
def on_notebook_label_changed(self, component, text):
@@ -611,6 +615,10 @@ class MeldApp(gnomeglade.Component):
def on_can_redo(self, undosequence, can):
self.actiongroup.get_action("Redo").set_sensitive(can)
+ def on_next_diff_changed(self, doc, have_prev, have_next):
+ self.actiongroup.get_action("Up").set_sensitive(have_prev)
+ self.actiongroup.get_action("Down").set_sensitive(have_next)
+
def on_size_allocate(self, window, rect):
self.prefs.window_size_x = rect.width
self.prefs.window_size_y = rect.height
diff --git a/meld/melddoc.py b/meld/melddoc.py
index 062cec6..32223c6 100644
--- a/meld/melddoc.py
+++ b/meld/melddoc.py
@@ -32,7 +32,8 @@ class MeldDoc(gobject.GObject):
'label-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
'file-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
'create-diff': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
- 'status-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
+ 'status-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)),
+ 'next-diff-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (bool, bool)),
}
def __init__(self, prefs):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]