feat(gui): add auto complete for jadx scripts

This commit is contained in:
Skylot
2022-07-17 22:25:04 +01:00
parent 18fe9f305c
commit d9af91bc4d
18 changed files with 388 additions and 23 deletions
+6
View File
@@ -8,6 +8,12 @@ plugins {
dependencies {
implementation(project(':jadx-core'))
implementation(project(":jadx-cli"))
// jadx-script autocomplete support
implementation(project(":jadx-plugins::jadx-script:jadx-script-ide"))
implementation("org.jetbrains.kotlin:kotlin-scripting-common:1.7.20")
implementation 'com.fifesoft:autocomplete:3.3.0'
implementation 'com.beust:jcommander:1.82'
implementation 'ch.qos.logback:logback-classic:1.3.5'
@@ -0,0 +1,26 @@
package jadx.gui.plugins.script;
import javax.swing.JList;
import org.fife.ui.autocomplete.Completion;
import org.fife.ui.autocomplete.CompletionCellRenderer;
import jadx.gui.settings.JadxSettings;
import static jadx.gui.utils.UiUtils.escapeHtml;
import static jadx.gui.utils.UiUtils.fadeHtml;
import static jadx.gui.utils.UiUtils.wrapHtml;
public class CompletionRenderer extends CompletionCellRenderer {
public CompletionRenderer(JadxSettings settings) {
setDisplayFont(settings.getFont());
}
@Override
protected void prepareForOtherCompletion(JList list, Completion c, int index, boolean selected, boolean hasFocus) {
JadxScriptCompletion cmpl = (JadxScriptCompletion) c;
setText(wrapHtml(escapeHtml(cmpl.getInputText()) + " "
+ fadeHtml(escapeHtml(cmpl.getSummary()))));
}
}
@@ -0,0 +1,154 @@
package jadx.gui.plugins.script;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.swing.Icon;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import org.fife.ui.autocomplete.Completion;
import org.fife.ui.autocomplete.CompletionProviderBase;
import org.fife.ui.autocomplete.ParameterizedCompletion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import kotlin.script.experimental.api.ScriptDiagnostic;
import kotlin.script.experimental.api.SourceCode;
import kotlin.script.experimental.api.SourceCodeCompletionVariant;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.Icons;
import jadx.plugins.script.ide.JadxScriptAutoComplete;
import jadx.plugins.script.ide.ScriptCompletionResult;
import static jadx.plugins.script.ide.CompleteKt.AUTO_COMPLETE_INSERT_STR;
public class JadxScriptCompleteProvider extends CompletionProviderBase {
private static final Logger LOG = LoggerFactory.getLogger(JadxScriptCompleteProvider.class);
private static final Map<String, Icon> ICONS_MAP = buildIconsMap();
private static Map<String, Icon> buildIconsMap() {
Map<String, Icon> map = new HashMap<>();
map.put("class", Icons.CLASS);
map.put("method", Icons.METHOD);
map.put("field", Icons.FIELD);
map.put("property", Icons.PROPERTY);
map.put("parameter", Icons.PARAMETER);
map.put("package", Icons.PACKAGE);
return map;
}
private final AbstractCodeArea codeArea;
public JadxScriptCompleteProvider(AbstractCodeArea codeArea) {
this.codeArea = codeArea;
}
private List<Completion> getCompletions() {
try {
String code = codeArea.getText();
int caretPos = codeArea.getCaretPosition();
JadxScriptAutoComplete scriptComplete = new JadxScriptAutoComplete(codeArea.getNode().getName());
ScriptCompletionResult result = scriptComplete.complete(code, caretPos);
int replacePos = getReplacePos(caretPos, result);
if (!result.getReports().isEmpty()) {
LOG.debug("Script completion reports: {}", result.getReports());
}
return convertCompletions(result.getCompletions(), code, replacePos);
} catch (Exception e) {
LOG.error("Code completion failed", e);
return Collections.emptyList();
}
}
private List<Completion> convertCompletions(List<SourceCodeCompletionVariant> completions, String code, int replacePos) {
if (completions.isEmpty()) {
return Collections.emptyList();
}
if (LOG.isDebugEnabled()) {
String cmplStr = completions.stream().map(SourceCodeCompletionVariant::toString).collect(Collectors.joining("\n"));
LOG.debug("Completions:\n{}", cmplStr);
}
int count = completions.size();
List<Completion> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
SourceCodeCompletionVariant c = completions.get(i);
if (Objects.equals(c.getIcon(), "keyword")) {
// too many, not very useful
continue;
}
JadxScriptCompletion cmpl = new JadxScriptCompletion(this, count - i);
cmpl.setData(c.getText(), code, replacePos);
if (Objects.equals(c.getIcon(), "method") && !Objects.equals(c.getText(), c.getDisplayText())) {
// add method args details for methods
cmpl.setSummary(c.getDisplayText() + " " + c.getTail());
} else {
cmpl.setSummary(c.getTail());
}
cmpl.setToolTip(c.getDisplayText());
Icon icon = ICONS_MAP.get(c.getIcon());
cmpl.setIcon(icon != null ? icon : Icons.FILE);
list.add(cmpl);
}
return list;
}
private int getReplacePos(int caretPos, ScriptCompletionResult result) throws BadLocationException {
int lineRaw = codeArea.getLineOfOffset(caretPos);
int lineStart = codeArea.getLineStartOffset(lineRaw);
int line = lineRaw + 1;
int col = caretPos - lineStart + 1;
List<ScriptDiagnostic> reports = result.getReports();
ScriptDiagnostic cmplReport = ListUtils.filterOnlyOne(reports, r -> {
if (r.getSeverity() == ScriptDiagnostic.Severity.ERROR && r.getLocation() != null) {
SourceCode.Position start = r.getLocation().getStart();
return start.getLine() == line && r.getMessage().endsWith(AUTO_COMPLETE_INSERT_STR);
}
return false;
});
if (cmplReport == null) {
LOG.warn("Failed to find completion report in: {}", reports);
return caretPos;
}
reports.remove(cmplReport);
int reportCol = Objects.requireNonNull(cmplReport.getLocation()).getStart().getCol();
return caretPos - (col - reportCol);
}
@Override
public String getAlreadyEnteredText(JTextComponent comp) {
try {
int pos = codeArea.getCaretPosition();
return codeArea.getText(0, pos);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to get text before caret", e);
}
}
@Override
public List<Completion> getCompletionsAt(JTextComponent comp, Point p) {
return getCompletions();
}
@Override
protected List<Completion> getCompletionsImpl(JTextComponent comp) {
return getCompletions();
}
@Override
public List<ParameterizedCompletion> getParameterizedCompletions(JTextComponent tc) {
return null;
}
}
@@ -0,0 +1,93 @@
package jadx.gui.plugins.script;
import javax.swing.Icon;
import javax.swing.text.JTextComponent;
import org.fife.ui.autocomplete.Completion;
import org.fife.ui.autocomplete.CompletionProvider;
public class JadxScriptCompletion implements Completion {
private final CompletionProvider provider;
private final int relevance;
private String input;
private String code;
private int replacePos;
private Icon icon;
private String summary;
private String toolTip;
public JadxScriptCompletion(CompletionProvider provider, int relevance) {
this.provider = provider;
this.relevance = relevance;
}
public void setData(String input, String code, int replacePos) {
this.input = input;
this.code = code;
this.replacePos = replacePos;
}
@Override
public String getInputText() {
return input;
}
@Override
public CompletionProvider getProvider() {
return provider;
}
@Override
public String getAlreadyEntered(JTextComponent comp) {
return provider.getAlreadyEnteredText(comp);
}
@Override
public int getRelevance() {
return relevance;
}
@Override
public String getReplacementText() {
return code.substring(0, replacePos) + input;
}
@Override
public Icon getIcon() {
return icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
@Override
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public String getToolTipText() {
return toolTip;
}
public void setToolTip(String toolTip) {
this.toolTip = toolTip;
}
@Override
public int compareTo(Completion other) {
return Integer.compare(relevance, other.getRelevance());
}
@Override
public String toString() {
return input;
}
}
@@ -20,13 +20,13 @@ import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class JClass extends JLoadableNode {
private static final long serialVersionUID = -1239986875244097177L;
private static final ImageIcon ICON_CLASS = UiUtils.openSvgIcon("nodes/class");
private static final ImageIcon ICON_CLASS_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractClass");
private static final ImageIcon ICON_CLASS_PUBLIC = UiUtils.openSvgIcon("nodes/publicClass");
private static final ImageIcon ICON_CLASS_PRIVATE = UiUtils.openSvgIcon("nodes/privateClass");
@@ -155,7 +155,7 @@ public class JClass extends JLoadableNode {
if (accessInfo.isPublic()) {
return ICON_CLASS_PUBLIC;
}
return ICON_CLASS;
return Icons.CLASS;
}
@Override
@@ -15,13 +15,12 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.Icons;
import jadx.gui.utils.UiUtils;
public class JField extends JNode {
private static final long serialVersionUID = 1712572192106793359L;
private static final ImageIcon ICON_FLD_DEF = UiUtils.openSvgIcon("nodes/field");
private static final ImageIcon ICON_FLD_PRI = UiUtils.openSvgIcon("nodes/privateField");
private static final ImageIcon ICON_FLD_PRO = UiUtils.openSvgIcon("nodes/protectedField");
private static final ImageIcon ICON_FLD_PUB = UiUtils.openSvgIcon("nodes/publicField");
@@ -65,8 +64,7 @@ public class JField extends JNode {
@Override
public Icon getIcon() {
AccessInfo af = field.getAccessFlags();
OverlayIcon icon = UiUtils.makeIcon(af, ICON_FLD_PUB, ICON_FLD_PRI, ICON_FLD_PRO, ICON_FLD_DEF);
return icon;
return UiUtils.makeIcon(af, ICON_FLD_PUB, ICON_FLD_PRI, ICON_FLD_PRO, Icons.FIELD);
}
@Override
@@ -23,8 +23,6 @@ import jadx.gui.utils.UiUtils;
public class JMethod extends JNode {
private static final long serialVersionUID = 3834526867464663751L;
private static final ImageIcon ICON_METHOD = UiUtils.openSvgIcon("nodes/method");
private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod");
private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod");
private static final ImageIcon ICON_METHOD_PROTECTED = UiUtils.openSvgIcon("nodes/protectedMethod");
@@ -66,7 +64,7 @@ public class JMethod extends JNode {
@Override
public Icon getIcon() {
AccessInfo accessFlags = mth.getAccessFlags();
Icon icon = ICON_METHOD;
Icon icon = Icons.METHOD;
if (accessFlags.isAbstract()) {
icon = ICON_METHOD_ABSTRACT;
}
@@ -5,7 +5,6 @@ import java.util.Collections;
import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import jadx.api.JavaPackage;
@@ -13,13 +12,11 @@ import jadx.core.utils.Utils;
import jadx.gui.JadxWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.popupmenu.JPackagePopupMenu;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.Icons;
public class JPackage extends JNode {
private static final long serialVersionUID = -4120718634156839804L;
private static final ImageIcon PACKAGE_ICON = UiUtils.openSvgIcon("nodes/package");
private String fullName;
private String name;
private boolean enabled;
@@ -118,7 +115,7 @@ public class JPackage extends JNode {
@Override
public Icon getIcon() {
return PACKAGE_ICON;
return Icons.PACKAGE;
}
@Override
@@ -20,6 +20,7 @@ import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
@@ -29,6 +30,7 @@ import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultCaret;
import org.fife.ui.autocomplete.AutoCompletion;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Token;
@@ -44,9 +46,12 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.plugins.script.CompletionRenderer;
import jadx.gui.plugins.script.JadxScriptCompleteProvider;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JEditableNode;
import jadx.gui.treemodel.JInputScript;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.panel.ContentPanel;
@@ -101,6 +106,9 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
JEditableNode editableNode = (JEditableNode) node;
addSaveActions(editableNode);
addChangeUpdates(editableNode);
if (node instanceof JInputScript) {
addAutoComplete(settings);
}
} else {
addCaretActions();
addFastCopyAction();
@@ -216,6 +224,17 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}));
}
private void addAutoComplete(JadxSettings settings) {
JadxScriptCompleteProvider provider = new JadxScriptCompleteProvider(this);
provider.setAutoActivationRules(false, ".");
AutoCompletion ac = new AutoCompletion(provider);
ac.setListCellRenderer(new CompletionRenderer(settings));
ac.setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, UiUtils.ctrlButton()));
ac.setAutoActivationEnabled(true);
ac.setAutoCompleteSingleChoices(true);
ac.install(this);
}
private String highlightCaretWord(String lastText, int pos) {
String text = getWordByPosition(pos);
if (StringUtils.isEmpty(text)) {
@@ -18,7 +18,6 @@ import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
@@ -33,12 +32,12 @@ import javax.swing.tree.TreePath;
import jadx.api.JavaPackage;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class ExcludePkgDialog extends JDialog {
private static final long serialVersionUID = -1111111202104151030L;
private static final ImageIcon PACKAGE_ICON = UiUtils.openSvgIcon("nodes/package");
private final transient MainWindow mainWindow;
private transient JTree tree;
@@ -311,7 +310,7 @@ public class ExcludePkgDialog extends JDialog {
return node.checkbox;
}
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
setIcon(PACKAGE_ICON);
setIcon(Icons.PACKAGE);
return c;
}
}
@@ -20,4 +20,11 @@ public class Icons {
public static final ImageIcon FOLDER = UiUtils.openSvgIcon("nodes/folder");
public static final ImageIcon FILE = UiUtils.openSvgIcon("nodes/file_any_type");
public static final ImageIcon PACKAGE = UiUtils.openSvgIcon("nodes/package");
public static final ImageIcon CLASS = UiUtils.openSvgIcon("nodes/class");
public static final ImageIcon METHOD = UiUtils.openSvgIcon("nodes/method");
public static final ImageIcon FIELD = UiUtils.openSvgIcon("nodes/field");
public static final ImageIcon PROPERTY = UiUtils.openSvgIcon("nodes/property");
public static final ImageIcon PARAMETER = UiUtils.openSvgIcon("nodes/parameter");
}
@@ -0,0 +1,7 @@
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#F4AF3D" fill-opacity=".6" d="M15,8 C15,11.866 11.866,15 8,15 C4.134,15 1,11.866 1,8 C1,4.134 4.134,1 8,1 C11.866,1 15,4.134 15,8"/>
<path fill="#231F20" fill-opacity=".7" d="M0.9974,2.8021 C0.9974,3.83569336 1.23855513,4.8771 2.5354,4.8771 C3.83224487,4.8771 4,3.4641 4,2.7921 C4,2.1061 3.90228271,0.7461 2.5244,0.7461 C1.14651729,0.7461 0.9974,1.76850664 0.9974,2.8021 Z M1,5 C1.008,5.074 1.00713333,6.074 0.9974,8 L0.0004,8 L0.0004,0.0001 L0.9974,0.0001 L0.9974,0.7461 C1.36693099,0.248766667 1.93993099,0.0001 2.7164,0.0001 C3.88110352,0.0001 5.0004,0.578186035 5.0004,2.8021 C5.0004,5.02601396 3.65454102,5.6231 2.7164,5.6231 C2.09097266,5.6231 1.51883932,5.4154 1,5 Z" transform="translate(6 5)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,7 @@
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#B99BF8" fill-opacity=".6" d="M15,8 C15,11.866 11.866,15 8,15 C4.134,15 1,11.866 1,8 C1,4.134 4.134,1 8,1 C11.866,1 15,4.134 15,8"/>
<path fill="#231F20" fill-opacity=".7" d="M0.9974,2.8021 C0.9974,3.83569336 1.23855513,4.8771 2.5354,4.8771 C3.83224487,4.8771 4,3.4641 4,2.7921 C4,2.1061 3.90228271,0.7461 2.5244,0.7461 C1.14651729,0.7461 0.9974,1.76850664 0.9974,2.8021 Z M1,5 C1.008,5.074 1.00713333,6.074 0.9974,8 L0.0004,8 L0.0004,0.0001 L0.9974,0.0001 L0.9974,0.7461 C1.36693099,0.248766667 1.93993099,0.0001 2.7164,0.0001 C3.88110352,0.0001 5.0004,0.578186035 5.0004,2.8021 C5.0004,5.02601396 3.65454102,5.6231 2.7164,5.6231 C2.09097266,5.6231 1.51883932,5.4154 1,5 Z" transform="translate(6 5)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,16 @@
plugins {
kotlin("jvm") version "1.7.20"
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
implementation("org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable")
implementation("org.jetbrains.kotlin:kotlin-scripting-ide-services")
implementation(project(":jadx-plugins:jadx-script:jadx-script-runtime"))
implementation(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
}
@@ -0,0 +1,34 @@
package jadx.plugins.script.ide
import jadx.plugins.script.runner.ScriptEval
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.util.toSourceCodePosition
const val AUTO_COMPLETE_INSERT_STR = "ABCDEF" // defined at KJvmReplCompleter.INSERTED_STRING
data class ScriptCompletionResult(
val completions: List<SourceCodeCompletionVariant>,
val reports: List<ScriptDiagnostic>
)
class JadxScriptAutoComplete(private val scriptName: String) {
private val replCompiler = KJvmReplCompilerWithIdeServices()
private val compileConf = ScriptEval().buildCompileConf()
fun complete(code: String, cursor: Int): ScriptCompletionResult {
val result = complete(code.toScriptSource(scriptName), cursor)
return ScriptCompletionResult(
completions = result.valueOrNull()?.toList() ?: listOf(),
reports = result.reports
)
}
private fun complete(code: SourceCode, cursor: Int): ResultWithDiagnostics<ReplCompletionResult> {
return runBlocking {
replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConf)
}
}
}
@@ -2,7 +2,6 @@ package jadx.plugins.script.runner
import jadx.api.JadxDecompiler
import jadx.api.plugins.JadxPluginContext
import jadx.api.plugins.pass.JadxPassContext
import jadx.plugins.script.runtime.JadxScript
import jadx.plugins.script.runtime.JadxScriptData
import mu.KotlinLogging
@@ -38,11 +37,17 @@ class ScriptEval {
processEvalResult(result, scriptFile)
}
private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics<EvaluationResult> {
val compilationConf = createJvmCompilationConfigurationFromTemplate<JadxScript>()
val evalConf = createJvmEvaluationConfigurationFromTemplate<JadxScript> {
fun buildCompileConf() = createJvmCompilationConfigurationFromTemplate<JadxScript>()
fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration {
return createJvmEvaluationConfigurationFromTemplate<JadxScript> {
constructorArgs(scriptData)
}
}
private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics<EvaluationResult> {
val compilationConf = buildCompileConf()
val evalConf = buildEvalConf(scriptData)
return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConf, evalConf)
}
@@ -4,8 +4,6 @@ plugins {
kotlin("jvm") version "1.7.20"
}
group = "jadx-script-context"
dependencies {
implementation("org.jetbrains.kotlin:kotlin-scripting-common")
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
+1
View File
@@ -14,4 +14,5 @@ include("jadx-plugins:jadx-java-convert")
include("jadx-plugins:jadx-script:jadx-script-plugin")
include("jadx-plugins:jadx-script:jadx-script-runtime")
include("jadx-plugins:jadx-script:jadx-script-ide")
include("jadx-plugins:jadx-script:examples")