Skip to content

Commit b2c80ff

Browse files
committed
POC of google-java-format support
Signed-off-by: Shi Chen <[email protected]>
1 parent 8f0efa4 commit b2c80ff

File tree

9 files changed

+166
-10
lines changed

9 files changed

+166
-10
lines changed

launch/jdt.ls.remote.server.launch

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
3737
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
3838
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
39-
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false"/>
39+
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"/>
4040
<stringAttribute key="pde.version" value="3.3"/>
4141
<stringAttribute key="product" value="org.eclipse.sdk.ide"/>
4242
<setAttribute key="selected_features"/>

launch/jdt.ls.socket-stream.launch

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
3838
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
3939
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
40-
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false"/>
40+
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"/>
4141
<stringAttribute key="pde.version" value="3.3"/>
4242
<stringAttribute key="product" value="org.eclipse.sdk.ide"/>
4343
<setAttribute key="selected_features"/>

org.eclipse.jdt.ls.core/.classpath

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
55
<classpathentry exported="true" kind="lib" path="lib/jsoup-1.14.2.jar"/>
66
<classpathentry exported="true" kind="lib" path="lib/remark-1.2.0.jar"/>
7+
<classpathentry exported="true" kind="lib" path="lib/google-java-format-1.13.0.jar"/>
78
<classpathentry kind="src" path="src/"/>
89
<classpathentry kind="output" path="target/classes"/>
910
</classpath>

org.eclipse.jdt.ls.core/META-INF/MANIFEST.MF

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Export-Package: org.eclipse.jdt.ls.core.contentassist;x-friends:="org.eclipse.jd
6262
org.eclipse.lsp4j.legacy.typeHierarchy;x-friends:="org.eclipse.jdt.ls.tests"
6363
Bundle-ClassPath: lib/jsoup-1.14.2.jar,
6464
lib/remark-1.2.0.jar,
65+
lib/google-java-format-1.13.0.jar,
6566
.
6667
Bundle-Vendor: %Bundle-Vendor
6768
Automatic-Module-Name: org.eclipse.jdt.ls.core

org.eclipse.jdt.ls.core/build.properties

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ bin.includes = META-INF/,\
55
plugin.xml,\
66
lib/jsoup-1.14.2.jar,\
77
lib/remark-1.2.0.jar,\
8+
lib/google-java-format-1.13.0.jar,\
89
lifecycle-mapping-metadata.xml,\
910
plugin.properties,\
1011
gradle/checksums/checksums.json,\

org.eclipse.jdt.ls.core/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
<artifactId>jsoup</artifactId>
2828
<version>1.14.2</version>
2929
</artifactItem>
30+
<artifactItem>
31+
<groupId>com.google.googlejavaformat</groupId>
32+
<artifactId>google-java-format</artifactId>
33+
<version>1.13.0</version>
34+
</artifactItem>
3035
</artifactItems>
3136
</configuration>
3237
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.google.googlejavaformat.java;
2+
3+
import com.google.common.base.Preconditions;
4+
import com.google.common.collect.Range;
5+
import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.eclipse.jdt.core.dom.ASTParser;
9+
import org.eclipse.jdt.core.formatter.CodeFormatter;
10+
import org.eclipse.jface.text.IRegion;
11+
import org.eclipse.jface.text.Region;
12+
import org.eclipse.text.edits.MultiTextEdit;
13+
import org.eclipse.text.edits.ReplaceEdit;
14+
import org.eclipse.text.edits.TextEdit;
15+
16+
/** Runs the Google Java formatter on the given code. */
17+
public class GoogleJavaFormatter extends CodeFormatter {
18+
19+
private static final int INDENTATION_SIZE = 2;
20+
21+
@Override
22+
public TextEdit format(
23+
int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
24+
IRegion[] regions = new IRegion[] {new Region(offset, length)};
25+
return formatInternal(kind, source, regions, indentationLevel);
26+
}
27+
28+
@Override
29+
public TextEdit format(
30+
int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
31+
return formatInternal(kind, source, regions, indentationLevel);
32+
}
33+
34+
@Override
35+
public String createIndentationString(int indentationLevel) {
36+
Preconditions.checkArgument(
37+
indentationLevel >= 0,
38+
"Indentation level cannot be less than zero. Given: %s",
39+
indentationLevel);
40+
int spaces = indentationLevel * INDENTATION_SIZE;
41+
StringBuilder buf = new StringBuilder(spaces);
42+
for (int i = 0; i < spaces; i++) {
43+
buf.append(' ');
44+
}
45+
return buf.toString();
46+
}
47+
48+
/** Runs the Google Java formatter on the given source, with only the given ranges specified. */
49+
private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
50+
try {
51+
boolean includeComments =
52+
(kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
53+
kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
54+
SnippetKind snippetKind;
55+
switch (kind) {
56+
case ASTParser.K_EXPRESSION:
57+
snippetKind = SnippetKind.EXPRESSION;
58+
break;
59+
case ASTParser.K_STATEMENTS:
60+
snippetKind = SnippetKind.STATEMENTS;
61+
break;
62+
case ASTParser.K_CLASS_BODY_DECLARATIONS:
63+
snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
64+
break;
65+
case ASTParser.K_COMPILATION_UNIT:
66+
snippetKind = SnippetKind.COMPILATION_UNIT;
67+
break;
68+
default:
69+
throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
70+
}
71+
List<Replacement> replacements =
72+
new SnippetFormatter()
73+
.format(
74+
snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
75+
if (idempotent(source, regions, replacements)) {
76+
// Do not create edits if there's no diff.
77+
return null;
78+
}
79+
// Convert replacements to text edits.
80+
return editFromReplacements(replacements);
81+
} catch (IllegalArgumentException | FormatterException exception) {
82+
// Do not format on errors.
83+
return null;
84+
}
85+
}
86+
87+
private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
88+
List<Range<Integer>> ranges = new ArrayList<>();
89+
for (IRegion region : regions) {
90+
ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
91+
}
92+
return ranges;
93+
}
94+
95+
/** @return {@code true} if input and output texts are equal, else {@code false}. */
96+
private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
97+
// This implementation only checks for single replacement.
98+
if (replacements.size() == 1) {
99+
Replacement replacement = replacements.get(0);
100+
String output = replacement.getReplacementString();
101+
// Entire source case: input = output, nothing changed.
102+
if (output.equals(source)) {
103+
return true;
104+
}
105+
// Single region and single replacement case: if they are equal, nothing changed.
106+
if (regions.length == 1) {
107+
Range<Integer> range = replacement.getReplaceRange();
108+
String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
109+
if (output.equals(snippet)) {
110+
return true;
111+
}
112+
}
113+
}
114+
return false;
115+
}
116+
117+
private TextEdit editFromReplacements(List<Replacement> replacements) {
118+
// Split the replacements that cross line boundaries.
119+
TextEdit edit = new MultiTextEdit();
120+
for (Replacement replacement : replacements) {
121+
Range<Integer> replaceRange = replacement.getReplaceRange();
122+
edit.addChild(
123+
new ReplaceEdit(
124+
replaceRange.lowerEndpoint(),
125+
replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
126+
replacement.getReplacementString()));
127+
}
128+
return edit;
129+
}
130+
}

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FormatterHandler.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
4141
import org.eclipse.jdt.ls.core.internal.preferences.FormatterPreferences;
4242
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
43+
import org.eclipse.jdt.ls.core.internal.preferences.Preferences.FormatterScheme;
4344
import org.eclipse.jface.text.BadLocationException;
4445
import org.eclipse.jface.text.Document;
4546
import org.eclipse.jface.text.IDocument;
@@ -57,6 +58,8 @@
5758
import org.eclipse.text.edits.ReplaceEdit;
5859
import org.eclipse.text.edits.TextEdit;
5960

