PY-11855 Run manage.py task improvements
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Wed, 4 Feb 2015 15:51:55 +0000 (18:51 +0300)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Wed, 4 Feb 2015 15:51:55 +0000 (18:51 +0300)
Arguments values, validation added

18 files changed:
python/src/com/jetbrains/python/PyBundle.properties
python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java
python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java
python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java
python/src/com/jetbrains/python/optParse/WordWithPosition.java

index 574bb835de66daf791d04f040f7b9341cdf80ecf..03014b095ff9eac97ad7843a836f30d1805d4629 100644 (file)
@@ -850,4 +850,9 @@ remote.interpreter.configure.path.label=Python interpreter path:
 remote.interpreter.configure.temp.files.path.label=PyCharm helpers path:
 
 # Message we display for inspection if user uses custom class type members that do not exist
-custom.type.mimic.name=Dynamic class based on {0}
\ No newline at end of file
+custom.type.mimic.name=Dynamic class based on {0}
+
+# Values for command argument value validation
+commandsWithArgs.validation.badValue=Argument can't have this value
+commandsWithArgs.validation.excess=Excess argument
+commandsWithArgs.enterCommand.label=Enter command here
\ No newline at end of file
index e0780a72caf9459d4bb902e21ba006684ef91cdc..6990709a8bcd02b26b416105c9e519e6bf9f2582 100644 (file)
  */
 package com.jetbrains.python.commandInterface;
 
-import com.jetbrains.python.suggestionList.SuggestionsBuilder;
 import com.jetbrains.python.optParse.WordWithPosition;
+import com.jetbrains.python.suggestionList.SuggestionsBuilder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
+import java.util.List;
 
 /**
  * View for command-line interface to be paired with view.
@@ -44,11 +45,12 @@ public interface CommandInterfaceView {
   void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
 
   /**
-   * Displays error (like red line)
+   * Emphasize errors (like red line and special message).
    *
-   * @param lastOnly underline only last letter
+   * @param errors            list of errors (coordinates and error message. Message may be empty not to display any text)
+   * @param specialErrorPlace if you want to underline special place, you may provide it here
    */
-  void showError(boolean lastOnly);
+  void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable SpecialErrorPlace specialErrorPlace);
 
   /**
    * Change text to the one provided
@@ -98,4 +100,19 @@ public interface CommandInterfaceView {
    *                 from 1 to 3, so you add 'foo',1,4 here)
    */
   void setBalloons(@NotNull final Collection<WordWithPosition> balloons);
+
+
+  /**
+   * Special place that may be underlined
+   */
+  enum SpecialErrorPlace {
+    /**
+     * Whole text (from start to end)
+     */
+    WHOLE_TEXT,
+    /**
+     * Only after last character
+     */
+    AFTER_LAST_CHAR
+  }
 }
index 8ad1ed76218e20082b023d247028a1d08d1c3f31..5826a45a4dd40da2dfcf1e3fb43b48dd4092038f 100644 (file)
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+// TODO: Support regex validation as well
 
 /**
- * Command argument
+ * Command <strong>positional, not named</strong> argument (not option!).
+ * This class represents command argument, not its value.
+ *
  *
  * @author Ilya.Kazakevich
  */
