Repository: nvaccess/nvda

Issue:
Wrong behaviour in word with UIA when navigating tables
<!-- Please read the text in this edit field before filling it in.
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/blob/master/devDocs/githubIssueTemplateExplanationAndExamples.md
Please also note that the NVDA project has a Citizen and Contributor Code of Conduct which can be found at https://github.com/nvaccess/nvda/blob/master/CODE_OF_CONDUCT.md. NV Access expects that all contributors and other community members read and abide by the rules set out in this document while participating or contributing to this project. This includes creating or commenting on issues and pull requests. 

Each of the questions and sections below start with multiple hash symbols (#). Place your answers and information on the blank line below each question.
-->

### Steps to reproduce:
1. Turn on UIA in word documents in advanced setting of NVDA.
2. Open document with tables in word 2019.
### Actual behavior:
Bad navigation by pressing CTRL+alt+arrows inside the table.
Synth speeks not in table cell after that I press left or right and next alt+CTR+arrows can move me by not empty cells.
If I move to empty cell, it speeks me not in table. Usual arrows move me normally.
### Expected behavior:
CTRL+alt+arrows should move me by cells.
### System configuration
#### NVDA installed/portable/running from source:
Installable NVDA
#### NVDA version:
2021.1 beta
#### Windows version:
windows 10 version 20H2 (build ОС 19042.985)
#### Name and version of other software in use when reproducing the issue:
Word 2019
#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?
Yes.
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
NVDA older versions work normally.
2019.3, 20.1,2,3 work normally.
#### If add-ons are disabled, is your problem still occurring?
no
#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
no


Discussion:
After testing, I found that NVDA 2020.4 also contains this bug, in fact it exists as far back as 2020.2.
I'm investigating why this broke and if it is a quick fix. But most likely we won't address this in 2021.1 as we are too close to the release. 
This is not trivial and further investigation is needed. Pushing to at least 2021.2.
Hi, this is the same as #11043, it is a well old bug that happens with any version of NVDA from 2019.1 onwards, and only happens with the latest versions of Office that came out around March last year. With previous versions it still works fine, they probably made some change that affected that in Office.

Resolving patch:
diff --git a/source/NVDAObjects/UIA/wordDocument.py b/source/NVDAObjects/UIA/wordDocument.py
--- a/source/NVDAObjects/UIA/wordDocument.py
+++ b/source/NVDAObjects/UIA/wordDocument.py
@@ -23,6 +23,9 @@
 
 """Support for Microsoft Word via UI Automation."""
 
+#: the non-printable unicode character that represents the end of cell or end of row mark in Microsoft Word
+END_OF_ROW_MARK = '\x07'
+
 class ElementsListDialog(browseMode.ElementsListDialog):
 
 	ELEMENT_TYPES=(browseMode.ElementsListDialog.ELEMENT_TYPES[0],browseMode.ElementsListDialog.ELEMENT_TYPES[1],
@@ -183,7 +186,7 @@ def _getTextFromUIARange(self, textRange):
 			# Really better as carage returns
 			t=t.replace('\v','\r')
 			# Remove end-of-row markers from the text - they are not useful
-			t=t.replace('\x07','')
+			t = t.replace(END_OF_ROW_MARK, '')
 		return t
 
 	def _isEndOfRow(self):
@@ -215,10 +218,25 @@ def expand(self,unit):
 				self.setEndPoint(docInfo,"endToEnd")
 
 	def getTextWithFields(self,formatConfig=None):
-		if self.isCollapsed:
-			# #7652: We cannot fetch fields on collapsed ranges otherwise we end up with repeating controlFields in braille (such as list list list). 
-			return []
-		fields=super(WordDocumentTextInfo,self).getTextWithFields(formatConfig=formatConfig)
+		fields = None
+		# #11043: when a non-collapsed text range is positioned within a blank table cell
+		# MS Word does not return the table  cell as an enclosing element,
+		# Thus NVDa thinks the range is not inside the cell.
+		# This can be detected by asking for the first 2 characters of the range's text,
+		# Which will either be an empty string, or the single end-of-row mark.
+		# Anything else means it is not on an empty table cell,
+		# or the range really does span more than the cell itself.
+		# If this situation is detected,
+		# copy and collapse the range, and fetch the content from that instead,
+		# As a collapsed range on an empty cell does correctly return the table cell as its first enclosing element.
+		if not self.isCollapsed:
+			rawText = self._rangeObj.GetText(2)
+			if not rawText or rawText == END_OF_ROW_MARK:
+				r = self.copy()
+				r.end = r.start
+				fields = super(WordDocumentTextInfo, r).getTextWithFields(formatConfig=formatConfig)
+		if fields is None:
+			fields = super().getTextWithFields(formatConfig=formatConfig)
 		if len(fields)==0: 
 			# Nothing to do... was probably a collapsed range.
 			return fields
diff --git a/source/braille.py b/source/braille.py
--- a/source/braille.py
+++ b/source/braille.py
@@ -880,7 +880,13 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
 		formatFieldAttributesCache = getattr(info.obj, "_brailleFormatFieldAttributesCache", {})
 		# When true, we are inside a clickable field, and should therefore not report any more new clickable fields
 		inClickable=False
-		for command in info.getTextWithFields(formatConfig=formatConfig):
+		# Collapsed ranges should never produce text and fields,
+		# But later on we may still need to draw the cursor at this position.
+		if not info.isCollapsed:
+			commands = info.getTextWithFields(formatConfig=formatConfig)
+		else:
+			commands = []
+		for command in commands:
 			if isinstance(command, str):
 				# Text should break a run of clickables
 				inClickable=False


Test patch: