diff: support BOM differences

This commit is contained in:
Aleksey Pivovarov
2016-10-03 14:28:21 +03:00
parent b6473f4dfd
commit 49c94dda7a
10 changed files with 73 additions and 20 deletions

View File

@@ -59,6 +59,12 @@ public interface DocumentContent extends DiffContent {
@Nullable
default Charset getCharset() { return null; }
/**
* @return original file byte order mark
*/
@Nullable
default Boolean getBOM() { return null; }
@Nullable
@Deprecated
default OpenFileDescriptor getOpenFileDescriptor(int offset) {

View File

@@ -94,7 +94,7 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
@Override
public DocumentContent create(@NotNull Document document, @Nullable DocumentContent referent) {
if (referent == null) return new DocumentContentImpl(document);
return new DocumentContentImpl(document, referent.getContentType(), referent.getHighlightFile(), null, null);
return new DocumentContentImpl(document, referent.getContentType(), referent.getHighlightFile(), null, null, null);
}
@Override
@@ -107,7 +107,7 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
@NotNull
public DocumentContent create(@Nullable Project project, @NotNull Document document, @Nullable FileType fileType) {
VirtualFile file = FileDocumentManager.getInstance().getFile(document);
if (file == null) return new DocumentContentImpl(document, fileType, null, null, null);
if (file == null) return new DocumentContentImpl(document, fileType, null, null, null, null);
return create(project, document, file);
}
@@ -189,7 +189,7 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
LineSeparator separator = respectLineSeparators ? StringUtil.detectSeparators(text) : null;
Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(text));
if (readOnly) document.setReadOnly(true);
return new DocumentContentImpl(document, type, highlightFile, separator, charset);
return new DocumentContentImpl(document, type, highlightFile, separator, charset, null);
}
@NotNull

View File

