Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 63 additions & 39 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,35 @@


class consoleUIATextInfo(UIATextInfo):
#: At least on Windows 10 1903, expanding then collapsing the text info
#: caused review to get stuck, so disable it.
#: There may be no need to disable this anymore, but doing so doesn't seem
#: to do much good either.
_expandCollapseBeforeReview = False

def __init__(self, obj, position, _rangeObj=None):
# We want to limit textInfos to just the visible part of the console.
# Therefore we specifically handle POSITION_FIRST, POSITION_LAST and POSITION_ALL.
# We could use IUIAutomationTextRange::getVisibleRanges, but it seems very broken in consoles
# once more than a few screens worth of content has been written to the console.
# Therefore we resort to using IUIAutomationTextPattern::rangeFromPoint
# for the top left, and bottom right of the console window.
if position is textInfos.POSITION_FIRST:
_rangeObj = self.__class__(obj, obj.location.topLeft)._rangeObj
elif position is textInfos.POSITION_LAST:
# Asking for the range at the bottom right of the window
# Seems to sometimes ignore the x coordinate.
# Therefore use the bottom left, then move to the last character on that line.
tempInfo = self.__class__(obj, obj.location.bottomLeft)
tempInfo.expand(textInfos.UNIT_LINE)
# We must pull back the end by one character otherwise when we collapse to end,
# a console bug results in a textRange covering the entire console buffer!
# Strangely the *very* last character is a special blank point
# so we never seem to miss a real character.
UIATextInfo.move(tempInfo, textInfos.UNIT_CHARACTER, -1, endPoint="end")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if we don't do this? We already expanded to line, so the endpoint should be the end of the line already?

tempInfo.setEndPoint(tempInfo, "startToEnd")
_rangeObj = tempInfo._rangeObj
elif position is textInfos.POSITION_ALL:
first = self.__class__(obj, textInfos.POSITION_FIRST)
last = self.__class__(obj, textInfos.POSITION_LAST)
first.setEndPoint(last, "endToEnd")
_rangeObj = first._rangeObj
super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)
# Re-implement POSITION_FIRST and POSITION_LAST in terms of
# visible ranges to fix review top/bottom scripts.
if position == textInfos.POSITION_FIRST:
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
firstVisiRange = visiRanges.GetElement(0)
self._rangeObj = firstVisiRange
self.collapse()
elif position == textInfos.POSITION_LAST:
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
lastVisiRange = visiRanges.GetElement(visiRanges.length - 1)
self._rangeObj = lastVisiRange
self.collapse(True)

def collapse(self, end=False):
"""Works around a UIA bug on Windows 10 1803 and later."""
Expand All @@ -54,14 +63,12 @@ def collapse(self, end=False):
)

def move(self, unit, direction, endPoint=None):
oldRange = None
oldInfo = None
if self.basePosition != textInfos.POSITION_CARET:
# Insure we haven't gone beyond the visible text.
# UIA adds thousands of blank lines to the end of the console.
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
visiLength = visiRanges.length
if visiLength > 0:
oldRange = self._rangeObj.clone()
boundingInfo = self.obj.makeTextInfo(textInfos.POSITION_ALL)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency with other code

Suggested change
boundingInfo = self.obj.makeTextInfo(textInfos.POSITION_ALL)
boundingInfo = self.__class__(obj, textInfos.POSITION_ALL)

oldInfo = self.copy()
if unit == textInfos.UNIT_WORD and direction != 0:
# UIA doesn't implement word movement, so we need to do it manually.
# Relative to the current line, calculate our offset
Expand Down Expand Up @@ -116,16 +123,32 @@ def move(self, unit, direction, endPoint=None):
else: # moving by a unit other than word
res = super(consoleUIATextInfo, self).move(unit, direction,
endPoint)
try:
if (
oldRange
and isTextRangeOffscreen(self._rangeObj, visiRanges)
and not isTextRangeOffscreen(oldRange, visiRanges)
):
self._rangeObj = oldRange
return 0
except (COMError, RuntimeError):
pass
if not endPoint:
# #10191: IUIAutomationTextRange::move in consoles does not correctly produce a collapsed range
# after moving.
# Therefore manually collapse.
self.collapse()
# Console textRanges have access to the entire console buffer.
# However, we want to limit ourselves to onscreen text.
# Therefore, if the textInfo was originally visible,
# but we are now above or below the visible range,
# Restore the original textRange and pretend the move didn't work.
if oldInfo:
try:
if (
(
self.compareEndPoints(boundingInfo, "startToStart") < 0
or self.compareEndPoints(boundingInfo, "startToEnd") >= 0
)
and not (
oldInfo.compareEndPoints(boundingInfo, "startToStart") < 0
or oldInfo.compareEndPoints(boundingInfo, "startToEnd") >= 0
)
):
self._rangeObj = oldInfo._rangeObj
return 0
except (COMError, RuntimeError):
pass
return res

def expand(self, unit):
Expand Down Expand Up @@ -207,12 +230,12 @@ def _getCurrentOffsetInThisLine(self, lineInfo):
# position a textInfo from the start of the line up to the current position.
charInfo = lineInfo.copy()
charInfo.setEndPoint(self, "endToStart")
text = charInfo.text
text = charInfo._rangeObj.getText(-1)
offset = textUtils.WideStringOffsetConverter(text).wideStringLength
return offset

