mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
experimental type answer refactor
- when answer not correct, show both the given and correct string in separate markup. we use red/green for the given string to indicate what was correct, but we use grey rather than red on the correct string to indicate what was missing, as red is misleading - colours can now be customized in css with .typeGood, .typeBad and .typeMissing - answer now shown in monospace so given/correct lines up; can be customized with code#typeans - do away with 'correct answer was' text that people didn't like
This commit is contained in:
parent
f10b336fb9
commit
c3b0ae37aa
1 changed files with 55 additions and 60 deletions
115
aqt/reviewer.py
115
aqt/reviewer.py
|
@ -320,6 +320,9 @@ body { margin:1.5em; }
|
||||||
img { max-width: 95%; max-height: 95%; }
|
img { max-width: 95%; max-height: 95%; }
|
||||||
.marked { position:absolute; right: 7px; top: 7px; display: none; }
|
.marked { position:absolute; right: 7px; top: 7px; display: none; }
|
||||||
#typeans { width: 100%; }
|
#typeans { width: 100%; }
|
||||||
|
.typeGood { background: #0f0; }
|
||||||
|
.typeBad { background: #f00; }
|
||||||
|
.typeMissed { background: #ccc; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _styles(self):
|
def _styles(self):
|
||||||
|
@ -328,8 +331,6 @@ img { max-width: 95%; max-height: 95%; }
|
||||||
# Type in the answer
|
# Type in the answer
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
failedCharColour = "#FF0000"
|
|
||||||
passedCharColour = "#00FF00"
|
|
||||||
typeAnsPat = "\[\[type:(.+?)\]\]"
|
typeAnsPat = "\[\[type:(.+?)\]\]"
|
||||||
|
|
||||||
def typeAnsFilter(self, buf):
|
def typeAnsFilter(self, buf):
|
||||||
|
@ -390,11 +391,7 @@ Please run Tools>Empty Cards""")
|
||||||
cor = parser.unescape(cor)
|
cor = parser.unescape(cor)
|
||||||
given = self.typedAnswer
|
given = self.typedAnswer
|
||||||
# compare with typed answer
|
# compare with typed answer
|
||||||
res = self.correct(cor, given)
|
res = self.correct(given, cor, showBad=False)
|
||||||
if cor != given:
|
|
||||||
# Wrap the extra text in an id-ed span.
|
|
||||||
res += u"<span id=rightanswer><br> {0} <br> {1} </span>".format(
|
|
||||||
_(u"Correct answer was:"), cor)
|
|
||||||
# and update the type answer area
|
# and update the type answer area
|
||||||
def repl(match):
|
def repl(match):
|
||||||
# can't pass a string in directly, and can't use re.escape as it
|
# can't pass a string in directly, and can't use re.escape as it
|
||||||
|
@ -419,62 +416,60 @@ Please run Tools>Empty Cards""")
|
||||||
txt = matches[0]
|
txt = matches[0]
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
# following type answer functions thanks to Bernhard
|
def tokenizeComparison(self, given, correct):
|
||||||
def calculateOkBadStyle(self):
|
s = difflib.SequenceMatcher(None, given, correct)
|
||||||
"Precalculates styles for correct and incorrect part of answer"
|
givenElems = []
|
||||||
st = "background: %s; color: #000;"
|
correctElems = []
|
||||||
self.styleOk = st % self.passedCharColour
|
givenPoint = 0
|
||||||
self.styleBad = st % self.failedCharColour
|
correctPoint = 0
|
||||||
|
offby = 0
|
||||||
|
def logBad(old, new, str, array):
|
||||||
|
if old != new:
|
||||||
|
array.append((False, str[old:new]))
|
||||||
|
def logGood(start, cnt, str, array):
|
||||||
|
if cnt:
|
||||||
|
array.append((True, str[start:start+cnt]))
|
||||||
|
for x, y, cnt in s.get_matching_blocks():
|
||||||
|
# if anything was missed in correct, pad given
|
||||||
|
if cnt and y-offby > x:
|
||||||
|
givenElems.append((False, "-"*(y-x-offby)))
|
||||||
|
offby = y-x
|
||||||
|
# log any proceeding bad elems
|
||||||
|
logBad(givenPoint, x, given, givenElems)
|
||||||
|
logBad(correctPoint, y, correct, correctElems)
|
||||||
|
givenPoint = x+cnt
|
||||||
|
correctPoint = y+cnt
|
||||||
|
# log the match
|
||||||
|
logGood(x, cnt, given, givenElems)
|
||||||
|
logGood(y, cnt, correct, correctElems)
|
||||||
|
return givenElems, correctElems
|
||||||
|
|
||||||
def ok(self, a):
|
def correct(self, given, correct, showBad=True):
|
||||||
"returns given sring in style correct (green)"
|
|
||||||
if len(a) == 0:
|
|
||||||
return ""
|
|
||||||
return "<span style='%s'>%s</span>" % (self.styleOk, cgi.escape(a))
|
|
||||||
|
|
||||||
def bad(self, a):
|
|
||||||
"returns given sring in style incorrect (red)"
|
|
||||||
if len(a) == 0:
|
|
||||||
return ""
|
|
||||||
return "<span style='%s'>%s</span>" % (self.styleBad, cgi.escape(a))
|
|
||||||
|
|
||||||
def applyStyle(self, testChar, correct, wrong):
|
|
||||||
"Calculates answer fragment depending on testChar's unicode category"
|
|
||||||
ZERO_SIZE = 'Mn'
|
|
||||||
def head(a):
|
|
||||||
return a[:len(a) - 1]
|
|
||||||
def tail(a):
|
|
||||||
return a[len(a) - 1:]
|
|
||||||
if ucd.category(testChar) == ZERO_SIZE:
|
|
||||||
return self.ok(head(correct)) + self.bad(tail(correct) + wrong)
|
|
||||||
return self.ok(correct) + self.bad(wrong)
|
|
||||||
|
|
||||||
def correct(self, a, b):
|
|
||||||
"Diff-corrects the typed-in answer."
|
"Diff-corrects the typed-in answer."
|
||||||
if b == "":
|
givenElems, correctElems = self.tokenizeComparison(given, correct)
|
||||||
return "";
|
def good(s):
|
||||||
self.calculateOkBadStyle()
|
return "<span class=typeGood>"+s+"</span>"
|
||||||
ret = ""
|
def bad(s):
|
||||||
lastEqual = ""
|
return "<span class=typeBad>"+s+"</span>"
|
||||||
s = difflib.SequenceMatcher(None, b, a)
|
def missed(s):
|
||||||
for tag, i1, i2, j1, j2 in s.get_opcodes():
|
return "<span class=typeMissed>"+s+"</span>"
|
||||||
if tag == "equal":
|
if given == correct:
|
||||||
lastEqual = b[i1:i2]
|
res = good(given)
|
||||||
elif tag == "replace":
|
else:
|
||||||
ret += self.applyStyle(b[i1], lastEqual,
|
res = ""
|
||||||
b[i1:i2] + ("-" * ((j2 - j1) - (i2 - i1))))
|
for ok, txt in givenElems:
|
||||||
lastEqual = ""
|
if ok:
|
||||||
elif tag == "delete":
|
res += good(txt)
|
||||||
ret += self.applyStyle(b[i1], lastEqual, b[i1:i2])
|
|
||||||
lastEqual = ""
|
|
||||||
elif tag == "insert":
|
|
||||||
if ucd.category(a[j1]) != 'Mn':
|
|
||||||
dashNum = (j2 - j1)
|
|
||||||
else:
|
else:
|
||||||
dashNum = ((j2 - j1) - 1)
|
res += bad(txt)
|
||||||
ret += self.applyStyle(a[j1], lastEqual, "-" * dashNum)
|
res += "<br>↓<br>"
|
||||||
lastEqual = ""
|
for ok, txt in correctElems:
|
||||||
return ret + self.ok(lastEqual)
|
if ok:
|
||||||
|
res += good(txt)
|
||||||
|
else:
|
||||||
|
res += missed(txt)
|
||||||
|
res = "<div><code id=typeans>" + res + "</code></div>"
|
||||||
|
return res
|
||||||
|
|
||||||
# Bottom bar
|
# Bottom bar
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
Loading…
Reference in a new issue