/* * The MIT License (MIT) * * Copyright (c) 2017 hsz Jakub Chrzanowski <jakub@hsz.mobi> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package mobi.hsz.idea.gitignore.command; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.VisualPosition; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import mobi.hsz.idea.gitignore.IgnoreBundle; import mobi.hsz.idea.gitignore.lang.IgnoreLanguage; import mobi.hsz.idea.gitignore.psi.IgnoreEntry; import mobi.hsz.idea.gitignore.psi.IgnoreVisitor; import mobi.hsz.idea.gitignore.settings.IgnoreSettings; import mobi.hsz.idea.gitignore.util.Constants; import mobi.hsz.idea.gitignore.util.Utils; import org.jetbrains.annotations.NotNull; import java.util.*; /** * Command action that appends specified file to rules list. * * @author Jakub Chrzanowski <jakub@hsz.mobi> * @since 0.4 */ public class AppendFileCommandAction extends WriteCommandAction<PsiFile> { /** Current project. */ private final Project project; /** Current file. */ private final PsiFile file; /** Rules set to add. */ private final Set<String> content; /** {@link PsiDocumentManager} instance. */ private final PsiDocumentManager manager; /** Ignore duplicated entries. */ private final boolean ignoreDuplicates; /** Ignore comments and empty lines. */ private final boolean ignoreComments; /** Defines if new content should be inserted at the cursor's position or at the document end. */ private final boolean insertAtCursor; /** * Builds a new instance of {@link AppendFileCommandAction}. * Takes a {@link Set} of the rules to add. * * @param project current project * @param file working file * @param content rule * @param ignoreDuplicates ignore duplicated entries * @param ignoreComments ignore comments and empty lines */ public AppendFileCommandAction(@NotNull Project project, @NotNull PsiFile file, @NotNull Set<String> content, boolean ignoreDuplicates, boolean ignoreComments) { super(project, file); this.project = project; this.file = file; this.content = content; this.manager = PsiDocumentManager.getInstance(project); this.ignoreDuplicates = ignoreDuplicates; this.ignoreComments = ignoreComments; this.insertAtCursor = IgnoreSettings.getInstance().isInsertAtCursor(); } /** * Builds a new instance of {@link AppendFileCommandAction}. * Takes a {@link String} rule. * * @param project current project * @param file working file * @param content rule * @param ignoreDuplicates ignore duplicated entries * @param ignoreComments ignore comments and empty lines */ public AppendFileCommandAction(@NotNull Project project, @NotNull PsiFile file, @NotNull final String content, boolean ignoreDuplicates, boolean ignoreComments) { this(project, file, ContainerUtil.newHashSet(content), ignoreDuplicates, ignoreComments); } /** * Adds {@link #content} to the given {@link #file}. Checks if file contains content and sends a notification. * * @param result ignored parameter * @throws Throwable */ @Override protected void run(@NotNull Result<PsiFile> result) throws Throwable { if (content.isEmpty()) { return; } final Document document = manager.getDocument(file); if (document == null) { return; } file.acceptChildren(new IgnoreVisitor() { @Override public void visitEntry(@NotNull IgnoreEntry entry) { final VirtualFile baseDir = project.getBaseDir(); if (content.contains(entry.getText()) && baseDir != null) { Notifications.Bus.notify(new Notification(IgnoreLanguage.GROUP, IgnoreBundle.message("action.appendFile.entryExists", entry.getText()), IgnoreBundle.message( "action.appendFile.entryExists.in", Utils.getRelativePath(baseDir, file.getVirtualFile()) ), NotificationType.WARNING), project); content.remove(entry.getText()); } } }); int offset = document.getTextLength(); if (insertAtCursor) { Editor[] editors = EditorFactory.getInstance().getEditors(document); if (editors.length > 0) { VisualPosition position = editors[0].getSelectionModel().getSelectionStartPosition(); if (position != null) { offset = document.getLineStartOffset(position.line); } } } for (String entry : content) { if (ignoreDuplicates) { List<String> currentLines = ContainerUtil.filter(document.getText().split(Constants.NEWLINE), new Condition<String>() { @Override public boolean value(String s) { return !s.isEmpty() && !s.startsWith(Constants.HASH); } }); List<String> entryLines = new ArrayList<String>(Arrays.asList(entry.split(Constants.NEWLINE))); Iterator<String> iterator = entryLines.iterator(); while (iterator.hasNext()) { String line = iterator.next().trim(); if (line.isEmpty() || line.startsWith(Constants.HASH)) { continue; } if (currentLines.contains(line)) { iterator.remove(); } else { currentLines.add(line); } } entry = StringUtil.join(entryLines, Constants.NEWLINE); } if (ignoreComments) { List<String> entryLines = new ArrayList<String>(Arrays.asList(entry.split(Constants.NEWLINE))); Iterator<String> iterator = entryLines.iterator(); while (iterator.hasNext()) { String line = iterator.next().trim(); if (line.isEmpty() || line.startsWith(Constants.HASH)) { iterator.remove(); } } entry = StringUtil.join(entryLines, Constants.NEWLINE); } entry = StringUtil.replace(entry, "\r", ""); if (!StringUtil.isEmpty(entry)) { entry += Constants.NEWLINE; } if (!insertAtCursor && !document.getText().endsWith(Constants.NEWLINE) && !StringUtil.isEmpty(entry)) { entry = Constants.NEWLINE + entry; } document.insertString(offset, entry); offset += entry.length(); } manager.commitDocument(document); } }