From 7866a91d212f9fd255b39d90e3042f89197125f8 Mon Sep 17 00:00:00 2001 From: James Allen Date: Thu, 8 Feb 2018 13:32:15 +0000 Subject: [PATCH] Refactor auto-compile trigger logic --- .../ide/pdf/controllers/PdfController.coffee | 114 ++++++++++++------ 1 file changed, 79 insertions(+), 35 deletions(-) diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 57fa2bee83..1145625db4 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/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 + $scope.recompile(isAutoCompileOnChange: true) - 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 + startTryingAutoCompile = () -> + return if autoCompileInterval? + $scope.startedTryingAutoCompileAt = Date.now() + autoCompileInterval = setInterval autoCompileIfReady, 200 - 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 + 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 - onDocChanged = () -> - $scope.autoCompileLintingError = false - $scope.docLastChanged = Date.now() + _updateDocLastChangedAt = () -> + $scope.docLastChangedAt = Date.now() recalculateUncompiledChanges() + onDocChanged = () -> + $scope.autoCompileLintingError = false + _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