mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
优化
This commit is contained in:
parent
2bc94c0f7b
commit
3f5ded2c4e
@ -1,449 +1,422 @@
|
||||
package io.legado.app.ui.widget.code;
|
||||
package io.legado.app.ui.widget.code
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Paint.FontMetricsInt
|
||||
import android.graphics.Rect
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.*
|
||||
import android.text.style.BackgroundColorSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.ReplacementSpan
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView
|
||||
import java.util.*
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView;
|
||||
@Suppress("unused")
|
||||
class CodeView : AppCompatMultiAutoCompleteTextView {
|
||||
private var tabWidth = 0
|
||||
private var tabWidthInCharacters = 0
|
||||
private var mUpdateDelayTime = 500
|
||||
private var modified = true
|
||||
private var highlightWhileTextChanging = true
|
||||
private var hasErrors = false
|
||||
private var mRemoveErrorsWhenTextChanged = true
|
||||
private val mUpdateHandler = Handler(Looper.getMainLooper())
|
||||
private var mAutoCompleteTokenizer: Tokenizer? = null
|
||||
private val displayDensity = resources.displayMetrics.density
|
||||
private val mErrorHashSet: SortedMap<Int, Int> = TreeMap()
|
||||
private val mSyntaxPatternMap: MutableMap<Pattern, Int> = HashMap()
|
||||
private var mIndentCharacterList = mutableListOf('{', '+', '-', '*', '/', '=')
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class CodeView extends AppCompatMultiAutoCompleteTextView {
|
||||
|
||||
private int tabWidth;
|
||||
private int tabWidthInCharacters;
|
||||
private int mUpdateDelayTime = 500;
|
||||
|
||||
private boolean modified = true;
|
||||
private boolean highlightWhileTextChanging = true;
|
||||
|
||||
private boolean hasErrors = false;
|
||||
private boolean mRemoveErrorsWhenTextChanged = true;
|
||||
|
||||
private final Handler mUpdateHandler = new Handler();
|
||||
private Tokenizer mAutoCompleteTokenizer;
|
||||
private final float displayDensity = getResources().getDisplayMetrics().density;
|
||||
|
||||
private static final Pattern PATTERN_LINE = Pattern.compile("(^.+$)+", Pattern.MULTILINE);
|
||||
private static final Pattern PATTERN_TRAILING_WHITE_SPACE = Pattern.compile("[\\t ]+$", Pattern.MULTILINE);
|
||||
|
||||
private final SortedMap<Integer, Integer> mErrorHashSet = new TreeMap<>();
|
||||
private final Map<Pattern, Integer> mSyntaxPatternMap = new HashMap<>();
|
||||
private List<Character> mIndentCharacterList = Arrays.asList('{', '+', '-', '*', '/', '=');
|
||||
|
||||
public CodeView(Context context) {
|
||||
super(context);
|
||||
initEditorView();
|
||||
constructor(context: Context?) : super(context!!) {
|
||||
initEditorView()
|
||||
}
|
||||
|
||||
public CodeView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initEditorView();
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(
|
||||
context!!, attrs
|
||||
) {
|
||||
initEditorView()
|
||||
}
|
||||
|
||||
public CodeView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initEditorView();
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context!!, attrs, defStyleAttr
|
||||
) {
|
||||
initEditorView()
|
||||
}
|
||||
|
||||
private void initEditorView() {
|
||||
private fun initEditorView() {
|
||||
if (mAutoCompleteTokenizer == null) {
|
||||
mAutoCompleteTokenizer = new KeywordTokenizer();
|
||||
mAutoCompleteTokenizer = KeywordTokenizer()
|
||||
}
|
||||
|
||||
setTokenizer(mAutoCompleteTokenizer);
|
||||
|
||||
setFilters(new InputFilter[]{
|
||||
new InputFilter() {
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end,
|
||||
Spanned dest, int dstart, int dend) {
|
||||
if (modified &&
|
||||
end - start == 1 &&
|
||||
start < source.length() &&
|
||||
dstart < dest.length()) {
|
||||
char c = source.charAt(start);
|
||||
|
||||
if (c == '\n') {
|
||||
return autoIndent(source, dest, dstart, dend);
|
||||
}
|
||||
}
|
||||
return source;
|
||||
setTokenizer(mAutoCompleteTokenizer)
|
||||
filters = arrayOf(
|
||||
InputFilter { source, start, end, dest, dStart, dEnd ->
|
||||
if (modified && end - start == 1 && start < source.length && dStart < dest.length) {
|
||||
val c = source[start]
|
||||
if (c == '\n') {
|
||||
return@InputFilter autoIndent(source, dest, dStart, dEnd)
|
||||
}
|
||||
}
|
||||
});
|
||||
addTextChangedListener(mEditorTextWatcher);
|
||||
source
|
||||
}
|
||||
)
|
||||
addTextChangedListener(mEditorTextWatcher)
|
||||
}
|
||||
|
||||
private CharSequence autoIndent(CharSequence source, Spanned dest, int dstart, int dend) {
|
||||
String indent = "";
|
||||
int istart = dstart - 1;
|
||||
|
||||
boolean dataBefore = false;
|
||||
int pt = 0;
|
||||
|
||||
for (; istart > -1; --istart) {
|
||||
char c = dest.charAt(istart);
|
||||
|
||||
if (c == '\n') break;
|
||||
|
||||
private fun autoIndent(
|
||||
source: CharSequence,
|
||||
dest: Spanned,
|
||||
dStart: Int,
|
||||
dEnd: Int
|
||||
): CharSequence {
|
||||
var indent = ""
|
||||
var iStart = dStart - 1
|
||||
var dataBefore = false
|
||||
var pt = 0
|
||||
while (iStart > -1) {
|
||||
val c = dest[iStart]
|
||||
if (c == '\n') break
|
||||
if (c != ' ' && c != '\t') {
|
||||
if (!dataBefore) {
|
||||
if (mIndentCharacterList.contains(c)) --pt;
|
||||
dataBefore = true;
|
||||
if (mIndentCharacterList.contains(c)) --pt
|
||||
dataBefore = true
|
||||
}
|
||||
|
||||
if (c == '(') {
|
||||
--pt;
|
||||
--pt
|
||||
} else if (c == ')') {
|
||||
++pt;
|
||||
++pt
|
||||
}
|
||||
}
|
||||
--iStart
|
||||
}
|
||||
|
||||
if (istart > -1) {
|
||||
char charAtCursor = dest.charAt(dstart);
|
||||
int iend;
|
||||
|
||||
for (iend = ++istart; iend < dend; ++iend) {
|
||||
char c = dest.charAt(iend);
|
||||
|
||||
if (charAtCursor != '\n' &&
|
||||
c == '/' &&
|
||||
iend + 1 < dend &&
|
||||
dest.charAt(iend) == c) {
|
||||
iend += 2;
|
||||
break;
|
||||
if (iStart > -1) {
|
||||
val charAtCursor = dest[dStart]
|
||||
var iEnd: Int = ++iStart
|
||||
while (iEnd < dEnd) {
|
||||
val c = dest[iEnd]
|
||||
if (charAtCursor != '\n' && c == '/' && iEnd + 1 < dEnd && dest[iEnd] == c) {
|
||||
iEnd += 2
|
||||
break
|
||||
}
|
||||
|
||||
if (c != ' ' && c != '\t') {
|
||||
break;
|
||||
break
|
||||
}
|
||||
++iEnd
|
||||
}
|
||||
|
||||
indent += dest.subSequence(istart, iend);
|
||||
indent += dest.subSequence(iStart, iEnd)
|
||||
}
|
||||
|
||||
if (pt < 0) {
|
||||
indent += "\t";
|
||||
indent += "\t"
|
||||
}
|
||||
|
||||
return source + indent;
|
||||
return source.toString() + indent
|
||||
}
|
||||
|
||||
private void highlightSyntax(Editable editable) {
|
||||
if (mSyntaxPatternMap.isEmpty()) return;
|
||||
|
||||
for (Pattern pattern : mSyntaxPatternMap.keySet()) {
|
||||
int color = mSyntaxPatternMap.get(pattern);
|
||||
for (Matcher m = pattern.matcher(editable); m.find(); ) {
|
||||
createForegroundColorSpan(editable, m, color);
|
||||
private fun highlightSyntax(editable: Editable) {
|
||||
if (mSyntaxPatternMap.isEmpty()) return
|
||||
for (pattern in mSyntaxPatternMap.keys) {
|
||||
val color = mSyntaxPatternMap[pattern]!!
|
||||
val m = pattern.matcher(editable)
|
||||
while (m.find()) {
|
||||
createForegroundColorSpan(editable, m, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void highlightErrorLines(Editable editable) {
|
||||
if (mErrorHashSet.isEmpty()) return;
|
||||
int maxErrorLineValue = mErrorHashSet.lastKey();
|
||||
|
||||
int lineNumber = 0;
|
||||
Matcher matcher = PATTERN_LINE.matcher(editable);
|
||||
private fun highlightErrorLines(editable: Editable) {
|
||||
if (mErrorHashSet.isEmpty()) return
|
||||
val maxErrorLineValue = mErrorHashSet.lastKey()
|
||||
var lineNumber = 0
|
||||
val matcher = PATTERN_LINE.matcher(editable)
|
||||
while (matcher.find()) {
|
||||
if (mErrorHashSet.containsKey(lineNumber)) {
|
||||
int color = mErrorHashSet.get(lineNumber);
|
||||
createBackgroundColorSpan(editable, matcher, color);
|
||||
val color = mErrorHashSet[lineNumber]!!
|
||||
createBackgroundColorSpan(editable, matcher, color)
|
||||
}
|
||||
lineNumber = lineNumber + 1;
|
||||
if (lineNumber > maxErrorLineValue) break;
|
||||
lineNumber += 1
|
||||
if (lineNumber > maxErrorLineValue) break
|
||||
}
|
||||
}
|
||||
|
||||
private void createForegroundColorSpan(Editable editable, Matcher matcher, @ColorInt int color) {
|
||||
editable.setSpan(new ForegroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
private fun createForegroundColorSpan(
|
||||
editable: Editable,
|
||||
matcher: Matcher,
|
||||
@ColorInt color: Int
|
||||
) {
|
||||
editable.setSpan(
|
||||
ForegroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
private void createBackgroundColorSpan(Editable editable, Matcher matcher, @ColorInt int color) {
|
||||
editable.setSpan(new BackgroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
private fun createBackgroundColorSpan(
|
||||
editable: Editable,
|
||||
matcher: Matcher,
|
||||
@ColorInt color: Int
|
||||
) {
|
||||
editable.setSpan(
|
||||
BackgroundColorSpan(color),
|
||||
matcher.start(), matcher.end(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
private Editable highlight(Editable editable) {
|
||||
if (editable.length() == 0) return editable;
|
||||
|
||||
private fun highlight(editable: Editable): Editable {
|
||||
if (editable.isEmpty()) return editable
|
||||
try {
|
||||
clearSpans(editable);
|
||||
highlightErrorLines(editable);
|
||||
highlightSyntax(editable);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
clearSpans(editable)
|
||||
highlightErrorLines(editable)
|
||||
highlightSyntax(editable)
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return editable;
|
||||
return editable
|
||||
}
|
||||
|
||||
private void highlightWithoutChange(Editable editable) {
|
||||
modified = false;
|
||||
highlight(editable);
|
||||
modified = true;
|
||||
private fun highlightWithoutChange(editable: Editable) {
|
||||
modified = false
|
||||
highlight(editable)
|
||||
modified = true
|
||||
}
|
||||
|
||||
public void setTextHighlighted(CharSequence text) {
|
||||
if (text == null || text.length() == 0) return;
|
||||
|
||||
cancelHighlighterRender();
|
||||
|
||||
removeAllErrorLines();
|
||||
|
||||
modified = false;
|
||||
setText(highlight(new SpannableStringBuilder(text)));
|
||||
modified = true;
|
||||
fun setTextHighlighted(text: CharSequence?) {
|
||||
if (text.isNullOrEmpty()) return
|
||||
cancelHighlighterRender()
|
||||
removeAllErrorLines()
|
||||
modified = false
|
||||
setText(highlight(SpannableStringBuilder(text)))
|
||||
modified = true
|
||||
}
|
||||
|
||||
public void setTabWidth(int characters) {
|
||||
if (tabWidthInCharacters == characters) return;
|
||||
tabWidthInCharacters = characters;
|
||||
tabWidth = Math.round(getPaint().measureText("m") * characters);
|
||||
fun setTabWidth(characters: Int) {
|
||||
if (tabWidthInCharacters == characters) return
|
||||
tabWidthInCharacters = characters
|
||||
tabWidth = (paint.measureText("m") * characters).roundToInt()
|
||||
}
|
||||
|
||||
private void clearSpans(Editable editable) {
|
||||
int length = editable.length();
|
||||
ForegroundColorSpan[] foregroundSpans = editable.getSpans(
|
||||
0, length, ForegroundColorSpan.class);
|
||||
|
||||
for (int i = foregroundSpans.length; i-- > 0; )
|
||||
editable.removeSpan(foregroundSpans[i]);
|
||||
|
||||
BackgroundColorSpan[] backgroundSpans = editable.getSpans(
|
||||
0, length, BackgroundColorSpan.class);
|
||||
|
||||
for (int i = backgroundSpans.length; i-- > 0; )
|
||||
editable.removeSpan(backgroundSpans[i]);
|
||||
}
|
||||
|
||||
public void cancelHighlighterRender() {
|
||||
mUpdateHandler.removeCallbacks(mUpdateRunnable);
|
||||
}
|
||||
|
||||
private void convertTabs(Editable editable, int start, int count) {
|
||||
if (tabWidth < 1) return;
|
||||
|
||||
String s = editable.toString();
|
||||
|
||||
for (int stop = start + count;
|
||||
(start = s.indexOf("\t", start)) > -1 && start < stop;
|
||||
++start) {
|
||||
editable.setSpan(new TabWidthSpan(),
|
||||
start,
|
||||
start + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
private fun clearSpans(editable: Editable) {
|
||||
val length = editable.length
|
||||
val foregroundSpans = editable.getSpans(
|
||||
0, length, ForegroundColorSpan::class.java
|
||||
)
|
||||
run {
|
||||
var i = foregroundSpans.size
|
||||
while (i-- > 0) {
|
||||
editable.removeSpan(foregroundSpans[i])
|
||||
}
|
||||
}
|
||||
val backgroundSpans = editable.getSpans(
|
||||
0, length, BackgroundColorSpan::class.java
|
||||
)
|
||||
var i = backgroundSpans.size
|
||||
while (i-- > 0) {
|
||||
editable.removeSpan(backgroundSpans[i])
|
||||
}
|
||||
}
|
||||
|
||||
public void setSyntaxPatternsMap(Map<Pattern, Integer> syntaxPatterns) {
|
||||
if (!mSyntaxPatternMap.isEmpty()) mSyntaxPatternMap.clear();
|
||||
mSyntaxPatternMap.putAll(syntaxPatterns);
|
||||
fun cancelHighlighterRender() {
|
||||
mUpdateHandler.removeCallbacks(mUpdateRunnable)
|
||||
}
|
||||
|
||||
public void addSyntaxPattern(Pattern pattern, @ColorInt int Color) {
|
||||
mSyntaxPatternMap.put(pattern, Color);
|
||||
private fun convertTabs(editable: Editable, start: Int, count: Int) {
|
||||
var startIndex = start
|
||||
if (tabWidth < 1) return
|
||||
val s = editable.toString()
|
||||
val stop = startIndex + count
|
||||
while (s.indexOf("\t", startIndex).also { startIndex = it } > -1 && startIndex < stop) {
|
||||
editable.setSpan(
|
||||
TabWidthSpan(),
|
||||
startIndex,
|
||||
startIndex + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
++startIndex
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSyntaxPattern(Pattern pattern) {
|
||||
mSyntaxPatternMap.remove(pattern);
|
||||
fun setSyntaxPatternsMap(syntaxPatterns: Map<Pattern, Int>?) {
|
||||
if (mSyntaxPatternMap.isNotEmpty()) mSyntaxPatternMap.clear()
|
||||
mSyntaxPatternMap.putAll(syntaxPatterns!!)
|
||||
}
|
||||
|
||||
public int getSyntaxPatternsSize() {
|
||||
return mSyntaxPatternMap.size();
|
||||
fun addSyntaxPattern(pattern: Pattern, @ColorInt Color: Int) {
|
||||
mSyntaxPatternMap[pattern] = Color
|
||||
}
|
||||
|
||||
public void resetSyntaxPatternList() {
|
||||
mSyntaxPatternMap.clear();
|
||||
fun removeSyntaxPattern(pattern: Pattern) {
|
||||
mSyntaxPatternMap.remove(pattern)
|
||||
}
|
||||
|
||||
public void setAutoIndentCharacterList(List<Character> characterList) {
|
||||
mIndentCharacterList = characterList;
|
||||
fun getSyntaxPatternsSize(): Int {
|
||||
return mSyntaxPatternMap.size
|
||||
}
|
||||
|
||||
public void clearAutoIndentCharacterList() {
|
||||
mIndentCharacterList.clear();
|
||||
fun resetSyntaxPatternList() {
|
||||
mSyntaxPatternMap.clear()
|
||||
}
|
||||
|
||||
public List<Character> getAutoIndentCharacterList() {
|
||||
return mIndentCharacterList;
|
||||
fun setAutoIndentCharacterList(characterList: MutableList<Char>) {
|
||||
mIndentCharacterList = characterList
|
||||
}
|
||||
|
||||
public void addErrorLine(int lineNum, int color) {
|
||||
mErrorHashSet.put(lineNum, color);
|
||||
hasErrors = true;
|
||||
fun clearAutoIndentCharacterList() {
|
||||
mIndentCharacterList.clear()
|
||||
}
|
||||
|
||||
public void removeErrorLine(int lineNum) {
|
||||
mErrorHashSet.remove(lineNum);
|
||||
hasErrors = mErrorHashSet.size() > 0;
|
||||
fun getAutoIndentCharacterList(): List<Char> {
|
||||
return mIndentCharacterList
|
||||
}
|
||||
|
||||
public void removeAllErrorLines() {
|
||||
mErrorHashSet.clear();
|
||||
hasErrors = false;
|
||||
fun addErrorLine(lineNum: Int, color: Int) {
|
||||
mErrorHashSet[lineNum] = color
|
||||
hasErrors = true
|
||||
}
|
||||
|
||||
public int getErrorsSize() {
|
||||
return mErrorHashSet.size();
|
||||
fun removeErrorLine(lineNum: Int) {
|
||||
mErrorHashSet.remove(lineNum)
|
||||
hasErrors = mErrorHashSet.size > 0
|
||||
}
|
||||
|
||||
public String getTextWithoutTrailingSpace() {
|
||||
fun removeAllErrorLines() {
|
||||
mErrorHashSet.clear()
|
||||
hasErrors = false
|
||||
}
|
||||
|
||||
fun getErrorsSize(): Int {
|
||||
return mErrorHashSet.size
|
||||
}
|
||||
|
||||
fun getTextWithoutTrailingSpace(): String {
|
||||
return PATTERN_TRAILING_WHITE_SPACE
|
||||
.matcher(getText())
|
||||
.replaceAll("");
|
||||
.matcher(text)
|
||||
.replaceAll("")
|
||||
}
|
||||
|
||||
public void setAutoCompleteTokenizer(Tokenizer tokenizer) {
|
||||
mAutoCompleteTokenizer = tokenizer;
|
||||
fun setAutoCompleteTokenizer(tokenizer: Tokenizer?) {
|
||||
mAutoCompleteTokenizer = tokenizer
|
||||
}
|
||||
|
||||
public void setRemoveErrorsWhenTextChanged(boolean removeErrors) {
|
||||
mRemoveErrorsWhenTextChanged = removeErrors;
|
||||
fun setRemoveErrorsWhenTextChanged(removeErrors: Boolean) {
|
||||
mRemoveErrorsWhenTextChanged = removeErrors
|
||||
}
|
||||
|
||||
public void reHighlightSyntax() {
|
||||
highlightSyntax(getEditableText());
|
||||
fun reHighlightSyntax() {
|
||||
highlightSyntax(editableText)
|
||||
}
|
||||
|
||||
public void reHighlightErrors() {
|
||||
highlightErrorLines(getEditableText());
|
||||
fun reHighlightErrors() {
|
||||
highlightErrorLines(editableText)
|
||||
}
|
||||
|
||||
public boolean isHasError() {
|
||||
return hasErrors;
|
||||
fun isHasError(): Boolean {
|
||||
return hasErrors
|
||||
}
|
||||
|
||||
public void setUpdateDelayTime(int time) {
|
||||
mUpdateDelayTime = time;
|
||||
fun setUpdateDelayTime(time: Int) {
|
||||
mUpdateDelayTime = time
|
||||
}
|
||||
|
||||
public int getUpdateDelayTime() {
|
||||
return mUpdateDelayTime;
|
||||
fun getUpdateDelayTime(): Int {
|
||||
return mUpdateDelayTime
|
||||
}
|
||||
|
||||
public void setHighlightWhileTextChanging(boolean updateWhileTextChanging) {
|
||||
this.highlightWhileTextChanging = updateWhileTextChanging;
|
||||
fun setHighlightWhileTextChanging(updateWhileTextChanging: Boolean) {
|
||||
highlightWhileTextChanging = updateWhileTextChanging
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDropDown() {
|
||||
int[] screenPoint = new int[2];
|
||||
getLocationOnScreen(screenPoint);
|
||||
|
||||
final Rect displayFrame = new Rect();
|
||||
getWindowVisibleDisplayFrame(displayFrame);
|
||||
|
||||
int position = getSelectionStart();
|
||||
Layout layout = getLayout();
|
||||
int line = layout.getLineForOffset(position);
|
||||
|
||||
float verticalDistanceInDp = (750 + 140 * line) / displayDensity;
|
||||
setDropDownVerticalOffset((int) verticalDistanceInDp);
|
||||
|
||||
float horizontalDistanceInDp = layout.getPrimaryHorizontal(position) / displayDensity;
|
||||
setDropDownHorizontalOffset((int) horizontalDistanceInDp);
|
||||
super.showDropDown();
|
||||
override fun showDropDown() {
|
||||
val screenPoint = IntArray(2)
|
||||
getLocationOnScreen(screenPoint)
|
||||
val displayFrame = Rect()
|
||||
getWindowVisibleDisplayFrame(displayFrame)
|
||||
val position = selectionStart
|
||||
val layout = layout
|
||||
val line = layout.getLineForOffset(position)
|
||||
val verticalDistanceInDp = (750 + 140 * line) / displayDensity
|
||||
dropDownVerticalOffset = verticalDistanceInDp.toInt()
|
||||
val horizontalDistanceInDp = layout.getPrimaryHorizontal(position) / displayDensity
|
||||
dropDownHorizontalOffset = horizontalDistanceInDp.toInt()
|
||||
super.showDropDown()
|
||||
}
|
||||
|
||||
private final Runnable mUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Editable source = getText();
|
||||
highlightWithoutChange(source);
|
||||
}
|
||||
};
|
||||
|
||||
private final TextWatcher mEditorTextWatcher = new TextWatcher() {
|
||||
|
||||
private int start;
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
private val mUpdateRunnable = Runnable {
|
||||
val source = text
|
||||
highlightWithoutChange(source)
|
||||
}
|
||||
private val mEditorTextWatcher: TextWatcher = object : TextWatcher {
|
||||
private var start = 0
|
||||
private var count = 0
|
||||
override fun beforeTextChanged(
|
||||
charSequence: CharSequence,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {
|
||||
this.start = start
|
||||
this.count = count
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (!modified) return;
|
||||
|
||||
override fun onTextChanged(
|
||||
charSequence: CharSequence,
|
||||
start: Int,
|
||||
before: Int,
|
||||
count: Int
|
||||
) {
|
||||
if (!modified) return
|
||||
if (highlightWhileTextChanging) {
|
||||
if (mSyntaxPatternMap.size() > 0) {
|
||||
convertTabs(getEditableText(), start, count);
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime);
|
||||
if (mSyntaxPatternMap.isNotEmpty()) {
|
||||
convertTabs(editableText, start, count)
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
if (mRemoveErrorsWhenTextChanged) removeAllErrorLines();
|
||||
if (mRemoveErrorsWhenTextChanged) removeAllErrorLines()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
if (!highlightWhileTextChanging) {
|
||||
if (!modified) return;
|
||||
|
||||
cancelHighlighterRender();
|
||||
|
||||
if (mSyntaxPatternMap.size() > 0) {
|
||||
convertTabs(getEditableText(), start, count);
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime);
|
||||
if (!modified) return
|
||||
cancelHighlighterRender()
|
||||
if (mSyntaxPatternMap.isNotEmpty()) {
|
||||
convertTabs(editableText, start, count)
|
||||
mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final class TabWidthSpan extends ReplacementSpan {
|
||||
|
||||
@Override
|
||||
public int getSize(
|
||||
@NonNull Paint paint,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
Paint.FontMetricsInt fm) {
|
||||
return tabWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(
|
||||
@NonNull Canvas canvas,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
float x,
|
||||
int top,
|
||||
int y,
|
||||
int bottom,
|
||||
@NonNull Paint paint) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class TabWidthSpan : ReplacementSpan() {
|
||||
override fun getSize(
|
||||
paint: Paint,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
fm: FontMetricsInt?
|
||||
): Int {
|
||||
return tabWidth
|
||||
}
|
||||
|
||||
override fun draw(
|
||||
canvas: Canvas,
|
||||
text: CharSequence,
|
||||
start: Int,
|
||||
end: Int,
|
||||
x: Float,
|
||||
top: Int,
|
||||
y: Int,
|
||||
bottom: Int,
|
||||
paint: Paint
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PATTERN_LINE = Pattern.compile("(^.+$)+", Pattern.MULTILINE)
|
||||
private val PATTERN_TRAILING_WHITE_SPACE = Pattern.compile("[\\t ]+$", Pattern.MULTILINE)
|
||||
}
|
||||
}
|
@ -1,31 +1,25 @@
|
||||
package io.legado.app.ui.widget.code;
|
||||
package io.legado.app.ui.widget.code
|
||||
|
||||
import android.widget.MultiAutoCompleteTextView;
|
||||
import android.widget.MultiAutoCompleteTextView
|
||||
import kotlin.math.max
|
||||
|
||||
public class KeywordTokenizer implements MultiAutoCompleteTextView.Tokenizer {
|
||||
|
||||
@Override
|
||||
public int findTokenStart(CharSequence charSequence, int cursor) {
|
||||
String sequenceStr = charSequence.toString();
|
||||
sequenceStr = sequenceStr.substring(0, cursor);
|
||||
|
||||
int spaceIndex = sequenceStr.lastIndexOf(" ");
|
||||
int lineIndex = sequenceStr.lastIndexOf("\n");
|
||||
int bracketIndex = sequenceStr.lastIndexOf("(");
|
||||
|
||||
int index = Math.max(0, Math.max(spaceIndex, Math.max(lineIndex, bracketIndex)));
|
||||
if (index == 0) return 0;
|
||||
return (index + 1 < charSequence.length()) ? index + 1 : index;
|
||||
class KeywordTokenizer : MultiAutoCompleteTextView.Tokenizer {
|
||||
override fun findTokenStart(charSequence: CharSequence, cursor: Int): Int {
|
||||
var sequenceStr = charSequence.toString()
|
||||
sequenceStr = sequenceStr.substring(0, cursor)
|
||||
val spaceIndex = sequenceStr.lastIndexOf(" ")
|
||||
val lineIndex = sequenceStr.lastIndexOf("\n")
|
||||
val bracketIndex = sequenceStr.lastIndexOf("(")
|
||||
val index = max(0, max(spaceIndex, max(lineIndex, bracketIndex)))
|
||||
if (index == 0) return 0
|
||||
return if (index + 1 < charSequence.length) index + 1 else index
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findTokenEnd(CharSequence charSequence, int cursor) {
|
||||
return charSequence.length();
|
||||
override fun findTokenEnd(charSequence: CharSequence, cursor: Int): Int {
|
||||
return charSequence.length
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence terminateToken(CharSequence charSequence) {
|
||||
return charSequence;
|
||||
override fun terminateToken(charSequence: CharSequence): CharSequence {
|
||||
return charSequence
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user