blob: 6ba31d7edb47a28753c91f99efbb41f51506e9a6 [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;
import com.intellij.codeStyle.CodeStyleFacade;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.MockDocumentEvent;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.util.Producer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.List;
public class EditorModificationUtil {
private EditorModificationUtil() { }
public static void deleteSelectedText(Editor editor) {
deleteSelectedTextNoScrolling(editor);
if (editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret()) {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
private static void deleteSelectedTextNoScrolling(Editor editor) {
SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) deleteBlockSelection(editor);
if(!selectionModel.hasSelection()) return;
int selectionStart = selectionModel.getSelectionStart();
int selectionEnd = selectionModel.getSelectionEnd();
VisualPosition selectionStartPosition = selectionModel.getSelectionStartPosition();
if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets() && selectionStartPosition != null) {
editor.getCaretModel().moveToVisualPosition(selectionStartPosition);
}
else {
editor.getCaretModel().moveToOffset(selectionStart);
}
selectionModel.removeSelection();
editor.getDocument().deleteString(selectionStart, selectionEnd);
}
public static void deleteSelectedTextForAllCarets(@NotNull final Editor editor) {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
deleteSelectedTextNoScrolling(editor);
}
});
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
public static void deleteBlockSelection(Editor editor) {
SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasBlockSelection()) return;
LogicalPosition blockStart = selectionModel.getBlockStart();
LogicalPosition blockEnd = selectionModel.getBlockEnd();
if (blockStart == null || blockEnd == null) {
return;
}
int startLine = blockStart.line;
int endLine = blockEnd.line;
int[] starts = selectionModel.getBlockSelectionStarts();
int[] ends = selectionModel.getBlockSelectionEnds();
for (int i = starts.length - 1; i >= 0; i--) {
editor.getDocument().deleteString(starts[i], ends[i]);
}
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
zeroWidthBlockSelectionAtCaretColumn(editor, startLine, endLine);
}
public static void zeroWidthBlockSelectionAtCaretColumn(final Editor editor, final int startLine, final int endLine) {
int caretColumn = editor.getCaretModel().getLogicalPosition().column;
editor.getSelectionModel().setBlockSelection(new LogicalPosition(startLine, caretColumn), new LogicalPosition(endLine, caretColumn));
}
public static void insertStringAtCaret(Editor editor, @NotNull String s) {
insertStringAtCaret(editor, s, false, true);
}
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode) {
return insertStringAtCaret(editor, s, toProcessOverwriteMode, s.length());
}
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret) {
return insertStringAtCaret(editor, s, toProcessOverwriteMode, toMoveCaret, s.length());
}
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, int caretShift) {
return insertStringAtCaret(editor, s, toProcessOverwriteMode, true, caretShift);
}
public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret, int caretShift) {
int result = insertStringAtCaretNoScrolling(editor, s, toProcessOverwriteMode, toMoveCaret, caretShift);
if (toMoveCaret && editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret()) {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
return result;
}
private static int insertStringAtCaretNoScrolling(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret, int caretShift) {
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasSelection()) {
VisualPosition startPosition = selectionModel.getSelectionStartPosition();
if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets() && startPosition != null) {
editor.getCaretModel().moveToVisualPosition(startPosition);
}
else {
editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
}
}
// There is a possible case that particular soft wraps become hard wraps if the caret is located at soft wrap-introduced virtual
// space, hence, we need to give editor a chance to react accordingly.
editor.getSoftWrapModel().beforeDocumentChangeAtCaret();
int oldOffset = editor.getCaretModel().getOffset();
String filler = calcStringToFillVirtualSpace(editor);
if (filler.length() > 0) {
s = filler + s;
}
Document document = editor.getDocument();
if (editor.isInsertMode() || !toProcessOverwriteMode) {
if (selectionModel.hasSelection()) {
oldOffset = selectionModel.getSelectionStart();
document.replaceString(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), s);
} else {
document.insertString(oldOffset, s);
}
} else {
deleteSelectedText(editor);
int lineNumber = editor.getCaretModel().getLogicalPosition().line;
if (lineNumber >= document.getLineCount()){
return insertStringAtCaretNoScrolling(editor, s, false, toMoveCaret, s.length());
}
int endOffset = document.getLineEndOffset(lineNumber);
document.replaceString(oldOffset, Math.min(endOffset, oldOffset + s.length()), s);
}
int offset = oldOffset + filler.length() + caretShift;
if (toMoveCaret){
editor.getCaretModel().moveToOffset(offset, true);
selectionModel.removeSelection();
}
else if (editor.getCaretModel().getOffset() != oldOffset) { // handling the case when caret model tracks document changes
editor.getCaretModel().moveToOffset(oldOffset);
}
return offset;
}
/**
* @deprecated Use {@link com.intellij.openapi.editor.EditorCopyPasteHelper} methods instead.
* (to remove in IDEA 15)
*/
@Nullable
public static TextRange pasteTransferable(final Editor editor, @Nullable Producer<Transferable> producer) {
EditorCopyPasteHelper helper = EditorCopyPasteHelper.getInstance();
if (producer == null) {
TextRange[] ranges = helper.pasteFromClipboard(editor);
return ranges != null && ranges.length == 1 ? ranges[0] : null;
}
Transferable transferable = producer.produce();
if (transferable == null) {
return null;
}
TextRange[] ranges = helper.pasteTransferable(editor, transferable);
return ranges != null && ranges.length == 1 ? ranges[0] : null;
}
public static void pasteTransferableAsBlock(Editor editor, @Nullable Producer<Transferable> producer) {
Transferable content = getTransferable(producer);
if (content == null) return;
String text = getStringContent(content);
if (text == null) return;
int caretLine = editor.getCaretModel().getLogicalPosition().line;
int originalCaretLine = caretLine;
int selectedLinesCount = 0;
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) {
final LogicalPosition start = selectionModel.getBlockStart();
final LogicalPosition end = selectionModel.getBlockEnd();
assert start != null;
assert end != null;
LogicalPosition caret = new LogicalPosition(Math.min(start.line, end.line), Math.min(start.column, end.column));
selectedLinesCount = Math.abs(end.line - start.line);
caretLine = caret.line;
deleteSelectedText(editor);
editor.getCaretModel().moveToLogicalPosition(caret);
}
LogicalPosition caretToRestore = editor.getCaretModel().getLogicalPosition();
String[] lines = LineTokenizer.tokenize(text.toCharArray(), false);
if (lines.length > 1 || selectedLinesCount == 0) {
int longestLineLength = 0;
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
longestLineLength = Math.max(longestLineLength, line.length());
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
insertStringAtCaret(editor, line, false, true);
}
caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + longestLineLength);
}
else {
for (int i = 0; i <= selectedLinesCount; i++) {
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
insertStringAtCaret(editor, text, false, true);
}
caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + text.length());
}
editor.getCaretModel().moveToLogicalPosition(caretToRestore);
zeroWidthBlockSelectionAtCaretColumn(editor, caretLine, caretLine + selectedLinesCount);
}
@Nullable
private static String getStringContent(@NotNull Transferable content) {
RawText raw = RawText.fromTransferable(content);
if (raw != null) return raw.rawText;
try {
return (String)content.getTransferData(DataFlavor.stringFlavor);
}
catch (UnsupportedFlavorException ignore) { }
catch (IOException ignore) { }
return null;
}
private static Transferable getTransferable(Producer<Transferable> producer) {
Transferable content = null;
if (producer != null) {
content = producer.produce();
}
else {
CopyPasteManager manager = CopyPasteManager.getInstance();
if (manager.areDataFlavorsAvailable(DataFlavor.stringFlavor)) {
content = manager.getContents();
}
}
return content;
}
/**
* Calculates difference in columns between current editor caret position and end of the logical line fragment displayed
* on a current visual line.
*
* @param editor target editor
* @return difference in columns between current editor caret position and end of the logical line fragment displayed
* on a current visual line
*/
public static int calcAfterLineEnd(Editor editor) {
Document document = editor.getDocument();
CaretModel caretModel = editor.getCaretModel();
LogicalPosition logicalPosition = caretModel.getLogicalPosition();
int lineNumber = logicalPosition.line;
int columnNumber = logicalPosition.column;
if (lineNumber >= document.getLineCount()) {
return columnNumber;
}
int caretOffset = caretModel.getOffset();
int anchorLineEndOffset = document.getLineEndOffset(lineNumber);
List<? extends SoftWrap> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
for (SoftWrap softWrap : softWraps) {
if (!editor.getSoftWrapModel().isVisible(softWrap)) {
continue;
}
int softWrapOffset = softWrap.getStart();
if (softWrapOffset == caretOffset) {
// There are two possible situations:
// *) caret is located on a visual line before soft wrap-introduced line feed;
// *) caret is located on a visual line after soft wrap-introduced line feed;
VisualPosition position = editor.offsetToVisualPosition(caretOffset - 1);
VisualPosition visualCaret = caretModel.getVisualPosition();
if (position.line == visualCaret.line) {
return visualCaret.column - position.column - 1;
}
}
if (softWrapOffset > caretOffset) {
anchorLineEndOffset = softWrapOffset;
break;
}
// Same offset corresponds to all soft wrap-introduced symbols, however, current method should behave differently in
// situations when the caret is located just before the soft wrap and at the next visual line.
if (softWrapOffset == caretOffset) {
boolean visuallyBeforeSoftWrap = caretModel.getVisualPosition().line < editor.offsetToVisualPosition(caretOffset).line;
if (visuallyBeforeSoftWrap) {
anchorLineEndOffset = softWrapOffset;
break;
}
}
}
int lineEndColumnNumber = editor.offsetToLogicalPosition(anchorLineEndOffset).column;
return columnNumber - lineEndColumnNumber;
}
public static String calcStringToFillVirtualSpace(Editor editor) {
int afterLineEnd = calcAfterLineEnd(editor);
if (afterLineEnd > 0) {
return calcStringToFillVirtualSpace(editor, afterLineEnd);
}
return "";
}
public static String calcStringToFillVirtualSpace(Editor editor, int afterLineEnd) {
final Project project = editor.getProject();
StringBuilder buf = new StringBuilder();
final Document doc = editor.getDocument();
final int caretOffset = editor.getCaretModel().getOffset();
boolean atLineStart = caretOffset >= doc.getTextLength() || doc.getLineStartOffset(doc.getLineNumber(caretOffset)) == caretOffset;
if (atLineStart && project != null) {
int offset = editor.getCaretModel().getOffset();
PsiDocumentManager.getInstance(project).commitDocument(doc); // Sync document and PSI before formatting.
String properIndent = offset >= doc.getTextLength() ? "" : CodeStyleFacade.getInstance(project).getLineIndent(doc, offset);
if (properIndent != null) {
int tabSize = editor.getSettings().getTabSize(project);
for (int i = 0; i < properIndent.length(); i++) {
if (properIndent.charAt(i) == ' ') {
afterLineEnd--;
}
else if (properIndent.charAt(i) == '\t') {
if (afterLineEnd < tabSize) {
break;
}
afterLineEnd -= tabSize;
}
buf.append(properIndent.charAt(i));
if (afterLineEnd == 0) break;
}
}
}
for (int i = 0; i < afterLineEnd; i++) {
buf.append(' ');
}
return buf.toString();
}
public static void typeInStringAtCaretHonorBlockSelection(final Editor editor, final String str, final boolean toProcessOverwriteMode)
throws ReadOnlyFragmentModificationException
{
Document doc = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) {
RangeMarker guard = selectionModel.getBlockSelectionGuard();
if (guard != null) {
DocumentEvent evt = new MockDocumentEvent(doc, editor.getCaretModel().getOffset());
ReadOnlyFragmentModificationException e = new ReadOnlyFragmentModificationException(evt, guard);
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
}
else {
final LogicalPosition start = selectionModel.getBlockStart();
final LogicalPosition end = selectionModel.getBlockEnd();
assert start != null;
assert end != null;
int column = Math.min(start.column, end.column);
int startLine = Math.min(start.line, end.line);
int endLine = Math.max(start.line, end.line);
deleteBlockSelection(editor);
for (int i = startLine; i <= endLine; i++) {
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(i, column));
insertStringAtCaret(editor, str, toProcessOverwriteMode, true);
}
selectionModel.setBlockSelection(new LogicalPosition(startLine, column + str.length()),
new LogicalPosition(endLine, column + str.length()));
}
}
else {
insertStringAtCaret(editor, str, toProcessOverwriteMode, true);
}
}
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str) {
typeInStringAtCaretHonorMultipleCarets(editor, str, true, str.length());
}
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str, final int caretShift) {
typeInStringAtCaretHonorMultipleCarets(editor, str, true, caretShift);
}
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str, final boolean toProcessOverwriteMode) {
typeInStringAtCaretHonorMultipleCarets(editor, str, toProcessOverwriteMode, str.length());
}
/**
* Inserts given string at each caret's position. Effective caret shift will be equal to <code>caretShift</code> for each caret.
*/
public static void typeInStringAtCaretHonorMultipleCarets(final Editor editor, @NotNull final String str, final boolean toProcessOverwriteMode, final int caretShift)
throws ReadOnlyFragmentModificationException
{
Document doc = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
if (selectionModel.hasBlockSelection()) {
RangeMarker guard = selectionModel.getBlockSelectionGuard();
if (guard != null) {
DocumentEvent evt = new MockDocumentEvent(doc, editor.getCaretModel().getOffset());
ReadOnlyFragmentModificationException e = new ReadOnlyFragmentModificationException(evt, guard);
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
}
else {
final LogicalPosition start = selectionModel.getBlockStart();
final LogicalPosition end = selectionModel.getBlockEnd();
assert start != null;
assert end != null;
int column = Math.min(start.column, end.column);
int startLine = Math.min(start.line, end.line);
int endLine = Math.max(start.line, end.line);
deleteBlockSelection(editor);
for (int i = startLine; i <= endLine; i++) {
editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(i, column));
insertStringAtCaret(editor, str, toProcessOverwriteMode, true, caretShift);
}
selectionModel.setBlockSelection(new LogicalPosition(startLine, column + str.length()),
new LogicalPosition(endLine, column + str.length()));
}
}
else {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
insertStringAtCaretNoScrolling(editor, str, toProcessOverwriteMode, true, caretShift);
}
});
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
}
public static void moveAllCaretsRelatively(@NotNull Editor editor, final int caretShift) {
editor.getCaretModel().runForEachCaret(new CaretAction() {
@Override
public void perform(Caret caret) {
caret.moveToOffset(caret.getOffset() + caretShift);
}
});
}
public static void moveCaretRelatively(@NotNull Editor editor, final int caretShift) {
CaretModel caretModel = editor.getCaretModel();
caretModel.moveToOffset(caretModel.getOffset() + caretShift);
}
}