Skip to content

Commit ed3eb10

Browse files
Merge pull request #502 from M17764017422/main
feat: 添加底部日志面板功能
2 parents 9f14534 + 49ea44e commit ed3eb10

13 files changed

Lines changed: 392 additions & 7 deletions

File tree

app/src/main/java/org/autojs/autojs/core/console/ConsoleView.java

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
import android.content.Context;
55
import android.content.res.TypedArray;
66
import android.graphics.Color;
7+
import android.text.SpannableString;
8+
import android.text.Spanned;
9+
import android.text.method.LinkMovementMethod;
10+
import android.text.style.ClickableSpan;
11+
import android.text.style.ForegroundColorSpan;
712
import android.util.AttributeSet;
813
import android.util.Log;
914
import android.util.TypedValue;
@@ -17,18 +22,21 @@
1722
import androidx.annotation.Nullable;
1823
import androidx.recyclerview.widget.LinearLayoutManager;
1924
import androidx.recyclerview.widget.RecyclerView;
25+
26+
import java.lang.ref.WeakReference;
27+
import java.util.ArrayList;
28+
import java.util.Map;
29+
import java.util.Objects;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
32+
2033
import org.autojs.autojs.theme.ThemeColorHelper;
2134
import org.autojs.autojs.tool.MapBuilder;
2235
import org.autojs.autojs.ui.log.LogActivity;
2336
import org.autojs.autojs.util.DisplayUtils;
2437
import org.autojs.autojs.util.ViewUtils;
2538
import org.autojs.autojs6.R;
2639

