update todo tree on removing todo comment in a file
[fedora-idea.git] / platform / lang-impl / src / com / intellij / ide / todo / TodoTreeBuilder.java
blob1cd586e1cc2912d230a7265c0b966a6884781eae
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.
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;
51 import javax.swing.*;
52 import javax.swing.tree.DefaultMutableTreeNode;
53 import javax.swing.tree.DefaultTreeModel;
54 import javax.swing.tree.TreePath;
55 import java.util.*;
57 /**
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;
64 /**
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;
69 /**
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;
80 /**
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);
93 myProject = project;
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
111 * been invoked.
113 public final void init() {
114 TodoTreeStructure todoTreeStructure = createTreeStructure();
115 setTreeStructure(todoTreeStructure);
116 todoTreeStructure.setTreeBuilder(this);
118 rebuildCache();
119 initRootNode();
121 Object selectableElement = todoTreeStructure.getFirstSelectableElement();
122 if (selectableElement != null) {
123 buildNodeForElement(selectableElement);
124 DefaultMutableTreeNode node = getNodeForElement(selectableElement);
125 if (node != null) {
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);
135 super.dispose();
138 final boolean isUpdatable() {
139 return myUpdatable;
143 * Sets whenther the builder updates the tree when data change.
145 final void setUpdatable(boolean updatable) {
146 if (myUpdatable != updatable) {
147 myUpdatable = updatable;
148 if (updatable) {
149 DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
150 public void run() {
151 updateTree(false);
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) {
165 return false;
168 return true;
171 public final TodoTreeStructure getTodoTreeStructure() {
172 return (TodoTreeStructure)getTreeStructure();
175 protected final AbstractTreeUpdater createUpdater() {
176 return new AbstractTreeUpdater(this) {
177 @Override
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() {
182 public void run() {
183 try {
184 validateCache();
185 getTodoTreeStructure().validateCache();
187 finally {
188 callback.setDone();
192 return callback;
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()) {
218 return null;
220 PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
221 if (psiFile == null || !psiFile.isValid()) {
222 return null;
224 return psiFile;
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
337 * have happend.
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());
374 i.remove();
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){
387 return false;
388 } else if(element instanceof PsiFileNode) {
389 return getTodoTreeStructure().mySearchHelper.getTodoItemsCount(((PsiFileNode)element).getValue()) > 0;
391 return true;
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;
402 else {
403 Object[] children = getTreeStructure().getChildElements(element);
404 if (children.length == 0) {
405 return null;
407 Object firstChild = children[0];
408 if (firstChild instanceof TodoItemNode) {
409 return (TodoItemNode)firstChild;
411 else {
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;
425 else {
426 Object[] children = getTreeStructure().getChildElements(element);
427 if (children.length == 0) {
428 return null;
430 Object firstChild = children[children.length - 1];
431 if (firstChild instanceof TodoItemNode) {
432 return (TodoItemNode)firstChild;
434 else {
435 return getLastPointerForElement(firstChild);
440 protected final void updateTree(boolean later) {
441 if (myUpdatable) {
442 getUpdater().addSubtreeToUpdate(getRootNode());
443 if (!later) {
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();
458 return null;
461 void collapseAll() {
462 int row = getTree().getRowCount() - 1;
463 while (row > 0) {
464 getTree().collapseRow(row);
465 row--;
469 void expandAll() {
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();
485 updateTree(false);
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();
500 updateTree(false);
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);
511 rebuildCache();
512 updateTree(false);
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) {
522 return null;
524 if (sibling instanceof TodoItemNode) {
525 return (TodoItemNode)sibling;
527 else {
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) {
539 return null;
541 Object[] children = getTreeStructure().getChildElements(parent);
542 int idx = -1;
543 for (int i = 0; i < children.length; i++) {
544 if (obj.equals(children[i])) {
545 idx = i;
546 break;
549 if (idx == -1) {
550 return null;
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) {
567 return null;
569 if (sibling instanceof TodoItemNode) {
570 return (TodoItemNode)sibling;
572 else {
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) {
584 return null;
586 Object[] children = getTreeStructure().getChildElements(parent);
587 int idx = -1;
588 for (int i = 0; i < children.length; i++) {
589 if (obj.equals(children[i])) {
590 idx = i;
592 break;
595 if (idx == -1) {
596 return null;
598 if (idx > 0) {
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);
615 else {
616 EditorHighlighter highlighter = HighlighterFactory.createHighlighter(UsageTreeColorsScheme.getInstance().getScheme(), file.getName(), myProject);
617 highlighter.setText(document.getCharsSequence());
618 myFile2Highlighter.put(file, highlighter);
619 return 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();
630 updateTree(false);
631 TreeBuilderUtil.restorePaths(this, pathsToExpand, pathsToSelect, true);
634 public boolean isDirectoryEmpty(@NotNull PsiDirectory psiDirectory){
635 return myFileTree.isDirectoryEmpty(psiDirectory.getVirtualFile());
638 @NotNull
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;
652 else {
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());
663 updateTree(true);
664 return;
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)) {
669 return;
671 PsiFile psiFile = (PsiFile)e.getChild();
672 markFileAsDirty(psiFile);
673 updateTree(true);
676 public void beforeChildRemoval(PsiTreeChangeEvent e) {
677 // If local midification
678 final PsiFile file = e.getFile();
679 if (file != null) {
680 markFileAsDirty(file);
681 updateTree(true);
682 return;
685 PsiElement child = e.getChild();
686 if (child instanceof PsiFile) { // file will be removed
687 PsiFile psiFile = (PsiFile)child;
688 markFileAsDirty(psiFile);
689 updateTree(true);
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
696 continue;
698 if (PsiTreeUtil.isAncestor(psiDirectory, psiFile, true)) {
699 markFileAsDirty(psiFile);
702 updateTree(true);
704 else {
705 if (PsiTreeUtil.getParentOfType(child, PsiComment.class, false) != null) { // change inside comment
706 markFileAsDirty(child.getContainingFile());
707 updateTree(true);
712 public void childMoved(PsiTreeChangeEvent e) {
713 if (e.getFile() != null) { // local change
714 markFileAsDirty(e.getFile());
715 updateTree(true);
716 return;
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
721 return;
723 markFileAsDirty(psiFile);
724 updateTree(true);
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
732 continue;
734 if (PsiTreeUtil.isAncestor(psiDirectory, psiFile, true)) {
735 markFileAsDirty(psiFile);
736 shouldUpdate = true;
739 if (shouldUpdate) {
740 updateTree(true);
745 public void childReplaced(PsiTreeChangeEvent e) {
746 if (e.getFile() != null) {
747 markFileAsDirty(e.getFile());
748 updateTree(true);
752 public void childrenChanged(PsiTreeChangeEvent e) {
753 if (e.getFile() != null) {
754 markFileAsDirty(e.getFile());
755 updateTree(true);
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(
763 new Runnable() {
764 public void run() {
765 rebuildCache();
769 updateTree(true);
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
774 return;
776 updateTree(true);
778 else if (PsiTreeChangeEvent.PROP_FILE_NAME.equals(propertyName)) {
779 PsiFile psiFile = (PsiFile)e.getElement();
780 if (!canContainTodoItems(psiFile)) {
781 return;
783 updateTree(true);
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()) {
789 updateTree(true);
795 private final class MyFileStatusListener implements FileStatusListener {
796 public void fileStatusesChanged() {
797 updateTree(true);
800 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
801 PsiFile psiFile = PsiManager.getInstance(myProject).findFile(virtualFile);
802 if (psiFile != null && canContainTodoItems(psiFile)) {
803 updateTree(true);