From e9b0ce8e04552f6839cd5e353343a6d984d5027e Mon Sep 17 00:00:00 2001
From: "Cheng-Han, Wu" <jackymaxj@gmail.com>
Date: Fri, 27 May 2016 00:12:07 +0800
Subject: [PATCH] Update to improve sync scroll to edit performance and fix
 sync scroll behavior on many situations

---
 public/js/index.js      | 41 ++++++++++++++++++++++-----
 public/js/syncscroll.js | 62 ++++++++++++++++++++++++-----------------
 2 files changed, 70 insertions(+), 33 deletions(-)

diff --git a/public/js/index.js b/public/js/index.js
index 29e2297..832e0d1 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -722,8 +722,11 @@ function windowResizeInner(callback) {
     if (loaded) {
         if (editor.getOption('scrollbarStyle') === 'native') {
             clearMap();
-            syncScrollToView();
-            syncScrollToEdit();
+            if (editorHasFocus()) {
+                syncScrollToView();
+            } else {
+                syncScrollToEdit();
+            }
             updateScrollspy();
             if (callback && typeof callback === 'function')
                 callback();
@@ -732,8 +735,11 @@ function windowResizeInner(callback) {
             editor.setOption('viewportMargin', Infinity);
             setTimeout(function () {
                 clearMap();
-                syncScrollToView();
-                syncScrollToEdit();
+                if (editorHasFocus()) {
+                    syncScrollToView();
+                } else {
+                    syncScrollToEdit();
+                }
                 editor.setOption('viewportMargin', viewportMargin);
                 //add or update user cursors
                 for (var i = 0; i < onlineUsers.length; i++) {
@@ -774,6 +780,7 @@ function checkResponsive() {
 }
 
 var lastEditorWidth = 0;
+var previousFocusOnEditor = null;
 
 function checkEditorStyle() {
     var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height();
@@ -806,6 +813,11 @@ function checkEditorStyle() {
     }
     if (!ui.area.resize.syncToggle.length) {
         ui.area.resize.syncToggle = $('<button class="btn btn-lg btn-default ui-sync-toggle" title="Toggle sync scrolling"><i class="fa fa-link fa-fw"></i></button>');
+        ui.area.resize.syncToggle.hover(function () {
+            previousFocusOnEditor = editorHasFocus();
+        }, function () {
+            previousFocusOnEditor = null;
+        });
         ui.area.resize.syncToggle.click(function () {
             syncscroll = !syncscroll;
             checkSyncToggle();
@@ -822,6 +834,13 @@ function checkEditorStyle() {
 
 function checkSyncToggle() {
     if (syncscroll) {
+        if (previousFocusOnEditor) {
+            preventSyncScrollToView = false;
+            syncScrollToView();
+        } else {
+            preventSyncScrollToEdit = false;
+            syncScrollToEdit();
+        }
         ui.area.resize.syncToggle.find('i').removeClass('fa-unlink').addClass('fa-link');
     } else {
         ui.area.resize.syncToggle.find('i').removeClass('fa-link').addClass('fa-unlink');
@@ -1003,9 +1022,14 @@ function changeMode(type) {
     restoreInfo();
 
     if (lastMode == modeType.view && currentMode == modeType.both) {
-        preventSyncScrollToView = true;
+        preventSyncScrollToView = 2;
         syncScrollToEdit();
     }
+    
+    if (lastMode == modeType.edit && currentMode == modeType.both) {
+        preventSyncScrollToEdit = 2;
+        syncScrollToView();
+    }
 
     if (lastMode != modeType.edit && currentMode == modeType.edit) {
         editor.refresh();
@@ -2595,8 +2619,11 @@ function updateViewInner() {
     clearMap();
     //buildMap();
     updateTitleReminder();
-    syncScrollToView();
-    syncScrollToEdit();
+    if (editorHasFocus()) {
+        syncScrollToView();
+    } else {
+        syncScrollToEdit();
+    }
 }
 
 var updateHistoryDebounce = 600;
diff --git a/public/js/syncscroll.js b/public/js/syncscroll.js
index 348af2d..9d0bbe0 100644
--- a/public/js/syncscroll.js
+++ b/public/js/syncscroll.js
@@ -110,17 +110,15 @@ var syncscroll = true;
 var preventSyncScrollToEdit = false;
 var preventSyncScrollToView = false;
 
-var editScrollThrottle = 1;
+var editScrollThrottle = 2;
 var viewScrollThrottle = 10;
 var buildMapThrottle = 100;
 
 var viewScrolling = false;
-var viewScrollingDelay = 200;
-var viewScrollingTimer = null;
+var viewScrollingDebounce = 200;
 
 var editScrolling = false;
-var editScrollingDelay = 100;
-var editScrollingTimer = null;
+var editScrollingDebounce = 200;
 
 if (editor.getOption('scrollbarStyle') === 'native') {
     ui.area.codemirrorScroll.on('scroll', _.throttle(syncScrollToView, editScrollThrottle));
@@ -145,7 +143,7 @@ var buildMap = _.throttle(buildMapInner, buildMapThrottle);
 // Build offsets for each line (lines can be wrapped)
 // That's a bit dirty to process each line everytime, but ok for demo.
 // Optimizations are required only for big texts.
-function buildMapInner(syncBack) {
+function buildMapInner(callback) {
     var i, offset, nonEmptyList, pos, a, b, _lineHeightMap, linesCount,
         acc, _scrollMap;
 
@@ -217,12 +215,15 @@ function buildMapInner(syncBack) {
     scrollMap = _scrollMap;
     lineHeightMap = _lineHeightMap;
 
-    if (loaded && syncBack) {
-        syncScrollToView();
-        syncScrollToEdit();
-    }
+    if (loaded && callback) callback();
 }
 
+// sync view scroll progress to edit 
+var viewScrollingTimeout = _.debounce(viewScrollingTimeoutInner, viewScrollingDebounce);
+
+function viewScrollingTimeoutInner() {
+    viewScrolling = false;
+}
 
 function syncScrollToEdit(e) {
     if (currentMode != modeType.both || !syncscroll) return;
@@ -235,7 +236,7 @@ function syncScrollToEdit(e) {
         return;
     }
     if (!scrollMap || !lineHeightMap) {
-        buildMap(true);
+        buildMap(syncScrollToEdit);
         return;
     }
     if (editScrolling) return;
@@ -272,21 +273,30 @@ function syncScrollToEdit(e) {
     if (scrollInfo.height > scrollInfo.clientHeight && scrollTop >= preLastLinePos) {
         posTo = preLastLineHeight;
         topDiffPercent = (scrollTop - preLastLinePos) / (viewBottom - preLastLinePos);
-        posToNextDiff = Math.ceil(textHeight * topDiffPercent);
+        posToNextDiff = textHeight * topDiffPercent;
+        posTo += Math.floor(posToNextDiff);
     } else {
         posTo = lineNo * textHeight;
         topDiffPercent = (scrollTop - scrollMap[lineNo]) / (scrollMap[lineNo + lineDiff] - scrollMap[lineNo]);
-        posToNextDiff = Math.ceil(textHeight * lineDiff * topDiffPercent);
+        posToNextDiff = textHeight * lineDiff * topDiffPercent;
+        posTo += Math.floor(posToNextDiff);
     }
     
-    editor.scrollTo(0, posTo + posToNextDiff);
-    preventSyncScrollToView = true;
+    var posDiff = Math.abs(scrollInfo.top - posTo);
+    var duration = posDiff / 50;
+    ui.area.codemirrorScroll.stop(true, true).animate({
+        scrollTop: posTo
+    }, duration >= 100 ? duration : 100, "linear");
     
     viewScrolling = true;
-    clearTimeout(viewScrollingTimer);
-    viewScrollingTimer = setTimeout(function () {
-        viewScrolling = false;
-    }, viewScrollingDelay);
+    viewScrollingTimeout();
+}
+
+// sync edit scroll progress to view
+var editScrollingTimeout = _.debounce(editScrollingTimeoutInner, editScrollingDebounce);
+
+function editScrollingTimeoutInner() {
+    editScrolling = false;
 }
 
 function syncScrollToView(event, _lineNo) {
@@ -300,7 +310,7 @@ function syncScrollToView(event, _lineNo) {
         return;
     }
     if (!scrollMap || !lineHeightMap) {
-        buildMap(true);
+        buildMap(syncScrollToView);
         return;
     }
     if (viewScrolling) return;
@@ -328,12 +338,12 @@ function syncScrollToView(event, _lineNo) {
         posTo = scrollMap[lineHeightMap[_lineNo]];
     }
     
-    ui.area.view.stop(true, true).scrollTop(posTo);
-    preventSyncScrollToEdit = true;
+    var posDiff = Math.abs(ui.area.view.scrollTop() - posTo);
+    var duration = posDiff / 50;
+    ui.area.view.stop(true, true).animate({
+        scrollTop: posTo
+    }, duration >= 100 ? duration : 100, "linear");
     
     editScrolling = true;
-    clearTimeout(editScrollingTimer);
-    editScrollingTimer = setTimeout(function () {
-        editScrolling = false;
-    }, editScrollingDelay);
+    editScrollingTimeout();
 }
\ No newline at end of file