From 87581ffe8d23aaf8ad677ffbb9de1ecfec3cfe80 Mon Sep 17 00:00:00 2001 From: Joseph Burton Date: Sat, 18 Oct 2025 16:38:40 +0100 Subject: 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--- .../src/main/java/cuchaz/enigma/gui/Gui.java | 6 + .../main/java/cuchaz/enigma/gui/GuiController.java | 27 +++ .../java/cuchaz/enigma/gui/config/LookAndFeel.java | 7 +- .../cuchaz/enigma/gui/elements/GutterIcon.java | 42 ++++ .../java/cuchaz/enigma/gui/panels/EditorPanel.java | 83 ++++++- .../java/cuchaz/enigma/gui/panels/GutterPanel.java | 238 +++++++++++++++++++++ .../cuchaz/enigma/gui/util/EnigmaIconImpl.java | 8 + .../enigma/gui/util/IconLoadingServiceImpl.java | 16 ++ .../cuchaz.enigma.utils.IconLoadingService | 1 + .../src/main/java/cuchaz/enigma/EnigmaProject.java | 5 + .../enigma/analysis/index/BridgeMethodIndex.java | 16 +- .../cuchaz/enigma/analysis/index/JarIndex.java | 1 + .../cuchaz/enigma/api/DataInvalidationEvent.java | 2 + .../main/java/cuchaz/enigma/api/EnigmaIcon.java | 39 ++++ .../java/cuchaz/enigma/api/service/GuiService.java | 18 ++ .../main/java/cuchaz/enigma/api/view/GuiView.java | 13 ++ .../java/cuchaz/enigma/api/view/ProjectView.java | 4 + .../enigma/api/view/entry/ClassDefEntryView.java | 2 + .../enigma/api/view/entry/ClassEntryView.java | 3 + .../cuchaz/enigma/api/view/entry/DefEntryView.java | 3 + .../enigma/api/view/entry/EntryReferenceView.java | 3 + .../cuchaz/enigma/api/view/entry/EntryView.java | 2 + .../enigma/api/view/entry/FieldEntryView.java | 3 + .../api/view/entry/LocalVariableDefEntryView.java | 3 + .../api/view/entry/LocalVariableEntryView.java | 10 + .../enigma/api/view/entry/MethodEntryView.java | 3 + .../api/view/index/BridgeMethodIndexView.java | 15 ++ .../enigma/api/view/index/EntryIndexView.java | 3 + .../api/view/index/InheritanceIndexView.java | 3 + .../cuchaz/enigma/api/view/index/JarIndexView.java | 4 + .../enigma/api/view/index/ReferenceIndexView.java | 3 + .../java/cuchaz/enigma/source/SourceIndex.java | 15 +- .../cuchaz/enigma/utils/IconLoadingService.java | 14 ++ 33 files changed, 604 insertions(+), 11 deletions(-) create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/GutterIcon.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/panels/GutterPanel.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/EnigmaIconImpl.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/IconLoadingServiceImpl.java create mode 100644 enigma-swing/src/main/resources/META-INF/services/cuchaz.enigma.utils.IconLoadingService create mode 100644 enigma/src/main/java/cuchaz/enigma/api/EnigmaIcon.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/view/index/BridgeMethodIndexView.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/IconLoadingService.java 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 { return activeEditor == null ? null : activeEditor.getCursorReference(); } + @Nullable + public Entry getCursorDeclaration() { + EditorPanel activeEditor = this.editorTabbedPane.getActiveEditor(); + return activeEditor == null ? null : activeEditor.getCursorDeclaration(); + } + public void startDocChange(EditorPanel editor) { EntryReference, Entry> cursorReference = editor.getCursorReference(); 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; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -52,12 +53,15 @@ import cuchaz.enigma.api.service.ObfuscationTestService; import cuchaz.enigma.api.service.ProjectService; import cuchaz.enigma.api.view.GuiView; import cuchaz.enigma.api.view.entry.EntryReferenceView; +import cuchaz.enigma.api.view.entry.EntryView; import cuchaz.enigma.classhandle.ClassHandle; import cuchaz.enigma.classhandle.ClassHandleProvider; +import cuchaz.enigma.gui.config.LookAndFeel; import cuchaz.enigma.gui.config.NetConfig; import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.gui.newabstraction.EntryValidation; +import cuchaz.enigma.gui.panels.EditorPanel; import cuchaz.enigma.gui.stats.StatsGenerator; import cuchaz.enigma.gui.stats.StatsMember; import cuchaz.enigma.gui.util.History; @@ -130,6 +134,23 @@ public class GuiController implements ClientPacketHandler, GuiView, DataInvalida return gui.getFrame(); } + @Override + public float getScale() { + return UiConfig.getActiveScaleFactor(); + } + + @Override + public boolean isDarkTheme() { + return LookAndFeel.isDarkLaf(); + } + + @Override + public JEditorPane createEditorPane() { + JEditorPane editor = new JEditorPane(); + EditorPanel.customizeEditor(editor); + return editor; + } + public boolean isDirty() { return project != null && project.getMapper().isDirty(); } @@ -349,6 +370,12 @@ public class GuiController implements ClientPacketHandler, GuiView, DataInvalida return gui.getCursorReference(); } + @Override + @Nullable + public EntryView getCursorDeclaration() { + return gui.getCursorDeclaration(); + } + /** * Navigates to the declaration with respect to navigation history. * 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 { // the "JVM default" look and feel, get it at the beginning and store it so we can set it later private static final javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); private final boolean needsScaling; + private static Boolean isDarkLaf = null; LookAndFeel(boolean needsScaling) { this.needsScaling = needsScaling; @@ -52,6 +53,10 @@ public enum LookAndFeel { } public static boolean isDarkLaf() { + if (isDarkLaf != null) { + return isDarkLaf; + } + // 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 JPanel panel = new JPanel(); panel.setSize(new Dimension(10, 10)); @@ -64,6 +69,6 @@ public enum LookAndFeel { // convert the color we got to grayscale int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); - return b < 85; + return isDarkLaf = b < 85; } } 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 @@ +package cuchaz.enigma.gui.elements; + +import java.awt.Cursor; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +import javax.swing.JButton; + +import cuchaz.enigma.api.service.GuiService; +import cuchaz.enigma.gui.util.EnigmaIconImpl; +import cuchaz.enigma.gui.util.ScaleUtil; + +public class GutterIcon extends JButton implements GuiService.GutterMarkerBuilder { + private Runnable clickAction = () -> { }; + + public GutterIcon(EnigmaIconImpl icon) { + super(icon.icon()); + setContentAreaFilled(false); + setCursor(Cursor.getDefaultCursor()); + addActionListener(e -> clickAction.run()); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + setIcon(icon.icon().derive(ScaleUtil.invert(getWidth()), ScaleUtil.invert(getHeight()))); + } + }); + } + + @Override + public GuiService.GutterMarkerBuilder setClickAction(Runnable action) { + this.clickAction = action; + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + return this; + } + + @Override + public GuiService.GutterMarkerBuilder setTooltip(String tooltip) { + setToolTipText(tooltip); + return this; + } +} 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; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -35,10 +36,12 @@ import javax.swing.text.Highlighter.HighlightPainter; import de.sciss.syntaxpane.DefaultSyntaxKit; import de.sciss.syntaxpane.SyntaxDocument; +import de.sciss.syntaxpane.actions.ActionUtils; import org.jetbrains.annotations.Nullable; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.api.service.GuiService; import cuchaz.enigma.classhandle.ClassHandle; import cuchaz.enigma.classhandle.ClassHandleError; import cuchaz.enigma.events.ClassHandleListener; @@ -50,14 +53,17 @@ import cuchaz.enigma.gui.config.LookAndFeel; import cuchaz.enigma.gui.config.Themes; import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.elements.EditorPopupMenu; +import cuchaz.enigma.gui.elements.GutterIcon; import cuchaz.enigma.gui.events.EditorActionListener; import cuchaz.enigma.gui.events.ThemeChangeListener; import cuchaz.enigma.gui.highlight.BoxHighlightPainter; import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; +import cuchaz.enigma.gui.util.EnigmaIconImpl; import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; import cuchaz.enigma.gui.util.ScaleUtil; import cuchaz.enigma.source.DecompiledClassSource; import cuchaz.enigma.source.RenamableTokenType; +import cuchaz.enigma.source.SourceIndex; import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.mapping.EntryRemapper; import cuchaz.enigma.translation.mapping.EntryResolver; @@ -65,12 +71,14 @@ import cuchaz.enigma.translation.mapping.ResolutionStrategy; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.utils.Pair; import cuchaz.enigma.utils.Result; public class EditorPanel { private final JPanel ui = new JPanel(); private final JEditorPane editor = new JEditorPane(); private final JScrollPane editorScrollPane = new JScrollPane(this.editor); + private final GutterPanel gutterPanel; private final EditorPopupMenu popupMenu; // progress UI @@ -109,20 +117,16 @@ public class EditorPanel { this.gui = gui; this.controller = gui.getController(); - this.editor.setEditable(false); - this.editor.setSelectionColor(new Color(31, 46, 90)); - this.editor.setCaret(new BrowserCaret()); + customizeEditor(this.editor); this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); - this.editor.setCaretColor(UiConfig.getCaretColor()); - this.editor.setContentType("text/enigma-sources"); - this.editor.setBackground(UiConfig.getEditorBackgroundColor()); - DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); - kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); // set unit increment to height of one line, the amount scrolled per // mouse wheel rotation is then controlled by OS settings this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight()); + this.gutterPanel = new GutterPanel(this.editor, (JComponent) this.editorScrollPane.getRowHeader().getView()); + this.editorScrollPane.setRowHeaderView(this.gutterPanel); + // init editor popup menu this.popupMenu = new EditorPopupMenu(this, gui); this.editor.setComponentPopupMenu(this.popupMenu.getUi()); @@ -255,6 +259,17 @@ public class EditorPanel { this.ui.putClientProperty(EditorPanel.class, this); } + public static void customizeEditor(JEditorPane editor) { + editor.setEditable(false); + editor.setSelectionColor(new Color(31, 46, 90)); + editor.setCaret(new BrowserCaret()); + editor.setCaretColor(UiConfig.getCaretColor()); + editor.setContentType("text/enigma-sources"); + editor.setBackground(UiConfig.getEditorBackgroundColor()); + DefaultSyntaxKit kit = (DefaultSyntaxKit) editor.getEditorKit(); + kit.toggleComponent(editor, "de.sciss.syntaxpane.components.TokenMarker"); + } + @Nullable public static EditorPanel byUi(Component ui) { if (ui instanceof JComponent) { @@ -512,6 +527,7 @@ public class EditorPanel { this.editor.setCaretPosition(newCaretPos); } + addGutterMarkers(source.getIndex()); setHighlightedTokens(source.getHighlightedTokens()); setCursorReference(getReference(getToken(this.editor.getCaretPosition()))); } finally { @@ -524,6 +540,44 @@ public class EditorPanel { } } + private void addGutterMarkers(SourceIndex sourceIndex) { + List services = this.gui.getController().enigma.getServices().get(GuiService.TYPE); + + if (services.isEmpty()) { + return; + } + + this.gutterPanel.clearMarkers(); + + List, Token>> declarationTokens = new ArrayList<>(); + + for (Entry declaration : sourceIndex.declarations()) { + declarationTokens.add(new Pair<>(declaration, sourceIndex.getDeclarationToken(declaration))); + } + + declarationTokens.sort(Comparator.comparing(pair -> pair.b)); + + for (Pair, Token> declaration : declarationTokens) { + int lineNumber; + + try { + lineNumber = ActionUtils.getLineNumber(this.editor, declaration.b.start); + } catch (BadLocationException e) { + continue; + } + + for (GuiService service : services) { + service.addGutterMarkers(this.gui.getController(), declaration.a, (icon, alignment) -> { + GutterIcon button = new GutterIcon((EnigmaIconImpl) icon); + this.gutterPanel.addMarker(lineNumber, alignment, button); + return button; + }); + } + } + + this.editor.revalidate(); + } + public void setHighlightedTokens(Map> tokens) { // remove any old highlighters this.editor.getHighlighter().removeAllHighlights(); @@ -565,10 +619,23 @@ public class EditorPanel { } } + @Nullable public EntryReference, Entry> getCursorReference() { return this.cursorReference; } + @Nullable + public Entry getCursorDeclaration() { + int pos = this.editor.getCaretPosition(); + Token token = getToken(pos); + + if (token == null) { + return null; + } + + return this.source.getIndex().getDeclaration(token); + } + public void showReference(EntryReference, Entry> reference) { if (this.mode == DisplayMode.SUCCESS) { 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 @@ +package cuchaz.enigma.gui.panels; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Insets; +import java.awt.LayoutManager2; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; + +import de.sciss.syntaxpane.actions.ActionUtils; + +import cuchaz.enigma.api.service.GuiService; +import cuchaz.enigma.gui.config.UiConfig; + +public class GutterPanel extends JPanel { + private final JPanel markerPanel; + + public GutterPanel(JEditorPane editor, JComponent lineNumbers) { + markerPanel = new MarkerPanel(editor); + + setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + add(lineNumbers); + add(markerPanel); + + setBackground(UiConfig.getLineNumbersBackgroundColor()); + setForeground(UiConfig.getLineNumbersForegroundColor()); + markerPanel.setBackground(UiConfig.getLineNumbersBackgroundColor()); + markerPanel.setForeground(UiConfig.getLineNumbersForegroundColor()); + setBorder(lineNumbers.getBorder()); + lineNumbers.setBorder(null); + } + + public void clearMarkers() { + markerPanel.removeAll(); + } + + public void addMarker(int line, GuiService.GutterMarkerAlignment alignment, Component marker) { + markerPanel.add(marker, new MarkerLayout.Constraint(line, alignment)); + } + + private static class MarkerPanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener { + private final JEditorPane editor; + private final Color currentLineColor = UiConfig.getLineNumbersSelectedColor(); + + private MarkerPanel(JEditorPane editor) { + this.editor = editor; + setLayout(new MarkerLayout()); + + Insets editorInsets = editor.getInsets(); + + if (editorInsets.top != 0 || editorInsets.bottom != 0) { + setBorder(BorderFactory.createEmptyBorder(editorInsets.top, 0, editorInsets.bottom, 0)); + } + + setFont(editor.getFont()); + + editor.addCaretListener(this); + editor.getDocument().addDocumentListener(this); + editor.addPropertyChangeListener(this); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + int currentLine; + + try { + currentLine = ActionUtils.getLineNumber(editor, editor.getCaretPosition()); + } catch (BadLocationException ex) { + return; // no valid caret -> nothing to draw + } + + FontMetrics fm = getFontMetrics(getFont()); + int lh = fm.getHeight(); + Insets insets = getInsets(); + + int y = currentLine * lh; + + g.setColor(currentLineColor); + g.fillRect(0, y, getWidth(), lh); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("document")) { + repaint(); + } + } + + @Override + public void caretUpdate(CaretEvent e) { + repaint(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + documentChanged(); + } + + private void documentChanged() { + SwingUtilities.invokeLater(this::repaint); + } + } + + private static class MarkerLayout implements LayoutManager2 { + private static final int GAP = 2; + private static final int HUGE_HEIGHT = 0x100000; // taken from LineNumbersRuler + + private final Map constraints = new HashMap<>(); + + @Override + public void addLayoutComponent(Component comp, Object constraints) { + this.constraints.put(comp, (Constraint) constraints); + } + + @Override + public Dimension maximumLayoutSize(Container target) { + return preferredLayoutSize(target); + } + + @Override + public float getLayoutAlignmentX(Container target) { + return 0.5f; + } + + @Override + public float getLayoutAlignmentY(Container target) { + return 0.5f; + } + + @Override + public void invalidateLayout(Container target) { + } + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + constraints.remove(comp); + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + synchronized (parent.getTreeLock()) { + Insets insets = parent.getInsets(); + + if (constraints.isEmpty()) { + return new Dimension(insets.left + insets.right, HUGE_HEIGHT); + } + + Map leftCount = new HashMap<>(); + Map rightCount = new HashMap<>(); + + for (Constraint constraint : constraints.values()) { + switch (constraint.alignment) { + case LEFT -> leftCount.merge(constraint.line, 1, Integer::sum); + case RIGHT -> rightCount.merge(constraint.line, 1, Integer::sum); + } + } + + int maxLeft = leftCount.values().stream().mapToInt(Integer::intValue).max().orElse(0); + int maxRight = rightCount.values().stream().mapToInt(Integer::intValue).max().orElse(0); + + int lineHeight = parent.getFontMetrics(parent.getFont()).getHeight(); + return new Dimension( + GAP + insets.left + insets.right + (maxLeft + maxRight) * lineHeight, + HUGE_HEIGHT + ); + } + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return preferredLayoutSize(parent); + } + + @Override + public void layoutContainer(Container parent) { + synchronized (parent.getTreeLock()) { + Map leftCount = new HashMap<>(); + Map rightCount = new HashMap<>(); + + int lineHeight = parent.getFontMetrics(parent.getFont()).getHeight(); + + int numComponents = parent.getComponentCount(); + + for (int i = 0; i < numComponents; i++) { + Component comp = parent.getComponent(i); + Constraint constraint = constraints.get(comp); + + if (constraint == null) { + continue; + } + + int left = switch (constraint.alignment) { + case LEFT -> GAP + (leftCount.merge(constraint.line, 1, Integer::sum) - 1) * lineHeight + GAP / 2; + case RIGHT -> parent.getWidth() - rightCount.merge(constraint.line, 1, Integer::sum) * lineHeight + GAP / 2; + }; + comp.setBounds(left, constraint.line * lineHeight + GAP / 2, lineHeight - GAP, lineHeight - GAP); + } + } + } + + private record Constraint(int line, GuiService.GutterMarkerAlignment alignment) { + } + } +} 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 @@ +package cuchaz.enigma.gui.util; + +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import cuchaz.enigma.api.EnigmaIcon; + +public record EnigmaIconImpl(FlatSVGIcon icon) implements EnigmaIcon { +} 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 @@ +package cuchaz.enigma.gui.util; + +import java.io.IOException; +import java.io.InputStream; + +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import cuchaz.enigma.api.EnigmaIcon; +import cuchaz.enigma.utils.IconLoadingService; + +public class IconLoadingServiceImpl implements IconLoadingService { + @Override + public EnigmaIcon loadIcon(InputStream in) throws IOException { + return new EnigmaIconImpl(new FlatSVGIcon(in)); + } +} 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 @@ -409,6 +409,11 @@ public class EnigmaProject implements ProjectView { return projectClasses; } + @Override + public Collection getProjectAndLibraryClasses() { + return classProvider.getClassNames(); + } + @Override @Nullable public ClassNode getBytecode(String 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; import org.jetbrains.annotations.Nullable; +import cuchaz.enigma.api.view.entry.MethodEntryView; +import cuchaz.enigma.api.view.index.BridgeMethodIndexView; import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.MethodDescriptor; import cuchaz.enigma.translation.representation.TypeDescriptor; @@ -17,7 +19,7 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; -public class BridgeMethodIndex implements JarIndexer { +public class BridgeMethodIndex implements JarIndexer, BridgeMethodIndexView { private final EntryIndex entryIndex; private final InheritanceIndex inheritanceIndex; private final ReferenceIndex referenceIndex; @@ -154,6 +156,18 @@ public class BridgeMethodIndex implements JarIndexer { return bridgeToSpecialized.get(bridge); } + @Override + @Nullable + public MethodEntryView getBridgeFromSpecialized(MethodEntryView specialized) { + return getBridgeFromSpecialized((MethodEntry) specialized); + } + + @Override + @Nullable + public MethodEntryView getSpecializedFromBridge(MethodEntryView bridge) { + return getSpecializedFromBridge((MethodEntry) bridge); + } + /** Includes "renamed specialized -> bridge" entries. */ public Map getSpecializedToBridge() { 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 { return referenceIndex; } + @Override public BridgeMethodIndex getBridgeMethodIndex() { return bridgeMethodIndex; } 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; import java.util.Collection; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +@ApiStatus.NonExtendable public interface DataInvalidationEvent { /** * 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 @@ +package cuchaz.enigma.api; + +import java.io.IOException; +import java.io.InputStream; + +import org.jetbrains.annotations.ApiStatus; + +import cuchaz.enigma.utils.IconLoadingService; + +@ApiStatus.NonExtendable +public interface EnigmaIcon { + /** + * Loads an icon resource from the given SVG resource path. + * + * @param resource the path to the resource to be loaded + * @return The loaded icon + * @throws IOException if the resource could not be found or an error occurred while reading it + */ + static EnigmaIcon loadResource(String resource) throws IOException { + try (InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource)) { + if (in == null) { + throw new IOException("Could not find resource: " + resource); + } + + return load(in); + } + } + + /** + * Loads an icon in SVG format from the given input stream. + * + * @param in the input stream to load from + * @return The loaded icon + * @throws IOException if the stream throws an error + */ + static EnigmaIcon load(InputStream in) throws IOException { + return IconLoadingService.INSTANCE.loadIcon(in); + } +} 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; import javax.swing.KeyStroke; +import cuchaz.enigma.api.EnigmaIcon; import cuchaz.enigma.api.view.GuiView; +import cuchaz.enigma.api.view.entry.EntryView; public interface GuiService extends EnigmaService { EnigmaServiceType TYPE = EnigmaServiceType.create("gui"); @@ -16,6 +18,9 @@ public interface GuiService extends EnigmaService { default void addToEditorContextMenu(GuiView gui, MenuRegistrar registrar) { } + default void addGutterMarkers(GuiView gui, EntryView entry, GutterMarkerAdder gutter) { + } + interface MenuRegistrar { void addSeparator(); @@ -31,4 +36,17 @@ public interface GuiService extends EnigmaService { MenuItemBuilder setEnabledWhen(BooleanSupplier condition); MenuItemBuilder setAction(Runnable action); } + + interface GutterMarkerAdder { + GutterMarkerBuilder addMarker(EnigmaIcon icon, GutterMarkerAlignment alignment); + } + + interface GutterMarkerBuilder { + GutterMarkerBuilder setClickAction(Runnable action); + GutterMarkerBuilder setTooltip(String tooltip); + } + + enum GutterMarkerAlignment { + LEFT, RIGHT + } } 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 @@ package cuchaz.enigma.api.view; +import javax.swing.JEditorPane; import javax.swing.JFrame; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import cuchaz.enigma.api.view.entry.EntryReferenceView; +import cuchaz.enigma.api.view.entry.EntryView; +@ApiStatus.NonExtendable public interface GuiView { @Nullable ProjectView getProject(); @@ -13,5 +17,14 @@ public interface GuiView { @Nullable EntryReferenceView getCursorReference(); + @Nullable + EntryView getCursorDeclaration(); + JFrame getFrame(); + + float getScale(); + + boolean isDarkTheme(); + + JEditorPane createEditorPane(); } 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; import java.util.Collection; import java.util.List; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; @@ -11,6 +12,7 @@ import cuchaz.enigma.api.DataInvalidationListener; import cuchaz.enigma.api.view.entry.EntryView; import cuchaz.enigma.api.view.index.JarIndexView; +@ApiStatus.NonExtendable public interface ProjectView { T deobfuscate(T entry); @@ -25,6 +27,8 @@ public interface ProjectView { Collection getProjectClasses(); + Collection getProjectAndLibraryClasses(); + @Nullable ClassNode getBytecode(String className); 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +@ApiStatus.NonExtendable public interface ClassDefEntryView extends ClassEntryView, DefEntryView { @Nullable 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.translation.representation.entry.ClassEntry; +@ApiStatus.NonExtendable public interface ClassEntryView extends EntryView { ClassEntryView getParent(); 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable public interface DefEntryView { int getAccessFlags(); } 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable public interface EntryReferenceView { EntryView getEntry(); } 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +@ApiStatus.NonExtendable public interface EntryView { /** * 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.FieldEntry; +@ApiStatus.NonExtendable public interface FieldEntryView extends EntryView { String getDescriptor(); 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable public interface LocalVariableDefEntryView extends LocalVariableEntryView { String getDescriptor(); } 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +@ApiStatus.NonExtendable public interface LocalVariableEntryView extends EntryView { int getIndex(); boolean isArgument(); MethodEntryView getParent(); + + static LocalVariableEntryView create(MethodEntryView parent, int index, String name, boolean argument) { + return new LocalVariableEntry((MethodEntry) parent, index, name, argument, null); + } } 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 @@ package cuchaz.enigma.api.view.entry; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.translation.representation.MethodDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; +@ApiStatus.NonExtendable public interface MethodEntryView extends EntryView { String getDescriptor(); 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 @@ +package cuchaz.enigma.api.view.index; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import cuchaz.enigma.api.view.entry.MethodEntryView; + +@ApiStatus.NonExtendable +public interface BridgeMethodIndexView { + @Nullable + MethodEntryView getBridgeFromSpecialized(MethodEntryView specialized); + + @Nullable + MethodEntryView getSpecializedFromBridge(MethodEntryView bridge); +} 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; import java.util.Collection; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.api.view.entry.ClassDefEntryView; import cuchaz.enigma.api.view.entry.ClassEntryView; import cuchaz.enigma.api.view.entry.EntryView; +@ApiStatus.NonExtendable public interface EntryIndexView { boolean hasEntry(EntryView entry); 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; import java.util.Collection; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.api.view.entry.ClassEntryView; +@ApiStatus.NonExtendable public interface InheritanceIndexView { Collection getParents(ClassEntryView entry); Collection 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 @@ package cuchaz.enigma.api.view.index; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable public interface JarIndexView { EntryIndexView getEntryIndex(); InheritanceIndexView getInheritanceIndex(); ReferenceIndexView getReferenceIndex(); + BridgeMethodIndexView getBridgeMethodIndex(); } 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; import java.util.Collection; +import org.jetbrains.annotations.ApiStatus; + import cuchaz.enigma.api.view.entry.ClassEntryView; import cuchaz.enigma.api.view.entry.EntryReferenceView; import cuchaz.enigma.api.view.entry.FieldEntryView; import cuchaz.enigma.api.view.entry.MethodEntryView; +@ApiStatus.NonExtendable public interface ReferenceIndexView { Collection getMethodsReferencedBy(MethodEntryView entry); Collection 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; import java.util.Map; import java.util.TreeMap; +import org.jetbrains.annotations.Nullable; + import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.ResolutionStrategy; @@ -17,11 +19,13 @@ public class SourceIndex { private List lineOffsets; private final TreeMap, Entry>> tokenToReference; private final Map, Entry>, Collection> referenceToTokens; + private final TreeMap> tokenToDeclaration; private final Map, Token> declarationToToken; public SourceIndex() { tokenToReference = new TreeMap<>(); referenceToTokens = new HashMap<>(); + tokenToDeclaration = new TreeMap<>(); declarationToToken = new HashMap<>(); } @@ -80,6 +84,11 @@ public class SourceIndex { return declarationToToken.get(entry); } + @Nullable + public Entry getDeclaration(Token token) { + return tokenToDeclaration.get(token); + } + public void addDeclaration(Token token, Entry deobfEntry) { if (token != null) { EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); @@ -88,6 +97,7 @@ public class SourceIndex { .add(token); referenceToTokens.computeIfAbsent(EntryReference.declaration(deobfEntry, token.text), key -> new ArrayList<>()) .add(token); + tokenToDeclaration.put(token, deobfEntry); declarationToToken.put(deobfEntry, token); } } @@ -108,6 +118,7 @@ public class SourceIndex { return tokenToReference.keySet(); } + @Nullable public Token getReferenceToken(int pos) { Token token = tokenToReference.floorKey(new Token(pos, pos, null)); @@ -153,7 +164,9 @@ public class SourceIndex { SourceIndex remapped = new SourceIndex(result.getSource()); for (Map.Entry, Token> entry : declarationToToken.entrySet()) { - remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); + Token remappedToken = result.getRemappedToken(entry.getValue()); + remapped.declarationToToken.put(entry.getKey(), remappedToken); + remapped.tokenToDeclaration.put(remappedToken, entry.getKey()); } for (Map.Entry, Entry>, Collection> 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 @@ +package cuchaz.enigma.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ServiceLoader; + +import cuchaz.enigma.api.EnigmaIcon; + +public interface IconLoadingService { + IconLoadingService INSTANCE = ServiceLoader.load(IconLoadingService.class).findFirst() + .orElseThrow(() -> new IllegalStateException("Trying to load icon on headless Enigma")); + + EnigmaIcon loadIcon(InputStream in) throws IOException; +} -- cgit v1.2.3