feat(debug): supports debugging across files; supports watching variables; supports evaluate code on debug;

This commit is contained in:
hyb1996
2018-09-08 17:30:01 +08:00
parent 5329278e8d
commit a2c46d0787
27 changed files with 786 additions and 103 deletions

View File

@@ -10,6 +10,7 @@ import com.stardust.pio.PFiles;
import org.autojs.autojs.R;
import org.autojs.autojs.ui.build.BuildActivity;
import org.autojs.autojs.ui.build.BuildActivity_;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
import org.autojs.autojs.ui.edit.editor.CodeEditor;
import org.autojs.autojs.ui.log.LogActivity_;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
@@ -68,7 +69,15 @@ public class EditorMenu {
mEditor.addOrRemoveBreakpointAtCurrentLine();
return true;
case R.id.action_launch_debugger:
mEditorView.launchDebugger();
new NotAskAgainDialog.Builder(mEditorView.getContext(), "editor.debug.long_click_hint")
.title(R.string.text_alert)
.content(R.string.hint_long_click_run_to_debug)
.positiveText(R.string.ok)
.show();
mEditorView.debug();
return true;
case R.id.action_remove_all_breakpoints:
mEditor.removeAllBreakpoints();
return true;
}
return false;

View File

@@ -48,6 +48,7 @@ import org.autojs.autojs.model.indices.Property;
import org.autojs.autojs.model.script.Scripts;
import org.autojs.autojs.ui.doc.ManualDialog;
import org.autojs.autojs.ui.edit.completion.CodeCompletionBar;
import org.autojs.autojs.ui.edit.debug.DebugBar;
import org.autojs.autojs.ui.edit.editor.CodeEditor;
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardHelper;
import org.autojs.autojs.ui.edit.keyboard.FunctionsKeyboardView;
@@ -104,6 +105,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
@ViewById(R.id.functions_keyboard)
FunctionsKeyboardView mFunctionsKeyboard;
@ViewById(R.id.debug_bar)
DebugBar mDebugBar;
@ViewById(R.id.docs)
EWebView mDocsWebView;
@@ -273,8 +277,15 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
private void initNormalToolbar() {
mNormalToolbar.setOnMenuItemClickListener(this);
mNormalToolbar.setOnMenuItemLongClickListener(id -> {
if(id == R.id.run){
debug();
return true;
}
return false;
});
Fragment fragment = getActivity().getSupportFragmentManager().findFragmentById(R.id.toolbar_menu);
if(fragment == null){
if (fragment == null) {
showNormalToolbar();
}
}
@@ -304,7 +315,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
setMenuItemStatus(R.id.undo, mEditor.canUndo());
setMenuItemStatus(R.id.redo, mEditor.canRedo());
}));
mEditor.setCursorChangeCallback(this::autoComplete);
mEditor.addCursorChangeCallback(this::autoComplete);
mEditor.getCodeEditText().setTextSize(Pref.getEditorTextSize((int) ViewUtils.pxToSp(getContext(), mEditor.getCodeEditText().getTextSize())));
}
@@ -312,6 +323,10 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
mAutoCompletion.onCursorChange(line, cursor);
}
public DebugBar getDebugBar() {
return mDebugBar;
}
public void setTheme(Theme theme) {
mEditorTheme = theme;
mEditor.setTheme(theme);
@@ -368,11 +383,13 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
@SuppressLint("CheckResult")
public void runAndSaveFileIfNeeded() {
save().observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> run());
.subscribe(s -> run(true));
}
public void run() {
Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
public void run(boolean showMessage) {
if(showMessage){
Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show();
}
mScriptExecutionId = Scripts.runWithBroadcastSender(mFile).getId();
setMenuItemStatus(R.id.run, false);
}
@@ -534,12 +551,14 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
}
public void launchDebugger() {
public void debug() {
DebugToolbarFragment debugToolbarFragment = DebugToolbarFragment_.builder()
.build();
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.toolbar_menu, debugToolbarFragment)
.commit();
mDebugBar.setVisibility(VISIBLE);
mInputMethodEnhanceBar.setVisibility(GONE);
mDebugging = true;
}
@@ -551,6 +570,8 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
}
showNormalToolbar();
mEditor.setDebuggingLine(-1);
mDebugBar.setVisibility(GONE);
mInputMethodEnhanceBar.setVisibility(VISIBLE);
mDebugging = false;
}
@@ -622,7 +643,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC
}
@Nullable
public ScriptExecution getScriptExecution(){
public ScriptExecution getScriptExecution() {
return AutoJs.getInstance().getScriptEngineService().getScriptExecution(mScriptExecutionId);
}

View File

@@ -0,0 +1,56 @@
package org.autojs.autojs.ui.edit.debug;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import org.autojs.autojs.R;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
public class CodeEvaluateDialogBuilder extends ThemeColorMaterialDialogBuilder {
private static final String KEY_CODE = CodeEvaluateDialogBuilder.class.getName() + ".code";
private CodeEvaluator mCodeEvaluator;
private TextView mResult;
private EditText mCode;
private SharedPreferences mSharedPreferences;
public CodeEvaluateDialogBuilder(@NonNull Context context) {
super(context);
setupViews();
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
mCode.setText(mSharedPreferences.getString(KEY_CODE, ""));
}
private void setupViews() {
View view = View.inflate(context, R.layout.dialog_code_evaluate, null);
customView(view, true);
mResult = view.findViewById(R.id.result);
mCode = view.findViewById(R.id.code);
positiveText(R.string.text_run);
negativeText(R.string.text_close);
autoDismiss(false);
onNegative((dialog, which) -> dialog.dismiss());
onPositive(((dialog, which) -> {
Editable code = mCode.getText();
if (!TextUtils.isEmpty(code)) {
String codeStr = code.toString();
mSharedPreferences.edit().putString(KEY_CODE, codeStr).apply();
mResult.setText(mCodeEvaluator.eval(codeStr));
}
}));
}
public CodeEvaluateDialogBuilder codeEvaluator(CodeEvaluator evaluator) {
mCodeEvaluator = evaluator;
return this;
}
}

View File

@@ -0,0 +1,5 @@
package org.autojs.autojs.ui.edit.debug;
public interface CodeEvaluator {
String eval(String code);
}

View File

@@ -0,0 +1,165 @@
package org.autojs.autojs.ui.edit.debug;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.stardust.util.ClipboardUtil;
import org.autojs.autojs.R;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import org.autojs.autojs.ui.widget.AutoAdapter;
import org.autojs.autojs.ui.widget.BindableViewHolder;
import java.util.List;
public class DebugBar extends FrameLayout {
private AutoAdapter<WatchingVariable> mVariablesAdapter;
private final WatchingVariable mCurrentVariable = new WatchingVariable(null, null, true);
private TextView mTitle;
private CodeEvaluator mCodeEvaluator;
public DebugBar(@NonNull Context context) {
super(context);
init();
}
public DebugBar(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public DebugBar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void setCodeEvaluator(CodeEvaluator codeEvaluator) {
mCodeEvaluator = codeEvaluator;
}
public List<WatchingVariable> getWatchingVariables() {
return mVariablesAdapter.getData();
}
private void init() {
inflate(getContext(), R.layout.debug_bar, this);
mVariablesAdapter = new AutoAdapter<>(VariableViewHolder::new, R.layout.item_debug_variable_recycler_view);
RecyclerView variablesView = findViewById(R.id.variables);
variablesView.setAdapter(mVariablesAdapter);
variablesView.setLayoutManager(new LinearLayoutManager(getContext()));
mVariablesAdapter.add(mCurrentVariable);
findViewById(R.id.add).setOnClickListener(view -> showNewWatchingVariableDialog());
findViewById(R.id.execute).setOnClickListener(view -> showExecuteCodeDialog());
mTitle = findViewById(R.id.title);
}
private void showExecuteCodeDialog() {
if (mCodeEvaluator == null) {
return;
}
new CodeEvaluateDialogBuilder(getContext())
.codeEvaluator(mCodeEvaluator)
.title(R.string.text_execute_code)
.show();
}
public void setTitle(String title) {
if (title == null) {
mTitle.setText(R.string.text_debug);
} else {
mTitle.setText(getResources().getString(R.string.format_debug_bar_title, title));
}
}
private void showNewWatchingVariableDialog() {
new ThemeColorMaterialDialogBuilder(getContext())
.title(R.string.text_new_watching_variable)
.input(getResources().getString(R.string.text_variable_or_expr), "", (dialog, input) -> {
if (TextUtils.isEmpty(input)) {
return;
}
mVariablesAdapter.add(new WatchingVariable(input.toString()));
})
.show();
}
public void updateCurrentVariable(String name, String value) {
mCurrentVariable.setDisplayName(name);
mCurrentVariable.setName(name);
mCurrentVariable.setValue(value);
mVariablesAdapter.notifyItemChanged(0);
}
public void refresh(int start, int count) {
mVariablesAdapter.notifyItemRangeChanged(start, count);
}
public void registerVariableChangeObserver(RecyclerView.AdapterDataObserver observer) {
mVariablesAdapter.registerAdapterDataObserver(observer);
}
public void unregisterVariableChangeObserver(RecyclerView.AdapterDataObserver observer) {
mVariablesAdapter.unregisterAdapterDataObserver(observer);
}
private void showVariable(WatchingVariable variable) {
new ThemeColorMaterialDialogBuilder(getContext())
.title(variable.getDisplayName())
.content(variable.getValue())
.positiveText(R.string.ok)
.negativeText(R.string.text_copy_value)
.autoDismiss(true)
.onNegative((dialog, which) -> ClipboardUtil.setClip(getContext(), variable.getValue()))
.show();
}
class VariableViewHolder extends BindableViewHolder<WatchingVariable> {
private final TextView mVariable;
private final ImageView mIcon;
VariableViewHolder(View itemView) {
super(itemView);
mVariable = itemView.findViewById(R.id.variable);
mIcon = itemView.findViewById(R.id.icon);
mIcon.setOnClickListener(view -> {
int pos = getAdapterPosition();
WatchingVariable variable = mVariablesAdapter.get(pos);
if (!variable.isPinned()) {
mVariablesAdapter.remove(pos);
}
});
itemView.setOnClickListener(view -> {
int pos = getAdapterPosition();
WatchingVariable variable = mVariablesAdapter.get(pos);
showVariable(variable);
});
}
@Override
public void bind(WatchingVariable data, int position) {
if (TextUtils.isEmpty(data.getDisplayName())) {
mVariable.setText("");
} else {
mVariable.setText(String.format("%s = %s", data.getDisplayName(), data.getSingleLineValue()));
}
mIcon.setVisibility(data.isPinned() ? View.INVISIBLE : View.VISIBLE);
}
}
}

View File

@@ -0,0 +1,53 @@
package org.autojs.autojs.ui.edit.debug;
public class WatchingVariable {
private String mDisplayName;
private String mName;
private final boolean mPinned;
private String mValue;
private String mSingleLineValue;
public WatchingVariable(String displayName, String name, boolean pinned) {
mDisplayName = displayName;
mName = name;
mPinned = pinned;
}
public WatchingVariable(String name) {
this(name, name, false);
}
public boolean isPinned() {
return mPinned;
}
public String getValue() {
return mValue;
}
public void setValue(String value) {
mValue = value;
mSingleLineValue = value == null ? null : value.replaceAll("\n", " ");
}
public String getSingleLineValue() {
return mSingleLineValue;
}
public void setDisplayName(String displayName) {
mDisplayName = displayName;
}
public void setName(String name) {
mName = name;
}
public String getDisplayName() {
return mDisplayName;
}
public String getName() {
return mName;
}
}

View File

@@ -32,18 +32,16 @@ import android.util.Log;
import android.util.TimingLogger;
import android.view.Gravity;
import org.autojs.autojs.R;
import org.autojs.autojs.ui.edit.theme.Theme;
import org.autojs.autojs.ui.edit.theme.TokenMapping;
import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.util.ClipboardUtil;
import com.stardust.util.TextUtils;
import org.mozilla.javascript.Token;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.autojs.autojs.ui.edit.editor.BracketMatching.UNMATCHED_BRACKET;
@@ -60,7 +58,7 @@ public class CodeEditText extends AppCompatEditText {
// 文字范围
protected HVScrollView mParentScrollView;
private CodeEditor.CursorChangeCallback mCallback;
private final CopyOnWriteArrayList<CodeEditor.CursorChangeCallback> mCursorChangeCallbacks = new CopyOnWriteArrayList<>();
private volatile JavaScriptHighlighter.HighlightTokens mHighlightTokens;
private Theme mTheme;
private TimingLogger mLogger = new TimingLogger(LOG_TAG, "draw");
@@ -110,6 +108,11 @@ public class CodeEditText extends AppCompatEditText {
if (mParentScrollView == null) {
mParentScrollView = (HVScrollView) getParent();
}
if (getLayout() == null) {
super.onDraw(canvas);
invalidate();
return;
}
updatePaddingForGutter();
updateLineRangeForDraw(canvas);
@@ -266,8 +269,12 @@ public class CodeEditText extends AppCompatEditText {
if (line < mFirstLineForDraw || line > mLastLineForDraw || mFirstLineForDraw < 0 || line < 0) {
return;
}
int lineTop = getLayout().getLineTop(line);
int lineBottom = getLayout().getLineTop(line + 1);
Layout layout = getLayout();
if(layout == null){
return;
}
int lineTop = layout.getLineTop(line);
int lineBottom = layout.getLineTop(line + 1);
canvas.drawRect(0, lineTop, canvas.getWidth(), lineBottom, paint);
}
@@ -322,7 +329,8 @@ public class CodeEditText extends AppCompatEditText {
protected void onSelectionChanged(int selStart, int selEnd) {
//调用父类的onSelectionChanged时会发送一个AccessibilityEvent当文本过大时造成异常
//super.onSelectionChanged(selStart, selEnd);
if (mCallback == null || selStart != selEnd) {
//父类构造函数会调用onSelectionChanged, 此时mCursorChangeCallbacks还没有初始化
if (mCursorChangeCallbacks == null || mCursorChangeCallbacks.isEmpty() || selStart != selEnd) {
return;
}
callCursorChangeCallback(getText(), selStart);
@@ -365,7 +373,7 @@ public class CodeEditText extends AppCompatEditText {
if (text.length() == 0) {
return;
}
if (mCallback == null)
if (mCursorChangeCallbacks.isEmpty())
return;
int lineStart = TextUtils.lastIndexOf(text, '\n', sel - 1) + 1;
if (lineStart < 0) {
@@ -382,13 +390,21 @@ public class CodeEditText extends AppCompatEditText {
return;
String line = text.subSequence(lineStart, lineEnd).toString();
int cursor = sel - lineStart;
mCallback.onCursorChange(line, cursor);
for(CodeEditor.CursorChangeCallback callback : mCursorChangeCallbacks){
callback.onCursorChange(line, cursor);
}
}
public void setCursorChangeCallback(CodeEditor.CursorChangeCallback callback) {
mCallback = callback;
public void addCursorChangeCallback(CodeEditor.CursorChangeCallback callback) {
mCursorChangeCallbacks.add(callback);
}
public boolean removeCursorChangeCallback(CodeEditor.CursorChangeCallback callback) {
return mCursorChangeCallbacks.remove(callback);
}
public void updateHighlightTokens(JavaScriptHighlighter.HighlightTokens highlightTokens) {
mHighlightTokens = highlightTokens;
postInvalidate();
@@ -427,7 +443,7 @@ public class CodeEditText extends AppCompatEditText {
Parcelable superData = bundle.getParcelable("super_data");
mDebuggingLine = bundle.getInt("debugging_line", -1);
int[] breakpoints = bundle.getIntArray("breakpoints");
if(breakpoints != null){
if (breakpoints != null) {
for (int breakpoint : breakpoints) {
mBreakpoints.put(breakpoint, new CodeEditor.Breakpoint(breakpoint));
}

View File

@@ -176,9 +176,14 @@ public class CodeEditor extends HVScrollView {
}
public void setReadOnly(boolean readOnly) {
setEnabled(!readOnly);
mCodeEditText.setEnabled(!readOnly);
}
public void setRedoUndoEnabled(boolean enabled) {
mTextViewRedoUndo.setEnabled(enabled);
}
public void setProgress(boolean progress) {
if (progress) {
if (mProcessDialog != null) {
@@ -202,8 +207,12 @@ public class CodeEditor extends HVScrollView {
mCodeEditText.setText(text);
}
public void setCursorChangeCallback(CursorChangeCallback callback) {
mCodeEditText.setCursorChangeCallback(callback);
public void addCursorChangeCallback(CursorChangeCallback callback) {
mCodeEditText.addCursorChangeCallback(callback);
}
public boolean removeCursorChangeCallback(CursorChangeCallback callback) {
return mCodeEditText.removeCursorChangeCallback(callback);
}
@@ -337,7 +346,6 @@ public class CodeEditor extends HVScrollView {
}
public void setDebuggingLine(int line) {
jumpTo(line, 0);
mCodeEditText.setDebuggingLine(line);
}
@@ -356,6 +364,11 @@ public class CodeEditor extends HVScrollView {
addOrRemoveBreakpoint(line);
}
public void removeAllBreakpoints(){
mCodeEditText.getBreakpoints().clear();
mCodeEditText.invalidate();
}
@Override
protected void onDraw(Canvas canvas) {

View File

@@ -42,6 +42,7 @@ public class TextViewRedoUndo {
private boolean flag = false;
private int mInitialHistoryStackSize;
private boolean mEnabled = true;
public TextViewRedoUndo(@NonNull EditText editText) {
this.editable = editText.getText();
@@ -156,6 +157,14 @@ public class TextViewRedoUndo {
mInitialHistoryStackSize = history.size();
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public boolean isEnabled() {
return mEnabled;
}
private class Watcher implements TextWatcher {
/**
@@ -168,6 +177,9 @@ public class TextViewRedoUndo {
*/
@Override
public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
int end = start + count;
if (end > start && end <= s.length()) {
@@ -201,6 +213,9 @@ public class TextViewRedoUndo {
*/
@Override
public final void onTextChanged(CharSequence s, int start, int before, int count) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
int end = start + count;
if (end > start) {
@@ -223,6 +238,9 @@ public class TextViewRedoUndo {
@Override
public final void afterTextChanged(Editable s) {
if (!editText.isEnabled() || !mEnabled) {
return;
}
if (flag) return;
if (s != editable) {
editable = s;

View File

@@ -4,20 +4,28 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
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 com.stardust.autojs.runtime.exception.ScriptInterruptedException;
import com.stardust.pio.PFiles;
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.debug.CodeEvaluator;
import org.autojs.autojs.ui.edit.debug.DebugBar;
import org.autojs.autojs.ui.edit.debug.WatchingVariable;
import org.autojs.autojs.ui.edit.editor.CodeEditor;
import org.mozilla.javascript.ContextFactory;
@@ -25,12 +33,23 @@ import java.util.Arrays;
import java.util.List;
@EFragment(R.layout.fragment_debug_toolbar)
public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback {
public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback, CodeEditor.CursorChangeCallback, CodeEvaluator {
private static final String LOG_TAG = "DebugToolbarFragment";
private Dim mDim;
private EditorView mEditorView;
private Handler mHandler;
private boolean mSkipOtherFileBreakpoint = false;
private String mCurrentEditorSourceUrl;
private String mInitialEditorSourceUrl;
private String mInitialEditorSource;
private boolean mCursorChangeFromUser = true;
private final RecyclerView.AdapterDataObserver mVariableChangeObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
updateWatchingVariables(positionStart, positionStart + itemCount);
}
};
public DebugToolbarFragment() {
Log.d(LOG_TAG, "DebugToolbarFragment()");
@@ -46,37 +65,64 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mEditorView = findEditorView(view);
ScriptExecution scriptExecution = mEditorView.getScriptExecution();
if (scriptExecution != null) {
mDim = (Dim) scriptExecution.getEngine().getTag(Dim.TAG);
}
if (mDim == null) {
mDim = new Dim();
mDim.setBreak();
mDim.setBreakOnExceptions(true);
mDim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal());
mDim.setGuiCallback(this);
setInterrupted(false);
mEditorView.run();
} else {
mDim.setGuiCallback(this);
}
mDim = createDim();
setInterrupted(false);
mSkipOtherFileBreakpoint = true;
mCurrentEditorSourceUrl = mInitialEditorSourceUrl = mEditorView.getFile().toString();
mInitialEditorSource = mEditorView.getEditor().getText();
setupEditor();
mEditorView.run(false);
Log.d(LOG_TAG, "onViewCreated");
}
private void setupEditor() {
CodeEditor editor = mEditorView.getEditor();
editor.setRedoUndoEnabled(false);
editor.addCursorChangeCallback(this);
DebugBar debugBar = mEditorView.getDebugBar();
debugBar.registerVariableChangeObserver(mVariableChangeObserver);
debugBar.setCodeEvaluator(this);
}
private Dim createDim() {
Dim dim = new Dim();
dim.setBreak();
dim.setBreakOnExceptions(true);
dim.attachTo(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal());
dim.setGuiCallback(this);
return dim;
}
private void setInterrupted(boolean interrupted) {
setMenuItemStatus(R.id.step_into, interrupted);
setMenuItemStatus(R.id.step_over, interrupted);
setMenuItemStatus(R.id.step_out, interrupted);
setMenuItemStatus(R.id.resume_script, interrupted);
if (!interrupted) {
if (!interrupted && mEditorView != null) {
mEditorView.getEditor().setDebuggingLine(-1);
}
}
public void detachDebugger() {
if (!mDim.isAttached()) {
return;
}
mDim.detach();
mDim.setGuiCallback(null);
if (mEditorView == null) {
return;
}
CodeEditor editor = mEditorView.getEditor();
editor.removeCursorChangeCallback(this);
editor.setRedoUndoEnabled(true);
if (!TextUtils.equals(mInitialEditorSourceUrl, mCurrentEditorSourceUrl)) {
editor.setText(mInitialEditorSource);
}
DebugBar debugBar = mEditorView.getDebugBar();
debugBar.setTitle(null);
debugBar.setCodeEvaluator(null);
debugBar.unregisterVariableChangeObserver(mVariableChangeObserver);
}
@Click(R.id.step_over)
@@ -125,18 +171,72 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba
}
@Override
public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String s1) {
public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String message) {
Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadName + ", url = " + stackFrame.getUrl() + ", line = " + stackFrame.getLineNumber());
if (stackFrame.getUrl().equals(mEditorView.getFile().toString())) {
final int line = stackFrame.getLineNumber() - 1;
mHandler.post(() -> {
mEditorView.getEditor().setDebuggingLine(line);
setInterrupted(true);
});
} else {
//刚启动调试时会在init脚本的第一行自动停下此时应该让脚本继续运行
if (mSkipOtherFileBreakpoint && !stackFrame.getUrl().equals(mInitialEditorSourceUrl) && message == null) {
mHandler.post(this::resumeScript);
return;
}
mSkipOtherFileBreakpoint = false;
showDebuggingLineOnEditor(stackFrame, message);
mHandler.post(this::updateWatchingVariables);
}
private void updateWatchingVariables() {
updateWatchingVariables(0, mEditorView.getDebugBar().getWatchingVariables().size());
}
private void updateWatchingVariables(int start, int end) {
if (!mDim.isAttached()) {
return;
}
DebugBar debugBar = mEditorView.getDebugBar();
List<WatchingVariable> variables = debugBar.getWatchingVariables();
for (int i = start; i < end; i++) {
WatchingVariable variable = variables.get(i);
String value = eval(variable.getName());
variable.setValue(value);
}
debugBar.refresh(start, end - start);
}
public String eval(String expr) {
if (expr == null || !mDim.isAttached() || !mDim.stringIsCompilableUnit(expr)) {
return null;
}
mDim.contextSwitch(0);
return mDim.eval(expr);
}
private void showDebuggingLineOnEditor(Dim.StackFrame stackFrame, String message) {
//如果调试进入到其他脚本(例如模块脚本),则改变当前编辑器的文本为自动调试的脚本的代码
String source;
//标记是否需要更改编辑器文本
boolean shouldChangeText = !stackFrame.getUrl().equals(mCurrentEditorSourceUrl);
if (shouldChangeText) {
source = stackFrame.sourceInfo().source();
} else {
source = null;
}
mCurrentEditorSourceUrl = stackFrame.getUrl();
final int line = stackFrame.getLineNumber() - 1;
mHandler.post(() -> {
if (mEditorView == null) {
return;
}
if (shouldChangeText) {
mEditorView.getEditor().setText(source);
}
mCursorChangeFromUser = false;
mEditorView.getEditor().setDebuggingLine(line);
mEditorView.getEditor().jumpTo(line, 0);
mEditorView.getDebugBar().setTitle(PFiles.getName(mCurrentEditorSourceUrl));
setInterrupted(true);
if (message != null && !message.equals(ScriptInterruptedException.class.getName())) {
Toast.makeText(mEditorView.getContext(), message, Toast.LENGTH_LONG).show();
}
});
}
@Override
@@ -155,9 +255,54 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba
}
@Override
public void onCursorChange(String line, int ch) {
if (ch == 0 && !mCursorChangeFromUser) {
mCursorChangeFromUser = true;
return;
}
mCursorChangeFromUser = true;
if (!mDim.isAttached()) {
return;
}
String variable = findVariableOnCursor(line, ch);
Log.d(LOG_TAG, "onCursorChange: variable = " + variable + ", ch = " + ch + ", line = " + line);
String value = eval(variable);
mEditorView.getDebugBar().updateCurrentVariable(variable, value);
}
private String findVariableOnCursor(String line, int ch) {
int end;
for (end = ch; end < line.length(); end++) {
if (!isIdentifierChar(line.charAt(end))) {
break;
}
}
int start;
for (start = Math.min(ch - 1, line.length() - 1); start >= 0; start--) {
if (!isIdentifierChar(line.charAt(start))) {
break;
}
}
start++;
if (start < end && start < line.length() && start >= 0) {
return line.substring(start, end);
}
return null;
}
private boolean isIdentifierChar(char c) {
return Character.isDigit(c) || Character.isLetter(c) || c == '.' || c == '_';
}
@Override
public List<Integer> getMenuItemIds() {
return Arrays.asList(R.id.step_over, R.id.step_into, R.id.step_out, R.id.resume_script, R.id.stop_script);
}
@Override
public void onDestroy() {
super.onDestroy();
detachDebugger();
}
}

View File

@@ -1,8 +1,5 @@
package org.autojs.autojs.ui.edit.toolbar;
import android.support.v4.app.Fragment;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.autojs.autojs.R;
@@ -12,7 +9,6 @@ import java.util.List;
@EFragment(R.layout.fragment_normal_toolbar)
public class NormalToolbarFragment extends ToolbarFragment {
@Override
public List<Integer> getMenuItemIds() {
return Arrays.asList(R.id.run, R.id.undo, R.id.redo, R.id.save);

View File

@@ -11,19 +11,29 @@ import org.autojs.autojs.ui.edit.EditorView;
import java.util.List;
public abstract class ToolbarFragment extends Fragment implements View.OnClickListener {
public abstract class ToolbarFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener {
public interface OnMenuItemClickListener {
void onToolbarMenuItemClick(int id);
}
public interface OnMenuItemLongClickListener {
boolean onToolbarMenuItemLongClick(int id);
}
private OnMenuItemClickListener mOnMenuItemClickListener;
private OnMenuItemLongClickListener mOnMenuItemLongClickListener;
private List<Integer> mMenuItemIds;
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mOnMenuItemClickListener = listener;
}
public void setOnMenuItemLongClickListener(OnMenuItemLongClickListener onMenuItemLongClickListener) {
mOnMenuItemLongClickListener = onMenuItemLongClickListener;
}
public abstract List<Integer> getMenuItemIds();
@Override
@@ -54,6 +64,7 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
for (int id : mMenuItemIds) {
View view = rootView.findViewById(id);
view.setOnClickListener(this);
view.setOnLongClickListener(this);
view.setEnabled(editorView.getMenuItemStatus(id, view.isEnabled()));
}
}
@@ -66,6 +77,13 @@ public abstract class ToolbarFragment extends Fragment implements View.OnClickLi
}
}
@Override
public boolean onLongClick(View v) {
return mOnMenuItemLongClickListener != null &&
mOnMenuItemLongClickListener.onToolbarMenuItemLongClick(v.getId());
}
public void setMenuItemStatus(int id, boolean enabled) {
if (mMenuItemIds == null) {
mMenuItemIds = getMenuItemIds();

View File

@@ -89,7 +89,7 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
RecyclerView mDrawerMenu;
private DrawerMenuItem mConnectionItem = new DrawerMenuItem(R.drawable.ic_debug, R.string.debug, 0, this::connectOrDisconnectToRemote);
private DrawerMenuItem mConnectionItem = new DrawerMenuItem(R.drawable.ic_connect_to_pc, R.string.debug, 0, this::connectOrDisconnectToRemote);
private DrawerMenuItem mAccessibilityServiceItem = new DrawerMenuItem(R.drawable.ic_service_green, R.string.text_accessibility_service, 0, this::enableOrDisableAccessibilityService);
private DrawerMenuItem mStableModeItem = new DrawerMenuItem(R.drawable.ic_stable, R.string.text_stable_mode, R.string.key_stable_mode, null) {
@Override

View File

@@ -14,12 +14,16 @@ import java.util.List;
public class AutoAdapter<DT> extends RecyclerView.Adapter<BindableViewHolder<DT>> {
private ViewHolderSupplier<? extends BindableViewHolder<DT>> mViewHolderSupplier;
private List<DT> mList = new ArrayList<>();
private final List<DT> mData = new ArrayList<>();
public AutoAdapter(ViewHolderSupplier<? extends BindableViewHolder<DT>> viewHolderSupplier) {
mViewHolderSupplier = viewHolderSupplier;
}
public AutoAdapter(ViewHolderSupplier.ViewHolderCreator<? extends BindableViewHolder<DT>> viewHolderCreator, int layoutRes) {
mViewHolderSupplier = ViewHolderSupplier.of(viewHolderCreator, layoutRes);
}
@Override
public BindableViewHolder<DT> onCreateViewHolder(ViewGroup parent, int viewType) {
@@ -28,36 +32,48 @@ public class AutoAdapter<DT> extends RecyclerView.Adapter<BindableViewHolder<DT>
@Override
public void onBindViewHolder(BindableViewHolder<DT> holder, int position) {
holder.bind(mList.get(position), position);
holder.bind(mData.get(position), position);
}
@Override
public int getItemCount() {
return mList.size();
return mData.size();
}
public void remove(DT data) {
int pos = mList.indexOf(data);
int pos = mData.indexOf(data);
if (pos < 0)
return;
mList.remove(pos);
mData.remove(pos);
notifyItemRemoved(pos);
}
public void remove(int index) {
mList.remove(index);
mData.remove(index);
notifyItemRemoved(index);
}
public void addAll(Collection<? extends DT> c) {
mList.addAll(c);
notifyItemRangeInserted(mList.size() - c.size() - 1, mList.size() - 1);
mData.addAll(c);
notifyItemRangeInserted(mData.size() - c.size() - 1, mData.size() - 1);
}
public DT get(int index){
return mData.get(index);
}
public void setData(Collection<? extends DT> c) {
mList.clear();
mList.addAll(c);
mData.clear();
mData.addAll(c);
notifyDataSetChanged();
}
public void add(DT item) {
mData.add(item);
notifyItemInserted(mData.size() - 1);
}
public List<DT> getData() {
return mData;
}
}

View File

@@ -11,22 +11,30 @@ import java.lang.reflect.Constructor;
* Created by Stardust on 2017/4/8.
*/
public abstract class ViewHolderSupplier<VH extends RecyclerView.ViewHolder> {
public interface ViewHolderSupplier<VH extends RecyclerView.ViewHolder> {
public abstract VH createViewHolder(ViewGroup parent, int viewType);
VH createViewHolder(ViewGroup parent, int viewType);
public static <VH extends RecyclerView.ViewHolder> ViewHolderSupplier<VH> of(final Class<VH> c, final int layoutRes) {
return new ViewHolderSupplier<VH>() {
@Override
public VH createViewHolder(ViewGroup parent, int viewType) {
try {
Constructor<VH> constructor = c.getConstructor(View.class);
return constructor.newInstance(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
} catch (Exception e) {
throw new RuntimeException(e);
}
interface ViewHolderCreator<VH extends RecyclerView.ViewHolder> {
VH createViewHolder(View itemView);
}
static <VH extends RecyclerView.ViewHolder> ViewHolderSupplier<VH> of(final Class<VH> c, final int layoutRes) {
return (parent, viewType) -> {
try {
Constructor<VH> constructor = c.getConstructor(View.class);
return constructor.newInstance(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
static <VH extends RecyclerView.ViewHolder> ViewHolderSupplier<VH> of(ViewHolderCreator<VH> creator, final int layoutRes) {
return (parent, viewType) ->
creator.createViewHolder(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false)
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#aaffa726"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginRight="8dp"
android:src="@drawable/ic_debug"/>
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/text_debug"
android:textColor="@android:color/white"
android:textSize="15sp"/>
<ImageView
android:id="@+id/execute"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginRight="8dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_script"
android:tint="@android:color/white"/>
<ImageView
android:id="@+id/add"
android:layout_width="28dp"
android:layout_height="28dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_add_white_48dp"
android:tint="@android:color/white"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/variables"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.stardust.theme.widget.ThemeColorTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="@string/text_code"
android:textSize="14sp"/>
<EditText
android:id="@+id/code"
android:layout_width="match_parent"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light"/>
<com.stardust.theme.widget.ThemeColorTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:text="@string/text_result"
android:textSize="14sp"/>
<TextView
android:id="@+id/result"
android:paddingLeft="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#727377"
android:textSize="16sp"/>
</LinearLayout>

View File

@@ -88,6 +88,13 @@
android:layout_height="match_parent"
android:visibility="gone"/>
<org.autojs.autojs.ui.edit.debug.DebugBar
android:id="@+id/debug_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout>
<org.autojs.autojs.ui.widget.EWebView

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="28dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="?selectableItemBackground"
android:paddingLeft="12dp">
<TextView
android:id="@+id/variable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@android:color/black"
android:textSize="14sp"
tools:text="variable = value"/>
<ImageView
android:id="@+id/icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:background="?selectableItemBackgroundBorderless"
android:padding="2dp"
android:layout_marginRight="6dp"
android:src="@drawable/ic_delete_black_48dp"
android:tint="@android:color/white"/>
</LinearLayout>

View File

@@ -94,6 +94,11 @@
android:id="@+id/action_launch_debugger"
android:title="@string/text_launch_debugger"
app:showAsAction="never"/>
<item
android:id="@+id/action_remove_all_breakpoints"
android:title="@string/text_remove_all_breakpoints"
app:showAsAction="never"/>
</menu>
</item>

View File

@@ -367,4 +367,15 @@
<string name="text_debug_step_over">单步</string>
<string name="text_debug_resume_script">继续</string>
<string name="text_stop">停止</string>
<string name="text_new_watching_variable">增加监视的变量或表达式</string>
<string name="text_variable_or_expr">变量或表达式</string>
<string name="text_variable">变量</string>
<string name="text_remove_all_breakpoints">删除所有断点</string>
<string name="hint_long_click_run_to_debug">长按\"运行\"图标也可以启动调试</string>
<string name="format_debug_bar_title">调试 [%s]</string>
<string name="text_copy_value">复制值</string>
<string name="text_execute_code">执行代码</string>
<string name="text_code">代码</string>
<string name="text_result">结果</string>
<string name="text_close">关闭</string>
</resources>