From 834280428de71614fac80d7252545fb75e2596f4 Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Tue, 11 Sep 2018 08:11:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(debug):=20=E4=BB=8EDebugToolFragment?= =?UTF-8?q?=E4=B8=AD=E5=88=86=E7=A6=BBdebug=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../autojs/pluginclient/JsonSocket.java | 64 +++ .../org/autojs/autojs/ui/edit/EditorMenu.java | 1 + .../org/autojs/autojs/ui/edit/EditorView.java | 7 +- .../ui/edit/debug/DebuggerSingleton.java | 15 + .../autojs/ui/edit/editor/CodeEditor.java | 6 +- ...wRedoUndo.java => TextViewRedoUndoer.java} | 7 +- .../ui/edit/editor/TextViewUndoRedo.java | 427 ++++++++++++++++++ .../ui/edit/toolbar/DebugToolbarFragment.java | 106 ++--- .../autojs/rhino/debug/DebugCallback.java | 35 +- .../rhino/debug/DebugCallbackInternal.java | 49 ++ .../stardust/autojs/rhino/debug/Debugger.java | 163 +++++++ .../com/stardust/autojs/rhino/debug/Dim.java | 5 +- 12 files changed, 764 insertions(+), 121 deletions(-) create mode 100644 app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/debug/DebuggerSingleton.java rename app/src/main/java/org/autojs/autojs/ui/edit/editor/{TextViewRedoUndo.java => TextViewRedoUndoer.java} (98%) create mode 100644 app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewUndoRedo.java create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallbackInternal.java create mode 100644 autojs/src/main/java/com/stardust/autojs/rhino/debug/Debugger.java diff --git a/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java new file mode 100644 index 00000000..d383a290 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/pluginclient/JsonSocket.java @@ -0,0 +1,64 @@ +package org.autojs.autojs.pluginclient; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.net.Socket; +import java.nio.ByteBuffer; + +public class JsonSocket { + + private Socket mSocket; + + public JsonSocket(Socket socket) { + mSocket = socket; + + } + + + private static class SocketReader implements Runnable { + + private final Socket mSocket; + private final InputStream mInputStream; + private ByteBuffer mByteBuffer; + private int mJsonDataLength = -1; + private int mReceivedDataLength = 0; + private byte[] mBuffer; + + private SocketReader(Socket socket) throws IOException { + mSocket = socket; + mInputStream = mSocket.getInputStream(); + mByteBuffer = ByteBuffer.allocateDirect(3); + } + + + @Override + public void run() { + + try { + readLoop(); + + } catch (IOException e) { + + } finally { + } + } + + private void readLoop() throws IOException { + byte[] buffer = new byte[4096]; + int n; + while ((n = mInputStream.read(buffer)) > 0) { + onChunk(buffer, 0, n); + } + } + + private void onChunk(byte[] data, int offset, int length) { + if(mJsonDataLength != 0){ + + } + } + } + + +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java index 6a8c008c..c9fe7b2b 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorMenu.java @@ -5,6 +5,7 @@ import android.support.design.widget.Snackbar; import android.text.InputType; import android.text.TextUtils; import android.view.MenuItem; +import android.widget.TextView; import com.stardust.pio.PFiles; diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.java b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.java index 47c15af2..637c31dc 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/EditorView.java @@ -28,6 +28,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.stardust.autojs.engine.JavaScriptEngine; import com.stardust.autojs.engine.ScriptEngine; import com.stardust.autojs.execution.ScriptExecution; +import com.stardust.autojs.rhino.debug.Debugger; import com.stardust.pio.PFiles; import com.stardust.util.BackPressedHandler; import com.stardust.util.Callback; @@ -386,12 +387,14 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC .subscribe(s -> run(true)); } - public void run(boolean showMessage) { + public ScriptExecution run(boolean showMessage) { if(showMessage){ Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show(); } - mScriptExecutionId = Scripts.runWithBroadcastSender(mFile).getId(); + ScriptExecution execution = Scripts.runWithBroadcastSender(mFile); + mScriptExecutionId = execution.getId(); setMenuItemStatus(R.id.run, false); + return execution; } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/debug/DebuggerSingleton.java b/app/src/main/java/org/autojs/autojs/ui/edit/debug/DebuggerSingleton.java new file mode 100644 index 00000000..be009782 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/debug/DebuggerSingleton.java @@ -0,0 +1,15 @@ +package org.autojs.autojs.ui.edit.debug; + +import com.stardust.autojs.rhino.debug.Debugger; + +import org.autojs.autojs.autojs.AutoJs; +import org.mozilla.javascript.ContextFactory; + +public class DebuggerSingleton { + + private static Debugger sDebugger = new Debugger(AutoJs.getInstance().getScriptEngineService(), ContextFactory.getGlobal()); + + public static Debugger get(){ + return sDebugger; + } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java index 26630e0c..c0dccbbd 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/CodeEditor.java @@ -50,7 +50,7 @@ public class CodeEditor extends HVScrollView { private CodeEditText mCodeEditText; - private TextViewRedoUndo mTextViewRedoUndo; + private TextViewUndoRedo mTextViewRedoUndo; private JavaScriptHighlighter mJavaScriptHighlighter; private Theme mTheme; private JsBeautifier mJsBeautifier; @@ -77,7 +77,7 @@ public class CodeEditor extends HVScrollView { inflate(getContext(), R.layout.code_editor, this); mCodeEditText = findViewById(R.id.code_edit_text); mCodeEditText.addTextChangedListener(new AutoIndent(mCodeEditText)); - mTextViewRedoUndo = new TextViewRedoUndo(mCodeEditText); + mTextViewRedoUndo = new TextViewUndoRedo(mCodeEditText); mJavaScriptHighlighter = new JavaScriptHighlighter(mTheme, mCodeEditText); setTheme(Theme.getDefault(getContext())); mJsBeautifier = new JsBeautifier(this, "js/beautify.js"); @@ -144,7 +144,7 @@ public class CodeEditor extends HVScrollView { } public boolean canUndo() { - return mTextViewRedoUndo.canUndo(); + return mTextViewRedoUndo.canRedo(); } public boolean canRedo() { diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndo.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndoer.java similarity index 98% rename from app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndo.java rename to app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndoer.java index aa20f7b1..06144eb0 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndo.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewRedoUndoer.java @@ -21,14 +21,13 @@ import android.text.Editable; import android.text.TextWatcher; import android.widget.EditText; -import java.util.Objects; import java.util.Stack; /** * 撤销和恢复撤销 * Created by 沈钦赐 on 16/6/23. */ -public class TextViewRedoUndo { +public class TextViewRedoUndoer { //操作序号(一次编辑可能对应多个操作,如替换文字,就是删除+插入) int index; //撤销栈 @@ -44,7 +43,7 @@ public class TextViewRedoUndo { private int mInitialHistoryStackSize; private boolean mEnabled = true; - public TextViewRedoUndo(@NonNull EditText editText) { + public TextViewRedoUndoer(@NonNull EditText editText) { this.editable = editText.getText(); this.editText = editText; editText.addTextChangedListener(new Watcher()); @@ -246,7 +245,7 @@ public class TextViewRedoUndo { editable = s; onEditableChanged(s); } - TextViewRedoUndo.this.onTextChanged(s); + TextViewRedoUndoer.this.onTextChanged(s); } } diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewUndoRedo.java b/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewUndoRedo.java new file mode 100644 index 00000000..90d43383 --- /dev/null +++ b/app/src/main/java/org/autojs/autojs/ui/edit/editor/TextViewUndoRedo.java @@ -0,0 +1,427 @@ +package org.autojs.autojs.ui.edit.editor; + +/* + * THIS CLASS IS PROVIDED TO THE PUBLIC DOMAIN FOR FREE WITHOUT ANY + * RESTRICTIONS OR ANY WARRANTY. + */ + +import java.util.LinkedList; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.text.Editable; +import android.text.Selection; +import android.text.TextWatcher; +import android.text.style.UnderlineSpan; +import android.widget.TextView; + +/** + * A generic undo/redo implementation for TextViews. + */ +public class TextViewUndoRedo { + + /** + * Is undo/redo being performed? This member signals if an undo/redo + * operation is currently being performed. Changes in the text during + * undo/redo are not recorded because it would mess up the undo history. + */ + private boolean mIsUndoOrRedo = false; + + /** + * The edit history. + */ + private EditHistory mEditHistory; + + /** + * The change listener. + */ + private EditTextChangeListener mChangeListener; + + /** + * The edit text. + */ + private TextView mTextView; + + private boolean mEnabled = true; + + private int mInitialHistoryStackSize; + + // =================================================================== // + + /** + * Create a new TextViewUndoRedo and attach it to the specified TextView. + * + * @param textView The text view for which the undo/redo is implemented. + */ + public TextViewUndoRedo(TextView textView) { + mTextView = textView; + mEditHistory = new EditHistory(); + mChangeListener = new EditTextChangeListener(); + mTextView.addTextChangedListener(mChangeListener); + } + + public boolean isEnabled() { + return mEnabled; + } + + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + public final void setDefaultText(CharSequence text) { + clearHistory(); + mIsUndoOrRedo = true; + ((Editable) mTextView.getText()).replace(0, text.length(), text); + mIsUndoOrRedo = false; + } + + public boolean isTextChanged(){ + return mInitialHistoryStackSize != mEditHistory.size(); + } + + + public void markTextAsUnchanged() { + mInitialHistoryStackSize = mEditHistory.size(); + } + + // =================================================================== // + + /** + * Disconnect this undo/redo from the text view. + */ + public void disconnect() { + mTextView.removeTextChangedListener(mChangeListener); + } + + /** + * Set the maximum history size. If size is negative, then history size is + * only limited by the device memory. + */ + public void setMaxHistorySize(int maxHistorySize) { + mEditHistory.setMaxHistorySize(maxHistorySize); + } + + /** + * Clear history. + */ + public void clearHistory() { + mEditHistory.clear(); + mInitialHistoryStackSize = 0; + } + + /** + * Can undo be performed? + */ + public boolean canUndo() { + return (mEditHistory.mmPosition > 0); + } + + /** + * Perform undo. + */ + public void undo() { + EditItem edit = mEditHistory.getPrevious(); + if (edit == null) { + return; + } + + Editable text = mTextView.getEditableText(); + int start = edit.mmStart; + int end = start + (edit.mmAfter != null ? edit.mmAfter.length() : 0); + + mIsUndoOrRedo = true; + text.replace(start, end, edit.mmBefore); + mIsUndoOrRedo = false; + + // This will get rid of underlines inserted when editor tries to come + // up with a suggestion. + for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) { + text.removeSpan(o); + } + + Selection.setSelection(text, edit.mmBefore == null ? start + : (start + edit.mmBefore.length())); + } + + /** + * Can redo be performed? + */ + public boolean canRedo() { + return (mEditHistory.mmPosition < mEditHistory.mmHistory.size()); + } + + /** + * Perform redo. + */ + public void redo() { + EditItem edit = mEditHistory.getNext(); + if (edit == null) { + return; + } + + Editable text = mTextView.getEditableText(); + int start = edit.mmStart; + int end = start + (edit.mmBefore != null ? edit.mmBefore.length() : 0); + + mIsUndoOrRedo = true; + text.replace(start, end, edit.mmAfter); + mIsUndoOrRedo = false; + + // This will get rid of underlines inserted when editor tries to come + // up with a suggestion. + for (Object o : text.getSpans(0, text.length(), UnderlineSpan.class)) { + text.removeSpan(o); + } + + Selection.setSelection(text, edit.mmAfter == null ? start + : (start + edit.mmAfter.length())); + } + + /** + * Store preferences. + */ + public void storePersistentState(Editor editor, String prefix) { + // Store hash code of text in the editor so that we can check if the + // editor contents has changed. + editor.putString(prefix + ".hash", + String.valueOf(mTextView.getText().toString().hashCode())); + editor.putInt(prefix + ".maxSize", mEditHistory.mmMaxHistorySize); + editor.putInt(prefix + ".position", mEditHistory.mmPosition); + editor.putInt(prefix + ".size", mEditHistory.mmHistory.size()); + + int i = 0; + for (EditItem ei : mEditHistory.mmHistory) { + String pre = prefix + "." + i; + + editor.putInt(pre + ".start", ei.mmStart); + editor.putString(pre + ".before", ei.mmBefore.toString()); + editor.putString(pre + ".after", ei.mmAfter.toString()); + + i++; + } + } + + /** + * Restore preferences. + * + * @param prefix The preference key prefix used when state was stored. + * @return did restore succeed? If this is false, the undo history will be + * empty. + */ + public boolean restorePersistentState(SharedPreferences sp, String prefix) + throws IllegalStateException { + + boolean ok = doRestorePersistentState(sp, prefix); + if (!ok) { + mEditHistory.clear(); + } + + return ok; + } + + private boolean doRestorePersistentState(SharedPreferences sp, String prefix) { + + String hash = sp.getString(prefix + ".hash", null); + if (hash == null) { + // No state to be restored. + return true; + } + + if (Integer.valueOf(hash) != mTextView.getText().toString().hashCode()) { + return false; + } + + mEditHistory.clear(); + mEditHistory.mmMaxHistorySize = sp.getInt(prefix + ".maxSize", -1); + + int count = sp.getInt(prefix + ".size", -1); + if (count == -1) { + return false; + } + + for (int i = 0; i < count; i++) { + String pre = prefix + "." + i; + + int start = sp.getInt(pre + ".start", -1); + String before = sp.getString(pre + ".before", null); + String after = sp.getString(pre + ".after", null); + + if (start == -1 || before == null || after == null) { + return false; + } + mEditHistory.add(new EditItem(start, before, after)); + } + + mEditHistory.mmPosition = sp.getInt(prefix + ".position", -1); + if (mEditHistory.mmPosition == -1) { + return false; + } + + return true; + } + + // =================================================================== // + + /** + * Keeps track of all the edit history of a text. + */ + private final class EditHistory { + + /** + * The position from which an EditItem will be retrieved when getNext() + * is called. If getPrevious() has not been called, this has the same + * value as mmHistory.size(). + */ + private int mmPosition = 0; + + /** + * Maximum undo history size. + */ + private int mmMaxHistorySize = -1; + + /** + * The list of edits in chronological order. + */ + private final LinkedList mmHistory = new LinkedList(); + + /** + * Clear history. + */ + private void clear() { + mmPosition = 0; + mmHistory.clear(); + } + + /** + * Adds a new edit operation to the history at the current position. If + * executed after a call to getPrevious() removes all the future history + * (elements with positions >= current history position). + */ + private void add(EditItem item) { + while (mmHistory.size() > mmPosition) { + mmHistory.removeLast(); + } + mmHistory.add(item); + mmPosition++; + + if (mmMaxHistorySize >= 0) { + trimHistory(); + } + } + + public int size(){ + return mmHistory.size(); + } + + /** + * Set the maximum history size. If size is negative, then history size + * is only limited by the device memory. + */ + private void setMaxHistorySize(int maxHistorySize) { + mmMaxHistorySize = maxHistorySize; + if (mmMaxHistorySize >= 0) { + trimHistory(); + } + } + + /** + * Trim history when it exceeds max history size. + */ + private void trimHistory() { + while (mmHistory.size() > mmMaxHistorySize) { + mmHistory.removeFirst(); + mmPosition--; + } + + if (mmPosition < 0) { + mmPosition = 0; + } + } + + /** + * Traverses the history backward by one position, returns and item at + * that position. + */ + private EditItem getPrevious() { + if (mmPosition == 0) { + return null; + } + mmPosition--; + return mmHistory.get(mmPosition); + } + + /** + * Traverses the history forward by one position, returns and item at + * that position. + */ + private EditItem getNext() { + if (mmPosition >= mmHistory.size()) { + return null; + } + + EditItem item = mmHistory.get(mmPosition); + mmPosition++; + return item; + } + } + + /** + * Represents the changes performed by a single edit operation. + */ + private final class EditItem { + private final int mmStart; + private final CharSequence mmBefore; + private final CharSequence mmAfter; + + /** + * Constructs EditItem of a modification that was applied at position + * start and replaced CharSequence before with CharSequence after. + */ + public EditItem(int start, CharSequence before, CharSequence after) { + mmStart = start; + mmBefore = before; + mmAfter = after; + } + } + + /** + * Class that listens to changes in the text. + */ + private final class EditTextChangeListener implements TextWatcher { + + /** + * The text that will be removed by the change event. + */ + private CharSequence mBeforeChange; + + /** + * The text that was inserted by the change event. + */ + private CharSequence mAfterChange; + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + if (mIsUndoOrRedo || !mEnabled) { + return; + } + + mBeforeChange = s.subSequence(start, start + count); + } + + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if (mIsUndoOrRedo || !mEnabled) { + return; + } + + mAfterChange = s.subSequence(start, start + count); + mEditHistory.add(new EditItem(start, mBeforeChange, mAfterChange)); + } + + public void afterTextChanged(Editable s) { + if (mEditHistory.size() < mInitialHistoryStackSize) { + mInitialHistoryStackSize = 0; + } + } + } +} diff --git a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java index f782321c..b377af51 100644 --- a/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java +++ b/app/src/main/java/org/autojs/autojs/ui/edit/toolbar/DebugToolbarFragment.java @@ -2,7 +2,6 @@ package org.autojs.autojs.ui.edit.toolbar; 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; @@ -10,25 +9,23 @@ 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.rhino.debug.Debugger; +import com.stardust.autojs.rhino.debug.Dim; 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.DebuggerSingleton; import org.autojs.autojs.ui.edit.debug.WatchingVariable; import org.autojs.autojs.ui.edit.editor.CodeEditor; -import org.mozilla.javascript.ContextFactory; +import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.List; @@ -36,15 +33,14 @@ import java.util.List; public class DebugToolbarFragment extends ToolbarFragment implements DebugCallback, CodeEditor.CursorChangeCallback, CodeEvaluator { private static final String LOG_TAG = "DebugToolbarFragment"; - private Dim mDim; private EditorView mEditorView; + private boolean mCursorChangeFromUser = true; + private Debugger mDebugger; private Handler mHandler; - private boolean mSkipOtherFileBreakpoint = false; private String mCurrentEditorSourceUrl; private String mInitialEditorSourceUrl; private String mInitialEditorSource; - private boolean mCursorChangeFromUser = true; - private Dim.SourceInfo mSourceInfo; + private final RecyclerView.AdapterDataObserver mVariableChangeObserver = new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { @@ -54,21 +50,17 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba private CodeEditor.BreakpointChangeListener mBreakpointChangeListener = new CodeEditor.BreakpointChangeListener() { @Override public void onBreakpointChange(int line, boolean enabled) { - if (mSourceInfo != null) { - mSourceInfo.breakpoint(line + 1, enabled); + if (mDebugger != null) { + mDebugger.breakpoint(line + 1, enabled); } } @Override public void onAllBreakpointRemoved(int count) { - mDim.clearAllBreakpoints(); + mDebugger.clearAllBreakpoints(); } }; - public DebugToolbarFragment() { - Log.d(LOG_TAG, "DebugToolbarFragment()"); - } - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -79,13 +71,13 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mEditorView = findEditorView(view); - mDim = createDim(); + mDebugger = DebuggerSingleton.get(); + mDebugger.setWeakDebugCallback(new WeakReference<>(this)); setInterrupted(false); - mSkipOtherFileBreakpoint = true; mCurrentEditorSourceUrl = mInitialEditorSourceUrl = mEditorView.getFile().toString(); mInitialEditorSource = mEditorView.getEditor().getText(); setupEditor(); - mEditorView.run(false); + mDebugger.attach(mEditorView.run(false)); Log.d(LOG_TAG, "onViewCreated"); } @@ -97,16 +89,6 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba 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) { @@ -120,19 +102,15 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba } public void detachDebugger() { - if (!mDim.isAttached()) { + if (!mDebugger.isAttached()) { return; } Log.d(LOG_TAG, "detachDebugger"); - mDim.detach(); - mDim.setGuiCallback(null); + mDebugger.detach(); if (mEditorView == null) { return; } CodeEditor editor = mEditorView.getEditor(); - editor.removeCursorChangeCallback(this); - editor.setBreakpointChangeListener(null); - mSourceInfo = null; editor.setRedoUndoEnabled(true); if (!TextUtils.equals(mInitialEditorSourceUrl, mCurrentEditorSourceUrl)) { editor.setText(mInitialEditorSource); @@ -140,25 +118,24 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba DebugBar debugBar = mEditorView.getDebugBar(); debugBar.setTitle(null); debugBar.setCodeEvaluator(null); - debugBar.unregisterVariableChangeObserver(mVariableChangeObserver); } @Click(R.id.step_over) void stepOver() { setInterrupted(false); - mDim.setReturnValue(Dim.STEP_OVER); + mDebugger.stepOver(); } @Click(R.id.step_into) void stepInto() { setInterrupted(false); - mDim.setReturnValue(Dim.STEP_INTO); + mDebugger.stepInto(); } @Click(R.id.step_out) void stepOut() { setInterrupted(false); - mDim.setReturnValue(Dim.STEP_OUT); + mDebugger.stepOut(); } @Click(R.id.stop_script) @@ -169,15 +146,12 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba @Click(R.id.resume_script) void resumeScript() { setInterrupted(false); - mDim.setReturnValue(Dim.GO); + mDebugger.resume(); } @Override public void updateSourceText(Dim.SourceInfo sourceInfo) { Log.d(LOG_TAG, "updateSourceText: url = " + sourceInfo.url()); - if (!sourceInfo.url().equals(mEditorView.getFile().toString())) { - return; - } sourceInfo.removeAllBreakpoints(); for (CodeEditor.Breakpoint breakpoint : mEditorView.getEditor().getBreakpoints().values()) { int line = breakpoint.line + 1; @@ -186,18 +160,10 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba Log.d(LOG_TAG, "not breakable: " + line); } } - mSourceInfo = sourceInfo; } @Override public void enterInterrupt(Dim.StackFrame stackFrame, String threadName, String message) { - Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadName + ", url = " + stackFrame.getUrl() + ", line = " + stackFrame.getLineNumber()); - //刚启动调试时会在init脚本的第一行自动停下,此时应该让脚本继续运行 - if (mSkipOtherFileBreakpoint && !stackFrame.getUrl().equals(mInitialEditorSourceUrl) && message == null) { - mHandler.post(this::resumeScript); - return; - } - mSkipOtherFileBreakpoint = false; showDebuggingLineOnEditor(stackFrame, message); mHandler.post(this::updateWatchingVariables); } @@ -207,7 +173,7 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba } private void updateWatchingVariables(int start, int end) { - if (!mDim.isAttached()) { + if (!mDebugger.isAttached()) { return; } DebugBar debugBar = mEditorView.getDebugBar(); @@ -221,11 +187,7 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba } public String eval(String expr) { - if (expr == null || !mDim.isAttached() || !mDim.stringIsCompilableUnit(expr)) { - return null; - } - mDim.contextSwitch(0); - return mDim.eval(expr); + return mDebugger.eval(expr); } private void showDebuggingLineOnEditor(Dim.StackFrame stackFrame, String message) { @@ -258,21 +220,6 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba }); } - @Override - public boolean isGuiEventThread() { - return Looper.getMainLooper() == Looper.myLooper(); - } - - @Override - public void dispatchNextGuiEvent() { - } - - @Override - public boolean shouldAttachDebugger(RhinoJavaScriptEngine engine) { - ScriptExecution execution = mEditorView.getScriptExecution(); - return execution != null && execution.getId() == engine.getId(); - - } @Override public void onCursorChange(String line, int ch) { @@ -281,7 +228,7 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba return; } mCursorChangeFromUser = true; - if (!mDim.isAttached()) { + if (!mDebugger.isAttached()) { return; } String variable = findVariableOnCursor(line, ch); @@ -322,6 +269,13 @@ public class DebugToolbarFragment extends ToolbarFragment implements DebugCallba @Override public void onDestroy() { super.onDestroy(); - detachDebugger(); + if (mEditorView == null) { + return; + } + CodeEditor editor = mEditorView.getEditor(); + editor.removeCursorChangeCallback(this); + editor.setBreakpointChangeListener(null); + DebugBar debugBar = mEditorView.getDebugBar(); + debugBar.unregisterVariableChangeObserver(mVariableChangeObserver); } } diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java index 3dd25922..eb6d1372 100644 --- a/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallback.java @@ -1,20 +1,5 @@ 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 { /** @@ -29,22 +14,4 @@ public interface DebugCallback { 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); -} \ No newline at end of file +} diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallbackInternal.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallbackInternal.java new file mode 100644 index 00000000..153b526d --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/DebugCallbackInternal.java @@ -0,0 +1,49 @@ +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. + */ +interface DebugCallbackInternal { + + /** + * 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); +} \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/Debugger.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Debugger.java new file mode 100644 index 00000000..6275fd48 --- /dev/null +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Debugger.java @@ -0,0 +1,163 @@ +package com.stardust.autojs.rhino.debug; + +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.stardust.autojs.ScriptEngineService; +import com.stardust.autojs.engine.RhinoJavaScriptEngine; +import com.stardust.autojs.execution.ScriptExecution; + +import org.mozilla.javascript.ContextFactory; + +import java.lang.ref.WeakReference; + +public class Debugger implements DebugCallbackInternal { + + + private static final String LOG_TAG = "Debugger"; + + private final ScriptEngineService mScriptEngineService; + private final ContextFactory mContextFactory; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Dim mDim = createDim(); + + private DebugCallback mDebugCallback; + + private boolean mSkipOtherFileBreakpoint = false; + @Nullable + private String mSourceUrl; + @Nullable + private ScriptExecution mScriptExecution; + @Nullable + private Dim.SourceInfo mSourceInfo; + private WeakReference mWeakDebugCallback; + + + public Debugger(ScriptEngineService scriptEngineService, ContextFactory contextFactory) { + mScriptEngineService = scriptEngineService; + mContextFactory = contextFactory; + } + + public void attach(ScriptExecution execution) { + if(isAttached()){ + detach(); + } + mScriptExecution = execution; + mSkipOtherFileBreakpoint = true; + mSourceUrl = execution.getSource().toString(); + mDim.attachTo(mScriptEngineService, mContextFactory); + } + + @Override + public void updateSourceText(Dim.SourceInfo sourceInfo) { + if (!sourceInfo.url().equals(mSourceUrl)) { + return; + } + mSourceInfo = sourceInfo; + if(mDebugCallback != null){ + mDebugCallback.updateSourceText(sourceInfo); + } + DebugCallback callback = mWeakDebugCallback == null ? null : mWeakDebugCallback.get(); + if(callback != null){ + callback.updateSourceText(sourceInfo); + } + } + + @Override + public void enterInterrupt(Dim.StackFrame lastFrame, String threadTitle, String alertMessage) { + Log.d(LOG_TAG, "enterInterrupt: threadName = " + threadTitle + ", url = " + lastFrame.getUrl() + ", line = " + lastFrame.getLineNumber()); + //刚启动调试时会在init脚本的第一行自动停下,此时应该让脚本继续运行 + if (mSkipOtherFileBreakpoint && !lastFrame.getUrl().equals(mSourceUrl) && alertMessage == null) { + mHandler.post(this::resume); + return; + } + mSkipOtherFileBreakpoint = false; + if(mDebugCallback != null){ + mDebugCallback.enterInterrupt(lastFrame, threadTitle, alertMessage); + } + DebugCallback callback = mWeakDebugCallback == null ? null : mWeakDebugCallback.get(); + if(callback != null){ + callback.enterInterrupt(lastFrame, threadTitle, alertMessage); + } + } + + @Override + public boolean isGuiEventThread() { + return Looper.getMainLooper() == Looper.myLooper(); + } + + @Override + public void dispatchNextGuiEvent() { + + } + + @Override + public boolean shouldAttachDebugger(RhinoJavaScriptEngine engine) { + return mScriptExecution != null && mScriptExecution.getId() == engine.getId(); + + } + + public void breakpoint(int line, boolean enabled) { + if (mSourceInfo != null) { + mSourceInfo.breakpoint(line, enabled); + } + } + + private Dim createDim() { + Dim dim = new Dim(); + dim.setBreak(); + dim.setBreakOnExceptions(true); + dim.setGuiCallback(this); + return dim; + } + + public void resume() { + mDim.setReturnValue(Dim.GO); + } + + public void stepOut() { + mDim.setReturnValue(Dim.STEP_OUT); + } + + public void stepInto() { + mDim.setReturnValue(Dim.STEP_INTO); + } + + public void stepOver() { + mDim.setReturnValue(Dim.STEP_OVER); + } + + public boolean isAttached() { + return mDim.isAttached(); + } + + public String eval(String expr) { + if (expr == null || !mDim.isAttached() || !mDim.stringIsCompilableUnit(expr)) { + return null; + } + mDim.contextSwitch(0); + return mDim.eval(expr); + } + + public void clearAllBreakpoints() { + mDim.clearAllBreakpoints(); + } + + public void detach() { + mDim.detach(); + mScriptExecution = null; + mSourceUrl = null; + mSourceInfo = null; + } + + public void setDebugCallback(DebugCallback debugCallback) { + mDebugCallback = debugCallback; + } + + public void setWeakDebugCallback(WeakReference debugCallback) { + mWeakDebugCallback = debugCallback; + } +} diff --git a/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java index 95722e05..da5b9de9 100644 --- a/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java +++ b/autojs/src/main/java/com/stardust/autojs/rhino/debug/Dim.java @@ -14,6 +14,7 @@ import com.stardust.autojs.engine.ScriptEngineManager; import org.mozilla.javascript.*; import org.mozilla.javascript.debug.*; import org.mozilla.javascript.tools.debugger.*; +import org.mozilla.javascript.debug.Debugger; import java.util.*; import java.io.*; @@ -47,7 +48,7 @@ public class Dim { /** * Interface to the debugger GUI. */ - private DebugCallback callback; + private DebugCallbackInternal callback; /** * Whether the debugger should break. @@ -162,7 +163,7 @@ public class Dim { /** * Sets the GuiCallback object to use. */ - public void setGuiCallback(DebugCallback callback) { + public void setGuiCallback(DebugCallbackInternal callback) { this.callback = callback; }