PY-11855 Run manage.py task improvements
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 9 Feb 2015 22:04:51 +0000 (01:04 +0300)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 9 Feb 2015 22:04:51 +0000 (01:04 +0300)
Refactored to new architecture (Presenter  / ChunkDriver).

45 files changed:
python/src/com/jetbrains/python/PyBundle.properties
python/src/com/jetbrains/python/WordWithPosition.java [moved from python/src/com/jetbrains/python/optParse/WordWithPosition.java with 91% similarity]
python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java
python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java [moved from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java with 96% similarity]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java [moved from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java with 51% similarity]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java [moved from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java with 69% similarity]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/KnownArgumentsInfo.java [moved from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java with 73% similarity]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/UnknownArgumentsInfo.java [moved from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java with 72% similarity]
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java [deleted file]
python/src/com/jetbrains/python/commandInterface/package-info.java
python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java
python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java
python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/MalformedCommandLineException.java [moved from python/src/com/jetbrains/python/optParse/MalformedCommandLineException.java with 94% similarity]
python/src/com/jetbrains/python/commandLineParser/OptParseCommandLineParser.java [moved from python/src/com/jetbrains/python/optParse/ParsedCommandLine.java with 56% similarity]
python/src/com/jetbrains/python/commandLineParser/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/optParse/package-info.java [deleted file]
python/src/com/jetbrains/python/suggestionList/SuggestionList.java

index 03014b095ff9eac97ad7843a836f30d1805d4629..08238498648c53bba55ad63b0fe7016d88b9ee8a 100644 (file)
@@ -852,7 +852,14 @@ 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}
 
+
 # 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
+commandLine.validation.badCommand=Unknown command
+commandLine.validation.argMissing=Required argument value is missing
+commandLine.validation.argBadValue=Argument can't have this value
+commandLine.validation.excessArg=Excess argument value
+# And for labels
+commandLine.subText.key.complete=Use {0} to complete selected variant
+commandLine.subText.key.suggestions=Use {0} to view available values
+commandLine.subText.key.executeUnknown=Click {0} to execute
+commandLine.subText.key.executeCommand=Click {0} to execute "{1}"
similarity index 91%
rename from python/src/com/jetbrains/python/optParse/WordWithPosition.java
rename to python/src/com/jetbrains/python/WordWithPosition.java
index cdfdb27e8ed90fd0cff2be3ca28737064c3a6007..35f10c3a2e810b4d505fe6fcee9f16137352c86a 100644 (file)
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.optParse;
+package com.jetbrains.python;
 
 import com.intellij.util.Range;
 import org.jetbrains.annotations.NotNull;