-public class Argument {
-  private final boolean myNamed;
+public final class Argument {
+  /**
+   * Argument help user-readable text
+   */
   @NotNull
-  private final String myName;
+  private final String myHelpText;
+  /**
+   * List of values argument may have. Null if any value is possible.
+   */
+  @Nullable
+  private final List<String> myAvailableValues;
+
 
   /**
-   * @param named is named argument or not
-   * @param name  name of argument
+   * @param helpText Argument help user-readable text
    */
-  public Argument(final boolean named, @NotNull final String name) {
-    myNamed = named;
-    myName = name;
+  public Argument(@NotNull final String helpText) {
+    this(helpText, null);
   }
 
   /**
-   * @return is named argument or not
+   * @param helpText        Argument help user-readable text
+   * @param availableValues List of values argument may have. Null if any value is possible.
    */
-  public boolean isNamed() {
-    return myNamed;
+  public Argument(@NotNull final String helpText, @Nullable final List<String> availableValues) {
+    myHelpText = helpText;
+    myAvailableValues = (availableValues == null ? null : new ArrayList<String>(availableValues));
   }
 
   /**
-   * @return name of argument
+   * @return Argument help user-readable text
    */
   @NotNull
-  public String getName() {
-    return myName;
+  public String getHelpText() {
+    return myHelpText;
+  }
+
+  /**
+   * @return List of values argument may have. Null if any value is possible.
+   */
+  @Nullable
+  public List<String> getAvailableValues() {
+    return (myAvailableValues == null ? null : Collections.unmodifiableList(myAvailableValues));
   }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java
new file mode 100644 (file)
index 0000000..594b7ac
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.commandsWithArgs;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * Information about command {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} and their value
+ * validation.
+ * Check optparse manual, package info and {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}
+ * manual for more info about arguments.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface ArgumentsInfo {
+  /**
+   * Returns argument by its position.
+   *
+   * @param argumentPosition argument position
+   * @return null if no argument value is available at this position. Returns argument otherwise.
+   */
+  @Nullable
+  Argument getArgument(int argumentPosition);
+
+
+  /**
+   * Validates argument <strong>values</strong>.
+   * Values should be provided as list. I.e. for <pre>my_command foo bar</pre> there should be list of "foo, bar".
+   *
+   * @param argumentValuesToCheck values to check
+   * @return validation result
+   */
+  @NotNull
+  ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck);
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java
new file mode 100644 (file)
index 0000000..2390747
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.commandsWithArgs;
+
+import com.intellij.util.containers.hash.HashMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Information about {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} values validation
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class ArgumentsValuesValidationInfo {
+  /**
+   * Validation with out of any error
+   */
+  @NotNull
+  static final ArgumentsValuesValidationInfo
+    NO_ERROR = new ArgumentsValuesValidationInfo(Collections.<Integer, ArgumentValueError>emptyMap(), false);
+
+  private final Map<Integer, ArgumentValueError> myPositionOfErrorArguments = new HashMap<Integer, ArgumentValueError>();
+  private final boolean myNotEnoughArguments;
+
+  /**
+   * @param positionOfErrorArguments map of [argument_position, its_value_error]
+   * @param notEnoughArguments       true if not enough arguments values provided (i.e. some required arg missed)
+   */
+  ArgumentsValuesValidationInfo(@NotNull final Map<Integer, ArgumentValueError> positionOfErrorArguments,
+                                final boolean notEnoughArguments) {
+    myPositionOfErrorArguments.putAll(positionOfErrorArguments);
+    myNotEnoughArguments = notEnoughArguments;
+  }
+
+  /**
+   * @return map of [argument_position, its_value_error]
+   */
+  @NotNull
+  Map<Integer, ArgumentValueError> getPositionOfErrorArguments() {
+    return Collections.unmodifiableMap(myPositionOfErrorArguments);
+  }
+
+  /**
+   * @return if not enough argument values provided (i.e. some required arg missed)
+   */
+  boolean isNotEnoughArguments() {
+    return myNotEnoughArguments;
+  }
+
+  /**
+   * Type of argument value error.
+   */
+  enum ArgumentValueError {
+    /**
+     * This argument is redundant
+     */
+    EXCESS,
+    /**
+     * Argument has bad value
+     */
+    BAD_VALUE
+  }
+}
index b639b992db888ebb2189543284ffafc8b18b8f7f..fb139df02ca2cd9cf9c8417d82cdbab193fc3755 100644 (file)
@@ -18,8 +18,6 @@ package com.jetbrains.python.commandInterface.commandsWithArgs;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
 /**
  * Command with arguments
  *
@@ -34,12 +32,6 @@ public interface Command {
   @NotNull
   String getName();
 
-  /**
-   * @return command arguments
-   */
-  @NotNull
-  List<Argument> getArguments();
-
   /**
    * @return Command readable help text
    */
@@ -48,8 +40,8 @@ public interface Command {
 
 
   /**
-   * @return Argument help string to display on arguments
+   * @return Information about command positional, unnamed {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} (not options!)
    */
-  @Nullable
-  String getArgumentHelp();
+  @NotNull
+  ArgumentsInfo getArgumentsInfo();
 }
index b1509e012974fa3d33c4fcbbb3c80b30858888e5..5acdd93e49d6631764086ad8532e49e1ecab55d0 100644 (file)
@@ -61,14 +61,6 @@ public class CommandAdapter implements Command {
     return myName;
   }
 
-  /**
-   * @return command arguments
-   */
-  @Override
-  @NotNull
-  public final List<Argument> getArguments() {
-    return Collections.unmodifiableList(myArguments);
-  }
 
   @Override
   @Nullable
@@ -76,9 +68,10 @@ public class CommandAdapter implements Command {
     return myHelp;
   }
 
-  @Nullable
+
+  @NotNull
   @Override
-  public String getArgumentHelp() {
-    return null;
+  public final ArgumentsInfo getArgumentsInfo() {
+    return NoArgumentsInfo.INSTANCE;
   }
 }
index 19c5291f109a1accd8173ce23ca1e75580d0be66..03981b5585b050a7f78be8ef337c6d8534b95ece 100644 (file)
  */
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
+import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.ArrayUtil;
 import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
 import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
 import com.jetbrains.python.optParse.MalformedCommandLineException;
 import com.jetbrains.python.optParse.ParsedCommandLine;
+import com.jetbrains.python.optParse.WordWithPosition;
 import com.jetbrains.python.suggestionList.SuggestionsBuilder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -71,7 +74,7 @@ public class CommandInterfacePresenterCommandBased<C extends Command> extends Co
 
   @Override
   public void launch() {
-    myView.setPreferredWidthInChars(getMaximumCommandWithArgsLength());
+    /*myView.setPreferredWidthInChars(getMaximumCommandWithArgsLength());*/
     super.launch();
     myStrategy = new NoCommandStrategy(this);
   }
@@ -81,16 +84,8 @@ public class CommandInterfacePresenterCommandBased<C extends Command> extends Co
     myLastSuggestionTiedToWord = false;
     configureStrategy();
     myView.setSubText(myStrategy.getSubText());
-    switch (myStrategy.getShowErrorInfo()) {
-      case NO:
-        break;
-      case FULL:
-        myView.showError(false);
-        break;
-      case RELATIVE:
-        myView.showError(true);
-        break;
-    }
+    final Pair<SpecialErrorPlace, List<WordWithPosition>> errorInfo = myStrategy.getErrorInfo();
+    myView.showErrors(errorInfo.getSecond(), errorInfo.first);
     myView.setBalloons(myStrategy.getBalloonsToShow());
 
     final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
@@ -200,7 +195,8 @@ public class CommandInterfacePresenterCommandBased<C extends Command> extends Co
     final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
     final List<String> suggestions = suggestionInfo.getSuggestions();
     if (!suggestions.isEmpty()) {
-      final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
+      // TODO: Uncomment when history would be fixed
+      final SuggestionsBuilder suggestionsBuilder = new SuggestionsBuilder();/*getBuilderWithHistory();*/
       suggestionsBuilder.add(suggestions);
       myView.displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
     }
@@ -254,20 +250,4 @@ public class CommandInterfacePresenterCommandBased<C extends Command> extends Co
     return myView;
   }
 
-  /**
-   * @return maximum length command window may need
-   */
-  private int getMaximumCommandWithArgsLength() {
-    int maxLength = 0;
-    for (final Command command : myCommands.values()) {
-      int commandLength = command.getName().length() + 1;
-      for (final Argument argument : command.getArguments()) {
-        commandLength += argument.getName().length() + 1;
-      }
-      if (commandLength > maxLength) {
-        maxLength = commandLength;
-      }
-    }
-    return maxLength;
-  }
 }
index f0a98d1f8fd3ab68d8e396fa9008f9e724f29443..1d3be479937a176ffe810daa4509a833e54dafe9 100644 (file)
  */
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
-import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.util.Pair;
 import com.intellij.util.ArrayUtil;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
+import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
 import com.jetbrains.python.optParse.ParsedCommandLine;
 import com.jetbrains.python.optParse.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
@@ -25,6 +29,8 @@ import org.jetbrains.annotations.Nullable;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Strategy implementation for case when user entered command
@@ -65,31 +71,41 @@ final class InCommandStrategy extends Strategy {
   @NotNull
   @Override
   SuggestionInfo getSuggestionInfo() {
-    final List<String> strings = new ArrayList<String>();
-    for (final Argument argument : myCommand.getArguments()) {
-      if (!myArguments.contains(argument.getName())) {
-        strings.add(argument.getName());
+    final Argument nextArgument = myCommand.getArgumentsInfo().getArgument(myCommandLine.getArguments().size());
+    if (nextArgument != null) {// TODO: Check options!
+      // If next arg exists
+      final List<String> availableValues = nextArgument.getAvailableValues();
+      if (availableValues != null) { // If has available values
+        return new SuggestionInfo(false, false, availableValues);
       }
     }
-    return new SuggestionInfo(false, false, strings);
+    return new SuggestionInfo(false, false, Collections.<String>emptyList());
   }
 
   @NotNull
   @Override
   List<WordWithPosition> getBalloonsToShow() {
     // Display argument balloons right from command end to last argument end
-    final String argumentHelp = myCommand.getArgumentHelp();
-    if (StringUtil.isEmpty(argumentHelp)) {
-      return Collections.emptyList();
-    }
+    final ArgumentsInfo argumentsInfo = myCommand.getArgumentsInfo();
     final List<WordWithPosition> arguments = myCommandLine.getArguments();
     if (arguments.isEmpty()) {
-      // If no arguments provided, then display popup right after command
-      return Collections.singletonList(new WordWithPosition(argumentHelp, myCommandLine.getCommand().getTo() + 1, Integer.MAX_VALUE));
+      // If no arguments provided, then display first argument popup right after command
+      final Argument firstArgument = argumentsInfo.getArgument(0);
+      if (firstArgument == null) {
+        return Collections.emptyList(); // Looks like no argument required
+      }
+      return Collections
+        .singletonList(new WordWithPosition(firstArgument.getHelpText(), myCommandLine.getCommand().getTo() + 1));
     }
+
     final List<WordWithPosition> result = new ArrayList<WordWithPosition>(arguments.size());
-    for (final WordWithPosition argument : arguments) {
-      result.add(argument.copyWithDifferentText(argumentHelp));
+    for (int i = 0; i < arguments.size(); i++) {
+      final WordWithPosition argEnteredByUser = arguments.get(i);
+      final Argument argument = argumentsInfo.getArgument(i);
+      if (argument != null) {
+        // Display argument help
+        result.add(argEnteredByUser.copyWithDifferentText(argument.getHelpText()));
+      }
     }
     return result;
   }
@@ -111,8 +127,24 @@ final class InCommandStrategy extends Strategy {
 
   @NotNull
   @Override
-  ErrorInfo getShowErrorInfo() {
-    final boolean noArgsLeft = getSuggestionInfo().getSuggestions().isEmpty();
-    return (noArgsLeft ? ErrorInfo.NO : ErrorInfo.RELATIVE);
+  Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
+    final List<WordWithPosition> userProvidedValues = myCommandLine.getArguments();
+    SpecialErrorPlace specialError = null;
+    final List<WordWithPosition> errors = new ArrayList<WordWithPosition>();
+
+    final ArgumentsValuesValidationInfo validation =
+      myCommand.getArgumentsInfo().validateArgumentValues(WordWithPosition.fetchText(userProvidedValues));
+    if (validation.isNotEnoughArguments()) {
+      specialError = SpecialErrorPlace.AFTER_LAST_CHAR;
+    }
+    for (final Entry<Integer, ArgumentValueError> errorEntry : validation.getPositionOfErrorArguments().entrySet()) {
+      final String errorText = (errorEntry.getValue() == ArgumentValueError.BAD_VALUE ?
+                                PyBundle.message("commandsWithArgs.validation.badValue") :
+                                PyBundle.message("commandsWithArgs.validation.excess") );
+      errors.add(userProvidedValues.get(errorEntry.getKey()).copyWithDifferentText(errorText));
+    }
+
+
+    return Pair.create(specialError, errors);
   }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java
new file mode 100644 (file)
index 0000000..2e8762d
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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.commandsWithArgs;
+
+import com.google.common.base.Preconditions;
+import com.intellij.util.containers.hash.HashMap;
+import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
+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.Map;
+
+/**
+ * In some special cases we have insight about command arguments.
+ * In this case we use this class to improve arguments and validation support.
+ * Command may have several arguments, while some are optional and some may have many values.
+ * Arguments are positional (not named!) by optparse design, so only last argument may be optional but it also may be repeated several times.
+ * <p>
+ * Examples:
+ * <pre>
+ *  my_command required_arg1_value required_arg2_value [optinal_arg3_value] [optinal_arg4_value]
+ * </pre>
+ * Or even like this:
+ * <pre>
+ *  my_command required_arg1_value [optional_another_arg1_value_1] [optional_another_arg1_value_2] ... [optional_another_arg1_value_N]
+ * </pre>.
+ * </p>
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class KnownArgumentsInfo implements ArgumentsInfo {
+  /**
+   * List of real arguments.
+   */
+  @NotNull
+  private final List<Argument> myArguments = new ArrayList<Argument>();
+  /**
+   * Minimum number of arguments this command requires (actually, number of required arguments)
+   */
+  private final int myMinArguments;
+  /**
+   * Maximum number of arguments this command accepts
+   * (number of required arguments + num of optional arguments or {@link java.lang.Integer#MAX_VALUE} if last argument may have infinite
+   * number of values)
+   */
+  private final int myMaxArguments;
+
+  /**
+   * For commands with infinite number of values last argument accepts (my_command VAL1 VAL2 .. VALN)
+   *
+   * @param arguments    list of known arguments (last one would be used to accept all residual values)
+   * @param minArguments number of required arguments
+   */
+  public KnownArgumentsInfo(@NotNull final Collection<Argument> arguments,
+                            final int minArguments) {
+    this(arguments, minArguments, Integer.MAX_VALUE);
+  }
+
+  /**
+   * For commands with finite number of values last argument accepts.
+   *
+   * @param arguments    list of known arguments (last one would be used to accept all residual values, but when {@link #myMaxArguments} reached,
+   *                     null will be returned)
+   * @param minArguments number of required arguments
+   * @param maxArguments maximum number of argument values this command accepts
+   */
+  public KnownArgumentsInfo(@NotNull final Collection<Argument> arguments,
+                            final int minArguments,
+                            final int maxArguments) {
+    Preconditions.checkArgument(!arguments.isEmpty(), "At least one argument should be provided");
+    myArguments.addAll(arguments);
+    myMinArguments = minArguments;
+    myMaxArguments = maxArguments;
+  }
+
+  @Nullable
+  @Override
+  public Argument getArgument(final int argumentPosition) {
+    if (myArguments.size() > argumentPosition) {
+      return myArguments.get(argumentPosition);
+    }
+
+    // We may need last one
+    if (argumentPosition <= myMaxArguments) {
+      return myArguments.get(myArguments.size() - 1);
+    }
+    return null;
+  }
+
+  @NotNull
+  @Override
+  public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
+    final Map<Integer, ArgumentValueError> errors = new HashMap<Integer, ArgumentValueError>();
+
+    for (int i = 0; i < argumentValuesToCheck.size(); i++) {
+      final String userValue = argumentValuesToCheck.get(i);
+      final Argument argument = getArgument(i);
+      if (argument == null) {
+        errors.put(i, ArgumentValueError.EXCESS);
+        continue;
+      }
+      final List<String> availableValues = argument.getAvailableValues();
+      if (availableValues != null && !availableValues.contains(userValue)) {
+        errors.put(i, ArgumentValueError.BAD_VALUE);
+      }
+    }
+
+    return new ArgumentsValuesValidationInfo(errors, argumentValuesToCheck.size() < myMinArguments);
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java
new file mode 100644 (file)
index 0000000..d08e8cc
--- /dev/null
@@ -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.commandsWithArgs;
+
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Range;
+import com.intellij.util.containers.hash.HashMap;
+import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Case when command has no arguments (for sure!)
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class NoArgumentsInfo implements ArgumentsInfo {
+  /**
+   * Instance to use when command has no arguments
+   */
+  public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo();
+
+  private NoArgumentsInfo() {
+  }
+
+  @Nullable
+  @Override
+  public Argument getArgument(final int argumentPosition) {
+    return null;
+  }
+
+  @NotNull
+  @Override
+  public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
+    if (argumentValuesToCheck.isEmpty()) {
+      return ArgumentsValuesValidationInfo.NO_ERROR;
+    }
+    final Map<Integer, ArgumentValueError> errors =
+      new HashMap<Integer, ArgumentsValuesValidationInfo.ArgumentValueError>();
+    for (final int errorPosition : ContiguousSet.create(Range.closedOpen(0, argumentValuesToCheck.size()), DiscreteDomain.integers())) {
+      errors.put(errorPosition, ArgumentValueError.EXCESS);
+    }
+    return new ArgumentsValuesValidationInfo(errors, false);
+  }
+}
index 7b4f3f7b07f8ec1441b4a27b64c381e4ffb91018..16ea46fe9fc6ef8796e4e95c5b8c5e748cc46dad 100644 (file)
  */
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
+import com.jetbrains.python.optParse.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Strategy implementation for case when no command parsed
  *
  * @author Ilya.Kazakevich
  */
-class NoCommandStrategy extends Strategy {
-  NoCommandStrategy(@NotNull final CommandInterfacePresenterCommandBased presenter) {
+final class NoCommandStrategy extends Strategy {
+
+  private static final Pair<SpecialErrorPlace, List<WordWithPosition>>
+    NO_ERROR = Pair.create(null, Collections.<WordWithPosition>emptyList());
+
+  NoCommandStrategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
     super(presenter);
   }
 
   @NotNull
   @Override
   String getSubText() {
-    return "Enter command here"; // TODO: Use u18n
+    return PyBundle.message("commandsWithArgs.enterCommand.label");
   }
 
   @NotNull
@@ -47,13 +57,15 @@ class NoCommandStrategy extends Strategy {
     return !myPresenter.getView().getText().isEmpty();
   }
 
+
   @NotNull
   @Override
-  ErrorInfo getShowErrorInfo() {
-    return (isTextBoxEmpty() ? ErrorInfo.NO : ErrorInfo.FULL);
+  Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
+    // No error if textbox empty, but mark everything as error if some text entered: it is junk (it can't be command,
+    // InCommand strategy were selected otherwise)
+    return isTextBoxEmpty() ? NO_ERROR : Pair.create(SpecialErrorPlace.WHOLE_TEXT, Collections.<WordWithPosition>emptyList());
   }
 
-
   private boolean isTextBoxEmpty() {
     return myPresenter.getView().getText().isEmpty();
   }
index 3facb047c0d7895950c019f50f65cbbc31109ace..231cf0207c17718d21dd12f34871b3d5e5f1d80f 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
 import com.jetbrains.python.optParse.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -51,6 +53,7 @@ abstract class Strategy {
   @NotNull
   abstract SuggestionInfo getSuggestionInfo();
 
+  // TODO: Merge baloon and error (actually the same)
   @NotNull
   List<WordWithPosition> getBalloonsToShow() {
     return Collections.emptyList();
@@ -67,28 +70,10 @@ abstract class Strategy {
    * @return errors
    */
   @NotNull
-  abstract ErrorInfo getShowErrorInfo();
+  abstract Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo();
 
   /**
    * @return if text entered by user contains some unknown commands
    */
   abstract boolean isUnknownTextExists();
-
-  /**
-   * Display error or not
-   */
-  enum ErrorInfo {
-    /**
-     * Yes, mark whole text as error
-     */
-    FULL,
-    /**
-     * Yes, mark last part as error
-     */
-    RELATIVE,
-    /**
-     * No, do not mark anything like error
-     */
-    NO
-  }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java
new file mode 100644 (file)
index 0000000..3199c34
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.commandsWithArgs;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * For many commands we know nothing about arguments but their help text.
+ * This strategy is for this case
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class UnknownArgumentsInfo implements ArgumentsInfo {
+  /**
+   * Argument help text
+   */
+  @NotNull
+  private final String myHelp;
+
+  /**
+   * @param allArgumentsHelpText argument help text
+   */
+  public UnknownArgumentsInfo(@NotNull final String allArgumentsHelpText) {
+    myHelp = allArgumentsHelpText;
+  }
+
+  @Nullable
+  @Override
+  public Argument getArgument(final int argumentPosition) {
+    return new Argument(myHelp); // We can't say argument does not exist.
+  }
+
+  @NotNull
+  @Override
+  public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
+    return ArgumentsValuesValidationInfo.NO_ERROR; // Actually, we have no idea
+  }
+}
index 5ce549811ea48e86da4830de69d8ec393e953b5d..2198b00dea60d3c01f3ac26fa92eddd7bc947f2e 100644 (file)
  */
 
 /**
+ * <h1>Optparse-based commandline interface presenter</h1>
+ * <p>
  * Command-line like interface presenter that uses conception of command and its arguments.
  * See {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
  * and its arguments: {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}.
  *
  * It supports <a href="https://docs.python.org/2/library/optparse.html">optparse</a> terminology, so
  * read it first and use {@link com.jetbrains.python.optParse} package
+ * </p>
+ * <h2>Arguments and validation</h2>
+ * <p>
+ *   Optparse arguments are <strong>positional and unnamed</strong>.
+ *   Each {@link com.jetbrains.python.commandInterface.commandsWithArgs.Command command} provides
+ *   {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo arguments info}.
+ *   It can be used to obtain information about argument (like list of possible values) and it also used to validate argument values,
+ *   provided by user. In most cases we have no idea about arguments: due to optparse limitations only help test is available.
+ *   But sometimes we do know (like when args are documented).
+ *   Different strategies exist, so be sure to check {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo} children
+ * </p>
+ *
+ *
  * @see com.jetbrains.python.optParse
  * @author Ilya.Kazakevich
  */
index 9eba919f58ae3aae4fb38ba61837afdf3771a378..4dd182e6645e90e4fcd1e3d3e8f823eddffa1cad 100644 (file)
  */
 package com.jetbrains.python.commandInterface.swingView;
 
+import com.google.common.base.Preconditions;
 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
 import com.intellij.openapi.fileEditor.impl.EditorWindow;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.*;
 import com.intellij.openapi.ui.popup.Balloon.Position;
-import com.intellij.openapi.ui.popup.JBPopup;
-import com.intellij.openapi.ui.popup.JBPopupAdapter;
-import com.intellij.openapi.ui.popup.JBPopupFactory;
-import com.intellij.openapi.ui.popup.LightweightWindowEvent;
+import com.intellij.ui.JBColor;
 import com.intellij.ui.awt.RelativePoint;
 import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
 import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.optParse.WordWithPosition;
 import com.jetbrains.python.suggestionList.SuggestionList;
 import com.jetbrains.python.suggestionList.SuggestionsBuilder;
-import com.jetbrains.python.optParse.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -49,6 +49,7 @@ import java.util.List;
  * @author Ilya.Kazakevich
  */
 public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener, CaretListener {
+  private static final JBColor ERROR_COLOR = JBColor.RED;
   /**
    * Pop-up we displayed in
    */
@@ -83,8 +84,11 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
    * Flag that indicates we are in "test forced" mode: current text set by presenter, not by user
    */
   private boolean myInForcedTextMode;
+  // TODO: Doc
   @NotNull
   private final List<WordWithPosition> myBalloons = new ArrayList<WordWithPosition>();
+  @NotNull
+  private final List<WordWithPosition> myErrorBalloons = new ArrayList<WordWithPosition>();
 
   /**
    * @param presenter       our presenter
@@ -165,8 +169,17 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
   }
 
   @Override
-  public void showError(final boolean lastOnly) {
-    myMainTextField.underlineText(Color.RED, lastOnly);
+  public final void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable final SpecialErrorPlace specialErrorPlace) {
+    for (final WordWithPosition error : errors) {
+      myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
+    }
+    if (specialErrorPlace != null) {
+      myMainTextField.underlineText(specialErrorPlace, ERROR_COLOR);
+    }
+    synchronized (myErrorBalloons) {
+      myErrorBalloons.clear();
+      myErrorBalloons.addAll(errors);
+    }
   }
 
 
@@ -204,15 +217,36 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
 
   @Override
   public final void caretUpdate(final CaretEvent e) {
-    // When caret moved, we need to check if baloon has to be displayed
+
+    // TODO: Stupid copy/paste, fix by method extract
+
+    // When caret moved, we need to check if balloon has to be displayed
+    synchronized (myErrorBalloons) {
+      showBaloons(myErrorBalloons, Position.below, MessageType.ERROR);
+    }
     synchronized (myBalloons) {
-      for (final WordWithPosition balloon : myBalloons) {
-        final int position = myMainTextField.getCaretPosition();
-        if (position >= balloon.getFrom() && position <= balloon.getTo()) {
-          final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCursorPosition(), 0));
-          JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(balloon.getText())).createBalloon()
-            .show(point, Position.above);
-        }
+      showBaloons(myBalloons, Position.above, MessageType.INFO);
+    }
+  }
+
+  // TODO: Doc
+  private void showBaloons(@NotNull final List<WordWithPosition> balloons,
+                           @NotNull final Position popUpPosition,
+                           @NotNull final MessageType messageType) {
+    Preconditions.checkArgument(popUpPosition == Position.above || popUpPosition == Position.below, "Only above or below is supported");
+    for (final WordWithPosition balloon : balloons) {
+      if (balloon.getText().isEmpty()) {
+        continue; // Can't be displayed if empty
+      }
+      final int caretPosition = myMainTextField.getCaretPosition();
+      if (caretPosition >= balloon.getFrom() && caretPosition <= balloon.getTo()) {
+        final int top = (popUpPosition == Position.above ? 0 : myMainTextField.getHeight() * 2); // Display below a little bit lower
+        final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCursorPosition(), top));
+        final Balloon balloonToShow =
+          JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(balloon.getText())).setFillColor(messageType.getPopupBackground())
+            .createBalloon();
+        balloonToShow.setAnimationEnabled(false);
+        balloonToShow.show(point, popUpPosition);
       }
     }
   }
index 8fec529e00c27e2074ad1c72e484e6f478345ea5..517568522461beb8788b183feb22c4e2ca9b867d 100644 (file)
@@ -18,12 +18,17 @@ package com.jetbrains.python.commandInterface.swingView;
 import com.intellij.openapi.editor.colors.EditorColorsManager;
 import com.intellij.openapi.editor.colors.EditorFontType;
 import com.intellij.openapi.util.Pair;
+import com.intellij.util.Range;
 import com.intellij.util.ui.StatusText;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
 import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collection;
 
 /**
  * Text field that has width to be changed and accepts error underline
@@ -37,12 +42,16 @@ public class SmartTextField extends JTextField {
    */
   private StatusText myPlaceHolder;
   /**
-   * error (underline) info in format "lastOnly => Color" where "lastOnly" is to underline last letter only (not the whole line)
-   * Null if nothing should be displayed.
+   * Error (underline) info to display. Check {@link UnderlineInfo} class for more info
    */
-  @Nullable
-  private Pair<Boolean, Color> myUnderlineInfo;
+  @NotNull
+  private final Collection<UnderlineInfo> myUnderlineInfo = new ArrayList<UnderlineInfo>();
   private int myPreferredWidth;
+  /**
+   * (color, special_place) tuple to underline special place, or null if no underline required
+   */
+  @Nullable
+  private Pair<Color, SpecialErrorPlace> mySpecialUnderlinePlace;
 
   public SmartTextField() {
     setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN));
@@ -54,15 +63,33 @@ public class SmartTextField extends JTextField {
     if (myPlaceHolder != null) {
       myPlaceHolder.paint(this, g);
     }
-    if (myUnderlineInfo != null) {
-      final int lineStart = (myUnderlineInfo.first ? getTextEndPosition() : getColumnWidth());
-      final int lineEnd = getTextEndPosition() + (myUnderlineInfo.first ? getColumnWidth() : 0);
-      g.setColor(myUnderlineInfo.second);
-      final int verticalPosition = getHeight() - 5;
-      g.drawLine(lineStart, verticalPosition, lineEnd, verticalPosition);
+    synchronized (myUnderlineInfo) {
+      for (final UnderlineInfo underlineInfo : myUnderlineInfo) {
+        g.setColor(underlineInfo.myColor);
+        underline(g, underlineInfo.getFrom(), underlineInfo.getTo());
+      }
+      if (mySpecialUnderlinePlace != null) {
+        final SpecialErrorPlace place = mySpecialUnderlinePlace.second;
+        g.setColor(mySpecialUnderlinePlace.first);
+        final int endPosition = getTextEndPosition();
+        final int from = (place == SpecialErrorPlace.WHOLE_TEXT ? 0 : endPosition - getColumnWidth());
+        underline(g, from, endPosition);
+      }
     }
   }
 
+  /**
+   * Underlines certain place
+   *
+   * @param g    canvas
+   * @param from from where (int px)
+   * @param to   to where (int px)
+   */
+  private void underline(@NotNull final Graphics g, final int from, final int to) {
+    final int verticalPosition = getHeight() - 5;
+    g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition);
+  }
+
   /**
    * @return place (in px) where entered text ends.
    */
@@ -95,18 +122,25 @@ public class SmartTextField extends JTextField {
   /**
    * Display underline
    *
-   * @param color    color to underline
-   * @param lastOnly last letter only (whole line otherwise)
+   * @param color color to underline
+   * @param from  from (in chars)
+   * @param to    (in chars)
    */
-  void underlineText(@NotNull final Color color, final boolean lastOnly) {
-    myUnderlineInfo = new Pair<Boolean, Color>(lastOnly, color);
+  final void underlineText(@NotNull final Color color, final int from, final int to) {
+    final int columnWidth = getColumnWidth();
+    synchronized (myUnderlineInfo) {
+      myUnderlineInfo.add(new UnderlineInfo(from * columnWidth, to * columnWidth, color));
+    }
   }
 
   /**
    * Removes underline
    */
   void hideUnderline() {
-    myUnderlineInfo = null;
+    synchronized (myUnderlineInfo) {
+      myUnderlineInfo.clear();
+      mySpecialUnderlinePlace = null;
+    }
   }
 
   /**
@@ -127,6 +161,19 @@ public class SmartTextField extends JTextField {
     myPreferredWidth = width;
   }
 
+  /**
+   * Display underline in special place
+   *
+   * @param color                 color to underline
+   * @param specialUnderlinePlace special place to underline
+   */
+  void underlineText(@NotNull final SpecialErrorPlace specialUnderlinePlace,
+                     @NotNull final Color color) {
+    synchronized (myUnderlineInfo) {
+      mySpecialUnderlinePlace = Pair.create(color, specialUnderlinePlace);
+    }
+  }
+
   /**
    * Wrapper to display placeholder
    */
@@ -140,4 +187,27 @@ public class SmartTextField extends JTextField {
       return SmartTextField.this.getText().isEmpty();
     }
   }
+
+  /**
+   * Information about underline
+   *
+   * @author Ilya.Kazakevich
+   */
+  private static final class UnderlineInfo extends Range<Integer> {
+    /**
+     * Color to use to underline
+     */
+    @NotNull
+    private final Color myColor;
+
+    /**
+     * @param from  underline from where (in px)
+     * @param to    underline to where (in px)
+     * @param color color to use to underline
+     */
+    UnderlineInfo(final int from, final int to, @NotNull final Color color) {
+      super(from, to);
+      myColor = color;
+    }
+  }
 }
index 069ce71825e2e533e5ed7b4ee06e1b178eb8163e..cdfdb27e8ed90fd0cff2be3ca28737064c3a6007 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.jetbrains.python.optParse;
 
+import com.intellij.util.Range;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
@@ -26,11 +27,18 @@ import java.util.List;
  *
  * @author Ilya.Kazakevich
  */
-public final class WordWithPosition {
+public final class WordWithPosition extends Range<Integer> {
   @NotNull
   private final String myWord;
-  private final int myFrom;
-  private final int myTo;
+
+  /**
+   * Creates word with beam (it has start, but it is infinite)
+   * @param word word
+   * @param from start
+   */
+  public WordWithPosition(@NotNull final String word, final int from) {
+    this(word, from, Integer.MAX_VALUE);
+  }
 
   /**
    * @param word text
@@ -38,9 +46,8 @@ public final class WordWithPosition {
    * @param to   to where
    */
   public WordWithPosition(@NotNull final String word, final int from, final int to) {
+    super(from, to);
     myWord = word;
-    myFrom = from;
-    myTo = to;
   }
 
   @NotNull
@@ -48,14 +55,6 @@ public final class WordWithPosition {
     return myWord;
   }
 
-  public int getFrom() {
-    return myFrom;
-  }
-
-  public int getTo() {
-    return myTo;
-  }
-
   /**
    * Returns texts only from collection of instances of this class
    *
@@ -63,8 +62,8 @@ public final class WordWithPosition {
    * @return collection of strings (text)
    */
   @NotNull
-  public static Collection<String> fetchText(@NotNull final Collection<WordWithPosition> words) {
-    final Collection<String> result = new ArrayList<String>(words.size());
+  public static List<String> fetchText(@NotNull final Collection<WordWithPosition> words) {
+    final List<String> result = new ArrayList<String>(words.size());
     for (final WordWithPosition word : words) {
       result.add(word.myWord);
     }
@@ -79,15 +78,15 @@ public final class WordWithPosition {
    */
   @NotNull
   public WordWithPosition copyWithDifferentText(@NotNull final String newText) {
-    return new WordWithPosition(newText, myFrom, myTo);
+    return new WordWithPosition(newText, getFrom(), getTo());
   }
 
   @Override
   public String toString() {
     return "WordWithPosition{" +
            "myWord='" + myWord + '\'' +
-           ", myFrom=" + myFrom +
-           ", myTo=" + myTo +
+           ", myFrom=" + getFrom() +
+           ", myTo=" + getTo() +
            '}';
   }
 
@@ -98,8 +97,8 @@ public final class WordWithPosition {
 
     WordWithPosition position = (WordWithPosition)o;
 
-    if (myFrom != position.myFrom) return false;
-    if (myTo != position.myTo) return false;
+    if (getFrom() != position.getFrom()) return false;
+    if (getTo() != position.getTo()) return false;
     if (!myWord.equals(position.myWord)) return false;
 
     return true;
@@ -108,8 +107,8 @@ public final class WordWithPosition {
   @Override
   public int hashCode() {
     int result = myWord.hashCode();
-    result = 31 * result + myFrom;
-    result = 31 * result + myTo;
+    result = 31 * result + getFrom();
+    result = 31 * result + getTo();
     return result;
   }