From f4a446caa899724715ad4ca8b4532e7b4cdbb7b6 Mon Sep 17 00:00:00 2001 From: Romain Failliot Date: Mon, 22 Nov 2021 18:52:26 -0500 Subject: [PATCH 1/5] Fix some typos and convert windows-installer scripts to Python 3 The scripts are poorly converted as Windows is not really supoported anymore and would need some love from a contributor who actually uses Windows --- src/diffuse/main.py | 3 +-- windows-installer/build.py | 8 ++++---- windows-installer/setup.new.py | 14 +++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/diffuse/main.py b/src/diffuse/main.py index 995e6df..b19f34b 100644 --- a/src/diffuse/main.py +++ b/src/diffuse/main.py @@ -534,8 +534,7 @@ class Diffuse(Gtk.Window): info.name, info.encoding, info.revision, info.label = name, encoding, None, label info.last_stat = info.stat = os.stat(name) self.setFileInfo(f, info) - # update the syntax highlighting incase we changed the file - # extension + # update the syntax highlighting in case we changed the file extension syntax = theResources.guessSyntaxForFile(name, ss) if syntax is not None: self.setSyntax(syntax) diff --git a/windows-installer/build.py b/windows-installer/build.py index 490c6e6..7a75155 100755 --- a/windows-installer/build.py +++ b/windows-installer/build.py @@ -46,7 +46,7 @@ def copyFile(src, dest, use_text_mode=False,enc=None): s = f.read() f.close() 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.write(s) f.close() @@ -190,12 +190,12 @@ for lang in os.listdir(d): while True: i = s.find('&#', idx) if i < 0: - a.append(unicode(s[idx:], 'latin_1')) + a.append(str(s[idx:], encoding='latin_1')) break - a.append(unicode(s[idx:i], 'latin_1')) + a.append(str(s[idx:i], encoding='latin_1')) i += 2 j = s.find(';', i) - a.append(unichr(int(s[i:j]))) + a.append(chr(int(s[i:j]))) idx = j + 1 s = ''.join(a) s = codecs.encode(s, 'utf-8') diff --git a/windows-installer/setup.new.py b/windows-installer/setup.new.py index 2bb4c85..8750ef7 100644 --- a/windows-installer/setup.new.py +++ b/windows-installer/setup.new.py @@ -10,14 +10,14 @@ gnomeSiteDir = os.path.join(siteDir, "gnome") # This is the list of dll which are required by PyGI. # I get this list of DLL using http://technet.microsoft.com/en-us/sysinternals/bb896656.aspx -# Procedure: +# Procedure: # 1) Run your from from your IDE # 2) Command for using listdlls.exe # c:/path/to/listdlls.exe python.exe > output.txt -# 3) This would return lists of all dll required by you program -# in my case most of dll file were located in c:\python27\Lib\site-packages\gnome +# 3) This would return lists of all dll required by you program +# in my case most of dll file were located in c:\python27\Lib\site-packages\gnome # (I am using PyGI (all in one) installer) -# 4) Below is the list of gnome dll I received from listdlls.exe result. +# 4) Below is the list of gnome dll I received from listdlls.exe result. # If you prefer you can import all dlls from c:\python27\Lib\site-packages\gnome folder #missingDll = glob.glob(gnomeSiteDir + "\\" + '*.dll') @@ -142,10 +142,10 @@ for dll in missingDll: includeFiles.append((os.path.join(gnomeSiteDir, dll), dll)) #includeFiles.append(dll) -# You can import all Gtk Runtime data from gtk folder +# You can import all Gtk Runtime data from gtk folder #gnomeLibs= ['etc','lib','share'] -# You can import only important Gtk Runtime data from gtk folder +# You can import only important Gtk Runtime data from gtk folder gnomeLibs = [ 'lib\\gdk-pixbuf-2.0', 'lib\\girepository-1.0', @@ -196,7 +196,7 @@ def copyFile(src, dest, use_text_mode=False,enc=None): s = f.read() f.close() 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.write(s) f.close() From 362c59f1500639ae016ba0c3a6aa3379f8ae7199 Mon Sep 17 00:00:00 2001 From: Romain Failliot Date: Mon, 22 Nov 2021 19:01:32 -0500 Subject: [PATCH 2/5] Add flake8 job in CI --- .github/workflows/main.yml | 15 +++++++-------- requirements.txt | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a77a65..6eb2d3d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,20 +17,18 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - uses: actions/setup-python@v2 + - run: sudo apt install libgirepository1.0-dev + - run: pip install -r requirements.txt - - name: Pylint - uses: cclauss/GitHub-Action-for-pylint@master - with: - args: "apk add --no-cache gtk+3.0-dev gobject-introspection-dev ; pip install -r requirements.txt ; pylint src/**/*.py" + - name: Flake8 + run: flake8 src/ meson-build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - with: - fetch-depth: 0 + - uses: actions/setup-python@v2 - name: Install dependencies run: sudo apt-get -y install appstream appstream-util desktop-file-utils gettext @@ -52,6 +50,7 @@ jobs: options: --privileged steps: - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 - name: Flatpak builder uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v3 with: diff --git a/requirements.txt b/requirements.txt index 95fc636..b36b8eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -PyGObject~=3.40 -pylint~=2.11 +flake8 ~= 3.8 +PyGObject ~= 3.40 From 8d25396f681dbbbb28031a98a9c2f6561ceb0fda Mon Sep 17 00:00:00 2001 From: Romain Failliot Date: Mon, 22 Nov 2021 20:11:51 -0500 Subject: [PATCH 3/5] Fix all flake8 errors --- .flake8 | 6 + src/diffuse/dialogs.py | 31 ++-- src/diffuse/main.py | 137 +++++++++-------- src/diffuse/preferences.py | 155 ++++++++++--------- src/diffuse/resources.py | 75 ++++----- src/diffuse/utils.py | 60 +++++--- src/diffuse/vcs/bzr.py | 23 +-- src/diffuse/vcs/cvs.py | 18 ++- src/diffuse/vcs/darcs.py | 19 +-- src/diffuse/vcs/folder_set.py | 1 + src/diffuse/vcs/git.py | 23 +-- src/diffuse/vcs/hg.py | 19 +-- src/diffuse/vcs/mtn.py | 26 ++-- src/diffuse/vcs/rcs.py | 17 ++- src/diffuse/vcs/svk.py | 1 + src/diffuse/vcs/svn.py | 27 ++-- src/diffuse/vcs/vcs_registry.py | 10 ++ src/diffuse/widgets.py | 259 +++++++++++++++++--------------- 18 files changed, 487 insertions(+), 420 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..35abd39 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +builtins = _ +max-line-length = 100 + +# Temporary +exclude = src/diffuse/main.py diff --git a/src/diffuse/dialogs.py b/src/diffuse/dialogs.py index 4701014..c1b9c63 100644 --- a/src/diffuse/dialogs.py +++ b/src/diffuse/dialogs.py @@ -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): @@ -41,8 +38,8 @@ class AboutDialog(Gtk.AboutDialog): self.set_comments(_('Diffuse is a graphical tool for merging and comparing text files.')) self.set_copyright(constants.COPYRIGHT) self.set_website(constants.WEBSITE) - self.set_authors([ 'Derrick Moser ', - 'Romain Failliot ' ]) + self.set_authors(['Derrick Moser ', + 'Romain Failliot ']) self.set_translator_credits(_('translator-credits')) license_text = [ constants.APP_NAME + ' ' + constants.VERSION + '\n\n', @@ -59,9 +56,10 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along 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)) + # custom dialogue for picking files with widgets for specifying the encoding # and revision class FileChooserDialog(Gtk.FileChooserDialog): @@ -85,7 +83,7 @@ class FileChooserDialog(Gtk.FileChooserDialog): label.show() self.encoding = entry = utils.EncodingMenu( prefs, - action in [ Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER ]) + action in [Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.SELECT_FOLDER]) hbox.pack_start(entry, False, False, 5) entry.show() if rev: @@ -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 diff --git a/src/diffuse/main.py b/src/diffuse/main.py index b19f34b..b7dd5de 100644 --- a/src/diffuse/main.py +++ b/src/diffuse/main.py @@ -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 @@ -120,10 +117,10 @@ class Diffuse(Gtk.Window): def __init__(self): Gtk.Box.__init__(self, orientation = Gtk.Orientation.HORIZONTAL, spacing = 0) _append_buttons(self, Gtk.IconSize.MENU, [ - [ Gtk.STOCK_OPEN, self.button_cb, 'open', _('Open File...') ], - [ Gtk.STOCK_REFRESH, self.button_cb, 'reload', _('Reload File') ], - [ Gtk.STOCK_SAVE, self.button_cb, 'save', _('Save File') ], - [ Gtk.STOCK_SAVE_AS, self.button_cb, 'save_as', _('Save File As...') ] ]) + [Gtk.STOCK_OPEN, self.button_cb, 'open', _('Open File...')], + [Gtk.STOCK_REFRESH, self.button_cb, 'reload', _('Reload File')], + [Gtk.STOCK_SAVE, self.button_cb, 'save', _('Save File')], + [Gtk.STOCK_SAVE_AS, self.button_cb, 'save_as', _('Save File As...') ]]) self.label = label = Gtk.Label.new() label.set_selectable(True) @@ -258,7 +255,7 @@ class Diffuse(Gtk.Window): self.connect('format-changed', self.format_changed_cb) for i, darea in enumerate(self.dareas): - darea.drag_dest_set(Gtk.DestDefaults.ALL, [ Gtk.TargetEntry.new('text/uri-list', 0, 0) ], Gdk.DragAction.COPY) + darea.drag_dest_set(Gtk.DestDefaults.ALL, [Gtk.TargetEntry.new('text/uri-list', 0, 0)], Gdk.DragAction.COPY) darea.connect('drag-data-received', self.drag_data_received_cb, i) # initialise status self.updateStatus() @@ -749,7 +746,7 @@ class Diffuse(Gtk.Window): [], [_('Pr_eferences...'), self.preferences_cb, None, Gtk.STOCK_PREFERENCES, 'preferences'] ] ]) - submenudef = [ [_('None'), self.syntax_cb, None, None, 'no_syntax_highlighting', True, None, ('syntax', None) ] ] + submenudef = [[_('None'), self.syntax_cb, None, None, 'no_syntax_highlighting', True, None, ('syntax', None) ]] names = theResources.getSyntaxNames() if len(names) > 0: submenudef.append([]) @@ -820,28 +817,28 @@ class Diffuse(Gtk.Window): # create button bar hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) _append_buttons(hbox, Gtk.IconSize.LARGE_TOOLBAR, [ - [ DIFFUSE_STOCK_NEW_2WAY_MERGE, self.new_2_way_file_merge_cb, None, _('New 2-Way File Merge') ], - [ DIFFUSE_STOCK_NEW_3WAY_MERGE, self.new_3_way_file_merge_cb, None, _('New 3-Way File Merge') ], + [DIFFUSE_STOCK_NEW_2WAY_MERGE, self.new_2_way_file_merge_cb, None, _('New 2-Way File Merge')], + [DIFFUSE_STOCK_NEW_3WAY_MERGE, self.new_3_way_file_merge_cb, None, _('New 3-Way File Merge')], [], - [ Gtk.STOCK_EXECUTE, self.button_cb, 'realign_all', _('Realign All') ], - [ Gtk.STOCK_GOTO_TOP, self.button_cb, 'first_difference', _('First Difference') ], - [ Gtk.STOCK_GO_UP, self.button_cb, 'previous_difference', _('Previous Difference') ], - [ Gtk.STOCK_GO_DOWN, self.button_cb, 'next_difference', _('Next Difference') ], - [ Gtk.STOCK_GOTO_BOTTOM, self.button_cb, 'last_difference', _('Last Difference') ], + [Gtk.STOCK_EXECUTE, self.button_cb, 'realign_all', _('Realign All')], + [Gtk.STOCK_GOTO_TOP, self.button_cb, 'first_difference', _('First Difference')], + [Gtk.STOCK_GO_UP, self.button_cb, 'previous_difference', _('Previous Difference')], + [Gtk.STOCK_GO_DOWN, self.button_cb, 'next_difference', _('Next Difference')], + [Gtk.STOCK_GOTO_BOTTOM, self.button_cb, 'last_difference', _('Last Difference')], [], - [ Gtk.STOCK_GOTO_LAST, self.button_cb, 'copy_selection_right', _('Copy Selection Right') ], - [ Gtk.STOCK_GOTO_FIRST, self.button_cb, 'copy_selection_left', _('Copy Selection Left') ], - [ Gtk.STOCK_GO_FORWARD, self.button_cb, 'copy_left_into_selection', _('Copy Left Into Selection') ], - [ Gtk.STOCK_GO_BACK, self.button_cb, 'copy_right_into_selection', _('Copy Right Into Selection') ], - [ DIFFUSE_STOCK_LEFT_RIGHT, self.button_cb, 'merge_from_left_then_right', _('Merge From Left Then Right') ], - [ DIFFUSE_STOCK_RIGHT_LEFT, self.button_cb, 'merge_from_right_then_left', _('Merge From Right Then Left') ], + [Gtk.STOCK_GOTO_LAST, self.button_cb, 'copy_selection_right', _('Copy Selection Right')], + [Gtk.STOCK_GOTO_FIRST, self.button_cb, 'copy_selection_left', _('Copy Selection Left')], + [Gtk.STOCK_GO_FORWARD, self.button_cb, 'copy_left_into_selection', _('Copy Left Into Selection')], + [Gtk.STOCK_GO_BACK, self.button_cb, 'copy_right_into_selection', _('Copy Right Into Selection')], + [DIFFUSE_STOCK_LEFT_RIGHT, self.button_cb, 'merge_from_left_then_right', _('Merge From Left Then Right')], + [DIFFUSE_STOCK_RIGHT_LEFT, self.button_cb, 'merge_from_right_then_left', _('Merge From Right Then Left')], [], - [ Gtk.STOCK_UNDO, self.button_cb, 'undo', _('Undo') ], - [ Gtk.STOCK_REDO, self.button_cb, 'redo', _('Redo') ], - [ Gtk.STOCK_CUT, self.button_cb, 'cut', _('Cut') ], - [ Gtk.STOCK_COPY, self.button_cb, 'copy', _('Copy') ], - [ Gtk.STOCK_PASTE, self.button_cb, 'paste', _('Paste') ], - [ Gtk.STOCK_CLEAR, self.button_cb, 'clear_edits', _('Clear Edits') ] ]) + [Gtk.STOCK_UNDO, self.button_cb, 'undo', _('Undo')], + [Gtk.STOCK_REDO, self.button_cb, 'redo', _('Redo')], + [Gtk.STOCK_CUT, self.button_cb, 'cut', _('Cut')], + [Gtk.STOCK_COPY, self.button_cb, 'copy', _('Copy')], + [Gtk.STOCK_PASTE, self.button_cb, 'paste', _('Paste')], + [Gtk.STOCK_CLEAR, self.button_cb, 'clear_edits', _('Clear Edits') ]]) # avoid the button bar from dictating the minimum window size hbox.set_size_request(0, hbox.get_size_request()[1]) vbox.pack_start(hbox, False, False, 0) @@ -986,7 +983,7 @@ class Diffuse(Gtk.Window): treeview.connect('row-activated', self._confirmClose_row_activated_cb, model) 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) @@ -1017,7 +1014,7 @@ class Diffuse(Gtk.Window): nb = self.notebook if nb.get_n_pages() > 1: # warn about losing unsaved changes before removing a tab - if self.confirmCloseViewers([ data ]): + if self.confirmCloseViewers([data]): self.closed_tabs.append((nb.page_num(data), data, nb.get_tab_label(data))) nb.remove(data) nb.set_show_tabs(self.prefs.getBool('tabs_always_show') or nb.get_n_pages() > 1) @@ -1180,9 +1177,9 @@ class Diffuse(Gtk.Window): # create a new viewer for each item in 'items' def createSeparateTabs(self, items, labels, options): # all tabs inherit the first tab's revision and encoding specifications - items = [ (name, items[0][1]) for name, data in items ] + items = [(name, items[0][1]) for name, data in items] for item in _assign_file_labels(items, labels): - self.newLoadedFileDiffViewer([ item ]).setOptions(options) + self.newLoadedFileDiffViewer([item]).setOptions(options) # create a new viewer for each modified file found in 'items' def createCommitFileTabs(self, items, labels, options): @@ -1196,7 +1193,7 @@ class Diffuse(Gtk.Window): if dn == old_dn: break if len(new_items) == 0 or dn != new_items[-1][0]: - new_items.append([ dn, None, [] ]) + new_items.append([dn, None, []]) dst = new_items[-1] dst[1] = data[-1][1] dst[2].append(name) @@ -1225,7 +1222,7 @@ class Diffuse(Gtk.Window): if dn == old_dn: break if len(new_items) == 0 or dn != new_items[-1][0]: - new_items.append([ dn, None, [] ]) + new_items.append([dn, None, []]) dst = new_items[-1] dst[1] = data[-1][1] dst[2].append(name) @@ -1251,7 +1248,7 @@ class Diffuse(Gtk.Window): # returns True if the application can safely quit def confirmQuit(self): nb = self.notebook - return self.confirmCloseViewers([ nb.get_nth_page(i) for i in range(nb.get_n_pages()) ]) + return self.confirmCloseViewers([nb.get_nth_page(i) for i in range(nb.get_n_pages())]) # respond to close window request from the window manager def delete_cb(self, widget, event): @@ -1279,7 +1276,7 @@ class Diffuse(Gtk.Window): rev = None dialog.destroy() if accept: - viewer = self.newLoadedFileDiffViewer([ (name, [ (rev, encoding) ], None) ]) + viewer = self.newLoadedFileDiffViewer([(name, [(rev, encoding)], None)]) self.notebook.set_current_page(self.notebook.get_n_pages() - 1) viewer.grab_focus() @@ -1293,7 +1290,7 @@ class Diffuse(Gtk.Window): dialog.destroy() if accept: n = self.notebook.get_n_pages() - self.createModifiedFileTabs([ (name, [ (None, encoding) ]) ], [], {}) + self.createModifiedFileTabs([(name, [(None, encoding)])], [], {}) if self.notebook.get_n_pages() > n: # we added some new tabs, focus on the first one self.notebook.set_current_page(n) @@ -1311,7 +1308,7 @@ class Diffuse(Gtk.Window): dialog.destroy() if accept: n = self.notebook.get_n_pages() - self.createCommitFileTabs([ (name, [ (None, encoding) ]) ], [], { 'commit': rev }) + self.createCommitFileTabs([(name, [(None, encoding)])], [], { 'commit': rev }) if self.notebook.get_n_pages() > n: # we added some new tabs, focus on the first one self.notebook.set_current_page(n) @@ -1488,9 +1485,9 @@ class Diffuse(Gtk.Window): if utils.isWindows(): # help documentation is distributed as local HTML files # search for localised manual first - parts = [ 'manual' ] + parts = ['manual'] if utils.lang is not None: - parts = [ 'manual' ] + parts = ['manual'] parts.extend(utils.lang.split('_')) while len(parts) > 0: help_file = os.path.join(utils.bin_dir, '_'.join(parts) + '.html') @@ -1524,7 +1521,7 @@ class Diffuse(Gtk.Window): d = 'C' help_file = os.path.join(os.path.join(s, d), 'diffuse.xml') if os.path.isfile(help_file): - args = [ browser, _path2url(help_file, 'ghelp') ] + args = [browser, _path2url(help_file, 'ghelp')] # spawnvp is not available on some systems, use spawnv instead os.spawnv(os.P_NOWAIT, args[0], args) return @@ -1582,7 +1579,7 @@ def _append_buttons(box, size, specs): # constructs a full URL for the named file def _path2url(path, proto='file'): - r = [ proto, ':///' ] + r = [proto, ':///'] s = os.path.abspath(path) i = 0 while i < len(s) and s[i] == os.sep: @@ -1626,11 +1623,11 @@ def main(): args = sys.argv argc = len(args) - if argc == 2 and args[1] in [ '-v', '--version' ]: + if argc == 2 and args[1] in ['-v', '--version']: print('%s %s\n%s' % (constants.APP_NAME, constants.VERSION, constants.COPYRIGHT)) return 0 - if argc == 2 and args[1] in [ '-h', '-?', '--help' ]: + if argc == 2 and args[1] in ['-h', '-?', '--help']: print(_('''Usage: diffuse [ [OPTION...] [FILE...] ]... diffuse ( -h | -? | --help | -v | --version ) @@ -1683,7 +1680,7 @@ Display Options: subdirs = ['diffuse'] if data_dir is None: data_dir = os.path.expanduser('~') - subdirs[:0] = [ '.local', 'share' ] + subdirs[:0] = ['.local', 'share'] data_dir = utils.make_subdirs(data_dir, subdirs) # load resource files @@ -1739,58 +1736,58 @@ Display Options: while i < argc: arg = args[i] if len(arg) > 0 and arg[0] == '-': - if i + 1 < argc and arg in [ '-c', '--commit' ]: + if i + 1 < argc and arg in ['-c', '--commit']: # specified revision funcs[mode](specs, labels, options) i += 1 rev = args[i] specs, labels, options = [], [], { 'commit': args[i] } mode = 'commit' - elif arg in [ '-D', '--close-if-same' ]: + elif arg in ['-D', '--close-if-same']: close_on_same = True - elif i + 1 < argc and arg in [ '-e', '--encoding' ]: + elif i + 1 < argc and arg in ['-e', '--encoding']: i += 1 encoding = args[i] encoding = encodings.aliases.aliases.get(encoding, encoding) - elif arg in [ '-m', '--modified' ]: + elif arg in ['-m', '--modified']: funcs[mode](specs, labels, options) specs, labels, options = [], [], {} mode = 'modified' - elif i + 1 < argc and arg in [ '-r', '--revision' ]: + elif i + 1 < argc and arg in ['-r', '--revision']: # specified revision i += 1 revs.append((args[i], encoding)) - elif arg in [ '-s', '--separate' ]: + elif arg in ['-s', '--separate']: funcs[mode](specs, labels, options) specs, labels, options = [], [], {} # open items in separate tabs mode = 'separate' - elif arg in [ '-t', '--tab' ]: + elif arg in ['-t', '--tab']: funcs[mode](specs, labels, options) specs, labels, options = [], [], {} # start a new tab mode = 'single' - elif i + 1 < argc and arg in [ '-V', '--vcs' ]: + elif i + 1 < argc and arg in ['-V', '--vcs']: i += 1 diff.prefs.setString('vcs_search_order', args[i]) diff.preferences_updated() - elif arg in [ '-b', '--ignore-space-change' ]: + elif arg in ['-b', '--ignore-space-change']: diff.prefs.setBool('display_ignore_whitespace_changes', True) diff.prefs.setBool('align_ignore_whitespace_changes', True) diff.preferences_updated() - elif arg in [ '-B', '--ignore-blank-lines' ]: + elif arg in ['-B', '--ignore-blank-lines']: diff.prefs.setBool('display_ignore_blanklines', True) diff.prefs.setBool('align_ignore_blanklines', True) diff.preferences_updated() - elif arg in [ '-E', '--ignore-end-of-line' ]: + elif arg in ['-E', '--ignore-end-of-line']: diff.prefs.setBool('display_ignore_endofline', True) diff.prefs.setBool('align_ignore_endofline', True) diff.preferences_updated() - elif arg in [ '-i', '--ignore-case' ]: + elif arg in ['-i', '--ignore-case']: diff.prefs.setBool('display_ignore_case', True) diff.prefs.setBool('align_ignore_case', True) diff.preferences_updated() - elif arg in [ '-w', '--ignore-all-space' ]: + elif arg in ['-w', '--ignore-all-space']: diff.prefs.setBool('display_ignore_whitespace', True) diff.prefs.setBool('align_ignore_whitespace', True) diff.preferences_updated() @@ -1828,8 +1825,8 @@ Display Options: revs = [] had_specs = True i += 1 - if mode in [ 'modified', 'commit' ] and len(specs) == 0: - specs.append((os.curdir, [ (None, encoding) ])) + if mode in ['modified', 'commit'] and len(specs) == 0: + specs.append((os.curdir, [(None, encoding)])) had_specs = True funcs[mode](specs, labels, options) diff --git a/src/diffuse/preferences.py b/src/diffuse/preferences.py index 029e6a7..06497f9 100644 --- a/src/diffuse/preferences.py +++ b/src/diffuse/preferences.py @@ -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: @@ -52,7 +49,7 @@ class Preferences: else: svk_bin = 'svk' - auto_detect_codecs = [ 'utf_8', 'utf_16', 'latin_1' ] + auto_detect_codecs = ['utf_8', 'utf_16', 'latin_1'] e = utils.norm_encoding(sys.getfilesystemencoding()) if e not in auto_detect_codecs: # insert after UTF-8 as the default encoding may prevent UTF-8 from @@ -75,50 +72,53 @@ class Preferences: # [ 'String', name, default, label ] # [ 'File', name, default, label ] # [ 'Font', name, default, label ] - # pylint: disable=line-too-long self.template = [ 'FolderSet', _('Display'), - [ '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') ], - [ 'Integer', 'display_right_margin', 80, _('Right margin'), 1, 8192 ], - [ '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_blanklines', False, _('Ignore blank line differences') ], - [ 'Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences') ] + [ + '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')], + ['Integer', 'display_right_margin', 80, _('Right margin'), 1, 8192], + ['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')], # 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', - [ '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_blanklines', False, _('Ignore blank lines') ], - [ 'Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters') ] + [ + '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')], # noqa: E501 + ['Boolean', 'align_ignore_blanklines', False, _('Ignore blank lines')], + ['Boolean', 'align_ignore_endofline', True, _('Ignore end of line characters')] ], _('Editor'), - [ '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 ] + [ + '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', - [ '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 ] + [ + '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] # 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 = { @@ -138,22 +138,21 @@ class Preferences: root += '\\' self.template.extend([ _('Cygwin'), - [ 'List', - [ 'File', 'cygwin_root', os.path.join(root, 'cygwin'), _('Root directory') ], - [ 'String', 'cygwin_cygdrive_prefix', '/cygdrive', _('Cygdrive prefix') ] - ] + ['List', + ['File', 'cygwin_root', os.path.join(root, 'cygwin'), _('Root directory')], + ['String', 'cygwin_cygdrive_prefix', '/cygdrive', _('Cygdrive prefix')]] ]) # create template for Version Control options - vcs = [ ('bzr', 'Bazaar', 'bzr'), - ('cvs', 'CVS', 'cvs'), - ('darcs', 'Darcs', 'darcs'), - ('git', 'Git', 'git'), - ('hg', 'Mercurial', 'hg'), - ('mtn', 'Monotone', 'mtn'), - ('rcs', 'RCS', None), - ('svn', 'Subversion', 'svn'), - ('svk', 'SVK', svk_bin) ] + vcs = [('bzr', 'Bazaar', 'bzr'), + ('cvs', 'CVS', 'cvs'), + ('darcs', 'Darcs', 'darcs'), + ('git', 'Git', 'git'), + ('hg', 'Mercurial', 'hg'), + ('mtn', 'Monotone', 'mtn'), + ('rcs', 'RCS', None), + ('svn', 'Subversion', 'svn'), + ('svk', 'SVK', svk_bin)] vcs_template = [ 'List', [ @@ -163,15 +162,15 @@ class Preferences: _('Version control system search order') ] ] - vcs_folders_template = [ 'FolderSet' ] + vcs_folders_template = ['FolderSet'] for key, name, cmd in vcs: - temp = [ 'List' ] + temp = ['List'] if key == 'rcs': # RCS uses multiple commands - temp.extend([ [ 'File', key + '_bin_co', 'co', _('"co" command') ], - [ 'File', key + '_bin_rlog', 'rlog', _('"rlog" command') ] ]) + temp.extend([['File', key + '_bin_co', 'co', _('"co" command')], + ['File', key + '_bin_rlog', 'rlog', _('"rlog" command')]]) else: - temp.extend([ [ 'File', key + '_bin', cmd, _('Command') ] ]) + temp.extend([['File', key + '_bin', cmd, _('Command')]]) if utils.isWindows(): temp.append([ 'Boolean', @@ -186,10 +185,10 @@ class Preferences: False, _('Update paths for Cygwin') ]) - vcs_folders_template.extend([ name, temp ]) + vcs_folders_template.extend([name, temp]) vcs_template.append(vcs_folders_template) - self.template.extend([ _('Version Control'), vcs_template ]) + self.template.extend([_('Version Control'), vcs_template]) self._initFromTemplate(self.template) self.default_bool_prefs = self.bool_prefs.copy() self.default_int_prefs = self.int_prefs.copy() @@ -208,7 +207,9 @@ class Preferences: if len(a) == 2 and p in self.bool_prefs: 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: @@ -238,7 +239,7 @@ class Preferences: self.int_prefs[template[1]] = template[2] self.int_prefs_min[template[1]] = template[4] self.int_prefs_max[template[1]] = template[5] - elif template[0] in [ 'String', 'File', 'Font', 'Encoding' ]: + elif template[0] in ['String', 'File', 'Font', 'Encoding']: self.string_prefs[template[1]] = template[2] # callback used when a preference is toggled @@ -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: @@ -337,7 +337,7 @@ class Preferences: label.set_yalign(0.5) table.attach(label, 0, i, 1, 1) label.show() - if tpl[0] in [ 'Font', 'Integer' ]: + if tpl[0] in ['Font', 'Integer']: entry = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) if tpl[0] == 'Font': button = _FontButton() @@ -393,12 +393,12 @@ class Preferences: # attempt to convert a string to unicode from an unknown encoding def convertToUnicode(self, s): # a BOM is required for autodetecting UTF16 and UTF32 - magic = { 'utf16': [ codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE ], - 'utf32': [ codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE ] } + magic = {'utf16': [codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE], + 'utf32': [codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE]} for encoding in self._getDefaultEncodings(): try: encoding = encoding.lower().replace('-', '').replace('_', '') - for m in magic.get(encoding, [ b'' ]): + for m in magic.get(encoding, [b'']): if s.startswith(m): break else: @@ -406,7 +406,7 @@ class Preferences: return str(s, encoding=encoding), encoding except (UnicodeDecodeError, LookupError): pass - return ''.join([ chr(ord(c)) for c in s ]), None + return ''.join([chr(ord(c)) for c in s]), None # cygwin and native applications can be used on windows, use this method # to convert a path to the usual form expected on sys.platform @@ -415,37 +415,37 @@ class Preferences: # treat as a cygwin path s = s.replace(os.sep, '/') # convert to a Windows native style path - p = [ a for a in s.split('/') if a != '' ] + p = [a for a in s.split('/') if a != ''] if s.startswith('//'): - p[:0] = [ '', '' ] + p[:0] = ['', ''] elif s.startswith('/'): - pr = [ a for a in self.getString('cygwin_cygdrive_prefix').split('/') if a != '' ] + pr = [a for a in self.getString('cygwin_cygdrive_prefix').split('/') if a != ''] n = len(pr) if len(p) > n and len(p[n]) == 1 and p[:n] == pr: # path starts with cygdrive prefix - p[:n + 1] = [ p[n] + ':' ] + p[:n + 1] = [p[n] + ':'] else: # full path - p[:0] = [ a for a in self.getString('cygwin_root').split(os.sep) if a != '' ] + p[:0] = [a for a in self.getString('cygwin_root').split(os.sep) if a != ''] # add trailing slash if p[-1] != '' and s.endswith('/'): p.append('') 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, diff --git a/src/diffuse/resources.py b/src/diffuse/resources.py index 9d9cf8d..f060849 100644 --- a/src/diffuse/resources.py +++ b/src/diffuse/resources.py @@ -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): @@ -142,8 +141,8 @@ class Resources: set_binding('line_mode', 'next_difference', 'n') set_binding('line_mode', 'last_difference', 'Ctrl+End') set_binding('line_mode', 'last_difference', 'Shift+N') - #set_binding('line_mode', 'copy_selection_right', 'Shift+L') - #set_binding('line_mode', 'copy_selection_left', 'Shift+H') + # set_binding('line_mode', 'copy_selection_right', 'Shift+L') + # set_binding('line_mode', 'copy_selection_left', 'Shift+H') set_binding('line_mode', 'copy_left_into_selection', 'Shift+L') set_binding('line_mode', 'copy_right_into_selection', 'Shift+H') set_binding('line_mode', 'merge_from_left_then_right', 'm') @@ -170,31 +169,33 @@ class Resources: # default colours self.colours = { - 'alignment' : _Colour(1.0, 1.0, 0.0), - 'character_selection' : _Colour(0.7, 0.7, 1.0), - 'cursor' : _Colour(0.0, 0.0, 0.0), - 'difference_1' : _Colour(1.0, 0.625, 0.625), - 'difference_2' : _Colour(0.85, 0.625, 0.775), - 'difference_3' : _Colour(0.85, 0.775, 0.625), - 'hatch' : _Colour(0.8, 0.8, 0.8), - 'line_number' : _Colour(0.0, 0.0, 0.0), - 'line_number_background' : _Colour(0.75, 0.75, 0.75), - 'line_selection' : _Colour(0.7, 0.7, 1.0), - 'map_background' : _Colour(0.6, 0.6, 0.6), - 'margin' : _Colour(0.8, 0.8, 0.8), - '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) } + 'alignment': _Colour(1.0, 1.0, 0.0), + 'character_selection': _Colour(0.7, 0.7, 1.0), + 'cursor': _Colour(0.0, 0.0, 0.0), + 'difference_1': _Colour(1.0, 0.625, 0.625), + 'difference_2': _Colour(0.85, 0.625, 0.775), + 'difference_3': _Colour(0.85, 0.775, 0.625), + 'hatch': _Colour(0.8, 0.8, 0.8), + 'line_number': _Colour(0.0, 0.0, 0.0), + 'line_number_background': _Colour(0.75, 0.75, 0.75), + 'line_selection': _Colour(0.7, 0.7, 1.0), + 'map_background': _Colour(0.6, 0.6, 0.6), + 'margin': _Colour(0.8, 0.8, 0.8), + '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) + } # default floats self.floats = { - 'alignment_opacity' : 1.0, - 'character_difference_opacity' : 0.4, - 'character_selection_opacity' : 0.4, - 'edited_opacity' : 0.4, - 'line_difference_opacity' : 0.3, - 'line_selection_opacity' : 0.4 } + 'alignment_opacity': 1.0, + 'character_difference_opacity': 0.4, + 'character_selection_opacity': 0.4, + 'edited_opacity': 0.4, + 'line_difference_opacity': 0.3, + '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: @@ -265,7 +266,7 @@ class Resources: def getKeyBindings(self, ctx, s): try: - return [ t for c, t in self.keybindings[(ctx, s)].keys() ] + return [t for c, t in self.keybindings[(ctx, s)].keys()] except KeyError: return [] @@ -351,7 +352,7 @@ class Resources: path = os.path.join(utils.globEscape(os.path.dirname(file_name)), path) paths = glob.glob(path) if len(paths) == 0: - paths = [ path ] + paths = [path] for path in paths: # convert to absolute path so the location of # any processing errors are reported with @@ -363,7 +364,7 @@ class Resources: self.setKeyBinding(args[1], args[2], args[3]) # eg. set the regular background colour to white # colour text_background 1.0 1.0 1.0 - elif args[0] in [ 'colour', 'color' ] and len(args) == 5: + elif args[0] in ['colour', 'color'] and len(args) == 5: self.colours[args[1]] = _Colour(float(args[2]), float(args[3]), float(args[4])) # eg. set opacity of the line_selection colour # float line_selection_opacity 0.4 @@ -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 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 @@ -502,7 +504,7 @@ class _SyntaxParser: # mappings from a state to a list of (pattern, token_type, next_state) # tuples indicating the new state for the state machine when 'pattern' # is matched and how to classify the matched characters - self.transitions_lookup = { initial_state : [] } + self.transitions_lookup = {initial_state: []} # Adds a new edge to the finite state machine from prev_state to # next_state. Characters will be identified as token_type when pattern is @@ -536,4 +538,5 @@ class _SyntaxParser: start = end return state_name, blocks + theResources = Resources() diff --git a/src/diffuse/utils.py b/src/diffuse/utils.py index e104952..b121343 100644 --- a/src/diffuse/utils.py +++ b/src/diffuse/utils.py @@ -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): +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,20 +120,22 @@ 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(): if _drive_from_path(a) != _drive_from_path(b): return b - c1 = [ c for c in a.split(os.sep) if c != '' ] - c2 = [ c for c in b.split(os.sep) if c != '' ] + c1 = [c for c in a.split(os.sep) if c != ''] + c2 = [c for c in b.split(os.sep) if c != ''] i, n = 0, len(c1) while i < n and i < len(c2) and c1[i] == c2[i]: i += 1 - r = (n - i) * [ os.pardir ] + r = (n - i) * [os.pardir] 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,24 +147,28 @@ 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: - success_results = [ 0 ] + success_results = [0] if isWindows() and prefs.getBool(bash_pref): # launch the command from a bash shell is requested cmd = [ 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(): @@ -166,9 +178,8 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None): else: info = None if _use_flatpak(): - cmd = [ 'flatpak-spawn', '--host' ] + cmd - with ( - subprocess.Popen( + cmd = ['flatpak-spawn', '--host'] + cmd + 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,28 +210,34 @@ 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 ] + 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 ]) + 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]: @@ -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 diff --git a/src/diffuse/vcs/bzr.py b/src/diffuse/vcs/bzr.py index 53f6ad9..3277f01 100644 --- a/src/diffuse/vcs/bzr.py +++ b/src/diffuse/vcs/bzr.py @@ -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): @@ -30,13 +31,13 @@ class Bzr(VcsInterface): left = name + '.OTHER' right = name + '.THIS' if os.path.isfile(left) and os.path.isfile(right): - return [ (left, None), (name, None), (right, None) ] + return [(left, None), (name, None), (right, None)] # default case - return [ (name, '-1'), (name, None) ] + return [(name, '-1'), (name, None)] def getCommitTemplate(self, prefs, rev, names): # build command - args = [ prefs.getString('bzr_bin'), 'log', '-v', '-r', rev ] + args = [prefs.getString('bzr_bin'), 'log', '-v', '-r', rev] # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False for name in names: @@ -62,7 +63,7 @@ class Bzr(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, rev) ] + added[k] = [(None, None), (k, rev)] elif s.startswith('modified:'): # modified files while i < n and ss[i].startswith(' '): @@ -73,7 +74,7 @@ class Bzr(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - modified[k] = [ (k, prev), (k, rev) ] + modified[k] = [(k, prev), (k, rev)] elif s.startswith('removed:'): # removed files while i < n and ss[i].startswith(' '): @@ -84,7 +85,7 @@ class Bzr(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] elif s.startswith('renamed:'): # renamed files while i < n and ss[i].startswith(' '): @@ -100,7 +101,7 @@ class Bzr(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - renamed[k1] = [ (k0, prev), (k1, rev) ] + renamed[k1] = [(k0, prev), (k1, rev)] # sort the results result, r = [], set() for m in removed, added, modified, renamed: @@ -113,7 +114,7 @@ class Bzr(VcsInterface): def getFolderTemplate(self, prefs, names): # build command - args = [ prefs.getString('bzr_bin'), 'status', '-SV' ] + args = [prefs.getString('bzr_bin'), 'status', '-SV'] # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False for name in names: @@ -136,7 +137,7 @@ class Bzr(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] elif y == 'N': # added k = prefs.convertToNativePath(k) @@ -145,7 +146,7 @@ class Bzr(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, None) ] + added[k] = [(None, None), (k, None)] elif y == 'M': # modified or merge conflict k = prefs.convertToNativePath(k) @@ -168,7 +169,7 @@ class Bzr(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - renamed[k1] = [ (k0, prev), (k1, None) ] + renamed[k1] = [(k0, prev), (k1, None)] # sort the results result, r = [], set() for m in removed, added, modified, renamed: diff --git a/src/diffuse/vcs/cvs.py b/src/diffuse/vcs/cvs.py index b39318f..56f33a2 100644 --- a/src/diffuse/vcs/cvs.py +++ b/src/diffuse/vcs/cvs.py @@ -23,10 +23,11 @@ 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): - return [ (name, 'BASE'), (name, None) ] + return [(name, 'BASE'), (name, None)] def getCommitTemplate(self, prefs, rev, names): result = [] @@ -45,14 +46,14 @@ class Cvs(VcsInterface): k0 = None else: k0 = k - result.append([ (k0, prev), (k, rev) ]) + result.append([(k0, prev), (k, rev)]) except ValueError: utils.logError(_('Error parsing revision %s.') % (rev, )) return result def getFolderTemplate(self, prefs, names): # build command - args = [ prefs.getString('cvs_bin'), '-nq', 'update', '-R' ] + args = [prefs.getString('cvs_bin'), '-nq', 'update', '-R'] # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False for name in names: @@ -72,15 +73,15 @@ class Cvs(VcsInterface): k = utils.relpath(pwd, k) if s[0] == 'R': # removed - modified[k] = [ (k, prev), (None, None) ] + modified[k] = [(k, prev), (None, None)] elif s[0] == 'A': # added - modified[k] = [ (None, None), (k, None) ] + modified[k] = [(None, None), (k, None)] else: # modified - modified[k] = [ (k, prev), (k, None) ] + modified[k] = [(k, prev), (k, None)] # sort the results - return [ modified[k] for k in sorted(modified.keys()) ] + return [modified[k] for k in sorted(modified.keys())] def getRevision(self, prefs, name, rev): if rev == 'BASE' and not os.path.exists(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( diff --git a/src/diffuse/vcs/darcs.py b/src/diffuse/vcs/darcs.py index d5ef3bc..28331f3 100644 --- a/src/diffuse/vcs/darcs.py +++ b/src/diffuse/vcs/darcs.py @@ -23,15 +23,16 @@ 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): - return [ (name, ''), (name, None) ] + return [(name, ''), (name, None)] def _getCommitTemplate(self, prefs, names, rev): mods = (rev is None) # build command - args = [ prefs.getString('darcs_bin') ] + args = [prefs.getString('darcs_bin')] if mods: args.extend(['whatsnew', '-s']) else: @@ -84,7 +85,7 @@ class Darcs(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] elif x == 'A': # added k = prefs.convertToNativePath(s[2:]) @@ -93,7 +94,7 @@ class Darcs(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, rev) ] + added[k] = [(None, None), (k, rev)] elif x == 'M': # modified k = prefs.convertToNativePath(s[2:].split(' ')[0]) @@ -103,7 +104,7 @@ class Darcs(VcsInterface): if not isabs: k = utils.relpath(pwd, k) if k not in renamed: - modified[k] = [ (k, prev), (k, rev) ] + modified[k] = [(k, prev), (k, rev)] elif x == ' ': # renamed k = s[1:].split(' -> ') @@ -117,7 +118,7 @@ class Darcs(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - renamed[k1] = [ (k0, prev), (k1, rev) ] + renamed[k1] = [(k0, prev), (k1, rev)] # sort the results result, r = [], set() for m in added, modified, removed, renamed: @@ -135,10 +136,10 @@ class Darcs(VcsInterface): return self._getCommitTemplate(prefs, names, None) def getRevision(self, prefs, name, rev): - args = [ prefs.getString('darcs_bin'), 'show', 'contents' ] + args = [prefs.getString('darcs_bin'), 'show', 'contents'] try: - args.extend([ '-n', str(int(rev)) ]) + args.extend(['-n', str(int(rev))]) except ValueError: - args.extend([ '-h', rev ]) + args.extend(['-h', rev]) args.append(utils.safeRelativePath(self.root, name, prefs, 'darcs_cygwin')) return utils.popenRead(self.root, args, prefs, 'darcs_bash') diff --git a/src/diffuse/vcs/folder_set.py b/src/diffuse/vcs/folder_set.py index 9fe050e..da1e0ba 100644 --- a/src/diffuse/vcs/folder_set.py +++ b/src/diffuse/vcs/folder_set.py @@ -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 diff --git a/src/diffuse/vcs/git.py b/src/diffuse/vcs/git.py index 5aabef5..7b1b915 100644 --- a/src/diffuse/vcs/git.py +++ b/src/diffuse/vcs/git.py @@ -23,14 +23,15 @@ 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): - return [ (name, 'HEAD'), (name, None) ] + return [(name, 'HEAD'), (name, None)] def getCommitTemplate(self, prefs, rev, names): # build command - args = [ prefs.getString('git_bin'), 'show', '--pretty=format:', '--name-status', rev ] + args = [prefs.getString('git_bin'), 'show', '--pretty=format:', '--name-status', rev] # build list of interesting files pwd = os.path.abspath(os.curdir) isabs = False @@ -50,15 +51,15 @@ class Git(VcsInterface): k = utils.relpath(pwd, k) if s[0] == 'D': # removed - modified[k] = [ (k, prev), (None, None) ] + modified[k] = [(k, prev), (None, None)] elif s[0] == 'A': # added - modified[k] = [ (None, None), (k, rev) ] + modified[k] = [(None, None), (k, rev)] else: # modified - modified[k] = [ (k, prev), (k, rev) ] + modified[k] = [(k, prev), (k, rev)] # sort the results - return [ modified[k] for k in sorted(modified.keys()) ] + return [modified[k] for k in sorted(modified.keys())] def _extractPath(self, s, prefs): return os.path.join(self.root, prefs.convertToNativePath(s.strip())) @@ -98,7 +99,7 @@ class Git(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - renamed[k1] = [ (k0, prev), (k1, None) ] + renamed[k1] = [(k0, prev), (k1, None)] elif x == 'U' or y == 'U' or (x == 'D' and y == 'D'): # merge conflict k = self._extractPath(k, prefs) @@ -106,9 +107,9 @@ class Git(VcsInterface): if not isabs: k = utils.relpath(pwd, k) if x == 'D': - panes = [ (None, None) ] + panes = [(None, None)] else: - panes = [ (k, ':2') ] + panes = [(k, ':2')] panes.append((k, None)) if y == 'D': panes.append((None, None)) @@ -124,9 +125,9 @@ class Git(VcsInterface): k = utils.relpath(pwd, k) if x == 'A': # added - panes = [ (None, None) ] + panes = [(None, None)] else: - panes = [ (k, prev) ] + panes = [(k, prev)] # staged changes if x == 'D': panes.append((None, None)) diff --git a/src/diffuse/vcs/hg.py b/src/diffuse/vcs/hg.py index 5cf550e..8a2d378 100644 --- a/src/diffuse/vcs/hg.py +++ b/src/diffuse/vcs/hg.py @@ -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): @@ -34,7 +35,7 @@ class Hg(VcsInterface): if self.working_rev is None: ss = utils.popenReadLines( self.root, - [ prefs.getString('hg_bin'), 'id', '-i', '-t' ], + [prefs.getString('hg_bin'), 'id', '-i', '-t'], prefs, 'hg_bash') if len(ss) != 1: @@ -49,11 +50,11 @@ class Hg(VcsInterface): return f'p1({rev})' def getFileTemplate(self, prefs, name): - return [ (name, self._getPreviousRevision(prefs, None)), (name, None) ] + return [(name, self._getPreviousRevision(prefs, None)), (name, None)] def _getCommitTemplate(self, prefs, names, cmd, rev): # build command - args = [ prefs.getString('hg_bin') ] + args = [prefs.getString('hg_bin')] args.extend(cmd) # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False @@ -74,25 +75,25 @@ class Hg(VcsInterface): k = utils.relpath(pwd, k) if s[0] == 'R': # removed - modified[k] = [ (k, prev), (None, None) ] + modified[k] = [(k, prev), (None, None)] elif s[0] == 'A': # added - modified[k] = [ (None, None), (k, rev) ] + modified[k] = [(None, None), (k, rev)] else: # modified or merge conflict - modified[k] = [ (k, prev), (k, rev) ] + modified[k] = [(k, prev), (k, rev)] # sort the results - return [ modified[k] for k in sorted(modified.keys()) ] + return [modified[k] for k in sorted(modified.keys())] def getCommitTemplate(self, prefs, rev, names): return self._getCommitTemplate( prefs, names, - [ 'log', '--template', 'A\t{file_adds}\nM\t{file_mods}\nR\t{file_dels}\n', '-r', rev ], + ['log', '--template', 'A\t{file_adds}\nM\t{file_mods}\nR\t{file_dels}\n', '-r', rev], rev) def getFolderTemplate(self, prefs, names): - return self._getCommitTemplate(prefs, names, [ 'status', '-q' ], None) + return self._getCommitTemplate(prefs, names, ['status', '-q'], None) def getRevision(self, prefs, name, rev): return utils.popenRead( diff --git a/src/diffuse/vcs/mtn.py b/src/diffuse/vcs/mtn.py index fc551f4..7330e32 100644 --- a/src/diffuse/vcs/mtn.py +++ b/src/diffuse/vcs/mtn.py @@ -24,23 +24,24 @@ 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): # FIXME: merge conflicts? - return [ (name, 'h:'), (name, None) ] + return [(name, 'h:'), (name, None)] def getCommitTemplate(self, prefs, rev, names): # build command vcs_bin = prefs.getString('mtn_bin') ss = utils.popenReadLines( self.root, - [ vcs_bin, 'automate', 'select', '-q', rev ], + [vcs_bin, 'automate', 'select', '-q', rev], prefs, 'mtn_bash') if len(ss) != 1: raise IOError('Ambiguous revision specifier') - args = [ vcs_bin, 'automate', 'get_revision', ss[0] ] + args = [vcs_bin, 'automate', 'get_revision', ss[0]] # build list of interesting files fs = FolderSet(names) pwd, isabs = os.path.abspath(os.curdir), False @@ -94,9 +95,10 @@ class Mtn(VcsInterface): removed_dirs = set() for s in utils.popenReadLines( self.root, - [ vcs_bin, 'automate', 'get_manifest_of', prev ], + [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]) @@ -117,12 +119,12 @@ class Mtn(VcsInterface): k = removed[k] if not isabs: k = utils.relpath(pwd, k) - result.append([ (k, prev), (None, None) ]) + result.append([(k, prev), (None, None)]) elif k in added: k = added[k] if not isabs: k = utils.relpath(pwd, k) - result.append([ (None, None), (k, rev) ]) + result.append([(None, None), (k, rev)]) else: if k in renamed: arg1, k0, k1 = renamed[k] @@ -131,7 +133,7 @@ class Mtn(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - result.append([ (k0, prev), (k1, rev) ]) + result.append([(k0, prev), (k1, rev)]) return result def getFolderTemplate(self, prefs, names): @@ -173,7 +175,7 @@ class Mtn(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] processed = True if 'added' in s and 'file' in m.get('new_type', []): # new file @@ -181,7 +183,7 @@ class Mtn(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, None) ] + added[k] = [(None, None), (k, None)] processed = True if ( 'rename_target' in s and @@ -195,7 +197,7 @@ class Mtn(VcsInterface): if not isabs: k0 = utils.relpath(pwd, k0) k1 = utils.relpath(pwd, k1) - renamed[k1] = [ (k0, prev), (k1, None) ] + renamed[k1] = [(k0, prev), (k1, None)] processed = True if not processed and 'file' in m.get('fs_type', []): # modified file or merge conflict @@ -203,7 +205,7 @@ class Mtn(VcsInterface): if fs.contains(k): if not isabs: k = utils.relpath(pwd, k) - modified[k] = [ (k, prev), (k, None) ] + modified[k] = [(k, prev), (k, None)] # sort the results r = set() for m in removed, added, modified, renamed: diff --git a/src/diffuse/vcs/rcs.py b/src/diffuse/vcs/rcs.py index 8d8fe01..b6c0b84 100644 --- a/src/diffuse/vcs/rcs.py +++ b/src/diffuse/vcs/rcs.py @@ -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): @@ -35,7 +36,7 @@ class Rcs(VcsInterface): for line in utils.popenReadLines(self.root, args, prefs, 'rcs_bash'): if line.startswith('head: '): rev = line[6:] - return [ (name, rev), (name, None) ] + return [(name, rev), (name, None)] def getCommitTemplate(self, prefs, rev, names): result = [] @@ -54,7 +55,7 @@ class Rcs(VcsInterface): k0 = None else: k0 = k - result.append([ (k0, prev), (k, rev) ]) + result.append([(k0, prev), (k, rev)]) except ValueError: utils.logError(_('Error parsing revision %s.') % (rev, )) return result @@ -64,11 +65,11 @@ class Rcs(VcsInterface): # os.sysconf() is only available on Unix if hasattr(os, 'sysconf'): maxsize = os.sysconf('SC_ARG_MAX') - maxsize -= sum([ len(k) + len(v) + 2 for k, v in os.environ.items() ]) + maxsize -= sum([len(k) + len(v) + 2 for k, v in os.environ.items()]) else: # assume the Window's limit to CreateProcess() maxsize = 32767 - maxsize -= sum([ len(k) + 1 for k in cmd ]) + maxsize -= sum([len(k) + 1 for k in cmd]) ss = [] i, s, a = 0, 0, [] @@ -91,14 +92,14 @@ class Rcs(VcsInterface): def getFolderTemplate(self, prefs, names): # build command - cmd = [ prefs.getString('rcs_bin_rlog'), '-L', '-h' ] + cmd = [prefs.getString('rcs_bin_rlog'), '-L', '-h'] # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False r = [] for k in names: if os.path.isdir(k): # the user specified a folder - n, ex = [ k ], True + n, ex = [k], True while len(n) > 0: s = n.pop() recurse = os.path.isdir(os.path.join(s, 'RCS')) @@ -135,7 +136,7 @@ class Rcs(VcsInterface): r.append(k) for k in r: isabs |= os.path.isabs(k) - args = [ utils.safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r ] + args = [utils.safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r] # run command r, k = {}, '' for line in self._popen_xargs_readlines(cmd, args, prefs, 'rcs_bash'): @@ -148,7 +149,7 @@ class Rcs(VcsInterface): elif line.startswith('head: '): r[k] = line[6:] # sort the results - return [ [ (k, r[k]), (k, None) ] for k in sorted(r.keys()) ] + return [[(k, r[k]), (k, None)] for k in sorted(r.keys())] def getRevision(self, prefs, name, rev): return utils.popenRead( diff --git a/src/diffuse/vcs/svk.py b/src/diffuse/vcs/svk.py index d4add43..03ed959 100644 --- a/src/diffuse/vcs/svk.py +++ b/src/diffuse/vcs/svk.py @@ -22,6 +22,7 @@ import os from diffuse import utils from diffuse.vcs.svn import Svn + class Svk(Svn): @staticmethod def _getVcs(): diff --git a/src/diffuse/vcs/svn.py b/src/diffuse/vcs/svn.py index 7aded8b..95824c5 100644 --- a/src/diffuse/vcs/svn.py +++ b/src/diffuse/vcs/svn.py @@ -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): @@ -60,7 +61,7 @@ class Svn(VcsInterface): if self.url is None: vcs, prefix = self._getVcs(), self._getURLPrefix() n = len(prefix) - args = [ prefs.getString(vcs + '_bin'), 'info' ] + args = [prefs.getString(vcs + '_bin'), 'info'] for s in utils.popenReadLines(self.root, args, prefs, vcs + '_bash'): if s.startswith(prefix): self.url = s[n:] @@ -74,15 +75,15 @@ class Svn(VcsInterface): left = glob.glob(escaped_name + '.merge-left.r*') right = glob.glob(escaped_name + '.merge-right.r*') if len(left) > 0 and len(right) > 0: - return [ (left[-1], None), (name, None), (right[-1], None) ] + return [(left[-1], None), (name, None), (right[-1], None)] # update conflict left = sorted(glob.glob(escaped_name + '.r*')) right = glob.glob(escaped_name + '.mine') right.extend(glob.glob(escaped_name + '.working')) if len(left) > 0 and len(right) > 0: - return [ (left[-1], None), (name, None), (right[0], None) ] + return [(left[-1], None), (name, None), (right[0], None)] # default case - return [ (name, self._getPreviousRevision(None)), (name, None) ] + return [(name, self._getPreviousRevision(None)), (name, None)] def _getCommitTemplate(self, prefs, rev, names): result = [] @@ -96,9 +97,9 @@ class Svn(VcsInterface): vcs = self._getVcs() vcs_bin, vcs_bash = prefs.getString(vcs + '_bin'), vcs + '_bash' if rev is None: - args = [ vcs_bin, 'status', '-q' ] + args = [vcs_bin, 'status', '-q'] else: - args = [ vcs_bin, 'diff', '--summarize', '-c', rev ] + args = [vcs_bin, 'diff', '--summarize', '-c', rev] # build list of interesting files pwd, isabs = os.path.abspath(os.curdir), False for name in names: @@ -129,7 +130,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, k) if not isabs: k = utils.relpath(pwd, k) - modified[k] = [ (k, prev), (k, rev) ] + modified[k] = [(k, prev), (k, rev)] elif v == 'C': # merge conflict modified[k] = self.getFileTemplate(prefs, k) @@ -146,7 +147,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, k) if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, None) ] + added[k] = [(None, None), (k, None)] else: m = {} for k in added: @@ -181,7 +182,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, os.path.join(p, s)) if not isabs: k = utils.relpath(pwd, k) - added[k] = [ (None, None), (k, rev) ] + added[k] = [(None, None), (k, rev)] # determine if removed items are files or directories if prev == 'BASE': m, removed = removed, {} @@ -191,7 +192,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, k) if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] else: m = {} for k in removed: @@ -224,7 +225,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, os.path.join(p, s)) if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] # recursively find all unreported removed files while removed_dir: tmp = removed_dir @@ -250,7 +251,7 @@ class Svn(VcsInterface): k = os.path.join(self.root, os.path.join(p, s)) if not isabs: k = utils.relpath(pwd, k) - removed[k] = [ (k, prev), (None, None) ] + removed[k] = [(k, prev), (None, None)] # sort the results r = set() for m in removed, added, modified: @@ -269,7 +270,7 @@ class Svn(VcsInterface): def getRevision(self, prefs, name, rev): vcs_bin = prefs.getString('svn_bin') - if rev in [ 'BASE', 'COMMITTED', 'PREV' ]: + if rev in ['BASE', 'COMMITTED', 'PREV']: return utils.popenRead( self.root, [ diff --git a/src/diffuse/vcs/vcs_registry.py b/src/diffuse/vcs/vcs_registry.py index a08b792..5517339 100644 --- a/src/diffuse/vcs/vcs_registry.py +++ b/src/diffuse/vcs/vcs_registry.py @@ -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 diff --git a/src/diffuse/widgets.py b/src/diffuse/widgets.py index 7a220d7..d83eb93 100644 --- a/src/diffuse/widgets.py +++ b/src/diffuse/widgets.py @@ -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): @@ -169,7 +169,7 @@ class FileDiffViewer(Gtk.Grid): # class describing a single line of a pane class Line: - def __init__(self, line_number = None, text = None): + def __init__(self, line_number=None, text=None): # line number self.line_number = line_number # original text for the line @@ -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 = [] @@ -334,8 +338,8 @@ class FileDiffViewer(Gtk.Grid): # add diff map self.diffmap = diffmap = Gtk.DrawingArea.new() diffmap.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | - Gdk.EventMask.BUTTON1_MOTION_MASK | - Gdk.EventMask.SCROLL_MASK) + Gdk.EventMask.BUTTON1_MOTION_MASK | + Gdk.EventMask.SCROLL_MASK) diffmap.connect('button-press-event', self.diffmap_button_press_cb) diffmap.connect('motion-notify-event', self.diffmap_button_press_cb) diffmap.connect('scroll-event', self.diffmap_scroll_cb) @@ -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: @@ -635,7 +638,7 @@ class FileDiffViewer(Gtk.Grid): if f is None: panes = self.panes else: - panes = [ self.panes[f] ] + panes = [self.panes[f]] for _, pane in enumerate(panes): del pane.syntax_cache[:] del pane.diff_cache[:] @@ -644,7 +647,7 @@ class FileDiffViewer(Gtk.Grid): for line in pane.lines: if line is not None: line.compare_string = None - text = [ line.text ] + text = [line.text] if line.is_modified: text.append(line.modified_text) for s in text: @@ -869,7 +872,7 @@ class FileDiffViewer(Gtk.Grid): for f, pane in enumerate(self.panes): if i < len(pane.diff_cache): if i2 + 1 < len(pane.diff_cache): - pane.diff_cache[i:i2] = new_n * [ None ] + pane.diff_cache[i:i2] = new_n * [None] else: del pane.diff_cache[i:] self.dareas[f].queue_draw() @@ -942,7 +945,7 @@ class FileDiffViewer(Gtk.Grid): # insert 'n' blank lines in all panes def insertLines(self, i, n): # insert lines - self.updateAlignment(i, 0, [ n * [ None ] for pane in self.panes ]) + self.updateAlignment(i, 0, [n * [None] for pane in self.panes]) pre, post = _cut_blocks(i, self.blocks) pre.append(n) pre.extend(post) @@ -1092,19 +1095,19 @@ class FileDiffViewer(Gtk.Grid): # needed for alignment are inserted in all lists of lines for a particular # side to keep them all in sync. def alignBlocks(self, leftblocks, leftlines, rightblocks, rightlines): - blocks = ( leftblocks, rightblocks ) - lines = ( leftlines, rightlines ) + blocks = (leftblocks, rightblocks) + lines = (leftlines, rightlines) # get the inner lines we are to match - middle = ( leftlines[-1], rightlines[0] ) + middle = (leftlines[-1], rightlines[0]) # eliminate any existing spacer lines - mlines = ( [ line for line in middle[0] if line is not None ], - [ line for line in middle[1] if line is not None ] ) + mlines = ([line for line in middle[0] if line is not None], + [line for line in middle[1] if line is not None]) s1, s2 = mlines n1, n2 = 0, 0 # hash lines according to the alignment preferences a = self._alignmentHash - t1 = [ a(s) for s in s1 ] - t2 = [ a(s) for s in s2 ] + t1 = [a(s) for s in s1] + t2 = [a(s) for s in s2] # align s1 and s2 by inserting spacer lines # this will be used to determine which lines from the inner lists of # lines should be neighbours @@ -1113,12 +1116,12 @@ class FileDiffViewer(Gtk.Grid): if delta < 0: # insert spacer lines in s1 i = n1 + block[0] - s1[i:i] = -delta * [ None ] + s1[i:i] = -delta * [None] n1 -= delta elif delta > 0: # insert spacer lines in s2 i = n2 + block[1] - s2[i:i] = delta * [ None ] + s2[i:i] = delta * [None] n2 += delta nmatch = len(s1) @@ -1128,19 +1131,19 @@ 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 ] + bi = [0, 0] + bn = [0, 0] while True: # if we have reached the end of the list for any side, it needs # spacer lines to align with the other side - insert = [ i >= len(m) for m in middle ] - if insert == [ True, True ]: + insert = [i >= len(m) for m in middle] + if insert == [True, True]: # we have reached the end of both inner lists of lines # we are done break - if insert == [ False, False ] and k < nmatch: + if insert == [False, False] and k < nmatch: # determine if either side needs spacer lines to make the # inner list of lines match up accept = True @@ -1162,7 +1165,7 @@ class FileDiffViewer(Gtk.Grid): k += 1 else: # insert spacer lines as needed - insert = [ m[i] is not None for m in middle ] + insert = [m[i] is not None for m in middle] for j in range(2): if insert[j]: # insert spacers lines for side 'j' @@ -1196,13 +1199,13 @@ class FileDiffViewer(Gtk.Grid): blocks.append(n) # create line objects for the text Line = FileDiffViewer.Line - mid = [ [ Line(j + 1, ss[j]) for j in range(n) ] ] + mid = [[Line(j + 1, ss[j]) for j in range(n)]] if f > 0: # align with panes to the left # use copies so the originals can be used by the Undo object leftblocks = self.blocks[:] - leftlines = [ pane.lines[:] for pane in self.panes[:f] ] + leftlines = [pane.lines[:] for pane in self.panes[:f]] _remove_null_lines(leftblocks, leftlines) self.alignBlocks(leftblocks, leftlines, blocks, mid) mid[:0] = leftlines @@ -1211,7 +1214,7 @@ class FileDiffViewer(Gtk.Grid): # align with panes to the right # use copies so the originals can be used by the Undo object rightblocks = self.blocks[:] - rightlines = [ pane.lines[:] for pane in self.panes[f + 1:] ] + rightlines = [pane.lines[:] for pane in self.panes[f + 1:]] _remove_null_lines(rightblocks, rightlines) self.alignBlocks(blocks, mid, rightblocks, rightlines) mid.extend(rightlines) @@ -1295,7 +1298,7 @@ class FileDiffViewer(Gtk.Grid): # change the format to that of the target pane if pane.format == 0: self.setFormat(f, _get_format(ss)) - ss = [ _convert_to_format(s, pane.format) for s in ss ] + ss = [_convert_to_format(s, pane.format) for s in ss] # prepend original text that was before the selection if col0 > 0: pre = self.getLineText(f, line0)[:col0] @@ -1379,11 +1382,11 @@ class FileDiffViewer(Gtk.Grid): # 2. the matched pair # 3. lines after the matched pair # each section has lines and blocks for left and right sides - lines_s = [ [], [], [] ] - cutblocks = [ [], [], [] ] - lines = [ pane.lines for pane in self.panes ] + lines_s = [[], [], []] + cutblocks = [[], [], []] + lines = [pane.lines for pane in self.panes] nlines = len(lines[0]) - for temp, m in zip([ lines[:f + 1], lines[f + 1:] ], [ line1, line2 ]): + for temp, m in zip([lines[:f + 1], lines[f + 1:]], [line1, line2]): # cut the blocks just before the line being matched pre, post = _cut_blocks(m - start, mid) if len(temp) == 1: @@ -1391,12 +1394,12 @@ class FileDiffViewer(Gtk.Grid): # preserve other cuts pre = _create_block(sum(pre)) # the first section of lines to match - lines_s[0].append([ s[start:m] for s in temp ]) + lines_s[0].append([s[start:m] for s in temp]) cutblocks[0].append(pre) # the line to match may be after the actual lines if m < nlines: - m1 = [ [ s[m] ] for s in temp ] - m2 = [ s[m + 1:end] for s in temp ] + m1 = [[s[m]] for s in temp] + m2 = [s[m + 1:end] for s in temp] # cut the blocks just after the line being matched b1, b2 = _cut_blocks(1, post) if len(temp) == 1: @@ -1404,8 +1407,8 @@ class FileDiffViewer(Gtk.Grid): # preserve other cuts b2 = _create_block(sum(b2)) else: - m1 = [ [] for s in temp ] - m2 = [ [] for s in temp ] + m1 = [[] for s in temp] + m2 = [[] for s in temp] b1, b2 = [], [] # the second section of lines to match lines_s[1].append(m1) @@ -1415,7 +1418,7 @@ class FileDiffViewer(Gtk.Grid): cutblocks[2].append(b2) # align each section and concatenate the results - finallines = [ [] for s in lines ] + finallines = [[] for s in lines] for b, lines_t in zip(cutblocks, lines_s): _remove_null_lines(b[0], lines_t[0]) _remove_null_lines(b[1], lines_t[1]) @@ -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 @@ -1705,14 +1707,14 @@ class FileDiffViewer(Gtk.Grid): end += 1 # get the text for the selected lines end = min(end, len(self.panes[f].lines)) - ss = [ self.getLineText(f, i) for i in range(start, end) ] + ss = [self.getLineText(f, i) for i in range(start, end)] # trim out the unselected parts of the lines # check for col > 0 as some lines may be null if col1 > 0: ss[-1] = ss[-1][:col1] if col0 > 0: ss[0] = ss[0][col0:] - return ''.join([ s for s in ss if s is not None ]) + return ''.join([s for s in ss if s is not None]) # expands the selection to include everything def select_all(self): @@ -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,21 +1835,20 @@ 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], - [_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate ], - [_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge], - [], - [_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select], - [_('Copy'), self.button_cb, 'copy', Gtk.STOCK_COPY, 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], - [_('Clear Edits'), self.button_cb, 'clear_edits', Gtk.STOCK_CLEAR, None, can_isolate], - [], - [_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap] ]) - # pylint: enable=line-too-long + 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], + [], + [_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select], + [_('Copy'), self.button_cb, 'copy', Gtk.STOCK_COPY, 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], + [_('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] + ]) 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 @@ -2036,7 +2038,7 @@ class FileDiffViewer(Gtk.Grid): # enlarge cache to fit pan.diff_cache[i] if i >= len(pane.diff_cache): - pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [ None ]) + pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [None]) # construct a list of ranges for this lines character # differences if not already cached if pane.diff_cache[i] is None: @@ -2198,7 +2200,7 @@ class FileDiffViewer(Gtk.Grid): if temp < x: temp += ((x - temp) // h) * h h_half = 0.5 * h - phase = [ h_half, h_half, -h_half, -h_half ] + phase = [h_half, h_half, -h_half, -h_half] for j in range(4): x_temp = temp y_temp = y_start @@ -2224,7 +2226,7 @@ class FileDiffViewer(Gtk.Grid): if temp is None: blocks = None else: - blocks = [ (0, len(temp), 'text') ] + blocks = [(0, len(temp), 'text')] else: # apply the syntax highlighting rules to identify # ranges of similarly coloured characters @@ -2364,9 +2366,9 @@ class FileDiffViewer(Gtk.Grid): # flags & 8 indicates regular lines with text if self.diffmap_cache is None: nlines = len(self.panes[0].lines) - start = n * [ 0 ] - flags = n * [ 0 ] - self.diffmap_cache = [ [] for f in range(n) ] + start = n * [0] + flags = n * [0] + self.diffmap_cache = [[] for f in range(n)] # iterate over each row of lines for i in range(nlines): nextflag = 0 @@ -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 @@ -2443,8 +2446,8 @@ class FileDiffViewer(Gtk.Grid): break yh = max(rect.height * self.font_height * end // hmax - ymin, 1) - #if ymin + yh <= rect.y: - # continue + # if ymin + yh <= rect.y: + # continue cr.set_source_rgb(colour.red, colour.green, colour.blue) cr.rectangle(wx + pad, ymin, wn - 2 * pad, yh) @@ -2458,7 +2461,7 @@ class FileDiffViewer(Gtk.Grid): yh = rect.height * vmax // hmax - ymin if yh > 1: yh -= 1 - #if ymin + yh > rect.y: + # if ymin + yh > rect.y: colour = theResources.getColour('line_selection') alpha = theResources.getFloat('line_selection_opacity') cr.set_source_rgba(colour.red, colour.green, colour.blue, alpha) @@ -2675,7 +2678,7 @@ class FileDiffViewer(Gtk.Grid): elif event.keyval == Gdk.KEY_Tab and event.state & Gdk.ModifierType.CONTROL_MASK: retval = False # up/down cursor navigation - elif event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down ]: + elif event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down]: i = self.current_line # move back to the remembered cursor column if possible col = self.cursor_column @@ -2683,11 +2686,11 @@ class FileDiffViewer(Gtk.Grid): # find the current cursor column s = utils.null_to_empty(self.getLineText(f, i))[:self.current_char] col = self.stringWidth(s) - if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down ]: + if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down]: delta = 1 else: delta = int(self.vadj.get_page_size() // self.font_height) - if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Page_Up ]: + if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Page_Up]: delta = -delta i += delta j = 0 @@ -2818,7 +2821,7 @@ class FileDiffViewer(Gtk.Grid): self.current_char = j self.replaceText('') # return key, add the platform specific end of line characters - elif event.keyval in [ Gdk.KEY_Return, Gdk.KEY_KP_Enter ]: + elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]: s = os.linesep if self.prefs.getBool('editor_auto_indent'): start_i, start_j = self.selection_line, self.selection_char @@ -2840,7 +2843,7 @@ class FileDiffViewer(Gtk.Grid): s += ' ' * (w % tab_width) self.replaceText(s) # insert key - elif event.keyval in [ Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab ]: + elif event.keyval in [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: start_i, start_j = self.selection_line, self.selection_char end_i, end_j = self.current_line, self.current_char if start_i != end_i or start_j != end_j or event.keyval == Gdk.KEY_ISO_Left_Tab: @@ -3075,7 +3078,7 @@ class FileDiffViewer(Gtk.Grid): blocks = [] for pane in self.panes: # create a new list of lines with no spacers - newlines = [ [ line for line in pane.lines if line is not None ] ] + newlines = [[line for line in pane.lines if line is not None]] newblocks = _create_block(len(newlines[0])) if len(lines) > 0: # match with neighbour to the left @@ -3118,8 +3121,8 @@ class FileDiffViewer(Gtk.Grid): end = min(end, nlines) n = end - start if n > 0: - lines = [ pane.lines[start:end] for pane in self.panes ] - space = [ n * [ None ] for pane in self.panes ] + lines = [pane.lines[start:end] for pane in self.panes] + space = [n * [None] for pane in self.panes] lines[f], space[f] = space[f], lines[f] pre, post = _cut_blocks(end, self.blocks) @@ -3343,14 +3346,14 @@ class FileDiffViewer(Gtk.Grid): if end < start: start, end = end, start # get set of lines - ss = [ self.getLineText(f, i) for i in range(start, end + 1) ] + ss = [self.getLineText(f, i) for i in range(start, end + 1)] # create sorted list, removing any nulls - temp = [ s for s in ss if s is not None ] + temp = [s for s in ss if s is not None] temp.sort() if descending: temp.reverse() # add back in the nulls - temp.extend((len(ss) - len(temp)) * [ None ]) + temp.extend((len(ss) - len(temp)) * [None]) for i, s in enumerate(temp): # update line if it changed if ss[i] != s: @@ -3468,7 +3471,7 @@ class FileDiffViewer(Gtk.Grid): j += 1 if col >= tab_width: # convert to tabs - s = ''.join([ '\t' * (col // tab_width), ' ' * (col % tab_width), text[j:] ]) + s = ''.join(['\t' * (col // tab_width), ' ' * (col % tab_width), text[j:]]) # update line only if it changed if text != s: self.updateText(f, i, s) @@ -3551,7 +3554,7 @@ class FileDiffViewer(Gtk.Grid): if end < start: start, end = end, start end = min(end + 1, len(pane.lines)) - ss = [ self.getLineText(f_src, i) for i in range(start, end) ] + ss = [self.getLineText(f_src, i) for i in range(start, end)] if pane.format == 0: # copy the format of the source pane if the format for the # destination pane as not yet been determined @@ -3610,20 +3613,20 @@ class FileDiffViewer(Gtk.Grid): end = min(end, nlines) n = end - start if n > 0: - lines = [ pane.lines[start:end] for pane in self.panes ] - spaces = [ n * [ None ] for pane in self.panes ] - old_content = [ line for line in lines[f] if line is not None ] + lines = [pane.lines[start:end] for pane in self.panes] + spaces = [n * [None] for pane in self.panes] + old_content = [line for line in lines[f] if line is not None] for i in range(f + 1, npanes): lines[i], spaces[i] = spaces[i], lines[i] # replace f's lines with merge content if f > 0: lines[f] = lines[f - 1][:] else: - lines[f] = n * [ None ] + lines[f] = n * [None] if f + 1 < npanes: spaces[f] = spaces[f + 1][:] else: - spaces[f] = n * [ None ] + spaces[f] = n * [None] if right_first: lines, spaces = spaces, lines @@ -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 @@ -3647,14 +3650,14 @@ class FileDiffViewer(Gtk.Grid): delta = -delta for i in range(npanes): if i != f: - lines[i].extend(delta * [ None ]) + lines[i].extend(delta * [None]) # grow last block if len(b) > 0: b[-1] += delta else: b = _create_block(delta) elif delta > 0: - old_content.extend(delta * [ None ]) + old_content.extend(delta * [None]) new_n = len(old_content) # update lines and blocks @@ -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,11 +3748,12 @@ 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) if len_a and len_b: - blocks = [ (0, len_a, 0, len_b, 0) ] + blocks = [(0, len_a, 0, len_b, 0)] while blocks: start_a, end_a, start_b, end_b, match_idx = blocks.pop() aa, bb = a[start_a:end_a], b[start_b:end_b] @@ -3822,6 +3829,7 @@ def _patience_diff(a, b): matches.append((len_a, len_b, 0)) 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 = {}, {} @@ -3888,7 +3897,7 @@ def _lcs_approx(a, b): if s in lookup: lookup[s].append(i) else: - lookup[s] = [ i ] + lookup[s] = [i] if set(lookup).intersection(count1): # we have some common elements # identify popular entries @@ -3921,7 +3930,7 @@ def _lcs_approx(a, b): max_indices.append((ai, bi)) else: max_length = v - max_indices = [ (ai, bi) ] + max_indices = [(ai, bi)] else: prev_get = prev_matches.get for bi in lookup[s]: @@ -3932,7 +3941,7 @@ def _lcs_approx(a, b): max_indices.append((ai, bi)) else: max_length = v - max_indices = [ (ai, bi) ] + max_indices = [(ai, bi)] prev_matches, matches = matches, {} if max_indices: # include any popular entries at the beginning @@ -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,10 +3988,11 @@ 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: - old_format = _get_format([ s ]) + old_format = _get_format([s]) if old_format != 0 and (old_format & fmt) == 0: s = utils.strip_eol(s) # prefer the host line ending style @@ -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 [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 # @@ -4053,7 +4070,7 @@ def _merge_blocks(leftblocks, rightblocks): # this method will return the union of two sorted lists of ranges def _merge_ranges(r1, r2): r1, r2, result, start = r1[:], r2[:], [], 0 - rs = [ r1, r2 ] + rs = [r1, r2] while len(r1) > 0 and len(r2) > 0: flags, start = 0, min(r1[0][0], r2[0][0]) if start == r1[0][0]: @@ -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 From 72b4832ee4887c425e56645d2d07cd3b7894e038 Mon Sep 17 00:00:00 2001 From: Romain Failliot Date: Mon, 22 Nov 2021 20:54:44 -0500 Subject: [PATCH 4/5] Add mypy job in CI --- .github/workflows/main.yml | 3 +++ requirements.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6eb2d3d..5abdfa8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,9 @@ jobs: - name: Flake8 run: flake8 src/ + - name: MyPy + run: mypy src/ + meson-build-test: runs-on: ubuntu-latest steps: diff --git a/requirements.txt b/requirements.txt index b36b8eb..a3eb634 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ flake8 ~= 3.8 +mypy ~= 0.910 PyGObject ~= 3.40 From d89ac0540bae5c01525144aa633d6a0b4a2c09ab Mon Sep 17 00:00:00 2001 From: Romain Failliot Date: Mon, 22 Nov 2021 20:50:05 -0500 Subject: [PATCH 5/5] Fix all mypy errors --- .mypy.ini | 2 ++ src/diffuse/dialogs.py | 6 ++--- src/diffuse/main.py | 14 +++++----- src/diffuse/preferences.py | 6 ++--- src/diffuse/resources.py | 4 +-- src/diffuse/utils.py | 6 ++--- src/diffuse/widgets.py | 55 ++++++++++++++++++++------------------ 7 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 .mypy.ini diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..b9a22a7 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,2 @@ +[mypy] +warn_unused_ignores = True diff --git a/src/diffuse/dialogs.py b/src/diffuse/dialogs.py index c1b9c63..dea49e6 100644 --- a/src/diffuse/dialogs.py +++ b/src/diffuse/dialogs.py @@ -19,13 +19,13 @@ import os -from diffuse import constants +from diffuse import constants # type: ignore from diffuse import utils -import gi +import gi # type: ignore gi.require_version('GObject', '2.0') gi.require_version('Gtk', '3.0') -from gi.repository import GObject, Gtk # noqa: E402 +from gi.repository import GObject, Gtk # type: ignore # noqa: E402 # the about dialog diff --git a/src/diffuse/main.py b/src/diffuse/main.py index b7dd5de..1567288 100644 --- a/src/diffuse/main.py +++ b/src/diffuse/main.py @@ -27,23 +27,23 @@ import webbrowser from urllib.parse import urlparse -from diffuse import constants +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 FileDiffViewer +from diffuse.widgets import FileDiffViewerBase from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE -import gi +import gi # type: ignore 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 +from gi.repository import GObject, Gtk, Gdk, GdkPixbuf, Pango, PangoCairo # type: ignore # noqa: E402 theVCSs = VcsRegistry() @@ -110,8 +110,8 @@ class FileInfo: # this class displays tab for switching between viewers and dispatches menu # commands to the current viewer class Diffuse(Gtk.Window): - # specialisation of FileDiffViewer for Diffuse - class FileDiffViewer(FileDiffViewer): + # specialization of FileDiffViewerBase for Diffuse + class FileDiffViewer(FileDiffViewerBase): # pane header class PaneHeader(Gtk.Box): def __init__(self): @@ -223,7 +223,7 @@ class Diffuse(Gtk.Window): self.encoding.set_text(s) def __init__(self, n, prefs, title): - FileDiffViewer.__init__(self, n, prefs) + FileDiffViewerBase.__init__(self, n, prefs) self.title = title self.status = '' diff --git a/src/diffuse/preferences.py b/src/diffuse/preferences.py index 06497f9..8c1c8fd 100644 --- a/src/diffuse/preferences.py +++ b/src/diffuse/preferences.py @@ -23,12 +23,12 @@ import os import shlex import sys -from diffuse import constants +from diffuse import constants # type: ignore from diffuse import utils -import gi +import gi # type: ignore gi.require_version('Gtk', '3.0') -from gi.repository import Gtk # noqa: E402 +from gi.repository import Gtk # type: ignore # noqa: E402 # class to store preferences and construct a dialogue for manipulating them diff --git a/src/diffuse/resources.py b/src/diffuse/resources.py index f060849..200bab2 100644 --- a/src/diffuse/resources.py +++ b/src/diffuse/resources.py @@ -31,9 +31,9 @@ import shlex from diffuse import utils -import gi +import gi # type: ignore gi.require_version('Gdk', '3.0') -from gi.repository import Gdk # noqa: E402 +from gi.repository import Gdk # type: ignore # noqa: E402 class Resources: diff --git a/src/diffuse/utils.py b/src/diffuse/utils.py index b121343..b644869 100644 --- a/src/diffuse/utils.py +++ b/src/diffuse/utils.py @@ -23,11 +23,11 @@ import locale import subprocess import traceback -from diffuse import constants +from diffuse import constants # type: ignore -import gi +import gi # type: ignore gi.require_version('Gtk', '3.0') -from gi.repository import Gtk # noqa: E402 +from gi.repository import Gtk # type: ignore # noqa: E402 # convenience class for displaying a message dialogue diff --git a/src/diffuse/widgets.py b/src/diffuse/widgets.py index d83eb93..5adb41e 100644 --- a/src/diffuse/widgets.py +++ b/src/diffuse/widgets.py @@ -21,23 +21,25 @@ import difflib import os import unicodedata +from typing import Dict + from diffuse import utils from diffuse.resources import theResources -import gi +import gi # type: ignore 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 # noqa: E402 +from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo # type: ignore # noqa: E402 # 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, -# and hotkey behaviour +# and hotkey behavior LINE_MODE = 0 CHAR_MODE = 1 ALIGN_MODE = 2 @@ -142,7 +144,7 @@ class ScrolledWindow(Gtk.Grid): # widget used to compare and merge text files -class FileDiffViewer(Gtk.Grid): +class FileDiffViewerBase(Gtk.Grid): # class describing a text pane class Pane: def __init__(self): @@ -315,7 +317,7 @@ class FileDiffViewer(Gtk.Grid): self.hadj = 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): - pane = FileDiffViewer.Pane() + pane = FileDiffViewerBase.Pane() self.panes.append(pane) # pane contents @@ -712,7 +714,7 @@ class FileDiffViewer(Gtk.Grid): pane = self.panes[f] if self.undoblock is not None: # 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 self.emit('format_changed', f, fmt) @@ -734,12 +736,12 @@ class FileDiffViewer(Gtk.Grid): def instanceLine(self, f, i, reverse=False): if self.undoblock is not None: # 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] if reverse: pane.lines[i] = None else: - line = FileDiffViewer.Line() + line = FileDiffViewerBase.Line() pane.lines[i] = line # Undo for changing the text for a Line object @@ -774,7 +776,7 @@ class FileDiffViewer(Gtk.Grid): flags = self.getMapFlags(f, i) if self.undoblock is not None: # create an Undo object for the action - self.addUndo(FileDiffViewer.UpdateLineTextUndo( + self.addUndo(FileDiffViewerBase.UpdateLineTextUndo( f, i, line.is_modified, @@ -835,7 +837,7 @@ class FileDiffViewer(Gtk.Grid): def insertNull(self, f, i, reverse): if self.undoblock is not None: # 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] lines = pane.lines # update/invalidate all relevant caches @@ -866,7 +868,7 @@ class FileDiffViewer(Gtk.Grid): def invalidateLineMatching(self, i, n, new_n): if self.undoblock is not None: # 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 i2 = i + n for f, pane in enumerate(self.panes): @@ -896,7 +898,7 @@ class FileDiffViewer(Gtk.Grid): def alignmentChange(self, finished): if self.undoblock is not None: # create an Undo object for the action - self.addUndo(FileDiffViewer.AlignmentChangeUndo(finished)) + self.addUndo(FileDiffViewerBase.AlignmentChangeUndo(finished)) if finished: self.updateSize(False) @@ -939,7 +941,7 @@ class FileDiffViewer(Gtk.Grid): def updateBlocks(self, blocks): if self.undoblock is not None: # create an Undo object for the action - self.addUndo(FileDiffViewer.UpdateBlocksUndo(self.blocks, blocks)) + self.addUndo(FileDiffViewerBase.UpdateBlocksUndo(self.blocks, blocks)) self.blocks = blocks # insert 'n' blank lines in all panes @@ -1031,7 +1033,8 @@ class FileDiffViewer(Gtk.Grid): def replaceLines(self, f, lines, new_lines, max_num, new_max_num): if self.undoblock is not None: # 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.lines = new_lines # update/invalidate all relevant caches and queue widgets for redraw @@ -1198,7 +1201,7 @@ class FileDiffViewer(Gtk.Grid): if n > 0: blocks.append(n) # create line objects for the text - Line = FileDiffViewer.Line + Line = FileDiffViewerBase.Line mid = [[Line(j + 1, ss[j]) for j in range(n)]] if f > 0: @@ -1256,7 +1259,7 @@ class FileDiffViewer(Gtk.Grid): lines.append(None) else: line_num += 1 - lines.append(FileDiffViewer.Line(line_num, s)) + lines.append(FileDiffViewerBase.Line(line_num, s)) # update loaded pane self.replaceLines(f, pane.lines, lines, pane.max_line_number, line_num) @@ -1490,7 +1493,7 @@ class FileDiffViewer(Gtk.Grid): # selection def recordEditMode(self): if self.undoblock is not None: - self.addUndo(FileDiffViewer.EditModeUndo( + self.addUndo(FileDiffViewerBase.EditModeUndo( self.mode, self.current_pane, self.current_line, @@ -3243,7 +3246,7 @@ class FileDiffViewer(Gtk.Grid): # swap the contents of two panes def swapPanes(self, f_dst, f_src): 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 f0 = self.panes[f_dst] f1 = self.panes[f_src] @@ -4128,10 +4131,10 @@ def _pixels(size): return int(size / Pango.SCALE + 0.5) -# create 'title_changed' signal for FileDiffViewer -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 +# create 'title_changed' signal for FileDiffViewerBase +GObject.signal_new('swapped-panes', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501 +GObject.signal_new('num-edits-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) # noqa: E501 +GObject.signal_new('mode-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501 +GObject.signal_new('cursor-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501 +GObject.signal_new('syntax-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) # noqa: E501 +GObject.signal_new('format-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501