Fix all flake8 errors

This commit is contained in:
Romain Failliot 2021-11-22 20:11:51 -05:00
parent 362c59f150
commit 8d25396f68
18 changed files with 487 additions and 420 deletions

6
.flake8 Normal file
View File

@ -0,0 +1,6 @@
[flake8]
builtins = _
max-line-length = 100
# Temporary
exclude = src/diffuse/main.py

View File

@ -19,17 +19,14 @@
import os import os
# pylint: disable=wrong-import-position from diffuse import constants
from diffuse import utils
import gi import gi
gi.require_version('GObject', '2.0') gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gtk from gi.repository import GObject, Gtk # noqa: E402
# pylint: enable=wrong-import-position
# pylint: disable-next=no-name-in-module
from diffuse import constants
from diffuse import utils
# the about dialog # the about dialog
class AboutDialog(Gtk.AboutDialog): class AboutDialog(Gtk.AboutDialog):
@ -41,8 +38,8 @@ class AboutDialog(Gtk.AboutDialog):
self.set_comments(_('Diffuse is a graphical tool for merging and comparing text files.')) self.set_comments(_('Diffuse is a graphical tool for merging and comparing text files.'))
self.set_copyright(constants.COPYRIGHT) self.set_copyright(constants.COPYRIGHT)
self.set_website(constants.WEBSITE) self.set_website(constants.WEBSITE)
self.set_authors([ 'Derrick Moser <derrick_moser@yahoo.com>', self.set_authors(['Derrick Moser <derrick_moser@yahoo.com>',
'Romain Failliot <romain.failliot@foolstep.com>' ]) 'Romain Failliot <romain.failliot@foolstep.com>'])
self.set_translator_credits(_('translator-credits')) self.set_translator_credits(_('translator-credits'))
license_text = [ license_text = [
constants.APP_NAME + ' ' + constants.VERSION + '\n\n', constants.APP_NAME + ' ' + constants.VERSION + '\n\n',
@ -59,9 +56,10 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.''') ] 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.''')]
self.set_license(''.join(license_text)) self.set_license(''.join(license_text))
# custom dialogue for picking files with widgets for specifying the encoding # custom dialogue for picking files with widgets for specifying the encoding
# and revision # and revision
class FileChooserDialog(Gtk.FileChooserDialog): class FileChooserDialog(Gtk.FileChooserDialog):
@ -85,7 +83,7 @@ class FileChooserDialog(Gtk.FileChooserDialog):
label.show() label.show()
self.encoding = entry = utils.EncodingMenu( self.encoding = entry = utils.EncodingMenu(
prefs, prefs,
action in [ Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER ]) action in [Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER])
hbox.pack_start(entry, False, False, 5) hbox.pack_start(entry, False, False, 5)
entry.show() entry.show()
if rev: if rev:
@ -96,7 +94,7 @@ class FileChooserDialog(Gtk.FileChooserDialog):
hbox.pack_end(label, False, False, 0) hbox.pack_end(label, False, False, 0)
label.show() label.show()
self.vbox.pack_start(hbox, False, False, 0) # pylint: disable=no-member self.vbox.pack_start(hbox, False, False, 0)
hbox.show() hbox.show()
self.set_current_folder(self.last_chosen_folder) self.set_current_folder(self.last_chosen_folder)
self.connect('current-folder-changed', self._current_folder_changed_cb) self.connect('current-folder-changed', self._current_folder_changed_cb)
@ -110,10 +108,10 @@ class FileChooserDialog(Gtk.FileChooserDialog):
def get_revision(self): def get_revision(self):
return self.revision.get_text() return self.revision.get_text()
# pylint: disable-next=arguments-differ
def get_filename(self): def get_filename(self):
# convert from UTF-8 string to unicode # convert from UTF-8 string to unicode
return Gtk.FileChooserDialog.get_filename(self) # pylint: disable=no-member return Gtk.FileChooserDialog.get_filename(self)
# dialogue used to search for text # dialogue used to search for text
class NumericDialog(Gtk.Dialog): class NumericDialog(Gtk.Dialog):
@ -138,12 +136,13 @@ class NumericDialog(Gtk.Dialog):
vbox.pack_start(hbox, True, True, 0) vbox.pack_start(hbox, True, True, 0)
hbox.show() hbox.show()
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member self.vbox.pack_start(vbox, False, False, 0)
vbox.show() vbox.show()
def button_cb(self, widget): def button_cb(self, widget):
self.response(Gtk.ResponseType.ACCEPT) self.response(Gtk.ResponseType.ACCEPT)
# dialogue used to search for text # dialogue used to search for text
class SearchDialog(Gtk.Dialog): class SearchDialog(Gtk.Dialog):
def __init__(self, parent, pattern=None, history=None): def __init__(self, parent, pattern=None, history=None):
@ -190,7 +189,7 @@ class SearchDialog(Gtk.Dialog):
vbox.pack_start(button, False, False, 0) vbox.pack_start(button, False, False, 0)
button.show() button.show()
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member self.vbox.pack_start(vbox, False, False, 0)
vbox.show() vbox.show()
# callback used when the Enter key is pressed # callback used when the Enter key is pressed

View File