61+
import com.google.googlejavaformat.java.GoogleJavaFormatter;
62+
6063
/**
6164
* @author IBM Corporation (Markus Keller)
6265
*/
@@ -109,18 +112,29 @@ private List<org.eclipse.lsp4j.TextEdit> format(ICompilationUnit cu, IDocument d
109112
return Collections.emptyList();
110113
}
111114

112-
CodeFormatter formatter = ToolFactory.createCodeFormatter(getOptions(options, cu));
115+
CodeFormatter formatter;
116+
if (this.preferenceManager.getPreferences().getFormatterScheme().equals(FormatterScheme.google)) {
117+
formatter = new GoogleJavaFormatter();
118+
} else {
119+
formatter = ToolFactory.createCodeFormatter(getOptions(options, cu));
120+
}
113121

114122
String lineDelimiter = TextUtilities.getDefaultLineDelimiter(document);
115123
String sourceToFormat = document.get();
116124
int kind = getFormattingKind(cu, includeComments);
117-
TextEdit format = formatter.format(kind, sourceToFormat, region.getOffset(), region.getLength(), 0, lineDelimiter);
118-
if (format == null || format.getChildren().length == 0 || monitor.isCanceled()) {
119-
// nothing to return
120-
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();
125+
126+
try {
127+
TextEdit format = formatter.format(kind, sourceToFormat, region.getOffset(), region.getLength(), 0, lineDelimiter);
128+
if (format == null || format.getChildren().length == 0 || monitor.isCanceled()) {
129+
// nothing to return
130+
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();
131+
}
132+
MultiTextEdit flatEdit = TextEditUtil.flatten(format);
133+
return convertEdits(flatEdit.getChildren(), document);
134+
} catch (Throwable e) {
135+
JavaLanguageServerPlugin.logException(e);
121136
}
122-
MultiTextEdit flatEdit = TextEditUtil.flatten(format);
123-
return convertEdits(flatEdit.getChildren(), document);
137+
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();
124138
}
125139

126140
private int getFormattingKind(ICompilationUnit cu, boolean includeComments) {

org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ static FeatureStatus fromString(String value, FeatureStatus defaultStatus) {
729729
}
730730

731731
public static enum FormatterScheme {
732-
eclipse;
732+
eclipse, google;
733733

734734
static FormatterScheme fromString(String value, FormatterScheme defaultValue) {
735735
if (value != null) {
@@ -1690,6 +1690,10 @@ public Map<String, String> getFormatterSettings() {
16901690
return this.formatterSettings;
16911691
}
16921692

1693+
public FormatterScheme getFormatterScheme() {
1694+
return this.formatterScheme;
1695+
}
1696+
16931697
public String getSettingsUrl() {
16941698
return settingsUrl;
16951699
}

0 commit comments

Comments
 (0)