package krasa.formatter.plugin;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.util.IncorrectOperationException;
import krasa.formatter.eclipse.Classloaders;
import krasa.formatter.eclipse.CodeFormatterFacade;
import krasa.formatter.eclipse.JavaCodeFormatterFacade;
import krasa.formatter.exception.FileDoesNotExistsException;
import krasa.formatter.exception.FormattingFailedException;
import krasa.formatter.settings.DisabledFileTypeSettings;
import krasa.formatter.settings.ProjectSettingsComponent;
import krasa.formatter.settings.Settings;
import krasa.formatter.settings.provider.CppPropertiesProvider;
import krasa.formatter.settings.provider.JSPropertiesProvider;
import krasa.formatter.utils.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class EclipseCodeStyleManager {
private final Logger LOG = Logger.getInstance(this.getClass().getName());
@NotNull
private final CodeStyleManager original;
@NotNull
private Settings settings;
@NotNull
private Notifier notifier;
@Nullable
private EclipseCodeFormatter eclipseCodeFormatterJava;
@Nullable
private EclipseCodeFormatter eclipseCodeFormatterJs;
@Nullable
private EclipseCodeFormatter eclipseCodeFormatterCpp;
public EclipseCodeStyleManager(@NotNull CodeStyleManager original, @NotNull Settings settings) {
this.original = original;
this.settings = settings;
notifier = new Notifier();
}
private final static Comparator<TextRange> RANGE_COMPARATOR = new Comparator<TextRange>() {
@Override
public int compare(TextRange range1, TextRange range2) {
int startOffsetDiff = range1.getStartOffset() - range2.getStartOffset();
return startOffsetDiff != 0 ? startOffsetDiff : range1.getEndOffset() - range2.getEndOffset();
}
};
// 15
// @Override
public void reformatTextWithContext(@NotNull PsiFile psiFile, @NotNull Collection<TextRange> collection)
throws IncorrectOperationException {
reformatText(psiFile, collection);
}
// @Override
public void reformatText(@NotNull PsiFile psiFile, @NotNull Collection<TextRange> textRanges)
throws IncorrectOperationException {
List<TextRange> list = new ArrayList<TextRange>(textRanges);
Collections.sort(list, Collections.reverseOrder(RANGE_COMPARATOR));
format(psiFile, list, Mode.ALWAYS_FORMAT);
}
// @Override
// todo should I even override this method?
public void reformatText(@NotNull final PsiFile psiFile, final int startOffset, final int endOffset)
throws IncorrectOperationException {
format(psiFile, Arrays.asList(new TextRange(startOffset, endOffset)), Mode.WITH_CTRL_SHIFT_ENTER_CHECK);
}
private void format(PsiFile psiFile, List<TextRange> list, Mode mode) {
ApplicationManager.getApplication().assertWriteAccessAllowed();
PsiDocumentManager.getInstance(original.getProject()).commitAllDocuments();
boolean formattedByIntelliJ = false;
CheckUtil.checkWritable(psiFile);
if (psiFile.getVirtualFile() == null) {
LOG.debug("virtual file is null");
Notification notification = ProjectSettingsComponent.GROUP_DISPLAY_ID_ERROR.createNotification(
Notifier.NO_FILE_TO_FORMAT, NotificationType.ERROR);
notifier.showNotification(notification, psiFile.getProject());
return;
}
int startOffset = -1;
int endOffset = -1;
try {
boolean canReformatWithEclipse = canReformatWithEclipse(psiFile);
boolean wholeFileOrSelectedText = wholeFileOrSelectedText(psiFile, list);
boolean notify = false;
for (TextRange textRange : list) {
startOffset = textRange.getStartOffset();
endOffset = textRange.getEndOffset();
LOG.debug("format " + psiFile.getName() + " " + startOffset + " " + endOffset);
if (canReformatWithEclipse && shouldReformat(wholeFileOrSelectedText, mode)) {
try {
formatWithEclipse(psiFile, startOffset, endOffset);
notify = notify || shouldNotify(psiFile, startOffset, endOffset);
} catch (ReformatItInIntelliJ e) {
formattedByIntelliJ = true;
formatWithIntelliJ(psiFile, startOffset, endOffset);
}
} else {
formattedByIntelliJ = true;
if (shouldSkipFormatting(psiFile, startOffset, endOffset)) {
notifier.notifyFormattingWasDisabled(psiFile);
} else {
formatWithIntelliJ(psiFile, startOffset, endOffset);
notify = notify || wholeFileOrSelectedText;
}
}
}
if (notify) {
notifier.notifySuccessFormatting(psiFile, formattedByIntelliJ);
}
} catch (final FileDoesNotExistsException e) {
LOG.debug(e);
notifier.notifyFailedFormatting(psiFile, formattedByIntelliJ, e);
} catch (final InvalidPropertyFile e) {
LOG.debug(e);
notifier.notifyFailedFormatting(psiFile, formattedByIntelliJ, e);
} catch (final ImportSorterException e) {
LOG.error(e);
notifier.notifyBrokenImportSorter(psiFile.getProject());
} catch (final FormattingFailedException e) {
LOG.debug("startOffset" + startOffset + ", endOffset:" + endOffset + ", length of file "
+ psiFile.getText().length(), e);
notifier.notifyFailedFormatting(psiFile, formattedByIntelliJ, getReason(e));
} catch (final Throwable e) {
LOG.error(e);
}
}
private boolean shouldNotify(PsiFile psiFile, int startOffset, int endOffset) {
boolean isShort = endOffset - startOffset < settings.getNotifyFromTextLenght();
boolean skipSuccessFormattingNotification = isShort
&& !FileUtils.isWholeFile(startOffset, endOffset, psiFile.getText());
return !skipSuccessFormattingNotification;
}
private boolean wholeFileOrSelectedText(PsiFile psiFile, List<TextRange> list) {
boolean wholeFileOrSelectedText = false;
final Editor editor = PsiUtilBase.findEditor(psiFile);
boolean result;
if (editor == null) {
wholeFileOrSelectedText = true;
} else {
Document document = editor.getDocument();
String text = document.getText();
boolean hasSelection = editor.getSelectionModel().hasSelection();
for (TextRange textRange : list) {
boolean wholeFile = FileUtils.isWholeFile(textRange.getStartOffset(), textRange.getEndOffset(), text);
result = hasSelection || wholeFile;
wholeFileOrSelectedText = wholeFileOrSelectedText || result;
}
}
return wholeFileOrSelectedText;
}
private String getReason(FormattingFailedException e) {
if (e.isUserError() && e.getMessage() != null) {
return "<br>" + e.getMessage();
}
String result = "Probably due to syntax error or wrong configuration file.";
String message = e.getMessage();
if (message != null) {
result = result + "<br>" + message;
}
return result;
}
private boolean shouldReformat(boolean wholeFileOrSelectedText, Mode mode) {
switch (mode) {
/* when formatting only vcs changes, this is needed. */
case ALWAYS_FORMAT:
return true;
/* live templates gets broken without that */
case WITH_CTRL_SHIFT_ENTER_CHECK:
return wholeFileOrSelectedText;
}
return true;
}
private void formatWithEclipse(PsiFile psiFile, int startOffset, int endOffset) throws FileDoesNotExistsException {
if (FileUtils.isJavaScript(psiFile)) {
if (eclipseCodeFormatterJs == null) {
JSPropertiesProvider jsProperties = settings.getJSProperties();
CodeFormatterFacade jsFormatter = Classloaders.getJsFormatter(jsProperties);
eclipseCodeFormatterJs = new EclipseCodeFormatter(settings, jsFormatter);
}
eclipseCodeFormatterJs.format(psiFile, startOffset, endOffset);
} else if (FileUtils.isCpp(psiFile)) {
if (eclipseCodeFormatterCpp == null) {
CppPropertiesProvider cppProperties = settings.getCppProperties();
CodeFormatterFacade cpp = Classloaders.getCppFormatter(cppProperties);
eclipseCodeFormatterCpp = new EclipseCodeFormatter(settings, cpp);
}
eclipseCodeFormatterCpp.format(psiFile, startOffset, endOffset);
} else {
if (eclipseCodeFormatterJava == null) {
JavaCodeFormatterFacade facade = new JavaCodeFormatterFacade(settings.getJavaProperties(),
settings.getEclipseVersion(), original.getProject(), settings.getPathToEclipse());
eclipseCodeFormatterJava = new EclipseCodeFormatter(settings, facade);
}
eclipseCodeFormatterJava.format(psiFile, startOffset, endOffset);
}
}
private boolean shouldSkipFormatting(PsiFile psiFile, int startOffset, int endOffset) {
VirtualFile virtualFile = psiFile.getVirtualFile();
if (settings.isFormatSeletedTextInAllFileTypes()) {
// when file is being edited, it is important to load text from editor, i think
final Editor editor = PsiUtilBase.findEditor(psiFile);
if (editor != null) {
Document document = editor.getDocument();
String text = document.getText();
if (!FileUtils.isWholeFile(startOffset, endOffset, text) || isFocusInEditorAndSelectedText()) {
return false;
}
}
}
if (settings.isFormatOtherFileTypesWithIntelliJ()) {
return isDisabledFileType(virtualFile);
}
return true;
}
// todo rozlisit oznacenej celej file v editoru od normalniho formatovani
private boolean isFocusInEditorAndSelectedText() {
return false;
}
public boolean canReformatWithEclipse(PsiFile psiFile) {
Project project = psiFile.getProject();
return psiFile.getVirtualFile().isInLocalFileSystem() && FileUtils.isWritable(psiFile.getVirtualFile(), project)
&& fileTypeIsEnabled(psiFile);
}
private void formatWithIntelliJ(PsiFile psiFile, int startOffset, int endOffset) {
LOG.debug("formatting with IntelliJ formatter");
original.reformatText(psiFile, startOffset, endOffset);
}
private boolean isDisabledFileType(VirtualFile virtualFile) {
String path = virtualFile.getPath();
DisabledFileTypeSettings disabledFileTypeSettings = settings.geDisabledFileTypeSettings();
return disabledFileTypeSettings.isDisabled(path);
}
private boolean fileTypeIsEnabled(@NotNull PsiFile psiFile) {
return (FileUtils.isJava(psiFile) && settings.isEnableJavaFormatting())
|| (FileUtils.isJavaScript(psiFile) && settings.isEnableJSFormatting())
|| (FileUtils.isCpp(psiFile) && settings.isEnableCppFormatting());
}
}