gui: show source line numbers
This commit is contained in:
@@ -130,6 +130,11 @@ public final class JavaClass {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
decompile();
|
||||
return cls.getCode().getLineMapping().get(decompiledLine);
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
@@ -63,12 +63,10 @@ public class ClassGen {
|
||||
addClassCode(clsBody);
|
||||
|
||||
CodeWriter clsCode = new CodeWriter();
|
||||
|
||||
if (!"".equals(cls.getPackage())) {
|
||||
clsCode.add("package ").add(cls.getPackage()).add(';');
|
||||
clsCode.newLine();
|
||||
}
|
||||
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||
@@ -85,7 +83,6 @@ public class ClassGen {
|
||||
sortImports.clear();
|
||||
imports.clear();
|
||||
}
|
||||
|
||||
clsCode.add(clsBody);
|
||||
return clsCode;
|
||||
}
|
||||
|
||||
@@ -37,17 +37,13 @@ public class CodeWriter {
|
||||
private int line = 1;
|
||||
private int offset = 0;
|
||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public CodeWriter() {
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
}
|
||||
|
||||
public CodeWriter(int indent) {
|
||||
this.indent = indent;
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
@@ -68,11 +64,6 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(Object obj) {
|
||||
add(obj.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
@@ -91,6 +82,9 @@ public class CodeWriter {
|
||||
CodePosition pos = entry.getKey();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
@@ -102,6 +96,11 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addLine() {
|
||||
buf.append(NL);
|
||||
line++;
|
||||
@@ -114,11 +113,6 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateIndent() {
|
||||
int curIndent = indent;
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
@@ -178,6 +172,25 @@ public class CodeWriter {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||
if (lineMap.isEmpty()) {
|
||||
lineMap = new HashMap<Integer, Integer>();
|
||||
}
|
||||
lineMap.put(decompiledLine, sourceLine);
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void attachSourceLine(int sourceLine) {
|
||||
attachSourceLine(line, sourceLine);
|
||||
}
|
||||
|
||||
public Map<Integer, Integer> getLineMapping() {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<CodePosition, Object>();
|
||||
@@ -185,10 +198,6 @@ public class CodeWriter {
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
buf.trimToSize();
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
@@ -206,9 +215,8 @@ public class CodeWriter {
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
if (str.startsWith(NL)) {
|
||||
return str.substring(NL.length());
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
||||
@@ -198,7 +198,7 @@ public class InsnGen {
|
||||
} else {
|
||||
code.startLine();
|
||||
if (insn.getSourceLine() != 0) {
|
||||
code.attachAnnotation(insn.getSourceLine());
|
||||
code.attachSourceLine(insn.getSourceLine());
|
||||
}
|
||||
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
|
||||
assignVar(code, insn);
|
||||
@@ -469,7 +469,8 @@ public class InsnGen {
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
|
||||
String key = sw.getKeys()[i].toString();
|
||||
code.startLine("case ").add(key).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||
}
|
||||
code.startLine("default: goto ");
|
||||
|
||||
@@ -120,7 +120,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
if (region instanceof IRegion) {
|
||||
IRegion r = (IRegion) region;
|
||||
dot.startLine("subgraph " + makeName(region) + " {");
|
||||
dot.startLine("label = \"").add(r);
|
||||
dot.startLine("label = \"").add(r.toString());
|
||||
String attrs = attributesString(r);
|
||||
if (attrs.length() != 0) {
|
||||
dot.add(" | ").add(attrs);
|
||||
@@ -146,7 +146,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.add("color=red,");
|
||||
}
|
||||
dot.add("label=\"{");
|
||||
dot.add(block.getId()).add("\\:\\ ");
|
||||
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
|
||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||
if (attrs.length() != 0) {
|
||||
dot.add('|').add(attrs);
|
||||
@@ -210,7 +210,7 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
}
|
||||
return str.toString();
|
||||
} else {
|
||||
CodeWriter code = new CodeWriter(0);
|
||||
CodeWriter code = new CodeWriter();
|
||||
MethodGen.addFallbackInsns(code, mth, block.getInstructions(), false);
|
||||
String str = escape(code.newLine().toString());
|
||||
if (str.startsWith(NL)) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class CodeArea extends RSyntaxTextArea {
|
||||
|
||||
private static final long serialVersionUID = 6312736869579635796L;
|
||||
|
||||
private static final Color BACKGROUND = new Color(0xf7f7f7);
|
||||
public static final Color BACKGROUND = new Color(0xf7f7f7);
|
||||
private static final Color JUMP_FOREGROUND = new Color(0x785523);
|
||||
private static final Color JUMP_BACKGROUND = new Color(0xE6E6FF);
|
||||
|
||||
@@ -96,7 +96,11 @@ class CodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
|
||||
Position getCurrentPosition() {
|
||||
return new Position(cls, getCaretLineNumber());
|
||||
return new Position(cls, getCaretLineNumber() + 1);
|
||||
}
|
||||
|
||||
Integer getSourceLine(int line) {
|
||||
return cls.getCls().getSourceLine(line);
|
||||
}
|
||||
|
||||
void scrollToLine(int line) {
|
||||
|
||||
@@ -5,14 +5,13 @@ import jadx.gui.utils.Utils;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.KeyStroke;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
|
||||
class CodePanel extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 5310536092010045565L;
|
||||
@@ -21,7 +20,7 @@ class CodePanel extends JPanel {
|
||||
private final JClass jClass;
|
||||
private final SearchBar searchBar;
|
||||
private final CodeArea codeArea;
|
||||
private final RTextScrollPane scrollPane;
|
||||
private final JScrollPane scrollPane;
|
||||
|
||||
CodePanel(TabbedPane panel, JClass cls) {
|
||||
tabbedPane = panel;
|
||||
@@ -29,8 +28,8 @@ class CodePanel extends JPanel {
|
||||
codeArea = new CodeArea(this);
|
||||
searchBar = new SearchBar(codeArea);
|
||||
|
||||
scrollPane = new RTextScrollPane(codeArea);
|
||||
scrollPane.setFoldIndicatorEnabled(true);
|
||||
scrollPane = new JScrollPane(codeArea);
|
||||
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(searchBar, BorderLayout.NORTH);
|
||||
@@ -65,7 +64,7 @@ class CodePanel extends JPanel {
|
||||
return codeArea;
|
||||
}
|
||||
|
||||
RTextScrollPane getScrollPane() {
|
||||
JScrollPane getScrollPane() {
|
||||
return scrollPane;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.MatteBorder;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.Utilities;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class LineNumbers extends JPanel implements CaretListener {
|
||||
|
||||
private final static Border OUTER = new MatteBorder(0, 0, 0, 1, Color.LIGHT_GRAY);
|
||||
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
|
||||
|
||||
public static final Color FOREGROUND = Color.GRAY;
|
||||
public static final Color BACKGROUND = CodeArea.BACKGROUND;
|
||||
public static final Color CURRENT_LINE_FOREGROUND = new Color(227, 0, 0);
|
||||
|
||||
private CodeArea codeArea;
|
||||
private boolean useSourceLines = true;
|
||||
|
||||
private int lastDigits;
|
||||
private int lastLine;
|
||||
private HashMap<String, FontMetrics> fonts;
|
||||
|
||||
public LineNumbers(CodeArea component) {
|
||||
this.codeArea = component;
|
||||
setFont(component.getFont());
|
||||
setBackground(BACKGROUND);
|
||||
setForeground(FOREGROUND);
|
||||
|
||||
setBorderGap(5);
|
||||
setPreferredWidth();
|
||||
|
||||
component.addCaretListener(this);
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
useSourceLines = !useSourceLines;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setBorderGap(int borderGap) {
|
||||
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
|
||||
setBorder(new CompoundBorder(OUTER, inner));
|
||||
lastDigits = 0;
|
||||
}
|
||||
|
||||
private void setPreferredWidth() {
|
||||
Element root = codeArea.getDocument().getDefaultRootElement();
|
||||
int lines = root.getElementCount();
|
||||
int digits = Math.max(String.valueOf(lines).length(), 3);
|
||||
if (lastDigits != digits) {
|
||||
lastDigits = digits;
|
||||
FontMetrics fontMetrics = getFontMetrics(getFont());
|
||||
int width = fontMetrics.charWidth('0') * digits;
|
||||
Insets insets = getInsets();
|
||||
int preferredWidth = insets.left + insets.right + width;
|
||||
|
||||
Dimension d = getPreferredSize();
|
||||
if (d != null) {
|
||||
d.setSize(preferredWidth, HEIGHT);
|
||||
setPreferredSize(d);
|
||||
setSize(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
FontMetrics fontMetrics = codeArea.getFontMetrics(codeArea.getFont());
|
||||
Insets insets = getInsets();
|
||||
int availableWidth = getSize().width - insets.left - insets.right;
|
||||
Rectangle clip = g.getClipBounds();
|
||||
int rowStartOffset = codeArea.viewToModel(new Point(0, clip.y));
|
||||
int endOffset = codeArea.viewToModel(new Point(0, clip.y + clip.height));
|
||||
|
||||
while (rowStartOffset <= endOffset) {
|
||||
try {
|
||||
if (isCurrentLine(rowStartOffset)) {
|
||||
g.setColor(CURRENT_LINE_FOREGROUND);
|
||||
} else {
|
||||
g.setColor(FOREGROUND);
|
||||
}
|
||||
String lineNumber = getTextLineNumber(rowStartOffset);
|
||||
int stringWidth = fontMetrics.stringWidth(lineNumber);
|
||||
int x = availableWidth - stringWidth + insets.left;
|
||||
int y = getOffsetY(rowStartOffset, fontMetrics);
|
||||
g.drawString(lineNumber, x, y);
|
||||
rowStartOffset = Utilities.getRowEnd(codeArea, rowStartOffset) + 1;
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCurrentLine(int rowStartOffset) {
|
||||
int caretPosition = codeArea.getCaretPosition();
|
||||
Element root = codeArea.getDocument().getDefaultRootElement();
|
||||
return root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition);
|
||||
}
|
||||
|
||||
protected String getTextLineNumber(int rowStartOffset) {
|
||||
Element root = codeArea.getDocument().getDefaultRootElement();
|
||||
int index = root.getElementIndex(rowStartOffset);
|
||||
Element line = root.getElement(index);
|
||||
if (line.getStartOffset() == rowStartOffset) {
|
||||
int lineNumber = index + 1;
|
||||
if (useSourceLines) {
|
||||
Integer sourceLine = codeArea.getSourceLine(lineNumber);
|
||||
if (sourceLine != null) {
|
||||
return String.valueOf(sourceLine);
|
||||
}
|
||||
} else {
|
||||
return String.valueOf(lineNumber);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics) throws BadLocationException {
|
||||
Rectangle r = codeArea.modelToView(rowStartOffset);
|
||||
if (r == null) {
|
||||
throw new BadLocationException("Can't get Y offset", rowStartOffset);
|
||||
}
|
||||
int lineHeight = fontMetrics.getHeight();
|
||||
int y = r.y + r.height;
|
||||
int descent = 0;
|
||||
if (r.height == lineHeight) {
|
||||
descent = fontMetrics.getDescent();
|
||||
} else {
|
||||
if (fonts == null) {
|
||||
fonts = new HashMap<String, FontMetrics>();
|
||||
}
|
||||
Element root = codeArea.getDocument().getDefaultRootElement();
|
||||
int index = root.getElementIndex(rowStartOffset);
|
||||
Element line = root.getElement(index);
|
||||
for (int i = 0; i < line.getElementCount(); i++) {
|
||||
Element child = line.getElement(i);
|
||||
AttributeSet as = child.getAttributes();
|
||||
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
|
||||
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
|
||||
String key = fontFamily + fontSize;
|
||||
FontMetrics fm = fonts.get(key);
|
||||
if (fm == null) {
|
||||
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
|
||||
fm = codeArea.getFontMetrics(font);
|
||||
fonts.put(key, fm);
|
||||
}
|
||||
descent = Math.max(descent, fm.getDescent());
|
||||
}
|
||||
}
|
||||
return y - descent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caretUpdate(CaretEvent e) {
|
||||
int caretPosition = codeArea.getCaretPosition();
|
||||
Element root = codeArea.getDocument().getDefaultRootElement();
|
||||
int currentLine = root.getElementIndex(caretPosition);
|
||||
if (lastLine != currentLine) {
|
||||
repaint();
|
||||
lastLine = currentLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,13 +54,13 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private static final double BORDER_RATIO = 0.15;
|
||||
private static final double WINDOW_RATIO = 1 - BORDER_RATIO * 2;
|
||||
private static final double SPLIT_PANE_RESIZE_WEIGHT = 0.15;
|
||||
|
||||
private static final ImageIcon ICON_OPEN = Utils.openIcon("folder");
|
||||
private static final ImageIcon ICON_SAVE_ALL = Utils.openIcon("disk_multiple");
|
||||
private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross");
|
||||
private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj");
|
||||
private static final ImageIcon ICON_SEARCH = Utils.openIcon("magnifier");
|
||||
|
||||
private static final ImageIcon ICON_BACK = Utils.openIcon("icon_back");
|
||||
private static final ImageIcon ICON_FORWARD = Utils.openIcon("icon_forward");
|
||||
|
||||
@@ -248,7 +248,7 @@ public class MainWindow extends JFrame {
|
||||
private void initUI() {
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
JSplitPane splitPane = new JSplitPane();
|
||||
splitPane.setResizeWeight(0.2);
|
||||
splitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
|
||||
mainPanel.add(splitPane);
|
||||
|
||||
DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("Please open file");
|
||||
|
||||
@@ -36,6 +36,7 @@ class TestJumpManager extends Specification {
|
||||
then:
|
||||
noExceptionThrown()
|
||||
jm.getPrev() == mock1
|
||||
jm.getPrev() == null
|
||||
jm.getNext() == mock2
|
||||
jm.getNext() == null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user