82150ccc18e6a897cf3ba5803fa03049b9e883b6
[idea/community.git] / platform / analysis-impl / src / com / intellij / codeInsight / completion / CompletionAssertions.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.completion;
3
4 import com.intellij.codeInsight.lookup.LookupElement;
5 import com.intellij.injected.editor.DocumentWindow;
6 import com.intellij.injected.editor.EditorWindow;
7 import com.intellij.lang.FileASTNode;
8 import com.intellij.lang.injection.InjectedLanguageManager;
9 import com.intellij.openapi.diagnostic.Attachment;
10 import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments;
11 import com.intellij.openapi.editor.Document;
12 import com.intellij.openapi.editor.Editor;
13 import com.intellij.openapi.editor.event.DocumentEvent;
14 import com.intellij.openapi.editor.ex.RangeMarkerEx;
15 import com.intellij.openapi.util.TextRange;
16 import com.intellij.openapi.util.text.StringUtil;
17 import com.intellij.openapi.vfs.VirtualFile;
18 import com.intellij.psi.FileViewProvider;
19 import com.intellij.psi.PsiDocumentManager;
20 import com.intellij.psi.PsiElement;
21 import com.intellij.psi.PsiFile;
22 import com.intellij.psi.impl.DebugUtil;
23 import com.intellij.psi.util.PsiUtilCore;
24 import com.intellij.util.text.ImmutableCharSequence;
25 import org.jetbrains.annotations.Contract;
26
27 import java.util.List;
28
29 /**
30  * @author peter
31  */
32 class CompletionAssertions {
33
34   static void assertCommitSuccessful(Editor editor, PsiFile psiFile) {
35     Document document = editor.getDocument();
36     int docLength = document.getTextLength();
37     int psiLength = psiFile.getTextLength();
38     PsiDocumentManager manager = PsiDocumentManager.getInstance(psiFile.getProject());
39     boolean committed = !manager.isUncommited(document);
40     if (docLength == psiLength && committed) {
41       return;
42     }
43
44     FileViewProvider viewProvider = psiFile.getViewProvider();
45
46     String message = "unsuccessful commit:";
47     message += "\nmatching=" + (psiFile == manager.getPsiFile(document));
48     message += "\ninjectedEditor=" + (editor instanceof EditorWindow);
49     message += "\ninjectedFile=" + InjectedLanguageManager.getInstance(psiFile.getProject()).isInjectedFragment(psiFile);
50     message += "\ncommitted=" + committed;
51     message += "\nfile=" + psiFile.getName();
52     message += "\nfile class=" + psiFile.getClass();
53     message += "\nfile.valid=" + psiFile.isValid();
54     message += "\nfile.physical=" + psiFile.isPhysical();
55     message += "\nfile.eventSystemEnabled=" + viewProvider.isEventSystemEnabled();
56     message += "\nlanguage=" + psiFile.getLanguage();
57     message += "\ndoc.length=" + docLength;
58     message += "\npsiFile.length=" + psiLength;
59     String fileText = psiFile.getText();
60     if (fileText != null) {
61       message += "\npsiFile.text.length=" + fileText.length();
62     }
63     FileASTNode node = psiFile.getNode();
64     if (node != null) {
65       message += "\nnode.length=" + node.getTextLength();
66       String nodeText = node.getText();
67       message += "\nnode.text.length=" + nodeText.length();
68     }
69     VirtualFile virtualFile = viewProvider.getVirtualFile();
70     message += "\nvirtualFile=" + virtualFile;
71     message += "\nvirtualFile.class=" + virtualFile.getClass();
72     message += "\n" + DebugUtil.currentStackTrace();
73
74     throw new RuntimeExceptionWithAttachments(
75       "Commit unsuccessful", message,
76       new Attachment(virtualFile.getPath() + "_file.txt", StringUtil.notNullize(fileText)),
77       createAstAttachment(psiFile, psiFile),
78       new Attachment("docText.txt", document.getText()));
79   }
80
81   static void checkEditorValid(Editor editor) {
82     if (!isEditorValid(editor)) {
83       throw new AssertionError();
84     }
85   }
86
87   static boolean isEditorValid(Editor editor) {
88     return !(editor instanceof EditorWindow) || ((EditorWindow)editor).isValid();
89   }
90
91   private static Attachment createAstAttachment(PsiFile fileCopy, final PsiFile originalFile) {
92     return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath() + " syntactic tree.txt", DebugUtil.psiToString(fileCopy, false, true));
93   }
94
95   private static Attachment createFileTextAttachment(PsiFile fileCopy, final PsiFile originalFile) {
96     return new Attachment(originalFile.getViewProvider().getVirtualFile().getPath(), fileCopy.getText());
97   }
98
99   static void assertInjectedOffsets(int hostStartOffset, PsiFile injected, DocumentWindow documentWindow) {
100     assert documentWindow != null : "no DocumentWindow for an injected fragment";
101
102     InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(injected.getProject());
103     TextRange injectedRange = injected.getTextRange();
104     int hostMinOffset = injectedLanguageManager.injectedToHost(injected, injectedRange.getStartOffset(), true);
105     int hostMaxOffset = injectedLanguageManager.injectedToHost(injected, injectedRange.getEndOffset(), false);
106     assert hostStartOffset >= hostMinOffset : "startOffset before injected";
107     assert hostStartOffset <= hostMaxOffset : "startOffset after injected";
108   }
109
110   static void assertHostInfo(PsiFile hostCopy, OffsetMap hostMap) {
111     PsiUtilCore.ensureValid(hostCopy);
112     if (hostMap.getOffset(CompletionInitializationContext.START_OFFSET) > hostCopy.getTextLength()) {
113       throw new AssertionError("startOffset outside the host file: " + hostMap.getOffset(CompletionInitializationContext.START_OFFSET) + "; " + hostCopy);
114     }
115   }
116
117   @Contract("_,_,_,null->fail")
118   static void assertCompletionPositionPsiConsistent(OffsetsInFile offsets,
119                                                     int offset,
120                                                     PsiFile originalFile, PsiElement insertedElement) {
121     PsiFile fileCopy = offsets.getFile();
122     if (insertedElement == null) {
123       throw new RuntimeExceptionWithAttachments(
124         "No element at insertion offset",
125         "offset=" + offset,
126         createFileTextAttachment(fileCopy, originalFile),
127         createAstAttachment(fileCopy, originalFile));
128     }
129
130     final TextRange range = insertedElement.getTextRange();
131     CharSequence fileCopyText = fileCopy.getViewProvider().getContents();
132     if ((range.getEndOffset() > fileCopyText.length()) ||
133         !isEquals(fileCopyText.subSequence(range.getStartOffset(), range.getEndOffset()),
134                   insertedElement.getNode().getChars())) {
135       throw new RuntimeExceptionWithAttachments(
136         "Inconsistent completion tree",
137         "range=" + range,
138         createFileTextAttachment(fileCopy, originalFile),
139         createAstAttachment(fileCopy, originalFile),
140         new Attachment("Element at caret.txt", insertedElement.getText()));
141     }
142   }
143
144   private static boolean isEquals(CharSequence left, CharSequence right) {
145     if (left == right) return true;
146     if (left instanceof ImmutableCharSequence && right instanceof ImmutableCharSequence) {
147       return left.equals(right);
148     }
149     return left.toString().equals(right.toString());
150   }
151
152   static void assertCorrectOriginalFile(String prefix, PsiFile file, PsiFile copy) {
153     if (copy.getOriginalFile() != file) {
154       throw new AssertionError(prefix + " copied file doesn't have correct original: noOriginal=" + (copy.getOriginalFile() == copy) +
155                                "\n file " + fileInfo(file) +
156                                "\n copy " + fileInfo(copy));
157     }
158   }
159
160   private static String fileInfo(PsiFile file) {
161     return file + " of " + file.getClass() +
162            " in " + file.getViewProvider() + ", languages=" + file.getViewProvider().getLanguages() +
163            ", physical=" + file.isPhysical();
164   }
165
166   static class WatchingInsertionContext extends InsertionContext {
167     private RangeMarkerEx tailWatcher;
168     Throwable invalidateTrace;
169     DocumentEvent killer;
170     private RangeMarkerSpy spy;
171
172     WatchingInsertionContext(OffsetMap offsetMap, PsiFile file, char completionChar, List<LookupElement> items, Editor editor) {
173       super(offsetMap, completionChar, items.toArray(LookupElement.EMPTY_ARRAY),
174             file, editor,
175             shouldAddCompletionChar(completionChar));
176     }
177
178     @Override
179     public void setTailOffset(int offset) {
180       super.setTailOffset(offset);
181       watchTail(offset);
182     }
183
184     private void watchTail(int offset) {
185       stopWatching();
186       tailWatcher = (RangeMarkerEx)getDocument().createRangeMarker(offset, offset);
187       if (!tailWatcher.isValid()) {
188         throw new AssertionError(getDocument() + "; offset=" + offset);
189       }
190       tailWatcher.setGreedyToRight(true);
191       spy = new RangeMarkerSpy(tailWatcher) {
192         @Override
193         protected void invalidated(DocumentEvent e) {
194           if (invalidateTrace == null) {
195             invalidateTrace = new Throwable();
196             killer = e;
197           }
198         }
199       };
200       getDocument().addDocumentListener(spy);
201     }
202
203     void stopWatching() {
204       if (tailWatcher != null) {
205         getDocument().removeDocumentListener(spy);
206         tailWatcher.dispose();
207       }
208     }
209
210     @Override
211     public int getTailOffset() {
212       if (!getOffsetMap().containsOffset(TAIL_OFFSET) && invalidateTrace != null) {
213         throw new RuntimeExceptionWithAttachments("Tail offset invalid", new Attachment("invalidated", invalidateTrace));
214       }
215
216       int offset = super.getTailOffset();
217       if (tailWatcher.getStartOffset() != tailWatcher.getEndOffset() && offset > 0) {
218         watchTail(offset);
219       }
220
221       return offset;
222     }
223   }
224
225 }