pull/1395/head
parent
2bc94c0f7b
commit
3f5ded2c4e
@ -1,449 +1,422 @@ |
||||
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 androidx.annotation.ColorInt; |
||||
import androidx.annotation.NonNull; |
||||
import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView; |
||||
|
||||
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(); |
||||
} |
||||
|
||||
public CodeView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
initEditorView(); |
||||
} |
||||
|
||||
public CodeView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
initEditorView(); |
||||
} |
||||
|
||||
private void initEditorView() { |
||||
package io.legado.app.ui.widget.code |
||||
|
||||
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 |
||||
|
||||
@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('{', '+', '-', '*', '/', '=') |
||||
|
||||
constructor(context: Context?) : super(context!!) { |
||||
initEditorView() |
||||
} |
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super( |
||||
context!!, attrs |
||||
) { |
||||
initEditorView() |
||||
} |
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( |
||||
context!!, attrs, defStyleAttr |
||||
) { |
||||
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); |
||||
} |
||||
|
||||
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; |
||||
|
||||
source |
||||
} |
||||
) |
||||
addTextChangedListener(mEditorTextWatcher) |
||||
} |
||||
|
||||
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 void createBackgroundColorSpan(Editable editable, Matcher matcher, @ColorInt int color) { |
||||
editable.setSpan(new BackgroundColorSpan(color), |
||||
matcher.start(), matcher.end(), |
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
||||
} |
||||
|
||||
private Editable highlight(Editable editable) { |
||||
if (editable.length() == 0) return editable; |
||||
|
||||
private fun createForegroundColorSpan( |
||||
editable: Editable, |
||||
matcher: Matcher, |
||||
@ColorInt color: Int |
||||
) { |
||||
editable.setSpan( |
||||
ForegroundColorSpan(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 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]); |
||||
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 cancelHighlighterRender() { |
||||
mUpdateHandler.removeCallbacks(mUpdateRunnable); |
||||
fun 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 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 setSyntaxPatternsMap(Map<Pattern, Integer> syntaxPatterns) { |
||||
if (!mSyntaxPatternMap.isEmpty()) mSyntaxPatternMap.clear(); |
||||
mSyntaxPatternMap.putAll(syntaxPatterns); |
||||
fun setSyntaxPatternsMap(syntaxPatterns: Map<Pattern, Int>?) { |
||||
if (mSyntaxPatternMap.isNotEmpty()) mSyntaxPatternMap.clear() |
||||
mSyntaxPatternMap.putAll(syntaxPatterns!!) |
||||
} |
||||
|
||||
public void addSyntaxPattern(Pattern pattern, @ColorInt int Color) { |
||||
mSyntaxPatternMap.put(pattern, Color); |
||||
fun addSyntaxPattern(pattern: Pattern, @ColorInt Color: Int) { |
||||
mSyntaxPatternMap[pattern] = Color |
||||
} |
||||
|
||||
public void removeSyntaxPattern(Pattern pattern) { |
||||
mSyntaxPatternMap.remove(pattern); |
||||
fun removeSyntaxPattern(pattern: Pattern) { |
||||
mSyntaxPatternMap.remove(pattern) |
||||
} |
||||
|
||||
public int getSyntaxPatternsSize() { |
||||
return mSyntaxPatternMap.size(); |
||||
fun getSyntaxPatternsSize(): Int { |
||||
return mSyntaxPatternMap.size |
||||
} |
||||
|
||||
public void resetSyntaxPatternList() { |
||||
mSyntaxPatternMap.clear(); |
||||
fun resetSyntaxPatternList() { |
||||
mSyntaxPatternMap.clear() |
||||
} |
||||
|
||||
public void setAutoIndentCharacterList(List<Character> characterList) { |
||||
mIndentCharacterList = characterList; |
||||
fun setAutoIndentCharacterList(characterList: MutableList<Char>) { |
||||
mIndentCharacterList = characterList |
||||
} |
||||
|
||||
public void clearAutoIndentCharacterList() { |
||||
mIndentCharacterList.clear(); |
||||
fun clearAutoIndentCharacterList() { |
||||
mIndentCharacterList.clear() |
||||
} |
||||
|
||||
public List<Character> getAutoIndentCharacterList() { |
||||
return mIndentCharacterList; |
||||
fun getAutoIndentCharacterList(): List<Char> { |
||||
return mIndentCharacterList |
||||
} |
||||
|
||||
public void addErrorLine(int lineNum, int color) { |
||||
mErrorHashSet.put(lineNum, color); |
||||
hasErrors = true; |
||||
fun addErrorLine(lineNum: Int, color: Int) { |
||||
mErrorHashSet[lineNum] = color |
||||
hasErrors = true |
||||
} |
||||
|
||||
public void removeErrorLine(int lineNum) { |
||||
mErrorHashSet.remove(lineNum); |
||||
hasErrors = mErrorHashSet.size() > 0; |
||||
fun removeErrorLine(lineNum: Int) { |
||||
mErrorHashSet.remove(lineNum) |
||||
hasErrors = mErrorHashSet.size > 0 |
||||
} |
||||
|
||||
public void removeAllErrorLines() { |
||||
mErrorHashSet.clear(); |
||||
hasErrors = false; |
||||
fun removeAllErrorLines() { |
||||
mErrorHashSet.clear() |
||||
hasErrors = false |
||||
} |
||||
|
||||
public int getErrorsSize() { |
||||
return mErrorHashSet.size(); |
||||
fun getErrorsSize(): Int { |
||||
return mErrorHashSet.size |
||||
} |
||||
|
||||
public String getTextWithoutTrailingSpace() { |
||||
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; |
||||
} |
||||
|
||||
private inner class TabWidthSpan : ReplacementSpan() { |
||||
override fun getSize( |
||||
paint: Paint, |
||||
text: CharSequence, |
||||
start: Int, |
||||
end: Int, |
||||
fm: FontMetricsInt? |
||||
): Int { |
||||
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) { |
||||
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; |
||||
|
||||
import android.widget.MultiAutoCompleteTextView; |
||||
|
||||
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; |
||||
package io.legado.app.ui.widget.code |
||||
|
||||
import android.widget.MultiAutoCompleteTextView |
||||
import kotlin.math.max |
||||
|
||||
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