1 /* 2 * Copyright 2023 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.googlejavaformat.intellij; 18 19 import com.google.common.util.concurrent.Runnables; 20 import com.google.googlejavaformat.java.FormatterException; 21 import com.google.googlejavaformat.java.ImportOrderer; 22 import com.google.googlejavaformat.java.JavaFormatterOptions; 23 import com.google.googlejavaformat.java.RemoveUnusedImports; 24 import com.intellij.ide.highlighter.JavaFileType; 25 import com.intellij.lang.ImportOptimizer; 26 import com.intellij.openapi.editor.Document; 27 import com.intellij.openapi.project.Project; 28 import com.intellij.psi.PsiDocumentManager; 29 import com.intellij.psi.PsiFile; 30 import org.jetbrains.annotations.NotNull; 31 32 /** Uses {@code google-java-format} to optimize imports. */ 33 public class GoogleJavaFormatImportOptimizer implements ImportOptimizer { 34 35 @Override supports(@otNull PsiFile file)36 public boolean supports(@NotNull PsiFile file) { 37 return JavaFileType.INSTANCE.equals(file.getFileType()) 38 && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled(); 39 } 40 41 @Override processFile(@otNull PsiFile file)42 public @NotNull Runnable processFile(@NotNull PsiFile file) { 43 Project project = file.getProject(); 44 45 if (!JreConfigurationChecker.checkJreConfiguration(file.getProject())) { 46 return Runnables.doNothing(); 47 } 48 49 PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); 50 Document document = documentManager.getDocument(file); 51 52 if (document == null) { 53 return Runnables.doNothing(); 54 } 55 56 JavaFormatterOptions.Style style = GoogleJavaFormatSettings.getInstance(project).getStyle(); 57 58 final String origText = document.getText(); 59 String text; 60 try { 61 text = ImportOrderer.reorderImports(RemoveUnusedImports.removeUnusedImports(origText), style); 62 } catch (FormatterException e) { 63 Notifications.displayParsingErrorNotification(project, file.getName()); 64 return Runnables.doNothing(); 65 } 66 67 // pointless to change document text if it hasn't changed, plus this can interfere with 68 // e.g. GoogleJavaFormattingService's output, i.e. it can overwrite the results from the main 69 // formatter. 70 if (text.equals(origText)) { 71 return Runnables.doNothing(); 72 } 73 74 return () -> { 75 if (documentManager.isDocumentBlockedByPsi(document)) { 76 documentManager.doPostponedOperationsAndUnblockDocument(document); 77 } 78 79 // similarly to above, don't overwrite new document text if it has changed - we use 80 // getCharsSequence() as we should have `writeAction()` (which I think means effectively a 81 // write-lock) and it saves calling getText(), which apparently is expensive. 82 CharSequence newText = document.getCharsSequence(); 83 if (CharSequence.compare(origText, newText) != 0) { 84 return; 85 } 86 87 document.setText(text); 88 }; 89 } 90 } 91