Commit 3a2fd991 authored by Chris Mayo's avatar Chris Mayo Committed by Kai Willadsen

Initial URI support for file diffs

- Use GFiles in FileDiff
- Use GFiles in MeldWindow file functions
- Store URIs in recent comparison files

 - Fix "Password dialogue cancelled" on accessing remote location
 - fileentry support for URIs
 - shorten_names() specific support for URIs
parent f766e90f
......@@ -641,10 +641,11 @@ class DirDiff(melddoc.MeldDoc, gnomeglade.Component):
def get_comparison(self):
root = self.model.get_iter_first()
if root:
folders = self.model.value_paths(root)
uris = [Gio.File.new_for_path(d).get_uri()
for d in self.model.value_paths(root)]
folders = []
return recent.TYPE_FOLDER, folders
uris = []
return recent.TYPE_FOLDER, uris
def recursively_update( self, path ):
"""Recursively update from tree path 'path'.
......@@ -1144,8 +1145,8 @@ class DirDiff(melddoc.MeldDoc, gnomeglade.Component):
if not rows[pane]:
if os.path.isfile(rows[pane]):
self.emit("create-diff", [r for r in rows if os.path.isfile(r)],
self.emit("create-diff", [Gio.File.new_for_path(r)
for r in rows if os.path.isfile(r)], {})
elif os.path.isdir(rows[pane]):
if view.row_expanded(path):
......@@ -1189,8 +1190,9 @@ class DirDiff(melddoc.MeldDoc, gnomeglade.Component):
def run_diff_from_iter(self, it):
row_paths = self.model.value_paths(it)
paths = [p for p in row_paths if os.path.exists(p)]
self.emit("create-diff", paths, {})
gfiles = [Gio.File.new_for_path(p)
for p in row_paths if os.path.exists(p)]
self.emit("create-diff", gfiles, {})
def on_button_diff_clicked(self, button):
pane = self._get_focused_pane()
......@@ -1025,12 +1025,12 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
self.tooltip_text = self.label_text
def set_files(self, files):
def set_files(self, gfiles):
"""Load the given files
If an element is None, the text of a pane is left as is.
if len(files) != self.num_panes:
if len(gfiles) != self.num_panes:
......@@ -1039,14 +1039,16 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
custom_candidates = get_custom_encoding_candidates()
files = [(pane, Gio.File.new_for_path(filename))
for pane, filename in enumerate(files) if filename]
gfiles_enum = [(pane, gfile)
for pane, gfile in enumerate(gfiles) if gfile]
if not files:
if not gfiles_enum:
for pane, gfile in files:
for pane, gfile in gfiles_enum:
# TODO: filentry handling of URIs
buf = self.textbuffer[pane]
......@@ -1067,8 +1069,9 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
def get_comparison(self):
files = [ for b in self.textbuffer[:self.num_panes]]
return recent.TYPE_FILE, files
uris = [
for b in self.textbuffer[:self.num_panes]]
return recent.TYPE_FILE, uris
def file_loaded(self, loader, result, user_data):
......@@ -1176,8 +1179,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
except ValueError:
# Notification for unknown buffer
gfile = Gio.File.new_for_path(data.filename)
display_name = gfile.get_parse_name()
display_name = data.gfile.get_parse_name()
primary = _("File %s has changed on disk") % display_name
secondary = _("Do you want to reload the file?")
......@@ -1463,7 +1465,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
def save_file(self, pane, saveas=False, force_overwrite=False):
buf = self.textbuffer[pane]
bufdata =
if saveas or not (bufdata.filename or bufdata.savefile) \
if saveas or not (bufdata.gfile or bufdata.savefile) \
or not bufdata.writable:
if pane == 0:
prompt = _("Save Left Pane As")
......@@ -1622,9 +1624,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
def on_fileentry_file_set(self, entry):
entries = self.fileentry[:self.num_panes]
if self.check_save_modified() != Gtk.ResponseType.CANCEL:
files = [e.get_file() for e in entries]
paths = [f.get_path() if f is not None else f for f in files]
gfiles = [e.get_file() for e in entries]
idx = entries.index(entry)
existing_path = self.textbuffer[idx].data.filename
......@@ -1651,8 +1652,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
if response == Gtk.ResponseType.OK:
files = [ for b in self.textbuffer[:self.num_panes]]
gfiles = [ for b in self.textbuffer[:self.num_panes]]
def on_refresh_activate(self, *extra):
......@@ -142,19 +142,17 @@ class MeldApp(Gtk.Application):
return self.get_active_window().meldwindow
def open_files(
self, files, *, window=None, close_on_error=False, **kwargs):
self, gfiles, *, window=None, close_on_error=False, **kwargs):
"""Open a comparison between files in a Meld window
:param files: list of Gio.File to be compared
:param gfiles: list of Gio.File to be compared
:param window: window in which to open comparison tabs; if
None, the current window is used
:param close_on_error: if true, close window if an error occurs
window = window or self.get_meld_window()
paths = [f.get_path() for f in files]
return window.open_paths(paths, **kwargs)
return window.open_paths(gfiles, **kwargs)
except ValueError:
if close_on_error:
......@@ -345,7 +343,10 @@ class MeldApp(Gtk.Application):
auto_merge = options.auto_merge and i == 0
for p, f in zip(paths, files):
if f.get_path() is None:
# TODO: support for directories specified by URIs
if f.get_uri() is None or (not f.is_native() and
f.query_file_type(Gio.FileQueryInfoFlags.NONE, None) ==
raise ValueError(_("invalid path or URI “%s”") % p)
tab = self.open_files(
files, window=window,
......@@ -118,7 +118,10 @@ class MeldBufferData(GObject.GObject):
def reset(self, gfile):
same_file = gfile and self._gfile and gfile.equal(self._gfile)
self.gfile = gfile
self.label = self._label if same_file else self.filename
if same_file:
self.label = self._label
self.label = gfile.get_parse_name() if gfile else None
self.loaded = False
self.savefile = None
......@@ -107,7 +107,7 @@ class MeldDoc(LabeledObjectMixin, GObject.GObject):
self._state = value
def get_comparison(self):
"""Get the comparison type and path(s) being compared"""
"""Get the comparison type and URI(s) being compared"""
def save(self):
......@@ -271,11 +271,8 @@ class MeldWindow(gnomeglade.Component):
def on_widget_drag_data_received(self, wid, context, x, y, selection_data,
info, time):
if len(selection_data.get_uris()) != 0:
paths = []
for uri in selection_data.get_uris():
if len(selection_data.get_files()) != 0:
return True
def on_idle(self):
......@@ -569,7 +566,8 @@ class MeldWindow(gnomeglade.Component):
doc.connect("diff-created", diff_created_cb)
return doc
def append_dirdiff(self, dirs, auto_compare=False):
def append_dirdiff(self, gfiles, auto_compare=False):
dirs = [d.get_path() for d in gfiles if d]
assert len(dirs) in (1, 2, 3)
doc = dirdiff.DirDiff(len(dirs))
self._append_page(doc, "folder")
......@@ -578,93 +576,101 @@ class MeldWindow(gnomeglade.Component):
return doc
def append_filediff(self, files, merge_output=None, meta=None):
assert len(files) in (1, 2, 3)
doc = filediff.FileDiff(len(files))
def append_filediff(self, gfiles, merge_output=None, meta=None):
assert len(gfiles) in (1, 2, 3)
doc = filediff.FileDiff(len(gfiles))
self._append_page(doc, "text-x-generic")
if merge_output is not None:
if meta is not None:
return doc
def append_filemerge(self, files, merge_output=None):
if len(files) != 3:
def append_filemerge(self, gfiles, merge_output=None):
if len(gfiles) != 3:
raise ValueError(
_("Need three files to auto-merge, got: %r") % files)
doc = filemerge.FileMerge(len(files))
_("Need three files to auto-merge, got: %r") %
[f.get_parse_name() for f in gfiles])
doc = filemerge.FileMerge(len(gfiles))
self._append_page(doc, "text-x-generic")
if merge_output is not None:
return doc
def append_diff(self, paths, auto_compare=False, auto_merge=False,
def append_diff(self, gfiles, auto_compare=False, auto_merge=False,
merge_output=None, meta=None):
dirslist = [p for p in paths if os.path.isdir(p)]
fileslist = [p for p in paths if os.path.isfile(p)]
if dirslist and fileslist:
have_directories = False
have_files = False
for f in gfiles:
if f.query_file_type(
Gio.FileQueryInfoFlags.NONE, None) == Gio.FileType.DIRECTORY:
have_directories = True
have_files = True
if have_directories and have_files:
raise ValueError(
_("Cannot compare a mixture of files and directories"))
elif dirslist:
return self.append_dirdiff(paths, auto_compare)
elif have_directories:
return self.append_dirdiff(gfiles, auto_compare)
elif auto_merge:
return self.append_filemerge(paths, merge_output=merge_output)
return self.append_filemerge(gfiles, merge_output=merge_output)
return self.append_filediff(
paths, merge_output=merge_output, meta=meta)
gfiles, merge_output=merge_output, meta=meta)
def append_vcview(self, location, auto_compare=False):
doc = vcview.VcView()
self._append_page(doc, "meld-version-control")
location = location[0] if isinstance(location, list) else location
if auto_compare:
return doc
def append_recent(self, uri):
comparison_type, files, flags =
comparison_type, gfiles, flags =
if comparison_type == recent.TYPE_MERGE:
tab = self.append_filemerge(files)
tab = self.append_filemerge(gfiles)
elif comparison_type == recent.TYPE_FOLDER:
tab = self.append_dirdiff(files)
tab = self.append_dirdiff(gfiles)
elif comparison_type == recent.TYPE_VC:
# Files should be a single-element iterable
tab = self.append_vcview(files[0])
tab = self.append_vcview(gfiles[0])
else: # comparison_type == recent.TYPE_FILE:
tab = self.append_filediff(files)
tab = self.append_filediff(gfiles)
return tab
def _single_file_open(self, path):
def _single_file_open(self, gfile):
doc = vcview.VcView()
def cleanup():
path = os.path.abspath(path)
path = gfile.get_path()
doc.connect("create-diff", lambda obj, arg, kwargs:
self.append_diff(arg, **kwargs))
def open_paths(self, paths, auto_compare=False, auto_merge=False,
def open_paths(self, gfiles, auto_compare=False, auto_merge=False,
tab = None
if len(paths) == 1:
a = paths[0]
if os.path.isfile(a):
if len(gfiles) == 1:
a = gfiles[0]
if a.query_file_type(Gio.FileQueryInfoFlags.NONE, None) == \
tab = self.append_vcview(a, auto_compare)
elif len(paths) in (2, 3):
tab = self.append_diff(
paths, auto_compare=auto_compare, auto_merge=auto_merge)
elif len(gfiles) in (2, 3):
tab = self.append_diff(gfiles, auto_compare=auto_compare,
if tab:
if focus:
......@@ -249,7 +249,7 @@ def all_same(lst):
def shorten_names(*names):
"""Remove redunant parts of a list of names (e.g. /tmp/foo{1,2} -> foo{1,2}
# TODO: Update for different path separators
# TODO: Update for different path separators and URIs
prefix = os.path.commonprefix(names)
prefixslash = prefix.rfind("/") + 1
......@@ -263,7 +263,7 @@ def shorten_names(*names):
if all_same(basenames):
def firstpart(alist):
if len(alist) > 1:
if len(alist) > 1 and alist[0]:
return "[%s] " % alist[0]
return ""
......@@ -15,6 +15,7 @@
import os
from gi.repository import Gio
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk
......@@ -105,16 +106,9 @@ class NewDiffTab(LabeledObjectMixin, GObject.GObject, gnomeglade.Component):
def on_button_compare_clicked(self, *args):
type_choosers = (self.file_chooser, self.dir_chooser, self.vc_chooser)
compare_paths = []
num_paths = self._get_num_paths()
for chooser in type_choosers[self.diff_type][:num_paths]:
gfile = chooser.get_file()
path = gfile.get_path() if gfile else ""
# TODO: We should be migrating to passing around GFiles
tab = self.diff_methods[self.diff_type](compare_paths)
compare_gfiles = [c.get_file() for c in
tab = self.diff_methods[self.diff_type](compare_gfiles)
self.emit('diff-created', tab)
......@@ -122,8 +116,8 @@ class NewDiffTab(LabeledObjectMixin, GObject.GObject, gnomeglade.Component):
# TODO: This doesn't work the way I'd like for DirDiff and VCView.
# It should do something similar to FileDiff; give a tab with empty
# file entries and no comparison done.
compare_paths = [""] * self._get_num_paths()
tab = self.diff_methods[self.diff_type](compare_paths)
compare_gfiles = [Gio.File.new_for_path("")] * self._get_num_paths()
tab = self.diff_methods[self.diff_type](compare_gfiles)
self.emit('diff-created', tab)
def on_container_switch_in_event(self, *args):
......@@ -81,35 +81,35 @@ class RecentFiles(object):
The passed flags are currently ignored. In the future these are to be
used for extra initialisation not captured by the tab itself.
comp_type, paths = tab.get_comparison()
comp_type, uris = tab.get_comparison()
# While Meld handles comparisons including None, recording these as
# recently-used comparisons just isn't that sane.
if None in paths:
if None in uris:
# If a (type, paths) comparison is already registered, then re-add
# If a (type, uris) comparison is already registered, then re-add
# the corresponding comparison file
comparison_key = (comp_type, tuple(paths))
paths = [unicodeify(p) for p in paths]
comparison_key = (comp_type, tuple(uris))
uris = [unicodeify(u) for u in uris]
if comparison_key in self._stored_comparisons:
gio_file = Gio.File.new_for_uri(
gfile = Gio.File.new_for_uri(
recent_path = self._write_recent_file(comp_type, paths)
gio_file = Gio.File.new_for_path(recent_path)
recent_path = self._write_recent_file(comp_type, uris)
gfile = Gio.File.new_for_path(recent_path)
if len(paths) > 1:
display_name = " : ".join(meld.misc.shorten_names(*paths))
if len(uris) > 1:
display_name = " : ".join(meld.misc.shorten_names(*uris))
display_path = paths[0]
display_path = uris[0]
userhome = os.path.expanduser("~")
if display_path.startswith(userhome):
# FIXME: What should we show on Windows?
display_path = "~" + display_path[len(userhome):]
display_name = _("Version control:") + " " + display_path
# FIXME: Should this be translatable? It's not actually used anywhere.
description = "%s comparison\n%s" % (comp_type, ", ".join(paths))
description = "%s comparison\n%s" % (comp_type, ", ".join(uris))
recent_metadata = Gtk.RecentData()
recent_metadata.mime_type = self.mime_type
......@@ -118,38 +118,45 @@ class RecentFiles(object):
recent_metadata.display_name = display_name
recent_metadata.description = description
recent_metadata.is_private = True
self.recent_manager.add_full(gio_file.get_uri(), recent_metadata)
self.recent_manager.add_full(gfile.get_uri(), recent_metadata)
def read(self, uri):
"""Read stored comparison from URI
Returns the comparison type, the paths involved and the comparison
Returns the comparison type, the URIs involved and the comparison
gio_file = Gio.File.new_for_uri(uri)
path = gio_file.get_path()
if not gio_file.query_exists(None) or not path:
raise IOError("File does not exist")
comp_gfile = Gio.File.new_for_uri(uri)
comp_path = comp_gfile.get_path()
if not comp_gfile.query_exists(None) or not comp_path:
raise IOError("Recent comparison file does not exist")
# TODO: remove reading paths in next release
config = configparser.RawConfigParser()
assert (config.has_section("Comparison") and
config.has_option("Comparison", "type") and
config.has_option("Comparison", "paths"))
(config.has_option("Comparison", "paths") or
config.has_option("Comparison", "uris")))
except (configparser.Error, AssertionError):
raise ValueError("Invalid recent comparison file")
comp_type = config.get("Comparison", "type")
paths = tuple(config.get("Comparison", "paths").split(";"))
flags = tuple()
if comp_type not in COMPARISON_TYPES:
raise ValueError("Invalid recent comparison file")
return comp_type, paths, flags
if config.has_option("Comparison", "uris"):
gfiles = tuple([Gio.File.new_for_uri(u)
for u in tuple(config.get("Comparison", "uris").split(";"))])
gfiles = tuple([Gio.File.new_for_path(p)
for p in tuple(config.get("Comparison", "paths").split(";"))])
flags = tuple()
return comp_type, gfiles, flags
def _write_recent_file(self, comp_type, paths):
def _write_recent_file(self, comp_type, uris):
# TODO: Use GKeyFile instead, and return a Gio.File. This is why we're
# using ';' to join comparison paths.
with tempfile.NamedTemporaryFile(
......@@ -158,7 +165,7 @@ class RecentFiles(object):
config = configparser.RawConfigParser()
config.set("Comparison", "type", comp_type)
config.set("Comparison", "paths", ";".join(paths))
config.set("Comparison", "uris", ";".join(uris))
name =
return name
......@@ -325,7 +325,8 @@ class VcView(melddoc.MeldDoc, gnomeglade.Component):
def get_comparison(self):
return recent.TYPE_VC, [self.location]
uris = [Gio.File.new_for_path(self.location).get_uri()]
return recent.TYPE_VC, uris
def recompute_label(self):
self.label_text = os.path.basename(self.location)
......@@ -436,7 +437,7 @@ class VcView(melddoc.MeldDoc, gnomeglade.Component):
def run_diff(self, path):
if os.path.isdir(path):
self.emit("create-diff", [path], {})
self.emit("create-diff", [Gio.File.new_for_path(path)], {})
basename = os.path.basename(path)
......@@ -492,7 +493,8 @@ class VcView(melddoc.MeldDoc, gnomeglade.Component):
os.chmod(temp_file, 0o444)
self.emit("create-diff", diffs, kwargs)
[Gio.File.new_for_path(d) for d in diffs], kwargs)
def do_popup_treeview_menu(self, widget, event):
if event:
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment