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

{"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@@ -235,10 +235,16 @@\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_TextInfo = consoleUIATextInfo\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 _get_caretMovementDetectionUsesEvents(self):\n \t\t\"\"\"Using caret events in consoles sometimes causes the last character of the\n \t\tprompt to be read when quickly deleting text.\"\"\"\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 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 _get_isCollapsed(self):\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# Therefore to decide if the textRange is collapsed,\n\t\t# Check if it has no text.\n\t\treturn not bool(self._rangeObj.getText(1))\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\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_TextInfo = consoleUIATextInfo\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_caretMovementDetectionUsesEvents(self):\n\t\t\"\"\"Using caret events in consoles sometimes causes the last character of the\n\t\tprompt to be read when quickly deleting text.\"\"\"\n\t\treturn False\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"}], "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 _get_isCollapsed(self):\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# Therefore to decide if the textRange is collapsed,\n\t\t# Check if it has no text.\n\t\treturn not bool(self._rangeObj.getText(1))\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\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 _get_caretMovementDetectionUsesEvents(self):\n\t\t\"\"\"Using caret events in consoles sometimes causes the last character of the\n\t\tprompt to be read when quickly deleting text.\"\"\"\n\t\treturn False\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"}]}