@ -25,22 +25,9 @@ import shlex
import stat import stat
import webbrowser import webbrowser
# pylint: disable=wrong-import-position
import gi
gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('GdkPixbuf', '2.0')
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Pango, PangoCairo
# pylint: enable=wrong-import-position
from urllib.parse import urlparse from urllib.parse import urlparse
# pylint: disable-next=no-name-in-module
from diffuse import constants from diffuse import constants
from diffuse import utils from diffuse import utils
from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog
from diffuse.preferences import Preferences from diffuse.preferences import Preferences
@ -49,6 +36,16 @@ from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import FileDiffViewer from diffuse.widgets import FileDiffViewer
from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE
import gi
gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('GdkPixbuf', '2.0')
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Pango, PangoCairo # noqa: E402
theVCSs = VcsRegistry() theVCSs = VcsRegistry()
# widget classed to create notebook tabs with labels and a close button # widget classed to create notebook tabs with labels and a close button
@ -120,10 +117,10 @@ class Diffuse(Gtk.Window):
def __init__(self): def __init__(self):
Gtk.Box.__init__(self, orientation = Gtk.Orientation.HORIZONTAL, spacing = 0) Gtk.Box.__init__(self, orientation = Gtk.Orientation.HORIZONTAL, spacing = 0)
_append_buttons(self, Gtk.IconSize.MENU, [ _append_buttons(self, Gtk.IconSize.MENU, [
[ Gtk.STOCK_OPEN, self.button_cb, 'open', _('Open File...') ], [Gtk.STOCK_OPEN, self.button_cb, 'open', _('Open File...')],
[ Gtk.STOCK_REFRESH, self.button_cb, 'reload', _('Reload File') ], [Gtk.STOCK_REFRESH, self.button_cb, 'reload', _('Reload File')],
[ Gtk.STOCK_SAVE, self.button_cb, 'save', _('Save File') ], [Gtk.STOCK_SAVE, self.button_cb, 'save', _('Save File')],
[ Gtk.STOCK_SAVE_AS, self.button_cb, 'save_as', _('Save File As...') ] ]) [Gtk.STOCK_SAVE_AS, self.button_cb, 'save_as', _('Save File As...') ]])
self.label = label = Gtk.Label.new() self.label = label = Gtk.Label.new()
label.set_selectable(True) label.set_selectable(True)
@ -258,7 +255,7 @@ class Diffuse(Gtk.Window):
self.connect('format-changed', self.format_changed_cb) self.connect('format-changed', self.format_changed_cb)
for i, darea in enumerate(self.dareas): for i, darea in enumerate(self.dareas):
darea.drag_dest_set(Gtk.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/uri-list', 0, 0) ], Gdk.DragAction.COPY) darea.drag_dest_set(Gtk.DestDefaults.ALL, [Gtk.TargetEntry.new('text/uri-list', 0, 0)], Gdk.DragAction.COPY)
darea.connect('drag-data-received', self.drag_data_received_cb, i) darea.connect('drag-data-received', self.drag_data_received_cb, i)
# initialise status # initialise status
self.updateStatus() self.updateStatus()
@ -749,7 +746,7 @@ class Diffuse(Gtk.Window):
[], [],
[_('Pr_eferences...'), self.preferences_cb, None, Gtk.STOCK_PREFERENCES, 'preferences'] ] ]) [_('Pr_eferences...'), self.preferences_cb, None, Gtk.STOCK_PREFERENCES, 'preferences'] ] ])
submenudef = [ [_('None'), self.syntax_cb, None, None, 'no_syntax_highlighting', True, None, ('syntax', None) ] ] submenudef = [[_('None'), self.syntax_cb, None, None, 'no_syntax_highlighting', True, None, ('syntax', None) ]]
names = theResources.getSyntaxNames() names = theResources.getSyntaxNames()
if len(names) > 0: if len(names) > 0:
submenudef.append([]) submenudef.append([])
@ -820,28 +817,28 @@ class Diffuse(Gtk.Window):
# create button bar # create button bar
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
_append_buttons(hbox, Gtk.IconSize.LARGE_TOOLBAR, [ _append_buttons(hbox, Gtk.IconSize.LARGE_TOOLBAR, [
[ DIFFUSE_STOCK_NEW_2WAY_MERGE, self.new_2_way_file_merge_cb, None, _('New 2-Way File Merge') ], [DIFFUSE_STOCK_NEW_2WAY_MERGE, self.new_2_way_file_merge_cb, None, _('New 2-Way File Merge')],
[ DIFFUSE_STOCK_NEW_3WAY_MERGE, self.new_3_way_file_merge_cb, None, _('New 3-Way File Merge') ], [DIFFUSE_STOCK_NEW_3WAY_MERGE, self.new_3_way_file_merge_cb, None, _('New 3-Way File Merge')],
[], [],
[ Gtk.STOCK_EXECUTE, self.button_cb, 'realign_all', _('Realign All') ], [Gtk.STOCK_EXECUTE, self.button_cb, 'realign_all', _('Realign All')],
[ Gtk.STOCK_GOTO_TOP, self.button_cb, 'first_difference', _('First Difference') ], [Gtk.STOCK_GOTO_TOP, self.button_cb, 'first_difference', _('First Difference')],
[ Gtk.STOCK_GO_UP, self.button_cb, 'previous_difference', _('Previous Difference') ], [Gtk.STOCK_GO_UP, self.button_cb, 'previous_difference', _('Previous Difference')],
[ Gtk.STOCK_GO_DOWN, self.button_cb, 'next_difference', _('Next Difference') ], [Gtk.STOCK_GO_DOWN, self.button_cb, 'next_difference', _('Next Difference')],
[ Gtk.STOCK_GOTO_BOTTOM, self.button_cb, 'last_difference', _('Last Difference') ], [Gtk.STOCK_GOTO_BOTTOM, self.button_cb, 'last_difference', _('Last Difference')],
[], [],
[ Gtk.STOCK_GOTO_LAST, self.button_cb, 'copy_selection_right', _('Copy Selection Right') ], [Gtk.STOCK_GOTO_LAST, self.button_cb, 'copy_selection_right', _('Copy Selection Right')],
[ Gtk.STOCK_GOTO_FIRST, self.button_cb, 'copy_selection_left', _('Copy Selection Left') ], [Gtk.STOCK_GOTO_FIRST, self.button_cb, 'copy_selection_left', _('Copy Selection Left')],
[ Gtk.STOCK_GO_FORWARD, self.button_cb, 'copy_left_into_selection', _('Copy Left Into Selection') ], [Gtk.STOCK_GO_FORWARD, self.button_cb, 'copy_left_into_selection', _('Copy Left Into Selection')],
[ Gtk.STOCK_GO_BACK, self.button_cb, 'copy_right_into_selection', _('Copy Right Into Selection') ], [Gtk.STOCK_GO_BACK, self.button_cb, 'copy_right_into_selection', _('Copy Right Into Selection')],
[ DIFFUSE_STOCK_LEFT_RIGHT, self.button_cb, 'merge_from_left_then_right', _('Merge From Left Then Right') ], [DIFFUSE_STOCK_LEFT_RIGHT, self.button_cb, 'merge_from_left_then_right', _('Merge From Left Then Right')],
[ DIFFUSE_STOCK_RIGHT_LEFT, self.button_cb, 'merge_from_right_then_left', _('Merge From Right Then Left') ], [DIFFUSE_STOCK_RIGHT_LEFT, self.button_cb, 'merge_from_right_then_left', _('Merge From Right Then Left')],
[], [],
[ Gtk.STOCK_UNDO, self.button_cb, 'undo', _('Undo') ], [Gtk.STOCK_UNDO, self.button_cb, 'undo', _('Undo')],
[ Gtk.STOCK_REDO, self.button_cb, 'redo', _('Redo') ], [Gtk.STOCK_REDO, self.button_cb, 'redo', _('Redo')],
[ Gtk.STOCK_CUT, self.button_cb, 'cut', _('Cut') ], [Gtk.STOCK_CUT, self.button_cb, 'cut', _('Cut')],
[ Gtk.STOCK_COPY, self.button_cb, 'copy', _('Copy') ], [Gtk.STOCK_COPY, self.button_cb, 'copy', _('Copy')],
[ Gtk.STOCK_PASTE, self.button_cb, 'paste', _('Paste') ], [Gtk.STOCK_PASTE, self.button_cb, 'paste', _('Paste')],
[ Gtk.STOCK_CLEAR, self.button_cb, 'clear_edits', _('Clear Edits') ] ]) [Gtk.STOCK_CLEAR, self.button_cb, 'clear_edits', _('Clear Edits') ]])
# avoid the button bar from dictating the minimum window size # avoid the button bar from dictating the minimum window size
hbox.set_size_request(0, hbox.get_size_request()[1]) hbox.set_size_request(0, hbox.get_size_request()[1])
vbox.pack_start(hbox, False, False, 0) vbox.pack_start(hbox, False, False, 0)
@ -986,7 +983,7 @@ class Diffuse(Gtk.Window):
treeview.connect('row-activated', self._confirmClose_row_activated_cb, model) treeview.connect('row-activated', self._confirmClose_row_activated_cb, model)
sw.add(treeview) sw.add(treeview)
treeview.show() treeview.show()
dialog.vbox.pack_start(sw, True, True, 0) # pylint: disable=no-member dialog.vbox.pack_start(sw, True, True, 0)
sw.show() sw.show()
# add custom set of action buttons # add custom set of action buttons
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
@ -1017,7 +1014,7 @@ class Diffuse(Gtk.Window):
nb = self.notebook nb = self.notebook
if nb.get_n_pages() > 1: if nb.get_n_pages() > 1:
# warn about losing unsaved changes before removing a tab # warn about losing unsaved changes before removing a tab
if self.confirmCloseViewers([ data ]): if self.confirmCloseViewers([data]):
self.closed_tabs.append((nb.page_num(data), data, nb.get_tab_label(data))) self.closed_tabs.append((nb.page_num(data), data, nb.get_tab_label(data)))
nb.remove(data) nb.remove(data)
nb.set_show_tabs(self.prefs.getBool('tabs_always_show') or nb.get_n_pages() > 1) nb.set_show_tabs(self.prefs.getBool('tabs_always_show') or nb.get_n_pages() > 1)
@ -1180,9 +1177,9 @@ class Diffuse(Gtk.Window):
# create a new viewer for each item in 'items' # create a new viewer for each item in 'items'
def createSeparateTabs(self, items, labels, options): def createSeparateTabs(self, items, labels, options):
# all tabs inherit the first tab's revision and encoding specifications # all tabs inherit the first tab's revision and encoding specifications
items = [ (name, items[0][1]) for name, data in items ] items = [(name, items[0][1]) for name, data in items]
for item in _assign_file_labels(items, labels): for item in _assign_file_labels(items, labels):
self.newLoadedFileDiffViewer([ item ]).setOptions(options) self.newLoadedFileDiffViewer([item]).setOptions(options)
# create a new viewer for each modified file found in 'items' # create a new viewer for each modified file found in 'items'
def createCommitFileTabs(self, items, labels, options): def createCommitFileTabs(self, items, labels, options):
@ -1196,7 +1193,7 @@ class Diffuse(Gtk.Window):
if dn == old_dn: if dn == old_dn:
break break
if len(new_items) == 0 or dn != new_items[-1][0]: if len(new_items) == 0 or dn != new_items[-1][0]:
new_items.append([ dn, None, [] ]) new_items.append([dn, None, []])
dst = new_items[-1] dst = new_items[-1]
dst[1] = data[-1][1] dst[1] = data[-1][1]
dst[2].append(name) dst[2].append(name)
@ -1225,7 +1222,7 @@ class Diffuse(Gtk.Window):
if dn == old_dn: if dn == old_dn:
break break
if len(new_items) == 0 or dn != new_items[-1][0]: if len(new_items) == 0 or dn != new_items[-1][0]:
new_items.append([ dn, None, [] ]) new_items.append([dn, None, []])
dst = new_items[-1] dst = new_items[-1]
dst[1] = data[-1][1] dst[1] = data[-1][1]
dst[2].append(name) dst[2].append(name)
@ -1251,7 +1248,7 @@ class Diffuse(Gtk.Window):
# returns True if the application can safely quit # returns True if the application can safely quit
def confirmQuit(self): def confirmQuit(self):
nb = self.notebook nb = self.notebook
return self.confirmCloseViewers([ nb.get_nth_page(i) for i in range(nb.get_n_pages()) ]) return self.confirmCloseViewers([nb.get_nth_page(i) for i in range(nb.get_n_pages())])
# respond to close window request from the window manager # respond to close window request from the window manager
def delete_cb(self, widget, event): def delete_cb(self, widget, event):
@ -1279,7 +1276,7 @@ class Diffuse(Gtk.Window):
rev = None rev = None
dialog.destroy() dialog.destroy()
if accept: if accept:
viewer = self.newLoadedFileDiffViewer([ (name, [ (rev, encoding) ], None) ]) viewer = self.newLoadedFileDiffViewer([(name, [(rev, encoding)], None)])
self.notebook.set_current_page(self.notebook.get_n_pages() - 1) self.notebook.set_current_page(self.notebook.get_n_pages() - 1)
viewer.grab_focus() viewer.grab_focus()
@ -1293,7 +1290,7 @@ class Diffuse(Gtk.Window):
dialog.destroy() dialog.destroy()
if accept: if accept:
n = self.notebook.get_n_pages() n = self.notebook.get_n_pages()
self.createModifiedFileTabs([ (name, [ (None, encoding) ]) ], [], {}) self.createModifiedFileTabs([(name, [(None, encoding)])], [], {})
if self.notebook.get_n_pages() > n: if self.notebook.get_n_pages() > n:
# we added some new tabs, focus on the first one # we added some new tabs, focus on the first one
self.notebook.set_current_page(n) self.notebook.set_current_page(n)
@ -1311,7 +1308,7 @@ class Diffuse(Gtk.Window):
dialog.destroy() dialog.destroy()
if accept: if accept:
n = self.notebook.get_n_pages() n = self.notebook.get_n_pages()
self.createCommitFileTabs([ (name, [ (None, encoding) ]) ], [], { 'commit': rev }) self.createCommitFileTabs([(name, [(None, encoding)])], [], { 'commit': rev })
if self.notebook.get_n_pages() > n: if self.notebook.get_n_pages() > n:
# we added some new tabs, focus on the first one # we added some new tabs, focus on the first one
self.notebook.set_current_page(n) self.notebook.set_current_page(n)
@ -1488,9 +1485,9 @@ class Diffuse(Gtk.Window):
if utils.isWindows(): if utils.isWindows():
# help documentation is distributed as local HTML files # help documentation is distributed as local HTML files
# search for localised manual first # search for localised manual first
parts = [ 'manual' ] parts = ['manual']
if utils.lang is not None: if utils.lang is not None:
parts = [ 'manual' ] parts = ['manual']
parts.extend(utils.lang.split('_')) parts.extend(utils.lang.split('_'))
while len(parts) > 0: while len(parts) > 0:
help_file = os.path.join(utils.bin_dir, '_'.join(parts) + '.html') help_file = os.path.join(utils.bin_dir, '_'.join(parts) + '.html')
@ -1524,7 +1521,7 @@ class Diffuse(Gtk.Window):
d = 'C' d = 'C'
help_file = os.path.join(os.path.join(s, d), 'diffuse.xml') help_file = os.path.join(os.path.join(s, d), 'diffuse.xml')
if os.path.isfile(help_file): if os.path.isfile(help_file):
args = [ browser, _path2url(help_file, 'ghelp') ] args = [browser, _path2url(help_file, 'ghelp')]
# spawnvp is not available on some systems, use spawnv instead # spawnvp is not available on some systems, use spawnv instead
os.spawnv(os.P_NOWAIT, args[0], args) os.spawnv(os.P_NOWAIT, args[0], args)
return return
@ -1582,7 +1579,7 @@ def _append_buttons(box, size, specs):
# constructs a full URL for the named file # constructs a full URL for the named file
def _path2url(path, proto='file'): def _path2url(path, proto='file'):
r = [ proto, ':///' ] r = [proto, ':///']
s = os.path.abspath(path) s = os.path.abspath(path)
i = 0 i = 0
while i < len(s) and s[i] == os.sep: while i < len(s) and s[i] == os.sep:
@ -1626,11 +1623,11 @@ def main():
args = sys.argv args = sys.argv
argc = len(args) argc = len(args)
if argc == 2 and args[1] in [ '-v', '--version' ]: if argc == 2 and args[1] in ['-v', '--version']:
print('%s %s\n%s' % (constants.APP_NAME, constants.VERSION, constants.COPYRIGHT)) print('%s %s\n%s' % (constants.APP_NAME, constants.VERSION, constants.COPYRIGHT))
return 0 return 0
if argc == 2 and args[1] in [ '-h', '-?', '--help' ]: if argc == 2 and args[1] in ['-h', '-?', '--help']:
print(_('''Usage: print(_('''Usage:
diffuse [ [OPTION...] [FILE...] ]... diffuse [ [OPTION...] [FILE...] ]...
diffuse ( -h | -? | --help | -v | --version ) diffuse ( -h | -? | --help | -v | --version )
@ -1683,7 +1680,7 @@ Display Options:
subdirs = ['diffuse'] subdirs = ['diffuse']
if data_dir is None: if data_dir is None:
data_dir = os.path.expanduser('~') data_dir = os.path.expanduser('~')
subdirs[:0] = [ '.local', 'share' ] subdirs[:0] = ['.local', 'share']
data_dir = utils.make_subdirs(data_dir, subdirs) data_dir = utils.make_subdirs(data_dir, subdirs)
# load resource files # load resource files
@ -1739,58 +1736,58 @@ Display Options:
while i < argc: while i < argc:
arg = args[i] arg = args[i]
if len(arg) > 0 and arg[0] == '-': if len(arg) > 0 and arg[0] == '-':
if i + 1 < argc and arg in [ '-c', '--commit' ]: if i + 1 < argc and arg in ['-c', '--commit']:
# specified revision # specified revision
funcs[mode](specs, labels, options) funcs[mode](specs, labels, options)
i += 1 i += 1
rev = args[i] rev = args[i]
specs, labels, options = [], [], { 'commit': args[i] } specs, labels, options = [], [], { 'commit': args[i] }
mode = 'commit' mode = 'commit'
elif arg in [ '-D', '--close-if-same' ]: elif arg in ['-D', '--close-if-same']:
close_on_same = True close_on_same = True
elif i + 1 < argc and arg in [ '-e', '--encoding' ]: elif i + 1 < argc and arg in ['-e', '--encoding']:
i += 1 i += 1
encoding = args[i] encoding = args[i]
encoding = encodings.aliases.aliases.get(encoding, encoding) encoding = encodings.aliases.aliases.get(encoding, encoding)
elif arg in [ '-m', '--modified' ]: elif arg in ['-m', '--modified']:
funcs[mode](specs, labels, options) funcs[mode](specs, labels, options)
specs, labels, options = [], [], {} specs, labels, options = [], [], {}
mode = 'modified' mode = 'modified'
elif i + 1 < argc and arg in [ '-r', '--revision' ]: elif i + 1 < argc and arg in ['-r', '--revision']:
# specified revision # specified revision
i += 1 i += 1
revs.append((args[i], encoding)) revs.append((args[i], encoding))
elif arg in [ '-s', '--separate' ]: elif arg in ['-s', '--separate']:
funcs[mode](specs, labels, options) funcs[mode](specs, labels, options)
specs, labels, options = [], [], {} specs, labels, options = [], [], {}
# open items in separate tabs # open items in separate tabs
mode = 'separate' mode = 'separate'
elif arg in [ '-t', '--tab' ]: elif arg in ['-t', '--tab']:
funcs[mode](specs, labels, options) funcs[mode](specs, labels, options)
specs, labels, options = [], [], {} specs, labels, options = [], [], {}
# start a new tab # start a new tab
mode = 'single' mode = 'single'
elif i + 1 < argc and arg in [ '-V', '--vcs' ]: elif i + 1 < argc and arg in ['-V', '--vcs']:
i += 1 i += 1
diff.prefs.setString('vcs_search_order', args[i]) diff.prefs.setString('vcs_search_order', args[i])
diff.preferences_updated() diff.preferences_updated()
elif arg in [ '-b', '--ignore-space-change' ]: elif arg in ['-b', '--ignore-space-change']:
diff.prefs.setBool('display_ignore_whitespace_changes', True) diff.prefs.setBool('display_ignore_whitespace_changes', True)
diff.prefs.setBool('align_ignore_whitespace_changes', True) diff.prefs.setBool('align_ignore_whitespace_changes', True)
diff.preferences_updated() diff.preferences_updated()
elif arg in [ '-B', '--ignore-blank-lines' ]: elif arg in ['-B', '--ignore-blank-lines']:
diff.prefs.setBool('display_ignore_blanklines', True) diff.prefs.setBool('display_ignore_blanklines', True)
diff.prefs.setBool('align_ignore_blanklines', True) diff.prefs.setBool('align_ignore_blanklines', True)
diff.preferences_updated() diff.preferences_updated()
elif arg in [ '-E', '--ignore-end-of-line' ]: elif arg in ['-E', '--ignore-end-of-line']:
diff.prefs.setBool('display_ignore_endofline', True) diff.prefs.setBool('display_ignore_endofline', True)
diff.prefs.setBool('align_ignore_endofline', True) diff.prefs.setBool('align_ignore_endofline', True)
diff.preferences_updated() diff.preferences_updated()
elif arg in [ '-i', '--ignore-case' ]: elif arg in ['-i', '--ignore-case']:
diff.prefs.setBool('display_ignore_case', True) diff.prefs.setBool('display_ignore_case', True)
diff.prefs.setBool('align_ignore_case', True) diff.prefs.setBool('align_ignore_case', True)
diff.preferences_updated() diff.preferences_updated()
elif arg in [ '-w', '--ignore-all-space' ]: elif arg in ['-w', '--ignore-all-space']:
diff.prefs.setBool('display_ignore_whitespace', True) diff.prefs.setBool('display_ignore_whitespace', True)
diff.prefs.setBool('align_ignore_whitespace', True) diff.prefs.setBool('align_ignore_whitespace', True)
diff.preferences_updated() diff.preferences_updated()
@ -1828,8 +1825,8 @@ Display Options:
revs = [] revs = []
had_specs = True had_specs = True
i += 1 i += 1
if mode in [ 'modified', 'commit' ] and len(specs) == 0: if mode in ['modified', 'commit'] and len(specs) == 0:
specs.append((os.curdir, [ (None, encoding) ])) specs.append((os.curdir, [(None, encoding)]))
had_specs = True had_specs = True
funcs[mode](specs, labels, options) funcs[mode](specs, labels, options)

View File

@ -23,16 +23,13 @@ import os
import shlex import shlex
import sys import sys
# pylint: disable=wrong-import-position from diffuse import constants
from diffuse import utils
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk # noqa: E402
# pylint: enable=wrong-import-position
# pylint: disable-next=no-name-in-module
from diffuse import constants
from diffuse import utils
# class to store preferences and construct a dialogue for manipulating them # class to store preferences and construct a dialogue for manipulating them
class Preferences: class Preferences:
@ -52,7 +49,7 @@ class Preferences:
else: else:
svk_bin = 'svk' svk_bin = 'svk'
auto_detect_codecs = [ 'utf_8', 'utf_16', 'latin_1' ] auto_detect_codecs = ['utf_8', 'utf_16', 'latin_1']
e = utils.norm_encoding(sys.getfilesystemencoding()) e = utils.norm_encoding(sys.getfilesystemencoding())
if e not in auto_detect_codecs: if e not in auto_detect_codecs:
# insert after UTF-8 as the default encoding may prevent UTF-8 from # insert after UTF-8 as the default encoding may prevent UTF-8 from
@ -75,50 +72,53 @@ class Preferences:
# [ 'String', name, default, label ] # [ 'String', name, default, label ]
# [ 'File', name, default, label ] # [ 'File', name, default, label ]
# [ 'Font', name, default, label ] # [ 'Font', name, default, label ]
# pylint: disable=line-too-long
self.template = [ self.template = [
'FolderSet', 'FolderSet',
_('Display'), _('Display'),
[ 'List', [
[ 'Font', 'display_font', 'Monospace 10', _('Font') ], 'List',
[ 'Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024 ], ['Font', 'display_font', 'Monospace 10', _('Font')],
[ 'Boolean', 'display_show_right_margin', True, _('Show right margin') ], ['Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024],
[ 'Integer', 'display_right_margin', 80, _('Right margin'), 1, 8192 ], ['Boolean', 'display_show_right_margin', True, _('Show right margin')],
[ 'Boolean', 'display_show_line_numbers', True, _('Show line numbers') ], ['Integer', 'display_right_margin', 80, _('Right margin'), 1, 8192],
[ 'Boolean', 'display_show_whitespace', False, _('Show white space characters') ], ['Boolean', 'display_show_line_numbers', True, _('Show line numbers')],
[ 'Boolean', 'display_ignore_case', False, _('Ignore case differences') ], ['Boolean', 'display_show_whitespace', False, _('Show white space characters')],
[ 'Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences') ], ['Boolean', 'display_ignore_case', False, _('Ignore case differences')],
[ 'Boolean', 'display_ignore_whitespace_changes', False, _('Ignore changes to white space') ], ['Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences')], # noqa: E501
[ 'Boolean', 'display_ignore_blanklines', False, _('Ignore blank line differences') ], ['Boolean', 'display_ignore_whitespace_changes', False, _('Ignore changes to white space')], # noqa: E501
[ 'Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences') ] ['Boolean', 'display_ignore_blanklines', False, _('Ignore blank line differences')],
['Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences')]
], ],
_('Alignment'), _('Alignment'),
[ 'List', [
[ 'Boolean', 'align_ignore_case', False, _('Ignore case') ], 'List',
[ 'Boolean', 'align_ignore_whitespace', True, _('Ignore white space') ], ['Boolean', 'align_ignore_case', False, _('Ignore case')],
[ 'Boolean', 'align_ignore_whitespace_changes', False, _('Ignore changes to white space') ], ['Boolean', 'align_ignore_whitespace', True, _('Ignore white space')],
[ 'Boolean', 'align_ignore_blanklines', False, _('Ignore blank lines') ], ['Boolean', 'align_ignore_whitespace_changes', False, _('Ignore changes to white space')], # noqa: E501
[ 'Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters') ] ['Boolean', 'align_ignore_blanklines', False, _('Ignore blank lines')],
['Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters')]
], ],
_('Editor'), _('Editor'),
[ 'List', [
[ 'Boolean', 'editor_auto_indent', True, _('Auto indent') ], 'List',
[ 'Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces') ], ['Boolean', 'editor_auto_indent', True, _('Auto indent')],
[ 'Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024 ] ['Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces')],
['Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024]
], ],
_('Tabs'), _('Tabs'),
[ 'List', [
[ 'Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16 ], 'List',
[ 'Boolean', 'tabs_always_show', False, _('Always show the tab bar') ], ['Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16],
[ 'Boolean', 'tabs_warn_before_quit', True, _('Warn me when closing a tab will quit %s') % constants.APP_NAME ] ['Boolean', 'tabs_always_show', False, _('Always show the tab bar')],
['Boolean', 'tabs_warn_before_quit', True, _('Warn me when closing a tab will quit %s') % constants.APP_NAME] # noqa: E501
], ],
_('Regional Settings'), _('Regional Settings'),
[ 'List', [
[ 'Encoding', 'encoding_default_codec', sys.getfilesystemencoding(), _('Default codec') ], 'List',
[ 'String', 'encoding_auto_detect_codecs', ' '.join(auto_detect_codecs), _('Order of codecs used to identify encoding') ] ['Encoding', 'encoding_default_codec', sys.getfilesystemencoding(), _('Default codec')], # noqa: E501
['String', 'encoding_auto_detect_codecs', ' '.join(auto_detect_codecs), _('Order of codecs used to identify encoding')] # noqa: E501
], ],
] ]
# pylint: disable=line-too-long
# conditions used to determine if a preference should be greyed out # conditions used to determine if a preference should be greyed out
self.disable_when = { self.disable_when = {
@ -138,14 +138,13 @@ class Preferences:
root += '\\' root += '\\'
self.template.extend([ self.template.extend([
_('Cygwin'), _('Cygwin'),
[ 'List', ['List',
[ 'File', 'cygwin_root', os.path.join(root, 'cygwin'), _('Root directory') ], ['File', 'cygwin_root', os.path.join(root, 'cygwin'), _('Root directory')],
[ 'String', 'cygwin_cygdrive_prefix', '/cygdrive', _('Cygdrive prefix') ] ['String', 'cygwin_cygdrive_prefix', '/cygdrive', _('Cygdrive prefix')]]
]
]) ])
# create template for Version Control options # create template for Version Control options
vcs = [ ('bzr', 'Bazaar', 'bzr'), vcs = [('bzr', 'Bazaar', 'bzr'),
('cvs', 'CVS', 'cvs'), ('cvs', 'CVS', 'cvs'),
('darcs', 'Darcs', 'darcs'), ('darcs', 'Darcs', 'darcs'),
('git', 'Git', 'git'), ('git', 'Git', 'git'),
@ -153,7 +152,7 @@ class Preferences:
('mtn', 'Monotone', 'mtn'), ('mtn', 'Monotone', 'mtn'),
('rcs', 'RCS', None), ('rcs', 'RCS', None),
('svn', 'Subversion', 'svn'), ('svn', 'Subversion', 'svn'),
('svk', 'SVK', svk_bin) ] ('svk', 'SVK', svk_bin)]
vcs_template = [ vcs_template = [
'List', [ 'List', [
@ -163,15 +162,15 @@ class Preferences:
_('Version control system search order') _('Version control system search order')
] ]
] ]
vcs_folders_template = [ 'FolderSet' ] vcs_folders_template = ['FolderSet']
for key, name, cmd in vcs: for key, name, cmd in vcs:
temp = [ 'List' ] temp = ['List']
if key == 'rcs': if key == 'rcs':
# RCS uses multiple commands # RCS uses multiple commands
temp.extend([ [ 'File', key + '_bin_co', 'co', _('"co" command') ], temp.extend([['File', key + '_bin_co', 'co', _('"co" command')],
[ 'File', key + '_bin_rlog', 'rlog', _('"rlog" command') ] ]) ['File', key + '_bin_rlog', 'rlog', _('"rlog" command')]])
else: else:
temp.extend([ [ 'File', key + '_bin', cmd, _('Command') ] ]) temp.extend([['File', key + '_bin', cmd, _('Command')]])
if utils.isWindows(): if utils.isWindows():
temp.append([ temp.append([
'Boolean', 'Boolean',
@ -186,10 +185,10 @@ class Preferences:
False, False,
_('Update paths for Cygwin') _('Update paths for Cygwin')
]) ])
vcs_folders_template.extend([ name, temp ]) vcs_folders_template.extend([name, temp])
vcs_template.append(vcs_folders_template) vcs_template.append(vcs_folders_template)
self.template.extend([ _('Version Control'), vcs_template ]) self.template.extend([_('Version Control'), vcs_template])
self._initFromTemplate(self.template) self._initFromTemplate(self.template)
self.default_bool_prefs = self.bool_prefs.copy() self.default_bool_prefs = self.bool_prefs.copy()
self.default_int_prefs = self.int_prefs.copy() self.default_int_prefs = self.int_prefs.copy()
@ -208,7 +207,9 @@ class Preferences:
if len(a) == 2 and p in self.bool_prefs: if len(a) == 2 and p in self.bool_prefs:
self.bool_prefs[p] = (a[1] == 'True') self.bool_prefs[p] = (a[1] == 'True')
elif len(a) == 2 and p in self.int_prefs: elif len(a) == 2 and p in self.int_prefs:
self.int_prefs[p] = max(self.int_prefs_min[p], min(int(a[1]), self.int_prefs_max[p])) self.int_prefs[p] = max(
self.int_prefs_min[p],
min(int(a[1]), self.int_prefs_max[p]))
elif len(a) == 2 and p in self.string_prefs: elif len(a) == 2 and p in self.string_prefs:
self.string_prefs[p] = a[1] self.string_prefs[p] = a[1]
else: else:
@ -238,7 +239,7 @@ class Preferences:
self.int_prefs[template[1]] = template[2] self.int_prefs[template[1]] = template[2]
self.int_prefs_min[template[1]] = template[4] self.int_prefs_min[template[1]] = template[4]
self.int_prefs_max[template[1]] = template[5] self.int_prefs_max[template[1]] = template[5]
elif template[0] in [ 'String', 'File', 'Font', 'Encoding' ]: elif template[0] in ['String', 'File', 'Font', 'Encoding']:
self.string_prefs[template[1]] = template[2] self.string_prefs[template[1]] = template[2]
# callback used when a preference is toggled # callback used when a preference is toggled
@ -263,10 +264,10 @@ class Preferences:
p, t = v p, t = v
if widgets[p].get_active() == t: if widgets[p].get_active() == t:
widgets[k].set_sensitive(False) widgets[k].set_sensitive(False)
dialog.vbox.add(w) # pylint: disable=no-member dialog.vbox.add(w)
w.show() w.show()
accept = (dialog.run() == Gtk.ResponseType.OK) # pylint: disable=no-member accept = (dialog.run() == Gtk.ResponseType.OK)
if accept: if accept:
for k in self.bool_prefs: for k in self.bool_prefs:
self.bool_prefs[k] = widgets[k].get_active() self.bool_prefs[k] = widgets[k].get_active()
@ -288,8 +289,7 @@ class Preferences:
ss.append(f'{k} "{v_escaped}"\n') ss.append(f'{k} "{v_escaped}"\n')
ss.sort() ss.sort()
with open(self.path, 'w', encoding='utf-8') as f: with open(self.path, 'w', encoding='utf-8') as f:
# pylint: disable-next=line-too-long f.write(f'# This prefs file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n') # noqa: E501
f.write(f'# This prefs file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n')
for s in ss: for s in ss:
f.write(s) f.write(s)
except IOError: except IOError:
@ -337,7 +337,7 @@ class Preferences:
label.set_yalign(0.5) label.set_yalign(0.5)
table.attach(label, 0, i, 1, 1) table.attach(label, 0, i, 1, 1)
label.show() label.show()
if tpl[0] in [ 'Font', 'Integer' ]: if tpl[0] in ['Font', 'Integer']:
entry = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) entry = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
if tpl[0] == 'Font': if tpl[0] == 'Font':
button = _FontButton() button = _FontButton()
@ -393,12 +393,12 @@ class Preferences:
# attempt to convert a string to unicode from an unknown encoding # attempt to convert a string to unicode from an unknown encoding
def convertToUnicode(self, s): def convertToUnicode(self, s):
# a BOM is required for autodetecting UTF16 and UTF32 # a BOM is required for autodetecting UTF16 and UTF32
magic = { 'utf16': [ codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE ], magic = {'utf16': [codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE],
'utf32': [ codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE ] } 'utf32': [codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE]}
for encoding in self._getDefaultEncodings(): for encoding in self._getDefaultEncodings():
try: try:
encoding = encoding.lower().replace('-', '').replace('_', '') encoding = encoding.lower().replace('-', '').replace('_', '')
for m in magic.get(encoding, [ b'' ]): for m in magic.get(encoding, [b'']):
if s.startswith(m): if s.startswith(m):
break break
else: else:
@ -406,7 +406,7 @@ class Preferences:
return str(s, encoding=encoding), encoding return str(s, encoding=encoding), encoding
except (UnicodeDecodeError, LookupError): except (UnicodeDecodeError, LookupError):
pass pass
return ''.join([ chr(ord(c)) for c in s ]), None return ''.join([chr(ord(c)) for c in s]), None
# cygwin and native applications can be used on windows, use this method # cygwin and native applications can be used on windows, use this method
# to convert a path to the usual form expected on sys.platform # to convert a path to the usual form expected on sys.platform
@ -415,37 +415,37 @@ class Preferences:
# treat as a cygwin path # treat as a cygwin path
s = s.replace(os.sep, '/') s = s.replace(os.sep, '/')
# convert to a Windows native style path # convert to a Windows native style path
p = [ a for a in s.split('/') if a != '' ] p = [a for a in s.split('/') if a != '']
if s.startswith('//'): if s.startswith('//'):
p[:0] = [ '', '' ] p[:0] = ['', '']
elif s.startswith('/'): elif s.startswith('/'):
pr = [ a for a in self.getString('cygwin_cygdrive_prefix').split('/') if a != '' ] pr = [a for a in self.getString('cygwin_cygdrive_prefix').split('/') if a != '']
n = len(pr) n = len(pr)
if len(p) > n and len(p[n]) == 1 and p[:n] == pr: if len(p) > n and len(p[n]) == 1 and p[:n] == pr:
# path starts with cygdrive prefix # path starts with cygdrive prefix
p[:n + 1] = [ p[n] + ':' ] p[:n + 1] = [p[n] + ':']
else: else:
# full path # full path
p[:0] = [ a for a in self.getString('cygwin_root').split(os.sep) if a != '' ] p[:0] = [a for a in self.getString('cygwin_root').split(os.sep) if a != '']
# add trailing slash # add trailing slash
if p[-1] != '' and s.endswith('/'): if p[-1] != '' and s.endswith('/'):
p.append('') p.append('')
s = os.sep.join(p) s = os.sep.join(p)
return s return s
# adaptor class to allow a Gtk.FontButton to be read like a Gtk.Entry # adaptor class to allow a Gtk.FontButton to be read like a Gtk.Entry
class _FontButton(Gtk.FontButton): class _FontButton(Gtk.FontButton):
def __init__(self): def __init__(self):
Gtk.FontButton.__init__(self) Gtk.FontButton.__init__(self)
def get_text(self): def get_text(self):
# pylint: disable=no-member
return self.get_font_name() return self.get_font_name()
# text entry widget with a button to help pick file names # text entry widget with a button to help pick file names
class _FileEntry(Gtk.Box): class _FileEntry(Gtk.Box):
def __init__(self, parent, title): def __init__(self, parent, title):
# pylint: disable=no-member
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.toplevel = parent self.toplevel = parent
self.title = title self.title = title
@ -463,7 +463,6 @@ class _FileEntry(Gtk.Box):
# action performed when the pick file button is pressed # action performed when the pick file button is pressed
def chooseFile(self, widget): def chooseFile(self, widget):
# pylint: disable=no-member
dialog = Gtk.FileChooserDialog( dialog = Gtk.FileChooserDialog(
self.title, self.title,
self.toplevel, self.toplevel,

View File

@ -29,13 +29,12 @@ import os
import re import re
import shlex import shlex
# pylint: disable=wrong-import-position from diffuse import utils
import gi import gi
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
from gi.repository import Gdk from gi.repository import Gdk # noqa: E402
# pylint: enable=wrong-import-position
from diffuse import utils
class Resources: class Resources:
def __init__(self): def __init__(self):
@ -142,8 +141,8 @@ class Resources:
set_binding('line_mode', 'next_difference', 'n') set_binding('line_mode', 'next_difference', 'n')
set_binding('line_mode', 'last_difference', 'Ctrl+End') set_binding('line_mode', 'last_difference', 'Ctrl+End')
set_binding('line_mode', 'last_difference', 'Shift+N') set_binding('line_mode', 'last_difference', 'Shift+N')
#set_binding('line_mode', 'copy_selection_right', 'Shift+L') # set_binding('line_mode', 'copy_selection_right', 'Shift+L')
#set_binding('line_mode', 'copy_selection_left', 'Shift+H') # set_binding('line_mode', 'copy_selection_left', 'Shift+H')
set_binding('line_mode', 'copy_left_into_selection', 'Shift+L') set_binding('line_mode', 'copy_left_into_selection', 'Shift+L')
set_binding('line_mode', 'copy_right_into_selection', 'Shift+H') set_binding('line_mode', 'copy_right_into_selection', 'Shift+H')
set_binding('line_mode', 'merge_from_left_then_right', 'm') set_binding('line_mode', 'merge_from_left_then_right', 'm')
@ -170,31 +169,33 @@ class Resources:
# default colours # default colours
self.colours = { self.colours = {
'alignment' : _Colour(1.0, 1.0, 0.0), 'alignment': _Colour(1.0, 1.0, 0.0),
'character_selection' : _Colour(0.7, 0.7, 1.0), 'character_selection': _Colour(0.7, 0.7, 1.0),
'cursor' : _Colour(0.0, 0.0, 0.0), 'cursor': _Colour(0.0, 0.0, 0.0),
'difference_1' : _Colour(1.0, 0.625, 0.625), 'difference_1': _Colour(1.0, 0.625, 0.625),
'difference_2' : _Colour(0.85, 0.625, 0.775), 'difference_2': _Colour(0.85, 0.625, 0.775),
'difference_3' : _Colour(0.85, 0.775, 0.625), 'difference_3': _Colour(0.85, 0.775, 0.625),
'hatch' : _Colour(0.8, 0.8, 0.8), 'hatch': _Colour(0.8, 0.8, 0.8),
'line_number' : _Colour(0.0, 0.0, 0.0), 'line_number': _Colour(0.0, 0.0, 0.0),
'line_number_background' : _Colour(0.75, 0.75, 0.75), 'line_number_background': _Colour(0.75, 0.75, 0.75),
'line_selection' : _Colour(0.7, 0.7, 1.0), 'line_selection': _Colour(0.7, 0.7, 1.0),
'map_background' : _Colour(0.6, 0.6, 0.6), 'map_background': _Colour(0.6, 0.6, 0.6),
'margin' : _Colour(0.8, 0.8, 0.8), 'margin': _Colour(0.8, 0.8, 0.8),
'edited' : _Colour(0.5, 1.0, 0.5), 'edited': _Colour(0.5, 1.0, 0.5),
'preedit' : _Colour(0.0, 0.0, 0.0), 'preedit': _Colour(0.0, 0.0, 0.0),
'text' : _Colour(0.0, 0.0, 0.0), 'text': _Colour(0.0, 0.0, 0.0),
'text_background' : _Colour(1.0, 1.0, 1.0) } 'text_background': _Colour(1.0, 1.0, 1.0)
}
# default floats # default floats
self.floats = { self.floats = {
'alignment_opacity' : 1.0, 'alignment_opacity': 1.0,
'character_difference_opacity' : 0.4, 'character_difference_opacity': 0.4,
'character_selection_opacity' : 0.4, 'character_selection_opacity': 0.4,
'edited_opacity' : 0.4, 'edited_opacity': 0.4,
'line_difference_opacity' : 0.3, 'line_difference_opacity': 0.3,
'line_selection_opacity' : 0.4 } 'line_selection_opacity': 0.4
}
# default strings # default strings
self.strings = {} self.strings = {}
@ -222,7 +223,7 @@ class Resources:
elif token == 'Ctrl': elif token == 'Ctrl':
modifiers |= Gdk.ModifierType.CONTROL_MASK modifiers |= Gdk.ModifierType.CONTROL_MASK
elif token == 'Alt': elif token == 'Alt':
modifiers |= Gdk.ModifierType.MOD1_MASK # pylint: disable=no-member modifiers |= Gdk.ModifierType.MOD1_MASK
elif len(token) == 0 or token[0] == '_': elif len(token) == 0 or token[0] == '_':
raise ValueError() raise ValueError()
else: else:
@ -265,7 +266,7 @@ class Resources:
def getKeyBindings(self, ctx, s): def getKeyBindings(self, ctx, s):
try: try:
return [ t for c, t in self.keybindings[(ctx, s)].keys() ] return [t for c, t in self.keybindings[(ctx, s)].keys()]
except KeyError: except KeyError:
return [] return []
@ -351,7 +352,7 @@ class Resources:
path = os.path.join(utils.globEscape(os.path.dirname(file_name)), path) path = os.path.join(utils.globEscape(os.path.dirname(file_name)), path)
paths = glob.glob(path) paths = glob.glob(path)
if len(paths) == 0: if len(paths) == 0:
paths = [ path ] paths = [path]
for path in paths: for path in paths:
# convert to absolute path so the location of # convert to absolute path so the location of
# any processing errors are reported with # any processing errors are reported with
@ -363,7 +364,7 @@ class Resources:
self.setKeyBinding(args[1], args[2], args[3]) self.setKeyBinding(args[1], args[2], args[3])
# eg. set the regular background colour to white # eg. set the regular background colour to white
# colour text_background 1.0 1.0 1.0 # colour text_background 1.0 1.0 1.0
elif args[0] in [ 'colour', 'color' ] and len(args) == 5: elif args[0] in ['colour', 'color'] and len(args) == 5:
self.colours[args[1]] = _Colour(float(args[2]), float(args[3]), float(args[4])) self.colours[args[1]] = _Colour(float(args[2]), float(args[3]), float(args[4]))
# eg. set opacity of the line_selection colour # eg. set opacity of the line_selection colour
# float line_selection_opacity 0.4 # float line_selection_opacity 0.4
@ -459,11 +460,11 @@ class Resources:
self.syntax_magic_patterns[key] = re.compile(args[2], flags) self.syntax_magic_patterns[key] = re.compile(args[2], flags)
else: else:
raise ValueError() raise ValueError()
# pylint: disable-next=bare-except # except ValueError:
except: # Grr... the 're' module throws weird errors except: # noqa: E722 # Grr... the 're' module throws weird errors
#except ValueError:
utils.logError(_(f'Error processing line {i + 1} of {file_name}.')) utils.logError(_(f'Error processing line {i + 1} of {file_name}.'))
# colour resources # colour resources
class _Colour: class _Colour:
def __init__(self, r, g, b, a=1.0): def __init__(self, r, g, b, a=1.0):
@ -489,6 +490,7 @@ class _Colour:
def over(self, other): def over(self, other):
return self + other * (1 - self.alpha) return self + other * (1 - self.alpha)
# class to build and run a finite state machine for identifying syntax tokens # class to build and run a finite state machine for identifying syntax tokens
class _SyntaxParser: class _SyntaxParser:
# create a new state machine that begins in initial_state and classifies # create a new state machine that begins in initial_state and classifies
@ -502,7 +504,7 @@ class _SyntaxParser:
# mappings from a state to a list of (pattern, token_type, next_state) # mappings from a state to a list of (pattern, token_type, next_state)
# tuples indicating the new state for the state machine when 'pattern' # tuples indicating the new state for the state machine when 'pattern'
# is matched and how to classify the matched characters # is matched and how to classify the matched characters
self.transitions_lookup = { initial_state : [] } self.transitions_lookup = {initial_state: []}
# Adds a new edge to the finite state machine from prev_state to # Adds a new edge to the finite state machine from prev_state to
# next_state. Characters will be identified as token_type when pattern is # next_state. Characters will be identified as token_type when pattern is
@ -536,4 +538,5 @@ class _SyntaxParser:
start = end start = end
return state_name, blocks return state_name, blocks
theResources = Resources() theResources = Resources()

View File

@ -23,14 +23,12 @@ import locale
import subprocess import subprocess
import traceback import traceback
# pylint: disable=wrong-import-position from diffuse import constants
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk # noqa: E402
# pylint: enable=wrong-import-position
# pylint: disable-next=no-name-in-module
from diffuse import constants
# convenience class for displaying a message dialogue # convenience class for displaying a message dialogue
class MessageDialog(Gtk.MessageDialog): class MessageDialog(Gtk.MessageDialog):
@ -48,6 +46,7 @@ class MessageDialog(Gtk.MessageDialog):
text=s) text=s)
self.set_title(constants.APP_NAME) self.set_title(constants.APP_NAME)
# widget to help pick an encoding # widget to help pick an encoding
class EncodingMenu(Gtk.Box): class EncodingMenu(Gtk.Box):
def __init__(self, prefs, autodetect=False): def __init__(self, prefs, autodetect=False):
@ -59,7 +58,7 @@ class EncodingMenu(Gtk.Box):
if autodetect: if autodetect:
self.encodings.insert(0, None) self.encodings.insert(0, None)
combobox.prepend_text(_('Auto Detect')) combobox.prepend_text(_('Auto Detect'))
self.pack_start(combobox, False, False, 0) # pylint: disable=no-member self.pack_start(combobox, False, False, 0)
combobox.show() combobox.show()
def set_text(self, encoding): def set_text(self, encoding):
@ -71,31 +70,37 @@ class EncodingMenu(Gtk.Box):
i = self.combobox.get_active() i = self.combobox.get_active()
return self.encodings[i] if i >= 0 else None return self.encodings[i] if i >= 0 else None
# platform test # platform test
def isWindows(): def isWindows():
return os.name == 'nt' return os.name == 'nt'
def _logPrintOutput(msg): def _logPrintOutput(msg):
if constants.log_print_output: if constants.log_print_output:
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
if constants.log_print_stack: if constants.log_print_stack:
traceback.print_stack() traceback.print_stack()
# convenience function to display debug messages # convenience function to display debug messages
def logDebug(msg): def logDebug(msg):
_logPrintOutput(f'DEBUG: {msg}') _logPrintOutput(f'DEBUG: {msg}')
# report error messages # report error messages
def logError(msg): def logError(msg):
_logPrintOutput(f'ERROR: {msg}') _logPrintOutput(f'ERROR: {msg}')
# report error messages and show dialog # report error messages and show dialog
def logErrorAndDialog(msg,parent=None): def logErrorAndDialog(msg, parent=None):
logError(msg) logError(msg)
dialog = MessageDialog(parent, Gtk.MessageType.ERROR, msg) dialog = MessageDialog(parent, Gtk.MessageType.ERROR, msg)
dialog.run() # pylint: disable=no-member dialog.run()
dialog.destroy() dialog.destroy()
# create nested subdirectories and return the complete path # create nested subdirectories and return the complete path
def make_subdirs(p, ss): def make_subdirs(p, ss):
for s in ss: for s in ss:
@ -107,6 +112,7 @@ def make_subdirs(p, ss):
pass pass
return p return p
# returns the Windows drive or share from a from an absolute path # returns the Windows drive or share from a from an absolute path
def _drive_from_path(path): def _drive_from_path(path):
d = path.split(os.sep) d = path.split(os.sep)
@ -114,20 +120,22 @@ def _drive_from_path(path):
return os.path.join(d[:4]) return os.path.join(d[:4])
return d[0] return d[0]
# constructs a relative path from 'a' to 'b', both should be absolute paths # constructs a relative path from 'a' to 'b', both should be absolute paths
def relpath(a, b): def relpath(a, b):
if isWindows(): if isWindows():
if _drive_from_path(a) != _drive_from_path(b): if _drive_from_path(a) != _drive_from_path(b):
return b return b
c1 = [ c for c in a.split(os.sep) if c != '' ] c1 = [c for c in a.split(os.sep) if c != '']
c2 = [ c for c in b.split(os.sep) if c != '' ] c2 = [c for c in b.split(os.sep) if c != '']
i, n = 0, len(c1) i, n = 0, len(c1)
while i < n and i < len(c2) and c1[i] == c2[i]: while i < n and i < len(c2) and c1[i] == c2[i]:
i += 1 i += 1
r = (n - i) * [ os.pardir ] r = (n - i) * [os.pardir]
r.extend(c2[i:]) r.extend(c2[i:])
return os.sep.join(r) return os.sep.join(r)
# helper function prevent files from being confused with command line options # helper function prevent files from being confused with command line options
# by prepending './' to the basename # by prepending './' to the basename
def safeRelativePath(abspath1, name, prefs, cygwin_pref): def safeRelativePath(abspath1, name, prefs, cygwin_pref):
@ -139,24 +147,28 @@ def safeRelativePath(abspath1, name, prefs, cygwin_pref):
s = s.replace('/', '\\') s = s.replace('/', '\\')
return s return s
# escape arguments for use with bash # escape arguments for use with bash
def _bash_escape(s): def _bash_escape(s):
return "'" + s.replace("'", "'\\''") + "'" return "'" + s.replace("'", "'\\''") + "'"
def _use_flatpak(): def _use_flatpak():
return constants.use_flatpak return constants.use_flatpak
# use popen to read the output of a command # use popen to read the output of a command
def popenRead(dn, cmd, prefs, bash_pref, success_results=None): def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
if success_results is None: if success_results is None:
success_results = [ 0 ] success_results = [0]
if isWindows() and prefs.getBool(bash_pref): if isWindows() and prefs.getBool(bash_pref):
# launch the command from a bash shell is requested # launch the command from a bash shell is requested
cmd = [ cmd = [
prefs.convertToNativePath('/bin/bash.exe'), prefs.convertToNativePath('/bin/bash.exe'),
'-l', '-l',
'-c', '-c',
f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}" ] f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}"
]
dn = None dn = None
# use subprocess.Popen to retrieve the file contents # use subprocess.Popen to retrieve the file contents
if isWindows(): if isWindows():
@ -166,9 +178,8 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
else: else:
info = None info = None
if _use_flatpak(): if _use_flatpak():
cmd = [ 'flatpak-spawn', '--host' ] + cmd cmd = ['flatpak-spawn', '--host'] + cmd
with ( with (subprocess.Popen(
subprocess.Popen(
cmd, cmd,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -186,6 +197,7 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
raise IOError('Command failed.') raise IOError('Command failed.')
return s return s
# returns the number of characters in the string excluding any line ending # returns the number of characters in the string excluding any line ending
# characters # characters
def len_minus_line_ending(s): def len_minus_line_ending(s):
@ -198,28 +210,34 @@ def len_minus_line_ending(s):
n -= 1 n -= 1
return n return n
# returns the string without the line ending characters # returns the string without the line ending characters
def strip_eol(s): def strip_eol(s):
if s: if s:
s = s[:len_minus_line_ending(s)] s = s[:len_minus_line_ending(s)]
return s return s
# returns the list of strings without line ending characters # returns the list of strings without line ending characters
def _strip_eols(ss): def _strip_eols(ss):
return [ strip_eol(s) for s in ss ] return [strip_eol(s) for s in ss]
# use popen to read the output of a command # use popen to read the output of a command
def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None): def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None):
return _strip_eols(splitlines(popenRead( return _strip_eols(splitlines(popenRead(
dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore'))) dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore')))
def readconfiglines(fd): def readconfiglines(fd):
return fd.read().replace('\r', '').split('\n') return fd.read().replace('\r', '').split('\n')
# escape special glob characters # escape special glob characters
def globEscape(s): def globEscape(s):
m = { c: f'[{c}]' for c in '[]?*' } m = {c: f'[{c}]' for c in '[]?*'}
return ''.join([ m.get(c, c) for c in s ]) return ''.join([m.get(c, c) for c in s])
# split string into lines based upon DOS, Mac, and Unix line endings # split string into lines based upon DOS, Mac, and Unix line endings
def splitlines(text: str) -> list[str]: def splitlines(text: str) -> list[str]:
@ -249,21 +267,25 @@ def splitlines(text: str) -> list[str]:
i = j i = j
return ss return ss
# also recognize old Mac OS line endings # also recognize old Mac OS line endings
def readlines(fd): def readlines(fd):
return _strip_eols(splitlines(fd.read())) return _strip_eols(splitlines(fd.read()))
# map an encoding name to its standard form # map an encoding name to its standard form
def norm_encoding(e): def norm_encoding(e):
if e is not None: if e is not None:
return e.replace('-', '_').lower() return e.replace('-', '_').lower()
return None return None
def null_to_empty(s): def null_to_empty(s):
if s is None: if s is None:
s = '' s = ''
return s return s
# utility method to step advance an adjustment # utility method to step advance an adjustment
def step_adjustment(adj, delta): def step_adjustment(adj, delta):
v = adj.get_value() + delta v = adj.get_value() + delta

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Bazaar support # Bazaar support
class Bzr(VcsInterface): class Bzr(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
@ -30,13 +31,13 @@ class Bzr(VcsInterface):
left = name + '.OTHER' left = name + '.OTHER'
right = name + '.THIS' right = name + '.THIS'
if os.path.isfile(left) and os.path.isfile(right): if os.path.isfile(left) and os.path.isfile(right):
return [ (left, None), (name, None), (right, None) ] return [(left, None), (name, None), (right, None)]
# default case # default case
return [ (name, '-1'), (name, None) ] return [(name, '-1'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
# build command # build command
args = [ prefs.getString('bzr_bin'), 'log', '-v', '-r', rev ] args = [prefs.getString('bzr_bin'), 'log', '-v', '-r', rev]
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
for name in names: for name in names:
@ -62,7 +63,7 @@ class Bzr(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, rev) ] added[k] = [(None, None), (k, rev)]
elif s.startswith('modified:'): elif s.startswith('modified:'):
# modified files # modified files
while i < n and ss[i].startswith(' '): while i < n and ss[i].startswith(' '):
@ -73,7 +74,7 @@ class Bzr(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
modified[k] = [ (k, prev), (k, rev) ] modified[k] = [(k, prev), (k, rev)]
elif s.startswith('removed:'): elif s.startswith('removed:'):
# removed files # removed files
while i < n and ss[i].startswith(' '): while i < n and ss[i].startswith(' '):
@ -84,7 +85,7 @@ class Bzr(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
elif s.startswith('renamed:'): elif s.startswith('renamed:'):
# renamed files # renamed files
while i < n and ss[i].startswith(' '): while i < n and ss[i].startswith(' '):
@ -100,7 +101,7 @@ class Bzr(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
renamed[k1] = [ (k0, prev), (k1, rev) ] renamed[k1] = [(k0, prev), (k1, rev)]
# sort the results # sort the results
result, r = [], set() result, r = [], set()
for m in removed, added, modified, renamed: for m in removed, added, modified, renamed:
@ -113,7 +114,7 @@ class Bzr(VcsInterface):
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
# build command # build command
args = [ prefs.getString('bzr_bin'), 'status', '-SV' ] args = [prefs.getString('bzr_bin'), 'status', '-SV']
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
for name in names: for name in names:
@ -136,7 +137,7 @@ class Bzr(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
elif y == 'N': elif y == 'N':
# added # added
k = prefs.convertToNativePath(k) k = prefs.convertToNativePath(k)
@ -145,7 +146,7 @@ class Bzr(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, None) ] added[k] = [(None, None), (k, None)]
elif y == 'M': elif y == 'M':
# modified or merge conflict # modified or merge conflict
k = prefs.convertToNativePath(k) k = prefs.convertToNativePath(k)
@ -168,7 +169,7 @@ class Bzr(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
renamed[k1] = [ (k0, prev), (k1, None) ] renamed[k1] = [(k0, prev), (k1, None)]
# sort the results # sort the results
result, r = [], set() result, r = [], set()
for m in removed, added, modified, renamed: for m in removed, added, modified, renamed:

View File

@ -23,10 +23,11 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# CVS support # CVS support
class Cvs(VcsInterface): class Cvs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
return [ (name, 'BASE'), (name, None) ] return [(name, 'BASE'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
result = [] result = []
@ -45,14 +46,14 @@ class Cvs(VcsInterface):
k0 = None k0 = None
else: else:
k0 = k k0 = k
result.append([ (k0, prev), (k, rev) ]) result.append([(k0, prev), (k, rev)])
except ValueError: except ValueError:
utils.logError(_('Error parsing revision %s.') % (rev, )) utils.logError(_('Error parsing revision %s.') % (rev, ))
return result return result
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
# build command # build command
args = [ prefs.getString('cvs_bin'), '-nq', 'update', '-R' ] args = [prefs.getString('cvs_bin'), '-nq', 'update', '-R']
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
for name in names: for name in names:
@ -72,15 +73,15 @@ class Cvs(VcsInterface):
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if s[0] == 'R': if s[0] == 'R':
# removed # removed
modified[k] = [ (k, prev), (None, None) ] modified[k] = [(k, prev), (None, None)]
elif s[0] == 'A': elif s[0] == 'A':
# added # added
modified[k] = [ (None, None), (k, None) ] modified[k] = [(None, None), (k, None)]
else: else:
# modified # modified
modified[k] = [ (k, prev), (k, None) ] modified[k] = [(k, prev), (k, None)]
# sort the results # sort the results
return [ modified[k] for k in sorted(modified.keys()) ] return [modified[k] for k in sorted(modified.keys())]
def getRevision(self, prefs, name, rev): def getRevision(self, prefs, name, rev):
if rev == 'BASE' and not os.path.exists(name): if rev == 'BASE' and not os.path.exists(name):
@ -93,7 +94,8 @@ class Cvs(VcsInterface):
utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin') utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin')
], ],
prefs, prefs,
'cvs_bash'): 'cvs_bash'
):
if s.startswith(' Working revision:\t-'): if s.startswith(' Working revision:\t-'):
rev = s.split('\t')[1][1:] rev = s.split('\t')[1][1:]
return utils.popenRead( return utils.popenRead(

View File

@ -23,15 +23,16 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Darcs support # Darcs support
class Darcs(VcsInterface): class Darcs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
return [ (name, ''), (name, None) ] return [(name, ''), (name, None)]
def _getCommitTemplate(self, prefs, names, rev): def _getCommitTemplate(self, prefs, names, rev):
mods = (rev is None) mods = (rev is None)
# build command # build command
args = [ prefs.getString('darcs_bin') ] args = [prefs.getString('darcs_bin')]
if mods: if mods:
args.extend(['whatsnew', '-s']) args.extend(['whatsnew', '-s'])
else: else:
@ -84,7 +85,7 @@ class Darcs(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
elif x == 'A': elif x == 'A':
# added # added
k = prefs.convertToNativePath(s[2:]) k = prefs.convertToNativePath(s[2:])
@ -93,7 +94,7 @@ class Darcs(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, rev) ] added[k] = [(None, None), (k, rev)]
elif x == 'M': elif x == 'M':
# modified # modified
k = prefs.convertToNativePath(s[2:].split(' ')[0]) k = prefs.convertToNativePath(s[2:].split(' ')[0])
@ -103,7 +104,7 @@ class Darcs(VcsInterface):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if k not in renamed: if k not in renamed:
modified[k] = [ (k, prev), (k, rev) ] modified[k] = [(k, prev), (k, rev)]
elif x == ' ': elif x == ' ':
# renamed # renamed
k = s[1:].split(' -> ') k = s[1:].split(' -> ')
@ -117,7 +118,7 @@ class Darcs(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
renamed[k1] = [ (k0, prev), (k1, rev) ] renamed[k1] = [(k0, prev), (k1, rev)]
# sort the results # sort the results
result, r = [], set() result, r = [], set()
for m in added, modified, removed, renamed: for m in added, modified, removed, renamed:
@ -135,10 +136,10 @@ class Darcs(VcsInterface):
return self._getCommitTemplate(prefs, names, None) return self._getCommitTemplate(prefs, names, None)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs, name, rev):
args = [ prefs.getString('darcs_bin'), 'show', 'contents' ] args = [prefs.getString('darcs_bin'), 'show', 'contents']
try: try:
args.extend([ '-n', str(int(rev)) ]) args.extend(['-n', str(int(rev))])
except ValueError: except ValueError:
args.extend([ '-h', rev ]) args.extend(['-h', rev])
args.append(utils.safeRelativePath(self.root, name, prefs, 'darcs_cygwin')) args.append(utils.safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
return utils.popenRead(self.root, args, prefs, 'darcs_bash') return utils.popenRead(self.root, args, prefs, 'darcs_bash')

View File

@ -19,6 +19,7 @@
import os import os
class FolderSet: class FolderSet:
'''Utility class to help support Git and Monotone. '''Utility class to help support Git and Monotone.
Represents a set of files and folders of interest for "git status" or Represents a set of files and folders of interest for "git status" or

View File

@ -23,14 +23,15 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Git support # Git support
class Git(VcsInterface): class Git(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
return [ (name, 'HEAD'), (name, None) ] return [(name, 'HEAD'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
# build command # build command
args = [ prefs.getString('git_bin'), 'show', '--pretty=format:', '--name-status', rev ] args = [prefs.getString('git_bin'), 'show', '--pretty=format:', '--name-status', rev]
# build list of interesting files # build list of interesting files
pwd = os.path.abspath(os.curdir) pwd = os.path.abspath(os.curdir)
isabs = False isabs = False
@ -50,15 +51,15 @@ class Git(VcsInterface):
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if s[0] == 'D': if s[0] == 'D':
# removed # removed
modified[k] = [ (k, prev), (None, None) ] modified[k] = [(k, prev), (None, None)]
elif s[0] == 'A': elif s[0] == 'A':
# added # added
modified[k] = [ (None, None), (k, rev) ] modified[k] = [(None, None), (k, rev)]
else: else:
# modified # modified
modified[k] = [ (k, prev), (k, rev) ] modified[k] = [(k, prev), (k, rev)]
# sort the results # sort the results
return [ modified[k] for k in sorted(modified.keys()) ] return [modified[k] for k in sorted(modified.keys())]
def _extractPath(self, s, prefs): def _extractPath(self, s, prefs):
return os.path.join(self.root, prefs.convertToNativePath(s.strip())) return os.path.join(self.root, prefs.convertToNativePath(s.strip()))
@ -98,7 +99,7 @@ class Git(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
renamed[k1] = [ (k0, prev), (k1, None) ] renamed[k1] = [(k0, prev), (k1, None)]
elif x == 'U' or y == 'U' or (x == 'D' and y == 'D'): elif x == 'U' or y == 'U' or (x == 'D' and y == 'D'):
# merge conflict # merge conflict
k = self._extractPath(k, prefs) k = self._extractPath(k, prefs)
@ -106,9 +107,9 @@ class Git(VcsInterface):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if x == 'D': if x == 'D':
panes = [ (None, None) ] panes = [(None, None)]
else: else:
panes = [ (k, ':2') ] panes = [(k, ':2')]
panes.append((k, None)) panes.append((k, None))
if y == 'D': if y == 'D':
panes.append((None, None)) panes.append((None, None))
@ -124,9 +125,9 @@ class Git(VcsInterface):
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if x == 'A': if x == 'A':
# added # added
panes = [ (None, None) ] panes = [(None, None)]
else: else:
panes = [ (k, prev) ] panes = [(k, prev)]
# staged changes # staged changes
if x == 'D': if x == 'D':
panes.append((None, None)) panes.append((None, None))

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Mercurial support # Mercurial support
class Hg(VcsInterface): class Hg(VcsInterface):
def __init__(self, root): def __init__(self, root):
@ -34,7 +35,7 @@ class Hg(VcsInterface):
if self.working_rev is None: if self.working_rev is None:
ss = utils.popenReadLines( ss = utils.popenReadLines(
self.root, self.root,
[ prefs.getString('hg_bin'), 'id', '-i', '-t' ], [prefs.getString('hg_bin'), 'id', '-i', '-t'],
prefs, prefs,
'hg_bash') 'hg_bash')
if len(ss) != 1: if len(ss) != 1:
@ -49,11 +50,11 @@ class Hg(VcsInterface):
return f'p1({rev})' return f'p1({rev})'
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
return [ (name, self._getPreviousRevision(prefs, None)), (name, None) ] return [(name, self._getPreviousRevision(prefs, None)), (name, None)]
def _getCommitTemplate(self, prefs, names, cmd, rev): def _getCommitTemplate(self, prefs, names, cmd, rev):
# build command # build command
args = [ prefs.getString('hg_bin') ] args = [prefs.getString('hg_bin')]
args.extend(cmd) args.extend(cmd)
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
@ -74,25 +75,25 @@ class Hg(VcsInterface):
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
if s[0] == 'R': if s[0] == 'R':
# removed # removed
modified[k] = [ (k, prev), (None, None) ] modified[k] = [(k, prev), (None, None)]
elif s[0] == 'A': elif s[0] == 'A':
# added # added
modified[k] = [ (None, None), (k, rev) ] modified[k] = [(None, None), (k, rev)]
else: else:
# modified or merge conflict # modified or merge conflict
modified[k] = [ (k, prev), (k, rev) ] modified[k] = [(k, prev), (k, rev)]
# sort the results # sort the results
return [ modified[k] for k in sorted(modified.keys()) ] return [modified[k] for k in sorted(modified.keys())]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
return self._getCommitTemplate( return self._getCommitTemplate(
prefs, prefs,
names, names,
[ 'log', '--template', 'A\t{file_adds}\nM\t{file_mods}\nR\t{file_dels}\n', '-r', rev ], ['log', '--template', 'A\t{file_adds}\nM\t{file_mods}\nR\t{file_dels}\n', '-r', rev],
rev) rev)
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
return self._getCommitTemplate(prefs, names, [ 'status', '-q' ], None) return self._getCommitTemplate(prefs, names, ['status', '-q'], None)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs, name, rev):
return utils.popenRead( return utils.popenRead(

View File

@ -24,23 +24,24 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Monotone support # Monotone support
class Mtn(VcsInterface): class Mtn(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
# FIXME: merge conflicts? # FIXME: merge conflicts?
return [ (name, 'h:'), (name, None) ] return [(name, 'h:'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
# build command # build command
vcs_bin = prefs.getString('mtn_bin') vcs_bin = prefs.getString('mtn_bin')
ss = utils.popenReadLines( ss = utils.popenReadLines(
self.root, self.root,
[ vcs_bin, 'automate', 'select', '-q', rev ], [vcs_bin, 'automate', 'select', '-q', rev],
prefs, prefs,
'mtn_bash') 'mtn_bash')
if len(ss) != 1: if len(ss) != 1:
raise IOError('Ambiguous revision specifier') raise IOError('Ambiguous revision specifier')
args = [ vcs_bin, 'automate', 'get_revision', ss[0] ] args = [vcs_bin, 'automate', 'get_revision', ss[0]]
# build list of interesting files # build list of interesting files
fs = FolderSet(names) fs = FolderSet(names)
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
@ -94,9 +95,10 @@ class Mtn(VcsInterface):
removed_dirs = set() removed_dirs = set()
for s in utils.popenReadLines( for s in utils.popenReadLines(
self.root, self.root,
[ vcs_bin, 'automate', 'get_manifest_of', prev ], [vcs_bin, 'automate', 'get_manifest_of', prev],
prefs, prefs,
'mtn_bash'): 'mtn_bash'
):
s = shlex.split(s) s = shlex.split(s)
if len(s) > 1 and s[0] == 'dir': if len(s) > 1 and s[0] == 'dir':
removed_dirs.add(s[1]) removed_dirs.add(s[1])
@ -117,12 +119,12 @@ class Mtn(VcsInterface):
k = removed[k] k = removed[k]
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
result.append([ (k, prev), (None, None) ]) result.append([(k, prev), (None, None)])
elif k in added: elif k in added:
k = added[k] k = added[k]
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
result.append([ (None, None), (k, rev) ]) result.append([(None, None), (k, rev)])
else: else:
if k in renamed: if k in renamed:
arg1, k0, k1 = renamed[k] arg1, k0, k1 = renamed[k]
@ -131,7 +133,7 @@ class Mtn(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
result.append([ (k0, prev), (k1, rev) ]) result.append([(k0, prev), (k1, rev)])
return result return result
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
@ -173,7 +175,7 @@ class Mtn(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
processed = True processed = True
if 'added' in s and 'file' in m.get('new_type', []): if 'added' in s and 'file' in m.get('new_type', []):
# new file # new file
@ -181,7 +183,7 @@ class Mtn(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, None) ] added[k] = [(None, None), (k, None)]
processed = True processed = True
if ( if (
'rename_target' in s and 'rename_target' in s and
@ -195,7 +197,7 @@ class Mtn(VcsInterface):
if not isabs: if not isabs:
k0 = utils.relpath(pwd, k0) k0 = utils.relpath(pwd, k0)
k1 = utils.relpath(pwd, k1) k1 = utils.relpath(pwd, k1)
renamed[k1] = [ (k0, prev), (k1, None) ] renamed[k1] = [(k0, prev), (k1, None)]
processed = True processed = True
if not processed and 'file' in m.get('fs_type', []): if not processed and 'file' in m.get('fs_type', []):
# modified file or merge conflict # modified file or merge conflict
@ -203,7 +205,7 @@ class Mtn(VcsInterface):
if fs.contains(k): if fs.contains(k):
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
modified[k] = [ (k, prev), (k, None) ] modified[k] = [(k, prev), (k, None)]
# sort the results # sort the results
r = set() r = set()
for m in removed, added, modified, renamed: for m in removed, added, modified, renamed:

View File

@ -22,6 +22,7 @@ import os
from diffuse import utils from diffuse import utils
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# RCS support # RCS support
class Rcs(VcsInterface): class Rcs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
@ -35,7 +36,7 @@ class Rcs(VcsInterface):
for line in utils.popenReadLines(self.root, args, prefs, 'rcs_bash'): for line in utils.popenReadLines(self.root, args, prefs, 'rcs_bash'):
if line.startswith('head: '): if line.startswith('head: '):
rev = line[6:] rev = line[6:]
return [ (name, rev), (name, None) ] return [(name, rev), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
result = [] result = []
@ -54,7 +55,7 @@ class Rcs(VcsInterface):
k0 = None k0 = None
else: else:
k0 = k k0 = k
result.append([ (k0, prev), (k, rev) ]) result.append([(k0, prev), (k, rev)])
except ValueError: except ValueError:
utils.logError(_('Error parsing revision %s.') % (rev, )) utils.logError(_('Error parsing revision %s.') % (rev, ))
return result return result
@ -64,11 +65,11 @@ class Rcs(VcsInterface):
# os.sysconf() is only available on Unix # os.sysconf() is only available on Unix
if hasattr(os, 'sysconf'): if hasattr(os, 'sysconf'):
maxsize = os.sysconf('SC_ARG_MAX') maxsize = os.sysconf('SC_ARG_MAX')
maxsize -= sum([ len(k) + len(v) + 2 for k, v in os.environ.items() ]) maxsize -= sum([len(k) + len(v) + 2 for k, v in os.environ.items()])
else: else:
# assume the Window's limit to CreateProcess() # assume the Window's limit to CreateProcess()
maxsize = 32767 maxsize = 32767
maxsize -= sum([ len(k) + 1 for k in cmd ]) maxsize -= sum([len(k) + 1 for k in cmd])
ss = [] ss = []
i, s, a = 0, 0, [] i, s, a = 0, 0, []
@ -91,14 +92,14 @@ class Rcs(VcsInterface):
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
# build command # build command
cmd = [ prefs.getString('rcs_bin_rlog'), '-L', '-h' ] cmd = [prefs.getString('rcs_bin_rlog'), '-L', '-h']
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
r = [] r = []
for k in names: for k in names:
if os.path.isdir(k): if os.path.isdir(k):
# the user specified a folder # the user specified a folder
n, ex = [ k ], True n, ex = [k], True
while len(n) > 0: while len(n) > 0:
s = n.pop() s = n.pop()
recurse = os.path.isdir(os.path.join(s, 'RCS')) recurse = os.path.isdir(os.path.join(s, 'RCS'))
@ -135,7 +136,7 @@ class Rcs(VcsInterface):
r.append(k) r.append(k)
for k in r: for k in r:
isabs |= os.path.isabs(k) isabs |= os.path.isabs(k)
args = [ utils.safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r ] args = [utils.safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r]
# run command # run command
r, k = {}, '' r, k = {}, ''
for line in self._popen_xargs_readlines(cmd, args, prefs, 'rcs_bash'): for line in self._popen_xargs_readlines(cmd, args, prefs, 'rcs_bash'):
@ -148,7 +149,7 @@ class Rcs(VcsInterface):
elif line.startswith('head: '): elif line.startswith('head: '):
r[k] = line[6:] r[k] = line[6:]
# sort the results # sort the results
return [ [ (k, r[k]), (k, None) ] for k in sorted(r.keys()) ] return [[(k, r[k]), (k, None)] for k in sorted(r.keys())]
def getRevision(self, prefs, name, rev): def getRevision(self, prefs, name, rev):
return utils.popenRead( return utils.popenRead(

View File

@ -22,6 +22,7 @@ import os
from diffuse import utils from diffuse import utils
from diffuse.vcs.svn import Svn from diffuse.vcs.svn import Svn
class Svk(Svn): class Svk(Svn):
@staticmethod @staticmethod
def _getVcs(): def _getVcs():

View File

@ -24,6 +24,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Subversion support # Subversion support
# SVK support subclasses from this # SVK support subclasses from this
class Svn(VcsInterface): class Svn(VcsInterface):
@ -60,7 +61,7 @@ class Svn(VcsInterface):
if self.url is None: if self.url is None:
vcs, prefix = self._getVcs(), self._getURLPrefix() vcs, prefix = self._getVcs(), self._getURLPrefix()
n = len(prefix) n = len(prefix)
args = [ prefs.getString(vcs + '_bin'), 'info' ] args = [prefs.getString(vcs + '_bin'), 'info']
for s in utils.popenReadLines(self.root, args, prefs, vcs + '_bash'): for s in utils.popenReadLines(self.root, args, prefs, vcs + '_bash'):
if s.startswith(prefix): if s.startswith(prefix):
self.url = s[n:] self.url = s[n:]
@ -74,15 +75,15 @@ class Svn(VcsInterface):
left = glob.glob(escaped_name + '.merge-left.r*') left = glob.glob(escaped_name + '.merge-left.r*')
right = glob.glob(escaped_name + '.merge-right.r*') right = glob.glob(escaped_name + '.merge-right.r*')
if len(left) > 0 and len(right) > 0: if len(left) > 0 and len(right) > 0:
return [ (left[-1], None), (name, None), (right[-1], None) ] return [(left[-1], None), (name, None), (right[-1], None)]
# update conflict # update conflict
left = sorted(glob.glob(escaped_name + '.r*')) left = sorted(glob.glob(escaped_name + '.r*'))
right = glob.glob(escaped_name + '.mine') right = glob.glob(escaped_name + '.mine')
right.extend(glob.glob(escaped_name + '.working')) right.extend(glob.glob(escaped_name + '.working'))
if len(left) > 0 and len(right) > 0: if len(left) > 0 and len(right) > 0:
return [ (left[-1], None), (name, None), (right[0], None) ] return [(left[-1], None), (name, None), (right[0], None)]
# default case # default case
return [ (name, self._getPreviousRevision(None)), (name, None) ] return [(name, self._getPreviousRevision(None)), (name, None)]
def _getCommitTemplate(self, prefs, rev, names): def _getCommitTemplate(self, prefs, rev, names):
result = [] result = []
@ -96,9 +97,9 @@ class Svn(VcsInterface):
vcs = self._getVcs() vcs = self._getVcs()
vcs_bin, vcs_bash = prefs.getString(vcs + '_bin'), vcs + '_bash' vcs_bin, vcs_bash = prefs.getString(vcs + '_bin'), vcs + '_bash'
if rev is None: if rev is None:
args = [ vcs_bin, 'status', '-q' ] args = [vcs_bin, 'status', '-q']
else: else:
args = [ vcs_bin, 'diff', '--summarize', '-c', rev ] args = [vcs_bin, 'diff', '--summarize', '-c', rev]
# build list of interesting files # build list of interesting files
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
for name in names: for name in names:
@ -129,7 +130,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, k) k = os.path.join(self.root, k)
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
modified[k] = [ (k, prev), (k, rev) ] modified[k] = [(k, prev), (k, rev)]
elif v == 'C': elif v == 'C':
# merge conflict # merge conflict
modified[k] = self.getFileTemplate(prefs, k) modified[k] = self.getFileTemplate(prefs, k)
@ -146,7 +147,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, k) k = os.path.join(self.root, k)
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, None) ] added[k] = [(None, None), (k, None)]
else: else:
m = {} m = {}
for k in added: for k in added:
@ -181,7 +182,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, os.path.join(p, s)) k = os.path.join(self.root, os.path.join(p, s))
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
added[k] = [ (None, None), (k, rev) ] added[k] = [(None, None), (k, rev)]
# determine if removed items are files or directories # determine if removed items are files or directories
if prev == 'BASE': if prev == 'BASE':
m, removed = removed, {} m, removed = removed, {}
@ -191,7 +192,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, k) k = os.path.join(self.root, k)
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
else: else:
m = {} m = {}
for k in removed: for k in removed:
@ -224,7 +225,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, os.path.join(p, s)) k = os.path.join(self.root, os.path.join(p, s))
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
# recursively find all unreported removed files # recursively find all unreported removed files
while removed_dir: while removed_dir:
tmp = removed_dir tmp = removed_dir
@ -250,7 +251,7 @@ class Svn(VcsInterface):
k = os.path.join(self.root, os.path.join(p, s)) k = os.path.join(self.root, os.path.join(p, s))
if not isabs: if not isabs:
k = utils.relpath(pwd, k) k = utils.relpath(pwd, k)
removed[k] = [ (k, prev), (None, None) ] removed[k] = [(k, prev), (None, None)]
# sort the results # sort the results
r = set() r = set()
for m in removed, added, modified: for m in removed, added, modified:
@ -269,7 +270,7 @@ class Svn(VcsInterface):
def getRevision(self, prefs, name, rev): def getRevision(self, prefs, name, rev):
vcs_bin = prefs.getString('svn_bin') vcs_bin = prefs.getString('svn_bin')
if rev in [ 'BASE', 'COMMITTED', 'PREV' ]: if rev in ['BASE', 'COMMITTED', 'PREV']:
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -31,6 +31,7 @@ from diffuse.vcs.rcs import Rcs
from diffuse.vcs.svk import Svk from diffuse.vcs.svk import Svk
from diffuse.vcs.svn import Svn from diffuse.vcs.svn import Svn
class VcsRegistry: class VcsRegistry:
def __init__(self): def __init__(self):
# initialise the VCS objects # initialise the VCS objects
@ -74,17 +75,21 @@ def _find_parent_dir_with(path, dir_name):
break break
path = newpath path = newpath
def _get_bzr_repo(path, prefs): def _get_bzr_repo(path, prefs):
p = _find_parent_dir_with(path, '.bzr') p = _find_parent_dir_with(path, '.bzr')
return Bzr(p) if p else None return Bzr(p) if p else None
def _get_cvs_repo(path, prefs): def _get_cvs_repo(path, prefs):
return Cvs(path) if os.path.isdir(os.path.join(path, 'CVS')) else None return Cvs(path) if os.path.isdir(os.path.join(path, 'CVS')) else None
def _get_darcs_repo(path, prefs): def _get_darcs_repo(path, prefs):
p = _find_parent_dir_with(path, '_darcs') p = _find_parent_dir_with(path, '_darcs')
return Darcs(p) if p else None return Darcs(p) if p else None
def _get_git_repo(path, prefs): def _get_git_repo(path, prefs):
if 'GIT_DIR' in os.environ: if 'GIT_DIR' in os.environ:
try: try:
@ -127,14 +132,17 @@ def _get_git_repo(path, prefs):
break break
path = newpath path = newpath
def _get_hg_repo(path, prefs): def _get_hg_repo(path, prefs):
p = _find_parent_dir_with(path, '.hg') p = _find_parent_dir_with(path, '.hg')
return Hg(p) if p else None return Hg(p) if p else None
def _get_mtn_repo(path, prefs): def _get_mtn_repo(path, prefs):
p = _find_parent_dir_with(path, '_MTN') p = _find_parent_dir_with(path, '_MTN')
return Mtn(p) if p else None return Mtn(p) if p else None
def _get_rcs_repo(path, prefs): def _get_rcs_repo(path, prefs):
if os.path.isdir(os.path.join(path, 'RCS')): if os.path.isdir(os.path.join(path, 'RCS')):
return Rcs(path) return Rcs(path)
@ -151,10 +159,12 @@ def _get_rcs_repo(path, prefs):
pass pass
return None return None
def _get_svn_repo(path, prefs): def _get_svn_repo(path, prefs):
p = _find_parent_dir_with(path, '.svn') p = _find_parent_dir_with(path, '.svn')
return Svn(p) if p else None return Svn(p) if p else None
def _get_svk_repo(path, prefs): def _get_svk_repo(path, prefs):
name = path name = path
# parse the ~/.svk/config file to discover which directories are part of # parse the ~/.svk/config file to discover which directories are part of

View File

@ -21,18 +21,17 @@ import difflib
import os import os
import unicodedata import unicodedata
# pylint: disable=wrong-import-position from diffuse import utils
from diffuse.resources import theResources
import gi import gi
gi.require_version('GObject', '2.0') gi.require_version('GObject', '2.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('Pango', '1.0') gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0') gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo # noqa: E402
# pylint: enable=wrong-import-position
from diffuse import utils
from diffuse.resources import theResources
# mapping to column width of a character (tab will never be in this map) # mapping to column width of a character (tab will never be in this map)
_char_width_cache = {} _char_width_cache = {}
@ -43,6 +42,7 @@ LINE_MODE = 0
CHAR_MODE = 1 CHAR_MODE = 1
ALIGN_MODE = 2 ALIGN_MODE = 2
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be # This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
# handled immediately after changing the viewport position. This could cause # handled immediately after changing the viewport position. This could cause
# the application to become unresponsive for a while as it processed a large # the application to become unresponsive for a while as it processed a large
@ -140,9 +140,9 @@ class ScrolledWindow(Gtk.Grid):
self.partial_redraw = True self.partial_redraw = True
self.darea_queue_draw_area(x, y, w, h) self.darea_queue_draw_area(x, y, w, h)
# widget used to compare and merge text files # widget used to compare and merge text files
class FileDiffViewer(Gtk.Grid): class FileDiffViewer(Gtk.Grid):
# pylint: disable=too-many-public-methods
# class describing a text pane # class describing a text pane
class Pane: class Pane:
def __init__(self): def __init__(self):
@ -169,7 +169,7 @@ class FileDiffViewer(Gtk.Grid):
# class describing a single line of a pane # class describing a single line of a pane
class Line: class Line:
def __init__(self, line_number = None, text = None): def __init__(self, line_number=None, text=None):
# line number # line number
self.line_number = line_number self.line_number = line_number
# original text for the line # original text for the line
@ -254,7 +254,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection, 'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection, 'copy_right_into_selection': self.copy_right_into_selection,
'merge_from_left_then_right': self.merge_from_left_then_right, 'merge_from_left_then_right': self.merge_from_left_then_right,
'merge_from_right_then_left': self.merge_from_right_then_left } 'merge_from_right_then_left': self.merge_from_right_then_left
}
self._align_mode_actions = { self._align_mode_actions = {
'enter_line_mode': self._align_mode_enter_line_mode, 'enter_line_mode': self._align_mode_enter_line_mode,
'enter_character_mode': self.setCharMode, 'enter_character_mode': self.setCharMode,
@ -266,9 +267,11 @@ class FileDiffViewer(Gtk.Grid):
'right': self._line_mode_right, 'right': self._line_mode_right,
'page_up': self._line_mode_page_up, 'page_up': self._line_mode_page_up,
'page_down': self._line_mode_page_down, 'page_down': self._line_mode_page_down,
'align': self._align_text } 'align': self._align_text
}
self._character_mode_actions = { self._character_mode_actions = {
'enter_line_mode': self.setLineMode } 'enter_line_mode': self.setLineMode
}
self._button_actions = { self._button_actions = {
'undo': self.undo, 'undo': self.undo,
'redo': self.redo, 'redo': self.redo,
@ -303,7 +306,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection, 'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection, 'copy_right_into_selection': self.copy_right_into_selection,
'merge_from_left_then_right': self.merge_from_left_then_right, 'merge_from_left_then_right': self.merge_from_left_then_right,
'merge_from_right_then_left': self.merge_from_right_then_left } 'merge_from_right_then_left': self.merge_from_right_then_left
}
# create panes # create panes
self.dareas = [] self.dareas = []
@ -343,7 +347,6 @@ class FileDiffViewer(Gtk.Grid):
self.attach(diffmap, n, 1, 1, 1) self.attach(diffmap, n, 1, 1, 1)
diffmap.show() diffmap.show()
diffmap.set_size_request(16 * n, 0) diffmap.set_size_request(16 * n, 0)
# pylint: disable-next=no-member
self.add_events(Gdk.EventMask.KEY_PRESS_MASK | self.add_events(Gdk.EventMask.KEY_PRESS_MASK |
Gdk.EventMask.FOCUS_CHANGE_MASK) Gdk.EventMask.FOCUS_CHANGE_MASK)
self.connect('focus-in-event', self.focus_in_cb) self.connect('focus-in-event', self.focus_in_cb)
@ -372,7 +375,7 @@ class FileDiffViewer(Gtk.Grid):
# this must be connected with 'connect_after()' so the final widget sizes # this must be connected with 'connect_after()' so the final widget sizes
# are known and the scroll bar can be moved to the first difference # are known and the scroll bar can be moved to the first difference
def _realise_cb(self, widget): def _realise_cb(self, widget):
self.im_context.set_client_window(self.get_window()) # pylint: disable=no-member self.im_context.set_client_window(self.get_window())
try: try:
self.go_to_line(self.options['line']) self.go_to_line(self.options['line'])
except KeyError: except KeyError:
@ -635,7 +638,7 @@ class FileDiffViewer(Gtk.Grid):
if f is None: if f is None:
panes = self.panes panes = self.panes
else: else:
panes = [ self.panes[f] ] panes = [self.panes[f]]
for _, pane in enumerate(panes): for _, pane in enumerate(panes):
del pane.syntax_cache[:] del pane.syntax_cache[:]
del pane.diff_cache[:] del pane.diff_cache[:]
@ -644,7 +647,7 @@ class FileDiffViewer(Gtk.Grid):
for line in pane.lines: for line in pane.lines:
if line is not None: if line is not None:
line.compare_string = None line.compare_string = None
text = [ line.text ] text = [line.text]
if line.is_modified: if line.is_modified:
text.append(line.modified_text) text.append(line.modified_text)
for s in text: for s in text:
@ -869,7 +872,7 @@ class FileDiffViewer(Gtk.Grid):
for f, pane in enumerate(self.panes): for f, pane in enumerate(self.panes):
if i < len(pane.diff_cache): if i < len(pane.diff_cache):
if i2 + 1 < len(pane.diff_cache): if i2 + 1 < len(pane.diff_cache):
pane.diff_cache[i:i2] = new_n * [ None ] pane.diff_cache[i:i2] = new_n * [None]
else: else:
del pane.diff_cache[i:] del pane.diff_cache[i:]
self.dareas[f].queue_draw() self.dareas[f].queue_draw()
@ -942,7 +945,7 @@ class FileDiffViewer(Gtk.Grid):
# insert 'n' blank lines in all panes # insert 'n' blank lines in all panes
def insertLines(self, i, n): def insertLines(self, i, n):
# insert lines # insert lines
self.updateAlignment(i, 0, [ n * [ None ] for pane in self.panes ]) self.updateAlignment(i, 0, [n * [None] for pane in self.panes])
pre, post = _cut_blocks(i, self.blocks) pre, post = _cut_blocks(i, self.blocks)
pre.append(n) pre.append(n)
pre.extend(post) pre.extend(post)
@ -1092,19 +1095,19 @@ class FileDiffViewer(Gtk.Grid):
# needed for alignment are inserted in all lists of lines for a particular # needed for alignment are inserted in all lists of lines for a particular
# side to keep them all in sync. # side to keep them all in sync.
def alignBlocks(self, leftblocks, leftlines, rightblocks, rightlines): def alignBlocks(self, leftblocks, leftlines, rightblocks, rightlines):
blocks = ( leftblocks, rightblocks ) blocks = (leftblocks, rightblocks)
lines = ( leftlines, rightlines ) lines = (leftlines, rightlines)
# get the inner lines we are to match # get the inner lines we are to match
middle = ( leftlines[-1], rightlines[0] ) middle = (leftlines[-1], rightlines[0])
# eliminate any existing spacer lines # eliminate any existing spacer lines
mlines = ( [ line for line in middle[0] if line is not None ], mlines = ([line for line in middle[0] if line is not None],
[ line for line in middle[1] if line is not None ] ) [line for line in middle[1] if line is not None])
s1, s2 = mlines s1, s2 = mlines
n1, n2 = 0, 0 n1, n2 = 0, 0
# hash lines according to the alignment preferences # hash lines according to the alignment preferences
a = self._alignmentHash a = self._alignmentHash
t1 = [ a(s) for s in s1 ] t1 = [a(s) for s in s1]
t2 = [ a(s) for s in s2 ] t2 = [a(s) for s in s2]
# align s1 and s2 by inserting spacer lines # align s1 and s2 by inserting spacer lines
# this will be used to determine which lines from the inner lists of # this will be used to determine which lines from the inner lists of
# lines should be neighbours # lines should be neighbours
@ -1113,12 +1116,12 @@ class FileDiffViewer(Gtk.Grid):
if delta < 0: if delta < 0:
# insert spacer lines in s1 # insert spacer lines in s1
i = n1 + block[0] i = n1 + block[0]
s1[i:i] = -delta * [ None ] s1[i:i] = -delta * [None]
n1 -= delta n1 -= delta
elif delta > 0: elif delta > 0:
# insert spacer lines in s2 # insert spacer lines in s2
i = n2 + block[1] i = n2 + block[1]
s2[i:i] = delta * [ None ] s2[i:i] = delta * [None]
n2 += delta n2 += delta
nmatch = len(s1) nmatch = len(s1)
@ -1128,19 +1131,19 @@ class FileDiffViewer(Gtk.Grid):
# #
# advance one row at a time inserting spacer lines as we go # advance one row at a time inserting spacer lines as we go
# 'i' indicates which row we are processing # 'i' indicates which row we are processing
# 'k' indicates which pair of neighbours we are processing # 'k' indicates which pair of neighbors we are processing
i, k = 0, 0 i, k = 0, 0
bi = [ 0, 0 ] bi = [0, 0]
bn = [ 0, 0 ] bn = [0, 0]
while True: while True:
# if we have reached the end of the list for any side, it needs # if we have reached the end of the list for any side, it needs
# spacer lines to align with the other side # spacer lines to align with the other side
insert = [ i >= len(m) for m in middle ] insert = [i >= len(m) for m in middle]
if insert == [ True, True ]: if insert == [True, True]:
# we have reached the end of both inner lists of lines # we have reached the end of both inner lists of lines
# we are done # we are done
break break
if insert == [ False, False ] and k < nmatch: if insert == [False, False] and k < nmatch:
# determine if either side needs spacer lines to make the # determine if either side needs spacer lines to make the
# inner list of lines match up # inner list of lines match up
accept = True accept = True
@ -1162,7 +1165,7 @@ class FileDiffViewer(Gtk.Grid):
k += 1 k += 1
else: else:
# insert spacer lines as needed # insert spacer lines as needed
insert = [ m[i] is not None for m in middle ] insert = [m[i] is not None for m in middle]
for j in range(2): for j in range(2):
if insert[j]: if insert[j]:
# insert spacers lines for side 'j' # insert spacers lines for side 'j'
@ -1196,13 +1199,13 @@ class FileDiffViewer(Gtk.Grid):
blocks.append(n) blocks.append(n)
# create line objects for the text # create line objects for the text
Line = FileDiffViewer.Line Line = FileDiffViewer.Line
mid = [ [ Line(j + 1, ss[j]) for j in range(n) ] ] mid = [[Line(j + 1, ss[j]) for j in range(n)]]
if f > 0: if f > 0:
# align with panes to the left # align with panes to the left
# use copies so the originals can be used by the Undo object # use copies so the originals can be used by the Undo object
leftblocks = self.blocks[:] leftblocks = self.blocks[:]
leftlines = [ pane.lines[:] for pane in self.panes[:f] ] leftlines = [pane.lines[:] for pane in self.panes[:f]]
_remove_null_lines(leftblocks, leftlines) _remove_null_lines(leftblocks, leftlines)
self.alignBlocks(leftblocks, leftlines, blocks, mid) self.alignBlocks(leftblocks, leftlines, blocks, mid)
mid[:0] = leftlines mid[:0] = leftlines
@ -1211,7 +1214,7 @@ class FileDiffViewer(Gtk.Grid):
# align with panes to the right # align with panes to the right
# use copies so the originals can be used by the Undo object # use copies so the originals can be used by the Undo object
rightblocks = self.blocks[:] rightblocks = self.blocks[:]
rightlines = [ pane.lines[:] for pane in self.panes[f + 1:] ] rightlines = [pane.lines[:] for pane in self.panes[f + 1:]]
_remove_null_lines(rightblocks, rightlines) _remove_null_lines(rightblocks, rightlines)
self.alignBlocks(blocks, mid, rightblocks, rightlines) self.alignBlocks(blocks, mid, rightblocks, rightlines)
mid.extend(rightlines) mid.extend(rightlines)
@ -1295,7 +1298,7 @@ class FileDiffViewer(Gtk.Grid):
# change the format to that of the target pane # change the format to that of the target pane
if pane.format == 0: if pane.format == 0:
self.setFormat(f, _get_format(ss)) self.setFormat(f, _get_format(ss))
ss = [ _convert_to_format(s, pane.format) for s in ss ] ss = [_convert_to_format(s, pane.format) for s in ss]
# prepend original text that was before the selection # prepend original text that was before the selection
if col0 > 0: if col0 > 0:
pre = self.getLineText(f, line0)[:col0] pre = self.getLineText(f, line0)[:col0]
@ -1379,11 +1382,11 @@ class FileDiffViewer(Gtk.Grid):
# 2. the matched pair # 2. the matched pair
# 3. lines after the matched pair # 3. lines after the matched pair
# each section has lines and blocks for left and right sides # each section has lines and blocks for left and right sides
lines_s = [ [], [], [] ] lines_s = [[], [], []]
cutblocks = [ [], [], [] ] cutblocks = [[], [], []]
lines = [ pane.lines for pane in self.panes ] lines = [pane.lines for pane in self.panes]
nlines = len(lines[0]) nlines = len(lines[0])
for temp, m in zip([ lines[:f + 1], lines[f + 1:] ], [ line1, line2 ]): for temp, m in zip([lines[:f + 1], lines[f + 1:]], [line1, line2]):
# cut the blocks just before the line being matched # cut the blocks just before the line being matched
pre, post = _cut_blocks(m - start, mid) pre, post = _cut_blocks(m - start, mid)
if len(temp) == 1: if len(temp) == 1:
@ -1391,12 +1394,12 @@ class FileDiffViewer(Gtk.Grid):
# preserve other cuts # preserve other cuts
pre = _create_block(sum(pre)) pre = _create_block(sum(pre))
# the first section of lines to match # the first section of lines to match
lines_s[0].append([ s[start:m] for s in temp ]) lines_s[0].append([s[start:m] for s in temp])
cutblocks[0].append(pre) cutblocks[0].append(pre)
# the line to match may be after the actual lines # the line to match may be after the actual lines
if m < nlines: if m < nlines:
m1 = [ [ s[m] ] for s in temp ] m1 = [[s[m]] for s in temp]
m2 = [ s[m + 1:end] for s in temp ] m2 = [s[m + 1:end] for s in temp]
# cut the blocks just after the line being matched # cut the blocks just after the line being matched
b1, b2 = _cut_blocks(1, post) b1, b2 = _cut_blocks(1, post)
if len(temp) == 1: if len(temp) == 1:
@ -1404,8 +1407,8 @@ class FileDiffViewer(Gtk.Grid):
# preserve other cuts # preserve other cuts
b2 = _create_block(sum(b2)) b2 = _create_block(sum(b2))
else: else:
m1 = [ [] for s in temp ] m1 = [[] for s in temp]
m2 = [ [] for s in temp ] m2 = [[] for s in temp]
b1, b2 = [], [] b1, b2 = [], []
# the second section of lines to match # the second section of lines to match
lines_s[1].append(m1) lines_s[1].append(m1)
@ -1415,7 +1418,7 @@ class FileDiffViewer(Gtk.Grid):
cutblocks[2].append(b2) cutblocks[2].append(b2)
# align each section and concatenate the results # align each section and concatenate the results
finallines = [ [] for s in lines ] finallines = [[] for s in lines]
for b, lines_t in zip(cutblocks, lines_s): for b, lines_t in zip(cutblocks, lines_s):
_remove_null_lines(b[0], lines_t[0]) _remove_null_lines(b[0], lines_t[0])
_remove_null_lines(b[1], lines_t[1]) _remove_null_lines(b[1], lines_t[1])
@ -1606,7 +1609,6 @@ class FileDiffViewer(Gtk.Grid):
x -= int(self.hadj.get_value()) x -= int(self.hadj.get_value())
y -= int(self.vadj.get_value()) y -= int(self.vadj.get_value())
# translate to a position relative to the window # translate to a position relative to the window
# pylint: disable=no-member
x, y = self.dareas[self.current_pane].translate_coordinates(self.get_toplevel(), x, y) x, y = self.dareas[self.current_pane].translate_coordinates(self.get_toplevel(), x, y)
# input methods support widgets are centred horizontally about the # input methods support widgets are centred horizontally about the
# cursor, a width of 50 seems to give a better widget positions # cursor, a width of 50 seems to give a better widget positions
@ -1705,14 +1707,14 @@ class FileDiffViewer(Gtk.Grid):
end += 1 end += 1
# get the text for the selected lines # get the text for the selected lines
end = min(end, len(self.panes[f].lines)) end = min(end, len(self.panes[f].lines))
ss = [ self.getLineText(f, i) for i in range(start, end) ] ss = [self.getLineText(f, i) for i in range(start, end)]
# trim out the unselected parts of the lines # trim out the unselected parts of the lines
# check for col > 0 as some lines may be null # check for col > 0 as some lines may be null
if col1 > 0: if col1 > 0:
ss[-1] = ss[-1][:col1] ss[-1] = ss[-1][:col1]
if col0 > 0: if col0 > 0:
ss[0] = ss[0][col0:] ss[0] = ss[0][col0:]
return ''.join([ s for s in ss if s is not None ]) return ''.join([s for s in ss if s is not None])
# expands the selection to include everything # expands the selection to include everything
def select_all(self): def select_all(self):
@ -1771,14 +1773,14 @@ class FileDiffViewer(Gtk.Grid):
# callback for mouse button presses in the text window # callback for mouse button presses in the text window
def darea_button_press_cb(self, widget, event, f): def darea_button_press_cb(self, widget, event, f):
self.get_toplevel().set_focus(self) # pylint: disable=no-member self.get_toplevel().set_focus(self)
x = int(event.x + self.hadj.get_value()) x = int(event.x + self.hadj.get_value())
y = int(event.y + self.vadj.get_value()) y = int(event.y + self.vadj.get_value())
nlines = len(self.panes[f].lines) nlines = len(self.panes[f].lines)
i = min(y // self.font_height, nlines) i = min(y // self.font_height, nlines)
if event.button == 1: if event.button == 1:
# left mouse button # left mouse button
if event.type == Gdk.EventType._2BUTTON_PRESS: # pylint: disable=no-member,protected-access if event.type == Gdk.EventType._2BUTTON_PRESS:
# double click # double click
if self.mode == ALIGN_MODE: if self.mode == ALIGN_MODE:
self.setLineMode() self.setLineMode()
@ -1804,7 +1806,7 @@ class FileDiffViewer(Gtk.Grid):
while j < n and _get_character_class(text[j]) == c: while j < n and _get_character_class(text[j]) == c:
j += 1 j += 1
self.setCurrentChar(i, j, i, k) self.setCurrentChar(i, j, i, k)
elif event.type == Gdk.EventType._3BUTTON_PRESS: # pylint: disable=no-member,protected-access elif event.type == Gdk.EventType._3BUTTON_PRESS:
# triple click, select a whole line # triple click, select a whole line
if self.mode == CHAR_MODE and self.current_pane == f: if self.mode == CHAR_MODE and self.current_pane == f:
i2 = min(i + 1, nlines) i2 = min(i + 1, nlines)
@ -1833,10 +1835,9 @@ class FileDiffViewer(Gtk.Grid):
can_select = self.mode in (LINE_MODE, CHAR_MODE) and f == self.current_pane can_select = self.mode in (LINE_MODE, CHAR_MODE) and f == self.current_pane
can_swap = (f != self.current_pane) can_swap = (f != self.current_pane)
# pylint: disable=line-too-long menu = createMenu([
menu = createMenu( [_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align], # noqa: E501
[ [_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align], [_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate],
[_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate ],
[_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge], [_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge],
[], [],
[_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select], [_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select],
@ -1844,10 +1845,10 @@ class FileDiffViewer(Gtk.Grid):
[_('Paste'), self.button_cb, 'paste', Gtk.STOCK_PASTE, None, can_select], [_('Paste'), self.button_cb, 'paste', Gtk.STOCK_PASTE, None, can_select],
[], [],
[_('Select All'), self.button_cb, 'select_all', None, None, can_select], [_('Select All'), self.button_cb, 'select_all', None, None, can_select],
[_('Clear Edits'), self.button_cb, 'clear_edits', Gtk.STOCK_CLEAR, None, can_isolate], [_('Clear Edits'), self.button_cb, 'clear_edits', Gtk.STOCK_CLEAR, None, can_isolate], # noqa: E501
[], [],
[_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap] ]) [_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap]
# pylint: enable=line-too-long ])
menu.attach_to_widget(self) menu.attach_to_widget(self)
menu.popup(None, None, None, None, event.button, event.time) menu.popup(None, None, None, None, event.button, event.time)
@ -1996,7 +1997,8 @@ class FileDiffViewer(Gtk.Grid):
diffcolours = [ diffcolours = [
theResources.getDifferenceColour(f), theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ] theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5) diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
# iterate over each exposed line # iterate over each exposed line
@ -2036,7 +2038,7 @@ class FileDiffViewer(Gtk.Grid):
# enlarge cache to fit pan.diff_cache[i] # enlarge cache to fit pan.diff_cache[i]
if i >= len(pane.diff_cache): if i >= len(pane.diff_cache):
pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [ None ]) pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [None])
# construct a list of ranges for this lines character # construct a list of ranges for this lines character
# differences if not already cached # differences if not already cached
if pane.diff_cache[i] is None: if pane.diff_cache[i] is None:
@ -2198,7 +2200,7 @@ class FileDiffViewer(Gtk.Grid):
if temp < x: if temp < x:
temp += ((x - temp) // h) * h temp += ((x - temp) // h) * h
h_half = 0.5 * h h_half = 0.5 * h
phase = [ h_half, h_half, -h_half, -h_half ] phase = [h_half, h_half, -h_half, -h_half]
for j in range(4): for j in range(4):
x_temp = temp x_temp = temp
y_temp = y_start y_temp = y_start
@ -2224,7 +2226,7 @@ class FileDiffViewer(Gtk.Grid):
if temp is None: if temp is None:
blocks = None blocks = None
else: else:
blocks = [ (0, len(temp), 'text') ] blocks = [(0, len(temp), 'text')]
else: else:
# apply the syntax highlighting rules to identify # apply the syntax highlighting rules to identify
# ranges of similarly coloured characters # ranges of similarly coloured characters
@ -2364,9 +2366,9 @@ class FileDiffViewer(Gtk.Grid):
# flags & 8 indicates regular lines with text # flags & 8 indicates regular lines with text
if self.diffmap_cache is None: if self.diffmap_cache is None:
nlines = len(self.panes[0].lines) nlines = len(self.panes[0].lines)
start = n * [ 0 ] start = n * [0]
flags = n * [ 0 ] flags = n * [0]
self.diffmap_cache = [ [] for f in range(n) ] self.diffmap_cache = [[] for f in range(n)]
# iterate over each row of lines # iterate over each row of lines
for i in range(nlines): for i in range(nlines):
nextflag = 0 nextflag = 0
@ -2419,7 +2421,8 @@ class FileDiffViewer(Gtk.Grid):
for f in range(n): for f in range(n):
diffcolours = [ diffcolours = [
theResources.getDifferenceColour(f), theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ] theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5) diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
wx = f * wn wx = f * wn
# draw in two passes, more important stuff in the second pass # draw in two passes, more important stuff in the second pass
@ -2443,7 +2446,7 @@ class FileDiffViewer(Gtk.Grid):
break break
yh = max(rect.height * self.font_height * end // hmax - ymin, 1) yh = max(rect.height * self.font_height * end // hmax - ymin, 1)
#if ymin + yh <= rect.y: # if ymin + yh <= rect.y:
# continue # continue
cr.set_source_rgb(colour.red, colour.green, colour.blue) cr.set_source_rgb(colour.red, colour.green, colour.blue)
@ -2458,7 +2461,7 @@ class FileDiffViewer(Gtk.Grid):
yh = rect.height * vmax // hmax - ymin yh = rect.height * vmax // hmax - ymin
if yh > 1: if yh > 1:
yh -= 1 yh -= 1
#if ymin + yh > rect.y: # if ymin + yh > rect.y:
colour = theResources.getColour('line_selection') colour = theResources.getColour('line_selection')
alpha = theResources.getFloat('line_selection_opacity') alpha = theResources.getFloat('line_selection_opacity')
cr.set_source_rgba(colour.red, colour.green, colour.blue, alpha) cr.set_source_rgba(colour.red, colour.green, colour.blue, alpha)
@ -2675,7 +2678,7 @@ class FileDiffViewer(Gtk.Grid):
elif event.keyval == Gdk.KEY_Tab and event.state & Gdk.ModifierType.CONTROL_MASK: elif event.keyval == Gdk.KEY_Tab and event.state & Gdk.ModifierType.CONTROL_MASK:
retval = False retval = False
# up/down cursor navigation # up/down cursor navigation
elif event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down ]: elif event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down]:
i = self.current_line i = self.current_line
# move back to the remembered cursor column if possible # move back to the remembered cursor column if possible
col = self.cursor_column col = self.cursor_column
@ -2683,11 +2686,11 @@ class FileDiffViewer(Gtk.Grid):
# find the current cursor column # find the current cursor column
s = utils.null_to_empty(self.getLineText(f, i))[:self.current_char] s = utils.null_to_empty(self.getLineText(f, i))[:self.current_char]
col = self.stringWidth(s) col = self.stringWidth(s)
if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down ]: if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down]:
delta = 1 delta = 1
else: else:
delta = int(self.vadj.get_page_size() // self.font_height) delta = int(self.vadj.get_page_size() // self.font_height)
if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Page_Up ]: if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Page_Up]:
delta = -delta delta = -delta
i += delta i += delta
j = 0 j = 0
@ -2818,7 +2821,7 @@ class FileDiffViewer(Gtk.Grid):
self.current_char = j self.current_char = j
self.replaceText('') self.replaceText('')
# return key, add the platform specific end of line characters # return key, add the platform specific end of line characters
elif event.keyval in [ Gdk.KEY_Return, Gdk.KEY_KP_Enter ]: elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
s = os.linesep s = os.linesep
if self.prefs.getBool('editor_auto_indent'): if self.prefs.getBool('editor_auto_indent'):
start_i, start_j = self.selection_line, self.selection_char start_i, start_j = self.selection_line, self.selection_char
@ -2840,7 +2843,7 @@ class FileDiffViewer(Gtk.Grid):
s += ' ' * (w % tab_width) s += ' ' * (w % tab_width)
self.replaceText(s) self.replaceText(s)
# insert key # insert key
elif event.keyval in [ Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab ]: elif event.keyval in [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]:
start_i, start_j = self.selection_line, self.selection_char start_i, start_j = self.selection_line, self.selection_char
end_i, end_j = self.current_line, self.current_char end_i, end_j = self.current_line, self.current_char
if start_i != end_i or start_j != end_j or event.keyval == Gdk.KEY_ISO_Left_Tab: if start_i != end_i or start_j != end_j or event.keyval == Gdk.KEY_ISO_Left_Tab:
@ -3075,7 +3078,7 @@ class FileDiffViewer(Gtk.Grid):
blocks = [] blocks = []
for pane in self.panes: for pane in self.panes:
# create a new list of lines with no spacers # create a new list of lines with no spacers
newlines = [ [ line for line in pane.lines if line is not None ] ] newlines = [[line for line in pane.lines if line is not None]]
newblocks = _create_block(len(newlines[0])) newblocks = _create_block(len(newlines[0]))
if len(lines) > 0: if len(lines) > 0:
# match with neighbour to the left # match with neighbour to the left
@ -3118,8 +3121,8 @@ class FileDiffViewer(Gtk.Grid):
end = min(end, nlines) end = min(end, nlines)
n = end - start n = end - start
if n > 0: if n > 0:
lines = [ pane.lines[start:end] for pane in self.panes ] lines = [pane.lines[start:end] for pane in self.panes]
space = [ n * [ None ] for pane in self.panes ] space = [n * [None] for pane in self.panes]
lines[f], space[f] = space[f], lines[f] lines[f], space[f] = space[f], lines[f]
pre, post = _cut_blocks(end, self.blocks) pre, post = _cut_blocks(end, self.blocks)
@ -3343,14 +3346,14 @@ class FileDiffViewer(Gtk.Grid):
if end < start: if end < start:
start, end = end, start start, end = end, start
# get set of lines # get set of lines
ss = [ self.getLineText(f, i) for i in range(start, end + 1) ] ss = [self.getLineText(f, i) for i in range(start, end + 1)]
# create sorted list, removing any nulls # create sorted list, removing any nulls
temp = [ s for s in ss if s is not None ] temp = [s for s in ss if s is not None]
temp.sort() temp.sort()
if descending: if descending:
temp.reverse() temp.reverse()
# add back in the nulls # add back in the nulls
temp.extend((len(ss) - len(temp)) * [ None ]) temp.extend((len(ss) - len(temp)) * [None])
for i, s in enumerate(temp): for i, s in enumerate(temp):
# update line if it changed # update line if it changed
if ss[i] != s: if ss[i] != s:
@ -3468,7 +3471,7 @@ class FileDiffViewer(Gtk.Grid):
j += 1 j += 1
if col >= tab_width: if col >= tab_width:
# convert to tabs # convert to tabs
s = ''.join([ '\t' * (col // tab_width), ' ' * (col % tab_width), text[j:] ]) s = ''.join(['\t' * (col // tab_width), ' ' * (col % tab_width), text[j:]])
# update line only if it changed # update line only if it changed
if text != s: if text != s:
self.updateText(f, i, s) self.updateText(f, i, s)
@ -3551,7 +3554,7 @@ class FileDiffViewer(Gtk.Grid):
if end < start: if end < start:
start, end = end, start start, end = end, start
end = min(end + 1, len(pane.lines)) end = min(end + 1, len(pane.lines))
ss = [ self.getLineText(f_src, i) for i in range(start, end) ] ss = [self.getLineText(f_src, i) for i in range(start, end)]
if pane.format == 0: if pane.format == 0:
# copy the format of the source pane if the format for the # copy the format of the source pane if the format for the
# destination pane as not yet been determined # destination pane as not yet been determined
@ -3610,20 +3613,20 @@ class FileDiffViewer(Gtk.Grid):
end = min(end, nlines) end = min(end, nlines)
n = end - start n = end - start
if n > 0: if n > 0:
lines = [ pane.lines[start:end] for pane in self.panes ] lines = [pane.lines[start:end] for pane in self.panes]
spaces = [ n * [ None ] for pane in self.panes ] spaces = [n * [None] for pane in self.panes]
old_content = [ line for line in lines[f] if line is not None ] old_content = [line for line in lines[f] if line is not None]
for i in range(f + 1, npanes): for i in range(f + 1, npanes):
lines[i], spaces[i] = spaces[i], lines[i] lines[i], spaces[i] = spaces[i], lines[i]
# replace f's lines with merge content # replace f's lines with merge content
if f > 0: if f > 0:
lines[f] = lines[f - 1][:] lines[f] = lines[f - 1][:]
else: else:
lines[f] = n * [ None ] lines[f] = n * [None]
if f + 1 < npanes: if f + 1 < npanes:
spaces[f] = spaces[f + 1][:] spaces[f] = spaces[f + 1][:]
else: else:
spaces[f] = n * [ None ] spaces[f] = n * [None]
if right_first: if right_first:
lines, spaces = spaces, lines lines, spaces = spaces, lines
@ -3632,8 +3635,8 @@ class FileDiffViewer(Gtk.Grid):
# join and remove null lines # join and remove null lines
b.extend(b) b.extend(b)
for l, s in zip(lines, spaces): for line, space in zip(lines, spaces):
l.extend(s) line.extend(space)
_remove_null_lines(b, lines) _remove_null_lines(b, lines)
# replace f's lines with original, growing if necessary # replace f's lines with original, growing if necessary
@ -3647,14 +3650,14 @@ class FileDiffViewer(Gtk.Grid):
delta = -delta delta = -delta
for i in range(npanes): for i in range(npanes):
if i != f: if i != f:
lines[i].extend(delta * [ None ]) lines[i].extend(delta * [None])
# grow last block # grow last block
if len(b) > 0: if len(b) > 0:
b[-1] += delta b[-1] += delta
else: else:
b = _create_block(delta) b = _create_block(delta)
elif delta > 0: elif delta > 0:
old_content.extend(delta * [ None ]) old_content.extend(delta * [None])
new_n = len(old_content) new_n = len(old_content)
# update lines and blocks # update lines and blocks
@ -3686,6 +3689,7 @@ class FileDiffViewer(Gtk.Grid):
def merge_from_right_then_left(self): def merge_from_right_then_left(self):
self._mergeBoth(True) self._mergeBoth(True)
# convenience method for creating a menu according to a template # convenience method for creating a menu according to a template
def createMenu(specs, radio=None, accel_group=None): def createMenu(specs, radio=None, accel_group=None):
menu = Gtk.Menu.new() menu = Gtk.Menu.new()
@ -3707,7 +3711,7 @@ def createMenu(specs, radio=None, accel_group=None):
item.connect('activate', cb, data) item.connect('activate', cb, data)
if len(spec) > 3 and spec[3] is not None: if len(spec) > 3 and spec[3] is not None:
image = Gtk.Image.new() image = Gtk.Image.new()
image.set_from_stock(spec[3], Gtk.IconSize.MENU) # pylint: disable=no-member image.set_from_stock(spec[3], Gtk.IconSize.MENU)
item.set_image(image) item.set_image(image)
if accel_group is not None and len(spec) > 4: if accel_group is not None and len(spec) > 4:
a = theResources.getKeyBindings('menu', spec[4]) a = theResources.getKeyBindings('menu', spec[4])
@ -3730,10 +3734,12 @@ def createMenu(specs, radio=None, accel_group=None):
menu.append(item) menu.append(item)
return menu return menu
ALPHANUMERIC_CLASS = 0 ALPHANUMERIC_CLASS = 0
WHITESPACE_CLASS = 1 WHITESPACE_CLASS = 1
OTHER_CLASS = 2 OTHER_CLASS = 2
# maps similar types of characters to a group # maps similar types of characters to a group
def _get_character_class(c): def _get_character_class(c):
if c.isalnum() or c == '_': if c.isalnum() or c == '_':
@ -3742,11 +3748,12 @@ def _get_character_class(c):
return WHITESPACE_CLASS return WHITESPACE_CLASS
return OTHER_CLASS return OTHER_CLASS
# patience diff with difflib-style fallback # patience diff with difflib-style fallback
def _patience_diff(a, b): def _patience_diff(a, b):
matches, len_a, len_b = [], len(a), len(b) matches, len_a, len_b = [], len(a), len(b)
if len_a and len_b: if len_a and len_b:
blocks = [ (0, len_a, 0, len_b, 0) ] blocks = [(0, len_a, 0, len_b, 0)]
while blocks: while blocks:
start_a, end_a, start_b, end_b, match_idx = blocks.pop() start_a, end_a, start_b, end_b, match_idx = blocks.pop()
aa, bb = a[start_a:end_a], b[start_b:end_b] aa, bb = a[start_a:end_a], b[start_b:end_b]
@ -3822,6 +3829,7 @@ def _patience_diff(a, b):
matches.append((len_a, len_b, 0)) matches.append((len_a, len_b, 0))
return matches return matches
# longest common subsequence of unique elements common to 'a' and 'b' # longest common subsequence of unique elements common to 'a' and 'b'
def _patience_subsequence(a, b): def _patience_subsequence(a, b):
# value unique lines by their order in each list # value unique lines by their order in each list
@ -3877,6 +3885,7 @@ def _patience_subsequence(a, b):
result.reverse() result.reverse()
return result return result
# difflib-style approximation of the longest common subsequence # difflib-style approximation of the longest common subsequence
def _lcs_approx(a, b): def _lcs_approx(a, b):
count1, lookup = {}, {} count1, lookup = {}, {}
@ -3888,7 +3897,7 @@ def _lcs_approx(a, b):
if s in lookup: if s in lookup:
lookup[s].append(i) lookup[s].append(i)
else: else:
lookup[s] = [ i ] lookup[s] = [i]
if set(lookup).intersection(count1): if set(lookup).intersection(count1):
# we have some common elements # we have some common elements
# identify popular entries # identify popular entries
@ -3921,7 +3930,7 @@ def _lcs_approx(a, b):
max_indices.append((ai, bi)) max_indices.append((ai, bi))
else: else:
max_length = v max_length = v
max_indices = [ (ai, bi) ] max_indices = [(ai, bi)]
else: else:
prev_get = prev_matches.get prev_get = prev_matches.get
for bi in lookup[s]: for bi in lookup[s]:
@ -3932,7 +3941,7 @@ def _lcs_approx(a, b):
max_indices.append((ai, bi)) max_indices.append((ai, bi))
else: else:
max_length = v max_length = v
max_indices = [ (ai, bi) ] max_indices = [(ai, bi)]
prev_matches, matches = matches, {} prev_matches, matches = matches, {}
if max_indices: if max_indices:
# include any popular entries at the beginning # include any popular entries at the beginning
@ -3950,18 +3959,22 @@ def _lcs_approx(a, b):
return aidx, bidx, nidx return aidx, bidx, nidx
return None return None
# True if the string ends with '\r\n' # True if the string ends with '\r\n'
def _has_dos_line_ending(s): def _has_dos_line_ending(s):
return s.endswith('\r\n') return s.endswith('\r\n')
# True if the string ends with '\r' # True if the string ends with '\r'
def _has_mac_line_ending(s): def _has_mac_line_ending(s):
return s.endswith('\r') return s.endswith('\r')
# True if the string ends with '\n' but not '\r\n' # True if the string ends with '\n' but not '\r\n'
def _has_unix_line_ending(s): def _has_unix_line_ending(s):
return s.endswith('\n') and not s.endswith('\r\n') return s.endswith('\n') and not s.endswith('\r\n')
# returns the format mask for a list of strings # returns the format mask for a list of strings
def _get_format(ss): def _get_format(ss):
flags = 0 flags = 0
@ -3975,10 +3988,11 @@ def _get_format(ss):
flags |= utils.UNIX_FORMAT flags |= utils.UNIX_FORMAT
return flags return flags
# convenience method to change the line ending of a string # convenience method to change the line ending of a string
def _convert_to_format(s, fmt): def _convert_to_format(s, fmt):
if s is not None and fmt != 0: if s is not None and fmt != 0:
old_format = _get_format([ s ]) old_format = _get_format([s])
if old_format != 0 and (old_format & fmt) == 0: if old_format != 0 and (old_format & fmt) == 0:
s = utils.strip_eol(s) s = utils.strip_eol(s)
# prefer the host line ending style # prefer the host line ending style
@ -3996,6 +4010,7 @@ def _convert_to_format(s, fmt):
s += '\r' s += '\r'
return s return s
# Enforcing manual alignment is accomplished by dividing the lines of text into # Enforcing manual alignment is accomplished by dividing the lines of text into
# sections that are matched independently. 'blocks' is an array of integers # sections that are matched independently. 'blocks' is an array of integers
# describing how many lines (including null lines for spacing) that are in each # describing how many lines (including null lines for spacing) that are in each
@ -4005,12 +4020,12 @@ def _convert_to_format(s, fmt):
# in this array so 'blocks' will be an empty array when there are no lines. A # in this array so 'blocks' will be an empty array when there are no lines. A
# 'cut' at location 'i' means a line 'i-1' and line 'i' belong to different # 'cut' at location 'i' means a line 'i-1' and line 'i' belong to different
# sections # sections
def _create_block(n): def _create_block(n):
if n > 0: if n > 0:
return [ n ] return [n]
return [] return []
# returns the two sets of blocks after cutting at 'i' # returns the two sets of blocks after cutting at 'i'
def _cut_blocks(i, blocks): def _cut_blocks(i, blocks):
pre, post, nlines = [], [], 0 pre, post, nlines = [], [], 0
@ -4026,6 +4041,7 @@ def _cut_blocks(i, blocks):
nlines += b nlines += b
return pre, post return pre, post
# returns a set of blocks containing all of the cuts in the inputs # returns a set of blocks containing all of the cuts in the inputs
def _merge_blocks(leftblocks, rightblocks): def _merge_blocks(leftblocks, rightblocks):
leftblocks, rightblocks, b = leftblocks[:], rightblocks[:], [] leftblocks, rightblocks, b = leftblocks[:], rightblocks[:], []
@ -4043,6 +4059,7 @@ def _merge_blocks(leftblocks, rightblocks):
b.append(n) b.append(n)
return b return b
# utility method to simplify working with structures used to describe character # utility method to simplify working with structures used to describe character
# differences of a line # differences of a line
# #
@ -4053,7 +4070,7 @@ def _merge_blocks(leftblocks, rightblocks):
# this method will return the union of two sorted lists of ranges # this method will return the union of two sorted lists of ranges
def _merge_ranges(r1, r2): def _merge_ranges(r1, r2):
r1, r2, result, start = r1[:], r2[:], [], 0 r1, r2, result, start = r1[:], r2[:], [], 0
rs = [ r1, r2 ] rs = [r1, r2]
while len(r1) > 0 and len(r2) > 0: while len(r1) > 0 and len(r2) > 0:
flags, start = 0, min(r1[0][0], r2[0][0]) flags, start = 0, min(r1[0][0], r2[0][0])
if start == r1[0][0]: if start == r1[0][0]:
@ -4078,6 +4095,7 @@ def _merge_ranges(r1, r2):
result.extend(r2) result.extend(r2)
return result return result
# eliminates lines that are spacing lines in all panes # eliminates lines that are spacing lines in all panes
def _remove_null_lines(blocks, lines_set): def _remove_null_lines(blocks, lines_set):
bi, bn, i = 0, 0, 0 bi, bn, i = 0, 0, 0
@ -4097,22 +4115,23 @@ def _remove_null_lines(blocks, lines_set):
bn += blocks[bi] bn += blocks[bi]
bi += 1 bi += 1
# returns true if the string only contains whitespace characters # returns true if the string only contains whitespace characters
def _is_blank(s): def _is_blank(s):
for c in utils.whitespace: for c in utils.whitespace:
s = s.replace(c, '') s = s.replace(c, '')
return len(s) == 0 return len(s) == 0
# use Pango.SCALE instead of Pango.PIXELS to avoid overflow exception # use Pango.SCALE instead of Pango.PIXELS to avoid overflow exception
def _pixels(size): def _pixels(size):
return int(size / Pango.SCALE + 0.5) return int(size / Pango.SCALE + 0.5)
# create 'title_changed' signal for FileDiffViewer # create 'title_changed' signal for FileDiffViewer
# pylint: disable=line-too-long GObject.signal_new('swapped-panes', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
GObject.signal_new('swapped-panes', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) GObject.signal_new('num-edits-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) # noqa: E501
GObject.signal_new('num-edits-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) GObject.signal_new('mode-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('mode-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) GObject.signal_new('cursor-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('cursor-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) GObject.signal_new('syntax-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) # noqa: E501
GObject.signal_new('syntax-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) GObject.signal_new('format-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
GObject.signal_new('format-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int))
# pylint: enable=line-too-long