We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
repetition when focusing a console window
<!--
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/wiki/Github-issue-template-explanation-and-examples
-->

### Steps to reproduce:
open a console window, in my case I open bash
2. open any other windows such a browser. 
3. tab back to your console window.
### Actual behavior:
I hear root@Computer name, window, terminal, selected, terminal selected
### Expected behavior:
hearing only the window name, + "console" to know the type of focused app
### System configuration
core -i7 24 gb ram, ssd drive
#### NVDA installed/portable/running from source:
installed
#### NVDA version:
alpha-18290,7b5cd2d0
#### Windows version:
windows Version 1903 (OS Build 18950.1000)
#### Name and version of other software in use when reproducing the issue:
none
#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your PC?
yes
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
console work has just landed, so unrelated.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `source/NVDAObjects/UIA/winConsoleUIA.py`
Content:
```
1 # NVDAObjects/UIA/winConsoleUIA.py
2 # A part of NonVisual Desktop Access (NVDA)
3 # This file is covered by the GNU General Public License.
4 # See the file COPYING for more details.
5 # Copyright (C) 2019 Bill Dengler
6 
7 import ctypes
8 import NVDAHelper
9 import textInfos
10 import textUtils
11 import UIAHandler
12 
13 from comtypes import COMError
14 from UIAUtils import isTextRangeOffscreen
15 from . import UIATextInfo
16 from ..behaviors import KeyboardHandlerBasedTypedCharSupport
17 from ..window import Window
18 
19 
20 class consoleUIATextInfo(UIATextInfo):
21 	#: At least on Windows 10 1903, expanding then collapsing the text info
22 	#: caused review to get stuck, so disable it.
23 	#: There may be no need to disable this anymore, but doing so doesn't seem
24 	#: to do much good either.
25 	_expandCollapseBeforeReview = False
26 
27 	def __init__(self, obj, position, _rangeObj=None):
28 		super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)
29 		# Re-implement POSITION_FIRST and POSITION_LAST in terms of
30 		# visible ranges to fix review top/bottom scripts.
31 		if position == textInfos.POSITION_FIRST:
32 			visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
33 			firstVisiRange = visiRanges.GetElement(0)
34 			self._rangeObj = firstVisiRange
35 			self.collapse()
36 		elif position == textInfos.POSITION_LAST:
37 			visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
38 			lastVisiRange = visiRanges.GetElement(visiRanges.length - 1)
39 			self._rangeObj = lastVisiRange
40 			self.collapse(True)
41 
42 	def collapse(self, end=False):
43 		"""Works around a UIA bug on Windows 10 1803 and later."""
44 		# When collapsing, consoles seem to incorrectly push the start of the
45 		# textRange back one character.
46 		# Correct this by bringing the start back up to where the end is.
47 		oldInfo = self.copy()
48 		super(consoleUIATextInfo, self).collapse(end=end)
49 		if not end:
50 			self._rangeObj.MoveEndpointByRange(
51 				UIAHandler.TextPatternRangeEndpoint_Start,
52 				oldInfo._rangeObj,
53 				UIAHandler.TextPatternRangeEndpoint_Start
54 			)
55 
56 	def move(self, unit, direction, endPoint=None):
57 		oldRange = None
58 		if self.basePosition != textInfos.POSITION_CARET:
59 			# Insure we haven't gone beyond the visible text.
60 			# UIA adds thousands of blank lines to the end of the console.
61 			visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
62 			visiLength = visiRanges.length
63 			if visiLength > 0:
64 				oldRange = self._rangeObj.clone()
65 		if unit == textInfos.UNIT_WORD and direction != 0:
66 			# UIA doesn't implement word movement, so we need to do it manually.
67 			# Relative to the current line, calculate our offset
68 			# and the current word's offsets.
69 			lineInfo = self.copy()
70 			lineInfo.expand(textInfos.UNIT_LINE)
71 			offset = self._getCurrentOffsetInThisLine(lineInfo)
72 			start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
73 			if direction > 0:
74 				# Moving in a forward direction, we can just jump to the
75 				# end offset of the current word and we're done.
76 				res = self.move(
77 					textInfos.UNIT_CHARACTER,
78 					end - offset,
79 					endPoint=endPoint
80 				)
81 			else:
82 				# Moving backwards
83 				wordStartDistance = (offset - start) * -1
84 				if wordStartDistance < 0:
85 					# We are after the beginning of a word.
86 					# So first move back to the start of the word.
87 					self.move(
88 						textInfos.UNIT_CHARACTER,
89 						wordStartDistance,
90 						endPoint=endPoint
91 					)
92 					offset += wordStartDistance
93 				# Try to move one character back before the start of the word.
94 				res = self.move(textInfos.UNIT_CHARACTER, -1, endPoint=endPoint)
95 				if res == 0:
96 					return 0
97 				offset -= 1
98 				# We are now positioned  within the previous word.
99 				if offset < 0:
100 					# We've moved on to the previous line.
101 					# Recalculate the current offset based on the new line we are now on.
102 					lineInfo = self.copy()
103 					lineInfo.expand(textInfos.UNIT_LINE)
104 					offset = self._getCurrentOffsetInThisLine(lineInfo)
105 				# Finally using the new offset,
106 				# Calculate the current word offsets and move to the start of
107 				# this word if we are not already there.
108 				start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
109 				wordStartDistance = (offset - start) * -1
110 				if wordStartDistance < 0:
111 					self.move(
112 						textInfos.UNIT_CHARACTER,
113 						wordStartDistance,
114 						endPoint=endPoint
115 					)
116 		else:  # moving by a unit other than word
117 			res = super(consoleUIATextInfo, self).move(unit, direction,
118 														endPoint)
119 		try:
120 			if (
121 				oldRange
122 				and isTextRangeOffscreen(self._rangeObj, visiRanges)
123 				and not isTextRangeOffscreen(oldRange, visiRanges)
124 			):
125 				self._rangeObj = oldRange
126 				return 0
127 		except (COMError, RuntimeError):
128 			pass
129 		return res
130 
131 	def expand(self, unit):
132 		if unit == textInfos.UNIT_WORD:
133 			# UIA doesn't implement word movement, so we need to do it manually.
134 			lineInfo = self.copy()
135 			lineInfo.expand(textInfos.UNIT_LINE)
136 			offset = self._getCurrentOffsetInThisLine(lineInfo)
137 			start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
138 			wordEndPoints = (
139 				(offset - start) * -1,
140 				end - offset - 1
141 			)
142 			if wordEndPoints[0]:
143 				self._rangeObj.MoveEndpointByUnit(
144 					UIAHandler.TextPatternRangeEndpoint_Start,
145 					UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
146 					wordEndPoints[0]
147 				)
148 			if wordEndPoints[1]:
149 				self._rangeObj.MoveEndpointByUnit(
150 					UIAHandler.TextPatternRangeEndpoint_End,
151 					UIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],
152 					wordEndPoints[1]
153 				)
154 		else:
155 			return super(consoleUIATextInfo, self).expand(unit)
156 
157 	def compareEndPoints(self, other, which):
158 		"""Works around a UIA bug on Windows 10 1803 and later."""
159 		# Even when a console textRange's start and end have been moved to the
160 		# same position, the console incorrectly reports the end as being
161 		# past the start.
162 		# Compare to the start (not the end) when collapsed.
163 		selfEndPoint, otherEndPoint = which.split("To")
164 		if selfEndPoint == "end" and self._isCollapsed():
165 			selfEndPoint = "start"
166 		if otherEndPoint == "End" and other._isCollapsed():
167 			otherEndPoint = "Start"
168 		which = f"{selfEndPoint}To{otherEndPoint}"
169 		return super().compareEndPoints(other, which=which)
170 
171 	def setEndPoint(self, other, which):
172 		"""Override of L{textInfos.TextInfo.setEndPoint}.
173 		Works around a UIA bug on Windows 10 1803 and later that means we can
174 		not trust the "end" endpoint of a collapsed (empty) text range
175 		for comparisons.
176 		"""
177 		selfEndPoint, otherEndPoint = which.split("To")
178 		# In this case, there is no need to check if self is collapsed
179 		# since the point of this method is to change its text range, modifying the "end" endpoint of a collapsed
180 		# text range is fine.
181 		if otherEndPoint == "End" and other._isCollapsed():
182 			otherEndPoint = "Start"
183 		which = f"{selfEndPoint}To{otherEndPoint}"
184 		return super().setEndPoint(other, which=which)
185 
186 	def _isCollapsed(self):
187 		"""Works around a UIA bug on Windows 10 1803 and later that means we
188 		cannot trust the "end" endpoint of a collapsed (empty) text range
189 		for comparisons.
190 		Instead we check to see if we can get the first character from the
191 		text range. A collapsed range will not have any characters
192 		and will return an empty string."""
193 		return not bool(self._rangeObj.getText(1))
194 
195 	def _get_isCollapsed(self):
196 		# To decide if the textRange is collapsed,
197 		# Check if it has no text.
198 		return self._isCollapsed()
199 
200 	def _getCurrentOffsetInThisLine(self, lineInfo):
201 		"""
202 		Given a caret textInfo expanded to line, returns the index into the
203 		line where the caret is located.
204 		This is necessary since Uniscribe requires indices into the text to
205 		find word boundaries, but UIA only allows for relative movement.
206 		"""
207 		# position a textInfo from the start of the line up to the current position.
208 		charInfo = lineInfo.copy()
209 		charInfo.setEndPoint(self, "endToStart")
210 		text = charInfo.text
211 		offset = textUtils.WideStringOffsetConverter(text).wideStringLength
212 		return offset
213 
214 	def _getWordOffsetsInThisLine(self, offset, lineInfo):
215 		lineText = lineInfo.text or u" "
216 		# Convert NULL and non-breaking space to space to make sure
217 		# that words will break on them
218 		lineText = lineText.translate({0: u' ', 0xa0: u' '})
219 		start = ctypes.c_int()
220 		end = ctypes.c_int()
221 		# Uniscribe does some strange things when you give it a string  with
222 		# not more than two alphanumeric chars in a row.
223 		# Inject two alphanumeric characters at the end to fix this.
224 		lineText += "xx"
225 		lineTextLen = textUtils.WideStringOffsetConverter(lineText).wideStringLength
226 		NVDAHelper.localLib.calculateWordOffsets(
227 			lineText,
228 			lineTextLen,
229 			offset,
230 			ctypes.byref(start),
231 			ctypes.byref(end)
232 		)
233 		return (
234 			start.value,
235 			min(end.value, max(1, lineTextLen - 2))
236 		)
237 
238 	def __ne__(self, other):
239 		"""Support more accurate caret move detection."""
240 		return not self == other
241 
242 
243 class consoleUIAWindow(Window):
244 	def _get_focusRedirect(self):
245 		"""
246 		Sometimes, attempting to interact with the console too quickly after
247 		focusing the window can make NVDA unable to get any caret or review
248 		information or receive new text events.
249 		To work around this, we must redirect focus to the console text area.
250 		"""
251 		for child in self.children:
252 			if isinstance(child, WinConsoleUIA):
253 				return child
254 		return None
255 
256 
257 class WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):
258 	#: Disable the name as it won't be localized
259 	name = ""
260 	#: Only process text changes every 30 ms, in case the console is getting
261 	#: a lot of text.
262 	STABILIZE_DELAY = 0.03
263 	#: the caret in consoles can take a while to move on Windows 10 1903 and later.
264 	_caretMovementTimeoutMultiplier = 1.5
265 
266 	def _get_TextInfo(self):
267 		"""Overriding _get_TextInfo and thus the TextInfo property
268 		on NVDAObjects.UIA.UIA
269 		consoleUIATextInfo fixes expand/collapse, implements word movement, and
270 		bounds review to the visible text."""
271 		return consoleUIATextInfo
272 
273 	def _getTextLines(self):
274 		# Filter out extraneous empty lines from UIA
275 		ptr = self.UIATextPattern.GetVisibleRanges()
276 		res = [ptr.GetElement(i).GetText(-1) for i in range(ptr.length)]
277 		return res
278 
279 
280 def findExtraOverlayClasses(obj, clsList):
281 	if obj.UIAElement.cachedAutomationId == "Text Area":
282 		clsList.append(WinConsoleUIA)
283 	elif obj.UIAElement.cachedAutomationId == "Console Window":
284 		clsList.append(consoleUIAWindow)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py
--- a/source/NVDAObjects/UIA/winConsoleUIA.py
+++ b/source/NVDAObjects/UIA/winConsoleUIA.py
@@ -250,17 +250,8 @@
 
 
 class consoleUIAWindow(Window):
-	def _get_focusRedirect(self):
-		"""
-		Sometimes, attempting to interact with the console too quickly after
-		focusing the window can make NVDA unable to get any caret or review
-		information or receive new text events.
-		To work around this, we must redirect focus to the console text area.
-		"""
-		for child in self.children:
-			if isinstance(child, WinConsoleUIA):
-				return child
-		return None
+	# This is the parent of the console text area, which sometimes gets focus after the text area.
+	shouldAllowUIAFocusEvent = False
 
 
 class WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):

{"golden_diff": "diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py\n--- a/source/NVDAObjects/UIA/winConsoleUIA.py\n+++ b/source/NVDAObjects/UIA/winConsoleUIA.py\n@@ -250,17 +250,8 @@\n \n \n class consoleUIAWindow(Window):\n-\tdef _get_focusRedirect(self):\n-\t\t\"\"\"\n-\t\tSometimes, attempting to interact with the console too quickly after\n-\t\tfocusing the window can make NVDA unable to get any caret or review\n-\t\tinformation or receive new text events.\n-\t\tTo work around this, we must redirect focus to the console text area.\n-\t\t\"\"\"\n-\t\tfor child in self.children:\n-\t\t\tif isinstance(child, WinConsoleUIA):\n-\t\t\t\treturn child\n-\t\treturn None\n+\t# This is the parent of the console text area, which sometimes gets focus after the text area.\n+\tshouldAllowUIAFocusEvent = False\n \n \n class WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):\n", "issue": "repetition when focusing a console window\n<!--\r\nPlease thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.\r\nIssues may be closed if the required information is not present.\r\nhttps://github.com/nvaccess/nvda/wiki/Github-issue-template-explanation-and-examples\r\n-->\r\n\r\n### Steps to reproduce:\r\nopen a console window, in my case I open bash\r\n2. open any other windows such a browser. \r\n3. tab back to your console window.\r\n### Actual behavior:\r\nI hear root@Computer name, window, terminal, selected, terminal selected\r\n### Expected behavior:\r\nhearing only the window name, + \"console\" to know the type of focused app\r\n### System configuration\r\ncore -i7 24 gb ram, ssd drive\r\n#### NVDA installed/portable/running from source:\r\ninstalled\r\n#### NVDA version:\r\nalpha-18290,7b5cd2d0\r\n#### Windows version:\r\nwindows Version 1903 (OS Build 18950.1000)\r\n#### Name and version of other software in use when reproducing the issue:\r\nnone\r\n#### Other information about your system:\r\n\r\n### Other questions\r\n#### Does the issue still occur after restarting your PC?\r\nyes\r\n#### Have you tried any other versions of NVDA? If so, please report their behaviors.\r\nconsole work has just landed, so unrelated.\n", "before_files": [{"content": "# NVDAObjects/UIA/winConsoleUIA.py\n# A part of NonVisual Desktop Access (NVDA)\n# This file is covered by the GNU General Public License.\n# See the file COPYING for more details.\n# Copyright (C) 2019 Bill Dengler\n\nimport ctypes\nimport NVDAHelper\nimport textInfos\nimport textUtils\nimport UIAHandler\n\nfrom comtypes import COMError\nfrom UIAUtils import isTextRangeOffscreen\nfrom . import UIATextInfo\nfrom ..behaviors import KeyboardHandlerBasedTypedCharSupport\nfrom ..window import Window\n\n\nclass consoleUIATextInfo(UIATextInfo):\n\t#: At least on Windows 10 1903, expanding then collapsing the text info\n\t#: caused review to get stuck, so disable it.\n\t#: There may be no need to disable this anymore, but doing so doesn't seem\n\t#: to do much good either.\n\t_expandCollapseBeforeReview = False\n\n\tdef __init__(self, obj, position, _rangeObj=None):\n\t\tsuper(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)\n\t\t# Re-implement POSITION_FIRST and POSITION_LAST in terms of\n\t\t# visible ranges to fix review top/bottom scripts.\n\t\tif position == textInfos.POSITION_FIRST:\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tfirstVisiRange = visiRanges.GetElement(0)\n\t\t\tself._rangeObj = firstVisiRange\n\t\t\tself.collapse()\n\t\telif position == textInfos.POSITION_LAST:\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tlastVisiRange = visiRanges.GetElement(visiRanges.length - 1)\n\t\t\tself._rangeObj = lastVisiRange\n\t\t\tself.collapse(True)\n\n\tdef collapse(self, end=False):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later.\"\"\"\n\t\t# When collapsing, consoles seem to incorrectly push the start of the\n\t\t# textRange back one character.\n\t\t# Correct this by bringing the start back up to where the end is.\n\t\toldInfo = self.copy()\n\t\tsuper(consoleUIATextInfo, self).collapse(end=end)\n\t\tif not end:\n\t\t\tself._rangeObj.MoveEndpointByRange(\n\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start,\n\t\t\t\toldInfo._rangeObj,\n\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start\n\t\t\t)\n\n\tdef move(self, unit, direction, endPoint=None):\n\t\toldRange = None\n\t\tif self.basePosition != textInfos.POSITION_CARET:\n\t\t\t# Insure we haven't gone beyond the visible text.\n\t\t\t# UIA adds thousands of blank lines to the end of the console.\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tvisiLength = visiRanges.length\n\t\t\tif visiLength > 0:\n\t\t\t\toldRange = self._rangeObj.clone()\n\t\tif unit == textInfos.UNIT_WORD and direction != 0:\n\t\t\t# UIA doesn't implement word movement, so we need to do it manually.\n\t\t\t# Relative to the current line, calculate our offset\n\t\t\t# and the current word's offsets.\n\t\t\tlineInfo = self.copy()\n\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\tif direction > 0:\n\t\t\t\t# Moving in a forward direction, we can just jump to the\n\t\t\t\t# end offset of the current word and we're done.\n\t\t\t\tres = self.move(\n\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\tend - offset,\n\t\t\t\t\tendPoint=endPoint\n\t\t\t\t)\n\t\t\telse:\n\t\t\t\t# Moving backwards\n\t\t\t\twordStartDistance = (offset - start) * -1\n\t\t\t\tif wordStartDistance < 0:\n\t\t\t\t\t# We are after the beginning of a word.\n\t\t\t\t\t# So first move back to the start of the word.\n\t\t\t\t\tself.move(\n\t\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\t\twordStartDistance,\n\t\t\t\t\t\tendPoint=endPoint\n\t\t\t\t\t)\n\t\t\t\t\toffset += wordStartDistance\n\t\t\t\t# Try to move one character back before the start of the word.\n\t\t\t\tres = self.move(textInfos.UNIT_CHARACTER, -1, endPoint=endPoint)\n\t\t\t\tif res == 0:\n\t\t\t\t\treturn 0\n\t\t\t\toffset -= 1\n\t\t\t\t# We are now positioned  within the previous word.\n\t\t\t\tif offset < 0:\n\t\t\t\t\t# We've moved on to the previous line.\n\t\t\t\t\t# Recalculate the current offset based on the new line we are now on.\n\t\t\t\t\tlineInfo = self.copy()\n\t\t\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\t\t# Finally using the new offset,\n\t\t\t\t# Calculate the current word offsets and move to the start of\n\t\t\t\t# this word if we are not already there.\n\t\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\t\twordStartDistance = (offset - start) * -1\n\t\t\t\tif wordStartDistance < 0:\n\t\t\t\t\tself.move(\n\t\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\t\twordStartDistance,\n\t\t\t\t\t\tendPoint=endPoint\n\t\t\t\t\t)\n\t\telse:  # moving by a unit other than word\n\t\t\tres = super(consoleUIATextInfo, self).move(unit, direction,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tendPoint)\n\t\ttry:\n\t\t\tif (\n\t\t\t\toldRange\n\t\t\t\tand isTextRangeOffscreen(self._rangeObj, visiRanges)\n\t\t\t\tand not isTextRangeOffscreen(oldRange, visiRanges)\n\t\t\t):\n\t\t\t\tself._rangeObj = oldRange\n\t\t\t\treturn 0\n\t\texcept (COMError, RuntimeError):\n\t\t\tpass\n\t\treturn res\n\n\tdef expand(self, unit):\n\t\tif unit == textInfos.UNIT_WORD:\n\t\t\t# UIA doesn't implement word movement, so we need to do it manually.\n\t\t\tlineInfo = self.copy()\n\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\twordEndPoints = (\n\t\t\t\t(offset - start) * -1,\n\t\t\t\tend - offset - 1\n\t\t\t)\n\t\t\tif wordEndPoints[0]:\n\t\t\t\tself._rangeObj.MoveEndpointByUnit(\n\t\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start,\n\t\t\t\t\tUIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],\n\t\t\t\t\twordEndPoints[0]\n\t\t\t\t)\n\t\t\tif wordEndPoints[1]:\n\t\t\t\tself._rangeObj.MoveEndpointByUnit(\n\t\t\t\t\tUIAHandler.TextPatternRangeEndpoint_End,\n\t\t\t\t\tUIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],\n\t\t\t\t\twordEndPoints[1]\n\t\t\t\t)\n\t\telse:\n\t\t\treturn super(consoleUIATextInfo, self).expand(unit)\n\n\tdef compareEndPoints(self, other, which):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later.\"\"\"\n\t\t# Even when a console textRange's start and end have been moved to the\n\t\t# same position, the console incorrectly reports the end as being\n\t\t# past the start.\n\t\t# Compare to the start (not the end) when collapsed.\n\t\tselfEndPoint, otherEndPoint = which.split(\"To\")\n\t\tif selfEndPoint == \"end\" and self._isCollapsed():\n\t\t\tselfEndPoint = \"start\"\n\t\tif otherEndPoint == \"End\" and other._isCollapsed():\n\t\t\totherEndPoint = \"Start\"\n\t\twhich = f\"{selfEndPoint}To{otherEndPoint}\"\n\t\treturn super().compareEndPoints(other, which=which)\n\n\tdef setEndPoint(self, other, which):\n\t\t\"\"\"Override of L{textInfos.TextInfo.setEndPoint}.\n\t\tWorks around a UIA bug on Windows 10 1803 and later that means we can\n\t\tnot trust the \"end\" endpoint of a collapsed (empty) text range\n\t\tfor comparisons.\n\t\t\"\"\"\n\t\tselfEndPoint, otherEndPoint = which.split(\"To\")\n\t\t# In this case, there is no need to check if self is collapsed\n\t\t# since the point of this method is to change its text range, modifying the \"end\" endpoint of a collapsed\n\t\t# text range is fine.\n\t\tif otherEndPoint == \"End\" and other._isCollapsed():\n\t\t\totherEndPoint = \"Start\"\n\t\twhich = f\"{selfEndPoint}To{otherEndPoint}\"\n\t\treturn super().setEndPoint(other, which=which)\n\n\tdef _isCollapsed(self):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later that means we\n\t\tcannot trust the \"end\" endpoint of a collapsed (empty) text range\n\t\tfor comparisons.\n\t\tInstead we check to see if we can get the first character from the\n\t\ttext range. A collapsed range will not have any characters\n\t\tand will return an empty string.\"\"\"\n\t\treturn not bool(self._rangeObj.getText(1))\n\n\tdef _get_isCollapsed(self):\n\t\t# To decide if the textRange is collapsed,\n\t\t# Check if it has no text.\n\t\treturn self._isCollapsed()\n\n\tdef _getCurrentOffsetInThisLine(self, lineInfo):\n\t\t\"\"\"\n\t\tGiven a caret textInfo expanded to line, returns the index into the\n\t\tline where the caret is located.\n\t\tThis is necessary since Uniscribe requires indices into the text to\n\t\tfind word boundaries, but UIA only allows for relative movement.\n\t\t\"\"\"\n\t\t# position a textInfo from the start of the line up to the current position.\n\t\tcharInfo = lineInfo.copy()\n\t\tcharInfo.setEndPoint(self, \"endToStart\")\n\t\ttext = charInfo.text\n\t\toffset = textUtils.WideStringOffsetConverter(text).wideStringLength\n\t\treturn offset\n\n\tdef _getWordOffsetsInThisLine(self, offset, lineInfo):\n\t\tlineText = lineInfo.text or u\" \"\n\t\t# Convert NULL and non-breaking space to space to make sure\n\t\t# that words will break on them\n\t\tlineText = lineText.translate({0: u' ', 0xa0: u' '})\n\t\tstart = ctypes.c_int()\n\t\tend = ctypes.c_int()\n\t\t# Uniscribe does some strange things when you give it a string  with\n\t\t# not more than two alphanumeric chars in a row.\n\t\t# Inject two alphanumeric characters at the end to fix this.\n\t\tlineText += \"xx\"\n\t\tlineTextLen = textUtils.WideStringOffsetConverter(lineText).wideStringLength\n\t\tNVDAHelper.localLib.calculateWordOffsets(\n\t\t\tlineText,\n\t\t\tlineTextLen,\n\t\t\toffset,\n\t\t\tctypes.byref(start),\n\t\t\tctypes.byref(end)\n\t\t)\n\t\treturn (\n\t\t\tstart.value,\n\t\t\tmin(end.value, max(1, lineTextLen - 2))\n\t\t)\n\n\tdef __ne__(self, other):\n\t\t\"\"\"Support more accurate caret move detection.\"\"\"\n\t\treturn not self == other\n\n\nclass consoleUIAWindow(Window):\n\tdef _get_focusRedirect(self):\n\t\t\"\"\"\n\t\tSometimes, attempting to interact with the console too quickly after\n\t\tfocusing the window can make NVDA unable to get any caret or review\n\t\tinformation or receive new text events.\n\t\tTo work around this, we must redirect focus to the console text area.\n\t\t\"\"\"\n\t\tfor child in self.children:\n\t\t\tif isinstance(child, WinConsoleUIA):\n\t\t\t\treturn child\n\t\treturn None\n\n\nclass WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):\n\t#: Disable the name as it won't be localized\n\tname = \"\"\n\t#: Only process text changes every 30 ms, in case the console is getting\n\t#: a lot of text.\n\tSTABILIZE_DELAY = 0.03\n\t#: the caret in consoles can take a while to move on Windows 10 1903 and later.\n\t_caretMovementTimeoutMultiplier = 1.5\n\n\tdef _get_TextInfo(self):\n\t\t\"\"\"Overriding _get_TextInfo and thus the TextInfo property\n\t\ton NVDAObjects.UIA.UIA\n\t\tconsoleUIATextInfo fixes expand/collapse, implements word movement, and\n\t\tbounds review to the visible text.\"\"\"\n\t\treturn consoleUIATextInfo\n\n\tdef _getTextLines(self):\n\t\t# Filter out extraneous empty lines from UIA\n\t\tptr = self.UIATextPattern.GetVisibleRanges()\n\t\tres = [ptr.GetElement(i).GetText(-1) for i in range(ptr.length)]\n\t\treturn res\n\n\ndef findExtraOverlayClasses(obj, clsList):\n\tif obj.UIAElement.cachedAutomationId == \"Text Area\":\n\t\tclsList.append(WinConsoleUIA)\n\telif obj.UIAElement.cachedAutomationId == \"Console Window\":\n\t\tclsList.append(consoleUIAWindow)\n", "path": "source/NVDAObjects/UIA/winConsoleUIA.py"}], "after_files": [{"content": "# NVDAObjects/UIA/winConsoleUIA.py\n# A part of NonVisual Desktop Access (NVDA)\n# This file is covered by the GNU General Public License.\n# See the file COPYING for more details.\n# Copyright (C) 2019 Bill Dengler\n\nimport ctypes\nimport NVDAHelper\nimport textInfos\nimport UIAHandler\n\nfrom comtypes import COMError\nfrom UIAUtils import isTextRangeOffscreen\nfrom . import UIATextInfo\nfrom ..behaviors import KeyboardHandlerBasedTypedCharSupport\nfrom ..window import Window\n\n\nclass consoleUIATextInfo(UIATextInfo):\n\t#: At least on Windows 10 1903, expanding then collapsing the text info\n\t#: caused review to get stuck, so disable it.\n\t#: There may be no need to disable this anymore, but doing so doesn't seem\n\t#: to do much good either.\n\t_expandCollapseBeforeReview = False\n\n\tdef __init__(self, obj, position, _rangeObj=None):\n\t\tsuper(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)\n\t\t# Re-implement POSITION_FIRST and POSITION_LAST in terms of\n\t\t# visible ranges to fix review top/bottom scripts.\n\t\tif position == textInfos.POSITION_FIRST:\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tfirstVisiRange = visiRanges.GetElement(0)\n\t\t\tself._rangeObj = firstVisiRange\n\t\t\tself.collapse()\n\t\telif position == textInfos.POSITION_LAST:\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tlastVisiRange = visiRanges.GetElement(visiRanges.length - 1)\n\t\t\tself._rangeObj = lastVisiRange\n\t\t\tself.collapse(True)\n\n\tdef collapse(self, end=False):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later.\"\"\"\n\t\t# When collapsing, consoles seem to incorrectly push the start of the\n\t\t# textRange back one character.\n\t\t# Correct this by bringing the start back up to where the end is.\n\t\toldInfo = self.copy()\n\t\tsuper(consoleUIATextInfo, self).collapse(end=end)\n\t\tif not end:\n\t\t\tself._rangeObj.MoveEndpointByRange(\n\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start,\n\t\t\t\toldInfo._rangeObj,\n\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start\n\t\t\t)\n\n\tdef move(self, unit, direction, endPoint=None):\n\t\toldRange = None\n\t\tif self.basePosition != textInfos.POSITION_CARET:\n\t\t\t# Insure we haven't gone beyond the visible text.\n\t\t\t# UIA adds thousands of blank lines to the end of the console.\n\t\t\tvisiRanges = self.obj.UIATextPattern.GetVisibleRanges()\n\t\t\tvisiLength = visiRanges.length\n\t\t\tif visiLength > 0:\n\t\t\t\toldRange = self._rangeObj.clone()\n\t\tif unit == textInfos.UNIT_WORD and direction != 0:\n\t\t\t# UIA doesn't implement word movement, so we need to do it manually.\n\t\t\t# Relative to the current line, calculate our offset\n\t\t\t# and the current word's offsets.\n\t\t\tlineInfo = self.copy()\n\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\tif direction > 0:\n\t\t\t\t# Moving in a forward direction, we can just jump to the\n\t\t\t\t# end offset of the current word and we're done.\n\t\t\t\tres = self.move(\n\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\tend - offset,\n\t\t\t\t\tendPoint=endPoint\n\t\t\t\t)\n\t\t\telse:\n\t\t\t\t# Moving backwards\n\t\t\t\twordStartDistance = (offset - start) * -1\n\t\t\t\tif wordStartDistance < 0:\n\t\t\t\t\t# We are after the beginning of a word.\n\t\t\t\t\t# So first move back to the start of the word.\n\t\t\t\t\tself.move(\n\t\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\t\twordStartDistance,\n\t\t\t\t\t\tendPoint=endPoint\n\t\t\t\t\t)\n\t\t\t\t\toffset += wordStartDistance\n\t\t\t\t# Try to move one character back before the start of the word.\n\t\t\t\tres = self.move(textInfos.UNIT_CHARACTER, -1, endPoint=endPoint)\n\t\t\t\tif res == 0:\n\t\t\t\t\treturn 0\n\t\t\t\toffset -= 1\n\t\t\t\t# We are now positioned  within the previous word.\n\t\t\t\tif offset < 0:\n\t\t\t\t\t# We've moved on to the previous line.\n\t\t\t\t\t# Recalculate the current offset based on the new line we are now on.\n\t\t\t\t\tlineInfo = self.copy()\n\t\t\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\t\t# Finally using the new offset,\n\t\t\t\t# Calculate the current word offsets and move to the start of\n\t\t\t\t# this word if we are not already there.\n\t\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\t\twordStartDistance = (offset - start) * -1\n\t\t\t\tif wordStartDistance < 0:\n\t\t\t\t\tself.move(\n\t\t\t\t\t\ttextInfos.UNIT_CHARACTER,\n\t\t\t\t\t\twordStartDistance,\n\t\t\t\t\t\tendPoint=endPoint\n\t\t\t\t\t)\n\t\telse:  # moving by a unit other than word\n\t\t\tres = super(consoleUIATextInfo, self).move(unit, direction,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tendPoint)\n\t\ttry:\n\t\t\tif (\n\t\t\t\toldRange\n\t\t\t\tand isTextRangeOffscreen(self._rangeObj, visiRanges)\n\t\t\t\tand not isTextRangeOffscreen(oldRange, visiRanges)\n\t\t\t):\n\t\t\t\tself._rangeObj = oldRange\n\t\t\t\treturn 0\n\t\texcept (COMError, RuntimeError):\n\t\t\tpass\n\t\treturn res\n\n\tdef expand(self, unit):\n\t\tif unit == textInfos.UNIT_WORD:\n\t\t\t# UIA doesn't implement word movement, so we need to do it manually.\n\t\t\tlineInfo = self.copy()\n\t\t\tlineInfo.expand(textInfos.UNIT_LINE)\n\t\t\toffset = self._getCurrentOffsetInThisLine(lineInfo)\n\t\t\tstart, end = self._getWordOffsetsInThisLine(offset, lineInfo)\n\t\t\twordEndPoints = (\n\t\t\t\t(offset - start) * -1,\n\t\t\t\tend - offset - 1\n\t\t\t)\n\t\t\tif wordEndPoints[0]:\n\t\t\t\tself._rangeObj.MoveEndpointByUnit(\n\t\t\t\t\tUIAHandler.TextPatternRangeEndpoint_Start,\n\t\t\t\t\tUIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],\n\t\t\t\t\twordEndPoints[0]\n\t\t\t\t)\n\t\t\tif wordEndPoints[1]:\n\t\t\t\tself._rangeObj.MoveEndpointByUnit(\n\t\t\t\t\tUIAHandler.TextPatternRangeEndpoint_End,\n\t\t\t\t\tUIAHandler.NVDAUnitsToUIAUnits[textInfos.UNIT_CHARACTER],\n\t\t\t\t\twordEndPoints[1]\n\t\t\t\t)\n\t\telse:\n\t\t\treturn super(consoleUIATextInfo, self).expand(unit)\n\n\tdef compareEndPoints(self, other, which):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later.\"\"\"\n\t\t# Even when a console textRange's start and end have been moved to the\n\t\t# same position, the console incorrectly reports the end as being\n\t\t# past the start.\n\t\t# Compare to the start (not the end) when collapsed.\n\t\tselfEndPoint, otherEndPoint = which.split(\"To\")\n\t\tif selfEndPoint == \"end\" and self._isCollapsed():\n\t\t\tselfEndPoint = \"start\"\n\t\tif otherEndPoint == \"End\" and other._isCollapsed():\n\t\t\totherEndPoint = \"Start\"\n\t\twhich = f\"{selfEndPoint}To{otherEndPoint}\"\n\t\treturn super().compareEndPoints(other, which=which)\n\n\tdef setEndPoint(self, other, which):\n\t\t\"\"\"Override of L{textInfos.TextInfo.setEndPoint}.\n\t\tWorks around a UIA bug on Windows 10 1803 and later that means we can\n\t\tnot trust the \"end\" endpoint of a collapsed (empty) text range\n\t\tfor comparisons.\n\t\t\"\"\"\n\t\tselfEndPoint, otherEndPoint = which.split(\"To\")\n\t\t# In this case, there is no need to check if self is collapsed\n\t\t# since the point of this method is to change its text range, modifying the \"end\" endpoint of a collapsed\n\t\t# text range is fine.\n\t\tif otherEndPoint == \"End\" and other._isCollapsed():\n\t\t\totherEndPoint = \"Start\"\n\t\twhich = f\"{selfEndPoint}To{otherEndPoint}\"\n\t\treturn super().setEndPoint(other, which=which)\n\n\tdef _isCollapsed(self):\n\t\t\"\"\"Works around a UIA bug on Windows 10 1803 and later that means we\n\t\tcannot trust the \"end\" endpoint of a collapsed (empty) text range\n\t\tfor comparisons.\n\t\tInstead we check to see if we can get the first character from the\n\t\ttext range. A collapsed range will not have any characters\n\t\tand will return an empty string.\"\"\"\n\t\treturn not bool(self._rangeObj.getText(1))\n\n\tdef _get_isCollapsed(self):\n\t\t# To decide if the textRange is collapsed,\n\t\t# Check if it has no text.\n\t\treturn self._isCollapsed()\n\n\tdef _getCurrentOffsetInThisLine(self, lineInfo):\n\t\t\"\"\"\n\t\tGiven a caret textInfo expanded to line, returns the index into the\n\t\tline where the caret is located.\n\t\tThis is necessary since Uniscribe requires indices into the text to\n\t\tfind word boundaries, but UIA only allows for relative movement.\n\t\t\"\"\"\n\t\tcharInfo = self.copy()\n\t\tres = 0\n\t\tchars = None\n\t\twhile charInfo.compareEndPoints(\n\t\t\tlineInfo,\n\t\t\t\"startToEnd\"\n\t\t) <= 0:\n\t\t\tcharInfo.expand(textInfos.UNIT_CHARACTER)\n\t\t\tchars = charInfo.move(textInfos.UNIT_CHARACTER, -1) * -1\n\t\t\tif chars != 0 and charInfo.compareEndPoints(\n\t\t\t\tlineInfo,\n\t\t\t\t\"startToStart\"\n\t\t\t) >= 0:\n\t\t\t\tres += chars\n\t\t\telse:\n\t\t\t\tbreak\n\t\treturn res\n\n\tdef _getWordOffsetsInThisLine(self, offset, lineInfo):\n\t\tlineText = lineInfo.text or u\" \"\n\t\t# Convert NULL and non-breaking space to space to make sure\n\t\t# that words will break on them\n\t\tlineText = lineText.translate({0: u' ', 0xa0: u' '})\n\t\tstart = ctypes.c_int()\n\t\tend = ctypes.c_int()\n\t\t# Uniscribe does some strange things when you give it a string  with\n\t\t# not more than two alphanumeric chars in a row.\n\t\t# Inject two alphanumeric characters at the end to fix this.\n\t\tlineText += \"xx\"\n\t\tNVDAHelper.localLib.calculateWordOffsets(\n\t\t\tlineText,\n\t\t\tlen(lineText),\n\t\t\toffset,\n\t\t\tctypes.byref(start),\n\t\t\tctypes.byref(end)\n\t\t)\n\t\treturn (\n\t\t\tstart.value,\n\t\t\tmin(end.value, max(1, len(lineText) - 2))\n\t\t)\n\n\tdef __ne__(self, other):\n\t\t\"\"\"Support more accurate caret move detection.\"\"\"\n\t\treturn not self == other\n\n\nclass consoleUIAWindow(Window):\n\t# This is the parent of the console text area, which sometimes gets focus after the text area.\n\tshouldAllowUIAFocusEvent = False\n\n\nclass WinConsoleUIA(KeyboardHandlerBasedTypedCharSupport):\n\t#: Disable the name as it won't be localized\n\tname = \"\"\n\t#: Only process text changes every 30 ms, in case the console is getting\n\t#: a lot of text.\n\tSTABILIZE_DELAY = 0.03\n\t#: the caret in consoles can take a while to move on Windows 10 1903 and later.\n\t_caretMovementTimeoutMultiplier = 1.5\n\n\tdef _get_TextInfo(self):\n\t\t\"\"\"Overriding _get_TextInfo and thus the TextInfo property\n\t\ton NVDAObjects.UIA.UIA\n\t\tconsoleUIATextInfo fixes expand/collapse, implements word movement, and\n\t\tbounds review to the visible text.\"\"\"\n\t\treturn consoleUIATextInfo\n\n\tdef _getTextLines(self):\n\t\t# Filter out extraneous empty lines from UIA\n\t\treturn (\n\t\t\tself.makeTextInfo(textInfos.POSITION_ALL)\n\t\t\t._rangeObj.getText(-1)\n\t\t\t.rstrip()\n\t\t\t.split(\"\\r\\n\")\n\t\t)\n\n\ndef findExtraOverlayClasses(obj, clsList):\n\tif obj.UIAElement.cachedAutomationId == \"Text Area\":\n\t\tclsList.append(WinConsoleUIA)\n\telif obj.UIAElement.cachedAutomationId == \"Console Window\":\n\t\tclsList.append(consoleUIAWindow)\n", "path": "source/NVDAObjects/UIA/winConsoleUIA.py"}]}