| /* |
| * Copyright 2000-2013 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.codeInsight.completion; |
| |
| import com.intellij.codeInsight.lookup.Lookup; |
| import com.intellij.codeInsight.lookup.LookupElement; |
| import com.intellij.diagnostic.LogEventException; |
| import com.intellij.openapi.diagnostic.Attachment; |
| import com.intellij.injected.editor.DocumentWindow; |
| import com.intellij.injected.editor.EditorWindow; |
| import com.intellij.lang.FileASTNode; |
| import com.intellij.lang.injection.InjectedLanguageManager; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.editor.ex.RangeMarkerEx; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.impl.DebugUtil; |
| |
| import java.util.List; |
| |
| /** |
| * @author peter |
| */ |
| class CompletionAssertions { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.CompletionAssertions"); |
| |
| static void assertCommitSuccessful(Editor editor, PsiFile psiFile) { |
| Document document = editor.getDocument(); |
| int docLength = document.getTextLength(); |
| int psiLength = psiFile.getTextLength(); |
| PsiDocumentManager manager = PsiDocumentManager.getInstance(psiFile.getProject()); |
| boolean committed = !manager.isUncommited(document); |
| if (docLength == psiLength && committed) { |
| return; |
| } |
| |
| String message = "unsuccessful commit:"; |
| message += "\nmatching=" + (psiFile == manager.getPsiFile(document)); |
| message += "\ninjectedEditor=" + (editor instanceof EditorWindow); |
| message += "\ninjectedFile=" + InjectedLanguageManager.getInstance(psiFile.getProject()).isInjectedFragment(psiFile); |
| message += "\ncommitted=" + committed; |
| message += "\nfile=" + psiFile.getName(); |
| message += "\nfile class=" + psiFile.getClass(); |
| message += "\nfile.valid=" + psiFile.isValid(); |
| message += "\nlanguage=" + psiFile.getLanguage(); |
| message += "\ndoc.length=" + docLength; |
| message += "\npsiFile.length=" + psiLength; |
| String fileText = psiFile.getText(); |
| if (fileText != null) { |
| message += "\npsiFile.text.length=" + fileText.length(); |
| } |
| FileASTNode node = psiFile.getNode(); |
| if (node != null) { |
| message += "\nnode.length=" + node.getTextLength(); |
| String nodeText = node.getText(); |
| if (nodeText != null) { |
| message += "\nnode.text.length=" + nodeText.length(); |
| } |
| } |
| message += "\n" + DebugUtil.currentStackTrace(); |
| |
| throw new LogEventException("Commit unsuccessful", message, |
| new Attachment(psiFile.getViewProvider().getVirtualFile().getPath() + "_file.txt", fileText), |
| createAstAttachment(psiFile, psiFile), |
| new Attachment("docText.txt", document.getText())); |
| } |
| |
| static void checkEditorValid(Editor editor) { |
| if (editor instanceof EditorWindow && !((EditorWindow)editor).isValid()) { |
| throw new AssertionError(); |
| } |
| } |
| |
| private static Attachment createAstAttachment(PsiFile fileCopy, final PsiFile originalFile) { |
| return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath() + " syntactic tree.txt", DebugUtil.psiToString(fileCopy, false, true)); |
| } |
| |
| private static Attachment createFileTextAttachment(PsiFile fileCopy, final PsiFile originalFile) { |
| return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath(), fileCopy.getText()); |
| } |
| |
| static void assertFinalOffsets(PsiFile originalFile, CompletionContext context, PsiFile injected) { |
| if (context.getStartOffset() >= context.file.getTextLength()) { |
| String msg = "start outside the file; file=" + context.file + " " + context.file.getTextLength(); |
| msg += "; injected=" + (injected != null); |
| msg += "; original " + originalFile + " " + originalFile.getTextLength(); |
| throw new AssertionError(msg); |
| } |
| assert context.getStartOffset() >= 0 : "start < 0"; |
| } |
| |
| static void assertInjectedOffsets(int hostStartOffset, |
| InjectedLanguageManager injectedLanguageManager, |
| PsiFile injected, |
| DocumentWindow documentWindow) { |
| assert documentWindow != null : "no DocumentWindow for an injected fragment"; |
| |
| TextRange host = injectedLanguageManager.injectedToHost(injected, injected.getTextRange()); |
| assert hostStartOffset >= host.getStartOffset() : "startOffset before injected"; |
| assert hostStartOffset <= host.getEndOffset() : "startOffset after injected"; |
| } |
| |
| static void assertHostInfo(PsiFile hostCopy, OffsetMap hostMap) { |
| assert hostCopy.isValid() : "file became invalid: " + hostCopy.getClass(); |
| if (hostMap.getOffset(CompletionInitializationContext.START_OFFSET) >= hostCopy.getTextLength()) { |
| throw new AssertionError("startOffset outside the host file: " + hostMap.getOffset(CompletionInitializationContext.START_OFFSET) + "; " + hostCopy); |
| } |
| } |
| |
| static void assertCompletionPositionPsiConsistent(CompletionContext newContext, |
| int offset, |
| PsiFile fileCopy, |
| PsiFile originalFile, PsiElement insertedElement) { |
| if (insertedElement == null) { |
| throw new LogEventException("No element at insertion offset", |
| "offset=" + |
| newContext.getStartOffset() + |
| "\n" + |
| DebugUtil.currentStackTrace(), |
| createFileTextAttachment(fileCopy, originalFile), |
| createAstAttachment(fileCopy, originalFile)); |
| } |
| |
| if (fileCopy.findElementAt(offset) != insertedElement) { |
| throw new AssertionError("wrong offset"); |
| } |
| |
| final TextRange range = insertedElement.getTextRange(); |
| String fileCopyText = fileCopy.getText(); |
| if ((range.getEndOffset() > fileCopyText.length()) || !range.substring(fileCopyText).equals(insertedElement.getText())) { |
| throw new LogEventException("Inconsistent completion tree", "range=" + range + "\n" + DebugUtil.currentStackTrace(), |
| createFileTextAttachment(fileCopy, originalFile), createAstAttachment(fileCopy, originalFile), |
| new Attachment("Element at caret.txt", insertedElement.getText())); |
| } |
| } |
| |
| static class WatchingInsertionContext extends InsertionContext { |
| private RangeMarkerEx tailWatcher; |
| String invalidateTrace; |
| DocumentEvent killer; |
| private RangeMarkerSpy spy; |
| |
| public WatchingInsertionContext(CompletionProgressIndicator indicator, char completionChar, List<LookupElement> items, Editor editor) { |
| super(indicator.getOffsetMap(), completionChar, items.toArray(new LookupElement[items.size()]), |
| indicator.getParameters().getOriginalFile(), editor, |
| completionChar != Lookup.AUTO_INSERT_SELECT_CHAR && completionChar != Lookup.REPLACE_SELECT_CHAR && |
| completionChar != Lookup.NORMAL_SELECT_CHAR); |
| } |
| |
| @Override |
| public void setTailOffset(int offset) { |
| super.setTailOffset(offset); |
| watchTail(offset); |
| } |
| |
| private void watchTail(int offset) { |
| stopWatching(); |
| tailWatcher = (RangeMarkerEx)getDocument().createRangeMarker(offset, offset); |
| if (!tailWatcher.isValid()) { |
| throw new AssertionError(getDocument() + "; offset=" + offset); |
| } |
| tailWatcher.setGreedyToRight(true); |
| spy = new RangeMarkerSpy(tailWatcher) { |
| @Override |
| protected void invalidated(DocumentEvent e) { |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| LOG.error("Tail offset invalidated, say thanks to the "+ e); |
| } |
| |
| if (invalidateTrace == null) { |
| invalidateTrace = DebugUtil.currentStackTrace(); |
| killer = e; |
| } |
| } |
| }; |
| getDocument().addDocumentListener(spy); |
| } |
| |
| void stopWatching() { |
| if (tailWatcher != null) { |
| getDocument().removeDocumentListener(spy); |
| tailWatcher.dispose(); |
| } |
| } |
| |
| @Override |
| public int getTailOffset() { |
| int offset = super.getTailOffset(); |
| if (tailWatcher.getStartOffset() != tailWatcher.getEndOffset() && offset > 0) { |
| watchTail(offset); |
| } |
| |
| return offset; |
| } |
| } |
| } |