blob: be87a940896d490dafd925932d81dc60ec7ca64a [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.openapi.vfs.encoding;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.command.undo.GlobalUndoableAction;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.command.undo.UndoableAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectLocator;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* @author cdr
*/
public class ChangeFileEncodingAction extends AnAction implements DumbAware {
private final boolean allowDirectories;
public ChangeFileEncodingAction() {
this(false);
}
public ChangeFileEncodingAction(boolean allowDirectories) {
this.allowDirectories = allowDirectories;
}
private boolean checkEnabled(@NotNull VirtualFile virtualFile) {
if (allowDirectories && virtualFile.isDirectory()) return true;
FileDocumentManager documentManager = FileDocumentManager.getInstance();
Document document = documentManager.getDocument(virtualFile);
if (document == null) return false;
return EncodingUtil.checkCanConvert(virtualFile) == null || EncodingUtil.checkCanReload(virtualFile).second == null;
}
@Override
public void update(AnActionEvent e) {
VirtualFile myFile = e.getData(CommonDataKeys.VIRTUAL_FILE);
boolean enabled = myFile != null && checkEnabled(myFile);
e.getPresentation().setEnabled(enabled);
e.getPresentation().setVisible(myFile != null);
}
@Override
public final void actionPerformed(final AnActionEvent e) {
DataContext dataContext = e.getDataContext();
ListPopup popup = createPopup(dataContext);
if (popup != null) {
popup.showInBestPositionFor(dataContext);
}
}
@Nullable
public ListPopup createPopup(@NotNull DataContext dataContext) {
final VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext);
if (virtualFile == null) return null;
boolean enabled = checkEnabled(virtualFile);
if (!enabled) return null;
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
FileDocumentManager documentManager = FileDocumentManager.getInstance();
final Document document = documentManager.getDocument(virtualFile);
if (!allowDirectories && virtualFile.isDirectory() || document == null && !virtualFile.isDirectory()) return null;
final byte[] bytes;
try {
bytes = virtualFile.isDirectory() ? null : virtualFile.contentsToByteArray();
}
catch (IOException e) {
return null;
}
DefaultActionGroup group = createActionGroup(virtualFile, editor, document, bytes, null);
return JBPopupFactory.getInstance().createActionGroupPopup(getTemplatePresentation().getText(),
group, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
}
public DefaultActionGroup createActionGroup(@NotNull final VirtualFile myFile,
final Editor editor,
final Document document,
final byte[] bytes,
@Nullable final String clearItemText) {
final String text = document == null ? null : document.getText();
return new ChooseFileEncodingAction(myFile) {
@Override
public void update(final AnActionEvent e) {
}
@NotNull
@Override
protected DefaultActionGroup createPopupActionGroup(JComponent button) {
return createCharsetsActionGroup(clearItemText, null, new Function<Charset, String>() {
@Override
public String fun(Charset charset) {
assert myFile.isDirectory() || text != null : charset;
EncodingUtil.Magic8 safeToReload = myFile.isDirectory() ? EncodingUtil.Magic8.ABSOLUTELY : EncodingUtil.isSafeToReloadIn(myFile, text, bytes, charset);
boolean enabled = safeToReload != EncodingUtil.Magic8.NO_WAY;
if (!enabled) {
EncodingUtil.Magic8 safeToConvert = myFile.isDirectory() ? EncodingUtil.Magic8.ABSOLUTELY : EncodingUtil.isSafeToConvertTo(myFile, text, bytes, charset);
enabled = safeToConvert != EncodingUtil.Magic8.NO_WAY;
}
return enabled ? "Change encoding to '"+charset.displayName()+"'" : null;
}
}); // no 'clear'
}
@Override
protected void chosen(@Nullable VirtualFile virtualFile, @NotNull Charset charset) {
if (virtualFile != null) {
ChangeFileEncodingAction.this.chosen(document, editor, virtualFile, bytes, charset);
}
}
}
.createPopupActionGroup(null);
}
// returns true if charset was changed, false if failed
protected boolean chosen(final Document document,
final Editor editor,
@NotNull final VirtualFile virtualFile,
byte[] bytes,
@NotNull final Charset charset) {
String text = document.getText();
EncodingUtil.Magic8 isSafeToConvert = EncodingUtil.isSafeToConvertTo(virtualFile, text, bytes, charset);
EncodingUtil.Magic8 isSafeToReload = EncodingUtil.isSafeToReloadIn(virtualFile, text, bytes, charset);
final Project project = ProjectLocator.getInstance().guessProjectForFile(virtualFile);
final Charset oldCharset = virtualFile.getCharset();
final Runnable undo;
final Runnable redo;
if (isSafeToConvert == EncodingUtil.Magic8.ABSOLUTELY && isSafeToReload == EncodingUtil.Magic8.ABSOLUTELY) {
//change and forget
undo = new Runnable() {
@Override
public void run() {
EncodingManager.getInstance().setEncoding(virtualFile, oldCharset);
}
};
redo = new Runnable() {
@Override
public void run() {
EncodingManager.getInstance().setEncoding(virtualFile, charset);
}
};
}
else {
IncompatibleEncodingDialog dialog = new IncompatibleEncodingDialog(virtualFile, charset, isSafeToReload, isSafeToConvert);
dialog.show();
if (dialog.getExitCode() == IncompatibleEncodingDialog.RELOAD_EXIT_CODE) {
undo = new Runnable() {
@Override
public void run() {
EncodingUtil.reloadIn(virtualFile, oldCharset);
}
};
redo = new Runnable() {
@Override
public void run() {
EncodingUtil.reloadIn(virtualFile, charset);
}
};
}
else if (dialog.getExitCode() == IncompatibleEncodingDialog.CONVERT_EXIT_CODE) {
undo = new Runnable() {
@Override
public void run() {
EncodingUtil.saveIn(document, editor, virtualFile, oldCharset);
}
};
redo = new Runnable() {
@Override
public void run() {
EncodingUtil.saveIn(document, editor, virtualFile, charset);
}
};
}
else {
return false;
}
}
final UndoableAction action = new GlobalUndoableAction(virtualFile) {
@Override
public void undo() {
// invoke later because changing document inside undo/redo is not allowed
Application application = ApplicationManager.getApplication();
application.invokeLater(undo, ModalityState.NON_MODAL, (project == null ? application : project).getDisposed());
}
@Override
public void redo() {
// invoke later because changing document inside undo/redo is not allowed
Application application = ApplicationManager.getApplication();
application.invokeLater(redo, ModalityState.NON_MODAL, (project == null ? application : project).getDisposed());
}
};
redo.run();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
UndoManager undoManager = project == null ? UndoManager.getGlobalInstance() : UndoManager.getInstance(project);
undoManager.undoableActionPerformed(action);
}
}, "Change encoding for '" + virtualFile.getName() + "'", null, UndoConfirmationPolicy.REQUEST_CONFIRMATION);
return true;
}
}