27-
import java.lang.ref.WeakReference;
28-
import java.util.ArrayList;
29-
import java.util.Map;
30-
import java.util.Objects;
31-
3240
/**
3341
* Created by Stardust on May 2, 2017.
3442
* <p>
@@ -37,6 +45,21 @@
3745
public class ConsoleView extends FrameLayout implements ConsoleImpl.LogListener {
3846

3947
private final static int sRefreshInterval = 100;
48+
49+
// Stack frame link pattern: matches "file:line" or "file:line:col" format
50+
// Examples: "/sdcard/script.js:10", "script.js:10:5", "file:///path/to/script.js:42:3"
51+
private static final Pattern STACK_FRAME_PATTERN = Pattern.compile(
52+
"(?:file:)?((?:/[\\S]+?|[^\\s:]+?)\\.js):(\\d+)(?::(\\d+))?"
53+
);
54+
private static final int LINK_COLOR = 0xFF2196F3; // Blue color for clickable links
55+
56+
private boolean mEnableStackFrameLinks = false;
57+
private OnStackFrameClickListener mStackFrameClickListener;
58+
59+
public interface OnStackFrameClickListener {
60+
void onStackFrameClick(String fileName, int lineNumber, int columnNumber);
61+
}
62+
4063
private final Map<Integer, Integer> mColors = new MapBuilder<Integer, Integer>().build();
4164
private ConsoleImpl mConsole;
4265
private WeakReference<LogActivity> mLogActivity = null;
@@ -228,6 +251,66 @@ public void setPinchToZoomEnabled(boolean enabled) {
228251
mIsPinchToZoomEnabled = enabled;
229252
}
230253

254+
/**
255+
* Enable or disable clickable stack frame links in log entries
256+
* @param enabled true to enable, false to disable
257+
*/
258+
public void setEnableStackFrameLinks(boolean enabled) {
259+
mEnableStackFrameLinks = enabled;
260+
}
261+
262+
/**
263+
* Set the listener for stack frame click events
264+
* @param listener the listener to receive click events
265+
*/
266+
public void setOnStackFrameClickListener(OnStackFrameClickListener listener) {
267+
mStackFrameClickListener = listener;
268+
}
269+
270+
/**
271+
* Parse log content and create clickable spans for stack frames
272+
* @param content the original log content
273+
* @param baseColor the base text color
274+
* @return CharSequence with clickable spans for stack frames
275+
*/
276+
private CharSequence createClickableContent(CharSequence content, int baseColor) {
277+
if (!mEnableStackFrameLinks) {
278+
return content;
279+
}
280+
281+
String text = content.toString();
282+
SpannableString spannable = new SpannableString(text);
283+
284+
Matcher matcher = STACK_FRAME_PATTERN.matcher(text);
285+
boolean hasLinks = false;
286+
287+
while (matcher.find()) {
288+
hasLinks = true;
289+
final String fileName = matcher.group(1);
290+
final int lineNumber = Integer.parseInt(matcher.group(2));
291+
final int columnNumber = matcher.group(3) != null ? Integer.parseInt(matcher.group(3)) : 0;
292+
293+
final int start = matcher.start();
294+
final int end = matcher.end();
295+
296+
ClickableSpan clickableSpan = new ClickableSpan() {
297+
@Override
298+
public void onClick(@NonNull View widget) {
299+
if (mStackFrameClickListener != null) {
300+
mStackFrameClickListener.onStackFrameClick(fileName, lineNumber - 1, columnNumber);
301+
}
302+
}
303+
};
304+
305+
spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
306+
307+
ForegroundColorSpan colorSpan = new ForegroundColorSpan(LINK_COLOR);
308+
spannable.setSpan(colorSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
309+
}
310+
311+
return hasLinks ? spannable : content;
312+
}
313+
231314
protected Map<Integer, Integer> getLogLevelMap() {
232315
return new MapBuilder<Integer, Integer>()
233316
.put(Log.VERBOSE, R.color.console_view_verbose)
@@ -323,6 +406,7 @@ private class ViewHolder extends RecyclerView.ViewHolder {
323406
public ViewHolder(View itemView) {
324407
super(itemView);
325408
textView = (TextView) itemView;
409+
textView.setMovementMethod(LinkMovementMethod.getInstance());
326410
}
327411

328412
}
@@ -341,12 +425,18 @@ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
341425
public void onBindViewHolder(ViewHolder holder, int position) {
342426
TextView textView = holder.textView;
343427
ConsoleImpl.LogEntry logEntry = mLogEntries.get(position);
344-
textView.setText(logEntry.content);
345-
ThemeColorHelper.setThemeColorPrimary(textView, true);
428+
346429
Integer color = mColors.get(logEntry.level);
347430
if (color != null) {
431+
// Create clickable content with stack frame links
432+
CharSequence content = createClickableContent(logEntry.content, color);
433+
textView.setText(content);
348434
textView.setTextColor(color);
435+
} else {
436+
textView.setText(logEntry.content);
349437
}
438+
439+
ThemeColorHelper.setThemeColorPrimary(textView, true);
350440
if (textSize > 0) {
351441
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
352442
} else {

app/src/main/java/org/autojs/autojs/ui/edit/EditActivity.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import org.autojs.autojs.storage.file.StableDraftFileHelper
3535
import org.autojs.autojs.theme.widget.ThemeColorToolbar
3636
import org.autojs.autojs.ui.BaseActivity
3737
import org.autojs.autojs.ui.error.ErrorDialogActivity
38+
import org.autojs.autojs.ui.log.LogBottomSheet
3839
import org.autojs.autojs.ui.main.MainActivity
3940
import org.autojs.autojs.ui.main.scripts.EditableFileInfoDialogManager
4041
import org.autojs.autojs.util.DialogUtils
@@ -157,6 +158,39 @@ open class EditActivity : BaseActivity(), DelegateHost, PermissionRequestProxyAc
157158

158159
setToolbarAsBack(editorView.name)
159160
onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback)
161+
162+
// Setup log bottom sheet
163+
setUpLogSheet()
164+
}
165+
166+
private fun setUpLogSheet() {
167+
mEditorView.setLogPanelCallback(object : EditorView.LogPanelCallback {
168+
override fun onShowLogPanel() {
169+
val scriptName = mEditorView.name
170+
val scriptPath = mEditorView.uri?.path
171+
172+
val bottomSheet = LogBottomSheet.newInstance(scriptName, scriptPath)
173+
bottomSheet.setOnStackFrameClickListener(object : LogBottomSheet.OnStackFrameClickListener {
174+
override fun onStackFrameClick(fileName: String, lineNumber: Int, columnNumber: Int) {
175+
// Jump to the line in editor
176+
try {
177+
val editor = mEditorView.editor
178+
val layout = editor.codeEditText.layout
179+
if (lineNumber >= 0 && lineNumber < layout.lineCount) {
180+
val lineStart = layout.getLineStart(lineNumber)
181+
val column = if (columnNumber > 0) columnNumber else 0
182+
val offset = minOf(lineStart + column, layout.getLineEnd(lineNumber) - 1)
183+
editor.codeEditText.setSelection(offset)
184+
editor.codeEditText.requestFocus()
185+
}
186+
} catch (e: Exception) {
187+
e.printStackTrace()
188+
}
189+
}
190+
})
191+
bottomSheet.show(supportFragmentManager, "LogBottomSheet")
192+
}
193+
})
160194
}
161195

162196
private fun onLoadFileError(message: String?) {

app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
9898
if (itemId == R.id.action_log) {
9999
return ConsoleUtils.launch(mContext);
100100
}
101+
if (itemId == R.id.action_show_log) {
102+
return tryDoing(mEditorView::showLogPanel);
103+
}
101104
if (itemId == R.id.action_force_stop) {
102105
return tryDoing(mEditorView::forceStop);
103106
}
@@ -160,6 +163,10 @@ private boolean onMoreOptionsSelected(MenuItem item) {
160163
if (itemId == R.id.action_console) {
161164
return tryDoing(mEditorView::showConsole);
162165
}
166+
if (itemId == R.id.action_show_log) {
167+
showLogPanel();
168+
return true;
169+
}
163170
if (itemId == R.id.action_import_java_class) {
164171
importJavaPackageOrClass();
165172
return true;
@@ -399,6 +406,10 @@ private void showJumpDialog(final int lineCount) {
399406
builder.show();
400407
}
401408

409+
private void showLogPanel() {
410+
mEditorView.showLogPanel();
411+
}
412+
402413
private void showFileDetails() {
403414
Uri uri = mEditorView.getUri();
404415
String path;

app/src/main/java/org/autojs/autojs/ui/edit/EditorView.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,23 @@ import java.util.regex.Pattern
127127
@SuppressLint("CheckResult")
128128
class EditorView : LinearLayout, OnHintClickListener, ClickCallback, ToolbarFragment.OnMenuItemClickListener {
129129

130+
/**
131+
* Callback interface for showing the log panel from the editor
132+
*/
133+
interface LogPanelCallback {
134+
fun onShowLogPanel()
135+
}
136+
137+
private var mLogPanelCallback: LogPanelCallback? = null
138+
139+
fun setLogPanelCallback(callback: LogPanelCallback) {
140+
mLogPanelCallback = callback
141+
}
142+
143+
fun showLogPanel() {
144+
mLogPanelCallback?.onShowLogPanel()
145+
}
146+
130147
private var binding: EditorViewBinding = EditorViewBinding.bind(inflate(context, R.layout.editor_view, this))
131148

132149
@JvmField
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.autojs.autojs.ui.log
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
9+
import org.autojs.autojs.AutoJs
10+
import org.autojs.autojs6.R
11+
import org.autojs.autojs6.databinding.BottomSheetLogBinding
12+
13+
/**
14+
* Bottom sheet dialog for displaying script logs in the editor.
15+
* Provides quick access to log output without leaving the editor.
16+
* Clickable stack frames (file:line) are highlighted in blue.
17+
*/
18+
class LogBottomSheet : BottomSheetDialogFragment() {
19+
20+
private var _binding: BottomSheetLogBinding? = null
21+
private val binding get() = _binding!!
22+
23+
private var mScriptName: String? = null
24+
private var mScriptPath: String? = null
25+
private var mStackFrameClickListener: OnStackFrameClickListener? = null
26+
27+
interface OnStackFrameClickListener {
28+
fun onStackFrameClick(fileName: String, lineNumber: Int, columnNumber: Int)
29+
}
30+
31+
companion object {
32+
private const val ARG_SCRIPT_NAME = "script_name"
33+
private const val ARG_SCRIPT_PATH = "script_path"
34+
35+
/**
36+
* Create a new instance with script info
37+
*/
38+
@JvmStatic
39+
fun newInstance(scriptName: String?, scriptPath: String?): LogBottomSheet {
40+
val fragment = LogBottomSheet()
41+
val args = Bundle()
42+
args.putString(ARG_SCRIPT_NAME, scriptName)
43+
args.putString(ARG_SCRIPT_PATH, scriptPath)
44+
fragment.arguments = args
45+
return fragment
46+
}
47+
}
48+
49+
fun setOnStackFrameClickListener(listener: OnStackFrameClickListener) {
50+
mStackFrameClickListener = listener
51+
}
52+
53+
override fun onCreate(savedInstanceState: Bundle?) {
54+
super.onCreate(savedInstanceState)
55+
arguments?.let {
56+
mScriptName = it.getString(ARG_SCRIPT_NAME)
57+
mScriptPath = it.getString(ARG_SCRIPT_PATH)
58+
}
59+
}
60+
61+
override fun onCreateView(
62+
inflater: LayoutInflater,
63+
container: ViewGroup?,
64+
savedInstanceState: Bundle?
65+
): View {
66+
_binding = BottomSheetLogBinding.inflate(inflater, container, false)
67+
return binding.root
68+
}
69+
70+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
71+
super.onViewCreated(view, savedInstanceState)
72+
setupViews()
73+
}
74+
75+
private fun setupViews() {
76+
// Set title
77+
if (!mScriptName.isNullOrEmpty()) {
78+
binding.tvTitle.text = mScriptName
79+
}
80+
81+
// Setup ConsoleView with global console
82+
val autoJs = AutoJs.getInstance()
83+
if (autoJs != null) {
84+
binding.console.setConsole(autoJs.globalConsole)
85+
86+
// Hide input container (not needed in bottom sheet)
87+
binding.console.findViewById<View>(R.id.input_container)?.visibility = View.GONE
88+
89+
// Enable clickable stack frame links (only in bottom sheet, not in LogActivity)
90+
binding.console.setEnableStackFrameLinks(true)
91+
92+
// Set up clickable stack frame listener
93+
binding.console.setOnStackFrameClickListener { fileName, lineNumber, columnNumber ->
94+
mStackFrameClickListener?.onStackFrameClick(fileName, lineNumber, columnNumber)
95+
dismiss()
96+
}
97+
}
98+
99+
// Clear button
100+
binding.btnClear.setOnClickListener {
101+
AutoJs.getInstance()?.globalConsole?.clear()
102+
}
103+
104+
// Open full log activity button
105+
binding.btnOpenFull.setOnClickListener {
106+
startActivity(Intent(requireContext(), LogActivity::class.java))
107+
dismiss()
108+
}
109+
}
110+
111+
override fun onDestroyView() {
112+
super.onDestroyView()
113+
_binding = null
114+
}
115+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="?android:attr/windowBackground"/>
5+
<corners
6+
android:topLeftRadius="16dp"
7+
android:topRightRadius="16dp"
8+
android:bottomLeftRadius="0dp"
9+
android:bottomRightRadius="0dp"/>
10+
</shape>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
<solid android:color="#DDDDDD"/>
5+
<corners android:radius="2dp"/>
6+
<size android:width="40dp" android:height="4dp"/>
7+
</shape>

0 commit comments

Comments
 (0)