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

TODO:
 - 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)]
else:
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]:
return
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):
view.collapse_row(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
self.label_changed()
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:
return
self._disconnect_buffer_handlers()
......@@ -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:
self.scheduler.add_task(self._compare_files_internal())
for pane, gfile in files:
for pane, gfile in gfiles_enum:
self.fileentry[pane].set_file(gfile)
# TODO: filentry handling of URIs
self.fileentry[pane].set_sensitive(gfile.is_native())
self.msgarea_mgr[pane].clear()
buf = self.textbuffer[pane]
......@@ -1067,8 +1069,9 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
)
def get_comparison(self):
files = [b.data.filename for b in self.textbuffer[:self.num_panes]]
return recent.TYPE_FILE, files
uris = [b.data.gfile.get_uri()
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
return
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?")
self.msgarea_mgr[pane].add_action_msg(
......@@ -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 = buf.data
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]
self.set_files(paths)
gfiles = [e.get_file() for e in entries]
self.set_files(gfiles)
else:
idx = entries.index(entry)
existing_path = self.textbuffer[idx].data.filename
......@@ -1651,8 +1652,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
dialog.widget.destroy()
if response == Gtk.ResponseType.OK:
files = [b.data.filename for b in self.textbuffer[:self.num_panes]]
self.set_files(files)
gfiles = [b.data.gfile for b in self.textbuffer[:self.num_panes]]
self.set_files(gfiles)
def on_refresh_activate(self, *extra):
self.refresh_comparison()
......
......@@ -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]
try:
return window.open_paths(paths, **kwargs)
return window.open_paths(gfiles, **kwargs)
except ValueError:
if close_on_error:
self.remove_window(window.widget)
......@@ -345,7 +343,10 @@ class MeldApp(Gtk.Application):
auto_merge = options.auto_merge and i == 0
try:
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) ==
Gio.FileType.DIRECTORY):
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
else:
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"""
pass
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():
paths.append(Gio.File.new_for_uri(uri).get_path())
self.open_paths(paths)
if len(selection_data.get_files()) != 0:
self.open_paths(selection_data.get_files())
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):
doc.scheduler.add_task(doc.auto_compare)
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")
doc.set_files(files)
doc.set_files(gfiles)
if merge_output is not None:
doc.set_merge_output_file(merge_output)
if meta is not None:
doc.set_meta(meta)
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")
doc.set_files(files)
doc.set_files(gfiles)
if merge_output is not None:
doc.set_merge_output_file(merge_output)
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
else:
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)
else:
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
doc.set_location(location)
doc.set_location(location.get_path())
if auto_compare:
doc.scheduler.add_task(doc.auto_compare)
return doc
def append_recent(self, uri):
comparison_type, files, flags = recent_comparisons.read(uri)
comparison_type, gfiles, flags = recent_comparisons.read(uri)
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)
self.notebook.set_current_page(self.notebook.page_num(tab.widget))
recent_comparisons.add(tab)
return tab
def _single_file_open(self, path):
def _single_file_open(self, gfile):
doc = vcview.VcView()
def cleanup():
self.scheduler.remove_scheduler(doc.scheduler)
self.scheduler.add_task(cleanup)
self.scheduler.add_scheduler(doc.scheduler)
path = os.path.abspath(path)
path = gfile.get_path()
doc.set_location(path)
doc.connect("create-diff", lambda obj, arg, kwargs:
self.append_diff(arg, **kwargs))
doc.run_diff(path)
def open_paths(self, paths, auto_compare=False, auto_merge=False,
def open_paths(self, gfiles, auto_compare=False, auto_merge=False,
focus=False):
tab = None
if len(paths) == 1:
a = paths[0]
if os.path.isfile(a):
self._single_file_open(a)
else:
if len(gfiles) == 1:
a = gfiles[0]
if a.query_file_type(Gio.FileQueryInfoFlags.NONE, None) == \
Gio.FileType.DIRECTORY:
tab = self.append_vcview(a, auto_compare)
else:
self._single_file_open(a)
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,
auto_merge=auto_merge)
if tab:
recent_comparisons.add(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):
else:
if all_same(basenames):
def firstpart(alist):
if len(alist) > 1:
if len(alist) > 1 and alist[0]:
return "[%s] " % alist[0]
else:
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 ""
compare_paths.append(path)
# 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
type_choosers[self.diff_type][:self._get_num_paths()]]
tab = self.diff_methods[self.diff_type](compare_gfiles)
recent_comparisons.add(tab)
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:
return
# 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(
self._stored_comparisons[comparison_key])
else:
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))
else:
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
flags.
"""
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
try:
config = configparser.RawConfigParser()
config.read(path)
config.read(comp_path)
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(";"))])
else:
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.add_section("Comparison")
config.set("Comparison", "type", comp_type)
config.set("Comparison", "paths", ";".join(paths))
config.set("Comparison", "uris", ";".join(uris))
config.write(f)
name = f.name
return name
......
......@@ -325,7 +325,8 @@ class VcView(melddoc.MeldDoc, gnomeglade.Component):
self.scheduler.add_task(self.on_treeview_cursor_changed)
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)], {})
return
basename = os.path.basename(path)
......@@ -492,7 +493,8 @@ class VcView(melddoc.MeldDoc, gnomeglade.Component):
os.chmod(temp_file, 0o444)
_temp_files.append(temp_file)
self.emit("create-diff", diffs, kwargs)
self.emit("create-diff",
[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