/* * 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.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileTypes.StdFileTypes; 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.CodeStyleManager; import com.intellij.psi.impl.CheckUtil; import com.intellij.util.IncorrectOperationException; import java.util.Collection; 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 IJ's default implementation. */ class GoogleJavaFormatCodeStyleManager extends CodeStyleManagerDecorator { public GoogleJavaFormatCodeStyleManager(@NotNull CodeStyleManager original) { super(original); } @Override public void reformatText(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(PsiFile file, Collection<TextRange> ranges) throws IncorrectOperationException { if (overrideFormatterForFile(file)) { formatInternal(file, ranges); } else { super.reformatText(file, ranges); } } @Override public void reformatTextWithContext(PsiFile file, Collection<TextRange> ranges) { if (overrideFormatterForFile(file)) { formatInternal(file, ranges); } else { super.reformatTextWithContext(file, ranges); } } @Override public PsiElement reformatRange( 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 or not this formatter can handle formatting the given file. */ private boolean overrideFormatterForFile(PsiFile file) { return StdFileTypes.JAVA.equals(file.getFileType()) && GoogleJavaFormatSettings.getInstance(getProject()).isEnabled(); } private void formatInternal(PsiFile file, Collection<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<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); }); } }