Merge pull request #125 from MightyCreak/flake8

Use Flake8 instead of PyLint + integrate MyPy
This commit is contained in:
Creak 2021-11-22 20:58:33 -05:00 committed by GitHub
commit 6a90c81a9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 549 additions and 475 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

@ -17,20 +17,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: - uses: actions/setup-python@v2
fetch-depth: 0 - run: sudo apt install libgirepository1.0-dev
- run: pip install -r requirements.txt
- name: Pylint - name: Flake8
uses: cclauss/GitHub-Action-for-pylint@master run: flake8 src/
with:
args: "apk add --no-cache gtk+3.0-dev gobject-introspection-dev ; pip install -r requirements.txt ; pylint src/**/*.py" - name: MyPy
run: mypy src/
meson-build-test: meson-build-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: - uses: actions/setup-python@v2
fetch-depth: 0
- name: Install dependencies - name: Install dependencies
run: sudo apt-get -y install appstream appstream-util desktop-file-utils gettext run: sudo apt-get -y install appstream appstream-util desktop-file-utils gettext
@ -52,6 +53,7 @@ jobs:
options: --privileged options: --privileged
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: Flatpak builder - name: Flatpak builder
uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v3 uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v3
with: with:

2
.mypy.ini Normal file
View File

@ -0,0 +1,2 @@
[mypy]
warn_unused_ignores = True

View File

@ -1,2 +1,3 @@
flake8 ~= 3.8
mypy ~= 0.910
PyGObject ~= 3.40 PyGObject ~= 3.40
pylint~=2.11

View File

