PY-11855 Run manage.py task improvements
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 16 Feb 2015 22:04:34 +0000 (01:04 +0300)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 16 Feb 2015 22:04:34 +0000 (01:04 +0300)
Refactored, options support added

36 files changed:
python/src/com/jetbrains/python/PyBundle.properties
python/src/com/jetbrains/python/WordWithPosition.java
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java [deleted file]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java [deleted file]
python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java [deleted file]
python/src/com/jetbrains/python/commandInterface/command/Command.java
python/src/com/jetbrains/python/commandInterface/command/Option.java
python/src/com/jetbrains/python/commandInterface/command/OptionTypedArgumentInfo.java
python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java [deleted file]
python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/package-info.java [moved from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java with 81% similarity]
python/src/com/jetbrains/python/commandInterface/package-info.java
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/SuggestionInfo.java [moved from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java with 86% similarity]
python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLine.java [moved from python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java with 58% similarity]
python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java
python/src/com/jetbrains/python/commandLineParser/CommandLinePart.java [moved from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java with 51% similarity]
python/src/com/jetbrains/python/commandLineParser/CommandLinePartVisitor.java [moved from python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java with 57% similarity]
python/src/com/jetbrains/python/commandLineParser/optParse/LongOptionParser.java
python/src/com/jetbrains/python/commandLineParser/optParse/OptParseCommandLineParser.java
python/src/com/jetbrains/python/commandLineParser/optParse/OptionParser.java
python/src/com/jetbrains/python/commandLineParser/optParse/OptionParserRegexBased.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandLineParser/optParse/ShortOptionParser.java
python/src/com/jetbrains/python/commandLineParser/package-info.java
python/src/com/jetbrains/python/suggestionList/SuggestionList.java

index 08238498648c53bba55ad63b0fe7016d88b9ee8a..90e83cf3c1cf3bcdcc9b0863d484d297c1b7a7a4 100644 (file)
@@ -856,8 +856,11 @@ custom.type.mimic.name=Dynamic class based on {0}
 # Values for command argument value validation
 commandLine.validation.badCommand=Unknown command
 commandLine.validation.argMissing=Required argument value is missing
+commandLine.validation.optArgMissing=Option argument value is missing
 commandLine.validation.argBadValue=Argument can't have this value
 commandLine.validation.excessArg=Excess argument value
+commandLine.validation.badOption=Bad option or option already set
+commandLine.validation.noArgAllowed=No argument allowed for this option
 # And for labels
 commandLine.subText.key.complete=Use {0} to complete selected variant
 commandLine.subText.key.suggestions=Use {0} to view available values
