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.IndexNotReadyException;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.roots.ModuleRootManager;
37 import com.intellij.openapi.roots.ProjectFileIndex;
38 import com.intellij.openapi.roots.ProjectRootManager;
39 import com.intellij.openapi.util.ActionCallback;
40 import com.intellij.openapi.util.AsyncResult;
41 import com.intellij.openapi.vcs.FileStatusListener;
42 import com.intellij.openapi.vcs.FileStatusManager;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.psi.*;
45 import com.intellij.psi.search.PsiSearchHelper;
46 import com.intellij.psi.util.PsiTreeUtil;
47 import com.intellij.usageView.UsageTreeColorsScheme;
48 import com.intellij.util.containers.HashMap;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
53 import javax.swing.tree.DefaultMutableTreeNode;
54 import javax.swing.tree.DefaultTreeModel;
55 import javax.swing.tree.TreePath;
59 * @author Vladimir Kondratyev
61 public abstract class TodoTreeBuilder extends AbstractTreeBuilder {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.todo.TodoTreeBuilder");
63 protected final Project myProject;
66 * All files that have T.O.D.O items are presented as tree. This tree help a lot
67 * to separate these files by directories.
69 protected final FileTree myFileTree;
71 * This set contains "dirty" files. File is "dirty" if it's currently not nkown
72 * whether the file contains T.O.D.O item or not. To determine this it's necessary
73 * to perform some (perhaps, CPU expensive) operation. These "dirty" files are
74 * validated in <code>validateCache()</code> method.
76 protected final HashSet<VirtualFile> myDirtyFileSet;
78 protected final HashMap<VirtualFile, EditorHighlighter> myFile2Highlighter;
80 protected final PsiSearchHelper mySearchHelper;
82 * If this flag is false then the updateTree() method does nothing. But when
83 * the flag becomes true and myDirtyFileSet isn't empty the update is invoked.
84 * This is done for optimization reasons: if TodoPane is not visible then
85 * updates isn't invoked.
87 private boolean myUpdatable;
89 /** Updates tree if containing files change VCS status. */
90 private final MyFileStatusListener myFileStatusListener;
92 TodoTreeBuilder(JTree tree, DefaultTreeModel treeModel, Project project) {
93 super(tree, treeModel, null, MyComparator.ourInstance, false);
96 myFileTree = new FileTree();
97 myDirtyFileSet = new HashSet<VirtualFile>();
99 myFile2Highlighter = new HashMap<VirtualFile, EditorHighlighter>();
101 PsiManager psiManager = PsiManager.getInstance(myProject);
102 mySearchHelper = PsiSearchHelper.SERVICE.getInstance(myProject);
103 psiManager.addPsiTreeChangeListener(new MyPsiTreeChangeListener());
105 myFileStatusListener = new MyFileStatusListener();
107 setCanYieldUpdate(true);
111 * Initializes the builder. Subclasses should don't forget to call this method after constuctor has
114 public final void init() {
115 TodoTreeStructure todoTreeStructure = createTreeStructure();
116 setTreeStructure(todoTreeStructure);
117 todoTreeStructure.setTreeBuilder(this);
122 Object selectableElement = todoTreeStructure.getFirstSelectableElement();
123 if (selectableElement != null) {
124 buildNodeForElement(selectableElement);
125 DefaultMutableTreeNode node = getNodeForElement(selectableElement);
127 getTree().getSelectionModel().setSelectionPath(new TreePath(node.getPath()));
131 FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener);
134 public final void dispose() {
135 FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener);
139 final boolean isUpdatable() {
144 * Sets whenther the builder updates the tree when data change.
146 final void setUpdatable(boolean updatable) {
147 if (myUpdatable != updatable) {
148 myUpdatable = updatable;
150 DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
159 protected abstract TodoTreeStructure createTreeStructure();
161 protected boolean validateNode(final Object child) {
162 if (child instanceof ProjectViewNode) {
163 final ProjectViewNode projectViewNode = (ProjectViewNode)child;
164 projectViewNode.update();
165 if (projectViewNode.getValue() == null) {
172 public final TodoTreeStructure getTodoTreeStructure() {
173 return (TodoTreeStructure)getTreeStructure();
176 protected final AbstractTreeUpdater createUpdater() {
177 return new AbstractTreeUpdater(this) {
179 protected ActionCallback beforeUpdate(final TreeUpdatePass pass) {
180 if (!myDirtyFileSet.isEmpty()) { // suppress redundant cache validations
181 final AsyncResult callback = new AsyncResult();
182 DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
186 getTodoTreeStructure().validateCache();
196 return new ActionCallback.Done();
202 * @return read-only iterator of all current PSI files that can contain TODOs.
203 * Don't invoke its <code>remove</code> method. For "removing" use <code>markFileAsDirty</code> method.
204 * <b>Note, that <code>next()</code> method of iterator can return <code>null</code> elements.</b>
205 * These <code>null</code> elements correspond to the invalid PSI files (PSI file cannot be found by
206 * virtual file, or virtual file is invalid).
207 * The reason why we return such "dirty" iterator is the peformance.
209 public Iterator<PsiFile> getAllFiles() {
210 final Iterator<VirtualFile> iterator = myFileTree.getFileIterator();
211 return new Iterator<PsiFile>() {
212 public boolean hasNext() {
213 return iterator.hasNext();
216 @Nullable public PsiFile next() {
217 VirtualFile vFile = iterator.next();
218 if (vFile == null || !vFile.isValid()) {
221 PsiFile psiFile = PsiManager.getInstance(myProject).findFile(vFile);
222 if (psiFile == null || !psiFile.isValid()) {
228 public void remove() {
229 throw new IllegalArgumentException();
235 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
236 * and which are located under specified <code>psiDirctory</code>.
237 * @see com.intellij.ide.todo.FileTree#getFiles(com.intellij.openapi.vfs.VirtualFile)
239 public Iterator<PsiFile> getFiles(PsiDirectory psiDirectory) {
240 return getFiles(psiDirectory, true);
244 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
245 * and which are located under specified <code>psiDirctory</code>.
246 * @see FileTree#getFiles(VirtualFile)
248 public Iterator<PsiFile> getFiles(PsiDirectory psiDirectory, final boolean skip) {
249 ArrayList<VirtualFile> files = myFileTree.getFiles(psiDirectory.getVirtualFile());
250 ArrayList<PsiFile> psiFileList = new ArrayList<PsiFile>(files.size());
251 PsiManager psiManager = PsiManager.getInstance(myProject);
252 for (VirtualFile file : files) {
253 final Module module = ModuleUtil.findModuleForPsiElement(psiDirectory);
254 if (module != null) {
255 final boolean isInContent = ModuleRootManager.getInstance(module).getFileIndex().isInContent(file);
256 if (!isInContent) continue;
258 if (file.isValid()) {
259 PsiFile psiFile = psiManager.findFile(file);
260 if (psiFile != null) {
261 final PsiDirectory directory = psiFile.getContainingDirectory();
262 if (directory == null || !skip || !TodoTreeHelper.getInstance(myProject).skipDirectory(directory)) {
263 psiFileList.add(psiFile);
268 return psiFileList.iterator();
272 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
273 * and which are located under specified <code>psiDirctory</code>.
274 * @see FileTree#getFiles(VirtualFile)
276 public Iterator<PsiFile> getFilesUnderDirectory(PsiDirectory psiDirectory) {
277 ArrayList<VirtualFile> files = myFileTree.getFilesUnderDirectory(psiDirectory.getVirtualFile());
278 ArrayList<PsiFile> psiFileList = new ArrayList<PsiFile>(files.size());
279 PsiManager psiManager = PsiManager.getInstance(myProject);
280 for (VirtualFile file : files) {
281 final Module module = ModuleUtil.findModuleForPsiElement(psiDirectory);
282 if (module != null) {
283 final boolean isInContent = ModuleRootManager.getInstance(module).getFileIndex().isInContent(file);
284 if (!isInContent) continue;
286 if (file.isValid()) {
287 PsiFile psiFile = psiManager.findFile(file);
288 if (psiFile != null) {
289 psiFileList.add(psiFile);
293 return psiFileList.iterator();
299 * @return read-only iterator of all valid PSI files that can have T.O.D.O items
300 * and which in specified <code>module</code>.
301 * @see FileTree#getFiles(VirtualFile)
303 public Iterator<PsiFile> getFiles(Module module) {
304 if (module.isDisposed()) return Collections.<PsiFile>emptyList().iterator();
305 ArrayList<PsiFile> psiFileList = new ArrayList<PsiFile>();
306 final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
307 final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
308 for (VirtualFile virtualFile : contentRoots) {
309 ArrayList<VirtualFile> files = myFileTree.getFiles(virtualFile);
310 PsiManager psiManager = PsiManager.getInstance(myProject);
311 for (VirtualFile file : files) {
312 if (fileIndex.getModuleForFile(file) != module) continue;
313 if (file.isValid()) {
314 PsiFile psiFile = psiManager.findFile(file);
315 if (psiFile != null) {
316 psiFileList.add(psiFile);
321 return psiFileList.iterator();
326 * @return <code>true</code> if specified <code>psiFile</code> can contains too items.
327 * It means that file is in "dirty" file set or in "current" file set.
329 private boolean canContainTodoItems(PsiFile psiFile) {
330 VirtualFile vFile = psiFile.getVirtualFile();
331 return myFileTree.contains(vFile) || myDirtyFileSet.contains(vFile);
335 * Marks specified PsiFile as dirty. It means that file is being add into "dirty" file set.
336 * It presents in current file set also but the next validateCache call will validate this
337 * "dirty" file. This method should be invoked when any modifications inside the file
340 private void markFileAsDirty(@NotNull PsiFile psiFile) {
341 VirtualFile vFile = psiFile.getVirtualFile();
342 if (vFile != null) { // If PSI file isn't valid then its VirtualFile can be null
343 myDirtyFileSet.add(vFile);
348 * Clear and rebuild whole the caches. It means that after rebuilding all caches are valid.
350 abstract void rebuildCache();
352 private void validateCache() {
353 TodoTreeStructure treeStructure = getTodoTreeStructure();
354 // First of all we need to update "dirty" file set.
355 for (Iterator<VirtualFile> i = myDirtyFileSet.iterator(); i.hasNext();) {
356 VirtualFile file = i.next();
357 PsiFile psiFile = file.isValid() ? PsiManager.getInstance(myProject).findFile(file) : null;
358 if (psiFile == null || !treeStructure.accept(psiFile)) {
359 if (myFileTree.contains(file)) {
360 myFileTree.removeFile(file);
361 if (myFile2Highlighter.containsKey(file)) { // highlighter isn't needed any more
362 myFile2Highlighter.remove(file);
366 else { // file is valid and contains T.O.D.O items
367 myFileTree.removeFile(file);
368 myFileTree.add(file); // file can be moved. remove/add calls move it to another place
369 if (myFile2Highlighter.containsKey(file)) { // update highlighter's text
370 Document document = PsiDocumentManager.getInstance(myProject).getDocument(psiFile);
371 EditorHighlighter highlighter = myFile2Highlighter.get(file);
372 highlighter.setText(document.getCharsSequence());
377 LOG.assertTrue(myDirtyFileSet.isEmpty());
378 // Now myDirtyFileSet should be empty
381 protected boolean isAutoExpandNode(NodeDescriptor descriptor) {
382 return getTodoTreeStructure().isAutoExpandNode(descriptor);
385 protected boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor) {
386 final Object element= nodeDescriptor.getElement();
387 if (element instanceof TodoItemNode){
389 } else if(element instanceof PsiFileNode) {
391 return getTodoTreeStructure().mySearchHelper.getTodoItemsCount(((PsiFileNode)element).getValue()) > 0;
393 catch (IndexNotReadyException e) {
401 * @return first <code>SmartTodoItemPointer</code> that is the children (in depth) of the specified <code>element</code>.
402 * If <code>element</code> itself is a <code>TodoItem</code> then the method returns the <code>element</code>.
404 public TodoItemNode getFirstPointerForElement(Object element) {
405 if (element instanceof TodoItemNode) {
406 return (TodoItemNode)element;
409 Object[] children = getTreeStructure().getChildElements(element);
410 if (children.length == 0) {
413 Object firstChild = children[0];
414 if (firstChild instanceof TodoItemNode) {
415 return (TodoItemNode)firstChild;
418 return getFirstPointerForElement(firstChild);
424 * @return last <code>SmartTodoItemPointer</code> that is the children (in depth) of the specified <code>element</code>.
425 * If <code>element</code> itself is a <code>TodoItem</code> then the method returns the <code>element</code>.
427 public TodoItemNode getLastPointerForElement(Object element) {
428 if (element instanceof TodoItemNode) {
429 return (TodoItemNode)element;
432 Object[] children = getTreeStructure().getChildElements(element);
433 if (children.length == 0) {
436 Object firstChild = children[children.length - 1];
437 if (firstChild instanceof TodoItemNode) {
438 return (TodoItemNode)firstChild;
441 return getLastPointerForElement(firstChild);
446 protected final void updateTree(boolean later) {
448 getUpdater().addSubtreeToUpdate(getRootNode());
450 getUpdater().performUpdate();
455 static PsiFile getFileForNode(DefaultMutableTreeNode node) {
456 Object obj = node.getUserObject();
457 if (obj instanceof TodoFileNode) {
458 return ((TodoFileNode)obj).getValue();
460 else if (obj instanceof TodoItemNode) {
461 SmartTodoItemPointer pointer = ((TodoItemNode)obj).getValue();
462 return pointer.getTodoItem().getFile();
468 int row = getTree().getRowCount() - 1;
470 getTree().collapseRow(row);
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);
542 Arrays.sort(children, getUi().getNodeDescriptorComparator());
544 for (int i = 0; i < children.length; i++) {
545 if (obj.equals(children[i])) {
553 if (idx < children.length - 1) {
554 return children[idx + 1];
556 // passed object is the last in the list. In this case we have to return first child of the
557 // next parent's sibling.
558 return getNextSibling(parent);
562 * @return next <code>SmartTodoItemPointer</code> for the passed <code>pointer</code>. Returns <code>null</code>
563 * if the <code>pointer</code> is the last t.o.d.o item in the tree.
565 public TodoItemNode getPreviousPointer(TodoItemNode pointer) {
566 Object sibling = getPreviousSibling(pointer);
567 if (sibling == null) {
570 if (sibling instanceof TodoItemNode) {
571 return (TodoItemNode)sibling;
574 return getLastPointerForElement(sibling);
579 * @return previous sibling of the element of passed type. If there is no sibling then
580 * returns <code>null</code>.
582 Object getPreviousSibling(Object obj) {
583 Object parent = getTreeStructure().getParentElement(obj);
584 if (parent == null) {
587 Object[] children = getTreeStructure().getChildElements(parent);
588 Arrays.sort(children, getUi().getNodeDescriptorComparator());
590 for (int i = 0; i < children.length; i++) {
591 if (obj.equals(children[i])) {
601 return children[idx - 1];
603 // passed object is the first in the list. In this case we have to return last child of the
604 // previous parent's sibling.
605 return getPreviousSibling(parent);
609 * @return <code>SelectInEditorManager</code> for the specified <code>psiFile</code>. Highlighters are
610 * lazy created and initialized.
612 public EditorHighlighter getHighlighter(PsiFile psiFile, Document document) {
613 VirtualFile file = psiFile.getVirtualFile();
614 if (myFile2Highlighter.containsKey(file)) {
615 return myFile2Highlighter.get(file);
618 EditorHighlighter highlighter = HighlighterFactory.createHighlighter(UsageTreeColorsScheme.getInstance().getScheme(), file.getName(), myProject);
619 highlighter.setText(document.getCharsSequence());
620 myFile2Highlighter.put(file, highlighter);
625 void setShowModules(boolean state) {
626 getTodoTreeStructure().setShownModules(state);
627 ArrayList<Object> pathsToExpand = new ArrayList<Object>();
628 ArrayList<Object> pathsToSelect = new ArrayList<Object>();
629 TreeBuilderUtil.storePaths(this, getRootNode(), pathsToExpand, pathsToSelect, true);
630 getTree().clearSelection();
631 getTodoTreeStructure().validateCache();
633 TreeBuilderUtil.restorePaths(this, pathsToExpand, pathsToSelect, true);
636 public boolean isDirectoryEmpty(@NotNull PsiDirectory psiDirectory){
637 return myFileTree.isDirectoryEmpty(psiDirectory.getVirtualFile());
641 protected ProgressIndicator createProgressIndicator() {
642 return new StatusBarProgress();
645 private static final class MyComparator implements Comparator<NodeDescriptor> {
646 public static final Comparator<NodeDescriptor> ourInstance = new MyComparator();
648 public int compare(NodeDescriptor descriptor1, NodeDescriptor descriptor2) {
649 int weight1 = descriptor1.getWeight();
650 int weight2 = descriptor2.getWeight();
651 if (weight1 != weight2) {
652 return weight1 - weight2;
655 return descriptor1.getIndex() - descriptor2.getIndex();
660 private final class MyPsiTreeChangeListener extends PsiTreeChangeAdapter {
661 public void childAdded(PsiTreeChangeEvent e) {
662 // If local modification
663 if (e.getFile() != null) {
664 markFileAsDirty(e.getFile());
668 // If added element if PsiFile and it doesn't contains TODOs, then do nothing
669 PsiElement child = e.getChild();
670 if (!(child instanceof PsiFile)) {
673 PsiFile psiFile = (PsiFile)e.getChild();
674 markFileAsDirty(psiFile);
678 public void beforeChildRemoval(PsiTreeChangeEvent e) {
679 // If local midification
680 final PsiFile file = e.getFile();
682 markFileAsDirty(file);
687 PsiElement child = e.getChild();
688 if (child instanceof PsiFile) { // file will be removed
689 PsiFile psiFile = (PsiFile)child;
690 markFileAsDirty(psiFile);
693 else if (child instanceof PsiDirectory) { // directory will be removed
694 PsiDirectory psiDirectory = (PsiDirectory)child;
695 for (Iterator<PsiFile> i = getAllFiles(); i.hasNext();) {
696 PsiFile psiFile = i.next();
697 if (psiFile == null) { // skip invalid PSI files
700 if (PsiTreeUtil.isAncestor(psiDirectory, psiFile, true)) {
701 markFileAsDirty(psiFile);
707 if (PsiTreeUtil.getParentOfType(child, PsiComment.class, false) != null) { // change inside comment
708 markFileAsDirty(child.getContainingFile());
714 public void childMoved(PsiTreeChangeEvent e) {
715 if (e.getFile() != null) { // local change
716 markFileAsDirty(e.getFile());
720 if (e.getChild() instanceof PsiFile) { // file was moved
721 PsiFile psiFile = (PsiFile)e.getChild();
722 if (!canContainTodoItems(psiFile)) { // moved file doesn't contain TODOs
725 markFileAsDirty(psiFile);
728 else if (e.getChild() instanceof PsiDirectory) { // directory was moved. mark all its files as dirty.
729 PsiDirectory psiDirectory = (PsiDirectory)e.getChild();
730 boolean shouldUpdate = false;
731 for (Iterator<PsiFile> i = getAllFiles(); i.hasNext();) {
732 PsiFile psiFile = i.next();
733 if (psiFile == null) { // skip invalid PSI files
736 if (PsiTreeUtil.isAncestor(psiDirectory, psiFile, true)) {
737 markFileAsDirty(psiFile);
747 public void childReplaced(PsiTreeChangeEvent e) {
748 if (e.getFile() != null) {
749 markFileAsDirty(e.getFile());
754 public void childrenChanged(PsiTreeChangeEvent e) {
755 if (e.getFile() != null) {
756 markFileAsDirty(e.getFile());
761 public void propertyChanged(PsiTreeChangeEvent e) {
762 String propertyName = e.getPropertyName();
763 if (propertyName.equals(PsiTreeChangeEvent.PROP_ROOTS)) { // rebuild all tree when source roots were changed
764 getUpdater().runBeforeUpdate(
767 DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
778 else if (PsiTreeChangeEvent.PROP_WRITABLE.equals(propertyName)) {
779 PsiFile psiFile = (PsiFile)e.getElement();
780 if (!canContainTodoItems(psiFile)) { // don't do anything if file cannot contain todos
785 else if (PsiTreeChangeEvent.PROP_FILE_NAME.equals(propertyName)) {
786 PsiFile psiFile = (PsiFile)e.getElement();
787 if (!canContainTodoItems(psiFile)) {
792 else if (PsiTreeChangeEvent.PROP_DIRECTORY_NAME.equals(propertyName)) {
793 PsiDirectory psiDirectory = (PsiDirectory)e.getElement();
794 Iterator<PsiFile> iterator = getFiles(psiDirectory);
795 if (iterator.hasNext()) {
802 private final class MyFileStatusListener implements FileStatusListener {
803 public void fileStatusesChanged() {
807 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
808 PsiFile psiFile = PsiManager.getInstance(myProject).findFile(virtualFile);
809 if (psiFile != null && canContainTodoItems(psiFile)) {