| /* |
| * 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; |
| } |
| } |