Merge pull request #140 from MightyCreak/static-types

Convert some functions to static typing
This commit is contained in:
Creak 2022-01-08 18:20:33 -05:00 committed by GitHub
commit 8ac41b0b90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 173 additions and 159 deletions

View File

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

View File

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Renamed application name from "Diffuse Merge Tool" to "Diffuse"
- Linters can be run sooner (before installation)
- Better messages when an error occurs while parsing the config file
- Start converting the code to static types
### Fixed
- Removed the lasting lint errors (i.e. in main.py)

View File

@ -18,11 +18,12 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from gettext import gettext as _
from typing import Final
APP_NAME = 'Diffuse'
COPYRIGHT = '''{copyright} © 2006-2019 Derrick Moser
APP_NAME: Final[str] = 'Diffuse'
COPYRIGHT: Final[str] = '''{copyright} © 2006-2019 Derrick Moser
{copyright} © 2015-2021 Romain Failliot'''.format(copyright=_("Copyright"))
WEBSITE = 'https://mightycreak.github.io/diffuse/'
WEBSITE: Final[str] = 'https://mightycreak.github.io/diffuse/'
# Constants are set in main()
VERSION = '0.0.0'
VERSION: str = '0.0.0'

View File

@ -32,7 +32,7 @@ from gi.repository import GObject, Gtk # type: ignore # noqa: E402
# the about dialog
class AboutDialog(Gtk.AboutDialog):
def __init__(self):
def __init__(self) -> None:
Gtk.AboutDialog.__init__(self)
self.set_logo_icon_name('io.github.mightycreak.Diffuse')
self.set_program_name(constants.APP_NAME)
@ -104,13 +104,13 @@ class FileChooserDialog(Gtk.FileChooserDialog):
def set_encoding(self, encoding):
self.encoding.set_text(encoding)
def get_encoding(self):
def get_encoding(self) -> str:
return self.encoding.get_text()
def get_revision(self):
def get_revision(self) -> str:
return self.revision.get_text()
def get_filename(self):
def get_filename(self) -> str:
# convert from UTF-8 string to unicode
return Gtk.FileChooserDialog.get_filename(self)

View File

@ -26,6 +26,7 @@ import stat
import webbrowser
from gettext import gettext as _
from typing import Optional
from urllib.parse import urlparse
from diffuse import constants
@ -33,6 +34,7 @@ from diffuse import utils
from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, SearchDialog
from diffuse.preferences import Preferences
from diffuse.resources import theResources
from diffuse.utils import LineEnding
from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import FileDiffViewerBase
from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE
@ -118,7 +120,7 @@ class Diffuse(Gtk.Window):
class FileDiffViewer(FileDiffViewerBase):
# pane header
class PaneHeader(Gtk.Box):
def __init__(self):
def __init__(self) -> None:
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...')],
@ -171,7 +173,7 @@ class Diffuse(Gtk.Window):
# pane footer
class PaneFooter(Gtk.Box):
def __init__(self):
def __init__(self) -> None:
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.cursor = label = Gtk.Label.new()
self.cursor.set_size_request(-1, -1)
@ -212,11 +214,11 @@ class Diffuse(Gtk.Window):
# set the format label
def setFormat(self, s):
v = []
if s & utils.DOS_FORMAT:
if s & LineEnding.DOS_FORMAT:
v.append('DOS')
if s & utils.MAC_FORMAT:
if s & LineEnding.MAC_FORMAT:
v.append('Mac')
if s & utils.UNIX_FORMAT:
if s & LineEnding.UNIX_FORMAT:
v.append('Unix')
self.format.set_text('/'.join(v))
@ -1108,7 +1110,7 @@ class Diffuse(Gtk.Window):
self.quit_cb(widget, data)
# convenience method to request confirmation when closing the last tab
def _confirm_tab_close(self):
def _confirm_tab_close(self) -> bool:
dialog = utils.MessageDialog(
self.get_toplevel(),
Gtk.MessageType.WARNING,
@ -1193,7 +1195,7 @@ class Diffuse(Gtk.Window):
self.setSyntax(s)
# create an empty viewer with 'n' panes
def newFileDiffViewer(self, n):
def newFileDiffViewer(self, n: int) -> FileDiffViewer:
self.viewer_count += 1
tabname = _('File Merge %d') % (self.viewer_count, )
tab = NotebookTab(tabname, Gtk.STOCK_FILE)
@ -1338,13 +1340,13 @@ class Diffuse(Gtk.Window):
)
# close all tabs without differences
def closeOnSame(self):
def closeOnSame(self) -> None:
for i in range(self.notebook.get_n_pages() - 1, -1, -1):
if not self.notebook.get_nth_page(i).hasDifferences():
self.notebook.remove_page(i)
# returns True if the application can safely quit
def confirmQuit(self):
def confirmQuit(self) -> bool:
nb = self.notebook
return self.confirmCloseViewers([nb.get_nth_page(i) for i in range(nb.get_n_pages())])
@ -1356,7 +1358,7 @@ class Diffuse(Gtk.Window):
return True
# returns the currently focused viewer
def getCurrentViewer(self):
def getCurrentViewer(self) -> Optional[Gtk.Widget]:
return self.notebook.get_nth_page(self.notebook.get_current_page())
# callback for the open file menu item
@ -1570,7 +1572,7 @@ class Diffuse(Gtk.Window):
self.getCurrentViewer().go_to_line_cb(widget, data)
# notify all viewers of changes to the preferences
def preferences_updated(self):
def preferences_updated(self) -> None:
n = self.notebook.get_n_pages()
self.notebook.set_show_tabs(self.prefs.getBool('tabs_always_show') or n > 1)
for i in range(n):
@ -1712,7 +1714,7 @@ def _append_buttons(box, size, specs):
# constructs a full URL for the named file
def _path2url(path, proto='file'):
def _path2url(path: str, proto: str = 'file') -> str:
r = [proto, ':///']
s = os.path.abspath(path)
i = 0

View File

@ -199,7 +199,7 @@ class Preferences:
self.path = path
if os.path.isfile(self.path):
try:
with open(self.path, 'r', encoding='utf-8') as f:
with open(self.path, 'r', encoding='utf-8') as f:
ss = utils.readconfiglines(f)
for j, s in enumerate(ss):
try:
@ -342,7 +342,7 @@ class Preferences:
if tpl[0] in ['Font', 'Integer']:
entry = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
if tpl[0] == 'Font':
button = _FontButton()
button = Gtk.FontButton()
button.set_font(self.string_prefs[tpl[1]])
else:
button = Gtk.SpinButton.new(
@ -368,28 +368,28 @@ class Preferences:
return table
# get/set methods to manipulate the preference values
def getBool(self, name):
def getBool(self, name: str) -> bool:
return self.bool_prefs[name]
def setBool(self, name, value):
def setBool(self, name: str, value: bool) -> None:
self.bool_prefs[name] = value
def getInt(self, name):
def getInt(self, name: str) -> int:
return self.int_prefs[name]
def getString(self, name):
def getString(self, name: str) -> str:
return self.string_prefs[name]
def setString(self, name, value):
def setString(self, name: str, value: str) -> None:
self.string_prefs[name] = value
def getEncodings(self):
return self.encodings
def _getDefaultEncodings(self):
def _getDefaultEncodings(self) -> list[str]:
return self.string_prefs['encoding_auto_detect_codecs'].split()
def getDefaultEncoding(self):
def getDefaultEncoding(self) -> str:
return self.string_prefs['encoding_default_codec']
# attempt to convert a string to unicode from an unknown encoding
@ -412,7 +412,7 @@ class Preferences:
# cygwin and native applications can be used on windows, use this method
# to convert a path to the usual form expected on sys.platform
def convertToNativePath(self, s):
def convertToNativePath(self, s: str) -> str:
if utils.isWindows() and s.find('/') >= 0:
# treat as a cygwin path
s = s.replace(os.sep, '/')
@ -436,15 +436,6 @@ class Preferences:
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):
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):
@ -475,8 +466,8 @@ class _FileEntry(Gtk.Box):
self.entry.set_text(dialog.get_filename())
dialog.destroy()
def set_text(self, s):
def set_text(self, s: str) -> None:
self.entry.set_text(s)
def get_text(self):
def get_text(self) -> str:
return self.entry.get_text()

View File

@ -31,6 +31,7 @@ import shlex
from distutils import util
from gettext import gettext as _
from typing import Final
from diffuse import utils
@ -529,7 +530,7 @@ class Resources:
# colour resources
class _Colour:
def __init__(self, r, g, b, a=1.0):
def __init__(self, r: float, g: float, b: float, a: float = 1.0):
# the individual colour components as floats in the range [0, 1]
self.red = r
self.green = g
@ -601,4 +602,4 @@ class _SyntaxParser:
return state_name, blocks
theResources = Resources()
theResources: Final[Resources] = Resources()

View File

@ -23,7 +23,9 @@ import locale
import subprocess
import traceback
from enum import IntFlag
from gettext import gettext as _
from typing import Final, Optional, TextIO
from diffuse import constants
from diffuse.resources import theResources
@ -74,38 +76,38 @@ class EncodingMenu(Gtk.Box):
return self.encodings[i] if i >= 0 else None
# platform test
def isWindows():
def isWindows() -> bool:
'''Returns true if OS is Windows; otherwise false.'''
return os.name == 'nt'
def _logPrintOutput(msg):
def _logPrintOutput(msg: str) -> None:
if theResources.getOptionAsBool('log_print_output'):
print(msg, file=sys.stderr)
if theResources.getOptionAsBool('log_print_stack'):
traceback.print_stack()
# convenience function to display debug messages
def logDebug(msg):
def logDebug(msg: str) -> None:
'''Report debug message.'''
_logPrintOutput(f'DEBUG: {msg}')
# report error messages
def logError(msg):
def logError(msg: str) -> None:
'''Report error message.'''
_logPrintOutput(f'ERROR: {msg}')
# report error messages and show dialog
def logErrorAndDialog(msg, parent=None):
def logErrorAndDialog(msg: str, parent: Gtk.Widget = None) -> None:
'''Report error message and show dialog.'''
logError(msg)
dialog = MessageDialog(parent, Gtk.MessageType.ERROR, msg)
dialog.run()
dialog.destroy()
# create nested subdirectories and return the complete path
def make_subdirs(p, ss):
def make_subdirs(p: str, ss: list[str]) -> str:
'''Create nested subdirectories and return the complete path.'''
for s in ss:
p = os.path.join(p, s)
if not os.path.exists(p):
@ -116,16 +118,16 @@ def make_subdirs(p, ss):
return p
# returns the Windows drive or share from a from an absolute path
def _drive_from_path(path):
def _drive_from_path(path: str) -> str:
'''Returns the Windows drive or share from a from an absolute path.'''
d = path.split(os.sep)
if len(d) > 3 and d[0] == '' and d[1] == '':
return os.path.join(d[:4])
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):
def relpath(a: str, b: str) -> str:
'''Constructs a relative path from 'a' to 'b', both should be absolute paths.'''
if isWindows():
if _drive_from_path(a) != _drive_from_path(b):
return b
@ -151,12 +153,12 @@ def safeRelativePath(abspath1, name, prefs, cygwin_pref):
return s
# escape arguments for use with bash
def _bash_escape(s):
def _bash_escape(s: str) -> str:
'''Escape arguments for use with bash.'''
return "'" + s.replace("'", "'\\''") + "'"
def _use_flatpak():
def _use_flatpak() -> bool:
return theResources.getOptionAsBool('use_flatpak')
@ -201,9 +203,8 @@ def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
return s
# returns the number of characters in the string excluding any line ending
# characters
def len_minus_line_ending(s):
def len_minus_line_ending(s: str) -> int:
'''Returns the number of characters in the string excluding any line ending characters.'''
if s is None:
return 0
n = len(s)
@ -214,15 +215,15 @@ def len_minus_line_ending(s):
return n
# returns the string without the line ending characters
def strip_eol(s):
def strip_eol(s: str) -> str:
'''Returns the string without the line ending characters.'''
if s:
s = s[:len_minus_line_ending(s)]
return s
# returns the list of strings without line ending characters
def _strip_eols(ss):
def _strip_eols(ss: list[str]) -> list[str]:
'''Returns the list of strings without line ending characters.'''
return [strip_eol(s) for s in ss]
@ -232,12 +233,12 @@ def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None):
dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore')))
def readconfiglines(fd):
def readconfiglines(fd: TextIO) -> list[str]:
return fd.read().replace('\r', '').split('\n')
# escape special glob characters
def globEscape(s):
def globEscape(s: str) -> str:
'''Escape special glob characters.'''
m = {c: f'[{c}]' for c in '[]?*'}
return ''.join([m.get(c, c) for c in s])
@ -272,25 +273,25 @@ def splitlines(text: str) -> list[str]:
# also recognize old Mac OS line endings
def readlines(fd):
def readlines(fd: TextIO) -> list[str]:
return _strip_eols(splitlines(fd.read()))
# map an encoding name to its standard form
def norm_encoding(e):
def norm_encoding(e: Optional[str]) -> Optional[str]:
'''Map an encoding name to its standard form.'''
if e is not None:
return e.replace('-', '_').lower()
return None
def null_to_empty(s):
def null_to_empty(s: Optional[str]) -> str:
if s is None:
s = ''
return s
# utility method to step advance an adjustment
def step_adjustment(adj, delta):
def step_adjustment(adj: Gtk.Adjustment, delta: int) -> None:
v = adj.get_value() + delta
# clamp to the allowed range
v = max(v, int(adj.get_lower()))
@ -298,36 +299,43 @@ def step_adjustment(adj, delta):
adj.set_value(v)
# masks used to indicate the presence of particular line endings
DOS_FORMAT = 1
MAC_FORMAT = 2
UNIX_FORMAT = 4
def _get_default_lang() -> Optional[str]:
lang = locale.getdefaultlocale()[0]
if isWindows():
# gettext looks for the language using environment variables which
# are normally not set on Windows so we try setting it for them
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
if lang_env in os.environ:
lang = os.environ[lang_env]
# remove any additional languages, encodings, or modifications
for c in ':.@':
lang = lang.split(c)[0]
break
else:
if lang is not None:
os.environ['LANG'] = lang
return lang
class LineEnding(IntFlag):
'''Enumeration of line endings.
Values can be used as flags in bitwise operations.'''
DOS_FORMAT = 1
MAC_FORMAT = 2
UNIX_FORMAT = 4
# avoid some dictionary lookups when string.whitespace is used in loops
# this is sorted based upon frequency to speed up code for stripping whitespace
whitespace = ' \t\n\r\x0b\x0c'
whitespace: Final[str] = ' \t\n\r\x0b\x0c'
# use the program's location as a starting place to search for supporting files
# such as icon and help documentation
if hasattr(sys, 'frozen'):
app_path = sys.executable
else:
app_path = os.path.realpath(sys.argv[0])
bin_dir = os.path.dirname(app_path)
app_path: Final[str] = sys.executable if hasattr(sys, 'frozen') else os.path.realpath(sys.argv[0])
bin_dir: Final[str] = os.path.dirname(app_path)
# translation location: '../share/locale/<LANG>/LC_MESSAGES/diffuse.mo'
# where '<LANG>' is the language key
lang = locale.getdefaultlocale()[0]
if isWindows():
# gettext looks for the language using environment variables which
# are normally not set on Windows so we try setting it for them
for lang_env in 'LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE':
if lang_env in os.environ:
lang = os.environ[lang_env]
# remove any additional languages, encodings, or modifications
for c in ':.@':
lang = lang.split(c)[0]
break
else:
if lang is not None:
os.environ['LANG'] = lang
lang: Final[Optional[str]] = _get_default_lang()

View File

@ -20,6 +20,7 @@
import os
from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
@ -61,7 +62,7 @@ class Git(VcsInterface):
# sort the results
return [modified[k] for k in sorted(modified.keys())]
def _extractPath(self, s, prefs):
def _extractPath(self, s: str, prefs: Preferences) -> str:
return os.path.join(self.root, prefs.convertToNativePath(s.strip()))
def getFolderTemplate(self, prefs, names):

View File

@ -19,6 +19,8 @@
import os
from typing import Optional
from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
@ -26,9 +28,9 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Mercurial support
class Hg(VcsInterface):
def __init__(self, root):
def __init__(self, root: str):
VcsInterface.__init__(self, root)
self.working_rev = None
self.working_rev: Optional[str] = None
def _getPreviousRevision(self, prefs, rev):
if rev is None:

View File

@ -25,21 +25,21 @@ from diffuse.vcs.svn import Svn
class Svk(Svn):
@staticmethod
def _getVcs():
def _getVcs() -> str:
return 'svk'
@staticmethod
def _getURLPrefix():
def _getURLPrefix() -> str:
return 'Depot Path: '
@staticmethod
def _parseStatusLine(s):
def _parseStatusLine(s: str) -> tuple[str, str]:
if len(s) < 4 or s[0] not in 'ACDMR':
return '', ''
return s[0], s[4:]
@staticmethod
def _getPreviousRevision(rev):
def _getPreviousRevision(rev: str) -> str:
if rev is None:
return 'HEAD'
if rev.endswith('@'):

View File

@ -21,6 +21,7 @@ import os
import glob
from gettext import gettext as _
from typing import Optional
from diffuse import utils
from diffuse.vcs.folder_set import FolderSet
@ -30,20 +31,20 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Subversion support
# SVK support subclasses from this
class Svn(VcsInterface):
def __init__(self, root):
def __init__(self, root: str):
VcsInterface.__init__(self, root)
self.url = None
self.url: Optional[str] = None
@staticmethod
def _getVcs():
def _getVcs() -> str:
return 'svn'
@staticmethod
def _getURLPrefix():
def _getURLPrefix() -> str:
return 'URL: '
@staticmethod
def _parseStatusLine(s):
def _parseStatusLine(s: str) -> tuple[str, str]:
if len(s) < 8 or s[0] not in 'ACDMR':
return '', ''
# subversion 1.6 adds a new column
@ -53,7 +54,7 @@ class Svn(VcsInterface):
return s[0], s[k:]
@staticmethod
def _getPreviousRevision(rev):
def _getPreviousRevision(rev: str) -> str:
if rev is None:
return 'BASE'
m = int(rev)

View File

@ -20,7 +20,7 @@
class VcsInterface:
"""Interface for the VCSs."""
def __init__(self, root):
def __init__(self, root: str):
"""The object will initialized with the repository's root folder."""
self.root = root

View File

@ -20,9 +20,12 @@
import os
from gettext import gettext as _
from typing import Optional
from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface
from diffuse.vcs.bzr import Bzr
from diffuse.vcs.cvs import Cvs
from diffuse.vcs.darcs import Darcs
@ -35,7 +38,7 @@ from diffuse.vcs.svn import Svn
class VcsRegistry:
def __init__(self):
def __init__(self) -> None:
# initialise the VCS objects
self._get_repo = {
'bzr': _get_bzr_repo,
@ -50,7 +53,7 @@ class VcsRegistry:
}
# determines which VCS to use for files in the named folder
def findByFolder(self, path, prefs):
def findByFolder(self, path: str, prefs: Preferences) -> Optional[VcsInterface]:
path = os.path.abspath(path)
for vcs in prefs.getString('vcs_search_order').split():
if vcs in self._get_repo:
@ -60,14 +63,14 @@ class VcsRegistry:
return None
# determines which VCS to use for the named file
def findByFilename(self, name, prefs):
def findByFilename(self, name: str, prefs: Preferences) -> Optional[VcsInterface]:
if name is not None:
return self.findByFolder(os.path.dirname(name), prefs)
return None
# utility method to help find folders used by version control systems
def _find_parent_dir_with(path, dir_name):
def _find_parent_dir_with(path: str, dir_name: str) -> Optional[str]:
while True:
name = os.path.join(path, dir_name)
if os.path.isdir(name):
@ -76,28 +79,28 @@ def _find_parent_dir_with(path, dir_name):
if newpath == path:
break
path = newpath
return None
def _get_bzr_repo(path, prefs):
def _get_bzr_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
p = _find_parent_dir_with(path, '.bzr')
return Bzr(p) if p else None
def _get_cvs_repo(path, prefs):
def _get_cvs_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
return Cvs(path) if os.path.isdir(os.path.join(path, 'CVS')) else None
def _get_darcs_repo(path, prefs):
def _get_darcs_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
p = _find_parent_dir_with(path, '_darcs')
return Darcs(p) if p else None
def _get_git_repo(path, prefs):
def _get_git_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
if 'GIT_DIR' in os.environ:
try:
d = path
ss = utils.popenReadLines(
d,
ss: list[str] = utils.popenReadLines(
path,
[
prefs.getString('git_bin'),
'rev-parse',
@ -107,20 +110,20 @@ def _get_git_repo(path, prefs):
'git_bash')
if len(ss) > 0:
# be careful to handle trailing slashes
d = d.split(os.sep)
if d[-1] != '':
d.append('')
dirs = path.split(os.sep)
if dirs[-1] != '':
dirs.append('')
ss = utils.strip_eol(ss[0]).split('/')
if ss[-1] != '':
ss.append('')
n = len(ss)
if n <= len(d):
del d[-n:]
if len(d) == 0:
d = os.curdir
if n <= len(dirs):
del dirs[-n:]
if len(dirs) == 0:
path = os.curdir
else:
d = os.sep.join(d)
return Git(d)
path = os.sep.join(dirs)
return Git(path)
except (IOError, OSError):
# working tree not found
pass
@ -133,19 +136,20 @@ def _get_git_repo(path, prefs):
if newpath == path:
break
path = newpath
return None
def _get_hg_repo(path, prefs):
def _get_hg_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
p = _find_parent_dir_with(path, '.hg')
return Hg(p) if p else None
def _get_mtn_repo(path, prefs):
def _get_mtn_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
p = _find_parent_dir_with(path, '_MTN')
return Mtn(p) if p else None
def _get_rcs_repo(path, prefs):
def _get_rcs_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
if os.path.isdir(os.path.join(path, 'RCS')):
return Rcs(path)
@ -162,12 +166,12 @@ def _get_rcs_repo(path, prefs):
return None
def _get_svn_repo(path, prefs):
def _get_svn_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
p = _find_parent_dir_with(path, '.svn')
return Svn(p) if p else None
def _get_svk_repo(path, prefs):
def _get_svk_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
name = path
# parse the ~/.svk/config file to discover which directories are part of
# SVK repositories
@ -181,7 +185,7 @@ def _get_svk_repo(path, prefs):
try:
# find working copies by parsing the config file
with open(svkconfig, 'r', encoding='utf-8') as f:
ss = utils.readlines(f)
ss: list[str] = utils.readlines(f)
projs, sep = [], os.sep
# find the separator character
for s in ss:

View File

@ -22,10 +22,11 @@ import os
import unicodedata
from gettext import gettext as _
from typing import Dict
from typing import Any, Dict, List
from diffuse import utils
from diffuse.resources import theResources
from diffuse.utils import LineEnding
import gi # type: ignore
gi.require_version('GObject', '2.0')
@ -148,9 +149,9 @@ class ScrolledWindow(Gtk.Grid):
class FileDiffViewerBase(Gtk.Grid):
# class describing a text pane
class Pane:
def __init__(self):
def __init__(self) -> None:
# list of lines displayed in this pane (including spacing lines)
self.lines = []
self.lines: List[FileDiffViewerBase.Line] = []
# high water mark for line length in Pango units (used to determine
# the required horizontal scroll range)
self.line_lengths = 0
@ -160,11 +161,11 @@ class FileDiffViewerBase(Gtk.Grid):
# self.syntax_cache[i] corresponds to self.lines[i]
# the list is truncated when a change to a line invalidates a
# portion of the cache
self.syntax_cache = []
self.syntax_cache: List[List[Any]] = []
# cache of character differences for each line
# self.diff_cache[i] corresponds to self.lines[i]
# portion of the cache are cleared by setting entries to None
self.diff_cache = []
self.diff_cache: List[List[Any]] = []
# mask indicating the type of line endings present
self.format = 0
# number of lines with edits
@ -314,7 +315,7 @@ class FileDiffViewerBase(Gtk.Grid):
# create panes
self.dareas = []
self.panes = []
self.panes: List[FileDiffViewerBase.Pane] = []
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):
@ -3539,15 +3540,15 @@ class FileDiffViewerBase(Gtk.Grid):
# 'convert_to_dos' action
def convert_to_dos(self):
self.convert_format(utils.DOS_FORMAT)
self.convert_format(LineEnding.DOS_FORMAT)
# 'convert_to_mac' action
def convert_to_mac(self):
self.convert_format(utils.MAC_FORMAT)
self.convert_format(LineEnding.MAC_FORMAT)
# 'convert_to_unix' action
def convert_to_unix(self):
self.convert_format(utils.UNIX_FORMAT)
self.convert_format(LineEnding.UNIX_FORMAT)
# copies the selected range of lines from pane 'f_src' to 'f_dst'
def merge_lines(self, f_dst, f_src):
@ -3985,11 +3986,11 @@ def _get_format(ss):
for s in ss:
if s is not None:
if _has_dos_line_ending(s):
flags |= utils.DOS_FORMAT
flags |= LineEnding.DOS_FORMAT
elif _has_mac_line_ending(s):
flags |= utils.MAC_FORMAT
flags |= LineEnding.MAC_FORMAT
elif _has_unix_line_ending(s):
flags |= utils.UNIX_FORMAT
flags |= LineEnding.UNIX_FORMAT
return flags
@ -4000,17 +4001,17 @@ def _convert_to_format(s, fmt):
if old_format != 0 and (old_format & fmt) == 0:
s = utils.strip_eol(s)
# prefer the host line ending style
if (fmt & utils.DOS_FORMAT) and os.linesep == '\r\n':
if (fmt & LineEnding.DOS_FORMAT) and os.linesep == '\r\n':
s += os.linesep
elif (fmt & utils.MAC_FORMAT) and os.linesep == '\r':
elif (fmt & LineEnding.MAC_FORMAT) and os.linesep == '\r':
s += os.linesep
elif (fmt & utils.UNIX_FORMAT) and os.linesep == '\n':
elif (fmt & LineEnding.UNIX_FORMAT) and os.linesep == '\n':
s += os.linesep
elif fmt & utils.UNIX_FORMAT:
elif fmt & LineEnding.UNIX_FORMAT:
s += '\n'
elif fmt & utils.DOS_FORMAT:
elif fmt & LineEnding.DOS_FORMAT:
s += '\r\n'
elif fmt & utils.MAC_FORMAT:
elif fmt & LineEnding.MAC_FORMAT:
s += '\r'
return s