From 0cfcd9dc2569fb054d2304dcbed9587e67cf4670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 30 May 2025 01:45:36 +0200 Subject: [PATCH] AS1/2/3 - highlight variables and errors on panel next to vertical scrollbar --- CHANGELOG.md | 2 +- lib/substance-fix.jar | Bin 175381 -> 179093 bytes .../decompiler/flash/ParseException.java | 4 +- .../script/ActionScript3SimpleParser.java | 118 ++++++---- .../script/ActionScript2SimpleParser.java | 19 +- .../flash/gui/editor/HighlightsPanel.java | 208 ++++++++++++++++++ .../flash/gui/editor/OccurencesMarker.java | 31 +++ .../flash/gui/editor/ScrollbarOverlay.java | 89 -------- .../flash/gui/editor/VariableMarker.java | 45 ++-- .../flash/gui/locales/MainFrame.properties | 6 +- 10 files changed, 344 insertions(+), 178 deletions(-) create mode 100644 src/com/jpexs/decompiler/flash/gui/editor/HighlightsPanel.java create mode 100644 src/com/jpexs/decompiler/flash/gui/editor/OccurencesMarker.java delete mode 100644 src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a75537b..d828bdfff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ All notable changes to this project will be documented in this file. - AS1/2/3 - highlight variable definition and all its instances on cursor place (also in edit mode) - AS1/2/3 - underline errors in the code (also in edit mode) -- AS1/2/3 - highlight variables and errors on scrollbar +- AS1/2/3 - highlight variables and errors on panel next to vertical scrollbar ### Changed - AS1/2 - Single DoAction tag inside frame is now displayed directly as frame node diff --git a/lib/substance-fix.jar b/lib/substance-fix.jar index ef7e2c2ddb630ed00701f8c93843f404dbbc964f..77204c70e87b61f7efe362c21862e309e285d2b6 100644 GIT binary patch delta 3361 zcmZuzYgAO%75>hd$A#ezFF_d)#Q5SNj1Vh2;{$vUKm`J*MM-3UtBfKuGNYhvL=zK@ zMg>PWzKEhIKF~zgwK8kXy?cLW zKhD|TKIfjUKij&V+3e*jO=cSq21MSz*J)2P*>B$)oIjnJNDXadM`Gx`9^-y#qH_gH zXWrXiv31|+qZvmc4kXwC`Onu!=X#x$JL&>KPjkIin;br79=bP@B6uHLo;E9}1%k_b+x)&}tvR@?Me_u`zUHb% z5*obCo7Z}MO*$gZXYoXKf#!48uRF^E+#K}8(m877L~D{P`@Q+aA+ z3?`{afP+EVtOdo*Jf7^7=1D3hBbg!08z}R8w(rmwOlj%s<kisi6V#T;ZY zSQ|C(=Ek56(~zs;Ma-q-o4id;6zx1_o0`pYlJ)c0KuSMJ3;@i>OARQN| zaKYVG!ltwXj<0q;6@EC6dTm73=h>)%k)!-!>bH zP%Zmuu7V8=sawQCg(W2vRjpnXhF2KOjlpeAauFL<)WXwU#TPqK&k#SpKtZa4bFEiv zwV@80icQ!|Dg7RAGZkm1$eG9-;#Lx~#8)pb zy2WB}GRtENMNA2sHSNc?shb+EUhXk%4G}nB6nxI6h?40n$zrajJt%sUS+Y1jjpc}F z4^QN*UTmDglEl<7o}ws0y@^EmM=T-J>K*Nl7^9uW^#*mEUQgL@71VK}*TUkc57|zJ zNxJp(ZXF|ore&`T(mq@5@mG23nluHM2pVgiLSJ)GoTHt^vgMRhyJMJHB+chGG4c^h zjSlluO)I^-X#<;OOi{3(B~BYVnq(MEr9*=2C!HBY7_9U>B>pgy#kb=y;Zg^3@D4py zUAEu|j*=FRW9X39B)cevv{N%afN~f1>S09PBAx3os=9wO2amg!K2(9n+wnX?^=!@{%V9M}hF9Hhyc^hK9q0?CC`(jr8l z7_lfpBJJJ`tVS-K0(mH>bLeHPKn0DiK^fMPUp2b19%rxtm)r3Q-ovZ7P46Mvxsrhz zqdKDuP@QlBU3%q*$?-VV*@E{WaFQkwfdQPtX{vfYS}EjN8Y@F1dhjl>dYDe-b2tx$ z{HCH87pU?VDMJd(-^*jWPiaT`2vQWZ{D2w-D-{$esEeXxm(f2;*I^1tA5jcv4k3Cq zJ(ic=MS67>m6iS#we%>X|C-uiG`q~1|KsK%><&xp(jja=1)F54AA!t=h{}9k>3*2o znak?1K17zoTrKIgnugFC`?I>kwZD+$6PE3;nhCS(P&Y{eov-Ve>?nin7xG{F|dQ` zwG(&f1e45EP$7fNDB*Ej)7i?#+qh0_ zxh*;&8L<9`WyK&>)*7_C_$PIM~TE_;wzOvE+sJ z@Cb4C1dkTh<8%_bEd;JGfK~}yjv!#Zczm4a@tQbYUKk?z<|JLdA>|8+QV!ipv>T4@ z6XaNyE~a+#1)}2wpU3~?)RoZJ&L|PxMah4YD*;#N^W4$won7R!pg^MTP)WgPBuh7; z>8m8#!nIO8aIpHQXS`P^r}o-V@x#chu?fw3B39sD*k13q;pRn#tn; z$;JXh2iu}ms;~a*+xMVN5Ba+`BV_36q4A?%9nwdi9cbp|W=b9dK$u+x(?4hElgSwtL zXcz5L|LneQ`}ZEQo%FFTSG`N}rANB_iIo5NI8^Y&Ef$<3rVohi=Xii`eL_%?$Ue{2 zRC)3JzpL%@_0qd}D?Cq3NoK0(InU?t#zKkY zi>O}yB0sarQ1k<(fcZCQ|eIVfy@#TGH5$#vVEAW{__Hy&rhC|d}m|g6> zN+Z|4F_4mfusES!rw~a`+4z?E^@w3`%*o@$o~N|$jt-Dj&o!QC>alXM?|rsJEc%vB zklq<4U-T#xWuhzhQ3*{>7==ExQHZDkg0zfL$e+yZ;@|+EW7 MOQBPvM}DOL3+t9-AOHXW delta 1153 zcmY+DSx8i26vyv(#$}wkrnv-SD$!z^IgXY~lnNT+hEY8Pii4yG_8=G{k!eJ>xy8*+ zXf6}VxYnqd&DbW16-iJpk!?s11xbq;MHzO!@0*A5KK=i{^FQBm?!7I)3@x(;H7`r& zW>6Hpq9irjFRJ0X#Af?&!bYZx?ikk!S;MFavj3b}^kU+*K^-0Utcq1wmk5#UZ}! zf_Mn@7Xh2UiZd1plvyZHx=sY{TPTnpB((7wX|Jt9Te#MuMProFM>J})CZE-c^XWcW zXA;%0SXy=ne6@_!H7?a3DWcktDtmEVH*NYMOTg-EC%nL6XfDBYobRT4UCkq%i2HhA zCrlP{t>v57$Yam-P&m3syI7Pik8_~d^*BngSi7}Y-coy2;aWXiA*!KAoim8D(qhM| zIK;D7h-Yp1zzXFo4r6+u9CF%3<#jJjA+tm1>^{=>EJEMqnsp1ky&samWaV0$v8s5Z zpYHLwPt^PdC^u~rHS@4@Ys&y_-FQ#H@cSyixUaT(%5`|pABt+(l=GFI8l;xON802g zd39Xmp#G`YBx8sUkordGJ6vy?7ut9og3)muLSXw6&Xuru+-+cM(KK;>TMI-S7; zj2VX%Y`Oul@P3apZZ?R4%5-TO_cJ}#*yw7zawPd|0~06*r70Yytk)rFUJTPJRRz*C zmoZPw9ihf=MUps1DC>Phny67S(-qQmar5!GG|T6yG3X{W>gyy4yF{dclhP(hYTKFT z errors, TypeItem thisType, Reference needsActivation, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables, List importedClasses, ABC abc) throws IOException, AVM2ParseException, SimpleParseException, InterruptedException { ParsedSymbol s = lex(); - DottedChain name = new DottedChain(new String[]{}, new String[]{""}); - boolean attribute = false; - String name2 = ""; + + String lastName = ""; if (s.type == SymbolType.ATTRIBUTE) { - attribute = true; + lastName = s.value.toString(); s = lex(); } - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); - name2 += s.value.toString(); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP)) { + return true; + } + lastName += s.value.toString(); int identPos = s.position; s = lex(); boolean attrBracket = false; - String nsSuffix = ""; if (s.type == SymbolType.NAMESPACESUFFIX) { s = lex(); - nsSuffix = "#" + s.value; + lastName += "#" + s.value; } - name = name.add(attribute, name2, nsSuffix); + String fullName = lastName; + while (s.isType(SymbolType.DOT)) { - variables.add(new Variable(false, name.toPrintableString(true), identPos)); + variables.add(new Variable(false, fullName, identPos)); s = lex(); - name2 = ""; - attribute = false; + lastName = ""; if (s.type == SymbolType.ATTRIBUTE) { - attribute = true; + lastName += s.value.toString(); s = lex(); if (s.type == SymbolType.MULTIPLY) { - name2 += s.value.toString(); + lastName += s.value.toString(); identPos = s.position; } else if (s.group == SymbolGroup.IDENTIFIER) { - name2 += s.value.toString(); + lastName += s.value.toString(); identPos = s.position; } else { if (s.type != SymbolType.BRACKET_OPEN) { @@ -287,39 +289,34 @@ public class ActionScript3SimpleParser implements SimpleParser { continue; } } else { - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.NAMESPACE, SymbolType.MULTIPLY); - name2 += s.value.toString(); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.NAMESPACE, SymbolType.MULTIPLY)) { + return true; + } + lastName = s.value.toString(); identPos = s.position; } s = lex(); - nsSuffix = ""; if (s.type == SymbolType.NAMESPACESUFFIX) { - nsSuffix = "#" + s.value; + lastName += "#" + s.value; s = lex(); } - name = name.add(attribute, name2, nsSuffix); + fullName += "." + lastName; } if (s.type == SymbolType.NAMESPACE_OP) { - String nsname = name.getLast(); - boolean nsAtribute = name.isLastAttribute(); s = lex(); if (s.group == SymbolGroup.IDENTIFIER) { String nsprop = s.value.toString(); - variables.add(new Variable(false, (nsAtribute ? "@" : "") + nsname + "::" + nsprop, s.position)); + variables.add(new Variable(false, lastName + "::" + nsprop, s.position)); } else if (s.type == SymbolType.BRACKET_OPEN) { expression(errors, thisType, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables, false, abc); expectedType(errors, SymbolType.BRACKET_CLOSE); } - name = name.getWithoutLast(); s = lex(); } else { - variables.add(new Variable(false, name.toPrintableString(true), identPos)); + variables.add(new Variable(false, fullName, identPos)); } - boolean ret = false; - if (!name.isEmpty()) { - ret = true; - } + boolean ret = true; if (s.type == SymbolType.BRACKET_OPEN) { lexer.pushback(s); if (attrBracket) { @@ -333,7 +330,7 @@ public class ActionScript3SimpleParser implements SimpleParser { return ret; } - private void expected(List errors, ParsedSymbol symb, int line, Object... expected) throws IOException, AVM2ParseException, SimpleParseException { + private boolean expected(List errors, ParsedSymbol symb, int line, Object... expected) throws IOException, AVM2ParseException, SimpleParseException { boolean found = false; for (Object t : expected) { if (symb.type == t) { @@ -355,6 +352,7 @@ public class ActionScript3SimpleParser implements SimpleParser { } errors.add(new SimpleParseException("" + expStr + " expected but " + symb.type + " found", line, symb.position)); } + return found; } private ParsedSymbol expectedType(List errors, Object... type) throws IOException, AVM2ParseException, SimpleParseException, InterruptedException { @@ -383,7 +381,9 @@ public class ActionScript3SimpleParser implements SimpleParser { } expression(errors, thisType, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables, false, abc); s = lex(); - expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); + if (!expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { + break; + } } } @@ -408,7 +408,9 @@ public class ActionScript3SimpleParser implements SimpleParser { hasRest = true; s = lex(); } - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } paramNames.add(s.value.toString()); paramPositions.add(s.position); @@ -513,7 +515,7 @@ public class ActionScript3SimpleParser implements SimpleParser { return metadata; } - private void classTraits(List errors, boolean outsidePackage, Reference cinitNeedsActivation, List importedClasses, List openedNamespaces, String classNameStr, boolean isInterface, Reference iinitNeedsActivation, ABC abc, List classVariables) throws AVM2ParseException, SimpleParseException, IOException, CompilationException, InterruptedException { + private void classTraits(List errors, boolean outsidePackage, Reference cinitNeedsActivation, List importedClasses, List openedNamespaces, DottedChain pkg, String classNameStr, boolean isInterface, Reference iinitNeedsActivation, ABC abc, List classVariables) throws AVM2ParseException, SimpleParseException, IOException, CompilationException, InterruptedException { Stack cinitLoops = new Stack<>(); Map cinitLoopLabels = new HashMap<>(); @@ -626,6 +628,8 @@ public class ActionScript3SimpleParser implements SimpleParser { s = lex(); } + String prefix = isStatic ? pkg.addWithSuffix(classNameStr).toPrintableString(true) + "." : "this."; + switch (s.type) { case FUNCTION: s = lex(); @@ -636,8 +640,10 @@ public class ActionScript3SimpleParser implements SimpleParser { isSetter = true; s = lex(); } - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.PARENT_OPEN); - String fname = null; + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.PARENT_OPEN)) { + break; + } + String fname; int fnamePos = s.position; //fix for methods with name "get" or "set" - they are not getters/setters! @@ -651,10 +657,12 @@ public class ActionScript3SimpleParser implements SimpleParser { //isSetter = false; } else { errors.add(new SimpleParseException("Missing method name", lexer.yyline(), s.position)); + break; } } else { fname = s.value.toString(); } + classVariables.add(new Variable(true, prefix + fname, fnamePos)); if (fname.equals(classNameStr)) { //constructor if (isStatic) { errors.add(new SimpleParseException("Constructor cannot be static", lexer.yyline(), s.position)); @@ -709,9 +717,13 @@ public class ActionScript3SimpleParser implements SimpleParser { errors.add(new SimpleParseException("Interface cannot have namespace fields", lexer.yyline(), s.position)); } s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } String nname = s.value.toString(); int npos = s.position; + + classVariables.add(new Variable(true, prefix + nname, npos)); s = lex(); if (s.type == SymbolType.ASSIGN) { @@ -739,8 +751,10 @@ public class ActionScript3SimpleParser implements SimpleParser { } s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); - classVariables.add(new Variable(true, s.value.toString(), s.position)); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } + classVariables.add(new Variable(true, prefix + s.value.toString(), s.position)); s = lex(); if (s.type == SymbolType.NAMESPACESUFFIX) { @@ -909,8 +923,12 @@ public class ActionScript3SimpleParser implements SimpleParser { String subNameStr; s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } subNameStr = s.value.toString(); + int subNamePos = s.position; + sinitVariables.add(new Variable(true, pkgName.addWithSuffix(subNameStr).toPrintableString(true), subNamePos)); s = lex(); if (s.type == SymbolType.NOT) { @@ -946,7 +964,7 @@ public class ActionScript3SimpleParser implements SimpleParser { Reference iinitNeedsActivation = new Reference<>(false); List classVariables = new ArrayList<>(); - classTraits(errors, !inPackage, cinitNeedsActivation, importedClasses, subOpenedNamespaces, subNameStr, isInterface, iinitNeedsActivation, abc, classVariables); + classTraits(errors, !inPackage, cinitNeedsActivation, importedClasses, subOpenedNamespaces, pkgName, subNameStr, isInterface, iinitNeedsActivation, abc, classVariables); sinitVariables.add(new ClassScope(classVariables)); @@ -969,7 +987,9 @@ public class ActionScript3SimpleParser implements SimpleParser { } s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } sinitVariables.add(new Variable(true, s.value.toString(), s.position)); s = lex(); if (s.type == SymbolType.COLON) { @@ -993,8 +1013,11 @@ public class ActionScript3SimpleParser implements SimpleParser { errors.add(new SimpleParseException("Final flag not allowed for namespaces", lexer.yyline(), s.position)); } s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } sinitVariables.add(new Variable(true, s.value.toString(), s.position)); + s = lex(); if (s.type == SymbolType.ASSIGN) { @@ -1205,7 +1228,9 @@ public class ActionScript3SimpleParser implements SimpleParser { case VAR: case CONST: s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER); + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER)) { + break; + } String varIdentifier = s.value.toString(); int varPos = s.position; s = lex(); @@ -1410,8 +1435,9 @@ public class ActionScript3SimpleParser implements SimpleParser { while (s.type == SymbolType.CATCH) { expectedType(errors, SymbolType.PARENT_OPEN); s = lex(); - expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); - + if (!expected(errors, s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP)) { + break; + } String enamestr = s.value.toString(); int ePos = s.position; expectedType(errors, SymbolType.COLON); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2SimpleParser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2SimpleParser.java index 6e2c141f8..d9eb0a8be 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2SimpleParser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/action/parser/script/ActionScript2SimpleParser.java @@ -153,7 +153,9 @@ public class ActionScript2SimpleParser implements SimpleParser { } expression(errors, inFunction, inMethod, inTellTarget, true, variables, false, hasEval); s = lex(); - expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); + if (!expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { + break; + } } return ret; } @@ -181,7 +183,9 @@ public class ActionScript2SimpleParser implements SimpleParser { } if (!s.isType(SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { - expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); + if (!expected(errors, s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { + break; + } } } List subvariables = new ArrayList<>(); @@ -1619,17 +1623,16 @@ public class ActionScript2SimpleParser implements SimpleParser { inOnHandler = true; } else { lexer.pushback(symb); - } + } + Reference hasEval = new Reference<>(false); + commands(errors, false, false, 0, false, vars, hasEval); + if (inOnHandler) { expectedType(errors, SymbolType.CURLY_CLOSE); - } + } if (lexer.lex().type != SymbolType.EOF) { errors.add(new SimpleParseException("Parsing finished before end of the file", lexer.yyline(), lexer.yychar())); } - - Reference hasEval = new Reference<>(false); - commands(errors, false, false, 0, false, vars, hasEval); - } catch (ActionParseException ex) { errors.add(new SimpleParseException(ex.getMessage(), ex.line, ex.position)); } diff --git a/src/com/jpexs/decompiler/flash/gui/editor/HighlightsPanel.java b/src/com/jpexs/decompiler/flash/gui/editor/HighlightsPanel.java new file mode 100644 index 000000000..825b2a892 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/editor/HighlightsPanel.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2025 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.editor; + +import com.jpexs.decompiler.flash.gui.AppStrings; +import java.awt.Color; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.plaf.ScrollBarUI; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import jsyntaxpane.actions.ActionUtils; +import jsyntaxpane.components.Markers; + +/** + * + * @author JPEXS + */ +public class HighlightsPanel extends JPanel { + + private final LineMarkedEditorPane editorPane; + private Cursor HAND_CURSOR = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + private Cursor DEFAULT_CURSOR = Cursor.getDefaultCursor(); + private Map errors = new LinkedHashMap<>(); + private int scrollBarButtonSize = 0; + private JScrollPane scrollPane; + + public HighlightsPanel(LineMarkedEditorPane editorPane) { + this.editorPane = editorPane; + + scrollPane = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, editorPane); + + JScrollBar bar = new JScrollBar(JScrollBar.VERTICAL, 0, 1, 0, 100); + scrollBarButtonSize = bar.getPreferredSize().width; + setPreferredSize(new Dimension(16, Integer.MAX_VALUE)); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + int totalLineCount = ActionUtils.getLineCount(editorPane); + Rectangle r = getScrollbarTrackRect(); + int line = (e.getY() - r.y) * totalLineCount / r.height; + int linesCountPer1Px = totalLineCount / r.height; + if (!scrollPane.getVerticalScrollBar().isVisible()) { + linesCountPer1Px = 0; + } + + for (Highlighter.Highlight highlight : editorPane.getHighlighter().getHighlights()) { + Highlighter.HighlightPainter painter = highlight.getPainter(); + if (painter instanceof Markers.SimpleMarker) { + try { + int lineNum = ActionUtils.getLineNumber(editorPane, highlight.getStartOffset()); + if (lineNum >= line - linesCountPer1Px && lineNum <= line + linesCountPer1Px) { + editorPane.setCaretPosition(highlight.getStartOffset()); + return; + } + } catch (BadLocationException ex) { + //ignore + } + } + } + } + }); + addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + int totalLineCount = ActionUtils.getLineCount(editorPane); + Rectangle r = getScrollbarTrackRect(); + int line = (e.getY() - r.y) * totalLineCount / r.height; + + int linesCountPer1Px = totalLineCount / r.height; + if (!scrollPane.getVerticalScrollBar().isVisible()) { + linesCountPer1Px = 0; + } + + int lineStart = line * r.height / totalLineCount; + int lineEnd = (line + 1) * r.height / totalLineCount; + if (e.getY() - r.y < lineStart - 1 || e.getY() - r.y > lineStart + 2) { + if (getCursor() != DEFAULT_CURSOR) { + setCursor(DEFAULT_CURSOR); + } + + setToolTipText(null); + return; + } + + int currentLine = editorPane.getLine(); + + for (Highlighter.Highlight highlight : editorPane.getHighlighter().getHighlights()) { + Highlighter.HighlightPainter painter = highlight.getPainter(); + if (painter instanceof Markers.SimpleMarker) { + try { + int lineNum = ActionUtils.getLineNumber(editorPane, highlight.getStartOffset()); + if (lineNum >= line - linesCountPer1Px && lineNum <= line + linesCountPer1Px) { + if (line != currentLine) { + if (getCursor() != HAND_CURSOR) { + setCursor(HAND_CURSOR); + } + } + if (errors.containsKey(highlight.getStartOffset())) { + setToolTipText(AppStrings.translate("highlighter.error").replace("%error%", errors.get(highlight.getStartOffset()))); + return; + } + if (line != currentLine && painter instanceof OccurencesMarker) { + setToolTipText(AppStrings.translate("highlighter.occurences")); + return; + } + + if (line == currentLine) { + setToolTipText(AppStrings.translate("highlighter.currentLine")); + } + return; + } + } catch (BadLocationException ex) { + //ignore + } + } + } + + if (getCursor() != DEFAULT_CURSOR) { + setCursor(DEFAULT_CURSOR); + } + + setToolTipText(null); + } + }); + } + + public void setErrors(Map errors) { + this.errors = errors; + } + + private Rectangle getScrollbarTrackRect() { + return new Rectangle(0, scrollBarButtonSize, 16, getHeight() + - 2 * scrollBarButtonSize); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Rectangle r = getScrollbarTrackRect(); + + int totalLineCount = ActionUtils.getLineCount(editorPane); + int h = 3; + Highlighter.Highlight[] highlights = editorPane.getHighlighter().getHighlights(); + Set ignoredLines = new HashSet<>(); + int currentLine = editorPane.getLine(); + + for (Highlighter.Highlight highlight : highlights) { + Highlighter.HighlightPainter painter = highlight.getPainter(); + if (painter instanceof Markers.SimpleMarker) { + Markers.SimpleMarker simpleMarker = (Markers.SimpleMarker) painter; + g.setColor(simpleMarker.getColor()); + + try { + int line = ActionUtils.getLineNumber(editorPane, highlight.getStartOffset()); + + //prefer error lines + if (painter instanceof WavyUnderLinePainter) { + ignoredLines.add(line); + } else if (ignoredLines.contains(line)) { + continue; + } + float ratio = (float) line / totalLineCount; + int y = r.y + (int) (ratio * r.height); + g.fillRect(0, y, getWidth(), h); + } catch (BadLocationException ex) { + //ignore + } + } + } + + if (currentLine >= 0) { + float ratio = (float) currentLine / totalLineCount; + int y = r.y + (int) (ratio * r.height) + 1; + g.setColor(Color.gray); + g.fillRect(0, y, getWidth(), 1); + g.fillOval(16 / 2 - 2, y - 1, 3, 3); + } + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/editor/OccurencesMarker.java b/src/com/jpexs/decompiler/flash/gui/editor/OccurencesMarker.java new file mode 100644 index 000000000..e1c68b111 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/editor/OccurencesMarker.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2025 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.gui.editor; + +import java.awt.Color; +import jsyntaxpane.components.Markers; + +/** + * + * @author JPEXS + */ +public class OccurencesMarker extends Markers.SimpleMarker { + + public OccurencesMarker(Color color) { + super(color); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java b/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java deleted file mode 100644 index 3de5a011e..000000000 --- a/src/com/jpexs/decompiler/flash/gui/editor/ScrollbarOverlay.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2025 JPEXS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.jpexs.decompiler.flash.gui.editor; - -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import javax.swing.JComponent; -import javax.swing.JLayer; -import javax.swing.JScrollBar; -import javax.swing.plaf.LayerUI; -import javax.swing.plaf.ScrollBarUI; -import jsyntaxpane.actions.ActionUtils; - -/** - * - * @author JPEXS - */ -public class ScrollbarOverlay extends LayerUI { - - private final LineMarkedEditorPane editorPane; - private final Map markedLines = new LinkedHashMap<>(); - - public ScrollbarOverlay(LineMarkedEditorPane editorPane) { - this.editorPane = editorPane; - } - - public void removeMarkers(Color color) { - Set lines = new HashSet<>(markedLines.keySet()); - for (int key : lines) { - if (markedLines.get(key).equals(color)) { - markedLines.remove(key); - } - } - } - - public void clearMarkers() { - markedLines.clear(); - } - - public void addMarker(int line, Color color) { - markedLines.put(line, color); - } - - public void setMarkedLines(Map markedLines) { - this.markedLines.clear(); - this.markedLines.putAll(markedLines); - } - - @Override - public void paint(Graphics g, JComponent c) { - super.paint(g, c); - JScrollBar bar = (JScrollBar) ((JLayer) c).getView(); - ScrollBarUI ui = bar.getUI(); - Rectangle r; - if (ui instanceof TrackRectSubstanceScrollbarUI) { - r = ((TrackRectSubstanceScrollbarUI) ui).getTrackBounds(); - } else { - r = new Rectangle(0, 16, 16, bar.getHeight() - 32); - } - - int totalLineCount = ActionUtils.getLineCount(editorPane); - for (Map.Entry entry : markedLines.entrySet()) { - g.setColor(entry.getValue()); - - float ratio = (float) entry.getKey() / totalLineCount; - int y = r.y + (int) (ratio * r.height); - g.drawLine(0, y, bar.getWidth(), y); - } - } -} diff --git a/src/com/jpexs/decompiler/flash/gui/editor/VariableMarker.java b/src/com/jpexs/decompiler/flash/gui/editor/VariableMarker.java index a53bec0d0..582ba6fe1 100644 --- a/src/com/jpexs/decompiler/flash/gui/editor/VariableMarker.java +++ b/src/com/jpexs/decompiler/flash/gui/editor/VariableMarker.java @@ -75,7 +75,7 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC private static final Color SCROLLBAR_ERROR_COLOR = new Color(0xff0000); private JEditorPane pane; private final Set tokenTypes = new HashSet<>(); - private Markers.SimpleMarker marker; + private OccurencesMarker marker; private Markers.SimpleMarker errorMarker; private Status status; private Map errors = new LinkedHashMap<>(); @@ -87,9 +87,7 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC private MouseMotionAdapter mouseMotionAdapter; - private JLayer layer; - - private ScrollbarOverlay scrollbarOverlay; + private HighlightsPanel highlightsPanel; private ScrollPaneUI originalScrollPaneUI; @@ -120,7 +118,7 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC if (token != null && tokenTypes.contains(token.type)) { addMarkers(token); } - layer.repaint(); + highlightsPanel.repaint(); } } @@ -129,12 +127,10 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC */ public void removeMarkers() { Markers.removeMarkers(pane, marker); - scrollbarOverlay.removeMarkers(SCROLLBAR_VARIABLE_COLOR); } public void removeErrorMarkers() { Markers.removeMarkers(pane, errorMarker); - scrollbarOverlay.removeMarkers(SCROLLBAR_ERROR_COLOR); } private Token getIdentifierTokenAt(SyntaxDocument sDoc, int pos) { @@ -188,6 +184,8 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC } void addErrorMarkers() { + highlightsPanel.setErrors(errors); + SyntaxDocument doc = ActionUtils.getSyntaxDocument(pane); if (doc == null) { return; @@ -197,10 +195,9 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC Token token = getNearestTokenAt(doc, position); if (token != null) { Markers.markToken(pane, token, errorMarker); - markPositionOnScrollbar(position, SCROLLBAR_ERROR_COLOR); } } - layer.repaint(); + highlightsPanel.repaint(); doc.readUnlock(); } @@ -220,12 +217,10 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC if (definitionToken != null) { if (definitionPosToReferences.containsKey(definitionPos)) { Markers.markToken(pane, definitionToken, marker); - markPositionOnScrollbar(definitionToken.start, SCROLLBAR_VARIABLE_COLOR); for (int i : definitionPosToReferences.get(definitionPos)) { Token referenceToken = getIdentifierTokenAt(sDoc, i); if (referenceToken != null) { Markers.markToken(pane, referenceToken, marker); - markPositionOnScrollbar(referenceToken.start, SCROLLBAR_VARIABLE_COLOR); } } } @@ -233,19 +228,11 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC sDoc.readUnlock(); } - private void markPositionOnScrollbar(int position, Color color) { - try { - scrollbarOverlay.addMarker(ActionUtils.getLineNumber(pane, position), color); - } catch (BadLocationException ex) { - //ignore - } - } - @Override public void config(Configuration config) { Color markerColor = config.getColor(PROPERTY_COLOR, DEFAULT_COLOR); Color errorColor = config.getColor(PROPERTY_ERRORCOLOR, DEFAULT_ERRORCOLOR); - this.marker = new Markers.SimpleMarker(markerColor); + this.marker = new OccurencesMarker(markerColor); this.errorMarker = new WavyUnderLinePainter(errorColor); //Markers.SimpleMarker(errorColor); String types = config.getString( PROPERTY_TOKENTYPES, DEFAULT_TOKENTYPES); @@ -290,13 +277,10 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC verticalScrollBar.setUI(new TrackRectSubstanceScrollbarUI(verticalScrollBar)); } - scrollbarOverlay = new ScrollbarOverlay((LineMarkedEditorPane) pane); - - layer = new JLayer<>(verticalScrollBar, scrollbarOverlay); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); - + highlightsPanel = new HighlightsPanel((LineMarkedEditorPane) pane); + //scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); JPanel panel = (JPanel) SwingUtilities.getAncestorOfClass(JPanel.class, scrollPane); - panel.add(layer, BorderLayout.EAST); + panel.add(highlightsPanel, BorderLayout.EAST); documentUpdated(); markTokenAt(editor.getCaretPosition()); @@ -335,8 +319,8 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC } JScrollPane scrollPane = (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, editor); JPanel panel = (JPanel) SwingUtilities.getAncestorOfClass(JPanel.class, scrollPane); - panel.remove(layer); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + panel.remove(highlightsPanel); + //scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); panel.revalidate(); panel.repaint(); scrollPane.setUI(originalScrollPaneUI); @@ -362,7 +346,7 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC private void documentUpdated() { errors.clear(); removeErrorMarkers(); - layer.repaint(); + highlightsPanel.repaint(); try { SyntaxDocument sDoc = (SyntaxDocument) pane.getDocument(); @@ -392,8 +376,7 @@ public class VariableMarker implements SyntaxComponent, CaretListener, PropertyC } catch (SimpleParseException ex) { definitionPosToReferences.clear(); referenceToDefinition.clear(); - errors.put((int) ex.position, ex.getMessage()); - ex.printStackTrace(); + errors.put((int) ex.position, ex.getMessage()); } Timer tim = errorsTimer; if (tim != null) { diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index daeb8e932..c31c20f83 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1057,4 +1057,8 @@ work.debugging.start = Starting Flash content debugger menu.file.view.alwaysOnTop = Always on top -contextmenu.showDetail = Show detail \ No newline at end of file +contextmenu.showDetail = Show detail + +highlighter.occurences = Mark occurences +highlighter.currentLine = Current line +highlighter.error = Error: %error% \ No newline at end of file