/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.core; import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.impl.ModuleRootEventImpl; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent; import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent; import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; import com.intellij.psi.PsiClassOwner; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.PsiDocumentTransactionListener; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.testFramework.LightVirtualFile; import gw.config.CommonServices; import gw.fs.IFile; import gw.fs.IResource; import gw.lang.init.GosuInitialization; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.module.IExecutionEnvironment; import gw.lang.reflect.module.IModule; import gw.plugin.ij.filetypes.GosuFileTypes; import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl; import gw.plugin.ij.util.DelayedRunner; import gw.plugin.ij.util.FileUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.List; public class FileModificationManager implements PsiDocumentTransactionListener, BulkFileListener { public static int TYPE_REFRESH_DELAY_MS = 0; private final DelayedRunner typeRefresher = new DelayedRunner(); private final Project project; private IFileListener[] fileListeners; private class Refresher implements Runnable { private final Project project; private final VirtualFile file; public Refresher(Project project, VirtualFile file) { this.project = project; this.file = file; } public void run() { ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { final TypeSystemStarter starter = TypeSystemStarter.instance(project); if (!starter.isStarted()) { return; } try { starter.startRefresh(); // in case <stop> just got called, check again after bumping refresh counter if (!starter.isStarted()) { return; } fireModifiedEvent(file); } finally { starter.stopRefresh(); } } }); } } public FileModificationManager(Project project) { this.project = project; final FileListenerExtensionBean[] extensions = Extensions.getExtensions(FileListenerExtensionBean.EP_NAME); fileListeners = new IFileListener[extensions.length]; for (int i = 0; i < extensions.length; i++) { fileListeners[i] = extensions[i].getHandler(); } } @Nullable private VirtualFile getVirtualFile(@NotNull PsiFile psiFile) { return psiFile instanceof AbstractGosuClassFileImpl ? FileUtil.getFileFromPsi(psiFile) : psiFile.getVirtualFile(); } private String getRefreshTaskId(@NotNull VirtualFile virtualFile) { return virtualFile.getPath(); } private String oldText; // PsiDocumentTransactionListener public void transactionStarted(final Document doc, final PsiFile file) { final FileElement myTreeElementBeingReparsedSoItWontBeCollected = ((PsiFileImpl) file).calcTreeElement(); oldText = myTreeElementBeingReparsedSoItWontBeCollected.getText(); } // Type creation and refresh // FIXME-dp this method does NOT get invoked when a Gosu class gets created, why? // Also, this will not fire when a file is created externally (Use VirtualFileManager.VFS_CHANGES) public void transactionCompleted(final Document doc, @NotNull final PsiFile psiFile) { VirtualFile file = getVirtualFile(psiFile); if (file instanceof VirtualFileWindow) { file = ((VirtualFileWindow) file).getDelegate(); } if (!GosuFileTypes.isGosuFile(file)) { final Runnable refresher = new Refresher(project, file); if (TYPE_REFRESH_DELAY_MS == 0) { refresher.run(); } else { if (PluginLoaderUtil.instance(project).isStarted()) { typeRefresher.scheduleTask(getRefreshTaskId(file), TYPE_REFRESH_DELAY_MS, refresher); } } } // process inner class changes if (psiFile instanceof PsiClassOwner) { String newText = doc.getText(); for (IFileListener fileListener : fileListeners) { fileListener.modified(FileUtil.toIFile(file), oldText, newText); } } } // BulkRefreshListener public void before(@NotNull final List<? extends VFileEvent> events) { // Nothing to do } public void after(@NotNull final List<? extends VFileEvent> events) { final IExecutionEnvironment env = TypeSystem.getExecutionEnvironment(PluginLoaderUtil.getFrom(project)); IModule globalModule = env.getGlobalModule(); TypeSystem.pushModule(globalModule); try { if (GosuInitialization.instance(env).isInitialized()) { for (VFileEvent event : events) { final VirtualFile file = event.getFile(); if (file != null) { if (event instanceof VFilePropertyChangeEvent) { processPropertyChangeEvent((VFilePropertyChangeEvent) event); } else if (event instanceof VFileMoveEvent) { processFileMoveEvent((VFileMoveEvent) event); } else if (event instanceof VFileCreateEvent) { fireCreatedEvent(file); } else if (event instanceof VFileDeleteEvent) { fireDeletedEvent(file); } else if (event instanceof VFileCopyEvent) { processFileCopyEvent((VFileCopyEvent) event); } else { fireModifiedEvent(file); } } } } } finally { TypeSystem.popModule(globalModule); } } private void processFileCopyEvent(@NotNull VFileCopyEvent event) { String newFileName = event.getNewParent().getPath() + "/" + event.getNewChildName(); IFile newFile = CommonServices.getFileSystem().getIFile(new File(newFileName)); fireCreatedEvent(newFile); } private void processFileMoveEvent(@NotNull VFileMoveEvent event) { VirtualFile newFile = event.getFile(); String oldFileName = event.getOldParent().getPath() + "/" + newFile.getName(); IFile oldFile = CommonServices.getFileSystem().getIFile(new File(oldFileName)); fireDeletedEvent(oldFile); fireCreatedEvent(newFile); } private void processPropertyChangeEvent(@NotNull VFilePropertyChangeEvent event) { if (event.getFile().isDirectory()) { // a source folder could have been renamed PluginLoaderUtil.instance(project).getModuleClasspathListener().rootsChanged(new ModuleRootEventImpl(project, false)); } if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { // collect file renames final VirtualFile newFile = event.getFile(); if (newFile instanceof LightVirtualFile) { return; } final String oldFileName = (String) event.getOldValue(); final IFile oldFile = FileUtil.toIDirectory(newFile.getParent()).file(oldFileName); fireDeletedEvent(oldFile); fireCreatedEvent(FileUtil.toIResource(newFile)); } } private void fireModifiedEvent(VirtualFile file) { fireModifiedEvent(FileUtil.toIResource(file)); } private void fireDeletedEvent(VirtualFile file) { fireDeletedEvent(FileUtil.toIResource(file)); } private void fireCreatedEvent(VirtualFile file) { fireCreatedEvent(FileUtil.toIResource(file)); } private void fireModifiedEvent(IResource file) { for (IFileListener fileListener : fileListeners) { fileListener.modified(file); } } private void fireDeletedEvent(IResource file) { for (IFileListener fileListener : fileListeners) { fileListener.deleted(file); } } private void fireCreatedEvent(IResource file) { for (IFileListener fileListener : fileListeners) { fileListener.created(file); } } }