Procházet zdrojové kódy

Refactor auto-compile trigger logic

James Allen před 8 roky
rodič
revize
7866a91d21

+ 80 - 36
services/web/public/coffee/ide/pdf/controllers/PdfController.coffee

@@ -5,8 +5,12 @@ define [
 	"libs/bib-log-parser"
 	"services/log-hints-feedback"
 ], (App, Ace, HumanReadableLogs, BibLogParser) ->
-	AUTO_COMPILE_TIMEOUT = 5000
-	OP_ACKNOWLEDGEMENT_TIMEOUT = 1100
+	AUTO_COMPILE_MAX_WAIT = 5000
+	# We add a 1 second debounce to sending user changes to server if they aren't
+	# collaborating with anyone. This needs to be higher than that, and allow for
+	# client to server latency, otherwise we compile before the op reaches the server
+	# and then again on ack.
+	AUTO_COMPILE_DEBOUNCE = 2000
 
 	App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, logHintsFeedback, localStorage) ->
 		# enable per-user containers by default
@@ -75,59 +79,96 @@ define [
 			$scope.pdf.view = 'errors'
 			$scope.pdf.renderingError = true
 
-		autoCompileTimeout = null
+		autoCompileInterval = null
+		autoCompileIfReady = () ->
+			if $scope.pdf.compiling
+				return
+
+			# Only checking linting if syntaxValidation is on and visible to the user
+			autoCompileLintingError = ide.$scope.hasLintingError and ide.$scope.settings.syntaxValidation
+			if $scope.autoCompileLintingError != autoCompileLintingError
+				$scope.$apply () ->
+					$scope.autoCompileLintingError = autoCompileLintingError
+					# We've likely been waiting a while until the user fixed the linting, but we
+					# don't want to compile as soon as it is fixed, so reset the timeout.
+					$scope.startedTryingAutoCompileAt = Date.now()
+					$scope.docLastChangedAt = Date.now()
+			if autoCompileLintingError
+				return
+
+			# If there's a longish compile, don't compile immediately after if user is still typing
+			startedTryingAt = Math.max($scope.startedTryingAutoCompileAt, $scope.lastFinishedCompileAt || 0)
+
+			timeSinceStartedTrying = Date.now() - startedTryingAt
+			timeSinceLastChange = Date.now() - $scope.docLastChangedAt
+
+			shouldCompile = false
+			if timeSinceLastChange > AUTO_COMPILE_DEBOUNCE # Don't compile in the middle of the user typing
+				shouldCompile = true
+			else if timeSinceStartedTrying > AUTO_COMPILE_MAX_WAIT # Unless they type for a long time
+				shouldCompile = true
+			else if timeSinceStartedTrying < 0 or timeSinceLastChange < 0
+				# If time is non-monotonic, assume that the user's system clock has been
+				# changed and continue with compile
+				shouldCompile = true
+
+			if shouldCompile
+				triggerAutoCompile()
+
 		triggerAutoCompile = () ->
-			return if autoCompileTimeout or $scope.ui.pdfHidden
-
-			timeSinceLastCompile = Date.now() - $scope.recompiledAt
-			# If time is non-monotonic, assume that the user's system clock has been
-			# changed and continue with recompile
-			isTimeNonMonotonic = timeSinceLastCompile < 0
-
-			if isTimeNonMonotonic || timeSinceLastCompile >= AUTO_COMPILE_TIMEOUT
-				# If user has code check disabled, it is likely because they have
-				# linting errors that they are ignoring. Therefore it doesn't make sense
-				# to block auto compiles. It also causes problems where server-provided
-				# linting errors aren't cleared after typing
-				if (ide.$scope.settings.syntaxValidation and !ide.$scope.hasLintingError)
-					$scope.recompile(isAutoCompileOnChange: true) # compile if no linting errors
-				else if !ide.$scope.settings.syntaxValidation
-					$scope.recompile(isAutoCompileOnChange: true) # always recompile
-				else
-					$scope.$apply () ->
-						$scope.autoCompileLintingError = true
+			$scope.recompile(isAutoCompileOnChange: true)
+
+		startTryingAutoCompile = () ->
+			return if autoCompileInterval?
+			$scope.startedTryingAutoCompileAt = Date.now()
+			autoCompileInterval = setInterval autoCompileIfReady, 200
+
+		stopTryingAutoCompile = () ->
+			clearInterval autoCompileInterval
+			autoCompileInterval = null
+
+		$scope.$watch "uncompiledChanges", (uncompiledChanges) ->
+			if uncompiledChanges
+				startTryingAutoCompile()
 			else
-				# Extend remainder of timeout
-				autoCompileTimeout = setTimeout () ->
-					autoCompileTimeout = null
-					triggerAutoCompile()
-				, AUTO_COMPILE_TIMEOUT - timeSinceLastCompile
+				stopTryingAutoCompile()
 
 		$scope.uncompiledChanges = false
 		recalculateUncompiledChanges = () ->
-			if $scope.docLastChanged > $scope.projectLastCompiled
+			if $scope.ui.pdfHidden
+				# Don't both auto-compiling if pdf isn't visible
+				$scope.uncompiledChanges = false
+			else if !$scope.docLastChangedAt?
+				$scope.uncompiledChanges = false
+			else if !$scope.lastStartedCompileAt? or $scope.docLastChangedAt > $scope.lastStartedCompileAt
 				$scope.uncompiledChanges = true
 			else
 				$scope.uncompiledChanges = false
 
+		_updateDocLastChangedAt = () ->
+			$scope.docLastChangedAt = Date.now()
+			recalculateUncompiledChanges()
+
 		onDocChanged = () ->
 			$scope.autoCompileLintingError = false
-			$scope.docLastChanged = Date.now()
-			recalculateUncompiledChanges()
+			_updateDocLastChangedAt()
+
+		onDocSaved = () ->
+			# We use the save as a trigger too, to account for the delay between the client
+			# and server. Otherwise, we might have compiled after the user made
+			# the change on the client, but before the server had it.
+			_updateDocLastChangedAt()
 
 		onCompilingStateChanged = (compiling) ->
-			if compiling
-				$scope.projectLastCompiled = Date.now()
-				recalculateUncompiledChanges()
+			recalculateUncompiledChanges()
 
 		autoCompileListeners = []
 		toggleAutoCompile = (enabling) ->
 			if enabling
 				autoCompileListeners = [
 					ide.$scope.$on "doc:changed", onDocChanged
-					ide.$scope.$on "doc:saved", onDocChanged
+					ide.$scope.$on "doc:saved", onDocSaved
 					$scope.$watch "pdf.compiling", onCompilingStateChanged
-					ide.$scope.$on "ide:opAcknowledged", _.debounce(triggerAutoCompile, OP_ACKNOWLEDGEMENT_TIMEOUT)
 				]
 			else
 				for unbind in autoCompileListeners
@@ -455,6 +496,7 @@ define [
 
 			event_tracking.sendMBSampled "editor-recompile-sampled", options
 
+			$scope.lastStartedCompileAt = Date.now()
 			$scope.pdf.compiling = true
 			$scope.pdf.isAutoCompileOnLoad = options?.isAutoCompileOnLoad # initial autocompile
 
@@ -472,6 +514,8 @@ define [
 
 			options.rootDocOverride_id = getRootDocOverride_id()
 
+			$scope.$apply()
+
 			sendCompileRequest(options)
 				.then (response) ->
 					{ data } = response
@@ -487,7 +531,7 @@ define [
 					$scope.pdf.error = true
 					$scope.pdf.view = 'errors'
 				.finally () ->
-					$scope.recompiledAt = Date.now()
+					$scope.lastFinishedCompileAt = Date.now()
 
 		# This needs to be public.
 		ide.$scope.recompile = $scope.recompile