diff --git a/plugins/properties/properties-psi-api/resources/messages/PropertiesBundle.properties b/plugins/properties/properties-psi-api/resources/messages/PropertiesBundle.properties index 7ec03ad5923a..0772f4cb094f 100644 --- a/plugins/properties/properties-psi-api/resources/messages/PropertiesBundle.properties +++ b/plugins/properties/properties-psi-api/resources/messages/PropertiesBundle.properties @@ -43,7 +43,8 @@ options.properties.attribute.descriptor.valid.string.escape=Valid string escape options.properties.attribute.descriptor.invalid.string.escape=Invalid string escape new.property.dialog.title=New Property Key -new.property.dialog.name.prompt.text=Enter new property key name +new.property.dialog.name.prompt.text=Enter new property key name: +new.property.dialog.checkbox.text=Insert after selected property resource.bundle.renamer=Rename Resource Bundle Properties Files resource.bundle.renamer.dialog.description=Rename resource bundle properties files with the following names to: diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundleFileStructureViewElement.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundleFileStructureViewElement.java index 6caf5e828137..d2af3f6dcc2e 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundleFileStructureViewElement.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/editor/ResourceBundleFileStructureViewElement.java @@ -43,7 +43,7 @@ public class ResourceBundleFileStructureViewElement implements StructureViewTree private final ResourceBundle myResourceBundle; private volatile boolean myShowOnlyIncomplete; - private final Map myElements = ContainerUtil.newHashMap(); + private final Map myElements = ContainerUtil.newLinkedHashMap(); public ResourceBundleFileStructureViewElement(final ResourceBundle resourceBundle) { myResourceBundle = resourceBundle; @@ -83,7 +83,12 @@ public class ResourceBundleFileStructureViewElement implements StructureViewTree myElements.remove(remain); } - return myElements.values().toArray(new StructureViewTreeElement[myElements.size()]); + StructureViewTreeElement[] result = new StructureViewTreeElement[propertyNames.size()]; + int i = 0; + for (String key : propertyNames.keySet()) { + result[i++] = myElements.get(key); + } + return result; } public static MultiMap getPropertiesMap(ResourceBundle resourceBundle, boolean onlyIncomplete) { diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java b/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java index 983bb5c3e6ba..94b971c6f080 100644 --- a/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java +++ b/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java @@ -16,6 +16,8 @@ package com.intellij.lang.properties.editor; import com.intellij.icons.AllIcons; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.lang.properties.IProperty; import com.intellij.lang.properties.PropertiesBundle; import com.intellij.lang.properties.ResourceBundle; import com.intellij.lang.properties.psi.PropertiesFile; @@ -23,12 +25,14 @@ import com.intellij.lang.properties.structureView.PropertiesPrefixGroup; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -38,6 +42,9 @@ import org.jetbrains.annotations.Nullable; * @author Dmitry Batkovich */ class NewPropertyAction extends AnAction { + private final static Logger LOG = Logger.getInstance(NewPropertyAction.class); + + private final static String ADD_NEW_PROPERTY_AFTER_SELECTED_PROP = "add.property.after.selected"; private final boolean myEnabledForce; @@ -111,12 +118,60 @@ class NewPropertyAction extends AnAction { throw new IllegalStateException("unsupported type: " + selectedElement.getClass()); } } - Messages.showInputDialog(project, - PropertiesBundle.message("new.property.dialog.name.prompt.text"), - PropertiesBundle.message("new.property.dialog.title"), - Messages.getQuestionIcon(), - null, - new NewPropertyNameValidator(resourceBundleEditor, prefix, separator)); + + final ResourceBundlePropertiesUpdateManager propertiesUpdateManager = resourceBundleEditor.getPropertiesInsertDeleteManager(); + final NewPropertyNameValidator nameValidator = new NewPropertyNameValidator(resourceBundleEditor, prefix, separator); + final String keyToInsert; + + final IProperty anchor; + IProperty selectedProperty = resourceBundleEditor.getSelectedProperty(); + if (propertiesUpdateManager.isAlphaSorted() || !propertiesUpdateManager.isSorted() || selectedProperty == null) { + keyToInsert = Messages.showInputDialog(project, + PropertiesBundle.message("new.property.dialog.name.prompt.text"), + PropertiesBundle.message("new.property.dialog.title"), + Messages.getQuestionIcon(), + null, + nameValidator); + anchor = null; + } else { + final Pair keyNameAndInsertPlaceModification = + Messages.showInputDialogWithCheckBox(PropertiesBundle.message("new.property.dialog.name.prompt.text"), + PropertiesBundle.message("new.property.dialog.title"), + PropertiesBundle.message("new.property.dialog.checkbox.text"), + PropertiesComponent.getInstance().getBoolean(ADD_NEW_PROPERTY_AFTER_SELECTED_PROP, false), + true, + Messages.getQuestionIcon(), + null, + nameValidator); + keyToInsert = keyNameAndInsertPlaceModification.getFirst(); + final Boolean insertAfterSelecterProperty = keyNameAndInsertPlaceModification.getSecond(); + PropertiesComponent.getInstance().setValue(ADD_NEW_PROPERTY_AFTER_SELECTED_PROP, insertAfterSelecterProperty, false); + anchor = insertAfterSelecterProperty ? selectedProperty : null; + } + if (keyToInsert != null) { + final ResourceBundlePropertiesUpdateManager updateManager = resourceBundleEditor.getPropertiesInsertDeleteManager(); + final Runnable insertionAction = () -> { + if (anchor == null) { + updateManager.insertNewProperty(keyToInsert, ""); + } else { + final String anchorKey = anchor.getKey(); + LOG.assertTrue(anchorKey != null); + updateManager.insertAfter(keyToInsert, "", anchorKey); + } + }; + ResourceBundleEditor finalResourceBundleEditor = resourceBundleEditor; + ApplicationManager.getApplication().runWriteAction(() -> { + WriteCommandAction.runWriteCommandAction(bundle.getProject(), insertionAction); + finalResourceBundleEditor.flush(); + }); + + resourceBundleEditor.updateTreeRoot(); + resourceBundleEditor + .getStructureViewComponent() + .getTreeBuilder() + .queueUpdate() + .doWhenDone(() -> finalResourceBundleEditor.selectProperty(keyToInsert)); + } } @Override @@ -160,14 +215,6 @@ class NewPropertyAction extends AnAction { } } - final Runnable action = () -> { - myResourceBundleEditor.getPropertiesInsertDeleteManager().insertNewProperty(newPropertyName, ""); - myResourceBundleEditor.flush(); - }; - ApplicationManager.getApplication().runWriteAction(() -> WriteCommandAction.runWriteCommandAction(resourceBundle.getProject(), action)); - - myResourceBundleEditor.updateTreeRoot(); - myResourceBundleEditor.selectProperty(newPropertyName); return true; } } diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java index fb619d7c27e0..d36c3c586def 100644 --- a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java +++ b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java @@ -615,7 +615,7 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document } @Nullable - private IProperty getSelectedProperty() { + IProperty getSelectedProperty() { final Collection selectedNode = getSelectedNodes(); if (selectedNode.isEmpty()) { return null; diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java index 7525df9f2338..2d34a3de9342 100644 --- a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java +++ b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundlePropertiesUpdateManager.java @@ -34,8 +34,8 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.graph.CachingSemiGraph; import com.intellij.util.graph.DFSTBuilder; import com.intellij.util.graph.GraphGenerator; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; import java.util.*; @@ -72,6 +72,17 @@ public class ResourceBundlePropertiesUpdateManager { } } + public void insertAfter(@NotNull String key, @NotNull String value, @NotNull String anchor) { + if (myAlphaSorted || !myOrdered) { + throw new IllegalStateException("Can't insert new properties by anchor while resource bundle is alpha-sorted"); + } + final PropertiesFile file = myResourceBundle.getDefaultPropertiesFile(); + final IProperty anchorProperty = file.findPropertyByKey(anchor); + file.addPropertyAfter(key, value, anchorProperty); + final int anchorIndex = myKeysOrder.indexOf(anchor); + myKeysOrder.add(anchorIndex + 1, key); + } + public void insertOrUpdateTranslation(String key, String value, final PropertiesFile propertiesFile) throws IncorrectOperationException { final IProperty property = propertiesFile.findPropertyByKey(key); if (property != null) { @@ -214,8 +225,11 @@ public class ResourceBundlePropertiesUpdateManager { } } - @TestOnly public boolean isAlphaSorted() { return myAlphaSorted; } + + public boolean isSorted() { + return myOrdered; + } }