summaryrefslogtreecommitdiff
path: root/platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java')
-rw-r--r--platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java216
1 files changed, 216 insertions, 0 deletions
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java
new file mode 100644
index 000000000000..165ff5776756
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/TrailingSpacesStripper.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.editor.impl;
+
+import com.intellij.ide.DataManager;
+import com.intellij.injected.editor.DocumentWindow;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.ShutDownTracker;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.text.CharArrayUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public final class TrailingSpacesStripper extends FileDocumentManagerAdapter {
+ public static final Key<String> OVERRIDE_STRIP_TRAILING_SPACES_KEY = Key.create("OVERRIDE_TRIM_TRAILING_SPACES_KEY");
+ public static final Key<Boolean> OVERRIDE_ENSURE_NEWLINE_KEY = Key.create("OVERRIDE_ENSURE_NEWLINE_KEY");
+
+ private final Set<Document> myDocumentsToStripLater = new THashSet<Document>();
+
+ @Override
+ public void beforeAllDocumentsSaving() {
+ Set<Document> documentsToStrip = new THashSet<Document>(myDocumentsToStripLater);
+ myDocumentsToStripLater.clear();
+ for (Document document : documentsToStrip) {
+ strip(document);
+ }
+ }
+
+ @Override
+ public void beforeDocumentSaving(@NotNull Document document) {
+ strip(document);
+ }
+
+ private void strip(@NotNull final Document document) {
+ if (!document.isWritable()) return;
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ VirtualFile file = fileDocumentManager.getFile(document);
+ if (file == null || !file.isValid()) return;
+
+ final EditorSettingsExternalizable settings = EditorSettingsExternalizable.getInstance();
+ if (settings == null) return;
+
+ final String overrideStripTrailingSpacesData = file.getUserData(OVERRIDE_STRIP_TRAILING_SPACES_KEY);
+ final Boolean overrideEnsureNewlineData = file.getUserData(OVERRIDE_ENSURE_NEWLINE_KEY);
+ final String stripTrailingSpaces = overrideStripTrailingSpacesData != null ? overrideStripTrailingSpacesData : settings.getStripTrailingSpaces();
+ final boolean doStrip = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE);
+ final boolean ensureEOL = overrideEnsureNewlineData != null ? overrideEnsureNewlineData.booleanValue() : settings.isEnsureNewLineAtEOF();
+
+ if (doStrip) {
+ final boolean inChangedLinesOnly = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_WHOLE);
+ boolean success = stripIfNotCurrentLine(document, inChangedLinesOnly);
+ if (!success) {
+ myDocumentsToStripLater.add(document);
+ }
+ }
+
+ final int lines = document.getLineCount();
+ if (ensureEOL && lines > 0) {
+ final int start = document.getLineStartOffset(lines - 1);
+ final int end = document.getLineEndOffset(lines - 1);
+ if (start != end) {
+ final CharSequence content = document.getCharsSequence();
+ ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(document, null) {
+ @Override
+ public void run() {
+ CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
+ @Override
+ public void run() {
+ if (CharArrayUtil.containsOnlyWhiteSpaces(content.subSequence(start, end)) && doStrip) {
+ document.deleteString(start, end);
+ }
+ else {
+ document.insertString(end, "\n");
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+ }
+
+ // clears line modification flags except lines which was not stripped because the caret was in the way
+ public void clearLineModificationFlags(@NotNull Document document) {
+ if (document instanceof DocumentWindow) {
+ document = ((DocumentWindow)document).getDelegate();
+ }
+ if (!(document instanceof DocumentImpl)) {
+ return;
+ }
+
+ Component focusOwner = IdeFocusManager.getGlobalInstance().getFocusOwner();
+ DataContext dataContext = DataManager.getInstance().getDataContext(focusOwner);
+ boolean isDisposeInProgress = ApplicationManager.getApplication().isDisposeInProgress(); // ignore caret placing when exiting
+ Editor activeEditor = isDisposeInProgress ? null : CommonDataKeys.EDITOR.getData(dataContext);
+
+ // when virtual space enabled, we can strip whitespace anywhere
+ boolean isVirtualSpaceEnabled = activeEditor == null || activeEditor.getSettings().isVirtualSpace();
+
+ final EditorSettingsExternalizable settings = EditorSettingsExternalizable.getInstance();
+ if (settings == null) return;
+
+ String stripTrailingSpaces = settings.getStripTrailingSpaces();
+ final boolean doStrip = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE);
+ final boolean inChangedLinesOnly = !stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_WHOLE);
+
+ int[] caretLines;
+ if (activeEditor != null && inChangedLinesOnly && doStrip && !isVirtualSpaceEnabled) {
+ List<Caret> carets = activeEditor.getCaretModel().getAllCarets();
+ caretLines = new int[carets.size()];
+ for (int i = 0; i < carets.size(); i++) {
+ Caret caret = carets.get(i);
+ caretLines[i] = caret.getLogicalPosition().line;
+ }
+ }
+ else {
+ caretLines = ArrayUtil.EMPTY_INT_ARRAY;
+ }
+ ((DocumentImpl)document).clearLineModificationFlagsExcept(caretLines);
+ }
+
+ public static boolean stripIfNotCurrentLine(@NotNull Document document, boolean inChangedLinesOnly) {
+ if (document instanceof DocumentWindow) {
+ document = ((DocumentWindow)document).getDelegate();
+ }
+ if (!(document instanceof DocumentImpl)) {
+ return true;
+ }
+ DataContext dataContext = DataManager.getInstance().getDataContext(IdeFocusManager.getGlobalInstance().getFocusOwner());
+ boolean isDisposeInProgress = ApplicationManager.getApplication().isDisposeInProgress(); // ignore caret placing when exiting
+ Editor activeEditor = isDisposeInProgress ? null : CommonDataKeys.EDITOR.getData(dataContext);
+
+ // when virtual space enabled, we can strip whitespace anywhere
+ boolean isVirtualSpaceEnabled = activeEditor == null || activeEditor.getSettings().isVirtualSpace();
+
+ boolean markAsNeedsStrippingLater;
+
+ if (activeEditor != null && activeEditor.getCaretModel().supportsMultipleCarets()) {
+ List<Caret> carets = activeEditor.getCaretModel().getAllCarets();
+ List<VisualPosition> visualCarets = new ArrayList<VisualPosition>(carets.size());
+ int[] caretOffsets = new int[carets.size()];
+ for (int i = 0; i < carets.size(); i++) {
+ Caret caret = carets.get(i);
+ visualCarets.add(caret.getVisualPosition());
+ caretOffsets[i] = caret.getOffset();
+ }
+
+ markAsNeedsStrippingLater = ((DocumentImpl)document).stripTrailingSpaces(activeEditor.getProject(), inChangedLinesOnly, isVirtualSpaceEnabled, caretOffsets);
+
+ if (!ShutDownTracker.isShutdownHookRunning()) {
+ final Iterator<VisualPosition> visualCaretIterator = visualCarets.iterator();
+ activeEditor.getCaretModel().runForEachCaret(new CaretAction() {
+ @Override
+ public void perform(Caret caret) {
+ if (visualCaretIterator.hasNext()) {
+ caret.moveToVisualPosition(visualCaretIterator.next());
+ }
+ }
+ });
+ }
+ }
+ else {
+ VisualPosition visualCaret = activeEditor == null ? null : activeEditor.getCaretModel().getVisualPosition();
+ int caretLine = activeEditor == null ? -1 : activeEditor.getCaretModel().getLogicalPosition().line;
+ int caretOffset = activeEditor == null ? -1 : activeEditor.getCaretModel().getOffset();
+
+ final Project project = activeEditor == null ? null : activeEditor.getProject();
+ markAsNeedsStrippingLater = ((DocumentImpl)document).stripTrailingSpaces(project, inChangedLinesOnly, isVirtualSpaceEnabled,
+ caretLine, caretOffset);
+
+ if (!ShutDownTracker.isShutdownHookRunning() && activeEditor != null) {
+ activeEditor.getCaretModel().moveToVisualPosition(visualCaret);
+ }
+ }
+ return !markAsNeedsStrippingLater;
+ }
+
+ public void documentDeleted(@NotNull Document doc) {
+ myDocumentsToStripLater.remove(doc);
+ }
+
+ @Override
+ public void unsavedDocumentsDropped() {
+ myDocumentsToStripLater.clear();
+ }
+}