feat(api): access node under caret/mouse and jump (PR #1903)

* Access node under caret/mouse and jump
* Apply lint
This commit is contained in:
Yoav Sternberg
2023-06-09 17:50:57 +03:00
committed by GitHub
parent 347d6e2c2e
commit f558203a9d
6 changed files with 217 additions and 0 deletions
@@ -49,4 +49,19 @@ public interface JadxGuiContext {
* Access to GUI settings
*/
JadxGuiSettings settings();
ICodeNodeRef getNodeUnderCaret();
ICodeNodeRef getNodeUnderMouse();
ICodeNodeRef getEnclosingNodeUnderCaret();
ICodeNodeRef getEnclosingNodeUnderMouse();
/**
* Jump to a code ref
*
* @return if successfully jumped to the code ref
*/
boolean open(ICodeNodeRef ref);
}
@@ -1,5 +1,6 @@
package jadx.gui.plugins.context;
import java.awt.Container;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -14,7 +15,15 @@ import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.gui.JadxGuiSettings;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.plugins.PluginContext;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.UiUtils;
public class GuiPluginContext implements JadxGuiContext {
@@ -86,4 +95,85 @@ public class GuiPluginContext implements JadxGuiContext {
public @Nullable ISettingsGroup getCustomSettingsGroup() {
return customSettingsGroup;
}
@Nullable
private CodeArea getCodeArea() {
Container contentPane = commonContext.getMainWindow().getTabbedPane().getSelectedContentPanel();
if (contentPane instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) contentPane).getCodeArea();
if (codeArea instanceof CodeArea) {
return (CodeArea) codeArea;
}
}
return null;
}
@Override
public ICodeNodeRef getNodeUnderCaret() {
CodeArea codeArea = getCodeArea();
if (codeArea != null) {
JNode nodeUnderCaret = codeArea.getNodeUnderCaret();
if (nodeUnderCaret != null) {
return nodeUnderCaret.getCodeNodeRef();
}
}
return null;
}
@Override
public ICodeNodeRef getNodeUnderMouse() {
CodeArea codeArea = getCodeArea();
if (codeArea != null) {
JNode nodeUnderMouse = codeArea.getNodeUnderMouse();
if (nodeUnderMouse != null) {
return nodeUnderMouse.getCodeNodeRef();
}
}
return null;
}
@Override
public ICodeNodeRef getEnclosingNodeUnderCaret() {
CodeArea codeArea = getCodeArea();
if (codeArea != null) {
JNode nodeUnderMouse = codeArea.getEnclosingNodeUnderCaret();
if (nodeUnderMouse != null) {
return nodeUnderMouse.getCodeNodeRef();
}
}
return null;
}
@Override
public ICodeNodeRef getEnclosingNodeUnderMouse() {
CodeArea codeArea = getCodeArea();
if (codeArea != null) {
JNode nodeUnderMouse = codeArea.getEnclosingNodeUnderMouse();
if (nodeUnderMouse != null) {
return nodeUnderMouse.getCodeNodeRef();
}
}
return null;
}
@Override
public boolean open(ICodeNodeRef ref) {
JNodeCache cache = commonContext.getMainWindow().getWrapper().getCache().getNodeCache();
JNode node;
if (ref instanceof ClassNode) {
node = cache.makeFrom(((ClassNode) ref).getJavaNode());
} else if (ref instanceof MethodNode) {
node = cache.makeFrom(((MethodNode) ref).getJavaNode());
} else if (ref instanceof FieldNode) {
node = cache.makeFrom(((FieldNode) ref).getJavaNode());
} else {
// Package node - cannot jump to it
// TODO: Var node - might be possible
return false;
}
commonContext.getMainWindow().getTabbedPane().codeJump(node);
return true;
}
}
@@ -213,6 +213,20 @@ public final class CodeArea extends AbstractCodeArea {
return getJNodeAtOffset(start);
}
@Nullable
public JNode getEnclosingNodeUnderCaret() {
int caretPos = getCaretPosition();
Token token = modelToToken(caretPos);
if (token == null) {
return null;
}
int start = adjustOffsetForToken(token);
if (start == -1) {
start = caretPos;
}
return getEnclosingJNodeAtOffset(start);
}
@Nullable
public JNode getNodeUnderMouse() {
Point pos = UiUtils.getMousePosition(this);
@@ -220,6 +234,22 @@ public final class CodeArea extends AbstractCodeArea {
return getJNodeAtOffset(offset);
}
@Nullable
public JNode getEnclosingNodeUnderMouse() {
Point pos = UiUtils.getMousePosition(this);
int offset = adjustOffsetForToken(viewToToken(pos));
return getEnclosingJNodeAtOffset(offset);
}
@Nullable
public JNode getEnclosingJNodeAtOffset(int offset) {
JavaNode javaNode = getEnclosingJavaNode(offset);
if (javaNode != null) {
return convertJavaNode(javaNode);
}
return null;
}
@Nullable
public JNode getJNodeAtOffset(int offset) {
JavaNode javaNode = getJavaNodeAtOffset(offset);
@@ -253,6 +283,15 @@ public final class CodeArea extends AbstractCodeArea {
}
}
public JavaNode getEnclosingJavaNode(int offset) {
try {
return getJadxWrapper().getDecompiler().getEnclosingNode(getCodeInfo(), offset);
} catch (Exception e) {
LOG.error("Can't get java node by offset: {}", offset, e);
return null;
}
}
public JavaClass getJavaClassIfAtPos(int pos) {
try {
ICodeInfo codeInfo = getCodeInfo();
@@ -0,0 +1,43 @@
import jadx.api.metadata.ICodeNodeRef
import jadx.core.dex.nodes.MethodNode
val jadx = getJadxInstance()
var savedBookmark: ICodeNodeRef? = null
jadx.gui.ifAvailable {
addPopupMenuAction(
"Set bookmark",
enabled = { true },
keyBinding = "B",
action = ::setBookmark,
)
addMenuAction(
"Jump to bookmark",
action = ::jumpToBookmark,
)
}
fun setBookmark(node: ICodeNodeRef) {
val enclosing = jadx.gui.enclosingNodeUnderCaret ?: run {
jadx.log.info { "No enclosing node" }
return
}
// You can bookmark a field, method or a class
val target = if (enclosing is MethodNode) enclosing else node
jadx.log.info { "Setting bookmark to: $target" }
savedBookmark = target
}
fun jumpToBookmark() {
if (savedBookmark == null) {
jadx.log.info { "No bookmark" }
} else {
val res = jadx.gui.open(savedBookmark!!)
if (!res) {
jadx.log.info { "Failed to jump to bookmark" }
}
}
}
@@ -0,0 +1,19 @@
import jadx.api.metadata.ICodeNodeRef
val jadx = getJadxInstance()
jadx.gui.ifAvailable {
addPopupMenuAction(
"Print enclosing symbols under caret or mouse",
enabled = { true },
keyBinding = "G",
action = ::runAction,
)
}
fun runAction(node: ICodeNodeRef) {
jadx.log.info { "Node under caret: ${jadx.gui.nodeUnderCaret}" }
jadx.log.info { "Enclosing node under caret: ${jadx.gui.enclosingNodeUnderCaret}" }
jadx.log.info { "Node under mouse: ${jadx.gui.nodeUnderMouse}" }
jadx.log.info { "Enclosing Node under mouse: ${jadx.gui.enclosingNodeUnderMouse}" }
}
@@ -40,6 +40,17 @@ class Gui(
context().copyToClipboard(str)
}
fun open(ref: ICodeNodeRef): Boolean = context().open(ref)
val nodeUnderCaret: ICodeNodeRef?
get() = context().nodeUnderCaret
val nodeUnderMouse: ICodeNodeRef?
get() = context().nodeUnderMouse
val enclosingNodeUnderCaret: ICodeNodeRef?
get() = context().enclosingNodeUnderCaret
val enclosingNodeUnderMouse: ICodeNodeRef?
get() = context().enclosingNodeUnderMouse
private fun context(): JadxGuiContext =
guiContext ?: throw IllegalStateException("GUI plugins context not available!")
}