From d9af91bc4dfbaed77932c02426d42d31d8351656 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 17 Jul 2022 22:25:04 +0100 Subject: [PATCH] feat(gui): add auto complete for jadx scripts --- jadx-gui/build.gradle | 6 + .../plugins/script/CompletionRenderer.java | 26 +++ .../script/JadxScriptCompleteProvider.java | 154 ++++++++++++++++++ .../plugins/script/JadxScriptCompletion.java | 93 +++++++++++ .../main/java/jadx/gui/treemodel/JClass.java | 4 +- .../main/java/jadx/gui/treemodel/JField.java | 6 +- .../main/java/jadx/gui/treemodel/JMethod.java | 4 +- .../java/jadx/gui/treemodel/JPackage.java | 7 +- .../gui/ui/codearea/AbstractCodeArea.java | 19 +++ .../jadx/gui/ui/dialog/ExcludePkgDialog.java | 5 +- .../src/main/java/jadx/gui/utils/Icons.java | 7 + .../main/resources/icons/nodes/parameter.svg | 7 + .../main/resources/icons/nodes/property.svg | 7 + .../jadx-script-ide/build.gradle.kts | 16 ++ .../src/main/kotlin/complete.kt | 34 ++++ .../jadx/plugins/script/runner/ScriptEval.kt | 13 +- .../jadx-script-runtime/build.gradle.kts | 2 - settings.gradle.kts | 1 + 18 files changed, 388 insertions(+), 23 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/CompletionRenderer.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompleteProvider.java create mode 100644 jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompletion.java create mode 100644 jadx-gui/src/main/resources/icons/nodes/parameter.svg create mode 100644 jadx-gui/src/main/resources/icons/nodes/property.svg create mode 100644 jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts create mode 100644 jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/complete.kt diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index b208706e9..4c1b0fef9 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -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' diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/CompletionRenderer.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/CompletionRenderer.java new file mode 100644 index 000000000..2928f5f1d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/CompletionRenderer.java @@ -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())))); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompleteProvider.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompleteProvider.java new file mode 100644 index 000000000..f616d1655 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompleteProvider.java @@ -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 ICONS_MAP = buildIconsMap(); + + private static Map buildIconsMap() { + Map 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 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 convertCompletions(List 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 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 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 getCompletionsAt(JTextComponent comp, Point p) { + return getCompletions(); + } + + @Override + protected List getCompletionsImpl(JTextComponent comp) { + return getCompletions(); + } + + @Override + public List getParameterizedCompletions(JTextComponent tc) { + return null; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompletion.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompletion.java new file mode 100644 index 000000000..536e397a2 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/JadxScriptCompletion.java @@ -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; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 0e9a22a1f..3b216ea8c 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -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 diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java index 726fc39ad..723f3d483 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java @@ -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 diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index 15a5557f7..e42bb026b 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index ea5f1d317..fb0f70b1f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -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 diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index ba093c81e..b662a078f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -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)) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java index f7d94147f..73f62c542 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExcludePkgDialog.java @@ -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; } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java index 5dc6f9a47..72704cae1 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Icons.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Icons.java @@ -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"); } diff --git a/jadx-gui/src/main/resources/icons/nodes/parameter.svg b/jadx-gui/src/main/resources/icons/nodes/parameter.svg new file mode 100644 index 000000000..728e3f8ef --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/parameter.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/jadx-gui/src/main/resources/icons/nodes/property.svg b/jadx-gui/src/main/resources/icons/nodes/property.svg new file mode 100644 index 000000000..6287dad8f --- /dev/null +++ b/jadx-gui/src/main/resources/icons/nodes/property.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts b/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts new file mode 100644 index 000000000..8064b3b13 --- /dev/null +++ b/jadx-plugins/jadx-script/jadx-script-ide/build.gradle.kts @@ -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") +} diff --git a/jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/complete.kt b/jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/complete.kt new file mode 100644 index 000000000..5f73acb39 --- /dev/null +++ b/jadx-plugins/jadx-script/jadx-script-ide/src/main/kotlin/complete.kt @@ -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, + val reports: List +) + +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 { + return runBlocking { + replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConf) + } + } +} diff --git a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptEval.kt b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptEval.kt index 2eabefaae..20a868750 100644 --- a/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptEval.kt +++ b/jadx-plugins/jadx-script/jadx-script-plugin/src/main/kotlin/jadx/plugins/script/runner/ScriptEval.kt @@ -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 { - val compilationConf = createJvmCompilationConfigurationFromTemplate() - val evalConf = createJvmEvaluationConfigurationFromTemplate { + fun buildCompileConf() = createJvmCompilationConfigurationFromTemplate() + + fun buildEvalConf(scriptData: JadxScriptData): ScriptEvaluationConfiguration { + return createJvmEvaluationConfigurationFromTemplate { constructorArgs(scriptData) } + } + + private fun eval(scriptFile: File, scriptData: JadxScriptData): ResultWithDiagnostics { + val compilationConf = buildCompileConf() + val evalConf = buildEvalConf(scriptData) return BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), compilationConf, evalConf) } diff --git a/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts b/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts index 4f6f79543..7b3d2d12d 100644 --- a/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts +++ b/jadx-plugins/jadx-script/jadx-script-runtime/build.gradle.kts @@ -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") diff --git a/settings.gradle.kts b/settings.gradle.kts index 671852d3c..5da91a40d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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")