From 0ef0e3f7c19f064f971c45da570f9ec7c3a6735d Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 2 Jan 2020 21:16:20 +0100 Subject: [PATCH] i18n: show dialog with a table in batch mode under registry key: i18n.for.idea.project all properties would be put in the same properties file; resourceBundle name should correspond to the class name with static method `message` GitOrigin-RevId: ce1eed2e875533f1c1a2bc26f34083c4e088b381 --- plugins/java-i18n/src/META-INF/plugin.xml | 1 + .../codeInspection/i18n/I18nInspection.java | 37 +-- .../i18n/I18nizeBatchQuickFix.java | 239 ++++++++++++++++++ .../i18n/JavaI18nizeQuickFixDialog.java | 2 +- .../references/I18nizeQuickFixDialog.java | 17 +- 5 files changed, 272 insertions(+), 24 deletions(-) create mode 100644 plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nizeBatchQuickFix.java diff --git a/plugins/java-i18n/src/META-INF/plugin.xml b/plugins/java-i18n/src/META-INF/plugin.xml index eb586538f7fa..bda0cc96c63e 100644 --- a/plugins/java-i18n/src/META-INF/plugin.xml +++ b/plugins/java-i18n/src/META-INF/plugin.xml @@ -76,6 +76,7 @@ Edit property value at usage location, where it's displayed in folding placeholder + diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java index 67ff2fbcc02d..598f31eba1fe 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nInspection.java @@ -17,6 +17,7 @@ import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; @@ -519,27 +520,33 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen List fixes = new ArrayList<>(); - if (myOnTheFly && sourcePsi instanceof PsiLiteralExpression) { - if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) != null) { - fixes.add(new I18nizeConcatenationQuickFix()); - } - fixes.add(new I18nizeQuickFix()); + if (sourcePsi instanceof PsiLiteralExpression) { + if (myOnTheFly) { + if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) != null) { + fixes.add(new I18nizeConcatenationQuickFix()); + } + fixes.add(new I18nizeQuickFix()); - if (!isNotConstantFieldInitializer((PsiExpression)sourcePsi)) { - fixes.add(createIntroduceConstantFix()); - } + if (!isNotConstantFieldInitializer((PsiExpression)sourcePsi)) { + fixes.add(createIntroduceConstantFix()); + } - if (PsiUtil.isLanguageLevel5OrHigher(sourcePsi)) { - final JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); - for (PsiModifierListOwner element : nonNlsTargets) { - if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) { - if (!element.getManager().isInProject(element) || - facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) { - fixes.add(new NonNlsAnnotationProvider().createFix(element)); + if (PsiUtil.isLanguageLevel5OrHigher(sourcePsi)) { + final JavaPsiFacade facade = JavaPsiFacade.getInstance(myManager.getProject()); + for (PsiModifierListOwner element : nonNlsTargets) { + if (!AnnotationUtil.isAnnotated(element, AnnotationUtil.NLS, CHECK_HIERARCHY | CHECK_EXTERNAL)) { + if (!element.getManager().isInProject(element) || + facade.findClass(AnnotationUtil.NON_NLS, element.getResolveScope()) != null) { + fixes.add(new NonNlsAnnotationProvider().createFix(element)); + } } } } } + else if (Registry.is("i18n.for.idea.project") && + I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(sourcePsi) == null) { + fixes.add(new I18nizeBatchQuickFix()); + } } LocalQuickFix[] farr = fixes.toArray(LocalQuickFix.EMPTY_ARRAY); diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nizeBatchQuickFix.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nizeBatchQuickFix.java new file mode 100644 index 000000000000..e0b558fa1c8b --- /dev/null +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/I18nizeBatchQuickFix.java @@ -0,0 +1,239 @@ +// Copyright 2000-2017 JetBrains s.r.o. +// Use of this source code is governed by the Apache 2.0 license that can be +// found in the LICENSE file. +package com.intellij.codeInspection.i18n; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.codeInspection.BatchQuickFix; +import com.intellij.codeInspection.CommonProblemDescriptor; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.JavaTemplateUtil; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.lang.properties.PropertiesImplUtil; +import com.intellij.lang.properties.psi.PropertiesFile; +import com.intellij.lang.properties.references.I18nUtil; +import com.intellij.lang.properties.references.I18nizeQuickFixDialog; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.LabeledComponent; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.ui.*; +import com.intellij.ui.table.JBTable; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.ItemRemovable; +import gnu.trove.THashMap; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class I18nizeBatchQuickFix extends I18nizeQuickFix implements BatchQuickFix { + private static final Logger LOG = Logger.getInstance(I18nizeBatchQuickFix.class); + + + @Override + public void applyFix(@NotNull Project project, + @NotNull CommonProblemDescriptor[] descriptors, + @NotNull List psiElementsToIgnore, + @Nullable Runnable refreshViews) { + List keyValuePairs = ContainerUtil.mapNotNull(descriptors, descriptor -> { + PsiElement psiElement = ((ProblemDescriptor)descriptor).getPsiElement(); + if (psiElement instanceof PsiLiteralExpression && + I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(psiElement) == null) { + Object val = ((PsiLiteralExpression)psiElement).getValue(); + if (val instanceof String) { + String value = StringUtil.escapeStringCharacters((String)val); + String key = I18nizeQuickFixDialog.suggestUniquePropertyKey(value, null, null); + return new ReplacementBean(key, value, (PsiLiteralExpression)psiElement); + } + } + return null; + }); + + if (keyValuePairs.isEmpty()) return; + + I18NBatchDialog dialog = new I18NBatchDialog(project, keyValuePairs); + if (dialog.showAndGet()) { + PropertiesFile propertiesFile = dialog.getPropertiesFile(); + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + FileTemplate template = FileTemplateManager.getInstance(project).getCodeTemplate(JavaTemplateUtil.TEMPLATE_I18NIZED_EXPRESSION); + List files = ContainerUtil.mapNotNull(keyValuePairs, bean -> bean.getExpression().getContainingFile()); + if (files.isEmpty()) { + return; + } + files.add(propertiesFile.getContainingFile()); + + WriteCommandAction.runWriteCommandAction(project, getFamilyName(), null, () -> { + String bundleName = propertiesFile.getVirtualFile().getNameWithoutExtension(); + for (ReplacementBean bean : keyValuePairs) { + JavaI18nUtil.DEFAULT_PROPERTY_CREATION_HANDLER + .createProperty(project, Collections.singletonList(propertiesFile), bean.getKey(), bean.getValue(), PsiExpression.EMPTY_ARRAY); + PsiElement literalExpression = bean.getExpression(); + String i18NText = getI18NText(bean.getKey(), bean.getValue(), bundleName, template); + PsiExpression expression = factory.createExpressionFromText(i18NText, literalExpression); + literalExpression.replace(expression); + } + }, files.toArray(PsiFile.EMPTY_ARRAY)); + } + } + + private static String getI18NText(String key, String value, String bundleName, FileTemplate template) { + Map attributes = new THashMap<>(); + attributes.put(JavaI18nizeQuickFixDialog.PROPERTY_KEY_OPTION_KEY, StringUtil.escapeStringCharacters(key)); + attributes.put(JavaI18nizeQuickFixDialog.RESOURCE_BUNDLE_OPTION_KEY, bundleName); + attributes.put(JavaI18nizeQuickFixDialog.PROPERTY_VALUE_ATTR, StringUtil.escapeStringCharacters(value)); + + String text = null; + try { + text = template.getText(attributes); + } + catch (IOException e) { + LOG.error(e); + } + return text; + } + + private static class I18NBatchDialog extends DialogWrapper { + private static final @NonNls String LAST_USED_PROPERTIES_FILE = "LAST_USED_PROPERTIES_FILE"; + + @NotNull private final Project myProject; + private final List myKeyValuePairs; + private JComboBox myPropertiesFile; + + protected I18NBatchDialog(@NotNull Project project, List keyValuePairs) { + super(project, true); + myProject = project; + myKeyValuePairs = keyValuePairs; + setTitle(CodeInsightBundle.message("i18nize.dialog.title")); + init(); + } + + @Nullable + @Override + protected JComponent createNorthPanel() { + List files = I18nUtil.defaultSuggestPropertiesFiles(myProject); + myPropertiesFile = new ComboBox<>(ArrayUtil.toStringArray(files)); + new ComboboxSpeedSearch(myPropertiesFile); + if (!files.isEmpty()) { + myPropertiesFile.setSelectedItem(ObjectUtils.notNull(PropertiesComponent.getInstance(myProject).getValue(LAST_USED_PROPERTIES_FILE), + files.get(0))); + } + LabeledComponent> component = new LabeledComponent<>(); + component.setText("Property file:"); + component.setComponent(myPropertiesFile); + + return component; + } + + protected PropertiesFile getPropertiesFile() { + Object selectedItem = myPropertiesFile.getSelectedItem(); + if (selectedItem == null) return null; + String path = FileUtil.toSystemIndependentName((String)selectedItem); + VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); + return virtualFile != null + ? PropertiesImplUtil.getPropertiesFile(PsiManager.getInstance(myProject).findFile(virtualFile)) + : null; + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + JBTable table = new JBTable(new MyKeyValueModel()); + return ToolbarDecorator.createDecorator(table).setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + TableUtil.removeSelectedItems(table); + table.repaint(); + } + }).createPanel(); + } + + @Override + protected void doOKAction() { + PropertiesComponent.getInstance(myProject).setValue(LAST_USED_PROPERTIES_FILE, (String)myPropertiesFile.getSelectedItem()); + super.doOKAction(); + } + + private class MyKeyValueModel extends AbstractTableModel implements ItemRemovable { + @Override + public int getRowCount() { + return myKeyValuePairs.size(); + } + + @Override + public String getColumnName(int column) { + return column == 0 ? "Key" : "Value"; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == 0; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + ReplacementBean pair = myKeyValuePairs.get(rowIndex); + return columnIndex == 0 ? pair.getKey() : pair.getValue(); + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if (columnIndex == 0) { + ReplacementBean bean = myKeyValuePairs.get(rowIndex); + myKeyValuePairs.set(rowIndex, new ReplacementBean((String)aValue, bean.getValue(), bean.getExpression())); + } + } + + @Override + public void removeRow(int idx) { + myKeyValuePairs.remove(idx); + } + } + } + + private static class ReplacementBean { + private final String myKey; + private final String myValue; + private final PsiLiteralExpression myExpression; + + private ReplacementBean(String key, String value, PsiLiteralExpression expression) { + myKey = key; + myValue = value; + myExpression = expression; + } + + public String getKey() { + return myKey; + } + + private String getValue() { + return myValue; + } + + private PsiLiteralExpression getExpression() { + return myExpression; + } + } +} diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nizeQuickFixDialog.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nizeQuickFixDialog.java index c31da2a62f6c..e361b9200f21 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nizeQuickFixDialog.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaI18nizeQuickFixDialog.java @@ -56,7 +56,7 @@ public class JavaI18nizeQuickFixDialog extends I18nizeQuickFixDialog { private final boolean myShowPreview; @NonNls public static final String PROPERTY_KEY_OPTION_KEY = "PROPERTY_KEY"; - @NonNls private static final String RESOURCE_BUNDLE_OPTION_KEY = "RESOURCE_BUNDLE"; + @NonNls public static final String RESOURCE_BUNDLE_OPTION_KEY = "RESOURCE_BUNDLE"; @NonNls public static final String PROPERTY_VALUE_ATTR = "PROPERTY_VALUE"; public JavaI18nizeQuickFixDialog(@NotNull Project project, diff --git a/plugins/properties/src/com/intellij/lang/properties/references/I18nizeQuickFixDialog.java b/plugins/properties/src/com/intellij/lang/properties/references/I18nizeQuickFixDialog.java index ee824b3b468f..62f6657717df 100644 --- a/plugins/properties/src/com/intellij/lang/properties/references/I18nizeQuickFixDialog.java +++ b/plugins/properties/src/com/intellij/lang/properties/references/I18nizeQuickFixDialog.java @@ -209,11 +209,13 @@ public class I18nizeQuickFixDialog extends DialogWrapper implements I18nizeQuick if (myCustomization.suggestedName != null) { return myCustomization.suggestedName; } + return suggestUniquePropertyKey(value, defaultSuggestPropertyKey(value), getPropertiesFile()); + } + public static String suggestUniquePropertyKey(String value, String defaultKey, PropertiesFile propertiesFile) { // suggest property key not existing in this file - String key = defaultSuggestPropertyKey(value); value = PATTERN.matcher(Normalizer.normalize(value, Normalizer.Form.NFD)).replaceAll(""); - if (key == null) { + if (defaultKey == null) { final StringBuilder result = new StringBuilder(); boolean insertDotBeforeNextWord = false; for (int i = 0; i < value.length(); i++) { @@ -241,21 +243,20 @@ public class I18nizeQuickFixDialog extends DialogWrapper implements I18nizeQuick } } } - key = result.toString(); + defaultKey = result.toString(); } - PropertiesFile propertiesFile = getPropertiesFile(); if (propertiesFile != null) { - if (propertiesFile.findPropertyByKey(key) == null) return key; + if (propertiesFile.findPropertyByKey(defaultKey) == null) return defaultKey; int suffix = 1; - while (propertiesFile.findPropertyByKey(key + suffix) != null) { + while (propertiesFile.findPropertyByKey(defaultKey + suffix) != null) { suffix++; } - return key + suffix; + return defaultKey + suffix; } else { - return key; + return defaultKey; } }