1# Copyright (C) 2002-2006 Stephen Kennedy <stevek@gnome.org> 2# Copyright (C) 2009-2010, 2013 Kai Willadsen <kai.willadsen@gmail.com> 3# 4# This program is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 2 of the License, or (at 7# your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, but 10# WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12# General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17import difflib 18import os 19 20from gi.repository import Gdk 21from gi.repository import Gio 22from gi.repository import GLib 23from gi.repository import Gtk 24from gi.repository import GtkSource 25 26from meld.conf import _ 27from meld.iohelpers import prompt_save_filename 28from meld.misc import error_dialog 29from meld.settings import meldsettings 30from meld.sourceview import LanguageManager 31from meld.ui.gnomeglade import Component 32 33 34class PatchDialog(Component): 35 36 def __init__(self, filediff): 37 super().__init__("patch-dialog.ui", "patchdialog") 38 39 self.widget.set_transient_for(filediff.widget.get_toplevel()) 40 self.filediff = filediff 41 42 buf = GtkSource.Buffer() 43 self.textview.set_buffer(buf) 44 lang = LanguageManager.get_language_from_mime_type("text/x-diff") 45 buf.set_language(lang) 46 buf.set_highlight_syntax(True) 47 48 self.textview.modify_font(meldsettings.font) 49 self.textview.set_editable(False) 50 51 self.index_map = {self.left_radiobutton: (0, 1), 52 self.right_radiobutton: (1, 2)} 53 self.left_patch = True 54 self.reverse_patch = self.reverse_checkbutton.get_active() 55 56 if self.filediff.num_panes < 3: 57 self.side_selection_label.hide() 58 self.side_selection_box.hide() 59 60 meldsettings.connect('changed', self.on_setting_changed) 61 62 def on_setting_changed(self, setting, key): 63 if key == "font": 64 self.textview.modify_font(meldsettings.font) 65 66 def on_buffer_selection_changed(self, radiobutton): 67 if not radiobutton.get_active(): 68 return 69 self.left_patch = radiobutton == self.left_radiobutton 70 self.update_patch() 71 72 def on_reverse_checkbutton_toggled(self, checkbutton): 73 self.reverse_patch = checkbutton.get_active() 74 self.update_patch() 75 76 def update_patch(self): 77 indices = (0, 1) 78 if not self.left_patch: 79 indices = (1, 2) 80 if self.reverse_patch: 81 indices = (indices[1], indices[0]) 82 83 texts = [] 84 for b in self.filediff.textbuffer: 85 start, end = b.get_bounds() 86 text = b.get_text(start, end, False) 87 lines = text.splitlines(True) 88 89 # Ensure that the last line ends in a newline 90 barelines = text.splitlines(False) 91 if barelines and lines and barelines[-1] == lines[-1]: 92 # Final line lacks a line-break; add in a best guess 93 if len(lines) > 1: 94 previous_linebreak = lines[-2][len(barelines[-2]):] 95 else: 96 previous_linebreak = "\n" 97 lines[-1] += previous_linebreak 98 99 texts.append(lines) 100 101 names = [self.filediff.textbuffer[i].data.label for i in range(3)] 102 prefix = os.path.commonprefix(names) 103 names = [n[prefix.rfind("/") + 1:] for n in names] 104 105 buf = self.textview.get_buffer() 106 text0, text1 = texts[indices[0]], texts[indices[1]] 107 name0, name1 = names[indices[0]], names[indices[1]] 108 109 diff = difflib.unified_diff(text0, text1, name0, name1) 110 diff_text = "".join(d for d in diff) 111 buf.set_text(diff_text) 112 113 def save_patch(self, targetfile: Gio.File): 114 buf = self.textview.get_buffer() 115 sourcefile = GtkSource.File.new() 116 saver = GtkSource.FileSaver.new_with_target( 117 buf, sourcefile, targetfile) 118 saver.save_async( 119 GLib.PRIORITY_HIGH, 120 callback=self.file_saved_cb, 121 ) 122 123 def file_saved_cb(self, saver, result, *args): 124 gfile = saver.get_location() 125 try: 126 saver.save_finish(result) 127 except GLib.Error as err: 128 filename = GLib.markup_escape_text(gfile.get_parse_name()) 129 error_dialog( 130 primary=_("Could not save file %s.") % filename, 131 secondary=_("Couldn’t save file due to:\n%s") % ( 132 GLib.markup_escape_text(str(err))), 133 ) 134 135 def run(self): 136 self.update_patch() 137 138 result = self.widget.run() 139 if result < 0: 140 self.widget.hide() 141 return 142 143 # Copy patch to clipboard 144 if result == 1: 145 buf = self.textview.get_buffer() 146 start, end = buf.get_bounds() 147 clip = Gtk.Clipboard.get_default(Gdk.Display.get_default()) 148 clip.set_text(buf.get_text(start, end, False), -1) 149 clip.store() 150 # Save patch as a file 151 else: 152 gfile = prompt_save_filename(_("Save Patch")) 153 if gfile: 154 self.save_patch(gfile) 155 156 self.widget.hide() 157