blob: fd774b5e817d2b73c6af9747064e2fd5f379d8e8 [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.openapi.editor.*;
import com.intellij.openapi.editor.ex.FoldingModelEx;
import com.intellij.openapi.editor.impl.softwrap.mapping.CachingSoftWrapDataMapper;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.EditorTestUtil;
import com.intellij.testFramework.LightPlatformCodeInsightTestCase;
import com.intellij.testFramework.TestFileType;
import com.intellij.testFramework.fixtures.EditorMouseFixture;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.junit.Assert.assertArrayEquals;
/**
* Base super class for tests that check various IJ editor functionality on managed document modification.
* <p/>
* It's main purpose is to provide utility methods like fold regions addition and setup; typing etc.
*
* @author Denis Zhdanov
* @since 11/18/10 7:43 PM
*/
public abstract class AbstractEditorTest extends LightPlatformCodeInsightTestCase {
protected void init(@NotNull @NonNls String fileText, @NotNull TestFileType type) throws IOException {
configureFromFileText(getFileName(type), fileText);
}
private String getFileName(TestFileType type) {
return getTestName(false) + type.getExtension();
}
protected static FoldRegion addFoldRegion(final int startOffset, final int endOffset, final String placeholder) {
final FoldRegion[] result = new FoldRegion[1];
myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
result[0] = myEditor.getFoldingModel().addFoldRegion(startOffset, endOffset, placeholder);
}
});
return result[0];
}
protected static FoldRegion addCollapsedFoldRegion(final int startOffset, final int endOffset, final String placeholder) {
FoldRegion region = addFoldRegion(startOffset, endOffset, placeholder);
toggleFoldRegionState(getFoldRegion(startOffset), false);
return region;
}
protected static void toggleFoldRegionState(final FoldRegion foldRegion, final boolean expanded) {
myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
foldRegion.setExpanded(expanded);
}
});
}
protected static FoldRegion getFoldRegion(int startOffset) {
FoldRegion[] foldRegions = myEditor.getFoldingModel().getAllFoldRegions();
for (FoldRegion foldRegion : foldRegions) {
if (foldRegion.getStartOffset() == startOffset) {
return foldRegion;
}
}
throw new IllegalArgumentException(String.format(
"Can't find fold region with start offset %d. Registered fold regions: %s. Document text: '%s'",
startOffset, Arrays.toString(foldRegions), myEditor.getDocument().getCharsSequence()
));
}
protected static void foldOccurrences(String textToFoldRegexp, final String placeholder) {
final Matcher matcher = Pattern.compile(textToFoldRegexp).matcher(myEditor.getDocument().getCharsSequence());
myEditor.getFoldingModel().runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
while(matcher.find()) {
FoldRegion foldRegion = myEditor.getFoldingModel().addFoldRegion(matcher.start(), matcher.end(), placeholder);
assertNotNull(foldRegion);
foldRegion.setExpanded(false);
}
}
});
}
/**
* Setups document of the {@link #getEditor() current editor} according to the given text that is expected to contain
* information about document lines obtained from the {@link DocumentImpl#dumpState()}.
*
* @param data string representation of the target document's lines
*/
@SuppressWarnings("UnusedDeclaration")
protected static void setupDocument(@NotNull String data) {
Scanner scanner = new Scanner(data);
Pattern pattern = Pattern.compile("(\\d+)\\s*:\\s*(\\d+)\\s*-\\s*(\\d+)");
StringBuilder buffer = new StringBuilder();
while (scanner.findInLine(pattern) != null) {
final MatchResult match = scanner.match();
int startOffset = Integer.parseInt(match.group(2));
int endOffset = Integer.parseInt(match.group(3));
buffer.append(StringUtil.repeatSymbol('a', endOffset - startOffset)).append('\n');
}
if (buffer.length() > 0) {
buffer.setLength(buffer.length() - 1);
}
myEditor.getDocument().setText(buffer.toString());
}
/**
* Setups {@link Editor#getFoldingModel() folding model} of the {@link #getEditor() current editor} according to the given text
* that is expected to contain information obtained from the {@link FoldingModelImpl#toString()}.
*
* @param data string representation of the target fold regions
*/
protected static void setupFolding(@NotNull String data) {
Scanner scanner = new Scanner(data);
Pattern pattern = Pattern.compile("FoldRegion ([+-])\\((\\d+):(\\d+)");
final List<Trinity<Boolean, Integer, Integer>> infos = new ArrayList<Trinity<Boolean, Integer, Integer>>();
while (scanner.findInLine(pattern) != null) {
final MatchResult match = scanner.match();
boolean expanded = "-".equals(match.group(1));
int startOffset = Integer.parseInt(match.group(2));
int endOffset = Integer.parseInt(match.group(3));
infos.add(new Trinity<Boolean, Integer, Integer>(expanded, startOffset, endOffset));
}
final FoldingModel foldingModel = myEditor.getFoldingModel();
foldingModel.runBatchFoldingOperation(new Runnable() {
@Override
public void run() {
for (Trinity<Boolean, Integer, Integer> info : infos) {
final FoldRegion region = foldingModel.addFoldRegion(info.second, info.third, "...");
assert region != null;
region.setExpanded(info.first);
}
}
});
}
/**
* Setups {@link Editor#getSoftWrapModel() soft wraps model} of the {@link #getEditor() current editor} according to the given text
* that is expected to contain information obtained from the {@link CachingSoftWrapDataMapper#toString()}.
*
* @param data string representation of the target soft wraps cache
*/
@SuppressWarnings("UnusedDeclaration")
protected static void setupSoftWraps(@NotNull String data) {
Scanner scanner = new Scanner(data);
Pattern generalPattern =
Pattern.compile("visual line: (\\d+), offsets: (\\d+)-(\\d+), logical lines: (\\d+)-(\\d+), logical columns: (\\d+)-(\\d+), "
+ "end visual column: (\\d+), fold regions: \\[([^\\]]*)\\], tab data: \\[([^\\]]*)\\]");
Pattern foldPattern = Pattern.compile("width in columns: (-?\\d+), fold region: FoldRegion [-+]\\((\\d+):(\\d+)");
Pattern tabPattern = Pattern.compile("\\[(\\d+), width: (\\d+)");
final SoftWrapModelImpl softWrapModel = (SoftWrapModelImpl)myEditor.getSoftWrapModel();
final CachingSoftWrapDataMapper mapper = softWrapModel.getDataMapper();
mapper.release();
final FoldingModelEx foldingModel = (FoldingModelEx)myEditor.getFoldingModel();
while (scanner.findInLine(generalPattern) != null) {
final MatchResult generalMatch = scanner.match();
int visualLine = Integer.parseInt(generalMatch.group(1));
int startOffset = Integer.parseInt(generalMatch.group(2));
int endOffset = Integer.parseInt(generalMatch.group(3));
int startLogicalLine = Integer.parseInt(generalMatch.group(4));
int endLogicalLine = Integer.parseInt(generalMatch.group(5));
int startLogicalColumn = Integer.parseInt(generalMatch.group(6));
int endLogicalColumn = Integer.parseInt(generalMatch.group(7));
int endVisualColumn = Integer.parseInt(generalMatch.group(8));
List<Pair<Integer, FoldRegion>> foldRegions = new ArrayList<Pair<Integer, FoldRegion>>();
Scanner foldScanner = new Scanner(generalMatch.group(9));
while (foldScanner.findInLine(foldPattern) != null) {
final MatchResult foldMatch = foldScanner.match();
int widthInColumns = Integer.parseInt(foldMatch.group(1));
int foldStartOffset = Integer.parseInt(foldMatch.group(2));
int foldEndOffset = Integer.parseInt(foldMatch.group(3));
FoldRegion region = null;
for (FoldRegion candidate : foldingModel.getAllFoldRegions()) {
if (candidate.getStartOffset() == foldStartOffset && candidate.getEndOffset() == foldEndOffset) {
region = candidate;
break;
}
}
foldRegions.add(new Pair<Integer, FoldRegion>(widthInColumns, region));
}
List<Pair<Integer, Integer>> tabData = new ArrayList<Pair<Integer, Integer>>();
Scanner tabScanner = new Scanner(generalMatch.group(10));
while (tabScanner.findInLine(tabPattern) != null) {
final MatchResult tabMatch = tabScanner.match();
int offset = Integer.parseInt(tabMatch.group(1));
int widthInColumns = Integer.parseInt(tabMatch.group(2));
tabData.add(new Pair<Integer, Integer>(offset, widthInColumns));
}
mapper.rawAdd(visualLine, startOffset, endOffset, startLogicalLine, startLogicalColumn, endLogicalLine, endLogicalColumn, endVisualColumn, foldRegions, tabData);
}
}
public void assertSelectionRanges(int[][] ranges) {
int[] selectionStarts = myEditor.getSelectionModel().getBlockSelectionStarts();
int[] selectionEnds = myEditor.getSelectionModel().getBlockSelectionEnds();
int actualRangeCount = selectionStarts.length;
int[][] actualRanges = new int[actualRangeCount][];
for (int i = 0; i < actualRangeCount; i++) {
actualRanges[i] = new int[] {selectionStarts[i], selectionEnds[i]};
}
assertEquals("Wrong selected ranges", Arrays.deepToString(ranges), Arrays.deepToString(actualRanges));
}
public EditorMouseFixture mouse() {
return new EditorMouseFixture((EditorImpl)myEditor);
}
public void setEditorVisibleSize(int widthInChars, int heightInChars) {
EditorTestUtil.setEditorVisibleSize(myEditor, widthInChars, heightInChars);
}
/**
* Verifies visual positions of carets and their selection ranges. It's assumed that for each caret its position and selection range
* are within the same visual line.
*
* For each caret its visual position and visual positions of selection start an and should be provided in the following order:
* line, caretColumn, selectionStartColumn, selectionEndColumn
*/
public static void verifyCaretsAndSelections(int... coordinates) {
int caretCount = coordinates.length / 4;
List<Caret> carets = myEditor.getCaretModel().getAllCarets();
assertEquals("Unexpected caret count", caretCount, carets.size());
for (int i = 0; i < caretCount; i++) {
Caret caret = carets.get(i);
assertEquals("Unexpected position for caret " + (i + 1), new VisualPosition(coordinates[i * 4], coordinates[i * 4 + 1]), caret.getVisualPosition());
assertEquals("Unexpected selection start for caret " + (i + 1), new VisualPosition(coordinates[i * 4], coordinates[i * 4 + 2]), caret.getSelectionStartPosition());
assertEquals("Unexpected selection end for caret " + (i + 1), new VisualPosition(coordinates[i * 4], coordinates[i * 4 + 3]), caret.getSelectionEndPosition());
}
}
public static void verifySoftWrapPositions(Integer... positions) {
List<Integer> softWrapPositions = new ArrayList<Integer>();
for (SoftWrap softWrap : myEditor.getSoftWrapModel().getSoftWrapsForRange(0, myEditor.getDocument().getTextLength())) {
softWrapPositions.add(softWrap.getStart());
}
assertArrayEquals(positions, softWrapPositions.toArray());
}
}