6.7.0 - Alpha19 - 修复代码编辑器加载大文件可能导致应用崩溃的问题, 一定程度提升大文件加载流畅度
This commit is contained in:
@@ -99,6 +99,8 @@
|
|||||||
"部分设备代码编辑器空行显示方框字符的问题 (试修)",
|
"部分设备代码编辑器空行显示方框字符的问题 (试修)",
|
||||||
"代码编辑器在只读模式下依然可以编辑代码内容的问题",
|
"代码编辑器在只读模式下依然可以编辑代码内容的问题",
|
||||||
"代码编辑器在只读模式下点击标题区域及部分菜单项导致应用崩溃的问题",
|
"代码编辑器在只读模式下点击标题区域及部分菜单项导致应用崩溃的问题",
|
||||||
|
"代码编辑器加载大文件可能导致应用崩溃的问题 (试修)",
|
||||||
|
"代码编辑器自动打开新建文件时功能按钮状态初始化异常",
|
||||||
"ErrorDialogActivity 可能无法正常启动或短时间自动消失的问题 _[`issue #479`](http://issues.autojs6.com/479)_ _[`issue #471`](http://issues.autojs6.com/471)_ _[`issue #414`](http://issues.autojs6.com/414)_ _[`issue #340`](http://issues.autojs6.com/340#issuecomment-2973485826)_",
|
"ErrorDialogActivity 可能无法正常启动或短时间自动消失的问题 _[`issue #479`](http://issues.autojs6.com/479)_ _[`issue #471`](http://issues.autojs6.com/471)_ _[`issue #414`](http://issues.autojs6.com/414)_ _[`issue #340`](http://issues.autojs6.com/340#issuecomment-2973485826)_",
|
||||||
"Canvas 构造函数可接受的参数类型错误 _[`issue #402`](http://issues.autojs6.com/402)_",
|
"Canvas 构造函数可接受的参数类型错误 _[`issue #402`](http://issues.autojs6.com/402)_",
|
||||||
"崩溃报告页面复制详细信息功能失效的问题",
|
"崩溃报告页面复制详细信息功能失效的问题",
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
"文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机",
|
"文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机",
|
||||||
"文件管理器/任务面板支持显示文件/任务数量统计信息",
|
"文件管理器/任务面板支持显示文件/任务数量统计信息",
|
||||||
"代码编辑器保存文件失败时自动存为草稿并支持另存为新文件",
|
"代码编辑器保存文件失败时自动存为草稿并支持另存为新文件",
|
||||||
|
"代码编辑器加载大文件时提升一定程度的流畅度",
|
||||||
"打包应用页面默认勾选必要权限 (WAKE_LOCK/INTERNET/WRITE_EXTERNAL_STORAGE) _[`issue #397`](http://issues.autojs6.com/397)_",
|
"打包应用页面默认勾选必要权限 (WAKE_LOCK/INTERNET/WRITE_EXTERNAL_STORAGE) _[`issue #397`](http://issues.autojs6.com/397)_",
|
||||||
"打包应用设置页面增加前台服务开关 _[`issue #406`](http://issues.autojs6.com/406)_",
|
"打包应用设置页面增加前台服务开关 _[`issue #406`](http://issues.autojs6.com/406)_",
|
||||||
"脚本项目配置文件保存时增加键名冲突检测机制防止键名歧义",
|
"脚本项目配置文件保存时增加键名冲突检测机制防止键名歧义",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,7 @@ import java.util.concurrent.CopyOnWriteArrayList
|
|||||||
/**
|
/**
|
||||||
* Created by Administrator on Feb 11, 2018.
|
* Created by Administrator on Feb 11, 2018.
|
||||||
* Modified by SuperMonster003 as of May 1, 2023.
|
* Modified by SuperMonster003 as of May 1, 2023.
|
||||||
|
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||||
*/
|
*/
|
||||||
class CodeEditText : AppCompatEditText {
|
class CodeEditText : AppCompatEditText {
|
||||||
|
|
||||||
@@ -63,6 +64,16 @@ class CodeEditText : AppCompatEditText {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var mHighlightTokens: HighlightTokens? = null
|
private var mHighlightTokens: HighlightTokens? = null
|
||||||
|
|
||||||
|
// Loading state, used to suppress expensive callbacks during bulk text insertion.
|
||||||
|
// zh-CN: 加载状态, 用于在批量插入文本时抑制高开销回调.
|
||||||
|
@Volatile
|
||||||
|
private var mLoadingText = false
|
||||||
|
|
||||||
|
// Fixed gutter digits used during loading to avoid frequent requestLayout().
|
||||||
|
// zh-CN: 加载期间使用固定 gutter 位数, 避免频繁 requestLayout().
|
||||||
|
@Volatile
|
||||||
|
private var mLoadingGutterDigits: Int = 1
|
||||||
|
|
||||||
private var mTheme: Theme = Theme.getDefault(context)
|
private var mTheme: Theme = Theme.getDefault(context)
|
||||||
private val mLineHighlightPaint = Paint().apply { style = Paint.Style.FILL }
|
private val mLineHighlightPaint = Paint().apply { style = Paint.Style.FILL }
|
||||||
private var mFirstLineForDraw = -1
|
private var mFirstLineForDraw = -1
|
||||||
@@ -118,6 +129,43 @@ class CodeEditText : AppCompatEditText {
|
|||||||
isLongClickable = true
|
isLongClickable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle loading state.
|
||||||
|
// zh-CN: 切换加载状态.
|
||||||
|
fun setLoadingText(loading: Boolean) {
|
||||||
|
mLoadingText = loading
|
||||||
|
if (loading) {
|
||||||
|
applyFixedGutterPaddingForLoading()
|
||||||
|
} else {
|
||||||
|
// Recompute gutter once after loading.
|
||||||
|
// zh-CN: 加载结束后重新计算 gutter(仅一次).
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose loading state for outer components to suppress expensive UI updates.
|
||||||
|
// zh-CN: 对外暴露加载状态, 以便外部组件抑制高开销 UI 更新.
|
||||||
|
fun isLoadingText(): Boolean = mLoadingText
|
||||||
|
|
||||||
|
// Configure a fixed gutter width for loading.
|
||||||
|
// zh-CN: 配置加载期间的固定 gutter 宽度.
|
||||||
|
fun setLoadingGutterDigits(digits: Int) {
|
||||||
|
mLoadingGutterDigits = digits.coerceIn(2, 10)
|
||||||
|
if (mLoadingText) {
|
||||||
|
applyFixedGutterPaddingForLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyFixedGutterPaddingForLoading() {
|
||||||
|
// Pre-allocate gutter width based on digits, e.g., "888888".
|
||||||
|
// zh-CN: 基于位数预分配 gutter 宽度, 例如 "888888".
|
||||||
|
val sample = "8".repeat(mLoadingGutterDigits)
|
||||||
|
val gutterWidth = paint.measureText(sample) + 20
|
||||||
|
if (paddingLeft.toFloat() != gutterWidth) {
|
||||||
|
setPadding(gutterWidth.toInt(), 0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Public API to toggle read-only mode.
|
// Public API to toggle read-only mode.
|
||||||
// zh-CN: 切换只读模式的公开接口.
|
// zh-CN: 切换只读模式的公开接口.
|
||||||
fun setReadOnly(readOnly: Boolean) {
|
fun setReadOnly(readOnly: Boolean) {
|
||||||
@@ -173,7 +221,8 @@ class CodeEditText : AppCompatEditText {
|
|||||||
android.R.id.cut,
|
android.R.id.cut,
|
||||||
android.R.id.paste,
|
android.R.id.paste,
|
||||||
android.R.id.pasteAsPlainText,
|
android.R.id.pasteAsPlainText,
|
||||||
android.R.id.replaceText -> return false
|
android.R.id.replaceText,
|
||||||
|
-> return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.onTextContextMenuItem(id)
|
return super.onTextContextMenuItem(id)
|
||||||
@@ -279,6 +328,12 @@ class CodeEditText : AppCompatEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePaddingForGutter() {
|
private fun updatePaddingForGutter() {
|
||||||
|
// During loading, keep gutter fixed to avoid setPadding() loops and relayout storms.
|
||||||
|
// zh-CN: 加载期间保持 gutter 固定, 避免 setPadding() 循环与频繁 relayout.
|
||||||
|
if (mLoadingText) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 根据行号计算左边距 padding 留出绘制行号的空间
|
// 根据行号计算左边距 padding 留出绘制行号的空间
|
||||||
val max = lineCount.toString()
|
val max = lineCount.toString()
|
||||||
val gutterWidth = paint.measureText(max) + 20
|
val gutterWidth = paint.measureText(max) + 20
|
||||||
@@ -292,7 +347,9 @@ class CodeEditText : AppCompatEditText {
|
|||||||
private fun drawText(canvas: Canvas) {
|
private fun drawText(canvas: Canvas) {
|
||||||
if (mFirstLineForDraw < 0) return
|
if (mFirstLineForDraw < 0) return
|
||||||
|
|
||||||
val textLength = mHighlightTokens?.text?.length ?: 0
|
val highlightTokens = mHighlightTokens
|
||||||
|
val safeText = text ?: return
|
||||||
|
val textLength = highlightTokens?.text?.length ?: 0
|
||||||
val scrollX = (mParentScrollView!!.scrollX + scrollX - paddingLeft).coerceAtLeast(0)
|
val scrollX = (mParentScrollView!!.scrollX + scrollX - paddingLeft).coerceAtLeast(0)
|
||||||
|
|
||||||
for (line in mFirstLineForDraw..lineCount.coerceAtMost(mLastLineForDraw)) {
|
for (line in mFirstLineForDraw..lineCount.coerceAtMost(mLastLineForDraw)) {
|
||||||
@@ -303,7 +360,6 @@ class CodeEditText : AppCompatEditText {
|
|||||||
val lineBottom = layout.getLineTop(lineNumber)
|
val lineBottom = layout.getLineTop(lineNumber)
|
||||||
val lineTop = layout.getLineTop(line)
|
val lineTop = layout.getLineTop(line)
|
||||||
val lineBaseline = lineBottom - layout.getLineDescent(line)
|
val lineBaseline = lineBottom - layout.getLineDescent(line)
|
||||||
val highlightTokens = mHighlightTokens
|
|
||||||
|
|
||||||
// if there is a breakpoint at this line, draw a highlight background for line number
|
// if there is a breakpoint at this line, draw a highlight background for line number
|
||||||
if (breakpoints.containsKey(line)) {
|
if (breakpoints.containsKey(line)) {
|
||||||
@@ -324,15 +380,10 @@ class CodeEditText : AppCompatEditText {
|
|||||||
/* paint = */ paint.apply { color = mTheme.lineNumberColor },
|
/* paint = */ paint.apply { color = mTheme.lineNumberColor },
|
||||||
)
|
)
|
||||||
|
|
||||||
if (highlightTokens == null) continue
|
|
||||||
|
|
||||||
// Draw code
|
|
||||||
|
|
||||||
val lineStart = layout.getLineStart(line)
|
val lineStart = layout.getLineStart(line)
|
||||||
|
|
||||||
// Never draw line-break control characters.
|
// Never draw line-break control characters.
|
||||||
// zh-CN: 永远不要绘制换行等控制字符.
|
// zh-CN: 永远不要绘制换行等控制字符.
|
||||||
val safeText = text ?: continue
|
|
||||||
var lineEnd = layout.getLineEnd(line).coerceAtMost(safeText.length)
|
var lineEnd = layout.getLineEnd(line).coerceAtMost(safeText.length)
|
||||||
while (lineEnd > lineStart) {
|
while (lineEnd > lineStart) {
|
||||||
val ch = safeText[lineEnd - 1]
|
val ch = safeText[lineEnd - 1]
|
||||||
@@ -343,11 +394,28 @@ class CodeEditText : AppCompatEditText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fast path: no syntax highlighting, draw the line once with default color.
|
||||||
|
// zh-CN: 快速路径: 无语法高亮时, 使用默认颜色一次性绘制整行.
|
||||||
|
if (highlightTokens == null) {
|
||||||
|
val visibleCharStart = getVisibleCharIndex(paint, scrollX, lineStart, lineEnd)
|
||||||
|
val visibleCharEnd = (getVisibleCharIndex(paint, scrollX + mParentScrollView!!.width, lineStart, lineEnd) + 1)
|
||||||
|
.coerceAtMost(lineEnd)
|
||||||
|
|
||||||
|
if (visibleCharStart >= visibleCharEnd) continue
|
||||||
|
|
||||||
|
paint.color = mTheme.getColorForToken(Token.NAME)
|
||||||
|
runCatching {
|
||||||
|
val offsetX = paint.measureText(safeText, lineStart, visibleCharStart)
|
||||||
|
canvas.drawText(safeText, visibleCharStart, visibleCharEnd, paddingLeft + offsetX, lineBaseline.toFloat(), paint)
|
||||||
|
}.onFailure { it.printStackTrace() }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if (lineStart >= textLength) continue
|
if (lineStart >= textLength) continue
|
||||||
if (lineEnd > textLength) continue
|
if (lineEnd > textLength) continue
|
||||||
|
|
||||||
// If this is an empty line (or the line only contains line-break chars), skip drawing.
|
// If this is an empty line (or the line only contains line-break chars), skip drawing.
|
||||||
// zh-CN: 如果这是空白行(或该行只包含换行字符), 则跳过绘制.
|
// zh-CN: 如果这是空白行 (或该行只包含换行字符), 则跳过绘制.
|
||||||
if (lineStart >= lineEnd) continue
|
if (lineStart >= lineEnd) continue
|
||||||
|
|
||||||
val localColors = highlightTokens.colors
|
val localColors = highlightTokens.colors
|
||||||
@@ -403,8 +471,8 @@ class CodeEditText : AppCompatEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val offsetX = paint.measureText(currentText, lineStart, previousColorPos)
|
val offsetX = paint.measureText(safeText, lineStart, previousColorPos)
|
||||||
canvas.drawText(currentText, previousColorPos, visibleCharEnd, paddingLeft + offsetX, lineBaseline.toFloat(), paint)
|
canvas.drawText(safeText, previousColorPos, visibleCharEnd, paddingLeft + offsetX, lineBaseline.toFloat(), paint)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -460,6 +528,13 @@ class CodeEditText : AppCompatEditText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
||||||
|
// Skip selection callbacks during bulk loading to keep UI responsive.
|
||||||
|
// zh-CN: 批量加载期间跳过 selection 回调, 以保持 UI 响应.
|
||||||
|
if (mLoadingText) {
|
||||||
|
super.onSelectionChanged(selStart, selEnd)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 调用父类的 onSelectionChanged 时会发送一个 AccessibilityEvent, 当文本过大时造成异常
|
// 调用父类的 onSelectionChanged 时会发送一个 AccessibilityEvent, 当文本过大时造成异常
|
||||||
// super.onSelectionChanged(selStart, selEnd);
|
// super.onSelectionChanged(selStart, selEnd);
|
||||||
// 父类构造函数会调用 onSelectionChanged, 此时 mCursorChangeCallbacks 还没有初始化
|
// 父类构造函数会调用 onSelectionChanged, 此时 mCursorChangeCallbacks 还没有初始化
|
||||||
@@ -524,6 +599,13 @@ class CodeEditText : AppCompatEditText {
|
|||||||
|
|
||||||
fun removeCursorChangeCallback(callback: CursorChangeCallback) = mCursorChangeCallbacks!!.remove(callback)
|
fun removeCursorChangeCallback(callback: CursorChangeCallback) = mCursorChangeCallbacks!!.remove(callback)
|
||||||
|
|
||||||
|
// Clear syntax highlight tokens and redraw with plain text.
|
||||||
|
// zh-CN: 清空语法高亮 tokens, 并用纯文本方式重绘.
|
||||||
|
fun clearHighlightTokens() {
|
||||||
|
mHighlightTokens = null
|
||||||
|
postInvalidate()
|
||||||
|
}
|
||||||
|
|
||||||
fun updateHighlightTokens(highlightTokens: HighlightTokens) {
|
fun updateHighlightTokens(highlightTokens: HighlightTokens) {
|
||||||
if (mHighlightTokens != null && mHighlightTokens!!.id >= highlightTokens.id) {
|
if (mHighlightTokens != null && mHighlightTokens!!.id >= highlightTokens.id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ package org.autojs.autojs.ui.edit.editor
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.SystemClock
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ScaleGestureDetector
|
import android.view.ScaleGestureDetector
|
||||||
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
|
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
|
||||||
|
import android.view.WindowManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.autojs.autojs.core.pref.Pref.getEditorTextSize
|
import org.autojs.autojs.core.pref.Pref.getEditorTextSize
|
||||||
@@ -53,7 +57,7 @@ import kotlin.math.floor
|
|||||||
/**
|
/**
|
||||||
* Transformed by SuperMonster003 on Jul 16, 2023.
|
* Transformed by SuperMonster003 on Jul 16, 2023.
|
||||||
* Modified by SuperMonster003 as of Jul 16, 2023.
|
* Modified by SuperMonster003 as of Jul 16, 2023.
|
||||||
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 6, 2026.
|
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||||
*/
|
*/
|
||||||
class CodeEditor : HVScrollView {
|
class CodeEditor : HVScrollView {
|
||||||
|
|
||||||
@@ -65,6 +69,15 @@ class CodeEditor : HVScrollView {
|
|||||||
ThemeColorHelper.setThemeColorPrimary(it, true)
|
ThemeColorHelper.setThemeColorPrimary(it, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether the user is interacting with the editor via touch.
|
||||||
|
// zh-CN: 用户是否正在通过触摸与编辑器交互.
|
||||||
|
@Volatile
|
||||||
|
private var mUserTouching = false
|
||||||
|
|
||||||
|
// Public getter for streaming loader to prioritize scroll responsiveness.
|
||||||
|
// zh-CN: 给流式加载器使用的公开读取口, 用于优先保障滚动响应性.
|
||||||
|
fun isUserTouching(): Boolean = mUserTouching
|
||||||
|
|
||||||
val lineCount
|
val lineCount
|
||||||
get() = Observable.just(codeEditText.layout.lineCount)
|
get() = Observable.just(codeEditText.layout.lineCount)
|
||||||
|
|
||||||
@@ -146,6 +159,46 @@ class CodeEditor : HVScrollView {
|
|||||||
private var mFoundIndex = -1
|
private var mFoundIndex = -1
|
||||||
private var mLastScaleFactor = 1.0
|
private var mLastScaleFactor = 1.0
|
||||||
|
|
||||||
|
private val mUiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
// Delay showing the loading dialog to avoid flicker for fast operations.
|
||||||
|
// zh-CN: 延迟显示加载对话框, 避免快速操作产生闪烁.
|
||||||
|
private val mProgressShowDelayMs = 1500L
|
||||||
|
|
||||||
|
// Once shown, keep the dialog visible for at least this duration to avoid flash.
|
||||||
|
// zh-CN: 对话框一旦出现, 至少显示一段时间, 避免一闪而过.
|
||||||
|
private val mProgressMinShowMs = 500L
|
||||||
|
|
||||||
|
private var mProgressRequested = false
|
||||||
|
private var mProgressShownAtMs = 0L
|
||||||
|
private var mProgressInteractive = false
|
||||||
|
|
||||||
|
private val mShowProgressRunnable = Runnable {
|
||||||
|
if (!mProgressRequested) return@Runnable
|
||||||
|
if (mProcessDialog?.isShowing == true) return@Runnable
|
||||||
|
|
||||||
|
mProcessDialog = MaterialDialog.Builder(context)
|
||||||
|
.content(R.string.text_processing)
|
||||||
|
// Text only, no progress spinner.
|
||||||
|
// zh-CN: 仅显示文字, 不使用进度圆圈动画.
|
||||||
|
.cancelable(false)
|
||||||
|
.canceledOnTouchOutside(false)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
mProgressShownAtMs = SystemClock.uptimeMillis()
|
||||||
|
|
||||||
|
// Make it non-modal (optional) so user can scroll/view during loading.
|
||||||
|
// zh-CN: 可选地设置为非模态, 使用户在加载时仍可滚动/查看.
|
||||||
|
if (mProgressInteractive) {
|
||||||
|
mProcessDialog?.window?.let { w ->
|
||||||
|
w.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||||
|
w.setDimAmount(0f)
|
||||||
|
w.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
||||||
|
w.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(context: Context?) : super(context)
|
constructor(context: Context?) : super(context)
|
||||||
|
|
||||||
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
|
||||||
@@ -154,6 +207,92 @@ class CodeEditor : HVScrollView {
|
|||||||
applyScaleGesture()
|
applyScaleGesture()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshHighlightTokensIfAllowed() {
|
||||||
|
// Force a highlight refresh after bulk loading, when loading flags are cleared.
|
||||||
|
// zh-CN: 在批量加载结束且 loading 标记已清除后, 主动触发一次高亮刷新.
|
||||||
|
val t = codeEditText.text?.toString() ?: return
|
||||||
|
mJavaScriptHighlighter.updateTokens(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide a "processing" indicator.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Delayed show to avoid flicker.
|
||||||
|
* - Minimum show time once visible.
|
||||||
|
* - Optional interactive mode: don't block touches and don't dim background.
|
||||||
|
*
|
||||||
|
* zh-CN:
|
||||||
|
* 显示或隐藏 "处理中" 提示.
|
||||||
|
*
|
||||||
|
* 行为:
|
||||||
|
* - 延迟显示以避免闪烁.
|
||||||
|
* - 一旦出现则保证最短展示时间.
|
||||||
|
* - 可选交互模式: 不拦截触摸/不压暗背景.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Show or hide a "processing" indicator.
|
||||||
|
*
|
||||||
|
* Behavior:
|
||||||
|
* - Delayed show to avoid flicker.
|
||||||
|
* - Minimum show time once visible.
|
||||||
|
* - Optional interactive mode: don't block touches and don't dim background.
|
||||||
|
*
|
||||||
|
* zh-CN:
|
||||||
|
* 显示或隐藏 "处理中" 提示.
|
||||||
|
*
|
||||||
|
* 行为:
|
||||||
|
* - 延迟显示以避免闪烁.
|
||||||
|
* - 一旦出现则保证最短展示时间.
|
||||||
|
* - 可选交互模式: 不拦截触摸/不压暗背景.
|
||||||
|
*/
|
||||||
|
fun setProgress(progress: Boolean, interactive: Boolean = false) {
|
||||||
|
mProgressInteractive = interactive
|
||||||
|
|
||||||
|
if (progress) {
|
||||||
|
mProgressRequested = true
|
||||||
|
|
||||||
|
// If already showing, keep it.
|
||||||
|
// zh-CN: 若已显示则保持不变.
|
||||||
|
if (mProcessDialog?.isShowing == true) return
|
||||||
|
|
||||||
|
// Schedule delayed show.
|
||||||
|
// zh-CN: 延迟调度显示.
|
||||||
|
mUiHandler.removeCallbacks(mShowProgressRunnable)
|
||||||
|
mUiHandler.postDelayed(mShowProgressRunnable, mProgressShowDelayMs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide requested.
|
||||||
|
// zh-CN: 请求隐藏.
|
||||||
|
mProgressRequested = false
|
||||||
|
mUiHandler.removeCallbacks(mShowProgressRunnable)
|
||||||
|
|
||||||
|
val dlg = mProcessDialog
|
||||||
|
if (dlg == null || dlg.isShowing != true) {
|
||||||
|
mProcessDialog = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val elapsed = SystemClock.uptimeMillis() - mProgressShownAtMs
|
||||||
|
val remain = (mProgressMinShowMs - elapsed).coerceAtLeast(0L)
|
||||||
|
|
||||||
|
if (remain == 0L) {
|
||||||
|
dlg.dismiss()
|
||||||
|
mProcessDialog = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mUiHandler.postDelayed({
|
||||||
|
// Only dismiss if no new show request came in.
|
||||||
|
// zh-CN: 仅当没有新的显示请求时才 dismiss.
|
||||||
|
if (!mProgressRequested) {
|
||||||
|
runCatching { dlg.dismiss() }
|
||||||
|
mProcessDialog = null
|
||||||
|
}
|
||||||
|
}, remain)
|
||||||
|
}
|
||||||
|
|
||||||
private fun applyScaleGesture(key: String? = null) {
|
private fun applyScaleGesture(key: String? = null) {
|
||||||
var niceKey = key
|
var niceKey = key
|
||||||
if (niceKey == null) {
|
if (niceKey == null) {
|
||||||
@@ -174,6 +313,23 @@ class CodeEditor : HVScrollView {
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
when (ev.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN,
|
||||||
|
MotionEvent.ACTION_POINTER_DOWN,
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
// Mark touching as early as possible.
|
||||||
|
// zh-CN: 尽可能早地标记触摸中状态.
|
||||||
|
mUserTouching = true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP,
|
||||||
|
MotionEvent.ACTION_CANCEL,
|
||||||
|
MotionEvent.ACTION_POINTER_UP -> {
|
||||||
|
// Clear touching flag when gesture ends.
|
||||||
|
// zh-CN: 手势结束时清除触摸中标记.
|
||||||
|
mUserTouching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return mScaleGestureDetector?.let {
|
return mScaleGestureDetector?.let {
|
||||||
it.onTouchEvent(ev)
|
it.onTouchEvent(ev)
|
||||||
!it.isInProgress && super.onTouchEvent(ev)
|
!it.isInProgress && super.onTouchEvent(ev)
|
||||||
@@ -182,6 +338,14 @@ class CodeEditor : HVScrollView {
|
|||||||
|
|
||||||
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
|
override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
|
||||||
super.onScrollChanged(l, t, oldl, oldt)
|
super.onScrollChanged(l, t, oldl, oldt)
|
||||||
|
|
||||||
|
// Avoid scheduling extra invalidations during bulk loading.
|
||||||
|
// zh-CN: 批量加载期间避免额外调度 invalidate, 降低重绘压力.
|
||||||
|
if (codeEditText.isLoadingText()) {
|
||||||
|
codeEditText.invalidate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
codeEditText.postInvalidate()
|
codeEditText.postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,16 +465,10 @@ class CodeEditor : HVScrollView {
|
|||||||
mTextViewRedoUndo.isEnabled = enabled
|
mTextViewRedoUndo.isEnabled = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProgress(progress: Boolean) {
|
fun markUndoRedoBaselineAsUnchanged() {
|
||||||
mProcessDialog?.dismiss()
|
// Reset undo/redo history but keep current text intact.
|
||||||
mProcessDialog = when {
|
// zh-CN: 重置 undo/redo 历史但保持当前文本不变.
|
||||||
!progress -> null
|
mTextViewRedoUndo.resetHistoryAsUnchanged()
|
||||||
else -> MaterialDialog.Builder(context)
|
|
||||||
.content(R.string.text_processing)
|
|
||||||
.progress(true, 0)
|
|
||||||
.cancelable(false)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCursorChangeCallback(callback: CursorChangeCallback?) {
|
fun addCursorChangeCallback(callback: CursorChangeCallback?) {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to ScrollView and HorizontalScrollView
|
* Reference to ScrollView and HorizontalScrollView
|
||||||
|
*
|
||||||
|
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||||
*/
|
*/
|
||||||
public class HVScrollView extends FrameLayout {
|
public class HVScrollView extends FrameLayout {
|
||||||
static final int ANIMATED_SCROLL_GAP = 250;
|
static final int ANIMATED_SCROLL_GAP = 250;
|
||||||
@@ -385,6 +387,20 @@ public class HVScrollView extends FrameLayout {
|
|||||||
final int scrollX = getScrollX();
|
final int scrollX = getScrollX();
|
||||||
final int scrollY = getScrollY();
|
final int scrollY = getScrollY();
|
||||||
final View child = getChildAt(0);
|
final View child = getChildAt(0);
|
||||||
|
|
||||||
|
// Fallback for transient layout states during progressive loading.
|
||||||
|
// zh-CN: 渐进式加载期间可能出现的短暂布局状态回退处理.
|
||||||
|
//
|
||||||
|
// When the child is not measured/layouted yet (width/height == 0), the original
|
||||||
|
// bounds check may reject all ACTION_DOWN events, causing scrolling to be stuck.
|
||||||
|
// zh-CN: 当子 View 尚未完成测量/布局时(width/height == 0), 原边界判断可能拒绝所有 ACTION_DOWN,
|
||||||
|
// zh-CN: 从而导致滚动完全失效.
|
||||||
|
final int childWidth = child.getWidth();
|
||||||
|
final int childHeight = child.getHeight();
|
||||||
|
if (childWidth <= 0 || childHeight <= 0) {
|
||||||
|
return x >= 0 && x < getWidth() && y >= 0 && y < getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
return !(y < child.getTop() - scrollY
|
return !(y < child.getTop() - scrollY
|
||||||
|| y >= child.getBottom() - scrollY
|
|| y >= child.getBottom() - scrollY
|
||||||
|| x < child.getLeft() - scrollX
|
|| x < child.getLeft() - scrollX
|
||||||
|
|||||||
@@ -17,8 +17,15 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||||
|
*/
|
||||||
public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChangedListener {
|
public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChangedListener {
|
||||||
|
|
||||||
|
// Syntax highlight hard limit for performance and memory.
|
||||||
|
// zh-CN: 为性能和内存设置的语法高亮硬限制.
|
||||||
|
public static final int MAX_HIGHLIGHT_CHARS = 512 * 1024;
|
||||||
|
|
||||||
public static class HighlightTokens {
|
public static class HighlightTokens {
|
||||||
|
|
||||||
public final int[] colors;
|
public final int[] colors;
|
||||||
@@ -81,6 +88,28 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
if (mTheme == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip highlighting during progressive loading to avoid UI-thread allocations and CPU contention.
|
||||||
|
// zh-CN: 渐进式加载期间跳过高亮, 避免 UI 线程分配与 CPU 争用.
|
||||||
|
if (mCodeEditText != null && mCodeEditText.isLoadingText()) {
|
||||||
|
mRunningHighlighterId.incrementAndGet();
|
||||||
|
mCodeEditText.clearHighlightTokens();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid calling toString() on huge Editable, which allocates a large String on UI thread.
|
||||||
|
// zh-CN: 避免对超大的 Editable 调用 toString(), 否则会在 UI 线程分配巨大的 String.
|
||||||
|
if (s != null && s.length() > MAX_HIGHLIGHT_CHARS) {
|
||||||
|
mRunningHighlighterId.incrementAndGet();
|
||||||
|
if (mCodeEditText != null) {
|
||||||
|
mCodeEditText.clearHighlightTokens();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
updateTokens(s.toString());
|
updateTokens(s.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +121,24 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
if (mTheme == null) {
|
if (mTheme == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable highlighting for very large text to avoid ANR/OOM on low-end devices.
|
||||||
|
// zh-CN: 对超大文本禁用高亮, 避免低端设备出现 ANR/OOM.
|
||||||
|
if (sourceString.length() > MAX_HIGHLIGHT_CHARS) {
|
||||||
|
mRunningHighlighterId.incrementAndGet();
|
||||||
|
mCodeEditText.clearHighlightTokens();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final int id = mRunningHighlighterId.incrementAndGet();
|
final int id = mRunningHighlighterId.incrementAndGet();
|
||||||
if (mExecutorService.isShutdown() || mExecutorService.isTerminated() || mExecutorService.isTerminating()) {
|
if (mExecutorService.isShutdown() || mExecutorService.isTerminated() || mExecutorService.isTerminating()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep only the latest highlight task.
|
||||||
|
// zh-CN: 仅保留最新的高亮任务, 丢弃过期任务以避免队列堆积.
|
||||||
|
mExecutorService.getQueue().clear();
|
||||||
|
|
||||||
mExecutorService.execute(() -> {
|
mExecutorService.execute(() -> {
|
||||||
try {
|
try {
|
||||||
updateTokens(sourceString, id);
|
updateTokens(sourceString, id);
|
||||||
@@ -106,14 +149,27 @@ public class JavaScriptHighlighter implements SimpleTextWatcher.AfterTextChanged
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateTokens(String sourceString, int id) throws IOException {
|
private void updateTokens(String sourceString, int id) throws IOException {
|
||||||
|
// Drop stale tasks early.
|
||||||
|
// zh-CN: 尽早丢弃过期任务.
|
||||||
|
if (id != mRunningHighlighterId.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TokenStream ts = new TokenStream(null, sourceString, 0);
|
TokenStream ts = new TokenStream(null, sourceString, 0);
|
||||||
HighlightTokens highlightTokens = new HighlightTokens(sourceString, id);
|
HighlightTokens highlightTokens = new HighlightTokens(sourceString, id);
|
||||||
int token;
|
int token;
|
||||||
int color = mTheme.getColorForToken(Token.NAME);
|
int color = mTheme.getColorForToken(Token.NAME);
|
||||||
|
|
||||||
while ((token = ts.getToken()) != Token.EOF) {
|
while ((token = ts.getToken()) != Token.EOF) {
|
||||||
|
// Abort quickly if a newer task arrives.
|
||||||
|
// zh-CN: 如果有更新任务到来, 则尽快中止.
|
||||||
|
if (id != mRunningHighlighterId.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
color = mTheme.getColorForToken(token);
|
color = mTheme.getColorForToken(token);
|
||||||
highlightTokens.addToken(ts.getTokenBeg(), ts.getTokenEnd(), color);
|
highlightTokens.addToken(ts.getTokenBeg(), ts.getTokenEnd(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highlightTokens.getCharCount() < sourceString.length()) {
|
if (highlightTokens.getCharCount() < sourceString.length()) {
|
||||||
highlightTokens.addToken(highlightTokens.getCharCount(), sourceString.length(), color);
|
highlightTokens.addToken(highlightTokens.getCharCount(), sourceString.length(), color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic undo/redo implementation for TextViews.
|
* A generic undo/redo implementation for TextViews.
|
||||||
|
*
|
||||||
|
* Modified by JetBrains AI Assistant (GPT-5.2) as of Feb 8, 2026.
|
||||||
*/
|
*/
|
||||||
public class TextViewUndoRedo {
|
public class TextViewUndoRedo {
|
||||||
|
|
||||||
@@ -72,10 +74,39 @@ public class TextViewUndoRedo {
|
|||||||
mEnabled = enabled;
|
mEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset undo/redo history without modifying the current text.
|
||||||
|
*
|
||||||
|
* This is useful after bulk loading text progressively, where the text is already in the TextView
|
||||||
|
* and we only want to treat it as the "baseline" (unchanged) state.
|
||||||
|
*
|
||||||
|
* zh-CN:
|
||||||
|
*
|
||||||
|
* 在不修改当前文本的前提下重置 undo/redo 历史.
|
||||||
|
*
|
||||||
|
* 适用于渐进式批量加载文本后: 文本已经在 TextView 中, 此时只需要把它视为 "基线" (未修改) 状态.
|
||||||
|
*/
|
||||||
|
public final void resetHistoryAsUnchanged() {
|
||||||
|
clearHistory();
|
||||||
|
markTextAsUnchanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public final void setDefaultText(CharSequence text) {
|
||||||
|
// clearHistory();
|
||||||
|
// mIsUndoOrRedo = true;
|
||||||
|
// ((Editable) mTextView.getText()).replace(0, text.length(), text);
|
||||||
|
// mIsUndoOrRedo = false;
|
||||||
|
// }
|
||||||
|
|
||||||
public final void setDefaultText(CharSequence text) {
|
public final void setDefaultText(CharSequence text) {
|
||||||
clearHistory();
|
clearHistory();
|
||||||
mIsUndoOrRedo = true;
|
mIsUndoOrRedo = true;
|
||||||
((Editable) mTextView.getText()).replace(0, text.length(), text);
|
Editable editable = (Editable) mTextView.getText();
|
||||||
|
|
||||||
|
// Replace the entire old content, not based on the new text length.
|
||||||
|
// zh-CN: 替换整个旧内容, 而不是按新文本长度截断替换范围.
|
||||||
|
editable.replace(0, editable.length(), text);
|
||||||
|
|
||||||
mIsUndoOrRedo = false;
|
mIsUndoOrRedo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,29 @@
|
|||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:layout_toEndOf="@+id/functions" />
|
android:layout_toEndOf="@+id/functions" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/loading_bar_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:background="#A0000000">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loading_bar_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:textColor="@color/night_full"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="@string/text_loading_with_dots" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView
|
<org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView
|
||||||
|
|||||||
@@ -1286,4 +1286,5 @@
|
|||||||
<string name="text_write_secure_settings">修改安全设置</string>
|
<string name="text_write_secure_settings">修改安全设置</string>
|
||||||
<string name="text_write_system_settings">修改系统设置</string>
|
<string name="text_write_system_settings">修改系统设置</string>
|
||||||
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
|
<string name="text_xiaomi_background_popup_permission">后台弹出界面</string>
|
||||||
|
<string name="text_loading_completed">加载完毕</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1561,4 +1561,5 @@
|
|||||||
<string name="text_write_secure_settings">Write security settings</string>
|
<string name="text_write_secure_settings">Write security settings</string>
|
||||||
<string name="text_write_system_settings">Write system settings</string>
|
<string name="text_write_system_settings">Write system settings</string>
|
||||||
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
|
<string name="text_xiaomi_background_popup_permission">Display pop-up windows while running in the background</string>
|
||||||
|
<string name="text_loading_completed">Loading completed</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#Sat Feb 07 23:30:55 CST 2026
|
#Sun Feb 08 13:21:45 CST 2026
|
||||||
BUILD_TIME=1770478255016
|
BUILD_TIME=1770528105646
|
||||||
COMPILE_SDK_VERSION=36
|
COMPILE_SDK_VERSION=36
|
||||||
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
||||||
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
||||||
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
|
|||||||
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
||||||
TARGET_SDK_VERSION=36
|
TARGET_SDK_VERSION=36
|
||||||
TARGET_SDK_VERSION_INRT=29
|
TARGET_SDK_VERSION_INRT=29
|
||||||
VERSION_BUILD=3710
|
VERSION_BUILD=3713
|
||||||
VERSION_NAME=6.7.0 Alpha19
|
VERSION_NAME=6.7.0 Alpha19
|
||||||
VSCODE_EXT_REQUIRED_VERSION=1.0.13
|
VSCODE_EXT_REQUIRED_VERSION=1.0.13
|
||||||
|
|||||||
Reference in New Issue
Block a user