filter out project configuration files silently
[idea/community.git] / platform / platform-impl / src / com / intellij / execution / DelayedDocumentWatcher.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.execution;
17
18 import com.intellij.AppTopics;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.application.ModalityState;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.EditorFactory;
24 import com.intellij.openapi.editor.event.DocumentAdapter;
25 import com.intellij.openapi.editor.event.DocumentEvent;
26 import com.intellij.openapi.fileEditor.FileDocumentManager;
27 import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.project.ProjectCoreUtil;
30 import com.intellij.openapi.util.Computable;
31 import com.intellij.openapi.util.Condition;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.util.Alarm;
35 import com.intellij.util.Consumer;
36 import com.intellij.util.PsiErrorElementUtil;
37 import com.intellij.util.messages.MessageBusConnection;
38 import gnu.trove.THashSet;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.util.Collection;
43 import java.util.Set;
44
45 public class DelayedDocumentWatcher {
46
47   // All instance fields are be accessed from EDT
48   private final Project myProject;
49   private final Alarm myAlarm;
50   private final int myDelayMillis;
51   private final Consumer<Integer> myModificationStampConsumer;
52   private final Condition<VirtualFile> myChangedFileFilter;
53   private final MyDocumentAdapter myListener;
54   private final Runnable myAlarmRunnable;
55
56   private final Set<VirtualFile> myChangedFiles = new THashSet<VirtualFile>();
57   private boolean myDocumentSavingInProgress = false;
58   private MessageBusConnection myConnection;
59   private int myModificationStamp = 0;
60   private Disposable myListenerDisposable;
61
62   public DelayedDocumentWatcher(@NotNull Project project,
63                                 int delayMillis,
64                                 @NotNull Consumer<Integer> modificationStampConsumer,
65                                 @Nullable Condition<VirtualFile> changedFileFilter) {
66     myProject = project;
67     myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, myProject);
68     myDelayMillis = delayMillis;
69     myModificationStampConsumer = modificationStampConsumer;
70     myChangedFileFilter = changedFileFilter;
71     myListener = new MyDocumentAdapter();
72     myAlarmRunnable = new MyRunnable();
73   }
74
75   @NotNull
76   public Project getProject() {
77     return myProject;
78   }
79
80   public void activate() {
81     if (myConnection == null) {
82       myListenerDisposable = Disposer.newDisposable();
83       Disposer.register(myProject, myListenerDisposable);
84       EditorFactory.getInstance().getEventMulticaster().addDocumentListener(myListener, myListenerDisposable);
85       myConnection = ApplicationManager.getApplication().getMessageBus().connect(myProject);
86       myConnection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
87         @Override
88         public void beforeAllDocumentsSaving() {
89           myDocumentSavingInProgress = true;
90           ApplicationManager.getApplication().invokeLater(new Runnable() {
91             @Override
92             public void run() {
93               myDocumentSavingInProgress = false;
94             }
95           }, ModalityState.any());
96         }
97       });
98     }
99   }
100
101   public void deactivate() {
102     if (myConnection != null) {
103       if (myListenerDisposable != null) {
104         Disposer.dispose(myListenerDisposable);
105         myListenerDisposable = null;
106       }
107       myConnection.disconnect();
108       myConnection = null;
109     }
110   }
111
112   public boolean isUpToDate(int modificationStamp) {
113     return myModificationStamp == modificationStamp;
114   }
115
116   private class MyDocumentAdapter extends DocumentAdapter {
117     @Override
118     public void documentChanged(DocumentEvent event) {
119       if (myDocumentSavingInProgress) {
120         /** When {@link FileDocumentManager#saveAllDocuments} is called,
121          *  {@link com.intellij.openapi.editor.impl.TrailingSpacesStripper} can change a document.
122          *  These needless 'documentChanged' events should be filtered out.
123          */
124         return;
125       }
126       final Document document = event.getDocument();
127       final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
128       if (file == null) {
129         return;
130       }
131       if (!myChangedFiles.contains(file)) {
132         if (ProjectCoreUtil.isProjectOrWorkspaceFile(file)) {
133           return;
134         }
135         if (myChangedFileFilter != null && !myChangedFileFilter.value(file)) {
136           return;
137         }
138
139         myChangedFiles.add(file);
140       }
141
142       myAlarm.cancelRequest(myAlarmRunnable);
143       myAlarm.addRequest(myAlarmRunnable, myDelayMillis);
144       myModificationStamp++;
145     }
146   }
147
148   private class MyRunnable implements Runnable {
149     @Override
150     public void run() {
151       final int oldModificationStamp = myModificationStamp;
152       asyncCheckErrors(myChangedFiles, new Consumer<Boolean>() {
153         @Override
154         public void consume(Boolean errorsFound) {
155           if (myModificationStamp != oldModificationStamp) {
156             // 'documentChanged' event was raised during async checking files for errors
157             // Do nothing in that case, this method will be invoked subsequently.
158             return;
159           }
160           if (errorsFound) {
161             // Do nothing, if some changed file has syntax errors.
162             // This method will be invoked subsequently, when syntax errors are fixed.
163             return;
164           }
165           myChangedFiles.clear();
166           myModificationStampConsumer.consume(myModificationStamp);
167         }
168       });
169     }
170   }
171
172   private void asyncCheckErrors(@NotNull final Collection<VirtualFile> files,
173                                 @NotNull final Consumer<Boolean> errorsFoundConsumer) {
174     ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
175       @Override
176       public void run() {
177         final boolean errorsFound = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
178           @Override
179           public Boolean compute() {
180             for (VirtualFile file : files) {
181               if (PsiErrorElementUtil.hasErrors(myProject, file)) {
182                 return true;
183               }
184             }
185             return false;
186           }
187         });
188         ApplicationManager.getApplication().invokeLater(new Runnable() {
189           @Override
190           public void run() {
191             errorsFoundConsumer.consume(errorsFound);
192           }
193         }, ModalityState.any());
194       }
195     });
196   }
197 }