/* * 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 consulo.annotations.RequiredDispatchThread; 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; } @RequiredDispatchThread 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; } @RequiredDispatchThread @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); } @RequiredDispatchThread @Override public final void actionPerformed(final AnActionEvent e) { DataContext dataContext = e.getDataContext(); ListPopup popup = createPopup(dataContext); if (popup != null) { popup.showInBestPositionFor(dataContext); } } @Nullable @RequiredDispatchThread 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) { @RequiredDispatchThread @Override public void update(@NotNull 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 @RequiredDispatchThread public void run() { EncodingUtil.saveIn(document, editor, virtualFile, oldCharset); } }; redo = new Runnable() { @Override @RequiredDispatchThread 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; } }