feat(debug): supports debugging across files; supports watching variables; supports evaluate code on debug;
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.autojs.autojs.ui.edit.debug;
|
||||
|
||||
public interface CodeEvaluator {
|
||||
String eval(String code);
|
||||
}
|
||||
165
app/src/main/java/org/autojs/autojs/ui/edit/debug/DebugBar.java
Normal file
165
app/src/main/java/org/autojs/autojs/ui/edit/debug/DebugBar.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/drawable-xhdpi/ic_connect_to_pc.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_connect_to_pc.png
Normal file
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 |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable/ic_script.png
Normal file
BIN
app/src/main/res/drawable/ic_script.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
54
app/src/main/res/layout/debug_bar.xml
Normal file
54
app/src/main/res/layout/debug_bar.xml
Normal 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>
|
||||
38
app/src/main/res/layout/dialog_code_evaluate.xml
Normal file
38
app/src/main/res/layout/dialog_code_evaluate.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user