From 1bfd5ea7626a08a0ce5d60bdde6de16e92698990 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 13 Feb 2015 16:49:55 +0300 Subject: [PATCH 01/16] PY-11855 Run manage.py task improvements Useless import removed --- .../helpers/pycharm/django_manage_commands_provider/_optparse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py index 5680b80569c6..69e54d4db9b4 100644 --- a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py +++ b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py @@ -5,7 +5,6 @@ This module encapsulates Django semi-public API knowledge, and not very stable b """ from optparse import Option import django -from django.apps import registry from django.conf import settings from django.core.management import ManagementUtility, get_commands, BaseCommand From aa4aeb26a55e46b5945b4c5f5ead2fe31351d4b2 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Mon, 16 Feb 2015 20:15:52 +0100 Subject: [PATCH 02/16] add README file --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000000..6b9088da7f7c --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# IntelliJ IDEA Community Edition +### Building and Running from the IDE +To develop IntelliJ IDEA, you can use either IntelliJ IDEA Community Edition or IntelliJ IDEA Ultimate. To build and run the code: +* Make sure you have the **Groovy** plugin enabled. Parts of IntelliJ IDEA are written in Groovy, and you will get compilation errors if you don't have the plugin enabled. +* Make sure you have the **UI Designer** plugin enabled. Most of IntelliJ IDEA's UI is built using the **UI Designer**, and the version you build will not run correctly if you don't have the plugin enabled. +* Open the directory with the source code as a [directory-based project](http://www.jetbrains.com/idea/webhelp/project.html) +* Configure a JSDK named "**IDEA jdk**" (case sensitive), pointing to an installation of JDK 1.6 +* Add **lib\tools.jar** from the JDK installation directory to the classpath of IDEA JDK +* Use Build | Make Project to build the code. +* To run the code, use the provided shared run configuration "**IDEA**". + +You can find other useful information at [http://www.jetbrains.org](http://www.jetbrains.org) \ No newline at end of file From 6cfd749b340aa400c89f93c87a141b916afce353 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Mon, 16 Feb 2015 20:37:07 +0100 Subject: [PATCH 03/16] update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b9088da7f7c..7800487e9455 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # IntelliJ IDEA Community Edition ### Building and Running from the IDE To develop IntelliJ IDEA, you can use either IntelliJ IDEA Community Edition or IntelliJ IDEA Ultimate. To build and run the code: +* Run **getPlugins.sh** / **getPlugins.bat** from the project root directory to check out additional modules. * Make sure you have the **Groovy** plugin enabled. Parts of IntelliJ IDEA are written in Groovy, and you will get compilation errors if you don't have the plugin enabled. * Make sure you have the **UI Designer** plugin enabled. Most of IntelliJ IDEA's UI is built using the **UI Designer**, and the version you build will not run correctly if you don't have the plugin enabled. * Open the directory with the source code as a [directory-based project](http://www.jetbrains.com/idea/webhelp/project.html) * Configure a JSDK named "**IDEA jdk**" (case sensitive), pointing to an installation of JDK 1.6 -* Add **lib\tools.jar** from the JDK installation directory to the classpath of IDEA JDK +* Unless you're running on a Mac with an Apple JDK, add /lib/tools.jar to the set of "**IDEA jdk**" jars. * Use Build | Make Project to build the code. * To run the code, use the provided shared run configuration "**IDEA**". From b9bc182c21ee899a493f3f80fb3f0501efc3d192 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Mon, 16 Feb 2015 21:11:33 +0100 Subject: [PATCH 04/16] Remove outdated instruction about directory-based projects --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7800487e9455..5d4abee4d3c7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ To develop IntelliJ IDEA, you can use either IntelliJ IDEA Community Edition or * Run **getPlugins.sh** / **getPlugins.bat** from the project root directory to check out additional modules. * Make sure you have the **Groovy** plugin enabled. Parts of IntelliJ IDEA are written in Groovy, and you will get compilation errors if you don't have the plugin enabled. * Make sure you have the **UI Designer** plugin enabled. Most of IntelliJ IDEA's UI is built using the **UI Designer**, and the version you build will not run correctly if you don't have the plugin enabled. -* Open the directory with the source code as a [directory-based project](http://www.jetbrains.com/idea/webhelp/project.html) +* Open the project * Configure a JSDK named "**IDEA jdk**" (case sensitive), pointing to an installation of JDK 1.6 * Unless you're running on a Mac with an Apple JDK, add /lib/tools.jar to the set of "**IDEA jdk**" jars. * Use Build | Make Project to build the code. From e841fbd97e2a68d7f309db990c3fec9f4b2c4bd1 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 16 Feb 2015 22:39:20 +0100 Subject: [PATCH 05/16] Cleanup (formatting) --- .../src/com/intellij/ui/mac/growl/Growl.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ui/mac/growl/Growl.java b/platform/platform-impl/src/com/intellij/ui/mac/growl/Growl.java index 3316a0d44b33..b4ab66adb438 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/growl/Growl.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/growl/Growl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -47,10 +47,9 @@ public class Growl { final ID applicationIcon = getApplicationIcon(); final ID defaultNotifications = Foundation.fillArray(myDefaultNotification); final ID allNotifications = Foundation.fillArray(myAllNotifications); - - final ID userDict = Foundation.createDict(new String[]{GROWL_APP_NAME, GROWL_APP_ICON, GROWL_DEFAULT_NOTIFICATIONS, GROWL_ALL_NOTIFICATIONS}, - new Object[]{myProductName, applicationIcon, defaultNotifications, allNotifications}); - + final ID userDict = Foundation.createDict( + new String[]{GROWL_APP_NAME, GROWL_APP_ICON, GROWL_DEFAULT_NOTIFICATIONS, GROWL_ALL_NOTIFICATIONS}, + new Object[]{myProductName, applicationIcon, defaultNotifications, allNotifications}); final ID center = invoke("NSDistributedNotificationCenter", "defaultCenter"); final Object notificationName = Foundation.nsString(GROWL_APPLICATION_REGISTRATION_NOTIFICATION); invoke(center, "postNotificationName:object:userInfo:deliverImmediately:", notificationName, null, userDict, true); @@ -59,12 +58,11 @@ public class Growl { public void notifyGrowlOf(final String notification, final String title, final String description) { final ID autoReleasePool = createAutoReleasePool(); - final ID dict = Foundation.createDict(new String[]{ - GROWL_NOTIFICATION_NAME, GROWL_NOTIFICATION_TITLE, GROWL_NOTIFICATION_DESCRIPTION, GROWL_APP_NAME}, - new Object[]{notification, title, description, myProductName}); + final ID dict = Foundation.createDict( + new String[]{GROWL_NOTIFICATION_NAME, GROWL_NOTIFICATION_TITLE, GROWL_NOTIFICATION_DESCRIPTION, GROWL_APP_NAME}, + new Object[]{notification, title, description, myProductName}); final ID center = invoke("NSDistributedNotificationCenter", "defaultCenter"); final Object notificationName = Foundation.nsString(GROWL_NOTIFICATION); - invoke(center, "postNotificationName:object:userInfo:deliverImmediately:", notificationName, null, dict, true); invoke(autoReleasePool, "release"); } From 8b560739468ccecc914c58853f87033547d3320d Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 17 Feb 2015 01:04:34 +0300 Subject: [PATCH 06/16] PY-11855 Run manage.py task improvements Refactored, options support added --- .../com/jetbrains/python/PyBundle.properties | 3 + .../jetbrains/python/WordWithPosition.java | 4 +- .../ChunkAndInfo.java | 83 ------ .../ChunkDriverBasedPresenter.java | 247 ----------------- .../chunkDriverBasedPresenter/ChunkInfo.java | 81 ------ .../chunkDriverBasedPresenter/ParseInfo.java | 88 ------ .../package-info.java | 30 --- .../commandInterface/command/Command.java | 5 +- .../commandInterface/command/Option.java | 27 +- .../command/OptionTypedArgumentInfo.java | 4 +- .../CommandBasedChunkDriver.java | 190 ------------- .../CommandBasedRangeInfoDriver.java | 204 ++++++++++++++ .../CommandExecutor.java | 62 +++++ .../RangeInfoCollector.java | 250 ++++++++++++++++++ .../UnusedOptionsCollector.java | 97 +++++++ .../package-info.java | 6 +- .../python/commandInterface/package-info.java | 2 +- .../rangeBasedPresenter/Executor.java | 36 +++ .../RangeBasedPresenter.java | 246 +++++++++++++++++ .../rangeBasedPresenter/RangeInfo.java | 122 +++++++++ .../rangeBasedPresenter/RangeInfoDriver.java | 39 +++ .../SuggestionInfo.java | 11 +- .../rangeBasedPresenter/package-info.java | 32 +++ ...dLineParseResult.java => CommandLine.java} | 38 +-- .../CommandLineArgument.java | 36 +++ .../commandLineParser/CommandLineOption.java | 69 +++++ .../commandLineParser/CommandLineParser.java | 12 +- .../CommandLinePart.java} | 36 ++- ...tType.java => CommandLinePartVisitor.java} | 26 +- .../optParse/LongOptionParser.java | 39 +-- .../optParse/OptParseCommandLineParser.java | 123 +++------ .../optParse/OptionParser.java | 14 +- .../optParse/OptionParserRegexBased.java | 60 +++++ .../optParse/ShortOptionParser.java | 35 +-- .../commandLineParser/package-info.java | 11 +- .../python/suggestionList/SuggestionList.java | 7 +- 36 files changed, 1427 insertions(+), 948 deletions(-) delete mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java rename python/src/com/jetbrains/python/commandInterface/{commandBasedChunkDriver => commandBasedRangeDriver}/package-info.java (81%) create mode 100644 python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java create mode 100644 python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java create mode 100644 python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java rename python/src/com/jetbrains/python/commandInterface/{chunkDriverBasedPresenter => rangeBasedPresenter}/SuggestionInfo.java (86%) create mode 100644 python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java rename python/src/com/jetbrains/python/commandLineParser/{CommandLineParseResult.java => CommandLine.java} (58%) create mode 100644 python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java create mode 100644 python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java rename python/src/com/jetbrains/python/{commandInterface/chunkDriverBasedPresenter/ChunkDriver.java => commandLineParser/CommandLinePart.java} (51%) rename python/src/com/jetbrains/python/commandLineParser/{CommandLinePartType.java => CommandLinePartVisitor.java} (57%) create mode 100644 python/src/com/jetbrains/python/commandLineParser/optParse/OptionParserRegexBased.java diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 08238498648c..90e83cf3c1cf 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -856,8 +856,11 @@ custom.type.mimic.name=Dynamic class based on {0} # Values for command argument value validation commandLine.validation.badCommand=Unknown command commandLine.validation.argMissing=Required argument value is missing +commandLine.validation.optArgMissing=Option argument value is missing commandLine.validation.argBadValue=Argument can't have this value commandLine.validation.excessArg=Excess argument value +commandLine.validation.badOption=Bad option or option already set +commandLine.validation.noArgAllowed=No argument allowed for this option # And for labels commandLine.subText.key.complete=Use {0} to complete selected variant commandLine.subText.key.suggestions=Use {0} to view available values diff --git a/python/src/com/jetbrains/python/WordWithPosition.java b/python/src/com/jetbrains/python/WordWithPosition.java index 35f10c3a2e81..0c8043b53a87 100644 --- a/python/src/com/jetbrains/python/WordWithPosition.java +++ b/python/src/com/jetbrains/python/WordWithPosition.java @@ -107,8 +107,8 @@ public final class WordWithPosition extends Range { WordWithPosition position = (WordWithPosition)o; - if (getFrom() != position.getFrom()) return false; - if (getTo() != position.getTo()) return false; + if (!getFrom().equals(position.getFrom())) return false; + if (!getTo().equals(position.getTo())) return false; if (!myWord.equals(position.myWord)) return false; return true; diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java deleted file mode 100644 index 92a6910fdd5a..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2000-2015 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.jetbrains.python.commandInterface.chunkDriverBasedPresenter; - -import com.jetbrains.python.WordWithPosition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Chunk and its info pair. Chunk may be null, while chunk info always present. - * @author Ilya.Kazakevich - */ -final class ChunkAndInfo implements Comparable { - @Nullable - private final WordWithPosition myChunk; - @NotNull - private final ChunkInfo myChunkInfo; - - ChunkAndInfo(@Nullable final WordWithPosition chunk, @NotNull final ChunkInfo chunkInfo) { - myChunk = chunk; - myChunkInfo = chunkInfo; - } - - /** - * @return chunk (word). may be null - */ - @Nullable - WordWithPosition getChunk() { - return myChunk; - } - - /** - * @return chunk info. - */ - @NotNull - ChunkInfo getChunkInfo() { - return myChunkInfo; - } - - @Override - public int compareTo(@NotNull final ChunkAndInfo o) { - if (myChunk == null && o.myChunk == null) { - return 0; - } - if (myChunk == null) { - return 1; - } - if (o.myChunk == null) { - return -1; - } - return myChunk.getFrom().compareTo(o.myChunk.getFrom()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ChunkAndInfo info = (ChunkAndInfo)o; - - if (myChunk != null ? !myChunk.equals(info.myChunk) : info.myChunk != null) return false; - - return true; - } - - @Override - public int hashCode() { - return myChunk != null ? myChunk.hashCode() : 0; - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java deleted file mode 100644 index 946b2718d20c..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2000-2015 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.jetbrains.python.commandInterface.chunkDriverBasedPresenter; - -import com.intellij.util.Range; -import com.jetbrains.python.WordWithPosition; -import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter; -import com.jetbrains.python.commandInterface.CommandInterfaceView; -import com.jetbrains.python.suggestionList.SuggestionsBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -// TODO: Test - -/** - * Presenter that uses {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} to parse pack of chunks - * to obtain {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo}. - * Each chunk may be paired with certain chunk info. Such info tells presenter whether this chunk has info, error, suggestions and so on. - * If caret situated far from chunks, then next neariest chunk should be found (see {@link #findNearestChunkAndInfo()}. - * - * - * @author Ilya.Kazakevich - * @see com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo - */ -public final class ChunkDriverBasedPresenter extends CommandInterfacePresenterAdapter { - - @NotNull - private final ChunkDriver myChunkDriver; - @NotNull - private final SortedSet myChunkAndInfos = new TreeSet(); - @Nullable - private Runnable myExecutor; - - public ChunkDriverBasedPresenter(@NotNull final CommandInterfaceView view, - @NotNull final ChunkDriver chunkDriver) { - super(view); - myChunkDriver = chunkDriver; - } - - @Override - public void launch() { - super.launch(); - reparseText(true); - } - - @Override - public void textChanged() { - reparseText(false); - } - - private void reparseText(final boolean skipSuggestions) { - final List chunks = WordWithPosition.splitText(myView.getText()); - final ParseInfo parseInfo = myChunkDriver.parse(chunks); - myExecutor = parseInfo.getExecutor(); - final List chunkInfos = parseInfo.getChunkInfo(); - assert chunkInfos.size() >= chunks.size() : "Driver did not return enough chunks"; - assert !chunkInfos.isEmpty() : "At least one chunk info should exist"; - myChunkAndInfos.clear(); - for (int i = 0; i < chunkInfos.size(); i++) { - final ChunkInfo chunkInfo = chunkInfos.get(i); - final WordWithPosition chunk = chunks.size() > i ? chunks.get(i) : null; - myChunkAndInfos.add(new ChunkAndInfo(chunk, chunkInfo)); - } - - - // configure Errors And Balloons - - final Collection infoBalloons = new ArrayList(); - final Collection errorBalloons = new ArrayList(); - - - for (final ChunkAndInfo chunkInfoPair : myChunkAndInfos) { - final ChunkInfo chunkInfo = chunkInfoPair.getChunkInfo(); - Range chunk = chunkInfoPair.getChunk(); - if (chunk == null) { - // After the last! - chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE; - } - final String error = chunkInfo.getError(); - if (error != null) { - errorBalloons.add(new WordWithPosition(error, chunk)); - } - final String info = chunkInfo.getInfoBalloon(); - if (info != null) { - infoBalloons.add(new WordWithPosition(info, chunk)); - } - } - - myView.setInfoAndErrors(infoBalloons, errorBalloons); - - - if (!skipSuggestions) { - configureSuggestion(false); - } - - // Configure subtexts - final List> placesWhereSuggestionAvailable = new ArrayList>(); - for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) { - Range chunk = chunkAndInfo.getChunk(); - // If some place has suggestions, then add it - if (chunkAndInfo.getChunkInfo().getSuggestions() != null) { - if (chunk == null) { - // If there is no such chunk, that means we are after the last character, so use "special case" here - //noinspection ReuseOfLocalVariable - chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE; - } - placesWhereSuggestionAvailable.add(chunk); - } - } - myView.configureSubTexts(parseInfo.getStatusText(), placesWhereSuggestionAvailable); - } - - @Override - public void suggestionRequested() { - configureSuggestion(true); // Show or hide - } - - - /** - * Displays suggestions if needed. - * - * @param requestedExplicitly is suggesions where requested by user explicitly or not - */ - private void configureSuggestion(final boolean requestedExplicitly) { - myView.removeSuggestions(); - final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo(); - final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo(); - final WordWithPosition chunk = chunkAndInfo.getChunk(); - - final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions(); - if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) { - return; - } - final List suggestions = new ArrayList(suggestionInfo.getSuggestions()); - if (chunk != null && !requestedExplicitly) { - filterLeaveOnlyMatching(suggestions, chunk.getText()); - } - // TODO: Place to add history - if (!suggestions.isEmpty()) { - // No need to display empty suggestions - myView - .displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), (chunk == null ? null : chunk.getText())); - } - } - - - /** - * Filters collection of suggestions leaving only those starts with certain text. - * @param suggestions list to filter - * @param textToMatch leave only parts that start with this param - */ - private static void filterLeaveOnlyMatching(@NotNull final Iterable suggestions, @NotNull final String textToMatch) { - // TODO: use guava instead? - final Iterator iterator = suggestions.iterator(); - while (iterator.hasNext()) { - if (!iterator.next().startsWith(textToMatch)) { - iterator.remove(); - } - } - } - - /** - * Searches for the nearest chunk and info to use. It may or may not find chunk, but it should always provide some chunk info. - * - * @return nearest chunk info and, probably, chunk. - */ - @NotNull - private ChunkAndInfo findNearestChunkAndInfo() { - final int caretPosition = myView.getCaretPosition(); - - for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) { - final Range range = chunkAndInfo.getChunk(); - if (range != null && range.isWithin(caretPosition)) { - return chunkAndInfo; - } - if (range != null && range.getFrom() > caretPosition) { - return new ChunkAndInfo(null, chunkAndInfo.getChunkInfo()); - } - } - - return new ChunkAndInfo(null, myChunkAndInfos.last().getChunkInfo()); - } - - - @Override - public void completionRequested(@Nullable final String valueFromSuggestionList) { - final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo(); - final WordWithPosition chunk = chunkAndInfo.getChunk(); - - if (valueFromSuggestionList != null) { - // Just insert it - if (chunk != null) { // If caret is on the chunk itself - myView.replaceText(chunk.getFrom(), chunk.getTo(), valueFromSuggestionList); - } - else { - myView.insertTextAfterCaret(valueFromSuggestionList); - } - return; - } - - //User did not provide text no insert, do our best to find one - - final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo(); - final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions(); - if (suggestionInfo == null) { - return; // No suggestion available for this chunk - } - final List suggestions = new ArrayList(suggestionInfo.getSuggestions()); - if (chunk != null) { - filterLeaveOnlyMatching(suggestions, chunk.getText()); - } - if (suggestions.size() == 1) { - // Exclusive! - if (chunk != null) { - myView.replaceText(chunk.getFrom(), chunk.getTo(), suggestions.get(0)); - } - else { - myView.insertTextAfterCaret(suggestions.get(0)); - } - } - } - - @Override - public void executionRequested() { - if (myExecutor == null) { - // TODO: Display error somehow - } - else { - myExecutor.run(); - } - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java deleted file mode 100644 index 4397751ed011..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2000-2015 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.jetbrains.python.commandInterface.chunkDriverBasedPresenter; - -import org.jetbrains.annotations.Nullable; - -/** - * Information about certain place in text, provided by chunk driver. - * - * @author Ilya.Kazakevich - */ -public final class ChunkInfo { - @Nullable - private final String myInfoBalloon; - @Nullable - private final String myError; - @Nullable - private final SuggestionInfo mySuggestions; - - - public ChunkInfo(@Nullable final String infoBalloon, - @Nullable final String error) { - this(infoBalloon, error, null); - } - - - /** - * - * @param infoBalloon Info balloon to display when caret meets this place (null if display nothing) - * @param error Error balloon to display when caret meets this place and underline text as error (null if no error) - * @param suggestions list of suggestions available in this place (if any) - */ - public ChunkInfo(@Nullable final String infoBalloon, - @Nullable final String error, - @Nullable final SuggestionInfo suggestions) { - myInfoBalloon = infoBalloon; - myError = error; - mySuggestions = suggestions; - } - - - /** - * - * @return Info balloon to display when caret meets this place (null if display nothing) - */ - @Nullable - public String getInfoBalloon() { - return myInfoBalloon; - } - - /** - * - * @return Error balloon to display when caret meets this place and underline text as error (null if no error) - */ - @Nullable - public String getError() { - return myError; - } - - /** - * - * @return list of suggestions available in this place (if any) - */ - @Nullable - public SuggestionInfo getSuggestions() { - return mySuggestions; - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java deleted file mode 100644 index 005c4dae6150..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2000-2015 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.jetbrains.python.commandInterface.chunkDriverBasedPresenter; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Pack of {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo} for certain chunks and other parsing info. - * - * @author Ilya.Kazakevich - */ -public final class ParseInfo { - @Nullable - private final String myStatusText; - @NotNull - private final List myChunkInfo = new ArrayList(); - @Nullable - private final Runnable myExecutor; - - /** - * @param chunkInfo Chunk info should match chunks in 1-to-1 manner: - * If "chunk1 chunk2 chunk3" were provided, you then need to return list where first chunkInfo matches first chunk ets. - * And there also should be one more chunkInfo for tail. - * @param statusText Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display. - * @param executor Engine to process command-line execution - */ - public ParseInfo(@NotNull final Collection chunkInfo, - @Nullable final String statusText, - @Nullable final Runnable executor) { - myStatusText = statusText; - myChunkInfo.addAll(chunkInfo); - myExecutor = executor; - } - - /** - * Simple parse info with out of status text and executor - * - * @param chunkInfo Chunk info (See {@link #ParseInfo(java.util.Collection, String, Runnable)} - * @see #ParseInfo(java.util.Collection, String, Runnable) - */ - public ParseInfo(@NotNull final Collection chunkInfo) { - this(chunkInfo, null, null); - } - - /** - * @return Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display. - */ - @Nullable - String getStatusText() { - return myStatusText; - } - - /** - * @return Engine to process command-line execution - */ - @Nullable - Runnable getExecutor() { - return myExecutor; - } - - /** - * @return Chunk info should match chunks in 1-to-1 manner, and there also should be one more chunkInfo for tail ( - * see {@link #ParseInfo(java.util.Collection, String, Runnable) ctor} manual) - */ - @NotNull - List getChunkInfo() { - return Collections.unmodifiableList(myChunkInfo); - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java deleted file mode 100644 index 9281bc7f630c..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2000-2015 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. - */ - -/** - * {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of chunk - * and {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver}. - * This presenter explodes command line into several parts or chunks. - * Chunks then passed to {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver driver} and it returns - * all information it has about each chunk. Presenter uses this information to display chunks correctly using view. - * To use this package, {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} should be implemented. - * - * See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriverBasedPresenter} as entry point - * - * - * @author Ilya.Kazakevich - */ -package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandInterface/command/Command.java b/python/src/com/jetbrains/python/commandInterface/command/Command.java index 2d15d152c3c1..295e4a96dc4d 100644 --- a/python/src/com/jetbrains/python/commandInterface/command/Command.java +++ b/python/src/com/jetbrains/python/commandInterface/command/Command.java @@ -16,7 +16,6 @@ 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; @@ -59,7 +58,7 @@ public interface Command { * Execute command * * @param module module to execute command against - * @param commandLine command's command line + * @param parameters command's arguments and options (just like entered by user but splitted by space) */ - void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine); + void execute(@NotNull final Module module, @NotNull final List parameters); } diff --git a/python/src/com/jetbrains/python/commandInterface/command/Option.java b/python/src/com/jetbrains/python/commandInterface/command/Option.java index 0099e8c7b229..f362eda7af1f 100644 --- a/python/src/com/jetbrains/python/commandInterface/command/Option.java +++ b/python/src/com/jetbrains/python/commandInterface/command/Option.java @@ -26,7 +26,9 @@ import java.util.Collections; import java.util.List; /** - * Command option + * Command option. + * It may have some long names (like --foo) and short (like -f), help text and arguments (if not flag option) + * * @author Ilya.Kazakevich */ public final class Option { @@ -40,17 +42,17 @@ public final class Option { private final String myHelp; /** - * * @param argumentAndQuantity if option accepts argument, there should be pair of [argument_quantity, its_type_info] - * @param help option help - * @param shortNames option short names - * @param longNames option long names + * @param help option help + * @param shortNames option short names + * @param longNames option long names */ public Option(@Nullable final Pair argumentAndQuantity, @NotNull final String help, @NotNull final Collection shortNames, @NotNull final Collection longNames) { - Preconditions.checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity); + Preconditions + .checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity); myArgumentAndQuantity = argumentAndQuantity; myShortNames.addAll(shortNames); myLongNames.addAll(longNames); @@ -65,6 +67,16 @@ public final class Option { return Collections.unmodifiableList(myLongNames); } + /** + * @return all option names (long and short) + */ + @NotNull + public List getAllNames() { + final List result = new ArrayList(myLongNames); + result.addAll(myShortNames); + return result; + } + /** * @return Option short names */ @@ -74,8 +86,7 @@ public final class Option { } /** - * - * @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise. + * @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise. */ @Nullable public Pair getArgumentAndQuantity() { diff --git a/python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java b/python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java index 06bb72296321..42cadf0c5c81 100644 --- a/python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java @@ -24,7 +24,7 @@ import java.util.List; * For options, whose argument is based on certain type. * * @author Ilya.Kazakevich - * @see com.jetbrains.python.commandInterface.command.OptionArgumentType + * @see OptionArgumentType */ public final class OptionTypedArgumentInfo implements OptionArgumentInfo { @NotNull @@ -42,7 +42,7 @@ public final class OptionTypedArgumentInfo implements OptionArgumentInfo { // We only check integer for now if (myType == OptionArgumentType.INTEGER) { try { - // We just parse it to get exception + // We just getCommandLineInfo it to get exception //noinspection ResultOfMethodCallIgnored Integer.parseInt(value); } diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java deleted file mode 100644 index 2626692f226f..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2000-2015 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.jetbrains.python.commandInterface.commandBasedChunkDriver; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.util.Pair; -import com.jetbrains.python.PyBundle; -import com.jetbrains.python.WordWithPosition; -import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver; -import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo; -import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ParseInfo; -import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.SuggestionInfo; -import com.jetbrains.python.commandInterface.command.Argument; -import com.jetbrains.python.commandInterface.command.ArgumentsInfo; -import com.jetbrains.python.commandInterface.command.Command; -import com.jetbrains.python.commandLineParser.CommandLineParseResult; -import com.jetbrains.python.commandLineParser.CommandLineParser; -import com.jetbrains.python.commandLineParser.CommandLinePartType; -import com.jetbrains.python.commandLineParser.MalformedCommandLineException; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -/** - * Chunk driver that uses pack of commands. - * - * @author Ilya.Kazakevich - */ -public final class CommandBasedChunkDriver implements ChunkDriver { - @NotNull - private final CommandLineParser myCommandLineParser; - @NotNull - private final Map myCommands = new TreeMap(); // To sort commands by name - @NotNull - private final Module myModule; - - /** - * @param commandLineParser parser to use - * @param module module parsing takes place in - * @param commands available commands - */ - public CommandBasedChunkDriver(@NotNull final CommandLineParser commandLineParser, - @NotNull final Module module, - @NotNull final Collection commands) { - myCommandLineParser = commandLineParser; - for (final Command command : commands) { - myCommands.put(command.getName(), command); - } - myModule = module; - } - - @Override - @NotNull - public ParseInfo parse(@NotNull final List chunks) { - // TODO: Refactor to add command first to prevent copy/paste - if (chunks.isEmpty()) { - return createBadCommandInfo(chunks.size()); - } - - try { - final CommandLineParseResult commandLine = myCommandLineParser.parse(chunks); - final Command command = myCommands.get(commandLine.getCommand().getText()); - if (command == null) { - // Bad command inserted - return createBadCommandInfo(chunks.size()); - } - - // Command exists, lets check its arguments - - // TODO: Support options as well - - // First, validate values - final ArgumentsInfo commandArgumentsInfo = command.getArgumentsInfo(); - - - final List chunkInfo = new ArrayList(); - // First chunk iscommand and it seems to be ok - chunkInfo.add(new ChunkInfo(null, null, new SuggestionInfo(false, true, myCommands.keySet()))); - - - // Now add balloons, info and suggestions - for (int i = 0; i < commandLine.getParts().size(); i++) { - final Pair argumentPair = commandArgumentsInfo.getArgument(i); - if (argumentPair == null) { // Excess argument! - chunkInfo.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.excessArg"))); - continue; - } - final Argument argument = argumentPair.getSecond(); - final List availableValues = argument.getAvailableValues(); - final Pair part = commandLine.getParts().get(i); - if (part.first != CommandLinePartType.ARGUMENT) { - // Only arguments are supported now, so we have nothing to say about this chunk - chunkInfo.add(new ChunkInfo(null, null)); - } - final String argumentValue = part.second.getText(); - String errorMessage = null; - if (availableValues != null && !availableValues.contains(argumentValue)) { - // Bad value - errorMessage = PyBundle.message("commandLine.validation.argBadValue"); - } - // Argument seems to be ok. We suggest values automatically only if value is bad - chunkInfo.add(new ChunkInfo(argument.getHelpText(), errorMessage, - (availableValues != null ? new SuggestionInfo(errorMessage != null, false, availableValues) : null))); - } - - - final Pair nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size()); - if (nextArgumentPair != null) { - // Next arg exists - final Argument nextArgument = nextArgumentPair.getSecond(); - final List availableValues = nextArgument.getAvailableValues(); - // Only add error if required - final String error = nextArgumentPair.first ? PyBundle.message("commandLine.validation.argMissing") : null; - final ChunkInfo lastArgInfo = - new ChunkInfo(nextArgument.getHelpText(), error, - (availableValues != null ? new SuggestionInfo(false, false, availableValues) : null)); - chunkInfo.add(lastArgInfo); - } - else { - // Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc. - // This is a hack, but with out of it last chunkinfo will always be used, even 200 chars after last place - chunkInfo.add(new ChunkInfo(null, null)); - } - - assert chunkInfo.size() >= chunks.size() : "Contract broken: not enough chunks"; - - return new ParseInfo(chunkInfo, command.getHelp(), new MyExecutor(command, commandLine)); - } - catch (final MalformedCommandLineException ignored) { - // Junk enetered! - return createBadCommandInfo(chunks.size()); - } - } - - - /** - * Creates parse info signaling command is bad or junk - * - * @param numberOfChunks number of chunks provided by user (we must return chunk info for each chunk + 1, accroding to contract) - * @return parse info to return - */ - @NotNull - private ParseInfo createBadCommandInfo(final int numberOfChunks) { - final List result = new ArrayList(); - // We know that first chunk command line, but we can't say anything about outher chunks except they are bad. - // How ever, we must say something according to contract (number of infos should be equal or greater than number of chunks) - result - .add(new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()))); - for (int i = 1; i < numberOfChunks; i++) { - result.add( - new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"))); - } - - return new ParseInfo(result); - } - - - /** - * Adapter that executes command using {@link Command#execute(com.intellij.openapi.module.Module, com.jetbrains.python.commandLineParser.CommandLineParseResult)} - */ - private class MyExecutor implements Runnable { - @NotNull - private final Command myCommand; - @NotNull - private final CommandLineParseResult myCommandLine; - - MyExecutor(@NotNull final Command command, @NotNull final CommandLineParseResult line) { - myCommand = command; - myCommandLine = line; - } - - @Override - public void run() { - myCommand.execute(myModule, myCommandLine); - } - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java new file mode 100644 index 000000000000..a35fd4b8f621 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java @@ -0,0 +1,204 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.commandBasedRangeDriver; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.Range; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.commandInterface.command.Argument; +import com.jetbrains.python.commandInterface.command.Command; +import com.jetbrains.python.commandInterface.command.Option; +import com.jetbrains.python.commandInterface.command.OptionArgumentInfo; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo; +import com.jetbrains.python.commandLineParser.CommandLine; +import com.jetbrains.python.commandLineParser.CommandLineParser; +import com.jetbrains.python.commandLineParser.CommandLinePart; +import com.jetbrains.python.commandLineParser.MalformedCommandLineException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Driver that returns pack of range infos for certain command line + * + * @author Ilya.Kazakevich + */ +public final class CommandBasedRangeInfoDriver implements RangeInfoDriver { + @NotNull + private final Map myCommands = new TreeMap(); // To sort commands by name + @NotNull + private final Module myModule; + @NotNull + private final CommandLineParser myCommandLineParser; + + /** + * + * @param module module (to be used in execution) + * @param commandLineParser parser to parse command lines + * @param commands available commands + */ + public CommandBasedRangeInfoDriver( + @NotNull final Module module, + @NotNull final CommandLineParser commandLineParser, + @NotNull final Collection commands) { + for (final Command command : commands) { + myCommands.put(command.getName(), command); + } + myModule = module; + myCommandLineParser = commandLineParser; + } + + @NotNull + @Override + public Pair> getCommandLineInfo(@NotNull final String commandLineText) { + // TODO: Copty/paste with exceptiojn + if (StringUtil.isEmpty(commandLineText)) { + final RangeInfo info = new RangeInfo(null, "", new SuggestionInfo( + true, true, myCommands.keySet() + ), RangeInfo.TERMINATION_RANGE, false + ); + return new Pair>(null, Collections.singletonList(info)); + } + final CommandLine commandLine; + try { + commandLine = myCommandLineParser.parse(commandLineText); + } + catch (final MalformedCommandLineException ignored) { + return new Pair>(null, Collections.singletonList( + new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo( + false, true, myCommands.keySet() + ), new Range(0, commandLineText.length()), false) + )); + } + + final Command command = getExistingCommand(commandLine); + final List commandLineParts = commandLine.getParts(); + if (command == null) { + // Bad command inserted + return createBadCommandInfo(commandLine); + } + + final UnusedOptionsCollector unusedOptionsCollector = UnusedOptionsCollector.create(command, commandLineParts); + final RangeInfoCollector rangeInfoCollector = RangeInfoCollector.create(command, commandLineParts, unusedOptionsCollector); + + + + final SuggestionInfo commandSuggestions = new SuggestionInfo(false, true, myCommands.keySet()); + // Add command as first range info + final List rangeInfos = + new ArrayList(Collections.singletonList(new RangeInfo(null, null, commandSuggestions, commandLine.getCommand(), false))); + // Then add collected infos + rangeInfos.addAll(rangeInfoCollector.getRangeInfos()); + + + /////// What about "after the caret" ? + final Pair unsatisfiedArgument = rangeInfoCollector.getUnsatisfiedPositionalArgument(); + final OptionArgumentInfo unsatisfiedOptionArgument = rangeInfoCollector.getUnsatisfiedOptionArgument(); + + + // TODO: Move to collector after test + if (unsatisfiedOptionArgument != null) { + final List availableValues = unsatisfiedOptionArgument.getAvailableValues(); + final SuggestionInfo suggestionInfo; + if (availableValues != null) { + + suggestionInfo = + new SuggestionInfo(false, false, availableValues); + } + else { + suggestionInfo = null; + } + rangeInfos + .add(new RangeInfo(null, PyBundle.message("commandLine.validation.optArgMissing"), suggestionInfo, RangeInfo.TERMINATION_RANGE, + false)); + } + else if (unsatisfiedArgument != null) { + final boolean required = unsatisfiedArgument.first; + final Argument argument = unsatisfiedArgument.second; + // Only add error if required + final String error = required ? PyBundle.message("commandLine.validation.argMissing") : null; + final List availableValues = unusedOptionsCollector.addUnusedOptions(argument.getAvailableValues()); + final RangeInfo lastArgInfo = + new RangeInfo(argument.getHelpText(), error, + (availableValues != null ? new SuggestionInfo(false, false, availableValues) : null), RangeInfo.TERMINATION_RANGE, + false); + rangeInfos.add(lastArgInfo); + } + else { + // Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc. + // This is a hack, but with out of it last range info will always be used, even 200 chars after last place + rangeInfos.add(new RangeInfo(null, null, rangeInfoCollector.getCurrentSuggestions(false, null), RangeInfo.TERMINATION_RANGE, false)); + } + + assert rangeInfos.size() >= commandLineParts.size() : "Contract broken: not enough chunks"; + return Pair.>create(new CommandExecutor(command, myModule, commandLine.getPartsAsText()), rangeInfos); + } + + @Nullable + private Command getExistingCommand(@NotNull final CommandLine commandLine) { + return myCommands.get(commandLine.getCommand().getText()); + } + + + /** + * Creates range info info signaling command is bad or junk + * + * + * @param commandLine command line passed by user + * @return info to return + */ + @NotNull + private Pair> createBadCommandInfo(final CommandLine commandLine) { + final List result = new ArrayList(); + // We know that first chunk command line, but we can't say anything about outher chunks except they are bad. + // How ever, we must say something + result + .add(new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()), + commandLine.getCommand(), false)); + // Command is unknown, all other parts are junk + for (final CommandLinePart part : commandLine.getParts()) { + result.add(new RangeInfo(null, "", false, part.getWord())); + } + + + return Pair.create(null, result); + } + + + /** + * Finds option by its name + * @param command current command + * @param optionName option name + * @return option or null if no option found + */ + @Nullable + static Option findOptionByName(@NotNull final Command command, @NotNull final String optionName) { + for (final Option option : command.getOptions()) { + for (final String name : option.getAllNames()) { + if (name.equals(optionName)) { + return option; + } + } + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java new file mode 100644 index 000000000000..992f00c1fc45 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.commandBasedRangeDriver; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.jetbrains.python.commandInterface.command.Command; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; + +/** + * Executes commands according to {@link Executor} contract + * + * @author Ilya.Kazakevich + */ +final class CommandExecutor implements Executor { + @NotNull + private final Command myCommand; + @NotNull + private final Module myModule; + @NotNull + private final String[] myArguments; + + /** + * @param command command to execute + * @param module module to execute against + * @param argumentsLine all command arguments as testline + */ + CommandExecutor(@NotNull final Command command, @NotNull final Module module, @NotNull final String argumentsLine) { + myCommand = command; + myModule = module; + myArguments = (StringUtil.isEmpty(argumentsLine) ? ArrayUtil.EMPTY_STRING_ARRAY : argumentsLine.split(" ")); + } + + @Nullable + @Override + public String getExecutionDescription() { + return myCommand.getHelp(); + } + + @Override + public void execute() { + myCommand.execute(myModule, Arrays.asList(myArguments)); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java new file mode 100644 index 000000000000..cf2f92edf34e --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java @@ -0,0 +1,250 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.commandBasedRangeDriver; + +import com.google.common.collect.Sets; +import com.intellij.openapi.util.Pair; +import com.intellij.util.containers.HashSet; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.WordWithPosition; +import com.jetbrains.python.commandInterface.command.Argument; +import com.jetbrains.python.commandInterface.command.Command; +import com.jetbrains.python.commandInterface.command.Option; +import com.jetbrains.python.commandInterface.command.OptionArgumentInfo; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo; +import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo; +import com.jetbrains.python.commandLineParser.CommandLineArgument; +import com.jetbrains.python.commandLineParser.CommandLineOption; +import com.jetbrains.python.commandLineParser.CommandLinePart; +import com.jetbrains.python.commandLineParser.CommandLinePartVisitor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Visitor that collections ranges info by visiting options and arguments + * + * @author Ilya.Kazakevich + */ +final class RangeInfoCollector implements CommandLinePartVisitor { + /** + * Options already met in command line. It has nothing to do with {@link UnusedOptionsCollector}! + */ + @NotNull + private final Set myUsedOptions = new HashSet(); + @NotNull + private final List myRangeInfos = new ArrayList(); + @NotNull + private final Command myCommand; + + private int myNumOfProcessedPositionalArguments = 0; + private boolean mySkipNextArgument; + private Pair myExpectedOptionArgument; + @NotNull + private final UnusedOptionsCollector myUnusedOptionsCollector; + + /** + * @param command command + * @param unusedOptionsCollector instance of unused options collector + */ + private RangeInfoCollector(@NotNull final Command command, + @NotNull final UnusedOptionsCollector unusedOptionsCollector) { + myCommand = command; + myUnusedOptionsCollector = unusedOptionsCollector; + } + + @NotNull + static RangeInfoCollector create(@NotNull final Command command, + @NotNull final Iterable commandLineParts, + @NotNull final UnusedOptionsCollector unusedOptionsCollector) { + final RangeInfoCollector infoCollector = new RangeInfoCollector(command, unusedOptionsCollector); + for (final CommandLinePart part : commandLineParts) { + part.accept(infoCollector); + } + return infoCollector; + } + + @Override + public void visitOption(@NotNull final CommandLineOption option) { + if (processOptionArgument(option.getWord())) { + return; + } + + + final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName()); + final WordWithPosition attachedArgument = option.getAttachedArgument(); + if (commandOption == null) { + // There is no such option + // To Mark attached arg so we skip it in {@link #visitArgument} + if (attachedArgument != null) { + mySkipNextArgument = true; + } + // No such option + myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badOption"), getCurrentSuggestions(false, null), + option.getWord(), false)); + return; + } + + + final Pair argumentAndQuantity = commandOption.getArgumentAndQuantity(); + + + // If option already used, then mark it + final String optionAlreadyUsed = Sets.intersection(Sets.newHashSet(commandOption.getAllNames()), myUsedOptions).isEmpty() + ? null + : PyBundle.message("commandLine.validation.badOption"); + myRangeInfos + .add(new RangeInfo(commandOption.getHelp(), optionAlreadyUsed, getCurrentSuggestions(false, null), option.getWord(), + argumentAndQuantity != null)); + + // remove from existing options + myUsedOptions.addAll(commandOption.getAllNames()); + + + if (argumentAndQuantity == null) { + if (attachedArgument != null) { + myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.noArgAllowed"), false, attachedArgument)); + mySkipNextArgument = true; + } + } + else { + // Some option args required + myExpectedOptionArgument = argumentAndQuantity; + } + } + + /** + * Process option argument (not to be confused with positional!) + * + * @param currentPart part with argument. + * @return false if there should not be any option argument so this method did nothing. True if there should be and it is processed. + */ + private boolean processOptionArgument(@NotNull final WordWithPosition currentPart) { + if (myExpectedOptionArgument == null) { + return false; + } + final OptionArgumentInfo argumentInfo = myExpectedOptionArgument.second; + int argsLeft = myExpectedOptionArgument.first; + + final boolean valid = argumentInfo.isValid(currentPart.getText()); + final List availableValues = argumentInfo.getAvailableValues(); + final SuggestionInfo suggestions; + if (availableValues != null) { + suggestions = new SuggestionInfo(false, false, availableValues); + } + else { + suggestions = null; + } + + myRangeInfos.add(new RangeInfo(null, (valid ? null : PyBundle.message("commandLine.validation.argBadValue")), + suggestions, currentPart, false)); + + + if (--argsLeft == 0) { + myExpectedOptionArgument = null; + } + else { + myExpectedOptionArgument = Pair.create(argsLeft, argumentInfo); + } + return true; + } + + + /** + * Returns suggestions available for current position. + * + * @param showAutomatically make suggestions displayed automatically + * @param argumentPair argument to use. If null, current argument according to internal counter would be used. + * @return suggestiom info or null if no suggestion available + */ + @Nullable + SuggestionInfo getCurrentSuggestions(final boolean showAutomatically, @Nullable Pair argumentPair) { + if (argumentPair == null) { + argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments); + } + + final List suggestions = argumentPair != null ? myUnusedOptionsCollector.addUnusedOptions( + argumentPair.second.getAvailableValues()) : myUnusedOptionsCollector.addUnusedOptions( + null); + if (suggestions == null) { + return null; + } + return new SuggestionInfo(showAutomatically, false, suggestions); + } + + @Override + public void visitArgument(@NotNull final CommandLineArgument argument) { + if (mySkipNextArgument) { + // Skip argument, clear flag and do nothing + mySkipNextArgument = false; + return; + } + + if (processOptionArgument(argument.getWord())) { + return; + } + + final Pair argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments++); + if (argumentPair == null) { + //Exceed! + myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.excessArg"), false, argument.getWord())); + return; + } + final Argument commandArgument = argumentPair.second; + + final List argumentAvailableValues = commandArgument.getAvailableValues(); + final String argumentValue = argument.getWord().getText(); + String errorMessage = null; + if (argumentAvailableValues != null && !argumentAvailableValues.contains(argumentValue)) { + // Bad value + errorMessage = PyBundle.message("commandLine.validation.argBadValue"); + } + // Argument seems to be ok. We suggest values automatically only if value is bad + myRangeInfos.add(new RangeInfo(commandArgument.getHelpText(), errorMessage, + getCurrentSuggestions(errorMessage != null, argumentPair), + argument.getWord(), false)); + } + + /** + * @return calculates range infos for all command line parts (not the command itself!) + */ + @NotNull + Collection getRangeInfos() { + return Collections.unmodifiableList(myRangeInfos); + } + + + /** + * @return Unsatisfied (currently expected) positional argument ([required, arg]) or null if no arg expected + */ + @Nullable + Pair getUnsatisfiedPositionalArgument() { + return myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments); + } + + /** + * @return Unsatisfied (currently expected) option argument or null if no option argument expected + */ + public OptionArgumentInfo getUnsatisfiedOptionArgument() { + if (myExpectedOptionArgument != null) { + return myExpectedOptionArgument.second; + } + else { + return null; + } + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java new file mode 100644 index 000000000000..a72030ac696f --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java @@ -0,0 +1,97 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.commandBasedRangeDriver; + +import com.intellij.util.containers.HashSet; +import com.jetbrains.python.commandInterface.command.Command; +import com.jetbrains.python.commandInterface.command.Option; +import com.jetbrains.python.commandLineParser.CommandLineArgument; +import com.jetbrains.python.commandLineParser.CommandLineOption; +import com.jetbrains.python.commandLineParser.CommandLinePart; +import com.jetbrains.python.commandLineParser.CommandLinePartVisitor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Collections all used and unused options to report unused options then. + * It may be used to add unused options to suggestion list {@link #addUnusedOptions(Collection)} + * + * @author Ilya.Kazakevich + */ +final class UnusedOptionsCollector implements CommandLinePartVisitor { + @NotNull + private final Set myUnusedOptions = new HashSet(); + @NotNull + private final Command myCommand; + + /** + * @param command command to be used + * @param commandLineParts command line parts + * @return instance + */ + static UnusedOptionsCollector create(@NotNull final Command command, @NotNull final Iterable commandLineParts) { + final UnusedOptionsCollector collector = new UnusedOptionsCollector(command); + for (final CommandLinePart part : commandLineParts) { + part.accept(collector); + } + return collector; + } + + private UnusedOptionsCollector(@NotNull final Command command) { + for (final Option option : command.getOptions()) { + myUnusedOptions.addAll(option.getAllNames()); + } + myCommand = command; + } + + @Override + public void visitOption(@NotNull final CommandLineOption option) { + final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName()); + if (commandOption != null) { + myUnusedOptions.removeAll(commandOption.getAllNames()); + } + } + + /** + * Merges list of unused options and other values provided as argument. + * + * @param mainValues values to merge options with. May be null. + * @return null of no options and no values provided or merged list of values and options. See method usages for more info + */ + @Nullable + List addUnusedOptions(@Nullable final Collection mainValues) { + if (mainValues == null && myUnusedOptions.isEmpty()) { + return null; + } + final List result = new ArrayList(myUnusedOptions); + + if (mainValues != null) { + result.addAll(mainValues); + } + return (result.isEmpty() ? null : result); + } + + + @Override + public void visitArgument(@NotNull final CommandLineArgument argument) { + + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/package-info.java similarity index 81% rename from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/package-info.java index 20677dfe0d27..278f48371b70 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/package-info.java @@ -15,10 +15,10 @@ */ /** - * {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of + * {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} implementation based on idea of * {@link com.jetbrains.python.commandInterface.command command, option and argument}. * - * See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point. + * See {@link com.jetbrains.python.commandInterface.commandBasedRangeDriver.CommandBasedRangeInfoDriver} as entry point. * It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments * provided by user * @@ -26,4 +26,4 @@ * @see com.jetbrains.python.commandInterface.command.Command * @author Ilya.Kazakevich */ -package com.jetbrains.python.commandInterface.commandBasedChunkDriver; \ No newline at end of file +package com.jetbrains.python.commandInterface.commandBasedRangeDriver; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandInterface/package-info.java b/python/src/com/jetbrains/python/commandInterface/package-info.java index 7f1581af3bc7..d96540555e52 100644 --- a/python/src/com/jetbrains/python/commandInterface/package-info.java +++ b/python/src/com/jetbrains/python/commandInterface/package-info.java @@ -31,7 +31,7 @@ * *

* There is also swing-based view implementation in {@link com.jetbrains.python.commandInterface.swingView} - * and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter} + * and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter} *

* *

diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java new file mode 100644 index 000000000000..41ed72cec714 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.rangeBasedPresenter; + +import org.jetbrains.annotations.Nullable; + +/** + * Engine to execute command line + * + * @author Ilya.Kazakevich + */ +public interface Executor { + /** + * @return information about what to execute (like command name) + */ + @Nullable + String getExecutionDescription(); + + /** + * Execute command! + */ + void execute(); +} diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java new file mode 100644 index 000000000000..4ab64a444abe --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java @@ -0,0 +1,246 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.rangeBasedPresenter; + +import com.intellij.openapi.util.Pair; +import com.intellij.util.Range; +import com.jetbrains.python.WordWithPosition; +import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter; +import com.jetbrains.python.commandInterface.CommandInterfaceView; +import com.jetbrains.python.suggestionList.SuggestionsBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + + +/** + * Presenter that uses {@link RangeInfoDriver} to obtain {@link RangeInfo info}. + * Such info tells presenter whether this text has info, error, suggestions and so on. + * If caret situated far from text, then next neariest range should be found (see {@link #findNearestRangeInfo()}. + * + * @author Ilya.Kazakevich + * @see RangeInfo + */ +public final class RangeBasedPresenter extends CommandInterfacePresenterAdapter { + @NotNull + private final RangeInfoDriver myRangeInfoDriver; + @Nullable + private Executor myExecutor; + private final SortedSet myRangeInfos = new TreeSet(); + + public RangeBasedPresenter(@NotNull final CommandInterfaceView view, + @NotNull final RangeInfoDriver rangeInfoDriver) { + super(view); + myRangeInfoDriver = rangeInfoDriver; + } + + @Override + public void launch() { + super.launch(); + reparseText(true); + } + + @Override + public void textChanged() { + reparseText(false); + } + + private void reparseText(final boolean skipSuggestions) { + + + final Pair> rangeInfoStructure = myRangeInfoDriver.getCommandLineInfo(myView.getText()); + myExecutor = rangeInfoStructure.first; + final List rangeInfos = rangeInfoStructure.second; + + assert !rangeInfos.isEmpty() : "At least one chunk info should exist"; + myRangeInfos.clear(); + myRangeInfos.addAll(rangeInfos); + + + // configure Errors And Balloons + + final Collection infoBalloons = new ArrayList(); + final Collection errorBalloons = new ArrayList(); + + + for (final RangeInfo rangeInfo : rangeInfos) { + final String error = rangeInfo.getError(); + if (error != null) { + errorBalloons.add(new WordWithPosition(error, rangeInfo)); + } + final String info = rangeInfo.getInfoBalloon(); + if (info != null) { + infoBalloons.add(new WordWithPosition(info, rangeInfo)); + } + } + + myView.setInfoAndErrors(infoBalloons, errorBalloons); + + + if (!skipSuggestions) { + configureSuggestion(false); + } + + // Configure subtexts + final List> placesWhereSuggestionAvailable = new ArrayList>(); + for (final RangeInfo rangeInfo : rangeInfos) { + // If some place has suggestions, then add it + if (rangeInfo.getSuggestions() != null) { + placesWhereSuggestionAvailable.add(rangeInfo); + } + } + final String statusText = (myExecutor != null ? myExecutor.getExecutionDescription() : null); + myView.configureSubTexts(statusText, placesWhereSuggestionAvailable); + } + + @Override + public void suggestionRequested() { + configureSuggestion(true); // Show or hide + } + + + /** + * Displays suggestions if needed. + * + * @param requestedExplicitly is suggesions where requested by user explicitly or not + */ + private void configureSuggestion(final boolean requestedExplicitly) { + myView.removeSuggestions(); + final RangeInfo rangeInfo = findNearestRangeInfo(); + final String text = getTextByRange(rangeInfo); + + final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions(); + if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) { + return; + } + final List suggestions = new ArrayList(suggestionInfo.getSuggestions()); + if (text != null && !requestedExplicitly) { + filterLeaveOnlyMatching(suggestions, text); + } + // TODO: Place to add history + if (!suggestions.isEmpty()) { + // No need to display empty suggestions + myView + .displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), text); + } + } + + + /** + * Filters collection of suggestions leaving only those starts with certain text. + * + * @param suggestions list to filter + * @param textToMatch leave only parts that start with this param + */ + private static void filterLeaveOnlyMatching(@NotNull final Iterable suggestions, @NotNull final String textToMatch) { + // TODO: use guava instead? + final Iterator iterator = suggestions.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().startsWith(textToMatch)) { + iterator.remove(); + } + } + } + + /** + * Searches for the nearest range to use. + * + * @return nearest range info + */ + @NotNull + private RangeInfo findNearestRangeInfo() { + final int caretPosition = myView.getCaretPosition(); + + for (final RangeInfo range : myRangeInfos) { + if (range.isWithin(caretPosition)) { + return range; + } + if (range.getFrom() > caretPosition) { + return range; // Ranges are sorted, so we are on the next range. Take it, if caret is not within range + } + } + + return myRangeInfos.last(); + } + + + @Override + public void completionRequested(@Nullable final String valueFromSuggestionList) { + final RangeInfo rangeInfo = findNearestRangeInfo(); + final String text = getTextByRange(rangeInfo); + + if (valueFromSuggestionList != null) { + // Just insert it + if (text != null) { // If caret is on the text itself + // TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg) + myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), valueFromSuggestionList); + } + else { + myView.insertTextAfterCaret(valueFromSuggestionList); + } + return; + } + + //User did not provide text no insert, do our best to find one + + final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions(); + if (suggestionInfo == null) { + return; // No suggestion available for this chunk + } + final List suggestions = new ArrayList(suggestionInfo.getSuggestions()); + if (text != null) { + filterLeaveOnlyMatching(suggestions, text); + } + if (suggestions.size() == 1) { + // Exclusive! + if (text != null) { + // TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg) + myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), suggestions.get(0)); + } + else { + myView.insertTextAfterCaret(suggestions.get(0)); + } + } + } + + /** + * Searches for text under the range + * @param rangeInfo range + * @return text or null if range does not contain any text + */ + @Nullable + private String getTextByRange(@NotNull final RangeInfo rangeInfo) { + + if (rangeInfo.isTerminationRange()) { + return null; + } + else { + final String viewText = myView.getText(); + return viewText.substring(rangeInfo.getFrom(), rangeInfo.getTo()); + } + } + + @Override + public void executionRequested() { + if (myExecutor == null) { + // TODO: Display error somehow + } + else { + myExecutor.execute(); + } + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java new file mode 100644 index 000000000000..a695eea7db25 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java @@ -0,0 +1,122 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.rangeBasedPresenter; + +import com.intellij.util.Range; +import com.jetbrains.python.commandInterface.CommandInterfaceView; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Information about certain place in text, provided by driver. + * + * @author Ilya.Kazakevich + */ +public final class RangeInfo extends Range implements Comparable { + + + /** + * Special "terminator": the last range which is after all other ranges. + */ + public static final Range TERMINATION_RANGE = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE; + + @Nullable + private final String myInfoBalloon; + @Nullable + private final String myError; + @Nullable + private final SuggestionInfo mySuggestions; + private final boolean myExclusiveBorders; + + + /** + * @param infoBalloon info to bind to this range + * @param error error to bind to this range + * @param exclusiveBorders true if range has exclusive borders and right border is not part of it. I.e. 3 is part of 1-3 range with out + * of exclusive borders, but not part of exclusive range + * @param range from and to + */ + public RangeInfo(@Nullable final String infoBalloon, + @Nullable final String error, + final boolean exclusiveBorders, + @NotNull final Range range) { + this(infoBalloon, error, null, range, exclusiveBorders); + } + + + /** + * @param infoBalloon Info balloon to display when caret meets this place (null if display nothing) + * @param error Error balloon to display when caret meets this place and underline text as error (null if no error) + * @param suggestions list of suggestions available in this place (if any) + */ + public RangeInfo(@Nullable final String infoBalloon, + @Nullable final String error, + @Nullable final SuggestionInfo suggestions, + @NotNull final Range range, + final boolean exclusiveBorders) { + super(range.getFrom(), range.getTo()); + myInfoBalloon = infoBalloon; + myError = error; + mySuggestions = suggestions; + myExclusiveBorders = exclusiveBorders; + } + + + /** + * @return Info balloon to display when caret meets this place (null if display nothing) + */ + @Nullable + public String getInfoBalloon() { + return myInfoBalloon; + } + + /** + * @return Error balloon to display when caret meets this place and underline text as error (null if no error) + */ + @Nullable + public String getError() { + return myError; + } + + /** + * @return list of suggestions available in this place (if any) + */ + @Nullable + public SuggestionInfo getSuggestions() { + return mySuggestions; + } + + @Override + public int compareTo(RangeInfo o) { + return getFrom().compareTo(o.getFrom()); + } + + @Override + public boolean isWithin(final Integer object) { + if (!super.isWithin(object)) { + return false; + } + if (myExclusiveBorders) { + return object < getTo(); + } + return true; + } + + public boolean isTerminationRange() { + // TODO: copy/paste with view + return TERMINATION_RANGE.getFrom().equals(getFrom()) && TERMINATION_RANGE.getTo().equals(getTo()); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java new file mode 100644 index 000000000000..fb5887290654 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandInterface.rangeBasedPresenter; + +import com.intellij.openapi.util.Pair; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Driver that knows how to getCommandLineInfo pack of chunks into chunk info. + * + * @author Ilya.Kazakevich + */ +public interface RangeInfoDriver { + /** + * Parses command line text into executor and pack of range infos. + * There always should be at least one range info and the last one is almost always {@link RangeInfo#TERMINATION_RANGE). + * + * @param commandLineText command line text to parse. + * @return pair or executor (the one that knows how to execute command line) and pack of range infos. + * Warning! :Executor could be null if command can't be executed + */ + @NotNull + Pair> getCommandLineInfo(@NotNull String commandLineText); +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/SuggestionInfo.java similarity index 86% rename from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java rename to python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/SuggestionInfo.java index 59bea9abd12f..e7af84e52ea0 100644 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/SuggestionInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter; +package com.jetbrains.python.commandInterface.rangeBasedPresenter; import org.jetbrains.annotations.NotNull; @@ -67,4 +67,13 @@ public final class SuggestionInfo { public boolean isShowAbsolute() { return myShowAbsolute; } + + @Override + public String toString() { + return "SuggestionInfo{" + + "mySuggestions=" + mySuggestions + + ", myShowSuggestionsAutomatically=" + myShowSuggestionsAutomatically + + ", myShowAbsolute=" + myShowAbsolute + + '}'; + } } diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java new file mode 100644 index 000000000000..152071b1e34c --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2015 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. + */ + +/** + * {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of range info + * and {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver}. + * This presenter passes command line to driver, and it returns pack of range information. + * Each record contains everything presenter needs to know about certain range (i.e. error between 2 and 5 chars). + * Special type of range {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo#TERMINATION_RANGE} exists, that + * you may need to check. + *

+ * Presenter uses this information to display chunks correctly using view. + * To use this package, {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} should be implemented. + *

+ * See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeBasedPresenter} as entry point + * + * @author Ilya.Kazakevich + */ +package com.jetbrains.python.commandInterface.rangeBasedPresenter; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java b/python/src/com/jetbrains/python/commandLineParser/CommandLine.java similarity index 58% rename from python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java rename to python/src/com/jetbrains/python/commandLineParser/CommandLine.java index a49394b37ef0..201a779554a8 100644 --- a/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLine.java @@ -15,7 +15,7 @@ */ package com.jetbrains.python.commandLineParser; -import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; import com.jetbrains.python.WordWithPosition; import org.jetbrains.annotations.NotNull; @@ -25,22 +25,22 @@ import java.util.Collections; import java.util.List; /** - * Command line parse result. + * Command line getCommandLineInfo result. * It consists of command itself and its parts. * Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or * {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else. * * @author Ilya.Kazakevich */ -public final class CommandLineParseResult { +public final class CommandLine { @NotNull - private final List> myParts = new ArrayList>(); + private final List myParts = new ArrayList(); @NotNull private final WordWithPosition myCommand; - public CommandLineParseResult( + public CommandLine( @NotNull final WordWithPosition command, - @NotNull final Collection> parts) { + @NotNull final Collection parts) { myCommand = command; myParts.addAll(parts); } @@ -53,26 +53,26 @@ public final class CommandLineParseResult { return myCommand; } - /** - * @return list of parts in format [part_type, value]. - * For example (rm my_folder): [{@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument}, my_folder] - */ @NotNull - public List> getParts() { + public List getParts() { return Collections.unmodifiableList(myParts); } /** - * @return all command line parts with out of part information (just words and positions). - * Note tha command itself is not part, only args and options are - * @see #getParts() + * @return all command parts as text (actually by bindning them back together) */ @NotNull - public Collection getPartsNoType() { - final Collection result = new ArrayList(); - for (final Pair part : myParts) { - result.add(part.second); + public String getPartsAsText() { + final StringBuilder builder = new StringBuilder(); + int lastPosition = 0; + for (final CommandLinePart part : getParts()) { + final WordWithPosition partWord = part.getWord(); + if (lastPosition != partWord.getFrom()) { + builder.append(' '); + } + builder.append(partWord.getText()); + lastPosition = partWord.getTo(); } - return result; + return StringUtil.trim(builder.toString()); } } diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java new file mode 100644 index 000000000000..13d92b9b2451 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandLineParser; + +import com.jetbrains.python.WordWithPosition; +import org.jetbrains.annotations.NotNull; + +/** + * Positional or option argument. + * @author Ilya.Kazakevich + */ +public final class CommandLineArgument extends CommandLinePart { + + public CommandLineArgument(@NotNull final WordWithPosition argumentValue) { + super(argumentValue); + } + + + @Override + public void accept(@NotNull final CommandLinePartVisitor visitor) { + visitor.visitArgument(this); + } +} diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java new file mode 100644 index 000000000000..217b4fa44dc0 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2015 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.jetbrains.python.commandLineParser; + +import com.jetbrains.python.WordWithPosition; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Option. Each option has name and may also have attached argument like "--long-option=attached_arg" or "-sATTACHED_ARG" + * + * @author Ilya.Kazakevich + */ +public final class CommandLineOption extends CommandLinePart { + + @NotNull + private final String myOptionName; + + @Nullable + private final WordWithPosition myAttachedArgument; + + /** + * @param option option (text and position) + * @param optionName option name (like "--foo") + * @param attachedArgument option attached argument (like --foo=ATTACHED_ARG) + */ + public CommandLineOption(@NotNull final WordWithPosition option, + @NotNull final String optionName, + @Nullable final WordWithPosition attachedArgument) { + super(option); + myOptionName = optionName; + myAttachedArgument = attachedArgument; + } + + + /** + * @return option name (like "--foo") + */ + @NotNull + public String getOptionName() { + return myOptionName; + } + + /** + * @return option attached argument (like --foo=ATTACHED_ARG) + */ + @Nullable + public WordWithPosition getAttachedArgument() { + return myAttachedArgument; + } + + @Override + public void accept(@NotNull final CommandLinePartVisitor visitor) { + visitor.visitOption(this); + } +} diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java index b3813d8dd616..b52d785f54c9 100644 --- a/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java @@ -15,24 +15,20 @@ */ package com.jetbrains.python.commandLineParser; -import com.jetbrains.python.WordWithPosition; import org.jetbrains.annotations.NotNull; -import java.util.List; - /** - * Engine to parse command line. It understands how options and arguments are coded in certain commandline. - * It supportd {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of - * command line is command or argument. That helps you to underline or emphisize some parts. + * Engine to {@link CommandLine} structure from text. * * @author Ilya.Kazakevich */ public interface CommandLineParser { /** - * @param commandLineParts command line splitted into words. + * + * @param commandLineText command line to parse * @return command line information * @throws MalformedCommandLineException in case of bad commandline */ @NotNull - CommandLineParseResult parse(@NotNull List commandLineParts) throws MalformedCommandLineException; + CommandLine parse(@NotNull String commandLineText) throws MalformedCommandLineException; } diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java b/python/src/com/jetbrains/python/commandLineParser/CommandLinePart.java similarity index 51% rename from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java rename to python/src/com/jetbrains/python/commandLineParser/CommandLinePart.java index 12740a530052..93af9e0d92ea 100644 --- a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLinePart.java @@ -13,27 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter; +package com.jetbrains.python.commandLineParser; import com.jetbrains.python.WordWithPosition; import org.jetbrains.annotations.NotNull; -import java.util.List; - /** - * Driver that knows how to parse pack of chunks into chunk info. + * Part of command line. Known subclasses are {@link CommandLineOption} and {@link CommandLineArgument} * * @author Ilya.Kazakevich + * @see CommandLineArgument + * @see CommandLineOption */ -public interface ChunkDriver { +public abstract class CommandLinePart { + @NotNull + private final WordWithPosition myWord; + /** - * Parses chunks into pack of chunks. There always should be chunk+1 chunkInfos (one for the tail like - * {@link com.jetbrains.python.commandInterface.CommandInterfaceView#AFTER_LAST_CHARACTER_RANGE}). - * So, at least one chunk info should also exist! - * - * @param chunks chunks (parts of command line) - * @return parse info with chunks info. Warning: do not return less chunk infos than chunks provided. That leads to runtime error + * @param word word (and its position) this part represents + */ + protected CommandLinePart(@NotNull final WordWithPosition word) { + myWord = word; + } + + /** + * @return word (and its position) this part represents */ @NotNull - ParseInfo parse(@NotNull List chunks); + public final WordWithPosition getWord() { + return myWord; + } + + /** + * @param visitor visitor to accept + */ + public abstract void accept(@NotNull CommandLinePartVisitor visitor); } diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java b/python/src/com/jetbrains/python/commandLineParser/CommandLinePartVisitor.java similarity index 57% rename from python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java rename to python/src/com/jetbrains/python/commandLineParser/CommandLinePartVisitor.java index 1caf82eaad88..fe381c0957c4 100644 --- a/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLinePartVisitor.java @@ -15,27 +15,23 @@ */ package com.jetbrains.python.commandLineParser; +import org.jetbrains.annotations.NotNull; + /** - * Types of command line parts. + * Visitor to process options and arguments * * @author Ilya.Kazakevich + * @see CommandLinePart#accept(CommandLinePartVisitor) */ -public enum CommandLinePartType { +public interface CommandLinePartVisitor { + /** - * Argument (or positional, or unnamed argument) something that has only value. Like "my_folder" in "rm my_folder" + * @param option option to visit */ - ARGUMENT, + void visitOption(@NotNull CommandLineOption option); + /** - * Option is named but optional parameter. Like "-l" in "ls -l". + * @param argument argument to visit */ - OPTION, - /** - * Option argument like --folder-to-delete=/ - * Here root is option argument - */ - OPTION_ARGUMENT, - /** - * Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand - */ - UNKNOWN + void visitArgument(@NotNull CommandLineArgument argument); } diff --git a/python/src/com/jetbrains/python/commandLineParser/optParse/LongOptionParser.java b/python/src/com/jetbrains/python/commandLineParser/optParse/LongOptionParser.java index 63a173e393e3..82fd6dfe88e4 100644 --- a/python/src/com/jetbrains/python/commandLineParser/optParse/LongOptionParser.java +++ b/python/src/com/jetbrains/python/commandLineParser/optParse/LongOptionParser.java @@ -16,38 +16,27 @@ package com.jetbrains.python.commandLineParser.optParse; import com.intellij.openapi.util.Pair; -import com.jetbrains.python.commandInterface.command.Option; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Supports --long-option-style-with=value + * * @author Ilya.Kazakevich */ -final class LongOptionParser implements OptionParser { - @Nullable +final class LongOptionParser extends OptionParserRegexBased { + @NotNull + private static final Pattern LONG_OPT_PATTERN = Pattern.compile("^((--[a-zA-Z0-9-]+)=?)"); + + LongOptionParser() { + super(LONG_OPT_PATTERN); + } + + @NotNull @Override - public Pair findOptionAndValue(@NotNull final List

@@ -48,114 +44,67 @@ import java.util.*; public final class OptParseCommandLineParser implements CommandLineParser { /** * Supported option parsers (option types, actually) + * Short (-o) and long (--option) strategies are used here */ - @NotNull - private static final OptionParser[] OPTION_PARSERS = {new LongOptionParser(), new ShortOptionParser()}; - /** - * CommandName -> Options - */ - @NotNull - private final Map> myCommandOptions = new HashMap>(); + private static final OptionParser[] OPTION_PARSERS = {new ShortOptionParser(), new LongOptionParser()}; - /** - * @param commands commands (needed to parse out options) - */ - public OptParseCommandLineParser(@NotNull final Iterable commands) { - for (final Command command : commands) { - myCommandOptions.put(command.getName(), command.getOptions()); - } - } @NotNull @Override - public CommandLineParseResult parse(@NotNull final List commandLineParts) throws MalformedCommandLineException { - final Deque parts = new ArrayDeque(commandLineParts); + public CommandLine parse(@NotNull final String commandLineText) throws MalformedCommandLineException { + final Deque parts = new ArrayDeque(WordWithPosition.splitText(commandLineText)); if (parts.isEmpty()) { throw new MalformedCommandLineException("No command provided"); } final WordWithPosition command = parts.pop(); - final List

* Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of * command line is command or argument. That helps you to underline or emphisize some parts. * - * * @author Ilya.Kazakevich */ package com.jetbrains.python.commandLineParser; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/suggestionList/SuggestionList.java b/python/src/com/jetbrains/python/suggestionList/SuggestionList.java index d209235bec74..708d4418c05e 100644 --- a/python/src/com/jetbrains/python/suggestionList/SuggestionList.java +++ b/python/src/com/jetbrains/python/suggestionList/SuggestionList.java @@ -99,6 +99,7 @@ public class SuggestionList { } // Fill and select + int record = 0; // Record to select // Iterate through groups adding suggestions. Odd groups should be marked differently. for (int groupId = 0; groupId < values.size(); groupId++) { final List suggestions = values.get(groupId); @@ -106,11 +107,12 @@ public class SuggestionList { final Suggestion suggestion = suggestions.get(suggestionId); myListModel.addElement(new SuggestionListElement((groupId % 2) == 0, suggestion)); if (suggestion.getText().equals(elementToSelect)) { - myList.setSelectedIndex(suggestionId + groupId); + myList.setSelectedIndex(record); } + record++; } } - if ((elementToSelect == null) && (!myListModel.isEmpty())) { + if ((myList.getSelectedIndex() ==-1) && (!myListModel.isEmpty())) { myList.setSelectedIndex(0); // Select first element } @@ -124,6 +126,7 @@ public class SuggestionList { myListPopUp.addListener(myListener); } myListPopUp.show(displayPoint); + myList.ensureIndexIsVisible(myList.getSelectedIndex()); // Scrolls to selected } From 80fd1723c314174212a74a6a75858b65c73fc7a3 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 16 Feb 2015 23:28:12 +0100 Subject: [PATCH 07/16] IDEA-135970 (adjusted path included in JDK validation) --- .../intellij/openapi/projectRoots/impl/JavaSdkImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java index dbc4330202a8..4ee28ed0cbe3 100644 --- a/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java +++ b/java/java-impl/src/com/intellij/openapi/projectRoots/impl/JavaSdkImpl.java @@ -241,8 +241,11 @@ public class JavaSdkImpl extends JavaSdk { final FileChooserDescriptor descriptor = new FileChooserDescriptor(baseDescriptor) { @Override public void validateSelectedFiles(VirtualFile[] files) throws Exception { - if (files.length > 0 && JrtFileSystem.isModularJdk(files[0].getPath()) && !JrtFileSystem.isSupported()) { - throw new Exception(LangBundle.message("jrt.not.available.message")); + if (files.length > 0 && !JrtFileSystem.isSupported()) { + String path = files[0].getPath(); + if (JrtFileSystem.isModularJdk(path) || JrtFileSystem.isModularJdk(adjustSelectedSdkHome(path))) { + throw new Exception(LangBundle.message("jrt.not.available.message")); + } } baseDescriptor.validateSelectedFiles(files); } From 9c386248b9d5d715e7f198344efba8bb19b29467 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 17 Feb 2015 01:33:33 +0300 Subject: [PATCH 08/16] PY-11855 Run manage.py task improvements Workaround to support errors: * In Python side we skip command if it is misconfigured * On Java side we skip producer if it fails TODO: we need to log errors on python side somehow --- .../pycharm/django_manage_commands_provider/_optparse.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py index 69e54d4db9b4..1ef84ab96dd2 100644 --- a/python/helpers/pycharm/django_manage_commands_provider/_optparse.py +++ b/python/helpers/pycharm/django_manage_commands_provider/_optparse.py @@ -6,6 +6,7 @@ This module encapsulates Django semi-public API knowledge, and not very stable b from optparse import Option import django from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django.core.management import ManagementUtility, get_commands, BaseCommand __author__ = 'Ilya.Kazakevich' @@ -20,7 +21,11 @@ def report_data(dumper): """ utility = ManagementUtility() for command_name in get_commands().keys(): - command = utility.fetch_command(command_name) + try: + command = utility.fetch_command(command_name) + except ImproperlyConfigured: + continue # TODO: Log somehow + assert isinstance(command, BaseCommand) dumper.start_command(command_name=command_name, command_help_text=str(command.usage("").replace("%prog", command_name)), From 15466d57bbeb5cf4f7246bd573a32f04c7cc3806 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 16 Feb 2015 23:33:58 +0100 Subject: [PATCH 09/16] IDEA-135970 (validation errors in Mac file chooser) --- .../ui/mac/MacFileChooserDialogImpl.java | 93 +++++++++++-------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java b/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java index a333565f3607..766dc8c64cdb 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -15,6 +15,7 @@ */ package com.intellij.ui.mac; +import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; @@ -37,6 +38,7 @@ import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; import com.sun.jna.Callback; +import com.sun.jna.Pointer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,53 +61,69 @@ public class MacFileChooserDialogImpl implements PathChooserDialog { private final Project myProject; private Consumer> myCallback; - private static boolean checkFile(@NotNull ID self, ID url, boolean checkDirectories) { - MacFileChooserDialogImpl dialog = ourImplMap.get(self); - if (dialog == null) { - // Since it has already been removed from the map, the file is likely to be valid if the user was able to select it - return true; - } - - if (url == null || url.intValue() == 0) { - return false; - } - - ID filename = Foundation.invoke(url, "path"); - String fileName = Foundation.toStringViaUTF8(filename); - if (fileName == null) { - return false; - } - - VirtualFile file = LocalFileSystem.getInstance().findFileByPath(fileName); - return file == null || (!checkDirectories && file.isDirectory()) || dialog.myChooserDescriptor.isFileSelectable(file); - } - private static final Callback SHOULD_ENABLE_CALLBACK = new Callback() { @SuppressWarnings("UnusedDeclaration") public boolean callback(ID self, String selector, ID panel, ID url) { - // allow any directory - ability to select nested directories - return checkFile(self, url, false); + try { + return checkFile(self, url, true); // allow any directory - ability to select nested directories + } + catch (Exception e) { + return false; + } } }; private static final Callback VALIDATE_URL_CALLBACK = new Callback() { @SuppressWarnings("UnusedDeclaration") public boolean callback(ID self, String selector, ID panel, ID url, ID outError) { - if (checkFile(self, url, true)) { - return true; + try { + return checkFile(self, url, false); } - - /* - if (!outError.equals(ID.NIL)) { - ID error = Foundation.invoke("NSError", "errorWithDomain:code:userInfo:", Foundation.nsString("org.jetbrains"), - Foundation.createDict(new String[]{"NSLocalizedDescriptionKey"}, new Object[]{"Not allowed"})); - // todo "*outError = error" + catch (Exception e) { + if (!ID.NIL.equals(outError)) { + ID domain = Foundation.nsString(ApplicationNamesInfo.getInstance().getProductName()); + ID dict = Foundation.createDict(new String[]{"NSLocalizedDescription"}, new Object[]{e.getMessage()}); + ID error = Foundation.invoke("NSError", "errorWithDomain:code:userInfo:", domain, 100, dict); + new Pointer(outError.longValue()).setLong(0, error.longValue()); + } + return false; } - */ - return false; } }; + private static boolean checkFile(@NotNull ID self, ID url, boolean quickCheck) throws Exception { + MacFileChooserDialogImpl impl = ourImplMap.get(self); + if (impl == null) { + return true; // already removed from the map: the file is likely to be valid since the user was able to select it + } + + if (url == null || url.intValue() == 0) { + return false; + } + + ID filename = Foundation.invoke(url, "path"); + String path = Foundation.toStringViaUTF8(filename); + if (path == null) { + return false; + } + + VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); + if (file == null || quickCheck && file.isDirectory()) { + return true; + } + + if (!impl.myChooserDescriptor.isFileSelectable(file)) { + return false; + } + + if (!quickCheck) { + VirtualFile[] files = {file}; + impl.myChooserDescriptor.validateSelectedFiles(files); + } + + return true; + } + private static final Callback OPEN_PANEL_DID_END = new Callback() { @SuppressWarnings("UnusedDeclaration") public void callback(ID self, String selector, ID openPanelDidEnd, ID returnCode, ID contextInfo) { @@ -300,7 +318,6 @@ public class MacFileChooserDialogImpl implements PathChooserDialog { @Override public void choose(@Nullable final VirtualFile toSelect, @NotNull final Consumer> callback) { - ExtensionsInitializer.initialize(); myCallback = callback; @@ -356,12 +373,11 @@ public class MacFileChooserDialogImpl implements PathChooserDialog { return Foundation.invoke(id, Foundation.createSelector(selector), args); } - /** This class is intended to force extensions initialization on EDT thread (IDEA-107271) - */ + /** This class is intended to force extensions initialization on EDT thread (IDEA-107271) */ private static class ExtensionsInitializer { private ExtensionsInitializer() {} private static boolean initialized; - private static void initialize () { + private static void initialize() { if (initialized) return; UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override @@ -372,5 +388,4 @@ public class MacFileChooserDialogImpl implements PathChooserDialog { initialized = true; } } - } From be5639b56e8da6d5ea53ef718e8a71c115c567c8 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Tue, 17 Feb 2015 00:45:39 +0100 Subject: [PATCH 10/16] add doWhenDone() method --- .../intellij/openapi/actionSystem/RegistryToggleAction.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/RegistryToggleAction.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/RegistryToggleAction.java index 1813127a1af8..ca15381d7fa4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/RegistryToggleAction.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/RegistryToggleAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -54,5 +54,9 @@ public class RegistryToggleAction extends ToggleAction { @Override public void setSelected(AnActionEvent e, boolean state) { Registry.get(myKey).setValue(state); + doWhenDone(e); + } + + public void doWhenDone(AnActionEvent e) { } } From b75d22162cf68bb8bc7755cdd30c7ec9b1567c29 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Tue, 17 Feb 2015 00:46:35 +0100 Subject: [PATCH 11/16] update editor to add/remove inlined debug values when the option is set to on/off --- .../impl/actions/UseInlineDebuggerAction.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/UseInlineDebuggerAction.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/UseInlineDebuggerAction.java index 4ebac331ab4a..ab9bba4d37da 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/UseInlineDebuggerAction.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/UseInlineDebuggerAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 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. @@ -15,7 +15,11 @@ */ package com.intellij.xdebugger.impl.actions; +import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.RegistryToggleAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; /** * @author Konstantin Bulenkov @@ -24,4 +28,15 @@ public class UseInlineDebuggerAction extends RegistryToggleAction { public UseInlineDebuggerAction() { super("ide.debugger.inline"); } + + @Override + public void doWhenDone(AnActionEvent e) { + Project project = e.getProject(); + if (project != null) { + final Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); + if (editor != null) { + editor.getComponent().repaint(); + } + } + } } From 3f7f31501b5ffcab93cc320e2cfd6d9f7259cc71 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Tue, 17 Feb 2015 03:43:03 +0300 Subject: [PATCH 12/16] fix root type detection for directory --- .../com/intellij/ide/scratch/ScratchFileServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java index 0902fd9b6a10..6cfa536c4bf6 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/ScratchFileServiceImpl.java @@ -109,9 +109,9 @@ public class ScratchFileServiceImpl extends ScratchFileService implements Persis @Nullable @Override public RootType getRootType(@NotNull VirtualFile file) { - VirtualFile parent = file.getParent(); - if (!(parent instanceof VirtualFileWithId)) return null; - RootType result = myIndex.getInfoForFile(parent); + VirtualFile directory = file.isDirectory() ? file : file.getParent(); + if (!(directory instanceof VirtualFileWithId)) return null; + RootType result = myIndex.getInfoForFile(directory); return result == NULL_TYPE ? null : result; } From 7f43853129db9ab8ad06ecd0cb7198719ae78f2c Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 16 Feb 2015 18:03:37 +0300 Subject: [PATCH 13/16] resource java roots: added 'for generated resources' and 'relative output path' properties --- .../JavaResourceRootEditHandlerBase.java | 117 +++++++++++++++++- .../openapi/roots/JavaProjectRootsUtil.java | 4 +- .../jps/incremental/ResourcesTarget.java | 10 +- .../jps/builders/rebuild/ModuleRebuildTest.kt | 19 +++ .../java/JavaResourceRootProperties.java | 71 +++++++++++ .../jps/model/java/JavaResourceRootType.java | 11 +- .../model/java/JpsJavaExtensionService.java | 3 + .../impl/JpsJavaExtensionServiceImpl.java | 6 + .../java/JpsJavaModelSerializerExtension.java | 34 ++++- .../testData/resourceRoots/resourceRoots.iml | 15 +++ .../testData/resourceRoots/resourceRoots.ipr | 12 ++ .../JpsProjectSerializationTest.java | 26 +++- .../ContentEntryTreeCellRenderer.java | 14 ++- .../openapi/roots/impl/SourceFolderImpl.java | 33 +++-- 14 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootProperties.java create mode 100644 jps/model-serialization/testData/resourceRoots/resourceRoots.iml create mode 100644 jps/model-serialization/testData/resourceRoots/resourceRoots.ipr diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JavaResourceRootEditHandlerBase.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JavaResourceRootEditHandlerBase.java index ba7778da6397..ca72f1376e55 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JavaResourceRootEditHandlerBase.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/JavaResourceRootEditHandlerBase.java @@ -15,18 +15,30 @@ */ package com.intellij.openapi.roots.ui.configuration; +import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.CustomShortcutSet; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.SourceFolder; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.roots.IconActionComponent; +import com.intellij.util.ui.FormBuilder; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.model.JpsDummyElement; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; +import org.jetbrains.jps.model.java.JavaResourceRootProperties; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import javax.swing.*; +import java.awt.*; /** * @author nik */ -public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEditHandler { - protected JavaResourceRootEditHandlerBase(JpsModuleSourceRootType rootType) { +public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEditHandler { + public JavaResourceRootEditHandlerBase(JpsModuleSourceRootType rootType) { super(rootType); } @@ -41,4 +53,103 @@ public abstract class JavaResourceRootEditHandlerBase extends ModuleSourceRootEd public CustomShortcutSet getMarkRootShortcutSet() { return null; } + + @NotNull + @Override + public Icon getRootIcon(@NotNull JavaResourceRootProperties properties) { + return properties.isForGeneratedSources() ? getGeneratedRootIcon() : getRootIcon(); + } + + @NotNull + protected Icon getGeneratedRootIcon() { + return getRootIcon(); + } + + @Nullable + @Override + public String getPropertiesString(@NotNull JavaResourceRootProperties properties) { + StringBuilder buffer = new StringBuilder(); + if (properties.isForGeneratedSources()) { + buffer.append(" [generated]"); + } + String relativeOutputPath = properties.getRelativeOutputPath(); + if (!relativeOutputPath.isEmpty()) { + buffer.append(" (").append(relativeOutputPath).append(")"); + } + return buffer.length() > 0 ? buffer.toString() : null; + } + + @Nullable + @Override + public JComponent createPropertiesEditor(@NotNull final SourceFolder folder, + @NotNull final JComponent parentComponent, + @NotNull final ContentRootPanel.ActionCallback callback) { + final IconActionComponent iconComponent = new IconActionComponent(AllIcons.Modules.SetPackagePrefix, + AllIcons.Modules.SetPackagePrefixRollover, + ProjectBundle.message("module.paths.edit.properties.tooltip"), + new Runnable() { + @Override + public void run() { + JavaResourceRootProperties properties = folder.getJpsElement().getProperties( JavaModuleSourceRootTypes.RESOURCES); + assert properties != null; + ResourceRootPropertiesDialog + dialog = new ResourceRootPropertiesDialog(parentComponent, properties); + if (dialog.showAndGet()) { + callback.onSourceRootPropertiesChanged(folder); + } + } + }); + final JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + panel.add(iconComponent, BorderLayout.CENTER); + panel.add(Box.createHorizontalStrut(3), BorderLayout.EAST); + return panel; + } + + private static class ResourceRootPropertiesDialog extends DialogWrapper { + private final JTextField myRelativeOutputPathField; + private final JCheckBox myIsGeneratedCheckBox; + private final JPanel myMainPanel; + @NotNull private final JavaResourceRootProperties myProperties; + + private ResourceRootPropertiesDialog(@NotNull JComponent parentComponent, @NotNull JavaResourceRootProperties properties) { + super(parentComponent, true); + myProperties = properties; + setTitle(ProjectBundle.message("module.paths.edit.properties.title")); + myRelativeOutputPathField = new JTextField(); + myIsGeneratedCheckBox = new JCheckBox(UIUtil.replaceMnemonicAmpersand("For &generated resources")); + myMainPanel = FormBuilder.createFormBuilder() + .addLabeledComponent("Relative output &path:", myRelativeOutputPathField) + .addComponent(myIsGeneratedCheckBox) + .getPanel(); + myRelativeOutputPathField.setText(myProperties.getRelativeOutputPath()); + myRelativeOutputPathField.setColumns(25); + myIsGeneratedCheckBox.setSelected(myProperties.isForGeneratedSources()); + init(); + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myRelativeOutputPathField; + } + + @Override + protected void doOKAction() { + myProperties.setRelativeOutputPath(normalizePath(myRelativeOutputPathField.getText())); + myProperties.setForGeneratedSources(myIsGeneratedCheckBox.isSelected()); + super.doOKAction(); + } + + @NotNull + private static String normalizePath(String path) { + return StringUtil.trimEnd(StringUtil.trimStart(FileUtil.toSystemIndependentName(path.trim()), "/"), "/"); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myMainPanel; + } + } } diff --git a/java/java-analysis-impl/src/com/intellij/openapi/roots/JavaProjectRootsUtil.java b/java/java-analysis-impl/src/com/intellij/openapi/roots/JavaProjectRootsUtil.java index f7666eb21ffb..767bc5cc948d 100644 --- a/java/java-analysis-impl/src/com/intellij/openapi/roots/JavaProjectRootsUtil.java +++ b/java/java-analysis-impl/src/com/intellij/openapi/roots/JavaProjectRootsUtil.java @@ -12,6 +12,7 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; +import org.jetbrains.jps.model.java.JavaResourceRootProperties; import org.jetbrains.jps.model.java.JavaSourceRootProperties; import java.util.ArrayList; @@ -51,7 +52,8 @@ public class JavaProjectRootsUtil { private static boolean isForGeneratedSources(SourceFolder sourceFolder) { JavaSourceRootProperties properties = sourceFolder.getJpsElement().getProperties(JavaModuleSourceRootTypes.SOURCES); - return properties != null && properties.isForGeneratedSources(); + JavaResourceRootProperties resourceProperties = sourceFolder.getJpsElement().getProperties(JavaModuleSourceRootTypes.RESOURCES); + return properties != null && properties.isForGeneratedSources() || resourceProperties != null && resourceProperties.isForGeneratedSources(); } public static boolean isInGeneratedCode(@NotNull VirtualFile file, @NotNull Project project) { diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/ResourcesTarget.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/ResourcesTarget.java index 21c329149400..cb74bf7cfd78 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/ResourcesTarget.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/ResourcesTarget.java @@ -32,10 +32,7 @@ import org.jetbrains.jps.cmdline.ProjectDescriptor; import org.jetbrains.jps.indices.IgnoredFileIndex; import org.jetbrains.jps.indices.ModuleExcludeIndex; import org.jetbrains.jps.model.JpsModel; -import org.jetbrains.jps.model.java.JavaResourceRootType; -import org.jetbrains.jps.model.java.JavaSourceRootProperties; -import org.jetbrains.jps.model.java.JavaSourceRootType; -import org.jetbrains.jps.model.java.JpsJavaExtensionService; +import org.jetbrains.jps.model.java.*; import org.jetbrains.jps.model.module.JpsModule; import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot; @@ -101,10 +98,11 @@ public final class ResourcesTarget extends JVMModuleBuildTarget root : myModule.getSourceRoots(resourceType)) { if (!isExcludedFromCompilation(excludedRootProviders, root)) { File rootFile = root.getFile(); - roots.add(new ResourceRootDescriptor(rootFile, this, "", computeRootExcludes(rootFile, index))); + String relativeOutputPath = root.getProperties().getRelativeOutputPath(); + roots.add(new ResourceRootDescriptor(rootFile, this, relativeOutputPath.replace('/', '.'), computeRootExcludes(rootFile, index))); } } diff --git a/jps/jps-builders/testSrc/org/jetbrains/jps/builders/rebuild/ModuleRebuildTest.kt b/jps/jps-builders/testSrc/org/jetbrains/jps/builders/rebuild/ModuleRebuildTest.kt index 09e72b77cf2a..4c8e1cd13216 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/jps/builders/rebuild/ModuleRebuildTest.kt +++ b/jps/jps-builders/testSrc/org/jetbrains/jps/builders/rebuild/ModuleRebuildTest.kt @@ -14,6 +14,12 @@ * limitations under the License. */ package org.jetbrains.jps.builders.rebuild + +import org.jetbrains.jps.util.JpsPathUtil +import com.intellij.util.PathUtil +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JpsJavaExtensionService + /** * @author nik */ @@ -71,4 +77,17 @@ public class ModuleRebuildTest: JpsRebuildTestCase() { } }) } + + fun testResourceRootWithRelativeOutputPath() { + val m = addModule("m") + val res = PathUtil.getParentPath(createFile("res/a.txt", "42")) + val url = JpsPathUtil.pathToUrl(res) + m.addSourceRoot(url, JavaResourceRootType.RESOURCE, JpsJavaExtensionService.getInstance().createResourceRootProperties("foo", false)) + rebuild() + assertOutput(getAbsolutePath("out/production/m"), fs { + dir("foo") { + file("a.txt", "42") + } + }) + } } diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootProperties.java b/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootProperties.java new file mode 100644 index 000000000000..838e2d81a1ac --- /dev/null +++ b/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootProperties.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2015 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 org.jetbrains.jps.model.java; + +import com.intellij.openapi.util.Comparing; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.ex.JpsElementBase; + +/** + * @author nik + */ +public class JavaResourceRootProperties extends JpsElementBase { + private String myRelativeOutputPath = ""; + private boolean myForGeneratedSources; + + public JavaResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedSources) { + myRelativeOutputPath = relativeOutputPath; + myForGeneratedSources = forGeneratedSources; + } + + /** + * @return relative path to the target directory under the module output directory for resource files from this root + */ + @NotNull + public String getRelativeOutputPath() { + return myRelativeOutputPath; + } + + @NotNull + @Override + public JavaResourceRootProperties createCopy() { + return new JavaResourceRootProperties(myRelativeOutputPath, myForGeneratedSources); + } + + public boolean isForGeneratedSources() { + return myForGeneratedSources; + } + + public void setRelativeOutputPath(@NotNull String relativeOutputPath) { + if (!Comparing.equal(myRelativeOutputPath, relativeOutputPath)) { + myRelativeOutputPath = relativeOutputPath; + fireElementChanged(); + } + } + + public void setForGeneratedSources(boolean forGeneratedSources) { + if (myForGeneratedSources != forGeneratedSources) { + myForGeneratedSources = forGeneratedSources; + fireElementChanged(); + } + } + + @Override + public void applyChanges(@NotNull JavaResourceRootProperties modified) { + setRelativeOutputPath(modified.myRelativeOutputPath); + setForGeneratedSources(modified.myForGeneratedSources); + } +} diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootType.java b/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootType.java index 33adf96ebf1f..d3e122aa957d 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootType.java +++ b/jps/model-api/src/org/jetbrains/jps/model/java/JavaResourceRootType.java @@ -16,15 +16,14 @@ package org.jetbrains.jps.model.java; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jps.model.JpsDummyElement; -import org.jetbrains.jps.model.JpsElementFactory; -import org.jetbrains.jps.model.ex.JpsElementTypeWithDummyProperties; +import org.jetbrains.jps.model.ex.JpsElementTypeBase; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; /** * @author nik */ -public class JavaResourceRootType extends JpsElementTypeWithDummyProperties implements JpsModuleSourceRootType { +public class JavaResourceRootType extends JpsElementTypeBase implements + JpsModuleSourceRootType { public static final JavaResourceRootType RESOURCE = new JavaResourceRootType(); public static final JavaResourceRootType TEST_RESOURCE = new JavaResourceRootType(); @@ -33,7 +32,7 @@ public class JavaResourceRootType extends JpsElementTypeWithDummyProperties impl @NotNull @Override - public JpsDummyElement createDefaultProperties() { - return JpsElementFactory.getInstance().createDummyElement(); + public JavaResourceRootProperties createDefaultProperties() { + return JpsJavaExtensionService.getInstance().createResourceRootProperties("", false); } } diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/JpsJavaExtensionService.java b/jps/model-api/src/org/jetbrains/jps/model/java/JpsJavaExtensionService.java index bcd5eeff098b..5c5d42e4a6c2 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/java/JpsJavaExtensionService.java +++ b/jps/model-api/src/org/jetbrains/jps/model/java/JpsJavaExtensionService.java @@ -121,4 +121,7 @@ public abstract class JpsJavaExtensionService { @NotNull public abstract JavaSourceRootProperties createSourceRootProperties(@NotNull String packagePrefix); + + @NotNull + public abstract JavaResourceRootProperties createResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedResource); } diff --git a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JpsJavaExtensionServiceImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JpsJavaExtensionServiceImpl.java index 40179134c921..5ace6adc46b5 100644 --- a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JpsJavaExtensionServiceImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/JpsJavaExtensionServiceImpl.java @@ -188,6 +188,12 @@ public class JpsJavaExtensionServiceImpl extends JpsJavaExtensionService { return createSourceRootProperties(packagePrefix, false); } + @NotNull + @Override + public JavaResourceRootProperties createResourceRootProperties(@NotNull String relativeOutputPath, boolean forGeneratedResource) { + return new JavaResourceRootProperties(relativeOutputPath, forGeneratedResource); + } + @Override @NotNull public JpsProductionModuleOutputPackagingElement createProductionModuleOutput(@NotNull JpsModuleReference moduleReference) { diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java index 22188be46e2d..3cb6f8478239 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/JpsJavaModelSerializerExtension.java @@ -19,7 +19,9 @@ import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.text.StringUtil; import org.jdom.Element; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jps.model.*; +import org.jetbrains.jps.model.JpsElementFactory; +import org.jetbrains.jps.model.JpsProject; +import org.jetbrains.jps.model.JpsUrlList; import org.jetbrains.jps.model.java.*; import org.jetbrains.jps.model.library.JpsOrderRootType; import org.jetbrains.jps.model.module.JpsDependencyElement; @@ -32,7 +34,6 @@ import org.jetbrains.jps.model.serialization.artifact.JpsPackagingElementSeriali import org.jetbrains.jps.model.serialization.java.compiler.*; import org.jetbrains.jps.model.serialization.library.JpsLibraryRootTypeSerializer; import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer; -import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootDummyPropertiesSerializer; import org.jetbrains.jps.model.serialization.module.JpsModuleSourceRootPropertiesSerializer; import java.util.Arrays; @@ -56,6 +57,7 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension private static final String JAVADOC_PATHS_TAG = "javadoc-paths"; private static final String MODULE_LANGUAGE_LEVEL_ATTRIBUTE = "LANGUAGE_LEVEL"; public static final String ROOT_TAG = "root"; + private static final String RELATIVE_OUTPUT_PATH_ATTRIBUTE = "relativeOutputPath"; private static final String IS_GENERATED_ATTRIBUTE = "generated"; public static final JavaSourceRootPropertiesSerializer JAVA_SOURCE_ROOT_PROPERTIES_SERIALIZER = new JavaSourceRootPropertiesSerializer(JavaSourceRootType.SOURCE, JpsModuleRootModelSerializer.JAVA_SOURCE_ROOT_TYPE_ID); @@ -88,8 +90,8 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension public List> getModuleSourceRootPropertiesSerializers() { return Arrays.asList(JAVA_SOURCE_ROOT_PROPERTIES_SERIALIZER, new JavaSourceRootPropertiesSerializer(JavaSourceRootType.TEST_SOURCE, JpsModuleRootModelSerializer.JAVA_TEST_ROOT_TYPE_ID), - new JpsModuleSourceRootDummyPropertiesSerializer(JavaResourceRootType.RESOURCE, "java-resource"), - new JpsModuleSourceRootDummyPropertiesSerializer(JavaResourceRootType.TEST_RESOURCE, "java-test-resource")); + new JavaResourceRootPropertiesSerializer(JavaResourceRootType.RESOURCE, "java-resource"), + new JavaResourceRootPropertiesSerializer(JavaResourceRootType.TEST_RESOURCE, "java-test-resource")); } @Override @@ -328,4 +330,28 @@ public class JpsJavaModelSerializerExtension extends JpsModelSerializerExtension } } } + + private static class JavaResourceRootPropertiesSerializer extends JpsModuleSourceRootPropertiesSerializer { + private JavaResourceRootPropertiesSerializer(JpsModuleSourceRootType type, String typeId) { + super(type, typeId); + } + + @Override + public JavaResourceRootProperties loadProperties(@NotNull Element sourceRootTag) { + String relativeOutputPath = StringUtil.notNullize(sourceRootTag.getAttributeValue(RELATIVE_OUTPUT_PATH_ATTRIBUTE)); + boolean isGenerated = Boolean.parseBoolean(sourceRootTag.getAttributeValue(IS_GENERATED_ATTRIBUTE)); + return getService().createResourceRootProperties(relativeOutputPath, isGenerated); + } + + @Override + public void saveProperties(@NotNull JavaResourceRootProperties properties, @NotNull Element sourceRootTag) { + String relativeOutputPath = properties.getRelativeOutputPath(); + if (!relativeOutputPath.isEmpty()) { + sourceRootTag.setAttribute(RELATIVE_OUTPUT_PATH_ATTRIBUTE, relativeOutputPath); + } + if (properties.isForGeneratedSources()) { + sourceRootTag.setAttribute(IS_GENERATED_ATTRIBUTE, Boolean.TRUE.toString()); + } + } + } } diff --git a/jps/model-serialization/testData/resourceRoots/resourceRoots.iml b/jps/model-serialization/testData/resourceRoots/resourceRoots.iml new file mode 100644 index 000000000000..a13d44de4666 --- /dev/null +++ b/jps/model-serialization/testData/resourceRoots/resourceRoots.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/jps/model-serialization/testData/resourceRoots/resourceRoots.ipr b/jps/model-serialization/testData/resourceRoots/resourceRoots.ipr new file mode 100644 index 000000000000..81a4d8d2aa51 --- /dev/null +++ b/jps/model-serialization/testData/resourceRoots/resourceRoots.ipr @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java index 2567927c87e9..11fd9c14a968 100644 --- a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java +++ b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java @@ -151,11 +151,31 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase { assertEquals("UTF-8", configuration.getEncoding(new File(getAbsolutePath("other")))); } + public void testResourceRoots() { + String projectPath = "/jps/model-serialization/testData/resourceRoots/"; + loadProject(projectPath + "resourceRoots.ipr"); + JpsModule module = assertOneElement(myProject.getModules()); + List roots = module.getSourceRoots(); + assertSame(JavaSourceRootType.SOURCE, roots.get(0).getRootType()); + checkResourceRoot(roots.get(1), false, ""); + checkResourceRoot(roots.get(2), true, ""); + checkResourceRoot(roots.get(3), true, "foo"); + doTestSaveModule(module, projectPath + "resourceRoots.iml"); + } + + private static void checkResourceRoot(JpsModuleSourceRoot root, boolean forGenerated, String relativeOutput) { + assertSame(JavaResourceRootType.RESOURCE, root.getRootType()); + JavaResourceRootProperties properties = root.getProperties(JavaResourceRootType.RESOURCE); + assertNotNull(properties); + assertEquals(forGenerated, properties.isForGeneratedSources()); + assertEquals(relativeOutput, properties.getRelativeOutputPath()); + } + public void testSaveProject() { loadProject(SAMPLE_PROJECT_PATH); List modules = myProject.getModules(); - doTestSaveModule(modules.get(0), "main.iml"); - doTestSaveModule(modules.get(1), "util/util.iml"); + doTestSaveModule(modules.get(0), SAMPLE_PROJECT_PATH + "/main.iml"); + doTestSaveModule(modules.get(1), SAMPLE_PROJECT_PATH + "/util/util.iml"); //tod[nik] remember that test output root wasn't specified and doesn't save it to avoid unnecessary modifications of iml files //doTestSaveModule(modules.get(2), "xxx/xxx.iml"); @@ -188,7 +208,7 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase { try { Element actual = JDomSerializationUtil.createComponentElement("NewModuleRootManager"); JpsModuleRootModelSerializer.saveRootModel(module, actual); - File imlFile = getFileInSampleProject(moduleFilePath); + File imlFile = new File(getTestDataFileAbsolutePath(moduleFilePath)); Element rootElement = loadModuleRootTag(imlFile); Element expected = JDomSerializationUtil.findComponent(rootElement, "NewModuleRootManager"); PlatformTestUtil.assertElementsEqual(expected, actual); diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeCellRenderer.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeCellRenderer.java index bdbc44babd83..a349558422a0 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeCellRenderer.java @@ -28,6 +28,10 @@ import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; +import org.jetbrains.jps.model.java.JavaResourceRootProperties; +import org.jetbrains.jps.model.java.JavaSourceRootProperties; +import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import javax.swing.*; @@ -58,7 +62,7 @@ public class ContentEntryTreeCellRenderer extends NodeRenderer { if (file != null && file.isDirectory()) { final ContentEntry contentEntry = editor.getContentEntry(); if (contentEntry != null) { - final String prefix = getPrefix(contentEntry, file); + final String prefix = getPresentablePrefix(contentEntry, file); if (!prefix.isEmpty()) { append(" (" + prefix + ")", new SimpleTextAttributes(Font.PLAIN, JBColor.GRAY)); } @@ -70,10 +74,14 @@ public class ContentEntryTreeCellRenderer extends NodeRenderer { } } - private static String getPrefix(final ContentEntry entry, final VirtualFile file) { + private static String getPresentablePrefix(final ContentEntry entry, final VirtualFile file) { for (final SourceFolder sourceFolder : entry.getSourceFolders()) { if (file.equals(sourceFolder.getFile())) { - return sourceFolder.getPackagePrefix(); + JpsModuleSourceRoot element = sourceFolder.getJpsElement(); + JavaSourceRootProperties properties = element.getProperties(JavaModuleSourceRootTypes.SOURCES); + if (properties != null) return properties.getPackagePrefix(); + JavaResourceRootProperties resourceRootProperties = element.getProperties(JavaModuleSourceRootTypes.RESOURCES); + if (resourceRootProperties != null) return resourceRootProperties.getRelativeOutputPath(); } } return ""; diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java index 4f153d396405..3c217205509b 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/SourceFolderImpl.java @@ -27,10 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.JpsElement; import org.jetbrains.jps.model.JpsElementFactory; -import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; -import org.jetbrains.jps.model.java.JavaResourceRootType; -import org.jetbrains.jps.model.java.JavaSourceRootProperties; -import org.jetbrains.jps.model.java.JavaSourceRootType; +import org.jetbrains.jps.model.java.*; import org.jetbrains.jps.model.module.JpsModuleSourceRoot; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import org.jetbrains.jps.model.module.JpsTypedModuleSourceRoot; @@ -78,7 +75,14 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol @Override public String getPackagePrefix() { JavaSourceRootProperties properties = getJavaProperties(); - return properties != null ? properties.getPackagePrefix() : DEFAULT_PACKAGE_PREFIX; + if (properties != null) { + return properties.getPackagePrefix(); + } + JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties(); + if (resourceJavaProperties != null) { + return resourceJavaProperties.getRelativeOutputPath().replace('/', '.'); + } + return DEFAULT_PACKAGE_PREFIX; } @Nullable @@ -86,6 +90,11 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol return myJpsElement.getProperties(JavaModuleSourceRootTypes.SOURCES); } + @Nullable + private JavaResourceRootProperties getResourceJavaProperties() { + return myJpsElement.getProperties(JavaModuleSourceRootTypes.RESOURCES); + } + @Override public void setPackagePrefix(@NotNull String packagePrefix) { JavaSourceRootProperties properties = getJavaProperties(); @@ -112,6 +121,12 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol return myJpsElement; } + private boolean isForGeneratedSources() { + JavaSourceRootProperties properties = getJavaProperties(); + JavaResourceRootProperties resourceJavaProperties = getResourceJavaProperties(); + return properties != null && properties.isForGeneratedSources() || resourceJavaProperties != null && resourceJavaProperties.isForGeneratedSources(); + } + @Override public int compareTo(ContentFolderBaseImpl folder) { if (!(folder instanceof SourceFolderImpl)) return -1; @@ -124,12 +139,8 @@ public class SourceFolderImpl extends ContentFolderBaseImpl implements SourceFol if (i!= 0) return i; i = Boolean.valueOf(isTestSource()).compareTo(sourceFolder.isTestSource()); if (i != 0) return i; - JavaSourceRootProperties properties1 = getJavaProperties(); - JavaSourceRootProperties properties2 = sourceFolder.getJavaProperties(); - if (properties1 != null && properties2 != null) { - i = Boolean.valueOf(properties1.isForGeneratedSources()).compareTo(properties2.isForGeneratedSources()); - if (i != 0) return i; - } + i = Boolean.valueOf(isForGeneratedSources()).compareTo(sourceFolder.isForGeneratedSources()); + if (i != 0) return i; //todo[nik] perhaps we should use LinkedSet instead of SortedSet and get rid of this method return myJpsElement.getRootType().getClass().getName().compareTo(sourceFolder.getRootType().getClass().getName()); } From 056e63c5d0ba99d5c7b79d07038d05ea331d023a Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 16 Feb 2015 18:58:21 +0300 Subject: [PATCH 14/16] virtual file pointers: do not create several roots starting from slash --- .../openapi/vfs/impl/FilePointerPartNode.java | 2 +- .../openapi/vfs/impl/VirtualFilePointerTest.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java index 81456ec954b5..766134e43dda 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java @@ -180,7 +180,7 @@ class FilePointerPartNode { for (FilePointerPartNode child : children) { // find the right child (its part should start with ours) int i = child.indexOfFirstDifferentChar(path, index); - if (i != index && (i > index+1 || path.charAt(index) != '/')) { + if (i != index && (i > index+1 || path.charAt(index) != '/' || index == 0)) { FilePointerPartNode node = child.findPointerOrCreate(path, index, fileAndUrl); if (node.leaf == null) pointersUnder++; // the new node's been created return node; diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java index a25d96e1c894..98da1ad13120 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java @@ -163,6 +163,17 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertSameElements(myVirtualFilePointerManager.getPointersUnder(b, "p2"), p2); } + public void testUrlsHavingOnlyStartingSlashInCommonAndInvalidUrlBetweenThem() throws Exception { + VirtualFilePointer p1 = myVirtualFilePointerManager.create("file:///a/p1", disposable, null); + myVirtualFilePointerManager.create("file://invalid/path", disposable, null); + VirtualFilePointer p2 = myVirtualFilePointerManager.create("file:///b/p2", disposable, null); + final LightVirtualFile root = new LightVirtualFile("/"); + LightVirtualFile a = createLightFile(root, "a"); + LightVirtualFile b = createLightFile(root, "b"); + assertSameElements(myVirtualFilePointerManager.getPointersUnder(a, "p1"), p1); + assertSameElements(myVirtualFilePointerManager.getPointersUnder(b, "p2"), p2); + } + @NotNull private static LightVirtualFile createLightFile(final LightVirtualFile parent, final String name) { return new LightVirtualFile(name) { From 985281fd00aa490df937ed1b93ec860d878574be Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Feb 2015 09:54:13 +0300 Subject: [PATCH 15/16] enabled leak detection for Netty in tests --- build/scripts/utils.gant | 1 + 1 file changed, 1 insertion(+) diff --git a/build/scripts/utils.gant b/build/scripts/utils.gant index 43e3dc825cd1..2e68258aad9e 100644 --- a/build/scripts/utils.gant +++ b/build/scripts/utils.gant @@ -248,6 +248,7 @@ binding.setVariable("commonJvmArgsForTests", { if (debugPort != null) jdwp += ",address=$debugPort" return [ "-ea", + "-Dio.netty.leakDetectionLevel=PARANOID", "-server", "-Xbootclasspath/p:${projectBuilder.moduleOutput(findModule("boot"))}", "-XX:+HeapDumpOnOutOfMemoryError", From d7b3b8bc500ca44984491e4dd26bdc706a5a2e2e Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Mon, 16 Feb 2015 21:43:42 +0300 Subject: [PATCH 16/16] EA-64837 diff: convert line separators --- .../src/com/intellij/diff/DiffContentFactory.java | 8 +++++++- .../com/intellij/diff/DiffContentFactoryImpl.java | 13 ++++++++++--- .../com/intellij/diff/DiffRequestFactoryImpl.java | 2 +- .../diff/contents/DocumentContentWrapper.java | 5 ++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java b/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java index c1725fdab36a..cb110ff1190d 100644 --- a/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java +++ b/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java @@ -40,6 +40,9 @@ public abstract class DiffContentFactory { @NotNull public abstract EmptyContent createEmpty(); + @NotNull + public abstract DocumentContent create(@NotNull String text); + @NotNull public abstract DocumentContent create(@NotNull String text, @Nullable FileType type); @@ -58,8 +61,11 @@ public abstract class DiffContentFactory { @NotNull public abstract DiffContent createClipboardContent(); + /** + * @param referenceContent used to detect FileType and proper highlighting for clipboard content + */ @NotNull - public abstract DocumentContent createClipboardContent(@NotNull DocumentContent mainContent); + public abstract DocumentContent createClipboardContent(@NotNull DocumentContent referenceContent); @NotNull public abstract DiffContent createBinary(@Nullable Project project, diff --git a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java index 8c526b016ae8..b302e5b52c14 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java @@ -20,7 +20,6 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.impl.DocumentImpl; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.ide.CopyPasteManager; @@ -47,6 +46,12 @@ public class DiffContentFactoryImpl extends DiffContentFactory { return new EmptyContent(); } + @Override + @NotNull + public DocumentContent create(@NotNull String text) { + return create(text, null); + } + @Override @NotNull public DocumentContent create(@NotNull String text, @Nullable FileType type) { @@ -97,14 +102,16 @@ public class DiffContentFactoryImpl extends DiffContentFactory { @NotNull public DiffContent createClipboardContent() { String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor); - return new DocumentContentImpl(new DocumentImpl(StringUtil.notNullize(text))); + Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(StringUtil.notNullize(text))); + return new DocumentContentImpl(document); // TODO: show difference in line separators ? } @Override @NotNull public DocumentContent createClipboardContent(@NotNull DocumentContent mainContent) { String text = CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor); - return new DocumentContentWrapper(mainContent, StringUtil.notNullize(text)); + Document document = EditorFactory.getInstance().createDocument(StringUtil.convertLineSeparators(StringUtil.notNullize(text))); + return new DocumentContentWrapper(document, mainContent); } @Override diff --git a/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java b/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java index d89180384cbf..ea172e5791ea 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java @@ -47,7 +47,7 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory { @NotNull public ContentDiffRequest createClipboardVsValue(@NotNull String value) { DiffContent content1 = myContentFactory.createClipboardContent(); - DiffContent content2 = myContentFactory.create(value, null); + DiffContent content2 = myContentFactory.create(value); String title1 = DiffBundle.message("diff.content.clipboard.content.title"); String title2 = DiffBundle.message("diff.content.selected.value"); diff --git a/platform/diff-impl/src/com/intellij/diff/contents/DocumentContentWrapper.java b/platform/diff-impl/src/com/intellij/diff/contents/DocumentContentWrapper.java index 82f87e06ee40..fd65cf30af44 100644 --- a/platform/diff-impl/src/com/intellij/diff/contents/DocumentContentWrapper.java +++ b/platform/diff-impl/src/com/intellij/diff/contents/DocumentContentWrapper.java @@ -16,7 +16,6 @@ package com.intellij.diff.contents; import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.vfs.VirtualFile; @@ -30,9 +29,9 @@ public class DocumentContentWrapper implements DocumentContent { @NotNull private final DocumentContent myBase; @NotNull private final Document myDocument; - public DocumentContentWrapper(@NotNull DocumentContent base, @NotNull String text) { + public DocumentContentWrapper(@NotNull Document document, @NotNull DocumentContent base) { + myDocument = document; myBase = base; - myDocument = EditorFactory.getInstance().createDocument(text); } @NotNull