summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Joseph Burton2025-10-18 16:38:40 +0100
committerGravatar GitHub2025-10-18 16:38:40 +0100
commit87581ffe8d23aaf8ad677ffbb9de1ecfec3cfe80 (patch)
treef3191c08ee6be85c11d8f109e6923c320e74368e
parentFix class version check in AddFramesIfNecessaryClassProvider (#575) (diff)
downloadenigma-87581ffe8d23aaf8ad677ffbb9de1ecfec3cfe80.tar.gz
enigma-87581ffe8d23aaf8ad677ffbb9de1ecfec3cfe80.tar.xz
enigma-87581ffe8d23aaf8ad677ffbb9de1ecfec3cfe80.zip
Annotation editor support (#568)
* Add gutter markers * Add more GUI APIs * Use SVG icons for gutter markers * Add a little more padding between the line numbers and the gutter markers * Add API for creating an Enigma JEditorPane * Add API to list all classes including library classes * Add API to create a LocalVariableEntryView * Expose BrdigeMethodIndex to API * Require name to be passed to LocalVariableEntryView * Make implementation of isCursorOnDeclaration more robust * Checkstyle * Replace isCursorOnDeclaration with getCursorDeclaration * Checkstyle again * Refactor EnigmaIcon as per Juuz's suggestions * Add more @NonExtendable and add EnigmaIcon docs
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java6
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java27
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java7
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/GutterIcon.java42
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java83
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/GutterPanel.java238
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/EnigmaIconImpl.java8
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/IconLoadingServiceImpl.java16
-rw-r--r--enigma-swing/src/main/resources/META-INF/services/cuchaz.enigma.utils.IconLoadingService1
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProject.java5
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java16
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java1
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/EnigmaIcon.java39
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/GuiService.java18
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/GuiView.java13
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassDefEntryView.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassEntryView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/DefEntryView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryReferenceView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryView.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/FieldEntryView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableDefEntryView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableEntryView.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/entry/MethodEntryView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/index/BridgeMethodIndexView.java15
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/index/EntryIndexView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/index/InheritanceIndexView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/index/JarIndexView.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/view/index/ReferenceIndexView.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java15
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/IconLoadingService.java14
33 files changed, 604 insertions, 11 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
index 12877fed..3a0597e2 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -350,6 +350,12 @@ public class Gui {
350 return activeEditor == null ? null : activeEditor.getCursorReference(); 350 return activeEditor == null ? null : activeEditor.getCursorReference();
351 } 351 }
352 352
353 @Nullable
354 public Entry<?> getCursorDeclaration() {
355 EditorPanel activeEditor = this.editorTabbedPane.getActiveEditor();
356 return activeEditor == null ? null : activeEditor.getCursorDeclaration();
357 }
358
353 public void startDocChange(EditorPanel editor) { 359 public void startDocChange(EditorPanel editor) {
354 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 360 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
355 361
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
index a2b3bd9d..2c27376d 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ExecutionException;
27import java.util.stream.Collectors; 27import java.util.stream.Collectors;
28import java.util.stream.Stream; 28import java.util.stream.Stream;
29 29
30import javax.swing.JEditorPane;
30import javax.swing.JFrame; 31import javax.swing.JFrame;
31import javax.swing.JOptionPane; 32import javax.swing.JOptionPane;
32import javax.swing.SwingUtilities; 33import javax.swing.SwingUtilities;
@@ -52,12 +53,15 @@ import cuchaz.enigma.api.service.ObfuscationTestService;
52import cuchaz.enigma.api.service.ProjectService; 53import cuchaz.enigma.api.service.ProjectService;
53import cuchaz.enigma.api.view.GuiView; 54import cuchaz.enigma.api.view.GuiView;
54import cuchaz.enigma.api.view.entry.EntryReferenceView; 55import cuchaz.enigma.api.view.entry.EntryReferenceView;
56import cuchaz.enigma.api.view.entry.EntryView;
55import cuchaz.enigma.classhandle.ClassHandle; 57import cuchaz.enigma.classhandle.ClassHandle;
56import cuchaz.enigma.classhandle.ClassHandleProvider; 58import cuchaz.enigma.classhandle.ClassHandleProvider;
59import cuchaz.enigma.gui.config.LookAndFeel;
57import cuchaz.enigma.gui.config.NetConfig; 60import cuchaz.enigma.gui.config.NetConfig;
58import cuchaz.enigma.gui.config.UiConfig; 61import cuchaz.enigma.gui.config.UiConfig;
59import cuchaz.enigma.gui.dialog.ProgressDialog; 62import cuchaz.enigma.gui.dialog.ProgressDialog;
60import cuchaz.enigma.gui.newabstraction.EntryValidation; 63import cuchaz.enigma.gui.newabstraction.EntryValidation;
64import cuchaz.enigma.gui.panels.EditorPanel;
61import cuchaz.enigma.gui.stats.StatsGenerator; 65import cuchaz.enigma.gui.stats.StatsGenerator;
62import cuchaz.enigma.gui.stats.StatsMember; 66import cuchaz.enigma.gui.stats.StatsMember;
63import cuchaz.enigma.gui.util.History; 67import cuchaz.enigma.gui.util.History;
@@ -130,6 +134,23 @@ public class GuiController implements ClientPacketHandler, GuiView, DataInvalida
130 return gui.getFrame(); 134 return gui.getFrame();
131 } 135 }
132 136
137 @Override
138 public float getScale() {
139 return UiConfig.getActiveScaleFactor();
140 }
141
142 @Override
143 public boolean isDarkTheme() {
144 return LookAndFeel.isDarkLaf();
145 }
146
147 @Override
148 public JEditorPane createEditorPane() {
149 JEditorPane editor = new JEditorPane();
150 EditorPanel.customizeEditor(editor);
151 return editor;
152 }
153
133 public boolean isDirty() { 154 public boolean isDirty() {
134 return project != null && project.getMapper().isDirty(); 155 return project != null && project.getMapper().isDirty();
135 } 156 }
@@ -349,6 +370,12 @@ public class GuiController implements ClientPacketHandler, GuiView, DataInvalida
349 return gui.getCursorReference(); 370 return gui.getCursorReference();
350 } 371 }
351 372
373 @Override
374 @Nullable
375 public EntryView getCursorDeclaration() {
376 return gui.getCursorDeclaration();
377 }
378
352 /** 379 /**
353 * Navigates to the declaration with respect to navigation history. 380 * Navigates to the declaration with respect to navigation history.
354 * 381 *
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java
index 2088aac2..2ac5d748 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/LookAndFeel.java
@@ -22,6 +22,7 @@ public enum LookAndFeel {
22 // the "JVM default" look and feel, get it at the beginning and store it so we can set it later 22 // the "JVM default" look and feel, get it at the beginning and store it so we can set it later
23 private static final javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); 23 private static final javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel();
24 private final boolean needsScaling; 24 private final boolean needsScaling;
25 private static Boolean isDarkLaf = null;
25 26
26 LookAndFeel(boolean needsScaling) { 27 LookAndFeel(boolean needsScaling) {
27 this.needsScaling = needsScaling; 28 this.needsScaling = needsScaling;
@@ -52,6 +53,10 @@ public enum LookAndFeel {
52 } 53 }
53 54
54 public static boolean isDarkLaf() { 55 public static boolean isDarkLaf() {
56 if (isDarkLaf != null) {
57 return isDarkLaf;
58 }
59
55 // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel 60 // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel
56 JPanel panel = new JPanel(); 61 JPanel panel = new JPanel();
57 panel.setSize(new Dimension(10, 10)); 62 panel.setSize(new Dimension(10, 10));
@@ -64,6 +69,6 @@ public enum LookAndFeel {
64 69
65 // convert the color we got to grayscale 70 // convert the color we got to grayscale
66 int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); 71 int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue());
67 return b < 85; 72 return isDarkLaf = b < 85;
68 } 73 }
69} 74}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/GutterIcon.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/GutterIcon.java
new file mode 100644
index 00000000..946591a6
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/GutterIcon.java
@@ -0,0 +1,42 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.Cursor;
4import java.awt.event.ComponentAdapter;
5import java.awt.event.ComponentEvent;
6
7import javax.swing.JButton;
8
9import cuchaz.enigma.api.service.GuiService;
10import cuchaz.enigma.gui.util.EnigmaIconImpl;
11import cuchaz.enigma.gui.util.ScaleUtil;
12
13public class GutterIcon extends JButton implements GuiService.GutterMarkerBuilder {
14 private Runnable clickAction = () -> { };
15
16 public GutterIcon(EnigmaIconImpl icon) {
17 super(icon.icon());
18 setContentAreaFilled(false);
19 setCursor(Cursor.getDefaultCursor());
20 addActionListener(e -> clickAction.run());
21
22 addComponentListener(new ComponentAdapter() {
23 @Override
24 public void componentResized(ComponentEvent e) {
25 setIcon(icon.icon().derive(ScaleUtil.invert(getWidth()), ScaleUtil.invert(getHeight())));
26 }
27 });
28 }
29
30 @Override
31 public GuiService.GutterMarkerBuilder setClickAction(Runnable action) {
32 this.clickAction = action;
33 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
34 return this;
35 }
36
37 @Override
38 public GuiService.GutterMarkerBuilder setTooltip(String tooltip) {
39 setToolTipText(tooltip);
40 return this;
41 }
42}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
index 6b341ee0..92d1bd7f 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java
@@ -15,6 +15,7 @@ import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent; 15import java.awt.event.MouseEvent;
16import java.util.ArrayList; 16import java.util.ArrayList;
17import java.util.Collection; 17import java.util.Collection;
18import java.util.Comparator;
18import java.util.List; 19import java.util.List;
19import java.util.Map; 20import java.util.Map;
20 21
@@ -35,10 +36,12 @@ import javax.swing.text.Highlighter.HighlightPainter;
35 36
36import de.sciss.syntaxpane.DefaultSyntaxKit; 37import de.sciss.syntaxpane.DefaultSyntaxKit;
37import de.sciss.syntaxpane.SyntaxDocument; 38import de.sciss.syntaxpane.SyntaxDocument;
39import de.sciss.syntaxpane.actions.ActionUtils;
38import org.jetbrains.annotations.Nullable; 40import org.jetbrains.annotations.Nullable;
39 41
40import cuchaz.enigma.EnigmaProject; 42import cuchaz.enigma.EnigmaProject;
41import cuchaz.enigma.analysis.EntryReference; 43import cuchaz.enigma.analysis.EntryReference;
44import cuchaz.enigma.api.service.GuiService;
42import cuchaz.enigma.classhandle.ClassHandle; 45import cuchaz.enigma.classhandle.ClassHandle;
43import cuchaz.enigma.classhandle.ClassHandleError; 46import cuchaz.enigma.classhandle.ClassHandleError;
44import cuchaz.enigma.events.ClassHandleListener; 47import cuchaz.enigma.events.ClassHandleListener;
@@ -50,14 +53,17 @@ import cuchaz.enigma.gui.config.LookAndFeel;
50import cuchaz.enigma.gui.config.Themes; 53import cuchaz.enigma.gui.config.Themes;
51import cuchaz.enigma.gui.config.UiConfig; 54import cuchaz.enigma.gui.config.UiConfig;
52import cuchaz.enigma.gui.elements.EditorPopupMenu; 55import cuchaz.enigma.gui.elements.EditorPopupMenu;
56import cuchaz.enigma.gui.elements.GutterIcon;
53import cuchaz.enigma.gui.events.EditorActionListener; 57import cuchaz.enigma.gui.events.EditorActionListener;
54import cuchaz.enigma.gui.events.ThemeChangeListener; 58import cuchaz.enigma.gui.events.ThemeChangeListener;
55import cuchaz.enigma.gui.highlight.BoxHighlightPainter; 59import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
56import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; 60import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
61import cuchaz.enigma.gui.util.EnigmaIconImpl;
57import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; 62import cuchaz.enigma.gui.util.GridBagConstraintsBuilder;
58import cuchaz.enigma.gui.util.ScaleUtil; 63import cuchaz.enigma.gui.util.ScaleUtil;
59import cuchaz.enigma.source.DecompiledClassSource; 64import cuchaz.enigma.source.DecompiledClassSource;
60import cuchaz.enigma.source.RenamableTokenType; 65import cuchaz.enigma.source.RenamableTokenType;
66import cuchaz.enigma.source.SourceIndex;
61import cuchaz.enigma.source.Token; 67import cuchaz.enigma.source.Token;
62import cuchaz.enigma.translation.mapping.EntryRemapper; 68import cuchaz.enigma.translation.mapping.EntryRemapper;
63import cuchaz.enigma.translation.mapping.EntryResolver; 69import cuchaz.enigma.translation.mapping.EntryResolver;
@@ -65,12 +71,14 @@ import cuchaz.enigma.translation.mapping.ResolutionStrategy;
65import cuchaz.enigma.translation.representation.entry.ClassEntry; 71import cuchaz.enigma.translation.representation.entry.ClassEntry;
66import cuchaz.enigma.translation.representation.entry.Entry; 72import cuchaz.enigma.translation.representation.entry.Entry;
67import cuchaz.enigma.utils.I18n; 73import cuchaz.enigma.utils.I18n;
74import cuchaz.enigma.utils.Pair;
68import cuchaz.enigma.utils.Result; 75import cuchaz.enigma.utils.Result;
69 76
70public class EditorPanel { 77public class EditorPanel {
71 private final JPanel ui = new JPanel(); 78 private final JPanel ui = new JPanel();
72 private final JEditorPane editor = new JEditorPane(); 79 private final JEditorPane editor = new JEditorPane();
73 private final JScrollPane editorScrollPane = new JScrollPane(this.editor); 80 private final JScrollPane editorScrollPane = new JScrollPane(this.editor);
81 private final GutterPanel gutterPanel;
74 private final EditorPopupMenu popupMenu; 82 private final EditorPopupMenu popupMenu;
75 83
76 // progress UI 84 // progress UI
@@ -109,20 +117,16 @@ public class EditorPanel {
109 this.gui = gui; 117 this.gui = gui;
110 this.controller = gui.getController(); 118 this.controller = gui.getController();
111 119
112 this.editor.setEditable(false); 120 customizeEditor(this.editor);
113 this.editor.setSelectionColor(new Color(31, 46, 90));
114 this.editor.setCaret(new BrowserCaret());
115 this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); 121 this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed));
116 this.editor.setCaretColor(UiConfig.getCaretColor());
117 this.editor.setContentType("text/enigma-sources");
118 this.editor.setBackground(UiConfig.getEditorBackgroundColor());
119 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
120 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
121 122
122 // set unit increment to height of one line, the amount scrolled per 123 // set unit increment to height of one line, the amount scrolled per
123 // mouse wheel rotation is then controlled by OS settings 124 // mouse wheel rotation is then controlled by OS settings
124 this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight()); 125 this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight());
125 126
127 this.gutterPanel = new GutterPanel(this.editor, (JComponent) this.editorScrollPane.getRowHeader().getView());
128 this.editorScrollPane.setRowHeaderView(this.gutterPanel);
129
126 // init editor popup menu 130 // init editor popup menu
127 this.popupMenu = new EditorPopupMenu(this, gui); 131 this.popupMenu = new EditorPopupMenu(this, gui);
128 this.editor.setComponentPopupMenu(this.popupMenu.getUi()); 132 this.editor.setComponentPopupMenu(this.popupMenu.getUi());
@@ -255,6 +259,17 @@ public class EditorPanel {
255 this.ui.putClientProperty(EditorPanel.class, this); 259 this.ui.putClientProperty(EditorPanel.class, this);
256 } 260 }
257 261
262 public static void customizeEditor(JEditorPane editor) {
263 editor.setEditable(false);
264 editor.setSelectionColor(new Color(31, 46, 90));
265 editor.setCaret(new BrowserCaret());
266 editor.setCaretColor(UiConfig.getCaretColor());
267 editor.setContentType("text/enigma-sources");
268 editor.setBackground(UiConfig.getEditorBackgroundColor());
269 DefaultSyntaxKit kit = (DefaultSyntaxKit) editor.getEditorKit();
270 kit.toggleComponent(editor, "de.sciss.syntaxpane.components.TokenMarker");
271 }
272
258 @Nullable 273 @Nullable
259 public static EditorPanel byUi(Component ui) { 274 public static EditorPanel byUi(Component ui) {
260 if (ui instanceof JComponent) { 275 if (ui instanceof JComponent) {
@@ -512,6 +527,7 @@ public class EditorPanel {
512 this.editor.setCaretPosition(newCaretPos); 527 this.editor.setCaretPosition(newCaretPos);
513 } 528 }
514 529
530 addGutterMarkers(source.getIndex());
515 setHighlightedTokens(source.getHighlightedTokens()); 531 setHighlightedTokens(source.getHighlightedTokens());
516 setCursorReference(getReference(getToken(this.editor.getCaretPosition()))); 532 setCursorReference(getReference(getToken(this.editor.getCaretPosition())));
517 } finally { 533 } finally {
@@ -524,6 +540,44 @@ public class EditorPanel {
524 } 540 }
525 } 541 }
526 542
543 private void addGutterMarkers(SourceIndex sourceIndex) {
544 List<GuiService> services = this.gui.getController().enigma.getServices().get(GuiService.TYPE);
545
546 if (services.isEmpty()) {
547 return;
548 }
549
550 this.gutterPanel.clearMarkers();
551
552 List<Pair<Entry<?>, Token>> declarationTokens = new ArrayList<>();
553
554 for (Entry<?> declaration : sourceIndex.declarations()) {
555 declarationTokens.add(new Pair<>(declaration, sourceIndex.getDeclarationToken(declaration)));
556 }
557
558 declarationTokens.sort(Comparator.comparing(pair -> pair.b));
559
560 for (Pair<Entry<?>, Token> declaration : declarationTokens) {
561 int lineNumber;
562
563 try {
564 lineNumber = ActionUtils.getLineNumber(this.editor, declaration.b.start);
565 } catch (BadLocationException e) {
566 continue;
567 }
568
569 for (GuiService service : services) {
570 service.addGutterMarkers(this.gui.getController(), declaration.a, (icon, alignment) -> {
571 GutterIcon button = new GutterIcon((EnigmaIconImpl) icon);
572 this.gutterPanel.addMarker(lineNumber, alignment, button);
573 return button;
574 });
575 }
576 }
577
578 this.editor.revalidate();
579 }
580
527 public void setHighlightedTokens(Map<RenamableTokenType, ? extends Collection<Token>> tokens) { 581 public void setHighlightedTokens(Map<RenamableTokenType, ? extends Collection<Token>> tokens) {
528 // remove any old highlighters 582 // remove any old highlighters
529 this.editor.getHighlighter().removeAllHighlights(); 583 this.editor.getHighlighter().removeAllHighlights();
@@ -565,10 +619,23 @@ public class EditorPanel {
565 } 619 }
566 } 620 }
567 621
622 @Nullable
568 public EntryReference<Entry<?>, Entry<?>> getCursorReference() { 623 public EntryReference<Entry<?>, Entry<?>> getCursorReference() {
569 return this.cursorReference; 624 return this.cursorReference;
570 } 625 }
571 626
627 @Nullable
628 public Entry<?> getCursorDeclaration() {
629 int pos = this.editor.getCaretPosition();
630 Token token = getToken(pos);
631
632 if (token == null) {
633 return null;
634 }
635
636 return this.source.getIndex().getDeclaration(token);
637 }
638
572 public void showReference(EntryReference<Entry<?>, Entry<?>> reference) { 639 public void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
573 if (this.mode == DisplayMode.SUCCESS) { 640 if (this.mode == DisplayMode.SUCCESS) {
574 showReference0(reference); 641 showReference0(reference);
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/GutterPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/GutterPanel.java
new file mode 100644
index 00000000..28636a18
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/GutterPanel.java
@@ -0,0 +1,238 @@
1package cuchaz.enigma.gui.panels;
2
3import java.awt.Color;
4import java.awt.Component;
5import java.awt.Container;
6import java.awt.Dimension;
7import java.awt.FlowLayout;
8import java.awt.FontMetrics;
9import java.awt.Graphics;
10import java.awt.Insets;
11import java.awt.LayoutManager2;
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.HashMap;
15import java.util.Map;
16
17import javax.swing.BorderFactory;
18import javax.swing.JComponent;
19import javax.swing.JEditorPane;
20import javax.swing.JPanel;
21import javax.swing.SwingUtilities;
22import javax.swing.event.CaretEvent;
23import javax.swing.event.CaretListener;
24import javax.swing.event.DocumentEvent;
25import javax.swing.event.DocumentListener;
26import javax.swing.text.BadLocationException;
27
28import de.sciss.syntaxpane.actions.ActionUtils;
29
30import cuchaz.enigma.api.service.GuiService;
31import cuchaz.enigma.gui.config.UiConfig;
32
33public class GutterPanel extends JPanel {
34 private final JPanel markerPanel;
35
36 public GutterPanel(JEditorPane editor, JComponent lineNumbers) {
37 markerPanel = new MarkerPanel(editor);
38
39 setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
40 add(lineNumbers);
41 add(markerPanel);
42
43 setBackground(UiConfig.getLineNumbersBackgroundColor());
44 setForeground(UiConfig.getLineNumbersForegroundColor());
45 markerPanel.setBackground(UiConfig.getLineNumbersBackgroundColor());
46 markerPanel.setForeground(UiConfig.getLineNumbersForegroundColor());
47 setBorder(lineNumbers.getBorder());
48 lineNumbers.setBorder(null);
49 }
50
51 public void clearMarkers() {
52 markerPanel.removeAll();
53 }
54
55 public void addMarker(int line, GuiService.GutterMarkerAlignment alignment, Component marker) {
56 markerPanel.add(marker, new MarkerLayout.Constraint(line, alignment));
57 }
58
59 private static class MarkerPanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener {
60 private final JEditorPane editor;
61 private final Color currentLineColor = UiConfig.getLineNumbersSelectedColor();
62
63 private MarkerPanel(JEditorPane editor) {
64 this.editor = editor;
65 setLayout(new MarkerLayout());
66
67 Insets editorInsets = editor.getInsets();
68
69 if (editorInsets.top != 0 || editorInsets.bottom != 0) {
70 setBorder(BorderFactory.createEmptyBorder(editorInsets.top, 0, editorInsets.bottom, 0));
71 }
72
73 setFont(editor.getFont());
74
75 editor.addCaretListener(this);
76 editor.getDocument().addDocumentListener(this);
77 editor.addPropertyChangeListener(this);
78 }
79
80 @Override
81 protected void paintComponent(Graphics g) {
82 super.paintComponent(g);
83
84 int currentLine;
85
86 try {
87 currentLine = ActionUtils.getLineNumber(editor, editor.getCaretPosition());
88 } catch (BadLocationException ex) {
89 return; // no valid caret -> nothing to draw
90 }
91
92 FontMetrics fm = getFontMetrics(getFont());
93 int lh = fm.getHeight();
94 Insets insets = getInsets();
95
96 int y = currentLine * lh;
97
98 g.setColor(currentLineColor);
99 g.fillRect(0, y, getWidth(), lh);
100 }
101
102 @Override
103 public void propertyChange(PropertyChangeEvent evt) {
104 if (evt.getPropertyName().equals("document")) {
105 repaint();
106 }
107 }
108
109 @Override
110 public void caretUpdate(CaretEvent e) {
111 repaint();
112 }
113
114 @Override
115 public void insertUpdate(DocumentEvent e) {
116 documentChanged();
117 }
118
119 @Override
120 public void removeUpdate(DocumentEvent e) {
121 documentChanged();
122 }
123
124 @Override
125 public void changedUpdate(DocumentEvent e) {
126 documentChanged();
127 }
128
129 private void documentChanged() {
130 SwingUtilities.invokeLater(this::repaint);
131 }
132 }
133
134 private static class MarkerLayout implements LayoutManager2 {
135 private static final int GAP = 2;
136 private static final int HUGE_HEIGHT = 0x100000; // taken from LineNumbersRuler
137
138 private final Map<Component, Constraint> constraints = new HashMap<>();
139
140 @Override
141 public void addLayoutComponent(Component comp, Object constraints) {
142 this.constraints.put(comp, (Constraint) constraints);
143 }
144
145 @Override
146 public Dimension maximumLayoutSize(Container target) {
147 return preferredLayoutSize(target);
148 }
149
150 @Override
151 public float getLayoutAlignmentX(Container target) {
152 return 0.5f;
153 }
154
155 @Override
156 public float getLayoutAlignmentY(Container target) {
157 return 0.5f;
158 }
159
160 @Override
161 public void invalidateLayout(Container target) {
162 }
163
164 @Override
165 public void addLayoutComponent(String name, Component comp) {
166 }
167
168 @Override
169 public void removeLayoutComponent(Component comp) {
170 constraints.remove(comp);
171 }
172
173 @Override
174 public Dimension preferredLayoutSize(Container parent) {
175 synchronized (parent.getTreeLock()) {
176 Insets insets = parent.getInsets();
177
178 if (constraints.isEmpty()) {
179 return new Dimension(insets.left + insets.right, HUGE_HEIGHT);
180 }
181
182 Map<Integer, Integer> leftCount = new HashMap<>();
183 Map<Integer, Integer> rightCount = new HashMap<>();
184
185 for (Constraint constraint : constraints.values()) {
186 switch (constraint.alignment) {
187 case LEFT -> leftCount.merge(constraint.line, 1, Integer::sum);
188 case RIGHT -> rightCount.merge(constraint.line, 1, Integer::sum);
189 }
190 }
191
192 int maxLeft = leftCount.values().stream().mapToInt(Integer::intValue).max().orElse(0);
193 int maxRight = rightCount.values().stream().mapToInt(Integer::intValue).max().orElse(0);
194
195 int lineHeight = parent.getFontMetrics(parent.getFont()).getHeight();
196 return new Dimension(
197 GAP + insets.left + insets.right + (maxLeft + maxRight) * lineHeight,
198 HUGE_HEIGHT
199 );
200 }
201 }
202
203 @Override
204 public Dimension minimumLayoutSize(Container parent) {
205 return preferredLayoutSize(parent);
206 }
207
208 @Override
209 public void layoutContainer(Container parent) {
210 synchronized (parent.getTreeLock()) {
211 Map<Integer, Integer> leftCount = new HashMap<>();
212 Map<Integer, Integer> rightCount = new HashMap<>();
213
214 int lineHeight = parent.getFontMetrics(parent.getFont()).getHeight();
215
216 int numComponents = parent.getComponentCount();
217
218 for (int i = 0; i < numComponents; i++) {
219 Component comp = parent.getComponent(i);
220 Constraint constraint = constraints.get(comp);
221
222 if (constraint == null) {
223 continue;
224 }
225
226 int left = switch (constraint.alignment) {
227 case LEFT -> GAP + (leftCount.merge(constraint.line, 1, Integer::sum) - 1) * lineHeight + GAP / 2;
228 case RIGHT -> parent.getWidth() - rightCount.merge(constraint.line, 1, Integer::sum) * lineHeight + GAP / 2;
229 };
230 comp.setBounds(left, constraint.line * lineHeight + GAP / 2, lineHeight - GAP, lineHeight - GAP);
231 }
232 }
233 }
234
235 private record Constraint(int line, GuiService.GutterMarkerAlignment alignment) {
236 }
237 }
238}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/EnigmaIconImpl.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/EnigmaIconImpl.java
new file mode 100644
index 00000000..8e07df3c
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/EnigmaIconImpl.java
@@ -0,0 +1,8 @@
1package cuchaz.enigma.gui.util;
2
3import com.formdev.flatlaf.extras.FlatSVGIcon;
4
5import cuchaz.enigma.api.EnigmaIcon;
6
7public record EnigmaIconImpl(FlatSVGIcon icon) implements EnigmaIcon {
8}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/IconLoadingServiceImpl.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/IconLoadingServiceImpl.java
new file mode 100644
index 00000000..e948f5a1
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/IconLoadingServiceImpl.java
@@ -0,0 +1,16 @@
1package cuchaz.enigma.gui.util;
2
3import java.io.IOException;
4import java.io.InputStream;
5
6import com.formdev.flatlaf.extras.FlatSVGIcon;
7
8import cuchaz.enigma.api.EnigmaIcon;
9import cuchaz.enigma.utils.IconLoadingService;
10
11public class IconLoadingServiceImpl implements IconLoadingService {
12 @Override
13 public EnigmaIcon loadIcon(InputStream in) throws IOException {
14 return new EnigmaIconImpl(new FlatSVGIcon(in));
15 }
16}
diff --git a/enigma-swing/src/main/resources/META-INF/services/cuchaz.enigma.utils.IconLoadingService b/enigma-swing/src/main/resources/META-INF/services/cuchaz.enigma.utils.IconLoadingService
new file mode 100644
index 00000000..649dfd02
--- /dev/null
+++ b/enigma-swing/src/main/resources/META-INF/services/cuchaz.enigma.utils.IconLoadingService
@@ -0,0 +1 @@
cuchaz.enigma.gui.util.IconLoadingServiceImpl
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
index 348e6403..5242385f 100644
--- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -410,6 +410,11 @@ public class EnigmaProject implements ProjectView {
410 } 410 }
411 411
412 @Override 412 @Override
413 public Collection<String> getProjectAndLibraryClasses() {
414 return classProvider.getClassNames();
415 }
416
417 @Override
413 @Nullable 418 @Nullable
414 public ClassNode getBytecode(String className) { 419 public ClassNode getBytecode(String className) {
415 return classProvider.get(className); 420 return classProvider.get(className);
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
index adb48bab..b852b5f7 100644
--- a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
@@ -10,6 +10,8 @@ import java.util.concurrent.ConcurrentMap;
10 10
11import org.jetbrains.annotations.Nullable; 11import org.jetbrains.annotations.Nullable;
12 12
13import cuchaz.enigma.api.view.entry.MethodEntryView;
14import cuchaz.enigma.api.view.index.BridgeMethodIndexView;
13import cuchaz.enigma.translation.representation.AccessFlags; 15import cuchaz.enigma.translation.representation.AccessFlags;
14import cuchaz.enigma.translation.representation.MethodDescriptor; 16import cuchaz.enigma.translation.representation.MethodDescriptor;
15import cuchaz.enigma.translation.representation.TypeDescriptor; 17import cuchaz.enigma.translation.representation.TypeDescriptor;
@@ -17,7 +19,7 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import cuchaz.enigma.translation.representation.entry.MethodDefEntry; 19import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
18import cuchaz.enigma.translation.representation.entry.MethodEntry; 20import cuchaz.enigma.translation.representation.entry.MethodEntry;
19 21
20public class BridgeMethodIndex implements JarIndexer { 22public class BridgeMethodIndex implements JarIndexer, BridgeMethodIndexView {
21 private final EntryIndex entryIndex; 23 private final EntryIndex entryIndex;
22 private final InheritanceIndex inheritanceIndex; 24 private final InheritanceIndex inheritanceIndex;
23 private final ReferenceIndex referenceIndex; 25 private final ReferenceIndex referenceIndex;
@@ -154,6 +156,18 @@ public class BridgeMethodIndex implements JarIndexer {
154 return bridgeToSpecialized.get(bridge); 156 return bridgeToSpecialized.get(bridge);
155 } 157 }
156 158
159 @Override
160 @Nullable
161 public MethodEntryView getBridgeFromSpecialized(MethodEntryView specialized) {
162 return getBridgeFromSpecialized((MethodEntry) specialized);
163 }
164
165 @Override
166 @Nullable
167 public MethodEntryView getSpecializedFromBridge(MethodEntryView bridge) {
168 return getSpecializedFromBridge((MethodEntry) bridge);
169 }
170
157 /** Includes "renamed specialized -> bridge" entries. */ 171 /** Includes "renamed specialized -> bridge" entries. */
158 public Map<MethodEntry, MethodEntry> getSpecializedToBridge() { 172 public Map<MethodEntry, MethodEntry> getSpecializedToBridge() {
159 return Collections.unmodifiableMap(specializedToBridge); 173 return Collections.unmodifiableMap(specializedToBridge);
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
index 5c5e2c3d..242d7506 100644
--- a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
@@ -204,6 +204,7 @@ public class JarIndex implements JarIndexer, JarIndexView {
204 return referenceIndex; 204 return referenceIndex;
205 } 205 }
206 206
207 @Override
207 public BridgeMethodIndex getBridgeMethodIndex() { 208 public BridgeMethodIndex getBridgeMethodIndex() {
208 return bridgeMethodIndex; 209 return bridgeMethodIndex;
209 } 210 }
diff --git a/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java
index 812ad397..9396eff3 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/DataInvalidationEvent.java
@@ -2,8 +2,10 @@ package cuchaz.enigma.api;
2 2
3import java.util.Collection; 3import java.util.Collection;
4 4
5import org.jetbrains.annotations.ApiStatus;
5import org.jetbrains.annotations.Nullable; 6import org.jetbrains.annotations.Nullable;
6 7
8@ApiStatus.NonExtendable
7public interface DataInvalidationEvent { 9public interface DataInvalidationEvent {
8 /** 10 /**
9 * The classes for which the invalidation applies, or {@code null} if the invalidation applies to all classes. 11 * The classes for which the invalidation applies, or {@code null} if the invalidation applies to all classes.
diff --git a/enigma/src/main/java/cuchaz/enigma/api/EnigmaIcon.java b/enigma/src/main/java/cuchaz/enigma/api/EnigmaIcon.java
new file mode 100644
index 00000000..4a4bd0e0
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/EnigmaIcon.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.api;
2
3import java.io.IOException;
4import java.io.InputStream;
5
6import org.jetbrains.annotations.ApiStatus;
7
8import cuchaz.enigma.utils.IconLoadingService;
9
10@ApiStatus.NonExtendable
11public interface EnigmaIcon {
12 /**
13 * Loads an icon resource from the given SVG resource path.
14 *
15 * @param resource the path to the resource to be loaded
16 * @return The loaded icon
17 * @throws IOException if the resource could not be found or an error occurred while reading it
18 */
19 static EnigmaIcon loadResource(String resource) throws IOException {
20 try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) {
21 if (in == null) {
22 throw new IOException("Could not find resource: " + resource);
23 }
24
25 return load(in);
26 }
27 }
28
29 /**
30 * Loads an icon in SVG format from the given input stream.
31 *
32 * @param in the input stream to load from
33 * @return The loaded icon
34 * @throws IOException if the stream throws an error
35 */
36 static EnigmaIcon load(InputStream in) throws IOException {
37 return IconLoadingService.INSTANCE.loadIcon(in);
38 }
39}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/GuiService.java b/enigma/src/main/java/cuchaz/enigma/api/service/GuiService.java
index b00b94db..53b566f8 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/service/GuiService.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/service/GuiService.java
@@ -5,7 +5,9 @@ import java.util.function.Supplier;
5 5
6import javax.swing.KeyStroke; 6import javax.swing.KeyStroke;
7 7
8import cuchaz.enigma.api.EnigmaIcon;
8import cuchaz.enigma.api.view.GuiView; 9import cuchaz.enigma.api.view.GuiView;
10import cuchaz.enigma.api.view.entry.EntryView;
9 11
10public interface GuiService extends EnigmaService { 12public interface GuiService extends EnigmaService {
11 EnigmaServiceType<GuiService> TYPE = EnigmaServiceType.create("gui"); 13 EnigmaServiceType<GuiService> TYPE = EnigmaServiceType.create("gui");
@@ -16,6 +18,9 @@ public interface GuiService extends EnigmaService {
16 default void addToEditorContextMenu(GuiView gui, MenuRegistrar registrar) { 18 default void addToEditorContextMenu(GuiView gui, MenuRegistrar registrar) {
17 } 19 }
18 20
21 default void addGutterMarkers(GuiView gui, EntryView entry, GutterMarkerAdder gutter) {
22 }
23
19 interface MenuRegistrar { 24 interface MenuRegistrar {
20 void addSeparator(); 25 void addSeparator();
21 26
@@ -31,4 +36,17 @@ public interface GuiService extends EnigmaService {
31 MenuItemBuilder setEnabledWhen(BooleanSupplier condition); 36 MenuItemBuilder setEnabledWhen(BooleanSupplier condition);
32 MenuItemBuilder setAction(Runnable action); 37 MenuItemBuilder setAction(Runnable action);
33 } 38 }
39
40 interface GutterMarkerAdder {
41 GutterMarkerBuilder addMarker(EnigmaIcon icon, GutterMarkerAlignment alignment);
42 }
43
44 interface GutterMarkerBuilder {
45 GutterMarkerBuilder setClickAction(Runnable action);
46 GutterMarkerBuilder setTooltip(String tooltip);
47 }
48
49 enum GutterMarkerAlignment {
50 LEFT, RIGHT
51 }
34} 52}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/GuiView.java b/enigma/src/main/java/cuchaz/enigma/api/view/GuiView.java
index 519a8ced..1d91573b 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/GuiView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/GuiView.java
@@ -1,11 +1,15 @@
1package cuchaz.enigma.api.view; 1package cuchaz.enigma.api.view;
2 2
3import javax.swing.JEditorPane;
3import javax.swing.JFrame; 4import javax.swing.JFrame;
4 5
6import org.jetbrains.annotations.ApiStatus;
5import org.jetbrains.annotations.Nullable; 7import org.jetbrains.annotations.Nullable;
6 8
7import cuchaz.enigma.api.view.entry.EntryReferenceView; 9import cuchaz.enigma.api.view.entry.EntryReferenceView;
10import cuchaz.enigma.api.view.entry.EntryView;
8 11
12@ApiStatus.NonExtendable
9public interface GuiView { 13public interface GuiView {
10 @Nullable 14 @Nullable
11 ProjectView getProject(); 15 ProjectView getProject();
@@ -13,5 +17,14 @@ public interface GuiView {
13 @Nullable 17 @Nullable
14 EntryReferenceView getCursorReference(); 18 EntryReferenceView getCursorReference();
15 19
20 @Nullable
21 EntryView getCursorDeclaration();
22
16 JFrame getFrame(); 23 JFrame getFrame();
24
25 float getScale();
26
27 boolean isDarkTheme();
28
29 JEditorPane createEditorPane();
17} 30}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java b/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java
index b72e1c3a..c8cb384e 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/ProjectView.java
@@ -3,6 +3,7 @@ package cuchaz.enigma.api.view;
3import java.util.Collection; 3import java.util.Collection;
4import java.util.List; 4import java.util.List;
5 5
6import org.jetbrains.annotations.ApiStatus;
6import org.jetbrains.annotations.Nullable; 7import org.jetbrains.annotations.Nullable;
7import org.objectweb.asm.tree.ClassNode; 8import org.objectweb.asm.tree.ClassNode;
8 9
@@ -11,6 +12,7 @@ import cuchaz.enigma.api.DataInvalidationListener;
11import cuchaz.enigma.api.view.entry.EntryView; 12import cuchaz.enigma.api.view.entry.EntryView;
12import cuchaz.enigma.api.view.index.JarIndexView; 13import cuchaz.enigma.api.view.index.JarIndexView;
13 14
15@ApiStatus.NonExtendable
14public interface ProjectView { 16public interface ProjectView {
15 <T extends EntryView> T deobfuscate(T entry); 17 <T extends EntryView> T deobfuscate(T entry);
16 18
@@ -25,6 +27,8 @@ public interface ProjectView {
25 27
26 Collection<String> getProjectClasses(); 28 Collection<String> getProjectClasses();
27 29
30 Collection<String> getProjectAndLibraryClasses();
31
28 @Nullable 32 @Nullable
29 ClassNode getBytecode(String className); 33 ClassNode getBytecode(String className);
30 34
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassDefEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassDefEntryView.java
index 7cb18291..1fb6a7ff 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassDefEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassDefEntryView.java
@@ -1,7 +1,9 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
3import org.jetbrains.annotations.Nullable; 4import org.jetbrains.annotations.Nullable;
4 5
6@ApiStatus.NonExtendable
5public interface ClassDefEntryView extends ClassEntryView, DefEntryView { 7public interface ClassDefEntryView extends ClassEntryView, DefEntryView {
6 @Nullable 8 @Nullable
7 ClassEntryView getSuperClass(); 9 ClassEntryView getSuperClass();
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassEntryView.java
index 085188ba..330920bd 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/ClassEntryView.java
@@ -1,7 +1,10 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
3import cuchaz.enigma.translation.representation.entry.ClassEntry; 5import cuchaz.enigma.translation.representation.entry.ClassEntry;
4 6
7@ApiStatus.NonExtendable
5public interface ClassEntryView extends EntryView { 8public interface ClassEntryView extends EntryView {
6 ClassEntryView getParent(); 9 ClassEntryView getParent();
7 10
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/DefEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/DefEntryView.java
index bf246fb9..a52e685b 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/DefEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/DefEntryView.java
@@ -1,5 +1,8 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
5@ApiStatus.NonExtendable
3public interface DefEntryView { 6public interface DefEntryView {
4 int getAccessFlags(); 7 int getAccessFlags();
5} 8}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryReferenceView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryReferenceView.java
index d49aa1ee..6ca87ab2 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryReferenceView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryReferenceView.java
@@ -1,5 +1,8 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
5@ApiStatus.NonExtendable
3public interface EntryReferenceView { 6public interface EntryReferenceView {
4 EntryView getEntry(); 7 EntryView getEntry();
5} 8}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryView.java
index 2dad5629..963c1e7e 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/EntryView.java
@@ -1,7 +1,9 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
3import org.jetbrains.annotations.Nullable; 4import org.jetbrains.annotations.Nullable;
4 5
6@ApiStatus.NonExtendable
5public interface EntryView { 7public interface EntryView {
6 /** 8 /**
7 * Returns the default name of this entry. 9 * Returns the default name of this entry.
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/FieldEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/FieldEntryView.java
index ae44458c..bf228140 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/FieldEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/FieldEntryView.java
@@ -1,9 +1,12 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
3import cuchaz.enigma.translation.representation.TypeDescriptor; 5import cuchaz.enigma.translation.representation.TypeDescriptor;
4import cuchaz.enigma.translation.representation.entry.ClassEntry; 6import cuchaz.enigma.translation.representation.entry.ClassEntry;
5import cuchaz.enigma.translation.representation.entry.FieldEntry; 7import cuchaz.enigma.translation.representation.entry.FieldEntry;
6 8
9@ApiStatus.NonExtendable
7public interface FieldEntryView extends EntryView { 10public interface FieldEntryView extends EntryView {
8 String getDescriptor(); 11 String getDescriptor();
9 12
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableDefEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableDefEntryView.java
index d0915602..32e0af3e 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableDefEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableDefEntryView.java
@@ -1,5 +1,8 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
5@ApiStatus.NonExtendable
3public interface LocalVariableDefEntryView extends LocalVariableEntryView { 6public interface LocalVariableDefEntryView extends LocalVariableEntryView {
4 String getDescriptor(); 7 String getDescriptor();
5} 8}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableEntryView.java
index 391bc0f0..b4728b58 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/LocalVariableEntryView.java
@@ -1,9 +1,19 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
5import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
6import cuchaz.enigma.translation.representation.entry.MethodEntry;
7
8@ApiStatus.NonExtendable
3public interface LocalVariableEntryView extends EntryView { 9public interface LocalVariableEntryView extends EntryView {
4 int getIndex(); 10 int getIndex();
5 11
6 boolean isArgument(); 12 boolean isArgument();
7 13
8 MethodEntryView getParent(); 14 MethodEntryView getParent();
15
16 static LocalVariableEntryView create(MethodEntryView parent, int index, String name, boolean argument) {
17 return new LocalVariableEntry((MethodEntry) parent, index, name, argument, null);
18 }
9} 19}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/entry/MethodEntryView.java b/enigma/src/main/java/cuchaz/enigma/api/view/entry/MethodEntryView.java
index 7d7fcd2d..3020afa4 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/entry/MethodEntryView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/entry/MethodEntryView.java
@@ -1,9 +1,12 @@
1package cuchaz.enigma.api.view.entry; 1package cuchaz.enigma.api.view.entry;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
3import cuchaz.enigma.translation.representation.MethodDescriptor; 5import cuchaz.enigma.translation.representation.MethodDescriptor;
4import cuchaz.enigma.translation.representation.entry.ClassEntry; 6import cuchaz.enigma.translation.representation.entry.ClassEntry;
5import cuchaz.enigma.translation.representation.entry.MethodEntry; 7import cuchaz.enigma.translation.representation.entry.MethodEntry;
6 8
9@ApiStatus.NonExtendable
7public interface MethodEntryView extends EntryView { 10public interface MethodEntryView extends EntryView {
8 String getDescriptor(); 11 String getDescriptor();
9 12
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/index/BridgeMethodIndexView.java b/enigma/src/main/java/cuchaz/enigma/api/view/index/BridgeMethodIndexView.java
new file mode 100644
index 00000000..c07be882
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/index/BridgeMethodIndexView.java
@@ -0,0 +1,15 @@
1package cuchaz.enigma.api.view.index;
2
3import org.jetbrains.annotations.ApiStatus;
4import org.jetbrains.annotations.Nullable;
5
6import cuchaz.enigma.api.view.entry.MethodEntryView;
7
8@ApiStatus.NonExtendable
9public interface BridgeMethodIndexView {
10 @Nullable
11 MethodEntryView getBridgeFromSpecialized(MethodEntryView specialized);
12
13 @Nullable
14 MethodEntryView getSpecializedFromBridge(MethodEntryView bridge);
15}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/index/EntryIndexView.java b/enigma/src/main/java/cuchaz/enigma/api/view/index/EntryIndexView.java
index 56bf70ee..9b4fbaf4 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/index/EntryIndexView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/index/EntryIndexView.java
@@ -2,10 +2,13 @@ package cuchaz.enigma.api.view.index;
2 2
3import java.util.Collection; 3import java.util.Collection;
4 4
5import org.jetbrains.annotations.ApiStatus;
6
5import cuchaz.enigma.api.view.entry.ClassDefEntryView; 7import cuchaz.enigma.api.view.entry.ClassDefEntryView;
6import cuchaz.enigma.api.view.entry.ClassEntryView; 8import cuchaz.enigma.api.view.entry.ClassEntryView;
7import cuchaz.enigma.api.view.entry.EntryView; 9import cuchaz.enigma.api.view.entry.EntryView;
8 10
11@ApiStatus.NonExtendable
9public interface EntryIndexView { 12public interface EntryIndexView {
10 boolean hasEntry(EntryView entry); 13 boolean hasEntry(EntryView entry);
11 int getAccess(EntryView entry); 14 int getAccess(EntryView entry);
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/index/InheritanceIndexView.java b/enigma/src/main/java/cuchaz/enigma/api/view/index/InheritanceIndexView.java
index a016d119..a156155c 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/index/InheritanceIndexView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/index/InheritanceIndexView.java
@@ -2,8 +2,11 @@ package cuchaz.enigma.api.view.index;
2 2
3import java.util.Collection; 3import java.util.Collection;
4 4
5import org.jetbrains.annotations.ApiStatus;
6
5import cuchaz.enigma.api.view.entry.ClassEntryView; 7import cuchaz.enigma.api.view.entry.ClassEntryView;
6 8
9@ApiStatus.NonExtendable
7public interface InheritanceIndexView { 10public interface InheritanceIndexView {
8 Collection<? extends ClassEntryView> getParents(ClassEntryView entry); 11 Collection<? extends ClassEntryView> getParents(ClassEntryView entry);
9 Collection<? extends ClassEntryView> getChildren(ClassEntryView entry); 12 Collection<? extends ClassEntryView> getChildren(ClassEntryView entry);
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/index/JarIndexView.java b/enigma/src/main/java/cuchaz/enigma/api/view/index/JarIndexView.java
index 069b1156..b723267f 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/index/JarIndexView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/index/JarIndexView.java
@@ -1,7 +1,11 @@
1package cuchaz.enigma.api.view.index; 1package cuchaz.enigma.api.view.index;
2 2
3import org.jetbrains.annotations.ApiStatus;
4
5@ApiStatus.NonExtendable
3public interface JarIndexView { 6public interface JarIndexView {
4 EntryIndexView getEntryIndex(); 7 EntryIndexView getEntryIndex();
5 InheritanceIndexView getInheritanceIndex(); 8 InheritanceIndexView getInheritanceIndex();
6 ReferenceIndexView getReferenceIndex(); 9 ReferenceIndexView getReferenceIndex();
10 BridgeMethodIndexView getBridgeMethodIndex();
7} 11}
diff --git a/enigma/src/main/java/cuchaz/enigma/api/view/index/ReferenceIndexView.java b/enigma/src/main/java/cuchaz/enigma/api/view/index/ReferenceIndexView.java
index 00ab84ed..7c0653ff 100644
--- a/enigma/src/main/java/cuchaz/enigma/api/view/index/ReferenceIndexView.java
+++ b/enigma/src/main/java/cuchaz/enigma/api/view/index/ReferenceIndexView.java
@@ -2,11 +2,14 @@ package cuchaz.enigma.api.view.index;
2 2
3import java.util.Collection; 3import java.util.Collection;
4 4
5import org.jetbrains.annotations.ApiStatus;
6
5import cuchaz.enigma.api.view.entry.ClassEntryView; 7import cuchaz.enigma.api.view.entry.ClassEntryView;
6import cuchaz.enigma.api.view.entry.EntryReferenceView; 8import cuchaz.enigma.api.view.entry.EntryReferenceView;
7import cuchaz.enigma.api.view.entry.FieldEntryView; 9import cuchaz.enigma.api.view.entry.FieldEntryView;
8import cuchaz.enigma.api.view.entry.MethodEntryView; 10import cuchaz.enigma.api.view.entry.MethodEntryView;
9 11
12@ApiStatus.NonExtendable
10public interface ReferenceIndexView { 13public interface ReferenceIndexView {
11 Collection<? extends MethodEntryView> getMethodsReferencedBy(MethodEntryView entry); 14 Collection<? extends MethodEntryView> getMethodsReferencedBy(MethodEntryView entry);
12 Collection<? extends EntryReferenceView> getReferencesToClass(ClassEntryView entry); 15 Collection<? extends EntryReferenceView> getReferencesToClass(ClassEntryView entry);
diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java
index fec20e25..51636e3a 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java
@@ -7,6 +7,8 @@ import java.util.List;
7import java.util.Map; 7import java.util.Map;
8import java.util.TreeMap; 8import java.util.TreeMap;
9 9
10import org.jetbrains.annotations.Nullable;
11
10import cuchaz.enigma.analysis.EntryReference; 12import cuchaz.enigma.analysis.EntryReference;
11import cuchaz.enigma.translation.mapping.EntryResolver; 13import cuchaz.enigma.translation.mapping.EntryResolver;
12import cuchaz.enigma.translation.mapping.ResolutionStrategy; 14import cuchaz.enigma.translation.mapping.ResolutionStrategy;
@@ -17,11 +19,13 @@ public class SourceIndex {
17 private List<Integer> lineOffsets; 19 private List<Integer> lineOffsets;
18 private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference; 20 private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference;
19 private final Map<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> referenceToTokens; 21 private final Map<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> referenceToTokens;
22 private final TreeMap<Token, Entry<?>> tokenToDeclaration;
20 private final Map<Entry<?>, Token> declarationToToken; 23 private final Map<Entry<?>, Token> declarationToToken;
21 24
22 public SourceIndex() { 25 public SourceIndex() {
23 tokenToReference = new TreeMap<>(); 26 tokenToReference = new TreeMap<>();
24 referenceToTokens = new HashMap<>(); 27 referenceToTokens = new HashMap<>();
28 tokenToDeclaration = new TreeMap<>();
25 declarationToToken = new HashMap<>(); 29 declarationToToken = new HashMap<>();
26 } 30 }
27 31
@@ -80,6 +84,11 @@ public class SourceIndex {
80 return declarationToToken.get(entry); 84 return declarationToToken.get(entry);
81 } 85 }
82 86
87 @Nullable
88 public Entry<?> getDeclaration(Token token) {
89 return tokenToDeclaration.get(token);
90 }
91
83 public void addDeclaration(Token token, Entry<?> deobfEntry) { 92 public void addDeclaration(Token token, Entry<?> deobfEntry) {
84 if (token != null) { 93 if (token != null) {
85 EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text); 94 EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text);
@@ -88,6 +97,7 @@ public class SourceIndex {
88 .add(token); 97 .add(token);
89 referenceToTokens.computeIfAbsent(EntryReference.declaration(deobfEntry, token.text), key -> new ArrayList<>()) 98 referenceToTokens.computeIfAbsent(EntryReference.declaration(deobfEntry, token.text), key -> new ArrayList<>())
90 .add(token); 99 .add(token);
100 tokenToDeclaration.put(token, deobfEntry);
91 declarationToToken.put(deobfEntry, token); 101 declarationToToken.put(deobfEntry, token);
92 } 102 }
93 } 103 }
@@ -108,6 +118,7 @@ public class SourceIndex {
108 return tokenToReference.keySet(); 118 return tokenToReference.keySet();
109 } 119 }
110 120
121 @Nullable
111 public Token getReferenceToken(int pos) { 122 public Token getReferenceToken(int pos) {
112 Token token = tokenToReference.floorKey(new Token(pos, pos, null)); 123 Token token = tokenToReference.floorKey(new Token(pos, pos, null));
113 124
@@ -153,7 +164,9 @@ public class SourceIndex {
153 SourceIndex remapped = new SourceIndex(result.getSource()); 164 SourceIndex remapped = new SourceIndex(result.getSource());
154 165
155 for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) { 166 for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) {
156 remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); 167 Token remappedToken = result.getRemappedToken(entry.getValue());
168 remapped.declarationToToken.put(entry.getKey(), remappedToken);
169 remapped.tokenToDeclaration.put(remappedToken, entry.getKey());
157 } 170 }
158 171
159 for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.entrySet()) { 172 for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.entrySet()) {
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/IconLoadingService.java b/enigma/src/main/java/cuchaz/enigma/utils/IconLoadingService.java
new file mode 100644
index 00000000..0e66fba7
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/IconLoadingService.java
@@ -0,0 +1,14 @@
1package cuchaz.enigma.utils;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.util.ServiceLoader;
6
7import cuchaz.enigma.api.EnigmaIcon;
8
9public interface IconLoadingService {
10 IconLoadingService INSTANCE = ServiceLoader.load(IconLoadingService.class).findFirst()
11 .orElseThrow(() -> new IllegalStateException("Trying to load icon on headless Enigma"));
12
13 EnigmaIcon loadIcon(InputStream in) throws IOException;
14}