Repository: nvaccess/nvda

Issue:
UIA in Windows consoles: current character does not show up in braille
cc @codeofdusk 

### Steps to reproduce:
1. With braille enabled, open a windows console with UIA enabled
2. type hello
3. Press left arrow multiple times.

### Actual behavior:
Braille does not show the character the caret is at. When hello was typed, the whole word is shown. However, when pressing left arrow N times, the result after the prompt is as follows:

1. hell
2. helo
3. helo
4. hllo
5. ello

### Expected behavior:
The current character is not stripped from braille output

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
alpha-18290

#### Windows version:
Windows 10 1903 build 18362.239
#### Name and version of other software in use when reproducing the issue:
Command prompt (cmd)

### 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.
No


Discussion:

Resolving patch:
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
@@ -153,15 +153,49 @@ def expand(self, unit):
 		else:
 			return super(consoleUIATextInfo, self).expand(unit)
 
-	def _get_isCollapsed(self):
+	def compareEndPoints(self, other, which):
 		"""Works around a UIA bug on Windows 10 1803 and later."""
 		# Even when a console textRange's start and end have been moved to the
 		# same position, the console incorrectly reports the end as being
 		# past the start.
-		# Therefore to decide if the textRange is collapsed,
-		# Check if it has no text.
+		# Compare to the start (not the end) when collapsed.
+		selfEndPoint, otherEndPoint = which.split("To")
+		if selfEndPoint == "end" and not self._isCollapsed():
+			selfEndPoint = "start"
+		if otherEndPoint == "End" and not other._isCollapsed():
+			otherEndPoint = "Start"
+		which = f"{selfEndPoint}To{otherEndPoint}"
+		return super().compareEndPoints(other, which=which)
+
+	def setEndPoint(self, other, which):
+		"""Override of L{textInfos.TextInfo.setEndPoint}.
+		Works around a UIA bug on Windows 10 1803 and later that means we can
+		not trust the "end" endpoint of a collapsed (empty) text range
+		for comparisons.
+		"""
+		selfEndPoint, otherEndPoint = which.split("To")
+		# In this case, there is no need to check if self is collapsed
+		# since the point of this method is to change its text range, modifying the "end" endpoint of a collapsed
+		# text range is fine.
+		if otherEndPoint == "End" and not other._isCollapsed():
+			otherEndPoint = "Start"
+		which = f"{selfEndPoint}To{otherEndPoint}"
+		return super().setEndPoint(other, which=which)
+
+	def _isCollapsed(self):
+		"""Works around a UIA bug on Windows 10 1803 and later that means we
+		cannot trust the "end" endpoint of a collapsed (empty) text range
+		for comparisons.
+		Instead we check to see if we can get the first character from the
+		text range. A collapsed range will not have any characters
+		and will return an empty string."""
 		return not bool(self._rangeObj.getText(1))
 
+	def _get_isCollapsed(self):
+		# To decide if the textRange is collapsed,
+		# Check if it has no text.
+		return self._isCollapsed
+
 	def _getCurrentOffsetInThisLine(self, lineInfo):
 		"""
 		Given a caret textInfo expanded to line, returns the index into the


Test patch: