IDEADEV-41265 (IU-9.0 Beta - Undo does not revert file to unchanged)
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / LineStatusTrackerManager.java
blobf183b6b13503bfa123d9fa107d0b4ff86feca672
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.
18 * Created by IntelliJ IDEA.
19 * User: yole
20 * Date: 31.07.2006
21 * Time: 13:24:17
23 package com.intellij.openapi.vcs.impl;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.components.ProjectComponent;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.Editor;
30 import com.intellij.openapi.editor.EditorFactory;
31 import com.intellij.openapi.editor.colors.EditorColorsListener;
32 import com.intellij.openapi.editor.colors.EditorColorsManager;
33 import com.intellij.openapi.editor.colors.EditorColorsScheme;
34 import com.intellij.openapi.editor.event.EditorFactoryAdapter;
35 import com.intellij.openapi.editor.event.EditorFactoryEvent;
36 import com.intellij.openapi.editor.event.EditorFactoryListener;
37 import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener;
38 import com.intellij.openapi.fileEditor.FileDocumentManager;
39 import com.intellij.openapi.fileEditor.FileEditorManager;
40 import com.intellij.openapi.project.Project;
41 import com.intellij.openapi.util.Disposer;
42 import com.intellij.openapi.vcs.AbstractVcs;
43 import com.intellij.openapi.vcs.FileStatus;
44 import com.intellij.openapi.vcs.FileStatusListener;
45 import com.intellij.openapi.vcs.FileStatusManager;
46 import com.intellij.openapi.vcs.ex.LineStatusTracker;
47 import com.intellij.openapi.vfs.VirtualFile;
48 import com.intellij.openapi.vfs.VirtualFileAdapter;
49 import com.intellij.openapi.vfs.VirtualFileEvent;
50 import com.intellij.openapi.vfs.VirtualFileManager;
51 import com.intellij.openapi.Disposable;
52 import com.intellij.testFramework.LightVirtualFile;
53 import com.intellij.util.Alarm;
54 import com.intellij.util.containers.HashMap;
55 import org.jetbrains.annotations.NonNls;
56 import org.jetbrains.annotations.NotNull;
58 import java.util.Arrays;
59 import java.util.Collection;
61 public class LineStatusTrackerManager implements ProjectComponent {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.LineStatusTrackerManager");
64 public static LineStatusTrackerManager getInstance(Project project) {
65 return project.getComponent(LineStatusTrackerManager.class);
68 private final Project myProject;
70 private HashMap<Document, LineStatusTracker> myLineStatusTrackers =
71 new HashMap<Document, LineStatusTracker>();
73 private final HashMap<Document, Alarm> myLineStatusUpdateAlarms =
74 new HashMap<Document, Alarm>();
76 private final Object TRACKERS_LOCK = new Object();
77 private boolean myIsDisposed = false;
78 @NonNls protected static final String IGNORE_CHANGEMARKERS_KEY = "idea.ignore.changemarkers";
79 private final ProjectLevelVcsManagerImpl myVcsManager;
80 private final VcsFileStatusProvider myStatusProvider;
82 public LineStatusTrackerManager(final Project project, final ProjectLevelVcsManagerImpl vcsManager, final VcsFileStatusProvider statusProvider) {
83 myProject = project;
84 myVcsManager = vcsManager;
85 myStatusProvider = statusProvider;
87 project.getMessageBus().connect().subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() {
88 public void updateStarted(final Document doc) {
89 final LineStatusTracker tracker = getLineStatusTracker(doc);
90 if (tracker != null) tracker.startBulkUpdate();
93 public void updateFinished(final Document doc) {
94 final LineStatusTracker tracker = getLineStatusTracker(doc);
95 if (tracker != null) tracker.finishBulkUpdate();
97 });
100 public void projectOpened() {
101 trackAwtThread();
102 final MyFileStatusListener fileStatusListener = new MyFileStatusListener();
103 final EditorFactoryListener editorFactoryListener = new MyEditorFactoryListener();
104 final MyVirtualFileListener virtualFileListener = new MyVirtualFileListener();
105 final EditorColorsListener editorColorsListener = new EditorColorsListener() {
106 public void globalSchemeChange(EditorColorsScheme scheme) {
107 resetTrackersForOpenFiles();
111 myLineStatusTrackers = new HashMap<Document, LineStatusTracker>();
112 final FileStatusManager fsManager = FileStatusManager.getInstance(myProject);
113 fsManager.addFileStatusListener(fileStatusListener, myProject);
115 final EditorFactory editorFactory = EditorFactory.getInstance();
116 editorFactory.addEditorFactoryListener(editorFactoryListener);
118 final VirtualFileManager virtualFileManager = VirtualFileManager.getInstance();
119 virtualFileManager.addVirtualFileListener(virtualFileListener,myProject);
121 final EditorColorsManager editorColorsManager = EditorColorsManager.getInstance();
122 editorColorsManager.addEditorColorsListener(editorColorsListener);
124 Disposer.register(myProject, new Disposable() {
125 public void dispose() {
126 trackAwtThread();
127 fsManager.removeFileStatusListener(fileStatusListener);
128 editorFactory.removeEditorFactoryListener(editorFactoryListener);
129 virtualFileManager.removeVirtualFileListener(virtualFileListener);
130 editorColorsManager.removeEditorColorsListener(editorColorsListener);
135 public void projectClosed() {
136 try {
137 trackAwtThread();
138 dispose();
140 finally {
141 myIsDisposed = true;
145 @NonNls @NotNull
146 public String getComponentName() {
147 return "LineStatusTrackerManager";
150 public void initComponent() {
151 //To change body of implemented methods use File | Settings | File Templates.
154 public void disposeComponent() {
155 //To change body of implemented methods use File | Settings | File Templates.
158 private void dispose() {
159 final Collection<LineStatusTracker> trackers = myLineStatusTrackers.values();
160 final LineStatusTracker[] lineStatusTrackers = trackers.toArray(new LineStatusTracker[trackers.size()]);
161 for (LineStatusTracker tracker : lineStatusTrackers) {
162 releaseTracker(tracker.getDocument());
165 myLineStatusTrackers = null;
168 public LineStatusTracker getLineStatusTracker(Document document) {
169 trackAwtThread();
170 if (myLineStatusTrackers == null) return null;
171 return myLineStatusTrackers.get(document);
175 public LineStatusTracker setUpToDateContent(final Document document, final String lastUpToDateContent, final VirtualFile vf) {
176 trackAwtThread();
177 LineStatusTracker result = myLineStatusTrackers.get(document);
178 if (result == null) {
179 result = LineStatusTracker.createOn(document, lastUpToDateContent, myProject, vf);
180 myLineStatusTrackers.put(document, result);
182 return result;
185 private LineStatusTracker createTrackerForDocument(Document document, VirtualFile vf) {
186 LOG.assertTrue(!myLineStatusTrackers.containsKey(document));
187 LineStatusTracker result = LineStatusTracker.createOn(document, myProject, vf);
188 myLineStatusTrackers.put(document, result);
189 return result;
192 private void resetTracker(final VirtualFile virtualFile) {
193 if (System.getProperty(IGNORE_CHANGEMARKERS_KEY) != null) return;
195 final Document document = FileDocumentManager.getInstance().getCachedDocument(virtualFile);
196 if (document == null) {
197 if (LOG.isDebugEnabled()) {
198 LOG.debug("Skipping resetTracker() because no cached document for " + virtualFile.getPath());
200 return;
203 if (LOG.isDebugEnabled()) {
204 LOG.debug("resetting tracker for file " + virtualFile.getPath());
206 synchronized (TRACKERS_LOCK) {
207 final LineStatusTracker tracker = myLineStatusTrackers.get(document);
208 if (tracker != null) {
209 resetTracker(tracker);
211 else {
212 if (Arrays.asList(FileEditorManager.getInstance(myProject).getOpenFiles()).contains(virtualFile)) {
213 installTracker(virtualFile, document);
219 private boolean releaseTracker(Document document) {
220 synchronized (TRACKERS_LOCK) {
221 releaseUpdateAlarms(document);
222 if (myLineStatusTrackers == null) return false;
223 if (!myLineStatusTrackers.containsKey(document)) return false;
224 LineStatusTracker tracker = myLineStatusTrackers.remove(document);
225 tracker.release();
226 return true;
230 private void releaseUpdateAlarms(final Document document) {
231 if (myLineStatusUpdateAlarms.containsKey(document)) {
232 final Alarm alarm = myLineStatusUpdateAlarms.get(document);
233 if (alarm != null) {
234 alarm.cancelAllRequests();
236 myLineStatusUpdateAlarms.remove(document);
240 public void resetTracker(final LineStatusTracker tracker) {
241 trackAwtThread();
242 if (tracker != null) {
243 ApplicationManager.getApplication().invokeLater(new Runnable() {
244 public void run() {
245 if (myIsDisposed) return;
246 if (releaseTracker(tracker.getDocument())) {
247 installTracker(tracker.getVirtualFile(), tracker.getDocument());
254 private void installTracker(final VirtualFile virtualFile, final Document document) {
255 if (virtualFile == null || virtualFile instanceof LightVirtualFile) return;
256 ApplicationManager.getApplication().assertIsDispatchThread();
258 if (myProject.isDisposed()) return;
259 final FileStatusManager statusManager = FileStatusManager.getInstance(myProject);
260 if (statusManager == null) return;
261 final FileStatus status = statusManager.getStatus(virtualFile);
263 synchronized (TRACKERS_LOCK) {
264 if (myLineStatusTrackers.containsKey(document)) return;
266 if (status == FileStatus.NOT_CHANGED ||
267 status == FileStatus.ADDED ||
268 status == FileStatus.UNKNOWN ||
269 status == FileStatus.IGNORED) {
270 if (LOG.isDebugEnabled()) {
271 LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: status=" + status);
273 return;
276 AbstractVcs activeVcs = myVcsManager.getVcsFor(virtualFile);
278 if (activeVcs == null) {
279 if (LOG.isDebugEnabled()) {
280 LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: no active VCS");
282 return;
285 if (!virtualFile.isInLocalFileSystem()) return;
287 if (System.getProperty(IGNORE_CHANGEMARKERS_KEY) != null) return;
289 final Alarm alarm;
291 if (myLineStatusUpdateAlarms.containsKey(document)) {
292 alarm = myLineStatusUpdateAlarms.get(document);
293 alarm.cancelAllRequests();
295 else {
296 alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
297 myLineStatusUpdateAlarms.put(document, alarm);
300 final LineStatusTracker tracker = createTrackerForDocument(document, virtualFile);
302 alarm.addRequest(new Runnable() {
303 public void run() {
304 try {
305 alarm.cancelAllRequests();
306 if (!virtualFile.isValid()) {
307 if (LOG.isDebugEnabled()) {
308 LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: virtual file not valid");
310 return;
312 final String lastUpToDateContent = myStatusProvider.getBaseVersionContent(virtualFile);
313 if (lastUpToDateContent == null) {
314 if (LOG.isDebugEnabled()) {
315 LOG.debug("installTracker() for file " + virtualFile.getPath() + " failed: no up to date content");
317 return;
319 ApplicationManager.getApplication().invokeLater(new Runnable() {
320 public void run() {
321 if (!myProject.isDisposed()) {
322 ApplicationManager.getApplication().runWriteAction(new Runnable() {
323 public void run() {
324 if (LOG.isDebugEnabled()) {
325 LOG.debug("initializing tracker for file " + virtualFile.getPath());
327 synchronized (TRACKERS_LOCK) {
328 tracker.initialize(lastUpToDateContent);
336 finally {
337 // todo guard alarms!!!
338 myLineStatusUpdateAlarms.remove(document);
341 }, 10);
346 private void resetTrackersForOpenFiles() {
347 final VirtualFile[] openFiles = FileEditorManager.getInstance(myProject).getOpenFiles();
348 synchronized (TRACKERS_LOCK) {
349 for(VirtualFile openFile: openFiles) {
350 resetTracker(openFile);
355 private class MyFileStatusListener implements FileStatusListener {
356 public void fileStatusesChanged() {
357 if (myProject.isDisposed()) return;
358 LOG.debug("LineStatusTrackerManager: fileStatusesChanged");
359 trackAwtThread();
360 resetTrackersForOpenFiles();
363 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
364 trackAwtThread();
365 resetTracker(virtualFile);
369 private class MyEditorFactoryListener extends EditorFactoryAdapter {
370 public void editorCreated(EditorFactoryEvent event) {
371 trackAwtThread();
372 Editor editor = event.getEditor();
373 if (editor.getProject() != null && editor.getProject() != myProject) return;
374 Document document = editor.getDocument();
375 VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
376 installTracker(virtualFile, document);
379 public void editorReleased(EditorFactoryEvent event) {
380 trackAwtThread();
381 final Editor editor = event.getEditor();
382 if (editor.getProject() != null && editor.getProject() != myProject) return;
383 final Document doc = editor.getDocument();
384 final Editor[] editors = event.getFactory().getEditors(doc, myProject);
385 if (editors.length == 0) {
386 releaseTracker(doc);
391 private class MyVirtualFileListener extends VirtualFileAdapter {
392 public void beforeContentsChange(VirtualFileEvent event) {
393 trackAwtThread();
394 if (event.isFromRefresh()) {
395 resetTracker(event.getFile());
400 private void trackAwtThread() {
401 if (! ApplicationManager.getApplication().isDispatchThread()) {
402 LOG.info("NOT dispatch thread: " + Thread.currentThread().getName(), new Throwable());