fix postponed formatting
[fedora-idea.git] / platform / lang-impl / src / com / intellij / psi / SingleRootFileViewProvider.java
blob438eff30e62b4324e8cac32576751abf61ce5aad
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.psi;
18 import com.intellij.lang.Language;
19 import com.intellij.lang.LanguageParserDefinitions;
20 import com.intellij.lang.ParserDefinition;
21 import com.intellij.openapi.command.undo.UndoManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.extensions.Extensions;
25 import com.intellij.openapi.fileEditor.FileDocumentManager;
26 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
27 import com.intellij.openapi.fileTypes.*;
28 import com.intellij.openapi.progress.ProcessCanceledException;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.util.UserDataHolderBase;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem;
33 import com.intellij.psi.impl.PsiFileEx;
34 import com.intellij.psi.impl.PsiManagerEx;
35 import com.intellij.psi.impl.PsiManagerImpl;
36 import com.intellij.psi.impl.file.PsiBinaryFileImpl;
37 import com.intellij.psi.impl.file.impl.FileManagerImpl;
38 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
39 import com.intellij.psi.impl.source.PsiFileImpl;
40 import com.intellij.psi.impl.source.PsiPlainTextFileImpl;
41 import com.intellij.psi.impl.source.tree.FileElement;
42 import com.intellij.testFramework.LightVirtualFile;
43 import com.intellij.util.LocalTimeCounter;
44 import com.intellij.util.ReflectionCache;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
48 import java.lang.ref.SoftReference;
49 import java.util.Collections;
50 import java.util.List;
51 import java.util.Set;
52 import java.util.concurrent.atomic.AtomicReference;
54 public class SingleRootFileViewProvider extends UserDataHolderBase implements FileViewProvider {
55 private static final Logger LOG = Logger.getInstance("#" + SingleRootFileViewProvider.class.getCanonicalName());
56 private final PsiManager myManager;
57 private final VirtualFile myVirtualFile;
58 private final boolean myEventSystemEnabled;
59 private final boolean myPhysical;
60 private final AtomicReference<PsiFile> myPsiFile = new AtomicReference<PsiFile>();
61 private volatile Content myContent;
62 private volatile SoftReference<Document> myDocument;
63 private final Language myBaseLanguage;
65 public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile file) {
66 this(manager, file, true);
69 public SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean physical) {
70 this(manager, virtualFile, physical, calcBaseLanguage(virtualFile, manager.getProject()));
73 protected SingleRootFileViewProvider(@NotNull PsiManager manager, @NotNull VirtualFile virtualFile, final boolean physical, @NotNull Language language) {
74 myManager = manager;
75 myVirtualFile = virtualFile;
76 myEventSystemEnabled = physical;
77 myBaseLanguage = language;
78 setContent(new VirtualFileContent());
79 myPhysical = isEventSystemEnabled() &&
80 !(virtualFile instanceof LightVirtualFile) &&
81 !(virtualFile.getFileSystem() instanceof DummyFileSystem);
84 @NotNull
85 public Language getBaseLanguage() {
86 return myBaseLanguage;
89 private static Language calcBaseLanguage(VirtualFile file, Project project) {
90 if (file instanceof LightVirtualFile) {
91 final Language language = ((LightVirtualFile)file).getLanguage();
92 if (language != null) {
93 return language;
97 final FileType fileType = file.getFileType();
98 if (fileType.isBinary()) return Language.ANY;
99 if (isTooLarge(file)) return PlainTextLanguage.INSTANCE;
101 if (fileType instanceof LanguageFileType) {
102 return LanguageSubstitutors.INSTANCE.substituteLanguage(((LanguageFileType)fileType).getLanguage(), file, project);
105 final ContentBasedClassFileProcessor[] processors = Extensions.getExtensions(ContentBasedClassFileProcessor.EP_NAME);
106 for (ContentBasedClassFileProcessor processor : processors) {
107 Language language = processor.obtainLanguageForFile(file);
108 if (language != null) return language;
111 return PlainTextLanguage.INSTANCE;
114 @NotNull
115 public Set<Language> getLanguages() {
116 return Collections.singleton(getBaseLanguage());
119 @Nullable
120 public final PsiFile getPsi(@NotNull Language target) {
121 if (!isPhysical()) {
122 ((PsiManagerEx)myManager).getFileManager().setViewProvider(getVirtualFile(), this);
124 return getPsiInner(target);
127 @NotNull
128 public List<PsiFile> getAllFiles() {
129 return Collections.singletonList(getPsi(getBaseLanguage()));
132 @Nullable
133 protected PsiFile getPsiInner(final Language target) {
134 if (target != getBaseLanguage()) {
135 return null;
137 PsiFile psiFile = myPsiFile.get();
138 if (psiFile == null) {
139 psiFile = createFile();
140 myPsiFile.compareAndSet(null, psiFile);
141 psiFile = myPsiFile.get();
143 return psiFile;
146 public void beforeContentsSynchronized() {
147 unsetPsiContent();
150 public void contentsSynchronized() {
151 unsetPsiContent();
154 private void unsetPsiContent() {
155 if (!(myContent instanceof PsiFileContent)) return;
156 final Document cachedDocument = getCachedDocument();
157 setContent(cachedDocument == null ? new VirtualFileContent() : new DocumentContent());
160 public void beforeDocumentChanged() {
161 final PostprocessReformattingAspect component = myManager.getProject().getComponent(PostprocessReformattingAspect.class);
162 if (component.isViewProviderLocked(this)) {
163 throw new RuntimeException("Document is locked by write PSI operations. Use PsiDocumentManager.doPostponedOperationsAndUnblockDocument() to commit PSI changes to the document.");
165 component.postponedFormatting(this);
166 final PsiFileImpl psiFile = (PsiFileImpl)getCachedPsi(getBaseLanguage());
167 if (psiFile != null && psiFile.isContentsLoaded() && getContent()instanceof DocumentContent) {
168 setContent(new PsiFileContent(psiFile, getModificationStamp()));
172 public void rootChanged(PsiFile psiFile) {
173 if (((PsiFileEx)psiFile).isContentsLoaded()) {
174 setContent(new PsiFileContent((PsiFileImpl)psiFile, LocalTimeCounter.currentTime()));
178 public boolean isEventSystemEnabled() {
179 return myEventSystemEnabled;
182 public boolean isPhysical() {
183 return myPhysical;
186 public long getModificationStamp() {
187 return getContent().getModificationStamp();
190 public boolean supportsIncrementalReparse(final Language rootLanguage) {
191 return true;
195 public PsiFile getCachedPsi(Language target) {
196 return myPsiFile.get();
199 public FileElement[] getKnownTreeRoots() {
200 PsiFile psiFile = myPsiFile.get();
201 if (psiFile == null || !(psiFile instanceof PsiFileImpl)) return new FileElement[0];
202 if (((PsiFileImpl)psiFile).getTreeElement() == null) return new FileElement[0];
203 return new FileElement[]{(FileElement)psiFile.getNode()};
206 private PsiFile createFile() {
208 try {
209 final VirtualFile vFile = getVirtualFile();
210 if (vFile.isDirectory()) return null;
211 if (isIgnored()) return null;
213 final Project project = myManager.getProject();
214 if (isPhysical()) { // check directories consistency
215 final VirtualFile parent = vFile.getParent();
216 if (parent == null) return null;
217 final PsiDirectory psiDir = getManager().findDirectory(parent);
218 if (psiDir == null) return null;
221 return createFile(project, vFile, vFile.getFileType());
223 catch (ProcessCanceledException e) {
224 throw e;
226 catch (Throwable e) {
227 LOG.error(e);
228 return null;
232 protected boolean isIgnored() {
233 final VirtualFile file = getVirtualFile();
234 if (file instanceof LightVirtualFile) return false;
235 final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
236 return fileTypeManager.isFileIgnored(file.getName());
239 @Nullable
240 protected PsiFile createFile(final Project project, final VirtualFile vFile, final FileType fileType) {
241 if (fileType.isBinary()) {
242 return new PsiBinaryFileImpl((PsiManagerImpl)getManager(), this);
245 if (!isTooLarge(vFile)) {
246 final PsiFile psiFile = createFile(getBaseLanguage());
247 if (psiFile != null) return psiFile;
250 return new PsiPlainTextFileImpl(this);
253 public static boolean isTooLarge(final VirtualFile vFile) {
254 return fileSizeIsGreaterThan(vFile, FileManagerImpl.MAX_INTELLISENSE_FILESIZE);
257 private static boolean fileSizeIsGreaterThan(final VirtualFile vFile, final long maxInBytes) {
258 if (vFile instanceof LightVirtualFile) {
259 // This is optimization in order to avoid conversion of [large] file contents to bytes
260 final int lengthInChars = ((LightVirtualFile)vFile).getContent().length();
261 if (lengthInChars < maxInBytes / 2) return false;
262 if (lengthInChars > maxInBytes ) return true;
265 return vFile.getLength() > maxInBytes;
268 @Nullable
269 protected PsiFile createFile(Language lang) {
270 if (lang != getBaseLanguage()) return null;
271 final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(lang);
272 if (parserDefinition != null) {
273 return parserDefinition.createFile(this);
275 return null;
278 @NotNull
279 public PsiManager getManager() {
280 return myManager;
283 @NotNull
284 public CharSequence getContents() {
285 return getContent().getText();
288 @NotNull
289 public VirtualFile getVirtualFile() {
290 return myVirtualFile;
293 @Nullable
294 private Document getCachedDocument() {
295 final Document document = myDocument != null ? myDocument.get() : null;
296 if (document != null) return document;
297 return FileDocumentManager.getInstance().getCachedDocument(getVirtualFile());
300 public Document getDocument() {
301 Document document = myDocument != null ? myDocument.get() : null;
302 if (document == null/* TODO[ik] make this change && isEventSystemEnabled()*/) {
303 document = FileDocumentManager.getInstance().getDocument(getVirtualFile());
304 myDocument = new SoftReference<Document>(document);
306 if (document != null && getContent() instanceof VirtualFileContent) {
307 setContent(new DocumentContent());
309 return document;
312 public FileViewProvider clone() {
313 final VirtualFile origFile = getVirtualFile();
314 LightVirtualFile copy = new LightVirtualFile(origFile.getName(), origFile.getFileType(), getContents(), origFile.getCharset(), getModificationStamp());
315 copy.putUserData(UndoManager.DONT_RECORD_UNDO, Boolean.TRUE);
316 copy.setCharset(origFile.getCharset());
317 return createCopy(copy);
320 @NotNull
321 public SingleRootFileViewProvider createCopy(final LightVirtualFile copy) {
322 return new SingleRootFileViewProvider(getManager(), copy, false, myBaseLanguage);
325 public PsiReference findReferenceAt(final int offset) {
326 final PsiFileImpl psiFile = (PsiFileImpl)getPsi(getBaseLanguage());
327 return findReferenceAt(psiFile, offset);
330 public PsiElement findElementAt(final int offset, final Language language) {
331 final PsiFile psiFile = getPsi(language);
332 return psiFile != null ? findElementAt(psiFile, offset) : null;
335 @Nullable
336 public PsiReference findReferenceAt(final int offset, @NotNull final Language language) {
337 final PsiFile psiFile = getPsi(language);
338 return psiFile != null ? findReferenceAt(psiFile, offset) : null;
341 public boolean isLockedByPsiOperations() {
342 final PostprocessReformattingAspect component = myManager.getProject().getComponent(PostprocessReformattingAspect.class);
343 return component.isViewProviderLocked(this);
346 @Nullable
347 private static PsiReference findReferenceAt(final PsiFile psiFile, final int offset) {
348 if (psiFile == null) return null;
349 int offsetInElement = offset;
350 PsiElement child = psiFile.getFirstChild();
351 while (child != null) {
352 final int length = child.getTextLength();
353 if (length <= offsetInElement) {
354 offsetInElement -= length;
355 child = child.getNextSibling();
356 continue;
358 return child.findReferenceAt(offsetInElement);
360 return null;
363 public PsiElement findElementAt(final int offset) {
364 return findElementAt(getPsi(getBaseLanguage()), offset);
368 public PsiElement findElementAt(int offset, Class<? extends Language> lang) {
369 if (!ReflectionCache.isAssignable(lang, getBaseLanguage().getClass())) return null;
370 return findElementAt(offset);
373 @Nullable
374 protected static PsiElement findElementAt(final PsiElement psiFile, final int offset) {
375 if (psiFile == null) return null;
376 int offsetInElement = offset;
377 PsiElement child = psiFile.getFirstChild();
378 while (child != null) {
379 final int length = child.getTextLength();
380 if (length <= offsetInElement) {
381 offsetInElement -= length;
382 child = child.getNextSibling();
383 continue;
385 return child.findElementAt(offsetInElement);
387 return null;
390 public void forceCachedPsi(final PsiFile psiFile) {
391 myPsiFile.set(psiFile);
392 ((PsiManagerEx)myManager).getFileManager().setViewProvider(getVirtualFile(), this);
395 private Content getContent() {
396 return myContent;
399 private void setContent(final Content content) {
400 myContent = content;
403 private interface Content {
404 CharSequence getText();
406 long getModificationStamp();
409 private class VirtualFileContent implements Content {
410 public CharSequence getText() {
411 final VirtualFile virtualFile = getVirtualFile();
412 if (virtualFile instanceof LightVirtualFile) {
413 Document doc = getCachedDocument();
414 if (doc != null) return doc.getCharsSequence();
415 return ((LightVirtualFile)virtualFile).getContent();
418 final Document document = getDocument();
419 if (document == null) {
420 return LoadTextUtil.loadText(virtualFile);
422 else {
423 return document.getCharsSequence();
427 public long getModificationStamp() {
428 return getVirtualFile().getModificationStamp();
432 private class DocumentContent implements Content {
433 public CharSequence getText() {
434 final Document document = getDocument();
435 assert document != null;
436 return document.getCharsSequence();
439 public long getModificationStamp() {
440 Document document = myDocument == null ? null : myDocument.get();
441 if (document != null) return document.getModificationStamp();
442 return myVirtualFile.getModificationStamp();
446 private class PsiFileContent implements Content {
447 private final PsiFileImpl myFile;
448 private CharSequence myContent = null;
449 private final long myModificationStamp;
451 private PsiFileContent(final PsiFileImpl file, final long modificationStamp) {
452 myFile = file;
453 myModificationStamp = modificationStamp;
456 public CharSequence getText() {
457 if (!myFile.isContentsLoaded()) {
458 unsetPsiContent();
459 return getContents();
461 if (myContent != null) return myContent;
462 return myContent = myFile.calcTreeElement().getText();
465 public long getModificationStamp() {
466 if (!myFile.isContentsLoaded()) {
467 unsetPsiContent();
468 return SingleRootFileViewProvider.this.getModificationStamp();
470 return myModificationStamp;