diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index b0b0896c82e..e7719489f5a 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -9,6 +9,8 @@ import textInfos import UIAHandler +from comtypes import COMError +from UIAUtils import isTextRangeOffscreen from winVersion import isWin10 from . import UIATextInfo from ..behaviors import KeyboardHandlerBasedTypedCharSupport @@ -22,6 +24,21 @@ class consoleUIATextInfo(UIATextInfo): #: to do much good either. _expandCollapseBeforeReview = False + def __init__(self,obj,position,_rangeObj=None): + 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 1903 and later.""" if not isWin10(1903): @@ -46,8 +63,6 @@ def move(self, unit, direction, endPoint=None): visiRanges = self.obj.UIATextPattern.GetVisibleRanges() visiLength = visiRanges.length if visiLength > 0: - firstVisiRange = visiRanges.GetElement(0) - lastVisiRange = visiRanges.GetElement(visiLength - 1) oldRange = self._rangeObj.clone() if unit == textInfos.UNIT_WORD and direction != 0: # UIA doesn't implement word movement, so we need to do it manually. @@ -90,7 +105,6 @@ def move(self, unit, direction, endPoint=None): lineInfo.expand(textInfos.UNIT_LINE) offset = self._getCurrentOffsetInThisLine(lineInfo) # Finally using the new offset, - # Calculate the current word offsets and move to the start of # this word if we are not already there. start, end = self._getWordOffsetsInThisLine(offset, lineInfo) @@ -104,15 +118,16 @@ def move(self, unit, direction, endPoint=None): else: # moving by a unit other than word res = super(consoleUIATextInfo, self).move(unit, direction, endPoint) - if oldRange and ( - self._rangeObj.CompareEndPoints( - UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange, - UIAHandler.TextPatternRangeEndpoint_Start) < 0 - or self._rangeObj.CompareEndPoints( - UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange, - UIAHandler.TextPatternRangeEndpoint_End) >= 0): - self._rangeObj = oldRange - return 0 + try: + if ( + oldRange + and isTextRangeOffscreen(self._rangeObj, visiRanges) + and not isTextRangeOffscreen(oldRange, visiRanges) + ): + self._rangeObj = oldRange + return 0 + except (COMError, RuntimeError): + pass return res def expand(self, unit): @@ -236,9 +251,12 @@ def _get_caretMovementDetectionUsesEvents(self): def _getTextLines(self): # 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 + return ( + self.makeTextInfo(textInfos.POSITION_ALL) + ._rangeObj.getText(-1) + .rstrip() + .split("\r\n") + ) def findExtraOverlayClasses(obj, clsList): diff --git a/source/UIAUtils.py b/source/UIAUtils.py index b2e6e1c2f5e..6c4e8519e18 100644 --- a/source/UIAUtils.py +++ b/source/UIAUtils.py @@ -172,6 +172,23 @@ def getChildrenWithCacheFromUIATextRange(textRange,cacheRequest): c=CacheableUIAElementArray(c) return c +def isTextRangeOffscreen(textRange, visiRanges): + """Given a UIA text range and a visible textRanges array (returned from obj.UIATextPattern.GetVisibleRanges), determines if the given textRange is not within the visible textRanges.""" + visiLength = visiRanges.length + if visiLength > 0: + firstVisiRange = visiRanges.GetElement(0) + lastVisiRange = visiRanges.GetElement(visiLength - 1) + return textRange.CompareEndPoints( + UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange, + UIAHandler.TextPatternRangeEndpoint_Start + ) < 0 or textRange.CompareEndPoints( + UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange, + UIAHandler.TextPatternRangeEndpoint_End) >= 0 + else: + # Visible textRanges not available. + raise RuntimeError("Visible textRanges array is empty or invalid.") + + class UIATextRangeAttributeValueFetcher(object): def __init__(self,textRange): diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 846c676ae88..afed9c7cc78 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -942,6 +942,21 @@ When in the table view of added books: | Context menu | applications | Opens the context menu for the selected book. | %kc:endInclude +++ Windows Console ++[WinConsole] +NVDA provides support for the Windows command console used by Command Prompt, PowerShell, and the Windows Subsystem for Linux. +The console window is of fixed size, typically much smaller than the buffer that holds the output. +As new text is written, the content scroll upwards and previous text is no longer visible. +Text that is not visibly displayed in the window is not accessible with NVDA's text review commands. +Therefore, it is necessary to scroll the console window to read earlier text. +%kc:beginInclude +The following built-in Windows Console keyboard shortcuts may be useful when [reviewing text #ReviewingText] with NVDA: +|| Name | Key | Description | +| Scroll up | control+upArrow | Scrolls the console window up, so earlier text can be read. | +| Scroll down | control+downArrow | Scrolls the console window down, so later text can be read. | +| Scroll to start | control+home | Scrolls the console window to the beginning of the buffer. | +| Scroll to end | control+end | Scrolls the console window to the end of the buffer. | +%kc:endInclude + + Configuring NVDA +[ConfiguringNVDA] Most configuration can be performed using dialog boxes accessed through the Preferences sub-menu of the NVDA menu. Many of these settings can be found in the multi-page [NVDA Settings dialog #NVDASettings].