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" /> + +

加载中... 定位当前主题色 日志 + 显示日志 登录中 登录 登录成功 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6845df06..a4c73602 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1216,6 +1216,7 @@ Loading... Locate current theme color Log + Show log Logging in Login Login succeeded