Repository: nvaccess/nvda

Issue:
NVDA spells the current line even if the user did not perform a double keypress
### Steps to reproduce:

* Open windows Notepad
* Type the following text in the notepad:
  > Hello world!
  > Good bye world!
* Press Ctrl+F and search for the word 'world'
* When the first occurance is found, press Escape to close the 'Find' window
* Press Ctrl+Home to turn back to the start of the document
* Press quickly the 4 following keystrokes to find the second occurrence of the word 'world' and read the line where it is located:
  * F3
  * NVDA+UpArrow
  * F3
  * NVDA+UpArrow

### Actual behavior:

NVDA spells the second line.

### Expected behavior:

NVDA reads the second line and does not spell it.
Here, the user does not consider having done a double key press, but having pressed 3 keystrokes quickly.

### Analysis

Technically since 'F3' keystroke is not bound to a script, NVDA considers there is a double press when performing the third keystroke. Maybe a solution would be to set _lastScriptRef to None if a keystroke not bound to any script is executed.

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
2020.2rc1

#### Windows version:
Windows 10 1809

#### Name and version of other software in use when reproducing the issue:
N/A

#### Other information about your system:
N/A

### 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.
Not recently.
But I remember this issue has always been there at least for one year.

#### If addons are disabled, is your problem still occuring?
Yes

#### Did you try to run the COM registry fixing tool in NVDA menu / tools?
No



Discussion:
Looking at line 471 in the file inputCore.py:
`		if not script and scriptHandler._numIncompleteInterceptedCommandScripts:`
The issue disappear if I modify this line as follow:
`		if not script: `

Of course, this probably add some other side effect since I have not fully understood this part of the code. But this help to know where the issue is located.

@michaelDCurran any thought on this since you are the author of 7b7823c71f?
@jcsteh your comments are also appreciated given your comments in #2953 and #4499.


For the record, I can duplicate this behavior, but only just. I had to try it 
several times before finally getting it to happen.
Whatever timer that is at play for the double press is pretty short.

I agree that it should be fixed, because it is certainly not expected behavior.

I wonder also if the ability for this to happen is processor speed dependent. 
The machine I tested on was a hexacore I7 that was not loaded at all. If I tried 
it on a slower machine, perhaps it would happen more reliably.

I think the timing is not machine dependent. Indeed, looking at the code, there is a hard-coded 0.5 second to detect a double-press. Thus you actually need to perform keystrokes 2, 3 and 4 in less than 0.5 seconds. I agree the interval is short. But when you are used to perform the same keystrokes, this can happen frequently though.


Resolving patch:
diff --git a/source/inputCore.py b/source/inputCore.py
--- a/source/inputCore.py
+++ b/source/inputCore.py
@@ -492,7 +492,7 @@ def executeGesture(self, gesture):
 			queueHandler.queueFunction(queueHandler.eventQueue, speech.speakMessage, gesture.displayName)
 
 		gesture.reportExtra()
-
+		
 		# #2953: if an intercepted command Script (script that sends a gesture) is queued
 		# then queue all following gestures (that don't have a script) with a fake script so that they remain in order.
 		if not script and scriptHandler._numIncompleteInterceptedCommandScripts:
@@ -502,8 +502,11 @@ def executeGesture(self, gesture):
 		if script:
 			scriptHandler.queueScript(script, gesture)
 			return
-
-		raise NoInputGestureAction
+		else:
+			# Clear memorized last script to avoid getLastScriptRepeatCount detect a repeat
+			# in case an unbound gesture is executed between two identical bound gestures.
+			queueHandler.queueFunction(queueHandler.eventQueue, scriptHandler.clearLastScript)
+			raise NoInputGestureAction
 
 	def _get_isInputHelpActive(self):
 		"""Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed.
diff --git a/source/scriptHandler.py b/source/scriptHandler.py
--- a/source/scriptHandler.py
+++ b/source/scriptHandler.py
@@ -224,6 +224,19 @@ def getLastScriptRepeatCount():
 	else:
 		return _lastScriptCount
 
+
+def clearLastScript():
+	"""Clears the variables that keeps track of the execution of duplicate scripts with in a certain amount of
+	time, so that next script execution will always be detected as a first execution of this script.
+	This function should only be called from the main thread.
+	"""
+
+	global _lastScriptTime, _lastScriptRef, _lastScriptCount
+	_lastScriptTime = 0
+	_lastScriptRef = None
+	_lastScriptCount = 0
+
+
 def isScriptWaiting():
 	return bool(_numScriptsQueued)
 


Test patch: