Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion launch/jdt.ls.remote.server.launch
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<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"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false"/>
<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"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="product" value="org.eclipse.sdk.ide"/>
<setAttribute key="selected_features"/>
Expand Down
2 changes: 1 addition & 1 deletion launch/jdt.ls.socket-stream.launch
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<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"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djava.import.generatesMetadataFilesAtProjectRoot=false"/>
<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"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="product" value="org.eclipse.sdk.ide"/>
<setAttribute key="selected_features"/>
Expand Down
1 change: 1 addition & 0 deletions org.eclipse.jdt.ls.core/.classpath
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry exported="true" kind="lib" path="lib/jsoup-1.14.2.jar"/>
<classpathentry exported="true" kind="lib" path="lib/remark-1.2.0.jar"/>
<classpathentry exported="true" kind="lib" path="lib/google-java-format-1.13.0.jar"/>
<classpathentry kind="src" path="src/"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
1 change: 1 addition & 0 deletions org.eclipse.jdt.ls.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Export-Package: org.eclipse.jdt.ls.core.contentassist;x-friends:="org.eclipse.jd
org.eclipse.lsp4j.legacy.typeHierarchy;x-friends:="org.eclipse.jdt.ls.tests"
Bundle-ClassPath: lib/jsoup-1.14.2.jar,
lib/remark-1.2.0.jar,
lib/google-java-format-1.13.0.jar,
.
Bundle-Vendor: %Bundle-Vendor
Automatic-Module-Name: org.eclipse.jdt.ls.core
1 change: 1 addition & 0 deletions org.eclipse.jdt.ls.core/build.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ bin.includes = META-INF/,\
plugin.xml,\
lib/jsoup-1.14.2.jar,\
lib/remark-1.2.0.jar,\
lib/google-java-format-1.13.0.jar,\
lifecycle-mapping-metadata.xml,\
plugin.properties,\
gradle/checksums/checksums.json,\
Expand Down
5 changes: 5 additions & 0 deletions org.eclipse.jdt.ls.core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<artifactId>jsoup</artifactId>
<version>1.14.2</version>
</artifactItem>
<artifactItem>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
<version>1.13.0</version>
</artifactItem>
</artifactItems>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.google.googlejavaformat.java;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this file is directly copied from https://github.com/google/google-java-format/blob/master/eclipse_plugin/src/com/google/googlejavaformat/java/GoogleJavaFormatter.java, since the google-java-format plugin is not available in the maven central.


import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import com.google.googlejavaformat.java.SnippetFormatter.SnippetKind;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;

