Merge pull request #125 from MightyCreak/flake8

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

6
.flake8 Normal file
View File

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

View File

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

2
.mypy.ini Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,28 +21,30 @@ import difflib
import os import os
import unicodedata import unicodedata
# pylint: disable=wrong-import-position from typing import Dict
import gi
from diffuse import utils
from diffuse.resources import theResources
import gi # type: ignore
gi.require_version('GObject', '2.0') gi.require_version('GObject', '2.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('Pango', '1.0') gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0') gi.require_version('PangoCairo', '1.0')
from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo from gi.repository import GObject, Gdk, Gtk, Pango, PangoCairo # type: ignore # noqa: E402
# pylint: enable=wrong-import-position
from diffuse import utils
from diffuse.resources import theResources
# mapping to column width of a character (tab will never be in this map) # mapping to column width of a character (tab will never be in this map)
_char_width_cache = {} _char_width_cache: Dict[str, str] = {}
# the file diff viewer is always in one of these modes defining the cursor, # the file diff viewer is always in one of these modes defining the cursor,
# and hotkey behaviour # and hotkey behavior
LINE_MODE = 0 LINE_MODE = 0
CHAR_MODE = 1 CHAR_MODE = 1
ALIGN_MODE = 2 ALIGN_MODE = 2
# This is a replacement for Gtk.ScrolledWindow as it forced expose events to be # This is a replacement for Gtk.ScrolledWindow as it forced expose events to be
# handled immediately after changing the viewport position. This could cause # handled immediately after changing the viewport position. This could cause
# the application to become unresponsive for a while as it processed a large # the application to become unresponsive for a while as it processed a large
@ -140,9 +142,9 @@ class ScrolledWindow(Gtk.Grid):
self.partial_redraw = True self.partial_redraw = True
self.darea_queue_draw_area(x, y, w, h) self.darea_queue_draw_area(x, y, w, h)
# widget used to compare and merge text files # widget used to compare and merge text files
class FileDiffViewer(Gtk.Grid): class FileDiffViewerBase(Gtk.Grid):
# pylint: disable=too-many-public-methods
# class describing a text pane # class describing a text pane
class Pane: class Pane:
def __init__(self): def __init__(self):
@ -169,7 +171,7 @@ class FileDiffViewer(Gtk.Grid):
# class describing a single line of a pane # class describing a single line of a pane
class Line: class Line:
def __init__(self, line_number = None, text = None): def __init__(self, line_number=None, text=None):
# line number # line number
self.line_number = line_number self.line_number = line_number
# original text for the line # original text for the line
@ -254,7 +256,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection, 'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection, 'copy_right_into_selection': self.copy_right_into_selection,
'merge_from_left_then_right': self.merge_from_left_then_right, 'merge_from_left_then_right': self.merge_from_left_then_right,
'merge_from_right_then_left': self.merge_from_right_then_left } 'merge_from_right_then_left': self.merge_from_right_then_left
}
self._align_mode_actions = { self._align_mode_actions = {
'enter_line_mode': self._align_mode_enter_line_mode, 'enter_line_mode': self._align_mode_enter_line_mode,
'enter_character_mode': self.setCharMode, 'enter_character_mode': self.setCharMode,
@ -266,9 +269,11 @@ class FileDiffViewer(Gtk.Grid):
'right': self._line_mode_right, 'right': self._line_mode_right,
'page_up': self._line_mode_page_up, 'page_up': self._line_mode_page_up,
'page_down': self._line_mode_page_down, 'page_down': self._line_mode_page_down,
'align': self._align_text } 'align': self._align_text
}
self._character_mode_actions = { self._character_mode_actions = {
'enter_line_mode': self.setLineMode } 'enter_line_mode': self.setLineMode
}
self._button_actions = { self._button_actions = {
'undo': self.undo, 'undo': self.undo,
'redo': self.redo, 'redo': self.redo,
@ -303,7 +308,8 @@ class FileDiffViewer(Gtk.Grid):
'copy_left_into_selection': self.copy_left_into_selection, 'copy_left_into_selection': self.copy_left_into_selection,
'copy_right_into_selection': self.copy_right_into_selection, 'copy_right_into_selection': self.copy_right_into_selection,
'merge_from_left_then_right': self.merge_from_left_then_right, 'merge_from_left_then_right': self.merge_from_left_then_right,
'merge_from_right_then_left': self.merge_from_right_then_left } 'merge_from_right_then_left': self.merge_from_right_then_left
}
# create panes # create panes
self.dareas = [] self.dareas = []
@ -311,7 +317,7 @@ class FileDiffViewer(Gtk.Grid):
self.hadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0) self.hadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0)
self.vadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0) self.vadj = Gtk.Adjustment.new(0, 0, 0, 0, 0, 0)
for i in range(n): for i in range(n):
pane = FileDiffViewer.Pane() pane = FileDiffViewerBase.Pane()
self.panes.append(pane) self.panes.append(pane)
# pane contents # pane contents
@ -334,8 +340,8 @@ class FileDiffViewer(Gtk.Grid):
# add diff map # add diff map
self.diffmap = diffmap = Gtk.DrawingArea.new() self.diffmap = diffmap = Gtk.DrawingArea.new()
diffmap.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | diffmap.add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON1_MOTION_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK |
Gdk.EventMask.SCROLL_MASK) Gdk.EventMask.SCROLL_MASK)
diffmap.connect('button-press-event', self.diffmap_button_press_cb) diffmap.connect('button-press-event', self.diffmap_button_press_cb)
diffmap.connect('motion-notify-event', self.diffmap_button_press_cb) diffmap.connect('motion-notify-event', self.diffmap_button_press_cb)
diffmap.connect('scroll-event', self.diffmap_scroll_cb) diffmap.connect('scroll-event', self.diffmap_scroll_cb)
@ -343,7 +349,6 @@ class FileDiffViewer(Gtk.Grid):
self.attach(diffmap, n, 1, 1, 1) self.attach(diffmap, n, 1, 1, 1)
diffmap.show() diffmap.show()
diffmap.set_size_request(16 * n, 0) diffmap.set_size_request(16 * n, 0)
# pylint: disable-next=no-member
self.add_events(Gdk.EventMask.KEY_PRESS_MASK | self.add_events(Gdk.EventMask.KEY_PRESS_MASK |
Gdk.EventMask.FOCUS_CHANGE_MASK) Gdk.EventMask.FOCUS_CHANGE_MASK)
self.connect('focus-in-event', self.focus_in_cb) self.connect('focus-in-event', self.focus_in_cb)
@ -372,7 +377,7 @@ class FileDiffViewer(Gtk.Grid):
# this must be connected with 'connect_after()' so the final widget sizes # this must be connected with 'connect_after()' so the final widget sizes
# are known and the scroll bar can be moved to the first difference # are known and the scroll bar can be moved to the first difference
def _realise_cb(self, widget): def _realise_cb(self, widget):
self.im_context.set_client_window(self.get_window()) # pylint: disable=no-member self.im_context.set_client_window(self.get_window())
try: try:
self.go_to_line(self.options['line']) self.go_to_line(self.options['line'])
except KeyError: except KeyError:
@ -635,7 +640,7 @@ class FileDiffViewer(Gtk.Grid):
if f is None: if f is None:
panes = self.panes panes = self.panes
else: else:
panes = [ self.panes[f] ] panes = [self.panes[f]]
for _, pane in enumerate(panes): for _, pane in enumerate(panes):
del pane.syntax_cache[:] del pane.syntax_cache[:]
del pane.diff_cache[:] del pane.diff_cache[:]
@ -644,7 +649,7 @@ class FileDiffViewer(Gtk.Grid):
for line in pane.lines: for line in pane.lines:
if line is not None: if line is not None:
line.compare_string = None line.compare_string = None
text = [ line.text ] text = [line.text]
if line.is_modified: if line.is_modified:
text.append(line.modified_text) text.append(line.modified_text)
for s in text: for s in text:
@ -709,7 +714,7 @@ class FileDiffViewer(Gtk.Grid):
pane = self.panes[f] pane = self.panes[f]
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.SetFormatUndo(f, fmt, pane.format)) self.addUndo(FileDiffViewerBase.SetFormatUndo(f, fmt, pane.format))
pane.format = fmt pane.format = fmt
self.emit('format_changed', f, fmt) self.emit('format_changed', f, fmt)
@ -731,12 +736,12 @@ class FileDiffViewer(Gtk.Grid):
def instanceLine(self, f, i, reverse=False): def instanceLine(self, f, i, reverse=False):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InstanceLineUndo(f, i, reverse)) self.addUndo(FileDiffViewerBase.InstanceLineUndo(f, i, reverse))
pane = self.panes[f] pane = self.panes[f]
if reverse: if reverse:
pane.lines[i] = None pane.lines[i] = None
else: else:
line = FileDiffViewer.Line() line = FileDiffViewerBase.Line()
pane.lines[i] = line pane.lines[i] = line
# Undo for changing the text for a Line object # Undo for changing the text for a Line object
@ -771,7 +776,7 @@ class FileDiffViewer(Gtk.Grid):
flags = self.getMapFlags(f, i) flags = self.getMapFlags(f, i)
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.UpdateLineTextUndo( self.addUndo(FileDiffViewerBase.UpdateLineTextUndo(
f, f,
i, i,
line.is_modified, line.is_modified,
@ -832,7 +837,7 @@ class FileDiffViewer(Gtk.Grid):
def insertNull(self, f, i, reverse): def insertNull(self, f, i, reverse):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InsertNullUndo(f, i, reverse)) self.addUndo(FileDiffViewerBase.InsertNullUndo(f, i, reverse))
pane = self.panes[f] pane = self.panes[f]
lines = pane.lines lines = pane.lines
# update/invalidate all relevant caches # update/invalidate all relevant caches
@ -863,13 +868,13 @@ class FileDiffViewer(Gtk.Grid):
def invalidateLineMatching(self, i, n, new_n): def invalidateLineMatching(self, i, n, new_n):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.InvalidateLineMatchingUndo(i, n, new_n)) self.addUndo(FileDiffViewerBase.InvalidateLineMatchingUndo(i, n, new_n))
# update/invalidate all relevant caches and queue widgets for redraw # update/invalidate all relevant caches and queue widgets for redraw
i2 = i + n i2 = i + n
for f, pane in enumerate(self.panes): for f, pane in enumerate(self.panes):
if i < len(pane.diff_cache): if i < len(pane.diff_cache):
if i2 + 1 < len(pane.diff_cache): if i2 + 1 < len(pane.diff_cache):
pane.diff_cache[i:i2] = new_n * [ None ] pane.diff_cache[i:i2] = new_n * [None]
else: else:
del pane.diff_cache[i:] del pane.diff_cache[i:]
self.dareas[f].queue_draw() self.dareas[f].queue_draw()
@ -893,7 +898,7 @@ class FileDiffViewer(Gtk.Grid):
def alignmentChange(self, finished): def alignmentChange(self, finished):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.AlignmentChangeUndo(finished)) self.addUndo(FileDiffViewerBase.AlignmentChangeUndo(finished))
if finished: if finished:
self.updateSize(False) self.updateSize(False)
@ -936,13 +941,13 @@ class FileDiffViewer(Gtk.Grid):
def updateBlocks(self, blocks): def updateBlocks(self, blocks):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.UpdateBlocksUndo(self.blocks, blocks)) self.addUndo(FileDiffViewerBase.UpdateBlocksUndo(self.blocks, blocks))
self.blocks = blocks self.blocks = blocks
# insert 'n' blank lines in all panes # insert 'n' blank lines in all panes
def insertLines(self, i, n): def insertLines(self, i, n):
# insert lines # insert lines
self.updateAlignment(i, 0, [ n * [ None ] for pane in self.panes ]) self.updateAlignment(i, 0, [n * [None] for pane in self.panes])
pre, post = _cut_blocks(i, self.blocks) pre, post = _cut_blocks(i, self.blocks)
pre.append(n) pre.append(n)
pre.extend(post) pre.extend(post)
@ -1028,7 +1033,8 @@ class FileDiffViewer(Gtk.Grid):
def replaceLines(self, f, lines, new_lines, max_num, new_max_num): def replaceLines(self, f, lines, new_lines, max_num, new_max_num):
if self.undoblock is not None: if self.undoblock is not None:
# create an Undo object for the action # create an Undo object for the action
self.addUndo(FileDiffViewer.ReplaceLinesUndo(f, lines, new_lines, max_num, new_max_num)) self.addUndo(FileDiffViewerBase.ReplaceLinesUndo(
f, lines, new_lines, max_num, new_max_num))
pane = self.panes[f] pane = self.panes[f]
pane.lines = new_lines pane.lines = new_lines
# update/invalidate all relevant caches and queue widgets for redraw # update/invalidate all relevant caches and queue widgets for redraw
@ -1092,19 +1098,19 @@ class FileDiffViewer(Gtk.Grid):
# needed for alignment are inserted in all lists of lines for a particular # needed for alignment are inserted in all lists of lines for a particular
# side to keep them all in sync. # side to keep them all in sync.
def alignBlocks(self, leftblocks, leftlines, rightblocks, rightlines): def alignBlocks(self, leftblocks, leftlines, rightblocks, rightlines):
blocks = ( leftblocks, rightblocks ) blocks = (leftblocks, rightblocks)
lines = ( leftlines, rightlines ) lines = (leftlines, rightlines)
# get the inner lines we are to match # get the inner lines we are to match
middle = ( leftlines[-1], rightlines[0] ) middle = (leftlines[-1], rightlines[0])
# eliminate any existing spacer lines # eliminate any existing spacer lines
mlines = ( [ line for line in middle[0] if line is not None ], mlines = ([line for line in middle[0] if line is not None],
[ line for line in middle[1] if line is not None ] ) [line for line in middle[1] if line is not None])
s1, s2 = mlines s1, s2 = mlines
n1, n2 = 0, 0 n1, n2 = 0, 0
# hash lines according to the alignment preferences # hash lines according to the alignment preferences
a = self._alignmentHash a = self._alignmentHash
t1 = [ a(s) for s in s1 ] t1 = [a(s) for s in s1]
t2 = [ a(s) for s in s2 ] t2 = [a(s) for s in s2]
# align s1 and s2 by inserting spacer lines # align s1 and s2 by inserting spacer lines
# this will be used to determine which lines from the inner lists of # this will be used to determine which lines from the inner lists of
# lines should be neighbours # lines should be neighbours
@ -1113,12 +1119,12 @@ class FileDiffViewer(Gtk.Grid):
if delta < 0: if delta < 0:
# insert spacer lines in s1 # insert spacer lines in s1
i = n1 + block[0] i = n1 + block[0]
s1[i:i] = -delta * [ None ] s1[i:i] = -delta * [None]
n1 -= delta n1 -= delta
elif delta > 0: elif delta > 0:
# insert spacer lines in s2 # insert spacer lines in s2
i = n2 + block[1] i = n2 + block[1]
s2[i:i] = delta * [ None ] s2[i:i] = delta * [None]
n2 += delta n2 += delta
nmatch = len(s1) nmatch = len(s1)
@ -1128,19 +1134,19 @@ class FileDiffViewer(Gtk.Grid):
# #
# advance one row at a time inserting spacer lines as we go # advance one row at a time inserting spacer lines as we go
# 'i' indicates which row we are processing # 'i' indicates which row we are processing
# 'k' indicates which pair of neighbours we are processing # 'k' indicates which pair of neighbors we are processing
i, k = 0, 0 i, k = 0, 0
bi = [ 0, 0 ] bi = [0, 0]
bn = [ 0, 0 ] bn = [0, 0]
while True: while True:
# if we have reached the end of the list for any side, it needs # if we have reached the end of the list for any side, it needs
# spacer lines to align with the other side # spacer lines to align with the other side
insert = [ i >= len(m) for m in middle ] insert = [i >= len(m) for m in middle]
if insert == [ True, True ]: if insert == [True, True]:
# we have reached the end of both inner lists of lines # we have reached the end of both inner lists of lines
# we are done # we are done
break break
if insert == [ False, False ] and k < nmatch: if insert == [False, False] and k < nmatch:
# determine if either side needs spacer lines to make the # determine if either side needs spacer lines to make the
# inner list of lines match up # inner list of lines match up
accept = True accept = True
@ -1162,7 +1168,7 @@ class FileDiffViewer(Gtk.Grid):
k += 1 k += 1
else: else:
# insert spacer lines as needed # insert spacer lines as needed
insert = [ m[i] is not None for m in middle ] insert = [m[i] is not None for m in middle]
for j in range(2): for j in range(2):
if insert[j]: if insert[j]:
# insert spacers lines for side 'j' # insert spacers lines for side 'j'
@ -1195,14 +1201,14 @@ class FileDiffViewer(Gtk.Grid):
if n > 0: if n > 0:
blocks.append(n) blocks.append(n)
# create line objects for the text # create line objects for the text
Line = FileDiffViewer.Line Line = FileDiffViewerBase.Line
mid = [ [ Line(j + 1, ss[j]) for j in range(n) ] ] mid = [[Line(j + 1, ss[j]) for j in range(n)]]
if f > 0: if f > 0:
# align with panes to the left # align with panes to the left
# use copies so the originals can be used by the Undo object # use copies so the originals can be used by the Undo object
leftblocks = self.blocks[:] leftblocks = self.blocks[:]
leftlines = [ pane.lines[:] for pane in self.panes[:f] ] leftlines = [pane.lines[:] for pane in self.panes[:f]]
_remove_null_lines(leftblocks, leftlines) _remove_null_lines(leftblocks, leftlines)
self.alignBlocks(leftblocks, leftlines, blocks, mid) self.alignBlocks(leftblocks, leftlines, blocks, mid)
mid[:0] = leftlines mid[:0] = leftlines
@ -1211,7 +1217,7 @@ class FileDiffViewer(Gtk.Grid):
# align with panes to the right # align with panes to the right
# use copies so the originals can be used by the Undo object # use copies so the originals can be used by the Undo object
rightblocks = self.blocks[:] rightblocks = self.blocks[:]
rightlines = [ pane.lines[:] for pane in self.panes[f + 1:] ] rightlines = [pane.lines[:] for pane in self.panes[f + 1:]]
_remove_null_lines(rightblocks, rightlines) _remove_null_lines(rightblocks, rightlines)
self.alignBlocks(blocks, mid, rightblocks, rightlines) self.alignBlocks(blocks, mid, rightblocks, rightlines)
mid.extend(rightlines) mid.extend(rightlines)
@ -1253,7 +1259,7 @@ class FileDiffViewer(Gtk.Grid):
lines.append(None) lines.append(None)
else: else:
line_num += 1 line_num += 1
lines.append(FileDiffViewer.Line(line_num, s)) lines.append(FileDiffViewerBase.Line(line_num, s))
# update loaded pane # update loaded pane
self.replaceLines(f, pane.lines, lines, pane.max_line_number, line_num) self.replaceLines(f, pane.lines, lines, pane.max_line_number, line_num)
@ -1295,7 +1301,7 @@ class FileDiffViewer(Gtk.Grid):
# change the format to that of the target pane # change the format to that of the target pane
if pane.format == 0: if pane.format == 0:
self.setFormat(f, _get_format(ss)) self.setFormat(f, _get_format(ss))
ss = [ _convert_to_format(s, pane.format) for s in ss ] ss = [_convert_to_format(s, pane.format) for s in ss]
# prepend original text that was before the selection # prepend original text that was before the selection
if col0 > 0: if col0 > 0:
pre = self.getLineText(f, line0)[:col0] pre = self.getLineText(f, line0)[:col0]
@ -1379,11 +1385,11 @@ class FileDiffViewer(Gtk.Grid):
# 2. the matched pair # 2. the matched pair
# 3. lines after the matched pair # 3. lines after the matched pair
# each section has lines and blocks for left and right sides # each section has lines and blocks for left and right sides
lines_s = [ [], [], [] ] lines_s = [[], [], []]
cutblocks = [ [], [], [] ] cutblocks = [[], [], []]
lines = [ pane.lines for pane in self.panes ] lines = [pane.lines for pane in self.panes]
nlines = len(lines[0]) nlines = len(lines[0])
for temp, m in zip([ lines[:f + 1], lines[f + 1:] ], [ line1, line2 ]): for temp, m in zip([lines[:f + 1], lines[f + 1:]], [line1, line2]):
# cut the blocks just before the line being matched # cut the blocks just before the line being matched
pre, post = _cut_blocks(m - start, mid) pre, post = _cut_blocks(m - start, mid)
if len(temp) == 1: if len(temp) == 1:
@ -1391,12 +1397,12 @@ class FileDiffViewer(Gtk.Grid):
# preserve other cuts # preserve other cuts
pre = _create_block(sum(pre)) pre = _create_block(sum(pre))
# the first section of lines to match # the first section of lines to match
lines_s[0].append([ s[start:m] for s in temp ]) lines_s[0].append([s[start:m] for s in temp])
cutblocks[0].append(pre) cutblocks[0].append(pre)
# the line to match may be after the actual lines # the line to match may be after the actual lines
if m < nlines: if m < nlines:
m1 = [ [ s[m] ] for s in temp ] m1 = [[s[m]] for s in temp]
m2 = [ s[m + 1:end] for s in temp ] m2 = [s[m + 1:end] for s in temp]
# cut the blocks just after the line being matched # cut the blocks just after the line being matched
b1, b2 = _cut_blocks(1, post) b1, b2 = _cut_blocks(1, post)
if len(temp) == 1: if len(temp) == 1:
@ -1404,8 +1410,8 @@ class FileDiffViewer(Gtk.Grid):
# preserve other cuts # preserve other cuts
b2 = _create_block(sum(b2)) b2 = _create_block(sum(b2))
else: else:
m1 = [ [] for s in temp ] m1 = [[] for s in temp]
m2 = [ [] for s in temp ] m2 = [[] for s in temp]
b1, b2 = [], [] b1, b2 = [], []
# the second section of lines to match # the second section of lines to match
lines_s[1].append(m1) lines_s[1].append(m1)
@ -1415,7 +1421,7 @@ class FileDiffViewer(Gtk.Grid):
cutblocks[2].append(b2) cutblocks[2].append(b2)
# align each section and concatenate the results # align each section and concatenate the results
finallines = [ [] for s in lines ] finallines = [[] for s in lines]
for b, lines_t in zip(cutblocks, lines_s): for b, lines_t in zip(cutblocks, lines_s):
_remove_null_lines(b[0], lines_t[0]) _remove_null_lines(b[0], lines_t[0])
_remove_null_lines(b[1], lines_t[1]) _remove_null_lines(b[1], lines_t[1])
@ -1487,7 +1493,7 @@ class FileDiffViewer(Gtk.Grid):
# selection # selection
def recordEditMode(self): def recordEditMode(self):
if self.undoblock is not None: if self.undoblock is not None:
self.addUndo(FileDiffViewer.EditModeUndo( self.addUndo(FileDiffViewerBase.EditModeUndo(
self.mode, self.mode,
self.current_pane, self.current_pane,
self.current_line, self.current_line,
@ -1606,7 +1612,6 @@ class FileDiffViewer(Gtk.Grid):
x -= int(self.hadj.get_value()) x -= int(self.hadj.get_value())
y -= int(self.vadj.get_value()) y -= int(self.vadj.get_value())
# translate to a position relative to the window # translate to a position relative to the window
# pylint: disable=no-member
x, y = self.dareas[self.current_pane].translate_coordinates(self.get_toplevel(), x, y) x, y = self.dareas[self.current_pane].translate_coordinates(self.get_toplevel(), x, y)
# input methods support widgets are centred horizontally about the # input methods support widgets are centred horizontally about the
# cursor, a width of 50 seems to give a better widget positions # cursor, a width of 50 seems to give a better widget positions
@ -1705,14 +1710,14 @@ class FileDiffViewer(Gtk.Grid):
end += 1 end += 1
# get the text for the selected lines # get the text for the selected lines
end = min(end, len(self.panes[f].lines)) end = min(end, len(self.panes[f].lines))
ss = [ self.getLineText(f, i) for i in range(start, end) ] ss = [self.getLineText(f, i) for i in range(start, end)]
# trim out the unselected parts of the lines # trim out the unselected parts of the lines
# check for col > 0 as some lines may be null # check for col > 0 as some lines may be null
if col1 > 0: if col1 > 0:
ss[-1] = ss[-1][:col1] ss[-1] = ss[-1][:col1]
if col0 > 0: if col0 > 0:
ss[0] = ss[0][col0:] ss[0] = ss[0][col0:]
return ''.join([ s for s in ss if s is not None ]) return ''.join([s for s in ss if s is not None])
# expands the selection to include everything # expands the selection to include everything
def select_all(self): def select_all(self):
@ -1771,14 +1776,14 @@ class FileDiffViewer(Gtk.Grid):
# callback for mouse button presses in the text window # callback for mouse button presses in the text window
def darea_button_press_cb(self, widget, event, f): def darea_button_press_cb(self, widget, event, f):
self.get_toplevel().set_focus(self) # pylint: disable=no-member self.get_toplevel().set_focus(self)
x = int(event.x + self.hadj.get_value()) x = int(event.x + self.hadj.get_value())
y = int(event.y + self.vadj.get_value()) y = int(event.y + self.vadj.get_value())
nlines = len(self.panes[f].lines) nlines = len(self.panes[f].lines)
i = min(y // self.font_height, nlines) i = min(y // self.font_height, nlines)
if event.button == 1: if event.button == 1:
# left mouse button # left mouse button
if event.type == Gdk.EventType._2BUTTON_PRESS: # pylint: disable=no-member,protected-access if event.type == Gdk.EventType._2BUTTON_PRESS:
# double click # double click
if self.mode == ALIGN_MODE: if self.mode == ALIGN_MODE:
self.setLineMode() self.setLineMode()
@ -1804,7 +1809,7 @@ class FileDiffViewer(Gtk.Grid):
while j < n and _get_character_class(text[j]) == c: while j < n and _get_character_class(text[j]) == c:
j += 1 j += 1
self.setCurrentChar(i, j, i, k) self.setCurrentChar(i, j, i, k)
elif event.type == Gdk.EventType._3BUTTON_PRESS: # pylint: disable=no-member,protected-access elif event.type == Gdk.EventType._3BUTTON_PRESS:
# triple click, select a whole line # triple click, select a whole line
if self.mode == CHAR_MODE and self.current_pane == f: if self.mode == CHAR_MODE and self.current_pane == f:
i2 = min(i + 1, nlines) i2 = min(i + 1, nlines)
@ -1833,21 +1838,20 @@ class FileDiffViewer(Gtk.Grid):
can_select = self.mode in (LINE_MODE, CHAR_MODE) and f == self.current_pane can_select = self.mode in (LINE_MODE, CHAR_MODE) and f == self.current_pane
can_swap = (f != self.current_pane) can_swap = (f != self.current_pane)
# pylint: disable=line-too-long menu = createMenu([
menu = createMenu( [_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align], # noqa: E501
[ [_('Align with Selection'), self.align_with_selection_cb, [f, i], Gtk.STOCK_EXECUTE, None, can_align], [_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate],
[_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate ], [_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge],
[_('Merge Selection'), self.merge_lines_cb, f, None, None, can_merge], [],
[], [_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select],
[_('Cut'), self.button_cb, 'cut', Gtk.STOCK_CUT, None, can_select], [_('Copy'), self.button_cb, 'copy', Gtk.STOCK_COPY, 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],
[_('Paste'), self.button_cb, 'paste', Gtk.STOCK_PASTE, None, can_select], [],
[], [_('Select All'), self.button_cb, 'select_all', None, None, can_select],
[_('Select All'), self.button_cb, 'select_all', None, None, can_select], [_('Clear Edits'), self.button_cb, 'clear_edits', Gtk.STOCK_CLEAR, None, can_isolate], # noqa: E501
[_('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]
[_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap] ]) ])
# pylint: enable=line-too-long
menu.attach_to_widget(self) menu.attach_to_widget(self)
menu.popup(None, None, None, None, event.button, event.time) menu.popup(None, None, None, None, event.button, event.time)
@ -1996,7 +2000,8 @@ class FileDiffViewer(Gtk.Grid):
diffcolours = [ diffcolours = [
theResources.getDifferenceColour(f), theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ] theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5) diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
# iterate over each exposed line # iterate over each exposed line
@ -2036,7 +2041,7 @@ class FileDiffViewer(Gtk.Grid):
# enlarge cache to fit pan.diff_cache[i] # enlarge cache to fit pan.diff_cache[i]
if i >= len(pane.diff_cache): if i >= len(pane.diff_cache):
pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [ None ]) pane.diff_cache.extend((i - len(pane.diff_cache) + 1) * [None])
# construct a list of ranges for this lines character # construct a list of ranges for this lines character
# differences if not already cached # differences if not already cached
if pane.diff_cache[i] is None: if pane.diff_cache[i] is None:
@ -2198,7 +2203,7 @@ class FileDiffViewer(Gtk.Grid):
if temp < x: if temp < x:
temp += ((x - temp) // h) * h temp += ((x - temp) // h) * h
h_half = 0.5 * h h_half = 0.5 * h
phase = [ h_half, h_half, -h_half, -h_half ] phase = [h_half, h_half, -h_half, -h_half]
for j in range(4): for j in range(4):
x_temp = temp x_temp = temp
y_temp = y_start y_temp = y_start
@ -2224,7 +2229,7 @@ class FileDiffViewer(Gtk.Grid):
if temp is None: if temp is None:
blocks = None blocks = None
else: else:
blocks = [ (0, len(temp), 'text') ] blocks = [(0, len(temp), 'text')]
else: else:
# apply the syntax highlighting rules to identify # apply the syntax highlighting rules to identify
# ranges of similarly coloured characters # ranges of similarly coloured characters
@ -2364,9 +2369,9 @@ class FileDiffViewer(Gtk.Grid):
# flags & 8 indicates regular lines with text # flags & 8 indicates regular lines with text
if self.diffmap_cache is None: if self.diffmap_cache is None:
nlines = len(self.panes[0].lines) nlines = len(self.panes[0].lines)
start = n * [ 0 ] start = n * [0]
flags = n * [ 0 ] flags = n * [0]
self.diffmap_cache = [ [] for f in range(n) ] self.diffmap_cache = [[] for f in range(n)]
# iterate over each row of lines # iterate over each row of lines
for i in range(nlines): for i in range(nlines):
nextflag = 0 nextflag = 0
@ -2419,7 +2424,8 @@ class FileDiffViewer(Gtk.Grid):
for f in range(n): for f in range(n):
diffcolours = [ diffcolours = [
theResources.getDifferenceColour(f), theResources.getDifferenceColour(f),
theResources.getDifferenceColour(f + 1) ] theResources.getDifferenceColour(f + 1)
]
diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5) diffcolours.append((diffcolours[0] + diffcolours[1]) * 0.5)
wx = f * wn wx = f * wn
# draw in two passes, more important stuff in the second pass # draw in two passes, more important stuff in the second pass
@ -2443,8 +2449,8 @@ class FileDiffViewer(Gtk.Grid):
break break
yh = max(rect.height * self.font_height * end // hmax - ymin, 1) yh = max(rect.height * self.font_height * end // hmax - ymin, 1)
#if ymin + yh <= rect.y: # if ymin + yh <= rect.y:
# continue # continue
cr.set_source_rgb(colour.red, colour.green, colour.blue) cr.set_source_rgb(colour.red, colour.green, colour.blue)
cr.rectangle(wx + pad, ymin, wn - 2 * pad, yh) cr.rectangle(wx + pad, ymin, wn - 2 * pad, yh)
@ -2458,7 +2464,7 @@ class FileDiffViewer(Gtk.Grid):
yh = rect.height * vmax // hmax - ymin yh = rect.height * vmax // hmax - ymin
if yh > 1: if yh > 1:
yh -= 1 yh -= 1
#if ymin + yh > rect.y: # if ymin + yh > rect.y:
colour = theResources.getColour('line_selection') colour = theResources.getColour('line_selection')
alpha = theResources.getFloat('line_selection_opacity') alpha = theResources.getFloat('line_selection_opacity')
cr.set_source_rgba(colour.red, colour.green, colour.blue, alpha) cr.set_source_rgba(colour.red, colour.green, colour.blue, alpha)
@ -2675,7 +2681,7 @@ class FileDiffViewer(Gtk.Grid):
elif event.keyval == Gdk.KEY_Tab and event.state & Gdk.ModifierType.CONTROL_MASK: elif event.keyval == Gdk.KEY_Tab and event.state & Gdk.ModifierType.CONTROL_MASK:
retval = False retval = False
# up/down cursor navigation # up/down cursor navigation
elif event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down ]: elif event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Page_Up, Gdk.KEY_Page_Down]:
i = self.current_line i = self.current_line
# move back to the remembered cursor column if possible # move back to the remembered cursor column if possible
col = self.cursor_column col = self.cursor_column
@ -2683,11 +2689,11 @@ class FileDiffViewer(Gtk.Grid):
# find the current cursor column # find the current cursor column
s = utils.null_to_empty(self.getLineText(f, i))[:self.current_char] s = utils.null_to_empty(self.getLineText(f, i))[:self.current_char]
col = self.stringWidth(s) col = self.stringWidth(s)
if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Down ]: if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Down]:
delta = 1 delta = 1
else: else:
delta = int(self.vadj.get_page_size() // self.font_height) delta = int(self.vadj.get_page_size() // self.font_height)
if event.keyval in [ Gdk.KEY_Up, Gdk.KEY_Page_Up ]: if event.keyval in [Gdk.KEY_Up, Gdk.KEY_Page_Up]:
delta = -delta delta = -delta
i += delta i += delta
j = 0 j = 0
@ -2818,7 +2824,7 @@ class FileDiffViewer(Gtk.Grid):
self.current_char = j self.current_char = j
self.replaceText('') self.replaceText('')
# return key, add the platform specific end of line characters # return key, add the platform specific end of line characters
elif event.keyval in [ Gdk.KEY_Return, Gdk.KEY_KP_Enter ]: elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter]:
s = os.linesep s = os.linesep
if self.prefs.getBool('editor_auto_indent'): if self.prefs.getBool('editor_auto_indent'):
start_i, start_j = self.selection_line, self.selection_char start_i, start_j = self.selection_line, self.selection_char
@ -2840,7 +2846,7 @@ class FileDiffViewer(Gtk.Grid):
s += ' ' * (w % tab_width) s += ' ' * (w % tab_width)
self.replaceText(s) self.replaceText(s)
# insert key # insert key
elif event.keyval in [ Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab ]: elif event.keyval in [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]:
start_i, start_j = self.selection_line, self.selection_char start_i, start_j = self.selection_line, self.selection_char
end_i, end_j = self.current_line, self.current_char end_i, end_j = self.current_line, self.current_char
if start_i != end_i or start_j != end_j or event.keyval == Gdk.KEY_ISO_Left_Tab: if start_i != end_i or start_j != end_j or event.keyval == Gdk.KEY_ISO_Left_Tab:
@ -3075,7 +3081,7 @@ class FileDiffViewer(Gtk.Grid):
blocks = [] blocks = []
for pane in self.panes: for pane in self.panes:
# create a new list of lines with no spacers # create a new list of lines with no spacers
newlines = [ [ line for line in pane.lines if line is not None ] ] newlines = [[line for line in pane.lines if line is not None]]
newblocks = _create_block(len(newlines[0])) newblocks = _create_block(len(newlines[0]))
if len(lines) > 0: if len(lines) > 0:
# match with neighbour to the left # match with neighbour to the left
@ -3118,8 +3124,8 @@ class FileDiffViewer(Gtk.Grid):
end = min(end, nlines) end = min(end, nlines)
n = end - start n = end - start
if n > 0: if n > 0:
lines = [ pane.lines[start:end] for pane in self.panes ] lines = [pane.lines[start:end] for pane in self.panes]
space = [ n * [ None ] for pane in self.panes ] space = [n * [None] for pane in self.panes]
lines[f], space[f] = space[f], lines[f] lines[f], space[f] = space[f], lines[f]
pre, post = _cut_blocks(end, self.blocks) pre, post = _cut_blocks(end, self.blocks)
@ -3240,7 +3246,7 @@ class FileDiffViewer(Gtk.Grid):
# swap the contents of two panes # swap the contents of two panes
def swapPanes(self, f_dst, f_src): def swapPanes(self, f_dst, f_src):
if self.undoblock is not None: if self.undoblock is not None:
self.addUndo(FileDiffViewer.SwapPanesUndo(f_dst, f_src)) self.addUndo(FileDiffViewerBase.SwapPanesUndo(f_dst, f_src))
self.current_pane = f_dst self.current_pane = f_dst
f0 = self.panes[f_dst] f0 = self.panes[f_dst]
f1 = self.panes[f_src] f1 = self.panes[f_src]
@ -3343,14 +3349,14 @@ class FileDiffViewer(Gtk.Grid):
if end < start: if end < start:
start, end = end, start start, end = end, start
# get set of lines # get set of lines
ss = [ self.getLineText(f, i) for i in range(start, end + 1) ] ss = [self.getLineText(f, i) for i in range(start, end + 1)]
# create sorted list, removing any nulls # create sorted list, removing any nulls
temp = [ s for s in ss if s is not None ] temp = [s for s in ss if s is not None]
temp.sort() temp.sort()
if descending: if descending:
temp.reverse() temp.reverse()
# add back in the nulls # add back in the nulls
temp.extend((len(ss) - len(temp)) * [ None ]) temp.extend((len(ss) - len(temp)) * [None])
for i, s in enumerate(temp): for i, s in enumerate(temp):
# update line if it changed # update line if it changed
if ss[i] != s: if ss[i] != s:
@ -3468,7 +3474,7 @@ class FileDiffViewer(Gtk.Grid):
j += 1 j += 1
if col >= tab_width: if col >= tab_width:
# convert to tabs # convert to tabs
s = ''.join([ '\t' * (col // tab_width), ' ' * (col % tab_width), text[j:] ]) s = ''.join(['\t' * (col // tab_width), ' ' * (col % tab_width), text[j:]])
# update line only if it changed # update line only if it changed
if text != s: if text != s:
self.updateText(f, i, s) self.updateText(f, i, s)
@ -3551,7 +3557,7 @@ class FileDiffViewer(Gtk.Grid):
if end < start: if end < start:
start, end = end, start start, end = end, start
end = min(end + 1, len(pane.lines)) end = min(end + 1, len(pane.lines))
ss = [ self.getLineText(f_src, i) for i in range(start, end) ] ss = [self.getLineText(f_src, i) for i in range(start, end)]
if pane.format == 0: if pane.format == 0:
# copy the format of the source pane if the format for the # copy the format of the source pane if the format for the
# destination pane as not yet been determined # destination pane as not yet been determined
@ -3610,20 +3616,20 @@ class FileDiffViewer(Gtk.Grid):
end = min(end, nlines) end = min(end, nlines)
n = end - start n = end - start
if n > 0: if n > 0:
lines = [ pane.lines[start:end] for pane in self.panes ] lines = [pane.lines[start:end] for pane in self.panes]
spaces = [ n * [ None ] for pane in self.panes ] spaces = [n * [None] for pane in self.panes]
old_content = [ line for line in lines[f] if line is not None ] old_content = [line for line in lines[f] if line is not None]
for i in range(f + 1, npanes): for i in range(f + 1, npanes):
lines[i], spaces[i] = spaces[i], lines[i] lines[i], spaces[i] = spaces[i], lines[i]
# replace f's lines with merge content # replace f's lines with merge content
if f > 0: if f > 0:
lines[f] = lines[f - 1][:] lines[f] = lines[f - 1][:]
else: else:
lines[f] = n * [ None ] lines[f] = n * [None]
if f + 1 < npanes: if f + 1 < npanes:
spaces[f] = spaces[f + 1][:] spaces[f] = spaces[f + 1][:]
else: else:
spaces[f] = n * [ None ] spaces[f] = n * [None]
if right_first: if right_first:
lines, spaces = spaces, lines lines, spaces = spaces, lines
@ -3632,8 +3638,8 @@ class FileDiffViewer(Gtk.Grid):
# join and remove null lines # join and remove null lines
b.extend(b) b.extend(b)
for l, s in zip(lines, spaces): for line, space in zip(lines, spaces):
l.extend(s) line.extend(space)
_remove_null_lines(b, lines) _remove_null_lines(b, lines)
# replace f's lines with original, growing if necessary # replace f's lines with original, growing if necessary
@ -3647,14 +3653,14 @@ class FileDiffViewer(Gtk.Grid):
delta = -delta delta = -delta
for i in range(npanes): for i in range(npanes):
if i != f: if i != f:
lines[i].extend(delta * [ None ]) lines[i].extend(delta * [None])
# grow last block # grow last block
if len(b) > 0: if len(b) > 0:
b[-1] += delta b[-1] += delta
else: else:
b = _create_block(delta) b = _create_block(delta)
elif delta > 0: elif delta > 0:
old_content.extend(delta * [ None ]) old_content.extend(delta * [None])
new_n = len(old_content) new_n = len(old_content)
# update lines and blocks # update lines and blocks
@ -3686,6 +3692,7 @@ class FileDiffViewer(Gtk.Grid):
def merge_from_right_then_left(self): def merge_from_right_then_left(self):
self._mergeBoth(True) self._mergeBoth(True)
# convenience method for creating a menu according to a template # convenience method for creating a menu according to a template
def createMenu(specs, radio=None, accel_group=None): def createMenu(specs, radio=None, accel_group=None):
menu = Gtk.Menu.new() menu = Gtk.Menu.new()
@ -3707,7 +3714,7 @@ def createMenu(specs, radio=None, accel_group=None):
item.connect('activate', cb, data) item.connect('activate', cb, data)
if len(spec) > 3 and spec[3] is not None: if len(spec) > 3 and spec[3] is not None:
image = Gtk.Image.new() image = Gtk.Image.new()
image.set_from_stock(spec[3], Gtk.IconSize.MENU) # pylint: disable=no-member image.set_from_stock(spec[3], Gtk.IconSize.MENU)
item.set_image(image) item.set_image(image)
if accel_group is not None and len(spec) > 4: if accel_group is not None and len(spec) > 4:
a = theResources.getKeyBindings('menu', spec[4]) a = theResources.getKeyBindings('menu', spec[4])
@ -3730,10 +3737,12 @@ def createMenu(specs, radio=None, accel_group=None):
menu.append(item) menu.append(item)
return menu return menu
ALPHANUMERIC_CLASS = 0 ALPHANUMERIC_CLASS = 0
WHITESPACE_CLASS = 1 WHITESPACE_CLASS = 1
OTHER_CLASS = 2 OTHER_CLASS = 2
# maps similar types of characters to a group # maps similar types of characters to a group
def _get_character_class(c): def _get_character_class(c):
if c.isalnum() or c == '_': if c.isalnum() or c == '_':
@ -3742,11 +3751,12 @@ def _get_character_class(c):
return WHITESPACE_CLASS return WHITESPACE_CLASS
return OTHER_CLASS return OTHER_CLASS
# patience diff with difflib-style fallback # patience diff with difflib-style fallback
def _patience_diff(a, b): def _patience_diff(a, b):
matches, len_a, len_b = [], len(a), len(b) matches, len_a, len_b = [], len(a), len(b)
if len_a and len_b: if len_a and len_b:
blocks = [ (0, len_a, 0, len_b, 0) ] blocks = [(0, len_a, 0, len_b, 0)]
while blocks: while blocks:
start_a, end_a, start_b, end_b, match_idx = blocks.pop() start_a, end_a, start_b, end_b, match_idx = blocks.pop()
aa, bb = a[start_a:end_a], b[start_b:end_b] aa, bb = a[start_a:end_a], b[start_b:end_b]
@ -3822,6 +3832,7 @@ def _patience_diff(a, b):
matches.append((len_a, len_b, 0)) matches.append((len_a, len_b, 0))
return matches return matches
# longest common subsequence of unique elements common to 'a' and 'b' # longest common subsequence of unique elements common to 'a' and 'b'
def _patience_subsequence(a, b): def _patience_subsequence(a, b):
# value unique lines by their order in each list # value unique lines by their order in each list
@ -3877,6 +3888,7 @@ def _patience_subsequence(a, b):
result.reverse() result.reverse()
return result return result
# difflib-style approximation of the longest common subsequence # difflib-style approximation of the longest common subsequence
def _lcs_approx(a, b): def _lcs_approx(a, b):
count1, lookup = {}, {} count1, lookup = {}, {}
@ -3888,7 +3900,7 @@ def _lcs_approx(a, b):
if s in lookup: if s in lookup:
lookup[s].append(i) lookup[s].append(i)
else: else:
lookup[s] = [ i ] lookup[s] = [i]
if set(lookup).intersection(count1): if set(lookup).intersection(count1):
# we have some common elements # we have some common elements
# identify popular entries # identify popular entries
@ -3921,7 +3933,7 @@ def _lcs_approx(a, b):
max_indices.append((ai, bi)) max_indices.append((ai, bi))
else: else:
max_length = v max_length = v
max_indices = [ (ai, bi) ] max_indices = [(ai, bi)]
else: else:
prev_get = prev_matches.get prev_get = prev_matches.get
for bi in lookup[s]: for bi in lookup[s]:
@ -3932,7 +3944,7 @@ def _lcs_approx(a, b):
max_indices.append((ai, bi)) max_indices.append((ai, bi))
else: else:
max_length = v max_length = v
max_indices = [ (ai, bi) ] max_indices = [(ai, bi)]
prev_matches, matches = matches, {} prev_matches, matches = matches, {}
if max_indices: if max_indices:
# include any popular entries at the beginning # include any popular entries at the beginning
@ -3950,18 +3962,22 @@ def _lcs_approx(a, b):
return aidx, bidx, nidx return aidx, bidx, nidx
return None return None
# True if the string ends with '\r\n' # True if the string ends with '\r\n'
def _has_dos_line_ending(s): def _has_dos_line_ending(s):
return s.endswith('\r\n') return s.endswith('\r\n')
# True if the string ends with '\r' # True if the string ends with '\r'
def _has_mac_line_ending(s): def _has_mac_line_ending(s):
return s.endswith('\r') return s.endswith('\r')
# True if the string ends with '\n' but not '\r\n' # True if the string ends with '\n' but not '\r\n'
def _has_unix_line_ending(s): def _has_unix_line_ending(s):
return s.endswith('\n') and not s.endswith('\r\n') return s.endswith('\n') and not s.endswith('\r\n')
# returns the format mask for a list of strings # returns the format mask for a list of strings
def _get_format(ss): def _get_format(ss):
flags = 0 flags = 0
@ -3975,10 +3991,11 @@ def _get_format(ss):
flags |= utils.UNIX_FORMAT flags |= utils.UNIX_FORMAT
return flags return flags
# convenience method to change the line ending of a string # convenience method to change the line ending of a string
def _convert_to_format(s, fmt): def _convert_to_format(s, fmt):
if s is not None and fmt != 0: if s is not None and fmt != 0:
old_format = _get_format([ s ]) old_format = _get_format([s])
if old_format != 0 and (old_format & fmt) == 0: if old_format != 0 and (old_format & fmt) == 0:
s = utils.strip_eol(s) s = utils.strip_eol(s)
# prefer the host line ending style # prefer the host line ending style
@ -3996,6 +4013,7 @@ def _convert_to_format(s, fmt):
s += '\r' s += '\r'
return s return s
# Enforcing manual alignment is accomplished by dividing the lines of text into # Enforcing manual alignment is accomplished by dividing the lines of text into
# sections that are matched independently. 'blocks' is an array of integers # sections that are matched independently. 'blocks' is an array of integers
# describing how many lines (including null lines for spacing) that are in each # describing how many lines (including null lines for spacing) that are in each
@ -4005,12 +4023,12 @@ def _convert_to_format(s, fmt):
# in this array so 'blocks' will be an empty array when there are no lines. A # in this array so 'blocks' will be an empty array when there are no lines. A
# 'cut' at location 'i' means a line 'i-1' and line 'i' belong to different # 'cut' at location 'i' means a line 'i-1' and line 'i' belong to different
# sections # sections
def _create_block(n): def _create_block(n):
if n > 0: if n > 0:
return [ n ] return [n]
return [] return []
# returns the two sets of blocks after cutting at 'i' # returns the two sets of blocks after cutting at 'i'
def _cut_blocks(i, blocks): def _cut_blocks(i, blocks):
pre, post, nlines = [], [], 0 pre, post, nlines = [], [], 0
@ -4026,6 +4044,7 @@ def _cut_blocks(i, blocks):
nlines += b nlines += b
return pre, post return pre, post
# returns a set of blocks containing all of the cuts in the inputs # returns a set of blocks containing all of the cuts in the inputs
def _merge_blocks(leftblocks, rightblocks): def _merge_blocks(leftblocks, rightblocks):
leftblocks, rightblocks, b = leftblocks[:], rightblocks[:], [] leftblocks, rightblocks, b = leftblocks[:], rightblocks[:], []
@ -4043,6 +4062,7 @@ def _merge_blocks(leftblocks, rightblocks):
b.append(n) b.append(n)
return b return b
# utility method to simplify working with structures used to describe character # utility method to simplify working with structures used to describe character
# differences of a line # differences of a line
# #
@ -4053,7 +4073,7 @@ def _merge_blocks(leftblocks, rightblocks):
# this method will return the union of two sorted lists of ranges # this method will return the union of two sorted lists of ranges
def _merge_ranges(r1, r2): def _merge_ranges(r1, r2):
r1, r2, result, start = r1[:], r2[:], [], 0 r1, r2, result, start = r1[:], r2[:], [], 0
rs = [ r1, r2 ] rs = [r1, r2]
while len(r1) > 0 and len(r2) > 0: while len(r1) > 0 and len(r2) > 0:
flags, start = 0, min(r1[0][0], r2[0][0]) flags, start = 0, min(r1[0][0], r2[0][0])
if start == r1[0][0]: if start == r1[0][0]:
@ -4078,6 +4098,7 @@ def _merge_ranges(r1, r2):
result.extend(r2) result.extend(r2)
return result return result
# eliminates lines that are spacing lines in all panes # eliminates lines that are spacing lines in all panes
def _remove_null_lines(blocks, lines_set): def _remove_null_lines(blocks, lines_set):
bi, bn, i = 0, 0, 0 bi, bn, i = 0, 0, 0
@ -4097,22 +4118,23 @@ def _remove_null_lines(blocks, lines_set):
bn += blocks[bi] bn += blocks[bi]
bi += 1 bi += 1
# returns true if the string only contains whitespace characters # returns true if the string only contains whitespace characters
def _is_blank(s): def _is_blank(s):
for c in utils.whitespace: for c in utils.whitespace:
s = s.replace(c, '') s = s.replace(c, '')
return len(s) == 0 return len(s) == 0
# use Pango.SCALE instead of Pango.PIXELS to avoid overflow exception # use Pango.SCALE instead of Pango.PIXELS to avoid overflow exception
def _pixels(size): def _pixels(size):
return int(size / Pango.SCALE + 0.5) return int(size / Pango.SCALE + 0.5)
# create 'title_changed' signal for FileDiffViewer
# pylint: disable=line-too-long # create 'title_changed' signal for FileDiffViewerBase
GObject.signal_new('swapped-panes', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) GObject.signal_new('swapped-panes', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
GObject.signal_new('num-edits-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) GObject.signal_new('num-edits-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, )) # noqa: E501
GObject.signal_new('mode-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) GObject.signal_new('mode-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('cursor-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) GObject.signal_new('cursor-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ()) # noqa: E501
GObject.signal_new('syntax-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) GObject.signal_new('syntax-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (str, )) # noqa: E501
GObject.signal_new('format-changed', FileDiffViewer, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) GObject.signal_new('format-changed', FileDiffViewerBase, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (int, int)) # noqa: E501
# pylint: enable=line-too-long

View File

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

View File

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