index 35f10c3a2e810b4d505fe6fcee9f16137352c86a..0c8043b53a8728d79b547139d5574cb654ebf4d6 100644 (file)
@@ -107,8 +107,8 @@ public final class WordWithPosition extends Range<Integer> {
 
     WordWithPosition position = (WordWithPosition)o;
 
-    if (getFrom() != position.getFrom()) return false;
-    if (getTo() != position.getTo()) return false;
+    if (!getFrom().equals(position.getFrom())) return false;
+    if (!getTo().equals(position.getTo())) return false;
     if (!myWord.equals(position.myWord)) return false;
 
     return true;
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java
deleted file mode 100644 (file)
index 92a6910..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
-
-import com.jetbrains.python.WordWithPosition;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Chunk and its info pair. Chunk may be null, while chunk info always present.
- * @author Ilya.Kazakevich
- */
-final class ChunkAndInfo implements Comparable<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/ChunkDriverBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java
deleted file mode 100644 (file)
index 946b271..0000000
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
-
-import com.intellij.util.Range;
-import com.jetbrains.python.WordWithPosition;
-import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
-import com.jetbrains.python.commandInterface.CommandInterfaceView;
-import com.jetbrains.python.suggestionList.SuggestionsBuilder;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.*;
-
-// TODO: Test
-
-/**
- * Presenter that uses {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} to parse pack of chunks
- * to obtain {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo}.
- * Each chunk may be paired with certain chunk info. Such info tells presenter whether this chunk has info, error, suggestions and so on.
- * If caret situated far from chunks, then next neariest chunk should be found (see {@link #findNearestChunkAndInfo()}.
- *
- *
- * @author Ilya.Kazakevich
- * @see com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo
- */
-public final class ChunkDriverBasedPresenter extends CommandInterfacePresenterAdapter {
-
-  @NotNull
-  private final ChunkDriver myChunkDriver;
-  @NotNull
-  private final SortedSet<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
deleted file mode 100644 (file)
index 4397751..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
-
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Information about certain place in text, provided by chunk driver.
- *
- * @author Ilya.Kazakevich
- */
-public final class ChunkInfo {
-  @Nullable
-  private final String myInfoBalloon;
-  @Nullable
-  private final String myError;
-  @Nullable
-  private final SuggestionInfo mySuggestions;
-
-
-  public ChunkInfo(@Nullable final String infoBalloon,
-                   @Nullable final String error) {
-    this(infoBalloon, error, null);
-  }
-
-
-  /**
-   *
-   * @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
-   * @param error Error balloon to display when caret meets this place and underline text as error (null if no error)
-   * @param suggestions list of suggestions available in this place (if any)
-   */
-  public ChunkInfo(@Nullable final String infoBalloon,
-                   @Nullable final String error,
-                   @Nullable final SuggestionInfo suggestions) {
-    myInfoBalloon = infoBalloon;
-    myError = error;
-    mySuggestions = suggestions;
-  }
-
-
-  /**
-   *
-   * @return Info balloon to display when caret meets this place (null if display nothing)
-   */
-  @Nullable
-  public String getInfoBalloon() {
-    return myInfoBalloon;
-  }
-
-  /**
-   *
-   * @return Error balloon to display when caret meets this place and underline text as error (null if no error)
-   */
-  @Nullable
-  public String getError() {
-    return myError;
-  }
-
-  /**
-   *
-   * @return list of suggestions available in this place (if any)
-   */
-  @Nullable
-  public SuggestionInfo getSuggestions() {
-    return mySuggestions;
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java
deleted file mode 100644 (file)
index 005c4da..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Pack of {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo} for certain chunks and other parsing info.
- *
- * @author Ilya.Kazakevich
- */
-public final class ParseInfo {
-  @Nullable
-  private final String myStatusText;
-  @NotNull
-  private final List<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/package-info.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java
deleted file mode 100644 (file)
index 9281bc7..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <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
index 2d15d152c3c1a018dfa32694dcf0f79ccba8052e..295e4a96dc4dfddbfd42ee3b427e0e2adff167a9 100644 (file)
@@ -16,7 +16,6 @@
 package com.jetbrains.python.commandInterface.command;
 
 import com.intellij.openapi.module.Module;
-import com.jetbrains.python.commandLineParser.CommandLineParseResult;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -59,7 +58,7 @@ public interface Command {
    * Execute command
    *
    * @param module      module to execute command against
-   * @param commandLine command's command line
+   * @param parameters command's arguments and options (just like entered by user but splitted by space)
    */
-  void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine);
+  void execute(@NotNull final Module module, @NotNull final List<String> parameters);
 }
index 0099e8c7b2295deedb1a8abeeb4e2c92d74a0515..f362eda7af1fdc4fe5db8a7f6698ce6823ebb992 100644 (file)
@@ -26,7 +26,9 @@ import java.util.Collections;
 import java.util.List;
 
 /**
- * Command option
+ * Command option.
+ * It may have some long names (like --foo) and short (like -f), help text and arguments (if not flag option)
+ *
  * @author Ilya.Kazakevich
  */
 public final class Option {
@@ -40,17 +42,17 @@ public final class Option {
   private final String myHelp;
 
   /**
-   *
    * @param argumentAndQuantity if option accepts argument, there should be pair of [argument_quantity, its_type_info]
-   * @param help option help
-   * @param shortNames option short names
-   * @param longNames option long names
+   * @param help                option help
+   * @param shortNames          option short names
+   * @param longNames           option long names
    */
   public Option(@Nullable final Pair<Integer, OptionArgumentInfo> argumentAndQuantity,
                 @NotNull final String help,
                 @NotNull final Collection<String> shortNames,
                 @NotNull final Collection<String> longNames) {
-    Preconditions.checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity);
+    Preconditions
+      .checkArgument(argumentAndQuantity == null || argumentAndQuantity.first > 0, "Illegal args and quantity: " + argumentAndQuantity);
     myArgumentAndQuantity = argumentAndQuantity;
     myShortNames.addAll(shortNames);
     myLongNames.addAll(longNames);
@@ -65,6 +67,16 @@ public final class Option {
     return Collections.unmodifiableList(myLongNames);
   }
 
+  /**
+   * @return all option names (long and short)
+   */
+  @NotNull
+  public List<String> getAllNames() {
+    final List<String> result = new ArrayList<String>(myLongNames);
+    result.addAll(myShortNames);
+    return result;
+  }
+
   /**
    * @return Option short names
    */
@@ -74,8 +86,7 @@ public final class Option {
   }
 
   /**
-   *
-   * @return  if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise.
+   * @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise.
    */
   @Nullable
   public Pair<Integer, OptionArgumentInfo> getArgumentAndQuantity() {
index 06bb72296321092df9a5d009952fca4203b307a6..42cadf0c5c819dbdb3eb95ae5bdfb2fac25da428 100644 (file)
@@ -24,7 +24,7 @@ import java.util.List;
  * For options, whose argument is based on certain type.
  *
  * @author Ilya.Kazakevich
- * @see com.jetbrains.python.commandInterface.command.OptionArgumentType
+ * @see OptionArgumentType
  */
 public final class OptionTypedArgumentInfo implements OptionArgumentInfo {
   @NotNull
@@ -42,7 +42,7 @@ public final class OptionTypedArgumentInfo implements OptionArgumentInfo {
     // We only check integer for now
     if (myType == OptionArgumentType.INTEGER) {
       try {
-        // We just parse it to get exception
+        // We just getCommandLineInfo it to get exception
         //noinspection ResultOfMethodCallIgnored
         Integer.parseInt(value);
       }
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java
deleted file mode 100644 (file)
index 2626692..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
-
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.WordWithPosition;
-import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver;
-import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo;
-import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ParseInfo;
-import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.SuggestionInfo;
-import com.jetbrains.python.commandInterface.command.Argument;
-import com.jetbrains.python.commandInterface.command.ArgumentsInfo;
-import com.jetbrains.python.commandInterface.command.Command;
-import com.jetbrains.python.commandLineParser.CommandLineParseResult;
-import com.jetbrains.python.commandLineParser.CommandLineParser;
-import com.jetbrains.python.commandLineParser.CommandLinePartType;
-import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.*;
-
-/**
- * Chunk driver that uses pack of commands.
- *
- * @author Ilya.Kazakevich
- */
-public final class CommandBasedChunkDriver implements ChunkDriver {
-  @NotNull
-  private final CommandLineParser myCommandLineParser;
-  @NotNull
-  private final Map<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);
-    }
-  }
-}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandBasedRangeInfoDriver.java
new file mode 100644 (file)
index 0000000..a35fd4b
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Range;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.commandInterface.command.Argument;
+import com.jetbrains.python.commandInterface.command.Command;
+import com.jetbrains.python.commandInterface.command.Option;
+import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
+import com.jetbrains.python.commandLineParser.CommandLine;
+import com.jetbrains.python.commandLineParser.CommandLineParser;
+import com.jetbrains.python.commandLineParser.CommandLinePart;
+import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * Driver that returns pack of range infos for certain command line
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class CommandBasedRangeInfoDriver implements RangeInfoDriver {
+  @NotNull
+  private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
+  @NotNull
+  private final Module myModule;
+  @NotNull
+  private final CommandLineParser myCommandLineParser;
+
+  /**
+   *
+   * @param module module (to be used in execution)
+   * @param commandLineParser parser to parse command lines
+   * @param commands available commands
+   */
+  public CommandBasedRangeInfoDriver(
+    @NotNull final Module module,
+    @NotNull final CommandLineParser commandLineParser,
+    @NotNull final Collection<? extends Command> commands) {
+    for (final Command command : commands) {
+      myCommands.put(command.getName(), command);
+    }
+    myModule = module;
+    myCommandLineParser = commandLineParser;
+  }
+
+  @NotNull
+  @Override
+  public Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull final String commandLineText) {
+    // TODO: Copty/paste with exceptiojn
+    if (StringUtil.isEmpty(commandLineText)) {
+      final RangeInfo info = new RangeInfo(null, "", new SuggestionInfo(
+        true, true, myCommands.keySet()
+      ), RangeInfo.TERMINATION_RANGE, false
+      );
+      return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(info));
+    }
+    final CommandLine commandLine;
+    try {
+      commandLine = myCommandLineParser.parse(commandLineText);
+    }
+    catch (final MalformedCommandLineException ignored) {
+      return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(
+        new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(
+          false, true, myCommands.keySet()
+        ), new Range<Integer>(0, commandLineText.length()), false)
+      ));
+    }
+
+    final Command command = getExistingCommand(commandLine);
+    final List<CommandLinePart> commandLineParts = commandLine.getParts();
+    if (command == null) {
+      // Bad command inserted
+      return createBadCommandInfo(commandLine);
+    }
+
+    final UnusedOptionsCollector unusedOptionsCollector = UnusedOptionsCollector.create(command, commandLineParts);
+    final RangeInfoCollector rangeInfoCollector = RangeInfoCollector.create(command, commandLineParts, unusedOptionsCollector);
+
+
+
+    final SuggestionInfo commandSuggestions = new SuggestionInfo(false, true, myCommands.keySet());
+    // Add command as first range info
+    final List<RangeInfo> rangeInfos =
+      new ArrayList<RangeInfo>(Collections.singletonList(new RangeInfo(null, null, commandSuggestions, commandLine.getCommand(), false)));
+    // Then add collected infos
+    rangeInfos.addAll(rangeInfoCollector.getRangeInfos());
+
+
+    /////// What about "after the caret" ?
+    final Pair<Boolean, Argument> unsatisfiedArgument = rangeInfoCollector.getUnsatisfiedPositionalArgument();
+    final OptionArgumentInfo unsatisfiedOptionArgument = rangeInfoCollector.getUnsatisfiedOptionArgument();
+
+
+    // TODO: Move to collector after test
+    if (unsatisfiedOptionArgument != null) {
+      final List<String> availableValues = unsatisfiedOptionArgument.getAvailableValues();
+      final SuggestionInfo suggestionInfo;
+      if (availableValues != null) {
+
+        suggestionInfo =
+          new SuggestionInfo(false, false, availableValues);
+      }
+      else {
+        suggestionInfo = null;
+      }
+      rangeInfos
+        .add(new RangeInfo(null, PyBundle.message("commandLine.validation.optArgMissing"), suggestionInfo, RangeInfo.TERMINATION_RANGE,
+                           false));
+    }
+    else if (unsatisfiedArgument != null) {
+      final boolean required = unsatisfiedArgument.first;
+      final Argument argument = unsatisfiedArgument.second;
+      // Only add error if required
+      final String error = required ? PyBundle.message("commandLine.validation.argMissing") : null;
+      final List<String> availableValues = unusedOptionsCollector.addUnusedOptions(argument.getAvailableValues());
+      final RangeInfo lastArgInfo =
+        new RangeInfo(argument.getHelpText(), error,
+                      (availableValues != null ? new SuggestionInfo(false, false, availableValues) : null), RangeInfo.TERMINATION_RANGE,
+                      false);
+      rangeInfos.add(lastArgInfo);
+    }
+    else {
+      // Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
+      // This is a hack, but with out of it last range info will always be used, even 200 chars after last place
+      rangeInfos.add(new RangeInfo(null, null, rangeInfoCollector.getCurrentSuggestions(false, null), RangeInfo.TERMINATION_RANGE, false));
+    }
+
+    assert rangeInfos.size() >= commandLineParts.size() : "Contract broken: not enough chunks";
+    return Pair.<Executor, List<RangeInfo>>create(new CommandExecutor(command, myModule, commandLine.getPartsAsText()), rangeInfos);
+  }
+
+  @Nullable
+  private Command getExistingCommand(@NotNull final CommandLine commandLine) {
+    return myCommands.get(commandLine.getCommand().getText());
+  }
+
+
+  /**
+   * Creates range info info signaling command is bad or junk
+   *
+   *
+   * @param commandLine command line passed by user
+   * @return  info to return
+   */
+  @NotNull
+  private Pair<Executor, List<RangeInfo>> createBadCommandInfo(final CommandLine commandLine) {
+    final List<RangeInfo> result = new ArrayList<RangeInfo>();
+    // We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
+    // How ever, we must say something
+    result
+      .add(new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()),
+                         commandLine.getCommand(), false));
+    // Command is unknown, all other parts are junk
+    for (final CommandLinePart part : commandLine.getParts()) {
+      result.add(new RangeInfo(null, "", false, part.getWord()));
+    }
+
+
+    return Pair.create(null, result);
+  }
+
+
+  /**
+   * Finds option by its name
+   * @param command current command
+   * @param optionName option name
+   * @return option or null if no option found
+   */
+  @Nullable
+  static Option findOptionByName(@NotNull final Command command, @NotNull final String optionName) {
+    for (final Option option : command.getOptions()) {
+      for (final String name : option.getAllNames()) {
+        if (name.equals(optionName)) {
+          return option;
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/CommandExecutor.java
new file mode 100644 (file)
index 0000000..992f00c
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.ArrayUtil;
+import com.jetbrains.python.commandInterface.command.Command;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Arrays;
+
+/**
+ * Executes commands according to {@link Executor} contract
+ *
+ * @author Ilya.Kazakevich
+ */
+final class CommandExecutor implements Executor {
+  @NotNull
+  private final Command myCommand;
+  @NotNull
+  private final Module myModule;
+  @NotNull
+  private final String[] myArguments;
+
+  /**
+   * @param command       command to execute
+   * @param module        module to execute against
+   * @param argumentsLine all command arguments as testline
+   */
+  CommandExecutor(@NotNull final Command command, @NotNull final Module module, @NotNull final String argumentsLine) {
+    myCommand = command;
+    myModule = module;
+    myArguments = (StringUtil.isEmpty(argumentsLine) ? ArrayUtil.EMPTY_STRING_ARRAY : argumentsLine.split(" "));
+  }
+
+  @Nullable
+  @Override
+  public String getExecutionDescription() {
+    return myCommand.getHelp();
+  }
+
+  @Override
+  public void execute() {
+    myCommand.execute(myModule, Arrays.asList(myArguments));
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/RangeInfoCollector.java
new file mode 100644 (file)
index 0000000..cf2f92e
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
+
+import com.google.common.collect.Sets;
+import com.intellij.openapi.util.Pair;
+import com.intellij.util.containers.HashSet;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.WordWithPosition;
+import com.jetbrains.python.commandInterface.command.Argument;
+import com.jetbrains.python.commandInterface.command.Command;
+import com.jetbrains.python.commandInterface.command.Option;
+import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
+import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
+import com.jetbrains.python.commandLineParser.CommandLineArgument;
+import com.jetbrains.python.commandLineParser.CommandLineOption;
+import com.jetbrains.python.commandLineParser.CommandLinePart;
+import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * Visitor that collections ranges info by visiting options and arguments
+ *
+ * @author Ilya.Kazakevich
+ */
+final class RangeInfoCollector implements CommandLinePartVisitor {
+  /**
+   * Options already met in command line. It has nothing to do with {@link UnusedOptionsCollector}!
+   */
+  @NotNull
+  private final Set<String> myUsedOptions = new HashSet<String>();
+  @NotNull
+  private final List<RangeInfo> myRangeInfos = new ArrayList<RangeInfo>();
+  @NotNull
+  private final Command myCommand;
+
+  private int myNumOfProcessedPositionalArguments = 0;
+  private boolean mySkipNextArgument;
+  private Pair<Integer, OptionArgumentInfo> myExpectedOptionArgument;
+  @NotNull
+  private final UnusedOptionsCollector myUnusedOptionsCollector;
+
+  /**
+   * @param command                command
+   * @param unusedOptionsCollector instance of unused options collector
+   */
+  private RangeInfoCollector(@NotNull final Command command,
+                             @NotNull final UnusedOptionsCollector unusedOptionsCollector) {
+    myCommand = command;
+    myUnusedOptionsCollector = unusedOptionsCollector;
+  }
+
+  @NotNull
+  static RangeInfoCollector create(@NotNull final Command command,
+                                   @NotNull final Iterable<CommandLinePart> commandLineParts,
+                                   @NotNull final UnusedOptionsCollector unusedOptionsCollector) {
+    final RangeInfoCollector infoCollector = new RangeInfoCollector(command, unusedOptionsCollector);
+    for (final CommandLinePart part : commandLineParts) {
+      part.accept(infoCollector);
+    }
+    return infoCollector;
+  }
+
+  @Override
+  public void visitOption(@NotNull final CommandLineOption option) {
+    if (processOptionArgument(option.getWord())) {
+      return;
+    }
+
+
+    final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
+    final WordWithPosition attachedArgument = option.getAttachedArgument();
+    if (commandOption == null) {
+      // There is no such option
+      // To Mark attached arg so we skip it in {@link #visitArgument}
+      if (attachedArgument != null) {
+        mySkipNextArgument = true;
+      }
+      // No such option
+      myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badOption"), getCurrentSuggestions(false, null),
+                                     option.getWord(), false));
+      return;
+    }
+
+
+    final Pair<Integer, OptionArgumentInfo> argumentAndQuantity = commandOption.getArgumentAndQuantity();
+
+
+    // If option already used, then mark it
+    final String optionAlreadyUsed = Sets.intersection(Sets.newHashSet(commandOption.getAllNames()), myUsedOptions).isEmpty()
+                                     ? null
+                                     : PyBundle.message("commandLine.validation.badOption");
+    myRangeInfos
+      .add(new RangeInfo(commandOption.getHelp(), optionAlreadyUsed, getCurrentSuggestions(false, null), option.getWord(),
+                         argumentAndQuantity != null));
+
+    // remove from existing options
+    myUsedOptions.addAll(commandOption.getAllNames());
+
+
+    if (argumentAndQuantity == null) {
+      if (attachedArgument != null) {
+        myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.noArgAllowed"), false, attachedArgument));
+        mySkipNextArgument = true;
+      }
+    }
+    else {
+      // Some option args required
+      myExpectedOptionArgument = argumentAndQuantity;
+    }
+  }
+
+  /**
+   * Process option argument (not to be confused with positional!)
+   *
+   * @param currentPart part with argument.
+   * @return false if there should not be any option argument so this method did nothing. True if there should be and it is processed.
+   */
+  private boolean processOptionArgument(@NotNull final WordWithPosition currentPart) {
+    if (myExpectedOptionArgument == null) {
+      return false;
+    }
+    final OptionArgumentInfo argumentInfo = myExpectedOptionArgument.second;
+    int argsLeft = myExpectedOptionArgument.first;
+
+    final boolean valid = argumentInfo.isValid(currentPart.getText());
+    final List<String> availableValues = argumentInfo.getAvailableValues();
+    final SuggestionInfo suggestions;
+    if (availableValues != null) {
+      suggestions = new SuggestionInfo(false, false, availableValues);
+    }
+    else {
+      suggestions = null;
+    }
+
+    myRangeInfos.add(new RangeInfo(null, (valid ? null : PyBundle.message("commandLine.validation.argBadValue")),
+                                   suggestions, currentPart, false));
+
+
+    if (--argsLeft == 0) {
+      myExpectedOptionArgument = null;
+    }
+    else {
+      myExpectedOptionArgument = Pair.create(argsLeft, argumentInfo);
+    }
+    return true;
+  }
+
+
+  /**
+   * Returns suggestions available for current position.
+   *
+   * @param showAutomatically make suggestions displayed automatically
+   * @param argumentPair      argument to use. If null, current argument according to internal counter would be used.
+   * @return suggestiom info or null if no suggestion available
+   */
+  @Nullable
+  SuggestionInfo getCurrentSuggestions(final boolean showAutomatically, @Nullable Pair<Boolean, Argument> argumentPair) {
+    if (argumentPair == null) {
+      argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
+    }
+
+    final List<String> suggestions = argumentPair != null ? myUnusedOptionsCollector.addUnusedOptions(
+      argumentPair.second.getAvailableValues()) : myUnusedOptionsCollector.addUnusedOptions(
+      null);
+    if (suggestions == null) {
+      return null;
+    }
+    return new SuggestionInfo(showAutomatically, false, suggestions);
+  }
+
+  @Override
+  public void visitArgument(@NotNull final CommandLineArgument argument) {
+    if (mySkipNextArgument) {
+      // Skip argument, clear flag and do nothing
+      mySkipNextArgument = false;
+      return;
+    }
+
+    if (processOptionArgument(argument.getWord())) {
+      return;
+    }
+
+    final Pair<Boolean, Argument> argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments++);
+    if (argumentPair == null) {
+      //Exceed!
+      myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.excessArg"), false, argument.getWord()));
+      return;
+    }
+    final Argument commandArgument = argumentPair.second;
+
+    final List<String> argumentAvailableValues = commandArgument.getAvailableValues();
+    final String argumentValue = argument.getWord().getText();
+    String errorMessage = null;
+    if (argumentAvailableValues != null && !argumentAvailableValues.contains(argumentValue)) {
+      // Bad value
+      errorMessage = PyBundle.message("commandLine.validation.argBadValue");
+    }
+    // Argument  seems to be ok. We suggest values automatically only if value is bad
+    myRangeInfos.add(new RangeInfo(commandArgument.getHelpText(), errorMessage,
+                                   getCurrentSuggestions(errorMessage != null, argumentPair),
+                                   argument.getWord(), false));
+  }
+
+  /**
+   * @return calculates range infos for all command line parts (not the command itself!)
+   */
+  @NotNull
+  Collection<RangeInfo> getRangeInfos() {
+    return Collections.unmodifiableList(myRangeInfos);
+  }
+
+
+  /**
+   * @return Unsatisfied (currently expected) positional argument ([required, arg]) or null if no arg expected
+   */
+  @Nullable
+  Pair<Boolean, Argument> getUnsatisfiedPositionalArgument() {
+    return myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
+  }
+
+  /**
+   * @return Unsatisfied (currently expected) option argument or null if no option argument expected
+   */
+  public OptionArgumentInfo getUnsatisfiedOptionArgument() {
+    if (myExpectedOptionArgument != null) {
+      return myExpectedOptionArgument.second;
+    }
+    else {
+      return null;
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java b/python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/UnusedOptionsCollector.java
new file mode 100644 (file)
index 0000000..a72030a
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
+
+import com.intellij.util.containers.HashSet;
+import com.jetbrains.python.commandInterface.command.Command;
+import com.jetbrains.python.commandInterface.command.Option;
+import com.jetbrains.python.commandLineParser.CommandLineArgument;
+import com.jetbrains.python.commandLineParser.CommandLineOption;
+import com.jetbrains.python.commandLineParser.CommandLinePart;
+import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Collections all used and unused options to report unused options then.
+ * It may be used to add unused options to suggestion list {@link #addUnusedOptions(Collection)}
+ *
+ * @author Ilya.Kazakevich
+ */
+final class UnusedOptionsCollector implements CommandLinePartVisitor {
+  @NotNull
+  private final Set<String> myUnusedOptions = new HashSet<String>();
+  @NotNull
+  private final Command myCommand;
+
+  /**
+   * @param command          command to be used
+   * @param commandLineParts command line parts
+   * @return instance
+   */
+  static UnusedOptionsCollector create(@NotNull final Command command, @NotNull final Iterable<CommandLinePart> commandLineParts) {
+    final UnusedOptionsCollector collector = new UnusedOptionsCollector(command);
+    for (final CommandLinePart part : commandLineParts) {
+      part.accept(collector);
+    }
+    return collector;
+  }
+
+  private UnusedOptionsCollector(@NotNull final Command command) {
+    for (final Option option : command.getOptions()) {
+      myUnusedOptions.addAll(option.getAllNames());
+    }
+    myCommand = command;
+  }
+
+  @Override
+  public void visitOption(@NotNull final CommandLineOption option) {
+    final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
+    if (commandOption != null) {
+      myUnusedOptions.removeAll(commandOption.getAllNames());
+    }
+  }
+
+  /**
+   * Merges list of unused options and other values provided as argument.
+   *
+   * @param mainValues values to merge options with. May be null.
+   * @return null of no options and no values provided or merged list of values and options. See method usages for more info
+   */
+  @Nullable
+  List<String> addUnusedOptions(@Nullable final Collection<String> mainValues) {
+    if (mainValues == null && myUnusedOptions.isEmpty()) {
+      return null;
+    }
+    final List<String> result = new ArrayList<String>(myUnusedOptions);
+
+    if (mainValues != null) {
+      result.addAll(mainValues);
+    }
+    return (result.isEmpty() ? null : result);
+  }
+
+
+  @Override
+  public void visitArgument(@NotNull final CommandLineArgument argument) {
+
+  }
+}
similarity index 81%
rename from python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java
rename to python/src/com/jetbrains/python/commandInterface/commandBasedRangeDriver/package-info.java
index 20677dfe0d27077006130a83d6c0b9de38204a16..278f48371b70e6bc2de6d8c70ce11861f682c1fa 100644 (file)
  */
 
 /**
- * {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of
+ * {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} implementation based on idea of
  * {@link com.jetbrains.python.commandInterface.command command, option and argument}.
  *
- *  See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point.
+ *  See {@link com.jetbrains.python.commandInterface.commandBasedRangeDriver.CommandBasedRangeInfoDriver} as entry point.
  *  It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
  *  provided by user
  *
@@ -26,4 +26,4 @@
  * @see com.jetbrains.python.commandInterface.command.Command
  * @author Ilya.Kazakevich
  */
-package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
\ No newline at end of file
+package com.jetbrains.python.commandInterface.commandBasedRangeDriver;
\ No newline at end of file
index 7f1581af3bc7f80cc1fd507fee8be30b5c8918ce..d96540555e52111ea24f65d91e7eed5b27788568 100644 (file)
@@ -31,7 +31,7 @@
  *
  * <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.chunkDriverBasedPresenter}
+ *   and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter}
  * </p>
  *
  * <p>
diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/Executor.java
new file mode 100644 (file)
index 0000000..41ed72c
--- /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.commandInterface.rangeBasedPresenter;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Engine to execute command line
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface Executor {
+  /**
+   * @return information about what to execute (like command name)
+   */
+  @Nullable
+  String getExecutionDescription();
+
+  /**
+   * Execute command!
+   */
+  void execute();
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeBasedPresenter.java
new file mode 100644 (file)
index 0000000..4ab64a4
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.rangeBasedPresenter;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.util.Range;
+import com.jetbrains.python.WordWithPosition;
+import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.suggestionList.SuggestionsBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+
+/**
+ * Presenter that uses {@link RangeInfoDriver} to obtain {@link RangeInfo info}.
+ * Such info tells presenter whether this text has info, error, suggestions and so on.
+ * If caret situated far from text, then next neariest range should be found (see {@link #findNearestRangeInfo()}.
+ *
+ * @author Ilya.Kazakevich
+ * @see RangeInfo
+ */
+public final class RangeBasedPresenter extends CommandInterfacePresenterAdapter {
+  @NotNull
+  private final RangeInfoDriver myRangeInfoDriver;
+  @Nullable
+  private Executor myExecutor;
+  private final SortedSet<RangeInfo> myRangeInfos = new TreeSet<RangeInfo>();
+
+  public RangeBasedPresenter(@NotNull final CommandInterfaceView view,
+                             @NotNull final RangeInfoDriver rangeInfoDriver) {
+    super(view);
+    myRangeInfoDriver = rangeInfoDriver;
+  }
+
+  @Override
+  public void launch() {
+    super.launch();
+    reparseText(true);
+  }
+
+  @Override
+  public void textChanged() {
+    reparseText(false);
+  }
+
+  private void reparseText(final boolean skipSuggestions) {
+
+
+    final Pair<Executor, List<RangeInfo>> rangeInfoStructure = myRangeInfoDriver.getCommandLineInfo(myView.getText());
+    myExecutor = rangeInfoStructure.first;
+    final List<RangeInfo> rangeInfos = rangeInfoStructure.second;
+
+    assert !rangeInfos.isEmpty() : "At least one chunk info should exist";
+    myRangeInfos.clear();
+    myRangeInfos.addAll(rangeInfos);
+
+
+    // configure Errors And Balloons
+
+    final Collection<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
+    final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
+
+
+    for (final RangeInfo rangeInfo : rangeInfos) {
+      final String error = rangeInfo.getError();
+      if (error != null) {
+        errorBalloons.add(new WordWithPosition(error, rangeInfo));
+      }
+      final String info = rangeInfo.getInfoBalloon();
+      if (info != null) {
+        infoBalloons.add(new WordWithPosition(info, rangeInfo));
+      }
+    }
+
+    myView.setInfoAndErrors(infoBalloons, errorBalloons);
+
+
+    if (!skipSuggestions) {
+      configureSuggestion(false);
+    }
+
+    // Configure subtexts
+    final List<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
+    for (final RangeInfo rangeInfo : rangeInfos) {
+      // If some place has suggestions, then add it
+      if (rangeInfo.getSuggestions() != null) {
+        placesWhereSuggestionAvailable.add(rangeInfo);
+      }
+    }
+    final String statusText = (myExecutor != null ? myExecutor.getExecutionDescription() : null);
+    myView.configureSubTexts(statusText, placesWhereSuggestionAvailable);
+  }
+
+  @Override
+  public void suggestionRequested() {
+    configureSuggestion(true); // Show or hide
+  }
+
+
+  /**
+   * Displays suggestions if needed.
+   *
+   * @param requestedExplicitly is suggesions where requested by user explicitly or not
+   */
+  private void configureSuggestion(final boolean requestedExplicitly) {
+    myView.removeSuggestions();
+    final RangeInfo rangeInfo = findNearestRangeInfo();
+    final String text = getTextByRange(rangeInfo);
+
+    final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
+    if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) {
+      return;
+    }
+    final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
+    if (text != null && !requestedExplicitly) {
+      filterLeaveOnlyMatching(suggestions, text);
+    }
+    // TODO: Place to add history
+    if (!suggestions.isEmpty()) {
+      // No need to display empty suggestions
+      myView
+        .displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), text);
+    }
+  }
+
+
+  /**
+   * Filters collection of suggestions leaving only those starts with certain text.
+   *
+   * @param suggestions list to filter
+   * @param textToMatch leave only parts that start with this param
+   */
+  private static void filterLeaveOnlyMatching(@NotNull final Iterable<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 range to use.
+   *
+   * @return nearest range info
+   */
+  @NotNull
+  private RangeInfo findNearestRangeInfo() {
+    final int caretPosition = myView.getCaretPosition();
+
+    for (final RangeInfo range : myRangeInfos) {
+      if (range.isWithin(caretPosition)) {
+        return range;
+      }
+      if (range.getFrom() > caretPosition) {
+        return range; // Ranges are sorted, so we are on the next range. Take it, if caret is not within range
+      }
+    }
+
+    return myRangeInfos.last();
+  }
+
+
+  @Override
+  public void completionRequested(@Nullable final String valueFromSuggestionList) {
+    final RangeInfo rangeInfo = findNearestRangeInfo();
+    final String text = getTextByRange(rangeInfo);
+
+    if (valueFromSuggestionList != null) {
+      // Just insert it
+      if (text != null) { // If caret is on the text itself
+        // TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
+        myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), valueFromSuggestionList);
+      }
+      else {
+        myView.insertTextAfterCaret(valueFromSuggestionList);
+      }
+      return;
+    }
+
+    //User did not provide text no insert, do our best to find one
+
+    final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
+    if (suggestionInfo == null) {
+      return; // No suggestion available for this chunk
+    }
+    final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
+    if (text != null) {
+      filterLeaveOnlyMatching(suggestions, text);
+    }
+    if (suggestions.size() == 1) {
+      // Exclusive!
+      if (text != null) {
+        // TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
+        myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), suggestions.get(0));
+      }
+      else {
+        myView.insertTextAfterCaret(suggestions.get(0));
+      }
+    }
+  }
+
+  /**
+   * Searches for text under the range
+   * @param rangeInfo range
+   * @return text or null if range does not contain any text
+   */
+  @Nullable
+  private String getTextByRange(@NotNull final RangeInfo rangeInfo) {
+
+    if (rangeInfo.isTerminationRange()) {
+      return null;
+    }
+    else {
+      final String viewText = myView.getText();
+      return viewText.substring(rangeInfo.getFrom(), rangeInfo.getTo());
+    }
+  }
+
+  @Override
+  public void executionRequested() {
+    if (myExecutor == null) {
+      // TODO: Display error somehow
+    }
+    else {
+      myExecutor.execute();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfo.java
new file mode 100644 (file)
index 0000000..a695eea
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.rangeBasedPresenter;
+
+import com.intellij.util.Range;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Information about certain place in text, provided by driver.
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class RangeInfo extends Range<Integer> implements Comparable<RangeInfo> {
+
+
+  /**
+   * Special "terminator": the last range which is after all other ranges.
+   */
+  public static final Range<Integer> TERMINATION_RANGE = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
+
+  @Nullable
+  private final String myInfoBalloon;
+  @Nullable
+  private final String myError;
+  @Nullable
+  private final SuggestionInfo mySuggestions;
+  private final boolean myExclusiveBorders;
+
+
+  /**
+   * @param infoBalloon      info to bind to this range
+   * @param error            error to bind to this range
+   * @param exclusiveBorders true if range has exclusive borders and right border is not part of it. I.e. 3 is part of 1-3 range with out
+   *                         of exclusive borders, but not part of exclusive range
+   * @param range            from and to
+   */
+  public RangeInfo(@Nullable final String infoBalloon,
+                   @Nullable final String error,
+                   final boolean exclusiveBorders,
+                   @NotNull final Range<Integer> range) {
+    this(infoBalloon, error, null, range, exclusiveBorders);
+  }
+
+
+  /**
+   * @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
+   * @param error       Error balloon to display when caret meets this place and underline text as error (null if no error)
+   * @param suggestions list of suggestions available in this place (if any)
+   */
+  public RangeInfo(@Nullable final String infoBalloon,
+                   @Nullable final String error,
+                   @Nullable final SuggestionInfo suggestions,
+                   @NotNull final Range<Integer> range,
+                   final boolean exclusiveBorders) {
+    super(range.getFrom(), range.getTo());
+    myInfoBalloon = infoBalloon;
+    myError = error;
+    mySuggestions = suggestions;
+    myExclusiveBorders = exclusiveBorders;
+  }
+
+
+  /**
+   * @return Info balloon to display when caret meets this place (null if display nothing)
+   */
+  @Nullable
+  public String getInfoBalloon() {
+    return myInfoBalloon;
+  }
+
+  /**
+   * @return Error balloon to display when caret meets this place and underline text as error (null if no error)
+   */
+  @Nullable
+  public String getError() {
+    return myError;
+  }
+
+  /**
+   * @return list of suggestions available in this place (if any)
+   */
+  @Nullable
+  public SuggestionInfo getSuggestions() {
+    return mySuggestions;
+  }
+
+  @Override
+  public int compareTo(RangeInfo o) {
+    return getFrom().compareTo(o.getFrom());
+  }
+
+  @Override
+  public boolean isWithin(final Integer object) {
+    if (!super.isWithin(object)) {
+      return false;
+    }
+    if (myExclusiveBorders) {
+      return object < getTo();
+    }
+    return true;
+  }
+
+  public boolean isTerminationRange() {
+    // TODO: copy/paste with view
+    return TERMINATION_RANGE.getFrom().equals(getFrom()) && TERMINATION_RANGE.getTo().equals(getTo());
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/RangeInfoDriver.java
new file mode 100644 (file)
index 0000000..fb58872
--- /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.rangeBasedPresenter;
+
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Driver that knows how to getCommandLineInfo pack of chunks into chunk info.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface RangeInfoDriver {
+  /**
+   * Parses command line text into executor and pack of range infos.
+   * There <strong>always</strong> should be at least one range info and the last one is almost always {@link RangeInfo#TERMINATION_RANGE).
+   *
+   * @param commandLineText command line text to parse.
+   * @return pair or executor (the one that knows how to execute command line) and pack of range infos.
+   * <strong>Warning! </strong> :Executor could be null if command can't be executed
+   */
+  @NotNull
+  Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull String commandLineText);
+}
similarity index 86%
rename from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java
rename to python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/SuggestionInfo.java
index 59bea9abd12fd456cf6536bada3460c3598bbc33..e7af84e52ea06ffc8a1ebe7d787aa7fee271a718 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.chunkDriverBasedPresenter;
+package com.jetbrains.python.commandInterface.rangeBasedPresenter;
 
 import org.jetbrains.annotations.NotNull;
 
@@ -67,4 +67,13 @@ public final class SuggestionInfo {
   public boolean isShowAbsolute() {
     return myShowAbsolute;
   }
+
+  @Override
+  public String toString() {
+    return "SuggestionInfo{" +
+           "mySuggestions=" + mySuggestions +
+           ", myShowSuggestionsAutomatically=" + myShowSuggestionsAutomatically +
+           ", myShowAbsolute=" + myShowAbsolute +
+           '}';
+  }
 }
diff --git a/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java b/python/src/com/jetbrains/python/commandInterface/rangeBasedPresenter/package-info.java
new file mode 100644 (file)
index 0000000..152071b
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <strong>range info</strong>
+ * and {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver}.
+ * This presenter passes command line to driver, and it returns pack of range information.
+ * Each record contains everything presenter needs to know about certain range (i.e. error between 2 and 5 chars).
+ * Special type of range {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo#TERMINATION_RANGE} exists, that
+ * you may need to check.
+ * <p/>
+ * Presenter uses this information to display chunks correctly using view.
+ * To use this package, {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} should be implemented.
+ * <p/>
+ * See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeBasedPresenter} as entry point
+ *
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface.rangeBasedPresenter;
\ No newline at end of file
similarity index 58%
rename from python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java
rename to python/src/com/jetbrains/python/commandLineParser/CommandLine.java
index a49394b37ef0ea9a6d24fbd2caad1ce9555b04b1..201a779554a8b8bde52b5a4dbb528d889943a026 100644 (file)
@@ -15,7 +15,7 @@
  */
 package com.jetbrains.python.commandLineParser;
 
-import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.text.StringUtil;
 import com.jetbrains.python.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 
@@ -25,22 +25,22 @@ import java.util.Collections;
 import java.util.List;
 
 /**
- * Command line parse result.
+ * Command line getCommandLineInfo result.
  * It consists of command itself and its parts.
  * Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or
  * {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else.
  *
  * @author Ilya.Kazakevich
  */
-public final class CommandLineParseResult {
+public final class CommandLine {
   @NotNull
-  private final List<Pair<CommandLinePartType, WordWithPosition>> myParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
+  private final List<CommandLinePart> myParts = new ArrayList<CommandLinePart>();
   @NotNull
   private final WordWithPosition myCommand;
 
-  public CommandLineParseResult(
+  public CommandLine(
     @NotNull final WordWithPosition command,
-    @NotNull final Collection<Pair<CommandLinePartType, WordWithPosition>> parts) {
+    @NotNull final Collection<CommandLinePart> parts) {
     myCommand = command;
     myParts.addAll(parts);
   }
@@ -53,26 +53,26 @@ public final class CommandLineParseResult {
     return myCommand;
   }
 
-  /**
-   * @return list of parts in format [part_type, value].
-   * For example (rm my_folder): [{@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument}, my_folder]
-   */
   @NotNull
-  public List<Pair<CommandLinePartType, WordWithPosition>> getParts() {
+  public List<CommandLinePart> getParts() {
     return Collections.unmodifiableList(myParts);
   }
 
   /**
-   * @return all command line parts with out of part information (just words and positions).
-   * Note tha command itself is not part, only args and options are
-   * @see #getParts()
+   * @return all command parts as text (actually by bindning them back together)
    */
   @NotNull
-  public Collection<WordWithPosition> getPartsNoType() {
-    final Collection<WordWithPosition> result = new ArrayList<WordWithPosition>();
-    for (final Pair<CommandLinePartType, WordWithPosition> part : myParts) {
-      result.add(part.second);
+  public String getPartsAsText() {
+    final StringBuilder builder = new StringBuilder();
+    int lastPosition = 0;
+    for (final CommandLinePart part : getParts()) {
+      final WordWithPosition partWord = part.getWord();
+      if (lastPosition != partWord.getFrom()) {
+        builder.append(' ');
+      }
+      builder.append(partWord.getText());
+      lastPosition = partWord.getTo();
     }
-    return result;
+    return StringUtil.trim(builder.toString());
   }
 }
diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineArgument.java
new file mode 100644 (file)
index 0000000..13d92b9
--- /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;
+
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Positional or option argument.
+ * @author Ilya.Kazakevich
+ */
+public final class CommandLineArgument extends CommandLinePart {
+
+  public CommandLineArgument(@NotNull final WordWithPosition argumentValue) {
+    super(argumentValue);
+  }
+
+
+  @Override
+  public void accept(@NotNull final CommandLinePartVisitor visitor) {
+    visitor.visitArgument(this);
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineOption.java
new file mode 100644 (file)
index 0000000..217b4fa
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandLineParser;
+
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Option. Each option has name and may also have attached argument like "--long-option=attached_arg" or "-sATTACHED_ARG"
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class CommandLineOption extends CommandLinePart {
+
+  @NotNull
+  private final String myOptionName;
+
+  @Nullable
+  private final WordWithPosition myAttachedArgument;
+
+  /**
+   * @param option           option (text and position)
+   * @param optionName       option name (like "--foo")
+   * @param attachedArgument option  attached argument (like --foo=ATTACHED_ARG)
+   */
+  public CommandLineOption(@NotNull final WordWithPosition option,
+                           @NotNull final String optionName,
+                           @Nullable final WordWithPosition attachedArgument) {
+    super(option);
+    myOptionName = optionName;
+    myAttachedArgument = attachedArgument;
+  }
+
+
+  /**
+   * @return option name (like "--foo")
+   */
+  @NotNull
+  public String getOptionName() {
+    return myOptionName;
+  }
+
+  /**
+   * @return option  attached argument (like --foo=ATTACHED_ARG)
+   */
+  @Nullable
+  public WordWithPosition getAttachedArgument() {
+    return myAttachedArgument;
+  }
+
+  @Override
+  public void accept(@NotNull final CommandLinePartVisitor visitor) {
+    visitor.visitOption(this);
+  }
+}
index b3813d8dd6165745beb058bb5ba343faf32a5d28..b52d785f54c9a6dd792bc8c5ad3a9cd9b246586f 100644 (file)
  */
 package com.jetbrains.python.commandLineParser;
 
-import com.jetbrains.python.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.List;
-
 /**
- * Engine to parse command line. It understands how options and arguments are coded in certain commandline.
- * It supportd {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
- * command line is command or argument. That helps you to underline or emphisize some parts.
+ * Engine to {@link CommandLine} structure from text.
  *
  * @author Ilya.Kazakevich
  */
 public interface CommandLineParser {
   /**
-   * @param commandLineParts command line splitted into words.
+   *
+   * @param commandLineText command line to parse
    * @return command line information
    * @throws MalformedCommandLineException in case of bad commandline
    */
   @NotNull
-  CommandLineParseResult parse(@NotNull List<WordWithPosition> commandLineParts) throws MalformedCommandLineException;
+  CommandLine parse(@NotNull String commandLineText) throws MalformedCommandLineException;
 }
similarity index 51%
rename from python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java
rename to python/src/com/jetbrains/python/commandLineParser/CommandLinePart.java
index 12740a530052ea9a466fa69823a54ab1d2f9fd4e..93af9e0d92ea89c9ca1515e7704a23733bb1ffb6 100644 (file)
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
+package com.jetbrains.python.commandLineParser;
 
 import com.jetbrains.python.WordWithPosition;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.List;
-
 /**
- * Driver that knows how to parse pack of chunks into chunk info.
+ * Part of command line. Known subclasses are {@link CommandLineOption} and {@link CommandLineArgument}
  *
  * @author Ilya.Kazakevich
+ * @see CommandLineArgument
+ * @see CommandLineOption
  */
-public interface ChunkDriver {
+public abstract class CommandLinePart {
+  @NotNull
+  private final WordWithPosition myWord;
+
   /**
-   * Parses chunks into pack of chunks. There <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
+   * @param word word (and its position) this part represents
+   */
+  protected CommandLinePart(@NotNull final WordWithPosition word) {
+    myWord = word;
+  }
+
+  /**
+   * @return word (and its position) this part represents
    */
   @NotNull
-  ParseInfo parse(@NotNull List<WordWithPosition> chunks);
+  public final WordWithPosition getWord() {
+    return myWord;
+  }
+
+  /**
+   * @param visitor visitor to accept
+   */
+  public abstract void accept(@NotNull CommandLinePartVisitor visitor);
 }
similarity index 57%
rename from python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java
rename to python/src/com/jetbrains/python/commandLineParser/CommandLinePartVisitor.java
index 1caf82eaad8878c3b34737df5ab406075ee30f83..fe381c0957c4934495cf8cb0ce78bf89b1dd82fd 100644 (file)
  */
 package com.jetbrains.python.commandLineParser;
 
+import org.jetbrains.annotations.NotNull;
+
 /**
- * Types of command line parts.
+ * Visitor to process options and arguments
  *
  * @author Ilya.Kazakevich
+ * @see CommandLinePart#accept(CommandLinePartVisitor)
  */
-public enum CommandLinePartType {
-  /**
-   * 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,
+public interface CommandLinePartVisitor {
+
   /**
-   * Option argument like --folder-to-delete=/
-   * Here root is option argument
+   * @param option option to visit
    */
-  OPTION_ARGUMENT,
+  void visitOption(@NotNull CommandLineOption option);
+
   /**
-   * Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand
+   * @param argument argument to visit
    */
-  UNKNOWN
+  void visitArgument(@NotNull CommandLineArgument argument);
 }
index 63a173e393e3955360653a808f620e907a293458..82fd6dfe88e4382044851a55993999637e008f73 100644 (file)
 package com.jetbrains.python.commandLineParser.optParse;
 
 import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.commandInterface.command.Option;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Supports --long-option-style-with=value
+ *
  * @author Ilya.Kazakevich
  */
-final class LongOptionParser implements OptionParser {
-  @Nullable
-  @Override
-  public Pair<Option, String> findOptionAndValue(@NotNull final List<Option> availableOptions, @NotNull final String textToCheck) {
-    if (!textToCheck.startsWith("--")) {
-      return null;
-    }
+final class LongOptionParser extends OptionParserRegexBased {
+  @NotNull
+  private static final Pattern LONG_OPT_PATTERN = Pattern.compile("^((--[a-zA-Z0-9-]+)=?)");
 
-    for (final Option option : availableOptions) {
-      for (final String longName : option.getLongNames()) {
-        if (textToCheck.startsWith(longName)) {
-          final String[] parts = textToCheck.split("=");
-          if (parts.length == 0) {
-            return Pair.create(option, null);
-          }
-          else {
-            return Pair.create(option, parts[1]);
-          }
-        }
-      }
-    }
+  LongOptionParser() {
+    super(LONG_OPT_PATTERN);
+  }
 
-    return null;
+  @NotNull
+  @Override
+  protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
+    return Pair.create(matcher.group(1), matcher.group(2));
   }
 }
index 6b89a5345840ea31621b6688e208549f654b9ab4..4612b87dc9a4e20257277e58c759e547974e9f2a 100644 (file)
 package com.jetbrains.python.commandLineParser.optParse;
 
 import com.intellij.openapi.util.Pair;
-import com.intellij.util.containers.hash.HashMap;
 import com.jetbrains.python.WordWithPosition;
-import com.jetbrains.python.commandInterface.command.Command;
-import com.jetbrains.python.commandInterface.command.Option;
-import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
-import com.jetbrains.python.commandLineParser.CommandLineParseResult;
-import com.jetbrains.python.commandLineParser.CommandLineParser;
-import com.jetbrains.python.commandLineParser.CommandLinePartType;
-import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
+import com.jetbrains.python.commandLineParser.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
 
 /**
  * <p>
@@ -48,114 +44,67 @@ import java.util.*;
 public final class OptParseCommandLineParser implements CommandLineParser {
   /**
    * Supported option parsers (option types, actually)
+   * Short (-o) and long (--option) strategies are used here
    */
-  @NotNull
-  private static final OptionParser[] OPTION_PARSERS = {new LongOptionParser(), new ShortOptionParser()};
 
-  /**
-   * CommandName -> Options
-   */
-  @NotNull
-  private final Map<String, List<Option>> myCommandOptions = new HashMap<String, List<Option>>();
+  private static final OptionParser[] OPTION_PARSERS = {new ShortOptionParser(), new LongOptionParser()};
 
-  /**
-   * @param commands commands (needed to parse out options)
-   */
-  public OptParseCommandLineParser(@NotNull final Iterable<? extends Command> commands) {
-    for (final Command command : commands) {
-      myCommandOptions.put(command.getName(), command.getOptions());
-    }
-  }
 
   @NotNull
   @Override
-  public CommandLineParseResult parse(@NotNull final List<WordWithPosition> commandLineParts) throws MalformedCommandLineException {
-    final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(commandLineParts);
+  public CommandLine parse(@NotNull final String commandLineText) throws MalformedCommandLineException {
+    final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(WordWithPosition.splitText(commandLineText));
     if (parts.isEmpty()) {
       throw new MalformedCommandLineException("No command provided");
     }
     final WordWithPosition command = parts.pop();
-    final List<Option> options;
-    if (myCommandOptions.containsKey(command.getText())) {
-      options = myCommandOptions.get(command.getText());
-    }
-    else {
-      options = Collections.emptyList();
-    }
 
+    final List<CommandLinePart> optionsAndArguments = new ArrayList<CommandLinePart>();
 
-    final List<Pair<CommandLinePartType, WordWithPosition>> resultParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
-    if (isOption(command)) {
-      throw new MalformedCommandLineException("Command can't start with option prefix");
-    }
-
-    int optionArgumentsLeft = 0; // If option has N arguments, then next N arguments belong to this option
     for (final WordWithPosition part : parts) {
-      if (optionArgumentsLeft > 0) {
-        optionArgumentsLeft--;
-        resultParts.add(Pair.create(CommandLinePartType.OPTION_ARGUMENT, part));
-        continue;
-      }
-      if (isOption(part)) {
-        // This is option!
-        final Pair<Option, String> optionAndValue = findOptionAndValue(options, part.getText());
-        if (optionAndValue != null) {
-          final Option option = optionAndValue.first;
-          final Pair<Integer, OptionArgumentInfo> argumentAndQuantity = option.getArgumentAndQuantity();
-          if (argumentAndQuantity != null) {
-            optionArgumentsLeft = argumentAndQuantity.first;
-          }
-          final String optionArgumentValue = optionAndValue.second;
-          if (optionArgumentValue != null) {
-            // We found argument
-            optionArgumentsLeft--;
-            final String optionArgumentText =
-              part.getText().substring(0, part.getText().length() - optionArgumentValue.length());
-            final WordWithPosition optionPart =
-              new WordWithPosition(optionArgumentText, part.getFrom(), part.getFrom() + optionArgumentText.length());
-            final WordWithPosition valuePart = new WordWithPosition(optionArgumentValue, optionPart.getTo(), part.getTo());
-            resultParts.add(Pair.create(CommandLinePartType.OPTION, optionPart));
-            resultParts.add(Pair.create(CommandLinePartType.OPTION_ARGUMENT, valuePart));
-          }
-          else {
-            resultParts.add(Pair.create(CommandLinePartType.OPTION, part));
-          }
+      final Pair<String, String> optionTextAndName = findOptionTextAndName(part.getText());
+      if (optionTextAndName != null) {
+        final String optionText = optionTextAndName.first;
+        final String optionName = optionTextAndName.second;
+        final WordWithPosition option = new WordWithPosition(optionText, part.getFrom(), part.getFrom() + optionText.length());
+
+        final WordWithPosition optionArgument;
+        if (optionText.length() == part.getText().length()) {
+          optionArgument = null;
         }
         else {
-          resultParts.add(Pair.create(CommandLinePartType.UNKNOWN, part));
+          // Looks like we have option argument here.
+          final String argumentText = part.getText().substring(optionText.length());
+          optionArgument = new WordWithPosition(argumentText, option.getTo(), part.getTo());
+        }
+        optionsAndArguments.add(new CommandLineOption(option, optionName, optionArgument)); //Option
+        if (optionArgument != null) {
+          optionsAndArguments.add(new CommandLineArgument(optionArgument)); // And its argument
         }
       }
       else {
-        resultParts.add(Pair.create(CommandLinePartType.ARGUMENT, part));
+        optionsAndArguments.add(new CommandLineArgument(part)); //Not an option. Should be argument
       }
     }
-    return new CommandLineParseResult(command, resultParts);
+    return new CommandLine(command, optionsAndArguments);
   }
 
+
   /**
    * Parse out option and its value iterating through the parsers
    *
-   * @param options all available options
-   * @param text    text believed to be an option like "--foo=123"
-   * @return [option->argument_value] pair or null if provided text is not an option. ArgValue may also be null if not provided
-   * @see com.jetbrains.python.commandLineParser.optParse.OptionParser#findOptionAndValue(java.util.List, String)
+   * @param text text believed to be an option like "--foo=123"
+   * @return [option_text, option_name] or null of no such option
+   * @see OptionParser#findOptionTextAndName(String)
    */
   @Nullable
-  private static Pair<Option, String> findOptionAndValue(@NotNull final List<Option> options, @NotNull final String text) {
+  private static Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
     for (final OptionParser parser : OPTION_PARSERS) {
-      final Pair<Option, String> optionAndValue = parser.findOptionAndValue(options, text);
-      if (optionAndValue != null) {
-        return optionAndValue;
+      final Pair<String, String> optionTextAndName = parser.findOptionTextAndName(optionText);
+      if (optionTextAndName != null) {
+        return optionTextAndName;
       }
     }
     return null;
   }
-
-  /**
-   * @param command command to check
-   * @return if command is option or not
-   */
-  private static boolean isOption(@NotNull final WordWithPosition command) {
-    return command.getText().startsWith("-");
-  }
 }
index 499dbe9d82be8b04abe6515a7d40e61937604004..246e51fb47f96a062464dec46a26088b9ec55db3 100644 (file)
 package com.jetbrains.python.commandLineParser.optParse;
 
 import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.commandInterface.command.Option;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
-
 /**
- * Engine that knows how to deal with option of certain style
+ * Engine that knows how to deal with option of certain style (like long and short)
  *
  * @author Ilya.Kazakevich
  */
 interface OptionParser {
   /**
-   * Checks if some text that looks like and option is really option
-   *
-   * @param availableOptions all available options
-   * @param textToCheck      text believed to be an option like "--foo=123"
-   * @return [option->argument_value] pair or null if provided text is not an option. ArgValue may also be null if not provided
+   * @param optionText text to parse (like --foo=bar)
+   * @return null if option can't be parsed. Otherwise pair of [option_text, option_name]. That may match each other in some cases.
    */
   @Nullable
-  Pair<Option, String> findOptionAndValue(@NotNull List<Option> availableOptions, @NotNull String textToCheck);
+  Pair<String, String> findOptionTextAndName(@NotNull String optionText);
 }
diff --git a/python/src/com/jetbrains/python/commandLineParser/optParse/OptionParserRegexBased.java b/python/src/com/jetbrains/python/commandLineParser/optParse/OptionParserRegexBased.java
new file mode 100644 (file)
index 0000000..628bf41
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.optParse;
+
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses options using regex and template method
+ *
+ * @author Ilya.Kazakevich
+ */
+abstract class OptionParserRegexBased implements OptionParser {
+  @NotNull
+  private final Pattern myPattern;
+
+  /**
+   * @param pattern regex. If option matches pattern, ({@link Matcher#find() opened matcher}) would be passed to template method
+   *                {@link #getOptionTextAndNameFromMatcher(Matcher)}
+   */
+  protected OptionParserRegexBased(@NotNull final Pattern pattern) {
+    myPattern = pattern;
+  }
+
+  @Nullable
+  @Override
+  public final Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
+    final Matcher matcher = myPattern.matcher(optionText);
+    if (!matcher.find()) {
+      return null;
+    }
+    return getOptionTextAndNameFromMatcher(matcher);
+  }
+
+  /**
+   * Obtains [option_text, option_name] from matcher
+   *
+   * @param matcher opened (with find() called) matcher
+   * @return pair [option_text, option_name]
+   */
+  @NotNull
+  protected abstract Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull Matcher matcher);
+}
index 35e562be26c76714982002f61fcc4bab82d1024b..00c72ea2887add1f67fa346ee7916ab410da30af 100644 (file)
 package com.jetbrains.python.commandLineParser.optParse;
 
 import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.commandInterface.command.Option;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
-import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * Deals with short-style options like -b or -f
+ *
  * @author Ilya.Kazakevich
  */
-final class ShortOptionParser implements OptionParser {
+final class ShortOptionParser extends OptionParserRegexBased {
   /**
    * Short-style option regexp
    */
-  private static final Pattern SHORT_OPT_START = Pattern.compile("^(-[a-zA-Z0-9])([^ -])?");
-
-  @Nullable
-  @Override
-  public Pair<Option, String> findOptionAndValue(@NotNull final List<Option> availableOptions, @NotNull final String textToCheck) {
-    final Matcher matcher = SHORT_OPT_START.matcher(textToCheck);
-
-    if (!matcher.find()) {
-      return null;
-    }
-    final String optionText = matcher.group(1);
-    final String optionValueText = (matcher.groupCount() == 2 ? matcher.group(2) : null);
+  private static final Pattern SHORT_OPT_PATTERN = Pattern.compile("^(-[a-zA-Z0-9])([^ -])?");
 
+  ShortOptionParser() {
+    super(SHORT_OPT_PATTERN);
+  }
 
-    for (final Option option : availableOptions) {
-      for (final String shortName : option.getShortNames()) {
-        if (optionText.equals(shortName)) {
-          return Pair.create(option, optionValueText);
-        }
-      }
-    }
-
-    return null;
+  @NotNull
+  @Override
+  protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
+    return Pair.create(matcher.group(1), matcher.group(1));
   }
 }
index bbdd192167f4fa26dbc131b6ddc3f965489aa3cf..23d92d7c45c62257c3f26cfe8d917cf93b500af8 100644 (file)
  */
 
 /**
- * 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}.
+ * Engine to parse commandline from string to {@link com.jetbrains.python.commandLineParser.CommandLine} structure.
  *
+ * Command line consists of command itself,  {@link com.jetbrains.python.commandLineParser.CommandLineArgument arguments}
+ * and {@link com.jetbrains.python.commandLineParser.CommandLineOption options}.
+ * Use need to pass text to {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLine}.
+ * <p/>
  * 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
index d209235bec74c551a9ad75bc62c6c1504210b61b..708d4418c05ea0f66c3c645037ea305e9d146348 100644 (file)
@@ -99,6 +99,7 @@ public class SuggestionList {
     }
     // Fill and select
 
+    int record = 0; // Record to select
     // Iterate through groups adding suggestions. Odd groups should be marked differently.
     for (int groupId = 0; groupId < values.size(); groupId++) {
       final List<Suggestion> suggestions = values.get(groupId);
@@ -106,11 +107,12 @@ public class SuggestionList {
         final Suggestion suggestion = suggestions.get(suggestionId);
         myListModel.addElement(new SuggestionListElement((groupId % 2) == 0, suggestion));
         if (suggestion.getText().equals(elementToSelect)) {
-          myList.setSelectedIndex(suggestionId + groupId);
+          myList.setSelectedIndex(record);
         }
+        record++;
       }
     }
-    if ((elementToSelect == null) && (!myListModel.isEmpty())) {
+    if ((myList.getSelectedIndex() ==-1) && (!myListModel.isEmpty())) {
       myList.setSelectedIndex(0); // Select first element
     }
 
@@ -124,6 +126,7 @@ public class SuggestionList {
       myListPopUp.addListener(myListener);
     }
     myListPopUp.show(displayPoint);
+    myList.ensureIndexIsVisible(myList.getSelectedIndex()); // Scrolls to selected
   }