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
# pylint: disable=wrong-import-position
from diffuse import constants
from diffuse import utils
import gi
gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gtk
# pylint: enable=wrong-import-position
from gi.repository import GObject, Gtk # noqa: E402
# pylint: disable-next=no-name-in-module
from diffuse import constants
from diffuse import utils
# the about dialog
class AboutDialog(Gtk.AboutDialog):
@ -62,6 +59,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.''')]
self.set_license(''.join(license_text))
# custom dialogue for picking files with widgets for specifying the encoding
# and revision
class FileChooserDialog(Gtk.FileChooserDialog):
@ -96,7 +94,7 @@ class FileChooserDialog(Gtk.FileChooserDialog):
hbox.pack_end(label, False, False, 0)
label.show()
self.vbox.pack_start(hbox, False, False, 0) # pylint: disable=no-member
self.vbox.pack_start(hbox, False, False, 0)
hbox.show()
self.set_current_folder(self.last_chosen_folder)
self.connect('current-folder-changed', self._current_folder_changed_cb)
@ -110,10 +108,10 @@ class FileChooserDialog(Gtk.FileChooserDialog):
def get_revision(self):
return self.revision.get_text()
# pylint: disable-next=arguments-differ
def get_filename(self):
# 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
class NumericDialog(Gtk.Dialog):
@ -138,12 +136,13 @@ class NumericDialog(Gtk.Dialog):
vbox.pack_start(hbox, True, True, 0)
hbox.show()
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member
self.vbox.pack_start(vbox, False, False, 0)
vbox.show()
def button_cb(self, widget):
self.response(Gtk.ResponseType.ACCEPT)
# dialogue used to search for text
class SearchDialog(Gtk.Dialog):
def __init__(self, parent, pattern=None, history=None):
@ -190,7 +189,7 @@ class SearchDialog(Gtk.Dialog):
vbox.pack_start(button, False, False, 0)
button.show()
self.vbox.pack_start(vbox, False, False, 0) # pylint: disable=no-member
self.vbox.pack_start(vbox, False, False, 0)
vbox.show()
# callback used when the Enter key is pressed

View File

@ -25,22 +25,9 @@ import shlex
import stat
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
# pylint: disable-next=no-name-in-module
from diffuse import constants
from diffuse import utils
from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog
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 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()
# widget classed to create notebook tabs with labels and a close button
@ -986,7 +983,7 @@ class Diffuse(Gtk.Window):
treeview.connect('row-activated', self._confirmClose_row_activated_cb, model)
sw.add(treeview)
treeview.show()
dialog.vbox.pack_start(sw, True, True, 0) # pylint: disable=no-member
dialog.vbox.pack_start(sw, True, True, 0)
sw.show()
# add custom set of action buttons
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)

View File

@ -23,16 +23,13 @@ import os
import shlex
import sys
# pylint: disable=wrong-import-position
from diffuse import constants
from diffuse import utils
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# pylint: enable=wrong-import-position
from gi.repository import Gtk # noqa: E402
# 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 Preferences:
@ -75,11 +72,11 @@ class Preferences:
# [ 'String', name, default, label ]
# [ 'File', name, default, label ]
# [ 'Font', name, default, label ]
# pylint: disable=line-too-long
self.template = [
'FolderSet',
_('Display'),
[ 'List',
[
'List',
['Font', 'display_font', 'Monospace 10', _('Font')],
['Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024],
['Boolean', 'display_show_right_margin', True, _('Show right margin')],
@ -87,38 +84,41 @@ class Preferences:
['Boolean', 'display_show_line_numbers', True, _('Show line numbers')],
['Boolean', 'display_show_whitespace', False, _('Show white space characters')],
['Boolean', 'display_ignore_case', False, _('Ignore case differences')],
[ 'Boolean', 'display_ignore_whitespace', False, _('Ignore white space 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_whitespace_changes', False, _('Ignore changes to white space')], # noqa: E501
['Boolean', 'display_ignore_blanklines', False, _('Ignore blank line differences')],
['Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences')]
],
_('Alignment'),
[ 'List',
[
'List',
['Boolean', 'align_ignore_case', False, _('Ignore case')],
['Boolean', 'align_ignore_whitespace', True, _('Ignore white space')],
[ 'Boolean', 'align_ignore_whitespace_changes', False, _('Ignore changes to white space') ],
['Boolean', 'align_ignore_whitespace_changes', False, _('Ignore changes to white space')], # noqa: E501
['Boolean', 'align_ignore_blanklines', False, _('Ignore blank lines')],
['Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters')]
],
_('Editor'),
[ 'List',
[
'List',
['Boolean', 'editor_auto_indent', True, _('Auto indent')],
['Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces')],
['Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024]
],
_('Tabs'),
[ 'List',
[
'List',
['Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16],
['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 ]
['Boolean', 'tabs_warn_before_quit', True, _('Warn me when closing a tab will quit %s') % constants.APP_NAME] # noqa: E501
],
_('Regional Settings'),
[ 'List',
[ 'Encoding', 'encoding_default_codec', sys.getfilesystemencoding(), _('Default codec') ],
[ 'String', 'encoding_auto_detect_codecs', ' '.join(auto_detect_codecs), _('Order of codecs used to identify encoding') ]
[
'List',
['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
self.disable_when = {
@ -140,8 +140,7 @@ class Preferences:
_('Cygwin'),
['List',
['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
@ -208,7 +207,9 @@ class Preferences:
if len(a) == 2 and p in self.bool_prefs:
self.bool_prefs[p] = (a[1] == 'True')
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:
self.string_prefs[p] = a[1]
else:
@ -263,10 +264,10 @@ class Preferences:
p, t = v
if widgets[p].get_active() == t:
widgets[k].set_sensitive(False)
dialog.vbox.add(w) # pylint: disable=no-member
dialog.vbox.add(w)
w.show()
accept = (dialog.run() == Gtk.ResponseType.OK) # pylint: disable=no-member
accept = (dialog.run() == Gtk.ResponseType.OK)
if accept:
for k in self.bool_prefs:
self.bool_prefs[k] = widgets[k].get_active()
@ -288,8 +289,7 @@ class Preferences:
ss.append(f'{k} "{v_escaped}"\n')
ss.sort()
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')
f.write(f'# This prefs file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n') # noqa: E501
for s in ss:
f.write(s)
except IOError:
@ -433,19 +433,19 @@ class Preferences:
s = os.sep.join(p)
return s
# adaptor class to allow a Gtk.FontButton to be read like a Gtk.Entry
class _FontButton(Gtk.FontButton):
def __init__(self):
Gtk.FontButton.__init__(self)
def get_text(self):
# pylint: disable=no-member
return self.get_font_name()
# text entry widget with a button to help pick file names
class _FileEntry(Gtk.Box):
def __init__(self, parent, title):
# pylint: disable=no-member
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.toplevel = parent
self.title = title
@ -463,7 +463,6 @@ class _FileEntry(Gtk.Box):
# action performed when the pick file button is pressed
def chooseFile(self, widget):
# pylint: disable=no-member
dialog = Gtk.FileChooserDialog(
self.title,
self.toplevel,

View File

@ -29,13 +29,12 @@ import os
import re
import shlex
# pylint: disable=wrong-import-position
from diffuse import utils
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
# pylint: enable=wrong-import-position
from gi.repository import Gdk # noqa: E402
from diffuse import utils
class Resources:
def __init__(self):
@ -185,7 +184,8 @@ class Resources:
'edited': _Colour(0.5, 1.0, 0.5),
'preedit': _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
self.floats = {
@ -194,7 +194,8 @@ class Resources:
'character_selection_opacity': 0.4,
'edited_opacity': 0.4,
'line_difference_opacity': 0.3,
'line_selection_opacity' : 0.4 }
'line_selection_opacity': 0.4
}
# default strings
self.strings = {}
@ -222,7 +223,7 @@ class Resources:
elif token == 'Ctrl':
modifiers |= Gdk.ModifierType.CONTROL_MASK
elif token == 'Alt':
modifiers |= Gdk.ModifierType.MOD1_MASK # pylint: disable=no-member
modifiers |= Gdk.ModifierType.MOD1_MASK
elif len(token) == 0 or token[0] == '_':
raise ValueError()
else:
@ -459,11 +460,11 @@ class Resources:
self.syntax_magic_patterns[key] = re.compile(args[2], flags)
else:
raise ValueError()
# pylint: disable-next=bare-except
except: # Grr... the 're' module throws weird errors
# except ValueError:
except: # noqa: E722 # Grr... the 're' module throws weird errors
utils.logError(_(f'Error processing line {i + 1} of {file_name}.'))
# colour resources
class _Colour:
def __init__(self, r, g, b, a=1.0):
@ -489,6 +490,7 @@ class _Colour:
def over(self, other):
return self + other * (1 - self.alpha)
# class to build and run a finite state machine for identifying syntax tokens
class _SyntaxParser:
# create a new state machine that begins in initial_state and classifies
@ -536,4 +538,5 @@ class _SyntaxParser:
start = end
return state_name, blocks
theResources = Resources()

View File

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

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# CVS support
class Cvs(VcsInterface):
def getFileTemplate(self, prefs, name):
@ -93,7 +94,8 @@ class Cvs(VcsInterface):
utils.safeRelativePath(self.root, name, prefs, 'cvs_cygwin')
],
prefs,
'cvs_bash'):
'cvs_bash'
):
if s.startswith(' Working revision:\t-'):
rev = s.split('\t')[1][1:]
return utils.popenRead(

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# Darcs support
class Darcs(VcsInterface):
def getFileTemplate(self, prefs, name):

View File

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

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# Git support
class Git(VcsInterface):
def getFileTemplate(self, prefs, name):

View File

@ -23,6 +23,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# Mercurial support
class Hg(VcsInterface):
def __init__(self, root):

View File

@ -24,6 +24,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# Monotone support
class Mtn(VcsInterface):
def getFileTemplate(self, prefs, name):
@ -96,7 +97,8 @@ class Mtn(VcsInterface):
self.root,
[vcs_bin, 'automate', 'get_manifest_of', prev],
prefs,
'mtn_bash'):
'mtn_bash'
):
s = shlex.split(s)
if len(s) > 1 and s[0] == 'dir':
removed_dirs.add(s[1])

View File

@ -22,6 +22,7 @@ import os
from diffuse import utils
from diffuse.vcs.vcs_interface import VcsInterface
# RCS support
class Rcs(VcsInterface):
def getFileTemplate(self, prefs, name):

View File

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

View File

@ -24,6 +24,7 @@ from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
# Subversion support
# SVK support subclasses from this
class Svn(VcsInterface):

View File

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

View File

@ -21,18 +21,17 @@ import difflib
import os
import unicodedata
# pylint: disable=wrong-import-position
from diffuse import utils
from diffuse.resources import theResources
import gi
gi.require_version('GObject', '2.0')
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo
# pylint: enable=wrong-import-position
from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo # noqa: E402
from diffuse import utils
from diffuse.resources import theResources
# mapping to column width of a character (tab will never be in this map)
_char_width_cache = {}
@ -43,6 +42,7 @@ LINE_MODE = 0
CHAR_MODE = 1
ALIGN_MODE = 2
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
# handled immediately after changing the viewport position. This could cause
# 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.darea_queue_draw_area(x, y, w, h)
# widget used to compare and merge text files
class FileDiffViewer(Gtk.Grid):
# pylint: disable=too-many-public-methods
# class describing a text pane
class Pane:
def __init__(self):
@ -254,7 +254,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection,
'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 = {
'enter_line_mode': self._align_mode_enter_line_mode,
'enter_character_mode': self.setCharMode,
@ -266,9 +267,11 @@ class FileDiffViewer(Gtk.Grid):
'right': self._line_mode_right,
'page_up': self._line_mode_page_up,
'page_down': self._line_mode_page_down,
'align': self._align_text }
'align': self._align_text
}
self._character_mode_actions = {
'enter_line_mode': self.setLineMode }
'enter_line_mode': self.setLineMode
}
self._button_actions = {
'undo': self.undo,
'redo': self.redo,
@ -303,7 +306,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection,
'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
self.dareas = []
@ -343,7 +347,6 @@ class FileDiffViewer(Gtk.Grid):
self.attach(diffmap, n, 1, 1, 1)
diffmap.show()
diffmap.set_size_request(16 * n, 0)
# pylint: disable-next=no-member
self.add_events(Gdk.EventMask.KEY_PRESS_MASK |
Gdk.EventMask.FOCUS_CHANGE_MASK)
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
# are known and the scroll bar can be moved to the first difference
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:
self.go_to_line(self.options['line'])
except KeyError:
@ -1128,7 +1131,7 @@ class FileDiffViewer(Gtk.Grid):
#
# advance one row at a time inserting spacer lines as we go
# '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
bi = [0, 0]
bn = [0, 0]
@ -1606,7 +1609,6 @@ class FileDiffViewer(Gtk.Grid):
x -= int(self.hadj.get_value())
y -= int(self.vadj.get_value())
# 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)
# input methods support widgets are centred horizontally about the
# cursor, a width of 50 seems to give a better widget positions
@ -1771,14 +1773,14 @@ class FileDiffViewer(Gtk.Grid):
# callback for mouse button presses in the text window
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())
y = int(event.y + self.vadj.get_value())
nlines = len(self.panes[f].lines)
i = min(y // self.font_height, nlines)
if event.button == 1:
# 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
if self.mode == ALIGN_MODE:
self.setLineMode()
@ -1804,7 +1806,7 @@ class FileDiffViewer(Gtk.Grid):
while j < n and _get_character_class(text[j]) == c:
j += 1
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
if self.mode == CHAR_MODE and self.current_pane == f:
i2 = min(i + 1, nlines)
@ -1833,9 +1835,8 @@ class FileDiffViewer(Gtk.Grid):
can_select = self.mode in (LINE_MODE, CHAR_MODE) and f == self.current_pane
can_swap = (f != self.current_pane)
# pylint: disable=line-too-long
menu = createMenu(
[ [_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align],
menu = createMenu([
[_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align], # noqa: E501
[_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate],
[_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge],
[],
@ -1844,10 +1845,10 @@ class FileDiffViewer(Gtk.Grid):
[_('Paste'), self.button_cb, 'paste', Gtk.STOCK_PASTE, 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] ])
# pylint: enable=line-too-long
[_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap]
])
menu.attach_to_widget(self)
menu.popup(None, None, None, None, event.button, event.time)
@ -1996,7 +1997,8 @@ class FileDiffViewer(Gtk.Grid):
diffcolours = [
theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ]
theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
# iterate over each exposed line
@ -2419,7 +2421,8 @@ class FileDiffViewer(Gtk.Grid):
for f in range(n):
diffcolours = [
theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ]
theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
wx = f * wn
# draw in two passes, more important stuff in the second pass
@ -3632,8 +3635,8 @@ class FileDiffViewer(Gtk.Grid):
# join and remove null lines
b.extend(b)
for l, s in zip(lines, spaces):
l.extend(s)
for line, space in zip(lines, spaces):
line.extend(space)
_remove_null_lines(b, lines)
# replace f's lines with original, growing if necessary
@ -3686,6 +3689,7 @@ class FileDiffViewer(Gtk.Grid):
def merge_from_right_then_left(self):
self._mergeBoth(True)
# convenience method for creating a menu according to a template
def createMenu(specs, radio=None, accel_group=None):
menu = Gtk.Menu.new()
@ -3707,7 +3711,7 @@ def createMenu(specs, radio=None, accel_group=None):
item.connect('activate', cb, data)
if len(spec) > 3 and spec[3] is not None:
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)
if accel_group is not None and len(spec) > 4:
a = theResources.getKeyBindings('menu', spec[4])
@ -3730,10 +3734,12 @@ def createMenu(specs, radio=None, accel_group=None):
menu.append(item)
return menu
ALPHANUMERIC_CLASS = 0
WHITESPACE_CLASS = 1
OTHER_CLASS = 2
# maps similar types of characters to a group
def _get_character_class(c):
if c.isalnum() or c == '_':
@ -3742,6 +3748,7 @@ def _get_character_class(c):
return WHITESPACE_CLASS
return OTHER_CLASS
# patience diff with difflib-style fallback
def _patience_diff(a, b):
matches, len_a, len_b = [], len(a), len(b)
@ -3822,6 +3829,7 @@ def _patience_diff(a, b):
matches.append((len_a, len_b, 0))
return matches
# longest common subsequence of unique elements common to 'a' and 'b'
def _patience_subsequence(a, b):
# value unique lines by their order in each list
@ -3877,6 +3885,7 @@ def _patience_subsequence(a, b):
result.reverse()
return result
# difflib-style approximation of the longest common subsequence
def _lcs_approx(a, b):
count1, lookup = {}, {}
@ -3950,18 +3959,22 @@ def _lcs_approx(a, b):
return aidx, bidx, nidx
return None
# True if the string ends with '\r\n'
def _has_dos_line_ending(s):
return s.endswith('\r\n')
# True if the string ends with '\r'
def _has_mac_line_ending(s):
return s.endswith('\r')
# True if the string ends with '\n' but not '\r\n'
def _has_unix_line_ending(s):
return s.endswith('\n') and not s.endswith('\r\n')
# returns the format mask for a list of strings
def _get_format(ss):
flags = 0
@ -3975,6 +3988,7 @@ def _get_format(ss):
flags |= utils.UNIX_FORMAT
return flags
# convenience method to change the line ending of a string
def _convert_to_format(s, fmt):
if s is not None and fmt != 0:
@ -3996,6 +4010,7 @@ def _convert_to_format(s, fmt):
s += '\r'
return s
# Enforcing manual alignment is accomplished by dividing the lines of text into
# sections that are matched independently. 'blocks' is an array of integers
# 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
# 'cut' at location 'i' means a line 'i-1' and line 'i' belong to different
# sections
def _create_block(n):
if n > 0:
return [n]
return []
# returns the two sets of blocks after cutting at 'i'
def _cut_blocks(i, blocks):
pre, post, nlines = [], [], 0
@ -4026,6 +4041,7 @@ def _cut_blocks(i, blocks):
nlines += b
return pre, post
# returns a set of blocks containing all of the cuts in the inputs
def _merge_blocks(leftblocks, rightblocks):
leftblocks, rightblocks, b = leftblocks[:], rightblocks[:], []
@ -4043,6 +4059,7 @@ def _merge_blocks(leftblocks, rightblocks):
b.append(n)
return b
# utility method to simplify working with structures used to describe character
# differences of a line
#
@ -4078,6 +4095,7 @@ def _merge_ranges(r1, r2):
result.extend(r2)
return result
# eliminates lines that are spacing lines in all panes
def _remove_null_lines(blocks, lines_set):
bi, bn, i = 0, 0, 0
@ -4097,22 +4115,23 @@ def _remove_null_lines(blocks, lines_set):
bn += blocks[bi]
bi += 1
# returns true if the string only contains whitespace characters
def _is_blank(s):
for c in utils.whitespace:
s = s.replace(c, '')
return len(s) == 0
# use Pango.SCALE instead of Pango.PIXELS to avoid overflow exception
def _pixels(size):
return int(size / Pango.SCALE + 0.5)
# 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))
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, ())
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, ))
GObject.signal_new('format-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int))
# pylint: enable=line-too-long
GObject.signal_new('swapped-panes', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
GObject.signal_new('num-edits-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) # noqa: E501
GObject.signal_new('mode-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('cursor-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('syntax-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) # noqa: E501
GObject.signal_new('format-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501