Repository: nvaccess/nvda

Issue:
windows terminal: NVDA reads invisible lines when using pagers such as less and more
<!-- 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. launch windows terminal with wsl2 (tested with ubuntu 20.04)
2. run command which pages its output, for example `man test`
3. press nvda+numpad7, observe that NVDA navigated to the beginning of man output with command synopsis: press numpad9 a few times to hear "TEST(1)                                             User Commands                                             TEST(1)"
4. press space bar to scroll the page down, you may repeat a few times to see the full effect
5. press shift+numpad7 again to go to the top
6. try to read following lines, press numpad9

### Actual behavior:
NVDA reads the command output from the very beginning, as if no pagination appeared: "TEST(1)                                             User Commands                                             TEST(1)"
### Expected behavior:
NVDA should read only the last page displayed. It worked like that before and it also works like that when using wsl in windows console (without the new windows terminal)

### System configuration
#### NVDA installed/portable/running from source:
Installed
#### NVDA version:
2021.3
#### Windows version:
windows 10 21H1 (os build 19043.1348)
#### Name and version of other software in use when reproducing the issue:
Microsoft windows terminal  1.11.2921.0
#### 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.
I think it worked in 2020.4
#### If NVDA add-ons are disabled, is your problem still occurring?
N/A
#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
N/A


Discussion:
Tested, the issue does not occur with NVDA 2021.2
Not a bug, see PR #12669. Will be enabled as well in modern versions of Windows Console with #10964.
@codeofdusk could we please make it configurable? Not being able to reliably jump to the top of visible area to review the output line by line breaks experience when using terminal-based programs a lot. e.g. imagine reading git log page by page, however when one switches to the next page they can not review content of that page from top to bottom, since NVDA treats the output as a whole. the same can be said for using `man` and basically any interactive-type program where the user needs to granularly navigate the output.
As an idea, see the visi-range-commands branch on my fork. I've added (global for now) scripts to jump to the first/last visible line in UIA text controls. Does this help/is this the right approach? Cc @leonardder, @seanbudd, @carlos-zamora.
@codeofdusk  Thank you for considering approaches to address the issue!

I am not sure that adding a separate commands for this is the best experience. It suggests that we assume by default the user wants to jump to the top/bottom without taking into account visible range, which is not necessarily the case. It might be subjective, but I can't imagine good workflow examples from top of my head where I might prefer such a behavior by default when using terminal, while I use it everyday for years.

A couple ideas:
1. make jump to top/jump to bottom use visible ranges by default, add scripts to jump to top/bottom skipping them. It assumes that user might sometimes want to jump to the absolute top/bottom but still prefers most of the time to be limited by what is visible.
2. Make jump to top/bottom scripts to use visible ranges when pressed single time, and jump to the absolute top/bottom when pressed second time.

In any scenario, we might also allow the user to navigate out of the visible range by using next/previous line/word/character commands.

This way we:
* do not break experience for hard-time terminal users
* Allow the user to have flexibility to read the whole content of terminal window if they so desire (including copying the output to the clipboard etc)
* Address the concern of some lines displayed out of the visible range being completely inaccessible to NVDA
I think the problem with both of these ideas is that they cause terminals to have different behaviour from the rest of NVDA, which has caused user confusion. With upstream changes to the terminal, this differentiation is no longer necessary, so #12669 brings newer UIA terminals in line with other UIA controls. The original reason for bounding to start with was that older (`IMPROVED` and `END_INCLUSIVE`) UIA consoles had many thousands of empty lines at the end of the text. I tried adding configurable review bounds (see PRs #9687 and #9735) but those approaches were rejected.

One approach for idea 1 could be to bring back a `ConsoleUIATextInfo` for `FORMATTED` consoles and wt, with `POSITION_FIRST` and `POSITION_LAST` relative to the visible (not document) range. This would make review work as specified but I'm not sure if this has other unintended consequences.

For idea 2, do scripts on the object level override `globalCommands`?

CC @michaelDCurran.
Thanks for references, now I see that we had an approach in the past to address the issue by making bounding configurable with a global gesture.

