From ccf85fe2f5014ffccc02a36f7a728d522d1ee63c Mon Sep 17 00:00:00 2001 From: hyb1996 <946994919@qq.com> Date: Thu, 13 Sep 2018 11:07:19 +0800 Subject: [PATCH] opt(ui): js beautifier supports formatting xml --- .../assets/js/{ => js-beautify}/beautify.js | 31 ++++++++++--- .../assets/js/js-beautify/xml_formatter.js | 43 +++++++++++++++++++ .../org/autojs/autojs/ui/edit/EditorView.java | 15 ++++--- .../autojs/ui/edit/editor/CodeEditor.java | 2 +- .../AssetAndUrlModuleSourceProvider.java | 27 ++++++------ .../autojs/engine/RhinoJavaScriptEngine.java | 3 +- .../stardust/autojs/script/JsBeautifier.java | 36 ++++++++++++---- 7 files changed, 121 insertions(+), 36 deletions(-) rename app/src/main/assets/js/{ => js-beautify}/beautify.js (99%) create mode 100644 app/src/main/assets/js/js-beautify/xml_formatter.js diff --git a/app/src/main/assets/js/beautify.js b/app/src/main/assets/js/js-beautify/beautify.js similarity index 99% rename from app/src/main/assets/js/beautify.js rename to app/src/main/assets/js/js-beautify/beautify.js index c3af2d36..5fdfb6b6 100644 --- a/app/src/main/assets/js/beautify.js +++ b/app/src/main/assets/js/js-beautify/beautify.js @@ -88,6 +88,14 @@ */ (function() { +var formatXml = require("xml_formatter"); +function println(str){ + java.lang.System.out.println(str); +} +String.prototype.replaceAll = function(search, replacement) { + var target = this; + return target.split(search).join(replacement); +}; var legacy_beautify_js = /******/ (function(modules) { // webpackBootstrap /******/ // The module cache @@ -363,6 +371,7 @@ function Beautifier(js_source_text, options) { 'TK_WORD': handle_word, 'TK_RESERVED': handle_word, 'TK_SEMICOLON': handle_semicolon, + 'TK_XML': handle_string, 'TK_STRING': handle_string, 'TK_EQUALS': handle_equals, 'TK_OPERATOR': handle_operator, @@ -696,6 +705,13 @@ function Beautifier(js_source_text, options) { printable_token = printable_token || current_token.text; print_token_line_indentation(); + if(current_token.type == "TK_XML" && printable_token){ + var indent_count = output.current_line._indent_count; + println("indent_count: " + indent_count); + var indent = output.indent_cache[Math.max(0, indent_count - 1)]; + printable_token = formatXml(printable_token, {margin: indent}); + current_token.text = printable_token; + } output.add_token(printable_token); } @@ -1877,14 +1893,14 @@ module.exports.mergeOpts = mergeOpts; function OutputLine(parent) { var _character_count = 0; // use indent_count as a marker for lines that have preserved indentation - var _indent_count = -1; + this._indent_count = -1; var _items = []; var _empty = true; this.set_indent = function(level) { _character_count = parent.baseIndentLength + level * parent.indent_length; - _indent_count = level; + this._indent_count = level; }; this.get_character_count = function() { @@ -1920,8 +1936,8 @@ function OutputLine(parent) { }; this.remove_indent = function() { - if (_indent_count > 0) { - _indent_count -= 1; + if (this._indent_count > 0) { + this._indent_count -= 1; _character_count -= parent.indent_length; } }; @@ -1937,8 +1953,8 @@ function OutputLine(parent) { this.toString = function() { var result = ''; if (!this._empty) { - if (_indent_count >= 0) { - result = parent.indent_cache[_indent_count]; + if (this._indent_count >= 0) { + result = parent.indent_cache[this._indent_count]; } result += _items.join(''); } @@ -2567,7 +2583,8 @@ function Tokenizer(input_string, opts) { xmlStr += input.match(/[\s\S]*/g)[0]; } xmlStr = xmlStr.replace(acorn.allLineBreaks, '\n'); - return [xmlStr, "TK_STRING"]; + n_newlines = 1; + return [xmlStr, "TK_XML"]; } } else { // diff --git a/app/src/main/assets/js/js-beautify/xml_formatter.js b/app/src/main/assets/js/js-beautify/xml_formatter.js new file mode 100644 index 00000000..08a2d461 --- /dev/null +++ b/app/src/main/assets/js/js-beautify/xml_formatter.js @@ -0,0 +1,43 @@ + +const stringTimesN = (n, char) => Array(n + 1).join(char) + +// Adapted from https://gist.github.com/sente/1083506 +function prettifyXml(xmlInput, options) { + options = options || {}; + let newlineOption = options.newline || '\n'; + let indentOption = options.indent || 4; + let margin = options.margin || ''; + const indentString = stringTimesN(indentOption, ' ') + + let formatted = ''; + const regex = /(>)(<)(\/*)/g; + const xml = xmlInput.replace(regex, '$1' + newlineOption + '$2$3'); + let pad = 0; + xml.split(/\r?\n/).forEach(l => { + const line = l.trim(); + + let indent = 0; + if (line.match(/.+<\/\w[^>]*>$/)) { + indent = 0; + } else if (line.match(/^<\/\w/)) { + // Somehow istanbul doesn't see the else case as covered, although it is. Skip it. + /* istanbul ignore else */ + if (pad !== 0) { + pad -= 1 + } + } else if (line.match(/^<\w([^>]*[^\/])?>.*$/)) { + indent = 1 + } else { + indent = 0 + } + + const padding = stringTimesN(pad, indentString); + formatted += margin + padding + line + newlineOption // eslint-disable-line prefer-template + pad += indent + }) + + return formatted.trim() +} + +// For non-es2015 usage +module.exports = prettifyXml \ No newline at end of file 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 637c31dc..04726f8a 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 @@ -211,6 +211,9 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC if (content != null) { setInitialText(content); } else { + if (path == null) { + return; + } mFile = new File(path); if (mName == null) { mName = mFile.getName(); @@ -279,11 +282,11 @@ 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; + if (id == R.id.run) { + debug(); + return true; + } + return false; }); Fragment fragment = getActivity().getSupportFragmentManager().findFragmentById(R.id.toolbar_menu); if (fragment == null) { @@ -388,7 +391,7 @@ public class EditorView extends FrameLayout implements CodeCompletionBar.OnHintC } public ScriptExecution run(boolean showMessage) { - if(showMessage){ + if (showMessage) { Snackbar.make(this, R.string.text_start_running, Snackbar.LENGTH_SHORT).show(); } ScriptExecution execution = Scripts.runWithBroadcastSender(mFile); 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 c0dccbbd..89aa5571 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 @@ -80,7 +80,7 @@ public class CodeEditor extends HVScrollView { mTextViewRedoUndo = new TextViewUndoRedo(mCodeEditText); mJavaScriptHighlighter = new JavaScriptHighlighter(mTheme, mCodeEditText); setTheme(Theme.getDefault(getContext())); - mJsBeautifier = new JsBeautifier(this, "js/beautify.js"); + mJsBeautifier = new JsBeautifier(this, "js/js-beautify"); } diff --git a/autojs/src/main/java/com/stardust/autojs/engine/AssetAndUrlModuleSourceProvider.java b/autojs/src/main/java/com/stardust/autojs/engine/AssetAndUrlModuleSourceProvider.java index 6cb6e0d9..85ab1bfd 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/AssetAndUrlModuleSourceProvider.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/AssetAndUrlModuleSourceProvider.java @@ -1,5 +1,6 @@ package com.stardust.autojs.engine; +import android.content.res.AssetManager; import android.net.Uri; import org.mozilla.javascript.Scriptable; @@ -7,6 +8,7 @@ import org.mozilla.javascript.commonjs.module.provider.ModuleSource; import org.mozilla.javascript.commonjs.module.provider.UrlModuleSourceProvider; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; @@ -21,19 +23,17 @@ import java.util.List; public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider { - private static final String MODULES_PATH = "modules"; private android.content.Context mContext; - private List mModules; - private final URI mBaseURI = URI.create("file:///android_asset/modules"); + private final URI mBaseURI; + private final String mAssetDirPath; + private final AssetManager mAssetManager; - public AssetAndUrlModuleSourceProvider(android.content.Context context, List list) { + public AssetAndUrlModuleSourceProvider(android.content.Context context, String assetDirPath, List list) { super(list, null); mContext = context; - try { - mModules = Arrays.asList(mContext.getAssets().list(MODULES_PATH)); - } catch (IOException e) { - throw new RuntimeException(e); - } + mAssetDirPath = assetDirPath; + mBaseURI = URI.create("file:///android_asset/" + assetDirPath); + mAssetManager = mContext.getAssets(); } @Override @@ -42,10 +42,11 @@ public class AssetAndUrlModuleSourceProvider extends UrlModuleSourceProvider { if (!moduleIdWithExtension.endsWith(".js")) { moduleIdWithExtension += ".js"; } - if (mModules.contains(moduleIdWithExtension)) { - return new ModuleSource(new InputStreamReader(mContext.getAssets().open(MODULES_PATH + "/" + moduleIdWithExtension)), null, - URI.create(moduleIdWithExtension), mBaseURI, validator); + try { + return new ModuleSource(new InputStreamReader(mAssetManager.open(mAssetDirPath + "/" + moduleIdWithExtension)), null, + new URI(mBaseURI.toString() + "/" + moduleIdWithExtension), mBaseURI, validator); + } catch (FileNotFoundException e) { + return super.loadFromPrivilegedLocations(moduleId, validator); } - return super.loadFromPrivilegedLocations(moduleId, validator); } } \ No newline at end of file diff --git a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java index dfd1b86b..0b620be2 100644 --- a/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java +++ b/autojs/src/main/java/com/stardust/autojs/engine/RhinoJavaScriptEngine.java @@ -49,6 +49,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { private static final String LOG_TAG = "RhinoJavaScriptEngine"; + private static final String MODULES_PATH = "modules"; private static int contextCount = 0; private static StringScriptSource sInitScript; private static final ConcurrentHashMap sContextEngineMap = new ConcurrentHashMap<>(); @@ -129,7 +130,7 @@ public class RhinoJavaScriptEngine extends JavaScriptEngine { } void initRequireBuilder(Context context, Scriptable scope) { - AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(mAndroidContext, + AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(mAndroidContext, MODULES_PATH, Collections.singletonList(new File("/").toURI())); new RequireBuilder() .setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider)) diff --git a/autojs/src/main/java/com/stardust/autojs/script/JsBeautifier.java b/autojs/src/main/java/com/stardust/autojs/script/JsBeautifier.java index 0006f6b9..15b02ffa 100644 --- a/autojs/src/main/java/com/stardust/autojs/script/JsBeautifier.java +++ b/autojs/src/main/java/com/stardust/autojs/script/JsBeautifier.java @@ -4,14 +4,20 @@ import android.content.Context; import android.util.Log; import android.view.View; +import com.stardust.autojs.engine.AssetAndUrlModuleSourceProvider; import com.stardust.pio.PFiles; import com.stardust.pio.UncheckedIOException; import org.mozilla.javascript.Function; +import org.mozilla.javascript.ImporterTopLevel; import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.commonjs.module.RequireBuilder; +import org.mozilla.javascript.commonjs.module.provider.SoftCachingModuleScriptProvider; +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -21,6 +27,7 @@ import java.util.concurrent.Executors; public class JsBeautifier { + public interface Callback { void onSuccess(String beautifiedCode); @@ -34,12 +41,14 @@ public class JsBeautifier { private org.mozilla.javascript.Context mScriptContext; private Scriptable mScriptable; private final String mBeautifyJsPath; + private final String mBeautifyJsDir; private View mView; - public JsBeautifier(View view, String beautifyJsPath) { + public JsBeautifier(View view, String beautifyJsDirPath) { mContext = view.getContext(); mView = view; - mBeautifyJsPath = beautifyJsPath; + mBeautifyJsDir = beautifyJsDirPath; + mBeautifyJsPath = PFiles.join(beautifyJsDirPath, "beautify.js"); } public void beautify(final String code, final Callback callback) { @@ -65,11 +74,24 @@ public class JsBeautifier { } private void enterContext() { - if (mScriptContext == null) { - mScriptContext = org.mozilla.javascript.Context.enter(); - mScriptContext.setLanguageVersion(org.mozilla.javascript.Context.VERSION_1_8); - mScriptContext.setOptimizationLevel(-1); + if (mScriptContext != null) { + return; } + mScriptContext = org.mozilla.javascript.Context.enter(); + mScriptContext.setLanguageVersion(org.mozilla.javascript.Context.VERSION_1_8); + mScriptContext.setOptimizationLevel(-1); + if (mScriptable == null) { + ImporterTopLevel importerTopLevel = new ImporterTopLevel(); + importerTopLevel.initStandardObjects(mScriptContext, false); + mScriptable = importerTopLevel; + } + AssetAndUrlModuleSourceProvider provider = new AssetAndUrlModuleSourceProvider(mContext, mBeautifyJsDir, + Collections.singletonList(new File("/").toURI())); + new RequireBuilder() + .setModuleScriptProvider(new SoftCachingModuleScriptProvider(provider)) + .setSandboxed(false) + .createRequire(mScriptContext, mScriptable) + .install(mScriptable); } public void prepare() { @@ -93,8 +115,6 @@ public class JsBeautifier { try { enterContext(); InputStream is = mContext.getAssets().open(mBeautifyJsPath); - if (mScriptable == null) - mScriptable = mScriptContext.initSafeStandardObjects(); mJsBeautifyFunction = (Function) mScriptContext.evaluateString(mScriptable, PFiles.read(is), "", 1, null); } catch (IOException e) { exitContext();