package com.jetbrains.lang.dart.ide.refactoring;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.NameSuggestionsField;
import com.intellij.usageView.UsageInfo;
import com.intellij.usages.*;
import com.intellij.util.SmartList;
import com.intellij.util.ui.JBUI;
import com.intellij.xml.util.XmlStringUtil;
import com.intellij.xml.util.XmlTagUtilBase;
import com.jetbrains.lang.dart.DartBundle;
import com.jetbrains.lang.dart.DartComponentType;
import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService;
import com.jetbrains.lang.dart.assists.AssistUtils;
import com.jetbrains.lang.dart.ide.findUsages.DartServerFindUsagesHandler;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.dartlang.analysis.server.protocol.SourceChange;
import org.dartlang.analysis.server.protocol.SourceEdit;
import org.dartlang.analysis.server.protocol.SourceFileEdit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
class DartRenameDialog extends ServerRefactoringDialog<ServerRenameRefactoring> {
private final JLabel myNewNamePrefix = new JLabel("");
private NameSuggestionsField myNameSuggestionsField;
DartRenameDialog(@NotNull final Project project, @Nullable final Editor editor, @NotNull final ServerRenameRefactoring refactoring) {
super(project, editor, refactoring);
setTitle("Rename " + refactoring.getElementKindName());
createNewNameComponent();
init();
}
@Override
protected void canRun() throws ConfigurationException {
if (Comparing.strEqual(getNewName(), myRefactoring.getOldName())) {
throw new ConfigurationException(null);
}
super.canRun();
}
@Override
protected JComponent createCenterPanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbConstraints = new GridBagConstraints();
gbConstraints.insets = JBUI.insetsBottom(4);
gbConstraints.weighty = 0;
gbConstraints.weightx = 1;
gbConstraints.gridwidth = GridBagConstraints.REMAINDER;
gbConstraints.fill = GridBagConstraints.BOTH;
JLabel nameLabel = new JLabel();
panel.add(nameLabel, gbConstraints);
nameLabel.setText(XmlStringUtil.wrapInHtml(XmlTagUtilBase.escapeString(getLabelText(), false)));
gbConstraints.insets = JBUI.insetsBottom(4);
gbConstraints.gridwidth = 1;
gbConstraints.fill = GridBagConstraints.NONE;
gbConstraints.weightx = 0;
gbConstraints.gridx = 0;
gbConstraints.anchor = GridBagConstraints.WEST;
panel.add(myNewNamePrefix, gbConstraints);
gbConstraints.insets = JBUI.insetsBottom(8);
gbConstraints.gridwidth = 2;
gbConstraints.fill = GridBagConstraints.BOTH;
gbConstraints.weightx = 1;
gbConstraints.gridx = 0;
gbConstraints.weighty = 1;
panel.add(myNameSuggestionsField.getComponent(), gbConstraints);
return panel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myNameSuggestionsField.getFocusableComponent();
}
private void createNewNameComponent() {
String[] suggestedNames = getSuggestedNames();
myNameSuggestionsField = new NameSuggestionsField(suggestedNames, myProject, FileTypes.PLAIN_TEXT, myEditor) {
@Override
protected boolean shouldSelectAll() {
return myEditor == null || myEditor.getSettings().isPreselectRename();
}
};
myNameSuggestionsField.addDataChangedListener(this::processNewNameChanged);
}
@NotNull
private String getLabelText() {
final String kindName = myRefactoring.getElementKindName().toLowerCase(Locale.US);
final String name = myRefactoring.getOldName().isEmpty() ? kindName : kindName + " " + myRefactoring.getOldName();
return RefactoringBundle.message("rename.0.and.its.usages.to", name);
}
private String getNewName() {
return myNameSuggestionsField.getEnteredName().trim();
}
@NotNull
private String[] getSuggestedNames() {
return new String[]{myRefactoring.getOldName()};
}
private void processNewNameChanged() {
myRefactoring.setNewName(getNewName());
}
@Override
protected boolean hasPreviewButton() {
return true;
}
@Override
protected boolean isForcePreview() {
final Set<String> potentialEdits = myRefactoring.getPotentialEdits();
return !potentialEdits.isEmpty() && !ApplicationManager.getApplication().isUnitTestMode();
}
@Override
protected void previewRefactoring() {
final UsageViewPresentation presentation = new UsageViewPresentation();
presentation.setTabText(RefactoringBundle.message("usageView.tabText"));
presentation.setShowCancelButton(true);
presentation
.setTargetsNodeText(RefactoringBundle.message("0.to.be.renamed.to.1.2", myRefactoring.getElementKindName(), "", getNewName()));
presentation.setNonCodeUsagesString(DartBundle.message("usages.in.comments.to.rename"));
presentation.setCodeUsagesString(DartBundle.message("usages.in.code.to.rename"));
presentation.setDynamicUsagesString(DartBundle.message("dynamic.usages.to.rename"));
presentation.setUsageTypeFilteringAvailable(false);
final List<UsageTarget> usageTargets = new SmartList<>();
final Map<Usage, String> usageToEditIdMap = new THashMap<>();
fillTargetsAndUsageToEditIdMap(usageTargets, usageToEditIdMap);
final UsageTarget[] targets = usageTargets.toArray(new UsageTarget[usageTargets.size()]);
final Set<Usage> usageSet = usageToEditIdMap.keySet();
final Usage[] usages = usageSet.toArray(new Usage[usageSet.size()]);
final UsageView usageView = UsageViewManager.getInstance(myProject).showUsages(targets, usages, presentation);
final SourceChange sourceChange = myRefactoring.getChange();
assert sourceChange != null;
usageView.addPerformOperationAction(createRefactoringRunnable(usageView, usageToEditIdMap),
sourceChange.getMessage(),
DartBundle.message("rename.need.reRun"),
RefactoringBundle.message("usageView.doAction"), false);
}
private void fillTargetsAndUsageToEditIdMap(@NotNull final List<UsageTarget> usageTargets,
@NotNull final Map<Usage, String> usageToEditIdMap) {
final SourceChange change = myRefactoring.getChange();
assert change != null;
final DartAnalysisServerService service = DartAnalysisServerService.getInstance(myProject);
final PsiManager psiManager = PsiManager.getInstance(myProject);
for (SourceFileEdit fileEdit : change.getEdits()) {
final VirtualFile file = AssistUtils.findVirtualFile(fileEdit);
final PsiFile psiFile = file == null ? null : psiManager.findFile(file);
if (psiFile == null) continue;
for (SourceEdit sourceEdit : fileEdit.getEdits()) {
final int offset = service.getConvertedOffset(file, sourceEdit.getOffset());
final int length = service.getConvertedOffset(file, sourceEdit.getOffset() + sourceEdit.getLength()) - offset;
final TextRange range = TextRange.create(offset, offset + length);
final boolean potentialUsage = myRefactoring.getPotentialEdits().contains(sourceEdit.getId());
final PsiElement usageElement = DartServerFindUsagesHandler.getUsagePsiElement(psiFile, range);
if (usageElement != null) {
if (DartComponentType.typeOf(usageElement) != null) {
usageTargets.add(new PsiElement2UsageTargetAdapter(usageElement));
}
else {
final UsageInfo usageInfo = DartServerFindUsagesHandler.getUsageInfo(usageElement, range, potentialUsage);
if (usageInfo != null) {
usageToEditIdMap.put(new UsageInfo2UsageAdapter(usageInfo), sourceEdit.getId());
}
}
}
}
}
}
@NotNull
private Runnable createRefactoringRunnable(@NotNull final UsageView usageView, @NotNull final Map<Usage, String> usageToEditIdMap) {
return () -> {
final Set<String> excludedIds = new THashSet<>();
// usageView.getExcludedUsages() and usageView.getUsages() doesn't contain deleted usages, that's why we need to start with full set usageToEditIdMap.keySet()
final Set<Usage> excludedUsages = new THashSet<>(usageToEditIdMap.keySet());
excludedUsages.removeAll(usageView.getUsages());
excludedUsages.addAll(usageView.getExcludedUsages());
for (Usage excludedUsage : excludedUsages) {
excludedIds.add(usageToEditIdMap.get(excludedUsage));
}
super.doRefactoring(excludedIds);
};
}
}