diff --git a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
index 19fb1f3b..ac1d87e3 100644
--- a/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
+++ b/app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java
@@ -4,6 +4,11 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -17,6 +22,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import org.autojs.autojs.theme.ThemeColorHelper;
import org.autojs.autojs.tool.MapBuilder;
import org.autojs.autojs.ui.log.LogActivity;
@@ -24,11 +37,6 @@ import org.autojs.autojs.util.DisplayUtils;
import org.autojs.autojs.util.ViewUtils;
import org.autojs.autojs6.R;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.Objects;
-
/**
* Created by Stardust on May 2, 2017.
*
@@ -37,6 +45,21 @@ import java.util.Objects;
public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener {
private final static int sRefreshInterval = 100;
+
+ // Stack frame link pattern: matches "file:line" or "file:line:col" format
+ // Examples: "/sdcard/script.js:10", "script.js:10:5", "file:///path/to/script.js:42:3"
+ private static final Pattern STACK_FRAME_PATTERN = Pattern.compile(
+ "(?:file:)?((?:/[\\S]+?|[^\\s:]+?)\\.js):(\\d+)(?::(\\d+))?"
+ );
+ private static final int LINK_COLOR = 0xFF2196F3; // Blue color for clickable links
+
+ private boolean mEnableStackFrameLinks = false;
+ private OnStackFrameClickListener mStackFrameClickListener;
+
+ public interface OnStackFrameClickListener {
+ void onStackFrameClick(String fileName, int lineNumber, int columnNumber);
+ }
+
private final Map mColors = new MapBuilder().build();
private ConsoleImpl mConsole;
private WeakReference mLogActivity = null;
@@ -228,6 +251,66 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
mIsPinchToZoomEnabled = enabled;
}
+ /**
+ * Enable or disable clickable stack frame links in log entries
+ * @param enabled true to enable, false to disable
+ */
+ public void setEnableStackFrameLinks(boolean enabled) {
+ mEnableStackFrameLinks = enabled;
+ }
+
+ /**
+ * Set the listener for stack frame click events
+ * @param listener the listener to receive click events
+ */
+ public void setOnStackFrameClickListener(OnStackFrameClickListener listener) {
+ mStackFrameClickListener = listener;
+ }
+
+ /**
+ * Parse log content and create clickable spans for stack frames
+ * @param content the original log content
+ * @param baseColor the base text color
+ * @return CharSequence with clickable spans for stack frames
+ */
+ private CharSequence createClickableContent(CharSequence content, int baseColor) {
+ if (!mEnableStackFrameLinks) {
+ return content;
+ }
+
+ String text = content.toString();
+ SpannableString spannable = new SpannableString(text);
+
+ Matcher matcher = STACK_FRAME_PATTERN.matcher(text);
+ boolean hasLinks = false;
+
+ while (matcher.find()) {
+ hasLinks = true;
+ final String fileName = matcher.group(1);
+ final int lineNumber = Integer.parseInt(matcher.group(2));
+ final int columnNumber = matcher.group(3) != null ? Integer.parseInt(matcher.group(3)) : 0;
+
+ final int start = matcher.start();
+ final int end = matcher.end();
+
+ ClickableSpan clickableSpan = new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ if (mStackFrameClickListener != null) {
+ mStackFrameClickListener.onStackFrameClick(fileName, lineNumber - 1, columnNumber);
+ }
+ }
+ };
+
+ spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ ForegroundColorSpan colorSpan = new ForegroundColorSpan(LINK_COLOR);
+ spannable.setSpan(colorSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ return hasLinks ? spannable : content;
+ }
+
protected Map getLogLevelMap() {
return new MapBuilder()
.put(Log.VERBOSE, R.color.console_view_verbose)
@@ -323,6 +406,7 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
public ViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView;
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
@@ -341,12 +425,18 @@ public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener
public void onBindViewHolder(ViewHolder holder, int position) {
TextView textView = holder.textView;
ConsoleImpl.LogEntry logEntry = mLogEntries.get(position);
- textView.setText(logEntry.content);
- ThemeColorHelper.setThemeColorPrimary(textView, true);
+
Integer color = mColors.get(logEntry.level);
if (color != null) {
+ // Create clickable content with stack frame links
+ CharSequence content = createClickableContent(logEntry.content, color);
+ textView.setText(content);
textView.setTextColor(color);
+ } else {
+ textView.setText(logEntry.content);
}
+
+ ThemeColorHelper.setThemeColorPrimary(textView, true);
if (textSize > 0) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
} else {
diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt
index 1d39e7ad..40ab6814 100644
--- a/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt
+++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt
@@ -35,6 +35,7 @@ import org.autojs.autojs.storage.file.StableDraftFileHelper
import org.autojs.autojs.theme.widget.ThemeColorToolbar
import org.autojs.autojs.ui.BaseActivity
import org.autojs.autojs.ui.error.ErrorDialogActivity
+import org.autojs.autojs.ui.log.LogBottomSheet
import org.autojs.autojs.ui.main.MainActivity
import org.autojs.autojs.ui.main.scripts.EditableFileInfoDialogManager
import org.autojs.autojs.util.DialogUtils
@@ -157,6 +158,39 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
setToolbarAsBack(editorView.name)
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
+
+ // Setup log bottom sheet
+ setUpLogSheet()
+ }
+
+ private fun setUpLogSheet() {
+ mEditorView.setLogPanelCallback(object : EditorView.LogPanelCallback {
+ override fun onShowLogPanel() {
+ val scriptName = mEditorView.name
+ val scriptPath = mEditorView.uri?.path
+
+ val bottomSheet = LogBottomSheet.newInstance(scriptName, scriptPath)
+ bottomSheet.setOnStackFrameClickListener(object : LogBottomSheet.OnStackFrameClickListener {
+ override fun onStackFrameClick(fileName: String, lineNumber: Int, columnNumber: Int) {
+ // Jump to the line in editor
+ try {
+ val editor = mEditorView.editor
+ val layout = editor.codeEditText.layout
+ if (lineNumber >= 0 && lineNumber < layout.lineCount) {
+ val lineStart = layout.getLineStart(lineNumber)
+ val column = if (columnNumber > 0) columnNumber else 0
+ val offset = minOf(lineStart + column, layout.getLineEnd(lineNumber) - 1)
+ editor.codeEditText.setSelection(offset)
+ editor.codeEditText.requestFocus()
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ })
+ bottomSheet.show(supportFragmentManager, "LogBottomSheet")
+ }
+ })
}
private fun onLoadFileError(message: String?) {
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 2321e69d..9b8db94f 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
@@ -98,6 +98,9 @@ public class EditorMenu {
if (itemId == R.id.action_log) {
return ConsoleUtils.launch(mContext);
}
+ if (itemId == R.id.action_show_log) {
+ return tryDoing(mEditorView::showLogPanel);
+ }
if (itemId == R.id.action_force_stop) {
return tryDoing(mEditorView::forceStop);
}
@@ -160,6 +163,10 @@ public class EditorMenu {
if (itemId == R.id.action_console) {
return tryDoing(mEditorView::showConsole);
}
+ if (itemId == R.id.action_show_log) {
+ showLogPanel();
+ return true;
+ }
if (itemId == R.id.action_import_java_class) {
importJavaPackageOrClass();
return true;
@@ -399,6 +406,10 @@ public class EditorMenu {
builder.show();
}
+ private void showLogPanel() {
+ mEditorView.showLogPanel();
+ }
+
private void showFileDetails() {
Uri uri = mEditorView.getUri();
String path;
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 a52a5908..981925c4 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
@@ -127,6 +127,23 @@ import java.util.regex.Pattern
@SuppressLint("CheckResult")
class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener {
+ /**
+ * Callback interface for showing the log panel from the editor
+ */
+ interface LogPanelCallback {
+ fun onShowLogPanel()
+ }
+
+ private var mLogPanelCallback: LogPanelCallback? = null
+
+ fun setLogPanelCallback(callback: LogPanelCallback) {
+ mLogPanelCallback = callback
+ }
+
+ fun showLogPanel() {
+ mLogPanelCallback?.onShowLogPanel()
+ }
+
private var binding: EditorViewBinding = EditorViewBinding.bind(inflate(context, R.layout.editor_view, this))
@JvmField
diff --git a/app/src/main/java/org/autojs/autojs/ui/log/LogBottomSheet.kt b/app/src/main/java/org/autojs/autojs/ui/log/LogBottomSheet.kt
new file mode 100644
index 00000000..3b18d1fa
--- /dev/null
+++ b/app/src/main/java/org/autojs/autojs/ui/log/LogBottomSheet.kt
@@ -0,0 +1,115 @@
+package org.autojs.autojs.ui.log
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import org.autojs.autojs.AutoJs
+import org.autojs.autojs6.R
+import org.autojs.autojs6.databinding.BottomSheetLogBinding
+
+/**
+ * Bottom sheet dialog for displaying script logs in the editor.
+ * Provides quick access to log output without leaving the editor.
+ * Clickable stack frames (file:line) are highlighted in blue.
+ */
+class LogBottomSheet : BottomSheetDialogFragment() {
+
+ private var _binding: BottomSheetLogBinding? = null
+ private val binding get() = _binding!!
+
+ private var mScriptName: String? = null
+ private var mScriptPath: String? = null
+ private var mStackFrameClickListener: OnStackFrameClickListener? = null
+
+ interface OnStackFrameClickListener {
+ fun onStackFrameClick(fileName: String, lineNumber: Int, columnNumber: Int)
+ }
+
+ companion object {
+ private const val ARG_SCRIPT_NAME = "script_name"
+ private const val ARG_SCRIPT_PATH = "script_path"
+
+ /**
+ * Create a new instance with script info
+ */
+ @JvmStatic
+ fun newInstance(scriptName: String?, scriptPath: String?): LogBottomSheet {
+ val fragment = LogBottomSheet()
+ val args = Bundle()
+ args.putString(ARG_SCRIPT_NAME, scriptName)
+ args.putString(ARG_SCRIPT_PATH, scriptPath)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+
+ fun setOnStackFrameClickListener(listener: OnStackFrameClickListener) {
+ mStackFrameClickListener = listener
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ arguments?.let {
+ mScriptName = it.getString(ARG_SCRIPT_NAME)
+ mScriptPath = it.getString(ARG_SCRIPT_PATH)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = BottomSheetLogBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupViews()
+ }
+
+ private fun setupViews() {
+ // Set title
+ if (!mScriptName.isNullOrEmpty()) {
+ binding.tvTitle.text = mScriptName
+ }
+
+ // Setup ConsoleView with global console
+ val autoJs = AutoJs.getInstance()
+ if (autoJs != null) {
+ binding.console.setConsole(autoJs.globalConsole)
+
+ // Hide input container (not needed in bottom sheet)
+ binding.console.findViewById(R.id.input_container)?.visibility = View.GONE
+
+ // Enable clickable stack frame links (only in bottom sheet, not in LogActivity)
+ binding.console.setEnableStackFrameLinks(true)
+
+ // Set up clickable stack frame listener
+ binding.console.setOnStackFrameClickListener { fileName, lineNumber, columnNumber ->
+ mStackFrameClickListener?.onStackFrameClick(fileName, lineNumber, columnNumber)
+ dismiss()
+ }
+ }
+
+ // Clear button
+ binding.btnClear.setOnClickListener {
+ AutoJs.getInstance()?.globalConsole?.clear()
+ }
+
+ // Open full log activity button
+ binding.btnOpenFull.setOnClickListener {
+ startActivity(Intent(requireContext(), LogActivity::class.java))
+ dismiss()
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+}
diff --git a/app/src/main/res/drawable/bg_bottom_sheet_rounded.xml b/app/src/main/res/drawable/bg_bottom_sheet_rounded.xml
new file mode 100644
index 00000000..c25bd26e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_bottom_sheet_rounded.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_drag_handle.xml b/app/src/main/res/drawable/bg_drag_handle.xml
new file mode 100644
index 00000000..328bcc2c
--- /dev/null
+++ b/app/src/main/res/drawable/bg_drag_handle.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml
new file mode 100644
index 00000000..5aa45f4a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clear.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_logcat.xml b/app/src/main/res/drawable/ic_logcat.xml
new file mode 100644
index 00000000..b9e1d1bc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logcat.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/app/src/main/res/layout/bottom_sheet_log.xml b/app/src/main/res/layout/bottom_sheet_log.xml
new file mode 100644
index 00000000..d5d377a9
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_log.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/menu_editor.xml b/app/src/main/res/menu/menu_editor.xml
index cd84b961..46932a51 100644
--- a/app/src/main/res/menu/menu_editor.xml
+++ b/app/src/main/res/menu/menu_editor.xml
@@ -115,6 +115,11 @@
android:title="@string/text_log"
app:showAsAction="never" />
+
+
-