diff --git a/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformer.kt b/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformer.kt index dea0e4010256..3503ed96fd27 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformer.kt +++ b/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformer.kt @@ -6,7 +6,27 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal interface TextPresentationTransformer { - fun fromPersistent(text: CharSequence, virtualFile: VirtualFile):CharSequence + /** + * Transforms the given text from its persistent representation to its in-memory representation + * based on the specified virtual file's context. + * + * Used in Jupyter Notebooks to switch from JSON representation to source code text with #%% separators between cells. + * + * @param text The text in its persistent representation to be transformed + * @param virtualFile The virtual file providing context for the transformation + * @return The text in its in-memory representation + */ + fun fromPersistent(text: CharSequence, virtualFile: VirtualFile): CharSequence - fun toPersistent(text: CharSequence, virtualFile: VirtualFile):CharSequence + /** + * Transforms the given text from its in-memory representation to its persistent representation + * based on the context provided by the specified virtual file. + * + * Used in Jupyter Notebooks to switch from source code text with #%% separators between cells to JSON representation. + * + * @param text The text in its in-memory representation to be transformed + * @param virtualFile The virtual file providing context for the transformation + * @return The text in its persistent representation + */ + fun toPersistent(text: CharSequence, virtualFile: VirtualFile): CharSequence } \ No newline at end of file diff --git a/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformers.kt b/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformers.kt index 24f7bdf36962..28bd0b51c104 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformers.kt +++ b/platform/core-api/src/com/intellij/openapi/vfs/transformer/TextPresentationTransformers.kt @@ -34,6 +34,9 @@ class TextPresentationTransformers : FileTypeExtension> = ExtensionPointName("com.intellij.fileEditor.textPresentationTransformer") + /** + * See [TextPresentationTransformer.fromPersistent] + */ @JvmStatic fun fromPersistent(text: CharSequence, virtualFile: VirtualFile): CharSequence { val transformer = service().forFileType(virtualFile.fileType) @@ -44,6 +47,9 @@ class TextPresentationTransformers : FileTypeExtension().forFileType(virtualFile.fileType) diff --git a/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java b/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java index e4bd0138676a..62dcb7bf559c 100644 --- a/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java +++ b/platform/core-impl/src/com/intellij/openapi/fileEditor/impl/LoadTextUtil.java @@ -408,16 +408,17 @@ public final class LoadTextUtil { write(project, file, requestor, newText, -1); } - /** - * Normally, one should not use this method. - */ + @ApiStatus.Internal public static void write(@Nullable Project project, @NotNull VirtualFile virtualFile, @NotNull Object requestor, @NotNull String text, - long newModificationStamp) throws IOException { + long newModificationStamp, + boolean applyTextTransformer) throws IOException { Charset existing = virtualFile.getCharset(); - text = TextPresentationTransformers.toPersistent(text, virtualFile).toString(); + if (applyTextTransformer) { + text = TextPresentationTransformers.toPersistent(text, virtualFile).toString(); + } Pair.NonNull chosen = charsetForWriting(project, virtualFile, text, existing); Charset charset = chosen.first; byte[] buffer = chosen.second; @@ -433,6 +434,17 @@ public final class LoadTextUtil { } } + /** + * Normally, one should not use this method. + */ + public static void write(@Nullable Project project, + @NotNull VirtualFile virtualFile, + @NotNull Object requestor, + @NotNull String text, + long newModificationStamp) throws IOException { + write(project, virtualFile, requestor, text, newModificationStamp, true); + } + public static @NotNull Pair.NonNull charsetForWriting(@Nullable Project project, @NotNull VirtualFile virtualFile, @NotNull String text, diff --git a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java index 9b8ae480d897..999bc6e2a00e 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java @@ -32,6 +32,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.EncodingManager; import com.intellij.openapi.vfs.encoding.EncodingProjectManager; +import com.intellij.openapi.vfs.transformer.TextPresentationTransformers; import com.intellij.psi.PsiDocumentManager; import com.intellij.testFramework.BinaryLightVirtualFile; import com.intellij.testFramework.LightVirtualFile; @@ -394,7 +395,10 @@ public final class DiffContentFactoryImpl extends DiffContentFactoryEx { if (project == null || project.isDefault()) return null; if (fileType != null && fileType.isBinary()) return null; - LightVirtualFile file = new MyLightVirtualFile(lightFilePath, fileType, content); + // Here we need a dummy originalFile to pass it to [TextPresentationTransformers.fromPersistent] + LightVirtualFile originalFile = new MyLightVirtualFile(lightFilePath, fileType, content); + String convertedText = TextPresentationTransformers.fromPersistent(content, originalFile).toString(); + LightVirtualFile file = new MyLightVirtualFile(lightFilePath, fileType, convertedText); file.setWritable(!readOnly); return ReadAction.compute(() -> { @@ -402,6 +406,7 @@ public final class DiffContentFactoryImpl extends DiffContentFactoryEx { if (document == null) { return null; } + PsiDocumentManager.getInstance(project).getPsiFile(document); return document; }); @@ -884,8 +889,10 @@ public final class DiffContentFactoryImpl extends DiffContentFactoryEx { @Override public @Nullable FileType guessContentType() { VirtualFile file = myFilePath.getVirtualFile(); - if (file != null) return file.getFileType(); - return null; + if (file == null) { + return FileTypeManager.getInstance().getFileTypeByFileName(myFilePath.getName()); + } + return file.getFileType(); } } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CurrentContentRevision.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CurrentContentRevision.java index 8f8c2b90027f..aa50a51f093f 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CurrentContentRevision.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CurrentContentRevision.java @@ -2,12 +2,14 @@ package com.intellij.openapi.vcs.changes; import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.transformer.TextPresentationTransformers; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,7 +30,15 @@ public class CurrentContentRevision implements ByteBackedContentRevision { return null; } Document doc = ReadAction.compute(() -> FileDocumentManager.getInstance().getDocument(vFile)); - return doc == null ? null : doc.getText(); + if (doc == null) { + return null; + } + else { + // In some cases like Jupyter Notebooks we need to make TextPresentationTransformers.toPersistent to have a correct text representation + // In the case of Jupyter Notebooks it is a JSON representation of the notebook instead of representation with #%% cells separators + String docText = doc.getText(); + return ReadAction.compute(() -> TextPresentationTransformers.toPersistent(docText, vFile).toString()); + } } @Override diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java index 3b4279443be5..1d1b6a6d98c5 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/patch/apply/ApplyTextFilePatch.java @@ -7,11 +7,13 @@ import com.intellij.openapi.diff.impl.patch.CharsetEP; import com.intellij.openapi.diff.impl.patch.TextFilePatch; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.changes.CommitContext; import com.intellij.openapi.vcs.changes.patch.ApplyPatchForBaseRevisionTexts; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.transformer.TextPresentationTransformers; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,15 +38,17 @@ public final class ApplyTextFilePatch extends ApplyFilePatchBase throw new IOException("Failed to set contents for updated file " + fileToPatch.getPath()); } - GenericPatchApplier.AppliedPatch appliedPatch = GenericPatchApplier.apply(document.getText(), myPatch.getHunks()); + String documentText = document.getText(); + String fileTextPersistent = TextPresentationTransformers.toPersistent(documentText, fileToPatch).toString(); + GenericPatchApplier.AppliedPatch appliedPatch = GenericPatchApplier.apply(fileTextPersistent, myPatch.getHunks()); + if (appliedPatch != null) { if (appliedPatch.status == ApplyPatchStatus.ALREADY_APPLIED) { return new Result(appliedPatch.status); } if (appliedPatch.status == ApplyPatchStatus.SUCCESS) { - VcsFacade.getInstance().runHeavyModificationTask(project, document, () -> document.setText(appliedPatch.patchedText)); - FileDocumentManager.getInstance().saveDocument(document); + updateDocumentContent(project, document, fileToPatch, appliedPatch.patchedText); return new Result(appliedPatch.status); } } @@ -77,7 +81,23 @@ public final class ApplyTextFilePatch extends ApplyFilePatchBase catch (IllegalArgumentException ignore) { } } - document.setText(myPatch.getSingleHunkPatchText()); - FileDocumentManager.getInstance().saveDocument(document); + + String patchText = myPatch.getSingleHunkPatchText(); + updateDocumentContent(project, document, newFile, patchText); + } + + private void updateDocumentContent(@NotNull Project project, + @NotNull Document document, + @NotNull VirtualFile file, + @NotNull String patchedText) { + VcsFacade.getInstance().runHeavyModificationTask(project, document, () -> { + try { + LoadTextUtil.write(project, file, this, patchedText, -1, false); + } + catch (IOException e) { + throw new RuntimeException(e); + } + FileDocumentManager.getInstance().reloadFromDisk(document); + }); } }