@ -19,17 +19,14 @@
import os import os
# pylint: disable=wrong-import-position from diffuse import constants # type: ignore
import gi from diffuse import utils
import gi # type: ignore
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 # type: ignore # 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):
@ -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.''')] 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):
@ -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,29 +25,26 @@ import shlex
import stat import stat
import webbrowser import webbrowser
# pylint: disable=wrong-import-position from urllib.parse import urlparse
import gi
from diffuse import constants # type: ignore
from diffuse import utils
from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog
from diffuse.preferences import Preferences
from diffuse.resources import theResources
from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import FileDiffViewerBase
from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE
import gi # type: ignore
gi.require_version('GObject', '2.0') gi.require_version('GObject', '2.0')
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('GdkPixbuf', '2.0') gi.require_version('GdkPixbuf', '2.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, Gtk, Gdk, GdkPixbuf, Pango, PangoCairo from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Pango, PangoCairo # type: ignore # noqa: E402
# 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
from diffuse.resources import theResources
from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import FileDiffViewer
from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE
theVCSs = VcsRegistry() theVCSs = VcsRegistry()
@ -113,8 +110,8 @@ class FileInfo:
# this class displays tab for switching between viewers and dispatches menu # this class displays tab for switching between viewers and dispatches menu
# commands to the current viewer # commands to the current viewer
class Diffuse(Gtk.Window): class Diffuse(Gtk.Window):
# specialisation of FileDiffViewer for Diffuse # specialization of FileDiffViewerBase for Diffuse
class FileDiffViewer(FileDiffViewer): class FileDiffViewer(FileDiffViewerBase):
# pane header # pane header
class PaneHeader(Gtk.Box): class PaneHeader(Gtk.Box):
def __init__(self): def __init__(self):
@ -226,7 +223,7 @@ class Diffuse(Gtk.Window):
self.encoding.set_text(s) self.encoding.set_text(s)
def __init__(self, n, prefs, title): def __init__(self, n, prefs, title):
FileDiffViewer.__init__(self, n, prefs) FileDiffViewerBase.__init__(self, n, prefs)
self.title = title self.title = title
self.status = '' self.status = ''
@ -534,8 +531,7 @@ class Diffuse(Gtk.Window):
info.name, info.encoding, info.revision, info.label = name, encoding, None, label info.name, info.encoding, info.revision, info.label = name, encoding, None, label
info.last_stat = info.stat = os.stat(name) info.last_stat = info.stat = os.stat(name)
self.setFileInfo(f, info) self.setFileInfo(f, info)
# update the syntax highlighting incase we changed the file # update the syntax highlighting in case we changed the file extension
# extension
syntax = theResources.guessSyntaxForFile(name, ss) syntax = theResources.guessSyntaxForFile(name, ss)
if syntax is not None: if syntax is not None:
self.setSyntax(syntax) self.setSyntax(syntax)
@ -987,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)

View File

@ -23,17 +23,14 @@ import os
import shlex import shlex
import sys import sys
# pylint: disable=wrong-import-position from diffuse import constants # type: ignore
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# pylint: enable=wrong-import-position
# pylint: disable-next=no-name-in-module
from diffuse import constants
from diffuse import utils from diffuse import utils
import gi # type: ignore
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk # type: ignore # noqa: E402
# 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:
def __init__(self, path): def __init__(self, path):
@ -75,11 +72,11 @@ 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', [
'List',
['Font', 'display_font', 'Monospace 10', _('Font')], ['Font', 'display_font', 'Monospace 10', _('Font')],
['Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024], ['Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024],
['Boolean', 'display_show_right_margin', True, _('Show right margin')], ['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_line_numbers', True, _('Show line numbers')],
['Boolean', 'display_show_whitespace', False, _('Show white space characters')], ['Boolean', 'display_show_whitespace', False, _('Show white space characters')],
['Boolean', 'display_ignore_case', False, _('Ignore case differences')], ['Boolean', 'display_ignore_case', False, _('Ignore case differences')],
[ 'Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences') ], ['Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences')], # noqa: E501
[ 'Boolean', 'display_ignore_whitespace_changes', False, _('Ignore changes to white space') ], ['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_blanklines', False, _('Ignore blank line differences')],
['Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences')] ['Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences')]
], ],
_('Alignment'), _('Alignment'),
[ 'List', [
'List',
['Boolean', 'align_ignore_case', False, _('Ignore case')], ['Boolean', 'align_ignore_case', False, _('Ignore case')],
['Boolean', 'align_ignore_whitespace', True, _('Ignore white space')], ['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_blanklines', False, _('Ignore blank lines')],
['Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters')] ['Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters')]
], ],
_('Editor'), _('Editor'),
[ 'List', [
'List',
['Boolean', 'editor_auto_indent', True, _('Auto indent')], ['Boolean', 'editor_auto_indent', True, _('Auto indent')],
['Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces')], ['Boolean', 'editor_expand_tabs', False, _('Expand tabs to spaces')],
['Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024] ['Integer', 'editor_soft_tab_width', 8, _('Soft tab width'), 1, 1024]
], ],
_('Tabs'), _('Tabs'),
[ 'List', [
'List',
['Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16], ['Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16],
['Boolean', 'tabs_always_show', False, _('Always show the tab bar')], ['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'), _('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 = {
@ -140,8 +140,7 @@ class Preferences:
_('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
@ -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:
@ -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:
@ -433,19 +433,19 @@ class Preferences:
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,14 +29,13 @@ import os
import re import re
import shlex import shlex
# pylint: disable=wrong-import-position
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
# pylint: enable=wrong-import-position
from diffuse import utils from diffuse import utils
import gi # type: ignore
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk # type: ignore # noqa: E402
class Resources: class Resources:
def __init__(self): def __init__(self):
# default keybindings # default keybindings
@ -185,7 +184,8 @@ class Resources:
'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 = {
@ -194,7 +194,8 @@ class Resources:
'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:
@ -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: # Grr... the 're' module throws weird errors
# except ValueError: # except ValueError:
except: # noqa: E722 # Grr... the 're' module throws weird errors
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
@ -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 # type: ignore
import gi
gi.require_version('Gtk', '3.0') import gi # type: ignore
from gi.repository import Gtk gi.require_version('Gtk', '3.0')
# pylint: enable=wrong-import-position from gi.repository import Gtk # type: ignore # noqa: E402
# 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,6 +120,7 @@ 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():
@ -128,6 +135,7 @@ def relpath(a, b):
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,13 +147,16 @@ 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:
@ -156,7 +167,8 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
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():
@ -167,8 +179,7 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
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,29 +210,35 @@ 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]:
# split on new line characters # split on new line characters
@ -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):

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
# CVS support # CVS support
class Cvs(VcsInterface): class Cvs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, 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,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
# Darcs support # Darcs support
class Darcs(VcsInterface): class Darcs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):

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,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
# Git support # Git support
class Git(VcsInterface): class Git(VcsInterface):
def getFileTemplate(self, prefs, name): 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.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):

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
# Monotone support # Monotone support
class Mtn(VcsInterface): class Mtn(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs, name):
@ -96,7 +97,8 @@ class Mtn(VcsInterface):
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])

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):

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):

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,28 +21,30 @@ import difflib
import os import os
import unicodedata import unicodedata
# pylint: disable=wrong-import-position from typing import Dict
import gi
from diffuse import utils
from diffuse.resources import theResources
import gi # type: ignore
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 # type: ignore # 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: Dict[str, str] = {}
# the file diff viewer is always in one of these modes defining the cursor, # the file diff viewer is always in one of these modes defining the cursor,
# and hotkey behaviour # and hotkey behavior
LINE_MODE = 0 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 +142,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 FileDiffViewerBase(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):
@ -254,7 +256,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 +269,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 +308,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 = []
@ -311,7 +317,7 @@ class FileDiffViewer(Gtk.Grid):
self.hadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0) self.hadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0)
self.vadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0) self.vadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0)
for i in range(n): for i in range(n):
pane = FileDiffViewer.Pane() pane = FileDiffViewerBase.Pane()
self.panes.append(pane) self.panes.append(pane)
# pane contents # pane contents
@ -343,7 +349,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 +377,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:
@ -709,7 +714,7 @@ class FileDiffViewer(Gtk.Grid):
pane = self.panes[f] pane = self.panes[f]
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.SetFormatUndo(f, fmt, pane.format)) self.addUndo(FileDiffViewerBase.SetFormatUndo(f, fmt, pane.format))
pane.format = fmt pane.format = fmt
self.emit('format_changed', f, fmt) self.emit('format_changed', f, fmt)
@ -731,12 +736,12 @@ class FileDiffViewer(Gtk.Grid):
def instanceLine(self, f, i, reverse=False): def instanceLine(self, f, i, reverse=False):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InstanceLineUndo(f, i, reverse)) self.addUndo(FileDiffViewerBase.InstanceLineUndo(f, i, reverse))
pane = self.panes[f] pane = self.panes[f]
if reverse: if reverse:
pane.lines[i] = None pane.lines[i] = None
else: else:
line = FileDiffViewer.Line() line = FileDiffViewerBase.Line()
pane.lines[i] = line pane.lines[i] = line
# Undo for changing the text for a Line object # Undo for changing the text for a Line object
@ -771,7 +776,7 @@ class FileDiffViewer(Gtk.Grid):
flags = self.getMapFlags(f, i) flags = self.getMapFlags(f, i)
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.UpdateLineTextUndo( self.addUndo(FileDiffViewerBase.UpdateLineTextUndo(
f, f,
i, i,
line.is_modified, line.is_modified,
@ -832,7 +837,7 @@ class FileDiffViewer(Gtk.Grid):
def insertNull(self, f, i, reverse): def insertNull(self, f, i, reverse):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InsertNullUndo(f, i, reverse)) self.addUndo(FileDiffViewerBase.InsertNullUndo(f, i, reverse))
pane = self.panes[f] pane = self.panes[f]
lines = pane.lines lines = pane.lines
# update/invalidate all relevant caches # update/invalidate all relevant caches
@ -863,7 +868,7 @@ class FileDiffViewer(Gtk.Grid):
def invalidateLineMatching(self, i, n, new_n): def invalidateLineMatching(self, i, n, new_n):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InvalidateLineMatchingUndo(i, n, new_n)) self.addUndo(FileDiffViewerBase.InvalidateLineMatchingUndo(i, n, new_n))
# update/invalidate all relevant caches and queue widgets for redraw # update/invalidate all relevant caches and queue widgets for redraw
i2 = i + n i2 = i + n
for f, pane in enumerate(self.panes): for f, pane in enumerate(self.panes):
@ -893,7 +898,7 @@ class FileDiffViewer(Gtk.Grid):
def alignmentChange(self, finished): def alignmentChange(self, finished):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.AlignmentChangeUndo(finished)) self.addUndo(FileDiffViewerBase.AlignmentChangeUndo(finished))
if finished: if finished:
self.updateSize(False) self.updateSize(False)
@ -936,7 +941,7 @@ class FileDiffViewer(Gtk.Grid):
def updateBlocks(self, blocks): def updateBlocks(self, blocks):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.UpdateBlocksUndo(self.blocks, blocks)) self.addUndo(FileDiffViewerBase.UpdateBlocksUndo(self.blocks, blocks))
self.blocks = blocks self.blocks = blocks
# insert 'n' blank lines in all panes # insert 'n' blank lines in all panes
@ -1028,7 +1033,8 @@ class FileDiffViewer(Gtk.Grid):
def replaceLines(self, f, lines, new_lines, max_num, new_max_num): def replaceLines(self, f, lines, new_lines, max_num, new_max_num):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.ReplaceLinesUndo(f, lines, new_lines, max_num, new_max_num)) self.addUndo(FileDiffViewerBase.ReplaceLinesUndo(
f, lines, new_lines, max_num, new_max_num))
pane = self.panes[f] pane = self.panes[f]
pane.lines = new_lines pane.lines = new_lines
# update/invalidate all relevant caches and queue widgets for redraw # update/invalidate all relevant caches and queue widgets for redraw
@ -1128,7 +1134,7 @@ 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]
@ -1195,7 +1201,7 @@ class FileDiffViewer(Gtk.Grid):
if n > 0: if n > 0:
blocks.append(n) blocks.append(n)
# create line objects for the text # create line objects for the text
Line = FileDiffViewer.Line Line = FileDiffViewerBase.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:
@ -1253,7 +1259,7 @@ class FileDiffViewer(Gtk.Grid):
lines.append(None) lines.append(None)
else: else:
line_num += 1 line_num += 1
lines.append(FileDiffViewer.Line(line_num, s)) lines.append(FileDiffViewerBase.Line(line_num, s))
# update loaded pane # update loaded pane
self.replaceLines(f, pane.lines, lines, pane.max_line_number, line_num) self.replaceLines(f, pane.lines, lines, pane.max_line_number, line_num)
@ -1487,7 +1493,7 @@ class FileDiffViewer(Gtk.Grid):
# selection # selection
def recordEditMode(self): def recordEditMode(self):
if self.undoblock is not None: if self.undoblock is not None:
self.addUndo(FileDiffViewer.EditModeUndo( self.addUndo(FileDiffViewerBase.EditModeUndo(
self.mode, self.mode,
self.current_pane, self.current_pane,
self.current_line, self.current_line,
@ -1606,7 +1612,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
@ -1771,14 +1776,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 +1809,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,9 +1838,8 @@ 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],
[], [],
@ -1844,10 +1848,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 +2000,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
@ -2419,7 +2424,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
@ -3240,7 +3246,7 @@ class FileDiffViewer(Gtk.Grid):
# swap the contents of two panes # swap the contents of two panes
def swapPanes(self, f_dst, f_src): def swapPanes(self, f_dst, f_src):
if self.undoblock is not None: if self.undoblock is not None:
self.addUndo(FileDiffViewer.SwapPanesUndo(f_dst, f_src)) self.addUndo(FileDiffViewerBase.SwapPanesUndo(f_dst, f_src))
self.current_pane = f_dst self.current_pane = f_dst
f0 = self.panes[f_dst] f0 = self.panes[f_dst]
f1 = self.panes[f_src] f1 = self.panes[f_src]
@ -3632,8 +3638,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
@ -3686,6 +3692,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 +3714,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 +3737,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,6 +3751,7 @@ 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)
@ -3822,6 +3832,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 +3888,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 = {}, {}
@ -3950,18 +3962,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,6 +3991,7 @@ 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:
@ -3996,6 +4013,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 +4023,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 +4044,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 +4062,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
# #
@ -4078,6 +4098,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 +4118,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
# pylint: disable=line-too-long # create 'title_changed' signal for FileDiffViewerBase
GObject.signal_new('swapped-panes', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) GObject.signal_new('swapped-panes', FileDiffViewerBase, 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, )) GObject.signal_new('num-edits-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) # noqa: E501
GObject.signal_new('mode-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) GObject.signal_new('mode-changed', FileDiffViewerBase, 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('cursor-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('syntax-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) GObject.signal_new('syntax-changed', FileDiffViewerBase, 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)) GObject.signal_new('format-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
# pylint: enable=line-too-long

View File

@ -46,7 +46,7 @@ def copyFile(src, dest, use_text_mode=False,enc=None):
s = f.read() s = f.read()
f.close() f.close()
if enc is not None: if enc is not None:
s = codecs.encode(unicode(s, 'utf_8'), enc) s = codecs.encode(str(s, encoding='utf_8'), enc)
f = open(dest, w) f = open(dest, w)
f.write(s) f.write(s)
f.close() f.close()
@ -190,12 +190,12 @@ for lang in os.listdir(d):
while True: while True:
i = s.find('&#', idx) i = s.find('&#', idx)
if i < 0: if i < 0:
a.append(unicode(s[idx:], 'latin_1')) a.append(str(s[idx:], encoding='latin_1'))
break break
a.append(unicode(s[idx:i], 'latin_1')) a.append(str(s[idx:i], encoding='latin_1'))
i += 2 i += 2
j = s.find(';', i) j = s.find(';', i)
a.append(unichr(int(s[i:j]))) a.append(chr(int(s[i:j])))
idx = j + 1 idx = j + 1
s = ''.join(a) s = ''.join(a)
s = codecs.encode(s, 'utf-8') s = codecs.encode(s, 'utf-8')

View File

@ -196,7 +196,7 @@ def copyFile(src, dest, use_text_mode=False,enc=None):
s = f.read() s = f.read()
f.close() f.close()
if enc is not None: if enc is not None:
s = codecs.encode(unicode(s, 'utf_8'), enc) s = codecs.encode(str(s, encoding='utf_8'), enc)
f = open(dest, w) f = open(dest, w)
f.write(s) f.write(s)
f.close() f.close()