import logging
from retype.extras.utils import spacerstrip
logger = logging.getLogger(__name__)
[docs]def compareStrings(str1, str2):
"""Compare strings `str1' and `str2', returning index at which they stop
matching"""
length_of_shorter_str = min([len(str1), len(str2)])
for i, a, b in zip(range(length_of_shorter_str), str1, str2):
if a != b:
# Found character that doesn’t match; return index at which the
# strings stopped matching
return i
# Full match up to end of shorter string
return length_of_shorter_str
[docs]class HighlightingService(object):
[docs] def __init__(self, console, book_view):
self._console = console
self.book_view = book_view
self._console.textChanged.connect(self._handleHighlighting)
self.wrong = False
self.wrong_start = None
self.wrong_end = None
self.wrong_text = ""
def _handleHighlighting(self, text):
v = self.book_view
if not v.isVisible() or v.progress == 100:
return
# In case there is no book loaded / variables not been initialised
if any([getattr(v, 'persistent_pos', False) is False,
v.persistent_pos is None]):
return
# Remove highlighting if things were deleted
if len(text) + v.persistent_pos < v.cursor_pos:
v.highlight_cursor.mergeCharFormat(v.unhighlight_format)
# Cursor position in the line
end_correctness_index = compareStrings(text, v.current_line)
v.cursor_pos = v.persistent_pos + end_correctness_index
self._handleMistakes(v, text, end_correctness_index)
self.updateHighlighting()
# Next line / chapter, skipping trailing spaces if present
if text == v.current_line or text == spacerstrip(v.current_line):
self.advanceLine()
def _handleMistakes(self, v, text, end_correctness_index):
# The way this works is if there’s any previous wrong_text it gets
# entirely removed, then readded as necessary.
if self.wrong_start is not None:
self._removeWrongText(v, self.wrong_start, self.wrong_end)
else:
self.wrong_start = v.persistent_pos + end_correctness_index
self.wrong_text = text[end_correctness_index:]
# If not (or no longer) wrong, reset wrong-tracking variables and
# silently follow the cursor pos.
if not self.wrong_text:
self.wrong = False
self.wrong_start = None
self.wrong_end = None
v.mistake_cursor.setPosition(v.cursor_pos)
return
self.wrong = True
v.mistake_cursor.setPosition(self.wrong_start)
self._insertWrongText(v, v.mistake_cursor.position(), self.wrong_text)
self.wrong_end = self.wrong_start + len(self.wrong_text)
def _insertWrongText(self, v, pre_pos, text):
v.mistake_cursor.setPosition(pre_pos, v.mistake_cursor.MoveAnchor)
v.mistake_cursor.insertText(text, v.mistake_format)
def _removeWrongText(self, v, start, end):
v.mistake_cursor.setPosition(start, v.mistake_cursor.MoveAnchor)
v.mistake_cursor.setPosition(end, v.mistake_cursor.KeepAnchor)
v.mistake_cursor.removeSelectedText()
[docs] def advanceLine(self):
v = self.book_view
# Get out of here if there is no line to advance to
if v.onLastChapter():
if len(v.tobetyped_list)-1 == v.line_pos:
v.markComplete()
return logger.debug("On last line, marking complete")
elif len(v.tobetyped_list)-1 < v.line_pos:
return logger.error("line_pos ({}) larger than the list ({})\
for some reason ".format(len(v.tobetyped_list), v.line_pos))
v.line_pos += 1
# Compensate
len_typed = v.cursor_pos - v.persistent_pos
difference = len(v.current_line) - len_typed
v.cursor_pos += difference + 1
v.persistent_pos = v.cursor_pos
# Reached last line of this chapter, move to next one
if len(v.tobetyped_list) == v.line_pos:
v.nextChapter(True)
# Set the line that needs to be typed next
try:
v._setLine(v.line_pos)
except Exception as e:
logger.error('can’t advance line {}/{}\n\
error: {}'.format(v.line_pos, len(v.tobetyped_list), e))
return
self.updateHighlighting()
self._console.clear()
v.display.centreAroundCursor()
v.updateProgress()
[docs] def updateHighlighting(self):
v = self.book_view
v.updateCursorPosition()
v.updateHighlightCursor()
if self.wrong:
v.mistake_cursor.mergeCharFormat(v.mistake_format)
return
v.updateModeline()