362 lines
9.5 KiB
Java
362 lines
9.5 KiB
Java
package jadx.gui.ui;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.event.HyperlinkEvent;
|
|
import javax.swing.event.HyperlinkListener;
|
|
import javax.swing.event.PopupMenuEvent;
|
|
import javax.swing.event.PopupMenuListener;
|
|
import javax.swing.text.BadLocationException;
|
|
import javax.swing.text.Caret;
|
|
import javax.swing.text.DefaultCaret;
|
|
import java.awt.*;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
|
|
import org.fife.ui.rsyntaxtextarea.LinkGenerator;
|
|
import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult;
|
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
|
import org.fife.ui.rsyntaxtextarea.Token;
|
|
import org.fife.ui.rsyntaxtextarea.TokenTypes;
|
|
import org.fife.ui.rtextarea.SearchContext;
|
|
import org.fife.ui.rtextarea.SearchEngine;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import jadx.api.CodePosition;
|
|
import jadx.api.JavaNode;
|
|
import jadx.gui.settings.JadxSettings;
|
|
import jadx.gui.treemodel.JClass;
|
|
import jadx.gui.treemodel.JNode;
|
|
import jadx.gui.utils.Position;
|
|
|
|
public final class CodeArea extends RSyntaxTextArea {
|
|
private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class);
|
|
|
|
private static final long serialVersionUID = 6312736869579635796L;
|
|
|
|
private final CodePanel contentPanel;
|
|
private final JNode node;
|
|
|
|
CodeArea(CodePanel panel) {
|
|
this.contentPanel = panel;
|
|
this.node = panel.getNode();
|
|
|
|
setMarkOccurrences(true);
|
|
setEditable(false);
|
|
loadSettings();
|
|
|
|
Caret caret = getCaret();
|
|
if (caret instanceof DefaultCaret) {
|
|
((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
|
|
}
|
|
caret.setVisible(true);
|
|
|
|
setSyntaxEditingStyle(node.getSyntaxName());
|
|
if (node instanceof JClass) {
|
|
setHyperlinksEnabled(true);
|
|
CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator((JClass) node);
|
|
setLinkGenerator(codeLinkProcessor);
|
|
addHyperlinkListener(codeLinkProcessor);
|
|
addMenuItems(this, (JClass) node);
|
|
}
|
|
registerWordHighlighter();
|
|
setText(node.getContent());
|
|
}
|
|
|
|
private void registerWordHighlighter() {
|
|
addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mouseClicked(MouseEvent evt) {
|
|
if (evt.getClickCount() % 2 == 0 && !evt.isConsumed()) {
|
|
evt.consume();
|
|
String str = getSelectedText();
|
|
if (str != null) {
|
|
highlightAllMatches(str);
|
|
}
|
|
} else {
|
|
highlightAllMatches(null);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param str - if null -> reset current highlights
|
|
*/
|
|
private void highlightAllMatches(@Nullable String str) {
|
|
SearchContext context = new SearchContext(str);
|
|
context.setMarkAll(true);
|
|
context.setMatchCase(true);
|
|
context.setWholeWord(true);
|
|
SearchEngine.markAll(this, context);
|
|
}
|
|
|
|
private void addMenuItems(CodeArea codeArea, JClass jCls) {
|
|
Action findUsage = new FindUsageAction(codeArea, jCls);
|
|
|
|
JPopupMenu popup = getPopupMenu();
|
|
popup.addSeparator();
|
|
popup.add(findUsage);
|
|
popup.addPopupMenuListener((PopupMenuListener) findUsage);
|
|
}
|
|
|
|
public void loadSettings() {
|
|
loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this);
|
|
}
|
|
|
|
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
|
|
area.setAntiAliasingEnabled(true);
|
|
mainWindow.getEditorTheme().apply(area);
|
|
|
|
JadxSettings settings = mainWindow.getSettings();
|
|
area.setFont(settings.getFont());
|
|
}
|
|
|
|
public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) {
|
|
RSyntaxTextArea area = new RSyntaxTextArea();
|
|
loadCommonSettings(mainWindow, area);
|
|
return area;
|
|
}
|
|
|
|
private boolean isJumpToken(Token token) {
|
|
if (token.getType() == TokenTypes.IDENTIFIER) {
|
|
// fast skip
|
|
if (token.length() == 1) {
|
|
char ch = token.getTextArray()[token.getTextOffset()];
|
|
if (ch == '.' || ch == ',' || ch == ';') {
|
|
return false;
|
|
}
|
|
}
|
|
if (node instanceof JClass) {
|
|
Position pos = getDefPosition((JClass) node, this, token.getOffset());
|
|
if (pos != null) {
|
|
// don't underline definition place
|
|
try {
|
|
int defLine = pos.getLine();
|
|
int lineOfOffset = getLineOfOffset(token.getOffset()) + 1;
|
|
if (defLine == lineOfOffset) {
|
|
return false;
|
|
}
|
|
} catch (BadLocationException e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// @Override
|
|
// public Color getForegroundForToken(Token t) {
|
|
// if (isJumpToken(t)) {
|
|
// return getHyperlinkForeground();
|
|
// }
|
|
// return super.getForegroundForToken(t);
|
|
// }
|
|
|
|
static Position getDefPosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
|
|
JavaNode node = getJavaNodeAtOffset(jCls, textArea, offset);
|
|
if (node == null) {
|
|
return null;
|
|
}
|
|
CodePosition pos = jCls.getCls().getDefinitionPosition(node);
|
|
if (pos == null) {
|
|
return null;
|
|
}
|
|
return new Position(pos);
|
|
}
|
|
|
|
static JavaNode getJavaNodeAtOffset(JClass jCls, RSyntaxTextArea textArea, int offset) {
|
|
try {
|
|
int line = textArea.getLineOfOffset(offset);
|
|
int lineOffset = offset - textArea.getLineStartOffset(line);
|
|
return jCls.getCls().getJavaNodeAtPosition(line + 1, lineOffset + 1);
|
|
} catch (BadLocationException e) {
|
|
LOG.error("Can't get java node by offset", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public Position getCurrentPosition() {
|
|
return new Position(node, getCaretLineNumber() + 1);
|
|
}
|
|
|
|
Integer getSourceLine(int line) {
|
|
return node.getSourceLine(line);
|
|
}
|
|
|
|
void scrollToLine(int line) {
|
|
int lineNum = line - 1;
|
|
if (lineNum < 0) {
|
|
lineNum = 0;
|
|
}
|
|
setCaretAtLine(lineNum);
|
|
centerCurrentLine();
|
|
forceCurrentLineHighlightRepaint();
|
|
}
|
|
|
|
public void centerCurrentLine() {
|
|
JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this);
|
|
if (viewport == null) {
|
|
return;
|
|
}
|
|
try {
|
|
Rectangle r = modelToView(getCaretPosition());
|
|
if (r == null) {
|
|
return;
|
|
}
|
|
int extentHeight = viewport.getExtentSize().height;
|
|
Dimension viewSize = viewport.getViewSize();
|
|
if (viewSize == null) {
|
|
return;
|
|
}
|
|
int viewHeight = viewSize.height;
|
|
|
|
int y = Math.max(0, r.y - extentHeight / 2);
|
|
y = Math.min(y, viewHeight - extentHeight);
|
|
|
|
viewport.setViewPosition(new Point(0, y));
|
|
} catch (BadLocationException e) {
|
|
LOG.debug("Can't center current line", e);
|
|
}
|
|
}
|
|
|
|
private void setCaretAtLine(int line) {
|
|
try {
|
|
setCaretPosition(getLineStartOffset(line));
|
|
} catch (BadLocationException e) {
|
|
LOG.debug("Can't scroll to {}", line, e);
|
|
}
|
|
}
|
|
|
|
private class FindUsageAction extends AbstractAction implements PopupMenuListener {
|
|
private static final long serialVersionUID = 4692546569977976384L;
|
|
|
|
private final transient CodeArea codeArea;
|
|
private final transient JClass jCls;
|
|
|
|
private transient JavaNode node;
|
|
|
|
public FindUsageAction(CodeArea codeArea, JClass jCls) {
|
|
super("Find Usage");
|
|
this.codeArea = codeArea;
|
|
this.jCls = jCls;
|
|
}
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (node == null) {
|
|
return;
|
|
}
|
|
MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow();
|
|
JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(node);
|
|
UsageDialog usageDialog = new UsageDialog(mainWindow, jNode);
|
|
usageDialog.setVisible(true);
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
|
node = null;
|
|
Point pos = codeArea.getMousePosition();
|
|
if (pos != null) {
|
|
Token token = codeArea.viewToToken(pos);
|
|
if (token != null) {
|
|
node = getJavaNodeAtOffset(jCls, codeArea, token.getOffset());
|
|
}
|
|
}
|
|
setEnabled(node != null);
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
|
}
|
|
}
|
|
|
|
private class CodeLinkGenerator implements LinkGenerator, HyperlinkListener {
|
|
private final JClass jCls;
|
|
|
|
public CodeLinkGenerator(JClass cls) {
|
|
this.jCls = cls;
|
|
}
|
|
|
|
@Override
|
|
public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) {
|
|
try {
|
|
Token token = textArea.modelToToken(offset);
|
|
if (token == null) {
|
|
return null;
|
|
}
|
|
final int sourceOffset = token.getOffset();
|
|
final Position defPos = getDefPosition(jCls, textArea, sourceOffset);
|
|
if (defPos == null) {
|
|
return null;
|
|
}
|
|
return new LinkGeneratorResult() {
|
|
@Override
|
|
public HyperlinkEvent execute() {
|
|
return new HyperlinkEvent(defPos, HyperlinkEvent.EventType.ACTIVATED, null,
|
|
defPos.getNode().makeLongString());
|
|
}
|
|
|
|
@Override
|
|
public int getSourceOffset() {
|
|
return sourceOffset;
|
|
}
|
|
};
|
|
} catch (Exception e) {
|
|
LOG.error("isLinkAtOffset error", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void hyperlinkUpdate(HyperlinkEvent e) {
|
|
Object obj = e.getSource();
|
|
if (obj instanceof Position) {
|
|
contentPanel.getTabbedPane().codeJump((Position) obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static final class EditorTheme {
|
|
private final String name;
|
|
private final String path;
|
|
|
|
public EditorTheme(String name, String path) {
|
|
this.name = name;
|
|
this.path = path;
|
|
}
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
public String getPath() {
|
|
return path;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return name;
|
|
}
|
|
}
|
|
|
|
public static EditorTheme[] getAllThemes() {
|
|
return new EditorTheme[]{
|
|
new EditorTheme("default", "/org/fife/ui/rsyntaxtextarea/themes/default.xml"),
|
|
new EditorTheme("eclipse", "/org/fife/ui/rsyntaxtextarea/themes/eclipse.xml"),
|
|
new EditorTheme("idea", "/org/fife/ui/rsyntaxtextarea/themes/idea.xml"),
|
|
new EditorTheme("vs", "/org/fife/ui/rsyntaxtextarea/themes/vs.xml"),
|
|
new EditorTheme("dark", "/org/fife/ui/rsyntaxtextarea/themes/dark.xml"),
|
|
new EditorTheme("monokai", "/org/fife/ui/rsyntaxtextarea/themes/monokai.xml")
|
|
};
|
|
}
|
|
}
|