diff --git a/platform/core-api/src/com/intellij/openapi/vfs/CharsetToolkit.java b/platform/core-api/src/com/intellij/openapi/vfs/CharsetToolkit.java index 1dd76ef820c1..95ca3db56341 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/CharsetToolkit.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/CharsetToolkit.java @@ -94,12 +94,12 @@ public class CharsetToolkit { public static final byte[] UTF32LE_BOM = {-1, -2, 0, 0 }; @NonNls public static final String FILE_ENCODING_PROPERTY = "file.encoding"; - @NonNls private static final Map CHARSET_TO_BOM = new THashMap(2); + @NonNls private static final Map CHARSET_TO_MANDATORY_BOM = new THashMap(2); static { - CHARSET_TO_BOM.put(UTF_16LE_CHARSET, UTF16LE_BOM); - CHARSET_TO_BOM.put(UTF_16BE_CHARSET, UTF16BE_BOM); - CHARSET_TO_BOM.put(UTF_32BE_CHARSET, UTF32BE_BOM); - CHARSET_TO_BOM.put(UTF_32LE_CHARSET, UTF32LE_BOM); + CHARSET_TO_MANDATORY_BOM.put(UTF_16LE_CHARSET, UTF16LE_BOM); + CHARSET_TO_MANDATORY_BOM.put(UTF_16BE_CHARSET, UTF16BE_BOM); + CHARSET_TO_MANDATORY_BOM.put(UTF_32BE_CHARSET, UTF32BE_BOM); + CHARSET_TO_MANDATORY_BOM.put(UTF_32LE_CHARSET, UTF32LE_BOM); } /** @@ -503,12 +503,13 @@ public class CharsetToolkit { @Nullable public static byte[] getBom(@NotNull Charset charset) { - return CHARSET_TO_BOM.get(charset); + return CHARSET_TO_MANDATORY_BOM.get(charset); } // byte sequence for this encoding is allowed to be prepended with this BOM public static boolean canHaveBom(@NotNull Charset charset, @NotNull byte[] bom) { - return charset.equals(UTF8_CHARSET) && Arrays.equals(bom, UTF8_BOM) || Arrays.equals(getBom(charset), bom); + return charset.equals(UTF8_CHARSET) && Arrays.equals(bom, UTF8_BOM) + || Arrays.equals(getBom(charset), bom); } @Nullable diff --git a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java index e3eb52bde61d..8e2d5144f529 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java @@ -138,6 +138,9 @@ public class LossyEncodingInspection extends LocalInspectionTool { @NotNull String text, @NotNull Charset charset, @NotNull Project project) { + FileDocumentManager documentManager = FileDocumentManager.getInstance(); + Document document = documentManager.getDocument(virtualFile); + if (document == null) return true; byte[] bytes; try { bytes = virtualFile.contentsToByteArray(); @@ -145,9 +148,6 @@ public class LossyEncodingInspection extends LocalInspectionTool { catch (IOException e) { return true; } - FileDocumentManager documentManager = FileDocumentManager.getInstance(); - Document document = documentManager.getDocument(virtualFile); - if (document == null) return true; String separator = LoadTextUtil.detectLineSeparator(virtualFile, false); if (separator == null) { separator = documentManager.isDocumentUnsaved(document) ? diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/ChangeFileEncodingAction.java b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/ChangeFileEncodingAction.java index d69460b4f76b..b016f51e047b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/ChangeFileEncodingAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/ChangeFileEncodingAction.java @@ -62,10 +62,7 @@ public class ChangeFileEncodingAction extends AnAction implements DumbAware { Document document = documentManager.getDocument(virtualFile); if (document == null) return false; - boolean canConvert = EncodingUtil.checkCanConvert(virtualFile) == null; - boolean canReload = EncodingUtil.checkCanReload(virtualFile).second == null; - - return canConvert || canReload; + return EncodingUtil.checkCanConvert(virtualFile) == null || EncodingUtil.checkCanReload(virtualFile).second == null; } @Override @@ -94,7 +91,7 @@ public class ChangeFileEncodingAction extends AnAction implements DumbAware { Editor editor = PlatformDataKeys.EDITOR.getData(dataContext); FileDocumentManager documentManager = FileDocumentManager.getInstance(); final Document document = documentManager.getDocument(virtualFile); - if (!allowDirectories && !virtualFile.isDirectory() && document == null) return null; + if (!allowDirectories && virtualFile.isDirectory() || document == null && !virtualFile.isDirectory()) return null; final byte[] bytes; try { @@ -127,9 +124,13 @@ public class ChangeFileEncodingAction extends AnAction implements DumbAware { return createCharsetsActionGroup(clearItemText, null, new Function() { @Override public String fun(Charset charset) { + assert text != null : charset; EncodingUtil.Magic8 safeToReload = myFile.isDirectory() ? EncodingUtil.Magic8.ABSOLUTELY : EncodingUtil.isSafeToReloadIn(myFile, text, bytes, charset); - EncodingUtil.Magic8 safeToConvert = myFile.isDirectory() ? EncodingUtil.Magic8.ABSOLUTELY : EncodingUtil.isSafeToConvertTo(myFile, text, bytes, charset); - boolean enabled = safeToReload != EncodingUtil.Magic8.NO_WAY || safeToConvert != EncodingUtil.Magic8.NO_WAY; + 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' @@ -163,7 +164,6 @@ public class ChangeFileEncodingAction extends AnAction implements DumbAware { if (isSafeToConvert == EncodingUtil.Magic8.ABSOLUTELY && isSafeToReload == EncodingUtil.Magic8.ABSOLUTELY) { //change and forget - EncodingManager.getInstance().setEncoding(virtualFile, charset); undo = new Runnable() { @Override public void run() { @@ -178,7 +178,7 @@ public class ChangeFileEncodingAction extends AnAction implements DumbAware { }; } else { - IncompatibleEncodingDialog dialog = new IncompatibleEncodingDialog(document, virtualFile, bytes, charset, isSafeToReload, isSafeToConvert); + IncompatibleEncodingDialog dialog = new IncompatibleEncodingDialog(virtualFile, charset, isSafeToReload, isSafeToConvert); dialog.show(); if (dialog.getExitCode() == IncompatibleEncodingDialog.RELOAD_EXIT_CODE) { undo = new Runnable() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingProjectManagerImpl.java index 2a0fb28d006c..8536d29cb971 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingProjectManagerImpl.java @@ -48,6 +48,7 @@ import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.UIUtil; +import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -315,15 +316,17 @@ public class EncodingProjectManagerImpl extends EncodingProjectManager { if (!changed.isEmpty()) { final Processor reloadProcessor = createChangeCharsetProcessor(); - startReloadWithProgress(new Runnable() { + tryStartReloadWithProgress(new Runnable() { @Override public void run() { + Set processed = new THashSet(); + next: for (VirtualFile changedFile : changed) { - final Charset newCharset = newMap.get(changedFile); - Charset oldCharset = oldMap.get(changedFile); - if (!Comparing.equal(newCharset, oldCharset)) { - processSubFiles(changedFile, reloadProcessor); + for (VirtualFile processedFile : processed) { + if (VfsUtilCore.isAncestor(processedFile, changedFile, false)) continue next; } + processSubFiles(changedFile, reloadProcessor); + processed.add(changedFile); } } }); @@ -400,9 +403,9 @@ public class EncodingProjectManagerImpl extends EncodingProjectManager { } } - private boolean startReloadWithProgress(@NotNull final Runnable reloadAction) { + private boolean tryStartReloadWithProgress(@NotNull final Runnable reloadAction) { Boolean suppress = SUPPRESS_RELOAD.get(); - if (suppress != null && suppress) return false; + if (suppress == Boolean.TRUE) return false; FileDocumentManager.getInstance().saveAllDocuments(); // consider all files as unmodified return ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { @Override @@ -413,7 +416,7 @@ public class EncodingProjectManagerImpl extends EncodingProjectManager { } private void reloadAllFilesUnder(final VirtualFile root) { - startReloadWithProgress(new Runnable() { + tryStartReloadWithProgress(new Runnable() { @Override public void run() { processSubFiles(root, new Processor() { @@ -430,7 +433,8 @@ public class EncodingProjectManagerImpl extends EncodingProjectManager { } }); } - else if (file.isCharsetSet()) { + // for not loaded files deep under project, reset encoding to give them chance re-detect the right one later + else if (file.isCharsetSet() && !file.equals(root)) { file.setCharset(null); } return true; diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingUtil.java index f49441917a95..302dbc45de92 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/EncodingUtil.java @@ -103,18 +103,6 @@ public class EncodingUtil { } } - static boolean isSafeToChangeSilentlyTo(@NotNull VirtualFile virtualFile, @NotNull String text, @NotNull byte[] bytes, @NotNull Charset charset) { - String loaded = LoadTextUtil.getTextByBinaryPresentation(bytes, charset).toString(); - boolean canReloadSilently = loaded.equals(text); - - Pair chosen = LoadTextUtil.chooseMostlyHarmlessCharset(virtualFile.getCharset(), charset, text); - byte[] saved = chosen.second; - boolean canSaveSilently = Arrays.equals(saved, bytes); - - return canReloadSilently && canSaveSilently; - } - - public static void saveIn(@NotNull final Document document, final Editor editor, @NotNull final VirtualFile virtualFile, @NotNull final Charset charset) { FileDocumentManager documentManager = FileDocumentManager.getInstance(); documentManager.saveDocument(document); @@ -125,6 +113,7 @@ public class EncodingUtil { return; } + // first, save the file in the new charset and then mark the file as having the correct encoding virtualFile.setCharset(charset); try { LoadTextUtil.write(project, virtualFile, virtualFile, document.getText(), document.getModificationStamp()); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/IncompatibleEncodingDialog.java b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/IncompatibleEncodingDialog.java index 4adbddcb7706..4e4adfa8977d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/IncompatibleEncodingDialog.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/encoding/IncompatibleEncodingDialog.java @@ -16,7 +16,6 @@ package com.intellij.openapi.vfs.encoding; import com.intellij.icons.AllIcons; -import com.intellij.openapi.editor.Document; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; @@ -30,24 +29,17 @@ import java.awt.event.ActionEvent; import java.nio.charset.Charset; public class IncompatibleEncodingDialog extends DialogWrapper { - - @NotNull private final Document document; @NotNull private final VirtualFile virtualFile; - @NotNull private final byte[] bytes; @NotNull private final Charset charset; @NotNull private final EncodingUtil.Magic8 safeToReload; @NotNull private final EncodingUtil.Magic8 safeToConvert; - public IncompatibleEncodingDialog(@NotNull Document document, - @NotNull VirtualFile virtualFile, - @NotNull byte[] bytes, + public IncompatibleEncodingDialog(@NotNull VirtualFile virtualFile, @NotNull final Charset charset, @NotNull EncodingUtil.Magic8 safeToReload, @NotNull EncodingUtil.Magic8 safeToConvert) { super(false); - this.document = document; this.virtualFile = virtualFile; - this.bytes = bytes; this.charset = charset; this.safeToReload = safeToReload; this.safeToConvert = safeToConvert; diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java index eddb5d23d1b9..7b4f77d12148 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/EncodingPanel.java @@ -217,7 +217,7 @@ public class EncodingPanel extends EditorBasedWidget implements StatusBarWidget. String toolTip = "File Encoding" + (currentCharset == null ? "" : ": "+currentCharset.displayName()) + - (failReason == null ? "" : actionEnabled ? " ("+failReason+")" : " (change disabled: " + failReason + ")"); + (actionEnabled ? "" : " (change disabled: " + failReason + ")"); myComponent.setToolTipText(toolTip); myComponent.setText(text);