blob: 52424c2c8237fe80e1eb36b173f84a74ae112f20 [file] [log] [blame]
/*
* 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);
});
}
}