Skip to content

Commit 63137b1

Browse files
committed
Start work on syntax highlighting
1 parent d205f72 commit 63137b1

File tree

4 files changed

+148
-16
lines changed

4 files changed

+148
-16
lines changed

src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727

2828
public class ConsoleDecompiler implements /* IBytecodeProvider, */ IResultSaver, AutoCloseable {
2929
private static final Map<String, Object> CONSOLE_DEFAULT_OPTIONS = Map.of(
30-
IFernflowerPreferences.INCLUDE_JAVA_RUNTIME, JrtFinder.CURRENT
30+
IFernflowerPreferences.INCLUDE_JAVA_RUNTIME, JrtFinder.CURRENT,
31+
IFernflowerPreferences.COLORIZE_OUTPUT, "auto"
3132
);
3233

34+
static Runnable tokenizerInitializer;
35+
3336
@SuppressWarnings("UseOfSystemOutOrSystemErr")
3437
public static void main(String[] args) {
3538
List<String> params = new ArrayList<String>();
@@ -177,22 +180,24 @@ else if (args.length > x+1) {
177180

178181

179182
PrintStreamLogger logger = new PrintStreamLogger(System.out);
180-
ConsoleDecompiler decompiler = new ConsoleDecompiler(destination, mapOptions, logger, saveType);
183+
try (ConsoleDecompiler decompiler = new ConsoleDecompiler(destination, mapOptions, logger, saveType)) {
184+
for (File library : libraries) {
185+
decompiler.addLibrary(library);
186+
}
187+
for (File source : sources) {
188+
decompiler.addSource(source);
189+
}
190+
for (String prefix : whitelist) {
191+
decompiler.addWhitelist(prefix);
192+
}
181193

182-
for (File library : libraries) {
183-
decompiler.addLibrary(library);
184-
}
185-
for (File source : sources) {
186-
decompiler.addSource(source);
187-
}
188-
for (String prefix : whitelist) {
189-
decompiler.addWhitelist(prefix);
190-
}
194+
tokenizerInitializer.run();
191195

192-
try {
193196
decompiler.decompileContext();
194197
} catch (CancelationManager.CanceledException e) {
195198
System.out.println("Decompilation canceled");
199+
} catch (IOException e) {
200+
throw new UncheckedIOException("Decompilation failed", e);
196201
}
197202
}
198203

src/org/jetbrains/java/decompiler/main/decompiler/ConsoleFileSaver.java

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,121 @@
11
package org.jetbrains.java.decompiler.main.decompiler;
22

3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.java.decompiler.main.DecompilerContext;
5+
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
36
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
7+
import org.jetbrains.java.decompiler.main.extern.TextTokenVisitor;
8+
import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
9+
import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
10+
import org.jetbrains.java.decompiler.util.token.TextRange;
411

512
import java.io.File;
13+
import java.util.Set;
14+
import java.util.TreeSet;
615
import java.util.jar.Manifest;
716

817
// "Saves" a file to the standard out console
918
public final class ConsoleFileSaver implements IResultSaver {
19+
private static class ConsoleTokenVisitor extends TextTokenVisitor {
20+
record Token(TextRange range, Type type) implements Comparable<Token> {
21+
@Override
22+
public int compareTo(@NotNull ConsoleFileSaver.ConsoleTokenVisitor.Token o) {
23+
return Integer.compare(range.start, o.range.start);
24+
}
25+
26+
enum Type {
27+
CLASS(2),
28+
FIELD(3),
29+
METHOD(4),
30+
PARAMETER(5),
31+
LOCAL(6),
32+
33+
;
34+
35+
private final int color;
36+
37+
Type(int color) {
38+
this.color = color;
39+
}
40+
41+
String getEscapeCode() {
42+
return "\u001B[3" + color + "m";
43+
}
44+
}
45+
}
46+
47+
private final Set<Token> tokens = new TreeSet<>();
48+
private static final String ESCAPE_RESET = "\u001B[0m";
49+
50+
ConsoleTokenVisitor(TextTokenVisitor next) {
51+
super(next);
52+
}
53+
54+
@Override
55+
public void visitClass(TextRange range, boolean declaration, String name) {
56+
tokens.add(new Token(range, Token.Type.CLASS));
57+
super.visitClass(range, declaration, name);
58+
}
59+
60+
@Override
61+
public void visitField(TextRange range, boolean declaration, String className, String name, FieldDescriptor descriptor) {
62+
tokens.add(new Token(range, Token.Type.FIELD));
63+
super.visitField(range, declaration, className, name, descriptor);
64+
}
65+
66+
@Override
67+
public void visitMethod(TextRange range, boolean declaration, String className, String name, MethodDescriptor descriptor) {
68+
tokens.add(new Token(range, Token.Type.METHOD));
69+
super.visitMethod(range, declaration, className, name, descriptor);
70+
}
71+
72+
@Override
73+
public void visitParameter(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int index, String name) {
74+
tokens.add(new Token(range, Token.Type.PARAMETER));
75+
super.visitParameter(range, declaration, className, methodName, methodDescriptor, index, name);
76+
}
77+
78+
@Override
79+
public void visitLocal(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int index, String name) {
80+
tokens.add(new Token(range, Token.Type.LOCAL));
81+
super.visitLocal(range, declaration, className, methodName, methodDescriptor, index, name);
82+
}
83+
84+
void printClass(String content) {
85+
var index = 0;
86+
for (Token token : tokens) {
87+
System.out.print(content.substring(index, token.range().start));
88+
System.out.print(token.type().getEscapeCode());
89+
System.out.print(content.substring(token.range().start, token.range().getEnd()));
90+
System.out.print(ESCAPE_RESET);
91+
index = token.range().getEnd();
92+
}
93+
if (index < content.length()) {
94+
System.out.print(content.substring(index));
95+
}
96+
System.out.println();
97+
}
98+
}
1099

11-
public ConsoleFileSaver(File unused) {
12-
100+
private ConsoleTokenVisitor visitor;
101+
102+
public ConsoleFileSaver(File ignored) {
103+
ConsoleDecompiler.tokenizerInitializer = () -> {
104+
boolean color = switch (DecompilerContext.getProperty(IFernflowerPreferences.COLORIZE_OUTPUT).toString()) {
105+
case "always" -> true;
106+
case "1", "auto" -> System.console() != null;
107+
default -> false;
108+
};
109+
110+
if (color) {
111+
TextTokenVisitor.addVisitor(next -> {
112+
visitor = new ConsoleTokenVisitor(next);
113+
return visitor;
114+
});
115+
}
116+
};
13117
}
118+
14119
@Override
15120
public void saveFolder(String path) {
16121

@@ -24,7 +129,11 @@ public void copyFile(String source, String path, String entryName) {
24129
@Override
25130
public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
26131
System.out.println("==== " + entryName + " ====");
27-
System.out.println(content);
132+
if (visitor != null) {
133+
visitor.printClass(content);
134+
} else {
135+
System.out.println(content);
136+
}
28137
}
29138

30139
@Override
@@ -45,7 +154,11 @@ public void copyEntry(String source, String path, String archiveName, String ent
45154
@Override
46155
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) {
47156
System.out.println("==== " + entryName + " ====");
48-
System.out.println(content);
157+
if (visitor != null) {
158+
visitor.printClass(content);
159+
} else {
160+
System.out.println(content);
161+
}
49162
}
50163

51164
@Override

src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ public interface IFernflowerPreferences {
408408
@Type(DecompilerOption.Type.BOOLEAN)
409409
String PRETTIFY_IFS = "prettify-ifs";
410410

411+
@Name("Colorize console output")
412+
@Description("Export ANSI color codes when decompiling to console. If run using the standard main class in a TTY, this defaults to true, otherwise false. This also accepts always/auto/never values.")
413+
@Type(DecompilerOption.Type.BOOLEAN)
414+
String COLORIZE_OUTPUT = "color";
415+
411416
Map<String, Object> DEFAULTS = getDefaults();
412417

413418
static Map<String, Object> getDefaults() {
@@ -483,6 +488,7 @@ static Map<String, Object> getDefaults() {
483488
defaults.put(EXCLUDED_CLASSES, "");
484489
defaults.put(VALIDATE_INNER_CLASSES_NAMES, "1");
485490
defaults.put(PRETTIFY_IFS, "1");
491+
defaults.put(COLORIZE_OUTPUT, "0");
486492

487493
return Collections.unmodifiableMap(defaults);
488494
}

src/org/jetbrains/java/decompiler/main/extern/TextTokenVisitor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jetbrains.java.decompiler.main.extern;
22

3+
import org.jetbrains.annotations.MustBeInvokedByOverriders;
34
import org.jetbrains.java.decompiler.main.DecompilerContext;
45
import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
56
import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
@@ -49,42 +50,49 @@ public static TextTokenVisitor createVisitor(Factory factory) {
4950
return chainFactories().andThen(factory).create(EMPTY);
5051
}
5152

53+
@MustBeInvokedByOverriders
5254
public void start(String content) {
5355
if (next != null) {
5456
next.start(content);
5557
}
5658
}
5759

60+
@MustBeInvokedByOverriders
5861
public void visitClass(TextRange range, boolean declaration, String name) {
5962
if (next != null) {
6063
next.visitClass(range, declaration, name);
6164
}
6265
}
6366

67+
@MustBeInvokedByOverriders
6468
public void visitField(TextRange range, boolean declaration, String className, String name, FieldDescriptor descriptor) {
6569
if (next != null) {
6670
next.visitField(range, declaration, className, name, descriptor);
6771
}
6872
}
6973

74+
@MustBeInvokedByOverriders
7075
public void visitMethod(TextRange range, boolean declaration, String className, String name, MethodDescriptor descriptor) {
7176
if (next != null) {
7277
next.visitMethod(range, declaration, className, name, descriptor);
7378
}
7479
}
7580

81+
@MustBeInvokedByOverriders
7682
public void visitParameter(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int index, String name) {
7783
if (next != null) {
7884
next.visitParameter(range, declaration, className, methodName, methodDescriptor, index, name);
7985
}
8086
}
8187

88+
@MustBeInvokedByOverriders
8289
public void visitLocal(TextRange range, boolean declaration, String className, String methodName, MethodDescriptor methodDescriptor, int index, String name) {
8390
if (next != null) {
8491
next.visitLocal(range, declaration, className, methodName, methodDescriptor, index, name);
8592
}
8693
}
8794

95+
@MustBeInvokedByOverriders
8896
public void end() {
8997
if (next != null) {
9098
next.end();

0 commit comments

Comments
 (0)