From 47e6e070ab01fcdd50063b6022f339de6806b3bd Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Wed, 11 Feb 2015 15:21:37 +0300 Subject: [PATCH 01/27] XmlPropertiesFiles inserts property in alpha-order if it is possible --- .../properties/xml/XmlPropertiesFileImpl.java | 145 ++++++++++++++---- plugins/properties/testData/xml/bar.xml | 7 + .../properties/xml/XmlPropertiesTest.java | 19 +++ 3 files changed, 145 insertions(+), 26 deletions(-) create mode 100644 plugins/properties/testData/xml/bar.xml diff --git a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java index a3a94c8f7f18..3590681522f8 100644 --- a/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java +++ b/plugins/properties/properties-psi-impl/src/com/intellij/lang/properties/xml/XmlPropertiesFileImpl.java @@ -33,7 +33,10 @@ import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.reference.SoftLazyValue; +import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; @@ -47,29 +50,54 @@ import java.util.*; * Date: 7/26/11 */ public class XmlPropertiesFileImpl extends XmlPropertiesFile { - private static final Key> KEY = Key.create("xml properties file"); private final XmlFile myFile; - private final SoftLazyValue> myPropertiesMap = new SoftLazyValue>() { + private final SoftLazyValue myInfo = new SoftLazyValue() { @NotNull @Override - protected MultiMap compute() { - XmlTag rootTag = myFile.getRootTag(); - if (rootTag == null) { - return MultiMap.emptyInstance(); - } - - XmlTag[] entries = rootTag.findSubTags("entry"); - MultiMap map = new MultiMap(); - - for (XmlTag entry : entries) { - XmlProperty property = new XmlProperty(entry, XmlPropertiesFileImpl.this); - map.putValue(property.getKey(), property); - } - return map; + protected Info compute() { + return new Info(); } }; + private class Info { + private final MultiMap myPropertiesMap = MultiMap.create(); + private List myPropertiesOrder; + private boolean mySorted; + + public Info() { + XmlTag rootTag = myFile.getRootTag(); + final List propertiesOrder = new ArrayList(); + if (rootTag != null) { + XmlTag[] entries = rootTag.findSubTags("entry"); + for (XmlTag entry : entries) { + XmlProperty property = new XmlProperty(entry, XmlPropertiesFileImpl.this); + propertiesOrder.add(property); + myPropertiesMap.putValue(property.getKey(), property); + } + } + mySorted = PropertiesImplUtil.isAlphaSorted(propertiesOrder); + myPropertiesOrder = mySorted ? propertiesOrder : null; + } + + public void setSorted(boolean sorted) { + mySorted = sorted; + myPropertiesOrder = null; + } + + public MultiMap getPropertiesMap() { + return myPropertiesMap; + } + + public List getPropertiesOrder() { + return myPropertiesOrder; + } + + public boolean isSorted() { + return mySorted; + } + } + private XmlPropertiesFileImpl(XmlFile file) { myFile = file; } @@ -83,19 +111,19 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @NotNull @Override public List getProperties() { - return new ArrayList(myPropertiesMap.getValue().values()); + return new ArrayList(myInfo.getValue().getPropertiesMap().values()); } @Override public IProperty findPropertyByKey(@NotNull @NonNls String key) { - Collection properties = myPropertiesMap.getValue().get(key); + Collection properties = myInfo.getValue().getPropertiesMap().get(key); return properties.isEmpty() ? null : properties.iterator().next(); } @NotNull @Override public List findPropertiesByKey(@NotNull @NonNls String key) { - return new ArrayList(myPropertiesMap.getValue().get(key)); + return new ArrayList(myInfo.getValue().getPropertiesMap().get(key)); } @NotNull @@ -122,22 +150,62 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @Override public IProperty addPropertyAfter(String key, String value, Property anchor) { - return null; + return addPropertyAfterAndCheckAlphaSorting(key, value, anchor, true, true); + } + + @NotNull + public IProperty addPropertyAfterAndCheckAlphaSorting(String key, String value, @Nullable IProperty anchor, boolean addToEnd, boolean checkAlphaSorting) { + final XmlTag anchorTag = anchor == null ? null : (XmlTag)anchor.getPsiElement(); + final XmlTag rootTag = myFile.getRootTag(); + final XmlTag entry = createPropertyTag(key, value); + final XmlTag addedEntry = (XmlTag) (anchorTag == null ? myFile.getRootTag().addSubTag(entry, !addToEnd) : rootTag.addAfter(entry, anchorTag)); + final XmlProperty property = new XmlProperty(addedEntry, this); + myInfo.getValue().getPropertiesMap().putValue(key, property); + if (checkAlphaSorting) { + checkAlphaSorting(property); + } + return property; } @NotNull @Override public IProperty addProperty(String key, String value) { + final XmlTag entry = createPropertyTag(key, value); + if (myInfo.getValue().isSorted()) { + final XmlProperty dummyProperty = new XmlProperty(entry, this); + final int insertIndex = Collections.binarySearch(myInfo.getValue().getPropertiesOrder(), dummyProperty, new Comparator() { + @Override + public int compare(IProperty p1, IProperty p2) { + final String k1 = p1.getKey(); + final String k2 = p2.getKey(); + return k1.compareTo(k2); + } + }); + final IProperty insertPosition; + final IProperty inserted; + if (insertIndex == -1) { + inserted = addPropertyAfterAndCheckAlphaSorting(key, value, null, false, false); + myInfo.getValue().getPropertiesOrder().add(0, inserted); + } + else { + final int position = insertIndex < 0 ? -insertIndex - 2 : insertIndex; + insertPosition = myInfo.getValue().getPropertiesOrder().get(position); + inserted = addPropertyAfterAndCheckAlphaSorting(key, value, insertPosition, false, false); + myInfo.getValue().getPropertiesOrder().add(position + 1, inserted); + } + return inserted; + } else { + return addPropertyAfterAndCheckAlphaSorting(key, value, null, true, false); + } + } + + private XmlTag createPropertyTag(final String key, final String value) { XmlTag rootTag = myFile.getRootTag(); XmlTag entry = rootTag.createChildTag("entry", "", value, false); entry.setAttribute("key", key); - rootTag.addSubTag(entry, false); - final XmlProperty property = new XmlProperty(entry, this); - myPropertiesMap.getValue().putValue(key, property); - return property; + return entry; } - public static PropertiesFile getPropertiesFile(final PsiFile file) { CachedValuesManager manager = CachedValuesManager.getManager(file.getProject()); if (file instanceof XmlFile) { @@ -194,7 +262,7 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { @Override public boolean isAlphaSorted() { - return PropertiesImplUtil.isAlphaSorted(getProperties()); + return myInfo.getValue().isSorted(); } @Override @@ -213,4 +281,29 @@ public class XmlPropertiesFileImpl extends XmlPropertiesFile { public int hashCode() { return myFile.hashCode(); } + + private void checkAlphaSorting(final IProperty property) { + if (myInfo.getValue().isSorted()) { + final String key = property.getKey(); + final XmlTag prev = getSibling((XmlTag)property.getPsiElement(), true); + final String prevKey = prev == null ? null : new XmlProperty(prev, this).getKey(); + if (prevKey != null && key != null && prevKey.compareTo(key) > 0) { + myInfo.getValue().setSorted(false); + } else { + final XmlTag next = getSibling((XmlTag)property.getPsiElement(), false); + final String nextKey = next == null ? null : new XmlProperty(next, this).getKey(); + if (nextKey != null && key != null && nextKey.compareTo(key) < 0) { + myInfo.getValue().setSorted(false); + } + } + } + } + + private static XmlTag getSibling(final XmlTag entry, final boolean prev) { + XmlTag sibling = (XmlTag)(prev ? entry.getPrevSibling() : entry.getNextSibling()); + while (sibling != null && !"entry".equals(sibling.getName())) { + sibling = (XmlTag)(prev ? sibling.getPrevSibling() : sibling.getNextSibling()); + } + return sibling; + } } diff --git a/plugins/properties/testData/xml/bar.xml b/plugins/properties/testData/xml/bar.xml new file mode 100644 index 000000000000..b85abb53cc8e --- /dev/null +++ b/plugins/properties/testData/xml/bar.xml @@ -0,0 +1,7 @@ + + + + Hi + bar + baz + \ No newline at end of file diff --git a/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java b/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java index d16eaf352f38..fd4b1212107f 100644 --- a/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java +++ b/plugins/properties/testSrc/com/intellij/lang/properties/xml/XmlPropertiesTest.java @@ -9,6 +9,8 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.psi.PsiFile; import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import java.util.List; @@ -81,6 +83,23 @@ public class XmlPropertiesTest extends LightPlatformCodeInsightFixtureTestCase { assertEquals("vvv", property2.getValue()); } + public void testAddPropertyInAlphaOrder() { + final PsiFile psiFile = myFixture.configureByFile("bar.xml"); + final PropertiesFile propertiesFile = PropertiesImplUtil.getPropertiesFile(psiFile); + assertNotNull(propertiesFile); + + WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() { + public void run() { + propertiesFile.addProperty("d", "vvv"); + propertiesFile.addProperty("a", "vvv"); + propertiesFile.addProperty("l", "vvv"); + propertiesFile.addProperty("v", "vvv"); + } + }); + assertTrue(propertiesFile.isAlphaSorted()); + assertTrue(PropertiesImplUtil.getPropertiesFile(psiFile).isAlphaSorted()); + } + @Override protected String getTestDataPath() { return PluginPathManager.getPluginHomePath("properties") + "/testData/xml/"; From b496b88b94ff18fe2d08a20572fbfd190f1e49f4 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Wed, 11 Feb 2015 15:37:32 +0300 Subject: [PATCH 02/27] PY-11855 Run manage.py task improvements Options now fetched from python and returned to Java --- .../_optparse.py | 19 +++- .../django_manage_commands_provider/_xml.py | 38 +++++--- .../Argument.java | 2 +- .../ArgumentsInfo.java | 2 +- .../Command.java | 12 ++- .../KnownArgumentsInfo.java | 2 +- .../NoArgumentsInfo.java | 2 +- .../commandInterface/command/Option.java | 92 +++++++++++++++++++ .../command/OptionArgumentInfo.java | 42 +++++++++ .../command/OptionArgumentType.java | 31 +++++++ .../OptionChoiceBasedArgumentInfo.java | 51 ++++++++++ .../command/OptionTypedArgumentInfo.java | 61 ++++++++++++ .../UnknownArgumentsInfo.java | 2 +- .../command/package-info.java | 23 +++++ .../CommandBasedChunkDriver.java | 3 + .../commandBasedChunkDriver/package-info.java | 4 +- 16 files changed, 361 insertions(+), 25 deletions(-) rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/Argument.java (96%) rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/ArgumentsInfo.java (95%) rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/Command.java (87%) rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/KnownArgumentsInfo.java (98%) rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/NoArgumentsInfo.java (94%) create mode 100644 python/src/com/jetbrains/python/commandInterface/command/Option.java create mode 100644 python/src/com/jetbrains/python/commandInterface/command/OptionArgumentInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/command/OptionArgumentType.java create mode 100644 python/src/com/jetbrains/python/commandInterface/command/OptionChoiceBasedArgumentInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => command}/UnknownArgumentsInfo.java (95%) create mode 100644 python/src/com/jetbrains/python/commandInterface/command/package-info.java diff --git a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py index 16617ab40fcb..5680b80569c6 100644 --- a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py +++ b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py @@ -24,17 +24,26 @@ def report_data(dumper): command = utility.fetch_command(command_name) assert isinstance(command, BaseCommand) dumper.start_command(command_name=command_name, - command_help_text=str(command.usage("").replace("%prog", command_name)), # TODO: support subcommands + command_help_text=str(command.usage("").replace("%prog", command_name)), + # TODO: support subcommands command_args_text=str(command.args)) for opt in command.option_list: - opt_type = opt.type if opt.type in Option.TYPES else "" # Empty for unknown + num_of_args = int(opt.nargs) if opt.nargs else 0 + opt_type = None + if num_of_args > 0: + # If option accepts arg, we need to determine its type. It could be int, choices, or something other + # See https://docs.python.org/2/library/optparse.html#standard-option-types + if opt.type in ["int", "long"]: + opt_type = "int" + elif opt.choices: + assert isinstance(opt.choices, list), "Choices should be list" + opt_type = opt.choices + # There is no official way to access this field, so I use protected one. At least it is public API. # noinspection PyProtectedMember dumper.add_command_option( - opt_type=opt_type, - choices=opt.choices, long_opt_names=opt._long_opts, short_opt_names=opt._short_opts, help_text=opt.help, - num_of_args=opt.nargs) + argument_info=(num_of_args, opt_type) if num_of_args else None) dumper.close_command() \ No newline at end of file diff --git a/python/helpers/pycharm/django_manage_commands_provider/_xml.py b/python/helpers/pycharm/django_manage_commands_provider/_xml.py index 40260a01ab83..4f034026dd4d 100644 --- a/python/helpers/pycharm/django_manage_commands_provider/_xml.py +++ b/python/helpers/pycharm/django_manage_commands_provider/_xml.py @@ -6,13 +6,17 @@ It does not have schema (yet!) but here is XML format it uses. -- root -- info about command - +"option_type" is only set if "numberOfArgs" > 0, and it can be: "int" (means integer), +"choices" (means opt can have one of the values, provided in choices) or "str" that means "string" (option may have any value) + Classes like DjangoCommandsInfo is used on Java side. """ @@ -77,35 +81,45 @@ class XmlDumper(object): self.__command_element.setAttribute("args", command_args_text) self.__root.appendChild(self.__command_element) - def add_command_option(self, opt_type, choices, long_opt_names, short_opt_names, help_text, num_of_args): + def add_command_option(self, long_opt_names, short_opt_names, help_text, argument_info): """ Adds command option - :param opt_type: "string", "int", "long", "float", "complex", "choice" - :param choices: list of choices for "choice" type + :param argument_info: None if option does not accept any arguments or tuple of (num_of_args, type_info) \ + where num_of_args is int > 0 and type_info is str, representing type (only "int" and "string" are supported) \ + or list of available types in case of choices + :param long_opt_names: list of long opt names :param short_opt_names: list of short opt names :param help_text: help text - :param num_of_args: number of arguments - :type opt_type str - :type choices list of string :type long_opt_names list of str :type short_opt_names list of str :type help_text str - :type num_of_args int + :type argument_info tuple """ assert isinstance(self.__command_element, Element), "Add option in command only" - option = self.__document.createElement("option") - option.setAttribute("type", opt_type) - if choices: - self.__create_text_array(option, "choices", choices) + option = self.__document.createElement("option") + + opt_type_to_report = None + num_of_args = 0 + + if argument_info: + (num_of_args, type_info) = argument_info + if isinstance(type_info, list): + self.__create_text_array(option, "choices", type_info) + opt_type_to_report = "choices" + else: + opt_type_to_report = "int" if str(type_info) == "int" else "str" + if long_opt_names: self.__create_text_array(option, "longNames", long_opt_names) if short_opt_names: self.__create_text_array(option, "shortNames", short_opt_names) + if opt_type_to_report: + option.setAttribute("type", opt_type_to_report) option.setAttribute("help", help_text) if num_of_args: option.setAttribute("numberOfArgs", str(num_of_args)) diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java b/python/src/com/jetbrains/python/commandInterface/command/Argument.java similarity index 96% rename from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java rename to python/src/com/jetbrains/python/commandInterface/command/Argument.java index fc3f64192c17..4e33c3b585e9 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java +++ b/python/src/com/jetbrains/python/commandInterface/command/Argument.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.commandInterface.commandBasedChunkDriver; +package com.jetbrains.python.commandInterface.command; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/command/ArgumentsInfo.java similarity index 95% rename from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java rename to python/src/com/jetbrains/python/commandInterface/command/ArgumentsInfo.java index ad43da45adcd..c1eb793702e9 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/command/ArgumentsInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.commandInterface.commandBasedChunkDriver; +package com.jetbrains.python.commandInterface.command; import com.intellij.openapi.util.Pair; import org.jetbrains.annotations.Nullable; diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java b/python/src/com/jetbrains/python/commandInterface/command/Command.java similarity index 87% rename from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java rename to python/src/com/jetbrains/python/commandInterface/command/Command.java index 694021964fea..2d15d152c3c1 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java +++ b/python/src/com/jetbrains/python/commandInterface/command/Command.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.commandInterface.commandBasedChunkDriver; +package com.jetbrains.python.commandInterface.command; import com.intellij.openapi.module.Module; import com.jetbrains.python.commandLineParser.CommandLineParseResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** - * Command with arguments + * Command with arguments and options * * @author Ilya.Kazakevich */ @@ -47,6 +49,12 @@ public interface Command { @NotNull ArgumentsInfo getArgumentsInfo(); + /** + * @return command options + */ + @NotNull + List