Refactored to new architecture (Presenter / ChunkDriver).
# Message we display for inspection if user uses custom class type members that do not exist
custom.type.mimic.name=Dynamic class based on {0}
+
# Values for command argument value validation
-commandsWithArgs.validation.badValue=Argument can't have this value
-commandsWithArgs.validation.excess=Excess argument
-commandsWithArgs.enterCommand.label=Enter command here
\ No newline at end of file
+commandLine.validation.badCommand=Unknown command
+commandLine.validation.argMissing=Required argument value is missing
+commandLine.validation.argBadValue=Argument can't have this value
+commandLine.validation.excessArg=Excess argument value
+# And for labels
+commandLine.subText.key.complete=Use {0} to complete selected variant
+commandLine.subText.key.suggestions=Use {0} to view available values
+commandLine.subText.key.executeUnknown=Click {0} to execute
+commandLine.subText.key.executeCommand=Click {0} to execute "{1}"
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.optParse;
+package com.jetbrains.python;
import com.intellij.util.Range;
import org.jetbrains.annotations.NotNull;
/**
* Creates word with beam (it has start, but it is infinite)
+ *
* @param word word
* @param from start
*/
myWord = word;
}
+ /**
+ * Creates instance with certain text and range (start/end)
+ * @param word text
+ * @param range range
+ */
+ public WordWithPosition(@NotNull final String word, @NotNull final Range<Integer> range) {
+ this(word, range.getFrom(), range.getTo());
+ }
+
@NotNull
public String getText() {
return myWord;
* @return parse result
*/
@NotNull
- static List<WordWithPosition> splitText(@NotNull final String text) {
+ public static List<WordWithPosition> splitText(@NotNull final String text) {
// TODO: Rewrite using regex or scanner?
int position = 0;
int wordStart = -1;
/**
* Called by view when user types new text or text changed by some other reason
*
- * @param inForcedTextMode text changed not by user but by calling {@link com.jetbrains.python.commandInterface.CommandInterfaceView#forceText(String)}
*/
- void textChanged(boolean inForcedTextMode);
+ void textChanged();
/**
* Called by view when user requests for completion (like tab)
void completionRequested(@Nullable String valueFromSuggestionList);
/**
- * Called by view when user asks for suggestions (CTRL+Space)
+ * Called by view when user asks for suggestions (like CTRL+Space)
*/
void suggestionRequested();
/**
- * Called by view when user wants to execute command (Enter is presed)
+ * Called by view when user wants to execute command (like enter)
*
- * @param valueFromSuggestionList value selected from suggestion list (if any selected)
*/
- void executionRequested(@Nullable String valueFromSuggestionList);
+ void executionRequested();
}
*/
package com.jetbrains.python.commandInterface;
-import com.jetbrains.python.optParse.WordWithPosition;
+import com.intellij.util.Range;
+import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
*/
public interface CommandInterfaceView {
+ /**
+ * Special place in command line that represents "after the last character" place.
+ * To be used in methods like {@link #setInfoAndErrors(java.util.Collection, java.util.Collection)} to mark it.
+ */
+ @NotNull
+ Range<Integer> AFTER_LAST_CHARACTER_RANGE = new Range<Integer>(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
/**
* Launches view
*/
* Suggests user some elements (for completion reason)
*
* @param suggestions what to suggest (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
- * @param absolute display list in its main position, or directly near the text
- * @param toSelect word to select if list (if any)
+ * @param absolute display list in its main position, or directly near the caret
+ * @param toSelect word to select in list (if any)
*/
void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
- /**
- * Emphasize errors (like red line and special message).
- *
- * @param errors list of errors (coordinates and error message. Message may be empty not to display any text)
- * @param specialErrorPlace if you want to underline special place, you may provide it here
- */
- void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable SpecialErrorPlace specialErrorPlace);
-
- /**
- * Change text to the one provided
- *
- * @param newText text to display
- */
- void forceText(@NotNull String newText);
/**
- * Display text in sub part (like hint)
+ * Each time caret meets certain place, view should check whether some subtext has to be displayed.
+ * There are 2 types of subtext to be displayed:
+ * <ol>
+ * <li>Suggestion Text: View says something like "click FOO to see list of suggestions". Only presenter knows exact places where
+ * suggestions are available, so it should provide them</li>
+ * <li>Default text: In all other cases view displays default text (if available).</li>
+ * </ol>
+ * <p/>
+ * Presenter provides view list of special places
*
- * @param subText text to display
+ * @param defaultSubText default text
+ * @param suggestionAvailablePlaces list of places where suggestions are available in format [from, to].
*/
- void setSubText(@NotNull String subText);
+ void configureSubTexts(@Nullable String defaultSubText,
+ @NotNull List<Range<Integer>> suggestionAvailablePlaces);
/**
* Hide suggestion list
*/
void removeSuggestions();
- /**
- * Displays baloon with message right under the last letter.
- *
- * @param message text to display
- */
/**
* @return text, entered by user
@NotNull
String getText();
+
/**
- * Enlarges view to make it as big as required to display appropriate number of chars
+ * When caret meets certain place, view may display some info and some errors.
+ * Errors, how ever, may always be emphasized (with something like red line).
+ * This function configures view with pack of ranges and texts to display.
+ * Special place {@link #AFTER_LAST_CHARACTER_RANGE} may also be used.
+ * Each place is described as start-end position (in chars) where it should be enabled.
+ * Each call removes previously enabled information.
*
- * @param widthInChars number of chars
+ * @param errors places to be marked as errors with error text.
+ * @param infoBalloons places to display info balloon
+ * @see #AFTER_LAST_CHARACTER_RANGE
*/
- void setPreferredWidthInChars(int widthInChars);
+ void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons, @NotNull final Collection<WordWithPosition> errors);
+
/**
- * Displays help balloon when cursor meets certain place.
- * Each balloon is described as start-end position (in chars) where it should be enabled
- * and test to display.
- * <strong>Caution: Each call removes previuos balloons!</strong>
+ * Inserts text after caret moving next chars to the right
*
- * @param balloons list of balloons to display (i.e. you want to text 'foo' be displayed when user sets cursor on position
- * from 1 to 3, so you add 'foo',1,4 here)
+ * @param text text to insert
*/
- void setBalloons(@NotNull final Collection<WordWithPosition> balloons);
+ void insertTextAfterCaret(@NotNull String text);
/**
- * @return true if current caret position is on the word (no on whitespace)
+ * Replaces current text with another one.
+ *
+ * @param from from
+ * @param to to
+ * @param newText text to replace
*/
- boolean isCaretOnWord();
-
+ void replaceText(final int from, final int to, @NotNull String newText);
/**
- * Special place that may be underlined
+ * @return position of caret (in chars)
*/
- enum SpecialErrorPlace {
- /**
- * Whole text (from start to end)
- */
- WHOLE_TEXT,
- /**
- * Only after last character
- */
- AFTER_LAST_CHAR
- }
+ int getCaretPosition();
}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
+
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Driver that knows how to parse pack of chunks into chunk info.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface ChunkDriver {
+ /**
+ * Parses chunks into pack of chunks. There <strong>always</strong> should be chunk+1 chunkInfos (one for the tail like
+ * {@link com.jetbrains.python.commandInterface.CommandInterfaceView#AFTER_LAST_CHARACTER_RANGE}).
+ * So, at least one chunk info should also exist!
+ *
+ * @param chunks chunks (parts of command line)
+ * @return parse info with chunks info. Warning: do not return less chunk infos than chunks provided. That leads to runtime error
+ */
+ @NotNull
+ ParseInfo parse(@NotNull List<WordWithPosition> chunks);
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Information about suggestions
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class SuggestionInfo {
+ @NotNull
+ private final List<String> mySuggestions = new ArrayList<String>();
+ private final boolean myShowSuggestionsAutomatically;
+ private final boolean myShowAbsolute;
+
+ /**
+ * @param showSuggestionsAutomatically true it suggestions should be displayed even if user did not ask for that
+ * @param showAbsolute show suggestions at the absolute position (not relative to caret).
+ * @param suggestions List of suggestions to display
+ */
+ public SuggestionInfo(final boolean showSuggestionsAutomatically,
+ final boolean showAbsolute,
+ @NotNull final Collection<String> suggestions) {
+ myShowSuggestionsAutomatically = showSuggestionsAutomatically;
+ myShowAbsolute = showAbsolute;
+ mySuggestions.addAll(suggestions);
+ }
+
+ /**
+ * @return List of suggestions to display
+ */
+ @NotNull
+ public List<String> getSuggestions() {
+ return Collections.unmodifiableList(mySuggestions);
+ }
+
+ /**
+ * @return true it suggestions should be displayed even if user did not ask for that
+ */
+ public boolean isShowSuggestionsAutomatically() {
+ return myShowSuggestionsAutomatically;
+ }
+
+ /**
+ * @return show suggestions at the absolute position (not relative to caret).
+ */
+ public boolean isShowAbsolute() {
+ return myShowAbsolute;
+ }
+}
--- /dev/null
+/*
+ * 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
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
-import org.jetbrains.annotations.NotNull;
+import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.Nullable;
-import java.util.List;
-
/**
- * Information about command {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} and their value
+ * Information about command {@link Argument arguments} and their value
* validation.
- * Check optparse manual, package info and {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}
+ * Check optparse manual, package info and {@link Argument}
* manual for more info about arguments.
*
* @author Ilya.Kazakevich
*/
public interface ArgumentsInfo {
/**
- * Returns argument by its position.
+ * Returns argument by its position. It also returns hint whether argument is required or not.
*
* @param argumentPosition argument position
- * @return null if no argument value is available at this position. Returns argument otherwise.
+ * @return null if no argument value is available at this position.
+ * Returns argument otherwise. Boolean here should tell you if argument is required (command is invalid with out of it) or optional (
+ * it is acceptible, but command can work with our of it)
*/
@Nullable
- Argument getArgument(int argumentPosition);
-
-
- /**
- * Validates argument <strong>values</strong>.
- * Values should be provided as list. I.e. for <pre>my_command foo bar</pre> there should be list of "foo, bar".
- *
- * @param argumentValuesToCheck values to check
- * @return validation result
- */
- @NotNull
- ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck);
+ Pair<Boolean, Argument> getArgument(int argumentPosition);
}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
+import com.intellij.openapi.module.Module;
+import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
- * @return Information about command positional, unnamed {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} (not options!)
+ * @return Information about command positional, unnamed {@link Argument arguments} (not options!)
*/
@NotNull
ArgumentsInfo getArgumentsInfo();
+
+ /**
+ * Execute command
+ *
+ * @param module module to execute command against
+ * @param commandLine command's command line
+ */
+ void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine);
}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.WordWithPosition;
+import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver;
+import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo;
+import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ParseInfo;
+import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.SuggestionInfo;
+import com.jetbrains.python.commandLineParser.CommandLineParseResult;
+import com.jetbrains.python.commandLineParser.CommandLineParser;
+import com.jetbrains.python.commandLineParser.CommandLinePartType;
+import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * Chunk driver that uses pack of commands.
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class CommandBasedChunkDriver implements ChunkDriver {
+ @NotNull
+ private final CommandLineParser myCommandLineParser;
+ @NotNull
+ private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
+ @NotNull
+ private final Module myModule;
+
+ /**
+ * @param commandLineParser parser to use
+ * @param module module parsing takes place in
+ * @param commands available commands
+ */
+ public CommandBasedChunkDriver(@NotNull final CommandLineParser commandLineParser,
+ @NotNull final Module module,
+ @NotNull final Collection<? extends Command> commands) {
+ myCommandLineParser = commandLineParser;
+ for (final Command command : commands) {
+ myCommands.put(command.getName(), command);
+ }
+ myModule = module;
+ }
+
+ @Override
+ @NotNull
+ public ParseInfo parse(@NotNull final List<WordWithPosition> chunks) {
+ // TODO: Refactor to add command first to prevent copy/paste
+ if (chunks.isEmpty()) {
+ return createBadCommandInfo(chunks.size());
+ }
+
+ try {
+ final CommandLineParseResult commandLine = myCommandLineParser.parse(chunks);
+ final Command command = myCommands.get(commandLine.getCommand().getText());
+ if (command == null) {
+ // Bad command inserted
+ return createBadCommandInfo(chunks.size());
+ }
+
+ // Command exists, lets check its arguments
+
+ // TODO: Support options as well
+
+ // First, validate values
+ final ArgumentsInfo commandArgumentsInfo = command.getArgumentsInfo();
+
+
+ final List<ChunkInfo> chunkInfo = new ArrayList<ChunkInfo>();
+ // First chunk iscommand and it seems to be ok
+ chunkInfo.add(new ChunkInfo(null, null, new SuggestionInfo(false, true, myCommands.keySet())));
+
+
+ // Now add balloons, info and suggestions
+ for (int i = 0; i < commandLine.getParts().size(); i++) {
+ final Pair<Boolean, Argument> argumentPair = commandArgumentsInfo.getArgument(i);
+ if (argumentPair == null) { // Excess argument!
+ chunkInfo.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.excessArg")));
+ continue;
+ }
+ final Argument argument = argumentPair.getSecond();
+ final List<String> availableValues = argument.getAvailableValues();
+ final Pair<CommandLinePartType, WordWithPosition> part = commandLine.getParts().get(i);
+ if (part.first != CommandLinePartType.ARGUMENT) {
+ // Only arguments are supported now, so we have nothing to say about this chunk
+ chunkInfo.add(new ChunkInfo(null, null));
+ }
+ final String argumentValue = part.second.getText();
+ String errorMessage = null;
+ if (availableValues != null && !availableValues.contains(argumentValue)) {
+ // Bad value
+ errorMessage = PyBundle.message("commandLine.validation.argBadValue");
+ }
+ // Argument seems to be ok. We suggest values automatically only if value is bad
+ chunkInfo.add(new ChunkInfo(argument.getHelpText(), errorMessage,
+ (availableValues != null ? new SuggestionInfo(errorMessage != null, false, availableValues) : null)));
+ }
+
+
+ final Pair<Boolean, Argument> nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size());
+ if (nextArgumentPair != null) {
+ // Next arg exists
+ final Argument nextArgument = nextArgumentPair.getSecond();
+ final List<String> availableValues = nextArgument.getAvailableValues();
+ // Only add error if required
+ final String error = nextArgumentPair.first ? PyBundle.message("commandLine.validation.argMissing") : null;
+ final ChunkInfo lastArgInfo =
+ new ChunkInfo(nextArgument.getHelpText(), error,
+ (availableValues != null ? new SuggestionInfo(false, false, availableValues) : null));
+ chunkInfo.add(lastArgInfo);
+ }
+ else {
+ // Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
+ // This is a hack, but with out of it last chunkinfo will always be used, even 200 chars after last place
+ chunkInfo.add(new ChunkInfo(null, null));
+ }
+
+ assert chunkInfo.size() >= chunks.size() : "Contract broken: not enough chunks";
+
+ return new ParseInfo(chunkInfo, command.getHelp(), new MyExecutor(command, commandLine));
+ }
+ catch (final MalformedCommandLineException ignored) {
+ // Junk enetered!
+ return createBadCommandInfo(chunks.size());
+ }
+ }
+
+
+ /**
+ * Creates parse info signaling command is bad or junk
+ *
+ * @param numberOfChunks number of chunks provided by user (we must return chunk info for each chunk + 1, accroding to contract)
+ * @return parse info to return
+ */
+ @NotNull
+ private ParseInfo createBadCommandInfo(final int numberOfChunks) {
+ final List<ChunkInfo> result = new ArrayList<ChunkInfo>();
+ // We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
+ // How ever, we must say something according to contract (number of infos should be equal or greater than number of chunks)
+ result
+ .add(new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet())));
+ for (int i = 1; i < numberOfChunks; i++) {
+ result.add(
+ new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand")));
+ }
+
+ return new ParseInfo(result);
+ }
+
+
+ /**
+ * Adapter that executes command using {@link Command#execute(com.intellij.openapi.module.Module, com.jetbrains.python.commandLineParser.CommandLineParseResult)}
+ */
+ private class MyExecutor implements Runnable {
+ @NotNull
+ private final Command myCommand;
+ @NotNull
+ private final CommandLineParseResult myCommandLine;
+
+ MyExecutor(@NotNull final Command command, @NotNull final CommandLineParseResult line) {
+ myCommand = command;
+ myCommandLine = line;
+ }
+
+ @Override
+ public void run() {
+ myCommand.execute(myModule, myCommandLine);
+ }
+ }
+}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.google.common.base.Preconditions;
-import com.intellij.util.containers.hash.HashMap;
-import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
+import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
/**
* In some special cases we have insight about command arguments.
*/
private final int myMaxArguments;
+ /**
+ * For command with fixed number of arguments. In this case all arguments are fixed and required!
+ *
+ * @param arguments arguments this command have
+ */
+ public KnownArgumentsInfo(@NotNull final Collection<Argument> arguments) {
+ this(arguments, arguments.size(), arguments.size());
+ }
+
/**
* For commands with infinite number of values last argument accepts (my_command VAL1 VAL2 .. VALN)
*
@Nullable
@Override
- public Argument getArgument(final int argumentPosition) {
+ public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+ if (argumentPosition >= myMaxArguments) {
+ return null;
+ }
+ final boolean optional = argumentPosition >= myMinArguments;
+
if (myArguments.size() > argumentPosition) {
- return myArguments.get(argumentPosition);
+ return Pair.create(!optional, myArguments.get(argumentPosition));
}
// We may need last one
- if (argumentPosition <= myMaxArguments) {
- return myArguments.get(myArguments.size() - 1);
+ if (argumentPosition < myMaxArguments) {
+ return Pair.create(false, myArguments.get(myArguments.size() - 1));
}
return null;
}
-
- @NotNull
- @Override
- public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
- final Map<Integer, ArgumentValueError> errors = new HashMap<Integer, ArgumentValueError>();
-
- for (int i = 0; i < argumentValuesToCheck.size(); i++) {
- final String userValue = argumentValuesToCheck.get(i);
- final Argument argument = getArgument(i);
- if (argument == null) {
- errors.put(i, ArgumentValueError.EXCESS);
- continue;
- }
- final List<String> availableValues = argument.getAvailableValues();
- if (availableValues != null && !availableValues.contains(userValue)) {
- errors.put(i, ArgumentValueError.BAD_VALUE);
- }
- }
-
- return new ArgumentsValuesValidationInfo(errors, argumentValuesToCheck.size() < myMinArguments);
- }
}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
+
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * TODO: Redundant?!
+ * Case when command has no arguments (for sure!)
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class NoArgumentsInfo implements ArgumentsInfo {
+ /**
+ * Instance to use when command has no arguments
+ */
+ public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo();
+
+ private NoArgumentsInfo() {
+ }
+
+ @Nullable
+ @Override
+ public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+ return null;
+ }
+}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.commandInterface.commandsWithArgs;
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
+import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.List;
-
/**
* For many commands we know nothing about arguments but their help text.
* This strategy is for this case
myHelp = allArgumentsHelpText;
}
- @Nullable
- @Override
- public Argument getArgument(final int argumentPosition) {
- return new Argument(myHelp); // We can't say argument does not exist.
- }
- @NotNull
+ @Nullable
@Override
- public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
- return ArgumentsValuesValidationInfo.NO_ERROR; // Actually, we have no idea
+ public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
+ return Pair.create(false, new Argument(myHelp));
}
}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of
+ * {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Command command} and its {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Argument arguments}.
+ *
+ * See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point.
+ * It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
+ * provided by user
+ *
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.util.containers.hash.HashMap;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Information about {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} values validation
- *
- * @author Ilya.Kazakevich
- */
-public final class ArgumentsValuesValidationInfo {
- /**
- * Validation with out of any error
- */
- @NotNull
- static final ArgumentsValuesValidationInfo
- NO_ERROR = new ArgumentsValuesValidationInfo(Collections.<Integer, ArgumentValueError>emptyMap(), false);
-
- private final Map<Integer, ArgumentValueError> myPositionOfErrorArguments = new HashMap<Integer, ArgumentValueError>();
- private final boolean myNotEnoughArguments;
-
- /**
- * @param positionOfErrorArguments map of [argument_position, its_value_error]
- * @param notEnoughArguments true if not enough arguments values provided (i.e. some required arg missed)
- */
- ArgumentsValuesValidationInfo(@NotNull final Map<Integer, ArgumentValueError> positionOfErrorArguments,
- final boolean notEnoughArguments) {
- myPositionOfErrorArguments.putAll(positionOfErrorArguments);
- myNotEnoughArguments = notEnoughArguments;
- }
-
- /**
- * @return map of [argument_position, its_value_error]
- */
- @NotNull
- Map<Integer, ArgumentValueError> getPositionOfErrorArguments() {
- return Collections.unmodifiableMap(myPositionOfErrorArguments);
- }
-
- /**
- * @return if not enough argument values provided (i.e. some required arg missed)
- */
- boolean isNotEnoughArguments() {
- return myNotEnoughArguments;
- }
-
- /**
- * Type of argument value error.
- */
- enum ArgumentValueError {
- /**
- * This argument is redundant
- */
- EXCESS,
- /**
- * Argument has bad value
- */
- BAD_VALUE
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Simple command implementation
- *
- * @author Ilya.Kazakevich
- */
-public class CommandAdapter implements Command {
- @NotNull
- private final String myName;
- @Nullable
- private final String myHelp;
- @NotNull
- private final ArgumentsInfo myArgumentsInfo;
-
- /**
- * @param help help text
- * @param name command name
- * @param argumentsInfo arguments info
- */
- public CommandAdapter(@NotNull final String name, @Nullable final String help, @NotNull ArgumentsInfo argumentsInfo) {
- myName = name;
- myHelp = help;
- myArgumentsInfo = argumentsInfo;
- }
-
- /**
- * @return command name
- */
- @Override
- @NotNull
- public final String getName() {
- return myName;
- }
-
-
- @Override
- @Nullable
- public final String getHelp() {
- return myHelp;
- }
-
-
- @NotNull
- @Override
- public final ArgumentsInfo getArgumentsInfo() {
- return myArgumentsInfo;
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.ArrayUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Information about command and its arguments to save in history / pass to execution etc.
- *
- * @author Ilya.Kazakevich
- */
-public class CommandExecutionInfo {
- /**
- * Command and arguments separator.
- */
- private static final String SEPARATOR = " ";
- @NotNull
- private final String myCommandName;
- @NotNull
- private final String[] myArguments;
-
- /**
- * @param commandName command
- * @param arguments its arguments
- */
- public CommandExecutionInfo(@NotNull final String commandName, @NotNull final String... arguments) {
- myCommandName = commandName;
- myArguments = arguments.clone();
- }
-
- /**
- * @return command
- */
- @NotNull
- public String getCommandName() {
- return myCommandName;
- }
-
- /**
- * @return command arguments
- */
- @NotNull
- public String[] getArguments() {
- return myArguments.clone();
- }
-
- /**
- * @return command in format "command arg1 arg2". Opposite to {@link #fromString(String)}
- * @see #fromString(String)
- */
- @NotNull
- public String toString() {
- // TODO: What if command or argument has space in it? Escape somehow!
- return StringUtil.join(ArrayUtil.mergeArrays(new String[]{myCommandName}, myArguments), SEPARATOR);
- }
-
- /**
- * @param stringToUnserialize string created by {@link #toString()}
- * @return command parsed from string
- * @see #toString()
- */
- @Nullable
- public static CommandExecutionInfo fromString(@NotNull final String stringToUnserialize) {
- // TODO: What if command or argument has space in it? Escape somehow!
- final List<String> strings = StringUtil.split(stringToUnserialize, SEPARATOR);
- if (strings.isEmpty()) {
- return null;
- }
- return new CommandExecutionInfo(strings.get(0), ArrayUtil.toStringArray(strings.subList(1, strings.size())));
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof CommandExecutionInfo)) return false;
-
- CommandExecutionInfo info = (CommandExecutionInfo)o;
-
- if (!Arrays.equals(myArguments, info.myArguments)) return false;
- if (!myCommandName.equals(info.myCommandName)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = myCommandName.hashCode();
- result = 31 * result + Arrays.hashCode(myArguments);
- return result;
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.ArrayUtil;
-import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
-import com.jetbrains.python.commandInterface.CommandInterfaceView;
-import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
-import com.jetbrains.python.optParse.MalformedCommandLineException;
-import com.jetbrains.python.optParse.ParsedCommandLine;
-import com.jetbrains.python.optParse.WordWithPosition;
-import com.jetbrains.python.suggestionList.SuggestionsBuilder;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.*;
-
-/**
- * Command-line interface presenter that is command-based
- *
- * @param <C> Command type
- * @author Ilya.Kazakevich
- */
-public class CommandInterfacePresenterCommandBased<C extends Command> extends CommandInterfacePresenterAdapter {
- /**
- * [name] -> command. Linked is used to preserve order.
- */
- private final Map<String, C> myCommands = new LinkedHashMap<String, C>();
- /**
- * currenly used strategy (see interface for more info)
- */
- private Strategy myStrategy;
-
-
- /**
- * @param view view
- * @param commands available commands
- */
- public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
- @NotNull final Iterable<C> commands) {
- super(view);
- for (final C command : commands) {
- myCommands.put(command.getName(), command);
- }
- }
-
- /**
- * @param view view
- * @param commands available commands
- */
- public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
- @NotNull final C... commands) {
- this(view, Arrays.asList(commands));
- }
-
- @Override
- public void launch() {
- /*myView.setPreferredWidthInChars(getMaximumCommandWithArgsLength());*/
- super.launch();
- myStrategy = new NoCommandStrategy(this);
- }
-
- @Override
- public void textChanged(final boolean inForcedTextMode) {
- configureStrategy();
- myView.setSubText(myStrategy.getSubText());
- final Pair<SpecialErrorPlace, List<WordWithPosition>> errorInfo = myStrategy.getErrorInfo();
- myView.showErrors(errorInfo.getSecond(), errorInfo.first);
- myView.setBalloons(myStrategy.getBalloonsToShow());
-
- final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
- final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
-
- final String lastPart = getLastPart();
- if ((lastPart != null) && myStrategy.isUnknownTextExists()) {
- //Filter to starts from
- final Iterator<String> iterator = suggestions.iterator();
- while (iterator.hasNext()) {
- final String textToCheck = iterator.next();
-
- if (!textToCheck.startsWith(lastPart)) {
- iterator.remove();
- }
- }
- }
-
- if (!suggestionInfo.myShowOnlyWhenRequested && !suggestions.isEmpty()) {
- final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
- suggestionsBuilder.add(suggestions);
-
- myView
- .displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
- }
- else {
- myView.removeSuggestions();
- }
- }
-
- /**
- * @return builder that already has history in its prefix group (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
- */
- @NotNull
- private SuggestionsBuilder getBuilderWithHistory() {
- return new SuggestionsBuilder();
-
- // TODO: Uncomment when history would be fixed
- /*final SuggestionsBuilder suggestionsBuilder = new SuggestionsBuilder();
- final List<CommandExecutionInfo> history = getHistory();
- final Collection<String> historyCommands = new LinkedHashSet<String>();
- for (final CommandExecutionInfo info : history) {
- historyCommands.add(info.toString());
- }
-
- if (!historyCommands.isEmpty()) {
- // TODO: Later implement folding by name
- suggestionsBuilder.changeGroup(false);
- suggestionsBuilder
- .add(ArrayUtil.toStringArray(historyCommands));
- suggestionsBuilder.changeGroup(true);
- }
-
- return suggestionsBuilder;*/
- }
-
- /**
- * @return execution info from history. It is empty by default, child should implement it.
- */
- @NotNull
- protected List<CommandExecutionInfo> getHistory() {
- return Collections.emptyList();
- }
-
- /**
- * @return command that entered in box, or null of just entered
- */
- @Nullable
- protected CommandExecutionInfo getCommandToExecute() {
- return myStrategy.getCommandToExecute();
- }
-
- /**
- * Finds and sets appropriate strategy
- */
- private void configureStrategy() {
- final ParsedCommandLine line = getParsedCommandLine();
- if (line != null) {
- final Command command = myCommands.get(line.getCommand().getText());
- if (command != null) {
- myStrategy = new InCommandStrategy(command, line, this);
- return;
- }
- }
- myStrategy = new NoCommandStrategy(this); // No command or bad command found
- }
-
- @Override
- public void completionRequested(@Nullable final String valueFromSuggestionList) {
- if (valueFromSuggestionList != null) {
- final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
- if (suggestionInfo.getSuggestions().contains(valueFromSuggestionList)) {
- final ParsedCommandLine commandLine = getParsedCommandLine();
- final List<String> words = commandLine != null ? commandLine.getAsWords() : new ArrayList<String>();
- if (!words.isEmpty() && myView.isCaretOnWord()) {
- words.remove(words.size() - 1);
- }
- words.add(valueFromSuggestionList);
- myView.forceText(StringUtil.join(words, " "));
- }
- }
- myView.removeSuggestions();
- }
-
- @Override
- public void suggestionRequested() {
- final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
- final List<String> suggestions = suggestionInfo.getSuggestions();
- if (!suggestions.isEmpty()) {
- final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
- suggestionsBuilder.add(suggestions);
- myView.displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
- }
- }
-
- @Override
- public void executionRequested(@Nullable final String valueFromSuggestionList) {
-
- }
-
- /**
- * @return [command_name => command] all available commands
- */
- @NotNull
- protected final Map<String, C> getCommands() {
- return Collections.unmodifiableMap(myCommands);
- }
-
- /**
- * @return parsed commandline entered by user
- */
- @Nullable
- final ParsedCommandLine getParsedCommandLine() {
- try {
- return new ParsedCommandLine(myView.getText());
- }
- catch (final MalformedCommandLineException ignored) {
- return null;
- }
- }
-
-
- /**
- * @return last part of splitted text (if any). I.e. "foo bar spam" will return "spam"
- */
- @Nullable
- final String getLastPart() {
- final ParsedCommandLine commandLine = getParsedCommandLine();
- if (commandLine == null || commandLine.getAsWords().isEmpty()) {
- return null;
- }
- final List<String> words = commandLine.getAsWords();
- return words.get(words.size() - 1);
- }
-
- /**
- * @return view
- */
- @NotNull
- CommandInterfaceView getView() {
- return myView;
- }
-
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.openapi.util.Pair;
-import com.intellij.util.ArrayUtil;
-import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.commandInterface.CommandInterfaceView;
-import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
-import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
-import com.jetbrains.python.optParse.ParsedCommandLine;
-import com.jetbrains.python.optParse.WordWithPosition;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * Strategy implementation for case when user entered command
- *
- * @author Ilya.Kazakevich
- */
-final class InCommandStrategy extends Strategy {
- @NotNull
- private final List<String> myArguments = new ArrayList<String>();
- @NotNull
- private final Command myCommand;
- @NotNull
- private final ParsedCommandLine myCommandLine;
-
- /**
- * @param command command enrtered by user
- * @param presenter presenter
- */
- InCommandStrategy(@NotNull final Command command,
- @NotNull final ParsedCommandLine commandLine,
- @NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
- super(presenter);
- myArguments.addAll(WordWithPosition.fetchText(commandLine.getArguments()));
- myCommand = command;
- myCommandLine = commandLine;
- }
-
- @NotNull
- @Override
- public String getSubText() {
- final String help = myCommand.getHelp();
- if (help != null) {
- return help;
- }
- return "Place to display help";
- }
-
- @NotNull
- @Override
- SuggestionInfo getSuggestionInfo() {
- final Argument nextArgument = myCommand.getArgumentsInfo().getArgument(myCommandLine.getArguments().size());
- if (nextArgument != null) {// TODO: Check options!
- // If next arg exists
- final List<String> availableValues = nextArgument.getAvailableValues();
- if (availableValues != null) { // If has available values
- return new SuggestionInfo(false, false, availableValues);
- }
- }
- return new SuggestionInfo(false, false, Collections.<String>emptyList());
- }
-
- @NotNull
- @Override
- List<WordWithPosition> getBalloonsToShow() {
- // Display argument balloons right from command end to last argument end
- final ArgumentsInfo argumentsInfo = myCommand.getArgumentsInfo();
- final List<WordWithPosition> arguments = myCommandLine.getArguments();
- if (arguments.isEmpty()) {
- // If no arguments provided, then display first argument popup right after command
- final Argument firstArgument = argumentsInfo.getArgument(0);
- if (firstArgument == null) {
- return Collections.emptyList(); // Looks like no argument required
- }
- return Collections
- .singletonList(new WordWithPosition(firstArgument.getHelpText(), myCommandLine.getCommand().getTo() + 1));
- }
-
- final List<WordWithPosition> result = new ArrayList<WordWithPosition>(arguments.size());
- for (int i = 0; i < arguments.size(); i++) {
- final WordWithPosition argEnteredByUser = arguments.get(i);
- final Argument argument = argumentsInfo.getArgument(i);
- if (argument != null) {
- // Display argument help
- result.add(argEnteredByUser.copyWithDifferentText(argument.getHelpText()));
- }
- }
- return result;
- }
-
- @Override
- boolean isUnknownTextExists() {
- if (myCommandLine.getAsWords().isEmpty()) {
- return false; // Command only
- }
- final String lastPart = myPresenter.getLastPart();
- return ((lastPart != null) && !getSuggestionInfo().getSuggestions().contains(lastPart));
- }
-
- @Nullable
- @Override
- CommandExecutionInfo getCommandToExecute() {
- return new CommandExecutionInfo(myCommand.getName(), ArrayUtil.toStringArray(myArguments));
- }
-
- @NotNull
- @Override
- Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
- final List<WordWithPosition> userProvidedValues = myCommandLine.getArguments();
- SpecialErrorPlace specialError = null;
- final List<WordWithPosition> errors = new ArrayList<WordWithPosition>();
-
- final ArgumentsValuesValidationInfo validation =
- myCommand.getArgumentsInfo().validateArgumentValues(WordWithPosition.fetchText(userProvidedValues));
- if (validation.isNotEnoughArguments()) {
- specialError = SpecialErrorPlace.AFTER_LAST_CHAR;
- }
- for (final Entry<Integer, ArgumentValueError> errorEntry : validation.getPositionOfErrorArguments().entrySet()) {
- final String errorText = (errorEntry.getValue() == ArgumentValueError.BAD_VALUE ?
- PyBundle.message("commandsWithArgs.validation.badValue") :
- PyBundle.message("commandsWithArgs.validation.excess") );
- errors.add(userProvidedValues.get(errorEntry.getKey()).copyWithDifferentText(errorText));
- }
-
-
- return Pair.create(specialError, errors);
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.google.common.collect.ContiguousSet;
-import com.google.common.collect.DiscreteDomain;
-import com.google.common.collect.Range;
-import com.intellij.util.containers.hash.HashMap;
-import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Case when command has no arguments (for sure!)
- *
- * @author Ilya.Kazakevich
- */
-public final class NoArgumentsInfo implements ArgumentsInfo {
- /**
- * Instance to use when command has no arguments
- */
- public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo();
-
- private NoArgumentsInfo() {
- }
-
- @Nullable
- @Override
- public Argument getArgument(final int argumentPosition) {
- return null;
- }
-
- @NotNull
- @Override
- public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
- if (argumentValuesToCheck.isEmpty()) {
- return ArgumentsValuesValidationInfo.NO_ERROR;
- }
- final Map<Integer, ArgumentValueError> errors =
- new HashMap<Integer, ArgumentsValuesValidationInfo.ArgumentValueError>();
- for (final int errorPosition : ContiguousSet.create(Range.closedOpen(0, argumentValuesToCheck.size()), DiscreteDomain.integers())) {
- errors.put(errorPosition, ArgumentValueError.EXCESS);
- }
- return new ArgumentsValuesValidationInfo(errors, false);
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
-import com.jetbrains.python.optParse.WordWithPosition;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Strategy implementation for case when no command parsed
- *
- * @author Ilya.Kazakevich
- */
-final class NoCommandStrategy extends Strategy {
-
- private static final Pair<SpecialErrorPlace, List<WordWithPosition>>
- NO_ERROR = Pair.create(null, Collections.<WordWithPosition>emptyList());
-
- NoCommandStrategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
- super(presenter);
- }
-
- @NotNull
- @Override
- String getSubText() {
- return PyBundle.message("commandsWithArgs.enterCommand.label");
- }
-
- @NotNull
- @Override
- SuggestionInfo getSuggestionInfo() {
- return new SuggestionInfo(true, isTextBoxEmpty(), new ArrayList<String>(myPresenter.getCommands().keySet()));
- }
-
- @Override
- boolean isUnknownTextExists() {
- return !myPresenter.getView().getText().isEmpty();
- }
-
-
- @NotNull
- @Override
- Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
- // No error if textbox empty, but mark everything as error if some text entered: it is junk (it can't be command,
- // InCommand strategy were selected otherwise)
- return isTextBoxEmpty() ? NO_ERROR : Pair.create(SpecialErrorPlace.WHOLE_TEXT, Collections.<WordWithPosition>emptyList());
- }
-
- private boolean isTextBoxEmpty() {
- return myPresenter.getView().getText().isEmpty();
- }
-
- @Nullable
- @Override
- CommandExecutionInfo getCommandToExecute() {
- return null;
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import com.intellij.openapi.util.Pair;
-import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
-import com.jetbrains.python.optParse.WordWithPosition;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Strategy that helps {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
- * to deal with 2 states: when no text entered (or some junk enetered) or command name entered
- *
- * @author Ilya.Kazakevich
- */
-abstract class Strategy {
- @NotNull
- protected final CommandInterfacePresenterCommandBased<?> myPresenter;
-
- /**
- * @param presenter presenter
- */
- protected Strategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
- myPresenter = presenter;
- }
-
- /**
- * @return sub text to display
- */
- @NotNull
- abstract String getSubText();
-
- /**
- * @return suggestions
- */
- @NotNull
- abstract SuggestionInfo getSuggestionInfo();
-
- // TODO: Merge baloon and error (actually the same)
- @NotNull
- List<WordWithPosition> getBalloonsToShow() {
- return Collections.emptyList();
- }
-
- /**
- * @return command that entered in box, or null of just entered
- */
- @Nullable
- abstract CommandExecutionInfo getCommandToExecute();
-
-
- /**
- * @return errors
- */
- @NotNull
- abstract Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo();
-
- /**
- * @return if text entered by user contains some unknown commands
- */
- abstract boolean isUnknownTextExists();
-}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Information abouyt suggestion, provided by {@link com.jetbrains.python.commandInterface.commandsWithArgs.Strategy}
- *
- * @author Ilya.Kazakevich
- */
-@SuppressWarnings("PackageVisibleField")
-// No do not need to hide field: everything is internal API in package, anyway
-final class SuggestionInfo {
- /**
- * Suggestions
- */
- private final List<String> mySuggestions = new ArrayList<String>();
- /**
- * Display them at absolute location or relative to last letter
- */
- final boolean myAbsolute;
- /**
- * Show then any time, or only when user requests them
- */
- final boolean myShowOnlyWhenRequested;
-
- /**
- * @param absolute Display them at absolute location or relative to last letter
- * @param showOnlyWhenRequested Show then any time, or only when user requests them
- * @param suggestions Suggestions
- */
- SuggestionInfo(final boolean absolute,
- final boolean showOnlyWhenRequested,
- @NotNull final List<String> suggestions) {
- myAbsolute = absolute;
- myShowOnlyWhenRequested = showOnlyWhenRequested;
- mySuggestions.addAll(suggestions);
- }
-
- /**
- * @return suggestions
- */
- @NotNull
- List<String> getSuggestions() {
- return Collections.unmodifiableList(mySuggestions);
- }
-}
+++ /dev/null
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * <h1>Optparse-based commandline interface presenter</h1>
- * <p>
- * Command-line like interface presenter that uses conception of command and its arguments.
- * See {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
- * and its arguments: {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}.
- *
- * It supports <a href="https://docs.python.org/2/library/optparse.html">optparse</a> terminology, so
- * read it first and use {@link com.jetbrains.python.optParse} package
- * </p>
- * <h2>Arguments and validation</h2>
- * <p>
- * Optparse arguments are <strong>positional and unnamed</strong>.
- * Each {@link com.jetbrains.python.commandInterface.commandsWithArgs.Command command} provides
- * {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo arguments info}.
- * It can be used to obtain information about argument (like list of possible values) and it also used to validate argument values,
- * provided by user. In most cases we have no idea about arguments: due to optparse limitations only help test is available.
- * But sometimes we do know (like when args are documented).
- * Different strategies exist, so be sure to check {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo} children
- * </p>
- *
- *
- * @see com.jetbrains.python.optParse
- * @author Ilya.Kazakevich
- */
-package com.jetbrains.python.commandInterface.commandsWithArgs;
\ No newline at end of file
* It has several abilities, including (but not limited):
* <ol>
* <li>Suggestion box</li>
- * <li>Error marking</li>
+ * <li>Error emphasising</li>
* <li>Popups</li>
* <li>AutoCompletion</li>
* </ol>
*
* <p>
* There is also swing-based view implementation in {@link com.jetbrains.python.commandInterface.swingView}
- * and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.commandsWithArgs}
+ * and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter}
* </p>
*
+ * <p>
+ * Presenter and View talk to each other only in term of text and chars. Presenter knows nothing about pixels and should never
+ * assume view have certain pixel size.
+ * </p>
+ *
+ *
*
* @author Ilya.Kazakevich
*/
import com.google.common.base.Preconditions;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
-import com.intellij.openapi.fileEditor.impl.EditorWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.Range;
+import com.intellij.util.containers.HashSet;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
-import com.jetbrains.python.optParse.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionList;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
import java.awt.*;
-import java.awt.event.*;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
/**
- * Command-interface view implementation based on Swing
+ * Command-interface view implementation based on Swing.
+ * It uses balloons to display errors and infos, drop-down for suggestions and also underlines errors
*
* @author Ilya.Kazakevich
*/
public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener, CaretListener {
private static final JBColor ERROR_COLOR = JBColor.RED;
+
+ /**
+ * We need to track balloons, so we have field with callback
+ */
+ @NotNull
+ private final BalloonManager myBalloonManager = new BalloonManager();
/**
* Pop-up we displayed in
*/
* Lower (sub) label
*/
private JLabel mySubLabel;
+ /**
+ * "Suggestion area". Suggestion status is displayed when caret meets this area
+ */
+ private final List<Range<Integer>> myPlacesWhereSuggestionsAvailable = new ArrayList<Range<Integer>>();
@NotNull
private final CommandInterfacePresenter myPresenter;
/**
* List to display suggestions
*/
@NotNull
- private final SuggestionList myList;
+ private final SuggestionList mySuggestionList;
/**
* Displayed when there is no text
*/
@Nullable
private final String myPlaceHolderText;
+
/**
- * Flag that indicates we are in "test forced" mode: current text set by presenter, not by user
+ * Information balloons that should be displayed when caret meets their boundaries.
*/
- private boolean myInForcedTextMode;
- // TODO: Doc
- @NotNull
- private final List<WordWithPosition> myBalloons = new ArrayList<WordWithPosition>();
@NotNull
+ private final List<WordWithPosition> myInfoBalloons = new ArrayList<WordWithPosition>();
+ /**
+ * Error balloons that should be displayed when caret meets their boundaries.
+ * Errors are always underlined, but balloons are displayed only if caret meets error
+ */
private final List<WordWithPosition> myErrorBalloons = new ArrayList<WordWithPosition>();
+ /**
+ * Default subtext to display when caret is out of {@link #myPlacesWhereSuggestionsAvailable "suggestion" area}
+ */
+ @Nullable
+ private String myDefaultSubText;
/**
* @param presenter our presenter
myMainTextField.setFocusable(true);
- final EditorWindow window = FileEditorManagerEx.getInstanceEx(project).getCurrentWindow();
- final int windowSize;
- if (window != null) {
- windowSize = window.getSize().width;
- }
- else {
- windowSize = 0; // Windows size is unknown
- }
+ final int windowWidth = FileEditorManagerEx.getInstanceEx(project).getComponent().getRootPane().getWidth() - 10; // Little gap
myMainTextField
- .setPreferredWidthInPx(windowSize);
- myList = new SuggestionList(new MySuggestionListListener());
+ .setPreferredWidthInPx(windowWidth);
+ mySuggestionList = new SuggestionList(new MySuggestionListListener());
}
}
});
myMainTextField.setFocusTraversalKeysEnabled(false);
- myMainTextField.addKeyListener(new MyKeyListener());
+ myMainTextField.addKeyListener(new MyKeyListener()); // Up/down arrows are not handles with actions
+
+ // Register all available actions
+ for (final KeyStrokeInfo strokeInfo : KeyStrokeInfo.values()) {
+ strokeInfo.register(myPresenter, mySuggestionList, myMainTextField);
+ }
myMainPopUp.showInFocusCenter();
}
public void displaySuggestions(@NotNull final SuggestionsBuilder suggestions, final boolean absolute, @Nullable final String toSelect) {
int left = 0;
- // Display text right after line ends if not in "absolute" mode
+ // Display text right after caret
if (!absolute) {
- left = myMainTextField.getTextEndPosition();
+ left = myMainTextField.getTextCaretPositionInPx();
}
- myList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
+ mySuggestionList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
+ configureAppropriateStatus();
}
-
@Override
public void onClosed(final LightweightWindowEvent event) {
super.onClosed(event);
- myList.close();
- }
-
- @Override
- public final void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable final SpecialErrorPlace specialErrorPlace) {
- for (final WordWithPosition error : errors) {
- myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
- }
- if (specialErrorPlace != null) {
- myMainTextField.underlineText(specialErrorPlace, ERROR_COLOR);
- }
- synchronized (myErrorBalloons) {
- myErrorBalloons.clear();
- myErrorBalloons.addAll(errors);
- }
+ mySuggestionList.close();
}
processDocumentChange();
}
+
@Override
public void removeUpdate(final DocumentEvent e) {
processDocumentChange();
private void processDocumentChange() {
myMainTextField.hideUnderline();
- myPresenter.textChanged(myInForcedTextMode);
+ myPresenter.textChanged();
}
@Override
}
- @Override
- public void forceText(@NotNull final String newText) {
- myInForcedTextMode = true;
- myMainTextField.setText(newText);
- myInForcedTextMode = false;
- }
-
@Override
public void removeSuggestions() {
- myList.close();
+ mySuggestionList.close();
+ configureAppropriateStatus();
}
+
@Override
public final void caretUpdate(final CaretEvent e) {
- // TODO: Stupid copy/paste, fix by method extract
// When caret moved, we need to check if balloon has to be displayed
+ displayBalloonsIfRequired();
+ configureAppropriateStatus();
+ }
+
+ private void configureAppropriateStatus() {
+ if (!mySuggestionList.isClosed()) {
+ // Tell user she may use TAB to complete
+ mySubLabel.setText(PyBundle.message("commandLine.subText.key.complete", KeyStrokeInfo.COMPLETION.getText()));
+ return;
+ }
+
+ // If we are in "suggestion available" place -- tell it
+ for (final Range<Integer> range : myPlacesWhereSuggestionsAvailable) {
+ final boolean specialCaseAfterLastChar = isAfterLastCharRange(range) && getCaretPosition() == myMainTextField.getText().length();
+ if (range.isWithin(getCaretPosition()) || specialCaseAfterLastChar) {
+ mySubLabel.setText(PyBundle.message("commandLine.subText.key.suggestions", KeyStrokeInfo.SUGGESTION.getText()));
+ return;
+ }
+ }
+
+ // We may simply tell user she may execute command
+ if (myDefaultSubText != null) {
+ mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeCommand", KeyStrokeInfo.EXECUTION.getText(), myDefaultSubText));
+ }
+ else {
+ mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeUnknown", KeyStrokeInfo.EXECUTION.getText()));
+ }
+ }
+
+
+ private void displayBalloonsIfRequired() {
synchronized (myErrorBalloons) {
- showBaloons(myErrorBalloons, Position.below, MessageType.ERROR);
+ if (mySuggestionList.isClosed()) { // No need to display error popups when suggestion list is displayed. It intersects.
+ showBalloons(myErrorBalloons, Position.below, MessageType.ERROR);
+ }
}
- synchronized (myBalloons) {
- showBaloons(myBalloons, Position.above, MessageType.INFO);
+ synchronized (myInfoBalloons) {
+ showBalloons(myInfoBalloons, Position.above, MessageType.INFO);
}
}
- // TODO: Doc
- private void showBaloons(@NotNull final List<WordWithPosition> balloons,
- @NotNull final Position popUpPosition,
- @NotNull final MessageType messageType) {
+ /**
+ * Displays some balloons
+ *
+ * @param balloons balloons to display
+ * @param popUpPosition where ti display them. Only {@link Position#above} and {@link Position#below} are supported!
+ * @param messageType may be {@link MessageType#ERROR} or {@link MessageType#INFO} for example
+ */
+ private void showBalloons(@NotNull final List<WordWithPosition> balloons,
+ @NotNull final Position popUpPosition,
+ @NotNull final MessageType messageType) {
Preconditions.checkArgument(popUpPosition == Position.above || popUpPosition == Position.below, "Only above or below is supported");
for (final WordWithPosition balloon : balloons) {
if (balloon.getText().isEmpty()) {
continue; // Can't be displayed if empty
}
final int caretPosition = myMainTextField.getCaretPosition();
- if (caretPosition >= balloon.getFrom() && caretPosition <= balloon.getTo()) {
+ if ((caretPosition >= balloon.getFrom() && caretPosition <= balloon.getTo())) {
final int top = (popUpPosition == Position.above ? 0 : myMainTextField.getHeight() * 2); // Display below a little bit lower
- final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCursorPosition(), top));
+ final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCaretPositionInPx(), top));
final Balloon balloonToShow =
JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(balloon.getText())).setFillColor(messageType.getPopupBackground())
.createBalloon();
balloonToShow.setAnimationEnabled(false);
+ myBalloonManager.registerBalloon(balloonToShow);
balloonToShow.show(point, popUpPosition);
}
}
}
@Override
- public final boolean isCaretOnWord() {
- final int caretPosition = myMainTextField.getCaretPosition();
- if (caretPosition == 0) {
- return false; // At the beginning of the line
+ public final void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons,
+ @NotNull final Collection<WordWithPosition> errors) {
+ synchronized (myInfoBalloons) {
+ myInfoBalloons.clear();
+ myInfoBalloons.addAll(infoBalloons);
+ }
+ synchronized (myErrorBalloons) {
+ myErrorBalloons.clear();
+ myErrorBalloons.addAll(errors);
+ }
+ for (final WordWithPosition error : errors) {
+ if (isAfterLastCharRange(error)) {
+ // In "special" case we use last char
+ myMainTextField.underlineText(ERROR_COLOR, myMainTextField.getText().length(), myMainTextField.getText().length() + 1);
+ }
+ else {
+ myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
+ }
}
- return !Character.isWhitespace(myMainTextField.getText().toCharArray()[caretPosition - 1]);
+ }
+
+ /**
+ * Checks if some range is <strong>special case</strong> {@link #AFTER_LAST_CHARACTER_RANGE}.
+ *
+ * @param range range to check
+ * @return true if special case
+ */
+ private static boolean isAfterLastCharRange(@NotNull final Range<Integer> range) {
+ return AFTER_LAST_CHARACTER_RANGE.getFrom().equals(range.getFrom()) && AFTER_LAST_CHARACTER_RANGE.getTo().equals(range.getTo());
}
@Override
- public void setBalloons(@NotNull final Collection<WordWithPosition> balloons) {
- synchronized (myBalloons) {
- myBalloons.clear();
- myBalloons.addAll(balloons);
+ public final void insertTextAfterCaret(@NotNull final String text) {
+ try {
+ myMainTextField.getDocument().insertString(myMainTextField.getCaretPosition(), text, null);
+ }
+ catch (final BadLocationException e) {
+ // TODO: Display error somehow!
+ e.printStackTrace();
}
}
@Override
- public void setSubText(@NotNull final String subText) {
- mySubLabel.setText(subText);
+ public final void replaceText(final int from, final int to, @NotNull final String newText) {
+ myMainTextField.select(from, to);
+ myMainTextField.replaceSelection(newText);
+ myBalloonManager.closeAllBalloons();
+ myPresenter.textChanged();
+ displayBalloonsIfRequired(); // This crunch but we need to recalculate balloons in this case (position is changed!)
+ }
+
+ @Override
+ public final int getCaretPosition() {
+ return myMainTextField.getCaretPosition();
}
+ @Override
+ public final void configureSubTexts(@Nullable final String defaultSubText,
+ @NotNull final List<Range<Integer>> suggestionAvailablePlaces) {
+ synchronized (myPlacesWhereSuggestionsAvailable) {
+ myPlacesWhereSuggestionsAvailable.clear();
+ myPlacesWhereSuggestionsAvailable.addAll(suggestionAvailablePlaces);
+ myDefaultSubText = defaultSubText;
+ }
+ configureAppropriateStatus();
+ }
+
/**
* Reacts on keys, pressed by user
*/
@Override
public void keyPressed(final KeyEvent e) {
super.keyPressed(e);
+
final int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP) {
- myList.moveSelection(true);
+ mySuggestionList.moveSelection(true);
}
else if (keyCode == KeyEvent.VK_DOWN) {
- myList.moveSelection(false);
- }
- else if (keyCode == KeyEvent.VK_ENTER) {
- myPresenter.executionRequested(myList.getValue());
- }
- else if (keyCode == KeyEvent.VK_TAB) {
- myPresenter.completionRequested(myList.getValue());
- }
- else if ((keyCode == KeyEvent.VK_SPACE) && (e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK)) {
- myPresenter.suggestionRequested();
+ mySuggestionList.moveSelection(false);
}
}
}
return myMainTextField.getText();
}
- @Override
- public void setPreferredWidthInChars(final int widthInChars) {
- myMainTextField.setPreferredWidthInChars(widthInChars);
- }
/**
* Listener for suggestion list
removeSuggestions();
}
}
+
+ /**
+ * Keeps tracks for baloons to close all of them in case of text inserion
+ */
+ private static final class BalloonManager extends JBPopupAdapter {
+ @NotNull
+ private final Set<Balloon> myCurrentBaloons = new HashSet<Balloon>();
+
+ void registerBalloon(final Balloon balloon) {
+ synchronized (myCurrentBaloons) {
+ myCurrentBaloons.add(balloon);
+ balloon.addListener(this);
+ }
+ }
+
+ @Override
+ public void onClosed(final LightweightWindowEvent event) {
+ synchronized (myCurrentBaloons) {
+ myCurrentBaloons.remove(event.asBalloon());
+ }
+ super.onClosed(event);
+ }
+
+ void closeAllBalloons() {
+ synchronized (myCurrentBaloons) {
+ for (final Balloon balloon : myCurrentBaloons) {
+ balloon.dispose();
+ }
+ }
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Complete current command or argument" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class CompletionKeyStrokeAction extends KeyStrokeAction {
+ CompletionKeyStrokeAction() {
+ super(KeyStrokeInfo.COMPLETION);
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ myPresenter.completionRequested(mySuggestionList.getValue());
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Execute command" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class ExecutionKeyStrokeAction extends KeyStrokeAction {
+ ExecutionKeyStrokeAction() {
+ super(KeyStrokeInfo.EXECUTION);
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ myPresenter.executionRequested();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.swingView;
+
+import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
+import com.jetbrains.python.suggestionList.SuggestionList;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.text.TextAction;
+
+/**
+ * Action that should be taken for certain {@link javax.swing.KeyStroke} (wrapped in {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}).
+ * You need to call {@link #configure(com.jetbrains.python.commandInterface.CommandInterfacePresenter, com.jetbrains.python.suggestionList.SuggestionList)}
+ * to enable one.
+ *
+ * @author Ilya.Kazakevich
+ */
+@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "NonSerializableFieldInSerializableClass"}) // Will never serialize
+abstract class KeyStrokeAction extends TextAction {
+ @NotNull
+ private final String myName;
+ @NotNull
+ private final KeyStrokeInfo myStroke;
+
+ protected CommandInterfacePresenter myPresenter;
+ protected SuggestionList mySuggestionList;
+
+ /**
+ * @param stroke key stroke to bind this info to
+ */
+ KeyStrokeAction(@NotNull final KeyStrokeInfo stroke) {
+ super(stroke.name());
+ myName = stroke.name();
+ myStroke = stroke;
+ }
+
+
+ /**
+ * Configures action.
+ *
+ * @param presenter presenter to be used for call back.
+ * @param suggestionList list of suggestions to be used for call back
+ * @return name of this action to add to {@link javax.swing.InputMap}
+ */
+ @NotNull
+ final String configure(@NotNull final CommandInterfacePresenter presenter, @NotNull final SuggestionList suggestionList) {
+ myPresenter = presenter;
+ mySuggestionList = suggestionList;
+ return myName;
+ }
+
+ /**
+ * @return stroke bound to this action
+ */
+ @NotNull
+ final KeyStrokeInfo getStroke() {
+ return myStroke;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.swingView;
+
+import com.intellij.openapi.keymap.KeymapUtil;
+import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
+import com.jetbrains.python.suggestionList.SuggestionList;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+
+/**
+ * Key strokes to be used with view.
+ * Strokes paired with action. You need to register each action via {@link javax.swing.InputMap}
+ *
+ * @author Ilya.Kazakevich
+ */
+enum KeyStrokeInfo {
+ /**
+ * "Execute command" keystroke
+ */
+ EXECUTION(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)),
+ /**
+ * "Complete current command or argument" keystroke
+ */
+ COMPLETION(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)),
+ /**
+ * "Display suggestions" keystroke.
+ */
+ SUGGESTION(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)),;
+
+ /**
+ * List of actions. Each action should be bound to some {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}
+ */
+ @NotNull
+ private static final KeyStrokeAction[] ACTIONS = {
+ new CompletionKeyStrokeAction(),
+ new ExecutionKeyStrokeAction(),
+ new SuggestionKeyStrokeAction()};
+
+ private final KeyStroke myStroke;
+
+ KeyStrokeInfo(@NotNull final KeyStroke stroke) {
+ myStroke = stroke;
+ }
+
+ /**
+ * Registers action and binds it appropriate stroke. Call if for all instances to make all actions available.
+ *
+ * @param presenter presenter to be used as call back
+ * @param suggestionList suggestion list to be used as call back
+ * @param source Component with {@link javax.swing.InputMap} and {@link javax.swing.ActionMap} (swing view itself)
+ */
+ void register(@NotNull final CommandInterfacePresenter presenter,
+ @NotNull final SuggestionList suggestionList,
+ @NotNull final JComponent source) {
+ final KeyStrokeAction action = getAction();
+ final String strokeName = action.configure(presenter, suggestionList);
+ source.getInputMap().put(myStroke, strokeName);
+ source.getActionMap().put(strokeName, action);
+ }
+
+ /**
+ * @return Human-readable name of this action (to display it to user)
+ */
+ @NotNull
+ String getText() {
+ return KeymapUtil.getKeystrokeText(myStroke);
+ }
+
+
+ /**
+ * @return action paired with stroke
+ */
+ @NotNull
+ private KeyStrokeAction getAction() {
+ for (final KeyStrokeAction action : ACTIONS) {
+ if (action.getStroke() == this) {
+ return action;
+ }
+ }
+ throw new IllegalStateException("Failed to find action for " + name());
+ }
+}
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorFontType;
-import com.intellij.openapi.util.Pair;
import com.intellij.util.Range;
import com.intellij.util.ui.StatusText;
-import com.jetbrains.python.commandInterface.CommandInterfaceView;
-import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
@NotNull
private final Collection<UnderlineInfo> myUnderlineInfo = new ArrayList<UnderlineInfo>();
private int myPreferredWidth;
- /**
- * (color, special_place) tuple to underline special place, or null if no underline required
- */
- @Nullable
- private Pair<Color, SpecialErrorPlace> mySpecialUnderlinePlace;
public SmartTextField() {
setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN));
synchronized (myUnderlineInfo) {
for (final UnderlineInfo underlineInfo : myUnderlineInfo) {
g.setColor(underlineInfo.myColor);
+ // To prevent too long underlines: last char should really be last
underline(g, underlineInfo.getFrom(), underlineInfo.getTo());
}
- if (mySpecialUnderlinePlace != null) {
- final SpecialErrorPlace place = mySpecialUnderlinePlace.second;
- g.setColor(mySpecialUnderlinePlace.first);
- final int endPosition = getTextEndPosition();
- final int from = (place == SpecialErrorPlace.WHOLE_TEXT ? 0 : endPosition - getColumnWidth());
- underline(g, from, endPosition);
- }
}
}
g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition);
}
- /**
- * @return place (in px) where entered text ends.
- */
- int getTextEndPosition() {
- return (getText().length() + 1) * getColumnWidth();
- }
/**
* @return place (in px) where caret.
*/
- int getTextCursorPosition() {
+ int getTextCaretPositionInPx() {
return (getCaretPosition() + 1) * getColumnWidth();
}
void hideUnderline() {
synchronized (myUnderlineInfo) {
myUnderlineInfo.clear();
- mySpecialUnderlinePlace = null;
}
}
- /**
- * Sets appropriate width in chars
- *
- * @param widthInChars num of chars
- */
- void setPreferredWidthInChars(final int widthInChars) {
- setColumns(widthInChars);
- }
-
/**
* Sets appropriate width in pixels
*
myPreferredWidth = width;
}
- /**
- * Display underline in special place
- *
- * @param color color to underline
- * @param specialUnderlinePlace special place to underline
- */
- void underlineText(@NotNull final SpecialErrorPlace specialUnderlinePlace,
- @NotNull final Color color) {
- synchronized (myUnderlineInfo) {
- mySpecialUnderlinePlace = Pair.create(color, specialUnderlinePlace);
- }
- }
/**
* Wrapper to display placeholder
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandInterface.swingView;
+
+import java.awt.event.ActionEvent;
+
+/**
+ * "Suggestion request" action
+ *
+ * @author Ilya.Kazakevich
+ */
+final class SuggestionKeyStrokeAction extends KeyStrokeAction {
+ SuggestionKeyStrokeAction() {
+ super(KeyStrokeInfo.SUGGESTION);
+ }
+
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ myPresenter.suggestionRequested();
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandLineParser;
+
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Command line parse result.
+ * It consists of command itself and its parts.
+ * Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or
+ * {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else.
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class CommandLineParseResult {
+ @NotNull
+ private final List<Pair<CommandLinePartType, WordWithPosition>> myParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
+ @NotNull
+ private final WordWithPosition myCommand;
+
+ CommandLineParseResult(
+ @NotNull final WordWithPosition command,
+ @NotNull final Collection<Pair<CommandLinePartType, WordWithPosition>> parts) {
+ myCommand = command;
+ myParts.addAll(parts);
+ }
+
+ /**
+ * @return command (i.e. "startapp" in "startapp my_app")
+ */
+ @NotNull
+ public WordWithPosition getCommand() {
+ return myCommand;
+ }
+
+ /**
+ * @return list of parts in format [part_type, value].
+ * For example (rm my_folder): [{@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument}, my_folder]
+ */
+ @NotNull
+ public List<Pair<CommandLinePartType, WordWithPosition>> getParts() {
+ return Collections.unmodifiableList(myParts);
+ }
+
+ /**
+ * @return all command line parts with out of part information (just words and positions).
+ * Note tha command itself is not part, only args and options are
+ * @see #getParts()
+ */
+ @NotNull
+ public Collection<WordWithPosition> getPartsNoType() {
+ final Collection<WordWithPosition> result = new ArrayList<WordWithPosition>();
+ for (final Pair<CommandLinePartType, WordWithPosition> part : myParts) {
+ result.add(part.second);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandLineParser;
+
+import com.jetbrains.python.WordWithPosition;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Engine to parse command line. It understands how options and arguments are coded in certain commandline.
+ * It supportd {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
+ * command line is command or argument. That helps you to underline or emphisize some parts.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface CommandLineParser {
+ /**
+ * @param commandLineParts command line splitted into words.
+ * @return command line information
+ * @throws MalformedCommandLineException in case of bad commandline
+ */
+ @NotNull
+ CommandLineParseResult parse(@NotNull List<WordWithPosition> commandLineParts) throws MalformedCommandLineException;
+}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.commandLineParser;
+
+/**
+ * Types of command line parts.
+ *
+ * @author Ilya.Kazakevich
+ */
+public enum CommandLinePartType {
+ /**
+ * Argument (or positional, or unnamed argument) something that has only value. Like "my_folder" in "rm my_folder"
+ */
+ ARGUMENT,
+ /**
+ * Option is named but optional parameter. Like "-l" in "ls -l".
+ */
+ OPTION,
+ /**
+ * Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand
+ */
+ UNKNOWN
+}
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.optParse;
+package com.jetbrains.python.commandLineParser;
import org.jetbrains.annotations.NotNull;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.jetbrains.python.optParse;
+package com.jetbrains.python.commandLineParser;
+import com.intellij.openapi.util.Pair;
+import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
// TODO: Support options and their arguments
*
* @author Ilya.Kazakevich
*/
-public final class ParsedCommandLine {
+public final class OptParseCommandLineParser implements CommandLineParser {
@NotNull
- private final WordWithPosition myCommand;
- @NotNull
- private final List<WordWithPosition> myArguments = new ArrayList<WordWithPosition>();
-
- /**
- * @param commandLine command line to parse
- * @throws MalformedCommandLineException if malformed commandline can't be parsed
- */
- public ParsedCommandLine(@NotNull final String commandLine) throws MalformedCommandLineException {
- final Deque<WordWithPosition> parts = new LinkedList<WordWithPosition>(WordWithPosition.splitText(commandLine));
+ @Override
+ public CommandLineParseResult parse(@NotNull final List<WordWithPosition> commandLineParts) throws MalformedCommandLineException {
+ final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(commandLineParts);
if (parts.isEmpty()) {
throw new MalformedCommandLineException("No command provided");
}
- myCommand = parts.pop();
- if (myCommand.getText().startsWith("-")) {
+ final WordWithPosition command = parts.pop();
+ final List<Pair<CommandLinePartType, WordWithPosition>> resultParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
+ if (command.getText().startsWith("-")) {
throw new MalformedCommandLineException("Command can't start with option prefix");
}
for (final WordWithPosition part : parts) {
if (part.getText().startsWith("-")) {
// This is option!
+ resultParts.add(Pair.create(CommandLinePartType.OPTION, part));
}
else {
// TODO: Check optopn argument!
- myArguments.add(part);
+ resultParts.add(Pair.create(CommandLinePartType.ARGUMENT, part));
}
}
+ return new CommandLineParseResult(command, resultParts);
}
-
-
- /**
- * @return command (i.e. "startapp" in "startapp my_app")
- */
- @NotNull
- public WordWithPosition getCommand() {
- return myCommand;
- }
-
- /**
- * @return all arguments (not options or option arguments!)
- */
- @NotNull
- public List<WordWithPosition> getArguments() {
- return Collections.unmodifiableList(myArguments);
- }
-
- /**
- * @return all parts for command line as simple words
- */
- @NotNull
- public List<String> getAsWords() {
- final List<String> result = new ArrayList<String>();
-
- result.add(myCommand.getText());
-
- for (final WordWithPosition argument : myArguments) {
- result.add(argument.getText());
- }
- // TODO: Add options as well
- return result;
- }
-
- // TODO: Add options, arguments and option arguments
}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Engine to parse command-line.
+ * Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT arguments}
+ * and {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION options}.
+ * Use need to split command line into {@link com.jetbrains.python.WordWithPosition chunks}, pass them to
+ * {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLineParseResult}.
+ *
+ * Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
+ * command line is command or argument. That helps you to underline or emphisize some parts.
+ *
+ *
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandLineParser;
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * <p>
- * An <a href="https://docs.python.org/2/library/optparse.html">optparse</a> module twin, that parses command line.
- * Unlike any other GNU/Posix parsers, it knows how to:</p>
- * <ol>
- * <li>Parse options and args with out of any knowledge about required args</li>
- * <li>Provide actual places in command line where exactly such args or opts exist./li>
- * </ol>
- * <p>
- * Be sure to read optparse manual
- * (epecially <a href="https://docs.python.org/2/library/optparse.html#terminology">terminology</a>) part.
- * </p>
- * <p>Package entry point is {@link com.jetbrains.python.optParse.ParsedCommandLine}</p>
- * @author Ilya.Kazakevich
- */
-package com.jetbrains.python.optParse;
\ No newline at end of file
return null; // Nothing is selected if list is invisible
}
final Object value = myList.getSelectedValue();
- return ((value == null) ? "" : getElement(value).mySuggestion.getText());
+ return ((value == null) ? null : getElement(value).mySuggestion.getText());
+ }
+
+ /**
+ * @return true if no suggestion list is displayed now.
+ */
+ public final synchronized boolean isClosed() {
+ return myListPopUp == null || myListPopUp.isDisposed();
}
/**