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
;
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
) {
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
);
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) {
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
;
115 public Set
<Language
> getLanguages() {
116 return Collections
.singleton(getBaseLanguage());
120 public final PsiFile
getPsi(@NotNull Language target
) {
122 ((PsiManagerEx
)myManager
).getFileManager().setViewProvider(getVirtualFile(), this);
124 return getPsiInner(target
);
128 public List
<PsiFile
> getAllFiles() {
129 return Collections
.singletonList(getPsi(getBaseLanguage()));
133 protected PsiFile
getPsiInner(final Language target
) {
134 if (target
!= getBaseLanguage()) {
137 PsiFile psiFile
= myPsiFile
.get();
138 if (psiFile
== null) {
139 psiFile
= createFile();
140 myPsiFile
.compareAndSet(null, psiFile
);
141 psiFile
= myPsiFile
.get();
146 public void beforeContentsSynchronized() {
150 public void contentsSynchronized() {
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() {
186 public long getModificationStamp() {
187 return getContent().getModificationStamp();
190 public boolean supportsIncrementalReparse(final Language rootLanguage
) {
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() {
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
) {
226 catch (Throwable e
) {
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());
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
;
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);
279 public PsiManager
getManager() {
284 public CharSequence
getContents() {
285 return getContent().getText();
289 public VirtualFile
getVirtualFile() {
290 return myVirtualFile
;
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());
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
);
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;
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);
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();
358 return child
.findReferenceAt(offsetInElement
);
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
);
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();
385 return child
.findElementAt(offsetInElement
);
390 public void forceCachedPsi(final PsiFile psiFile
) {
391 myPsiFile
.set(psiFile
);
392 ((PsiManagerEx
)myManager
).getFileManager().setViewProvider(getVirtualFile(), this);
395 private Content
getContent() {
399 private void setContent(final Content 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
);
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
) {
453 myModificationStamp
= modificationStamp
;
456 public CharSequence
getText() {
457 if (!myFile
.isContentsLoaded()) {
459 return getContents();
461 if (myContent
!= null) return myContent
;
462 return myContent
= myFile
.calcTreeElement().getText();
465 public long getModificationStamp() {
466 if (!myFile
.isContentsLoaded()) {
468 return SingleRootFileViewProvider
.this.getModificationStamp();
470 return myModificationStamp
;