/*
* 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 Stylish Haskell formatter
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.sanselan.util.IOUtils;
import org.jetbrains.annotations.NotNull;
import com.haskforce.psi.HaskellFile;
import com.haskforce.settings.ToolKey;
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.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
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.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.ExceptionUtil;
/**
* Action that calls hindent on the buffer it is invoked for.
*/
public class HaskellHindentFormatAction extends AnAction implements DumbAware {
private static final String NOTIFICATION_TITLE = "Reformat code with Hindent";
private static final Logger LOG = Logger.getInstance(HaskellHindentFormatAction.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 hindent 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 hindentPath = ToolKey.HINDENT_KEY.getPath(project);
final String hindentFlags = ToolKey.HINDENT_KEY.getFlags(project);
if (hindentPath == null || hindentPath.isEmpty()) {
Notifications.Bus.notify(
new Notification(groupId, NOTIFICATION_TITLE,
"Hindent executable path is empty"+
"<br/><a href='configureHaskellTools'>Configure</a>",
NotificationType.WARNING, new HaskellToolsNotificationListener(project)), project);
return;
}
commandLine.setExePath(hindentPath);
commandLine.getParametersList().addParametersString(hindentFlags);
final VirtualFile backingFile = psiFile.getVirtualFile();
if (backingFile == null) return;
ApplicationManager.getApplication().saveAll();
final String commandLineString = commandLine.getCommandLineString();
OSProcessHandler handler = new OSProcessHandler(commandLine.createProcess(), commandLineString);
// Pipe file into the process
final InputStream fileContents = backingFile.getInputStream();
final OutputStream processStdin = handler.getProcessInput();
IOUtils.copyStreamToStream(fileContents, processStdin);
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("hindent:")) {
// Filter out the left part and keep the interesting stuff.
// Error message is on the format:
// hindent: interesting stuff.
String output = firstLine.split(": ", 2)[1];
Notifications.Bus.notify(new Notification(groupId,
"Hindent 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 Hindent.",
NotificationType.INFORMATION), project);
} catch (Exception ex) {
Notifications.Bus.notify(new Notification(groupId,
"Formatting " + psiFile.getName() + " with Hindent 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 Hindent failed", ExceptionUtil.getUserStackTrace(ex, LOG),
NotificationType.ERROR), project);
LOG.error(ex);
}
}
}