@@ -37,21 +37,24 @@ public class DocumentContentImpl extends DiffContentBase implements DocumentCont
@Nullable private final LineSeparator mySeparator;
@Nullable private final Charset myCharset;
@Nullable private final Boolean myBOM;
public DocumentContentImpl(@NotNull Document document) {
this(document, null, null, null, null);
this(document, null, null, null, null, null);
}
public DocumentContentImpl(@NotNull Document document,
@Nullable FileType type,
@Nullable VirtualFile highlightFile,
@Nullable LineSeparator separator,
@Nullable Charset charset) {
@Nullable Charset charset,
@Nullable Boolean bom) {
myDocument = document;
myType = type;
myHighlightFile = highlightFile;
mySeparator = separator;
myCharset = charset;
myBOM = bom;
}
@NotNull
@@ -84,6 +87,12 @@ public class DocumentContentImpl extends DiffContentBase implements DocumentCont
return mySeparator;
}
@Override
@Nullable
public Boolean getBOM() {
return myBOM;
}
@Nullable
@Override
public FileType getContentType() {

View File

@@ -40,8 +40,9 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
@Nullable FileType fileType,
@Nullable VirtualFile highlightFile,
@Nullable LineSeparator separator,
@Nullable Charset charset) {
super(document, fileType, highlightFile, separator, charset);
@Nullable Charset charset,
@Nullable Boolean bom) {
super(document, fileType, highlightFile, separator, charset, bom);
myProject = project;
}
@@ -78,6 +79,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
private VirtualFile myHighlightFile;
private LineSeparator mySeparator;
private Charset myCharset;
private Boolean myBOM;
private Charset mySuggestedCharset;
private boolean myMalformedContent;
private String myFileName;
@@ -140,6 +142,15 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
private Builder create(@NotNull byte[] content) {
assert mySuggestedCharset != null;
Charset charset = CharsetToolkit.guessFromBOM(content);
if (charset != null) {
mySuggestedCharset = charset;
myBOM = true;
}
else {
myBOM = false;
}
myCharset = mySuggestedCharset;
try {
String text = CharsetToolkit.tryDecodeString(content, mySuggestedCharset);
@@ -165,7 +176,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
public FileAwareDocumentContent build() {
if (FileTypes.UNKNOWN.equals(myFileType)) myFileType = PlainTextFileType.INSTANCE;
FileAwareDocumentContent content
= new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset);
= new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset, myBOM);
DiffUtil.addNotification(createNotification(), content);
content.putUserData(DiffUserDataKeysEx.FILE_NAME, myFileName);
return content;

View File

@@ -35,7 +35,7 @@ public class FileDocumentContentImpl extends DocumentContentImpl implements File
public FileDocumentContentImpl(@Nullable Project project,
@NotNull Document document,
@NotNull VirtualFile file) {
super(document, file.getFileType(), file, getSeparator(file), file.getCharset());
super(document, file.getFileType(), file, getSeparator(file), file.getCharset(), file.getBOM() != null);
myProject = project;
myFile = file;
}

View File

@@ -36,8 +36,10 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileWithoutContent;
import com.intellij.util.ArrayUtil;
import com.intellij.util.LineSeparator;
import com.intellij.util.PathUtil;
import com.intellij.util.TimeoutUtil;
@@ -115,6 +117,9 @@ public class ExternalDiffToolUtil {
Charset charset = content.getCharset();
if (charset == null) charset = Charset.defaultCharset();
Boolean hasBom = content.getBOM();
if (hasBom == null) hasBom = CharsetToolkit.getMandatoryBom(charset) != null;
String contentData = ReadAction.compute(() -> {
return content.getDocument().getText();
});
@@ -123,6 +128,12 @@ public class ExternalDiffToolUtil {
}
byte[] bytes = contentData.getBytes(charset);
byte[] bom = hasBom ? CharsetToolkit.getBom(charset) : null;
if (bom != null) {
bytes = ArrayUtil.mergeArrays(bom, bytes);
}
return createFile(bytes, fileName);
}

View File

@@ -142,7 +142,9 @@ public class TextDiffViewerUtil {
}
public static boolean areEqualCharsets(@NotNull List<? extends DiffContent> contents) {
return areEqualDocumentContentProperties(contents, DocumentContent::getCharset);
boolean sameCharset = areEqualDocumentContentProperties(contents, DocumentContent::getCharset);
boolean sameBOM = areEqualDocumentContentProperties(contents, DocumentContent::getBOM);
return sameCharset && sameBOM;
}
private static <T> boolean areEqualDocumentContentProperties(@NotNull List<? extends DiffContent> contents,

View File

@@ -467,23 +467,26 @@ public class DiffUtil {
boolean equalSeparators,
@Nullable Editor editor) {
if (content instanceof EmptyContent) return null;
DocumentContent documentContent = (DocumentContent)content;
Charset charset = equalCharsets ? null : ((DocumentContent)content).getCharset();
LineSeparator separator = equalSeparators ? null : ((DocumentContent)content).getLineSeparator();
Charset charset = equalCharsets ? null : documentContent.getCharset();
Boolean bom = equalCharsets ? null : documentContent.getBOM();
LineSeparator separator = equalSeparators ? null : documentContent.getLineSeparator();
boolean isReadOnly = editor == null || editor.isViewer() || !canMakeWritable(editor.getDocument());
return createTitle(title, charset, separator, isReadOnly);
return createTitle(title, separator, charset, bom, isReadOnly);
}
@NotNull
public static JComponent createTitle(@NotNull String title) {
return createTitle(title, null, null, false);
return createTitle(title, null, null, null, false);
}
@NotNull
public static JComponent createTitle(@NotNull String title,
@Nullable Charset charset,
@Nullable LineSeparator separator,
@Nullable Charset charset,
@Nullable Boolean bom,
boolean readOnly) {
if (readOnly) title += " " + DiffBundle.message("diff.content.read.only.content.title.suffix");
@@ -493,13 +496,13 @@ public class DiffUtil {
if (charset != null && separator != null) {
JPanel panel2 = new JPanel();
panel2.setLayout(new BoxLayout(panel2, BoxLayout.X_AXIS));
panel2.add(createCharsetPanel(charset));
panel2.add(createCharsetPanel(charset, bom));
panel2.add(Box.createRigidArea(new Dimension(4, 0)));
panel2.add(createSeparatorPanel(separator));
panel.add(panel2, BorderLayout.EAST);
}
else if (charset != null) {
panel.add(createCharsetPanel(charset), BorderLayout.EAST);
panel.add(createCharsetPanel(charset, bom), BorderLayout.EAST);
}
else if (separator != null) {
panel.add(createSeparatorPanel(separator), BorderLayout.EAST);
@@ -508,8 +511,13 @@ public class DiffUtil {
}
@NotNull
private static JComponent createCharsetPanel(@NotNull Charset charset) {
JLabel label = new JLabel(charset.displayName());
private static JComponent createCharsetPanel(@NotNull Charset charset, @Nullable Boolean bom) {
String text = charset.displayName();
if (bom != null && bom) {
text += " BOM";
}
JLabel label = new JLabel(text);
// TODO: specific colors for other charsets
if (charset.equals(Charset.forName("UTF-8"))) {
label.setForeground(JBColor.BLUE);

View File

@@ -624,6 +624,12 @@ public class CharsetToolkit {
return CHARSET_TO_MANDATORY_BOM.get(charset);
}
@Nullable
public static byte[] getBom(@NotNull Charset charset) {
if (charset.equals(UTF8_CHARSET)) return UTF8_BOM;
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)

View File

@@ -91,7 +91,7 @@ public class MigrateToNewDiffUtil {
else {
Document document = oldContent.getDocument();
if (document == null) return null;
return new DocumentContentImpl(document, oldContent.getContentType(), oldContent.getFile(), oldContent.getLineSeparator(), null) {
return new DocumentContentImpl(document, oldContent.getContentType(), oldContent.getFile(), oldContent.getLineSeparator(), null, null) {
@Nullable
@Override
public Navigatable getNavigatable(@NotNull LineCol position) {