package com.reason.ide; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.intellij.openapi.Disposable; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManagerEvent; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.WeakList; import com.reason.Compiler; import com.reason.ORCompilerManager; import com.reason.hints.InsightManager; import com.reason.hints.InsightUpdateQueue; import com.reason.ide.files.FileHelper; import com.reason.ide.hints.CodeLensView; import com.reason.ide.hints.InferredTypesService; /** * Listen to editor events and query merlin for types when editor gets the focus. */ public class ORFileEditorListener implements FileEditorManagerListener { private static final Key<InsightUpdateQueue> INSIGHT_QUEUE = new Key<>("reasonml.insight.queue"); private final Project m_project; private final ORCompilerManager m_compilerManager; private final List<VirtualFile> m_openedFiles = new ArrayList<>(); private final WeakList<InsightUpdateQueue> m_queues = new WeakList<>(); ORFileEditorListener(@NotNull Project project) { m_project = project; m_compilerManager = ServiceManager.getService(project, ORCompilerManager.class); } public void updateQueues() { for (InsightUpdateQueue queue : m_queues) { queue.initConfig(m_project); } } @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile sourceFile) { ServiceManager.getService(m_project, InsightManager.class).downloadRincewindIfNeeded(sourceFile); FileType fileType = sourceFile.getFileType(); if (FileHelper.isCompilable(fileType)) { FileEditor selectedEditor = source.getSelectedEditor(sourceFile); Document document = FileDocumentManager.getInstance().getDocument(sourceFile); if (selectedEditor instanceof TextEditor && document != null) { InsightUpdateQueue insightUpdateQueue = new InsightUpdateQueue(m_project, sourceFile); Disposer.register(selectedEditor, insightUpdateQueue); document.addDocumentListener(new ORDocumentEventListener(insightUpdateQueue), selectedEditor); ORPropertyChangeListener propertyChangeListener = new ORPropertyChangeListener(sourceFile, document, insightUpdateQueue); selectedEditor.addPropertyChangeListener(propertyChangeListener); Disposer.register(selectedEditor, () -> { selectedEditor.removePropertyChangeListener(propertyChangeListener); }); // Store the queue in the document, for easy access document.putUserData(INSIGHT_QUEUE, insightUpdateQueue); // Initial query when opening the editor insightUpdateQueue.queue(m_project, document); m_queues.add(insightUpdateQueue); } } m_openedFiles.add(sourceFile); } @Override public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { FileType fileType = file.getFileType(); if (FileHelper.isReason(fileType) || FileHelper.isOCaml(fileType)) { m_openedFiles.remove(file); } } boolean isOpen(@NotNull VirtualFile file) { return m_openedFiles.contains(file); } @Override public void selectionChanged(@NotNull FileEditorManagerEvent event) { VirtualFile newFile = event.getNewFile(); if (newFile != null) { FileType fileType = newFile.getFileType(); if (FileHelper.isReason(fileType) || FileHelper.isOCaml(fileType)) { // On tab change, we redo the background compilation Document document = FileDocumentManager.getInstance().getDocument(newFile); InsightUpdateQueue insightUpdateQueue = document == null ? null : document.getUserData(INSIGHT_QUEUE); if (insightUpdateQueue != null) { insightUpdateQueue.queue(m_project, document); } // and refresh inferred types InferredTypesService.queryForSelectedTextEditor(m_project); } } } class ORPropertyChangeListener implements PropertyChangeListener, Disposable { private final VirtualFile m_file; private final Document m_document; private final InsightUpdateQueue m_updateQueue; @Nullable private final Compiler m_compiler; ORPropertyChangeListener(@NotNull VirtualFile file, @NotNull Document document, @NotNull InsightUpdateQueue insightUpdateQueue) { m_file = file; m_document = document; m_updateQueue = insightUpdateQueue; m_compiler = m_compilerManager.getCompiler(m_file).orElse(null); } @Override public void dispose() { } @Override public void propertyChange(@NotNull PropertyChangeEvent evt) { if ("modified".equals(evt.getPropertyName()) && evt.getNewValue() == Boolean.FALSE && m_compiler != null) { // Document is saved, run the compiler !! m_compiler.runDefault(m_file, () -> m_updateQueue.queue(m_project, m_document)); //() -> ApplicationManager.getApplication().runReadAction(() -> { //InferredTypesService.clearTypes(m_project, m_file); //PsiFile psiFile = PsiManager.getInstance(m_project).findFile(m_file); //if (psiFile instanceof FileBase) { // ApplicationManager.getApplication().invokeLater(() -> { // Query types and update psi cache //PsiFile cmtFile = FileManager.findCmtFileFromSource(m_project, m_file.getNameWithoutExtension()); //if (cmtFile != null) { // Path cmtPath = FileSystems.getDefault().getPath(cmtFile.getVirtualFile().getPath()); // queryTypes(m_file, cmtPath, psiFile.getLanguage()); // EditorFactory.getInstance().refreshAllEditors(); //} //}); //} } } } class ORDocumentEventListener implements DocumentListener { private final InsightUpdateQueue m_queue; private int m_oldLinesCount; public ORDocumentEventListener(InsightUpdateQueue insightUpdateQueue) { m_queue = insightUpdateQueue; } @Override public void beforeDocumentChange(@NotNull DocumentEvent event) { Document document = event.getDocument(); m_oldLinesCount = document.getLineCount(); } @Override public void documentChanged(@NotNull DocumentEvent event) { Document document = event.getDocument(); // When document lines count change, we move the type annotations int newLineCount = document.getLineCount(); if (newLineCount != m_oldLinesCount) { CodeLensView.CodeLensInfo userData = m_project.getUserData(CodeLensView.CODE_LENS); if (userData != null) { VirtualFile file = FileDocumentManager.getInstance().getFile(document); if (file != null) { FileEditor selectedEditor = FileEditorManager.getInstance(m_project).getSelectedEditor(file); if (selectedEditor instanceof TextEditor) { TextEditor editor = (TextEditor) selectedEditor; LogicalPosition cursorPosition = editor.getEditor().offsetToLogicalPosition(event.getOffset()); int direction = newLineCount - m_oldLinesCount; userData.move(file, cursorPosition, direction); } } } } m_queue.queue(m_project, document); } } }