2 * Copyright (c) 2005 JetBrains s.r.o. All Rights Reserved.
4 package com
.intellij
.psi
;
6 import com
.intellij
.lang
.Language
;
7 import com
.intellij
.lang
.LanguageParserDefinitions
;
8 import com
.intellij
.lang
.ParserDefinition
;
9 import com
.intellij
.openapi
.command
.undo
.UndoManager
;
10 import com
.intellij
.openapi
.diagnostic
.Logger
;
11 import com
.intellij
.openapi
.editor
.Document
;
12 import com
.intellij
.openapi
.extensions
.Extensions
;
13 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
14 import com
.intellij
.openapi
.fileEditor
.impl
.LoadTextUtil
;
15 import com
.intellij
.openapi
.fileTypes
.*;
16 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
17 import com
.intellij
.openapi
.project
.Project
;
18 import com
.intellij
.openapi
.util
.UserDataHolderBase
;
19 import com
.intellij
.openapi
.vfs
.VirtualFile
;
20 import com
.intellij
.openapi
.vfs
.ex
.dummy
.DummyFileSystem
;
21 import com
.intellij
.openapi
.vfs
.ex
.temp
.TempFileSystem
;
22 import com
.intellij
.psi
.impl
.PsiFileEx
;
23 import com
.intellij
.psi
.impl
.PsiManagerEx
;
24 import com
.intellij
.psi
.impl
.PsiManagerImpl
;
25 import com
.intellij
.psi
.impl
.file
.PsiBinaryFileImpl
;
26 import com
.intellij
.psi
.impl
.file
.impl
.FileManagerImpl
;
27 import com
.intellij
.psi
.impl
.source
.PostprocessReformattingAspect
;
28 import com
.intellij
.psi
.impl
.source
.PsiFileImpl
;
29 import com
.intellij
.psi
.impl
.source
.PsiPlainTextFileImpl
;
30 import com
.intellij
.psi
.impl
.source
.tree
.FileElement
;
31 import com
.intellij
.testFramework
.LightVirtualFile
;
32 import com
.intellij
.util
.LocalTimeCounter
;
33 import com
.intellij
.util
.ReflectionCache
;
34 import org
.jetbrains
.annotations
.NotNull
;
35 import org
.jetbrains
.annotations
.Nullable
;
37 import java
.lang
.ref
.SoftReference
;
38 import java
.util
.Collections
;
39 import java
.util
.List
;
41 import java
.util
.concurrent
.atomic
.AtomicReference
;
43 public class SingleRootFileViewProvider
extends UserDataHolderBase
implements FileViewProvider
{
44 private static final Logger LOG
= Logger
.getInstance("#" + SingleRootFileViewProvider
.class.getCanonicalName());
45 private final PsiManager myManager
;
46 private final VirtualFile myVirtualFile
;
47 private final boolean myEventSystemEnabled
;
48 private final boolean myPhysical
;
49 private final AtomicReference
<PsiFile
> myPsiFile
= new AtomicReference
<PsiFile
>();
50 private volatile Content myContent
;
51 private volatile SoftReference
<Document
> myDocument
;
52 private final Language myBaseLanguage
;
54 public SingleRootFileViewProvider(@NotNull PsiManager manager
, @NotNull VirtualFile file
) {
55 this(manager
, file
, true);
58 public SingleRootFileViewProvider(@NotNull PsiManager manager
, @NotNull VirtualFile virtualFile
, final boolean physical
) {
59 this(manager
, virtualFile
, physical
, calcBaseLanguage(virtualFile
, manager
.getProject()));
62 private SingleRootFileViewProvider(@NotNull PsiManager manager
, @NotNull VirtualFile virtualFile
, final boolean physical
, @NotNull Language language
) {
64 myVirtualFile
= virtualFile
;
65 myEventSystemEnabled
= physical
;
66 myBaseLanguage
= language
;
67 setContent(new VirtualFileContent());
68 myPhysical
= isEventSystemEnabled() &&
69 !(virtualFile
instanceof LightVirtualFile
) &&
70 !(virtualFile
.getFileSystem() instanceof DummyFileSystem
) &&
71 !(virtualFile
.getFileSystem() instanceof TempFileSystem
);
75 public Language
getBaseLanguage() {
76 return myBaseLanguage
;
79 private static Language
calcBaseLanguage(VirtualFile file
, Project project
) {
80 if (file
instanceof LightVirtualFile
) {
81 final Language language
= ((LightVirtualFile
)file
).getLanguage();
82 if (language
!= null) {
87 final FileType fileType
= file
.getFileType();
88 if (fileType
.isBinary()) return Language
.ANY
;
89 if (isTooLarge(file
)) return PlainTextLanguage
.INSTANCE
;
91 if (fileType
instanceof LanguageFileType
) {
92 return LanguageSubstitutors
.INSTANCE
.substituteLanguage(((LanguageFileType
)fileType
).getLanguage(), file
, project
);
95 final ContentBasedClassFileProcessor
[] processors
= Extensions
.getExtensions(ContentBasedClassFileProcessor
.EP_NAME
);
96 for (ContentBasedClassFileProcessor processor
: processors
) {
97 Language language
= processor
.obtainLanguageForFile(file
);
98 if (language
!= null) return language
;
101 return PlainTextLanguage
.INSTANCE
;
105 public Set
<Language
> getLanguages() {
106 return Collections
.singleton(getBaseLanguage());
110 public final PsiFile
getPsi(@NotNull Language target
) {
112 ((PsiManagerEx
)myManager
).getFileManager().setViewProvider(getVirtualFile(), this);
114 return getPsiInner(target
);
118 public List
<PsiFile
> getAllFiles() {
119 return Collections
.singletonList(getPsi(getBaseLanguage()));
123 protected PsiFile
getPsiInner(final Language target
) {
124 if (target
!= getBaseLanguage()) {
127 PsiFile psiFile
= myPsiFile
.get();
128 if (psiFile
== null) {
129 psiFile
= createFile();
130 myPsiFile
.compareAndSet(null, psiFile
);
131 psiFile
= myPsiFile
.get();
136 public void beforeContentsSynchronized() {
140 public void contentsSynchronized() {
144 private void unsetPsiContent() {
145 if (!(myContent
instanceof PsiFileContent
)) return;
146 final Document cachedDocument
= getCachedDocument();
147 setContent(cachedDocument
== null ?
new VirtualFileContent() : new DocumentContent());
150 public void beforeDocumentChanged() {
151 final PostprocessReformattingAspect component
= myManager
.getProject().getComponent(PostprocessReformattingAspect
.class);
152 if (component
.isViewProviderLocked(this)) {
153 throw new RuntimeException("Document is locked by write PSI operations. Use PsiDocumentManager.doPostponedOperationsAndUnblockDocument() to commit PSI changes to the document.");
155 component
.doPostponedFormatting();
156 final PsiFileImpl psiFile
= (PsiFileImpl
)getCachedPsi(getBaseLanguage());
157 if (psiFile
!= null && psiFile
.isContentsLoaded() && getContent()instanceof DocumentContent
) {
158 setContent(new PsiFileContent(psiFile
, getModificationStamp()));
162 public void rootChanged(PsiFile psiFile
) {
163 if (((PsiFileEx
)psiFile
).isContentsLoaded()) {
164 setContent(new PsiFileContent((PsiFileImpl
)psiFile
, LocalTimeCounter
.currentTime()));
168 public boolean isEventSystemEnabled() {
169 return myEventSystemEnabled
;
172 public boolean isPhysical() {
176 public long getModificationStamp() {
177 return getContent().getModificationStamp();
180 public boolean supportsIncrementalReparse(final Language rootLanguage
) {
185 public PsiFile
getCachedPsi(Language target
) {
186 return myPsiFile
.get();
189 public FileElement
[] getKnownTreeRoots() {
190 PsiFile psiFile
= myPsiFile
.get();
191 if (psiFile
== null || !(psiFile
instanceof PsiFileImpl
)) return new FileElement
[0];
192 if (((PsiFileImpl
)psiFile
).getTreeElement() == null) return new FileElement
[0];
193 return new FileElement
[]{(FileElement
)psiFile
.getNode()};
196 private PsiFile
createFile() {
199 final VirtualFile vFile
= getVirtualFile();
200 if (vFile
.isDirectory()) return null;
201 if (isIgnored()) return null;
203 final Project project
= myManager
.getProject();
204 if (isPhysical()) { // check directories consistency
205 final VirtualFile parent
= vFile
.getParent();
206 if (parent
== null) return null;
207 final PsiDirectory psiDir
= getManager().findDirectory(parent
);
208 if (psiDir
== null) return null;
211 return creatFile(project
, vFile
, vFile
.getFileType());
213 catch (ProcessCanceledException e
) {
216 catch (Throwable e
) {
222 protected boolean isIgnored() {
223 final VirtualFile file
= getVirtualFile();
224 if (file
instanceof LightVirtualFile
) return false;
225 final FileTypeManager fileTypeManager
= FileTypeManager
.getInstance();
226 return fileTypeManager
.isFileIgnored(file
.getName());
230 protected PsiFile
creatFile(final Project project
, final VirtualFile vFile
, final FileType fileType
) {
231 if (fileType
.isBinary()) {
232 return new PsiBinaryFileImpl((PsiManagerImpl
)getManager(), this);
235 if (!isTooLarge(vFile
)) {
236 final PsiFile psiFile
= createFile(getBaseLanguage());
237 if (psiFile
!= null) return psiFile
;
240 return new PsiPlainTextFileImpl(this);
243 public static boolean isTooLarge(final VirtualFile vFile
) {
244 return FileManagerImpl
.MAX_INTELLISENSE_FILESIZE
!= -1 && fileSizeIsGreaterThan(vFile
, FileManagerImpl
.MAX_INTELLISENSE_FILESIZE
);
247 private static boolean fileSizeIsGreaterThan(final VirtualFile vFile
, final long maxInBytes
) {
248 if (vFile
instanceof LightVirtualFile
) {
249 // This is optimization in order to avoid conversion of [large] file contents to bytes
250 final int lengthInChars
= ((LightVirtualFile
)vFile
).getContent().length();
251 if (lengthInChars
< maxInBytes
/ 2) return false;
252 if (lengthInChars
> maxInBytes
) return true;
255 return vFile
.getLength() > maxInBytes
;
259 protected PsiFile
createFile(Language lang
) {
260 if (lang
!= getBaseLanguage()) return null;
261 final ParserDefinition parserDefinition
= LanguageParserDefinitions
.INSTANCE
.forLanguage(lang
);
262 if (parserDefinition
!= null) {
263 return parserDefinition
.createFile(this);
269 public PsiManager
getManager() {
274 public CharSequence
getContents() {
275 return getContent().getText();
279 public VirtualFile
getVirtualFile() {
280 return myVirtualFile
;
284 private Document
getCachedDocument() {
285 final Document document
= myDocument
!= null ? myDocument
.get() : null;
286 if (document
!= null) return document
;
287 return FileDocumentManager
.getInstance().getCachedDocument(getVirtualFile());
290 public Document
getDocument() {
291 Document document
= myDocument
!= null ? myDocument
.get() : null;
292 if (document
== null/* TODO[ik] make this change && isEventSystemEnabled()*/) {
293 document
= FileDocumentManager
.getInstance().getDocument(getVirtualFile());
294 myDocument
= new SoftReference
<Document
>(document
);
296 if (document
!= null && getContent() instanceof VirtualFileContent
) {
297 setContent(new DocumentContent());
302 public FileViewProvider
clone() {
303 final VirtualFile origFile
= getVirtualFile();
304 LightVirtualFile copy
= new LightVirtualFile(origFile
.getName(), origFile
.getFileType(), getContents(), origFile
.getCharset(), getModificationStamp());
305 copy
.putUserData(UndoManager
.DONT_RECORD_UNDO
, Boolean
.TRUE
);
306 copy
.setCharset(origFile
.getCharset());
307 return createCopy(copy
);
310 public SingleRootFileViewProvider
createCopy(final LightVirtualFile copy
) {
311 return new SingleRootFileViewProvider(getManager(), copy
, false, myBaseLanguage
);
314 public PsiReference
findReferenceAt(final int offset
) {
315 final PsiFileImpl psiFile
= (PsiFileImpl
)getPsi(getBaseLanguage());
316 return findReferenceAt(psiFile
, offset
);
319 public PsiElement
findElementAt(final int offset
, final Language language
) {
320 final PsiFile psiFile
= getPsi(language
);
321 return psiFile
!= null ?
findElementAt(psiFile
, offset
) : null;
325 public PsiReference
findReferenceAt(final int offset
, @NotNull final Language language
) {
326 final PsiFile psiFile
= getPsi(language
);
327 return psiFile
!= null ?
findReferenceAt(psiFile
, offset
) : null;
330 public boolean isLockedByPsiOperations() {
331 final PostprocessReformattingAspect component
= myManager
.getProject().getComponent(PostprocessReformattingAspect
.class);
332 return component
.isViewProviderLocked(this);
336 private static PsiReference
findReferenceAt(final PsiFile psiFile
, final int offset
) {
337 if (psiFile
== null) return null;
338 int offsetInElement
= offset
;
339 PsiElement child
= psiFile
.getFirstChild();
340 while (child
!= null) {
341 final int length
= child
.getTextLength();
342 if (length
<= offsetInElement
) {
343 offsetInElement
-= length
;
344 child
= child
.getNextSibling();
347 return child
.findReferenceAt(offsetInElement
);
352 public PsiElement
findElementAt(final int offset
) {
353 return findElementAt(getPsi(getBaseLanguage()), offset
);
357 public PsiElement
findElementAt(int offset
, Class
<?
extends Language
> lang
) {
358 if (!ReflectionCache
.isAssignable(lang
, getBaseLanguage().getClass())) return null;
359 return findElementAt(offset
);
363 protected static PsiElement
findElementAt(final PsiElement psiFile
, final int offset
) {
364 if (psiFile
== null) return null;
365 int offsetInElement
= offset
;
366 PsiElement child
= psiFile
.getFirstChild();
367 while (child
!= null) {
368 final int length
= child
.getTextLength();
369 if (length
<= offsetInElement
) {
370 offsetInElement
-= length
;
371 child
= child
.getNextSibling();
374 return child
.findElementAt(offsetInElement
);
379 public void forceCachedPsi(final PsiFile psiFile
) {
380 myPsiFile
.set(psiFile
);
381 ((PsiManagerEx
)myManager
).getFileManager().setViewProvider(getVirtualFile(), this);
384 private Content
getContent() {
388 private void setContent(final Content content
) {
392 private interface Content
{
393 CharSequence
getText();
395 long getModificationStamp();
398 private class VirtualFileContent
implements Content
{
399 public CharSequence
getText() {
400 final VirtualFile virtualFile
= getVirtualFile();
401 if (virtualFile
instanceof LightVirtualFile
) {
402 Document doc
= getCachedDocument();
403 if (doc
!= null) return doc
.getCharsSequence();
404 return ((LightVirtualFile
)virtualFile
).getContent();
407 final Document document
= getDocument();
408 if (document
== null) {
409 return LoadTextUtil
.loadText(virtualFile
);
412 return document
.getCharsSequence();
416 public long getModificationStamp() {
417 return getVirtualFile().getModificationStamp();
421 private class DocumentContent
implements Content
{
422 public CharSequence
getText() {
423 final Document document
= getDocument();
424 assert document
!= null;
425 return document
.getCharsSequence();
428 public long getModificationStamp() {
429 Document document
= myDocument
== null ?
null : myDocument
.get();
430 if (document
!= null) return document
.getModificationStamp();
431 return myVirtualFile
.getModificationStamp();
435 private class PsiFileContent
implements Content
{
436 private final PsiFileImpl myFile
;
437 private CharSequence myContent
= null;
438 private final long myModificationStamp
;
440 private PsiFileContent(final PsiFileImpl file
, final long modificationStamp
) {
442 myModificationStamp
= modificationStamp
;
445 public CharSequence
getText() {
446 if (!myFile
.isContentsLoaded()) {
448 return getContents();
450 if (myContent
!= null) return myContent
;
451 return myContent
= myFile
.calcTreeElement().getText();
454 public long getModificationStamp() {
455 if (!myFile
.isContentsLoaded()) {
457 return SingleRootFileViewProvider
.this.getModificationStamp();
459 return myModificationStamp
;