I'd argue that #12669 reasoning is not 100% precise regarding bringing the console review in precedent with the rest of NVDA. In word and web documents there is an independent cursor which does not lose its position when navigating down (as is the case when using pagers in the terminal); there also exists commands to move up and down by pages (again not the case with the terminal).
with the current implementation, when navigating to the next page using a pager in Windows Terminal, one loses the review position and there is no way to reliably restore it (which was possible by jumping to the top when that command was bounded by visible offsets). It is not the case with word or web documents.
In other words, those other places provide very different experience and should not be compared directly without taking into account the context.

I see that @michaelDCurran  was in favor of the global gesture so he clearly recognized the problem, let's wait what he has to say for this as well.
As a linux user who rarely uses windows, I would like to say that this change really makes it difficult to use the terminal as conveniently as I do on linux.

when windows received a normal terminal, I was very happy, but since recently, when this change has appeared, working in the windows terminal causes me only discomfort.

Resolving patch:
diff --git a/source/globalCommands.py b/source/globalCommands.py
--- a/source/globalCommands.py
+++ b/source/globalCommands.py
@@ -1433,6 +1433,61 @@ def script_review_nextLine(self, gesture):
 			api.setReviewPosition(info)
 		speech.speakTextInfo(newLine, unit=textInfos.UNIT_LINE, reason=controlTypes.OutputReason.CARET)
 
+	@script(
+		# Translators: Input help mode message for move review cursor to previous page command.
+		description=_("Moves the review cursor to the previous page of the current navigator object and speaks it"),
+		resumeSayAllMode=sayAll.CURSOR.REVIEW,
+		category=SCRCAT_TEXTREVIEW,
+		gestures=("kb:NVDA+pageUp", "kb(laptop):NVDA+shift+pageUp", "ts(text):flickUp")
+	)
+	def script_review_previousPage(self, gesture: inputCore.InputGesture) -> None:
+		info = api.getReviewPosition().copy()
+		try:
+			info.expand(textInfos.UNIT_PAGE)
+			info.collapse()
+			res = info.move(textInfos.UNIT_PAGE, -1)
+		except (ValueError, NotImplementedError):
+			# Translators: a message reported when movement by page is unsupported
+			ui.reviewMessage(_("Movement by page not supported"))
+			return
+		if res == 0:
+			# Translators: a message reported when review cursor is at the top line of the current navigator object.
+			ui.reviewMessage(_("Top"))
+		else:
+			api.setReviewPosition(info)
+		info.expand(textInfos.UNIT_PAGE)
+		speech.speakTextInfo(info, unit=textInfos.UNIT_PAGE, reason=controlTypes.OutputReason.CARET)
+
+	@script(
+		# Translators: Input help mode message for move review cursor to next page command.
+		description=_("Moves the review cursor to the next page of the current navigator object and speaks it"),
+		resumeSayAllMode=sayAll.CURSOR.REVIEW,
+		category=SCRCAT_TEXTREVIEW,
+		gestures=("kb:NVDA+pageDown", "kb(laptop):NVDA+shift+pageDown", "ts(text):flickUp")
+	)
+	def script_review_nextPage(self, gesture: inputCore.InputGesture) -> None:
+		origInfo = api.getReviewPosition().copy()
+		origInfo.collapse()
+		info = origInfo.copy()
+		try:
+			res = info.move(textInfos.UNIT_PAGE, 1)
+		except (ValueError, NotImplementedError):
+			# Translators: a message reported when movement by page is unsupported
+			ui.reviewMessage(_("Movement by page not supported"))
+			return
+		newPage = info.copy()
+		newPage.expand(textInfos.UNIT_PAGE)
+		# #12808: Some implementations of move forward may succeed one more time than expected,
+		# landing on the exclusive end of the document.
+		# Therefore, verify that expanding after the move does result in being on a new page,
+		# i.e. the new page starts after the original review cursor position.
+		if res == 0 or newPage.start <= origInfo.start:
+			# Translators: a message reported when review cursor is at the bottom line of the current navigator object.
+			ui.reviewMessage(_("Bottom"))
+		else:
+			api.setReviewPosition(info)
+		speech.speakTextInfo(newPage, unit=textInfos.UNIT_PAGE, reason=controlTypes.OutputReason.CARET)
+
 	@script(
 		# Translators: Input help mode message for move review cursor to bottom line command.
 		description=_("Moves the review cursor to the bottom line of the current navigator object and speaks it"),


Test patch: