blob: 648fb431ad22413360b413ef61c0755a07ec1e98 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.changeSignature;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorBundle;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* User: anna
* Date: Sep 6, 2010
*/
public class ChangeSignatureGestureDetector extends PsiTreeChangeAdapter implements EditorFactoryListener, Disposable {
private final Map<VirtualFile, MyDocumentChangeAdapter> myListenerMap = new HashMap<VirtualFile, MyDocumentChangeAdapter>();
private static final Logger LOG = Logger.getInstance("#" + ChangeSignatureGestureDetector.class.getName());
private boolean myDeaf = false;
private final FileDocumentManager myDocumentManager;
private final PsiManager myPsiManager;
private final FileEditorManager myFileEditorManager;
private final Project myProject;
private final PsiDocumentManager myPsiDocumentManager;
public ChangeSignatureGestureDetector(final PsiDocumentManager psiDocumentManager,
final FileDocumentManager documentManager,
final PsiManager psiManager,
final FileEditorManager fileEditorManager,
final Project project) {
myDocumentManager = documentManager;
myPsiDocumentManager = psiDocumentManager;
myPsiManager = psiManager;
myFileEditorManager = fileEditorManager;
myProject = project;
myPsiManager.addPsiTreeChangeListener(this, this);
EditorFactory.getInstance().addEditorFactoryListener(this, this);
Disposer.register(this, new Disposable() {
@Override
public void dispose() {
LOG.assertTrue(myListenerMap.isEmpty(), myListenerMap);
}
});
}
public static ChangeSignatureGestureDetector getInstance(Project project){
return project.getComponent(ChangeSignatureGestureDetector.class);
}
public boolean isChangeSignatureAvailable(@NotNull PsiElement element) {
final MyDocumentChangeAdapter adapter = myListenerMap.get(PsiUtilCore.getVirtualFile(element));
if (adapter != null) {
final ChangeInfo currentInfo = adapter.getCurrentInfo();
if (currentInfo != null && element.equals(adapter.getInitialChangeInfo().getMethod())) {
return true;
}
}
return false;
}
public void dismissForElement(PsiElement method) {
final PsiFile psiFile = method.getContainingFile();
final ChangeInfo initialChangeInfo = getInitialChangeInfo(psiFile);
if (initialChangeInfo != null && initialChangeInfo.getMethod() == method) {
clearSignatureChange(psiFile);
}
}
public boolean containsChangeSignatureChange(@NotNull PsiFile file) {
return getChangeInfo(file) != null;
}
@Nullable
public ChangeInfo getChangeInfo(@NotNull PsiFile file) {
final MyDocumentChangeAdapter adapter = myListenerMap.get(file.getVirtualFile());
return adapter != null ? adapter.getCurrentInfo() : null;
}
@Nullable
public ChangeInfo getInitialChangeInfo(@NotNull PsiFile file) {
final MyDocumentChangeAdapter adapter = myListenerMap.get(file.getVirtualFile());
return adapter != null ? adapter.getInitialChangeInfo() : null;
}
public void changeSignature(PsiFile file, final boolean silently) {
try {
myDeaf = true;
final MyDocumentChangeAdapter changeBean = myListenerMap.get(file.getVirtualFile());
final ChangeInfo currentInfo = changeBean.getCurrentInfo();
if (currentInfo != null) {
final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(currentInfo.getLanguage());
if (detector.performChange(currentInfo, changeBean.getInitialChangeInfo(), changeBean.getInitialText(), silently)) {
changeBean.reinit();
}
}
}
finally {
myDeaf = false;
}
}
@Override
public void beforeChildRemoval(@NotNull PsiTreeChangeEvent event) {
final PsiElement child = event.getChild();
if (child instanceof PsiFile) {
final PsiFile psiFile = (PsiFile)child;
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null && myListenerMap.containsKey(virtualFile)) {
final Document document = myDocumentManager.getDocument(virtualFile);
if (document != null) {
removeDocListener(document, virtualFile);
} else {
myListenerMap.remove(virtualFile);
}
}
}
}
@Override
public void childRemoved(@NotNull PsiTreeChangeEvent event) {
change(event.getParent());
}
@Override
public void childReplaced(@NotNull PsiTreeChangeEvent event) {
change(event.getChild());
}
@Override
public void childAdded(@NotNull PsiTreeChangeEvent event) {
change(event.getChild());
}
private void change(PsiElement child) {
if (myDeaf) return;
if (child == null || !child.isValid()) return;
final PsiFile file = child.getContainingFile();
if (file != null) {
final MyDocumentChangeAdapter changeBean = myListenerMap.get(file.getVirtualFile());
if (changeBean != null && changeBean.getInitialText() != null) {
final Editor editor = myFileEditorManager.getSelectedTextEditor();
if (editor != null && TemplateManager.getInstance(myProject).getActiveTemplate(editor) != null) return;
final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(child.getLanguage());
if (detector == null) return;
if (detector.ignoreChanges(child)) return;
final String currentSignature = detector.extractSignature(child, changeBean.getInitialChangeInfo());
if (currentSignature == null) {
changeBean.reinit();
} else {
changeBean.addSignature(currentSignature);
}
}
}
}
@Override
public void editorCreated(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (editor.getProject() != myProject) return;
addDocListener(editor.getDocument());
}
public void addDocListener(Document document) {
if (document == null) return;
final VirtualFile file = myDocumentManager.getFile(document);
if (file != null && file.isValid() && !myListenerMap.containsKey(file)) {
final PsiFile psiFile = myPsiManager.findFile(file);
if (psiFile == null || !psiFile.isPhysical()) return;
final MyDocumentChangeAdapter adapter = new MyDocumentChangeAdapter();
document.addDocumentListener(adapter);
myListenerMap.put(file, adapter);
}
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
final EditorEx editor = (EditorEx)event.getEditor();
final Document document = editor.getDocument();
VirtualFile file = myDocumentManager.getFile(document);
if (file == null) {
file = editor.getVirtualFile();
}
if (file != null && file.isValid()) {
for (FileEditor fileEditor : myFileEditorManager.getAllEditors(file)) {
if (fileEditor instanceof TextEditor && ((TextEditor)fileEditor).getEditor() != editor) {
return;
}
}
}
removeDocListener(document, file);
}
public void removeDocListener(Document document, VirtualFile file) {
final MyDocumentChangeAdapter adapter = myListenerMap.remove(file);
if (adapter != null) {
document.removeDocumentListener(adapter);
}
}
public void clearSignatureChange(PsiFile file) {
final MyDocumentChangeAdapter adapter = myListenerMap.get(file.getVirtualFile());
if (adapter != null) {
adapter.reinit();
}
}
@Nullable
private static ChangeInfo createCurrentChangeInfo(String signature, @NotNull ChangeInfo currentInfo, String initialName) {
final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(currentInfo.getLanguage());
return detector != null ? detector.createNextChangeInfo(signature, currentInfo, initialName) : null;
}
@Nullable
private static ChangeInfo createInitialChangeInfo(@NotNull PsiElement element) {
final LanguageChangeSignatureDetector detector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(element.getLanguage());
return detector != null ? detector.createInitialChangeInfo(element) : null;
}
private class MyDocumentChangeAdapter extends DocumentAdapter {
private final @NonNls String PASTE_COMMAND_NAME = EditorBundle.message("paste.command.name");
private final @NonNls String TYPING_COMMAND_NAME = EditorBundle.message("typing.in.editor.command.name");
private String myInitialText;
private String myInitialName;
private ChangeInfo myInitialChangeInfo;
private ChangeInfo myCurrentInfo;
private final List<String> mySignatures = new ArrayList<String>();
public MyDocumentChangeAdapter() {
}
public String getInitialText() {
return myInitialText;
}
public ChangeInfo getCurrentInfo() {
if (myInitialChangeInfo == null) return null;
synchronized (mySignatures) {
if (!mySignatures.isEmpty()) {
if (myCurrentInfo == null) {
myCurrentInfo = myInitialChangeInfo;
}
for (String signature : mySignatures) {
if (myInitialText.equals(signature)) {
reinit();
break;
}
try {
myCurrentInfo = createCurrentChangeInfo(signature, myCurrentInfo, myInitialName);
if (myCurrentInfo == null) {
reinit();
break;
}
}
catch (IncorrectOperationException ignore) {
}
}
mySignatures.clear();
}
}
if (myCurrentInfo instanceof RenameChangeInfo) return myCurrentInfo;
return myInitialChangeInfo != null && myInitialChangeInfo.equals(myCurrentInfo) ? null : myCurrentInfo;
}
public void addSignature(String signature) {
synchronized (mySignatures) {
if (!mySignatures.contains(signature)) {
mySignatures.add(signature);
}
}
}
@Override
public void beforeDocumentChange(DocumentEvent e) {
if (myDeaf) return;
if (DumbService.isDumb(myProject)) return;
if (myInitialText == null) {
final Document document = e.getDocument();
final PsiDocumentManager documentManager = myPsiDocumentManager;
if (!documentManager.isUncommited(document)) {
final CommandProcessor processor = CommandProcessor.getInstance();
final String currentCommandName = processor.getCurrentCommandName();
if (!Comparing.strEqual(TYPING_COMMAND_NAME, currentCommandName) &&
!Comparing.strEqual(PASTE_COMMAND_NAME, currentCommandName) &&
!Comparing.strEqual("Cut", currentCommandName) &&
!Comparing.strEqual(LanguageChangeSignatureDetector.MOVE_PARAMETER, currentCommandName) &&
!Comparing.equal(EditorActionUtil.DELETE_COMMAND_GROUP, processor.getCurrentCommandGroupId())) {
return;
}
final PsiFile file = documentManager.getPsiFile(document);
if (file != null) {
final PsiElement element = file.findElementAt(e.getOffset());
if (element != null) {
final ChangeInfo info = createInitialChangeInfo(element);
if (info != null) {
final PsiElement method = info.getMethod();
final TextRange textRange = method.getTextRange();
if (document.getTextLength() <= textRange.getEndOffset()) return;
if (method instanceof PsiNameIdentifierOwner) {
myInitialName = ((PsiNameIdentifierOwner)method).getName();
}
myInitialText = document.getText(textRange);
myInitialChangeInfo = info;
}
}
}
}
}
}
public ChangeInfo getInitialChangeInfo() {
return myInitialChangeInfo;
}
public void reinit() {
synchronized (mySignatures) {
mySignatures.clear();
}
myInitialText = null;
myInitialName = null;
myInitialChangeInfo = null;
myCurrentInfo = null;
}
}
@Override
public void dispose() {
}
}