diff --git a/app/src/main/java/net/gsantner/markor/format/AutoFormatter.java b/app/src/main/java/net/gsantner/markor/format/AutoFormatter.java
index 8ce8bedfa..c2108071e 100644
--- a/app/src/main/java/net/gsantner/markor/format/AutoFormatter.java
+++ b/app/src/main/java/net/gsantner/markor/format/AutoFormatter.java
@@ -11,6 +11,7 @@
import android.annotation.SuppressLint;
import android.text.Editable;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import net.gsantner.opoc.util.StringUtils;
@@ -236,7 +237,7 @@ public UnOrderedOrCheckListLine(CharSequence text, int position, PrefixPatterns
* @param position Position within current line
* @return OrderedListLine corresponding to top of current list
*/
- private static OrderedListLine getOrderedListStart(final Editable text, int position, final PrefixPatterns prefixPatterns) {
+ private static OrderedListLine getOrderedListStart(final CharSequence text, int position, final PrefixPatterns prefixPatterns) {
position = Math.max(Math.min(position, text.length() - 1), 0);
OrderedListLine listStart = new OrderedListLine(text, position, prefixPatterns);
@@ -261,14 +262,18 @@ private static OrderedListLine getOrderedListStart(final Editable text, int posi
*
* This is an unfortunately complex + complicated function. Tweak at your peril and test a *lot* :)
*/
- public static void renumberOrderedList(final Editable text, int cursorPosition, final PrefixPatterns prefixPatterns) {
+ public static void renumberOrderedList(final Editable edit, int cursorPosition, final PrefixPatterns prefixPatterns) {
// Top of list
- final OrderedListLine firstLine = getOrderedListStart(text, cursorPosition, prefixPatterns);
+ final OrderedListLine firstLine = getOrderedListStart(edit, cursorPosition, prefixPatterns);
if (!firstLine.isOrderedList) {
return;
}
+ // Copy all the text if we are going to process
+ // SpannableStringBuilder makes the spans _appear_ to transition smoothly
+ final Editable text = new SpannableStringBuilder(edit);
+
// Stack represents each level in the list up from current
final Stack levels = new Stack<>();
levels.push(firstLine);
@@ -277,6 +282,7 @@ public static void renumberOrderedList(final Editable text, int cursorPosition,
int position;
try {
+ boolean madeChange = false;
// Loop to end of list
while (firstLine.isParentLevelOf(line) || firstLine.isMatchingList(line)) {
@@ -312,7 +318,9 @@ else if (!line.isEmpty) {
final OrderedListLine peek = levels.peek();
final String newValue = line.equals(peek) ? "1" : getNextOrderedValue(peek.value);
if (!newValue.equals(line.value)) {
+
text.replace(line.numStart, line.numEnd, newValue);
+ madeChange = true;
// Re-create line as it has changed
line = new OrderedListLine(text, line.lineStart, prefixPatterns);
@@ -329,6 +337,13 @@ else if (!line.isEmpty) {
break;
}
}
+
+ // Replace the text in Editable in one chunk
+ if (madeChange) {
+ final int[] diff = StringUtils.findDiff(edit, text);
+ edit.replace(diff[0], diff[1], text.subSequence(diff[0], diff[2]));
+ }
+
} catch (EmptyStackException ex) {
// Usually means that indents and de-indents did not match up
ex.printStackTrace();
diff --git a/app/src/main/java/net/gsantner/markor/ui/hleditor/HighlightingEditor.java b/app/src/main/java/net/gsantner/markor/ui/hleditor/HighlightingEditor.java
index f03ed0a97..7b388c1ea 100644
--- a/app/src/main/java/net/gsantner/markor/ui/hleditor/HighlightingEditor.java
+++ b/app/src/main/java/net/gsantner/markor/ui/hleditor/HighlightingEditor.java
@@ -167,17 +167,28 @@ public void removeTextChangedListener(final TextWatcher listener) {
_activeListeners.remove(listener);
}
+ // Run some code with accessibility disabled
+ public void withAccessibilityDisabled(final Callback.a0 callback) {
+ try {
+ _accessibilityEnabled = false;
+ callback.callback();
+ } finally {
+ _accessibilityEnabled = true;
+ }
+ }
+
// Run some code with auto formatters disabled
+ // Also disables accessibility
public void withAutoFormatDisabled(final Callback.a0 callback) {
if (getAutoFormatEnabled()) {
try {
setAutoFormatEnabled(false);
- callback.callback();
+ withAccessibilityDisabled(() -> callback.callback());
} finally {
setAutoFormatEnabled(true);
}
} else {
- callback.callback();
+ withAccessibilityDisabled(() -> callback.callback());
}
}
@@ -213,16 +224,14 @@ private void highlightWithoutChange() {
if (MainActivity.IS_DEBUG_ENABLED) {
AppSettings.appendDebugLog("Start highlighting");
}
- setAccessibilityEnabled(false);
- _hl.run(getText());
+ withAccessibilityDisabled(() ->_hl.run(getText()));
} catch (Exception e) {
// In no case ever let highlighting crash the editor
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
- } finally {
- setAccessibilityEnabled(true);
}
+
if (MainActivity.IS_DEBUG_ENABLED) {
AppSettings.appendDebugLog(_hl._profiler.resetDebugText());
AppSettings.appendDebugLog("Finished highlighting");
@@ -323,12 +332,4 @@ protected void onSelectionChanged(int selStart, int selEnd) {
AppSettings.appendDebugLog("Selection changed: " + selStart + "->" + selEnd);
}
}
-
- public void setAccessibilityEnabled(final boolean enabled) {
- _accessibilityEnabled = enabled;
- }
-
- public boolean getAccessibilityEnabled() {
- return _accessibilityEnabled;
- }
}
diff --git a/app/src/main/java/net/gsantner/markor/ui/hleditor/TextActions.java b/app/src/main/java/net/gsantner/markor/ui/hleditor/TextActions.java
index 9f68b042f..01fa29d35 100644
--- a/app/src/main/java/net/gsantner/markor/ui/hleditor/TextActions.java
+++ b/app/src/main/java/net/gsantner/markor/ui/hleditor/TextActions.java
@@ -388,15 +388,10 @@ public static void runRegexReplaceAction(final EditText editor, final ReplacePat
* @param matchAll Whether to stop matching subsequent ReplacePatterns after first match+replace
*/
public static void runRegexReplaceAction(final EditText editor, final List patterns, final boolean matchAll) {
- try {
- if (editor instanceof HighlightingEditor) {
- ((HighlightingEditor) editor).setAccessibilityEnabled(false);
- }
+ if (editor instanceof HighlightingEditor) {
+ ((HighlightingEditor) editor).withAutoFormatDisabled(() -> _runRegexReplaceAction(editor, patterns, matchAll));
+ } else {
_runRegexReplaceAction(editor, patterns, matchAll);
- } finally {
- if (editor instanceof HighlightingEditor) {
- ((HighlightingEditor) editor).setAccessibilityEnabled(true);
- }
}
}
@@ -772,15 +767,7 @@ public final void runRenumberOrderedListIfRequired() {
public final void runRenumberOrderedListIfRequired(final boolean force) {
if (force || _hlEditor.getAutoFormatEnabled()) {
- final boolean isAccessibilityEnabled = _hlEditor.getAccessibilityEnabled();
- try {
- _hlEditor.setAccessibilityEnabled(false);
- _hlEditor.withAutoFormatDisabled(() -> renumberOrderedList(StringUtils.getSelection(_hlEditor)[0]));
- } finally {
- if (isAccessibilityEnabled) {
- _hlEditor.setAccessibilityEnabled(true);
- }
- }
+ _hlEditor.withAutoFormatDisabled(() -> renumberOrderedList(StringUtils.getSelection(_hlEditor)[0]));
}
}
diff --git a/app/src/main/java/net/gsantner/opoc/util/StringUtils.java b/app/src/main/java/net/gsantner/opoc/util/StringUtils.java
index d91d9fa0d..8a51d4267 100644
--- a/app/src/main/java/net/gsantner/opoc/util/StringUtils.java
+++ b/app/src/main/java/net/gsantner/opoc/util/StringUtils.java
@@ -452,4 +452,30 @@ public static String interpolateEscapedDateTime(final String snip) {
interpolated.append(temp); // Remaining text
return interpolated.toString();
}
+
+ // Find the smallest single difference region { a, b, c }
+ // s.t. setting dest[a:b] = source[a:c] makes dest == source
+ public static int[] findDiff(final CharSequence dest, final CharSequence source) {
+
+ final int dl = dest.length(), sl = source.length();
+ final int minLength = Math.min(dl, sl);
+
+ int start = 0;
+ while(start < minLength && source.charAt(start) == dest.charAt(start)) start++;
+
+ // Handle several special cases
+ if (sl == dl && start == sl) { // Case where 2 sequences are same
+ return new int[] { sl, sl, sl };
+ } else if (sl < dl && start == sl) { // Pure crop
+ return new int[] { sl, dl, sl };
+ } else if (dl < sl && start == dl) { // Pure append
+ return new int[] { dl, dl, sl };
+ }
+
+ int end = 0;
+ final int maxEnd = minLength - start;
+ while(end < maxEnd && source.charAt(sl - end - 1) == dest.charAt(dl - end - 1)) end++;
+
+ return new int[] { start, dl - end, sl - end };
+ }
}
diff --git a/app/src/test/java/net/gsantner/markor/StringUtilsTest.java b/app/src/test/java/net/gsantner/markor/StringUtilsTest.java
new file mode 100644
index 000000000..9d9d539a7
--- /dev/null
+++ b/app/src/test/java/net/gsantner/markor/StringUtilsTest.java
@@ -0,0 +1,29 @@
+package net.gsantner.markor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import net.gsantner.opoc.util.StringUtils;
+
+import org.junit.Test;
+
+public class StringUtilsTest {
+
+ @Test
+ public void findDiffTest() {
+ assertThat(StringUtils.findDiff("", "")).isEqualTo(new int[] {0, 0, 0});
+ assertThat(StringUtils.findDiff("abcd", "abcd")).isEqualTo(new int[] {4, 4, 4});
+ assertThat(StringUtils.findDiff("ab", "abcd")).isEqualTo(new int[] {2, 2, 4});
+ assertThat(StringUtils.findDiff("abcd", "ab")).isEqualTo(new int[] {2, 4, 2});
+ assertThat(StringUtils.findDiff("ab1d", "ab2d")).isEqualTo(new int[] {2, 3, 3});
+ assertThat(StringUtils.findDiff("ab12d", "ab34d")).isEqualTo(new int[] {2, 4, 4});
+ assertThat(StringUtils.findDiff("ab12d", "ab3d")).isEqualTo(new int[] {2, 4, 3});
+ assertThat(StringUtils.findDiff("ab12d", "abd")).isEqualTo(new int[] {2, 4, 2});
+ assertThat(StringUtils.findDiff("abd", "ab12d")).isEqualTo(new int[] {2, 2, 4});
+ assertThat(StringUtils.findDiff("abcd", "")).isEqualTo(new int[] {0, 4, 0});
+ assertThat(StringUtils.findDiff("", "abcd")).isEqualTo(new int[] {0, 0, 4});
+ assertThat(StringUtils.findDiff("ab11d", "ab1d")).isEqualTo(new int[] {3, 4, 3});
+ assertThat(StringUtils.findDiff("aaaaa", "aaa")).isEqualTo(new int[] {3, 5, 3});
+ assertThat(StringUtils.findDiff("aaa", "aaaaa")).isEqualTo(new int[] {3, 3, 5});
+ }
+}
+