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

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
pylint~=2.11
flake8 ~= 3.8
mypy ~= 0.910
PyGObject ~= 3.40

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,14 +10,14 @@ gnomeSiteDir = os.path.join(siteDir, "gnome")
# This is the list of dll which are required by PyGI.
# I get this list of DLL using http://technet.microsoft.com/en-us/sysinternals/bb896656.aspx
# Procedure:
# Procedure:
# 1) Run your from from your IDE
# 2) Command for using listdlls.exe
# c:/path/to/listdlls.exe python.exe > output.txt
# 3) This would return lists of all dll required by you program
# in my case most of dll file were located in c:\python27\Lib\site-packages\gnome
# 3) This would return lists of all dll required by you program
# in my case most of dll file were located in c:\python27\Lib\site-packages\gnome
# (I am using PyGI (all in one) installer)
# 4) Below is the list of gnome dll I received from listdlls.exe result.
# 4) Below is the list of gnome dll I received from listdlls.exe result.
# If you prefer you can import all dlls from c:\python27\Lib\site-packages\gnome folder
#missingDll = glob.glob(gnomeSiteDir + "\\" + '*.dll')
@ -142,10 +142,10 @@ for dll in missingDll:
includeFiles.append((os.path.join(gnomeSiteDir, dll), dll))
#includeFiles.append(dll)
# You can import all Gtk Runtime data from gtk folder
# You can import all Gtk Runtime data from gtk folder
#gnomeLibs= ['etc','lib','share']
# You can import only important Gtk Runtime data from gtk folder
# You can import only important Gtk Runtime data from gtk folder
gnomeLibs = [
'lib\\gdk-pixbuf-2.0',
'lib\\girepository-1.0',
@ -196,7 +196,7 @@ def copyFile(src, dest, use_text_mode=False,enc=None):
s = f.read()
f.close()
if enc is not None:
s = codecs.encode(unicode(s, 'utf_8'), enc)
s = codecs.encode(str(s, encoding='utf_8'), enc)
f = open(dest, w)
f.write(s)
f.close()