aboutsummaryrefslogtreecommitdiff
path: root/idea_plugin
diff options
context:
space:
mode:
Diffstat (limited to 'idea_plugin')
-rw-r--r--idea_plugin/build.gradle62
-rw-r--r--idea_plugin/build.gradle.kts66
-rw-r--r--idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java246
-rw-r--r--idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java64
-rw-r--r--idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java172
-rw-r--r--idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java57
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form (renamed from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form)0
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java (renamed from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java)0
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java141
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java90
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java (renamed from idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java)12
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java (renamed from idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java)19
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java104
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java38
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java (renamed from idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java)0
-rw-r--r--idea_plugin/src/main/resources/META-INF/plugin.xml (renamed from idea_plugin/resources/META-INF/plugin.xml)47
-rw-r--r--idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java250
-rw-r--r--idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java168
18 files changed, 913 insertions, 623 deletions
diff --git a/idea_plugin/build.gradle b/idea_plugin/build.gradle
deleted file mode 100644
index d9f769d..0000000
--- a/idea_plugin/build.gradle
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2017 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-plugins {
- id "org.jetbrains.intellij" version "1.3.1"
-}
-
-repositories {
- mavenCentral()
-}
-
-ext {
- googleJavaFormatVersion = "1.13.0"
-}
-
-apply plugin: "org.jetbrains.intellij"
-apply plugin: "java"
-
-sourceCompatibility = JavaVersion.VERSION_11
-targetCompatibility = JavaVersion.VERSION_11
-
-intellij {
- pluginName = "google-java-format"
- plugins = ["java"]
- version = "221.3427-EAP-CANDIDATE-SNAPSHOT"
-}
-
-patchPluginXml {
- pluginDescription = "Formats source code using the google-java-format tool. This version of " +
- "the plugin uses version ${googleJavaFormatVersion} of the tool."
- version.set("${googleJavaFormatVersion}.0")
- sinceBuild = "203"
- untilBuild = ""
-}
-
-publishPlugin {
- token = project.ext.properties.jetbrainsPluginRepoToken
-}
-
-sourceSets {
- main {
- java.srcDir "src"
- resources.srcDir "resources"
- }
-}
-
-dependencies {
- implementation "com.google.googlejavaformat:google-java-format:${googleJavaFormatVersion}"
-}
diff --git a/idea_plugin/build.gradle.kts b/idea_plugin/build.gradle.kts
new file mode 100644
index 0000000..5e96582
--- /dev/null
+++ b/idea_plugin/build.gradle.kts
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins { id("org.jetbrains.intellij") version "1.15.0" }
+
+apply(plugin = "org.jetbrains.intellij")
+
+apply(plugin = "java")
+
+repositories { mavenCentral() }
+
+val googleJavaFormatVersion = "1.17.0"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+intellij {
+ pluginName.set("google-java-format")
+ plugins.set(listOf("java"))
+ version.set("2021.3")
+}
+
+tasks {
+ patchPluginXml {
+ version.set("${googleJavaFormatVersion}.0")
+ sinceBuild.set("213")
+ untilBuild.set("")
+ }
+
+ publishPlugin {
+ val jetbrainsPluginRepoToken: String by project
+ token.set(jetbrainsPluginRepoToken)
+ }
+
+ withType<Test>().configureEach {
+ jvmArgs(
+ "--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",
+ )
+ }
+}
+
+dependencies {
+ implementation("com.google.googlejavaformat:google-java-format:${googleJavaFormatVersion}")
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("com.google.truth:truth:1.1.5")
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java b/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java
deleted file mode 100644
index af5da95..0000000
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/CodeStyleManagerDecorator.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.googlejavaformat.intellij;
-
-import com.intellij.formatting.FormattingMode;
-import com.intellij.lang.ASTNode;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.fileTypes.FileType;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Computable;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.codeStyle.ChangedRangesInfo;
-import com.intellij.psi.codeStyle.CodeStyleManager;
-import com.intellij.psi.codeStyle.DocCommentSettings;
-import com.intellij.psi.codeStyle.FormattingModeAwareIndentAdjuster;
-import com.intellij.psi.codeStyle.Indent;
-import com.intellij.util.IncorrectOperationException;
-import com.intellij.util.ThrowableRunnable;
-import java.util.Collection;
-import org.checkerframework.checker.nullness.qual.Nullable;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Decorates the {@link CodeStyleManager} abstract class by delegating to a concrete implementation
- * instance (likely IntelliJ's default instance).
- */
-@SuppressWarnings("deprecation")
-class CodeStyleManagerDecorator extends CodeStyleManager
- implements FormattingModeAwareIndentAdjuster {
-
- private final CodeStyleManager delegate;
-
- CodeStyleManagerDecorator(CodeStyleManager delegate) {
- this.delegate = delegate;
- }
-
- CodeStyleManager getDelegate() {
- return delegate;
- }
-
- @Override
- public @NotNull Project getProject() {
- return delegate.getProject();
- }
-
- @Override
- public @NotNull PsiElement reformat(@NotNull PsiElement element)
- throws IncorrectOperationException {
- return delegate.reformat(element);
- }
-
- @Override
- public @NotNull PsiElement reformat(@NotNull PsiElement element, boolean canChangeWhiteSpacesOnly)
- throws IncorrectOperationException {
- return delegate.reformat(element, canChangeWhiteSpacesOnly);
- }
-
- @Override
- public PsiElement reformatRange(@NotNull PsiElement element, int startOffset, int endOffset)
- throws IncorrectOperationException {
- return delegate.reformatRange(element, startOffset, endOffset);
- }
-
- @Override
- public PsiElement reformatRange(
- @NotNull PsiElement element, int startOffset, int endOffset, boolean canChangeWhiteSpacesOnly)
- throws IncorrectOperationException {
- return delegate.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly);
- }
-
- @Override
- public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset)
- throws IncorrectOperationException {
- delegate.reformatText(file, startOffset, endOffset);
- }
-
- @Override
- public void reformatText(@NotNull PsiFile file, @NotNull Collection<? extends TextRange> ranges)
- throws IncorrectOperationException {
- delegate.reformatText(file, ranges);
- }
-
- @Override
- public void reformatTextWithContext(
- @NotNull PsiFile psiFile, @NotNull ChangedRangesInfo changedRangesInfo)
- throws IncorrectOperationException {
- delegate.reformatTextWithContext(psiFile, changedRangesInfo);
- }
-
- @Override
- public void reformatTextWithContext(
- @NotNull PsiFile file, @NotNull Collection<? extends TextRange> ranges)
- throws IncorrectOperationException {
- delegate.reformatTextWithContext(file, ranges);
- }
-
- @Override
- public void adjustLineIndent(@NotNull PsiFile file, TextRange rangeToAdjust)
- throws IncorrectOperationException {
- delegate.adjustLineIndent(file, rangeToAdjust);
- }
-
- @Override
- public int adjustLineIndent(@NotNull PsiFile file, int offset)
- throws IncorrectOperationException {
- return delegate.adjustLineIndent(file, offset);
- }
-
- @Override
- public int adjustLineIndent(@NotNull Document document, int offset) {
- return delegate.adjustLineIndent(document, offset);
- }
-
- public void scheduleIndentAdjustment(@NotNull Document document, int offset) {
- delegate.scheduleIndentAdjustment(document, offset);
- }
-
- @Override
- public boolean isLineToBeIndented(@NotNull PsiFile file, int offset) {
- return delegate.isLineToBeIndented(file, offset);
- }
-
- @Override
- @Nullable
- public String getLineIndent(@NotNull PsiFile file, int offset) {
- return delegate.getLineIndent(file, offset);
- }
-
- @Override
- @Nullable
- public String getLineIndent(@NotNull PsiFile file, int offset, FormattingMode mode) {
- return delegate.getLineIndent(file, offset, mode);
- }
-
- @Override
- @Nullable
- public String getLineIndent(@NotNull Document document, int offset) {
- return delegate.getLineIndent(document, offset);
- }
-
- @Override
- public Indent getIndent(String text, FileType fileType) {
- return delegate.getIndent(text, fileType);
- }
-
- @Override
- public String fillIndent(Indent indent, FileType fileType) {
- return delegate.fillIndent(indent, fileType);
- }
-
- @Override
- public Indent zeroIndent() {
- return delegate.zeroIndent();
- }
-
- @Override
- public void reformatNewlyAddedElement(@NotNull ASTNode block, @NotNull ASTNode addedElement)
- throws IncorrectOperationException {
- delegate.reformatNewlyAddedElement(block, addedElement);
- }
-
- @Override
- public boolean isSequentialProcessingAllowed() {
- return delegate.isSequentialProcessingAllowed();
- }
-
- @Override
- public void performActionWithFormatterDisabled(Runnable r) {
- delegate.performActionWithFormatterDisabled(r);
- }
-
- @Override
- public <T extends Throwable> void performActionWithFormatterDisabled(ThrowableRunnable<T> r)
- throws T {
- delegate.performActionWithFormatterDisabled(r);
- }
-
- @Override
- public <T> T performActionWithFormatterDisabled(Computable<T> r) {
- return delegate.performActionWithFormatterDisabled(r);
- }
-
- @Override
- public int getSpacing(@NotNull PsiFile file, int offset) {
- return delegate.getSpacing(file, offset);
- }
-
- @Override
- public int getMinLineFeeds(@NotNull PsiFile file, int offset) {
- return delegate.getMinLineFeeds(file, offset);
- }
-
- @Override
- public void runWithDocCommentFormattingDisabled(
- @NotNull PsiFile file, @NotNull Runnable runnable) {
- delegate.runWithDocCommentFormattingDisabled(file, runnable);
- }
-
- @Override
- public @NotNull DocCommentSettings getDocCommentSettings(@NotNull PsiFile file) {
- return delegate.getDocCommentSettings(file);
- }
-
- // From FormattingModeAwareIndentAdjuster
-
- /** Uses same fallback as {@link CodeStyleManager#getCurrentFormattingMode}. */
- @Override
- public FormattingMode getCurrentFormattingMode() {
- if (delegate instanceof FormattingModeAwareIndentAdjuster) {
- return ((FormattingModeAwareIndentAdjuster) delegate).getCurrentFormattingMode();
- }
- return FormattingMode.REFORMAT;
- }
-
- @Override
- public int adjustLineIndent(
- final @NotNull Document document, final int offset, FormattingMode mode)
- throws IncorrectOperationException {
- if (delegate instanceof FormattingModeAwareIndentAdjuster) {
- return ((FormattingModeAwareIndentAdjuster) delegate)
- .adjustLineIndent(document, offset, mode);
- }
- return offset;
- }
-
- @Override
- public void scheduleReformatWhenSettingsComputed(@NotNull PsiFile file) {
- delegate.scheduleReformatWhenSettingsComputed(file);
- }
-}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java b/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java
deleted file mode 100644
index a5e69c9..0000000
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/FormatterUtil.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.googlejavaformat.intellij;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.common.collect.BoundType;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Range;
-import com.google.googlejavaformat.java.Formatter;
-import com.google.googlejavaformat.java.FormatterException;
-import com.intellij.openapi.util.TextRange;
-import java.util.Collection;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-final class FormatterUtil {
-
- private FormatterUtil() {}
-
- static Map<TextRange, String> getReplacements(
- Formatter formatter, String text, Collection<? extends TextRange> ranges) {
- try {
- ImmutableMap.Builder<TextRange, String> replacements = ImmutableMap.builder();
- formatter
- .getFormatReplacements(text, toRanges(ranges))
- .forEach(
- replacement ->
- replacements.put(
- toTextRange(replacement.getReplaceRange()),
- replacement.getReplacementString()));
- return replacements.build();
- } catch (FormatterException e) {
- return ImmutableMap.of();
- }
- }
-
- private static Collection<Range<Integer>> toRanges(Collection<? extends TextRange> textRanges) {
- return textRanges.stream()
- .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset()))
- .collect(Collectors.toList());
- }
-
- private static TextRange toTextRange(Range<Integer> range) {
- checkState(
- range.lowerBoundType().equals(BoundType.CLOSED)
- && range.upperBoundType().equals(BoundType.OPEN));
- return new TextRange(range.lowerEndpoint(), range.upperEndpoint());
- }
-}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java
deleted file mode 100644
index 3d56743..0000000
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatCodeStyleManager.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2015 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.googlejavaformat.intellij;
-
-import static java.util.Comparator.comparing;
-
-import com.google.common.collect.ImmutableList;
-import com.google.googlejavaformat.java.Formatter;
-import com.google.googlejavaformat.java.JavaFormatterOptions;
-import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
-import com.intellij.ide.highlighter.JavaFileType;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.command.WriteCommandAction;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.util.TextRange;
-import com.intellij.psi.PsiDocumentManager;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.codeStyle.ChangedRangesInfo;
-import com.intellij.psi.codeStyle.CodeStyleManager;
-import com.intellij.psi.impl.CheckUtil;
-import com.intellij.util.IncorrectOperationException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A {@link CodeStyleManager} implementation which formats .java files with google-java-format.
- * Formatting of all other types of files is delegated to IntelliJ's default implementation.
- */
-class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator {
-
- public GoogleJavaFormatCodeStyleManager(@NotNull CodeStyleManager original) {
- super(original);
- }
-
- @Override
- public void reformatText(@NotNull PsiFile file, int startOffset, int endOffset)
- throws IncorrectOperationException {
- if (overrideFormatterForFile(file)) {
- formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset)));
- } else {
- super.reformatText(file, startOffset, endOffset);
- }
- }
-
- @Override
- public void reformatText(@NotNull PsiFile file, @NotNull Collection<? extends TextRange> ranges)
- throws IncorrectOperationException {
- if (overrideFormatterForFile(file)) {
- formatInternal(file, ranges);
- } else {
- super.reformatText(file, ranges);
- }
- }
-
- @Override
- public void reformatTextWithContext(@NotNull PsiFile file, @NotNull ChangedRangesInfo info)
- throws IncorrectOperationException {
- List<TextRange> ranges = new ArrayList<>();
- if (info.insertedRanges != null) {
- ranges.addAll(info.insertedRanges);
- }
- ranges.addAll(info.allChangedRanges);
- reformatTextWithContext(file, ranges);
- }
-
- @Override
- public void reformatTextWithContext(
- @NotNull PsiFile file, @NotNull Collection<? extends TextRange> ranges) {
- if (overrideFormatterForFile(file)) {
- formatInternal(file, ranges);
- } else {
- super.reformatTextWithContext(file, ranges);
- }
- }
-
- @Override
- public PsiElement reformatRange(
- @NotNull PsiElement element,
- int startOffset,
- int endOffset,
- boolean canChangeWhiteSpacesOnly) {
- // Only handle elements that are PsiFile for now -- otherwise we need to search for some
- // element within the file at new locations given the original startOffset and endOffsets
- // to serve as the return value.
- PsiFile file = element instanceof PsiFile ? (PsiFile) element : null;
- if (file != null && canChangeWhiteSpacesOnly && overrideFormatterForFile(file)) {
- formatInternal(file, ImmutableList.of(new TextRange(startOffset, endOffset)));
- return file;
- } else {
- return super.reformatRange(element, startOffset, endOffset, canChangeWhiteSpacesOnly);
- }
- }
-
- /** Return whether this formatter can handle formatting the given file. */
- private boolean overrideFormatterForFile(PsiFile file) {
- return JavaFileType.INSTANCE.equals(file.getFileType())
- && GoogleJavaFormatSettings.getInstance(getProject()).isEnabled();
- }
-
- private void formatInternal(PsiFile file, Collection<? extends TextRange> ranges) {
- ApplicationManager.getApplication().assertWriteAccessAllowed();
- PsiDocumentManager documentManager = PsiDocumentManager.getInstance(getProject());
- documentManager.commitAllDocuments();
- CheckUtil.checkWritable(file);
-
- Document document = documentManager.getDocument(file);
-
- if (document == null) {
- return;
- }
- // If there are postponed PSI changes (e.g., during a refactoring), just abort.
- // If we apply them now, then the incoming text ranges may no longer be valid.
- if (documentManager.isDocumentBlockedByPsi(document)) {
- return;
- }
-
- format(document, ranges);
- }
-
- /**
- * Format the ranges of the given document.
- *
- * <p>Overriding methods will need to modify the document with the result of the external
- * formatter (usually using {@link #performReplacements(Document, Map)}).
- */
- private void format(Document document, Collection<? extends TextRange> ranges) {
- Style style = GoogleJavaFormatSettings.getInstance(getProject()).getStyle();
- Formatter formatter = new Formatter(JavaFormatterOptions.builder().style(style).build());
- performReplacements(
- document, FormatterUtil.getReplacements(formatter, document.getText(), ranges));
- }
-
- private void performReplacements(
- final Document document, final Map<TextRange, String> replacements) {
-
- if (replacements.isEmpty()) {
- return;
- }
-
- TreeMap<TextRange, String> sorted = new TreeMap<>(comparing(TextRange::getStartOffset));
- sorted.putAll(replacements);
- WriteCommandAction.runWriteCommandAction(
- getProject(),
- () -> {
- for (Entry<TextRange, String> entry : sorted.descendingMap().entrySet()) {
- document.replaceString(
- entry.getKey().getStartOffset(), entry.getKey().getEndOffset(), entry.getValue());
- }
- PsiDocumentManager.getInstance(getProject()).commitDocument(document);
- });
- }
-}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java b/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java
deleted file mode 100644
index c606736..0000000
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatInstaller.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2016 Google Inc. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.googlejavaformat.intellij;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.intellij.ide.plugins.IdeaPluginDescriptor;
-import com.intellij.ide.plugins.PluginManagerCore;
-import com.intellij.openapi.extensions.PluginId;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManagerListener;
-import com.intellij.psi.codeStyle.CodeStyleManager;
-import com.intellij.serviceContainer.ComponentManagerImpl;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * A component that replaces the default IntelliJ {@link CodeStyleManager} with one that formats via
- * google-java-format.
- */
-final class GoogleJavaFormatInstaller implements ProjectManagerListener {
-
- @Override
- public void projectOpened(@NotNull Project project) {
- installFormatter(project);
- }
-
- private static void installFormatter(Project project) {
- CodeStyleManager currentManager = CodeStyleManager.getInstance(project);
-
- if (currentManager instanceof GoogleJavaFormatCodeStyleManager) {
- currentManager = ((GoogleJavaFormatCodeStyleManager) currentManager).getDelegate();
- }
-
- setManager(project, new GoogleJavaFormatCodeStyleManager(currentManager));
- }
-
- private static void setManager(Project project, CodeStyleManager newManager) {
- ComponentManagerImpl platformComponentManager = (ComponentManagerImpl) project;
- IdeaPluginDescriptor plugin = PluginManagerCore.getPlugin(PluginId.getId("google-java-format"));
- checkState(plugin != null, "Couldn't locate our own PluginDescriptor.");
- platformComponentManager.registerServiceInstance(CodeStyleManager.class, newManager, plugin);
- }
-}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form
index 1db1d79..1db1d79 100644
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java
index 2f34b74..2f34b74 100644
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java
diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java
new file mode 100644
index 0000000..9d2d7a5
--- /dev/null
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Range;
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.FormatterException;
+import com.google.googlejavaformat.java.JavaFormatterOptions;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import com.intellij.formatting.service.AsyncDocumentFormattingService;
+import com.intellij.formatting.service.AsyncFormattingRequest;
+import com.intellij.ide.highlighter.JavaFileType;
+import com.intellij.lang.ImportOptimizer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.PsiFile;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+
+/** Uses {@code google-java-format} to reformat code. */
+public class GoogleJavaFormatFormattingService extends AsyncDocumentFormattingService {
+
+ public static final ImmutableSet<ImportOptimizer> IMPORT_OPTIMIZERS =
+ ImmutableSet.of(new GoogleJavaFormatImportOptimizer());
+
+ @Override
+ protected FormattingTask createFormattingTask(AsyncFormattingRequest request) {
+ Project project = request.getContext().getProject();
+
+ if (!JreConfigurationChecker.checkJreConfiguration(project)) {
+ return null;
+ }
+
+ Style style = GoogleJavaFormatSettings.getInstance(project).getStyle();
+ Formatter formatter = createFormatter(style, request.canChangeWhitespaceOnly());
+ return new GoogleJavaFormatFormattingTask(formatter, request);
+ }
+
+ @Override
+ protected String getNotificationGroupId() {
+ return Notifications.PARSING_ERROR_NOTIFICATION_GROUP;
+ }
+
+ @Override
+ protected String getName() {
+ return "google-java-format";
+ }
+
+ private static Formatter createFormatter(Style style, boolean canChangeWhiteSpaceOnly) {
+ JavaFormatterOptions.Builder optBuilder = JavaFormatterOptions.builder().style(style);
+ if (canChangeWhiteSpaceOnly) {
+ optBuilder.formatJavadoc(false).reorderModifiers(false);
+ }
+ return new Formatter(optBuilder.build());
+ }
+
+ @Override
+ public @NotNull Set<Feature> getFeatures() {
+ return Set.of(Feature.FORMAT_FRAGMENTS, Feature.OPTIMIZE_IMPORTS);
+ }
+
+ @Override
+ public boolean canFormat(@NotNull PsiFile file) {
+ return JavaFileType.INSTANCE.equals(file.getFileType())
+ && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled();
+ }
+
+ @Override
+ public @NotNull Set<ImportOptimizer> getImportOptimizers(@NotNull PsiFile file) {
+ return IMPORT_OPTIMIZERS;
+ }
+
+ private static final class GoogleJavaFormatFormattingTask implements FormattingTask {
+ private final Formatter formatter;
+ private final AsyncFormattingRequest request;
+
+ private GoogleJavaFormatFormattingTask(Formatter formatter, AsyncFormattingRequest request) {
+ this.formatter = formatter;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ try {
+ String formattedText = formatter.formatSource(request.getDocumentText(), toRanges(request));
+ request.onTextReady(formattedText);
+ } catch (FormatterException e) {
+ request.onError(
+ Notifications.PARSING_ERROR_TITLE,
+ Notifications.parsingErrorMessage(request.getContext().getContainingFile().getName()));
+ }
+ }
+
+ private static Collection<Range<Integer>> toRanges(AsyncFormattingRequest request) {
+ if (isWholeFile(request)) {
+ // The IDE sometimes passes invalid ranges when the file is unsaved before invoking the
+ // formatter. So this is a workaround for that issue.
+ return ImmutableList.of(Range.closedOpen(0, request.getDocumentText().length()));
+ }
+ return request.getFormattingRanges().stream()
+ .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset()))
+ .collect(ImmutableList.toImmutableList());
+ }
+
+ private static boolean isWholeFile(AsyncFormattingRequest request) {
+ List<TextRange> ranges = request.getFormattingRanges();
+ return ranges.size() == 1
+ && ranges.get(0).getStartOffset() == 0
+ // using greater than or equal because ranges are sometimes passed inaccurately
+ && ranges.get(0).getEndOffset() >= request.getDocumentText().length();
+ }
+
+ @Override
+ public boolean isRunUnderProgress() {
+ return true;
+ }
+
+ @Override
+ public boolean cancel() {
+ return false;
+ }
+ }
+}
diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java
new file mode 100644
index 0000000..4251242
--- /dev/null
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import com.google.common.util.concurrent.Runnables;
+import com.google.googlejavaformat.java.FormatterException;
+import com.google.googlejavaformat.java.ImportOrderer;
+import com.google.googlejavaformat.java.JavaFormatterOptions;
+import com.google.googlejavaformat.java.RemoveUnusedImports;
+import com.intellij.ide.highlighter.JavaFileType;
+import com.intellij.lang.ImportOptimizer;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+
+/** Uses {@code google-java-format} to optimize imports. */
+public class GoogleJavaFormatImportOptimizer implements ImportOptimizer {
+
+ @Override
+ public boolean supports(@NotNull PsiFile file) {
+ return JavaFileType.INSTANCE.equals(file.getFileType())
+ && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled();
+ }
+
+ @Override
+ public @NotNull Runnable processFile(@NotNull PsiFile file) {
+ Project project = file.getProject();
+
+ if (!JreConfigurationChecker.checkJreConfiguration(file.getProject())) {
+ return Runnables.doNothing();
+ }
+
+ PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
+ Document document = documentManager.getDocument(file);
+
+ if (document == null) {
+ return Runnables.doNothing();
+ }
+
+ JavaFormatterOptions.Style style = GoogleJavaFormatSettings.getInstance(project).getStyle();
+
+ final String origText = document.getText();
+ String text;
+ try {
+ text = ImportOrderer.reorderImports(RemoveUnusedImports.removeUnusedImports(origText), style);
+ } catch (FormatterException e) {
+ Notifications.displayParsingErrorNotification(project, file.getName());
+ return Runnables.doNothing();
+ }
+
+ // pointless to change document text if it hasn't changed, plus this can interfere with
+ // e.g. GoogleJavaFormattingService's output, i.e. it can overwrite the results from the main
+ // formatter.
+ if (text.equals(origText)) {
+ return Runnables.doNothing();
+ }
+
+ return () -> {
+ if (documentManager.isDocumentBlockedByPsi(document)) {
+ documentManager.doPostponedOperationsAndUnblockDocument(document);
+ }
+
+ // similarly to above, don't overwrite new document text if it has changed - we use
+ // getCharsSequence() as we should have `writeAction()` (which I think means effectively a
+ // write-lock) and it saves calling getText(), which apparently is expensive.
+ CharSequence newText = document.getCharsSequence();
+ if (CharSequence.compare(origText, newText) != 0) {
+ return;
+ }
+
+ document.setText(text);
+ };
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java
index 1e92a4b..ee187c0 100644
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java
@@ -18,7 +18,6 @@ package com.google.googlejavaformat.intellij;
import com.google.googlejavaformat.java.JavaFormatterOptions;
import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
@@ -30,10 +29,16 @@ import org.jetbrains.annotations.NotNull;
storages = {@Storage("google-java-format.xml")})
class GoogleJavaFormatSettings implements PersistentStateComponent<GoogleJavaFormatSettings.State> {
+ private final Project project;
+
private State state = new State();
+ GoogleJavaFormatSettings(Project project) {
+ this.project = project;
+ }
+
static GoogleJavaFormatSettings getInstance(Project project) {
- return ServiceManager.getService(project, GoogleJavaFormatSettings.class);
+ return project.getService(GoogleJavaFormatSettings.class);
}
@Nullable
@@ -56,6 +61,9 @@ class GoogleJavaFormatSettings implements PersistentStateComponent<GoogleJavaFor
}
void setEnabled(EnabledState enabled) {
+ if (enabled.equals(EnabledState.ENABLED)) {
+ JreConfigurationChecker.checkJreConfiguration(project);
+ }
state.enabled = enabled;
}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java
index 1906347..940def6 100644
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/InitialConfigurationProjectManagerListener.java
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java
@@ -21,22 +21,24 @@ import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManagerListener;
+import com.intellij.openapi.startup.StartupActivity;
import org.jetbrains.annotations.NotNull;
-final class InitialConfigurationProjectManagerListener implements ProjectManagerListener {
+final class InitialConfigurationStartupActivity implements StartupActivity.Background {
private static final String NOTIFICATION_TITLE = "Enable google-java-format";
private static final NotificationGroup NOTIFICATION_GROUP =
NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_TITLE);
@Override
- public void projectOpened(@NotNull Project project) {
+ public void runActivity(@NotNull Project project) {
GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project);
if (settings.isUninitialized()) {
settings.setEnabled(false);
displayNewUserNotification(project, settings);
+ } else if (settings.isEnabled()) {
+ JreConfigurationChecker.checkJreConfiguration(project);
}
}
@@ -47,11 +49,12 @@ final class InitialConfigurationProjectManagerListener implements ProjectManager
NOTIFICATION_TITLE,
"The google-java-format plugin is disabled by default. "
+ "<a href=\"enable\">Enable for this project</a>.",
- NotificationType.INFORMATION,
- (n, e) -> {
- settings.setEnabled(true);
- n.expire();
- });
+ NotificationType.INFORMATION);
+ notification.setListener(
+ (n, e) -> {
+ settings.setEnabled(true);
+ n.expire();
+ });
notification.notify(project);
}
}
diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java
new file mode 100644
index 0000000..5084b6a
--- /dev/null
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import com.google.common.base.Suppliers;
+import com.intellij.ide.ui.IdeUiService;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import java.util.function.Supplier;
+
+class JreConfigurationChecker {
+
+ private static final Supplier<Boolean> hasAccess =
+ Suppliers.memoize(JreConfigurationChecker::checkJreConfiguration);
+ private static final Logger logger = Logger.getInstance(JreConfigurationChecker.class);
+
+ private final Project project;
+
+ public JreConfigurationChecker(Project project) {
+ this.project = project;
+ }
+
+ static boolean checkJreConfiguration(Project project) {
+ var success = hasAccess.get();
+ if (!success) {
+ project.getService(JreConfigurationChecker.class).displayConfigurationErrorNotification();
+ }
+ return success;
+ }
+
+ /**
+ * Determine whether the JRE is configured to work with the google-java-format plugin. If not,
+ * display a notification with instructions and return false.
+ */
+ private static boolean checkJreConfiguration() {
+ try {
+ return testClassAccess(
+ "com.sun.tools.javac.api.JavacTrees",
+ "com.sun.tools.javac.code.Flags",
+ "com.sun.tools.javac.file.JavacFileManager",
+ "com.sun.tools.javac.parser.JavacParser",
+ "com.sun.tools.javac.tree.JCTree",
+ "com.sun.tools.javac.util.Log");
+ } catch (ClassNotFoundException e) {
+ logger.error("Error checking jre configuration for google-java-format", e);
+ return false;
+ }
+ }
+
+ private static boolean testClassAccess(String... classNames) throws ClassNotFoundException {
+ for (String className : classNames) {
+ if (!testClassAccess(className)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean testClassAccess(String className) throws ClassNotFoundException {
+ Class<?> klass = Class.forName(className);
+ return klass
+ .getModule()
+ // isExported returns true if the package is either open or exported. Either one is
+ // sufficient
+ // to run the google-java-format code (even though the documentation specifies --add-opens).
+ .isExported(
+ klass.getPackageName(),
+ JreConfigurationChecker.class.getClassLoader().getUnnamedModule());
+ }
+
+ private void displayConfigurationErrorNotification() {
+ Notification notification =
+ new Notification(
+ "Configure JRE for google-java-format",
+ "Configure the JRE for google-java-format",
+ "The google-java-format plugin needs additional configuration before it can be used. "
+ + "<a href=\"instructions\">Follow the instructions here</a>.",
+ NotificationType.INFORMATION);
+ notification.setListener(
+ (n, e) -> {
+ IdeUiService.getInstance()
+ .browse(
+ "https://github.com/google/google-java-format/blob/master/README.md#intellij-jre-config");
+ n.expire();
+ });
+ notification.notify(project);
+ }
+}
diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java
new file mode 100644
index 0000000..d32aa98
--- /dev/null
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import com.intellij.formatting.service.FormattingNotificationService;
+import com.intellij.openapi.project.Project;
+
+class Notifications {
+
+ static final String PARSING_ERROR_NOTIFICATION_GROUP = "google-java-format parsing error";
+ static final String PARSING_ERROR_TITLE = PARSING_ERROR_NOTIFICATION_GROUP;
+
+ static String parsingErrorMessage(String filename) {
+ return "google-java-format failed. Does " + filename + " have syntax errors?";
+ }
+
+ static void displayParsingErrorNotification(Project project, String filename) {
+ FormattingNotificationService.getInstance(project)
+ .reportError(
+ Notifications.PARSING_ERROR_NOTIFICATION_GROUP,
+ Notifications.PARSING_ERROR_TITLE,
+ Notifications.parsingErrorMessage(filename));
+ }
+}
diff --git a/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java
index 24ed6f6..24ed6f6 100644
--- a/idea_plugin/src/com/google/googlejavaformat/intellij/UiFormatterStyle.java
+++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java
diff --git a/idea_plugin/resources/META-INF/plugin.xml b/idea_plugin/src/main/resources/META-INF/plugin.xml
index f10cde5..1b1e67f 100644
--- a/idea_plugin/resources/META-INF/plugin.xml
+++ b/idea_plugin/src/main/resources/META-INF/plugin.xml
@@ -22,13 +22,36 @@
Google
</vendor>
- <!-- Mark it as available on all JetBrains IDEs. It's really only useful in
- IDEA and Android Studio, but there's no way to specify that for some
- reason. It won't crash PyCharm or anything, so whatever. -->
- <depends>com.intellij.java</depends>
+ <depends>com.intellij.modules.java</depends>
+ <depends>com.intellij.modules.lang</depends>
+ <depends>com.intellij.modules.platform</depends>
+ <description><![CDATA[
+ Formats source code using the google-java-format tool.<p>
+
+ This plugin requires additional IDE configuration. For more information,
+ <a href="https://github.com/google/google-java-format/blob/master/README.md#intellij-jre-config">read
+ the documentation.</a>
+ ]]></description>
<change-notes><![CDATA[
<dl>
+ <dt>1.17.0.0</dt>
+ <dd>Updated to use google-java-format 1.17.0.</dd>
+ <dd>Fixed "Document is locked" errors (Thanks, <code>@facboy</code>!)</dd>
+ <dt>1.16.0.2</dt>
+ <dd>Disable AD_HOC_FORMATTING, which should stop the formatter from running so often when it wasn't specifically requested.
+ <dt>1.16.0.1</dt>
+ <dd>When the plugin isn't configured correctly, show the error on every
+ format command. Previously it was only being shown at startup and going
+ unnoticed.
+ <dt>1.16.0.0</dt>
+ <dd>Updated to use google-java-format 1.16.0.</dd>
+ <dd>Use the new IDE formatting APIs for a simplified plugin.</dd>
+ <dd>Optimize Imports now uses google-java-format.</dd>
+ <dt>1.15.0.0</dt>
+ <dd>Updated to use google-java-format 1.15.0.</dd>
+ <dt>1.14.0.0</dt>
+ <dd>Updated to use google-java-format 1.14.</dd>
<dt>1.13.0.0</dt>
<dd>Updated to use google-java-format 1.13.</dd>
<dt>1.12.0.0</dt>
@@ -58,23 +81,23 @@
</dl>
]]></change-notes>
- <applicationListeners>
- <listener
- class="com.google.googlejavaformat.intellij.InitialConfigurationProjectManagerListener"
- topic="com.intellij.openapi.project.ProjectManagerListener"/>
- <listener class="com.google.googlejavaformat.intellij.GoogleJavaFormatInstaller"
- topic="com.intellij.openapi.project.ProjectManagerListener"/>
- </applicationListeners>
-
<extensions defaultExtensionNs="com.intellij">
+ <formattingService
+ implementation="com.google.googlejavaformat.intellij.GoogleJavaFormatFormattingService"/>
+ <postStartupActivity implementation="com.google.googlejavaformat.intellij.InitialConfigurationStartupActivity"/>
<projectConfigurable
instance="com.google.googlejavaformat.intellij.GoogleJavaFormatConfigurable"
id="google-java-format.settings"
displayName="google-java-format Settings"/>
<projectService
serviceImplementation="com.google.googlejavaformat.intellij.GoogleJavaFormatSettings"/>
+ <projectService serviceImplementation="com.google.googlejavaformat.intellij.JreConfigurationChecker"/>
<notificationGroup displayType="STICKY_BALLOON" id="Enable google-java-format"
+ isLogByDefault="false"/>
+ <notificationGroup displayType="STICKY_BALLOON" id="Configure JRE for google-java-format"
isLogByDefault="true"/>
+ <notificationGroup displayType="BALLOON" id="google-java-format parsing error"
+ isLogByDefault="false"/>
</extensions>
</idea-plugin>
diff --git a/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java
new file mode 100644
index 0000000..fec086c
--- /dev/null
+++ b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingServiceTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.State;
+import com.google.googlejavaformat.java.Formatter;
+import com.google.googlejavaformat.java.JavaFormatterOptions;
+import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
+import com.intellij.formatting.service.AsyncFormattingRequest;
+import com.intellij.formatting.service.FormattingService;
+import com.intellij.formatting.service.FormattingServiceUtil;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.testFramework.ExtensionTestUtil;
+import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
+import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
+import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.TestFixtureBuilder;
+import java.io.IOException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GoogleJavaFormatFormattingServiceTest {
+ private JavaCodeInsightTestFixture fixture;
+ private GoogleJavaFormatSettings settings;
+ private DelegatingFormatter delegatingFormatter;
+
+ @Before
+ public void setUp() throws Exception {
+ TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder =
+ IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getClass().getName());
+ fixture =
+ JavaTestFixtureFactory.getFixtureFactory()
+ .createCodeInsightFixture(projectBuilder.getFixture());
+ fixture.setUp();
+
+ delegatingFormatter = new DelegatingFormatter();
+ ExtensionTestUtil.maskExtensions(
+ FormattingService.EP_NAME,
+ ImmutableList.of(delegatingFormatter),
+ fixture.getProjectDisposable());
+
+ settings = GoogleJavaFormatSettings.getInstance(fixture.getProject());
+ State resetState = new State();
+ resetState.setEnabled("true");
+ settings.loadState(resetState);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ fixture.tearDown();
+ }
+
+ @Test
+ public void defaultFormatSettings() throws Exception {
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "static final String CONST_STR = \"Hello\";",
+ "}");
+ String origText = file.getText();
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ WriteCommandAction.runWriteCommandAction(
+ file.getProject(), () -> manager.reformatText(file, 0, file.getTextLength()));
+
+ assertThat(file.getText()).isEqualTo(new Formatter().formatSource(origText));
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void aospStyle() throws Exception {
+ settings.setStyle(Style.AOSP);
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "static final String CONST_STR = \"Hello\";",
+ "}");
+ String origText = file.getText();
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ WriteCommandAction.runWriteCommandAction(
+ file.getProject(), () -> manager.reformatText(file, 0, file.getTextLength()));
+
+ assertThat(file.getText())
+ .isEqualTo(
+ new Formatter(JavaFormatterOptions.builder().style(Style.AOSP).build())
+ .formatSource(origText));
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void canChangeWhitespaceOnlyDoesNotReorderModifiers() throws Exception {
+ settings.setStyle(Style.GOOGLE);
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "final static String CONST_STR = \"Hello\";",
+ "}");
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ var offset = file.getText().indexOf("final static");
+ WriteCommandAction.<PsiElement>runWriteCommandAction(
+ file.getProject(),
+ () ->
+ FormattingServiceUtil.formatElement(
+ file.findElementAt(offset), /* canChangeWhitespaceOnly= */ true));
+
+ // In non-whitespace mode, this would flip the order of these modifiers. (Also check for leading
+ // spaces to make sure the formatter actually ran.
+ assertThat(file.getText()).containsMatch(" final static");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void canChangeWhitespaceOnlyDoesNotReformatJavadoc() throws Exception {
+ settings.setStyle(Style.GOOGLE);
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "/**",
+ " * hello",
+ " */",
+ "static final String CONST_STR = \"Hello\";",
+ "}");
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ var offset = file.getText().indexOf("hello");
+ WriteCommandAction.<PsiElement>runWriteCommandAction(
+ file.getProject(),
+ () ->
+ FormattingServiceUtil.formatElement(
+ file.findElementAt(offset), /* canChangeWhitespaceOnly= */ true));
+
+ // In non-whitespace mode, this would join the Javadoc into a single line.
+ assertThat(file.getText()).containsMatch(" \\* hello");
+ // Also check for leading spaces to make sure the formatter actually ran. (Technically, this is
+ // outside the range that we asked to be formatted, but gjf will still format it.)
+ assertThat(file.getText()).containsMatch(" static final");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void canChangeNonWhitespaceReordersModifiers() throws Exception {
+ settings.setStyle(Style.GOOGLE);
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "final static String CONST_STR = \"Hello\";",
+ "}");
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ var offset = file.getText().indexOf("final static");
+ WriteCommandAction.<PsiElement>runWriteCommandAction(
+ file.getProject(),
+ () ->
+ FormattingServiceUtil.formatElement(
+ file.findElementAt(offset), /* canChangeWhitespaceOnly= */ false));
+
+ assertThat(file.getText()).containsMatch("static final");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void canChangeNonWhitespaceReformatsJavadoc() throws Exception {
+ settings.setStyle(Style.GOOGLE);
+ PsiFile file =
+ createPsiFile(
+ "com/foo/FormatTest.java",
+ "package com.foo;",
+ "public class FormatTest {",
+ "/**",
+ " * hello",
+ " */",
+ "static final String CONST_STR = \"Hello\";",
+ "}");
+ CodeStyleManager manager = CodeStyleManager.getInstance(file.getProject());
+ var offset = file.getText().indexOf("hello");
+ WriteCommandAction.<PsiElement>runWriteCommandAction(
+ file.getProject(),
+ () ->
+ FormattingServiceUtil.formatElement(
+ file.findElementAt(offset), /* canChangeWhitespaceOnly= */ false));
+
+ assertThat(file.getText()).containsMatch("/\\*\\* hello \\*/");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ private PsiFile createPsiFile(String path, String... contents) throws IOException {
+ VirtualFile virtualFile =
+ fixture.getTempDirFixture().createFile(path, String.join("\n", contents));
+ fixture.configureFromExistingVirtualFile(virtualFile);
+ PsiFile psiFile = fixture.getFile();
+ assertThat(psiFile).isNotNull();
+ return psiFile;
+ }
+
+ private static final class DelegatingFormatter extends GoogleJavaFormatFormattingService {
+
+ private boolean invoked = false;
+
+ private boolean wasInvoked() {
+ return invoked;
+ }
+
+ @Override
+ protected FormattingTask createFormattingTask(AsyncFormattingRequest request) {
+ FormattingTask delegateTask = super.createFormattingTask(request);
+ return new FormattingTask() {
+ @Override
+ public boolean cancel() {
+ return delegateTask.cancel();
+ }
+
+ @Override
+ public void run() {
+ invoked = true;
+ delegateTask.run();
+ }
+ };
+ }
+ }
+}
diff --git a/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java
new file mode 100644
index 0000000..ad9fe27
--- /dev/null
+++ b/idea_plugin/src/test/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2023 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.googlejavaformat.intellij;
+
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.State;
+import com.intellij.codeInsight.actions.OptimizeImportsProcessor;
+import com.intellij.formatting.service.FormattingService;
+import com.intellij.lang.ImportOptimizer;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiFile;
+import com.intellij.testFramework.ExtensionTestUtil;
+import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
+import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
+import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
+import com.intellij.testFramework.fixtures.TestFixtureBuilder;
+import java.io.IOException;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class GoogleJavaFormatImportOptimizerTest {
+ private JavaCodeInsightTestFixture fixture;
+ private DelegatingFormatter delegatingFormatter;
+
+ @Before
+ public void setUp() throws Exception {
+ TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder =
+ IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getClass().getName());
+ fixture =
+ JavaTestFixtureFactory.getFixtureFactory()
+ .createCodeInsightFixture(projectBuilder.getFixture());
+ fixture.setUp();
+
+ delegatingFormatter = new DelegatingFormatter();
+ ExtensionTestUtil.maskExtensions(
+ FormattingService.EP_NAME,
+ ImmutableList.of(delegatingFormatter),
+ fixture.getProjectDisposable());
+
+ var settings = GoogleJavaFormatSettings.getInstance(fixture.getProject());
+ State resetState = new State();
+ resetState.setEnabled("true");
+ settings.loadState(resetState);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ fixture.tearDown();
+ }
+
+ @Test
+ public void removesUnusedImports() throws Exception {
+ PsiFile file =
+ createPsiFile(
+ "com/foo/ImportTest.java",
+ "package com.foo;",
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "import java.util.Map;",
+ "public class ImportTest {",
+ "static final Map map;",
+ "}");
+ OptimizeImportsProcessor processor = new OptimizeImportsProcessor(file.getProject(), file);
+ WriteCommandAction.runWriteCommandAction(
+ file.getProject(),
+ () -> {
+ processor.run();
+ PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
+ });
+
+ assertThat(file.getText()).doesNotContain("List");
+ assertThat(file.getText()).contains("import java.util.Map;");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ @Test
+ public void reordersImports() throws Exception {
+ PsiFile file =
+ createPsiFile(
+ "com/foo/ImportTest.java",
+ "package com.foo;",
+ "import java.util.List;",
+ "import java.util.ArrayList;",
+ "import java.util.Map;",
+ "public class ImportTest {",
+ "static final ArrayList arrayList;",
+ "static final List list;",
+ "static final Map map;",
+ "}");
+ OptimizeImportsProcessor processor = new OptimizeImportsProcessor(file.getProject(), file);
+ WriteCommandAction.runWriteCommandAction(
+ file.getProject(),
+ () -> {
+ processor.run();
+ PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
+ });
+
+ assertThat(file.getText())
+ .contains(
+ "import java.util.ArrayList;\n"
+ + "import java.util.List;\n"
+ + "import java.util.Map;\n");
+ assertThat(delegatingFormatter.wasInvoked()).isTrue();
+ }
+
+ private PsiFile createPsiFile(String path, String... contents) throws IOException {
+ VirtualFile virtualFile =
+ fixture.getTempDirFixture().createFile(path, String.join("\n", contents));
+ fixture.configureFromExistingVirtualFile(virtualFile);
+ PsiFile psiFile = fixture.getFile();
+ assertThat(psiFile).isNotNull();
+ return psiFile;
+ }
+
+ private static final class DelegatingFormatter extends GoogleJavaFormatFormattingService {
+
+ private boolean invoked = false;
+
+ private boolean wasInvoked() {
+ return invoked;
+ }
+
+ @Override
+ public @NotNull Set<ImportOptimizer> getImportOptimizers(@NotNull PsiFile file) {
+ return super.getImportOptimizers(file).stream().map(this::wrap).collect(toImmutableSet());
+ }
+
+ private ImportOptimizer wrap(ImportOptimizer delegate) {
+ return new ImportOptimizer() {
+ @Override
+ public boolean supports(@NotNull PsiFile file) {
+ return delegate.supports(file);
+ }
+
+ @Override
+ public @NotNull Runnable processFile(@NotNull PsiFile file) {
+ return () -> {
+ invoked = true;
+ delegate.processFile(file).run();
+ };
+ }
+ };
+ }
+ }
+}