Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce VS Code/JDTLS formatter scheme #2589

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
Expand Down Expand Up @@ -150,6 +151,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,145 @@
/*******************************************************************************
* 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("parentheses.positions.in.method.invocation", DefaultCodeFormatterConstants.FORMATTER_PARENTHESES_POSITIONS_IN_METHOD_INVOCATION),
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public class Preferences {
*/
public static final String JAVA_CONFIGURATION_RUNTIMES = "java.configuration.runtimes";
public static final List<String> JAVA_CONFIGURATION_RUNTIMES_DEFAULT;
/**
* Specifies the java formatter scheme.
*/
public static final String JAVA_FORMATTER_SCHEME = "java.format.scheme";
/**
* Specifies the file path or url to the formatter xml url.
*/
Expand Down Expand Up @@ -619,6 +623,8 @@ public class Preferences {
private String formatterUrl;
private String settingsUrl;
private String formatterProfileName;
private Map<String, String> formatterSettings;
private FormatterScheme formatterScheme;
private Collection<IPath> rootPaths;
private Collection<IPath> triggerFiles;
private Collection<IPath> projectConfigurations;
Expand Down Expand Up @@ -722,6 +728,22 @@ static FeatureStatus fromString(String value, FeatureStatus defaultStatus) {
}
}

public static enum FormatterScheme {
eclipse;

static FormatterScheme fromString(String value, FormatterScheme defaultValue) {
if (value != null) {
String val = value.toLowerCase();
try {
return valueOf(val);
} catch (Exception e) {
//fall back to default severity
}
}
return defaultValue;
}
}

public static class ReferencedLibraries {
private Set<String> include;
private Set<String> exclude;
Expand Down Expand Up @@ -850,6 +872,8 @@ public Preferences() {
formatterUrl = null;
settingsUrl = null;
formatterProfileName = null;
formatterSettings = new HashMap<>();
formatterScheme = FormatterScheme.eclipse;
importOrder = JAVA_IMPORT_ORDER_DEFAULT;
filteredTypes = JAVA_COMPLETION_FILTERED_TYPES_DEFAULT;
parallelBuildsCount = PreferenceInitializer.PREF_MAX_CONCURRENT_BUILDS_DEFAULT;
Expand Down Expand Up @@ -1122,6 +1146,52 @@ public static Preferences createFrom(Map<String, Object> configuration) {
boolean javaFormatComments = getBoolean(configuration, JAVA_FORMAT_COMMENTS, true);
prefs.setJavaFormatComments(javaFormatComments);

String formatterSchemeString = getString(configuration, JAVA_FORMATTER_SCHEME);
if (formatterSchemeString != null) {
FormatterScheme formatterScheme = FormatterScheme.fromString(formatterSchemeString, FormatterScheme.eclipse);
if (formatterScheme != null) {
prefs.setFormatterScheme(formatterScheme);
}
} else {
Object formatterSchemeValue = getValue(configuration, JAVA_FORMATTER_SCHEME);
if (formatterSchemeValue instanceof Map) {
Map<String, Object> formatterSchemeValueMap = (Map<String, Object>) formatterSchemeValue;
Object style = formatterSchemeValueMap.getOrDefault("style", FormatterScheme.eclipse.toString());
if (style instanceof String schemeString) {
FormatterScheme formatterScheme = FormatterScheme.fromString(schemeString, FormatterScheme.eclipse);
if (formatterScheme != null) {
prefs.setFormatterScheme(formatterScheme);
}
}
Object path = formatterSchemeValueMap.getOrDefault("path", null);
if (path instanceof String pathString) {
prefs.setFormatterUrl(pathString);
}
Object profile = formatterSchemeValueMap.getOrDefault("profile", null);
if (profile instanceof String profileString) {
prefs.setFormatterProfileName(profileString);
}
Object configurations = formatterSchemeValueMap.getOrDefault("configurations", new HashMap<>());
if (configurations instanceof Map) {
Map<String, Object> configurationMap = (Map<String, Object>) configurations;
Map<String, String> formatterSettingsMap = new HashMap<>();
configurationMap.forEach((k, v) -> {
Object value = v;
if (value instanceof Double d) {
value = d.intValue();
} else if (value instanceof Map) {
Map<String, Object> valueMap = (Map<String, Object>) value;
value = FormatterPreferences.getEclipseAlignmentValue(valueMap);
}
if (value != null) {
formatterSettingsMap.put(String.valueOf(k), String.valueOf(value));
}
});
prefs.setFormatterSettings(formatterSettingsMap);
}
}
}

List<String> javaImportOrder = getList(configuration, JAVA_IMPORT_ORDER_KEY, JAVA_IMPORT_ORDER_DEFAULT);
prefs.setImportOrder(javaImportOrder);

Expand Down Expand Up @@ -1288,6 +1358,15 @@ public Preferences setSettingsUrl(String settingsUrl) {
return this;
}

public Preferences setFormatterSettings(Map<String, String> formatterSettings) {
this.formatterSettings = formatterSettings;
return this;
}

public void setFormatterScheme(FormatterScheme formatterScheme) {
this.formatterScheme = formatterScheme;
}

public Preferences setResourceFilters(List<String> resourceFilters) {
this.resourceFilters = resourceFilters == null ? new ArrayList<>() : resourceFilters;
return this;
Expand Down Expand Up @@ -1607,6 +1686,10 @@ public URI getFormatterAsURI() {
return asURI(formatterUrl);
}

public Map<String, String> getFormatterSettings() {
return this.formatterSettings;
}

public String getSettingsUrl() {
return settingsUrl;
}
Expand Down