def _getWordOffsetsInThisLine(self, offset, lineInfo):
lineText = lineInfo.text or u" "
lineText = lineInfo._rangeObj.getText(-1)
# Convert NULL and non-breaking space to space to make sure
# that words will break on them
lineText = lineText.translate({0: u' ', 0xa0: u' '})
Expand Down Expand Up @@ -282,11 +305,12 @@ def _get_TextInfo(self):
return consoleUIATextInfo

def _getTextLines(self):
Comment thread
LeonarddeR marked this conversation as resolved.
Resolved
# Filter out extraneous empty lines from UIA
ptr = self.UIATextPattern.GetVisibleRanges()
res = [ptr.GetElement(i).GetText(-1) for i in range(ptr.length)]
return res

# This override of _getTextLines takes advantage of the fact that
# the console text contains linefeeds for every line
# Thus a simple string splitlines is much faster than splitting by unit line.
ti = self.makeTextInfo(textInfos.POSITION_ALL)
text = ti.text or ""
return text.splitlines()

def findExtraOverlayClasses(obj, clsList):
if obj.UIAElement.cachedAutomationId == "Text Area":
Expand Down
30 changes: 12 additions & 18 deletions source/globalCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,9 +1026,8 @@ def script_review_top(self,gesture):

def script_review_previousLine(self,gesture):
info=api.getReviewPosition().copy()
if info._expandCollapseBeforeReview:
info.expand(textInfos.UNIT_LINE)
info.collapse()
info.expand(textInfos.UNIT_LINE)
info.collapse()
res=info.move(textInfos.UNIT_LINE,-1)
if res==0:
# Translators: a message reported when review cursor is at the top line of the current navigator object.
Expand Down Expand Up @@ -1058,9 +1057,8 @@ def script_review_currentLine(self,gesture):

def script_review_nextLine(self,gesture):
info=api.getReviewPosition().copy()
if info._expandCollapseBeforeReview:
info.expand(textInfos.UNIT_LINE)
info.collapse()
info.expand(textInfos.UNIT_LINE)
info.collapse()
res=info.move(textInfos.UNIT_LINE,1)
if res==0:
# Translators: a message reported when review cursor is at the bottom line of the current navigator object.
Expand All @@ -1086,9 +1084,8 @@ def script_review_bottom(self,gesture):

def script_review_previousWord(self,gesture):
info=api.getReviewPosition().copy()
if info._expandCollapseBeforeReview:
info.expand(textInfos.UNIT_WORD)
info.collapse()
info.expand(textInfos.UNIT_WORD)
info.collapse()
res=info.move(textInfos.UNIT_WORD,-1)
if res==0:
# Translators: a message reported when review cursor is at the top line of the current navigator object.
Expand Down Expand Up @@ -1117,9 +1114,8 @@ def script_review_currentWord(self,gesture):

def script_review_nextWord(self,gesture):
info=api.getReviewPosition().copy()
if info._expandCollapseBeforeReview:
info.expand(textInfos.UNIT_WORD)
info.collapse()
info.expand(textInfos.UNIT_WORD)
info.collapse()
res=info.move(textInfos.UNIT_WORD,1)
if res==0:
# Translators: a message reported when review cursor is at the bottom line of the current navigator object.
Expand Down Expand Up @@ -1148,9 +1144,8 @@ def script_review_previousCharacter(self,gesture):
lineInfo=api.getReviewPosition().copy()
lineInfo.expand(textInfos.UNIT_LINE)
charInfo=api.getReviewPosition().copy()
if charInfo._expandCollapseBeforeReview:
charInfo.expand(textInfos.UNIT_CHARACTER)
charInfo.collapse()
charInfo.expand(textInfos.UNIT_CHARACTER)
charInfo.collapse()
res=charInfo.move(textInfos.UNIT_CHARACTER,-1)
if res==0 or charInfo.compareEndPoints(lineInfo,"startToStart")<0:
# Translators: a message reported when review cursor is at the leftmost character of the current navigator object's text.
Expand Down Expand Up @@ -1195,9 +1190,8 @@ def script_review_nextCharacter(self,gesture):
lineInfo=api.getReviewPosition().copy()
lineInfo.expand(textInfos.UNIT_LINE)
charInfo=api.getReviewPosition().copy()
if charInfo._expandCollapseBeforeReview:
charInfo.expand(textInfos.UNIT_CHARACTER)
charInfo.collapse()
charInfo.expand(textInfos.UNIT_CHARACTER)
charInfo.collapse()
res=charInfo.move(textInfos.UNIT_CHARACTER,1)
if res==0 or charInfo.compareEndPoints(lineInfo,"endToEnd")>=0:
# Translators: a message reported when review cursor is at the rightmost character of the current navigator object's text.
Expand Down
4 changes: 0 additions & 4 deletions source/textInfos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,6 @@ class TextInfo(baseObject.AutoPropertyObject):
@type bookmark: L{Bookmark}
"""

#: whether this textInfo should be expanded then collapsed around its enclosing unit before review.
#: This can be problematic for some implementations.
_expandCollapseBeforeReview = True

def __init__(self,obj,position):
"""Constructor.
Subclasses must extend this, calling the superclass method first.
Expand Down