Repository: nvaccess/nvda

Issue:
Links in Word aren't displayed with the lnk indicator in braille
STR:
1. Open Word, tested with Word 2016.
2. Type "This is something to click on."
3. Select the word "click", right-click on it, choose Hyperlink... and make it a link.

Speech reads:
This is something to link click out of link on.
Braille shows:
This is something to click on.
Expected:
Braille somehow shows the link, e.g. with the "lnk" indicator.


Discussion:
Could be added to braille.getFormatFieldBraille(). But isAtStart is False if a link appears in the middle of some text.

We should probably also work something out for comments/revisions.
P2 because this is important info that isn't available to braille only users.

[**@dkager**](https://github.com/dkager) commented on [23 Jan. 2017, 5:19 am AEST](https://github.com/nvaccess/nvda/issues/6780#issuecomment-274352242 "2017-01-22T19:19:27Z - Replied by Github Reply Comments"):
> Could be added to braille.getFormatFieldBraille(). But isAtStart is False if a link appears in the middle of some text.

isAtStart isn't useful here, since that's to ensure we don't present info in the middle of the line that should only be presented at the start. However, we're probably going to need a cache of attributes we presented in the last chunk of the line so we don't present them again.

> We should probably also work something out for comments/revisions.

Indeed, though I don't consider that quite as high priority (though still important) and we should address it separately.
> However, we're probably going to need a cache of attributes we presented in the last chunk of the line so we don't present them again.

It looks like this only applies if you have some text selected. Then you have three chunks: text before and after selection, and the selection itself. But that implies there can be multiple "link start" commands for the same text. Is that true?
FormatFields contain all of the formatting relevant to the text they
immediately precede. So, if you have a link consisting of two characters
and you getTextWithFields just for the second character, the field will
contain link: True. That's why I say you'll need a cache.

I'm going with a full cache like is used for speech, because that looks the most extensible.

Resolving patch:
diff --git a/source/braille.py b/source/braille.py
--- a/source/braille.py
+++ b/source/braille.py
@@ -792,7 +792,19 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
 		return (_("%s end") %
 			getBrailleTextForProperties(role=role))
 
-def getFormatFieldBraille(field, isAtStart, formatConfig):
+def getFormatFieldBraille(field, fieldCache, isAtStart, formatConfig):
+	"""Generates the braille text for the given format field.
+	@param field: The format field to examine.
+	@type field: {str : str, ...}
+	@param fieldCache: The format field of the previous run; i.e. the cached format field.
+	@type fieldCache: {str : str, ...}
+	@param isAtStart: True if this format field precedes any text in the line/paragraph.
+	This is useful to restrict display of information which should only appear at the start of the line/paragraph;
+	e.g. the line number or line prefix (list bullet/number).
+	@type isAtStart: bool
+	@param formatConfig: The formatting config.
+	@type formatConfig: {str : bool, ...}
+	"""
 	textList = []
 	if isAtStart:
 		if formatConfig["reportLineNumber"]:
@@ -808,6 +820,13 @@ def getFormatFieldBraille(field, isAtStart, formatConfig):
 				# Translators: Displayed in braille for a heading with a level.
 				# %s is replaced with the level.
 				textList.append(_("h%s")%headingLevel)
+	if formatConfig["reportLinks"]:
+		link=field.get("link")
+		oldLink=fieldCache.get("link")
+		if link and link != oldLink:
+			textList.append(roleLabels[controlTypes.ROLE_LINK])
+	fieldCache.clear()
+	fieldCache.update(field)
 	return TEXT_SEPARATOR.join([x for x in textList if x])
 
 class TextInfoRegion(Region):
@@ -882,6 +901,7 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
 		shouldMoveCursorToFirstContent = not isSelection and self.cursorPos is not None
 		ctrlFields = []
 		typeform = louis.plain_text
+		formatFieldAttributesCache = getattr(info.obj, "_brailleFormatFieldAttributesCache", {})
 		for command in info.getTextWithFields(formatConfig=formatConfig):
 			if isinstance(command, basestring):
 				self._isFormatFieldAtStart = False
@@ -916,7 +936,7 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
 				field = command.field
 				if cmd == "formatChange":
 					typeform = self._getTypeformFromFormatField(field)
-					text = getFormatFieldBraille(field, self._isFormatFieldAtStart, formatConfig)
+					text = getFormatFieldBraille(field, formatFieldAttributesCache, self._isFormatFieldAtStart, formatConfig)
 					if not text:
 						continue
 					# Map this field text to the start of the field's content.
@@ -959,6 +979,7 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
 			# We only render fields that aren't at the start of their nodes for the first part of the reading unit.
 			# Otherwise, we'll render fields that have already been rendered.
 			self._skipFieldsNotAtStartOfNode = True
+		info.obj._brailleFormatFieldAttributesCache = formatFieldAttributesCache
 
 	def _getReadingUnit(self):
 		return textInfos.UNIT_PARAGRAPH if config.conf["braille"]["readByParagraph"] else textInfos.UNIT_LINE


Test patch: