Skip to content

Commit 89b418d

Browse files
authored
Merge f968c24 into fb2d1cd
2 parents fb2d1cd + f968c24 commit 89b418d

2 files changed

Lines changed: 129 additions & 111 deletions

File tree

source/NVDAObjects/UIA/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,10 @@ def findOverlayClasses(self,clsList):
937937
):
938938
from . import winConsoleUIA
939939
winConsoleUIA.findExtraOverlayClasses(self, clsList)
940+
elif UIAClassName == "TermControl":
941+
from . import winConsoleUIA
942+
clsList.append(winConsoleUIA.WinTerminalUIA)
943+
940944
# Add editableText support if UIA supports a text pattern
941945
if self.TextInfo==UIATextInfo:
942946
if UIAHandler.autoSelectDetectionAvailable:

source/NVDAObjects/UIA/winConsoleUIA.py

Lines changed: 125 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# A part of NonVisual Desktop Access (NVDA)
33
# This file is covered by the GNU General Public License.
44
# See the file COPYING for more details.
5-
# Copyright (C) 2019 Bill Dengler
5+
# Copyright (C) 2019-2020 Bill Dengler
66

77
import ctypes
88
import NVDAHelper
@@ -18,7 +18,6 @@
1818

1919

2020
class consoleUIATextInfo(UIATextInfo):
21-
2221
def __init__(self, obj, position, _rangeObj=None):
2322
# We want to limit textInfos to just the visible part of the console.
2423
# Therefore we specifically handle POSITION_FIRST, POSITION_LAST and POSITION_ALL.
@@ -48,6 +47,56 @@ def __init__(self, obj, position, _rangeObj=None):
4847
_rangeObj = first._rangeObj
4948
super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)
5049

50+
def move(self, unit, direction, endPoint=None):
51+
oldInfo = None
52+
if self.basePosition != textInfos.POSITION_CARET:
53+
# Insure we haven't gone beyond the visible text.
54+
# UIA adds thousands of blank lines to the end of the console.
55+
boundingInfo = self.obj.makeTextInfo(textInfos.POSITION_ALL)
56+
oldInfo = self.copy()
57+
res = self._move(unit, direction, endPoint)
58+
# Console textRanges have access to the entire console buffer.
59+
# However, we want to limit ourselves to onscreen text.
60+
# Therefore, if the textInfo was originally visible,
61+
# but we are now above or below the visible range,
62+
# Restore the original textRange and pretend the move didn't work.
63+
if oldInfo:
64+
try:
65+
if (
66+
(
67+
self.compareEndPoints(boundingInfo, "startToStart") < 0
68+
or self.compareEndPoints(boundingInfo, "startToEnd") >= 0
69+
)
70+
and not (
71+
oldInfo.compareEndPoints(boundingInfo, "startToStart") < 0
72+
or oldInfo.compareEndPoints(boundingInfo, "startToEnd") >= 0
73+
)
74+
):
75+
self._rangeObj = oldInfo._rangeObj
76+
return 0
77+
except (COMError, RuntimeError):
78+
pass
79+
return res
80+
81+
def _move(self, unit, direction, endPoint=None):
82+
"Perform a move without respect to bounding."
83+
return super(consoleUIATextInfo, self).move(unit, direction, endPoint)
84+
85+
def __ne__(self, other):
86+
"""Support more accurate caret move detection."""
87+
return not self == other
88+
89+
def _get_text(self):
90+
# #10036: return a space if the text range is empty.
91+
# Consoles don't actually store spaces, the character is merely left blank.
92+
res = super(consoleUIATextInfo, self)._get_text()
93+
if not res:
94+
return ' '
95+
else:
96+
return res
97+
98+
99+
class consoleUIATextInfoEndInclusive(consoleUIATextInfo):
51100
def collapse(self, end=False):
52101
"""Works around a UIA bug on Windows 10 1803 and later."""
53102
# When collapsing, consoles seem to incorrectly push the start of the
@@ -62,13 +111,62 @@ def collapse(self, end=False):
62111
UIAHandler.TextPatternRangeEndpoint_Start
63112
)
64113

65-
def move(self, unit, direction, endPoint=None):
66-
oldInfo = None
67-
if self.basePosition != textInfos.POSITION_CARET:
68-
# Insure we haven't gone beyond the visible text.
69-
# UIA adds thousands of blank lines to the end of the console.
70-
boundingInfo = self.obj.makeTextInfo(textInfos.POSITION_ALL)
71-
oldInfo = self.copy()
114+
def compareEndPoints(self, other, which):
115+
"""Works around a UIA bug on Windows 10 1803 and later."""
116+
# Even when a console textRange's start and end have been moved to the
117+
# same position, the console incorrectly reports the end as being
118+
# past the start.
119+
# Compare to the start (not the end) when collapsed.
120+
selfEndPoint, otherEndPoint = which.split("To")
121+
if selfEndPoint == "end" and self._isCollapsed():
122+
selfEndPoint = "start"
123+
if otherEndPoint == "End" and other._isCollapsed():
124+
otherEndPoint = "Start"
125+
which = f"{selfEndPoint}To{otherEndPoint}"
126+
return super().compareEndPoints(other, which=which)
127+
128+
def setEndPoint(self, other, which):
129+
"""Override of L{textInfos.TextInfo.setEndPoint}.
130+
Works around a UIA bug on Windows 10 1803 and later that means we can
131+
not trust the "end" endpoint of a collapsed (empty) text range
132+
for comparisons.
133+
"""
134+
selfEndPoint, otherEndPoint = which.split("To")
135+
# In this case, there is no need to check if self is collapsed
136+
# since the point of this method is to change its text range, modifying the "end" endpoint of a collapsed
137+
# text range is fine.
138+
if otherEndPoint == "End" and other._isCollapsed():
139+
otherEndPoint = "Start"
140+
which = f"{selfEndPoint}To{otherEndPoint}"
141+
return super().setEndPoint(other, which=which)
142+
143+
def expand(self, unit):
144+
if unit == textInfos.UNIT_WORD:
145+
# UIA doesn't implement word movement, so we need to do it manually.
146+
lineInfo = self.copy()
147+
lineInfo.expand(textInfos.UNIT_LINE)
148+
offset = self._getCurrentOffsetInThisLine(lineInfo)
149+
start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
150+
wordEndPoints = (
151+
(offset - start) * -1,
152+
end - offset - 1
153+
)
154+
if wordEndPoints[0]:
155+
self._rangeObj.MoveEndpointByUnit(
156+
UIAHandler.TextPatternRangeEndpoint_Start,
157+
UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
158+
wordEndPoints[0]
159+
)
160+
if wordEndPoints[1]:
161+
self._rangeObj.MoveEndpointByUnit(
162+
UIAHandler.TextPatternRangeEndpoint_End,
163+
UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
164+
wordEndPoints[1]
165+
)
166+
else:
167+
return super(consoleUIATextInfo, self).expand(unit)
168+
169+
def _move(self, unit, direction, endPoint=None):
72170
if unit == textInfos.UNIT_WORD and direction != 0:
73171
# UIA doesn't implement word movement, so we need to do it manually.
74172
# Relative to the current line, calculate our offset
@@ -128,98 +226,8 @@ def move(self, unit, direction, endPoint=None):
128226
# after moving.
129227
# Therefore manually collapse.
130228
self.collapse()
131-
# Console textRanges have access to the entire console buffer.
132-
# However, we want to limit ourselves to onscreen text.
133-
# Therefore, if the textInfo was originally visible,
134-
# but we are now above or below the visible range,
135-
# Restore the original textRange and pretend the move didn't work.
136-
if oldInfo:
137-
try:
138-
if (
139-
(
140-
self.compareEndPoints(boundingInfo, "startToStart") < 0
141-
or self.compareEndPoints(boundingInfo, "startToEnd") >= 0
142-
)
143-
and not (
144-
oldInfo.compareEndPoints(boundingInfo, "startToStart") < 0
145-
or oldInfo.compareEndPoints(boundingInfo, "startToEnd") >= 0
146-
)
147-
):
148-
self._rangeObj = oldInfo._rangeObj
149-
return 0
150-
except (COMError, RuntimeError):
151-
pass
152229
return res
153230