/** Runs the Google Java formatter on the given code. */
public class GoogleJavaFormatter extends CodeFormatter {

private static final int INDENTATION_SIZE = 2;

@Override
public TextEdit format(
int kind, String source, int offset, int length, int indentationLevel, String lineSeparator) {
IRegion[] regions = new IRegion[] {new Region(offset, length)};
return formatInternal(kind, source, regions, indentationLevel);
}

@Override
public TextEdit format(
int kind, String source, IRegion[] regions, int indentationLevel, String lineSeparator) {
return formatInternal(kind, source, regions, indentationLevel);
}

@Override
public String createIndentationString(int indentationLevel) {
Preconditions.checkArgument(
indentationLevel >= 0,
"Indentation level cannot be less than zero. Given: %s",
indentationLevel);
int spaces = indentationLevel * INDENTATION_SIZE;
StringBuilder buf = new StringBuilder(spaces);
for (int i = 0; i < spaces; i++) {
buf.append(' ');
}
return buf.toString();
}

/** Runs the Google Java formatter on the given source, with only the given ranges specified. */
private TextEdit formatInternal(int kind, String source, IRegion[] regions, int initialIndent) {
try {
boolean includeComments =
(kind & CodeFormatter.F_INCLUDE_COMMENTS) == CodeFormatter.F_INCLUDE_COMMENTS;
kind &= ~CodeFormatter.F_INCLUDE_COMMENTS;
SnippetKind snippetKind;
switch (kind) {
case ASTParser.K_EXPRESSION:
snippetKind = SnippetKind.EXPRESSION;
break;
case ASTParser.K_STATEMENTS:
snippetKind = SnippetKind.STATEMENTS;
break;
case ASTParser.K_CLASS_BODY_DECLARATIONS:
snippetKind = SnippetKind.CLASS_BODY_DECLARATIONS;
break;
case ASTParser.K_COMPILATION_UNIT:
snippetKind = SnippetKind.COMPILATION_UNIT;
break;
default:
throw new IllegalArgumentException(String.format("Unknown snippet kind: %d", kind));
}
List<Replacement> replacements =
new SnippetFormatter()
.format(
snippetKind, source, rangesFromRegions(regions), initialIndent, includeComments);
if (idempotent(source, regions, replacements)) {
// Do not create edits if there's no diff.
return null;
}
// Convert replacements to text edits.
return editFromReplacements(replacements);
} catch (IllegalArgumentException | FormatterException exception) {
// Do not format on errors.
return null;
}
}

private List<Range<Integer>> rangesFromRegions(IRegion[] regions) {
List<Range<Integer>> ranges = new ArrayList<>();
for (IRegion region : regions) {
ranges.add(Range.closedOpen(region.getOffset(), region.getOffset() + region.getLength()));
}
return ranges;
}

/** @return {@code true} if input and output texts are equal, else {@code false}. */
private boolean idempotent(String source, IRegion[] regions, List<Replacement> replacements) {
// This implementation only checks for single replacement.
if (replacements.size() == 1) {
Replacement replacement = replacements.get(0);
String output = replacement.getReplacementString();
// Entire source case: input = output, nothing changed.
if (output.equals(source)) {
return true;
}
// Single region and single replacement case: if they are equal, nothing changed.
if (regions.length == 1) {
Range<Integer> range = replacement.getReplaceRange();
String snippet = source.substring(range.lowerEndpoint(), range.upperEndpoint());
if (output.equals(snippet)) {
return true;
}
}
}
return false;
}

private TextEdit editFromReplacements(List<Replacement> replacements) {
// Split the replacements that cross line boundaries.
TextEdit edit = new MultiTextEdit();
for (Replacement replacement : replacements) {
Range<Integer> replaceRange = replacement.getReplaceRange();
edit.addChild(
new ReplaceEdit(
replaceRange.lowerEndpoint(),
replaceRange.upperEndpoint() - replaceRange.lowerEndpoint(),
replacement.getReplacementString()));
}
return edit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
import org.eclipse.jdt.internal.ui.preferences.formatter.ProfileVersionerCore;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.preferences.FormatterPreferences;
import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager;
import org.eclipse.jdt.ls.core.internal.preferences.Preferences.FormatterScheme;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
Expand All @@ -56,6 +58,8 @@
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;

import com.google.googlejavaformat.java.GoogleJavaFormatter;

/**
* @author IBM Corporation (Markus Keller)
*/
Expand Down Expand Up @@ -108,18 +112,29 @@ private List<org.eclipse.lsp4j.TextEdit> format(ICompilationUnit cu, IDocument d
return Collections.emptyList();
}

CodeFormatter formatter = ToolFactory.createCodeFormatter(getOptions(options, cu));
CodeFormatter formatter;
if (this.preferenceManager.getPreferences().getFormatterScheme().equals(FormatterScheme.google)) {
formatter = new GoogleJavaFormatter();
} else {
formatter = ToolFactory.createCodeFormatter(getOptions(options, cu));
}

String lineDelimiter = TextUtilities.getDefaultLineDelimiter(document);
String sourceToFormat = document.get();
int kind = getFormattingKind(cu, includeComments);
TextEdit format = formatter.format(kind, sourceToFormat, region.getOffset(), region.getLength(), 0, lineDelimiter);
if (format == null || format.getChildren().length == 0 || monitor.isCanceled()) {
// nothing to return
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();

try {
TextEdit format = formatter.format(kind, sourceToFormat, region.getOffset(), region.getLength(), 0, lineDelimiter);
if (format == null || format.getChildren().length == 0 || monitor.isCanceled()) {
// nothing to return
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();
}
MultiTextEdit flatEdit = TextEditUtil.flatten(format);
return convertEdits(flatEdit.getChildren(), document);
} catch (Throwable e) {
JavaLanguageServerPlugin.logException(e);
}
MultiTextEdit flatEdit = TextEditUtil.flatten(format);
return convertEdits(flatEdit.getChildren(), document);
return Collections.<org.eclipse.lsp4j.TextEdit>emptyList();
}

private int getFormattingKind(ICompilationUnit cu, boolean includeComments) {
Expand Down Expand Up @@ -150,6 +165,7 @@ public static Map<String, String> getOptions(FormattingOptions options, ICompila
Map<String, String> customOptions = options.entrySet().stream().filter(map -> chekIfValueIsNotNull(map.getValue())).collect(toMap(e -> e.getKey(), e -> getOptionValue(e.getValue())));

eclipseOptions.putAll(customOptions);
eclipseOptions.putAll(FormatterPreferences.toEclipseOptions(JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getFormatterSettings()));

Integer tabSize = options.getTabSize();
if (tabSize != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ls.core.internal.preferences;

import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;

public class FormatterPreferences {

// @formatter:off
// < JDTLS settings, eclipse settings >
private static Map<String, String> eclipseOptions = Map.ofEntries(
Map.entry("lineSplit", DefaultCodeFormatterConstants.FORMATTER_LINE_SPLIT),
Map.entry("comment.line.length", DefaultCodeFormatterConstants.FORMATTER_COMMENT_LINE_LENGTH),
Map.entry("join.wrapped.lines", DefaultCodeFormatterConstants.FORMATTER_JOIN_WRAPPED_LINES),
Map.entry("use.on.off.tags", DefaultCodeFormatterConstants.FORMATTER_USE_ON_OFF_TAGS),
Map.entry("disabling.tag", DefaultCodeFormatterConstants.FORMATTER_DISABLING_TAG),
Map.entry("enabling.tag", DefaultCodeFormatterConstants.FORMATTER_ENABLING_TAG),
Map.entry("indent.parameter.description", DefaultCodeFormatterConstants.FORMATTER_COMMENT_INDENT_PARAMETER_DESCRIPTION),
Map.entry("indent.root.tags", DefaultCodeFormatterConstants.FORMATTER_COMMENT_INDENT_ROOT_TAGS),
Map.entry("align.tags.descriptions.grouped", DefaultCodeFormatterConstants.FORMATTER_COMMENT_ALIGN_TAGS_DESCREIPTIONS_GROUPED),
Map.entry("align.tags.names.descriptions", DefaultCodeFormatterConstants.FORMATTER_COMMENT_ALIGN_TAGS_NAMES_DESCRIPTIONS),
Map.entry("clear.blank.lines.in.javadoc.comment", DefaultCodeFormatterConstants.FORMATTER_COMMENT_CLEAR_BLANK_LINES_IN_JAVADOC_COMMENT),
Map.entry("blank.lines.between.import.groups", DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BETWEEN_IMPORT_GROUPS),
Map.entry("format.line.comments", DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_LINE_COMMENT),
Map.entry("format.block.comments", DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_BLOCK_COMMENT),
Map.entry("format.javadoc.comments", DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_JAVADOC_COMMENT),
Map.entry("keep.loop.body.block.on.one.line", DefaultCodeFormatterConstants.FORMATTER_KEEP_LOOP_BODY_BLOCK_ON_ONE_LINE),
Map.entry("keep.anonymous.type.declaration.on.one.line", DefaultCodeFormatterConstants.FORMATTER_KEEP_ANONYMOUS_TYPE_DECLARATION_ON_ONE_LINE),
Map.entry("keep.type.declaration.on.one.line", DefaultCodeFormatterConstants.FORMATTER_KEEP_TYPE_DECLARATION_ON_ONE_LINE),
Map.entry("keep.method.body.on.one.line", DefaultCodeFormatterConstants.FORMATTER_KEEP_METHOD_BODY_ON_ONE_LINE),
Map.entry("insert.space.after.closing.angle.bracket.in.type.arguments", DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_CLOSING_ANGLE_BRACKET_IN_TYPE_ARGUMENTS),
Map.entry("insert.space.after.opening.brace.in.array.initializer", DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_OPENING_BRACE_IN_ARRAY_INITIALIZER),
Map.entry("insert.space.before.closing.brace.in.array.initializer", DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_CLOSING_BRACE_IN_ARRAY_INITIALIZER),
Map.entry("brace.position.for.block", DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK),
Map.entry("alignment.for.enum.constants", DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ENUM_CONSTANTS),
Map.entry("alignment.for.parameters.in.method.declaration", DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION)
);

// < JDTLS camelCase value, eclipse underline value>
private static Map<String, String> valueMap = Map.ofEntries(
Map.entry("commonLines", "common_lines"),
Map.entry("separateLinesIfNotEmpty", "separate_lines_if_not_empty"),
Map.entry("separateLinesIfWrapped", "separate_lines_if_wrapped"),
Map.entry("separateLines", "separate_lines"),
Map.entry("preservePositions", "preserve_positions"),
Map.entry("never", "one_line_never"),
Map.entry("ifEmpty", "one_line_if_empty"),
Map.entry("ifSingleItem", "one_line_if_single_item"),
Map.entry("always", "one_line_always"),
Map.entry("preserve", "one_line_preserve"),
Map.entry("doNotInsert", "do not insert"),
Map.entry("endOfLine", "end_of_line"),
Map.entry("nextLine", "next_line"),
Map.entry("nextLineIndented", "next_line_indented"),
Map.entry("nextLineOnWrap", "next_line_on_wrap")
);
// @formatter:on

/**
* Convert known language server formatter options to eclipse formatter
* settings.
*
* @param lsOptions
* the given language server formatter options
* @return the converted eclipse formatter options
*/
public static Map<String, String> toEclipseOptions(Map<String, String> lsOptions) {
return lsOptions.entrySet().stream().filter(option -> eclipseOptions.containsKey(option.getKey())).collect(Collectors.toMap(option -> eclipseOptions.get(option.getKey()), option -> {
String value = option.getValue();
if (valueMap.containsKey(value)) {
return valueMap.get(value);
}
return value;
}));
}

/**
* Convert language server formatter alignment value to eclipse formatter
* alignment value.
*
* @param alignmentValue
* the given language server formatter alignment value
* @return the converted eclipse formatter alignment value
*/
public static String getEclipseAlignmentValue(Map<String, Object> alignmentValue) {
Object forceSplit = alignmentValue.getOrDefault("force.split", Boolean.FALSE);
Object indentationStyle = alignmentValue.getOrDefault("indentation.style", "indentDefault");
Object wrappingStyle = alignmentValue.getOrDefault("wrapping.style", "compact");
if (forceSplit instanceof Boolean forceSplitBoolean && indentationStyle instanceof String indentationStyleString && wrappingStyle instanceof String wrappingStyleString) {
int indentationStyleInt = 0;
switch (indentationStyleString) {
case "indentDefault":
indentationStyleInt = DefaultCodeFormatterConstants.INDENT_DEFAULT;
break;
case "indentOnColumn":
indentationStyleInt = DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
break;
case "indentByOne":
indentationStyleInt = DefaultCodeFormatterConstants.INDENT_BY_ONE;
break;
default:
return null;
}
int wrappingStyleInt = 0;
switch (wrappingStyleString) {
case "noSplit":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_NO_SPLIT;
break;
case "compact":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_COMPACT;
break;
case "compactFirstBreak":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_COMPACT_FIRST_BREAK;
break;
case "onePerLine":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_ONE_PER_LINE;
break;
case "nextShifted":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_NEXT_SHIFTED;
break;
case "nextPerLine":
wrappingStyleInt = DefaultCodeFormatterConstants.WRAP_NEXT_PER_LINE;
break;
default:
return null;
}
return DefaultCodeFormatterConstants.createAlignmentValue(forceSplitBoolean, wrappingStyleInt, indentationStyleInt);
}
return null;
}
}
Loading