* feat(gui): add code comments (#359) * refactor: replace instanceof search with method dispatch in RegionGen * fix: various bug fixes and improvements for code comments * fix(gui): support multiline code comments * fix: resolve code differences after class reload * fix(gui): add search for comments, allow search in active tab only * fix: correct search for inner classes * fix(gui): run full index on search dialog open
This commit is contained in:
@@ -20,6 +20,7 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
|
||||
import static jadx.gui.utils.FileUtils.toFiles;
|
||||
@@ -29,6 +30,7 @@ public class JadxWrapper {
|
||||
|
||||
private final JadxSettings settings;
|
||||
private JadxDecompiler decompiler;
|
||||
private JadxProject project;
|
||||
private List<Path> openPaths = Collections.emptyList();
|
||||
|
||||
public JadxWrapper(JadxSettings settings) {
|
||||
@@ -41,6 +43,7 @@ public class JadxWrapper {
|
||||
try {
|
||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
||||
jadxArgs.setInputFiles(toFiles(paths));
|
||||
jadxArgs.setCodeData(project.getCodeData());
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.decompiler.load();
|
||||
@@ -155,10 +158,14 @@ public class JadxWrapper {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
|
||||
public void setProject(JadxProject project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fullName Full name of an outer class. Inner classes are not supported.
|
||||
*/
|
||||
public @Nullable JavaClass searchJavaClassByClassName(String fullName) {
|
||||
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
||||
return decompiler.getClasses().stream()
|
||||
.filter(cls -> cls.getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
@@ -166,10 +173,7 @@ public class JadxWrapper {
|
||||
}
|
||||
|
||||
public @Nullable JavaClass searchJavaClassByOrigClassName(String fullName) {
|
||||
return decompiler.getClasses().stream()
|
||||
.filter(cls -> cls.getClassNode().getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return decompiler.searchJavaClassByOrigFullName(fullName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ import jadx.gui.utils.search.TextSearchIndex;
|
||||
public class IndexJob extends BackgroundJob {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IndexJob.class);
|
||||
|
||||
private final CacheObject cache;
|
||||
|
||||
public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount) {
|
||||
@@ -29,18 +30,14 @@ public class IndexJob extends BackgroundJob {
|
||||
|
||||
@Override
|
||||
protected void runJob() {
|
||||
TextSearchIndex index = new TextSearchIndex(cache);
|
||||
CodeUsageInfo usageInfo = new CodeUsageInfo(cache.getNodeCache());
|
||||
|
||||
cache.setTextIndex(index);
|
||||
cache.setUsageInfo(usageInfo);
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
addTask(index::indexResource);
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(() -> indexCls(cache, cls));
|
||||
}
|
||||
}
|
||||
|
||||
public static void indexCls(CacheObject cache, JavaClass cls) {
|
||||
private static void indexCls(CacheObject cache, JavaClass cls) {
|
||||
try {
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
CodeUsageInfo usageInfo = cache.getUsageInfo();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -14,63 +15,76 @@ import org.slf4j.LoggerFactory;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.utils.GsonUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.PathTypeAdapter;
|
||||
|
||||
public class JadxProject {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxProject.class);
|
||||
private static final int CURRENT_SETTINGS_VERSION = 0;
|
||||
|
||||
private static final int CURRENT_PROJECT_VERSION = 1;
|
||||
public static final String PROJECT_EXTENSION = "jadx";
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
|
||||
.registerTypeAdapter(ICodeComment.class, GsonUtils.interfaceReplace(JadxCodeComment.class))
|
||||
.registerTypeAdapter(IJavaNodeRef.class, GsonUtils.interfaceReplace(JadxNodeRef.class))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
private transient MainWindow mainWindow;
|
||||
private transient JadxSettings settings;
|
||||
|
||||
private transient String name = "New Project";
|
||||
private transient Path projectPath;
|
||||
private List<Path> filesPath;
|
||||
private List<String[]> treeExpansions = new ArrayList<>();
|
||||
|
||||
private transient boolean saved;
|
||||
private transient boolean initial = true;
|
||||
private transient boolean saved;
|
||||
|
||||
private int projectVersion = 0;
|
||||
private List<Path> files;
|
||||
private List<String[]> treeExpansions = new ArrayList<>();
|
||||
private JadxCodeData codeData = new JadxCodeData();
|
||||
|
||||
private int projectVersion;
|
||||
|
||||
// Don't remove. Used in json serialization
|
||||
public JadxProject() {
|
||||
}
|
||||
|
||||
public JadxProject(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setSettings(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setMainWindow(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public Path getProjectPath() {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
private void setProjectPath(Path projectPath) {
|
||||
this.projectPath = projectPath;
|
||||
if (projectVersion != CURRENT_SETTINGS_VERSION) {
|
||||
upgradeSettings(projectVersion);
|
||||
}
|
||||
name = projectPath.getFileName().toString();
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
int dotPos = name.lastIndexOf('.');
|
||||
if (dotPos != -1) {
|
||||
name = name.substring(0, dotPos);
|
||||
}
|
||||
changed();
|
||||
}
|
||||
|
||||
public List<Path> getFilePaths() {
|
||||
return filesPath;
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFilePath(List<Path> files) {
|
||||
if (!files.equals(getFilePaths())) {
|
||||
this.filesPath = files;
|
||||
this.files = files;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
@@ -85,11 +99,7 @@ public class JadxProject {
|
||||
}
|
||||
|
||||
public void removeTreeExpansion(String[] expansion) {
|
||||
for (Iterator<String[]> it = treeExpansions.iterator(); it.hasNext();) {
|
||||
if (isParentOfExpansion(expansion, it.next())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
treeExpansions.removeIf(strings -> isParentOfExpansion(expansion, strings));
|
||||
changed();
|
||||
}
|
||||
|
||||
@@ -106,13 +116,25 @@ public class JadxProject {
|
||||
return false;
|
||||
}
|
||||
|
||||
public JadxCodeData getCodeData() {
|
||||
return codeData;
|
||||
}
|
||||
|
||||
public void setCodeData(JadxCodeData codeData) {
|
||||
this.codeData = codeData;
|
||||
changed();
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
if (settings.isAutoSaveProject()) {
|
||||
if (settings != null && settings.isAutoSaveProject()) {
|
||||
save();
|
||||
} else {
|
||||
saved = false;
|
||||
}
|
||||
initial = false;
|
||||
if (mainWindow != null) {
|
||||
mainWindow.updateProject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -134,8 +156,8 @@ public class JadxProject {
|
||||
|
||||
public void save() {
|
||||
if (getProjectPath() != null) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(getProjectPath())) {
|
||||
writer.write(GSON.toJson(this));
|
||||
try (Writer writer = Files.newBufferedWriter(getProjectPath(), StandardCharsets.UTF_8)) {
|
||||
GSON.toJson(this, writer);
|
||||
saved = true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving project", e);
|
||||
@@ -143,29 +165,29 @@ public class JadxProject {
|
||||
}
|
||||
}
|
||||
|
||||
public static JadxProject from(Path path, JadxSettings settings) {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
|
||||
if (!lines.isEmpty()) {
|
||||
JadxProject project = GSON.fromJson(lines.get(0), JadxProject.class);
|
||||
project.settings = settings;
|
||||
project.setProjectPath(path);
|
||||
project.saved = true;
|
||||
return project;
|
||||
}
|
||||
public static JadxProject from(Path path) {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
JadxProject project = GSON.fromJson(reader, JadxProject.class);
|
||||
project.saved = true;
|
||||
project.setProjectPath(path);
|
||||
project.upgrade();
|
||||
return project;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading project", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void upgradeSettings(int fromVersion) {
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||
private void upgrade() {
|
||||
int fromVersion = projectVersion;
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_PROJECT_VERSION);
|
||||
if (fromVersion == 0) {
|
||||
fromVersion++;
|
||||
}
|
||||
projectVersion = CURRENT_SETTINGS_VERSION;
|
||||
if (fromVersion != CURRENT_PROJECT_VERSION) {
|
||||
throw new JadxRuntimeException("Project update failed");
|
||||
}
|
||||
projectVersion = CURRENT_PROJECT_VERSION;
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return Collections.unmodifiableList(recentProjects);
|
||||
}
|
||||
|
||||
public void addRecentProject(Path projectPath) {
|
||||
public void addRecentProject(@Nullable Path projectPath) {
|
||||
if (projectPath == null) {
|
||||
return;
|
||||
}
|
||||
recentProjects.remove(projectPath);
|
||||
recentProjects.add(0, projectPath);
|
||||
int count = recentProjects.size();
|
||||
|
||||
@@ -64,9 +64,6 @@ public class JadxSettingsAdapter {
|
||||
} else {
|
||||
settings.fixOnLoad();
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded settings: {}", makeString(settings));
|
||||
}
|
||||
return settings;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load settings. Settings will reset.\n Loaded json string: {}", jsonSettings, e);
|
||||
@@ -77,7 +74,6 @@ public class JadxSettingsAdapter {
|
||||
public static void store(JadxSettings settings) {
|
||||
try {
|
||||
String jsonSettings = makeString(settings);
|
||||
LOG.debug("Saving settings: {}", jsonSettings);
|
||||
PREFS.put(JADX_GUI_KEY, jsonSettings);
|
||||
PREFS.sync();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.utils.search.StringRef;
|
||||
@@ -13,14 +13,14 @@ public class CodeNode extends JNode {
|
||||
private final transient JClass jParent;
|
||||
private final transient StringRef line;
|
||||
private final transient int lineNum;
|
||||
private transient int pos = -1;
|
||||
private transient boolean precise;
|
||||
private transient int pos;
|
||||
|
||||
public CodeNode(JNode jNode, int lineNum, StringRef lineStr) {
|
||||
public CodeNode(JNode jNode, StringRef lineStr, int lineNum, int pos) {
|
||||
this.jNode = jNode;
|
||||
this.jParent = this.jNode.getJParent();
|
||||
this.line = lineStr;
|
||||
this.lineNum = lineNum;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,6 +79,11 @@ public class CodeNode extends JNode {
|
||||
return makeString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return jNode.getSyntaxName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -96,24 +101,8 @@ public class CodeNode extends JNode {
|
||||
return jNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public CodeNode setPos(int pos) {
|
||||
this.pos = pos;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeNode setPrecisePos(int pos) {
|
||||
this.pos = pos;
|
||||
if (pos > -1) {
|
||||
this.precise = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isPrecisePos() {
|
||||
return precise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package jadx.gui.treemodel;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -71,6 +73,11 @@ public class JField extends JNode {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return UiUtils.typeFormat(field.getName(), field.getType());
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Iterator;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -73,6 +75,11 @@ public class JMethod extends JNode {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
|
||||
|
||||
@@ -101,6 +101,14 @@ public abstract class JNode extends DefaultMutableTreeNode {
|
||||
return makeLongString();
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
JavaNode javaNode = getJavaNode();
|
||||
if (javaNode == null) {
|
||||
return -1;
|
||||
}
|
||||
return javaNode.getDefPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return makeString();
|
||||
|
||||
@@ -6,6 +6,8 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.JavaVariable;
|
||||
|
||||
public class JVariable extends JNode {
|
||||
private static final long serialVersionUID = -3002100457834453783L;
|
||||
|
||||
JClass cls;
|
||||
JavaVariable var;
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dialog;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class CommentDialog extends JDialog {
|
||||
private static final long serialVersionUID = -1865682124935757528L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentDialog.class);
|
||||
|
||||
public static void show(CodeArea codeArea, ICodeComment blankComment) {
|
||||
ICodeComment existComment = searchForExistComment(codeArea, blankComment);
|
||||
Dialog dialog;
|
||||
if (existComment != null) {
|
||||
dialog = new CommentDialog(codeArea, existComment, true);
|
||||
} else {
|
||||
dialog = new CommentDialog(codeArea, blankComment, false);
|
||||
}
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
private static void updateCommentsData(CodeArea codeArea, Consumer<List<ICodeComment>> updater) {
|
||||
try {
|
||||
JadxProject project = codeArea.getProject();
|
||||
JadxCodeData codeData = project.getCodeData();
|
||||
if (codeData == null) {
|
||||
codeData = new JadxCodeData();
|
||||
}
|
||||
List<ICodeComment> list = new ArrayList<>(codeData.getComments());
|
||||
updater.accept(list);
|
||||
Collections.sort(list);
|
||||
codeData.setComments(list);
|
||||
project.setCodeData(codeData);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Comment action failed", e);
|
||||
}
|
||||
try {
|
||||
// refresh code
|
||||
codeArea.refreshClass();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to reload code", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ICodeComment searchForExistComment(CodeArea codeArea, ICodeComment blankComment) {
|
||||
try {
|
||||
JadxProject project = codeArea.getProject();
|
||||
JadxCodeData codeData = project.getCodeData();
|
||||
if (codeData == null || codeData.getComments().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (ICodeComment comment : codeData.getComments()) {
|
||||
if (Objects.equals(comment.getNodeRef(), blankComment.getNodeRef())
|
||||
&& comment.getOffset() == blankComment.getOffset()
|
||||
&& comment.getAttachType() == blankComment.getAttachType()) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error searching for exists comment", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final transient CodeArea codeArea;
|
||||
private final transient ICodeComment comment;
|
||||
private final transient boolean updateComment;
|
||||
|
||||
private transient JTextArea commentArea;
|
||||
|
||||
public CommentDialog(CodeArea codeArea, ICodeComment comment, boolean updateComment) {
|
||||
super(codeArea.getMainWindow());
|
||||
this.codeArea = codeArea;
|
||||
this.comment = comment;
|
||||
this.updateComment = updateComment;
|
||||
initUI();
|
||||
}
|
||||
|
||||
private void apply() {
|
||||
String newCommentStr = commentArea.getText().trim();
|
||||
if (newCommentStr.isEmpty()) {
|
||||
if (updateComment) {
|
||||
remove();
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
ICodeComment newComment = new JadxCodeComment(comment.getNodeRef(),
|
||||
newCommentStr, comment.getOffset(), comment.getAttachType());
|
||||
if (updateComment) {
|
||||
updateCommentsData(codeArea, list -> {
|
||||
list.remove(comment);
|
||||
list.add(newComment);
|
||||
});
|
||||
} else {
|
||||
updateCommentsData(codeArea, list -> list.add(newComment));
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void remove() {
|
||||
updateCommentsData(codeArea, list -> list.removeIf(c -> c == comment));
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
commentArea = new JTextArea();
|
||||
TextStandardActions.attach(commentArea);
|
||||
commentArea.setEditable(true);
|
||||
commentArea.setFont(codeArea.getMainWindow().getSettings().getFont());
|
||||
commentArea.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
commentArea.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_ENTER:
|
||||
if (e.isShiftDown() || e.isControlDown()) {
|
||||
commentArea.append("\n");
|
||||
} else {
|
||||
apply();
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.VK_ESCAPE:
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (updateComment) {
|
||||
commentArea.setText(comment.getComment());
|
||||
}
|
||||
|
||||
JScrollPane textAreaScrollPane = new JScrollPane(commentArea);
|
||||
textAreaScrollPane.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JLabel commentLabel = new JLabel(NLS.str("comment_dialog.label"), SwingConstants.LEFT);
|
||||
JLabel usageLabel = new JLabel(NLS.str("comment_dialog.usage"), SwingConstants.LEFT);
|
||||
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
|
||||
mainPanel.add(commentLabel);
|
||||
mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
mainPanel.add(textAreaScrollPane);
|
||||
mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
mainPanel.add(usageLabel);
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(mainPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
|
||||
if (updateComment) {
|
||||
setTitle(NLS.str("comment_dialog.title.update"));
|
||||
} else {
|
||||
setTitle(NLS.str("comment_dialog.title.add"));
|
||||
}
|
||||
pack();
|
||||
if (!codeArea.getMainWindow().getSettings().loadWindowPos(this)) {
|
||||
setSize(800, 140);
|
||||
}
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setModalityType(ModalityType.APPLICATION_MODAL);
|
||||
UiUtils.addEscapeShortCutToDispose(this);
|
||||
}
|
||||
|
||||
protected JPanel initButtonsPanel() {
|
||||
JButton cancelButton = new JButton(NLS.str("common_dialog.cancel"));
|
||||
cancelButton.addActionListener(event -> cancel());
|
||||
|
||||
String applyStr = updateComment ? NLS.str("common_dialog.update") : NLS.str("common_dialog.add");
|
||||
JButton renameBtn = new JButton(applyStr);
|
||||
renameBtn.addActionListener(event -> apply());
|
||||
getRootPane().setDefaultButton(renameBtn);
|
||||
|
||||
JButton removeBtn;
|
||||
if (updateComment) {
|
||||
removeBtn = new JButton(NLS.str("common_dialog.remove"));
|
||||
removeBtn.addActionListener(event -> remove());
|
||||
} else {
|
||||
removeBtn = null;
|
||||
}
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
buttonPane.add(Box.createHorizontalGlue());
|
||||
buttonPane.add(renameBtn);
|
||||
if (removeBtn != null) {
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
buttonPane.add(removeBtn);
|
||||
}
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
buttonPane.add(cancelButton);
|
||||
return buttonPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
codeArea.getMainWindow().getSettings().saveWindowPos(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@@ -13,14 +18,27 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.fife.ui.rtextarea.SearchContext;
|
||||
import org.fife.ui.rtextarea.SearchEngine;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -31,7 +49,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.gui.jobs.BackgroundJob;
|
||||
import jadx.gui.jobs.BackgroundWorker;
|
||||
import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResSearchNode;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
@@ -112,16 +131,9 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
JumpPosition jmpPos;
|
||||
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
|
||||
if (node instanceof JResSearchNode) {
|
||||
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine())
|
||||
.setPrecise(((JResSearchNode) node).getPos());
|
||||
} else if (node instanceof CodeNode) {
|
||||
CodeNode codeNode = (CodeNode) node;
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), codeNode.getPos());
|
||||
if (codeNode.isPrecisePos()) {
|
||||
jmpPos.setPrecise(codeNode.getPos());
|
||||
}
|
||||
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine(), node.getPos());
|
||||
} else {
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine());
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), node.getPos());
|
||||
}
|
||||
tabbedPane.codeJump(jmpPos);
|
||||
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
|
||||
@@ -285,11 +297,11 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
int firstColMaxWidth = (int) (width * 0.5);
|
||||
int rowCount = getRowCount();
|
||||
int columnCount = getColumnCount();
|
||||
if (!model.isAddDescColumn()) {
|
||||
boolean addDescColumn = model.isAddDescColumn();
|
||||
if (!addDescColumn) {
|
||||
firstColMaxWidth = width;
|
||||
}
|
||||
Component nodeComp = null;
|
||||
Component codeComp = null;
|
||||
setRowHeight(10); // reset all rows height
|
||||
for (int col = 0; col < columnCount; col++) {
|
||||
int colWidth = 50;
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
@@ -298,11 +310,9 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
continue;
|
||||
}
|
||||
colWidth = Math.max(comp.getPreferredSize().width, colWidth);
|
||||
if (nodeComp == null && col == 0) {
|
||||
nodeComp = comp;
|
||||
}
|
||||
if (codeComp == null && col == 1) {
|
||||
codeComp = comp;
|
||||
int h = Math.max(getRowHeight(row), getHeight(comp));
|
||||
if (h > 1) {
|
||||
setRowHeight(row, h);
|
||||
}
|
||||
}
|
||||
colWidth += 10;
|
||||
@@ -314,10 +324,7 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
TableColumn column = columnModel.getColumn(col);
|
||||
column.setPreferredWidth(colWidth);
|
||||
}
|
||||
// setRowHeight(Math.max(nodeComp.getPreferredSize().height, codeComp.getPreferredSize().height +
|
||||
// 4));
|
||||
updateUI();
|
||||
setRowHeight(Math.max(getHeight(nodeComp), getHeight(codeComp) + 4));
|
||||
}
|
||||
|
||||
private int getHeight(@Nullable Component nodeComp) {
|
||||
@@ -487,20 +494,25 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
if (!node.hasDescString()) {
|
||||
return emptyLabel;
|
||||
}
|
||||
|
||||
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
textArea.setLayout(new GridLayout(1, 1));
|
||||
textArea.setEditable(false);
|
||||
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
|
||||
textArea.setText(" " + node.makeDescString());
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(textArea.getText().length() + 1);
|
||||
textArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
String descStr = node.makeDescString();
|
||||
textArea.setText(descStr);
|
||||
if (descStr.contains("\n")) {
|
||||
textArea.setRows(textArea.getLineCount());
|
||||
} else {
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(descStr.length() + 1);
|
||||
}
|
||||
if (highlightText != null) {
|
||||
SearchContext searchContext = new SearchContext(highlightText);
|
||||
searchContext.setMatchCase(!highlightTextCaseInsensitive);
|
||||
searchContext.setMarkAll(true);
|
||||
searchContext.setRegularExpression(highlightTextUseRegex);
|
||||
searchContext.setMarkAll(true);
|
||||
SearchEngine.markAll(textArea, searchContext);
|
||||
}
|
||||
textArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
return textArea;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,12 +106,15 @@ import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.JadxUpdate.IUpdateCallback;
|
||||
import jadx.gui.update.data.Release;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeUsageInfo;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.search.CommentsIndex;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
||||
import static jadx.gui.utils.FileUtils.fileNamesToPaths;
|
||||
@@ -137,6 +140,7 @@ public class MainWindow extends JFrame {
|
||||
private static final ImageIcon ICON_FLAT_PKG = UiUtils.openIcon("empty_logical_package_obj");
|
||||
private static final ImageIcon ICON_SEARCH = UiUtils.openIcon("wand");
|
||||
private static final ImageIcon ICON_FIND = UiUtils.openIcon("magnifier");
|
||||
private static final ImageIcon ICON_COMMENT_SEARCH = UiUtils.openIcon("table_edit");
|
||||
private static final ImageIcon ICON_BACK = UiUtils.openIcon("icon_back");
|
||||
private static final ImageIcon ICON_FORWARD = UiUtils.openIcon("icon_forward");
|
||||
private static final ImageIcon ICON_PREF = UiUtils.openIcon("wrench");
|
||||
@@ -219,7 +223,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private void handleSelectClassOption() {
|
||||
if (settings.getCmdSelectClass() != null) {
|
||||
JavaNode javaNode = wrapper.searchJavaClassByClassName(settings.getCmdSelectClass());
|
||||
JavaNode javaNode = wrapper.searchJavaClassByFullAlias(settings.getCmdSelectClass());
|
||||
if (javaNode == null) {
|
||||
javaNode = wrapper.searchJavaClassByOrigClassName(settings.getCmdSelectClass());
|
||||
}
|
||||
@@ -230,8 +234,7 @@ public class MainWindow extends JFrame {
|
||||
return;
|
||||
}
|
||||
JNode node = cacheObject.getNodeCache().makeFrom(javaNode);
|
||||
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(node)));
|
||||
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine(), JumpPosition.getDefPos(node)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +314,10 @@ public class MainWindow extends JFrame {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = new JadxProject(settings);
|
||||
update();
|
||||
cancelBackgroundJobs();
|
||||
clearTree();
|
||||
wrapper.close();
|
||||
updateProject(new JadxProject());
|
||||
}
|
||||
|
||||
private void saveProject() {
|
||||
@@ -356,6 +360,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
project.saveAs(path);
|
||||
settings.addRecentProject(path);
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -408,18 +413,18 @@ public class MainWindow extends JFrame {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = JadxProject.from(path, settings);
|
||||
if (project == null) {
|
||||
JadxProject jadxProject = JadxProject.from(path);
|
||||
if (jadxProject == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
NLS.str("msg.project_error"),
|
||||
NLS.str("msg.project_error_title"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
jadxProject = new JadxProject();
|
||||
}
|
||||
update();
|
||||
updateProject(jadxProject);
|
||||
settings.addRecentProject(path);
|
||||
List<Path> filePaths = project.getFilePaths();
|
||||
List<Path> filePaths = jadxProject.getFilePaths();
|
||||
if (filePaths == null) {
|
||||
clearTree();
|
||||
} else {
|
||||
@@ -427,6 +432,15 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProject(JadxProject jadxProject) {
|
||||
jadxProject.setSettings(settings);
|
||||
jadxProject.setMainWindow(this);
|
||||
this.project = jadxProject;
|
||||
this.wrapper.setProject(jadxProject);
|
||||
this.cacheObject.setCommentsIndex(new CommentsIndex(wrapper, cacheObject, jadxProject));
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
newProjectAction.setEnabled(!project.isInitial());
|
||||
saveProjectAction.setEnabled(!project.isSaved());
|
||||
@@ -444,17 +458,14 @@ public class MainWindow extends JFrame {
|
||||
|
||||
protected void resetCache() {
|
||||
cacheObject.reset();
|
||||
// TODO: decompilation freezes sometime with several threads
|
||||
this.cacheObject.setJRoot(treeRoot);
|
||||
this.cacheObject.setJadxSettings(settings);
|
||||
cacheObject.setJRoot(treeRoot);
|
||||
cacheObject.setJadxSettings(settings);
|
||||
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
}
|
||||
|
||||
public void resetIndex() {
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
cacheObject.setUsageInfo(new CodeUsageInfo(cacheObject.getNodeCache()));
|
||||
cacheObject.setTextIndex(new TextSearchIndex(this));
|
||||
}
|
||||
|
||||
synchronized void runBackgroundJobs() {
|
||||
@@ -471,7 +482,9 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
public synchronized void cancelBackgroundJobs() {
|
||||
backgroundExecutor.cancelAll();
|
||||
if (backgroundExecutor != null) {
|
||||
backgroundExecutor.cancelAll();
|
||||
}
|
||||
if (backgroundWorker != null) {
|
||||
backgroundWorker.stop();
|
||||
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
|
||||
@@ -518,8 +531,7 @@ public class MainWindow extends JFrame {
|
||||
continue;
|
||||
}
|
||||
JNode newNode = cacheObject.getNodeCache().makeFrom(newClass);
|
||||
tabbedPane.codeJump(new JumpPosition(newNode, position)
|
||||
.setPrecise(JumpPosition.getDefPos(newNode)));
|
||||
tabbedPane.codeJump(new JumpPosition(newNode, position, JumpPosition.getDefPos(newNode)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,12 +663,7 @@ public class MainWindow extends JFrame {
|
||||
} else if (obj instanceof ApkSignature) {
|
||||
tabbedPane.showSimpleNode((JNode) obj);
|
||||
} else if (obj instanceof JNode) {
|
||||
JNode node = (JNode) obj;
|
||||
JClass cls = node.getRootClass();
|
||||
if (cls != null) {
|
||||
tabbedPane.codeJump(new JumpPosition(cls, node.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(node)));
|
||||
}
|
||||
tabbedPane.codeJump(new JumpPosition((JNode) obj));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Content loading error", e);
|
||||
@@ -829,7 +836,7 @@ public class MainWindow extends JFrame {
|
||||
return;
|
||||
}
|
||||
}
|
||||
new SearchDialog(MainWindow.this, true).setVisible(true);
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.TEXT);
|
||||
}
|
||||
};
|
||||
textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search"));
|
||||
@@ -839,12 +846,22 @@ public class MainWindow extends JFrame {
|
||||
Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new SearchDialog(MainWindow.this, false).setVisible(true);
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS);
|
||||
}
|
||||
};
|
||||
clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search"));
|
||||
clsSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_N, UiUtils.ctrlButton()));
|
||||
|
||||
Action commentSearchAction = new AbstractAction(NLS.str("menu.comment_search"), ICON_COMMENT_SEARCH) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.COMMENT);
|
||||
}
|
||||
};
|
||||
commentSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.comment_search"));
|
||||
commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON,
|
||||
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
|
||||
|
||||
Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -925,6 +942,7 @@ public class MainWindow extends JFrame {
|
||||
nav.setMnemonic(KeyEvent.VK_N);
|
||||
nav.add(textSearchAction);
|
||||
nav.add(clsSearchAction);
|
||||
nav.add(commentSearchAction);
|
||||
nav.addSeparator();
|
||||
nav.add(backAction);
|
||||
nav.add(forwardAction);
|
||||
@@ -969,6 +987,7 @@ public class MainWindow extends JFrame {
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(textSearchAction);
|
||||
toolbar.add(clsSearchAction);
|
||||
toolbar.add(commentSearchAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(backAction);
|
||||
toolbar.add(forwardAction);
|
||||
@@ -1202,6 +1221,10 @@ public class MainWindow extends JFrame {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public JadxProject getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public TabbedPane getTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -23,7 +27,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.*;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.deobf.DeobfPresets;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
@@ -35,10 +43,19 @@ import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JVariable;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.CodePanel;
|
||||
import jadx.gui.utils.*;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class RenameDialog extends JDialog {
|
||||
private static final long serialVersionUID = -3269715644416902410L;
|
||||
@@ -235,17 +252,11 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private void refreshTabs(TabbedPane tabbedPane, Set<JClass> updatedClasses) {
|
||||
for (Map.Entry<JNode, ContentPanel> entry : tabbedPane.getOpenTabs().entrySet()) {
|
||||
ContentPanel contentPanel = entry.getValue();
|
||||
if (contentPanel instanceof ClassCodeContentPanel) {
|
||||
JNode node = entry.getKey();
|
||||
JClass rootClass = node.getRootClass();
|
||||
if (updatedClasses.contains(rootClass)) {
|
||||
refreshJClass(rootClass);
|
||||
ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel;
|
||||
CodePanel javaPanel = codePanel.getJavaCodePanel();
|
||||
javaPanel.refresh();
|
||||
tabbedPane.refresh(rootClass);
|
||||
}
|
||||
JClass rootClass = entry.getKey().getRootClass();
|
||||
if (updatedClasses.remove(rootClass)) {
|
||||
ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) entry.getValue();
|
||||
CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea();
|
||||
codeArea.refreshClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +265,7 @@ public class RenameDialog extends JDialog {
|
||||
protected JPanel initButtonsPanel() {
|
||||
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
|
||||
cancelButton.addActionListener(event -> dispose());
|
||||
JButton renameBtn = new JButton(NLS.str("popup.rename"));
|
||||
JButton renameBtn = new JButton(NLS.str("common_dialog.ok"));
|
||||
renameBtn.addActionListener(event -> rename());
|
||||
getRootPane().setDefaultButton(renameBtn);
|
||||
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
@@ -21,109 +34,195 @@ import io.reactivex.Flowable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
public class SearchDialog extends CommonSearchDialog {
|
||||
private static final long serialVersionUID = -5105405456969134105L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SearchDialog.class);
|
||||
private static final long serialVersionUID = -5105405456969134105L;
|
||||
private final boolean textSearch;
|
||||
|
||||
public static void search(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, Collections.emptySet());
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public static void searchInActiveTab(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, EnumSet.of(SearchOptions.ACTIVE_TAB));
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public static void searchText(MainWindow window, String text) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, SearchPreset.TEXT, Collections.emptySet());
|
||||
searchDialog.initSearchText = text;
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public enum SearchPreset {
|
||||
TEXT, CLASS, COMMENT
|
||||
}
|
||||
|
||||
public enum SearchOptions {
|
||||
CLASS,
|
||||
METHOD,
|
||||
FIELD,
|
||||
CODE,
|
||||
RESOURCE,
|
||||
COMMENT,
|
||||
|
||||
IGNORE_CASE,
|
||||
USE_REGEX,
|
||||
Resource
|
||||
ACTIVE_TAB
|
||||
}
|
||||
|
||||
private transient Set<SearchOptions> options;
|
||||
private final transient SearchPreset searchPreset;
|
||||
private final transient Set<SearchOptions> options;
|
||||
|
||||
private transient JTextField searchField;
|
||||
|
||||
private transient Disposable searchDisposable;
|
||||
private transient SearchEventEmitter searchEmitter;
|
||||
private transient String text = null;
|
||||
private transient ChangeListener activeTabListener;
|
||||
|
||||
public SearchDialog(MainWindow mainWindow, boolean textSearch) {
|
||||
private transient String initSearchText = null;
|
||||
|
||||
private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptions> additionalOptions) {
|
||||
super(mainWindow);
|
||||
this.textSearch = textSearch;
|
||||
if (textSearch) {
|
||||
Set<SearchOptions> lastSearchOptions = cache.getLastSearchOptions();
|
||||
if (!lastSearchOptions.isEmpty()) {
|
||||
this.options = lastSearchOptions;
|
||||
} else {
|
||||
this.options = EnumSet.of(SearchOptions.CODE, SearchOptions.IGNORE_CASE);
|
||||
}
|
||||
} else {
|
||||
this.options = EnumSet.of(SearchOptions.CLASS);
|
||||
}
|
||||
this.searchPreset = preset;
|
||||
this.options = buildOptions(preset);
|
||||
this.options.addAll(additionalOptions);
|
||||
|
||||
initUI();
|
||||
searchFieldSubscribe();
|
||||
registerInitOnOpen();
|
||||
loadWindowPos();
|
||||
registerActiveTabListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
||||
searchDisposable.dispose();
|
||||
}
|
||||
removeActiveTabListener();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private Set<SearchOptions> buildOptions(SearchPreset preset) {
|
||||
Set<SearchOptions> searchOptions = cache.getLastSearchOptions().get(preset);
|
||||
if (searchOptions == null) {
|
||||
searchOptions = new HashSet<>();
|
||||
}
|
||||
switch (preset) {
|
||||
case TEXT:
|
||||
if (searchOptions.isEmpty()) {
|
||||
searchOptions.add(SearchOptions.CODE);
|
||||
searchOptions.add(SearchOptions.IGNORE_CASE);
|
||||
}
|
||||
break;
|
||||
|
||||
case CLASS:
|
||||
searchOptions.add(SearchOptions.CLASS);
|
||||
break;
|
||||
|
||||
case COMMENT:
|
||||
searchOptions.add(SearchOptions.COMMENT);
|
||||
searchOptions.remove(SearchOptions.ACTIVE_TAB);
|
||||
break;
|
||||
}
|
||||
return searchOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openInit() {
|
||||
prepare();
|
||||
String lastSearch = cache.getLastSearch();
|
||||
if (lastSearch != null) {
|
||||
searchField.setText(lastSearch);
|
||||
String searchText = initSearchText != null ? initSearchText : cache.getLastSearch();
|
||||
if (searchText != null) {
|
||||
searchField.setText(searchText);
|
||||
searchField.selectAll();
|
||||
}
|
||||
searchField.requestFocus();
|
||||
|
||||
if (searchField.getText().isEmpty()) {
|
||||
checkIndex();
|
||||
}
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
|
||||
private TextSearchIndex checkIndex() {
|
||||
if (!cache.getIndexJob().isComplete()) {
|
||||
if (isFullIndexNeeded()) {
|
||||
prepare();
|
||||
}
|
||||
}
|
||||
return cache.getTextIndex();
|
||||
}
|
||||
|
||||
private boolean isFullIndexNeeded() {
|
||||
for (SearchOptions option : options) {
|
||||
switch (option) {
|
||||
case CLASS:
|
||||
case METHOD:
|
||||
case FIELD:
|
||||
// TODO: split indexes so full decompilation not needed for these
|
||||
return true;
|
||||
|
||||
case CODE:
|
||||
return true;
|
||||
|
||||
case RESOURCE:
|
||||
case COMMENT:
|
||||
// full index not needed
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
searchField = new JTextField();
|
||||
searchField.setAlignmentX(LEFT_ALIGNMENT);
|
||||
new TextStandardActions(searchField);
|
||||
searchFieldSubscribe();
|
||||
TextStandardActions.attach(searchField);
|
||||
|
||||
JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
|
||||
JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX);
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
findLabel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JCheckBox resChBox = makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.Resource);
|
||||
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
|
||||
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
|
||||
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
|
||||
JCheckBox codeChBox = makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE);
|
||||
JPanel searchFieldPanel = new JPanel();
|
||||
searchFieldPanel.setLayout(new BoxLayout(searchFieldPanel, BoxLayout.PAGE_AXIS));
|
||||
searchFieldPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
|
||||
searchFieldPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
searchFieldPanel.add(findLabel);
|
||||
searchFieldPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchFieldPanel.add(searchField);
|
||||
|
||||
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
|
||||
searchInPanel.add(resChBox);
|
||||
searchInPanel.add(clsChBox);
|
||||
searchInPanel.add(mthChBox);
|
||||
searchInPanel.add(fldChBox);
|
||||
searchInPanel.add(codeChBox);
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.RESOURCE));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.comments"), SearchOptions.COMMENT));
|
||||
|
||||
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
|
||||
searchOptions.add(caseChBox);
|
||||
searchOptions.add(regexChBox);
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.active_tab"), SearchOptions.ACTIVE_TAB));
|
||||
|
||||
Box box = Box.createHorizontalBox();
|
||||
box.setAlignmentX(LEFT_ALIGNMENT);
|
||||
box.add(searchInPanel);
|
||||
box.add(searchOptions);
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT));
|
||||
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
optionsPanel.add(searchInPanel);
|
||||
optionsPanel.add(searchOptions);
|
||||
|
||||
JPanel searchPane = new JPanel();
|
||||
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
|
||||
findLabel.setLabelFor(searchField);
|
||||
searchPane.add(findLabel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(searchField);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(box);
|
||||
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
searchPane.add(searchFieldPanel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(optionsPanel);
|
||||
|
||||
initCommon();
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
@@ -181,9 +280,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
||||
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
||||
searchDisposable = searchEvents
|
||||
.filter(text -> text.length() > 0)
|
||||
.subscribeOn(Schedulers.single())
|
||||
.doOnNext(r -> LOG.debug("search event: {}", r))
|
||||
.switchMap(text -> prepareSearch(text)
|
||||
.doOnError(e -> LOG.error("Error prepare search: {}", e.getMessage(), e))
|
||||
.subscribeOn(Schedulers.single())
|
||||
@@ -195,13 +292,19 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
|
||||
private Flowable<JNode> prepareSearch(String text) {
|
||||
if (text == null || text.isEmpty() || options.isEmpty()) {
|
||||
if (text == null || options.isEmpty()) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
|
||||
TextSearchIndex index = checkIndex();
|
||||
if (index == null) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
LOG.debug("search event: {}", text);
|
||||
showSearchState();
|
||||
return index.buildSearch(text, options);
|
||||
}
|
||||
@@ -214,9 +317,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
|
||||
|
||||
cache.setLastSearch(text);
|
||||
if (textSearch) {
|
||||
cache.setLastSearchOptions(options);
|
||||
}
|
||||
cache.getLastSearchOptions().put(searchPreset, options);
|
||||
|
||||
resultsModel.clear();
|
||||
resultsModel.addAll(results);
|
||||
@@ -266,14 +367,6 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
||||
searchDisposable.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
|
||||
final JCheckBox chBox = new JCheckBox(name);
|
||||
chBox.setAlignmentX(LEFT_ALIGNMENT);
|
||||
@@ -291,23 +384,32 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
@Override
|
||||
protected void loadFinished() {
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
searchField.setText(text);
|
||||
}
|
||||
resultsTable.setEnabled(true);
|
||||
searchField.setEnabled(true);
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadStart() {
|
||||
text = cache.getLastSearch(); // SearchDialog is opened by menu item, let loadFinished to set text
|
||||
cache.setLastSearch("");
|
||||
resultsTable.setEnabled(false);
|
||||
searchField.setEnabled(false);
|
||||
}
|
||||
|
||||
public static void searchText(MainWindow window, String text) {
|
||||
window.getCacheObject().setLastSearch(text);
|
||||
new SearchDialog(window, true).setVisible(true);
|
||||
private void registerActiveTabListener() {
|
||||
removeActiveTabListener();
|
||||
activeTabListener = e -> {
|
||||
if (options.contains(SearchOptions.ACTIVE_TAB)) {
|
||||
LOG.debug("active tab change event received");
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
};
|
||||
mainWindow.getTabbedPane().addChangeListener(activeTabListener);
|
||||
}
|
||||
|
||||
private void removeActiveTabListener() {
|
||||
if (activeTabListener != null) {
|
||||
mainWindow.getTabbedPane().removeChangeListener(activeTabListener);
|
||||
activeTabListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.awt.Component;
|
||||
import java.awt.KeyEventDispatcher;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -19,14 +27,17 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.codearea.*;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.utils.JumpManager;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
|
||||
public class TabbedPane extends JTabbedPane {
|
||||
private static final long serialVersionUID = -8833600618794570904L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TabbedPane.class);
|
||||
private static final long serialVersionUID = -8833600618794570904L;
|
||||
|
||||
private final transient MainWindow mainWindow;
|
||||
private final transient Map<JNode, ContentPanel> openTabs = new LinkedHashMap<>();
|
||||
@@ -148,42 +159,42 @@ public class TabbedPane extends JTabbedPane {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
private void showCode(final JumpPosition pos) {
|
||||
final AbstractCodeContentPanel contentPanel = (AbstractCodeContentPanel) getContentPanel(pos.getNode());
|
||||
private void showCode(final JumpPosition jumpPos) {
|
||||
JNode jumpNode = jumpPos.getNode();
|
||||
Objects.requireNonNull(jumpNode, "Null node in JumpPosition");
|
||||
|
||||
final AbstractCodeContentPanel contentPanel = (AbstractCodeContentPanel) getContentPanel(jumpNode);
|
||||
if (contentPanel == null) {
|
||||
return;
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setSelectedComponent(contentPanel);
|
||||
AbstractCodeArea codeArea = contentPanel.getCodeArea();
|
||||
if (pos.isPrecise()) {
|
||||
codeArea.scrollToPos(pos.getPos());
|
||||
int pos = jumpPos.getPos();
|
||||
if (pos > 0) {
|
||||
codeArea.scrollToPos(pos);
|
||||
} else {
|
||||
int line = pos.getLine();
|
||||
int line = jumpPos.getLine();
|
||||
if (line < 0) {
|
||||
try {
|
||||
line = 1 + codeArea.getLineOfOffset(-line);
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Can't get line for: {}", pos, e);
|
||||
line = pos.getNode().getLine();
|
||||
LOG.error("Can't get line for: {}", jumpPos, e);
|
||||
line = jumpNode.getLine();
|
||||
}
|
||||
}
|
||||
if (pos.getPos() < 0) {
|
||||
codeArea.scrollToLine(line);
|
||||
} else {
|
||||
int lineNum = Math.max(0, line - 1);
|
||||
try {
|
||||
int offs = codeArea.getLineStartOffset(lineNum);
|
||||
while (StringUtils.isWhite(codeArea.getText(offs, 1).charAt(0))) {
|
||||
offs += 1;
|
||||
}
|
||||
offs += pos.getPos();
|
||||
pos.setPrecise(offs);
|
||||
codeArea.scrollToPos(offs);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
codeArea.scrollToLine(line);
|
||||
int lineNum = Math.max(0, line - 1);
|
||||
try {
|
||||
int offs = codeArea.getLineStartOffset(lineNum);
|
||||
while (StringUtils.isWhite(codeArea.getText(offs, 1).charAt(0))) {
|
||||
offs += 1;
|
||||
}
|
||||
offs += pos;
|
||||
jumpPos.setPos(offs);
|
||||
codeArea.scrollToPos(offs);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
codeArea.scrollToLine(line);
|
||||
}
|
||||
}
|
||||
codeArea.requestFocus();
|
||||
@@ -216,7 +227,7 @@ public class TabbedPane extends JTabbedPane {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JumpPosition getCurrentPosition() {
|
||||
public JumpPosition getCurrentPosition() {
|
||||
ContentPanel selectedCodePanel = getSelectedCodePanel();
|
||||
if (selectedCodePanel instanceof AbstractCodeContentPanel) {
|
||||
return ((AbstractCodeContentPanel) selectedCodePanel).getCodeArea().getCurrentPosition();
|
||||
@@ -273,6 +284,7 @@ public class TabbedPane extends JTabbedPane {
|
||||
ContentPanel panel = openTabs.get(node);
|
||||
if (panel != null) {
|
||||
setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel));
|
||||
fireStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
import javax.swing.text.*;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Caret;
|
||||
import javax.swing.text.DefaultCaret;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rtextarea.SearchContext;
|
||||
@@ -19,9 +30,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.ContentPanel;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
@@ -66,21 +79,11 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
});
|
||||
popupMenu.add(wrapItem);
|
||||
popupMenu.addPopupMenuListener(new PopupMenuListener() {
|
||||
popupMenu.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
wrapItem.setState(getLineWrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Caret caret = getCaret();
|
||||
@@ -305,9 +308,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
|
||||
public JumpPosition getCurrentPosition() {
|
||||
JumpPosition jp = new JumpPosition(node, getCaretLineNumber() + 1);
|
||||
jp.setPrecise(getCaretPosition());
|
||||
return jp;
|
||||
return new JumpPosition(node, getCaretLineNumber() + 1, getCaretPosition());
|
||||
}
|
||||
|
||||
public String getLineText(int line) throws BadLocationException {
|
||||
int lineNum = line - 1;
|
||||
int startOffset = getLineStartOffset(lineNum);
|
||||
int endOffset = getLineEndOffset(lineNum);
|
||||
return getText(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -322,4 +330,12 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
public JNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JClass getJClass() {
|
||||
if (node instanceof JClass) {
|
||||
return (JClass) node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -86,5 +86,4 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
|
||||
public AbstractCodeArea getSmaliCodeArea() {
|
||||
return smaliCodePanel.getCodeArea();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
@@ -15,12 +18,17 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.ContentPanel;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.CaretPositionFix;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
/**
|
||||
* The {@link AbstractCodeArea} implementation used for displaying Java code and text based
|
||||
@@ -85,15 +93,33 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
FindUsageAction findUsage = new FindUsageAction(this);
|
||||
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this);
|
||||
RenameAction rename = new RenameAction(this);
|
||||
CommentAction comment = new CommentAction(this);
|
||||
|
||||
JPopupMenu popup = getPopupMenu();
|
||||
popup.addSeparator();
|
||||
popup.add(findUsage);
|
||||
popup.add(goToDeclaration);
|
||||
popup.add(comment);
|
||||
popup.add(new CommentSearchAction(this));
|
||||
popup.add(rename);
|
||||
popup.addPopupMenuListener(findUsage);
|
||||
popup.addPopupMenuListener(goToDeclaration);
|
||||
popup.addPopupMenuListener(comment);
|
||||
popup.addPopupMenuListener(rename);
|
||||
|
||||
// move caret on mouse right button click
|
||||
popup.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
CodeArea codeArea = CodeArea.this;
|
||||
if (codeArea.getSelectedText() == null) {
|
||||
int offset = UiUtils.getOffsetAtMousePosition(codeArea);
|
||||
if (offset >= 0) {
|
||||
codeArea.setCaretPosition(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int adjustOffsetForToken(@Nullable Token token) {
|
||||
@@ -145,8 +171,7 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
return null;
|
||||
}
|
||||
JNode jNode = convertJavaNode(foundNode);
|
||||
return new JumpPosition(jNode.getRootClass(), pos.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(jNode));
|
||||
return new JumpPosition(jNode.getRootClass(), pos.getLine(), JumpPosition.getDefPos(jNode));
|
||||
}
|
||||
|
||||
private JNode convertJavaNode(JavaNode javaNode) {
|
||||
@@ -189,11 +214,34 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void refreshClass() {
|
||||
if (node instanceof JClass) {
|
||||
JClass cls = (JClass) node;
|
||||
try {
|
||||
CaretPositionFix caretFix = new CaretPositionFix(this);
|
||||
caretFix.save();
|
||||
|
||||
cls.reload();
|
||||
IndexJob.refreshIndex(getMainWindow().getCacheObject(), cls.getCls());
|
||||
|
||||
ClassCodeContentPanel codeContentPanel = (ClassCodeContentPanel) this.contentPanel;
|
||||
codeContentPanel.getTabbedPane().refresh(cls);
|
||||
codeContentPanel.getJavaCodePanel().refresh(caretFix);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to reload class: {}", cls.getFullName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MainWindow getMainWindow() {
|
||||
return contentPanel.getTabbedPane().getMainWindow();
|
||||
}
|
||||
|
||||
private JadxDecompiler getDecompiler() {
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return getMainWindow().getWrapper().getDecompiler();
|
||||
}
|
||||
|
||||
public JadxProject getProject() {
|
||||
return getMainWindow().getProject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JPopupMenu.Separator;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.CaretPositionFix;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -27,11 +37,13 @@ import jadx.gui.utils.UiUtils;
|
||||
* A panel combining a {@link SearchBar and a scollable {@link CodeArea}
|
||||
*/
|
||||
public class CodePanel extends JPanel {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodePanel.class);
|
||||
private static final long serialVersionUID = 1117721869391885865L;
|
||||
|
||||
private final SearchBar searchBar;
|
||||
private final AbstractCodeArea codeArea;
|
||||
private final JScrollPane codeScrollPane;
|
||||
private LineNumbers lineNumbers;
|
||||
|
||||
public CodePanel(AbstractCodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
@@ -71,7 +83,7 @@ public class CodePanel extends JPanel {
|
||||
globalSearchItem.setAction(globalSearchAction);
|
||||
Separator separator = new Separator();
|
||||
JPopupMenu popupMenu = codeArea.getPopupMenu();
|
||||
popupMenu.addPopupMenuListener(new PopupMenuListener() {
|
||||
popupMenu.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
String preferText = codeArea.getSelectedText();
|
||||
@@ -90,18 +102,7 @@ public class CodePanel extends JPanel {
|
||||
popupMenu.remove(searchItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void loadSettings() {
|
||||
@@ -115,9 +116,13 @@ public class CodePanel extends JPanel {
|
||||
}
|
||||
|
||||
private void initLineNumbers() {
|
||||
LineNumbers numbers = new LineNumbers(codeArea);
|
||||
numbers.setUseSourceLines(isUseSourceLines());
|
||||
codeScrollPane.setRowHeaderView(numbers);
|
||||
initLineNumbers(isUseSourceLines());
|
||||
}
|
||||
|
||||
private void initLineNumbers(boolean useSourceLines) {
|
||||
lineNumbers = new LineNumbers(codeArea);
|
||||
lineNumbers.setUseSourceLines(useSourceLines);
|
||||
codeScrollPane.setRowHeaderView(lineNumbers);
|
||||
}
|
||||
|
||||
private boolean isUseSourceLines() {
|
||||
@@ -148,79 +153,15 @@ public class CodePanel extends JPanel {
|
||||
return codeScrollPane;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
int line = 0;
|
||||
int tokenIndex;
|
||||
int pos = codeArea.getCaretPosition();
|
||||
int lineCount = codeArea.getLineCount();
|
||||
try {
|
||||
// after rename the change of document is undetectable, so
|
||||
// use Token offset to calculate the new caret position.
|
||||
line = codeArea.getLineOfOffset(pos);
|
||||
Token token = codeArea.getTokenListForLine(line);
|
||||
tokenIndex = getTokenIndexByOffset(token, pos);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
tokenIndex = -1;
|
||||
}
|
||||
if (tokenIndex == -1) {
|
||||
refreshToViewport();
|
||||
return;
|
||||
}
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
int lineDiff = codeArea.getLineCount() - lineCount;
|
||||
if (lineDiff > 0) {
|
||||
lineDiff--;
|
||||
} else if (lineDiff < 0) {
|
||||
lineDiff++;
|
||||
}
|
||||
Token token = codeArea.getTokenListForLine(line + lineDiff);
|
||||
int newPos = getOffsetOfTokenByIndex(tokenIndex, token);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (newPos != -1) {
|
||||
codeArea.scrollToPos(newPos);
|
||||
} else {
|
||||
codeArea.scrollToLine(codeArea.getLineCount() - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshToViewport() {
|
||||
public void refresh(CaretPositionFix caretFix) {
|
||||
JViewport viewport = getCodeScrollPane().getViewport();
|
||||
Point viewPosition = viewport.getViewPosition();
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
initLineNumbers(lineNumbers.isUseSourceLines());
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
viewport.setViewPosition(viewPosition);
|
||||
caretFix.restore();
|
||||
});
|
||||
}
|
||||
|
||||
private int getTokenIndexByOffset(Token token, int offset) {
|
||||
if (token != null) {
|
||||
int index = 1;
|
||||
while (token.getEndOffset() < offset) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getOffsetOfTokenByIndex(int index, Token token) {
|
||||
if (token != null && index != -1) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return token.getOffset();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.annotations.CustomOffsetRef;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.CommentDialog;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentAction extends AbstractAction implements DefaultPopupMenuListener {
|
||||
private static final long serialVersionUID = 4753838562204629112L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
|
||||
private final CodeArea codeArea;
|
||||
private final JavaClass topCls;
|
||||
|
||||
private ICodeComment actionComment;
|
||||
|
||||
public CommentAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.add_comment") + " (;)");
|
||||
this.codeArea = codeArea;
|
||||
JNode topNode = codeArea.getNode();
|
||||
if (topNode instanceof JClass) {
|
||||
this.topCls = ((JClass) topNode).getCls();
|
||||
} else {
|
||||
this.topCls = null;
|
||||
}
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, 0);
|
||||
codeArea.getInputMap().put(key, "popup.add_comment");
|
||||
codeArea.getActionMap().put("popup.add_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int line = codeArea.getCaretLineNumber() + 1;
|
||||
ICodeComment codeComment = getCommentRef(line);
|
||||
showCommentDialog(codeComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
ICodeComment codeComment = getCommentRef(getMouseLine());
|
||||
setEnabled(codeComment != null);
|
||||
this.actionComment = codeComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showCommentDialog(this.actionComment);
|
||||
}
|
||||
|
||||
private void showCommentDialog(ICodeComment codeComment) {
|
||||
if (codeComment == null) {
|
||||
UiUtils.showMessageBox(codeArea.getMainWindow(), NLS.str("msg.cant_add_comment"));
|
||||
return;
|
||||
}
|
||||
CommentDialog.show(codeArea, codeComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if possible insert comment at current line.
|
||||
*
|
||||
* @return blank code comment object (comment string empty)
|
||||
*/
|
||||
@Nullable
|
||||
private ICodeComment getCommentRef(int line) {
|
||||
if (line == -1 || this.topCls == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CodeLinesInfo linesInfo = new CodeLinesInfo(topCls, true); // TODO: cache and update on class refresh
|
||||
// add comment if node definition at this line
|
||||
JavaNode nodeAtLine = linesInfo.getDefAtLine(line);
|
||||
if (nodeAtLine != null) {
|
||||
// at node definition -> add comment for it
|
||||
JadxNodeRef nodeRef = JadxNodeRef.forJavaNode(nodeAtLine);
|
||||
return new JadxCodeComment(nodeRef, "");
|
||||
}
|
||||
Object ann = topCls.getAnnotationAt(new CodePosition(line));
|
||||
if (ann == null) {
|
||||
// check if line with comment above node definition
|
||||
try {
|
||||
JavaNode defNode = linesInfo.getJavaNodeBelowLine(line);
|
||||
if (defNode != null) {
|
||||
String lineStr = codeArea.getLineText(line).trim();
|
||||
if (lineStr.startsWith("//")) {
|
||||
return new JadxCodeComment(JadxNodeRef.forJavaNode(defNode), "");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to check comment line: " + line, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to add method line comment
|
||||
JavaNode node = linesInfo.getJavaNodeByLine(line);
|
||||
if (node instanceof JavaMethod) {
|
||||
JadxNodeRef nodeRef = JadxNodeRef.forMth((JavaMethod) node);
|
||||
if (ann instanceof InsnCodeOffset) {
|
||||
int rawOffset = ((InsnCodeOffset) ann).getOffset();
|
||||
return new JadxCodeComment(nodeRef, "", rawOffset);
|
||||
}
|
||||
if (ann instanceof CustomOffsetRef) {
|
||||
CustomOffsetRef customRef = (CustomOffsetRef) ann;
|
||||
JadxCodeComment comment = new JadxCodeComment(nodeRef, "", customRef.getOffset());
|
||||
comment.setAttachType(customRef.getAttachType());
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to add comment at line: " + line, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getMouseLine() {
|
||||
int closestOffset = UiUtils.getOffsetAtMousePosition(codeArea);
|
||||
if (closestOffset == -1) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return codeArea.getLineOfOffset(closestOffset) + 1;
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Failed to get line by offset: {}", closestOffset);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentSearchAction extends AbstractAction {
|
||||
private static final long serialVersionUID = -3646341661734961590L;
|
||||
|
||||
private final CodeArea codeArea;
|
||||
|
||||
public CommentSearchAction(CodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton());
|
||||
putValue(Action.NAME, NLS.str("popup.search_comment") + " (Ctrl + ;)");
|
||||
|
||||
codeArea.getInputMap().put(key, "popup.search_comment");
|
||||
codeArea.getActionMap().put("popup.search_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
startSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
startSearch();
|
||||
}
|
||||
|
||||
private void startSearch() {
|
||||
SearchDialog.searchInActiveTab(codeArea.getMainWindow(), SearchDialog.SearchPreset.COMMENT);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public abstract class JNodeMenuAction<T> extends AbstractAction implements PopupMenuListener {
|
||||
private static final long serialVersionUID = -2600154727884853550L;
|
||||
|
||||
@@ -36,8 +38,7 @@ public abstract class JNodeMenuAction<T> extends AbstractAction implements Popup
|
||||
|
||||
@Nullable
|
||||
private T getNode() {
|
||||
Point pos = MouseInfo.getPointerInfo().getLocation();
|
||||
SwingUtilities.convertPointFromScreen(pos, codeArea);
|
||||
Point pos = UiUtils.getMousePosition(codeArea);
|
||||
Token token = codeArea.viewToToken(pos);
|
||||
int offset = codeArea.adjustOffsetForToken(token);
|
||||
return getNodeByOffset(offset);
|
||||
|
||||
@@ -107,6 +107,7 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
codeInfo = codeArea.getNode().getCodeInfo();
|
||||
@@ -265,6 +266,10 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUseSourceLines() {
|
||||
return useSourceLines;
|
||||
}
|
||||
|
||||
public void setUseSourceLines(boolean useSourceLines) {
|
||||
this.useSourceLines = useSourceLines;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -10,6 +11,7 @@ import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.search.CommentsIndex;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
public class CacheObject {
|
||||
@@ -19,9 +21,11 @@ public class CacheObject {
|
||||
|
||||
private TextSearchIndex textIndex;
|
||||
private CodeUsageInfo usageInfo;
|
||||
private CommentsIndex commentsIndex;
|
||||
private String lastSearch;
|
||||
private JNodeCache jNodeCache;
|
||||
private Set<SearchDialog.SearchOptions> lastSearchOptions;
|
||||
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
|
||||
private JRoot jRoot;
|
||||
private JadxSettings settings;
|
||||
|
||||
@@ -38,7 +42,7 @@ public class CacheObject {
|
||||
lastSearch = null;
|
||||
jNodeCache = new JNodeCache();
|
||||
usageInfo = null;
|
||||
lastSearchOptions = EnumSet.noneOf(SearchDialog.SearchOptions.class);
|
||||
lastSearchOptions = new HashMap<>();
|
||||
}
|
||||
|
||||
public DecompileJob getDecompileJob() {
|
||||
@@ -49,7 +53,6 @@ public class CacheObject {
|
||||
this.decompileJob = decompileJob;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextSearchIndex getTextIndex() {
|
||||
return textIndex;
|
||||
}
|
||||
@@ -76,6 +79,14 @@ public class CacheObject {
|
||||
this.usageInfo = usageInfo;
|
||||
}
|
||||
|
||||
public CommentsIndex getCommentsIndex() {
|
||||
return commentsIndex;
|
||||
}
|
||||
|
||||
public void setCommentsIndex(CommentsIndex commentsIndex) {
|
||||
this.commentsIndex = commentsIndex;
|
||||
}
|
||||
|
||||
public IndexJob getIndexJob() {
|
||||
return indexJob;
|
||||
}
|
||||
@@ -88,11 +99,7 @@ public class CacheObject {
|
||||
return jNodeCache;
|
||||
}
|
||||
|
||||
public void setLastSearchOptions(Set<SearchDialog.SearchOptions> lastSearchOptions) {
|
||||
this.lastSearchOptions = lastSearchOptions;
|
||||
}
|
||||
|
||||
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
|
||||
public Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> getLastSearchOptions() {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
|
||||
/**
|
||||
* After class refresh (rename, comment, etc) the change of document is undetectable.
|
||||
* So use Token index or offset in line to calculate the new caret position.
|
||||
*/
|
||||
public class CaretPositionFix {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CaretPositionFix.class);
|
||||
|
||||
private final AbstractCodeArea codeArea;
|
||||
|
||||
private int linesCount;
|
||||
private int line;
|
||||
private int lineOffset;
|
||||
private TokenInfo tokenInfo;
|
||||
|
||||
private int javaNodeLine = -1;
|
||||
private int codeRawOffset = -1;
|
||||
|
||||
public CaretPositionFix(AbstractCodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save caret position by anchor to token under caret
|
||||
*/
|
||||
public void save() {
|
||||
try {
|
||||
linesCount = codeArea.getLineCount();
|
||||
int pos = codeArea.getCaretPosition();
|
||||
line = codeArea.getLineOfOffset(pos);
|
||||
lineOffset = pos - codeArea.getLineStartOffset(line);
|
||||
|
||||
tokenInfo = getTokenInfoByOffset(codeArea.getTokenListForLine(line), pos);
|
||||
|
||||
JClass cls = codeArea.getJClass();
|
||||
if (cls != null) {
|
||||
JavaClass topParentClass = cls.getJavaNode().getTopParentClass();
|
||||
Object ann = topParentClass.getAnnotationAt(new CodePosition(line));
|
||||
if (ann instanceof ICodeRawOffset) {
|
||||
codeRawOffset = ((ICodeRawOffset) ann).getOffset();
|
||||
CodeLinesInfo codeLinesInfo = new CodeLinesInfo(topParentClass);
|
||||
JavaNode javaNodeAtLine = codeLinesInfo.getJavaNodeByLine(line);
|
||||
if (javaNodeAtLine != null) {
|
||||
javaNodeLine = javaNodeAtLine.getDecompiledLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.debug("Saved position data: line={}, lineOffset={}, token={}, codeRawOffset={}, javaNodeLine={}",
|
||||
line, lineOffset, tokenInfo, codeRawOffset, javaNodeLine);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to save caret position before refresh", e);
|
||||
line = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore caret position in refreshed code.
|
||||
* Expected to be called in UI thread.
|
||||
*/
|
||||
public void restore() {
|
||||
if (line == -1) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int newLine = getNewLine();
|
||||
int lineStartOffset = codeArea.getLineStartOffset(newLine);
|
||||
int lineEndOffset = codeArea.getLineEndOffset(newLine) - 1;
|
||||
int lineLength = lineEndOffset - lineStartOffset;
|
||||
Token token = codeArea.getTokenListForLine(newLine);
|
||||
int newPos = getOffsetFromTokenInfo(tokenInfo, token);
|
||||
if (newPos == -1) {
|
||||
// can't restore using token -> just restore by line offset
|
||||
if (lineOffset < lineLength) {
|
||||
newPos = lineStartOffset + lineOffset;
|
||||
} else {
|
||||
// line truncated -> set caret at line end
|
||||
newPos = lineEndOffset;
|
||||
}
|
||||
}
|
||||
codeArea.setCaretPosition(newPos);
|
||||
LOG.debug("Restored caret position: {}, line: {}", newPos, newLine);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to restore caret position", e);
|
||||
}
|
||||
}
|
||||
|
||||
private int getNewLine() {
|
||||
int newLinesCount = codeArea.getLineCount();
|
||||
if (linesCount == newLinesCount) {
|
||||
return line;
|
||||
}
|
||||
// lines count changes, try find line by raw offset
|
||||
if (javaNodeLine != -1) {
|
||||
JClass cls = codeArea.getJClass();
|
||||
if (cls != null) {
|
||||
JavaClass topParentClass = cls.getJavaNode().getTopParentClass();
|
||||
for (Map.Entry<CodePosition, Object> entry : topParentClass.getCodeAnnotations().entrySet()) {
|
||||
CodePosition pos = entry.getKey();
|
||||
if (pos.getOffset() == 0 && pos.getLine() >= javaNodeLine) {
|
||||
Object ann = entry.getValue();
|
||||
if (ann instanceof ICodeRawOffset && ((ICodeRawOffset) ann).getOffset() == codeRawOffset) {
|
||||
return pos.getLine() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback: assume lines added/removed before caret
|
||||
return line - (linesCount - newLinesCount);
|
||||
}
|
||||
|
||||
private TokenInfo getTokenInfoByOffset(Token token, int offset) {
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
int index = 1;
|
||||
while (token.getEndOffset() < offset) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return new TokenInfo(index, token.getType());
|
||||
}
|
||||
|
||||
private int getOffsetFromTokenInfo(TokenInfo tokenInfo, Token token) {
|
||||
if (tokenInfo == null || token == null) {
|
||||
return -1;
|
||||
}
|
||||
int index = tokenInfo.getIndex();
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < index; i++) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (token.getType() != tokenInfo.getType()) {
|
||||
return -1;
|
||||
}
|
||||
return token.getOffset();
|
||||
}
|
||||
|
||||
private static final class TokenInfo {
|
||||
private final int index;
|
||||
private final int type;
|
||||
|
||||
public TokenInfo(int index, int type) {
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Token{index=" + index + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
|
||||
@@ -12,18 +13,27 @@ public class CodeLinesInfo {
|
||||
private final NavigableMap<Integer, JavaNode> map = new TreeMap<>();
|
||||
|
||||
public CodeLinesInfo(JavaClass cls) {
|
||||
addClass(cls);
|
||||
addClass(cls, false);
|
||||
}
|
||||
|
||||
public void addClass(JavaClass cls) {
|
||||
public CodeLinesInfo(JavaClass cls, boolean includeFields) {
|
||||
addClass(cls, includeFields);
|
||||
}
|
||||
|
||||
private void addClass(JavaClass cls, boolean includeFields) {
|
||||
map.put(cls.getDecompiledLine(), cls);
|
||||
for (JavaClass innerCls : cls.getInnerClasses()) {
|
||||
map.put(innerCls.getDecompiledLine(), innerCls);
|
||||
addClass(innerCls);
|
||||
addClass(innerCls, includeFields);
|
||||
}
|
||||
for (JavaMethod mth : cls.getMethods()) {
|
||||
map.put(mth.getDecompiledLine(), mth);
|
||||
}
|
||||
if (includeFields) {
|
||||
for (JavaField field : cls.getFields()) {
|
||||
map.put(field.getDecompiledLine(), field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JavaNode getJavaNodeByLine(int line) {
|
||||
@@ -33,4 +43,16 @@ public class CodeLinesInfo {
|
||||
}
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public JavaNode getJavaNodeBelowLine(int line) {
|
||||
Map.Entry<Integer, JavaNode> entry = map.ceilingEntry(line);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public JavaNode getDefAtLine(int line) {
|
||||
return map.get(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class CodeUsageInfo {
|
||||
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
|
||||
StringRef codeLine = lines.get(line - 1);
|
||||
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
|
||||
CodeNode codeNode = new CodeNode(node, line, codeLine).setPrecisePos(codePosition.getUsagePosition());
|
||||
CodeNode codeNode = new CodeNode(node, codeLine, line, codePosition.getPos());
|
||||
usageInfo.addUsage(codeNode);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
public interface DefaultPopupMenuListener extends PopupMenuListener {
|
||||
@Override
|
||||
default void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void popupMenuCanceled(PopupMenuEvent e) {
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import jadx.api.*;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public class JumpPosition {
|
||||
private final JNode node;
|
||||
private final int line;
|
||||
private int pos;
|
||||
private boolean precise;
|
||||
|
||||
public JumpPosition(JNode node, int line) {
|
||||
this(node, line, -1);
|
||||
public JumpPosition(JNode jumpNode) {
|
||||
this(jumpNode.getRootClass(), jumpNode.getLine(), jumpNode.getPos());
|
||||
}
|
||||
|
||||
public JumpPosition(JNode jumpNode, CodePosition codePos) {
|
||||
this(jumpNode.getRootClass(), codePos.getLine(), codePos.getPos());
|
||||
}
|
||||
|
||||
public JumpPosition(JNode node, int line, int pos) {
|
||||
@@ -20,20 +23,14 @@ public class JumpPosition {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public boolean isPrecise() {
|
||||
return precise;
|
||||
}
|
||||
|
||||
public JumpPosition setPrecise(int pos) {
|
||||
this.pos = pos;
|
||||
this.precise = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void setPos(int pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public JNode getNode() {
|
||||
return node;
|
||||
}
|
||||
@@ -43,35 +40,11 @@ public class JumpPosition {
|
||||
}
|
||||
|
||||
public static int getDefPos(JNode node) {
|
||||
if (node instanceof JClass) {
|
||||
return ((JClass) node).getCls().getClassNode().getDefPosition();
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
if (javaNode == null) {
|
||||
return -1;
|
||||
}
|
||||
if (node instanceof JMethod) {
|
||||
return ((JMethod) node).getJavaMethod().getMethodNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JField) {
|
||||
return ((JField) node).getJavaField().getFieldNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JVariable) {
|
||||
return ((JVariable) node).getJavaVarNode().getVariableNode().getDefPosition();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node " + node);
|
||||
}
|
||||
|
||||
public static int getDefPos(JavaNode node) {
|
||||
if (node instanceof JavaClass) {
|
||||
return ((JavaClass) node).getClassNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaMethod) {
|
||||
return ((JavaMethod) node).getMethodNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaField) {
|
||||
return ((JavaField) node).getFieldNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaVariable) {
|
||||
return ((JavaVariable) node).getVariableNode().getDefPosition();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node " + node);
|
||||
return javaNode.getDefPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,6 @@ import javax.swing.*;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.undo.UndoManager;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TextStandardActions {
|
||||
|
||||
private final JTextComponent textComponent;
|
||||
@@ -27,6 +26,10 @@ public class TextStandardActions {
|
||||
private Action deleteAction;
|
||||
private Action selectAllAction;
|
||||
|
||||
public static void attach(JTextComponent textComponent) {
|
||||
new TextStandardActions(textComponent);
|
||||
}
|
||||
|
||||
public TextStandardActions(JTextComponent textComponent) {
|
||||
this.textComponent = textComponent;
|
||||
this.undoManager = new UndoManager();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Component;
|
||||
import java.awt.Image;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
@@ -10,7 +15,14 @@ import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.slf4j.Logger;
|
||||
@@ -19,6 +31,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
|
||||
public class UiUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UiUtils.class);
|
||||
@@ -190,7 +203,7 @@ public class UiUtils {
|
||||
@SuppressWarnings("deprecation")
|
||||
@MagicConstant(flagsFromClass = InputEvent.class)
|
||||
private static int getCtrlButton() {
|
||||
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
|
||||
if (SystemInfo.IS_MAC) {
|
||||
return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||
} else {
|
||||
return InputEvent.CTRL_DOWN_MASK;
|
||||
@@ -210,4 +223,26 @@ public class UiUtils {
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
dialog.getRootPane().registerKeyboardAction(e -> dialog.dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get closest offset at mouse position
|
||||
*
|
||||
* @return -1 on error
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static int getOffsetAtMousePosition(AbstractCodeArea codeArea) {
|
||||
try {
|
||||
Point mousePos = getMousePosition(codeArea);
|
||||
return codeArea.viewToModel(mousePos);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get offset at mouse position", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static Point getMousePosition(Component comp) {
|
||||
Point pos = MouseInfo.getPointerInfo().getLocation();
|
||||
SwingUtilities.convertPointFromScreen(pos, comp);
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package jadx.gui.utils.layout;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Insets;
|
||||
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* FlowLayout subclass that fully supports wrapping of components.
|
||||
*/
|
||||
public class WrapLayout extends FlowLayout {
|
||||
private static final long serialVersionUID = 6109752116520941346L;
|
||||
|
||||
private Dimension preferredLayoutSize;
|
||||
|
||||
/**
|
||||
* Constructs a new <code>WrapLayout</code> with a left
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
*/
|
||||
public WrapLayout() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>FlowLayout</code> with the specified
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>WrapLayout</code>, <code>WrapLayout</code>,
|
||||
* or <code>WrapLayout</code>.
|
||||
*
|
||||
* @param align the alignment value
|
||||
*/
|
||||
public WrapLayout(int align) {
|
||||
super(align);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new flow layout manager with the indicated alignment
|
||||
* and the indicated horizontal and vertical gaps.
|
||||
* <p>
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>WrapLayout</code>, <code>WrapLayout</code>,
|
||||
* or <code>WrapLayout</code>.
|
||||
*
|
||||
* @param align the alignment value
|
||||
* @param hgap the horizontal gap between components
|
||||
* @param vgap the vertical gap between components
|
||||
*/
|
||||
public WrapLayout(int align, int hgap, int vgap) {
|
||||
super(align, hgap, vgap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred dimensions for this layout given the
|
||||
* <i>visible</i> components in the specified target container.
|
||||
*
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the preferred dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container target) {
|
||||
return layoutSize(target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum dimensions needed to layout the <i>visible</i>
|
||||
* components contained in the specified target container.
|
||||
*
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the minimum dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension minimumLayoutSize(Container target) {
|
||||
Dimension minimum = layoutSize(target, false);
|
||||
minimum.width -= (getHgap() + 1);
|
||||
return minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum or preferred dimension needed to layout the target
|
||||
* container.
|
||||
*
|
||||
* @param target target to get layout size for
|
||||
* @param preferred should preferred size be calculated
|
||||
* @return the dimension to layout the target container
|
||||
*/
|
||||
private Dimension layoutSize(Container target, boolean preferred) {
|
||||
synchronized (target.getTreeLock()) {
|
||||
// Each row must fit with the width allocated to the containter.
|
||||
// When the container width = 0, the preferred width of the container
|
||||
// has not yet been calculated so lets ask for the maximum.
|
||||
|
||||
int targetWidth = target.getSize().width;
|
||||
Container container = target;
|
||||
|
||||
while (container.getSize().width == 0 && container.getParent() != null) {
|
||||
container = container.getParent();
|
||||
}
|
||||
|
||||
targetWidth = container.getSize().width;
|
||||
|
||||
if (targetWidth == 0) {
|
||||
targetWidth = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
int hgap = getHgap();
|
||||
int vgap = getVgap();
|
||||
Insets insets = target.getInsets();
|
||||
int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
|
||||
int maxWidth = targetWidth - horizontalInsetsAndGap;
|
||||
|
||||
// Fit components into the allowed width
|
||||
|
||||
Dimension dim = new Dimension(0, 0);
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
int nmembers = target.getComponentCount();
|
||||
|
||||
for (int i = 0; i < nmembers; i++) {
|
||||
Component m = target.getComponent(i);
|
||||
|
||||
if (m.isVisible()) {
|
||||
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
|
||||
|
||||
// Can't add the component to current row. Start a new row.
|
||||
|
||||
if (rowWidth + d.width > maxWidth) {
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
rowWidth = 0;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
// Add a horizontal gap for all components after the first
|
||||
|
||||
if (rowWidth != 0) {
|
||||
rowWidth += hgap;
|
||||
}
|
||||
|
||||
rowWidth += d.width;
|
||||
rowHeight = Math.max(rowHeight, d.height);
|
||||
}
|
||||
}
|
||||
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
|
||||
dim.width += horizontalInsetsAndGap;
|
||||
dim.height += insets.top + insets.bottom + vgap * 2;
|
||||
|
||||
// When using a scroll pane or the DecoratedLookAndFeel we need to
|
||||
// make sure the preferred size is less than the size of the
|
||||
// target containter so shrinking the container size works
|
||||
// correctly. Removing the horizontal gap is an easy way to do this.
|
||||
|
||||
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
|
||||
|
||||
if (scrollPane != null && target.isValid()) {
|
||||
dim.width -= (hgap + 1);
|
||||
}
|
||||
|
||||
return dim;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A new row has been completed. Use the dimensions of this row
|
||||
* to update the preferred size for the container.
|
||||
* @param dim update the width and height when appropriate
|
||||
* @param rowWidth the width of the row to add
|
||||
* @param rowHeight the height of the row to add
|
||||
*/
|
||||
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
|
||||
dim.width = Math.max(dim.width, rowWidth);
|
||||
|
||||
if (dim.height > 0) {
|
||||
dim.height += getVgap();
|
||||
}
|
||||
|
||||
dim.height += rowHeight;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import io.reactivex.Flowable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class CodeIndex {
|
||||
@@ -32,13 +33,15 @@ public class CodeIndex {
|
||||
}
|
||||
|
||||
public Flowable<CodeNode> search(final SearchSettings searchSettings) {
|
||||
JClass activeCls = searchSettings.getActiveCls();
|
||||
return Flowable.create(emitter -> {
|
||||
LOG.debug("Code search started: {} ...", searchSettings.getSearchString());
|
||||
for (CodeNode node : values) {
|
||||
int pos = searchSettings.find(node.getLineStr());
|
||||
node.setPos(pos);
|
||||
if (pos > -1) {
|
||||
emitter.onNext(node);
|
||||
if (activeCls == null || node.getRootClass().equals(activeCls)) {
|
||||
int pos = searchSettings.find(node.getLineStr());
|
||||
if (pos > -1) {
|
||||
emitter.onNext(node);
|
||||
}
|
||||
}
|
||||
if (emitter.isCancelled()) {
|
||||
LOG.debug("Code search canceled: {}", searchSettings.getSearchString());
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
|
||||
public class CommentsIndex {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentsIndex.class);
|
||||
private final JadxWrapper wrapper;
|
||||
private final CacheObject cacheObject;
|
||||
private final JadxProject project;
|
||||
|
||||
public CommentsIndex(JadxWrapper wrapper, CacheObject cacheObject, JadxProject project) {
|
||||
this.wrapper = wrapper;
|
||||
this.cacheObject = cacheObject;
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JNode isMatch(SearchSettings searchSettings, ICodeComment comment) {
|
||||
boolean all = searchSettings.getSearchString().isEmpty();
|
||||
if (all || searchSettings.isMatch(comment.getComment())) {
|
||||
JNode refNode = getRefNode(comment);
|
||||
if (refNode != null) {
|
||||
JClass activeCls = searchSettings.getActiveCls();
|
||||
if (activeCls == null || Objects.equals(activeCls, refNode.getRootClass())) {
|
||||
return getCommentNode(comment, refNode);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Failed to get ref node for comment: {}", comment);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Flowable<JNode> search(SearchSettings searchSettings) {
|
||||
List<ICodeComment> comments = project.getCodeData().getComments();
|
||||
if (comments == null || comments.isEmpty()) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
LOG.debug("Total comments count: {}", comments.size());
|
||||
return Flowable.create(emitter -> {
|
||||
for (ICodeComment comment : comments) {
|
||||
JNode foundNode = isMatch(searchSettings, comment);
|
||||
if (foundNode != null) {
|
||||
emitter.onNext(foundNode);
|
||||
}
|
||||
if (emitter.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
emitter.onComplete();
|
||||
}, BackpressureStrategy.BUFFER);
|
||||
}
|
||||
|
||||
private @NotNull RefCommentNode getCommentNode(ICodeComment comment, JNode refNode) {
|
||||
IJavaNodeRef nodeRef = comment.getNodeRef();
|
||||
if (nodeRef.getType() == IJavaNodeRef.RefType.METHOD && comment.getOffset() > 0) {
|
||||
return new CodeCommentNode((JMethod) refNode, comment);
|
||||
}
|
||||
return new RefCommentNode(refNode, comment.getComment());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JNode getRefNode(ICodeComment comment) {
|
||||
IJavaNodeRef nodeRef = comment.getNodeRef();
|
||||
JavaClass javaClass = wrapper.searchJavaClassByOrigClassName(nodeRef.getDeclaringClass());
|
||||
if (javaClass == null) {
|
||||
return null;
|
||||
}
|
||||
JNodeCache nodeCache = cacheObject.getNodeCache();
|
||||
switch (nodeRef.getType()) {
|
||||
case CLASS:
|
||||
return nodeCache.makeFrom(javaClass);
|
||||
|
||||
case FIELD:
|
||||
for (JavaField field : javaClass.getFields()) {
|
||||
if (field.getFieldNode().getFieldInfo().getShortId().equals(nodeRef.getShortId())) {
|
||||
return nodeCache.makeFrom(field);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case METHOD:
|
||||
for (JavaMethod mth : javaClass.getMethods()) {
|
||||
if (mth.getMethodNode().getMethodInfo().getShortId().equals(nodeRef.getShortId())) {
|
||||
return nodeCache.makeFrom(mth);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class CodeCommentNode extends RefCommentNode {
|
||||
private static final long serialVersionUID = 6208192811789176886L;
|
||||
|
||||
private final int offset;
|
||||
private JumpPosition pos;
|
||||
|
||||
public CodeCommentNode(JMethod node, ICodeComment comment) {
|
||||
super(node, comment.getComment());
|
||||
this.offset = comment.getOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return getCachedPos().getLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPos() {
|
||||
return getCachedPos().getPos();
|
||||
}
|
||||
|
||||
private synchronized JumpPosition getCachedPos() {
|
||||
if (pos == null) {
|
||||
pos = getJumpPos();
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy decompilation to get comment location if requested
|
||||
*/
|
||||
private JumpPosition getJumpPos() {
|
||||
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
|
||||
int methodLine = javaMethod.getDecompiledLine();
|
||||
ICodeInfo codeInfo = javaMethod.getTopParentClass().getCodeInfo();
|
||||
for (Map.Entry<CodePosition, Object> entry : codeInfo.getAnnotations().entrySet()) {
|
||||
CodePosition codePos = entry.getKey();
|
||||
if (codePos.getOffset() == 0 && codePos.getLine() > methodLine) {
|
||||
Object ann = entry.getValue();
|
||||
if (ann instanceof ICodeRawOffset) {
|
||||
if (((ICodeRawOffset) ann).getOffset() == offset) {
|
||||
return new JumpPosition(node, codePos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new JumpPosition(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RefCommentNode extends JNode {
|
||||
private static final long serialVersionUID = 3887992236082515752L;
|
||||
|
||||
protected final JNode node;
|
||||
protected final String comment;
|
||||
|
||||
public RefCommentNode(JNode node, String comment) {
|
||||
this.node = node;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getRootClass() {
|
||||
return node.getRootClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaNode getJavaNode() {
|
||||
return node.getJavaNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return node.getJParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return node.getIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return node.getSyntaxName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return node.makeString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return node.getLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeDescString() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDescString() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,19 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
|
||||
public class SearchSettings {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SearchSettings.class);
|
||||
|
||||
private final String searchString;
|
||||
|
||||
private final boolean useRegex;
|
||||
|
||||
private final boolean ignoreCase;
|
||||
|
||||
private Pattern regexPattern;
|
||||
private JClass activeCls;
|
||||
|
||||
private Pattern regexPattern;
|
||||
private int startPos = 0;
|
||||
|
||||
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex) {
|
||||
@@ -27,109 +28,81 @@ public class SearchSettings {
|
||||
this.ignoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return whether Regex search should be done
|
||||
*/
|
||||
public boolean isUseRegex() {
|
||||
return this.useRegex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return whether case will be ignored
|
||||
*/
|
||||
public boolean isIgnoreCase() {
|
||||
return this.ignoreCase;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return search string
|
||||
*/
|
||||
public String getSearchString() {
|
||||
return this.searchString;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the starting index
|
||||
*/
|
||||
public int getStartPos() {
|
||||
return this.startPos;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set Starting Index
|
||||
*/
|
||||
public void setStartPos(int startPos) {
|
||||
this.startPos = startPos;
|
||||
}
|
||||
|
||||
/*
|
||||
* get Regex Pattern
|
||||
*/
|
||||
public Pattern getPattern() {
|
||||
return this.regexPattern;
|
||||
}
|
||||
|
||||
/*
|
||||
* Runs Pattern.compile if using Regex. If not using Regex return true
|
||||
* return false is invalid Regex
|
||||
*/
|
||||
public boolean preCompile() {
|
||||
try {
|
||||
if (this.useRegex && this.ignoreCase) {
|
||||
this.regexPattern = Pattern.compile(this.searchString, Pattern.CASE_INSENSITIVE);
|
||||
} else if (this.useRegex) {
|
||||
this.regexPattern = Pattern.compile(this.searchString);
|
||||
if (useRegex) {
|
||||
try {
|
||||
int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0;
|
||||
this.regexPattern = Pattern.compile(searchString, flags);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Invalid Regex: {}", this.searchString, e);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Invalid Regex: {}", this.searchString);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if searchArea matches the searched string found in searchSettings
|
||||
*/
|
||||
public boolean isMatch(StringRef searchArea) {
|
||||
return isMatch(searchArea.toString());
|
||||
return find(searchArea) != -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if searchArea matches the searched string found in searchSettings
|
||||
*/
|
||||
public boolean isMatch(String searchArea) {
|
||||
return find(searchArea) != -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the position within searchArea that the searched string found in searchSettings was
|
||||
* identified.
|
||||
* returns -1 if a match is not found
|
||||
*/
|
||||
public int find(StringRef searchArea) {
|
||||
return find(searchArea.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the position within searchArea that the searched string found in searchSettings was
|
||||
* identified.
|
||||
* returns -1 if a match is not found
|
||||
*/
|
||||
public int find(String searchArea) {
|
||||
int pos;
|
||||
if (this.useRegex) {
|
||||
Matcher matcher = this.regexPattern.matcher(searchArea);
|
||||
if (matcher.find(this.startPos)) {
|
||||
pos = matcher.start();
|
||||
} else {
|
||||
pos = -1;
|
||||
}
|
||||
} else if (this.ignoreCase) {
|
||||
pos = StringUtils.indexOfIgnoreCase(searchArea, this.searchString, this.startPos);
|
||||
} else {
|
||||
pos = searchArea.indexOf(this.searchString, this.startPos);
|
||||
if (useRegex) {
|
||||
return findWithRegex(searchArea.toString());
|
||||
}
|
||||
return pos;
|
||||
return searchArea.indexOf(this.searchString, this.startPos, this.ignoreCase);
|
||||
}
|
||||
|
||||
public int find(String searchArea) {
|
||||
if (useRegex) {
|
||||
return findWithRegex(searchArea);
|
||||
}
|
||||
if (ignoreCase) {
|
||||
return StringUtils.indexOfIgnoreCase(searchArea, searchString, startPos);
|
||||
}
|
||||
return searchArea.indexOf(searchString, startPos);
|
||||
}
|
||||
|
||||
private int findWithRegex(String searchArea) {
|
||||
Matcher matcher = regexPattern.matcher(searchArea);
|
||||
if (matcher.find(startPos)) {
|
||||
return matcher.start();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public JClass getActiveCls() {
|
||||
return activeCls;
|
||||
}
|
||||
|
||||
public void setActiveCls(JClass activeCls) {
|
||||
this.activeCls = activeCls;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public class SimpleIndex {
|
||||
@@ -20,15 +22,23 @@ public class SimpleIndex {
|
||||
data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls));
|
||||
}
|
||||
|
||||
private boolean isMatched(String str, SearchSettings searchSettings) {
|
||||
return searchSettings.isMatch(str);
|
||||
private boolean isMatched(String str, JNode node, SearchSettings searchSettings) {
|
||||
if (searchSettings.isMatch(str)) {
|
||||
JClass activeCls = searchSettings.getActiveCls();
|
||||
if (activeCls == null) {
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(node.getRootClass(), activeCls);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Flowable<JNode> search(final SearchSettings searchSettings) {
|
||||
return Flowable.create(emitter -> {
|
||||
for (Map.Entry<JNode, String> entry : data.entrySet()) {
|
||||
if (isMatched(entry.getValue(), searchSettings)) {
|
||||
emitter.onNext(entry.getKey());
|
||||
JNode node = entry.getKey();
|
||||
if (isMatched(entry.getValue(), node, searchSettings)) {
|
||||
emitter.onNext(node);
|
||||
}
|
||||
if (emitter.isCancelled()) {
|
||||
return;
|
||||
|
||||
@@ -18,24 +18,30 @@ import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.ACTIVE_TAB;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.CLASS;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.CODE;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.COMMENT;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.Resource;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.RESOURCE;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX;
|
||||
|
||||
public class TextSearchIndex {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
|
||||
|
||||
private final CacheObject cache;
|
||||
private final MainWindow mainWindow;
|
||||
private final JNodeCache nodeCache;
|
||||
|
||||
private final SimpleIndex clsNamesIndex;
|
||||
@@ -46,7 +52,9 @@ public class TextSearchIndex {
|
||||
|
||||
private final List<JavaClass> skippedClasses = new ArrayList<>();
|
||||
|
||||
public TextSearchIndex(CacheObject cache) {
|
||||
public TextSearchIndex(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.cache = mainWindow.getCacheObject();
|
||||
this.nodeCache = cache.getNodeCache();
|
||||
this.resIndex = new ResourceIndex(cache);
|
||||
this.clsNamesIndex = new SimpleIndex();
|
||||
@@ -81,8 +89,9 @@ public class TextSearchIndex {
|
||||
}
|
||||
int lineNum = i + 1;
|
||||
JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
|
||||
JNode nodeAtLine = nodeCache.makeFrom(node == null ? cls : node);
|
||||
codeIndex.put(new CodeNode(nodeAtLine, lineNum, line));
|
||||
JavaNode javaNode = node == null ? cls : node;
|
||||
JNode nodeAtLine = nodeCache.makeFrom(javaNode);
|
||||
codeIndex.put(new CodeNode(nodeAtLine, line, lineNum, javaNode.getDefPos()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to index class: {}", cls, e);
|
||||
@@ -108,10 +117,29 @@ public class TextSearchIndex {
|
||||
Flowable<JNode> result = Flowable.empty();
|
||||
|
||||
SearchSettings searchSettings = new SearchSettings(text, options.contains(IGNORE_CASE), options.contains(USE_REGEX));
|
||||
if (options.contains(ACTIVE_TAB)) {
|
||||
JumpPosition activeNode = mainWindow.getTabbedPane().getCurrentPosition();
|
||||
if (activeNode != null) {
|
||||
searchSettings.setActiveCls(activeNode.getNode().getRootClass());
|
||||
}
|
||||
if (searchSettings.getActiveCls() == null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!searchSettings.preCompile()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (options.contains(COMMENT)) {
|
||||
CommentsIndex commentsIndex = cache.getCommentsIndex();
|
||||
result = Flowable.concat(result, commentsIndex.search(searchSettings));
|
||||
if (text.isEmpty()) {
|
||||
// return all comments on empty search string
|
||||
// other searches don't support empty string, so return immediately
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.contains(CLASS)) {
|
||||
result = Flowable.concat(result, clsNamesIndex.search(searchSettings));
|
||||
}
|
||||
@@ -129,7 +157,7 @@ public class TextSearchIndex {
|
||||
result = Flowable.concat(result, searchInSkippedClasses(searchSettings));
|
||||
}
|
||||
}
|
||||
if (options.contains(Resource)) {
|
||||
if (options.contains(RESOURCE)) {
|
||||
result = Flowable.concat(result, resIndex.search(searchSettings));
|
||||
}
|
||||
return result;
|
||||
@@ -168,7 +196,7 @@ public class TextSearchIndex {
|
||||
int lineStart = 1 + code.lastIndexOf(ICodeWriter.NL, pos);
|
||||
int lineEnd = code.indexOf(ICodeWriter.NL, pos + searchSettings.getSearchString().length());
|
||||
StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
|
||||
emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()).setPos(pos));
|
||||
emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), line.trim(), -1, pos));
|
||||
return lineEnd;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ menu.heapUsageBar=Speicherverbrauchsleiste anzeigen
|
||||
menu.navigation=Navigation
|
||||
menu.text_search=Textsuche
|
||||
menu.class_search=Klassen-Suche
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=Tools
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log-Anzeige
|
||||
@@ -61,6 +62,12 @@ message.indexingClassesSkipped=<html>Jadx hat nur noch wenig Speicherplatz. Dahe
|
||||
|
||||
heapUsage.text=JADX-Speicherauslastung: %.2f GB von %.2f GB
|
||||
|
||||
#common_dialog.ok=
|
||||
#common_dialog.cancel=
|
||||
#common_dialog.add=
|
||||
#common_dialog.update=
|
||||
#common_dialog.remove=
|
||||
|
||||
search_dialog.open=Öffnen
|
||||
search_dialog.cancel=Beenden
|
||||
search_dialog.open_by_name=Nach Text suchen:
|
||||
@@ -77,6 +84,8 @@ search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d
|
||||
search_dialog.col_node=Knoten
|
||||
search_dialog.col_code=Code
|
||||
search_dialog.regex=Regex
|
||||
#search_dialog.active_tab=Active tab only
|
||||
#search_dialog.comments=Comments
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
@@ -84,6 +93,11 @@ search_dialog.regex=Regex
|
||||
usage_dialog.title=Verwendungssuche
|
||||
usage_dialog.label=Verwendung für:
|
||||
|
||||
#comment_dialog.title.add=Add code comment
|
||||
#comment_dialog.title.update=Update code comment
|
||||
#comment_dialog.label=Comment:
|
||||
#comment_dialog.usage=
|
||||
|
||||
log_viewer.title=Log-Anzeige
|
||||
log_viewer.log_level=Log-Level:
|
||||
|
||||
@@ -155,6 +169,7 @@ msg.rename_disabled_deobfuscation_disabled=Bitte aktivieren Sie die Umbenennung
|
||||
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
|
||||
#msg.rename_node_disabled=
|
||||
#msg.rename_node_failed=
|
||||
#msg.cant_add_comment=Can't add comment here
|
||||
|
||||
#popup.bytecode_col=
|
||||
#popup.line_wrap=
|
||||
@@ -168,6 +183,8 @@ popup.select_all=Alle auswählen
|
||||
popup.find_usage=Verwendung suchen
|
||||
popup.go_to_declaration=Zur Erklärung gehen
|
||||
popup.exclude=Ausschließen
|
||||
#popup.add_comment=Comment
|
||||
#popup.search_comment=Search comments
|
||||
popup.rename=Umbennen
|
||||
#popup.search=
|
||||
#popup.search_global=
|
||||
|
||||
@@ -11,6 +11,7 @@ menu.heapUsageBar=Show memory usage bar
|
||||
menu.navigation=Navigation
|
||||
menu.text_search=Text search
|
||||
menu.class_search=Class search
|
||||
menu.comment_search=Comment search
|
||||
menu.tools=Tools
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
@@ -61,6 +62,12 @@ message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d
|
||||
|
||||
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
|
||||
|
||||
common_dialog.ok=Ok
|
||||
common_dialog.cancel=Cancel
|
||||
common_dialog.add=Add
|
||||
common_dialog.update=Update
|
||||
common_dialog.remove=Remove
|
||||
|
||||
search_dialog.open=Open
|
||||
search_dialog.cancel=Cancel
|
||||
search_dialog.open_by_name=Search for text:
|
||||
@@ -77,6 +84,8 @@ search_dialog.info_label=Showing results %1$d to %2$d of %3$d
|
||||
search_dialog.col_node=Node
|
||||
search_dialog.col_code=Code
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.active_tab=Active tab only
|
||||
search_dialog.comments=Comments
|
||||
search_dialog.resource=Resource
|
||||
search_dialog.keep_open=Keep open
|
||||
search_dialog.tip_searching=Searching ...
|
||||
@@ -84,6 +93,11 @@ search_dialog.tip_searching=Searching ...
|
||||
usage_dialog.title=Usage search
|
||||
usage_dialog.label=Usage for:
|
||||
|
||||
comment_dialog.title.add=Add code comment
|
||||
comment_dialog.title.update=Update code comment
|
||||
comment_dialog.label=Comment:
|
||||
comment_dialog.usage=Use Shift + Enter for start a new line
|
||||
|
||||
log_viewer.title=Log Viewer
|
||||
log_viewer.log_level=Log level:
|
||||
|
||||
@@ -155,6 +169,7 @@ msg.rename_disabled_deobfuscation_disabled=Enable deobfuscation.
|
||||
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
|
||||
msg.rename_node_disabled=Can't rename this node
|
||||
msg.rename_node_failed=Can't rename %s
|
||||
msg.cant_add_comment=Can't add comment here
|
||||
|
||||
popup.bytecode_col=Show Bytecode
|
||||
popup.line_wrap=Line Wrap
|
||||
@@ -168,6 +183,8 @@ popup.select_all=Select All
|
||||
popup.find_usage=Find Usage
|
||||
popup.go_to_declaration=Go to declaration
|
||||
popup.exclude=Exclude
|
||||
popup.add_comment=Comment
|
||||
popup.search_comment=Search comments
|
||||
popup.rename=Rename
|
||||
popup.search=Search "%s"
|
||||
popup.search_global=Global Search "%s"
|
||||
|
||||
@@ -11,6 +11,7 @@ menu.flatten=Mostrar paquetes en vista plana
|
||||
menu.navigation=Navegación
|
||||
menu.text_search=Buscar texto
|
||||
menu.class_search=Buscar clase
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=Herramientas
|
||||
menu.deobfuscation=Desofuscación
|
||||
menu.log=Visor log
|
||||
@@ -61,6 +62,12 @@ nav.forward=Adelante
|
||||
|
||||
#heapUsage.text=
|
||||
|
||||
#common_dialog.ok=Ok
|
||||
#common_dialog.cancel=Cancel
|
||||
#common_dialog.add=Add
|
||||
#common_dialog.update=Update
|
||||
#common_dialog.remove=Remove
|
||||
|
||||
search_dialog.open=Abrir
|
||||
search_dialog.cancel=Cancelar
|
||||
search_dialog.open_by_name=Buscar texto:
|
||||
@@ -77,6 +84,8 @@ search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d
|
||||
search_dialog.col_node=Nodo
|
||||
search_dialog.col_code=Código
|
||||
search_dialog.regex=Regex
|
||||
#search_dialog.active_tab=Active tab only
|
||||
#search_dialog.comments=Comments
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
@@ -84,6 +93,11 @@ search_dialog.regex=Regex
|
||||
usage_dialog.title=Usage search
|
||||
usage_dialog.label=Usage for:
|
||||
|
||||
#comment_dialog.title.add=Add code comment
|
||||
#comment_dialog.title.update=Update code comment
|
||||
#comment_dialog.label=Comment:
|
||||
#comment_dialog.usage=
|
||||
|
||||
log_viewer.title=Visor log
|
||||
log_viewer.log_level=Nivel log:
|
||||
|
||||
@@ -155,6 +169,7 @@ msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivar
|
||||
#msg.cmd_select_class_error=
|
||||
#msg.rename_node_disabled=
|
||||
#msg.rename_node_failed=
|
||||
#msg.cant_add_comment=Can't add comment here
|
||||
|
||||
#popup.bytecode_col=
|
||||
#popup.line_wrap=
|
||||
@@ -168,6 +183,8 @@ popup.select_all=Seleccionar todo
|
||||
#popup.find_usage=
|
||||
#popup.go_to_declaration=
|
||||
#popup.exclude=
|
||||
#popup.add_comment=Comment
|
||||
#popup.search_comment=Search comments
|
||||
popup.rename=Nimeta ümber
|
||||
#popup.search=
|
||||
#popup.search_global=
|
||||
|
||||
@@ -11,6 +11,7 @@ menu.heapUsageBar=메모리 사용량 표시
|
||||
menu.navigation=네비게이션
|
||||
menu.text_search=텍스트 검색
|
||||
menu.class_search=클래스 검색
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=도구
|
||||
menu.deobfuscation=난독화 해제
|
||||
menu.log=로그 뷰어
|
||||
@@ -61,6 +62,12 @@ message.indexingClassesSkipped=<html>Jadx의 메모리가 부족합니다. 따
|
||||
|
||||
heapUsage.text=JADX 메모리 사용량 : %.2f GB / %.2f GB
|
||||
|
||||
#common_dialog.ok=Ok
|
||||
#common_dialog.cancel=Cancel
|
||||
#common_dialog.add=Add
|
||||
#common_dialog.update=Update
|
||||
#common_dialog.remove=Remove
|
||||
|
||||
search_dialog.open=열기
|
||||
search_dialog.cancel=취소
|
||||
search_dialog.open_by_name=텍스트 검색 :
|
||||
@@ -77,6 +84,8 @@ search_dialog.info_label=%3$d 중 %1$d-%2$d 결과 표시
|
||||
search_dialog.col_node=노드
|
||||
search_dialog.col_code=코드
|
||||
search_dialog.regex=정규식
|
||||
#search_dialog.active_tab=Active tab only
|
||||
#search_dialog.comments=Comments
|
||||
search_dialog.resource=리소스
|
||||
search_dialog.keep_open=열어 두기
|
||||
search_dialog.tip_searching=검색 중...
|
||||
@@ -84,6 +93,11 @@ search_dialog.tip_searching=검색 중...
|
||||
usage_dialog.title=사용 검색
|
||||
usage_dialog.label=다음의 사용 검색 결과:
|
||||
|
||||
#comment_dialog.title.add=Add code comment
|
||||
#comment_dialog.title.update=Update code comment
|
||||
#comment_dialog.label=Comment:
|
||||
#comment_dialog.usage=
|
||||
|
||||
log_viewer.title=로그 뷰어
|
||||
log_viewer.log_level=로그 레벨:
|
||||
|
||||
@@ -155,6 +169,7 @@ msg.rename_disabled_deobfuscation_disabled=난독 해제 활성화
|
||||
msg.cmd_select_class_error=클래스를 선택하지 못했습니다.\n%s\n클래스가 없습니다.
|
||||
msg.rename_node_disabled=이 노드의 이름을 바꿀 수 없습니다.
|
||||
msg.rename_node_failed=%s의 이름을 바꿀 수 없습니다.
|
||||
#msg.cant_add_comment=Can't add comment here
|
||||
|
||||
#popup.bytecode_col=
|
||||
popup.line_wrap=줄 바꿈
|
||||
@@ -168,6 +183,8 @@ popup.select_all=모두 선택
|
||||
popup.find_usage=사용 찾기
|
||||
popup.go_to_declaration=선언문으로 이동
|
||||
popup.exclude=제외
|
||||
#popup.add_comment=Comment
|
||||
#popup.search_comment=Search comments
|
||||
popup.rename=이름 바꾸기
|
||||
popup.search="%s" 검색
|
||||
popup.search_global="%s" 전역 검색
|
||||
|
||||
@@ -11,6 +11,7 @@ menu.heapUsageBar=显示内存使用栏
|
||||
menu.navigation=导航
|
||||
menu.text_search=搜索文本
|
||||
menu.class_search=搜索类
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=工具
|
||||
menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
@@ -61,6 +62,12 @@ message.indexingClassesSkipped=<html>Jadx 的内存不足。因此,%d 类没
|
||||
|
||||
heapUsage.text=JADX 内存使用率:%.2f GB 共 %.2f GB
|
||||
|
||||
#common_dialog.ok=Ok
|
||||
#common_dialog.cancel=Cancel
|
||||
#common_dialog.add=Add
|
||||
#common_dialog.update=Update
|
||||
#common_dialog.remove=Remove
|
||||
|
||||
search_dialog.open=转到
|
||||
search_dialog.cancel=取消
|
||||
search_dialog.open_by_name=搜索文本:
|
||||
@@ -77,6 +84,8 @@ search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个
|
||||
search_dialog.col_node=节点
|
||||
search_dialog.col_code=代码
|
||||
search_dialog.regex=正则表达式
|
||||
#search_dialog.active_tab=Active tab only
|
||||
#search_dialog.comments=Comments
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
@@ -84,6 +93,11 @@ search_dialog.regex=正则表达式
|
||||
usage_dialog.title=查找
|
||||
usage_dialog.label=查找用例:
|
||||
|
||||
#comment_dialog.title.add=Add code comment
|
||||
#comment_dialog.title.update=Update code comment
|
||||
#comment_dialog.label=Comment:
|
||||
#comment_dialog.usage=
|
||||
|
||||
log_viewer.title=日志查看器
|
||||
log_viewer.log_level=日志等级:
|
||||
|
||||
@@ -155,6 +169,7 @@ msg.rename_disabled_deobfuscation_disabled=请启用反混淆以重命名。
|
||||
msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。
|
||||
#msg.rename_node_disabled=
|
||||
#msg.rename_node_failed=
|
||||
#msg.cant_add_comment=Can't add comment here
|
||||
|
||||
#popup.bytecode_col=
|
||||
#popup.line_wrap=
|
||||
@@ -168,6 +183,8 @@ popup.select_all=全选
|
||||
popup.find_usage=查找用例
|
||||
popup.go_to_declaration=跳到声明
|
||||
popup.exclude=排除
|
||||
#popup.add_comment=Comment
|
||||
#popup.search_comment=Search comments
|
||||
popup.rename=改名
|
||||
#popup.search=
|
||||
#popup.search_global=
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 744 B |
@@ -118,6 +118,6 @@ class JumpManagerTest {
|
||||
}
|
||||
|
||||
private JumpPosition makeJumpPos() {
|
||||
return new JumpPosition(new TextNode(""), 0);
|
||||
return new JumpPosition(new TextNode(""), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user