|
1 | 1 | package the.bytecode.club.jda.gui.fileviewer; |
2 | 2 |
|
3 | 3 | import org.fife.ui.rsyntaxtextarea.*; |
| 4 | +import org.fife.ui.rsyntaxtextarea.folding.Fold; |
| 5 | +import org.fife.ui.rsyntaxtextarea.folding.FoldManager; |
4 | 6 |
|
5 | 7 | import javax.swing.event.HyperlinkEvent; |
6 | 8 | import java.net.MalformedURLException; |
7 | 9 | import java.net.URL; |
| 10 | +import java.util.ArrayList; |
| 11 | +import java.util.HashMap; |
| 12 | +import java.util.List; |
| 13 | +import java.util.Map; |
8 | 14 |
|
9 | 15 | public class BytecodeSyntaxArea extends RSyntaxTextArea { |
| 16 | + public Map<Fold, List<Token>> tokenIndex; |
| 17 | + public boolean foldsBuilt; |
| 18 | + |
10 | 19 | public BytecodeSyntaxArea() { |
11 | 20 | setSyntaxEditingStyle(BytecodeTokenizer.SYNTAX_STYLE_BYTECODE); |
12 | 21 |
|
13 | 22 | setLinkScanningMask(0); |
14 | 23 | setLinkGenerator(new BytecodeLinkGenerator()); |
15 | | - addHyperlinkListener(e -> { |
16 | | - URL url = e.getURL(); |
17 | | - String data = url.getFile(); |
18 | | - switch (url.getProtocol()) { |
19 | | - case "label": |
20 | | - System.out.println(data); |
21 | | - setCaretPosition(0); |
22 | | - break; |
23 | | - } |
| 24 | + addHyperlinkListener(this::processClick); |
| 25 | + |
| 26 | + foldsBuilt = false; |
| 27 | + getFoldManager().addPropertyChangeListener(evt -> { |
| 28 | + if (evt.getPropertyName().equals(FoldManager.PROPERTY_FOLDS_UPDATED) |
| 29 | + && evt.getNewValue() != null) |
| 30 | + parseLabels(); |
24 | 31 | }); |
25 | 32 | } |
26 | 33 |
|
27 | | - private static class BytecodeLinkGenerator implements LinkGenerator { |
| 34 | + private void processClick(HyperlinkEvent e) { |
| 35 | + URL url = e.getURL(); |
| 36 | + String data = url.getFile(); |
| 37 | + switch (url.getProtocol()) { |
| 38 | + case "setcaret": |
| 39 | + setCaretPosition(Integer.parseInt(data)); |
| 40 | + break; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + private void parseLabels() { |
| 45 | + tokenIndex = new HashMap<>(); |
| 46 | + FoldManager foldManager = getFoldManager(); |
| 47 | + if (foldManager.getFoldCount() != 1) { |
| 48 | + System.err.println("Fold count isn't 1, rather " + foldManager.getFoldCount()); |
| 49 | + return; |
| 50 | + } |
| 51 | + parseLabels(null, foldManager.getFold(0)); |
| 52 | + foldsBuilt = true; |
| 53 | + } |
| 54 | + |
| 55 | + private void parseLabels(Fold parent, Fold f) { |
| 56 | + for (Token t = getTokenListForLine(f.getStartLine()); t != null; t = t.getNextToken()) { |
| 57 | + if (t.getType() == BytecodeTokenizer.TOKENTYPE_LABEL) { |
| 58 | + List<Token> methodTokens = tokenIndex.computeIfAbsent(parent, k -> new ArrayList<>()); |
| 59 | + methodTokens.add(new TokenImpl(t)); |
| 60 | + if (methodTokens.size() != Integer.parseInt(t.getLexeme().substring(1))) |
| 61 | + throw new IllegalArgumentException("Invalid token numbering: " + methodTokens.size() + "vs" + Integer.parseInt(t.getLexeme().substring(1))); |
| 62 | + break; |
| 63 | + } |
| 64 | + } |
| 65 | + for (int i = 0; i < f.getChildCount(); i++) { |
| 66 | + parseLabels(f, f.getChild(i)); |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + private Fold getMethodFold(Token t) { |
| 71 | + FoldManager foldManager = getFoldManager(); |
| 72 | + Fold rootFold = foldManager.getFold(0); |
| 73 | + Fold curFold = foldManager.getDeepestFoldContaining(t.getOffset()); |
| 74 | + while (curFold != null) { |
| 75 | + Fold parentFold = curFold.getParent(); |
| 76 | + if (parentFold == rootFold) |
| 77 | + return curFold; |
| 78 | + curFold = parentFold; |
| 79 | + } |
| 80 | + throw new IllegalArgumentException("Token is not parented in top-level (class def) fold"); |
| 81 | + } |
| 82 | + |
| 83 | + private Token findLabelDefinition(Token label) { |
| 84 | + if (label.getType() != BytecodeTokenizer.TOKENTYPE_LABEL) |
| 85 | + throw new IllegalArgumentException("Token is not a label"); |
| 86 | + Fold parentFold = getMethodFold(label); |
| 87 | + for (Token t : tokenIndex.get(parentFold)) { |
| 88 | + if (t.getLexeme().equals(label.getLexeme())) |
| 89 | + return t; |
| 90 | + } |
| 91 | + return null; |
| 92 | + } |
| 93 | + |
| 94 | + private class BytecodeLinkGenerator implements LinkGenerator { |
28 | 95 | @Override |
29 | 96 | public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offs) { |
30 | 97 | Token t = textArea.modelToToken(offs); |
31 | | - if (t.getType() == TokenTypes.PREPROCESSOR) { |
32 | | - return new LinkGeneratorResult() { |
33 | | - @Override |
34 | | - public HyperlinkEvent execute() { |
35 | | - try { |
36 | | - URL url = new URL(null, "label:" + t.getLexeme(), new JDAURLHandler()); |
37 | | - return new HyperlinkEvent(textArea, HyperlinkEvent.EventType.ACTIVATED, url); |
38 | | - } catch (MalformedURLException e) { |
39 | | - e.printStackTrace(); |
40 | | - return null; |
41 | | - } |
42 | | - } |
43 | | - |
44 | | - @Override |
45 | | - public int getSourceOffset() { |
46 | | - return offs; |
47 | | - } |
48 | | - }; |
| 98 | + if (foldsBuilt && t.getType() == BytecodeTokenizer.TOKENTYPE_LABEL) { |
| 99 | + Token labelDef = findLabelDefinition(t); |
| 100 | + if (labelDef == null) |
| 101 | + return null; |
| 102 | + int caretTarget = labelDef.getOffset(); |
| 103 | + return new SetCaretLinkResult(caretTarget, textArea, offs); |
49 | 104 | } |
50 | 105 | return null; |
51 | 106 | } |
52 | 107 | } |
| 108 | + |
| 109 | + private class SetCaretLinkResult implements LinkGeneratorResult { |
| 110 | + private final int caretTarget; |
| 111 | + private final RSyntaxTextArea textArea; |
| 112 | + private final int offs; |
| 113 | + |
| 114 | + public SetCaretLinkResult(int caretTarget, RSyntaxTextArea textArea, int offs) { |
| 115 | + this.caretTarget = caretTarget; |
| 116 | + this.textArea = textArea; |
| 117 | + this.offs = offs; |
| 118 | + } |
| 119 | + |
| 120 | + @Override |
| 121 | + public HyperlinkEvent execute() { |
| 122 | + try { |
| 123 | + URL url = new URL(null, "setcaret:" + caretTarget, new JDAURLHandler()); |
| 124 | + return new HyperlinkEvent(textArea, HyperlinkEvent.EventType.ACTIVATED, url); |
| 125 | + } catch (MalformedURLException e) { |
| 126 | + e.printStackTrace(); |
| 127 | + return null; |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + @Override |
| 132 | + public int getSourceOffset() { |
| 133 | + return offs; |
| 134 | + } |
| 135 | + } |
53 | 136 | } |
0 commit comments