feat(ui): supports debugging

This commit is contained in:
hyb1996
2018-09-07 22:26:03 +08:00
parent e5e1b567e7
commit 32805aad34
28 changed files with 2027 additions and 57 deletions

Binary file not shown.

View File

@@ -8,8 +8,8 @@ android {
applicationId "org.autojs.autojs" applicationId "org.autojs.autojs"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 23 targetSdkVersion 23
versionCode 406 versionCode 408
versionName "4.0.2 Alpha2" versionName "4.0.2 Alpha3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
ndk { ndk {

View File

@@ -5,9 +5,11 @@
"editor.background": "#1E1E1E", "editor.background": "#1E1E1E",
"editor.foreground": "#D4D4D4", "editor.foreground": "#D4D4D4",
"editorLineNumber.foreground": "#404040", "editorLineNumber.foreground": "#404040",
"editorBreakpoint.foreground": "#1976D2",
"imeBar.background": "#dd1e1e1e", "imeBar.background": "#dd1e1e1e",
"imeBar.foreground": "#f1f1f1", "imeBar.foreground": "#f1f1f1",
"editor.lineHighlightBackground": "#2e2e35" "editor.lineHighlightBackground": "#2e2e35",
"editor.debuggingLineBackground": "#40c4ff"
}, },
"tokenColors": [ "tokenColors": [
{ {

View File

@@ -499,6 +499,8 @@
"editor.background": "#F5F5F5", "editor.background": "#F5F5F5",
"editor.foreground": "#000000", "editor.foreground": "#000000",
"editor.lineHighlightBackground": "#E4F6D4", "editor.lineHighlightBackground": "#E4F6D4",
"editor.debuggingLineBackground": "#40c4ff",
"editorBreakpoint.foreground": "#03A9F4",
"focusBorder": "#A6B39B", "focusBorder": "#A6B39B",
"pickerGroup.foreground": "#A6B39B", "pickerGroup.foreground": "#A6B39B",
"pickerGroup.border": "#749351", "pickerGroup.border": "#749351",

View File

@@ -22,6 +22,10 @@ public class EditorColors {
private String mImeForegroundColor; private String mImeForegroundColor;
@SerializedName("editor.lineHighlightBackground") @SerializedName("editor.lineHighlightBackground")
private String mLineHighlightBackground; private String mLineHighlightBackground;
@SerializedName("editorBreakpoint.foreground")
private String mBreakpointForeground;
@SerializedName("editor.debuggingLineBackground")
private String mDebuggingLineBackground;
public String getLineHighlightBackground() { public String getLineHighlightBackground() {
return mLineHighlightBackground; return mLineHighlightBackground;
@@ -86,4 +90,20 @@ public class EditorColors {
public void setImeForegroundColor(String imeForegroundColor) { public void setImeForegroundColor(String imeForegroundColor) {
mImeForegroundColor = imeForegroundColor; mImeForegroundColor = imeForegroundColor;
} }
public String getBreakpointForeground() {
return mBreakpointForeground;
}
public void setBreakpointForeground(String breakpointForeground) {
mBreakpointForeground = breakpointForeground;
}
public String getDebuggingLineBackground() {
return mDebuggingLineBackground;
}
public void setDebuggingLineBackground(String debuggingLineBackground) {
mDebuggingLineBackground = debuggingLineBackground;
}
} }

View File

@@ -6,12 +6,14 @@ import android.text.InputType;
import android.view.MenuItem; import android.view.MenuItem;
import com.stardust.pio.PFiles; import com.stardust.pio.PFiles;
import org.autojs.autojs.R; import org.autojs.autojs.R;
import org.autojs.autojs.ui.build.BuildActivity; import org.autojs.autojs.ui.build.BuildActivity;
import org.autojs.autojs.ui.build.BuildActivity_; import org.autojs.autojs.ui.build.BuildActivity_;
import org.autojs.autojs.ui.edit.editor.CodeEditor; import org.autojs.autojs.ui.edit.editor.CodeEditor;
import org.autojs.autojs.ui.log.LogActivity_; import org.autojs.autojs.ui.log.LogActivity_;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder; import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.ClipboardUtil; import com.stardust.util.ClipboardUtil;
import java.util.Locale; import java.util.Locale;
@@ -53,6 +55,21 @@ public class EditorMenu {
if (onMoreOptionsSelected(item)) { if (onMoreOptionsSelected(item)) {
return true; return true;
} }
if(onDebugOptionsSelected(item)){
return true;
}
}
return false;
}
private boolean onDebugOptionsSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_breakpoint:
mEditor.addOrRemoveBreakpointAtCurrentLine();
return true;
case R.id.action_launch_debugger:
mEditorView.launchDebugger();
return true;
} }
return false; return false;
} }

View File

@@ -11,10 +11,13 @@ import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@@ -50,6 +53,8 @@ import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardHelper;
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView; import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView;
import org.autojs.autojs.ui.edit.theme.Theme; import org.autojs.autojs.ui.edit.theme.Theme;
import org.autojs.autojs.ui.edit.theme.Themes; import org.autojs.autojs.ui.edit.theme.Themes;
import org.autojs.autojs.ui.edit.toolbar.DebugToolbarFragment;
import org.autojs.autojs.ui.edit.toolbar.DebugToolbarFragment_;
import org.autojs.autojs.ui.edit.toolbar.NormalToolbarFragment; import org.autojs.autojs.ui.edit.toolbar.NormalToolbarFragment;
import org.autojs.autojs.ui.edit.toolbar.NormalToolbarFragment_; import org.autojs.autojs.ui.edit.toolbar.NormalToolbarFragment_;
import org.autojs.autojs.ui.edit.toolbar.SearchToolbarFragment; import org.autojs.autojs.ui.edit.toolbar.SearchToolbarFragment;
@@ -117,6 +122,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (ACTION_ON_EXECUTION_FINISHED.equals(intent.getAction())) { if (ACTION_ON_EXECUTION_FINISHED.equals(intent.getAction())) {
mScriptExecutionId = ScriptExecution.NO_ID; mScriptExecutionId = ScriptExecution.NO_ID;
if (mDebugging) {
exitDebugging();
}
setMenuItemStatus(R.id.run, true); setMenuItemStatus(R.id.run, true);
String msg = intent.getStringExtra(Scripts.EXTRA_EXCEPTION_MESSAGE); String msg = intent.getStringExtra(Scripts.EXTRA_EXCEPTION_MESSAGE);
int line = intent.getIntExtra(Scripts.EXTRA_EXCEPTION_LINE_NUMBER, -1); int line = intent.getIntExtra(Scripts.EXTRA_EXCEPTION_LINE_NUMBER, -1);
@@ -130,8 +138,11 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
} }
} }
}; };
private SparseBooleanArray mMenuItemStatus = new SparseBooleanArray();
private String mRestoredText; private String mRestoredText;
private NormalToolbarFragment mNormalToolbar = new NormalToolbarFragment_(); private NormalToolbarFragment mNormalToolbar = new NormalToolbarFragment_();
private boolean mDebugging = false;
public EditorView(Context context) { public EditorView(Context context) {
super(context); super(context);
@@ -231,6 +242,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
private void setMenuItemStatus(int id, boolean enabled) { private void setMenuItemStatus(int id, boolean enabled) {
mMenuItemStatus.put(id, enabled);
ToolbarFragment fragment = (ToolbarFragment) getActivity().getSupportFragmentManager() ToolbarFragment fragment = (ToolbarFragment) getActivity().getSupportFragmentManager()
.findFragmentById(R.id.toolbar_menu); .findFragmentById(R.id.toolbar_menu);
if (fragment == null) { if (fragment == null) {
@@ -240,6 +252,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
} }
} }
public boolean getMenuItemStatus(int id, boolean defValue) {
return mMenuItemStatus.get(id, defValue);
}
@AfterViews @AfterViews
void init() { void init() {
@@ -347,6 +362,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
} }
} }
@SuppressLint("CheckResult")
public void runAndSaveFileIfNeeded() { public void runAndSaveFileIfNeeded() {
save().observeOn(AndroidSchedulers.mainThread()) save().observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> run()); .subscribe(s -> run());
@@ -515,6 +531,26 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
} }
public void launchDebugger() {
DebugToolbarFragment debugToolbarFragment = DebugToolbarFragment_.builder()
.build();
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.toolbar_menu, debugToolbarFragment)
.commit();
mDebugging = true;
}
public void exitDebugging() {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.toolbar_menu);
if (fragment instanceof DebugToolbarFragment) {
((DebugToolbarFragment) fragment).detachDebugger();
}
showNormalToolbar();
mEditor.setDebuggingLine(-1);
mDebugging = false;
}
private void showErrorMessage(String msg) { private void showErrorMessage(String msg) {
Snackbar.make(EditorView.this, getResources().getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG) Snackbar.make(EditorView.this, getResources().getString(R.string.text_error) + ": " + msg, Snackbar.LENGTH_LONG)
.setAction(R.string.text_detail, v -> LogActivity_.intent(getContext()).start()) .setAction(R.string.text_detail, v -> LogActivity_.intent(getContext()).start())

View File

@@ -22,7 +22,6 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Parcelable;
import android.support.v7.widget.AppCompatEditText; import android.support.v7.widget.AppCompatEditText;
import android.text.Editable; import android.text.Editable;
import android.text.Layout; import android.text.Layout;
@@ -31,14 +30,16 @@ import android.util.Log;
import android.util.TimingLogger; import android.util.TimingLogger;
import android.view.Gravity; import android.view.Gravity;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.ui.edit.theme.Theme; import org.autojs.autojs.ui.edit.theme.Theme;
import org.autojs.autojs.ui.edit.theme.TokenMapping; import org.autojs.autojs.ui.edit.theme.TokenMapping;
import com.stardust.util.ClipboardUtil; import com.stardust.util.ClipboardUtil;
import com.stardust.util.TextUtils; import com.stardust.util.TextUtils;
import org.mozilla.javascript.Token; import org.mozilla.javascript.Token;
import java.util.LinkedHashMap;
import static org.autojs.autojs.ui.edit.editor.BracketMatching.UNMATCHED_BRACKET; import static org.autojs.autojs.ui.edit.editor.BracketMatching.UNMATCHED_BRACKET;
/** /**
@@ -62,6 +63,8 @@ public class CodeEditText extends AppCompatEditText {
private int mFirstLineForDraw = -1, mLastLineForDraw; private int mFirstLineForDraw = -1, mLastLineForDraw;
private int[] mMatchingBrackets = {-1, -1}; private int[] mMatchingBrackets = {-1, -1};
private int mUnmatchedBracket = -1; private int mUnmatchedBracket = -1;
private LinkedHashMap<Integer, CodeEditor.Breakpoint> mBreakpoints = new LinkedHashMap<>();
private int mDebuggingLine = -1;
public CodeEditText(Context context) { public CodeEditText(Context context) {
@@ -87,6 +90,10 @@ public class CodeEditText extends AppCompatEditText {
mLineHighlightPaint.setStyle(Paint.Style.FILL); mLineHighlightPaint.setStyle(Paint.Style.FILL);
} }
public LinkedHashMap<Integer, CodeEditor.Breakpoint> getBreakpoints() {
return mBreakpoints;
}
public void setTheme(Theme theme) { public void setTheme(Theme theme) {
mTheme = theme; mTheme = theme;
invalidate(); invalidate();
@@ -101,8 +108,8 @@ public class CodeEditText extends AppCompatEditText {
updatePaddingForGutter(); updatePaddingForGutter();
updateLineRangeForDraw(canvas); updateLineRangeForDraw(canvas);
//绘制当前行高亮需要在绘制光标之前 //绘制行高亮需要在绘制光标之前
drawLineHighlight(canvas, mLineHighlightPaint, getCurrentLine()); drawLineHighlights(canvas);
//调用super.onDraw绘制光标和选择高亮。因为字体颜色被设置为透明因此super.onDraw()绘制的字体不显示 //调用super.onDraw绘制光标和选择高亮。因为字体颜色被设置为透明因此super.onDraw()绘制的字体不显示
// TODO: 2018/2/24 优化效率。不绘制透明字体。 // TODO: 2018/2/24 优化效率。不绘制透明字体。
@@ -118,6 +125,30 @@ public class CodeEditText extends AppCompatEditText {
mLogger.dumpToLog(); mLogger.dumpToLog();
} }
public int getDebuggingLine() {
return mDebuggingLine;
}
public void setDebuggingLine(int debuggingLine) {
mDebuggingLine = debuggingLine;
invalidate();
}
private void drawLineHighlights(Canvas canvas) {
int currentLine = getCurrentLine();
int debugHighlightLine = mDebuggingLine;
if(debugHighlightLine != currentLine){
//绘制当前行高亮
mLineHighlightPaint.setColor(mTheme.getLineHighlightBackgroundColor());
drawLineHighlight(canvas, mLineHighlightPaint, getCurrentLine());
}
if(debugHighlightLine != -1){
mLineHighlightPaint.setColor(mTheme.getDebuggingLineBackgroundColor());
drawLineHighlight(canvas, mLineHighlightPaint, debugHighlightLine);
}
}
private void updateLineRangeForDraw(Canvas canvas) { private void updateLineRangeForDraw(Canvas canvas) {
Layout layout = getLayout(); Layout layout = getLayout();
if (layout == null) if (layout == null)
@@ -159,15 +190,22 @@ public class CodeEditText extends AppCompatEditText {
int scrollX = Math.max(getRealScrollX() - paddingLeft, 0); int scrollX = Math.max(getRealScrollX() - paddingLeft, 0);
Paint paint = getPaint(); Paint paint = getPaint();
int lineNumberColor = mTheme.getLineNumberColor(); int lineNumberColor = mTheme.getLineNumberColor();
int breakPointColor = mTheme.getBreakpointColor();
if (DEBUG) if (DEBUG)
Log.d(LOG_TAG, "draw line: " + (mLastLineForDraw - mFirstLineForDraw + 1)); Log.d(LOG_TAG, "draw line: " + (mLastLineForDraw - mFirstLineForDraw + 1));
mLogger.addSplit("before draw line"); mLogger.addSplit("before draw line");
for (int line = mFirstLineForDraw; line <= mLastLineForDraw && line < lineCount; line++) { for (int line = mFirstLineForDraw; line <= mLastLineForDraw && line < lineCount; line++) {
int lineBottom = layout.getLineTop(line + 1); int lineBottom = layout.getLineTop(line + 1);
int lineTop = layout.getLineTop(line);
int lineBaseline = lineBottom - layout.getLineDescent(line); int lineBaseline = lineBottom - layout.getLineDescent(line);
//drawLineNumber //drawLineNumber
String lineNumberText = Integer.toString(line + 1); String lineNumberText = Integer.toString(line + 1);
// if there is a breakpoint at this line, draw highlight background for line number
if (mBreakpoints.containsKey(line)) {
paint.setColor(breakPointColor);
canvas.drawRect(0, lineTop, paddingLeft - 10, lineBottom, paint);
}
paint.setColor(lineNumberColor); paint.setColor(lineNumberColor);
canvas.drawText(lineNumberText, 0, lineNumberText.length(), 10, canvas.drawText(lineNumberText, 0, lineNumberText.length(), 10,
lineBaseline, paint); lineBaseline, paint);
@@ -225,7 +263,6 @@ public class CodeEditText extends AppCompatEditText {
} }
int lineTop = getLayout().getLineTop(line); int lineTop = getLayout().getLineTop(line);
int lineBottom = getLayout().getLineTop(line + 1); int lineBottom = getLayout().getLineTop(line + 1);
paint.setColor(mTheme.getLineHighlightBackgroundColor());
canvas.drawRect(0, lineTop, canvas.getWidth(), lineBottom, paint); canvas.drawRect(0, lineTop, canvas.getWidth(), lineBottom, paint);
} }

View File

@@ -7,13 +7,15 @@ import android.util.AttributeSet;
import android.widget.Toast; import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
import com.android.dx.util.IntList;
import com.stardust.autojs.script.JsBeautifier; import com.stardust.autojs.script.JsBeautifier;
import org.autojs.autojs.R; import org.autojs.autojs.R;
import org.autojs.autojs.ui.edit.theme.Theme; import org.autojs.autojs.ui.edit.theme.Theme;
import com.stardust.util.ClipboardUtil; import com.stardust.util.ClipboardUtil;
import com.stardust.util.TextUtils; import com.stardust.util.TextUtils;
import java.util.LinkedHashMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -53,7 +55,6 @@ public class CodeEditor extends HVScrollView {
private JsBeautifier mJsBeautifier; private JsBeautifier mJsBeautifier;
private MaterialDialog mProcessDialog; private MaterialDialog mProcessDialog;
private CharSequence mReplacement = ""; private CharSequence mReplacement = "";
private String mKeywords; private String mKeywords;
private Matcher mMatcher; private Matcher mMatcher;
@@ -73,7 +74,7 @@ public class CodeEditor extends HVScrollView {
private void init() { private void init() {
//setFillViewport(true); //setFillViewport(true);
inflate(getContext(), R.layout.code_editor, this); inflate(getContext(), R.layout.code_editor, this);
mCodeEditText = (CodeEditText) findViewById(R.id.code_edit_text); mCodeEditText = findViewById(R.id.code_edit_text);
mCodeEditText.addTextChangedListener(new AutoIndent(mCodeEditText)); mCodeEditText.addTextChangedListener(new AutoIndent(mCodeEditText));
mTextViewRedoUndo = new TextViewRedoUndo(mCodeEditText); mTextViewRedoUndo = new TextViewRedoUndo(mCodeEditText);
mJavaScriptHighlighter = new JavaScriptHighlighter(mTheme, mCodeEditText); mJavaScriptHighlighter = new JavaScriptHighlighter(mTheme, mCodeEditText);
@@ -260,7 +261,7 @@ public class CodeEditor extends HVScrollView {
} }
public void findPrev() { public void findPrev() {
if (mMatcher != null){ if (mMatcher != null) {
Toast.makeText(getContext(), R.string.error_regex_find_prev, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), R.string.error_regex_find_prev, Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -329,6 +330,30 @@ public class CodeEditor extends HVScrollView {
mTextViewRedoUndo.markTextAsUnchanged(); mTextViewRedoUndo.markTextAsUnchanged();
} }
public LinkedHashMap<Integer, Breakpoint> getBreakpoints() {
return mCodeEditText.getBreakpoints();
}
public void setDebuggingLine(int line){
mCodeEditText.setDebuggingLine(line);
}
public void addOrRemoveBreakpoint(int line) {
LinkedHashMap<Integer, Breakpoint> breakpoints = mCodeEditText.getBreakpoints();
if(breakpoints.remove(line) == null){
breakpoints.put(line, new Breakpoint(line));
}
mCodeEditText.invalidate();
}
public void addOrRemoveBreakpointAtCurrentLine() {
int line = LayoutHelper.getLineOfChar(mCodeEditText.getLayout(), mCodeEditText.getSelectionStart());
if (line < 0 || line >= mCodeEditText.getLayout().getLineCount())
return;
addOrRemoveBreakpoint(line);
}
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight(); int codeWidth = getWidth() - getPaddingLeft() - getPaddingRight();
@@ -340,4 +365,14 @@ public class CodeEditor extends HVScrollView {
} }
super.onDraw(canvas); super.onDraw(canvas);
} }
public static class Breakpoint {
public int line;
public boolean enabled = true;
public Breakpoint(int line) {
this.line = line;
}
}
} }

View File

@@ -25,6 +25,8 @@ public class Theme {
private int mImeBarForegroundColor = Color.WHITE; private int mImeBarForegroundColor = Color.WHITE;
private EditorTheme mEditorTheme; private EditorTheme mEditorTheme;
private int mLineHighlightBackground; private int mLineHighlightBackground;
private int mBreakpointColor;
private int mDebuggingLineBackground;
public Theme(EditorTheme theme) { public Theme(EditorTheme theme) {
mEditorTheme = theme; mEditorTheme = theme;
@@ -34,6 +36,8 @@ public class Theme {
mImeBarBackgroundColor = parseColor(theme.getEditorColors().getImeBackgroundColor(), mImeBarBackgroundColor); mImeBarBackgroundColor = parseColor(theme.getEditorColors().getImeBackgroundColor(), mImeBarBackgroundColor);
mImeBarForegroundColor = parseColor(theme.getEditorColors().getImeForegroundColor(), mImeBarForegroundColor); mImeBarForegroundColor = parseColor(theme.getEditorColors().getImeForegroundColor(), mImeBarForegroundColor);
mLineHighlightBackground = parseColor(theme.getEditorColors().getLineHighlightBackground(), mLineHighlightBackground); mLineHighlightBackground = parseColor(theme.getEditorColors().getLineHighlightBackground(), mLineHighlightBackground);
mDebuggingLineBackground = parseColor(theme.getEditorColors().getDebuggingLineBackground(), mDebuggingLineBackground);
mBreakpointColor = parseColor(theme.getEditorColors().getBreakpointForeground(), mBackgroundColor);
for (TokenColor tokenColor : theme.getTokenColors()) { for (TokenColor tokenColor : theme.getTokenColors()) {
String foregroundStr = tokenColor.getSettings().getForeground(); String foregroundStr = tokenColor.getSettings().getForeground();
@@ -141,4 +145,11 @@ public class Theme {
} }
public int getBreakpointColor() {
return mBreakpointColor;
}
public int getDebuggingLineBackgroundColor() {
return mDebuggingLineBackground;
}
} }

View File

@@ -1,35 +1,116 @@
package org.autojs.autojs.ui.edit.toolbar; package org.autojs.autojs.ui.edit.toolbar;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.view.View;
import com.stardust.autojs.engine.RhinoJavaScriptEngine;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.rhino.debug.Dim;
import com.stardust.autojs.rhino.debug.DebugCallback;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.ui.edit.EditorView;
import org.autojs.autojs.ui.edit.editor.CodeEditor;
import org.mozilla.javascript.ContextFactory; import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.tools.debugger.Dim;
import org.mozilla.javascript.tools.debugger.GuiCallback;
public class DebugToolbarFragment implements GuiCallback { import java.util.Arrays;
import java.util.List;
private Dim mDim; @EFragment(R.layout.fragment_debug_toolbar)
public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback {
public void attachDebugger(){ private static final String LOG_TAG = "DebugToolbarFragment";
mDim.attachTo(ContextFactory.getGlobal()); private Dim mDim = new Dim();
private EditorView mEditorView;
private Handler mHandler;
public DebugToolbarFragment() {
mDim.setGuiCallback(this);
mDim.setBreak();
mDim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal());
Log.d(LOG_TAG, "DebugToolbarFragment()");
} }
public void deattchDebugger(){ @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mEditorView = findEditorView(view);
mEditorView.run();
Log.d(LOG_TAG, "onViewCreated");
}
public void detachDebugger() {
mDim.detach(); mDim.detach();
} }
public void breakpoint(){ @Click(R.id.step_over)
mDim.setGuiCallback(this); void stepOver() {
mEditorView.getEditor().setDebuggingLine(-1);
mDim.setReturnValue(Dim.STEP_OVER);
}
@Click(R.id.step_into)
void stepInto() {
mEditorView.getEditor().setDebuggingLine(-1);
mDim.setReturnValue(Dim.STEP_INTO);
}
@Click(R.id.stop_out)
void stepOut() {
mEditorView.getEditor().setDebuggingLine(-1);
mDim.setReturnValue(Dim.STEP_OUT);
}
@Click(R.id.stop_script)
void stopScript() {
mEditorView.forceStop();
}
@Click(R.id.resume_script)
void resumeScript() {
mEditorView.getEditor().setDebuggingLine(-1);
mDim.setReturnValue(Dim.GO);
} }
@Override @Override
public void updateSourceText(Dim.SourceInfo sourceInfo) { public void updateSourceText(Dim.SourceInfo sourceInfo) {
Log.d(LOG_TAG, "updateSourceText: url = " + sourceInfo.url() + ", source = " + sourceInfo.source());
if (!sourceInfo.url().equals(mEditorView.getFile().toString())) {
return;
}
sourceInfo.removeAllBreakpoints();
for (CodeEditor.Breakpoint breakpoint : mEditorView.getEditor().getBreakpoints().values()) {
int line = breakpoint.line + 1;
if (sourceInfo.breakableLine(line)) {
sourceInfo.breakpoint(line, breakpoint.enabled);
Log.d(LOG_TAG, "not breakable: " + line);
}
}
} }
@Override @Override
public void enterInterrupt(Dim.StackFrame stackFrame, String s, String s1) { public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String s1) {
Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadName + ", url = " + stackFrame.getUrl() + ", line = " + stackFrame.getLineNumber());
if (stackFrame.getUrl().equals(mEditorView.getFile().toString())) {
mEditorView.getEditor().setDebuggingLine(stackFrame.getLineNumber() - 1);
} else {
mHandler.post(this::resumeScript);
}
} }
@Override @Override
@@ -38,7 +119,26 @@ public class DebugToolbarFragment implements GuiCallback {
} }
@Override @Override
public void dispatchNextGuiEvent() throws InterruptedException { public void dispatchNextGuiEvent() {
Log.d(LOG_TAG, "dispatchNextGuiEvent");
} }
@Override
public boolean shouldAttachDebugger(RhinoJavaScriptEngine engine) {
ScriptExecution execution = AutoJs.getInstance().getScriptEngineService().getScriptExecution(mEditorView.getScriptExecutionId());
return execution != null && execution.getId() == engine.getId();
}
@Override
public List<Integer> getMenuItemIds() {
return Arrays.asList(R.id.step_over, R.id.step_into, R.id.stop_out, R.id.resume_script, R.id.stop_script);
}
@Override
public void onDestroy() {
super.onDestroy();
mDim.detach();
}
} }

View File

@@ -1,11 +1,14 @@
package org.autojs.autojs.ui.edit.toolbar; package org.autojs.autojs.ui.edit.toolbar;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.view.View; import android.view.View;
import org.autojs.autojs.ui.edit.EditorView;
import java.util.List; import java.util.List;
public abstract class ToolbarFragment extends Fragment implements View.OnClickListener { public abstract class ToolbarFragment extends Fragment implements View.OnClickListener {
@@ -16,7 +19,6 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
private OnMenuItemClickListener mOnMenuItemClickListener; private OnMenuItemClickListener mOnMenuItemClickListener;
private List<Integer> mMenuItemIds; private List<Integer> mMenuItemIds;
private SparseBooleanArray mMenuItemStatus = new SparseBooleanArray();
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mOnMenuItemClickListener = listener; mOnMenuItemClickListener = listener;
@@ -27,21 +29,32 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
updateMenuItemStatus(); updateMenuItemStatus(view);
} }
private void updateMenuItemStatus() { protected EditorView findEditorView(View view) {
View rootView = getView(); while (!(view instanceof EditorView) && view.getParent() != null) {
view = (View) view.getParent();
}
if (!(view instanceof EditorView)) {
throw new IllegalStateException("cannot find EditorView from child: " + view);
}
return (EditorView) view;
}
private void updateMenuItemStatus(View rootView) {
if (rootView == null) { if (rootView == null) {
return; return;
} }
EditorView editorView = findEditorView(rootView);
if (mMenuItemIds == null) { if (mMenuItemIds == null) {
mMenuItemIds = getMenuItemIds(); mMenuItemIds = getMenuItemIds();
} }
for (int id : mMenuItemIds) { for (int id : mMenuItemIds) {
View view = rootView.findViewById(id); View view = rootView.findViewById(id);
view.setOnClickListener(this); view.setOnClickListener(this);
view.setEnabled(mMenuItemStatus.get(id, view.isEnabled())); view.setEnabled(editorView.getMenuItemStatus(id, view.isEnabled()));
} }
} }
@@ -60,7 +73,6 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
if (!mMenuItemIds.contains(id)) { if (!mMenuItemIds.contains(id)) {
return; return;
} }
mMenuItemStatus.put(id, enabled);
View rootView = getView(); View rootView = getView();
if (rootView == null) { if (rootView == null) {
return; return;

View File

@@ -64,7 +64,7 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
private ScriptExecutionListener mScriptExecutionListener = new SimpleScriptExecutionListener() { private ScriptExecutionListener mScriptExecutionListener = new SimpleScriptExecutionListener() {
@Override @Override
public void onStart(final ScriptExecution execution) { public void onStart(final ScriptExecution execution) {
mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(execution)); post(()-> mAdapter.notifyChildInserted(0, mRunningTaskGroup.addTask(execution)));
} }
@Override @Override
@@ -78,12 +78,14 @@ public class TaskListRecyclerView extends ThemeColorRecyclerView {
} }
private void onFinish(ScriptExecution execution){ private void onFinish(ScriptExecution execution){
final int i = mRunningTaskGroup.removeTask(execution); post(()->{
if (i >= 0) { final int i = mRunningTaskGroup.removeTask(execution);
mAdapter.notifyChildRemoved(0, i); if (i >= 0) {
} else { mAdapter.notifyChildRemoved(0, i);
refresh(); } else {
} refresh();
}
});
} }
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right"
android:orientation="horizontal">
<org.autojs.autojs.ui.widget.ToolbarMenuItem
android:id="@+id/step_over"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_debug_step_over"
app:text="@string/text_debug_step_over"/>
<org.autojs.autojs.ui.widget.ToolbarMenuItem
android:id="@+id/step_into"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_debug_step_into"
app:text="@string/text_debug_step_into"/>
<org.autojs.autojs.ui.widget.ToolbarMenuItem
android:id="@+id/stop_out"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_debug_step_out"
app:text="@string/text_debug_step_out"/>
<org.autojs.autojs.ui.widget.ToolbarMenuItem
android:id="@+id/resume_script"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_play_arrow_white_48dp"
app:text="@string/text_debug_resume_script"/>
<org.autojs.autojs.ui.widget.ToolbarMenuItem
android:id="@+id/stop_script"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_close_white_48dp"
app:text="@string/text_stop"/>
</LinearLayout>

View File

@@ -79,6 +79,24 @@
</item> </item>
<item
android:title="@string/text_debug"
app:showAsAction="never">
<menu>
<item
android:id="@+id/action_breakpoint"
android:title="@string/text_set_breakpoint"
app:showAsAction="never"/>
<item
android:id="@+id/action_launch_debugger"
android:title="@string/text_launch_debugger"
app:showAsAction="never"/>
</menu>
</item>
<item <item
android:id="@+id/action_build_apk" android:id="@+id/action_build_apk"

View File

@@ -359,4 +359,12 @@
<string name="key_script_dir_path">key_script_dir_path</string> <string name="key_script_dir_path">key_script_dir_path</string>
<string name="default_value_script_dir_path">/脚本/</string> <string name="default_value_script_dir_path">/脚本/</string>
<string name="text_error_copy_file" formatted="true">发生错误: %s</string> <string name="text_error_copy_file" formatted="true">发生错误: %s</string>
<string name="text_debug">调试</string>
<string name="text_set_breakpoint">断点</string>
<string name="text_launch_debugger">启动调试</string>
<string name="text_debug_step_out">跳出</string>
<string name="text_debug_step_into">进入</string>
<string name="text_debug_step_over">单步</string>
<string name="text_debug_resume_script">继续</string>
<string name="text_stop">停止</string>
</resources> </resources>

View File

@@ -101,6 +101,7 @@ public abstract class AutoJs {
engine.setRuntime(createRuntime()); engine.setRuntime(createRuntime());
return engine; return engine;
}); });
LoopBasedJavaScriptEngine.initEngine();
mScriptEngineManager.registerEngine(AutoFileSource.ENGINE, () -> new RootAutomatorEngine(mContext)); mScriptEngineManager.registerEngine(AutoFileSource.ENGINE, () -> new RootAutomatorEngine(mContext));
} }

View File

@@ -86,5 +86,9 @@ public class LoopBasedJavaScriptEngine extends RhinoJavaScriptEngine {
super.init(); super.init();
} }
public static void initEngine(){
RhinoJavaScriptEngine.initEngine();
}
} }

View File

@@ -3,6 +3,7 @@ package com.stardust.autojs.engine;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.BuildConfig; import com.stardust.autojs.BuildConfig;
import com.stardust.autojs.rhino.AndroidContextFactory; import com.stardust.autojs.rhino.AndroidContextFactory;
import com.stardust.autojs.rhino.NativeJavaClassWithPrototype; import com.stardust.autojs.rhino.NativeJavaClassWithPrototype;
@@ -33,6 +34,7 @@ import java.io.Reader;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -43,10 +45,13 @@ import java.util.concurrent.ConcurrentHashMap;
public class RhinoJavaScriptEngine extends JavaScriptEngine { public class RhinoJavaScriptEngine extends JavaScriptEngine {
public static final String SOURCE_NAME_INIT = "<init>";
private static final String LOG_TAG = "RhinoJavaScriptEngine"; private static final String LOG_TAG = "RhinoJavaScriptEngine";
private static int contextCount = 0; private static int contextCount = 0;
private static StringScriptSource sInitScript; private static StringScriptSource sInitScript;
private static final ConcurrentHashMap<Context, RhinoJavaScriptEngine> sContextEngineMap = new ConcurrentHashMap<>();
private Context mContext; private Context mContext;
private Scriptable mScriptable; private Scriptable mScriptable;
@@ -69,7 +74,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
Reader reader = source.getNonNullScriptReader(); Reader reader = source.getNonNullScriptReader();
try { try {
reader = preprocess(reader); reader = preprocess(reader);
return mContext.evaluateReader(mScriptable, reader, "<" + source.getName() + ">", 1, null); return mContext.evaluateReader(mScriptable, reader, source.toString(), 1, null);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
@@ -90,6 +95,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
public synchronized void destroy() { public synchronized void destroy() {
super.destroy(); super.destroy();
Log.d(LOG_TAG, "on destroy"); Log.d(LOG_TAG, "on destroy");
sContextEngineMap.remove(getContext());
Context.exit(); Context.exit();
contextCount--; contextCount--;
Log.d(LOG_TAG, "contextCount = " + contextCount); Log.d(LOG_TAG, "contextCount = " + contextCount);
@@ -105,7 +111,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
mThread = Thread.currentThread(); mThread = Thread.currentThread();
ScriptableObject.putProperty(mScriptable, "__engine__", this); ScriptableObject.putProperty(mScriptable, "__engine__", this);
initRequireBuilder(mContext, mScriptable); initRequireBuilder(mContext, mScriptable);
mContext.evaluateString(mScriptable, getInitScript().getScript(), "<init>", 1, null); mContext.evaluateString(mScriptable, getInitScript().getScript(), SOURCE_NAME_INIT, 1, null);
} }
private JavaScriptSource getInitScript() { private JavaScriptSource getInitScript() {
@@ -148,12 +154,10 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
} }
public Context createContext() { public Context createContext() {
if (!ContextFactory.hasExplicitGlobal()) {
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(mAndroidContext.getCacheDir(), "classes")));
}
Context context = new RhinoAndroidHelper(mAndroidContext).enterContext(); Context context = new RhinoAndroidHelper(mAndroidContext).enterContext();
contextCount++; contextCount++;
setupContext(context); setupContext(context);
sContextEngineMap.put(context, this);
return context; return context;
} }
@@ -164,6 +168,17 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine {
context.setWrapFactory(new WrapFactory()); context.setWrapFactory(new WrapFactory());
} }
public static void initEngine() {
if (!ContextFactory.hasExplicitGlobal()) {
android.content.Context context = GlobalAppContext.get();
ContextFactory.initGlobal(new InterruptibleAndroidContextFactory(new File(context.getCacheDir(), "classes")));
}
}
public static RhinoJavaScriptEngine getEngineOfContext(Context context) {
return sContextEngineMap.get(context);
}
private class WrapFactory extends org.mozilla.javascript.WrapFactory { private class WrapFactory extends org.mozilla.javascript.WrapFactory {

View File

@@ -3,7 +3,6 @@ package com.stardust.autojs.engine;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import com.stardust.autojs.execution.ScriptExecution; import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.runtime.exception.ScriptException;
import com.stardust.autojs.script.ScriptSource; import com.stardust.autojs.script.ScriptSource;
import java.util.Map; import java.util.Map;
@@ -14,7 +13,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* Created by Stardust on 2017/4/2. * Created by Stardust on 2017/4/2.
* <p> * <p>
* <p> * <p>
* A ScriptEngine is created by {@link ScriptEngineManager#createEngine(String)} ()}, and then can be * A ScriptEngine is created by {@link ScriptEngineManager#createEngine(String, int)} ()}, and then can be
* used to execute script with {@link ScriptEngine#execute(ScriptSource)} in the **same** thread. * used to execute script with {@link ScriptEngine#execute(ScriptSource)} in the **same** thread.
* When the execution finish successfully, the engine should be destroy in the thread that created it. * When the execution finish successfully, the engine should be destroy in the thread that created it.
* <p> * <p>

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.script.ScriptSource; import com.stardust.autojs.script.ScriptSource;
import com.stardust.util.Supplier; import com.stardust.util.Supplier;
@@ -96,37 +97,34 @@ public class ScriptEngineManager {
@Nullable @Nullable
public ScriptEngine createEngine(String name) { public ScriptEngine createEngine(String name, int id) {
Supplier<ScriptEngine> s = mEngineSuppliers.get(name); Supplier<ScriptEngine> s = mEngineSuppliers.get(name);
if (s == null) { if (s == null) {
return null; return null;
} }
ScriptEngine engine = s.get(); ScriptEngine engine = s.get();
engine.setId(id);
putProperties(engine); putProperties(engine);
addEngine(engine); addEngine(engine);
return engine; return engine;
} }
@Nullable @Nullable
public ScriptEngine createEngineOfSource(ScriptSource source) { public ScriptEngine createEngineOfSource(ScriptSource source, int id) {
return createEngine(source.getEngineName()); return createEngine(source.getEngineName(), id);
} }
@NonNull @NonNull
public ScriptEngine createEngineByNameOrThrow(String name) { public ScriptEngine createEngineOfSourceOrThrow(ScriptSource source, int id) {
ScriptEngine engine = createEngine(name); ScriptEngine engine = createEngineOfSource(source, id);
if (engine == null) if (engine == null)
throw new ScriptEngineFactory.EngineNotFoundException("name: " + name); throw new ScriptEngineFactory.EngineNotFoundException("source: " + source.toString());
return engine; return engine;
} }
@NonNull @NonNull
public ScriptEngine createEngineOfSourceOrThrow(ScriptSource source) { public ScriptEngine createEngineOfSourceOrThrow(ScriptSource source) {
ScriptEngine engine = createEngineOfSource(source); return createEngineOfSourceOrThrow(source, ScriptExecution.NO_ID);
if (engine == null)
throw new ScriptEngineFactory.EngineNotFoundException("source: " + source.toString());
return engine;
} }
public void registerEngine(String name, Supplier<ScriptEngine> supplier) { public void registerEngine(String name, Supplier<ScriptEngine> supplier) {

View File

@@ -28,8 +28,7 @@ public class RunnableScriptExecution extends ScriptExecution.AbstractScriptExecu
} }
public Object execute() { public Object execute() {
mScriptEngine = mScriptEngineManager.createEngineOfSourceOrThrow(getSource()); mScriptEngine = mScriptEngineManager.createEngineOfSourceOrThrow(getSource(), getId());
mScriptEngine.setId(getId());
return execute(mScriptEngine); return execute(mScriptEngine);
} }

View File

@@ -0,0 +1,50 @@
package com.stardust.autojs.rhino.debug;
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import com.stardust.autojs.engine.RhinoJavaScriptEngine;
import com.stardust.autojs.engine.ScriptEngine;
import org.mozilla.javascript.Context;
/**
* Interface for communication between the debugger and its GUI. This
* should be implemented by the GUI.
*/
public interface DebugCallback {
/**
* Called when the source text of some script has been changed.
*/
void updateSourceText(Dim.SourceInfo sourceInfo);
/**
* Called when the interrupt loop has been entered.
*/
void enterInterrupt(Dim.StackFrame lastFrame,
String threadTitle,
String alertMessage);
/**
* Returns whether the current thread is the GUI's event thread.
* This information is required to avoid blocking the event thread
* from the debugger.
*/
boolean isGuiEventThread();
/**
* Processes the next GUI event. This manual pumping of GUI events
* is necessary when the GUI event thread itself has been stopped.
*/
void dispatchNextGuiEvent() throws InterruptedException;
/**
*
* Returns whether the debugger should attach to this engine or not.
*/
boolean shouldAttachDebugger(RhinoJavaScriptEngine engine);
}

File diff suppressed because it is too large Load Diff