Merge pull request #142 from MightyCreak/more-static-typing

Convert more code to static typing
This commit is contained in:
Creak 2022-01-09 20:12:01 -05:00 committed by GitHub
commit 2231b79fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 585 additions and 534 deletions

View File

@ -26,7 +26,7 @@ import stat
import webbrowser import webbrowser
from gettext import gettext as _ from gettext import gettext as _
from typing import Optional from typing import List, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from diffuse import constants from diffuse import constants
@ -35,9 +35,10 @@ from diffuse.dialogs import AboutDialog, FileChooserDialog, NumericDialog, Searc
from diffuse.preferences import Preferences from diffuse.preferences import Preferences
from diffuse.resources import theResources from diffuse.resources import theResources
from diffuse.utils import LineEnding from diffuse.utils import LineEnding
from diffuse.vcs.vcs_interface import VcsInterface
from diffuse.vcs.vcs_registry import VcsRegistry from diffuse.vcs.vcs_registry import VcsRegistry
from diffuse.widgets import FileDiffViewerBase from diffuse.widgets import FileDiffViewerBase, EditMode
from diffuse.widgets import createMenu, LINE_MODE, CHAR_MODE, ALIGN_MODE from diffuse.widgets import createMenu
import gi # type: ignore import gi # type: ignore
gi.require_version('GObject', '2.0') gi.require_version('GObject', '2.0')
@ -57,7 +58,7 @@ theVCSs = VcsRegistry()
# make this a Gtk.EventBox so signals can be connected for MMB and RMB button # make this a Gtk.EventBox so signals can be connected for MMB and RMB button
# presses. # presses.
class NotebookTab(Gtk.EventBox): class NotebookTab(Gtk.EventBox):
def __init__(self, name, stock): def __init__(self, name: str, stock: str) -> None:
Gtk.EventBox.__init__(self) Gtk.EventBox.__init__(self)
self.set_visible_window(False) self.set_visible_window(False)
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0) hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
@ -66,13 +67,16 @@ class NotebookTab(Gtk.EventBox):
image.set_from_stock(stock, Gtk.IconSize.MENU) image.set_from_stock(stock, Gtk.IconSize.MENU)
hbox.pack_start(image, False, False, 5) hbox.pack_start(image, False, False, 5)
image.show() image.show()
self.label = label = Gtk.Label.new(name)
label = Gtk.Label.new(name)
# left justify the widget # left justify the widget
label.set_xalign(0.0) label.set_xalign(0.0)
label.set_yalign(0.5) label.set_yalign(0.5)
hbox.pack_start(label, True, True, 0) hbox.pack_start(label, True, True, 0)
label.show() label.show()
self.button = button = Gtk.Button.new() self.label = label
button = Gtk.Button.new()
button.set_relief(Gtk.ReliefStyle.NONE) button.set_relief(Gtk.ReliefStyle.NONE)
image = Gtk.Image.new() image = Gtk.Image.new()
image.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU) image.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU)
@ -81,13 +85,15 @@ class NotebookTab(Gtk.EventBox):
button.set_tooltip_text(_('Close Tab')) button.set_tooltip_text(_('Close Tab'))
hbox.pack_start(button, False, False, 0) hbox.pack_start(button, False, False, 0)
button.show() button.show()
self.button = button
self.add(hbox) self.add(hbox)
hbox.show() hbox.show()
def get_text(self): def get_text(self) -> str:
return self.label.get_text() return self.label.get_text()
def set_text(self, s): def set_text(self, s: str) -> None:
self.label.set_text(s) self.label.set_text(s)
@ -99,7 +105,7 @@ class FileInfo:
# name of codec used to translate the file contents to unicode text # name of codec used to translate the file contents to unicode text
self.encoding = encoding self.encoding = encoding
# the VCS object # the VCS object
self.vcs = vcs self.vcs: VcsInterface = vcs
# revision used to retrieve file from the VCS # revision used to retrieve file from the VCS
self.revision = revision self.revision = revision
# alternate text to display instead of the actual file name # alternate text to display instead of the actual file name
@ -147,7 +153,7 @@ class Diffuse(Gtk.Window):
self.emit(s) self.emit(s)
# creates an appropriate title for the pane header # creates an appropriate title for the pane header
def updateTitle(self): def updateTitle(self) -> None:
ss = [] ss = []
info = self.info info = self.info
if info.label is not None: if info.label is not None:
@ -166,7 +172,7 @@ class Diffuse(Gtk.Window):
self.emit('title_changed') self.emit('title_changed')
# set num edits # set num edits
def setEdits(self, has_edits): def setEdits(self, has_edits: bool) -> None:
if self.has_edits != has_edits: if self.has_edits != has_edits:
self.has_edits = has_edits self.has_edits = has_edits
self.updateTitle() self.updateTitle()
@ -198,8 +204,8 @@ class Diffuse(Gtk.Window):
self.show_all() self.show_all()
# set the cursor label # set the cursor label
def updateCursor(self, viewer, f): def updateCursor(self, viewer: FileDiffViewerBase, f: int) -> None:
if viewer.mode == CHAR_MODE and viewer.current_pane == f: if viewer.mode == EditMode.CHAR and viewer.current_pane == f:
# # TODO: Find a fix for the column bug (resizing issue when editing a line) # # TODO: Find a fix for the column bug (resizing issue when editing a line)
# j = viewer.current_char # j = viewer.current_char
# if j > 0: # if j > 0:
@ -212,7 +218,7 @@ class Diffuse(Gtk.Window):
self.cursor.set_text(s) self.cursor.set_text(s)
# set the format label # set the format label
def setFormat(self, s): def setFormat(self, s: LineEnding) -> None:
v = [] v = []
if s & LineEnding.DOS_FORMAT: if s & LineEnding.DOS_FORMAT:
v.append('DOS') v.append('DOS')
@ -223,19 +229,19 @@ class Diffuse(Gtk.Window):
self.format.set_text('/'.join(v)) self.format.set_text('/'.join(v))
# set the format label # set the format label
def setEncoding(self, s): def setEncoding(self, s: str) -> None:
if s is None: if s is None:
s = '' s = ''
self.encoding.set_text(s) self.encoding.set_text(s)
def __init__(self, n, prefs, title): def __init__(self, n: int, prefs: Preferences, title: str) -> None:
FileDiffViewerBase.__init__(self, n, prefs) super().__init__(n, prefs)
self.title = title self.title = title
self.status = '' self.status: Optional[str] = ''
self.headers = [] self.headers: List[Diffuse.FileDiffViewer.PaneHeader] = []
self.footers = [] self.footers: List[Diffuse.FileDiffViewer.PaneFooter] = []
for i in range(n): for i in range(n):
# pane header # pane header
w = Diffuse.FileDiffViewer.PaneHeader() w = Diffuse.FileDiffViewer.PaneHeader()
@ -272,7 +278,7 @@ class Diffuse(Gtk.Window):
# convenience method to request confirmation before loading a file if # convenience method to request confirmation before loading a file if
# it will cause existing edits to be lost # it will cause existing edits to be lost
def loadFromInfo(self, f, info): def loadFromInfo(self, f: int, info: FileInfo) -> None:
if self.headers[f].has_edits: if self.headers[f].has_edits:
# warn users of any unsaved changes they might lose # warn users of any unsaved changes they might lose
dialog = Gtk.MessageDialog( dialog = Gtk.MessageDialog(
@ -358,7 +364,7 @@ class Diffuse(Gtk.Window):
# load a new file into pane 'f' # load a new file into pane 'f'
# 'info' indicates the name of the file and how to retrieve it from the # 'info' indicates the name of the file and how to retrieve it from the
# version control system if applicable # version control system if applicable
def load(self, f, info): def load(self, f: int, info: FileInfo) -> None:
name = info.name name = info.name
encoding = info.encoding encoding = info.encoding
stat = None stat = None
@ -371,7 +377,7 @@ class Diffuse(Gtk.Window):
if rev is None: if rev is None:
# load the contents of a plain file # load the contents of a plain file
with open(name, 'rb') as fd: with open(name, 'rb') as fd:
s = fd.read() contents = fd.read()
# get the file's modification times so we can detect changes # get the file's modification times so we can detect changes
stat = os.stat(name) stat = os.stat(name)
else: else:
@ -379,12 +385,12 @@ class Diffuse(Gtk.Window):
raise IOError('Not under version control.') raise IOError('Not under version control.')
fullname = os.path.abspath(name) fullname = os.path.abspath(name)
# retrieve the revision from the version control system # retrieve the revision from the version control system
s = info.vcs.getRevision(self.prefs, fullname, rev) contents = info.vcs.getRevision(self.prefs, fullname, rev)
# convert file contents to unicode # convert file contents to unicode
if encoding is None: if encoding is None:
s, encoding = self.prefs.convertToUnicode(s) s, encoding = self.prefs.convertToUnicode(contents)
else: else:
s = str(s, encoding=encoding) s = str(contents, encoding=encoding)
ss = utils.splitlines(s) ss = utils.splitlines(s)
except (IOError, OSError, UnicodeDecodeError, LookupError): except (IOError, OSError, UnicodeDecodeError, LookupError):
# FIXME: this can occur before the toplevel window is drawn # FIXME: this can occur before the toplevel window is drawn
@ -408,7 +414,7 @@ class Diffuse(Gtk.Window):
self.setSyntax(syntax) self.setSyntax(syntax)
# load a new file into pane 'f' # load a new file into pane 'f'
def open_file(self, f, reload=False): def open_file(self, f: int, reload: bool = False) -> None:
h = self.headers[f] h = self.headers[f]
info = h.info info = h.info
if not reload: if not reload:
@ -487,7 +493,7 @@ class Diffuse(Gtk.Window):
pass pass
# save contents of pane 'f' to file # save contents of pane 'f' to file
def save_file(self, f, save_as=False): def save_file(self, f: int, save_as: bool = False) -> bool:
h = self.headers[f] h = self.headers[f]
info = h.info info = h.info
name, encoding, rev, label = info.name, info.encoding, info.revision, info.label name, encoding, rev, label = info.name, info.encoding, info.revision, info.label
@ -642,15 +648,15 @@ class Diffuse(Gtk.Window):
self.updateStatus() self.updateStatus()
# update the viewer's current status message # update the viewer's current status message
def updateStatus(self): def updateStatus(self) -> None:
if self.mode == LINE_MODE: if self.mode == EditMode.LINE:
s = _( s = _(
'Press the enter key or double click to edit. Press the space bar or use the ' 'Press the enter key or double click to edit. Press the space bar or use the '
'RMB menu to manually align.' 'RMB menu to manually align.'
) )
elif self.mode == CHAR_MODE: elif self.mode == EditMode.CHAR:
s = _('Press the escape key to finish editing.') s = _('Press the escape key to finish editing.')
elif self.mode == ALIGN_MODE: elif self.mode == EditMode.ALIGN:
s = _( s = _(
'Select target line and press the space bar to align. Press the escape key to ' 'Select target line and press the space bar to align. Press the escape key to '
'cancel.' 'cancel.'
@ -661,7 +667,7 @@ class Diffuse(Gtk.Window):
self.emit('status_changed', s) self.emit('status_changed', s)
# gets the status bar text # gets the status bar text
def getStatus(self): def getStatus(self) -> Optional[str]:
return self.status return self.status
# callback to display the cursor in a pane # callback to display the cursor in a pane
@ -674,7 +680,7 @@ class Diffuse(Gtk.Window):
self.footers[f].setFormat(fmt) self.footers[f].setFormat(fmt)
def __init__(self, rc_dir): def __init__(self, rc_dir):
Gtk.Window.__init__(self, type=Gtk.WindowType.TOPLEVEL) super().__init__(type=Gtk.WindowType.TOPLEVEL)
self.prefs = Preferences(os.path.join(rc_dir, 'prefs')) self.prefs = Preferences(os.path.join(rc_dir, 'prefs'))
# number of created viewers (used to label some tabs) # number of created viewers (used to label some tabs)
@ -702,8 +708,8 @@ class Diffuse(Gtk.Window):
self.connect('window-state-event', self.window_state_cb) self.connect('window-state-event', self.window_state_cb)
# search history is application wide # search history is application wide
self.search_pattern = None self.search_pattern: Optional[str] = None
self.search_history = [] self.search_history: List[str] = []
self.connect('delete-event', self.delete_cb) self.connect('delete-event', self.delete_cb)
accel_group = Gtk.AccelGroup() accel_group = Gtk.AccelGroup()
@ -968,7 +974,7 @@ class Diffuse(Gtk.Window):
) )
# load state information that should persist across sessions # load state information that should persist across sessions
def loadState(self, statepath): def loadState(self, statepath: str) -> None:
if os.path.isfile(statepath): if os.path.isfile(statepath):
try: try:
f = open(statepath, 'r') f = open(statepath, 'r')
@ -998,7 +1004,7 @@ class Diffuse(Gtk.Window):
self.maximize() self.maximize()
# save state information that should persist across sessions # save state information that should persist across sessions
def saveState(self, statepath): def saveState(self, statepath: str) -> None:
try: try:
ss = [] ss = []
for k, v in self.bool_state.items(): for k, v in self.bool_state.items():
@ -1025,7 +1031,7 @@ class Diffuse(Gtk.Window):
# returns True if the list of viewers can be closed. The user will be # returns True if the list of viewers can be closed. The user will be
# given a chance to save any modified files before this method completes. # given a chance to save any modified files before this method completes.
def confirmCloseViewers(self, viewers): def confirmCloseViewers(self, viewers: List[FileDiffViewer]) -> bool:
# make a list of modified files # make a list of modified files
model = Gtk.ListStore.new([ model = Gtk.ListStore.new([
GObject.TYPE_BOOLEAN, GObject.TYPE_BOOLEAN,
@ -1144,12 +1150,12 @@ class Diffuse(Gtk.Window):
menu.popup(None, None, None, event.button, event.time) menu.popup(None, None, None, event.button, event.time)
# update window's title # update window's title
def updateTitle(self, viewer): def updateTitle(self, viewer: FileDiffViewer) -> None:
title = self.notebook.get_tab_label(viewer).get_text() title = self.notebook.get_tab_label(viewer).get_text()
self.set_title(f'{title} - {constants.APP_NAME}') self.set_title(f'{title} - {constants.APP_NAME}')
# update the message in the status bar # update the message in the status bar
def setStatus(self, s): def setStatus(self, s: Optional[str]) -> None:
sb = self.statusbar sb = self.statusbar
context = sb.get_context_id('Message') context = sb.get_context_id('Message')
sb.pop(context) sb.pop(context)
@ -1358,7 +1364,7 @@ class Diffuse(Gtk.Window):
return True return True
# returns the currently focused viewer # returns the currently focused viewer
def getCurrentViewer(self) -> Optional[Gtk.Widget]: def getCurrentViewer(self) -> FileDiffViewer:
return self.notebook.get_nth_page(self.notebook.get_current_page()) return self.notebook.get_nth_page(self.notebook.get_current_page())
# callback for the open file menu item # callback for the open file menu item
@ -1504,7 +1510,7 @@ class Diffuse(Gtk.Window):
# request search parameters if force=True and then perform a search in the # request search parameters if force=True and then perform a search in the
# current viewer pane # current viewer pane
def find(self, force, reverse): def find(self, force: bool, reverse: bool) -> None:
viewer = self.getCurrentViewer() viewer = self.getCurrentViewer()
if force or self.search_pattern is None: if force or self.search_pattern is None:
# construct search dialog # construct search dialog

View File

@ -24,7 +24,7 @@ import shlex
import sys import sys
from gettext import gettext as _ from gettext import gettext as _
from typing import List from typing import Any, Dict, Final, List, Optional, Tuple
from diffuse import constants from diffuse import constants
from diffuse import utils from diffuse import utils
@ -36,16 +36,16 @@ from gi.repository import Gtk # type: ignore # noqa: E402
# class to store preferences and construct a dialogue for manipulating them # class to store preferences and construct a dialogue for manipulating them
class Preferences: class Preferences:
def __init__(self, path): def __init__(self, path: str) -> None:
self.bool_prefs = {} self.path = path
self.int_prefs = {} self.bool_prefs: Dict[str, bool] = {}
self.string_prefs = {} self.string_prefs: Dict[str, str] = {}
self.int_prefs_min = {} self.int_prefs: Dict[str, int] = {}
self.int_prefs_max = {} self.int_prefs_min: Dict[str, int] = {}
self.string_prefs_enums = {} self.int_prefs_max: Dict[str, int] = {}
# find available encodings # find available encodings
self.encodings = sorted(set(encodings.aliases.aliases.values())) self.encodings: List[Optional[str]] = sorted(set(encodings.aliases.aliases.values()))
if utils.isWindows(): if utils.isWindows():
svk_bin = 'svk.bat' svk_bin = 'svk.bat'
@ -54,7 +54,7 @@ class Preferences:
auto_detect_codecs = ['utf_8', 'utf_16', 'latin_1'] auto_detect_codecs = ['utf_8', 'utf_16', 'latin_1']
e = utils.norm_encoding(sys.getfilesystemencoding()) e = utils.norm_encoding(sys.getfilesystemencoding())
if e not in auto_detect_codecs: if e is not None and e not in auto_detect_codecs:
# insert after UTF-8 as the default encoding may prevent UTF-8 from # insert after UTF-8 as the default encoding may prevent UTF-8 from
# being tried # being tried
auto_detect_codecs.insert(2, e) auto_detect_codecs.insert(2, e)
@ -124,7 +124,7 @@ class Preferences:
] ]
# conditions used to determine if a preference should be greyed out # conditions used to determine if a preference should be greyed out
self.disable_when = { self.disable_when: Final[Dict[str, Tuple[str, bool]]] = {
'display_right_margin': ('display_show_right_margin', False), 'display_right_margin': ('display_show_right_margin', False),
'display_ignore_whitespace_changes': ('display_ignore_whitespace', True), 'display_ignore_whitespace_changes': ('display_ignore_whitespace', True),
'display_ignore_blanklines': ('display_ignore_whitespace', True), 'display_ignore_blanklines': ('display_ignore_whitespace', True),
@ -165,9 +165,9 @@ class Preferences:
_('Version control system search order') _('Version control system search order')
] ]
] ]
vcs_folders_template = ['FolderSet'] vcs_folders_template: List[Any] = ['FolderSet']
for key, name, cmd in vcs: for key, name, cmd in vcs:
temp = ['List'] temp: List[Any] = ['List']
if key == 'rcs': if key == 'rcs':
# RCS uses multiple commands # RCS uses multiple commands
temp.extend([['File', key + '_bin_co', 'co', _('"co" command')], temp.extend([['File', key + '_bin_co', 'co', _('"co" command')],
@ -196,8 +196,8 @@ class Preferences:
self.default_bool_prefs = self.bool_prefs.copy() self.default_bool_prefs = self.bool_prefs.copy()
self.default_int_prefs = self.int_prefs.copy() self.default_int_prefs = self.int_prefs.copy()
self.default_string_prefs = self.string_prefs.copy() self.default_string_prefs = self.string_prefs.copy()
# load the user's preferences # load the user's preferences
self.path = path
if os.path.isfile(self.path): if os.path.isfile(self.path):
try: try:
with open(self.path, 'r', encoding='utf-8') as f: with open(self.path, 'r', encoding='utf-8') as f:
@ -255,16 +255,16 @@ class Preferences:
# display the dialogue and update the preference values if the accept # display the dialogue and update the preference values if the accept
# button was pressed # button was pressed
def runDialog(self, parent): def runDialog(self, parent: Gtk.Widget) -> None:
dialog = Gtk.Dialog(_('Preferences'), parent=parent, destroy_with_parent=True) dialog = Gtk.Dialog(_('Preferences'), parent=parent, destroy_with_parent=True)
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
widgets = {} widgets: Dict[str, Gtk.Widget] = {}
w = self._buildPrefsDialog(parent, widgets, self.template) w = self._buildPrefsDialog(parent, widgets, self.template)
# disable any preferences than are not relevant # disable any preferences than are not relevant
for k, v in self.disable_when.items(): for k, tuple_value in self.disable_when.items():
p, t = v p, t = tuple_value
if widgets[p].get_active() == t: if widgets[p].get_active() == t:
widgets[k].set_sensitive(False) widgets[k].set_sensitive(False)
dialog.vbox.add(w) dialog.vbox.add(w)
@ -280,15 +280,15 @@ class Preferences:
self.string_prefs[k] = utils.null_to_empty(widgets[k].get_text()) self.string_prefs[k] = utils.null_to_empty(widgets[k].get_text())
try: try:
ss = [] ss = []
for k, v in self.bool_prefs.items(): for k, bool_value in self.bool_prefs.items():
if v != self.default_bool_prefs[k]: if bool_value != self.default_bool_prefs[k]:
ss.append(f'{k} {v}\n') ss.append(f'{k} {bool_value}\n')
for k, v in self.int_prefs.items(): for k, int_value in self.int_prefs.items():
if v != self.default_int_prefs[k]: if int_value != self.default_int_prefs[k]:
ss.append(f'{k} {v}\n') ss.append(f'{k} {int_value}\n')
for k, v in self.string_prefs.items(): for k, str_value in self.string_prefs.items():
if v != self.default_string_prefs[k]: if str_value != self.default_string_prefs[k]:
v_escaped = v.replace('\\', '\\\\').replace('"', '\\"') v_escaped = str_value.replace('\\', '\\\\').replace('"', '\\"')
ss.append(f'{k} "{v_escaped}"\n') ss.append(f'{k} "{v_escaped}"\n')
ss.sort() ss.sort()
with open(self.path, 'w', encoding='utf-8') as f: with open(self.path, 'w', encoding='utf-8') as f:
@ -384,7 +384,7 @@ class Preferences:
def setString(self, name: str, value: str) -> None: def setString(self, name: str, value: str) -> None:
self.string_prefs[name] = value self.string_prefs[name] = value
def getEncodings(self): def getEncodings(self) -> List[Optional[str]]:
return self.encodings return self.encodings
def _getDefaultEncodings(self) -> List[str]: def _getDefaultEncodings(self) -> List[str]:
@ -439,7 +439,7 @@ class Preferences:
# text entry widget with a button to help pick file names # text entry widget with a button to help pick file names
class _FileEntry(Gtk.Box): class _FileEntry(Gtk.Box):
def __init__(self, parent, title): def __init__(self, parent: Gtk.Widget, title: str) -> None:
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.toplevel = parent self.toplevel = parent
self.title = title self.title = title
@ -456,7 +456,7 @@ class _FileEntry(Gtk.Box):
button.show() button.show()
# action performed when the pick file button is pressed # action performed when the pick file button is pressed
def chooseFile(self, widget): def chooseFile(self, widget: Gtk.Widget) -> None:
dialog = Gtk.FileChooserDialog( dialog = Gtk.FileChooserDialog(
self.title, self.title,
self.toplevel, self.toplevel,

View File

@ -31,7 +31,7 @@ import shlex
from distutils import util from distutils import util
from gettext import gettext as _ from gettext import gettext as _
from typing import Final from typing import Dict, Final, List, Optional, Pattern, Set, Tuple
from diffuse import utils from diffuse import utils
@ -45,134 +45,133 @@ class Resources:
# default keybindings # default keybindings
self.keybindings = {} self.keybindings = {}
self.keybindings_lookup = {} self.keybindings_lookup = {}
set_binding = self.setKeyBinding self.setKeyBinding('menu', 'open_file', 'Ctrl+o')
set_binding('menu', 'open_file', 'Ctrl+o') self.setKeyBinding('menu', 'open_file_in_new_tab', 'Ctrl+t')
set_binding('menu', 'open_file_in_new_tab', 'Ctrl+t') self.setKeyBinding('menu', 'open_modified_files', 'Shift+Ctrl+O')
set_binding('menu', 'open_modified_files', 'Shift+Ctrl+O') self.setKeyBinding('menu', 'open_commit', 'Shift+Ctrl+T')
set_binding('menu', 'open_commit', 'Shift+Ctrl+T') self.setKeyBinding('menu', 'reload_file', 'Shift+Ctrl+R')
set_binding('menu', 'reload_file', 'Shift+Ctrl+R') self.setKeyBinding('menu', 'save_file', 'Ctrl+s')
set_binding('menu', 'save_file', 'Ctrl+s') self.setKeyBinding('menu', 'save_file_as', 'Shift+Ctrl+A')
set_binding('menu', 'save_file_as', 'Shift+Ctrl+A') self.setKeyBinding('menu', 'save_all', 'Shift+Ctrl+S')
set_binding('menu', 'save_all', 'Shift+Ctrl+S') self.setKeyBinding('menu', 'new_2_way_file_merge', 'Ctrl+2')
set_binding('menu', 'new_2_way_file_merge', 'Ctrl+2') self.setKeyBinding('menu', 'new_3_way_file_merge', 'Ctrl+3')
set_binding('menu', 'new_3_way_file_merge', 'Ctrl+3') self.setKeyBinding('menu', 'new_n_way_file_merge', 'Ctrl+4')
set_binding('menu', 'new_n_way_file_merge', 'Ctrl+4') self.setKeyBinding('menu', 'close_tab', 'Ctrl+w')
set_binding('menu', 'close_tab', 'Ctrl+w') self.setKeyBinding('menu', 'undo_close_tab', 'Shift+Ctrl+W')
set_binding('menu', 'undo_close_tab', 'Shift+Ctrl+W') self.setKeyBinding('menu', 'quit', 'Ctrl+q')
set_binding('menu', 'quit', 'Ctrl+q') self.setKeyBinding('menu', 'undo', 'Ctrl+z')
set_binding('menu', 'undo', 'Ctrl+z') self.setKeyBinding('menu', 'redo', 'Shift+Ctrl+Z')
set_binding('menu', 'redo', 'Shift+Ctrl+Z') self.setKeyBinding('menu', 'cut', 'Ctrl+x')
set_binding('menu', 'cut', 'Ctrl+x') self.setKeyBinding('menu', 'copy', 'Ctrl+c')
set_binding('menu', 'copy', 'Ctrl+c') self.setKeyBinding('menu', 'paste', 'Ctrl+v')
set_binding('menu', 'paste', 'Ctrl+v') self.setKeyBinding('menu', 'select_all', 'Ctrl+a')
set_binding('menu', 'select_all', 'Ctrl+a') self.setKeyBinding('menu', 'clear_edits', 'Ctrl+r')
set_binding('menu', 'clear_edits', 'Ctrl+r') self.setKeyBinding('menu', 'dismiss_all_edits', 'Ctrl+d')
set_binding('menu', 'dismiss_all_edits', 'Ctrl+d') self.setKeyBinding('menu', 'find', 'Ctrl+f')
set_binding('menu', 'find', 'Ctrl+f') self.setKeyBinding('menu', 'find_next', 'Ctrl+g')
set_binding('menu', 'find_next', 'Ctrl+g') self.setKeyBinding('menu', 'find_previous', 'Shift+Ctrl+G')
set_binding('menu', 'find_previous', 'Shift+Ctrl+G') self.setKeyBinding('menu', 'go_to_line', 'Shift+Ctrl+L')
set_binding('menu', 'go_to_line', 'Shift+Ctrl+L') self.setKeyBinding('menu', 'realign_all', 'Ctrl+l')
set_binding('menu', 'realign_all', 'Ctrl+l') self.setKeyBinding('menu', 'isolate', 'Ctrl+i')
set_binding('menu', 'isolate', 'Ctrl+i') self.setKeyBinding('menu', 'first_difference', 'Shift+Ctrl+Up')
set_binding('menu', 'first_difference', 'Shift+Ctrl+Up') self.setKeyBinding('menu', 'previous_difference', 'Ctrl+Up')
set_binding('menu', 'previous_difference', 'Ctrl+Up') self.setKeyBinding('menu', 'next_difference', 'Ctrl+Down')
set_binding('menu', 'next_difference', 'Ctrl+Down') self.setKeyBinding('menu', 'last_difference', 'Shift+Ctrl+Down')
set_binding('menu', 'last_difference', 'Shift+Ctrl+Down') self.setKeyBinding('menu', 'first_tab', 'Shift+Ctrl+Page_Up')
set_binding('menu', 'first_tab', 'Shift+Ctrl+Page_Up') self.setKeyBinding('menu', 'previous_tab', 'Ctrl+Page_Up')
set_binding('menu', 'previous_tab', 'Ctrl+Page_Up') self.setKeyBinding('menu', 'next_tab', 'Ctrl+Page_Down')
set_binding('menu', 'next_tab', 'Ctrl+Page_Down') self.setKeyBinding('menu', 'last_tab', 'Shift+Ctrl+Page_Down')
set_binding('menu', 'last_tab', 'Shift+Ctrl+Page_Down') self.setKeyBinding('menu', 'shift_pane_right', 'Shift+Ctrl+parenright')
set_binding('menu', 'shift_pane_right', 'Shift+Ctrl+parenright') self.setKeyBinding('menu', 'shift_pane_left', 'Shift+Ctrl+parenleft')
set_binding('menu', 'shift_pane_left', 'Shift+Ctrl+parenleft') self.setKeyBinding('menu', 'convert_to_upper_case', 'Ctrl+u')
set_binding('menu', 'convert_to_upper_case', 'Ctrl+u') self.setKeyBinding('menu', 'convert_to_lower_case', 'Shift+Ctrl+U')
set_binding('menu', 'convert_to_lower_case', 'Shift+Ctrl+U') self.setKeyBinding('menu', 'sort_lines_in_ascending_order', 'Ctrl+y')
set_binding('menu', 'sort_lines_in_ascending_order', 'Ctrl+y') self.setKeyBinding('menu', 'sort_lines_in_descending_order', 'Shift+Ctrl+Y')
set_binding('menu', 'sort_lines_in_descending_order', 'Shift+Ctrl+Y') self.setKeyBinding('menu', 'remove_trailing_white_space', 'Ctrl+k')
set_binding('menu', 'remove_trailing_white_space', 'Ctrl+k') self.setKeyBinding('menu', 'convert_tabs_to_spaces', 'Ctrl+b')
set_binding('menu', 'convert_tabs_to_spaces', 'Ctrl+b') self.setKeyBinding('menu', 'convert_leading_spaces_to_tabs', 'Shift+Ctrl+B')
set_binding('menu', 'convert_leading_spaces_to_tabs', 'Shift+Ctrl+B') self.setKeyBinding('menu', 'increase_indenting', 'Shift+Ctrl+greater')
set_binding('menu', 'increase_indenting', 'Shift+Ctrl+greater') self.setKeyBinding('menu', 'decrease_indenting', 'Shift+Ctrl+less')
set_binding('menu', 'decrease_indenting', 'Shift+Ctrl+less') self.setKeyBinding('menu', 'convert_to_dos', 'Shift+Ctrl+E')
set_binding('menu', 'convert_to_dos', 'Shift+Ctrl+E') self.setKeyBinding('menu', 'convert_to_mac', 'Shift+Ctrl+C')
set_binding('menu', 'convert_to_mac', 'Shift+Ctrl+C') self.setKeyBinding('menu', 'convert_to_unix', 'Ctrl+e')
set_binding('menu', 'convert_to_unix', 'Ctrl+e') self.setKeyBinding('menu', 'copy_selection_right', 'Shift+Ctrl+Right')
set_binding('menu', 'copy_selection_right', 'Shift+Ctrl+Right') self.setKeyBinding('menu', 'copy_selection_left', 'Shift+Ctrl+Left')
set_binding('menu', 'copy_selection_left', 'Shift+Ctrl+Left') self.setKeyBinding('menu', 'copy_left_into_selection', 'Ctrl+Right')
set_binding('menu', 'copy_left_into_selection', 'Ctrl+Right') self.setKeyBinding('menu', 'copy_right_into_selection', 'Ctrl+Left')
set_binding('menu', 'copy_right_into_selection', 'Ctrl+Left') self.setKeyBinding('menu', 'merge_from_left_then_right', 'Ctrl+m')
set_binding('menu', 'merge_from_left_then_right', 'Ctrl+m') self.setKeyBinding('menu', 'merge_from_right_then_left', 'Shift+Ctrl+M')
set_binding('menu', 'merge_from_right_then_left', 'Shift+Ctrl+M') self.setKeyBinding('menu', 'help_contents', 'F1')
set_binding('menu', 'help_contents', 'F1') self.setKeyBinding('line_mode', 'enter_align_mode', 'space')
set_binding('line_mode', 'enter_align_mode', 'space') self.setKeyBinding('line_mode', 'enter_character_mode', 'Return')
set_binding('line_mode', 'enter_character_mode', 'Return') self.setKeyBinding('line_mode', 'enter_character_mode', 'KP_Enter')
set_binding('line_mode', 'enter_character_mode', 'KP_Enter') self.setKeyBinding('line_mode', 'first_line', 'Home')
set_binding('line_mode', 'first_line', 'Home') self.setKeyBinding('line_mode', 'first_line', 'g')
set_binding('line_mode', 'first_line', 'g') self.setKeyBinding('line_mode', 'extend_first_line', 'Shift+Home')
set_binding('line_mode', 'extend_first_line', 'Shift+Home') self.setKeyBinding('line_mode', 'last_line', 'End')
set_binding('line_mode', 'last_line', 'End') self.setKeyBinding('line_mode', 'last_line', 'Shift+G')
set_binding('line_mode', 'last_line', 'Shift+G') self.setKeyBinding('line_mode', 'extend_last_line', 'Shift+End')
set_binding('line_mode', 'extend_last_line', 'Shift+End') self.setKeyBinding('line_mode', 'up', 'Up')
set_binding('line_mode', 'up', 'Up') self.setKeyBinding('line_mode', 'up', 'k')
set_binding('line_mode', 'up', 'k') self.setKeyBinding('line_mode', 'extend_up', 'Shift+Up')
set_binding('line_mode', 'extend_up', 'Shift+Up') self.setKeyBinding('line_mode', 'extend_up', 'Shift+K')
set_binding('line_mode', 'extend_up', 'Shift+K') self.setKeyBinding('line_mode', 'down', 'Down')
set_binding('line_mode', 'down', 'Down') self.setKeyBinding('line_mode', 'down', 'j')
set_binding('line_mode', 'down', 'j') self.setKeyBinding('line_mode', 'extend_down', 'Shift+Down')
set_binding('line_mode', 'extend_down', 'Shift+Down') self.setKeyBinding('line_mode', 'extend_down', 'Shift+J')
set_binding('line_mode', 'extend_down', 'Shift+J') self.setKeyBinding('line_mode', 'left', 'Left')
set_binding('line_mode', 'left', 'Left') self.setKeyBinding('line_mode', 'left', 'h')
set_binding('line_mode', 'left', 'h') self.setKeyBinding('line_mode', 'extend_left', 'Shift+Left')
set_binding('line_mode', 'extend_left', 'Shift+Left') self.setKeyBinding('line_mode', 'right', 'Right')
set_binding('line_mode', 'right', 'Right') self.setKeyBinding('line_mode', 'right', 'l')
set_binding('line_mode', 'right', 'l') self.setKeyBinding('line_mode', 'extend_right', 'Shift+Right')
set_binding('line_mode', 'extend_right', 'Shift+Right') self.setKeyBinding('line_mode', 'page_up', 'Page_Up')
set_binding('line_mode', 'page_up', 'Page_Up') self.setKeyBinding('line_mode', 'page_up', 'Ctrl+u')
set_binding('line_mode', 'page_up', 'Ctrl+u') self.setKeyBinding('line_mode', 'extend_page_up', 'Shift+Page_Up')
set_binding('line_mode', 'extend_page_up', 'Shift+Page_Up') self.setKeyBinding('line_mode', 'extend_page_up', 'Shift+Ctrl+U')
set_binding('line_mode', 'extend_page_up', 'Shift+Ctrl+U') self.setKeyBinding('line_mode', 'page_down', 'Page_Down')
set_binding('line_mode', 'page_down', 'Page_Down') self.setKeyBinding('line_mode', 'page_down', 'Ctrl+d')
set_binding('line_mode', 'page_down', 'Ctrl+d') self.setKeyBinding('line_mode', 'extend_page_down', 'Shift+Page_Down')
set_binding('line_mode', 'extend_page_down', 'Shift+Page_Down') self.setKeyBinding('line_mode', 'extend_page_down', 'Shift+Ctrl+D')
set_binding('line_mode', 'extend_page_down', 'Shift+Ctrl+D') self.setKeyBinding('line_mode', 'delete_text', 'BackSpace')
set_binding('line_mode', 'delete_text', 'BackSpace') self.setKeyBinding('line_mode', 'delete_text', 'Delete')
set_binding('line_mode', 'delete_text', 'Delete') self.setKeyBinding('line_mode', 'delete_text', 'x')
set_binding('line_mode', 'delete_text', 'x') self.setKeyBinding('line_mode', 'clear_edits', 'r')
set_binding('line_mode', 'clear_edits', 'r') self.setKeyBinding('line_mode', 'isolate', 'i')
set_binding('line_mode', 'isolate', 'i') self.setKeyBinding('line_mode', 'first_difference', 'Ctrl+Home')
set_binding('line_mode', 'first_difference', 'Ctrl+Home') self.setKeyBinding('line_mode', 'first_difference', 'Shift+P')
set_binding('line_mode', 'first_difference', 'Shift+P') self.setKeyBinding('line_mode', 'previous_difference', 'p')
set_binding('line_mode', 'previous_difference', 'p') self.setKeyBinding('line_mode', 'next_difference', 'n')
set_binding('line_mode', 'next_difference', 'n') self.setKeyBinding('line_mode', 'last_difference', 'Ctrl+End')
set_binding('line_mode', 'last_difference', 'Ctrl+End') self.setKeyBinding('line_mode', 'last_difference', 'Shift+N')
set_binding('line_mode', 'last_difference', 'Shift+N') # self.setKeyBinding('line_mode', 'copy_selection_right', 'Shift+L')
# set_binding('line_mode', 'copy_selection_right', 'Shift+L') # self.setKeyBinding('line_mode', 'copy_selection_left', 'Shift+H')
# set_binding('line_mode', 'copy_selection_left', 'Shift+H') self.setKeyBinding('line_mode', 'copy_left_into_selection', 'Shift+L')
set_binding('line_mode', 'copy_left_into_selection', 'Shift+L') self.setKeyBinding('line_mode', 'copy_right_into_selection', 'Shift+H')
set_binding('line_mode', 'copy_right_into_selection', 'Shift+H') self.setKeyBinding('line_mode', 'merge_from_left_then_right', 'm')
set_binding('line_mode', 'merge_from_left_then_right', 'm') self.setKeyBinding('line_mode', 'merge_from_right_then_left', 'Shift+M')
set_binding('line_mode', 'merge_from_right_then_left', 'Shift+M') self.setKeyBinding('align_mode', 'enter_line_mode', 'Escape')
set_binding('align_mode', 'enter_line_mode', 'Escape') self.setKeyBinding('align_mode', 'align', 'space')
set_binding('align_mode', 'align', 'space') self.setKeyBinding('align_mode', 'enter_character_mode', 'Return')
set_binding('align_mode', 'enter_character_mode', 'Return') self.setKeyBinding('align_mode', 'enter_character_mode', 'KP_Enter')
set_binding('align_mode', 'enter_character_mode', 'KP_Enter') self.setKeyBinding('align_mode', 'first_line', 'g')
set_binding('align_mode', 'first_line', 'g') self.setKeyBinding('align_mode', 'last_line', 'Shift+G')
set_binding('align_mode', 'last_line', 'Shift+G') self.setKeyBinding('align_mode', 'up', 'Up')
set_binding('align_mode', 'up', 'Up') self.setKeyBinding('align_mode', 'up', 'k')
set_binding('align_mode', 'up', 'k') self.setKeyBinding('align_mode', 'down', 'Down')
set_binding('align_mode', 'down', 'Down') self.setKeyBinding('align_mode', 'down', 'j')
set_binding('align_mode', 'down', 'j') self.setKeyBinding('align_mode', 'left', 'Left')
set_binding('align_mode', 'left', 'Left') self.setKeyBinding('align_mode', 'left', 'h')
set_binding('align_mode', 'left', 'h') self.setKeyBinding('align_mode', 'right', 'Right')
set_binding('align_mode', 'right', 'Right') self.setKeyBinding('align_mode', 'right', 'l')
set_binding('align_mode', 'right', 'l') self.setKeyBinding('align_mode', 'page_up', 'Page_Up')
set_binding('align_mode', 'page_up', 'Page_Up') self.setKeyBinding('align_mode', 'page_up', 'Ctrl+u')
set_binding('align_mode', 'page_up', 'Ctrl+u') self.setKeyBinding('align_mode', 'page_down', 'Page_Down')
set_binding('align_mode', 'page_down', 'Page_Down') self.setKeyBinding('align_mode', 'page_down', 'Ctrl+d')
set_binding('align_mode', 'page_down', 'Ctrl+d') self.setKeyBinding('character_mode', 'enter_line_mode', 'Escape')
set_binding('character_mode', 'enter_line_mode', 'Escape')
# default colours # default colours
self.colours = { self.colours: Dict[str, _Colour] = {
'alignment': _Colour(1.0, 1.0, 0.0), 'alignment': _Colour(1.0, 1.0, 0.0),
'character_selection': _Colour(0.7, 0.7, 1.0), 'character_selection': _Colour(0.7, 0.7, 1.0),
'cursor': _Colour(0.0, 0.0, 0.0), 'cursor': _Colour(0.0, 0.0, 0.0),
@ -192,7 +191,7 @@ class Resources:
} }
# default floats # default floats
self.floats = { self.floats: Dict[str, float] = {
'alignment_opacity': 1.0, 'alignment_opacity': 1.0,
'character_difference_opacity': 0.4, 'character_difference_opacity': 0.4,
'character_selection_opacity': 0.4, 'character_selection_opacity': 0.4,
@ -202,29 +201,29 @@ class Resources:
} }
# default options # default options
self.options = { self.options: Dict[str, str] = {
'log_print_output': 'False', 'log_print_output': 'False',
'log_print_stack': 'False', 'log_print_stack': 'False',
'use_flatpak': 'False' 'use_flatpak': 'False'
} }
# default strings # default strings
self.strings = {} self.strings: Dict[str, str] = {}
# syntax highlighting support # syntax highlighting support
self.syntaxes = {} self.syntaxes: Dict[str, _SyntaxParser] = {}
self.syntax_file_patterns = {} self.syntax_file_patterns: Dict[str, Pattern] = {}
self.syntax_magic_patterns = {} self.syntax_magic_patterns: Dict[str, Pattern] = {}
self.current_syntax = None self.current_syntax: Optional[_SyntaxParser] = None
# list of imported resources files (we only import each file once) # list of imported resources files (we only import each file once)
self.resource_files = set() self.resource_files: Set[str] = set()
# special string resources # special string resources
self.setDifferenceColours('difference_1 difference_2 difference_3') self.setDifferenceColours('difference_1 difference_2 difference_3')
# keyboard action processing # keyboard action processing
def setKeyBinding(self, ctx, s, v): def setKeyBinding(self, ctx: str, s: str, v: str) -> None:
action_tuple = (ctx, s) action_tuple = (ctx, s)
modifiers = Gdk.ModifierType(0) modifiers = Gdk.ModifierType(0)
key = None key = None
@ -282,7 +281,7 @@ class Resources:
return [] return []
# colours used for indicating differences # colours used for indicating differences
def setDifferenceColours(self, s): def setDifferenceColours(self, s: str) -> None:
colours = s.split() colours = s.split()
if len(colours) > 0: if len(colours) > 0:
self.difference_colours = colours self.difference_colours = colours
@ -322,7 +321,7 @@ class Resources:
return util.strtobool(self.getOption(option)) return util.strtobool(self.getOption(option))
# string resources # string resources
def getString(self, symbol): def getString(self, symbol: str) -> str:
try: try:
return self.strings[symbol] return self.strings[symbol]
except KeyError: except KeyError:
@ -336,7 +335,7 @@ class Resources:
def getSyntax(self, name): def getSyntax(self, name):
return self.syntaxes.get(name, None) return self.syntaxes.get(name, None)
def guessSyntaxForFile(self, name, ss): def guessSyntaxForFile(self, name: str, ss: List[str]) -> Optional[str]:
name = os.path.basename(name) name = os.path.basename(name)
for key, pattern in self.syntax_file_patterns.items(): for key, pattern in self.syntax_file_patterns.items():
if pattern.search(name): if pattern.search(name):
@ -350,7 +349,7 @@ class Resources:
return None return None
# parse resource files # parse resource files
def parse(self, file_name): def parse(self, file_name: str) -> None:
# only process files once # only process files once
if file_name in self.resource_files: if file_name in self.resource_files:
return return
@ -514,12 +513,12 @@ class Resources:
file=file_name file=file_name
) )
utils.logError(f'{error_msg}: {e.msg}') utils.logError(f'{error_msg}: {e.msg}')
except ValueError as e: except ValueError:
error_msg = _('Value error at line {line} of {file}').format( error_msg = _('Value error at line {line} of {file}').format(
line=i + 1, line=i + 1,
file=file_name file=file_name
) )
utils.logError(f'{error_msg}: {e.msg}') utils.logError(error_msg)
except re.error: except re.error:
error_msg = _('Regex error at line {line} of {file}.') error_msg = _('Regex error at line {line} of {file}.')
utils.logError(error_msg.format(line=i + 1, file=file_name)) utils.logError(error_msg.format(line=i + 1, file=file_name))
@ -558,7 +557,7 @@ class _Colour:
class _SyntaxParser: class _SyntaxParser:
# create a new state machine that begins in initial_state and classifies # create a new state machine that begins in initial_state and classifies
# all characters not matched by the patterns as default_token_type # all characters not matched by the patterns as default_token_type
def __init__(self, initial_state, default_token_type): def __init__(self, initial_state: str, default_token_type: str) -> None:
# initial state for the state machine when parsing a new file # initial state for the state machine when parsing a new file
self.initial_state = initial_state self.initial_state = initial_state
# default classification of characters that are not explicitly matched # default classification of characters that are not explicitly matched
@ -567,30 +566,33 @@ class _SyntaxParser:
# mappings from a state to a list of (pattern, token_type, next_state) # mappings from a state to a list of (pattern, token_type, next_state)
# tuples indicating the new state for the state machine when 'pattern' # tuples indicating the new state for the state machine when 'pattern'
# is matched and how to classify the matched characters # is matched and how to classify the matched characters
self.transitions_lookup = {initial_state: []} self.transitions_lookup: Dict[str, List[Tuple[Pattern, str, str]]] = {initial_state: []}
# Adds a new edge to the finite state machine from prev_state to # Adds a new edge to the finite state machine from prev_state to
# next_state. Characters will be identified as token_type when pattern is # next_state. Characters will be identified as token_type when pattern is
# matched. Any newly referenced state will be added. Patterns for edges # matched. Any newly referenced state will be added. Patterns for edges
# leaving a state will be tested in the order they were added to the finite # leaving a state will be tested in the order they were added to the finite
# state machine. # state machine.
def addPattern(self, prev_state, next_state, token_type, pattern): def addPattern(
lookup = self.transitions_lookup self,
prev_state: str,
next_state: str,
token_type: str,
pattern: Pattern) -> None:
for state in prev_state, next_state: for state in prev_state, next_state:
if state not in lookup: if state not in self.transitions_lookup:
lookup[state] = [] self.transitions_lookup[state] = []
lookup[prev_state].append([pattern, token_type, next_state]) self.transitions_lookup[prev_state].append((pattern, token_type, next_state))
# given a string and an initial state, identify the final state and tokens # given a string and an initial state, identify the final state and tokens
def parse(self, state_name, s): def parse(self, state_name, s):
lookup = self.transitions_lookup transitions, blocks, start = self.transitions_lookup[state_name], [], 0
transitions, blocks, start = lookup[state_name], [], 0
while start < len(s): while start < len(s):
for pattern, token_type, next_state in transitions: for pattern, token_type, next_state in transitions:
m = pattern.match(s, start) m = pattern.match(s, start)
if m is not None: if m is not None:
end, state_name = m.span()[1], next_state end, state_name = m.span()[1], next_state
transitions = lookup[state_name] transitions = self.transitions_lookup[state_name]
break break
else: else:
end, token_type = start + 1, self.default_token_type end, token_type = start + 1, self.default_token_type
@ -602,4 +604,4 @@ class _SyntaxParser:
return state_name, blocks return state_name, blocks
theResources: Final[Resources] = Resources() theResources: Final = Resources()

View File

@ -28,6 +28,7 @@ from gettext import gettext as _
from typing import Final, List, Optional, TextIO from typing import Final, List, Optional, TextIO
from diffuse import constants from diffuse import constants
from diffuse.preferences import Preferences
from diffuse.resources import theResources from diffuse.resources import theResources
import gi # type: ignore import gi # type: ignore
@ -37,7 +38,7 @@ from gi.repository import Gtk # type: ignore # noqa: E402
# convenience class for displaying a message dialogue # convenience class for displaying a message dialogue
class MessageDialog(Gtk.MessageDialog): class MessageDialog(Gtk.MessageDialog):
def __init__(self, parent, message_type, s): def __init__(self, parent: Gtk.Widget, message_type: Gtk.MessageType, s: str) -> None:
if message_type == Gtk.MessageType.ERROR: if message_type == Gtk.MessageType.ERROR:
buttons = Gtk.ButtonsType.OK buttons = Gtk.ButtonsType.OK
else: else:
@ -54,7 +55,7 @@ class MessageDialog(Gtk.MessageDialog):
# widget to help pick an encoding # widget to help pick an encoding
class EncodingMenu(Gtk.Box): class EncodingMenu(Gtk.Box):
def __init__(self, prefs, autodetect=False): def __init__(self, prefs: Preferences, autodetect: bool = False) -> None:
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL) Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.combobox = combobox = Gtk.ComboBoxText.new() self.combobox = combobox = Gtk.ComboBoxText.new()
self.encodings = prefs.getEncodings()[:] self.encodings = prefs.getEncodings()[:]
@ -66,12 +67,12 @@ class EncodingMenu(Gtk.Box):
self.pack_start(combobox, False, False, 0) self.pack_start(combobox, False, False, 0)
combobox.show() combobox.show()
def set_text(self, encoding): def set_text(self, encoding: Optional[str]) -> None:
encoding = norm_encoding(encoding) encoding = norm_encoding(encoding)
if encoding in self.encodings: if encoding in self.encodings:
self.combobox.set_active(self.encodings.index(encoding)) self.combobox.set_active(self.encodings.index(encoding))
def get_text(self): def get_text(self) -> Optional[str]:
i = self.combobox.get_active() i = self.combobox.get_active()
return self.encodings[i] if i >= 0 else None return self.encodings[i] if i >= 0 else None
@ -143,7 +144,7 @@ def relpath(a: str, b: str) -> str:
# helper function prevent files from being confused with command line options # helper function prevent files from being confused with command line options
# by prepending './' to the basename # by prepending './' to the basename
def safeRelativePath(abspath1, name, prefs, cygwin_pref): def safeRelativePath(abspath1: str, name: str, prefs: Preferences, cygwin_pref: str) -> str:
s = os.path.join(os.curdir, relpath(abspath1, os.path.abspath(name))) s = os.path.join(os.curdir, relpath(abspath1, os.path.abspath(name)))
if isWindows(): if isWindows():
if prefs.getBool(cygwin_pref): if prefs.getBool(cygwin_pref):
@ -163,43 +164,49 @@ def _use_flatpak() -> bool:
# use popen to read the output of a command # use popen to read the output of a command
def popenRead(dn, cmd, prefs, bash_pref, success_results=None): def popenRead(
cwd: str,
cmd: List[str],
prefs: Preferences,
bash_pref: str,
success_results: List[int] = None) -> bytes:
if success_results is None: if success_results is None:
success_results = [0] success_results = [0]
opt_cwd: Optional[str] = cwd
if isWindows() and prefs.getBool(bash_pref): if isWindows() and prefs.getBool(bash_pref):
# launch the command from a bash shell is requested # launch the command from a bash shell is requested
cmd = [ cmd = [
prefs.convertToNativePath('/bin/bash.exe'), prefs.convertToNativePath('/bin/bash.exe'),
'-l', '-l',
'-c', '-c',
f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}" f"cd {_bash_escape(cwd)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}"
] ]
dn = None opt_cwd = None
# use subprocess.Popen to retrieve the file contents # use subprocess.Popen to retrieve the file contents
if isWindows(): if isWindows():
info = subprocess.STARTUPINFO() info = subprocess.STARTUPINFO() # type: ignore
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
info.wShowWindow = subprocess.SW_HIDE info.wShowWindow = subprocess.SW_HIDE # type: ignore
else: else:
info = None info = None
if _use_flatpak(): if _use_flatpak():
cmd = ['flatpak-spawn', '--host'] + cmd cmd = ['flatpak-spawn', '--host'] + cmd
with subprocess.Popen( with subprocess.Popen(
cmd, cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=opt_cwd,
cwd=dn,
startupinfo=info) as proc: startupinfo=info) as proc:
proc.stdin.close() output: bytes
proc.stderr.close() if proc.stdout is not None:
fd = proc.stdout # read the command's output
# read the command's output output = proc.stdout.read()
s = fd.read() proc.stdout.close()
fd.close()
if proc.wait() not in success_results: if proc.wait() not in success_results:
raise IOError('Command failed.') raise IOError('Command failed.')
return s return output
def len_minus_line_ending(s: str) -> int: def len_minus_line_ending(s: str) -> int:
@ -227,9 +234,14 @@ def _strip_eols(ss: List[str]) -> List[str]:
# use popen to read the output of a command # use popen to read the output of a command
def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None): def popenReadLines(
cwd: str,
cmd: List[str],
prefs: Preferences,
bash_pref: str,
success_results: List[int] = None) -> List[str]:
return _strip_eols(splitlines(popenRead( return _strip_eols(splitlines(popenRead(
dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore'))) cwd, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore')))
def readconfiglines(fd: TextIO) -> List[str]: def readconfiglines(fd: TextIO) -> List[str]:
@ -321,6 +333,7 @@ class LineEnding(IntFlag):
Values can be used as flags in bitwise operations.''' Values can be used as flags in bitwise operations.'''
NO_FORMAT = 0
DOS_FORMAT = 1 DOS_FORMAT = 1
MAC_FORMAT = 2 MAC_FORMAT = 2
UNIX_FORMAT = 4 UNIX_FORMAT = 4

View File

@ -20,13 +20,14 @@
import os import os
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Bazaar support # Bazaar support
class Bzr(VcsInterface): class Bzr(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
# merge conflict # merge conflict
left = name + '.OTHER' left = name + '.OTHER'
right = name + '.THIS' right = name + '.THIS'
@ -180,7 +181,7 @@ class Bzr(VcsInterface):
result.append(m[k]) result.append(m[k])
return result return result
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -22,13 +22,14 @@ import os
from gettext import gettext as _ from gettext import gettext as _
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# CVS support # CVS support
class Cvs(VcsInterface): class Cvs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
return [(name, 'BASE'), (name, None)] return [(name, 'BASE'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
@ -85,10 +86,10 @@ class Cvs(VcsInterface):
# sort the results # sort the results
return [modified[k] for k in sorted(modified.keys())] return [modified[k] for k in sorted(modified.keys())]
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
if rev == 'BASE' and not os.path.exists(name): if rev == 'BASE' and not os.path.exists(name):
# find revision for removed files # find revision for removed files
for s in utils.popenReadLines( lines = utils.popenReadLines(
self.root, self.root,
[ [
prefs.getString('cvs_bin'), prefs.getString('cvs_bin'),
@ -97,9 +98,10 @@ class Cvs(VcsInterface):
], ],
prefs, prefs,
'cvs_bash' 'cvs_bash'
): )
if s.startswith(' Working revision:\t-'): for line in lines:
rev = s.split('\t')[1][1:] if line.startswith(' Working revision:\t-'):
rev = line.split('\t')[1][1:]
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -19,14 +19,17 @@
import os import os
from typing import List
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Darcs support # Darcs support
class Darcs(VcsInterface): class Darcs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
return [(name, ''), (name, None)] return [(name, ''), (name, None)]
def _getCommitTemplate(self, prefs, names, rev): def _getCommitTemplate(self, prefs, names, rev):
@ -135,8 +138,8 @@ class Darcs(VcsInterface):
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
return self._getCommitTemplate(prefs, names, None) return self._getCommitTemplate(prefs, names, None)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
args = [prefs.getString('darcs_bin'), 'show', 'contents'] args: List[str] = [prefs.getString('darcs_bin'), 'show', 'contents']
try: try:
args.extend(['-n', str(int(rev))]) args.extend(['-n', str(int(rev))])
except ValueError: except ValueError:

View File

@ -19,6 +19,8 @@
import os import os
from typing import List
class FolderSet: class FolderSet:
'''Utility class to help support Git and Monotone. '''Utility class to help support Git and Monotone.
@ -27,18 +29,18 @@ class FolderSet:
"mtn automate inventory." "mtn automate inventory."
''' '''
def __init__(self, names): def __init__(self, names: List[str]) -> None:
self.folders = f = [] self.folders: List[str] = []
for name in names: for name in names:
name = os.path.abspath(name) name = os.path.abspath(name)
# ensure all names end with os.sep # ensure all names end with os.sep
if not name.endswith(os.sep): if not name.endswith(os.sep):
name += os.sep name += os.sep
f.append(name) self.folders.append(name)
# returns True if the given abspath is a file that should be included in # returns True if the given abspath is a file that should be included in
# the interesting file subset # the interesting file subset
def contains(self, abspath): def contains(self, abspath: str) -> bool:
if not abspath.endswith(os.sep): if not abspath.endswith(os.sep):
abspath += os.sep abspath += os.sep
for f in self.folders: for f in self.folders:

View File

@ -27,7 +27,7 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Git support # Git support
class Git(VcsInterface): class Git(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
return [(name, 'HEAD'), (name, None)] return [(name, 'HEAD'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
@ -150,7 +150,7 @@ class Git(VcsInterface):
result.append(m[k]) result.append(m[k])
return result return result
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
relpath = utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/') relpath = utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')
return utils.popenRead( return utils.popenRead(
self.root, self.root,

View File

@ -22,6 +22,7 @@ import os
from typing import Optional from typing import Optional
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
@ -29,7 +30,7 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Mercurial support # Mercurial support
class Hg(VcsInterface): class Hg(VcsInterface):
def __init__(self, root: str): def __init__(self, root: str):
VcsInterface.__init__(self, root) super().__init__(root)
self.working_rev: Optional[str] = None self.working_rev: Optional[str] = None
def _getPreviousRevision(self, prefs, rev): def _getPreviousRevision(self, prefs, rev):
@ -51,7 +52,7 @@ class Hg(VcsInterface):
return self.working_rev return self.working_rev
return f'p1({rev})' return f'p1({rev})'
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
return [(name, self._getPreviousRevision(prefs, None)), (name, None)] return [(name, self._getPreviousRevision(prefs, None)), (name, None)]
def _getCommitTemplate(self, prefs, names, cmd, rev): def _getCommitTemplate(self, prefs, names, cmd, rev):
@ -97,7 +98,7 @@ class Hg(VcsInterface):
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
return self._getCommitTemplate(prefs, names, ['status', '-q'], None) return self._getCommitTemplate(prefs, names, ['status', '-q'], None)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -21,27 +21,28 @@ import os
import shlex import shlex
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# Monotone support # Monotone support
class Mtn(VcsInterface): class Mtn(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
# FIXME: merge conflicts? # FIXME: merge conflicts?
return [(name, 'h:'), (name, None)] return [(name, 'h:'), (name, None)]
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
# build command # build command
vcs_bin = prefs.getString('mtn_bin') vcs_bin = prefs.getString('mtn_bin')
ss = utils.popenReadLines( lines = utils.popenReadLines(
self.root, self.root,
[vcs_bin, 'automate', 'select', '-q', rev], [vcs_bin, 'automate', 'select', '-q', rev],
prefs, prefs,
'mtn_bash') 'mtn_bash')
if len(ss) != 1: if len(lines) != 1:
raise IOError('Ambiguous revision specifier') raise IOError('Ambiguous revision specifier')
args = [vcs_bin, 'automate', 'get_revision', ss[0]] args = [vcs_bin, 'automate', 'get_revision', lines[0]]
# build list of interesting files # build list of interesting files
fs = FolderSet(names) fs = FolderSet(names)
pwd, isabs = os.path.abspath(os.curdir), False pwd, isabs = os.path.abspath(os.curdir), False
@ -50,15 +51,15 @@ class Mtn(VcsInterface):
# run command # run command
prev = None prev = None
removed, added, modified, renamed = {}, {}, {}, {} removed, added, modified, renamed = {}, {}, {}, {}
ss = utils.popenReadLines(self.root, args, prefs, 'mtn_bash') lines = utils.popenReadLines(self.root, args, prefs, 'mtn_bash')
i = 0 i = 0
while i < len(ss): while i < len(lines):
# process results # process results
s = shlex.split(ss[i]) line_args = shlex.split(lines[i])
i += 1 i += 1
if len(s) < 2: if len(line_args) < 2:
continue continue
arg, arg1 = s[0], s[1] arg, arg1 = line_args[0], line_args[1]
if arg == 'old_revision' and len(arg1) > 2: if arg == 'old_revision' and len(arg1) > 2:
if prev is not None: if prev is not None:
break break
@ -82,26 +83,27 @@ class Mtn(VcsInterface):
if fs.contains(k): if fs.contains(k):
modified[arg1] = k modified[arg1] = k
elif arg == 'rename': elif arg == 'rename':
s = shlex.split(ss[i]) line_args = shlex.split(lines[i])
i += 1 i += 1
if len(s) > 1 and s[0] == 'to': if len(line_args) > 1 and line_args[0] == 'to':
# renamed file # renamed file
k0 = os.path.join(self.root, prefs.convertToNativePath(arg1)) k0 = os.path.join(self.root, prefs.convertToNativePath(arg1))
k1 = os.path.join(self.root, prefs.convertToNativePath(s[1])) k1 = os.path.join(self.root, prefs.convertToNativePath(line_args[1]))
if fs.contains(k0) or fs.contains(k1): if fs.contains(k0) or fs.contains(k1):
renamed[s[1]] = (arg1, k0, k1) renamed[line_args[1]] = (arg1, k0, k1)
if removed or renamed: if removed or renamed:
# remove directories # remove directories
removed_dirs = set() removed_dirs = set()
for s in utils.popenReadLines( lines = utils.popenReadLines(
self.root, self.root,
[vcs_bin, 'automate', 'get_manifest_of', prev], [vcs_bin, 'automate', 'get_manifest_of', prev],
prefs, prefs,
'mtn_bash' 'mtn_bash'
): )
s = shlex.split(s) for line in lines:
if len(s) > 1 and s[0] == 'dir': line_args = shlex.split(line)
removed_dirs.add(s[1]) if len(line_args) > 1 and line_args[0] == 'dir':
removed_dirs.add(line_args[1])
for k in removed_dirs: for k in removed_dirs:
for m in removed, modified: for m in removed, modified:
if k in m: if k in m:
@ -216,7 +218,7 @@ class Mtn(VcsInterface):
result.append(m[k]) result.append(m[k])
return result return result
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -22,12 +22,13 @@ import os
from gettext import gettext as _ from gettext import gettext as _
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
# RCS support # RCS support
class Rcs(VcsInterface): class Rcs(VcsInterface):
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
args = [ args = [
prefs.getString('rcs_bin_rlog'), prefs.getString('rcs_bin_rlog'),
'-L', '-L',
@ -153,7 +154,7 @@ class Rcs(VcsInterface):
# sort the results # sort the results
return [[(k, r[k]), (k, None)] for k in sorted(r.keys())] return [[(k, r[k]), (k, None)] for k in sorted(r.keys())]
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
return utils.popenRead( return utils.popenRead(
self.root, self.root,
[ [

View File

@ -19,9 +19,10 @@
import os import os
from typing import Tuple from typing import Optional, Tuple
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.svn import Svn from diffuse.vcs.svn import Svn
@ -41,14 +42,14 @@ class Svk(Svn):
return s[0], s[4:] return s[0], s[4:]
@staticmethod @staticmethod
def _getPreviousRevision(rev: str) -> str: def _getPreviousRevision(rev: Optional[str]) -> str:
if rev is None: if rev is None:
return 'HEAD' return 'HEAD'
if rev.endswith('@'): if rev.endswith('@'):
return str(int(rev[:-1]) - 1) + '@' return str(int(rev[:-1]) - 1) + '@'
return str(int(rev) - 1) return str(int(rev) - 1)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
relpath = utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/') relpath = utils.relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')
return utils.popenRead( return utils.popenRead(
self.root, self.root,

View File

@ -24,6 +24,7 @@ from gettext import gettext as _
from typing import Optional, Tuple from typing import Optional, Tuple
from diffuse import utils from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.folder_set import FolderSet from diffuse.vcs.folder_set import FolderSet
from diffuse.vcs.vcs_interface import VcsInterface from diffuse.vcs.vcs_interface import VcsInterface
@ -32,7 +33,7 @@ from diffuse.vcs.vcs_interface import VcsInterface
# SVK support subclasses from this # SVK support subclasses from this
class Svn(VcsInterface): class Svn(VcsInterface):
def __init__(self, root: str): def __init__(self, root: str):
VcsInterface.__init__(self, root) super().__init__(root)
self.url: Optional[str] = None self.url: Optional[str] = None
@staticmethod @staticmethod
@ -54,13 +55,13 @@ class Svn(VcsInterface):
return s[0], s[k:] return s[0], s[k:]
@staticmethod @staticmethod
def _getPreviousRevision(rev: str) -> str: def _getPreviousRevision(rev: Optional[str]) -> str:
if rev is None: if rev is None:
return 'BASE' return 'BASE'
m = int(rev) m = int(rev)
return str(max(m > 1, 0)) return str(max(m > 1, 0))
def _getURL(self, prefs): def _getURL(self, prefs: Preferences) -> Optional[str]:
if self.url is None: if self.url is None:
vcs, prefix = self._getVcs(), self._getURLPrefix() vcs, prefix = self._getVcs(), self._getURLPrefix()
n = len(prefix) n = len(prefix)
@ -71,7 +72,7 @@ class Svn(VcsInterface):
break break
return self.url return self.url
def getFileTemplate(self, prefs, name): def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
# FIXME: verify this # FIXME: verify this
# merge conflict # merge conflict
escaped_name = utils.globEscape(name) escaped_name = utils.globEscape(name)
@ -271,7 +272,7 @@ class Svn(VcsInterface):
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
return self._getCommitTemplate(prefs, None, names) return self._getCommitTemplate(prefs, None, names)
def getRevision(self, prefs, name, rev): def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
vcs_bin = prefs.getString('svn_bin') vcs_bin = prefs.getString('svn_bin')
if rev in ['BASE', 'COMMITTED', 'PREV']: if rev in ['BASE', 'COMMITTED', 'PREV']:
return utils.popenRead( return utils.popenRead(

View File

@ -17,22 +17,35 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
class VcsInterface: from abc import ABCMeta, abstractmethod
from typing import List, Optional, Tuple
from diffuse.preferences import Preferences
class VcsInterface(metaclass=ABCMeta):
"""Interface for the VCSs.""" """Interface for the VCSs."""
PathRevisionPair = Tuple[Optional[str], Optional[str]]
PathRevisionList = List[PathRevisionPair]
def __init__(self, root: str): def __init__(self, root: str):
"""The object will initialized with the repository's root folder.""" """The object will initialized with the repository's root folder."""
self.root = root self.root = root
def getFileTemplate(self, prefs, name): @abstractmethod
def getFileTemplate(self, prefs: Preferences, name: str) -> PathRevisionList:
"""Indicates which revisions to display for a file when none were explicitly """Indicates which revisions to display for a file when none were explicitly
requested.""" requested."""
@abstractmethod
def getCommitTemplate(self, prefs, rev, names): def getCommitTemplate(self, prefs, rev, names):
"""Indicates which file revisions to display for a commit.""" """Indicates which file revisions to display for a commit."""
@abstractmethod
def getFolderTemplate(self, prefs, names): def getFolderTemplate(self, prefs, names):
"""Indicates which file revisions to display for a set of folders.""" """Indicates which file revisions to display for a set of folders."""
def getRevision(self, prefs, name, rev): @abstractmethod
def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
"""Returns the contents of the specified file revision""" """Returns the contents of the specified file revision"""

View File

@ -99,7 +99,7 @@ def _get_darcs_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
def _get_git_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]: def _get_git_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
if 'GIT_DIR' in os.environ: if 'GIT_DIR' in os.environ:
try: try:
ss: List[str] = utils.popenReadLines( lines: List[str] = utils.popenReadLines(
path, path,
[ [
prefs.getString('git_bin'), prefs.getString('git_bin'),
@ -108,12 +108,12 @@ def _get_git_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
], ],
prefs, prefs,
'git_bash') 'git_bash')
if len(ss) > 0: if len(lines) > 0:
# be careful to handle trailing slashes # be careful to handle trailing slashes
dirs = path.split(os.sep) dirs = path.split(os.sep)
if dirs[-1] != '': if dirs[-1] != '':
dirs.append('') dirs.append('')
ss = utils.strip_eol(ss[0]).split('/') ss = utils.strip_eol(lines[0]).split('/')
if ss[-1] != '': if ss[-1] != '':
ss.append('') ss.append('')
n = len(ss) n = len(ss)
@ -186,7 +186,8 @@ def _get_svk_repo(path: str, prefs: Preferences) -> Optional[VcsInterface]:
# find working copies by parsing the config file # find working copies by parsing the config file
with open(svkconfig, 'r', encoding='utf-8') as f: with open(svkconfig, 'r', encoding='utf-8') as f:
ss: List[str] = utils.readlines(f) ss: List[str] = utils.readlines(f)
projs, sep = [], os.sep projs: List[str] = []
sep = os.sep
# find the separator character # find the separator character
for s in ss: for s in ss:
if s.startswith(' sep: ') and len(s) > 7: if s.startswith(' sep: ') and len(s) > 7:

File diff suppressed because it is too large Load Diff