/*
* Copyright 2012-2013 Sergey Ignatov
*
* 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.haskforce.actions;
/*
* Adapted from the Emacs-formatter in the Erlang plugin. 17 May 2014.
*/
import com.haskforce.settings.ToolKey;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.PsiFile;
import com.haskforce.psi.HaskellFile;
import com.haskforce.utils.HaskellToolsNotificationListener;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessAdapter;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.util.ExceptionUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Action that calls stylish-haskell on the buffer it is invoked for.
*/
public class HaskellStylishFormatAction extends AnAction implements DumbAware {
private static final String NOTIFICATION_TITLE = "Reformat code with Stylish-Haskell";
private static final Logger LOG = Logger.getInstance(HaskellStylishFormatAction.class);
/**
* Enable the action for Haskell files.
*/
@Override
public void update(AnActionEvent e) {
PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
boolean isHaskell = psiFile instanceof HaskellFile;
e.getPresentation().setEnabled(isHaskell);
}
/**
* Main entry point. Calls stylish-haskell and detects errors.
*/
@Override
public void actionPerformed(AnActionEvent e) {
final PsiFile psiFile = e.getData(CommonDataKeys.PSI_FILE);
final Project project = getEventProject(e);
if (project == null) return;
if (!(psiFile instanceof HaskellFile)) return;
VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile == null) return;
final String groupId = e.getPresentation().getText();
try {
GeneralCommandLine commandLine = new GeneralCommandLine();
final String stylishPath = ToolKey.STYLISH_HASKELL_KEY.getPath(project);
final String stylishFlags = ToolKey.STYLISH_HASKELL_KEY.getFlags(project);
if (stylishPath == null || stylishPath.isEmpty()) {
Notifications.Bus.notify(
new Notification(groupId, NOTIFICATION_TITLE,
"Stylish-Haskell executable path is empty"+
"<br/><a href='configureHaskellTools'>Configure</a>",
NotificationType.WARNING, new HaskellToolsNotificationListener(project)), project);
return;
}
commandLine.setExePath(stylishPath);
commandLine.getParametersList().addParametersString(stylishFlags);
final VirtualFile backingFile = psiFile.getVirtualFile();
if (backingFile == null) return;
String backingFilePath = backingFile.getCanonicalPath();
if (backingFilePath == null) return;
commandLine.addParameter(backingFilePath);
// Set the work dir so stylish can pick up the user config, if it exists.
commandLine.setWorkDirectory(backingFile.getParent().getCanonicalPath());
ApplicationManager.getApplication().saveAll();
final String commandLineString = commandLine.getCommandLineString();
OSProcessHandler handler = new OSProcessHandler(commandLine.createProcess(), commandLineString);
handler.addProcessListener(new CapturingProcessAdapter() {
@Override
public void processTerminated(@NotNull final ProcessEvent event) {
List<String> errorDetection = getOutput().getStderrLines();
if (!errorDetection.isEmpty()) {
String firstLine = errorDetection.get(0);
if (firstLine.startsWith("Language.Haskell.Stylish.Parse.parseModule:")) {
// Filter out the left part and keep the interesting stuff.
// Error message is on the format:
// moduleName: interesting stuff.
String output = firstLine.split(":", 2)[1];
Notifications.Bus.notify(new Notification(groupId,
"Stylish-Haskell error.", output,
NotificationType.ERROR), project);
return;
}
return;
}
final String text = getOutput().getStdout();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
try {
final Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile);
if (document == null) return;
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
document.setText(text);
}
});
}
}, NOTIFICATION_TITLE, "", document);
Notifications.Bus.notify(new Notification(groupId, NOTIFICATION_TITLE,
psiFile.getName() + " formatted with Stylish-Haskell.",
NotificationType.INFORMATION), project);
} catch (Exception ex) {
Notifications.Bus.notify(new Notification(groupId,
"Formatting " + psiFile.getName() + " with Stylish-Haskell failed.", ExceptionUtil.getUserStackTrace(ex, LOG),
NotificationType.ERROR), project);
LOG.error(ex);
}
}
});
}
});
handler.startNotify();
} catch (Exception ex) {
Notifications.Bus.notify(new Notification(groupId,
"Formatting " + psiFile.getName() + " with Stylish-Haskell failed", ExceptionUtil.getUserStackTrace(ex, LOG),
NotificationType.ERROR), project);
LOG.error(ex);
}
}
}