blob: 165ff5776756fa02cff5e5f499ead43234198212 [file] [log] [blame]
/*
* 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();
}
}