package fr.adrienbrault.idea.symfony2plugin.codeInsight.caret.overlay;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.caret.overlay.component.CaretOverlayComponent;
import fr.adrienbrault.idea.symfony2plugin.codeInsight.caret.overlay.util.CaretTextOverlayUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class CaretTextOverlayListener implements CaretListener {
private int startDelayMs = 250;
private final Object lock = new Object();
private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> schedule;
@Override
public void caretPositionChanged(final CaretEvent caretEvent) {
synchronized (lock) {
if(schedule != null) {
schedule.cancel(true);
schedule = null;
}
}
final Editor editor = caretEvent.getEditor();
removeOverlays(editor);
if(!(editor instanceof EditorEx)) {
return;
}
final Project project = editor.getProject();
if(project == null || DumbService.getInstance(project).isDumb() || !Symfony2ProjectComponent.isEnabled(project)) {
return;
}
synchronized (lock) {
schedule = executor.schedule(() ->
ApplicationManager.getApplication().runReadAction(new MyPsiElementRunnable(project, caretEvent, editor)), startDelayMs, TimeUnit.MILLISECONDS
);
}
}
@Override
public void caretAdded(CaretEvent caretEvent) {
}
@Override
public void caretRemoved(CaretEvent caretEvent) {
}
private void invokeUiComponent(final @NotNull Editor editor, final @NotNull PsiElement psiElement, final @NotNull CaretTextOverlayElement overlayElement) {
SwingUtilities.invokeLater(() -> {
CaretOverlayComponent component = new CaretOverlayComponent(editor, overlayElement.getText(), psiElement.getTextOffset(), psiElement.getLanguage());
editor.getContentComponent().add(component);
component.setSize(((EditorEx) editor).getScrollPane().getViewport().getViewSize());
});
}
private void removeOverlays(@NotNull Editor editor) {
for (Component component : editor.getContentComponent().getComponents()) {
if(component instanceof CaretOverlayComponent) {
editor.getContentComponent().remove(component);
}
}
}
synchronized public void clear() {
synchronized (lock) {
executor.shutdownNow();
schedule = null;
}
}
private class MyPsiElementRunnable implements Runnable {
private final Project project;
private final CaretEvent caretEvent;
private final Editor editor;
public MyPsiElementRunnable(Project project, CaretEvent caretEvent, Editor editor) {
this.project = project;
this.caretEvent = caretEvent;
this.editor = editor;
}
@Override
public void run() {
Caret caret = caretEvent.getCaret();
if(caret == null) {
return;
}
if (DumbService.getInstance(project).isDumb() || project.isDisposed()) {
return;
}
final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if(psiFile == null) {
return;
}
CaretTextOverlayArguments args = null;
for (CaretTextOverlay caretTextOverlay : CaretTextOverlayUtil.getExtensions()) {
if(!caretTextOverlay.accepts(psiFile.getVirtualFile())) {
continue;
}
if(args == null) {
PsiElement element = psiFile.findElementAt(caret.getOffset());
if (element == null) {
return;
}
args = new CaretTextOverlayArguments(caretEvent, psiFile, element);
}
CaretTextOverlayElement overlay = caretTextOverlay.getOverlay(args);
if(overlay == null) {
continue;
}
invokeUiComponent(editor, args.getPsiElement(), overlay);
return;
}
}
}
}