44import android .content .Context ;
55import android .content .res .TypedArray ;
66import 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 ;
712import android .util .AttributeSet ;
813import android .util .Log ;
914import android .util .TypedValue ;
1722import androidx .annotation .Nullable ;
1823import androidx .recyclerview .widget .LinearLayoutManager ;
1924import 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+
2033import org .autojs .autojs .theme .ThemeColorHelper ;
2134import org .autojs .autojs .tool .MapBuilder ;
2235import org .autojs .autojs .ui .log .LogActivity ;
2336import org .autojs .autojs .util .DisplayUtils ;
2437import org .autojs .autojs .util .ViewUtils ;
2538import 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>
3745public 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 {
0 commit comments