From c932c993e5cd945986e84a3be3a1d38f4271dbe2 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 21 Mar 2016 17:55:09 +0300 Subject: [PATCH] IDEA-126952 Scratches should have an extension --- .../intellij/ide/scratch/LRUPopupBuilder.java | 75 +++++++++++++++++-- .../ide/scratch/ScratchFileActions.java | 6 +- .../com/intellij/ide/scratch/ScratchUtil.java | 41 ++++++++++ 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/LRUPopupBuilder.java b/platform/lang-impl/src/com/intellij/ide/scratch/LRUPopupBuilder.java index 77601cc4b877..6a15f3c5b5b8 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/LRUPopupBuilder.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/LRUPopupBuilder.java @@ -19,12 +19,19 @@ import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.Language; import com.intellij.lang.LanguageUtil; import com.intellij.lang.PerFileMappings; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.command.undo.BasicUndoableAction; +import com.intellij.openapi.command.undo.UndoManager; +import com.intellij.openapi.command.undo.UnexpectedUndoException; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.*; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; @@ -38,10 +45,9 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; -import java.util.Collections; -import java.util.Comparator; +import java.io.IOException; +import java.util.*; import java.util.List; -import java.util.Map; /** * @author gregsh @@ -62,13 +68,23 @@ public abstract class LRUPopupBuilder { private JBIterable myExtraItems = JBIterable.empty(); @NotNull - public static ListPopup forFileLanguages(@NotNull Project project, @NotNull final Iterable files, @NotNull final PerFileMappings mappings) { + public static ListPopup forFileLanguages(@NotNull Project project, @NotNull Iterable files, @NotNull final PerFileMappings mappings) { + final VirtualFile[] filesCopy = VfsUtilCore.toVirtualFileArray(JBIterable.from(files).toList()); + Arrays.sort(filesCopy, new Comparator() { + @Override + public int compare(VirtualFile o1, VirtualFile o2) { + return StringUtil.compare(o1.getName(), o2.getName(), !o1.getFileSystem().isCaseSensitive()); + } + }); return forFileLanguages(project, null, new Consumer() { @Override - public void consume(Language t) { - for (VirtualFile file : files) { - mappings.setMapping(file, t); - } + public void consume(final Language t) { + new WriteCommandAction(project, "Change Language") { + @Override + protected void run(@NotNull Result result) throws Throwable { + changeLanguageWithUndo(project, t, filesCopy, mappings); + } + }.execute(); } }); } @@ -260,4 +276,47 @@ public abstract class LRUPopupBuilder { return getClass().getName() + "/" + myTitle; } + + private static void changeLanguageWithUndo(@NotNull Project project, + @NotNull final Language t, + @NotNull final VirtualFile[] sortedFiles, + @NotNull final PerFileMappings mappings) throws UnexpectedUndoException { + ReadonlyStatusHandler.OperationStatus status = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(sortedFiles); + if (status.hasReadonlyFiles()) return; + + final Set matchedExtensions = ContainerUtil.newLinkedHashSet(); + final Map oldMapping = ContainerUtil.newHashMap(); + for (VirtualFile file : sortedFiles) { + oldMapping.put(file, mappings.getMapping(file)); + if (ScratchUtil.hasMatchingExtension(project, file)) { + matchedExtensions.add(file); + } + } + + BasicUndoableAction action = new BasicUndoableAction(sortedFiles) { + @Override + public void undo() throws UnexpectedUndoException { + for (VirtualFile file : sortedFiles) { + mappings.setMapping(file, oldMapping.get(file)); + } + } + + @Override + public void redo() throws UnexpectedUndoException { + for (VirtualFile file : sortedFiles) { + mappings.setMapping(file, t); + } + } + }; + action.redo(); + UndoManager.getInstance(project).undoableActionPerformed(action); + + for (VirtualFile file : matchedExtensions) { + try { + ScratchUtil.updateFileExtension(project, file); + } + catch (IOException ignored) { + } + } + } } diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java index 80aa2bb61178..3e5edb681115 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileActions.java @@ -26,6 +26,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.editor.Caret; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; @@ -37,6 +38,7 @@ import com.intellij.psi.PsiFile; import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.ObjectUtils; +import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.JBIterable; import org.jetbrains.annotations.NotNull; @@ -111,9 +113,11 @@ public class ScratchFileActions { static void doCreateNewScratch(@NotNull Project project, boolean buffer, @NotNull Language language, @NotNull String text) { FeatureUsageTracker.getInstance().triggerFeatureUsed("scratch"); + LanguageFileType fileType = language.getAssociatedFileType(); + String ext = buffer || fileType == null? "" : fileType.getDefaultExtension(); String fileName = buffer ? "buffer" + nextBufferIndex() : "scratch"; ScratchFileService.Option option = buffer ? ScratchFileService.Option.create_if_missing : ScratchFileService.Option.create_new_always; - VirtualFile f = ScratchRootType.getInstance().createScratchFile(project, fileName, language, text, option); + VirtualFile f = ScratchRootType.getInstance().createScratchFile(project, PathUtil.makeFileName(fileName, ext), language, text, option); if (f != null) { FileEditorManager.getInstance(project).openFile(f, true); } diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchUtil.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchUtil.java index 124e220ecb9c..5e60577fb799 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchUtil.java @@ -15,9 +15,21 @@ */ package com.intellij.ide.scratch; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; + /** * @author gregsh */ @@ -33,4 +45,33 @@ public class ScratchUtil { public static boolean isScratch(@Nullable VirtualFile file) { return file != null && file.getFileType() == ScratchFileType.INSTANCE; } + + public static void updateFileExtension(@NotNull Project project, @Nullable VirtualFile file) throws IOException { + ApplicationManager.getApplication().assertWriteAccessAllowed(); + if (CommandProcessor.getInstance().getCurrentCommand() == null) { + throw new AssertionError("command required"); + } + + if (file == null) return; + Language language = LanguageUtil.getLanguageForPsi(project, file); + String extension = file.getExtension(); + FileType expected = extension == null ? null : FileTypeManager.getInstance().getFileTypeByExtension(extension); + FileType actual = language == null ? null : language.getAssociatedFileType(); + if (expected == actual || actual == null) return; + String ext = actual.getDefaultExtension(); + if (StringUtil.isEmpty(ext)) return; + + String newName = PathUtil.makeFileName(file.getNameWithoutExtension(), ext); + VirtualFile parent = file.getParent(); + newName = parent != null && parent.findChild(newName) != null ? PathUtil.makeFileName(file.getName(), ext) : newName; + file.rename(ScratchUtil.class, newName); + } + + public static boolean hasMatchingExtension(@NotNull Project project, @NotNull VirtualFile file) { + String extension = file.getExtension(); + Language language = LanguageUtil.getLanguageForPsi(project, file); + FileType expected = extension == null ? null : FileTypeManager.getInstance().getFileTypeByExtension(extension); + FileType actual = language == null ? null : language.getAssociatedFileType(); + return expected == actual && actual != null; + } }