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

View File

@ -24,7 +24,7 @@ import shlex
import sys
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 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 Preferences:
def __init__(self, path):
self.bool_prefs = {}
self.int_prefs = {}
self.string_prefs = {}
self.int_prefs_min = {}
self.int_prefs_max = {}
self.string_prefs_enums = {}
def __init__(self, path: str) -> None:
self.path = path
self.bool_prefs: Dict[str, bool] = {}
self.string_prefs: Dict[str, str] = {}
self.int_prefs: Dict[str, int] = {}
self.int_prefs_min: Dict[str, int] = {}
self.int_prefs_max: Dict[str, int] = {}
# 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():
svk_bin = 'svk.bat'
@ -54,7 +54,7 @@ class Preferences:
auto_detect_codecs = ['utf_8', 'utf_16', 'latin_1']
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
# being tried
auto_detect_codecs.insert(2, e)
@ -124,7 +124,7 @@ class Preferences:
]
# 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_ignore_whitespace_changes': ('display_ignore_whitespace', True),
'display_ignore_blanklines': ('display_ignore_whitespace', True),
@ -165,9 +165,9 @@ class Preferences:
_('Version control system search order')
]
]
vcs_folders_template = ['FolderSet']
vcs_folders_template: List[Any] = ['FolderSet']
for key, name, cmd in vcs:
temp = ['List']
temp: List[Any] = ['List']
if key == 'rcs':
# RCS uses multiple commands
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_int_prefs = self.int_prefs.copy()
self.default_string_prefs = self.string_prefs.copy()
# load the user's preferences
self.path = path
if os.path.isfile(self.path):
try:
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
# 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.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT)
dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
widgets = {}
widgets: Dict[str, Gtk.Widget] = {}
w = self._buildPrefsDialog(parent, widgets, self.template)
# disable any preferences than are not relevant
for k, v in self.disable_when.items():
p, t = v
for k, tuple_value in self.disable_when.items():
p, t = tuple_value
if widgets[p].get_active() == t:
widgets[k].set_sensitive(False)
dialog.vbox.add(w)
@ -280,15 +280,15 @@ class Preferences:
self.string_prefs[k] = utils.null_to_empty(widgets[k].get_text())
try:
ss = []
for k, v in self.bool_prefs.items():
if v != self.default_bool_prefs[k]:
ss.append(f'{k} {v}\n')
for k, v in self.int_prefs.items():
if v != self.default_int_prefs[k]:
ss.append(f'{k} {v}\n')
for k, v in self.string_prefs.items():
if v != self.default_string_prefs[k]:
v_escaped = v.replace('\\', '\\\\').replace('"', '\\"')
for k, bool_value in self.bool_prefs.items():
if bool_value != self.default_bool_prefs[k]:
ss.append(f'{k} {bool_value}\n')
for k, int_value in self.int_prefs.items():
if int_value != self.default_int_prefs[k]:
ss.append(f'{k} {int_value}\n')
for k, str_value in self.string_prefs.items():
if str_value != self.default_string_prefs[k]:
v_escaped = str_value.replace('\\', '\\\\').replace('"', '\\"')
ss.append(f'{k} "{v_escaped}"\n')
ss.sort()
with open(self.path, 'w', encoding='utf-8') as f:
@ -384,7 +384,7 @@ class Preferences:
def setString(self, name: str, value: str) -> None:
self.string_prefs[name] = value
def getEncodings(self):
def getEncodings(self) -> List[Optional[str]]:
return self.encodings
def _getDefaultEncodings(self) -> List[str]:
@ -439,7 +439,7 @@ class Preferences:
# text entry widget with a button to help pick file names
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)
self.toplevel = parent
self.title = title
@ -456,7 +456,7 @@ class _FileEntry(Gtk.Box):
button.show()
# action performed when the pick file button is pressed
def chooseFile(self, widget):
def chooseFile(self, widget: Gtk.Widget) -> None:
dialog = Gtk.FileChooserDialog(
self.title,
self.toplevel,

View File

@ -31,7 +31,7 @@ import shlex
from distutils import util
from gettext import gettext as _
from typing import Final
from typing import Dict, Final, List, Optional, Pattern, Set, Tuple
from diffuse import utils
@ -45,134 +45,133 @@ class Resources:
# default keybindings
self.keybindings = {}
self.keybindings_lookup = {}
set_binding = self.setKeyBinding
set_binding('menu', 'open_file', 'Ctrl+o')
set_binding('menu', 'open_file_in_new_tab', 'Ctrl+t')
set_binding('menu', 'open_modified_files', 'Shift+Ctrl+O')
set_binding('menu', 'open_commit', 'Shift+Ctrl+T')
set_binding('menu', 'reload_file', 'Shift+Ctrl+R')
set_binding('menu', 'save_file', 'Ctrl+s')
set_binding('menu', 'save_file_as', 'Shift+Ctrl+A')
set_binding('menu', 'save_all', 'Shift+Ctrl+S')
set_binding('menu', 'new_2_way_file_merge', 'Ctrl+2')
set_binding('menu', 'new_3_way_file_merge', 'Ctrl+3')
set_binding('menu', 'new_n_way_file_merge', 'Ctrl+4')
set_binding('menu', 'close_tab', 'Ctrl+w')
set_binding('menu', 'undo_close_tab', 'Shift+Ctrl+W')
set_binding('menu', 'quit', 'Ctrl+q')
set_binding('menu', 'undo', 'Ctrl+z')
set_binding('menu', 'redo', 'Shift+Ctrl+Z')
set_binding('menu', 'cut', 'Ctrl+x')
set_binding('menu', 'copy', 'Ctrl+c')
set_binding('menu', 'paste', 'Ctrl+v')
set_binding('menu', 'select_all', 'Ctrl+a')
set_binding('menu', 'clear_edits', 'Ctrl+r')
set_binding('menu', 'dismiss_all_edits', 'Ctrl+d')
set_binding('menu', 'find', 'Ctrl+f')
set_binding('menu', 'find_next', 'Ctrl+g')
set_binding('menu', 'find_previous', 'Shift+Ctrl+G')
set_binding('menu', 'go_to_line', 'Shift+Ctrl+L')
set_binding('menu', 'realign_all', 'Ctrl+l')
set_binding('menu', 'isolate', 'Ctrl+i')
set_binding('menu', 'first_difference', 'Shift+Ctrl+Up')
set_binding('menu', 'previous_difference', 'Ctrl+Up')
set_binding('menu', 'next_difference', 'Ctrl+Down')
set_binding('menu', 'last_difference', 'Shift+Ctrl+Down')
set_binding('menu', 'first_tab', 'Shift+Ctrl+Page_Up')
set_binding('menu', 'previous_tab', 'Ctrl+Page_Up')
set_binding('menu', 'next_tab', 'Ctrl+Page_Down')
set_binding('menu', 'last_tab', 'Shift+Ctrl+Page_Down')
set_binding('menu', 'shift_pane_right', 'Shift+Ctrl+parenright')
set_binding('menu', 'shift_pane_left', 'Shift+Ctrl+parenleft')
set_binding('menu', 'convert_to_upper_case', 'Ctrl+u')
set_binding('menu', 'convert_to_lower_case', 'Shift+Ctrl+U')
set_binding('menu', 'sort_lines_in_ascending_order', 'Ctrl+y')
set_binding('menu', 'sort_lines_in_descending_order', 'Shift+Ctrl+Y')
set_binding('menu', 'remove_trailing_white_space', 'Ctrl+k')
set_binding('menu', 'convert_tabs_to_spaces', 'Ctrl+b')
set_binding('menu', 'convert_leading_spaces_to_tabs', 'Shift+Ctrl+B')
set_binding('menu', 'increase_indenting', 'Shift+Ctrl+greater')
set_binding('menu', 'decrease_indenting', 'Shift+Ctrl+less')
set_binding('menu', 'convert_to_dos', 'Shift+Ctrl+E')
set_binding('menu', 'convert_to_mac', 'Shift+Ctrl+C')
set_binding('menu', 'convert_to_unix', 'Ctrl+e')
set_binding('menu', 'copy_selection_right', 'Shift+Ctrl+Right')
set_binding('menu', 'copy_selection_left', 'Shift+Ctrl+Left')
set_binding('menu', 'copy_left_into_selection', 'Ctrl+Right')
set_binding('menu', 'copy_right_into_selection', 'Ctrl+Left')
set_binding('menu', 'merge_from_left_then_right', 'Ctrl+m')
set_binding('menu', 'merge_from_right_then_left', 'Shift+Ctrl+M')
set_binding('menu', 'help_contents', 'F1')
set_binding('line_mode', 'enter_align_mode', 'space')
set_binding('line_mode', 'enter_character_mode', 'Return')
set_binding('line_mode', 'enter_character_mode', 'KP_Enter')
set_binding('line_mode', 'first_line', 'Home')
set_binding('line_mode', 'first_line', 'g')
set_binding('line_mode', 'extend_first_line', 'Shift+Home')
set_binding('line_mode', 'last_line', 'End')
set_binding('line_mode', 'last_line', 'Shift+G')
set_binding('line_mode', 'extend_last_line', 'Shift+End')
set_binding('line_mode', 'up', 'Up')
set_binding('line_mode', 'up', 'k')
set_binding('line_mode', 'extend_up', 'Shift+Up')
set_binding('line_mode', 'extend_up', 'Shift+K')
set_binding('line_mode', 'down', 'Down')
set_binding('line_mode', 'down', 'j')
set_binding('line_mode', 'extend_down', 'Shift+Down')
set_binding('line_mode', 'extend_down', 'Shift+J')
set_binding('line_mode', 'left', 'Left')
set_binding('line_mode', 'left', 'h')
set_binding('line_mode', 'extend_left', 'Shift+Left')
set_binding('line_mode', 'right', 'Right')
set_binding('line_mode', 'right', 'l')
set_binding('line_mode', 'extend_right', 'Shift+Right')
set_binding('line_mode', 'page_up', 'Page_Up')
set_binding('line_mode', 'page_up', 'Ctrl+u')
set_binding('line_mode', 'extend_page_up', 'Shift+Page_Up')
set_binding('line_mode', 'extend_page_up', 'Shift+Ctrl+U')
set_binding('line_mode', 'page_down', 'Page_Down')
set_binding('line_mode', 'page_down', 'Ctrl+d')
set_binding('line_mode', 'extend_page_down', 'Shift+Page_Down')
set_binding('line_mode', 'extend_page_down', 'Shift+Ctrl+D')
set_binding('line_mode', 'delete_text', 'BackSpace')
set_binding('line_mode', 'delete_text', 'Delete')
set_binding('line_mode', 'delete_text', 'x')
set_binding('line_mode', 'clear_edits', 'r')
set_binding('line_mode', 'isolate', 'i')
set_binding('line_mode', 'first_difference', 'Ctrl+Home')
set_binding('line_mode', 'first_difference', 'Shift+P')
set_binding('line_mode', 'previous_difference', 'p')
set_binding('line_mode', 'next_difference', 'n')
set_binding('line_mode', 'last_difference', 'Ctrl+End')
set_binding('line_mode', 'last_difference', 'Shift+N')
# set_binding('line_mode', 'copy_selection_right', 'Shift+L')
# set_binding('line_mode', 'copy_selection_left', 'Shift+H')
set_binding('line_mode', 'copy_left_into_selection', 'Shift+L')
set_binding('line_mode', 'copy_right_into_selection', 'Shift+H')
set_binding('line_mode', 'merge_from_left_then_right', 'm')
set_binding('line_mode', 'merge_from_right_then_left', 'Shift+M')
set_binding('align_mode', 'enter_line_mode', 'Escape')
set_binding('align_mode', 'align', 'space')
set_binding('align_mode', 'enter_character_mode', 'Return')
set_binding('align_mode', 'enter_character_mode', 'KP_Enter')
set_binding('align_mode', 'first_line', 'g')
set_binding('align_mode', 'last_line', 'Shift+G')
set_binding('align_mode', 'up', 'Up')
set_binding('align_mode', 'up', 'k')
set_binding('align_mode', 'down', 'Down')
set_binding('align_mode', 'down', 'j')
set_binding('align_mode', 'left', 'Left')
set_binding('align_mode', 'left', 'h')
set_binding('align_mode', 'right', 'Right')
set_binding('align_mode', 'right', 'l')
set_binding('align_mode', 'page_up', 'Page_Up')
set_binding('align_mode', 'page_up', 'Ctrl+u')
set_binding('align_mode', 'page_down', 'Page_Down')
set_binding('align_mode', 'page_down', 'Ctrl+d')
set_binding('character_mode', 'enter_line_mode', 'Escape')
self.setKeyBinding('menu', 'open_file', 'Ctrl+o')
self.setKeyBinding('menu', 'open_file_in_new_tab', 'Ctrl+t')
self.setKeyBinding('menu', 'open_modified_files', 'Shift+Ctrl+O')
self.setKeyBinding('menu', 'open_commit', 'Shift+Ctrl+T')
self.setKeyBinding('menu', 'reload_file', 'Shift+Ctrl+R')
self.setKeyBinding('menu', 'save_file', 'Ctrl+s')
self.setKeyBinding('menu', 'save_file_as', 'Shift+Ctrl+A')
self.setKeyBinding('menu', 'save_all', 'Shift+Ctrl+S')
self.setKeyBinding('menu', 'new_2_way_file_merge', 'Ctrl+2')
self.setKeyBinding('menu', 'new_3_way_file_merge', 'Ctrl+3')
self.setKeyBinding('menu', 'new_n_way_file_merge', 'Ctrl+4')
self.setKeyBinding('menu', 'close_tab', 'Ctrl+w')
self.setKeyBinding('menu', 'undo_close_tab', 'Shift+Ctrl+W')
self.setKeyBinding('menu', 'quit', 'Ctrl+q')
self.setKeyBinding('menu', 'undo', 'Ctrl+z')
self.setKeyBinding('menu', 'redo', 'Shift+Ctrl+Z')
self.setKeyBinding('menu', 'cut', 'Ctrl+x')
self.setKeyBinding('menu', 'copy', 'Ctrl+c')
self.setKeyBinding('menu', 'paste', 'Ctrl+v')
self.setKeyBinding('menu', 'select_all', 'Ctrl+a')
self.setKeyBinding('menu', 'clear_edits', 'Ctrl+r')
self.setKeyBinding('menu', 'dismiss_all_edits', 'Ctrl+d')
self.setKeyBinding('menu', 'find', 'Ctrl+f')
self.setKeyBinding('menu', 'find_next', 'Ctrl+g')
self.setKeyBinding('menu', 'find_previous', 'Shift+Ctrl+G')
self.setKeyBinding('menu', 'go_to_line', 'Shift+Ctrl+L')
self.setKeyBinding('menu', 'realign_all', 'Ctrl+l')
self.setKeyBinding('menu', 'isolate', 'Ctrl+i')
self.setKeyBinding('menu', 'first_difference', 'Shift+Ctrl+Up')
self.setKeyBinding('menu', 'previous_difference', 'Ctrl+Up')
self.setKeyBinding('menu', 'next_difference', 'Ctrl+Down')
self.setKeyBinding('menu', 'last_difference', 'Shift+Ctrl+Down')
self.setKeyBinding('menu', 'first_tab', 'Shift+Ctrl+Page_Up')
self.setKeyBinding('menu', 'previous_tab', 'Ctrl+Page_Up')
self.setKeyBinding('menu', 'next_tab', 'Ctrl+Page_Down')
self.setKeyBinding('menu', 'last_tab', 'Shift+Ctrl+Page_Down')
self.setKeyBinding('menu', 'shift_pane_right', 'Shift+Ctrl+parenright')
self.setKeyBinding('menu', 'shift_pane_left', 'Shift+Ctrl+parenleft')
self.setKeyBinding('menu', 'convert_to_upper_case', 'Ctrl+u')
self.setKeyBinding('menu', 'convert_to_lower_case', 'Shift+Ctrl+U')
self.setKeyBinding('menu', 'sort_lines_in_ascending_order', 'Ctrl+y')
self.setKeyBinding('menu', 'sort_lines_in_descending_order', 'Shift+Ctrl+Y')
self.setKeyBinding('menu', 'remove_trailing_white_space', 'Ctrl+k')
self.setKeyBinding('menu', 'convert_tabs_to_spaces', 'Ctrl+b')
self.setKeyBinding('menu', 'convert_leading_spaces_to_tabs', 'Shift+Ctrl+B')
self.setKeyBinding('menu', 'increase_indenting', 'Shift+Ctrl+greater')
self.setKeyBinding('menu', 'decrease_indenting', 'Shift+Ctrl+less')
self.setKeyBinding('menu', 'convert_to_dos', 'Shift+Ctrl+E')
self.setKeyBinding('menu', 'convert_to_mac', 'Shift+Ctrl+C')
self.setKeyBinding('menu', 'convert_to_unix', 'Ctrl+e')
self.setKeyBinding('menu', 'copy_selection_right', 'Shift+Ctrl+Right')
self.setKeyBinding('menu', 'copy_selection_left', 'Shift+Ctrl+Left')
self.setKeyBinding('menu', 'copy_left_into_selection', 'Ctrl+Right')
self.setKeyBinding('menu', 'copy_right_into_selection', 'Ctrl+Left')
self.setKeyBinding('menu', 'merge_from_left_then_right', 'Ctrl+m')
self.setKeyBinding('menu', 'merge_from_right_then_left', 'Shift+Ctrl+M')
self.setKeyBinding('menu', 'help_contents', 'F1')
self.setKeyBinding('line_mode', 'enter_align_mode', 'space')
self.setKeyBinding('line_mode', 'enter_character_mode', 'Return')
self.setKeyBinding('line_mode', 'enter_character_mode', 'KP_Enter')
self.setKeyBinding('line_mode', 'first_line', 'Home')
self.setKeyBinding('line_mode', 'first_line', 'g')
self.setKeyBinding('line_mode', 'extend_first_line', 'Shift+Home')
self.setKeyBinding('line_mode', 'last_line', 'End')
self.setKeyBinding('line_mode', 'last_line', 'Shift+G')
self.setKeyBinding('line_mode', 'extend_last_line', 'Shift+End')
self.setKeyBinding('line_mode', 'up', 'Up')
self.setKeyBinding('line_mode', 'up', 'k')
self.setKeyBinding('line_mode', 'extend_up', 'Shift+Up')
self.setKeyBinding('line_mode', 'extend_up', 'Shift+K')
self.setKeyBinding('line_mode', 'down', 'Down')
self.setKeyBinding('line_mode', 'down', 'j')
self.setKeyBinding('line_mode', 'extend_down', 'Shift+Down')
self.setKeyBinding('line_mode', 'extend_down', 'Shift+J')
self.setKeyBinding('line_mode', 'left', 'Left')
self.setKeyBinding('line_mode', 'left', 'h')
self.setKeyBinding('line_mode', 'extend_left', 'Shift+Left')
self.setKeyBinding('line_mode', 'right', 'Right')
self.setKeyBinding('line_mode', 'right', 'l')
self.setKeyBinding('line_mode', 'extend_right', 'Shift+Right')
self.setKeyBinding('line_mode', 'page_up', 'Page_Up')
self.setKeyBinding('line_mode', 'page_up', 'Ctrl+u')
self.setKeyBinding('line_mode', 'extend_page_up', 'Shift+Page_Up')
self.setKeyBinding('line_mode', 'extend_page_up', 'Shift+Ctrl+U')
self.setKeyBinding('line_mode', 'page_down', 'Page_Down')
self.setKeyBinding('line_mode', 'page_down', 'Ctrl+d')
self.setKeyBinding('line_mode', 'extend_page_down', 'Shift+Page_Down')
self.setKeyBinding('line_mode', 'extend_page_down', 'Shift+Ctrl+D')
self.setKeyBinding('line_mode', 'delete_text', 'BackSpace')
self.setKeyBinding('line_mode', 'delete_text', 'Delete')
self.setKeyBinding('line_mode', 'delete_text', 'x')
self.setKeyBinding('line_mode', 'clear_edits', 'r')
self.setKeyBinding('line_mode', 'isolate', 'i')
self.setKeyBinding('line_mode', 'first_difference', 'Ctrl+Home')
self.setKeyBinding('line_mode', 'first_difference', 'Shift+P')
self.setKeyBinding('line_mode', 'previous_difference', 'p')
self.setKeyBinding('line_mode', 'next_difference', 'n')
self.setKeyBinding('line_mode', 'last_difference', 'Ctrl+End')
self.setKeyBinding('line_mode', 'last_difference', 'Shift+N')
# self.setKeyBinding('line_mode', 'copy_selection_right', 'Shift+L')
# self.setKeyBinding('line_mode', 'copy_selection_left', 'Shift+H')
self.setKeyBinding('line_mode', 'copy_left_into_selection', 'Shift+L')
self.setKeyBinding('line_mode', 'copy_right_into_selection', 'Shift+H')
self.setKeyBinding('line_mode', 'merge_from_left_then_right', 'm')
self.setKeyBinding('line_mode', 'merge_from_right_then_left', 'Shift+M')
self.setKeyBinding('align_mode', 'enter_line_mode', 'Escape')
self.setKeyBinding('align_mode', 'align', 'space')
self.setKeyBinding('align_mode', 'enter_character_mode', 'Return')
self.setKeyBinding('align_mode', 'enter_character_mode', 'KP_Enter')
self.setKeyBinding('align_mode', 'first_line', 'g')
self.setKeyBinding('align_mode', 'last_line', 'Shift+G')
self.setKeyBinding('align_mode', 'up', 'Up')
self.setKeyBinding('align_mode', 'up', 'k')
self.setKeyBinding('align_mode', 'down', 'Down')
self.setKeyBinding('align_mode', 'down', 'j')
self.setKeyBinding('align_mode', 'left', 'Left')
self.setKeyBinding('align_mode', 'left', 'h')
self.setKeyBinding('align_mode', 'right', 'Right')
self.setKeyBinding('align_mode', 'right', 'l')
self.setKeyBinding('align_mode', 'page_up', 'Page_Up')
self.setKeyBinding('align_mode', 'page_up', 'Ctrl+u')
self.setKeyBinding('align_mode', 'page_down', 'Page_Down')
self.setKeyBinding('align_mode', 'page_down', 'Ctrl+d')
self.setKeyBinding('character_mode', 'enter_line_mode', 'Escape')
# default colours
self.colours = {
self.colours: Dict[str, _Colour] = {
'alignment': _Colour(1.0, 1.0, 0.0),
'character_selection': _Colour(0.7, 0.7, 1.0),
'cursor': _Colour(0.0, 0.0, 0.0),
@ -192,7 +191,7 @@ class Resources:
}
# default floats
self.floats = {
self.floats: Dict[str, float] = {
'alignment_opacity': 1.0,
'character_difference_opacity': 0.4,
'character_selection_opacity': 0.4,
@ -202,29 +201,29 @@ class Resources:
}
# default options
self.options = {
self.options: Dict[str, str] = {
'log_print_output': 'False',
'log_print_stack': 'False',
'use_flatpak': 'False'
}
# default strings
self.strings = {}
self.strings: Dict[str, str] = {}
# syntax highlighting support
self.syntaxes = {}
self.syntax_file_patterns = {}
self.syntax_magic_patterns = {}
self.current_syntax = None
self.syntaxes: Dict[str, _SyntaxParser] = {}
self.syntax_file_patterns: Dict[str, Pattern] = {}
self.syntax_magic_patterns: Dict[str, Pattern] = {}
self.current_syntax: Optional[_SyntaxParser] = None
# list of imported resources files (we only import each file once)
self.resource_files = set()
self.resource_files: Set[str] = set()
# special string resources
self.setDifferenceColours('difference_1 difference_2 difference_3')
# keyboard action processing
def setKeyBinding(self, ctx, s, v):
def setKeyBinding(self, ctx: str, s: str, v: str) -> None:
action_tuple = (ctx, s)
modifiers = Gdk.ModifierType(0)
key = None
@ -282,7 +281,7 @@ class Resources:
return []
# colours used for indicating differences
def setDifferenceColours(self, s):
def setDifferenceColours(self, s: str) -> None:
colours = s.split()
if len(colours) > 0:
self.difference_colours = colours
@ -322,7 +321,7 @@ class Resources:
return util.strtobool(self.getOption(option))
# string resources
def getString(self, symbol):
def getString(self, symbol: str) -> str:
try:
return self.strings[symbol]
except KeyError:
@ -336,7 +335,7 @@ class Resources:
def getSyntax(self, name):
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)
for key, pattern in self.syntax_file_patterns.items():
if pattern.search(name):
@ -350,7 +349,7 @@ class Resources:
return None
# parse resource files
def parse(self, file_name):
def parse(self, file_name: str) -> None:
# only process files once
if file_name in self.resource_files:
return
@ -514,12 +513,12 @@ class Resources:
file=file_name
)
utils.logError(f'{error_msg}: {e.msg}')
except ValueError as e:
except ValueError:
error_msg = _('Value error at line {line} of {file}').format(
line=i + 1,
file=file_name
)
utils.logError(f'{error_msg}: {e.msg}')
utils.logError(error_msg)
except re.error:
error_msg = _('Regex error at line {line} of {file}.')
utils.logError(error_msg.format(line=i + 1, file=file_name))
@ -558,7 +557,7 @@ class _Colour:
class _SyntaxParser:
# create a new state machine that begins in initial_state and classifies
# 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
self.initial_state = initial_state
# 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)
# tuples indicating the new state for the state machine when 'pattern'
# is matched and how to classify the matched characters
self.transitions_lookup = {initial_state: []}
self.transitions_lookup: Dict[str, List[Tuple[Pattern, str, str]]] = {initial_state: []}
# Adds a new edge to the finite state machine from prev_state to
# next_state. Characters will be identified as token_type when pattern is
# 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
# state machine.
def addPattern(self, prev_state, next_state, token_type, pattern):
lookup = self.transitions_lookup
def addPattern(
self,
prev_state: str,
next_state: str,
token_type: str,
pattern: Pattern) -> None:
for state in prev_state, next_state:
if state not in lookup:
lookup[state] = []
lookup[prev_state].append([pattern, token_type, next_state])
if state not in self.transitions_lookup:
self.transitions_lookup[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
def parse(self, state_name, s):
lookup = self.transitions_lookup
transitions, blocks, start = lookup[state_name], [], 0
transitions, blocks, start = self.transitions_lookup[state_name], [], 0
while start < len(s):
for pattern, token_type, next_state in transitions:
m = pattern.match(s, start)
if m is not None:
end, state_name = m.span()[1], next_state
transitions = lookup[state_name]
transitions = self.transitions_lookup[state_name]
break
else:
end, token_type = start + 1, self.default_token_type
@ -602,4 +604,4 @@ class _SyntaxParser:
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 diffuse import constants
from diffuse.preferences import Preferences
from diffuse.resources import theResources
import gi # type: ignore
@ -37,7 +38,7 @@ from gi.repository import Gtk # type: ignore # noqa: E402
# convenience class for displaying a message dialogue
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:
buttons = Gtk.ButtonsType.OK
else:
@ -54,7 +55,7 @@ class MessageDialog(Gtk.MessageDialog):
# widget to help pick an encoding
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)
self.combobox = combobox = Gtk.ComboBoxText.new()
self.encodings = prefs.getEncodings()[:]
@ -66,12 +67,12 @@ class EncodingMenu(Gtk.Box):
self.pack_start(combobox, False, False, 0)
combobox.show()
def set_text(self, encoding):
def set_text(self, encoding: Optional[str]) -> None:
encoding = norm_encoding(encoding)
if encoding in self.encodings:
self.combobox.set_active(self.encodings.index(encoding))
def get_text(self):
def get_text(self) -> Optional[str]:
i = self.combobox.get_active()
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
# 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)))
if isWindows():
if prefs.getBool(cygwin_pref):
@ -163,43 +164,49 @@ def _use_flatpak() -> bool:
# 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:
success_results = [0]
opt_cwd: Optional[str] = cwd
if isWindows() and prefs.getBool(bash_pref):
# launch the command from a bash shell is requested
cmd = [
prefs.convertToNativePath('/bin/bash.exe'),
'-l',
'-c',
f"cd {_bash_escape(dn)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}"
f"cd {_bash_escape(cwd)}; {' '.join([ _bash_escape(arg) for arg in cmd ])}"
]
dn = None
opt_cwd = None
# use subprocess.Popen to retrieve the file contents
if isWindows():
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = subprocess.SW_HIDE
info = subprocess.STARTUPINFO() # type: ignore
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
info.wShowWindow = subprocess.SW_HIDE # type: ignore
else:
info = None
if _use_flatpak():
cmd = ['flatpak-spawn', '--host'] + cmd
with subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=dn,
cwd=opt_cwd,
startupinfo=info) as proc:
proc.stdin.close()
proc.stderr.close()
fd = proc.stdout
# read the command's output
s = fd.read()
fd.close()
output: bytes
if proc.stdout is not None:
# read the command's output
output = proc.stdout.read()
proc.stdout.close()
if proc.wait() not in success_results:
raise IOError('Command failed.')
return s
return output
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
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(
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]:
@ -321,6 +333,7 @@ class LineEnding(IntFlag):
Values can be used as flags in bitwise operations.'''
NO_FORMAT = 0
DOS_FORMAT = 1
MAC_FORMAT = 2
UNIX_FORMAT = 4

View File

@ -20,13 +20,14 @@
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
# Bazaar support
class Bzr(VcsInterface):
def getFileTemplate(self, prefs, name):
def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
# merge conflict
left = name + '.OTHER'
right = name + '.THIS'
@ -180,7 +181,7 @@ class Bzr(VcsInterface):
result.append(m[k])
return result
def getRevision(self, prefs, name, rev):
def getRevision(self, prefs: Preferences, name: str, rev: str) -> bytes:
return utils.popenRead(
self.root,
[

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Git support
class Git(VcsInterface):
def getFileTemplate(self, prefs, name):
def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
return [(name, 'HEAD'), (name, None)]
def getCommitTemplate(self, prefs, rev, names):
@ -150,7 +150,7 @@ class Git(VcsInterface):
result.append(m[k])
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, '/')
return utils.popenRead(
self.root,

View File

@ -22,6 +22,7 @@ import os
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
@ -29,7 +30,7 @@ from diffuse.vcs.vcs_interface import VcsInterface
# Mercurial support
class Hg(VcsInterface):
def __init__(self, root: str):
VcsInterface.__init__(self, root)
super().__init__(root)
self.working_rev: Optional[str] = None
def _getPreviousRevision(self, prefs, rev):
@ -51,7 +52,7 @@ class Hg(VcsInterface):
return self.working_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)]
def _getCommitTemplate(self, prefs, names, cmd, rev):
@ -97,7 +98,7 @@ class Hg(VcsInterface):
def getFolderTemplate(self, prefs, names):
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(
self.root,
[

View File

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

View File

@ -22,12 +22,13 @@ import os
from gettext import gettext as _
from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.vcs_interface import VcsInterface
# RCS support
class Rcs(VcsInterface):
def getFileTemplate(self, prefs, name):
def getFileTemplate(self, prefs: Preferences, name: str) -> VcsInterface.PathRevisionList:
args = [
prefs.getString('rcs_bin_rlog'),
'-L',
@ -153,7 +154,7 @@ class Rcs(VcsInterface):
# sort the results
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(
self.root,
[

View File

@ -19,9 +19,10 @@
import os
from typing import Tuple
from typing import Optional, Tuple
from diffuse import utils
from diffuse.preferences import Preferences
from diffuse.vcs.svn import Svn
@ -41,14 +42,14 @@ class Svk(Svn):
return s[0], s[4:]
@staticmethod
def _getPreviousRevision(rev: str) -> str:
def _getPreviousRevision(rev: Optional[str]) -> str:
if rev is None:
return 'HEAD'
if rev.endswith('@'):
return str(int(rev[:-1]) - 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, '/')
return utils.popenRead(
self.root,

View File

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

View File

@ -17,22 +17,35 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 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."""
PathRevisionPair = Tuple[Optional[str], Optional[str]]
PathRevisionList = List[PathRevisionPair]
def __init__(self, root: str):
"""The object will initialized with the repository's root folder."""
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
requested."""
@abstractmethod
def getCommitTemplate(self, prefs, rev, names):
"""Indicates which file revisions to display for a commit."""
@abstractmethod
def getFolderTemplate(self, prefs, names):
"""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"""

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

File diff suppressed because it is too large Load Diff