Skip to content

Commit

Permalink
add support for scroll sync
Browse files Browse the repository at this point in the history
  • Loading branch information
guanglinn committed Sep 3, 2024
1 parent 92b30e1 commit 454fdf6
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 30 deletions.
68 changes: 68 additions & 0 deletions app/src/main/assets/scroll-sync/scroll-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Scroll to the target element by source line number (generally the first visible line number).
* Params: lineNumber - the line number of source code in editor.
*/
function editor2Preview(lineNumber) {
let range = 0;
let direction = 0;
let number = lineNumber;
while (number > 0) {
if (direction > 0) {
number = lineNumber + range;
direction = -1;
} else if (direction < 0) {
number = lineNumber - range;
direction = 1;
range++;
} else {
direction = 1;
range++;
}

let elements = document.querySelectorAll("[data-line='" + number + "']");
if (elements == null) {
continue;
}

let completed = false;
for (let i = 0; i < elements.length; i++) {
let element = elements[i];
if (element.offsetHeight > 0) {
element.scrollIntoView();
completed = true;
break;
}
}

if (completed) {
break;
}
}
}

/**
* Find the target line number, that is the value of data-line attribute of target element (generally the first visible element).
* Params: lineNumber - as a base position.
* Return: 0 if the line number found is around lineNumber (no need to adjust for this minor scrolling), or the target element cannot not be found.
*/
function preview2Editor(lineNumber) {
let elements = document.querySelectorAll("[data-line]");
if (elements == null || elements.length == 0) {
return 0;
}

for (let i = 0; i < elements.length; i++) {
let element = elements[i];
let top = element.getBoundingClientRect().top;
let bottom = element.getBoundingClientRect().bottom;
if (top > 0 && bottom < window.innerHeight) {
let number = parseInt(element.getAttribute("data-line"));
if (number > lineNumber - 3 && number < lineNumber + 3) {
return 0;
} else {
return number;
}
}
}
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@
import net.gsantner.opoc.util.GsContextUtils;
import net.gsantner.opoc.util.GsCoolExperimentalStuff;
import net.gsantner.opoc.web.GsWebViewChromeClient;
import net.gsantner.opoc.web.GsWebViewClient;
import net.gsantner.opoc.wrapper.GsTextWatcherAdapter;

import java.io.File;
import java.util.Collections;

@SuppressWarnings({"UnusedReturnValue"})
@SuppressLint("NonConstantResourceId")
public class DocumentEditAndViewFragment extends MarkorBaseFragment implements FormatRegistry.TextFormatApplier {
public static final String FRAGMENT_TAG = "DocumentEditAndViewFragment";
public static final String SAVESTATE_DOCUMENT = "DOCUMENT";
public static final String SAVE_STATE_DOCUMENT = "DOCUMENT";
public static final String START_PREVIEW = "START_PREVIEW";

public static DocumentEditAndViewFragment newInstance(final @NonNull Document document, final Integer lineNumber, final Boolean preview) {
Expand Down Expand Up @@ -102,6 +104,7 @@ public static DocumentEditAndViewFragment newInstance(final @NonNull Document do
private MenuItem _saveMenuItem, _undoMenuItem, _redoMenuItem;
private boolean _isPreviewVisible;
private boolean _nextConvertToPrintMode = false;
private int _firstVisibleLineNumber = 1;


public DocumentEditAndViewFragment() {
Expand All @@ -112,8 +115,8 @@ public DocumentEditAndViewFragment() {
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
if (savedInstanceState != null && savedInstanceState.containsKey(SAVESTATE_DOCUMENT)) {
_document = (Document) savedInstanceState.getSerializable(SAVESTATE_DOCUMENT);
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_STATE_DOCUMENT)) {
_document = (Document) savedInstanceState.getSerializable(SAVE_STATE_DOCUMENT);
} else if (args != null && args.containsKey(Document.EXTRA_DOCUMENT)) {
_document = (Document) args.get(Document.EXTRA_DOCUMENT);
}
Expand Down Expand Up @@ -152,6 +155,16 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
}

_webViewClient = new MarkorWebViewClient(_webView, activity);
_webViewClient.setOnPageFinishedListener(new GsWebViewClient.OnPageFinishedListener() {
@Override
public void onPageFinished(WebView v) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return;
}
_firstVisibleLineNumber = _hlEditor.getFirstVisibleLineNumber();
_webView.evaluateJavascript("editor2Preview(" + _firstVisibleLineNumber + ");", null);
}
});
_webView.setWebChromeClient(new GsWebViewChromeClient(_webView, activity, view.findViewById(R.id.document__fragment_fullscreen_overlay)));
_webView.setWebViewClient(_webViewClient);
_webView.addJavascriptInterface(this, "Android");
Expand Down Expand Up @@ -293,7 +306,7 @@ public void onPause() {

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putSerializable(SAVESTATE_DOCUMENT, _document);
outState.putSerializable(SAVE_STATE_DOCUMENT, _document);
super.onSaveInstanceState(outState);
}

Expand Down Expand Up @@ -858,17 +871,30 @@ public void setViewModeVisibility(boolean show, final boolean animate) {
if (show) {
updateViewModeText();
_cu.showSoftKeyboard(activity, false, _hlEditor);
_hlEditor.clearFocus();
_hlEditor.postDelayed(() -> _cu.showSoftKeyboard(activity, false, _hlEditor), 300);
_webView.requestFocus();
GsContextUtils.fadeInOut(_webView, _primaryScrollView, animate);
} else {
_webViewClient.setRestoreScrollY(_webView.getScrollY());
// _webViewClient.setRestoreScrollY(_webView.getScrollY());
_hlEditor.requestFocus();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
_webView.evaluateJavascript("preview2Editor(" + _firstVisibleLineNumber + ");", result -> {
Log.i("AAA", "preview2Editor result: " + result);
if (result.startsWith("null")) {
return;
}
if (Character.isDigit(result.charAt(0))) {
final int lineNumber = Integer.parseInt(result);
if (lineNumber > 0) {
TextViewUtils.selectLines(_hlEditor, false, Collections.singletonList(lineNumber));
}
}
});
}
GsContextUtils.fadeInOut(_primaryScrollView, _webView, animate);
}

_nextConvertToPrintMode = false;
_isPreviewVisible = show;

((AppCompatActivity) activity).supportInvalidateOptionsMenu();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Handler;
import android.text.Editable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.content.Context;
import android.net.Uri;
import android.text.format.DateFormat;
import android.util.Log;
import android.webkit.WebView;

import androidx.annotation.NonNull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ public String convertMarkup(String markup, Context context, boolean lightMode, b
onLoadJs += "enableLineNumbers(); adjustLineNumbers();";
}

// For scroll sync
head += JS_PREFIX + "scroll-sync/scroll-sync.js" + JS_POSTFIX;

// Deliver result
return putContentIntoTemplate(context, converted, lightMode, file, onLoadJs, head);
}
Expand Down Expand Up @@ -386,15 +389,15 @@ private String getViewHlPrismIncludes(final String theme, final boolean isLineNu
sb.append(CSS_PREFIX + "prism/plugins/toolbar/prism-toolbar.css" + CSS_POSTFIX);

sb.append(JS_PREFIX + "prism/prism.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/main.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/util.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/autoloader/prism-autoloader.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/toolbar/prism-toolbar.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/copy-to-clipboard/prism-copy-to-clipboard.js" + JS_POSTFIX);

if (isLineNumbersEnabled) {
sb.append(CSS_PREFIX + "prism/plugins/line-numbers/style.css" + CSS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/prism-line-numbers.min.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/main.js" + JS_POSTFIX);
sb.append(JS_PREFIX + "prism/plugins/line-numbers/util.js" + JS_POSTFIX);
}

return sb.toString();
Expand Down Expand Up @@ -457,8 +460,8 @@ private static class LineNumberIdProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, AttributablePart part, Attributes attributes) {
final Document document = node.getDocument();
final int lineNumber = document.getLineNumber(node.getStartOffset());
attributes.addValue("line", "" + lineNumber);
final int lineNumber = document.getLineNumber(node.getStartOffset()) + 1;
attributes.addValue("data-line", "" + lineNumber);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@

import net.gsantner.markor.ApplicationObject;
import net.gsantner.markor.R;
import net.gsantner.markor.format.markdown.MarkdownTextConverter;
import net.gsantner.markor.format.todotxt.TodoTxtBasicSyntaxHighlighter;
import net.gsantner.markor.format.todotxt.TodoTxtFilter;
import net.gsantner.markor.format.todotxt.TodoTxtTask;
Expand Down Expand Up @@ -844,7 +843,7 @@ public static void showHeadlineDialog(
final int line = headings.get(index).line;

TextViewUtils.selectLines(edit, line);
final String jumpJs = "document.querySelector('[line=\"" + line + "\"]').scrollIntoView();";
final String jumpJs = "document.querySelector(\"[data-line='" + line + "']\").scrollIntoView();";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript(jumpJs, null);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,34 @@ private int rowEnd(final int y) {
return layout.getLineEnd(line);
}

public int getFirstVisibleLineNumber() {
final Rect visibleRect = new Rect();
if (!getLocalVisibleRect(visibleRect)) {
return 0;
}

final CharSequence text = getText();
final Layout layout = getLayout();
if (text == null || layout == null) {
return 0;
}

// Calculate the first visible line number
final int count = layout.getLineCount();
for (int i = 0, number = 1; i < count; i++) {
final int start = layout.getLineStart(i);
if (start == 0 || text.charAt(start - 1) == '\n') {
final int y = layout.getLineBottom(i);
if (y > visibleRect.top) {
return number - 1;
}
number++;
}
}

return 0;
}

// Various overrides
// ---------------------------------------------------------------------------------------------
public void setSaveInstanceState(final boolean save) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,6 @@ public static int getIndexFromLineOffset(final CharSequence s, final int l, fina
return i;
}


public static void selectLines(final EditText edit, final Integer... positions) {
selectLines(edit, Arrays.asList(positions));
}

/**
* Select the given indices.
* Case 1: Only one index -> Put cursor on that line
Expand All @@ -263,14 +258,14 @@ public static void selectLines(final EditText edit, final Integer... positions)
*
* @param positions: Line indices to select
*/
public static void selectLines(final EditText edit, final List<Integer> positions) {
public static void selectLines(final EditText edit, final boolean setSelection, final List<Integer> positions) {
if (!edit.hasFocus()) {
edit.requestFocus();
}
final CharSequence text = edit.getText();
if (positions.size() == 1) { // Case 1 index
final int posn = TextViewUtils.getIndexFromLineOffset(text, positions.get(0), 0);
setSelectionAndShow(edit, posn);
final int sel = TextViewUtils.getIndexFromLineOffset(text, positions.get(0), 0);
setSelectionAndShow(edit, setSelection, sel);
} else if (positions.size() > 1) {
final TreeSet<Integer> pSet = new TreeSet<>(positions);
final int selStart, selEnd;
Expand All @@ -294,12 +289,19 @@ public static void selectLines(final EditText edit, final List<Integer> position
}
}

public static void showSelection(final TextView text) {
showSelection(text, text.getSelectionStart(), text.getSelectionEnd());
public static void selectLines(final EditText edit, final List<Integer> positions) {
selectLines(edit, true, positions);
}

public static void showSelection(final TextView text, final int start, final int end) {
public static void selectLines(final EditText edit, final Integer... positions) {
selectLines(edit, Arrays.asList(positions));
}

public static void selectLines(final EditText edit, final boolean setSelection, final Integer... positions) {
selectLines(edit, setSelection, Arrays.asList(positions));
}

public static void showSelection(final TextView text, final int start, final int end) {
// Get view info
// ------------------------------------------------------------
final Layout layout = text.getLayout();
Expand Down Expand Up @@ -351,7 +353,11 @@ public static void showSelection(final TextView text, final int start, final int
text.post(() -> text.requestRectangleOnScreen(region));
}

public static void setSelectionAndShow(final EditText edit, final int... sel) {
public static void showSelection(final TextView text) {
showSelection(text, text.getSelectionStart(), text.getSelectionEnd());
}

public static void setSelectionAndShow(final EditText edit, boolean setSelection, final int... sel) {
if (sel == null || sel.length == 0) {
return;
}
Expand All @@ -364,13 +370,18 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) {
if (!edit.hasFocus() && edit.getVisibility() != View.GONE) {
edit.requestFocus();
}

edit.setSelection(start, end);
if (setSelection) {
edit.setSelection(start, end);
}
edit.postDelayed(() -> showSelection(edit, start, end), 250);
});
}
}

public static void setSelectionAndShow(final EditText edit, final int... sel) {
setSelectionAndShow(edit, true, sel);
}

/**
* Snippets are evaluated in the following order:
* 1. {{*}} style placeholders are replaced (except {{cursor}})
Expand Down
Loading

0 comments on commit 454fdf6

Please sign in to comment.