diff --git a/platform/diff-impl/src/com/intellij/diff/contents/FileAwareDocumentContent.java b/platform/diff-impl/src/com/intellij/diff/contents/FileAwareDocumentContent.java index 6e78ea920ee0..e4c6d6ca83c2 100644 --- a/platform/diff-impl/src/com/intellij/diff/contents/FileAwareDocumentContent.java +++ b/platform/diff-impl/src/com/intellij/diff/contents/FileAwareDocumentContent.java @@ -1,5 +1,7 @@ package com.intellij.diff.contents; +import com.intellij.diff.tools.util.DiffNotifications; +import com.intellij.diff.util.DiffUserDataKeys; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.fileEditor.OpenFileDescriptor; @@ -11,11 +13,16 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.LightColors; import com.intellij.util.LineSeparator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; public class FileAwareDocumentContent extends DocumentContentImpl { @Nullable private final Project myProject; @@ -63,6 +70,8 @@ public class FileAwareDocumentContent extends DocumentContentImpl { private VirtualFile myHighlightFile; private LineSeparator mySeparator; private Charset myCharset; + private Charset mySuggestedCharset; + private boolean myMalformedContent; public Builder(@Nullable Project project) { myProject = project; @@ -76,7 +85,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl { private Builder init(@NotNull FilePath path) { myHighlightFile = path.getVirtualFile(); myFileType = path.getFileType(); - myCharset = path.getCharset(myProject); + mySuggestedCharset = path.getCharset(myProject); return this; } @@ -84,7 +93,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl { private Builder init(@NotNull VirtualFile highlightFile) { myHighlightFile = highlightFile; myFileType = highlightFile.getFileType(); - myCharset = highlightFile.getCharset(); + mySuggestedCharset = highlightFile.getCharset(); return this; } @@ -98,14 +107,37 @@ public class FileAwareDocumentContent extends DocumentContentImpl { @NotNull private Builder create(@NotNull byte[] content) { - assert myCharset != null; - return create(CharsetToolkit.decodeString(content, myCharset)); + assert mySuggestedCharset != null; + + myCharset = mySuggestedCharset; + try { + String text = CharsetToolkit.tryDecodeString(content, mySuggestedCharset); + return create(text); + } + catch (CharacterCodingException e) { + String text = CharsetToolkit.decodeString(content, mySuggestedCharset); + myMalformedContent = true; + return create(text); + } + } + + @Nullable + private List createNotifications() { + if (!myMalformedContent) return null; + assert mySuggestedCharset != null; + + String text = "Content was decoded with errors (using " + "'" + mySuggestedCharset.name() + "' charset)"; + JComponent notification = DiffNotifications.createNotification(text, LightColors.RED); + return Collections.singletonList(notification); } @NotNull public FileAwareDocumentContent build() { if (FileTypes.UNKNOWN.equals(myFileType)) myFileType = PlainTextFileType.INSTANCE; - return new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset); + FileAwareDocumentContent content + = new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset); + content.putUserData(DiffUserDataKeys.NOTIFICATIONS, createNotifications()); + return content; } } } diff --git a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java index f820be1b3a09..29edb502c696 100644 --- a/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java +++ b/platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java @@ -350,6 +350,7 @@ public class DiffUtil { @NotNull public static List createSimpleTitles(@NotNull ContentDiffRequest request) { + List contents = request.getContents(); List titles = request.getContentTitles(); if (!ContainerUtil.exists(titles, Condition.NOT_NULL)) { @@ -357,8 +358,10 @@ public class DiffUtil { } List components = new ArrayList(titles.size()); - for (String title : titles) { - components.add(createTitle(StringUtil.notNullize(title))); + for (int i = 0; i < contents.size(); i++) { + JComponent title = createTitle(StringUtil.notNullize(titles.get(i))); + title = createTitleWithNotifications(title, contents.get(i)); + components.add(title); } return components; @@ -394,12 +397,26 @@ public class DiffUtil { } for (int i = 0; i < contents.size(); i++) { - result.add(createTitle(StringUtil.notNullize(titles.get(i)), contents.get(i), equalCharsets, equalSeparators, editors.get(i))); + JComponent title = createTitle(StringUtil.notNullize(titles.get(i)), contents.get(i), equalCharsets, equalSeparators, editors.get(i)); + title = createTitleWithNotifications(title, contents.get(i)); + result.add(title); } return result; } + @Nullable + private static JComponent createTitleWithNotifications(@Nullable JComponent title, + @NotNull DiffContent content) { + List notifications = getCustomNotifications(content); + if (notifications.isEmpty()) return title; + + List components = new ArrayList(); + if (title != null) components.add(title); + components.addAll(notifications); + return createStackedComponents(components, TITLE_GAP); + } + private static boolean isEqualElements(@NotNull List elements) { for (int i = 0; i < elements.size(); i++) { for (int j = i + 1; j < elements.size(); j++) { @@ -1064,6 +1081,11 @@ public class DiffUtil { return ContainerUtil.concat(ContainerUtil.notNullize(contextComponents), ContainerUtil.notNullize(requestComponents)); } + @NotNull + public static List getCustomNotifications(@NotNull DiffContent content) { + return ContainerUtil.notNullize(content.getUserData(DiffUserDataKeys.NOTIFICATIONS)); + } + // // DataProvider // diff --git a/platform/util/src/com/intellij/openapi/vfs/CharsetToolkit.java b/platform/util/src/com/intellij/openapi/vfs/CharsetToolkit.java index 25a44601e7e6..c6940f02df20 100644 --- a/platform/util/src/com/intellij/openapi/vfs/CharsetToolkit.java +++ b/platform/util/src/com/intellij/openapi/vfs/CharsetToolkit.java @@ -24,9 +24,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.IllegalCharsetNameException; -import java.nio.charset.UnsupportedCharsetException; +import java.nio.charset.*; import java.util.Arrays; import java.util.Collection; import java.util.Map; @@ -308,6 +306,16 @@ public class CharsetToolkit { return charBuffer.toString(); } + @NotNull + public static String tryDecodeString(@NotNull byte[] bytes, @NotNull final Charset charset) throws CharacterCodingException { + int bomLength = CharsetToolkit.getBOMLength(bytes, charset); + ByteBuffer buffer = ByteBuffer.wrap(bytes, bomLength, bytes.length - bomLength); + CharsetDecoder decoder = charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + return decoder.decode(buffer).toString(); + } + public enum GuessedEncoding { SEVEN_BIT, // ASCII VALID_UTF8, // UTF-8