From bd5a32e2d5579c515cbb1aefb555fc00b521e9ff Mon Sep 17 00:00:00 2001 From: ms900 Date: Sun, 8 Mar 2026 21:45:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E6=B7=BB=E5=8A=A0=E5=A0=86?= =?UTF-8?q?=E6=A0=88=E5=B8=A7=E8=B7=B3=E8=BD=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 CodeEditor.kt 添加 StackFrame 数据类和堆栈帧管理方法 - 在 EditorView.kt 添加 parseStackTrace 方法解析错误堆栈 - 在 EditorMenu.java 添加跳转到出错行菜单项处理 - 在 menu_editor.xml 添加菜单项 - 添加字符串资源 (中英文) 功能说明: - 脚本运行出错时自动解析完整调用堆栈 - 通过菜单项可在堆栈帧间循环跳转 - 显示当前堆栈帧位置信息 (如: 堆栈 2/5: funcName() 行 42) --- .../org/autojs/autojs/ui/edit/EditorMenu.java | 19 +++++++ .../org/autojs/autojs/ui/edit/EditorView.kt | 50 +++++++++++++++++++ .../autojs/ui/edit/editor/CodeEditor.kt | 39 +++++++++++++++ app/src/main/res/menu/menu_editor.xml | 5 ++ app/src/main/res/values-zh/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 6 files changed, 119 insertions(+) diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java index d57d29369..7abc59b82 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java @@ -5,6 +5,7 @@ import android.text.InputType; import android.text.TextUtils; import android.view.MenuItem; +import android.widget.Toast; import androidx.annotation.Nullable; import com.afollestad.materialdialogs.MaterialDialog; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -16,6 +17,7 @@ import org.autojs.autojs.script.JavaScriptFileSource; import org.autojs.autojs.ui.common.NotAskAgainDialog; import org.autojs.autojs.ui.edit.editor.CodeEditor; +import org.autojs.autojs.ui.edit.editor.CodeEditor.StackFrame; import org.autojs.autojs.ui.main.scripts.EditableFileInfoDialogManager; import org.autojs.autojs.ui.project.BuildActivity; import org.autojs.autojs.util.ClipboardUtils; @@ -92,6 +94,10 @@ private boolean onJumpOptionsSelected(MenuItem item) { jumpToLine(); return true; } + if (itemId == R.id.action_jump_to_error_line) { + jumpToErrorLine(); + return true; + } if (itemId == R.id.action_jump_to_start) { mEditor.jumpToStart(); return true; @@ -311,6 +317,19 @@ private void jumpToLine() { .subscribe(this::showJumpDialog); } + private void jumpToErrorLine() { + if (mEditor.hasErrorLine()) { + CodeEditor.StackFrame frame = mEditor.getNextStackFrame(); + mEditor.jumpTo(frame.getLineNumber(), frame.getColumnNumber()); + int total = mEditor.getStackFrameCount(); + int current = mEditor.getCurrentStackIndex() + 1; + String info = mContext.getString(R.string.text_stack_frame_info, current, total, frame.getFunctionName(), frame.getLineNumber() + 1); + Toast.makeText(mContext, info, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mContext, R.string.text_no_error_line, Toast.LENGTH_SHORT).show(); + } + } + private void showJumpDialog(final int lineCount) { String hint = "1 - " + lineCount; MaterialDialog.Builder builder = new MaterialDialog.Builder(mContext) diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt index cf006d69d..238bc3d71 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt @@ -56,6 +56,7 @@ import org.autojs.autojs.ui.edit.completion.CodeCompletionBar.OnHintClickListene import org.autojs.autojs.ui.edit.debug.DebugBar import org.autojs.autojs.ui.edit.editor.CodeEditor import org.autojs.autojs.ui.edit.editor.CodeEditor.CheckedPatternSyntaxException +import org.autojs.autojs.ui.edit.editor.CodeEditor.StackFrame import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardHelper import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView.ClickCallback @@ -145,6 +146,9 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag val msg = intent.getStringExtra(EXTRA_EXCEPTION_MESSAGE) val line = intent.getIntExtra(EXTRA_EXCEPTION_LINE_NUMBER, -1) val col = intent.getIntExtra(EXTRA_EXCEPTION_COLUMN_NUMBER, 0) + // Parse stack trace and set stack frames for navigation + val stackFrames = parseStackTrace(msg, line, col) + editor.setStackFrames(stackFrames) if (line >= 1) { editor.jumpTo(line - 1, col) } @@ -754,6 +758,52 @@ class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFrag mTmpSavedFileForRunning?.deleteOnExit() } + /** + * Parse stack trace from error message + * Rhino stack trace format: "at funcName (/path/file.js:line:col)" or "at /path/file.js:line:col" + */ + private fun parseStackTrace(msg: String?, mainLine: Int, mainCol: Int): ArrayList { + val frames = ArrayList() + if (msg == null) { + if (mainLine >= 1) { + frames.add(CodeEditor.StackFrame("", mainLine - 1, mainCol)) + } + return frames + } + + // Add main error line first (from RhinoException) + if (mainLine >= 1) { + frames.add(CodeEditor.StackFrame("", mainLine - 1, mainCol)) + } + + // Parse stack frames: "at funcName (/path/file.js:line:col)" or "at /path/file.js:line:col" + // Pattern matches both formats + val stackPattern = Regex("""at\s+(?:(\w+)\s+)?\([^)]*?:(\d+):(\d+)\)|at\s+[^:]+:(\d+):(\d+)""") + stackPattern.findAll(msg).forEach { match -> + val (funcName, lineStr, colStr) = when { + match.groupValues[1] != "" -> Triple( + match.groupValues[1], + match.groupValues[2], + match.groupValues[3] + ) + match.groupValues[4] != "" -> Triple( + "", + match.groupValues[4], + match.groupValues[5] + ) + else -> return@forEach + } + val line = lineStr.toIntOrNull()?.minus(1) ?: return@forEach + val col = colStr.toIntOrNull() ?: 0 + // Skip duplicate entries + if (frames.none { it.lineNumber == line && it.columnNumber == col }) { + frames.add(CodeEditor.StackFrame(funcName, line, col)) + } + } + + return frames + } + companion object { private val TAG = EditorView::class.java.simpleName diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.kt b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.kt index 5830d2c7a..459d9d771 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.kt +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.kt @@ -520,6 +520,45 @@ class CodeEditor : HVScrollView { var enabled = true } + /** + * Stack frame for error navigation + * Used to store function name, line number and column number of each call frame + */ + data class StackFrame( + @JvmField val functionName: String, + @JvmField val lineNumber: Int, + @JvmField val columnNumber: Int + ) + + private val mStackFrames = ArrayList() + private var mCurrentStackIndex = -1 + private var mHasErrorLine = false + + fun setStackFrames(frames: ArrayList) { + mStackFrames.clear() + mStackFrames.addAll(frames) + mCurrentStackIndex = -1 + mHasErrorLine = frames.isNotEmpty() + } + + fun getNextStackFrame(): StackFrame? { + if (mStackFrames.isEmpty()) return null + mCurrentStackIndex = (mCurrentStackIndex + 1) % mStackFrames.size + return mStackFrames[mCurrentStackIndex] + } + + fun getStackFrameCount(): Int = mStackFrames.size + + fun getCurrentStackIndex(): Int = mCurrentStackIndex + + fun hasErrorLine(): Boolean = mHasErrorLine + + fun clearErrorLine() { + mStackFrames.clear() + mCurrentStackIndex = -1 + mHasErrorLine = false + } + class CheckedPatternSyntaxException(cause: PatternSyntaxException?) : Exception(cause) interface BreakpointChangeListener { diff --git a/app/src/main/res/menu/menu_editor.xml b/app/src/main/res/menu/menu_editor.xml index ec6833928..1048727c0 100644 --- a/app/src/main/res/menu/menu_editor.xml +++ b/app/src/main/res/menu/menu_editor.xml @@ -67,6 +67,11 @@ android:title="@string/text_jump_to_line" app:showAsAction="never" /> + + 跳转到行 跳转到行尾 跳转到行首 + 跳转到出错行 + 没有错误行 + 堆栈 %1$d/%2$d: %3$s() 行 %4$d 跳转到文件开始 前台时保持屏幕常亮 别名 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e986c5e5..00c4c3b91 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -904,6 +904,9 @@ Jump to line Jump to line end Jump to line start + Jump to error line + No error line + Stack %1$d/%2$d: %3$s() line %4$d Jump to start Keep screen on when in foreground Alias