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.
17 package com
.intellij
.ide
.todo
;
19 import com
.intellij
.ide
.highlighter
.HighlighterFactory
;
20 import com
.intellij
.ide
.projectView
.ProjectViewNode
;
21 import com
.intellij
.ide
.projectView
.impl
.nodes
.PsiFileNode
;
22 import com
.intellij
.ide
.todo
.nodes
.TodoFileNode
;
23 import com
.intellij
.ide
.todo
.nodes
.TodoItemNode
;
24 import com
.intellij
.ide
.todo
.nodes
.TodoTreeHelper
;
25 import com
.intellij
.ide
.util
.treeView
.*;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.editor
.Document
;
28 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
29 import com
.intellij
.openapi
.module
.Module
;
30 import com
.intellij
.openapi
.module
.ModuleUtil
;
31 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
32 import com
.intellij
.openapi
.progress
.util
.StatusBarProgress
;
33 import com
.intellij
.openapi
.project
.DumbService
;
34 import com
.intellij
.openapi
.project
.Project
;
35 import com
.intellij
.openapi
.roots
.ModuleRootManager
;
36 import com
.intellij
.openapi
.roots
.ProjectFileIndex
;
37 import com
.intellij
.openapi
.roots
.ProjectRootManager
;
38 import com
.intellij
.openapi
.util
.ActionCallback
;
39 import com
.intellij
.openapi
.util
.AsyncResult
;
40 import com
.intellij
.openapi
.vcs
.FileStatusListener
;
41 import com
.intellij
.openapi
.vcs
.FileStatusManager
;
42 import com
.intellij
.openapi
.vfs
.VirtualFile
;
43 import com
.intellij
.psi
.*;
44 import com
.intellij
.psi
.search
.PsiSearchHelper
;
45 import com
.intellij
.psi
.util
.PsiTreeUtil
;
46 import com
.intellij
.usageView
.UsageTreeColorsScheme
;
47 import com
.intellij
.util
.containers
.HashMap
;
48 import org
.jetbrains
.annotations
.NotNull
;
49 import org
.jetbrains
.annotations
.Nullable
;
52 import javax
.swing
.tree
.DefaultMutableTreeNode
;
53 import javax
.swing
.tree
.DefaultTreeModel
;
54 import javax
.swing
.tree
.TreePath
;
58 * @author Vladimir Kondratyev
60 public abstract class TodoTreeBuilder
extends AbstractTreeBuilder
{
61 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.todo.TodoTreeBuilder");
62 protected final Project myProject
;
65 * All files that have T.O.D.O items are presented as tree. This tree help a lot
66 * to separate these files by directories.
68 protected final FileTree myFileTree
;
70 * This set contains "dirty" files. File is "dirty" if it's currently not nkown
71 * whether the file contains T.O.D.O item or not. To determine this it's necessary
72 * to perform some (perhaps, CPU expensive) operation. These "dirty" files are
73 * validated in <code>validateCache()</code> method.
75 protected final HashSet
<VirtualFile
> myDirtyFileSet
;
77 protected final HashMap
<VirtualFile
, EditorHighlighter
> myFile2Highlighter
;
79 protected final PsiSearchHelper mySearchHelper
;
81 * If this flag is false then the updateTree() method does nothing. But when
82 * the flag becomes true and myDirtyFileSet isn't empty the update is invoked.
83 * This is done for optimization reasons: if TodoPane is not visible then
84 * updates isn't invoked.
86 private boolean myUpdatable
;
88 /** Updates tree if containing files change VCS status. */
89 private final MyFileStatusListener myFileStatusListener
;
91 TodoTreeBuilder(JTree tree
, DefaultTreeModel treeModel
, Project project
) {
92 super(tree
, treeModel
, null, MyComparator
.ourInstance
, false);
95 myFileTree
= new FileTree();
96 myDirtyFileSet
= new HashSet
<VirtualFile
>();
98 myFile2Highlighter
= new HashMap
<VirtualFile
, EditorHighlighter
>();
100 PsiManager psiManager
= PsiManager
.getInstance(myProject
);
101 mySearchHelper
= psiManager
.getSearchHelper();
102 psiManager
.addPsiTreeChangeListener(new MyPsiTreeChangeListener());
104 myFileStatusListener
= new MyFileStatusListener();
106 getUpdater().setDelay(1500);
110 * Initializes the builder. Subclasses should don't forget to call this method after constuctor has
113 public final void init() {
114 TodoTreeStructure todoTreeStructure
= createTreeStructure();
115 setTreeStructure(todoTreeStructure
);
116 todoTreeStructure
.setTreeBuilder(this);
121 Object selectableElement
= todoTreeStructure
.getFirstSelectableElement();
122 if (selectableElement
!= null) {
123 buildNodeForElement(selectableElement
);
124 DefaultMutableTreeNode node
= getNodeForElement(selectableElement
);
126 getTree().getSelectionModel().setSelectionPath(new TreePath(node
.getPath()));
130 FileStatusManager
.getInstance(myProject
).addFileStatusListener(myFileStatusListener
);
133 public final void dispose() {
134 FileStatusManager
.getInstance(myProject
).removeFileStatusListener(myFileStatusListener
);
138 final boolean isUpdatable() {
143 * Sets whenther the builder updates the tree when data change.
145 final void setUpdatable(boolean updatable
) {
146 if (myUpdatable
!= updatable
) {
147 myUpdatable
= updatable
;
149 DumbService
.getInstance(myProject
).runWhenSmart(new Runnable() {
158 protected abstract TodoTreeStructure
createTreeStructure();
160 protected boolean validateNode(final Object child
) {
161 if (child
instanceof ProjectViewNode
) {
162 final ProjectViewNode projectViewNode
= (ProjectViewNode
)child
;
163 projectViewNode
.update();
164 if (projectViewNode
.getValue() == null) {
171 public final TodoTreeStructure
getTodoTreeStructure() {
172 return (TodoTreeStructure
)getTreeStructure();
175 protected final AbstractTreeUpdater
createUpdater() {
176 return new AbstractTreeUpdater(this) {
178 protected ActionCallback
beforeUpdate(final TreeUpdatePass pass
) {
179 if (!myDirtyFileSet
.isEmpty()) { // suppress redundant cache validations
180 final AsyncResult callback
= new AsyncResult();
181 DumbService
.getInstance(myProject
).runWhenSmart(new Runnable() {
185 getTodoTreeStructure().validateCache();
195 return new ActionCallback
.Done();
201 * @return read-only iterator of all current PSI files that can contain TODOs.
202 * Don't invoke its <code>remove</code> method. For "removing" use <code>markFileAsDirty</code> method.
203 * <b>Note, that <code>next()</code> method of iterator can return <code>null</code> elements.</b>
204 * These <code>null</code> elements correspond to the invalid PSI files (PSI file cannot be found by
205 * virtual file, or virtual file is invalid).
206 * The reason why we return such "dirty" iterator is the peformance.
208 public Iterator
<PsiFile
> getAllFiles() {
209 final Iterator
<VirtualFile
> iterator
= myFileTree
.getFileIterator();
210 return new Iterator
<PsiFile
>() {
211 public boolean hasNext() {
212 return iterator
.hasNext();
215 @Nullable public PsiFile
next() {
216 VirtualFile vFile
= iterator
.next();
217 if (vFile
== null || !vFile
.isValid()) {
220 PsiFile psiFile
= PsiManager
.getInstance(myProject
).findFile(vFile
);
221 if (psiFile
== null || !psiFile
.isValid()) {
227 public void remove() {
228 throw new IllegalArgumentException();
234 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
235 * and which are located under specified <code>psiDirctory</code>.
236 * @see com.intellij.ide.todo.FileTree#getFiles(com.intellij.openapi.vfs.VirtualFile)
238 public Iterator
<PsiFile
> getFiles(PsiDirectory psiDirectory
) {
239 return getFiles(psiDirectory
, true);
243 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
244 * and which are located under specified <code>psiDirctory</code>.
245 * @see FileTree#getFiles(VirtualFile)
247 public Iterator
<PsiFile
> getFiles(PsiDirectory psiDirectory
, final boolean skip
) {
248 ArrayList
<VirtualFile
> files
= myFileTree
.getFiles(psiDirectory
.getVirtualFile());
249 ArrayList
<PsiFile
> psiFileList
= new ArrayList
<PsiFile
>(files
.size());
250 PsiManager psiManager
= PsiManager
.getInstance(myProject
);
251 for (VirtualFile file
: files
) {
252 final Module module
= ModuleUtil
.findModuleForPsiElement(psiDirectory
);
253 if (module
!= null) {
254 final boolean isInContent
= ModuleRootManager
.getInstance(module
).getFileIndex().isInContent(file
);
255 if (!isInContent
) continue;
257 if (file
.isValid()) {
258 PsiFile psiFile
= psiManager
.findFile(file
);
259 if (psiFile
!= null) {
260 final PsiDirectory directory
= psiFile
.getContainingDirectory();
261 if (directory
== null || !skip
|| !TodoTreeHelper
.getInstance(myProject
).skipDirectory(directory
)) {
262 psiFileList
.add(psiFile
);
267 return psiFileList
.iterator();
271 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
272 * and which are located under specified <code>psiDirctory</code>.
273 * @see FileTree#getFiles(VirtualFile)
275 public Iterator
<PsiFile
> getFilesUnderDirectory(PsiDirectory psiDirectory
) {
276 ArrayList
<VirtualFile
> files
= myFileTree
.getFilesUnderDirectory(psiDirectory
.getVirtualFile());
277 ArrayList
<PsiFile
> psiFileList
= new ArrayList
<PsiFile
>(files
.size());
278 PsiManager psiManager
= PsiManager
.getInstance(myProject
);
279 for (VirtualFile file
: files
) {
280 final Module module
= ModuleUtil
.findModuleForPsiElement(psiDirectory
);
281 if (module
!= null) {
282 final boolean isInContent
= ModuleRootManager
.getInstance(module
).getFileIndex().isInContent(file
);
283 if (!isInContent
) continue;
285 if (file
.isValid()) {
286 PsiFile psiFile
= psiManager
.findFile(file
);
287 if (psiFile
!= null) {
288 psiFileList
.add(psiFile
);
292 return psiFileList
.iterator();
298 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
299 * and which in specified <code>module</code>.
300 * @see FileTree#getFiles(VirtualFile)
302 public Iterator
<PsiFile
> getFiles(Module module
) {
303 if (module
.isDisposed()) return Collections
.<PsiFile
>emptyList().iterator();
304 ArrayList
<PsiFile
> psiFileList
= new ArrayList
<PsiFile
>();
305 final ProjectFileIndex fileIndex
= ProjectRootManager
.getInstance(myProject
).getFileIndex();
306 final VirtualFile
[] contentRoots
= ModuleRootManager
.getInstance(module
).getContentRoots();
307 for (VirtualFile virtualFile
: contentRoots
) {
308 ArrayList
<VirtualFile
> files
= myFileTree
.getFiles(virtualFile
);
309 PsiManager psiManager
= PsiManager
.getInstance(myProject
);
310 for (VirtualFile file
: files
) {
311 if (fileIndex
.getModuleForFile(file
) != module
) continue;
312 if (file
.isValid()) {
313 PsiFile psiFile
= psiManager
.findFile(file
);
314 if (psiFile
!= null) {
315 psiFileList
.add(psiFile
);
320 return psiFileList
.iterator();
325 * @return <code>true</code> if specified <code>psiFile</code> can contains too items.
326 * It means that file is in "dirty" file set or in "current" file set.
328 private boolean canContainTodoItems(PsiFile psiFile
) {
329 VirtualFile vFile
= psiFile
.getVirtualFile();
330 return myFileTree
.contains(vFile
) || myDirtyFileSet
.contains(vFile
);
334 * Marks specified PsiFile as dirty. It means that file is being add into "dirty" file set.
335 * It presents in current file set also but the next validateCache call will validate this
336 * "dirty" file. This method should be invoked when any modifications inside the file
339 private void markFileAsDirty(@NotNull PsiFile psiFile
) {
340 VirtualFile vFile
= psiFile
.getVirtualFile();
341 if (vFile
!= null) { // If PSI file isn't valid then its VirtualFile can be null
342 myDirtyFileSet
.add(vFile
);
347 * Clear and rebuild whole the caches. It means that after rebuilding all caches are valid.
349 abstract void rebuildCache();
351 private void validateCache() {
352 TodoTreeStructure treeStructure
= getTodoTreeStructure();
353 // First of all we need to update "dirty" file set.
354 for (Iterator
<VirtualFile
> i
= myDirtyFileSet
.iterator(); i
.hasNext();) {
355 VirtualFile file
= i
.next();
356 PsiFile psiFile
= file
.isValid() ? PsiManager
.getInstance(myProject
).findFile(file
) : null;
357 if (psiFile
== null || !treeStructure
.accept(psiFile
)) {
358 if (myFileTree
.contains(file
)) {
359 myFileTree
.removeFile(file
);
360 if (myFile2Highlighter
.containsKey(file
)) { // highlighter isn't needed any more
361 myFile2Highlighter
.remove(file
);
365 else { // file is valid and contains T.O.D.O items
366 myFileTree
.removeFile(file
);
367 myFileTree
.add(file
); // file can be moved. remove/add calls move it to another place
368 if (myFile2Highlighter
.containsKey(file
)) { // update highlighter's text
369 Document document
= PsiDocumentManager
.getInstance(myProject
).getDocument(psiFile
);
370 EditorHighlighter highlighter
= myFile2Highlighter
.get(file
);
371 highlighter
.setText(document
.getCharsSequence());
376 LOG
.assertTrue(myDirtyFileSet
.isEmpty());
377 // Now myDirtyFileSet should be empty
380 protected boolean isAutoExpandNode(NodeDescriptor descriptor
) {
381 return getTodoTreeStructure().isAutoExpandNode(descriptor
);
384 protected boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor
) {
385 final Object element
= nodeDescriptor
.getElement();
386 if (element
instanceof TodoItemNode
){
388 } else if(element
instanceof PsiFileNode
) {
389 return getTodoTreeStructure().mySearchHelper
.getTodoItemsCount(((PsiFileNode
)element
).getValue()) > 0;
395 * @return first <code>SmartTodoItemPointer</code> that is the children (in depth) of the specified <code>element</code>.
396 * If <code>element</code> itself is a <code>TodoItem</code> then the method returns the <code>element</code>.
398 public TodoItemNode
getFirstPointerForElement(Object element
) {
399 if (element
instanceof TodoItemNode
) {
400 return (TodoItemNode
)element
;
403 Object
[] children
= getTreeStructure().getChildElements(element
);
404 if (children
.length
== 0) {
407 Object firstChild
= children
[0];
408 if (firstChild
instanceof TodoItemNode
) {
409 return (TodoItemNode
)firstChild
;
412 return getFirstPointerForElement(firstChild
);
418 * @return last <code>SmartTodoItemPointer</code> that is the children (in depth) of the specified <code>element</code>.
419 * If <code>element</code> itself is a <code>TodoItem</code> then the method returns the <code>element</code>.
421 public TodoItemNode
getLastPointerForElement(Object element
) {
422 if (element
instanceof TodoItemNode
) {
423 return (TodoItemNode
)element
;
426 Object
[] children
= getTreeStructure().getChildElements(element
);
427 if (children
.length
== 0) {
430 Object firstChild
= children
[children
.length
- 1];
431 if (firstChild
instanceof TodoItemNode
) {
432 return (TodoItemNode
)firstChild
;
435 return getLastPointerForElement(firstChild
);
440 protected final void updateTree(boolean later
) {
442 getUpdater().addSubtreeToUpdate(getRootNode());
444 getUpdater().performUpdate();
449 static PsiFile
getFileForNode(DefaultMutableTreeNode node
) {
450 Object obj
= node
.getUserObject();
451 if (obj
instanceof TodoFileNode
) {
452 return ((TodoFileNode
)obj
).getValue();
454 else if (obj
instanceof TodoItemNode
) {
455 SmartTodoItemPointer pointer
= ((TodoItemNode
)obj
).getValue();
456 return pointer
.getTodoItem().getFile();
462 int row
= getTree().getRowCount() - 1;
464 getTree().collapseRow(row
);
470 for (int i
= 0; i
< getTree().getRowCount(); i
++) {
471 getTree().expandRow(i
);
476 * Sets whether packages are shown or not.
478 void setShowPackages(boolean state
) {
479 getTodoTreeStructure().setShownPackages(state
);
480 ArrayList
<Object
> pathsToExpand
= new ArrayList
<Object
>();
481 ArrayList
<Object
> pathsToSelect
= new ArrayList
<Object
>();
482 TreeBuilderUtil
.storePaths(this, getRootNode(), pathsToExpand
, pathsToSelect
, true);
483 getTree().clearSelection();
484 getTodoTreeStructure().validateCache();
486 TreeBuilderUtil
.restorePaths(this, pathsToExpand
, pathsToSelect
, true);
490 * @param state if <code>true</code> then view is in "flatten packages" mode.
492 void setFlattenPackages(boolean state
) {
493 ArrayList
<Object
> pathsToExpand
= new ArrayList
<Object
>();
494 ArrayList
<Object
> pathsToSelect
= new ArrayList
<Object
>();
495 TreeBuilderUtil
.storePaths(this, getRootNode(), pathsToExpand
, pathsToSelect
, true);
496 getTree().clearSelection();
497 TodoTreeStructure todoTreeStructure
= getTodoTreeStructure();
498 todoTreeStructure
.setFlattenPackages(state
);
499 todoTreeStructure
.validateCache();
501 TreeBuilderUtil
.restorePaths(this, pathsToExpand
, pathsToSelect
, true);
505 * Sets new <code>TodoFilter</code>, rebuild whole the caches and immediately update the tree.
507 * @see TodoTreeStructure#setTodoFilter
509 void setTodoFilter(TodoFilter filter
) {
510 getTodoTreeStructure().setTodoFilter(filter
);
516 * @return next <code>TodoItem</code> for the passed <code>pointer</code>. Returns <code>null</code>
517 * if the <code>pointer</code> is the last t.o.d.o item in the tree.
519 public TodoItemNode
getNextPointer(TodoItemNode pointer
) {
520 Object sibling
= getNextSibling(pointer
);
521 if (sibling
== null) {
524 if (sibling
instanceof TodoItemNode
) {
525 return (TodoItemNode
)sibling
;
528 return getFirstPointerForElement(sibling
);
533 * @return next sibling of the passed element. If there is no sibling then
534 * returns <code>null</code>.
536 Object
getNextSibling(Object obj
) {
537 Object parent
= getTreeStructure().getParentElement(obj
);
538 if (parent
== null) {
541 Object
[] children
= getTreeStructure().getChildElements(parent
);
543 for (int i
= 0; i
< children
.length
; i
++) {
544 if (obj
.equals(children
[i
])) {
552 if (idx
< children
.length
- 1) {
553 return children
[idx
+ 1];
555 // passed object is the last in the list. In this case we have to return first child of the
556 // next parent's sibling.
557 return getNextSibling(parent
);
561 * @return next <code>SmartTodoItemPointer</code> for the passed <code>pointer</code>. Returns <code>null</code>
562 * if the <code>pointer</code> is the last t.o.d.o item in the tree.
564 public TodoItemNode
getPreviousPointer(TodoItemNode pointer
) {
565 Object sibling
= getPreviousSibling(pointer
);
566 if (sibling
== null) {
569 if (sibling
instanceof TodoItemNode
) {
570 return (TodoItemNode
)sibling
;
573 return getLastPointerForElement(sibling
);
578 * @return previous sibling of the element of passed type. If there is no sibling then
579 * returns <code>null</code>.
581 Object
getPreviousSibling(Object obj
) {
582 Object parent
= getTreeStructure().getParentElement(obj
);
583 if (parent
== null) {
586 Object
[] children
= getTreeStructure().getChildElements(parent
);
588 for (int i
= 0; i
< children
.length
; i
++) {
589 if (obj
.equals(children
[i
])) {
599 return children
[idx
- 1];
601 // passed object is the first in the list. In this case we have to return last child of the
602 // previous parent's sibling.
603 return getPreviousSibling(parent
);
607 * @return <code>SelectInEditorManager</code> for the specified <code>psiFile</code>. Highlighters are
608 * lazy created and initialized.
610 public EditorHighlighter
getHighlighter(PsiFile psiFile
, Document document
) {
611 VirtualFile file
= psiFile
.getVirtualFile();
612 if (myFile2Highlighter
.containsKey(file
)) {
613 return myFile2Highlighter
.get(file
);
616 EditorHighlighter highlighter
= HighlighterFactory
.createHighlighter(UsageTreeColorsScheme
.getInstance().getScheme(), file
.getName(), myProject
);
617 highlighter
.setText(document
.getCharsSequence());
618 myFile2Highlighter
.put(file
, highlighter
);
623 void setShowModules(boolean state
) {
624 getTodoTreeStructure().setShownModules(state
);
625 ArrayList
<Object
> pathsToExpand
= new ArrayList
<Object
>();
626 ArrayList
<Object
> pathsToSelect
= new ArrayList
<Object
>();
627 TreeBuilderUtil
.storePaths(this, getRootNode(), pathsToExpand
, pathsToSelect
, true);
628 getTree().clearSelection();
629 getTodoTreeStructure().validateCache();
631 TreeBuilderUtil
.restorePaths(this, pathsToExpand
, pathsToSelect
, true);
634 public boolean isDirectoryEmpty(@NotNull PsiDirectory psiDirectory
){
635 return myFileTree
.isDirectoryEmpty(psiDirectory
.getVirtualFile());
639 protected ProgressIndicator
createProgressIndicator() {
640 return new StatusBarProgress();
643 private static final class MyComparator
implements Comparator
<NodeDescriptor
> {
644 public static final Comparator
<NodeDescriptor
> ourInstance
= new MyComparator();
646 public int compare(NodeDescriptor descriptor1
, NodeDescriptor descriptor2
) {
647 int weight1
= descriptor1
.getWeight();
648 int weight2
= descriptor2
.getWeight();
649 if (weight1
!= weight2
) {
650 return weight1
- weight2
;
653 return descriptor1
.getIndex() - descriptor2
.getIndex();
658 private final class MyPsiTreeChangeListener
extends PsiTreeChangeAdapter
{
659 public void childAdded(PsiTreeChangeEvent e
) {
660 // If local modification
661 if (e
.getFile() != null) {
662 markFileAsDirty(e
.getFile());
666 // If added element if PsiFile and it doesn't contains TODOs, then do nothing
667 PsiElement child
= e
.getChild();
668 if (!(child
instanceof PsiFile
)) {
671 PsiFile psiFile
= (PsiFile
)e
.getChild();
672 markFileAsDirty(psiFile
);
676 public void beforeChildRemoval(PsiTreeChangeEvent e
) {
677 // If local midification
678 final PsiFile file
= e
.getFile();
680 markFileAsDirty(file
);
685 PsiElement child
= e
.getChild();
686 if (child
instanceof PsiFile
) { // file will be removed
687 PsiFile psiFile
= (PsiFile
)child
;
688 markFileAsDirty(psiFile
);
691 else if (child
instanceof PsiDirectory
) { // directory will be removed
692 PsiDirectory psiDirectory
= (PsiDirectory
)child
;
693 for (Iterator
<PsiFile
> i
= getAllFiles(); i
.hasNext();) {
694 PsiFile psiFile
= i
.next();
695 if (psiFile
== null) { // skip invalid PSI files
698 if (PsiTreeUtil
.isAncestor(psiDirectory
, psiFile
, true)) {
699 markFileAsDirty(psiFile
);
705 if (PsiTreeUtil
.getParentOfType(child
, PsiComment
.class, false) != null) { // change inside comment
706 markFileAsDirty(child
.getContainingFile());
712 public void childMoved(PsiTreeChangeEvent e
) {
713 if (e
.getFile() != null) { // local change
714 markFileAsDirty(e
.getFile());
718 if (e
.getChild() instanceof PsiFile
) { // file was moved
719 PsiFile psiFile
= (PsiFile
)e
.getChild();
720 if (!canContainTodoItems(psiFile
)) { // moved file doesn't contain TODOs
723 markFileAsDirty(psiFile
);
726 else if (e
.getChild() instanceof PsiDirectory
) { // directory was moved. mark all its files as dirty.
727 PsiDirectory psiDirectory
= (PsiDirectory
)e
.getChild();
728 boolean shouldUpdate
= false;
729 for (Iterator
<PsiFile
> i
= getAllFiles(); i
.hasNext();) {
730 PsiFile psiFile
= i
.next();
731 if (psiFile
== null) { // skip invalid PSI files
734 if (PsiTreeUtil
.isAncestor(psiDirectory
, psiFile
, true)) {
735 markFileAsDirty(psiFile
);
745 public void childReplaced(PsiTreeChangeEvent e
) {
746 if (e
.getFile() != null) {
747 markFileAsDirty(e
.getFile());
752 public void childrenChanged(PsiTreeChangeEvent e
) {
753 if (e
.getFile() != null) {
754 markFileAsDirty(e
.getFile());
759 public void propertyChanged(PsiTreeChangeEvent e
) {
760 String propertyName
= e
.getPropertyName();
761 if (propertyName
.equals(PsiTreeChangeEvent
.PROP_ROOTS
)) { // rebuild all tree when source roots were changed
762 getUpdater().runBeforeUpdate(
771 else if (PsiTreeChangeEvent
.PROP_WRITABLE
.equals(propertyName
)) {
772 PsiFile psiFile
= (PsiFile
)e
.getElement();
773 if (!canContainTodoItems(psiFile
)) { // don't do anything if file cannot contain todos
778 else if (PsiTreeChangeEvent
.PROP_FILE_NAME
.equals(propertyName
)) {
779 PsiFile psiFile
= (PsiFile
)e
.getElement();
780 if (!canContainTodoItems(psiFile
)) {
785 else if (PsiTreeChangeEvent
.PROP_DIRECTORY_NAME
.equals(propertyName
)) {
786 PsiDirectory psiDirectory
= (PsiDirectory
)e
.getElement();
787 Iterator
<PsiFile
> iterator
= getFiles(psiDirectory
);
788 if (iterator
.hasNext()) {
795 private final class MyFileStatusListener
implements FileStatusListener
{
796 public void fileStatusesChanged() {
800 public void fileStatusChanged(@NotNull VirtualFile virtualFile
) {
801 PsiFile psiFile
= PsiManager
.getInstance(myProject
).findFile(virtualFile
);
802 if (psiFile
!= null && canContainTodoItems(psiFile
)) {