2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.diff;
18 import com.intellij.diff.actions.DocumentFragmentContent;
19 import com.intellij.diff.contents.*;
20 import com.intellij.diff.tools.util.DiffNotifications;
21 import com.intellij.diff.util.DiffUserDataKeysEx;
22 import com.intellij.diff.util.DiffUtil;
23 import com.intellij.ide.highlighter.ArchiveFileType;
24 import com.intellij.openapi.application.ReadAction;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.EditorFactory;
28 import com.intellij.openapi.fileEditor.FileDocumentManager;
29 import com.intellij.openapi.fileTypes.BinaryFileTypeDecompilers;
30 import com.intellij.openapi.fileTypes.FileType;
31 import com.intellij.openapi.fileTypes.FileTypes;
32 import com.intellij.openapi.fileTypes.PlainTextFileType;
33 import com.intellij.openapi.ide.CopyPasteManager;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.TextRange;
36 import com.intellij.openapi.util.io.FileUtil;
37 import com.intellij.openapi.util.registry.Registry;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.openapi.vcs.FilePath;
40 import com.intellij.openapi.vfs.CharsetToolkit;
41 import com.intellij.openapi.vfs.VfsUtil;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.psi.PsiDocumentManager;
44 import com.intellij.testFramework.BinaryLightVirtualFile;
45 import com.intellij.testFramework.LightVirtualFile;
46 import com.intellij.ui.LightColors;
47 import com.intellij.util.LineSeparator;
48 import com.intellij.util.PathUtil;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import java.awt.datatransfer.DataFlavor;
54 import java.io.IOException;
55 import java.nio.charset.CharacterCodingException;
56 import java.nio.charset.Charset;
58 public class DiffContentFactoryImpl extends DiffContentFactoryEx {
59 public static final Logger LOG = Logger.getInstance(DiffContentFactoryImpl.class);
63 public EmptyContent createEmpty() {
64 return new EmptyContent();
70 public DocumentContent create(@NotNull String text) {
71 return create(null, text);
76 public DocumentContent create(@NotNull String text, @Nullable FileType type) {
77 return create(null, text, type);
82 public DocumentContent create(@NotNull String text, @Nullable FileType type, boolean respectLineSeparators) {
83 return create(null, text, type, respectLineSeparators);
88 public DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile) {
89 return create(null, text, highlightFile);
94 public DocumentContent create(@NotNull String text, @Nullable DocumentContent referent) {
95 return create(null, text, referent);
101 public DocumentContent create(@Nullable Project project, @NotNull String text) {
102 return create(project, text, (FileType)null);
107 public DocumentContent create(@Nullable Project project, @NotNull String text, @Nullable FileType type) {
108 return create(project, text, type, true);
113 public DocumentContent create(@Nullable Project project, @NotNull String text, @Nullable FileType type, boolean respectLineSeparators) {
114 return createImpl(project, text, type, null, null, respectLineSeparators, true);
119 public DocumentContent create(@Nullable Project project, @NotNull String text, @NotNull FilePath filePath) {
120 return createImpl(project, text, filePath.getFileType(), filePath.getName(), filePath.getVirtualFile(), true, true);
125 public DocumentContent create(@Nullable Project project, @NotNull String text, @Nullable VirtualFile highlightFile) {
126 FileType fileType = highlightFile != null ? highlightFile.getFileType() : null;
127 String fileName = highlightFile != null ? highlightFile.getName() : null;
128 return createImpl(project, text, fileType, fileName, highlightFile, true, true);
133 public DocumentContent create(@Nullable Project project, @NotNull String text, @Nullable DocumentContent referent) {
134 if (referent == null) return create(text);
135 return createImpl(project, text, referent.getContentType(), null, referent.getHighlightFile(), false, true);
141 public DocumentContent create(@NotNull Document document, @Nullable DocumentContent referent) {
142 return create(null, document, referent);
148 public DocumentContent create(@Nullable Project project, @NotNull Document document) {
149 return create(project, document, (FileType)null);
154 public DocumentContent create(@Nullable Project project, @NotNull Document document, @Nullable FileType fileType) {
155 VirtualFile file = FileDocumentManager.getInstance().getFile(document);
156 if (file == null) return new DocumentContentImpl(project, document, fileType, null, null, null, null);
157 return create(project, document, file);
162 public DocumentContent create(@Nullable Project project, @NotNull Document document, @Nullable VirtualFile file) {
163 if (file != null) return new FileDocumentContentImpl(project, document, file);
164 return new DocumentContentImpl(document);
169 public DocumentContent create(@Nullable Project project, @NotNull Document document, @Nullable DocumentContent referent) {
170 if (referent == null) return new DocumentContentImpl(document);
171 return new DocumentContentImpl(project, document, referent.getContentType(), referent.getHighlightFile(), null, null, null);
177 public DiffContent create(@Nullable Project project, @NotNull VirtualFile file) {
178 if (file.isDirectory()) return new DirectoryContentImpl(project, file);
179 DocumentContent content = createDocument(project, file);
180 if (content != null) return content;
181 return new FileContentImpl(project, file);
186 public DocumentContent createDocument(@Nullable Project project, @NotNull final VirtualFile file) {
187 // TODO: add notification, that file is decompiled ?
188 if (file.isDirectory()) return null;
189 Document document = ReadAction.compute(() -> {
190 return FileDocumentManager.getInstance().getDocument(file);
192 if (document == null) return null;
193 return new FileDocumentContentImpl(project, document, file);
198 public FileContent createFile(@Nullable Project project, @NotNull VirtualFile file) {
199 if (file.isDirectory()) return null;
200 return (FileContent)create(project, file);
206 public DocumentContent createFragment(@Nullable Project project, @NotNull Document document, @NotNull TextRange range) {
207 DocumentContent content = create(project, document);
208 return new DocumentFragmentContent(project, content, range);
213 public DocumentContent createFragment(@Nullable Project project, @NotNull DocumentContent content, @NotNull TextRange range) {
214 return new DocumentFragmentContent(project, content, range);
220 public DiffContent createClipboardContent() {
221 return createClipboardContent(null, null);
226 public DocumentContent createClipboardContent(@Nullable DocumentContent referent) {
227 return createClipboardContent(null, referent);
232 public DiffContent createClipboardContent(@Nullable Project project) {
233 return createClipboardContent(project, null);
238 public DocumentContent createClipboardContent(@Nullable Project project, @Nullable DocumentContent referent) {
239 String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
241 FileType type = referent != null ? referent.getContentType() : null;
242 VirtualFile highlightFile = referent != null ? referent.getHighlightFile() : null;
244 return createImpl(project, StringUtil.notNullize(text), type, "Clipboard.txt", highlightFile, true, false);
250 public DiffContent createFromBytes(@Nullable Project project,
251 @NotNull byte[] content,
252 @NotNull FilePath filePath) throws IOException {
253 if (filePath.getFileType().isBinary()) {
254 return createBinary(project, content, filePath.getFileType(), filePath.getName());
257 return createDocumentFromBytes(project, content, filePath);
262 public DiffContent createFromBytes(@Nullable Project project,
263 @NotNull byte[] content,
264 @NotNull VirtualFile highlightFile) throws IOException {
265 // TODO: check if FileType.UNKNOWN is actually a text ?
266 if (highlightFile.getFileType().isBinary()) {
267 return createBinary(project, content, highlightFile.getFileType(), highlightFile.getName());
270 return createDocumentFromBytes(project, content, highlightFile);
275 public DocumentContent createDocumentFromBytes(@Nullable Project project, @NotNull byte[] content, @NotNull FilePath filePath) {
276 return createFromBytesImpl(project, content, filePath.getFileType(), filePath.getName(), filePath.getVirtualFile(),
277 filePath.getCharset());
282 public DocumentContent createDocumentFromBytes(@Nullable Project project, @NotNull byte[] content, @NotNull VirtualFile highlightFile) {
283 return createFromBytesImpl(project, content, highlightFile.getFileType(), highlightFile.getName(), highlightFile,
284 highlightFile.getCharset());
289 public DiffContent createBinary(@Nullable Project project,
290 @NotNull byte[] content,
291 @NotNull FileType type,
292 @NotNull String fileName) throws IOException {
293 // workaround - our JarFileSystem and decompilers can't process non-local files
294 boolean useTemporalFile = type instanceof ArchiveFileType || BinaryFileTypeDecompilers.INSTANCE.forFileType(type) != null;
297 if (useTemporalFile) {
298 file = createTemporalFile(project, "tmp", fileName, content);
301 file = new BinaryLightVirtualFile(fileName, type, content);
302 file.setWritable(false);
305 return create(project, file);
309 private static DocumentContent createImpl(@Nullable Project project,
310 @NotNull String text,
311 @Nullable FileType fileType,
312 @Nullable String fileName,
313 @Nullable VirtualFile highlightFile,
314 boolean respectLineSeparators,
316 return createImpl(project, text, fileType, fileName, highlightFile, null, null, respectLineSeparators, readOnly);
320 private static DocumentContent createImpl(@Nullable Project project,
321 @NotNull String text,
322 @Nullable FileType fileType,
323 @Nullable String fileName,
324 @Nullable VirtualFile highlightFile,
325 @Nullable Charset charset,
326 @Nullable Boolean bom,
327 boolean respectLineSeparators,
329 if (FileTypes.UNKNOWN.equals(fileType)) fileType = PlainTextFileType.INSTANCE;
331 // TODO: detect invalid (different across the file) separators ?
332 LineSeparator separator = respectLineSeparators ? StringUtil.detectSeparators(text) : null;
333 String correctedContent = StringUtil.convertLineSeparators(text);
335 Document document = createDocument(project, correctedContent, fileType, fileName, readOnly);
336 DocumentContent content = new DocumentContentImpl(project, document, fileType, highlightFile, separator, charset, bom);
338 if (fileName != null) content.putUserData(DiffUserDataKeysEx.FILE_NAME, fileName);
344 private static DocumentContent createFromBytesImpl(@Nullable Project project,
345 @NotNull byte[] content,
346 @NotNull FileType fileType,
347 @NotNull String fileName,
348 @Nullable VirtualFile highlightFile,
349 @NotNull Charset charset) {
350 Charset bomCharset = CharsetToolkit.guessFromBOM(content);
351 boolean isBOM = bomCharset != null;
352 if (isBOM) charset = bomCharset;
354 boolean malformedContent = false;
357 text = CharsetToolkit.tryDecodeString(content, charset);
359 catch (CharacterCodingException e) {
360 text = CharsetToolkit.decodeString(content, charset);
361 malformedContent = true;
364 LineSeparator separator = StringUtil.detectSeparators(text);
365 String correctedContent = StringUtil.convertLineSeparators(text);
367 DocumentContent documentContent = createImpl(project, correctedContent, fileType, fileName, highlightFile, charset, isBOM, true, true);
369 if (malformedContent) {
370 String notificationText = "Content was decoded with errors (using " + "'" + charset.name() + "' charset)";
371 DiffUtil.addNotification(DiffNotifications.createNotification(notificationText, LightColors.RED), documentContent);
374 return documentContent;
378 private static VirtualFile createTemporalFile(@Nullable Project project,
379 @NotNull String prefix,
380 @NotNull String suffix,
381 @NotNull byte[] content) throws IOException {
382 File tempFile = FileUtil.createTempFile(PathUtil.suggestFileName(prefix + "_", true, false),
383 PathUtil.suggestFileName("_" + suffix, true, false), true);
384 if (content.length != 0) {
385 FileUtil.writeToFile(tempFile, content);
387 if (!tempFile.setWritable(false, false)) LOG.warn("Can't set writable attribute of temporal file");
389 VirtualFile file = VfsUtil.findFileByIoFile(tempFile, true);
391 throw new IOException("Can't create temp file for revision content");
393 VfsUtil.markDirtyAndRefresh(true, true, true, file);
398 private static Document createDocument(@Nullable Project project,
399 @NotNull String content,
400 @Nullable FileType fileType,
401 @Nullable String fileName,
403 if (project != null && !project.isDefault() &&
404 fileType != null && !fileType.isBinary() &&
405 Registry.is("diff.enable.psi.highlighting")) {
406 if (fileName == null) {
407 fileName = "diff." + StringUtil.defaultIfEmpty(fileType.getDefaultExtension(), "txt");
410 Document document = createPsiDocument(project, content, fileType, fileName, readOnly);
411 if (document != null) return document;
414 Document document = EditorFactory.getInstance().createDocument(content);
415 document.setReadOnly(readOnly);
420 private static Document createPsiDocument(@NotNull Project project,
421 @NotNull String content,
422 @NotNull FileType fileType,
423 @NotNull String fileName,
425 return ReadAction.compute(() -> {
426 LightVirtualFile file = new LightVirtualFile(fileName, fileType, content);
427 file.setWritable(!readOnly);
429 file.putUserData(DiffPsiFileSupport.KEY, true);
431 Document document = FileDocumentManager.getInstance().getDocument(file);
432 if (document == null) return null;
434 PsiDocumentManager.getInstance(project).getPsiFile(document);