@@ -33,6 +33,7 @@ public final class WordWithPosition extends Range<Integer> {
 
   /**
    * Creates word with beam (it has start, but it is infinite)
+   *
    * @param word word
    * @param from start
    */
@@ -50,6 +51,15 @@ public final class WordWithPosition extends Range<Integer> {
     myWord = word;
   }
 
+  /**
+   * Creates instance with certain text and range (start/end)
+   * @param word text
+   * @param range range
+   */
+  public WordWithPosition(@NotNull final String word, @NotNull final Range<Integer> range) {
+    this(word, range.getFrom(), range.getTo());
+  }
+
   @NotNull
   public String getText() {
     return myWord;
@@ -121,7 +131,7 @@ public final class WordWithPosition extends Range<Integer> {
    * @return parse result
    */
   @NotNull
-  static List<WordWithPosition> splitText(@NotNull final String text) {
+  public static List<WordWithPosition> splitText(@NotNull final String text) {
     // TODO: Rewrite using regex or scanner?
     int position = 0;
     int wordStart = -1;
index 79eb731c448190e578ba2aa5f773f8541579c1a0..1eeecac550e5f4636fb33436415805c2f562b75e 100644 (file)
@@ -27,9 +27,8 @@ public interface CommandInterfacePresenter extends Presenter {
   /**
    * Called by view when user types new text or text changed by some other reason
    *
-   * @param inForcedTextMode text changed not by user but by calling {@link com.jetbrains.python.commandInterface.CommandInterfaceView#forceText(String)}
    */
-  void textChanged(boolean inForcedTextMode);
+  void textChanged();
 
   /**
    * Called by view when user requests for completion (like tab)
@@ -39,14 +38,13 @@ public interface CommandInterfacePresenter extends Presenter {
   void completionRequested(@Nullable String valueFromSuggestionList);
 
   /**
-   * Called by view when user asks for suggestions (CTRL+Space)
+   * Called by view when user asks for suggestions (like CTRL+Space)
    */
   void suggestionRequested();
 
   /**
-   * Called by view when user wants to execute command (Enter is presed)
+   * Called by view when user wants to execute command (like enter)
    *
-   * @param valueFromSuggestionList value selected from suggestion list (if any selected)
    */
-  void executionRequested(@Nullable String valueFromSuggestionList);
+  void executionRequested();
 }
index 3377431712c3c72d9179e28b970a1f0459eb50c5..317d4caeed496e36edaccf3026016186fb84a2a1 100644 (file)
@@ -15,7 +15,8 @@
  */
 package com.jetbrains.python.commandInterface;
 
-import com.jetbrains.python.optParse.WordWithPosition;
+import com.intellij.util.Range;
+import com.jetbrains.python.WordWithPosition;
 import com.jetbrains.python.suggestionList.SuggestionsBuilder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -30,6 +31,13 @@ import java.util.List;
  */
 public interface CommandInterfaceView {
 
+  /**
+   * Special place in command line that represents "after the last character" place.
+   * To be used in methods like {@link #setInfoAndErrors(java.util.Collection, java.util.Collection)} to mark it.
+   */
+  @NotNull
+  Range<Integer> AFTER_LAST_CHARACTER_RANGE = new Range<Integer>(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
   /**
    * Launches view
    */
@@ -39,43 +47,34 @@ public interface CommandInterfaceView {
    * Suggests user some elements (for completion reason)
    *
    * @param suggestions what to suggest (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
-   * @param absolute    display list in its main position, or directly near the text
-   * @param toSelect    word to select if list (if any)
+   * @param absolute    display list in its main position, or directly near the caret
+   * @param toSelect    word to select in list (if any)
    */
   void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
 
-  /**
-   * Emphasize errors (like red line and special message).
-   *
-   * @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 showErrors(@NotNull final List<WordWithPosition> errors, @Nullable SpecialErrorPlace specialErrorPlace);
-
-  /**
-   * Change text to the one provided
-   *
-   * @param newText text to display
-   */
-  void forceText(@NotNull String newText);
 
   /**
-   * Display text in sub part (like hint)
+   * Each time caret meets certain place, view should check whether some subtext has to be displayed.
+   * There are 2 types of subtext to be displayed:
+   * <ol>
+   * <li>Suggestion Text: View says something like "click FOO to see list of suggestions". Only presenter knows exact places where
+   * suggestions are available, so it should provide them</li>
+   * <li>Default text: In all other cases view displays default text (if available).</li>
+   * </ol>
+   * <p/>
+   * Presenter provides view list of special places
    *
-   * @param subText text to display
+   * @param defaultSubText            default text
+   * @param suggestionAvailablePlaces list of places where suggestions are available in format [from, to].
    */
-  void setSubText(@NotNull String subText);
+  void configureSubTexts(@Nullable String defaultSubText,
+                         @NotNull List<Range<Integer>> suggestionAvailablePlaces);
 
   /**
    * Hide suggestion list
    */
   void removeSuggestions();
 
-  /**
-   * Displays baloon with message right under the last letter.
-   *
-   * @param message text to display
-   */
 
   /**
    * @return text, entered by user
@@ -83,41 +82,40 @@ public interface CommandInterfaceView {
   @NotNull
   String getText();
 
+
   /**
-   * Enlarges view to make it as big as required to display appropriate number of chars
+   * When caret meets certain place, view may display some info and some errors.
+   * Errors, how ever, may always be emphasized (with something like red line).
+   * This function configures view with pack of ranges and texts to display.
+   * Special place {@link #AFTER_LAST_CHARACTER_RANGE} may also be used.
+   * Each place is described as start-end position (in chars) where it should be enabled.
+   * Each call removes previously enabled information.
    *
-   * @param widthInChars number of chars
+   * @param errors       places to be marked as errors with error text.
+   * @param infoBalloons places to display info balloon
+   * @see #AFTER_LAST_CHARACTER_RANGE
    */
-  void setPreferredWidthInChars(int widthInChars);
+  void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons, @NotNull final Collection<WordWithPosition> errors);
+
 
   /**
-   * Displays help balloon when cursor meets certain place.
-   * Each balloon is described as start-end position (in chars) where it should be enabled
-   * and test to display.
-   * <strong>Caution: Each call removes previuos balloons!</strong>
+   * Inserts text after caret moving next chars to the right
    *
-   * @param balloons list of balloons to display (i.e. you want to text 'foo' be displayed when user sets cursor on position
-   *                 from 1 to 3, so you add 'foo',1,4 here)
+   * @param text text to insert
    */
-  void setBalloons(@NotNull final Collection<WordWithPosition> balloons);
+  void insertTextAfterCaret(@NotNull String text);
 
   /**
-   * @return true if current caret position is on the word (no on whitespace)
+   * Replaces current text with another one.
+   *
+   * @param from    from
+   * @param to      to
+   * @param newText text to replace
    */
-  boolean isCaretOnWord();
-
+  void replaceText(final int from, final int to, @NotNull String newText);
 
   /**
-   * Special place that may be underlined
+   * @return position of caret (in chars)
    */
-  enum SpecialErrorPlace {
-    /**
-     * Whole text (from start to end)
-     */
-    WHOLE_TEXT,
-    /**
-     * Only after last character
-     */
-    AFTER_LAST_CHAR
-  }
+  int getCaretPosition();
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java
new file mode 100644 (file)
index 0000000..92a6910
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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<ChunkAndInfo> {
+  @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/ChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java
new file mode 100644 (file)
index 0000000..12740a5
--- /dev/null
@@ -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.chunkDriverBasedPresenter;
+
+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.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface ChunkDriver {
+  /**
+   * Parses chunks into pack of chunks. There <strong>always</strong> 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
+   */
+  @NotNull
+  ParseInfo parse(@NotNull List<WordWithPosition> chunks);
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java
new file mode 100644 (file)
index 0000000..946b271
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * 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<ChunkAndInfo> myChunkAndInfos = new TreeSet<ChunkAndInfo>();
+  @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<WordWithPosition> chunks = WordWithPosition.splitText(myView.getText());
+    final ParseInfo parseInfo = myChunkDriver.parse(chunks);
+    myExecutor = parseInfo.getExecutor();
+    final List<ChunkInfo> 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<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
+    final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
+
+
+    for (final ChunkAndInfo chunkInfoPair : myChunkAndInfos) {
+      final ChunkInfo chunkInfo = chunkInfoPair.getChunkInfo();
+      Range<Integer> 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<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
+    for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) {
+      Range<Integer> 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<String> suggestions = new ArrayList<String>(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<String> suggestions, @NotNull final String textToMatch) {
+    // TODO: use guava instead?
+    final Iterator<String> 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<Integer> 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<String> suggestions = new ArrayList<String>(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
new file mode 100644 (file)
index 0000000..4397751
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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
new file mode 100644 (file)
index 0000000..005c4da
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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<ChunkInfo> myChunkInfo = new ArrayList<ChunkInfo>();
+  @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> 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> 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<ChunkInfo> getChunkInfo() {
+    return Collections.unmodifiableList(myChunkInfo);
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java
new file mode 100644 (file)
index 0000000..59bea9a
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Information about suggestions
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class SuggestionInfo {
+  @NotNull
+  private final List<String> mySuggestions = new ArrayList<String>();
+  private final boolean myShowSuggestionsAutomatically;
+  private final boolean myShowAbsolute;
+
+  /**
+   * @param showSuggestionsAutomatically true it suggestions should be displayed even if user did not ask for that
+   * @param showAbsolute                 show suggestions at the absolute position (not relative to caret).
+   * @param suggestions                  List of suggestions to display
+   */
+  public SuggestionInfo(final boolean showSuggestionsAutomatically,
+                        final boolean showAbsolute,
+                        @NotNull final Collection<String> suggestions) {
+    myShowSuggestionsAutomatically = showSuggestionsAutomatically;
+    myShowAbsolute = showAbsolute;
+    mySuggestions.addAll(suggestions);
+  }
+
+  /**
+   * @return List of suggestions to display
+   */
+  @NotNull
+  public List<String> getSuggestions() {
+    return Collections.unmodifiableList(mySuggestions);
+  }
+
+  /**
+   * @return true it suggestions should be displayed even if user did not ask for that
+   */
+  public boolean isShowSuggestionsAutomatically() {
+    return myShowSuggestionsAutomatically;
+  }
+
+  /**
+   * @return show suggestions at the absolute position (not relative to caret).
+   */
+  public boolean isShowAbsolute() {
+    return myShowAbsolute;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java
new file mode 100644 (file)
index 0000000..9281bc7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 <strong>chunk</strong>
+ * 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
similarity index 96%
rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java
index 5826a45a4dd40da2dfcf1e3fb43b48dd4092038f..fc3f64192c179ed6ca1d9ec717cc481cf2d171c5 100644 (file)
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
 
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
similarity index 51%
rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java
index 594b7ac37a7071a8c910f32cf80ee6e5932ecc93..ad43da45adcd4b14dc8cb1fbf58233ae4c6b3acb 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
 
-import org.jetbrains.annotations.NotNull;
+import com.intellij.openapi.util.Pair;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
 /**
- * Information about command {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} and their value
+ * Information about command {@link Argument arguments} and their value
  * validation.
- * Check optparse manual, package info and {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}
+ * Check optparse manual, package info and {@link Argument}
  * manual for more info about arguments.
  *
  * @author Ilya.Kazakevich
  */
 public interface ArgumentsInfo {
   /**
-   * Returns argument by its position.
+   * Returns argument by its position. It also returns hint whether argument is required or not.
    *
    * @param argumentPosition argument position
-   * @return null if no argument value is available at this position. Returns argument otherwise.
+   * @return null if no argument value is available at this position.
+   * Returns argument otherwise. Boolean here should tell you if argument is required (command is invalid with out of it) or optional (
+   * it is acceptible, but command can work with our of it)
    */
   @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);
+  Pair<Boolean, Argument> getArgument(int argumentPosition);
 }
similarity index 69%
rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java
index fb139df02ca2cd9cf9c8417d82cdbab193fc3755..694021964feae20cb98dc069357f968b99b900c7 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
 
+import com.intellij.openapi.module.Module;
+import com.jetbrains.python.commandLineParser.CommandLineParseResult;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -40,8 +42,16 @@ public interface Command {
 
 
   /**
-   * @return Information about command positional, unnamed {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} (not options!)
+   * @return Information about command positional, unnamed {@link Argument arguments} (not options!)
    */
   @NotNull
   ArgumentsInfo getArgumentsInfo();
+
+  /**
+   * Execute command
+   *
+   * @param module      module to execute command against
+   * @param commandLine command's command line
+   */
+  void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine);
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java
new file mode 100644 (file)
index 0000000..02fd1d3
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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.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<String, Command> myCommands = new TreeMap<String, Command>(); // 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<? extends Command> commands) {
+    myCommandLineParser = commandLineParser;
+    for (final Command command : commands) {
+      myCommands.put(command.getName(), command);
+    }
+    myModule = module;
+  }
+
+  @Override
+  @NotNull
+  public ParseInfo parse(@NotNull final List<WordWithPosition> 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> chunkInfo = new ArrayList<ChunkInfo>();
+      // 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<Boolean, Argument> 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<String> availableValues = argument.getAvailableValues();
+        final Pair<CommandLinePartType, WordWithPosition> 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<Boolean, Argument> nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size());
+      if (nextArgumentPair != null) {
+        // Next arg exists
+        final Argument nextArgument = nextArgumentPair.getSecond();
+        final List<String> 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<ChunkInfo> result = new ArrayList<ChunkInfo>();
+    // 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);
+    }
+  }
+}
similarity index 73%
rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/KnownArgumentsInfo.java
index 2e8762d10001c1e63acbf1062c8a2e5fdb225e52..4c3a50295f78844068a2130aaafe94b60a454cca 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
 
 import com.google.common.base.Preconditions;
-import com.intellij.util.containers.hash.HashMap;
-import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
+import com.intellij.openapi.util.Pair;
 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.
@@ -61,6 +59,15 @@ public final class KnownArgumentsInfo implements ArgumentsInfo {
    */
   private final int myMaxArguments;
 
+  /**
+   * For command with fixed number of arguments. In this case all arguments are fixed and required!
+   *
+   * @param arguments arguments this command have
+   */
+  public KnownArgumentsInfo(@NotNull final Collection<Argument> arguments) {
+    this(arguments, arguments.size(), arguments.size());
+  }
+
   /**
    * For commands with infinite number of values last argument accepts (my_command VAL1 VAL2 .. VALN)
    *
@@ -91,36 +98,20 @@ public final class KnownArgumentsInfo implements ArgumentsInfo {
 
   @Nullable
   @Override
-  public Argument getArgument(final int argumentPosition) {
+  public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+    if (argumentPosition >= myMaxArguments) {
+      return null;
+    }
+    final boolean optional = argumentPosition >= myMinArguments;
+
     if (myArguments.size() > argumentPosition) {
-      return myArguments.get(argumentPosition);
+      return Pair.create(!optional, myArguments.get(argumentPosition));
     }
 
     // We may need last one
-    if (argumentPosition <= myMaxArguments) {
-      return myArguments.get(myArguments.size() - 1);
+    if (argumentPosition < myMaxArguments) {
+      return Pair.create(false, 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/commandBasedChunkDriver/NoArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java
new file mode 100644 (file)
index 0000000..9aaa0d5
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * TODO: Redundant?!
+ * 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 Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+    return null;
+  }
+}
similarity index 72%
rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/UnknownArgumentsInfo.java
index 3199c3431d1578545352e54307338d7144734cb2..04889e4109c9af933fa0525fb5cd7f55b1bc8d7b 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
 
+import com.intellij.openapi.util.Pair;
 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
@@ -40,15 +39,10 @@ public final class UnknownArgumentsInfo implements ArgumentsInfo {
     myHelp = allArgumentsHelpText;
   }
 
-  @Nullable
-  @Override
-  public Argument getArgument(final int argumentPosition) {
-    return new Argument(myHelp); // We can't say argument does not exist.
-  }
 
-  @NotNull
+  @Nullable
   @Override
-  public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
-    return ArgumentsValuesValidationInfo.NO_ERROR; // Actually, we have no idea
+  public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+    return Pair.create(false, new Argument(myHelp));
   }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java
new file mode 100644 (file)
index 0000000..cc72ae2
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of
+ * {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Command command} and its {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Argument arguments}.
+ *
+ *  See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point.
+ *  It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
+ *  provided by user
+ *
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java
deleted file mode 100644 (file)
index 2390747..0000000
+++ /dev/null
@@ -1,78 +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.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
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java
deleted file mode 100644 (file)
index 51d0a42..0000000
+++ /dev/null
@@ -1,67 +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.commandsWithArgs;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Simple command implementation
- *
- * @author Ilya.Kazakevich
- */
-public class CommandAdapter implements Command {
-  @NotNull
-  private final String myName;
-  @Nullable
-  private final String myHelp;
-  @NotNull
-  private final ArgumentsInfo myArgumentsInfo;
-
-  /**
-   * @param help          help text
-   * @param name          command name
-   * @param argumentsInfo arguments info
-   */
-  public CommandAdapter(@NotNull final String name, @Nullable final String help, @NotNull ArgumentsInfo argumentsInfo) {
-    myName = name;
-    myHelp = help;
-    myArgumentsInfo = argumentsInfo;
-  }
-
-  /**
-   * @return command name
-   */
-  @Override
-  @NotNull
-  public final String getName() {
-    return myName;
-  }
-
-
-  @Override
-  @Nullable
-  public final String getHelp() {
-    return myHelp;
-  }
-
-
-  @NotNull
-  @Override
-  public final ArgumentsInfo getArgumentsInfo() {
-    return myArgumentsInfo;
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java
deleted file mode 100644 (file)
index b7e533b..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2000-2014 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.openapi.util.text.StringUtil;
-import com.intellij.util.ArrayUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Information about command and its arguments to save in history / pass to execution etc.
- *
- * @author Ilya.Kazakevich
- */
-public class CommandExecutionInfo {
-  /**
-   * Command and arguments separator.
-   */
-  private static final String SEPARATOR = " ";
-  @NotNull
-  private final String myCommandName;
-  @NotNull
-  private final String[] myArguments;
-
-  /**
-   * @param commandName command
-   * @param arguments   its arguments
-   */
-  public CommandExecutionInfo(@NotNull final String commandName, @NotNull final String... arguments) {
-    myCommandName = commandName;
-    myArguments = arguments.clone();
-  }
-
-  /**
-   * @return command
-   */
-  @NotNull
-  public String getCommandName() {
-    return myCommandName;
-  }
-
-  /**
-   * @return command arguments
-   */
-  @NotNull
-  public String[] getArguments() {
-    return myArguments.clone();
-  }
-
-  /**
-   * @return command in format "command arg1 arg2". Opposite to {@link #fromString(String)}
-   * @see #fromString(String)
-   */
-  @NotNull
-  public String toString() {
-    // TODO: What if command or argument has space in it? Escape somehow!
-    return StringUtil.join(ArrayUtil.mergeArrays(new String[]{myCommandName}, myArguments), SEPARATOR);
-  }
-
-  /**
-   * @param stringToUnserialize string created by {@link #toString()}
-   * @return command parsed from string
-   * @see #toString()
-   */
-  @Nullable
-  public static CommandExecutionInfo fromString(@NotNull final String stringToUnserialize) {
-    // TODO: What if command or argument has space in it? Escape somehow!
-    final List<String> strings = StringUtil.split(stringToUnserialize, SEPARATOR);
-    if (strings.isEmpty()) {
-      return null;
-    }
-    return new CommandExecutionInfo(strings.get(0), ArrayUtil.toStringArray(strings.subList(1, strings.size())));
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (!(o instanceof CommandExecutionInfo)) return false;
-
-    CommandExecutionInfo info = (CommandExecutionInfo)o;
-
-    if (!Arrays.equals(myArguments, info.myArguments)) return false;
-    if (!myCommandName.equals(info.myCommandName)) return false;
-
-    return true;
-  }
-
-  @Override
-  public int hashCode() {
-    int result = myCommandName.hashCode();
-    result = 31 * result + Arrays.hashCode(myArguments);
-    return result;
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java
deleted file mode 100644 (file)
index 0090a9e..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2000-2014 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.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;
-
-import java.util.*;
-
-/**
- * Command-line interface presenter that is command-based
- *
- * @param <C> Command type
- * @author Ilya.Kazakevich
- */
-public class CommandInterfacePresenterCommandBased<C extends Command> extends CommandInterfacePresenterAdapter {
-  /**
-   * [name] -> command. Linked is used to preserve order.
-   */
-  private final Map<String, C> myCommands = new LinkedHashMap<String, C>();
-  /**
-   * currenly used strategy (see interface for more info)
-   */
-  private Strategy myStrategy;
-
-
-  /**
-   * @param view     view
-   * @param commands available commands
-   */
-  public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
-                                               @NotNull final Iterable<C> commands) {
-    super(view);
-    for (final C command : commands) {
-      myCommands.put(command.getName(), command);
-    }
-  }
-
-  /**
-   * @param view     view
-   * @param commands available commands
-   */
-  public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
-                                               @NotNull final C... commands) {
-    this(view, Arrays.asList(commands));
-  }
-
-  @Override
-  public void launch() {
-    /*myView.setPreferredWidthInChars(getMaximumCommandWithArgsLength());*/
-    super.launch();
-    myStrategy = new NoCommandStrategy(this);
-  }
-
-  @Override
-  public void textChanged(final boolean inForcedTextMode) {
-    configureStrategy();
-    myView.setSubText(myStrategy.getSubText());
-    final Pair<SpecialErrorPlace, List<WordWithPosition>> errorInfo = myStrategy.getErrorInfo();
-    myView.showErrors(errorInfo.getSecond(), errorInfo.first);
-    myView.setBalloons(myStrategy.getBalloonsToShow());
-
-    final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
-    final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
-
-    final String lastPart = getLastPart();
-    if ((lastPart != null) && myStrategy.isUnknownTextExists()) {
-      //Filter to starts from
-      final Iterator<String> iterator = suggestions.iterator();
-      while (iterator.hasNext()) {
-        final String textToCheck = iterator.next();
-
-        if (!textToCheck.startsWith(lastPart)) {
-          iterator.remove();
-        }
-      }
-    }
-
-    if (!suggestionInfo.myShowOnlyWhenRequested && !suggestions.isEmpty()) {
-      final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
-      suggestionsBuilder.add(suggestions);
-
-      myView
-        .displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
-    }
-    else {
-      myView.removeSuggestions();
-    }
-  }
-
-  /**
-   * @return builder that already has history in its prefix group (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
-   */
-  @NotNull
-  private SuggestionsBuilder getBuilderWithHistory() {
-    return new SuggestionsBuilder();
-
-    // TODO: Uncomment when history would be fixed
-    /*final SuggestionsBuilder suggestionsBuilder = new SuggestionsBuilder();
-    final List<CommandExecutionInfo> history = getHistory();
-    final Collection<String> historyCommands = new LinkedHashSet<String>();
-    for (final CommandExecutionInfo info : history) {
-      historyCommands.add(info.toString());
-    }
-
-    if (!historyCommands.isEmpty()) {
-      // TODO: Later implement folding by name
-      suggestionsBuilder.changeGroup(false);
-      suggestionsBuilder
-        .add(ArrayUtil.toStringArray(historyCommands));
-      suggestionsBuilder.changeGroup(true);
-    }
-
-    return suggestionsBuilder;*/
-  }
-
-  /**
-   * @return execution info from history. It is empty by default, child should implement it.
-   */
-  @NotNull
-  protected List<CommandExecutionInfo> getHistory() {
-    return Collections.emptyList();
-  }
-
-  /**
-   * @return command that entered in box, or null of just entered
-   */
-  @Nullable
-  protected CommandExecutionInfo getCommandToExecute() {
-    return myStrategy.getCommandToExecute();
-  }
-
-  /**
-   * Finds and sets appropriate strategy
-   */
-  private void configureStrategy() {
-    final ParsedCommandLine line = getParsedCommandLine();
-    if (line != null) {
-      final Command command = myCommands.get(line.getCommand().getText());
-      if (command != null) {
-        myStrategy = new InCommandStrategy(command, line, this);
-        return;
-      }
-    }
-    myStrategy = new NoCommandStrategy(this); // No command or bad command found
-  }
-
-  @Override
-  public void completionRequested(@Nullable final String valueFromSuggestionList) {
-    if (valueFromSuggestionList != null) {
-      final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
-      if (suggestionInfo.getSuggestions().contains(valueFromSuggestionList)) {
-        final ParsedCommandLine commandLine = getParsedCommandLine();
-        final List<String> words = commandLine != null ? commandLine.getAsWords() : new ArrayList<String>();
-        if (!words.isEmpty() && myView.isCaretOnWord()) {
-          words.remove(words.size() - 1);
-        }
-        words.add(valueFromSuggestionList);
-        myView.forceText(StringUtil.join(words, " "));
-      }
-    }
-    myView.removeSuggestions();
-  }
-
-  @Override
-  public void suggestionRequested() {
-    final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
-    final List<String> suggestions = suggestionInfo.getSuggestions();
-    if (!suggestions.isEmpty()) {
-      final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
-      suggestionsBuilder.add(suggestions);
-      myView.displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
-    }
-  }
-
-  @Override
-  public void executionRequested(@Nullable final String valueFromSuggestionList) {
-
-  }
-
-  /**
-   * @return [command_name => command] all available commands
-   */
-  @NotNull
-  protected final Map<String, C> getCommands() {
-    return Collections.unmodifiableMap(myCommands);
-  }
-
-  /**
-   * @return parsed commandline entered by user
-   */
-  @Nullable
-  final ParsedCommandLine getParsedCommandLine() {
-    try {
-      return new ParsedCommandLine(myView.getText());
-    }
-    catch (final MalformedCommandLineException ignored) {
-      return null;
-    }
-  }
-
-
-  /**
-   * @return last part of splitted text (if any). I.e. "foo bar spam" will return "spam"
-   */
-  @Nullable
-   final String getLastPart() {
-    final ParsedCommandLine commandLine = getParsedCommandLine();
-    if (commandLine == null || commandLine.getAsWords().isEmpty()) {
-      return null;
-    }
-    final List<String> words = commandLine.getAsWords();
-    return words.get(words.size() - 1);
-  }
-
-  /**
-   * @return view
-   */
-  @NotNull
-  CommandInterfaceView getView() {
-    return myView;
-  }
-
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java
deleted file mode 100644 (file)
index 1d3be47..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright 2000-2014 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.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;
-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
- *
- * @author Ilya.Kazakevich
- */
-final class InCommandStrategy extends Strategy {
-  @NotNull
-  private final List<String> myArguments = new ArrayList<String>();
-  @NotNull
-  private final Command myCommand;
-  @NotNull
-  private final ParsedCommandLine myCommandLine;
-
-  /**
-   * @param command   command enrtered by user
-   * @param presenter presenter
-   */
-  InCommandStrategy(@NotNull final Command command,
-                    @NotNull final ParsedCommandLine commandLine,
-                    @NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
-    super(presenter);
-    myArguments.addAll(WordWithPosition.fetchText(commandLine.getArguments()));
-    myCommand = command;
-    myCommandLine = commandLine;
-  }
-
-  @NotNull
-  @Override
-  public String getSubText() {
-    final String help = myCommand.getHelp();
-    if (help != null) {
-      return help;
-    }
-    return "Place to display help";
-  }
-
-  @NotNull
-  @Override
-  SuggestionInfo getSuggestionInfo() {
-    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, Collections.<String>emptyList());
-  }
-
-  @NotNull
-  @Override
-  List<WordWithPosition> getBalloonsToShow() {
-    // Display argument balloons right from command end to last argument end
-    final ArgumentsInfo argumentsInfo = myCommand.getArgumentsInfo();
-    final List<WordWithPosition> arguments = myCommandLine.getArguments();
-    if (arguments.isEmpty()) {
-      // 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 (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;
-  }
-
-  @Override
-  boolean isUnknownTextExists() {
-    if (myCommandLine.getAsWords().isEmpty()) {
-      return false; // Command only
-    }
-    final String lastPart = myPresenter.getLastPart();
-    return ((lastPart != null) && !getSuggestionInfo().getSuggestions().contains(lastPart));
-  }
-
-  @Nullable
-  @Override
-  CommandExecutionInfo getCommandToExecute() {
-    return new CommandExecutionInfo(myCommand.getName(), ArrayUtil.toStringArray(myArguments));
-  }
-
-  @NotNull
-  @Override
-  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/NoArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java
deleted file mode 100644 (file)
index d08e8cc..0000000
+++ /dev/null
@@ -1,62 +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.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);
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java
deleted file mode 100644 (file)
index 16ea46f..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2000-2014 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.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
- */
-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 PyBundle.message("commandsWithArgs.enterCommand.label");
-  }
-
-  @NotNull
-  @Override
-  SuggestionInfo getSuggestionInfo() {
-    return new SuggestionInfo(true, isTextBoxEmpty(), new ArrayList<String>(myPresenter.getCommands().keySet()));
-  }
-
-  @Override
-  boolean isUnknownTextExists() {
-    return !myPresenter.getView().getText().isEmpty();
-  }
-
-
-  @NotNull
-  @Override
-  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();
-  }
-
-  @Nullable
-  @Override
-  CommandExecutionInfo getCommandToExecute() {
-    return null;
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java
deleted file mode 100644 (file)
index 231cf02..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2000-2014 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.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;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Strategy that helps {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
- * to deal with 2 states: when no text entered (or some junk enetered) or command name entered
- *
- * @author Ilya.Kazakevich
- */
-abstract class Strategy {
-  @NotNull
-  protected final CommandInterfacePresenterCommandBased<?> myPresenter;
-
-  /**
-   * @param presenter presenter
-   */
-  protected Strategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
-    myPresenter = presenter;
-  }
-
-  /**
-   * @return sub text to display
-   */
-  @NotNull
-  abstract String getSubText();
-
-  /**
-   * @return suggestions
-   */
-  @NotNull
-  abstract SuggestionInfo getSuggestionInfo();
-
-  // TODO: Merge baloon and error (actually the same)
-  @NotNull
-  List<WordWithPosition> getBalloonsToShow() {
-    return Collections.emptyList();
-  }
-
-  /**
-   * @return command that entered in box, or null of just entered
-   */
-  @Nullable
-  abstract CommandExecutionInfo getCommandToExecute();
-
-
-  /**
-   * @return errors
-   */
-  @NotNull
-  abstract Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo();
-
-  /**
-   * @return if text entered by user contains some unknown commands
-   */
-  abstract boolean isUnknownTextExists();
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java
deleted file mode 100644 (file)
index c761d8d..0000000
+++ /dev/null
@@ -1,65 +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.commandsWithArgs;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Information abouyt suggestion, provided by {@link com.jetbrains.python.commandInterface.commandsWithArgs.Strategy}
- *
- * @author Ilya.Kazakevich
- */
-@SuppressWarnings("PackageVisibleField")
-// No do not need to hide field: everything is internal API in package, anyway
-final class SuggestionInfo {
-  /**
-   * Suggestions
-   */
-  private final List<String> mySuggestions = new ArrayList<String>();
-  /**
-   * Display them at absolute location or relative to last letter
-   */
-  final boolean myAbsolute;
-  /**
-   * Show then any time, or only when user requests them
-   */
-  final boolean myShowOnlyWhenRequested;
-
-  /**
-   * @param absolute              Display them at absolute location or relative to last letter
-   * @param showOnlyWhenRequested Show then any time, or only when user requests them
-   * @param suggestions           Suggestions
-   */
-  SuggestionInfo(final boolean absolute,
-                 final boolean showOnlyWhenRequested,
-                 @NotNull final List<String> suggestions) {
-    myAbsolute = absolute;
-    myShowOnlyWhenRequested = showOnlyWhenRequested;
-    mySuggestions.addAll(suggestions);
-  }
-
-  /**
-   * @return suggestions
-   */
-  @NotNull
-  List<String> getSuggestions() {
-    return Collections.unmodifiableList(mySuggestions);
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java
deleted file mode 100644 (file)
index 2198b00..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2000-2014 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.
- */
-
-/**
- * <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
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
\ No newline at end of file
index 19c0b13ac9792bf219ec029c8aa344f83aa0d5e6..7f1581af3bc7f80cc1fd507fee8be30b5c8918ce 100644 (file)
@@ -19,7 +19,7 @@
  * It has several abilities, including (but not limited):
  * <ol>
  *   <li>Suggestion box</li>
- *   <li>Error marking</li>
+ *   <li>Error emphasising</li>
  *   <li>Popups</li>
  *   <li>AutoCompletion</li>
  * </ol>
  *
  * <p>
  *   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.commandsWithArgs}
+ *   and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter}
  * </p>
  *
+ * <p>
+ *   Presenter and View talk to each other only in term of text and chars. Presenter knows nothing about pixels and should never
+ *   assume view have certain pixel size.
+ * </p>
+ *
+ *
  *
  * @author Ilya.Kazakevich
  */
index 3071a8dc8665bc50b4d45b0ee27fb7a0d456362f..955d07ee65801b3cf3d035e3d7cfb687b89b971e 100644 (file)
@@ -17,16 +17,18 @@ 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.ui.JBColor;
 import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.Range;
+import com.intellij.util.containers.HashSet;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.WordWithPosition;
 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 org.jetbrains.annotations.NotNull;
@@ -37,19 +39,31 @@ import javax.swing.event.CaretEvent;
 import javax.swing.event.CaretListener;
 import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
 import java.awt.*;
-import java.awt.event.*;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 /**
- * Command-interface view implementation based on Swing
+ * Command-interface view implementation based on Swing.
+ * It uses balloons to display errors and infos, drop-down for suggestions and also underlines errors
  *
  * @author Ilya.Kazakevich
  */
 public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener, CaretListener {
   private static final JBColor ERROR_COLOR = JBColor.RED;
+
+  /**
+   * We need to track balloons, so we have field with callback
+   */
+  @NotNull
+  private final BalloonManager myBalloonManager = new BalloonManager();
   /**
    * Pop-up we displayed in
    */
@@ -68,27 +82,38 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
    * Lower (sub) label
    */
   private JLabel mySubLabel;
+  /**
+   * "Suggestion area". Suggestion status is displayed when caret meets this area
+   */
+  private final List<Range<Integer>> myPlacesWhereSuggestionsAvailable = new ArrayList<Range<Integer>>();
   @NotNull
   private final CommandInterfacePresenter myPresenter;
   /**
    * List to display suggestions
    */
   @NotNull
-  private final SuggestionList myList;
+  private final SuggestionList mySuggestionList;
   /**
    * Displayed when there is no text
    */
   @Nullable
   private final String myPlaceHolderText;
+
   /**
-   * Flag that indicates we are in "test forced" mode: current text set by presenter, not by user
+   * Information balloons that should be displayed when caret meets their boundaries.
    */
-  private boolean myInForcedTextMode;
-  // TODO: Doc
-  @NotNull
-  private final List<WordWithPosition> myBalloons = new ArrayList<WordWithPosition>();
   @NotNull
+  private final List<WordWithPosition> myInfoBalloons = new ArrayList<WordWithPosition>();
+  /**
+   * Error balloons that should be displayed when caret meets their boundaries.
+   * Errors are always underlined, but balloons are displayed only if caret meets error
+   */
   private final List<WordWithPosition> myErrorBalloons = new ArrayList<WordWithPosition>();
+  /**
+   * Default subtext to display when caret is out of {@link #myPlacesWhereSuggestionsAvailable "suggestion" area}
+   */
+  @Nullable
+  private String myDefaultSubText;
 
   /**
    * @param presenter       our presenter
@@ -112,18 +137,11 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
     myMainTextField.setFocusable(true);
 
 
-    final EditorWindow window = FileEditorManagerEx.getInstanceEx(project).getCurrentWindow();
-    final int windowSize;
-    if (window != null) {
-      windowSize = window.getSize().width;
-    }
-    else {
-      windowSize = 0; // Windows size is unknown
-    }
+    final int windowWidth = FileEditorManagerEx.getInstanceEx(project).getComponent().getRootPane().getWidth() - 10; // Little gap
 
     myMainTextField
-      .setPreferredWidthInPx(windowSize);
-    myList = new SuggestionList(new MySuggestionListListener());
+      .setPreferredWidthInPx(windowWidth);
+    mySuggestionList = new SuggestionList(new MySuggestionListListener());
   }
 
 
@@ -145,7 +163,12 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
       }
     });
     myMainTextField.setFocusTraversalKeysEnabled(false);
-    myMainTextField.addKeyListener(new MyKeyListener());
+    myMainTextField.addKeyListener(new MyKeyListener()); // Up/down arrows are not handles with actions
+
+    // Register all available actions
+    for (final KeyStrokeInfo strokeInfo : KeyStrokeInfo.values()) {
+      strokeInfo.register(myPresenter, mySuggestionList, myMainTextField);
+    }
     myMainPopUp.showInFocusCenter();
   }
 
@@ -154,32 +177,18 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
   public void displaySuggestions(@NotNull final SuggestionsBuilder suggestions, final boolean absolute, @Nullable final String toSelect) {
 
     int left = 0;
-    // Display text right after line ends if not in "absolute" mode
+    // Display text right after caret
     if (!absolute) {
-      left = myMainTextField.getTextEndPosition();
+      left = myMainTextField.getTextCaretPositionInPx();
     }
-    myList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
+    mySuggestionList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
+    configureAppropriateStatus();
   }
 
-
   @Override
   public void onClosed(final LightweightWindowEvent event) {
     super.onClosed(event);
-    myList.close();
-  }
-
-  @Override
-  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);
-    }
+    mySuggestionList.close();
   }
 
 
@@ -188,6 +197,7 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
     processDocumentChange();
   }
 
+
   @Override
   public void removeUpdate(final DocumentEvent e) {
     processDocumentChange();
@@ -195,7 +205,7 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
 
   private void processDocumentChange() {
     myMainTextField.hideUnderline();
-    myPresenter.textChanged(myInForcedTextMode);
+    myPresenter.textChanged();
   }
 
   @Override
@@ -203,77 +213,157 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
 
   }
 
-  @Override
-  public void forceText(@NotNull final String newText) {
-    myInForcedTextMode = true;
-    myMainTextField.setText(newText);
-    myInForcedTextMode = false;
-  }
-
   @Override
   public void removeSuggestions() {
-    myList.close();
+    mySuggestionList.close();
+    configureAppropriateStatus();
   }
 
+
   @Override
   public final void caretUpdate(final CaretEvent e) {
 
-    // TODO: Stupid copy/paste, fix by method extract
 
     // When caret moved, we need to check if balloon has to be displayed
+    displayBalloonsIfRequired();
+    configureAppropriateStatus();
+  }
+
+  private void configureAppropriateStatus() {
+    if (!mySuggestionList.isClosed()) {
+      // Tell user she may use TAB to complete
+      mySubLabel.setText(PyBundle.message("commandLine.subText.key.complete", KeyStrokeInfo.COMPLETION.getText()));
+      return;
+    }
+
+    // If we are in "suggestion available" place -- tell it
+    for (final Range<Integer> range : myPlacesWhereSuggestionsAvailable) {
+      final boolean specialCaseAfterLastChar = isAfterLastCharRange(range) && getCaretPosition() == myMainTextField.getText().length();
+      if (range.isWithin(getCaretPosition()) || specialCaseAfterLastChar) {
+        mySubLabel.setText(PyBundle.message("commandLine.subText.key.suggestions", KeyStrokeInfo.SUGGESTION.getText()));
+        return;
+      }
+    }
+
+    // We may simply tell user she may execute command
+    if (myDefaultSubText != null) {
+      mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeCommand", KeyStrokeInfo.EXECUTION.getText(), myDefaultSubText));
+    }
+    else {
+      mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeUnknown", KeyStrokeInfo.EXECUTION.getText()));
+    }
+  }
+
+
+  private void displayBalloonsIfRequired() {
     synchronized (myErrorBalloons) {
-      showBaloons(myErrorBalloons, Position.below, MessageType.ERROR);
+      if (mySuggestionList.isClosed()) { // No need to display error popups when suggestion list is displayed. It intersects.
+        showBalloons(myErrorBalloons, Position.below, MessageType.ERROR);
+      }
     }
-    synchronized (myBalloons) {
-      showBaloons(myBalloons, Position.above, MessageType.INFO);
+    synchronized (myInfoBalloons) {
+      showBalloons(myInfoBalloons, Position.above, MessageType.INFO);
     }
   }
 
-  // TODO: Doc
-  private void showBaloons(@NotNull final List<WordWithPosition> balloons,
-                           @NotNull final Position popUpPosition,
-                           @NotNull final MessageType messageType) {
+  /**
+   * Displays some balloons
+   *
+   * @param balloons      balloons to display
+   * @param popUpPosition where ti display them. Only {@link Position#above} and {@link Position#below} are supported!
+   * @param messageType   may be {@link MessageType#ERROR} or {@link MessageType#INFO} for example
+   */
+  private void showBalloons(@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()) {
+      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 RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCaretPositionInPx(), top));
         final Balloon balloonToShow =
           JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(balloon.getText())).setFillColor(messageType.getPopupBackground())
             .createBalloon();
         balloonToShow.setAnimationEnabled(false);
+        myBalloonManager.registerBalloon(balloonToShow);
         balloonToShow.show(point, popUpPosition);
       }
     }
   }
 
   @Override
-  public final boolean isCaretOnWord() {
-    final int caretPosition = myMainTextField.getCaretPosition();
-    if (caretPosition == 0) {
-      return false; // At the beginning of the line
+  public final void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons,
+                                     @NotNull final Collection<WordWithPosition> errors) {
+    synchronized (myInfoBalloons) {
+      myInfoBalloons.clear();
+      myInfoBalloons.addAll(infoBalloons);
+    }
+    synchronized (myErrorBalloons) {
+      myErrorBalloons.clear();
+      myErrorBalloons.addAll(errors);
+    }
+    for (final WordWithPosition error : errors) {
+      if (isAfterLastCharRange(error)) {
+        // In "special" case we use last char
+        myMainTextField.underlineText(ERROR_COLOR, myMainTextField.getText().length(), myMainTextField.getText().length() + 1);
+      }
+      else {
+        myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
+      }
     }
-    return !Character.isWhitespace(myMainTextField.getText().toCharArray()[caretPosition - 1]);
+  }
+
+  /**
+   * Checks if some range is <strong>special case</strong> {@link #AFTER_LAST_CHARACTER_RANGE}.
+   *
+   * @param range range to check
+   * @return true if special case
+   */
+  private static boolean isAfterLastCharRange(@NotNull final Range<Integer> range) {
+    return AFTER_LAST_CHARACTER_RANGE.getFrom().equals(range.getFrom()) && AFTER_LAST_CHARACTER_RANGE.getTo().equals(range.getTo());
   }
 
   @Override
-  public void setBalloons(@NotNull final Collection<WordWithPosition> balloons) {
-    synchronized (myBalloons) {
-      myBalloons.clear();
-      myBalloons.addAll(balloons);
+  public final void insertTextAfterCaret(@NotNull final String text) {
+    try {
+      myMainTextField.getDocument().insertString(myMainTextField.getCaretPosition(), text, null);
+    }
+    catch (final BadLocationException e) {
+      // TODO: Display error somehow!
+      e.printStackTrace();
     }
   }
 
   @Override
-  public void setSubText(@NotNull final String subText) {
-    mySubLabel.setText(subText);
+  public final void replaceText(final int from, final int to, @NotNull final String newText) {
+    myMainTextField.select(from, to);
+    myMainTextField.replaceSelection(newText);
+    myBalloonManager.closeAllBalloons();
+    myPresenter.textChanged();
+    displayBalloonsIfRequired(); // This crunch but we need to recalculate balloons in this case (position is changed!)
+  }
+
+  @Override
+  public final int getCaretPosition() {
+    return myMainTextField.getCaretPosition();
   }
 
 
+  @Override
+  public final void configureSubTexts(@Nullable final String defaultSubText,
+                                      @NotNull final List<Range<Integer>> suggestionAvailablePlaces) {
+    synchronized (myPlacesWhereSuggestionsAvailable) {
+      myPlacesWhereSuggestionsAvailable.clear();
+      myPlacesWhereSuggestionsAvailable.addAll(suggestionAvailablePlaces);
+      myDefaultSubText = defaultSubText;
+    }
+    configureAppropriateStatus();
+  }
+
   /**
    * Reacts on keys, pressed by user
    */
@@ -281,21 +371,13 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
     @Override
     public void keyPressed(final KeyEvent e) {
       super.keyPressed(e);
+
       final int keyCode = e.getKeyCode();
       if (keyCode == KeyEvent.VK_UP) {
-        myList.moveSelection(true);
+        mySuggestionList.moveSelection(true);
       }
       else if (keyCode == KeyEvent.VK_DOWN) {
-        myList.moveSelection(false);
-      }
-      else if (keyCode == KeyEvent.VK_ENTER) {
-        myPresenter.executionRequested(myList.getValue());
-      }
-      else if (keyCode == KeyEvent.VK_TAB) {
-        myPresenter.completionRequested(myList.getValue());
-      }
-      else if ((keyCode == KeyEvent.VK_SPACE) && (e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK)) {
-        myPresenter.suggestionRequested();
+        mySuggestionList.moveSelection(false);
       }
     }
   }
@@ -306,10 +388,6 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
     return myMainTextField.getText();
   }
 
-  @Override
-  public void setPreferredWidthInChars(final int widthInChars) {
-    myMainTextField.setPreferredWidthInChars(widthInChars);
-  }
 
   /**
    * Listener for suggestion list
@@ -322,4 +400,35 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
       removeSuggestions();
     }
   }
+
+  /**
+   * Keeps tracks for baloons to close all of them in case of text inserion
+   */
+  private static final class BalloonManager extends JBPopupAdapter {
+    @NotNull
+    private final Set<Balloon> myCurrentBaloons = new HashSet<Balloon>();
+
+    void registerBalloon(final Balloon balloon) {
+      synchronized (myCurrentBaloons) {
+        myCurrentBaloons.add(balloon);
+        balloon.addListener(this);
+      }
+    }
+
+    @Override
+    public void onClosed(final LightweightWindowEvent event) {
+      synchronized (myCurrentBaloons) {
+        myCurrentBaloons.remove(event.asBalloon());
+      }
+      super.onClosed(event);
+    }
+
+    void closeAllBalloons() {
+      synchronized (myCurrentBaloons) {
+        for (final Balloon balloon : myCurrentBaloons) {
+          balloon.dispose();
+        }
+      }
+    }
+  }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java
new file mode 100644 (file)
index 0000000..8ed0c5a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Complete current command or argument" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class CompletionKeyStrokeAction extends KeyStrokeAction {
+  CompletionKeyStrokeAction() {
+    super(KeyStrokeInfo.COMPLETION);
+  }
+
+  @Override
+  public void actionPerformed(final ActionEvent e) {
+    myPresenter.completionRequested(mySuggestionList.getValue());
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java
new file mode 100644 (file)
index 0000000..7f25882
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Execute command" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class ExecutionKeyStrokeAction extends KeyStrokeAction {
+  ExecutionKeyStrokeAction() {
+    super(KeyStrokeInfo.EXECUTION);
+  }
+
+  @Override
+  public void actionPerformed(final ActionEvent e) {
+    myPresenter.executionRequested();
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java
new file mode 100644 (file)
index 0000000..a801580
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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.swingView;
+
+import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
+import com.jetbrains.python.suggestionList.SuggestionList;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.text.TextAction;
+
+/**
+ * Action that should be taken for certain {@link javax.swing.KeyStroke} (wrapped in {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}).
+ * You need to call {@link #configure(com.jetbrains.python.commandInterface.CommandInterfacePresenter, com.jetbrains.python.suggestionList.SuggestionList)}
+ * to enable one.
+ *
+ * @author Ilya.Kazakevich
+ */
+@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "NonSerializableFieldInSerializableClass"}) // Will never serialize
+abstract class KeyStrokeAction extends TextAction {
+  @NotNull
+  private final String myName;
+  @NotNull
+  private final KeyStrokeInfo myStroke;
+
+  protected CommandInterfacePresenter myPresenter;
+  protected SuggestionList mySuggestionList;
+
+  /**
+   * @param stroke key stroke to bind this info to
+   */
+  KeyStrokeAction(@NotNull final KeyStrokeInfo stroke) {
+    super(stroke.name());
+    myName = stroke.name();
+    myStroke = stroke;
+  }
+
+
+  /**
+   * Configures action.
+   *
+   * @param presenter      presenter to be used for call back.
+   * @param suggestionList list of suggestions to be used for call back
+   * @return name of this action to add to {@link javax.swing.InputMap}
+   */
+  @NotNull
+  final String configure(@NotNull final CommandInterfacePresenter presenter, @NotNull final SuggestionList suggestionList) {
+    myPresenter = presenter;
+    mySuggestionList = suggestionList;
+    return myName;
+  }
+
+  /**
+   * @return stroke bound to this action
+   */
+  @NotNull
+  final KeyStrokeInfo getStroke() {
+    return myStroke;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java
new file mode 100644 (file)
index 0000000..dcf8278
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.swingView;
+
+import com.intellij.openapi.keymap.KeymapUtil;
+import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
+import com.jetbrains.python.suggestionList.SuggestionList;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+
+/**
+ * Key strokes to be used with view.
+ * Strokes paired with action. You need to register each action via {@link javax.swing.InputMap}
+ *
+ * @author Ilya.Kazakevich
+ */
+enum KeyStrokeInfo {
+  /**
+   * "Execute command" keystroke
+   */
+  EXECUTION(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)),
+  /**
+   * "Complete current command or argument" keystroke
+   */
+  COMPLETION(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)),
+  /**
+   * "Display suggestions" keystroke.
+   */
+  SUGGESTION(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)),;
+
+  /**
+   * List of actions. Each action should be bound to some {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}
+   */
+  @NotNull
+  private static final KeyStrokeAction[] ACTIONS = {
+    new CompletionKeyStrokeAction(),
+    new ExecutionKeyStrokeAction(),
+    new SuggestionKeyStrokeAction()};
+
+  private final KeyStroke myStroke;
+
+  KeyStrokeInfo(@NotNull final KeyStroke stroke) {
+    myStroke = stroke;
+  }
+
+  /**
+   * Registers  action and binds it appropriate stroke. Call if for all instances to make all actions available.
+   *
+   * @param presenter      presenter to be used as call back
+   * @param suggestionList suggestion list to be used as call back
+   * @param source         Component with {@link javax.swing.InputMap} and {@link javax.swing.ActionMap} (swing view itself)
+   */
+  void register(@NotNull final CommandInterfacePresenter presenter,
+                @NotNull final SuggestionList suggestionList,
+                @NotNull final JComponent source) {
+    final KeyStrokeAction action = getAction();
+    final String strokeName = action.configure(presenter, suggestionList);
+    source.getInputMap().put(myStroke, strokeName);
+    source.getActionMap().put(strokeName, action);
+  }
+
+  /**
+   * @return Human-readable name of this action (to display it to user)
+   */
+  @NotNull
+  String getText() {
+    return KeymapUtil.getKeystrokeText(myStroke);
+  }
+
+
+  /**
+   * @return action paired with stroke
+   */
+  @NotNull
+  private KeyStrokeAction getAction() {
+    for (final KeyStrokeAction action : ACTIONS) {
+      if (action.getStroke() == this) {
+        return action;
+      }
+    }
+    throw new IllegalStateException("Failed to find action for " + name());
+  }
+}
index 0a247bbcec81e529e1aa9a02bb985f9e7d69ae20..2e7ff1a07352609c04f6babd3de5427cb9e7ef7c 100644 (file)
@@ -17,13 +17,9 @@ 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.*;
@@ -47,11 +43,6 @@ public class SmartTextField extends JTextField {
   @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));
@@ -66,15 +57,9 @@ public class SmartTextField extends JTextField {
     synchronized (myUnderlineInfo) {
       for (final UnderlineInfo underlineInfo : myUnderlineInfo) {
         g.setColor(underlineInfo.myColor);
+        // To prevent too long underlines: last char should really be last
         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);
-      }
     }
   }
 
@@ -90,17 +75,11 @@ public class SmartTextField extends JTextField {
     g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition);
   }
 
-  /**
-   * @return place (in px) where entered text ends.
-   */
-  int getTextEndPosition() {
-    return (getText().length() + 1) * getColumnWidth();
-  }
 
   /**
    * @return place (in px) where caret.
    */
-  int getTextCursorPosition() {
+  int getTextCaretPositionInPx() {
     return (getCaretPosition() + 1) * getColumnWidth();
   }
 
@@ -139,19 +118,9 @@ public class SmartTextField extends JTextField {
   void hideUnderline() {
     synchronized (myUnderlineInfo) {
       myUnderlineInfo.clear();
-      mySpecialUnderlinePlace = null;
     }
   }
 
-  /**
-   * Sets appropriate width in chars
-   *
-   * @param widthInChars num of chars
-   */
-  void setPreferredWidthInChars(final int widthInChars) {
-    setColumns(widthInChars);
-  }
-
   /**
    * Sets appropriate width in pixels
    *
@@ -161,18 +130,6 @@ 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
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java
new file mode 100644 (file)
index 0000000..088ff8c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Suggestion request" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class SuggestionKeyStrokeAction extends KeyStrokeAction {
+  SuggestionKeyStrokeAction() {
+    super(KeyStrokeInfo.SUGGESTION);
+  }
+
+  @Override
+  public void actionPerformed(final ActionEvent e) {
+    myPresenter.suggestionRequested();
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java
new file mode 100644 (file)
index 0000000..a2a9756
--- /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.commandLineParser;
+
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Command line parse 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 {
+  @NotNull
+  private final List<Pair<CommandLinePartType, WordWithPosition>> myParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
+  @NotNull
+  private final WordWithPosition myCommand;
+
+  CommandLineParseResult(
+    @NotNull final WordWithPosition command,
+    @NotNull final Collection<Pair<CommandLinePartType, WordWithPosition>> parts) {
+    myCommand = command;
+    myParts.addAll(parts);
+  }
+
+  /**
+   * @return command (i.e. "startapp" in "startapp my_app")
+   */
+  @NotNull
+  public WordWithPosition getCommand() {
+    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<Pair<CommandLinePartType, WordWithPosition>> 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()
+   */
+  @NotNull
+  public Collection<WordWithPosition> getPartsNoType() {
+    final Collection<WordWithPosition> result = new ArrayList<WordWithPosition>();
+    for (final Pair<CommandLinePartType, WordWithPosition> part : myParts) {
+      result.add(part.second);
+    }
+    return result;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java
new file mode 100644 (file)
index 0000000..b3813d8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 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.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface CommandLineParser {
+  /**
+   * @param commandLineParts command line splitted into words.
+   * @return command line information
+   * @throws MalformedCommandLineException in case of bad commandline
+   */
+  @NotNull
+  CommandLineParseResult parse(@NotNull List<WordWithPosition> commandLineParts) throws MalformedCommandLineException;
+}
diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java b/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java
new file mode 100644 (file)
index 0000000..0f2359e
--- /dev/null
@@ -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;
+
+/**
+ * Types of command line parts.
+ *
+ * @author Ilya.Kazakevich
+ */
+public enum CommandLinePartType {
+  /**
+   * Argument (or positional, or unnamed argument) something that has only value. Like "my_folder" in "rm my_folder"
+   */
+  ARGUMENT,
+  /**
+   * Option is named but optional parameter. Like "-l" in "ls -l".
+   */
+  OPTION,
+  /**
+   * Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand
+   */
+  UNKNOWN
+}
similarity index 94%
rename from python/src/com/jetbrains/python/optParse/MalformedCommandLineException.java
rename to python/src/com/jetbrains/python/commandLineParser/MalformedCommandLineException.java
index 3e218523036036c1f183513f9cc871c2a91588fd..8f69e96bf96edd0b784b3339b45ffefc6f705335 100644 (file)
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.optParse;
+package com.jetbrains.python.commandLineParser;
 
 import org.jetbrains.annotations.NotNull;
 
similarity index 56%
rename from python/src/com/jetbrains/python/optParse/ParsedCommandLine.java
rename to python/src/com/jetbrains/python/commandLineParser/OptParseCommandLineParser.java
index f4cab570ee452a462fab580d448d6eddb670d601..ddcd0822e53c7eebb409ce91dfa9d2448ba170e5 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.optParse;
+package com.jetbrains.python.commandLineParser;
 
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
 
 // TODO: Support options and their arguments
 
@@ -36,23 +41,17 @@ import java.util.*;
  *
  * @author Ilya.Kazakevich
  */
-public final class ParsedCommandLine {
+public final class OptParseCommandLineParser implements CommandLineParser {
   @NotNull
-  private final WordWithPosition myCommand;
-  @NotNull
-  private final List<WordWithPosition> myArguments = new ArrayList<WordWithPosition>();
-
-  /**
-   * @param commandLine command line to parse
-   * @throws MalformedCommandLineException if malformed commandline can't be parsed
-   */
-  public ParsedCommandLine(@NotNull final String commandLine) throws MalformedCommandLineException {
-    final Deque<WordWithPosition> parts = new LinkedList<WordWithPosition>(WordWithPosition.splitText(commandLine));
+  @Override
+  public CommandLineParseResult parse(@NotNull final List<WordWithPosition> commandLineParts) throws MalformedCommandLineException {
+    final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(commandLineParts);
     if (parts.isEmpty()) {
       throw new MalformedCommandLineException("No command provided");
     }
-    myCommand = parts.pop();
-    if (myCommand.getText().startsWith("-")) {
+    final WordWithPosition command = parts.pop();
+    final List<Pair<CommandLinePartType, WordWithPosition>> resultParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
+    if (command.getText().startsWith("-")) {
       throw new MalformedCommandLineException("Command can't start with option prefix");
     }
 
@@ -60,46 +59,13 @@ public final class ParsedCommandLine {
     for (final WordWithPosition part : parts) {
       if (part.getText().startsWith("-")) {
         // This is option!
+        resultParts.add(Pair.create(CommandLinePartType.OPTION, part));
       }
       else {
         // TODO: Check optopn argument!
-        myArguments.add(part);
+        resultParts.add(Pair.create(CommandLinePartType.ARGUMENT, part));
       }
     }
+    return new CommandLineParseResult(command, resultParts);
   }
-
-
-  /**
-   * @return command (i.e. "startapp" in "startapp my_app")
-   */
-  @NotNull
-  public WordWithPosition getCommand() {
-    return myCommand;
-  }
-
-  /**
-   * @return all arguments (not options or option arguments!)
-   */
-  @NotNull
-  public List<WordWithPosition> getArguments() {
-    return Collections.unmodifiableList(myArguments);
-  }
-
-  /**
-   * @return all parts for command line as simple words
-   */
-  @NotNull
-  public List<String> getAsWords() {
-    final List<String> result = new ArrayList<String>();
-
-    result.add(myCommand.getText());
-
-    for (final WordWithPosition argument : myArguments) {
-      result.add(argument.getText());
-    }
-    // TODO: Add options as well
-    return result;
-  }
-
-  // TODO: Add options, arguments and option arguments
 }
diff --git a/python/src/com/jetbrains/python/commandLineParser/package-info.java b/python/src/com/jetbrains/python/commandLineParser/package-info.java
new file mode 100644 (file)
index 0000000..bbdd192
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Engine to parse command-line.
+ * Command line consists of command itself,  {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT arguments}
+ * and {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION options}.
+ * Use need to split command line into {@link com.jetbrains.python.WordWithPosition chunks}, pass them to
+ * {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLineParseResult}.
+ *
+ * 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/optParse/package-info.java b/python/src/com/jetbrains/python/optParse/package-info.java
deleted file mode 100644 (file)
index 3be946b..0000000
+++ /dev/null
@@ -1,32 +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.
- */
-
-/**
- * <p>
- * An <a href="https://docs.python.org/2/library/optparse.html">optparse</a> module twin, that parses command line.
- * Unlike any other GNU/Posix parsers, it knows how to:</p>
- * <ol>
- *   <li>Parse options and args with out of any knowledge about required args</li>
- *   <li>Provide actual places in command line where exactly such args or opts exist./li>
- * </ol>
- * <p>
- * Be sure to read optparse manual
- * (epecially <a href="https://docs.python.org/2/library/optparse.html#terminology">terminology</a>) part.
- * </p>
- * <p>Package entry point is {@link com.jetbrains.python.optParse.ParsedCommandLine}</p>
- * @author Ilya.Kazakevich
- */
-package com.jetbrains.python.optParse;
\ No newline at end of file
index c43f23809806afdb93762bb0f0984f86cfe0975f..d209235bec74c551a9ad75bc62c6c1504210b61b 100644 (file)
@@ -166,7 +166,14 @@ public class SuggestionList {
       return null; // Nothing is selected if list is invisible
     }
     final Object value = myList.getSelectedValue();
-    return ((value == null) ? "" : getElement(value).mySuggestion.getText());
+    return ((value == null) ? null : getElement(value).mySuggestion.getText());
+  }
+
+  /**
+   * @return true if no suggestion list is displayed now.
+   */
+  public final synchronized boolean isClosed() {
+    return myListPopUp == null || myListPopUp.isDisposed();
   }
 
   /**