154-
def expand(self, unit):
155-
if unit == textInfos.UNIT_WORD:
156-
# UIA doesn't implement word movement, so we need to do it manually.
157-
lineInfo = self.copy()
158-
lineInfo.expand(textInfos.UNIT_LINE)
159-
offset = self._getCurrentOffsetInThisLine(lineInfo)
160-
start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
161-
wordEndPoints = (
162-
(offset - start) * -1,
163-
end - offset - 1
164-
)
165-
if wordEndPoints[0]:
166-
self._rangeObj.MoveEndpointByUnit(
167-
UIAHandler.TextPatternRangeEndpoint_Start,
168-
UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
169-
wordEndPoints[0]
170-
)
171-
if wordEndPoints[1]:
172-
self._rangeObj.MoveEndpointByUnit(
173-
UIAHandler.TextPatternRangeEndpoint_End,
174-
UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
175-
wordEndPoints[1]
176-
)
177-
else:
178-
return super(consoleUIATextInfo, self).expand(unit)
179-
180-
def compareEndPoints(self, other, which):
181-
"""Works around a UIA bug on Windows 10 1803 and later."""
182-
# Even when a console textRange's start and end have been moved to the
183-
# same position, the console incorrectly reports the end as being
184-
# past the start.
185-
# Compare to the start (not the end) when collapsed.
186-
selfEndPoint, otherEndPoint = which.split("To")
187-
if selfEndPoint == "end" and self._isCollapsed():
188-
selfEndPoint = "start"
189-
if otherEndPoint == "End" and other._isCollapsed():
190-
otherEndPoint = "Start"
191-
which = f"{selfEndPoint}To{otherEndPoint}"
192-
return super().compareEndPoints(other, which=which)
193-
194-
def setEndPoint(self, other, which):
195-
"""Override of L{textInfos.TextInfo.setEndPoint}.
196-
Works around a UIA bug on Windows 10 1803 and later that means we can
197-
not trust the "end" endpoint of a collapsed (empty) text range
198-
for comparisons.
199-
"""
200-
selfEndPoint, otherEndPoint = which.split("To")
201-
# In this case, there is no need to check if self is collapsed
202-
# since the point of this method is to change its text range, modifying the "end" endpoint of a collapsed
203-
# text range is fine.
204-
if otherEndPoint == "End" and other._isCollapsed():
205-
otherEndPoint = "Start"
206-
which = f"{selfEndPoint}To{otherEndPoint}"
207-
return super().setEndPoint(other, which=which)
208-
209-
def _isCollapsed(self):
210-
"""Works around a UIA bug on Windows 10 1803 and later that means we
211-
cannot trust the "end" endpoint of a collapsed (empty) text range
212-
for comparisons.
213-
Instead we check to see if we can get the first character from the
214-
text range. A collapsed range will not have any characters
215-
and will return an empty string."""
216-
return not bool(self._rangeObj.getText(1))
217-
218-
def _get_isCollapsed(self):
219-
# To decide if the textRange is collapsed,
220-
# Check if it has no text.
221-
return self._isCollapsed()
222-
223231
def _getCurrentOffsetInThisLine(self, lineInfo):
224232
"""
225233
Given a caret textInfo expanded to line, returns the index into the
@@ -258,18 +266,19 @@ def _getWordOffsetsInThisLine(self, offset, lineInfo):
258266
min(end.value, max(1, lineTextLen - 2))
259267
)
260268

261-
def __ne__(self, other):
262-
"""Support more accurate caret move detection."""
263-
return not self == other
269+
def _isCollapsed(self):
270+
"""Works around a UIA bug on Windows 10 1803 and later that means we
271+
cannot trust the "end" endpoint of a collapsed (empty) text range
272+
for comparisons.
273+
Instead we check to see if we can get the first character from the
274+
text range. A collapsed range will not have any characters
275+
and will return an empty string."""
276+
return not bool(self._rangeObj.getText(1))
264277

265-
def _get_text(self):
266-
# #10036: return a space if the text range is empty.
267-
# Consoles don't actually store spaces, the character is merely left blank.
268-
res = super(consoleUIATextInfo, self)._get_text()
269-
if not res:
270-
return ' '
271-
else:
272-
return res
278+
def _get_isCollapsed(self):
279+
# To decide if the textRange is collapsed,
280+
# Check if it has no text.
281+
return self._isCollapsed()
273282

274283

275284
class consoleUIAWindow(Window):
@@ -302,7 +311,7 @@ def _get_TextInfo(self):
302311
on NVDAObjects.UIA.UIA
303312
consoleUIATextInfo fixes expand/collapse, implements word movement, and
304313
bounds review to the visible text."""
305-
return consoleUIATextInfo
314+
return consoleUIATextInfoEndInclusive
306315

307316
def _getTextLines(self):
308317
# This override of _getTextLines takes advantage of the fact that
@@ -317,3 +326,8 @@ def findExtraOverlayClasses(obj, clsList):
317326
clsList.append(WinConsoleUIA)
318327
elif obj.UIAElement.cachedAutomationId == "Console Window":
319328
clsList.append(consoleUIAWindow)
329+
330+
331+
class WinTerminalUIA(KeyboardHandlerBasedTypedCharSupport):
332+
def _get_TextInfo(self):
333+
return consoleUIATextInfo

0 commit comments

Comments
 (0)