package com.intellij.testFramework;
import com.intellij.openapi.actionSystem.DataConstants;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actionSystem.TypedAction;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.editor.impl.injected.EditorWindow;
import com.intellij.openapi.editor.impl.injected.DocumentWindow;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.ide.DataManager;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
/**
* A TestCase for single PsiFile being opened in Editor conversion. See configureXXX and checkResultXXX method docs.
*/
public class LightCodeInsightTestCase extends LightIdeaTestCase {
private static final Logger LOG = Logger.getInstance("#com.intellij.testFramework.LightCodeInsightTestCase");
protected static Editor myEditor;
protected static PsiFile myFile;
protected static VirtualFile myVFile;
private static final String CARET_MARKER = "<caret>";
private static final String SELECTION_START_MARKER = "<selection>";
private static final String SELECTION_END_MARKER = "</selection>";
protected void runTest() throws Throwable {
final Throwable[] throwable = new Throwable[] {null};
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() {
public void run() {
try {
doRunTest();
} catch (Throwable t) {
throwable[0] = t;
}
}
}, "", null);
}
});
if (throwable[0] != null) {
throw throwable[0];
}
}
protected void doRunTest() throws Throwable {
LightCodeInsightTestCase.super.runTest();
}
/**
* Configure test from data file. Data file is usual java, xml or whatever file that needs to be tested except it
* has <caret> marker where caret should be placed when file is loaded in editor and <selection></selection>
* denoting selection bounds.
* @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/
* @throws Exception
*/
protected void configureByFile(@NonNls String filePath) throws Exception {
String fullPath = getTestDataPath() + filePath;
final File ioFile = new File(fullPath);
String fileText = new String(FileUtil.loadFileText(ioFile, CharsetToolkit.UTF8));
fileText = StringUtil.convertLineSeparators(fileText, "\n");
configureFromFileText(ioFile.getName(), fileText);
}
protected String getTestDataPath() {
return PathManagerEx.getTestDataPath();
}
/**
* Same as configureByFile but text is provided directly.
* @param fileName - name of the file.
* @param fileText - data file text.
* @throws IOException
*/
protected static void configureFromFileText(@NonNls final String fileName, @NonNls String fileText) throws IOException {
final Document fakeDocument = new DocumentImpl(fileText);
int caretIndex = fileText.indexOf(CARET_MARKER);
int selStartIndex = fileText.indexOf(SELECTION_START_MARKER);
int selEndIndex = fileText.indexOf(SELECTION_END_MARKER);
final RangeMarker caretMarker = caretIndex >= 0 ? fakeDocument.createRangeMarker(caretIndex, caretIndex) : null;
final RangeMarker selStartMarker = selStartIndex >= 0 ? fakeDocument.createRangeMarker(selStartIndex, selStartIndex) : null;
final RangeMarker selEndMarker = selEndIndex >= 0 ? fakeDocument.createRangeMarker(selEndIndex, selEndIndex) : null;
if (caretMarker != null) {
fakeDocument.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length());
}
if (selStartMarker != null) {
fakeDocument.deleteString(selStartMarker.getStartOffset(),
selStartMarker.getStartOffset() + SELECTION_START_MARKER.length());
}
if (selEndMarker != null) {
fakeDocument.deleteString(selEndMarker.getStartOffset(),
selEndMarker.getStartOffset() + SELECTION_END_MARKER.length());
}
String newFileText = fakeDocument.getText();
setupFileEditorAndDocument(fileName, newFileText);
setupCaret(caretMarker, newFileText);
setupSelection(selStartMarker, selEndMarker);
setupEditorForInjectedLanguage();
}
private static void setupSelection(final RangeMarker selStartMarker, final RangeMarker selEndMarker) {
if (selStartMarker != null) {
myEditor.getSelectionModel().setSelection(selStartMarker.getStartOffset(), selEndMarker.getStartOffset());
}
}
private static void setupCaret(final RangeMarker caretMarker, String fileText) {
if (caretMarker != null) {
int caretLine = StringUtil.offsetToLineNumber(fileText, caretMarker.getStartOffset());
int caretCol = EditorUtil.calcColumnNumber(null, myEditor.getDocument().getText(),
myEditor.getDocument().getLineStartOffset(caretLine), caretMarker.getStartOffset(),
CodeStyleSettingsManager.getSettings(getProject()).JAVA_INDENT_OPTIONS.TAB_SIZE);
LogicalPosition pos = new LogicalPosition(caretLine, caretCol);
myEditor.getCaretModel().moveToLogicalPosition(pos);
}
}
private static Editor createEditor(VirtualFile file) {
return FileEditorManager.getInstance(getProject()).openTextEditor(new OpenFileDescriptor(getProject(), file, 0), false);
}
private static void setupFileEditorAndDocument(final String fileName, String fileText) throws IOException {
deleteVFile();
myVFile = getSourceRoot().createChildData(null, fileName);
myVFile.setCharset(CharsetToolkit.UTF8_CHARSET);
VfsUtil.saveText(myVFile, fileText);
final FileDocumentManager manager = FileDocumentManager.getInstance();
final Document document = manager.getDocument(myVFile);
assertNotNull("Can't create document for '" + fileName + "'", document);
manager.reloadFromDisk(document);
myFile = getPsiManager().findFile(myVFile);
assertNotNull("Can't create PsiFile for '" + fileName + "'. Unknown file type most probably.", myFile);
assertTrue(myFile.isPhysical());
myEditor = createEditor(myVFile);
}
private static void setupEditorForInjectedLanguage() {
Editor editor = InjectedLanguageUtil.getEditorForInjectedLanguage(myEditor, myFile);
if (editor instanceof EditorWindow) {
myFile = ((EditorWindow)editor).getInjectedFile();
myEditor = editor;
}
}
private static void deleteVFile() {
if (myVFile != null) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
myVFile.delete(this);
} catch (IOException e) {
LOG.error(e);
}
}
});
}
}
protected void tearDown() throws Exception {
FileEditorManager editorManager = FileEditorManager.getInstance(getProject());
VirtualFile[] openFiles = editorManager.getOpenFiles();
for (VirtualFile openFile : openFiles) {
editorManager.closeFile(openFile);
}
deleteVFile();
myEditor = null;
myFile = null;
myVFile = null;
super.tearDown();
}
/**
* Validates that content of the editor as well as caret and selection matches one specified in data file that
* should be formed with the same format as one used in configureByFile
* @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/
* @throws Exception
*/
protected void checkResultByFile(@NonNls String filePath) throws Exception {
checkResultByFile(null, filePath, false);
}
/**
* Validates that content of the editor as well as caret and selection matches one specified in data file that
* should be formed with the same format as one used in configureByFile
* @param message - this check specific message. Added to text, caret position, selection checking. May be null
* @param filePath - relative path from %IDEA_INSTALLATION_HOME%/testData/
* @param ignoreTrailingSpaces - whether trailing spaces in editor in data file should be stripped prior to comparing.
* @throws Exception
*/
protected void checkResultByFile(String message, final String filePath, final boolean ignoreTrailingSpaces) throws Exception {
bringRealEditorBack();
getProject().getComponent(PostprocessReformattingAspect.class).doPostponedFormatting();
if (ignoreTrailingSpaces) {
((DocumentEx) myEditor.getDocument()).stripTrailingSpaces(false);
}
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
String fullPath = getTestDataPath() + filePath;
File ioFile = new File(fullPath);
assertTrue(getMessage("Cannot find file " + fullPath, message), ioFile.exists());
String fileText = null;
try {
fileText = new String(FileUtil.loadFileText(ioFile, CharsetToolkit.UTF8));
} catch (IOException e) {
LOG.error(e);
}
checkResultByText(message, StringUtil.convertLineSeparators(fileText, "\n"), ignoreTrailingSpaces);
}
/**
* Same as checkResultByFile but text is provided directly.
* @param fileText
*/
protected void checkResultByText(@NonNls String fileText) {
checkResultByText(null, fileText, false);
}
/**
* Same as checkResultByFile but text is provided directly.
* @param message - this check specific message. Added to text, caret position, selection checking. May be null
* @param fileText
* @param ignoreTrailingSpaces - whether trailing spaces in editor in data file should be stripped prior to comparing.
*/
protected void checkResultByText(String message, String fileText, final boolean ignoreTrailingSpaces) {
bringRealEditorBack();
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
}
});
final Document document = EditorFactory.getInstance().createDocument(fileText);
int caretIndex = fileText.indexOf(CARET_MARKER);
int selStartIndex = fileText.indexOf(SELECTION_START_MARKER);
int selEndIndex = fileText.indexOf(SELECTION_END_MARKER);
final RangeMarker caretMarker = caretIndex >= 0 ? document.createRangeMarker(caretIndex, caretIndex) : null;
final RangeMarker selStartMarker = selStartIndex >= 0
? document.createRangeMarker(selStartIndex, selStartIndex)
: null;
final RangeMarker selEndMarker = selEndIndex >= 0
? document.createRangeMarker(selEndIndex, selEndIndex)
: null;
if (caretMarker != null) {
document.deleteString(caretMarker.getStartOffset(), caretMarker.getStartOffset() + CARET_MARKER.length());
}
if (selStartMarker != null) {
document.deleteString(selStartMarker.getStartOffset(),
selStartMarker.getStartOffset() + SELECTION_START_MARKER.length());
}
if (selEndMarker != null) {
document.deleteString(selEndMarker.getStartOffset(),
selEndMarker.getStartOffset() + SELECTION_END_MARKER.length());
}
String newFileText = document.getText();
String newFileText1 = newFileText;
if (ignoreTrailingSpaces) {
Document document1 = EditorFactory.getInstance().createDocument(newFileText);
((DocumentEx) document1).stripTrailingSpaces(false);
newFileText1 = document1.getText();
}
PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting();
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
assertEquals(getMessage("Text mismatch", message), newFileText1, myFile.getText());
checkCaretPosition(caretMarker, newFileText, message);
checkSelection(selStartMarker, selEndMarker, newFileText, message);
}
private static String getMessage(String engineMessage, String userMessage) {
if (userMessage == null) return engineMessage;
StringBuffer buf = new StringBuffer(userMessage);
buf.append(" [").append(engineMessage).append("]");
return buf.toString();
}
private void checkSelection(final RangeMarker selStartMarker, final RangeMarker selEndMarker, String newFileText, String message) {
if (selStartMarker != null && selEndMarker != null) {
int selStartLine = StringUtil.offsetToLineNumber(newFileText, selStartMarker.getStartOffset());
int selStartCol = selStartMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, selStartLine, 0);
int selEndLine = StringUtil.offsetToLineNumber(newFileText, selEndMarker.getEndOffset());
int selEndCol = selEndMarker.getEndOffset() - StringUtil.lineColToOffset(newFileText, selEndLine, 0);
assertEquals(
getMessage("selectionStartLine", message),
selStartLine + 1,
StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionStart()) + 1);
assertEquals(
getMessage("selectionStartCol", message),
selStartCol + 1,
myEditor.getSelectionModel().getSelectionStart() -
StringUtil.lineColToOffset(newFileText, selStartLine, 0) +
1);
assertEquals(
getMessage("selectionEndLine", message),
selEndLine + 1,
StringUtil.offsetToLineNumber(newFileText, myEditor.getSelectionModel().getSelectionEnd()) + 1);
assertEquals(
getMessage("selectionEndCol", message),
selEndCol + 1,
myEditor.getSelectionModel().getSelectionEnd() - StringUtil.lineColToOffset(newFileText, selEndLine, 0) +
1);
} else {
assertTrue(getMessage("must not have selection", message), !myEditor.getSelectionModel().hasSelection());
}
}
private void checkCaretPosition(final RangeMarker caretMarker, String newFileText, String message) {
if (caretMarker != null) {
int caretLine = StringUtil.offsetToLineNumber(newFileText, caretMarker.getStartOffset());
//int caretCol = caretMarker.getStartOffset() - StringUtil.lineColToOffset(newFileText, caretLine, 0);
int caretCol = EditorUtil.calcColumnNumber(null, newFileText,
StringUtil.lineColToOffset(newFileText, caretLine, 0),
caretMarker.getStartOffset(),
CodeStyleSettingsManager.getSettings(getProject()).JAVA_INDENT_OPTIONS.TAB_SIZE);
assertEquals(getMessage("caretLine", message), caretLine + 1, myEditor.getCaretModel().getLogicalPosition().line + 1);
assertEquals(getMessage("caretColumn", message), caretCol + 1, myEditor.getCaretModel().getLogicalPosition().column + 1);
}
}
public Object getData(String dataId) {
if (dataId.equals(DataConstants.EDITOR)) {
return myEditor;
}
if (dataId.equals(AnActionEvent.injectedId(DataConstants.EDITOR))) {
return InjectedLanguageUtil.getEditorForInjectedLanguage(getEditor(), getFile());
}
if (dataId.equals(DataConstants.PSI_FILE)) {
return myFile;
}
if (dataId.equals(AnActionEvent.injectedId(DataConstants.PSI_FILE))) {
Editor editor = InjectedLanguageUtil.getEditorForInjectedLanguage(getEditor(), getFile());
return editor instanceof EditorWindow ? ((EditorWindow)editor).getInjectedFile() : getFile();
}
return super.getData(dataId);
}
/**
* @return Editor used in test.
*/
protected static Editor getEditor() {
return myEditor;
}
/**
* @return PsiFile opened in editor used in test
*/
protected static PsiFile getFile() {
return myFile;
}
protected static VirtualFile getVFile() {
return myVFile;
}
protected static void bringRealEditorBack() {
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
if (myEditor instanceof EditorWindow) {
DocumentEx document = ((DocumentWindow)myEditor.getDocument()).getDelegate();
myFile = PsiDocumentManager.getInstance(getProject()).getPsiFile(document);
myEditor = ((EditorWindow)myEditor).getDelegate();
myVFile = myFile.getVirtualFile();
}
}
protected static void type(char c) {
EditorActionManager actionManager = EditorActionManager.getInstance();
TypedAction action = actionManager.getTypedAction();
action.actionPerformed(getEditor(), c, DataManager.getInstance().getDataContext());
}
protected static void type(@NonNls String s) {
for (char c : s.toCharArray()) {
type(c);
}
}
protected static void backspace() {
EditorActionManager actionManager = EditorActionManager.getInstance();
EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE);
actionHandler.execute(getEditor(), DataManager.getInstance().getDataContext());
}
protected DataContext getCurrentEditorDataContext() {
final DataContext defaultContext = DataManager.getInstance().getDataContext();
DataContext dataContext = new DataContext() {
@Nullable
public Object getData(@NonNls String dataId) {
if (dataId.equals(DataConstants.EDITOR)) return getEditor();
if (dataId.equals(DataConstants.PROJECT)) return getProject();
if (dataId.equals(DataConstants.PSI_FILE)) return getFile();
if (dataId.equals(DataConstants.PSI_ELEMENT)) return getFile().findElementAt(getEditor().getCaretModel().getOffset());
return defaultContext.getData(dataId);
}
};
return dataContext;
}
}