PY-11855 Run manage.py task improvements
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 10 Nov 2014 22:19:59 +0000 (01:19 +0300)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Mon, 10 Nov 2014 22:19:59 +0000 (01:19 +0300)
Initial commit

20 files changed:
python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenterAdapter.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.form [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/swingView/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/console/PydevConsoleCommunication.java
python/src/com/jetbrains/python/suggestionList/Suggestion.java [new file with mode: 0644]
python/src/com/jetbrains/python/suggestionList/SuggestionList.java [new file with mode: 0644]
python/src/com/jetbrains/python/suggestionList/SuggestionsBuilder.java [new file with mode: 0644]
python/src/com/jetbrains/python/suggestionList/package-info.java [new file with mode: 0644]

diff --git a/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java b/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java
new file mode 100644 (file)
index 0000000..79eb731
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import com.jetbrains.python.vp.Presenter;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Presenter for command-line interface to be paired with view.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface CommandInterfacePresenter extends Presenter {
+  /**
+   * Called by view when user types new text or text changed by some other reason
+   *
+   * @param inForcedTextMode text changed not by user but by calling {@link com.jetbrains.python.commandInterface.CommandInterfaceView#forceText(String)}
+   */
+  void textChanged(boolean inForcedTextMode);
+
+  /**
+   * Called by view when user requests for completion (like tab)
+   *
+   * @param valueFromSuggestionList value selected from suggestion list (if any selected)
+   */
+  void completionRequested(@Nullable String valueFromSuggestionList);
+
+  /**
+   * Called by view when user asks for suggestions (CTRL+Space)
+   */
+  void suggestionRequested();
+
+  /**
+   * Called by view when user wants to execute command (Enter is presed)
+   *
+   * @param valueFromSuggestionList value selected from suggestion list (if any selected)
+   */
+  void executionRequested(@Nullable String valueFromSuggestionList);
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenterAdapter.java b/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenterAdapter.java
new file mode 100644 (file)
index 0000000..8983e38
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Adapter that holds view.
+ * @author Ilya.Kazakevich
+ */
+
+public abstract class CommandInterfacePresenterAdapter implements CommandInterfacePresenter {
+  @NotNull
+  protected final CommandInterfaceView myView;
+
+  protected CommandInterfacePresenterAdapter(@NotNull final CommandInterfaceView view) {
+    myView = view;
+  }
+
+  @Override
+  public void launch() {
+    myView.show();
+  }
+
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java b/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java
new file mode 100644 (file)
index 0000000..d1f88b3
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import com.jetbrains.python.suggestionList.SuggestionsBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * View for command-line interface to be paired with view.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface CommandInterfaceView {
+
+  /**
+   * Launches view
+   */
+  void show();
+
+  /**
+   * 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)
+   */
+  void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
+
+  /**
+   * Displays error (like red line)
+   *
+   * @param lastOnly underline only last letter
+   */
+  void showError(boolean lastOnly);
+
+  /**
+   * Change text to the one provided
+   *
+   * @param newText text to display
+   */
+  void forceText(@NotNull String newText);
+
+  /**
+   * Display text in sub part (like hint)
+   *
+   * @param subText text to display
+   */
+  void setSubText(@NotNull String subText);
+
+  /**
+   * Hide suggestion list
+   */
+  void removeSuggestions();
+
+  /**
+   * Displays baloon with message right under the last letter.
+   *
+   * @param message text to display
+   */
+  void displayInfoBaloon(@NotNull String message);
+
+  /**
+   * @return text, entered by user
+   */
+  @NotNull
+  String getText();
+
+  /**
+   * Enlarges view to make it as big as required to display appropriate number of chars
+   *
+   * @param widthInChars number of chars
+   */
+  void setPreferredWidthInChars(int widthInChars);
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java
new file mode 100644 (file)
index 0000000..8ad1ed7
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+/**
+ * Command argument
+ *
+ * @author Ilya.Kazakevich
+ */
+public class Argument {
+  private final boolean myNamed;
+  @NotNull
+  private final String myName;
+
+  /**
+   * @param named is named argument or not
+   * @param name  name of argument
+   */
+  public Argument(final boolean named, @NotNull final String name) {
+    myNamed = named;
+    myName = name;
+  }
+
+  /**
+   * @return is named argument or not
+   */
+  public boolean isNamed() {
+    return myNamed;
+  }
+
+  /**
+   * @return name of argument
+   */
+  @NotNull
+  public String getName() {
+    return myName;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java
new file mode 100644 (file)
index 0000000..38b526e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * Command
+ *
+ * @author Ilya.Kazakevich
+ */
+public class Command {
+  @NotNull
+  private final String myName;
+  @NotNull
+  private final List<Argument> myArguments = new ArrayList<Argument>();
+
+  /**
+   * @param name      command name
+   * @param arguments command arguments
+   */
+  public Command(@NotNull final String name, @NotNull final Argument... arguments) {
+    this(name, Arrays.asList(arguments));
+  }
+
+  public Command(@NotNull final String name, @NotNull final Collection<Argument> arguments) {
+    myName = name;
+    myArguments.addAll(arguments);
+  }
+
+  /**
+   * @return command name
+   */
+  @NotNull
+  String getName() {
+    return myName;
+  }
+
+  /**
+   * @return command arguments
+   */
+  @NotNull
+  List<Argument> getArguments() {
+    return Collections.unmodifiableList(myArguments);
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java
new file mode 100644 (file)
index 0000000..19fec24
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * 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 com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.commandInterface.commandsWithArgs.Strategy.SuggestionInfo;
+import com.jetbrains.python.suggestionList.SuggestionsBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * Command-line interface presenter that is command-based
+ *
+ * @author Ilya.Kazakevich
+ */
+public class CommandInterfacePresenterCommandBased extends CommandInterfacePresenterAdapter {
+  private static final Pattern EMPTY_SPACE = Pattern.compile("\\s+");
+  /**
+   * [name] -> command. Linked is used to preserve order.
+   */
+  private final Map<String, Command> myCommands = new LinkedHashMap<String, Command>();
+  /**
+   * currenly used strategy (see interface for more info)
+   */
+  private Strategy myStrategy;
+  /**
+   * When user asks for completion, last word should be removed if it is tied to suggestion
+   */
+  private boolean myLastSuggestionTiedToWord;
+
+
+  /**
+   * @param view     view
+   * @param commands available commands
+   */
+  public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
+                                               @NotNull final Iterable<Command> commands) {
+    super(view);
+    for (final Command command : commands) {
+      myCommands.put(command.getName(), command);
+    }
+  }
+
+  /**
+   * @param view     view
+   * @param commands available commands
+   */
+  public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
+                                               @NotNull final Command... 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) {
+    myLastSuggestionTiedToWord = false;
+    configureStrategy();
+    myView.setSubText(myStrategy.getSubText());
+    switch (myStrategy.getShowErrorInfo()) {
+      case NO:
+        break;
+      case FULL:
+        myView.showError(false);
+        break;
+      case RELATIVE:
+        myView.showError(true);
+        break;
+    }
+
+    final 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
+      myLastSuggestionTiedToWord = true;
+      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()) {
+      myView
+        .displaySuggestions(new SuggestionsBuilder(suggestions, true), suggestionInfo.myAbsolute, null);
+    }
+    else {
+      myView.removeSuggestions();
+    }
+  }
+
+  /**
+   * Finds and sets appropriate strategy
+   */
+  private void configureStrategy() {
+    if (myView.getText().isEmpty()) {
+      myStrategy = new NoCommandStrategy(this);
+      return;
+    }
+    final String[] parts = getTextAsParts();
+    if (parts.length > 0) {
+      final Command command = myCommands.get(parts[0]);
+      if (command != null) {
+        myStrategy = new InCommandStrategy(command, this);
+        return;
+      }
+    }
+    myStrategy = new NoCommandStrategy(this); // Junk
+  }
+
+  @Override
+  public void completionRequested(@Nullable final String valueFromSuggestionList) {
+    if (valueFromSuggestionList != null) {
+      final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
+      if (suggestionInfo.getSuggestions().contains(valueFromSuggestionList)) {
+        final List<String> words = new ArrayList<String>(Arrays.asList(getTextAsParts()));
+        if (!words.isEmpty() && myLastSuggestionTiedToWord) {
+          words.remove(words.size() - 1);
+        }
+        words.add(valueFromSuggestionList);
+        myView.forceText(StringUtil.join(words, " "));
+      }
+    }
+    myView.removeSuggestions();
+  }
+
+  @Override
+  public void suggestionRequested() {
+    myLastSuggestionTiedToWord = false;
+
+    final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
+    final List<String> suggestions = suggestionInfo.getSuggestions();
+    if (!suggestions.isEmpty()) {
+      myView.displaySuggestions(new SuggestionsBuilder(suggestions, true), suggestionInfo.myAbsolute, null);
+    }
+  }
+
+  @Override
+  public void executionRequested(@Nullable final String valueFromSuggestionList) {
+
+  }
+
+  /**
+   * @return [command_name => command] all available commands
+   */
+  @NotNull
+  Map<String, Command> getCommands() {
+    return Collections.unmodifiableMap(myCommands);
+  }
+
+  /**
+   * @return current text splitted into parts
+   */
+  @NotNull
+  String[] getTextAsParts() {
+    final String[] parts = EMPTY_SPACE.split(myView.getText());
+    return (((parts.length == 1) && parts[0].isEmpty()) ? ArrayUtil.EMPTY_STRING_ARRAY : parts);
+  }
+
+  /**
+   * @return last part of splitted text (if any). I.e. "foo bar spam" will return "spam"
+   */
+  @Nullable
+  String getLastPart() {
+    final String[] parts = getTextAsParts();
+    return ((parts.length > 0) ? parts[parts.length - 1] : null);
+  }
+
+  /**
+   * @return view
+   */
+  @NotNull
+  CommandInterfaceView getView() {
+    return myView;
+  }
+
+  /**
+   * @return maximum length command window may need
+   */
+  private int getMaximumCommandWithArgsLength() {
+    int maxLength = 0;
+    for (final Command command : myCommands.values()) {
+      int commandLength = command.getName().length() + 1;
+      for (final Argument argument : command.getArguments()) {
+        commandLength += argument.getName().length() + 1;
+      }
+      if (commandLength > maxLength) {
+        maxLength = commandLength;
+      }
+    }
+    return maxLength;
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java
new file mode 100644 (file)
index 0000000..aa423a5
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Strategy implementation for case when user entered command
+ *
+ * @author Ilya.Kazakevich
+ */
+class InCommandStrategy extends Strategy {
+  @NotNull
+  private final List<String> myArguments = new ArrayList<String>();
+  @NotNull
+  private final Command myCommand;
+
+  /**
+   * @param command   command enrtered by user
+   * @param presenter presenter
+   */
+  InCommandStrategy(@NotNull final Command command, @NotNull final CommandInterfacePresenterCommandBased presenter) {
+    super(presenter);
+    final List<String> parts = Arrays.asList(presenter.getTextAsParts());
+    assert !parts.isEmpty() && parts.get(0).equals(command.getName()) : "At least first argument should be command";
+    myArguments.addAll(parts.subList(1, parts.size()));
+    myCommand = command;
+  }
+
+  @NotNull
+  @Override
+  public String getSubText() {
+    return "D";
+  }
+
+  @NotNull
+  @Override
+  SuggestionInfo getSuggestionInfo() {
+    final List<String> strings = new ArrayList<String>();
+    for (final Argument argument : myCommand.getArguments()) {
+      if (!myArguments.contains(argument.getName())) {
+        strings.add(argument.getName());
+      }
+    }
+    return new SuggestionInfo(false, false, strings);
+  }
+
+  @Override
+  boolean isUnknownTextExists() {
+    if (myPresenter.getTextAsParts().length == 1) {
+      return false; // Command only
+    }
+    final String lastPart = myPresenter.getLastPart();
+    return ((lastPart != null) && !getSuggestionInfo().getSuggestions().contains(lastPart));
+  }
+
+  @NotNull
+  @Override
+  ErrorInfo getShowErrorInfo() {
+    final boolean noArgsLeft = getSuggestionInfo().getSuggestions().isEmpty();
+    return (noArgsLeft ? ErrorInfo.NO : ErrorInfo.RELATIVE);
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java
new file mode 100644 (file)
index 0000000..e56ad60
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+
+/**
+ * Strategy implementation for case when no command parsed
+ *
+ * @author Ilya.Kazakevich
+ */
+class NoCommandStrategy extends Strategy {
+  NoCommandStrategy(@NotNull final CommandInterfacePresenterCommandBased presenter) {
+    super(presenter);
+  }
+
+  @NotNull
+  @Override
+  String getSubText() {
+    return "vse ploho";
+  }
+
+  @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
+  ErrorInfo getShowErrorInfo() {
+    return (isTextBoxEmpty() ? ErrorInfo.NO : ErrorInfo.FULL);
+  }
+
+
+  private boolean isTextBoxEmpty() {
+    return myPresenter.getView().getText().isEmpty();
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java
new file mode 100644 (file)
index 0000000..9e61d2c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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 org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+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();
+
+
+  /**
+   * @return errors
+   */
+  @NotNull
+  abstract ErrorInfo getShowErrorInfo();
+
+  /**
+   * @return if text entered by user contains some unknown commands
+   */
+  abstract boolean isUnknownTextExists();
+
+  /**
+   * Display error or not
+   */
+  enum ErrorInfo {
+    /**
+     * Yes, mark whole text as error
+     */
+    FULL,
+    /**
+     * Yes, mark last part as error
+     */
+    RELATIVE,
+    /**
+     * No, do not mark anything like error
+     */
+    NO
+  }
+
+  @SuppressWarnings("PackageVisibleField") // No need to hide field: everything is internal API in package, anyway
+  static class SuggestionInfo {
+    /**
+     * Suggestions
+     */
+    private final List<String> mySuggestions = new ArrayList<String>();
+    /**
+     * Display them at absolute location or relative to last letter
+     */
+    final boolean myAbsolute;
+    /**
+     * Show then any time, or only when user requests them
+     */
+    final boolean myShowOnlyWhenRequested;
+
+    /**
+     * @param absolute              Display them at absolute location or relative to last letter
+     * @param showOnlyWhenRequested Show then any time, or only when user requests them
+     * @param suggestions           Suggestions
+     */
+    SuggestionInfo(final boolean absolute,
+                   final boolean showOnlyWhenRequested,
+                   @NotNull final List<String> suggestions) {
+      myAbsolute = absolute;
+      myShowOnlyWhenRequested = showOnlyWhenRequested;
+      mySuggestions.addAll(suggestions);
+    }
+
+    /**
+     * @return suggestions
+     */
+    @NotNull
+    List<String> getSuggestions() {
+      return Collections.unmodifiableList(mySuggestions);
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java
new file mode 100644 (file)
index 0000000..2db6a19
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * 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}
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface.commandsWithArgs;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/commandInterface/package-info.java b/python/src/com/jetbrains/python/commandInterface/package-info.java
new file mode 100644 (file)
index 0000000..19c0b13
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+/**
+ * View/Presenter pair that implements so-called "command line interface".
+ * It has several abilities, including (but not limited):
+ * <ol>
+ *   <li>Suggestion box</li>
+ *   <li>Error marking</li>
+ *   <li>Popups</li>
+ *   <li>AutoCompletion</li>
+ * </ol>
+ * <p>
+ *   System consists of {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter}
+ *   and appropriate {@link com.jetbrains.python.commandInterface.CommandInterfaceView}.
+ *   See {@link com.jetbrains.python.vp} for more info.
+ * </p>
+ *
+ * <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}
+ * </p>
+ *
+ *
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.form b/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.form
new file mode 100644 (file)
index 0000000..3540a57
--- /dev/null
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.commandInterface.swingView.CommandInterfaceViewSwingImpl">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="4" left="4" bottom="4" right="2"/>
+    <constraints>
+      <xy x="1" y="67" width="404" height="245"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <hspacer id="91552">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
+            <minimum-size width="-1" height="5"/>
+          </grid>
+        </constraints>
+      </hspacer>
+      <component id="4067e" class="javax.swing.JLabel" binding="myLabel">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text value="Label"/>
+        </properties>
+      </component>
+      <grid id="30372" class="com.jetbrains.python.commandInterface.swingView.SmartTextField" binding="myMainTextField" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <enabled value="true"/>
+          <font/>
+        </properties>
+        <border type="none"/>
+        <children/>
+      </grid>
+      <hspacer id="4b381">
+        <constraints>
+          <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </hspacer>
+      <component id="7a794" class="javax.swing.JLabel" binding="mySubLabel">
+        <constraints>
+          <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text value="Label"/>
+        </properties>
+      </component>
+      <hspacer id="c568">
+        <constraints>
+          <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </hspacer>
+    </children>
+  </grid>
+</form>
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java b/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java
new file mode 100644 (file)
index 0000000..9715f38
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * 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.swingView;
+
+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.popup.Balloon.Position;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupAdapter;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.LightweightWindowEvent;
+import com.intellij.ui.awt.RelativePoint;
+import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
+import com.jetbrains.python.commandInterface.CommandInterfaceView;
+import com.jetbrains.python.suggestionList.SuggestionList;
+import com.jetbrains.python.suggestionList.SuggestionsBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * Command-interface view implementation based on Swing
+ *
+ * @author Ilya.Kazakevich
+ */
+public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener {
+  /**
+   * Pop-up we displayed in
+   */
+  @NotNull
+  private final JBPopup myMainPopUp;
+  private JPanel myPanel;
+  /**
+   * Upper label
+   */
+  private JLabel myLabel;
+  /**
+   * Text field
+   */
+  private SmartTextField myMainTextField;
+  /**
+   * Lower (sub) label
+   */
+  private JLabel mySubLabel;
+  @NotNull
+  private final CommandInterfacePresenter myPresenter;
+  /**
+   * List to display suggestions
+   */
+  @NotNull
+  private final SuggestionList myList;
+  /**
+   * 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
+   */
+  private boolean myInForcedTextMode;
+
+  /**
+   * @param presenter       our presenter
+   * @param title           view title to display
+   * @param project         project
+   * @param placeholderText text for placeholder (to be displayed when there is not text)
+   */
+  public CommandInterfaceViewSwingImpl(@NotNull final CommandInterfacePresenter presenter,
+                                       @NotNull final String title,
+                                       @NotNull final Project project,
+                                       @Nullable final String placeholderText) {
+    myPresenter = presenter;
+    myLabel.setText(title);
+    myPlaceHolderText = placeholderText;
+
+    myMainPopUp = JBPopupFactory.getInstance().createComponentPopupBuilder(myPanel, myMainTextField)
+      .setFocusable(true)
+      .setRequestFocus(true)
+      .createPopup();
+    myMainTextField.setRequestFocusEnabled(true);
+    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
+    }
+
+    myMainTextField
+      .setPreferredWidthInPx(windowSize);
+    myList = new SuggestionList(new MySuggestionListListener());
+  }
+
+
+  @Override
+  public void show() {
+    myMainTextField.getDocument().addDocumentListener(this);
+
+    myMainPopUp.addListener(this);
+    if (myPlaceHolderText != null) {
+      myMainTextField.setWaterMarkPlaceHolderText(myPlaceHolderText);
+    }
+
+
+    myMainTextField.addFocusListener(new FocusAdapter() {
+      @Override
+      public void focusLost(final FocusEvent e) {
+        super.focusLost(e);
+        myMainPopUp.cancel();
+      }
+    });
+    myMainTextField.setFocusTraversalKeysEnabled(false);
+    myMainTextField.addKeyListener(new MyKeyListener());
+    myMainPopUp.showInFocusCenter();
+  }
+
+
+  @Override
+  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
+    if (!absolute) {
+      left = myMainTextField.getTextEndPosition();
+    }
+    myList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
+  }
+
+
+  @Override
+  public void onClosed(final LightweightWindowEvent event) {
+    super.onClosed(event);
+    myList.close();
+  }
+
+  @Override
+  public void showError(final boolean lastOnly) {
+    myMainTextField.underlineText(Color.RED, lastOnly);
+  }
+
+
+  @Override
+  public void insertUpdate(final DocumentEvent e) {
+    processDocumentChange();
+  }
+
+  @Override
+  public void removeUpdate(final DocumentEvent e) {
+    processDocumentChange();
+  }
+
+  private void processDocumentChange() {
+    myMainTextField.hideUnderline();
+    myPresenter.textChanged(myInForcedTextMode);
+  }
+
+  @Override
+  public void changedUpdate(final DocumentEvent e) {
+
+  }
+
+  @Override
+  public void forceText(@NotNull final String newText) {
+    myInForcedTextMode = true;
+    myMainTextField.setText(newText);
+    myInForcedTextMode = false;
+  }
+
+  @Override
+  public void removeSuggestions() {
+    myList.close();
+  }
+
+  @Override
+  public void displayInfoBaloon(@NotNull final String message) {
+    final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextEndPosition(), 0));
+    JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(message)).createBalloon().show(point, Position.above);
+  }
+
+  @Override
+  public void setSubText(@NotNull final String subText) {
+    mySubLabel.setText(subText);
+  }
+
+
+  /**
+   * Reacts on keys, pressed by user
+   */
+  private class MyKeyListener extends KeyAdapter {
+    @Override
+    public void keyPressed(final KeyEvent e) {
+      super.keyPressed(e);
+      final int keyCode = e.getKeyCode();
+      if (keyCode == KeyEvent.VK_UP) {
+        myList.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();
+      }
+    }
+  }
+
+  @NotNull
+  @Override
+  public String getText() {
+    return myMainTextField.getText();
+  }
+
+  @Override
+  public void setPreferredWidthInChars(final int widthInChars) {
+    myMainTextField.setPreferredWidthInChars(widthInChars);
+  }
+
+  /**
+   * Listener for suggestion list
+   */
+  private class MySuggestionListListener extends JBPopupAdapter {
+
+    @Override
+    public void onClosed(final LightweightWindowEvent event) {
+      super.onClosed(event);
+      removeSuggestions();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java b/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java
new file mode 100644 (file)
index 0000000..f4b2aa6
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.swingView;
+
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorFontType;
+import com.intellij.openapi.util.Pair;
+import com.intellij.util.ui.StatusText;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Text field that has width to be changed and accepts error underline
+ *
+ * @author Ilya.Kazakevich
+ */
+@SuppressWarnings({"NonSerializableFieldInSerializableClass", "SerializableHasSerializationMethods"}) // Will never serialize it
+public class SmartTextField extends JTextField {
+  /**
+   * Placeholder for this textbox
+   */
+  private StatusText myPlaceHolder;
+  /**
+   * error (underline) info in format "lastOnly => Color" where "lastOnly" is to underline last letter only (not the whole line)
+   * Null if nothing should be displayed.
+   */
+  @Nullable
+  private Pair<Boolean, Color> myUnderlineInfo;
+  private int myPreferredWidth;
+
+  public SmartTextField() {
+    setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN));
+  }
+
+  @Override
+  protected void paintComponent(final Graphics g) {
+    super.paintComponent(g);
+    if (myPlaceHolder != null) {
+      myPlaceHolder.paint(this, g);
+    }
+    if (myUnderlineInfo != null) {
+      final int lineStart = (myUnderlineInfo.first ? getTextEndPosition() : getColumnWidth());
+      final int lineEnd = getTextEndPosition() + (myUnderlineInfo.first ? getColumnWidth() : 0);
+      g.setColor(myUnderlineInfo.second);
+      final int verticalPosition = getHeight() - 5;
+      g.drawLine(lineStart, verticalPosition, lineEnd, verticalPosition);
+    }
+  }
+
+  /**
+   * @return place (in px) where entered text ends.
+   */
+  int getTextEndPosition() {
+    return (getText().length() + 1) * getColumnWidth();
+  }
+
+  void setWaterMarkPlaceHolderText(@NotNull final String watermark) {
+    myPlaceHolder = new MyStatusText(this);
+    myPlaceHolder.setText(watermark);
+  }
+
+
+  @Override
+  public Dimension getPreferredSize() {
+    final Dimension dimension = super.getPreferredSize();
+    final int placeHolderTextLength = ((myPlaceHolder != null) ? myPlaceHolder.getText().length() : 0);
+    final int columns = Math.max(Math.max(getText().length(), placeHolderTextLength), getColumns());
+    final int desiredSize = columns * getColumnWidth();
+    return new Dimension(Math.max(Math.max(myPreferredWidth, dimension.width), desiredSize), dimension.height);
+  }
+
+  /**
+   * Display underline
+   *
+   * @param color color to underline
+   * @param lastOnly last letter only (whole line otherwise)
+   */
+  void underlineText(@NotNull final Color color, final boolean lastOnly) {
+    myUnderlineInfo = new Pair<Boolean, Color>(lastOnly, color);
+  }
+
+  /**
+   * Removes underline
+   */
+  void hideUnderline() {
+    myUnderlineInfo = null;
+  }
+
+  /**
+   * Sets appropriate width in chars
+   * @param widthInChars num of chars
+   */
+  void setPreferredWidthInChars(final int widthInChars) {
+    setColumns(widthInChars);
+  }
+
+  /**
+   * Sets appropriate width in pixels
+   * @param width width in px
+   */
+  void setPreferredWidthInPx(final int width) {
+    myPreferredWidth = width;
+  }
+
+  /**
+   * Wrapper to display placeholder
+   */
+  private class MyStatusText extends StatusText {
+    MyStatusText(final JComponent owner) {
+      super(owner);
+    }
+
+    @Override
+    protected boolean isStatusVisible() {
+      return SmartTextField.this.getText().isEmpty();
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/package-info.java b/python/src/com/jetbrains/python/commandInterface/swingView/package-info.java
new file mode 100644 (file)
index 0000000..56a2572
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Command-line like interface view GUI. See {@link com.jetbrains.python.commandInterface.swingView.CommandInterfaceViewSwingImpl}
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.commandInterface.swingView;
\ No newline at end of file
index 5bf06782d376097041620df33508f28a72b9fb71..1052d98377c373b168aad71d2c02ade72b862c9f 100644 (file)
@@ -55,7 +55,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl
 
   private static final String EXEC_LINE = "execLine";
   private static final String EXEC_MULTILINE = "execMultipleLines";
-  private static final String GET_COMPLETIONS = "getCompletions";
+  private static final String GET_COMPLETIONS = "getSuggestions";
   private static final String GET_DESCRIPTION = "getDescription";
   private static final String GET_FRAME = "getFrame";
   private static final String GET_VARIABLE = "getVariable";
diff --git a/python/src/com/jetbrains/python/suggestionList/Suggestion.java b/python/src/com/jetbrains/python/suggestionList/Suggestion.java
new file mode 100644 (file)
index 0000000..f8a6d49
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.suggestionList;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Suggestion to display to user
+ *
+ * @author Ilya.Kazakevich
+ */
+public class Suggestion {
+  @NotNull
+  private final String myText;
+  private final boolean myStrong;
+
+  /**
+   * @param text   text to display
+   * @param strong is strong or not. Strong element will be marked somehow when displayed.
+   */
+  public Suggestion(@NotNull final String text, final boolean strong) {
+    myText = text;
+    myStrong = strong;
+  }
+
+  @NotNull
+  public String getText() {
+    return myText;
+  }
+
+  public boolean isStrong() {
+    return myStrong;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof Suggestion)) return false;
+
+    Suggestion that = (Suggestion)o;
+
+    if (myStrong != that.myStrong) return false;
+    if (!myText.equals(that.myText)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myText.hashCode();
+    result = 31 * result + (myStrong ? 1 : 0);
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "Suggestion{" +
+           "myText='" + myText + '\'' +
+           ", myStrong=" + myStrong +
+           '}';
+  }
+}
diff --git a/python/src/com/jetbrains/python/suggestionList/SuggestionList.java b/python/src/com/jetbrains/python/suggestionList/SuggestionList.java
new file mode 100644 (file)
index 0000000..ef799a5
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ * 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.suggestionList;
+
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.JBPopupListener;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+/**
+ * List of suggestions to be displayed.
+ * Suggestions may be separated into groups: Group1[suggestion1, suggestion2, suggestion3].
+ * Each suggestion may also be marked as strong (see {@link com.jetbrains.python.suggestionList.Suggestion}
+ *
+ * @author Ilya.Kazakevich
+ */
+public class SuggestionList {
+  /**
+   * Listens popup close event
+   */
+  @Nullable
+  private final JBPopupListener myListener;
+  /**
+   * Popup itself
+   */
+  @Nullable
+  private JBPopup myListPopUp;
+  /**
+   * Model of suggestion list
+   */
+  private final DefaultListModel myListModel = new DefaultListModel();
+  /**
+   * Suggestion list
+   */
+  private final JList myList = new JList(myListModel);
+
+  public SuggestionList() {
+    myListener = null;
+  }
+
+  /**
+   * @param listener popup listener (will be called back when popup closed)
+   */
+  public SuggestionList(@SuppressWarnings("NullableProblems") @NotNull final JBPopupListener listener) {
+    myListener = listener;
+  }
+
+  /**
+   * @param values          suggestions wrapped into suggestion builder.
+   *                        Prefered usage pattern.
+   * @param displayPoint    point on screen to display suggestions
+   * @param elementToSelect element in list to be selected (if any)
+   * @see com.jetbrains.python.suggestionList.SuggestionsBuilder
+   */
+  public void showSuggestions(@NotNull final SuggestionsBuilder values,
+                              @NotNull final RelativePoint displayPoint,
+                              @Nullable final String elementToSelect) {
+    showSuggestions(values.getList(), displayPoint, elementToSelect);
+  }
+
+  /**
+   * @param values          suggestions in format [group1[suggestions]]. See class doc for info about groups.
+   *                        We recommend you <strong>not to use</strong> this method.
+   *                        Use {@link #showSuggestions(SuggestionsBuilder, com.intellij.ui.awt.RelativePoint, String)} instead.
+   *                        {@link com.jetbrains.python.suggestionList.SuggestionsBuilder} is easier to use
+   * @param displayPoint    point on screen to display suggestions
+   * @param elementToSelect element in list to be selected (if any)
+   */
+  public void showSuggestions(@NotNull final List<List<Suggestion>> values,
+                              @NotNull final RelativePoint displayPoint,
+                              @Nullable final String elementToSelect) {
+    close();
+    myList.setCellRenderer(new MyCellRenderer());
+    myListModel.clear();
+    if (values.isEmpty()) {
+      return;
+    }
+    // Fill and select
+
+    // Iterate through groups adding suggestions. Odd groups should be marked differently.
+    for (int groupId = 0; groupId < values.size(); groupId++) {
+      final List<Suggestion> suggestions = values.get(groupId);
+      for (int suggestionId = 0; suggestionId < suggestions.size(); suggestionId++) {
+        final Suggestion suggestion = suggestions.get(suggestionId);
+        myListModel.addElement(new SuggestionListElement((groupId % 2) == 0, suggestion));
+        if (suggestion.getText().equals(elementToSelect)) {
+          myList.setSelectedIndex(suggestionId + groupId);
+        }
+      }
+    }
+    if ((elementToSelect == null) && (!myListModel.isEmpty())) {
+      myList.setSelectedIndex(0); // Select first element
+    }
+
+    // Use scroll bars
+    final JScrollPane content = new JScrollPane(myList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
+                                                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
+    myListPopUp =
+      JBPopupFactory.getInstance().createComponentPopupBuilder(content, null)
+        .createPopup();
+    if (myListener != null) {
+      myListPopUp.addListener(myListener);
+    }
+    myListPopUp.show(displayPoint);
+  }
+
+
+  /**
+   * Close suggestion list
+   */
+  public void close() {
+    if (myListPopUp != null) {
+      myListPopUp.cancel();
+    }
+  }
+
+  /**
+   * Move selection
+   *
+   * @param directionUp up if true. Down otherwise
+   */
+  public void moveSelection(final boolean directionUp) {
+    if (myListModel.isEmpty()) {
+      return;
+    }
+    // Handle separation
+    int newIndex = myList.getSelectedIndex() + (directionUp ? -1 : 1);
+    if (newIndex < 0) {
+      newIndex = 0;
+    }
+    if (newIndex >= myListModel.size()) {
+      newIndex = myListModel.size() - 1;
+    }
+    myList.setSelectedIndex(newIndex);
+    myList.scrollRectToVisible(myList.getCellBounds(newIndex, newIndex));
+  }
+
+  /**
+   * @return currently selected value (null if nothing is selected)
+   */
+  @Nullable
+  public String getValue() {
+    final Object value = myList.getSelectedValue();
+    return ((value == null) ? "" : getElement(value).mySuggestion.getText());
+  }
+
+  /**
+   * Element that represents suggestion
+   */
+  private static class SuggestionListElement {
+    /**
+     * is part of odd group
+     */
+    private final boolean myOddGroup;
+    /**
+     * suggestion itself
+     */
+    @NotNull
+    private final Suggestion mySuggestion;
+
+    private SuggestionListElement(final boolean oddGroup,
+                                  @NotNull final Suggestion suggestion) {
+      myOddGroup = oddGroup;
+      mySuggestion = suggestion;
+    }
+  }
+
+  /**
+   * Cell renderer for suggestion
+   */
+  private static class MyCellRenderer extends DefaultListCellRenderer {
+    @NotNull
+    private static final Color ODD_GROUP_SELECTED_BACKGROUND_COLOR =
+      EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR);
+    @NotNull
+    private static final Color ODD_GROUP_BACKGROUND_COLOR =
+      EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.GUTTER_BACKGROUND);
+
+    @Override
+    public Component getListCellRendererComponent(final JList list,
+                                                  final Object value,
+                                                  final int index,
+                                                  final boolean isSelected,
+                                                  final boolean cellHasFocus) {
+      final SuggestionListElement element = getElement(value);
+      // Out parent always uses label
+      final Component component = super.getListCellRendererComponent(list, element.mySuggestion.getText(), index, isSelected,
+                                                                     cellHasFocus);
+
+
+      if (element.myOddGroup) {
+        component.setBackground(isSelected ? ODD_GROUP_SELECTED_BACKGROUND_COLOR : ODD_GROUP_BACKGROUND_COLOR);
+      }
+
+      if (!(component instanceof JLabel)) {
+        return component; // We can't change any property here
+      }
+
+      final JLabel label = (JLabel)component;
+      if (element.mySuggestion.isStrong()) {
+        // We use icons to display "strong" element
+        label.setIcon(PlatformIcons.FOLDER_ICON);
+      }
+
+
+      return component;
+    }
+  }
+
+  /**
+   * Converts argument to {@link SuggestionListElement} which always should be.
+   *
+   * @param value argument to convert
+   * @return converted argument
+   */
+  @NotNull
+  private static SuggestionListElement getElement(final Object value) {
+    assert value instanceof SuggestionListElement : "Value is not valid element: " + value;
+    return (SuggestionListElement)value;
+  }
+}
diff --git a/python/src/com/jetbrains/python/suggestionList/SuggestionsBuilder.java b/python/src/com/jetbrains/python/suggestionList/SuggestionsBuilder.java
new file mode 100644 (file)
index 0000000..dbe3c6f
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.suggestionList;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * Builds list of suggestions.
+ * You may create suggestions from words, you may add new groups and do other useful things.
+ * It works like chain pattern, so it returns itself.
+ *
+ * @author Ilya.Kazakevich
+ */
+public class SuggestionsBuilder {
+  @NotNull
+  private final List<List<Suggestion>> myList = new ArrayList<List<Suggestion>>();
+
+  public SuggestionsBuilder() {
+    myList.add(new ArrayList<Suggestion>());
+  }
+
+  /**
+   * @param words list of words to add (to the first group)
+   * @param sort  sort passed words
+   */
+  public SuggestionsBuilder(@NotNull List<String> words, final boolean sort) {
+    this();
+    if (sort) {
+      // No guarantee passed argument is mutable
+      //noinspection AssignmentToMethodParameter
+      words = new ArrayList<String>(words);
+      Collections.sort(words);
+    }
+    add(words);
+  }
+
+  /**
+   * Creates next group and sets it as default
+   */
+  public SuggestionsBuilder nextGroup() {
+    if (!getCurrentGroup().isEmpty()) {
+      myList.add(new ArrayList<Suggestion>());
+    }
+    return this;
+  }
+
+  /**
+   * @param words words to add to current group
+   */
+  public final SuggestionsBuilder add(@NotNull final String... words) {
+    return add(Arrays.asList(words));
+  }
+
+  /**
+   * @param words words to add to current group
+   */
+  public final SuggestionsBuilder add(@NotNull final Collection<String> words) {
+    for (final String word : words) {
+      add(word, false);
+    }
+    return this;
+  }
+
+  /**
+   * @param text   text to add to current group
+   * @param strong strong or not
+   */
+  public SuggestionsBuilder add(@NotNull final String text, final boolean strong) {
+    getCurrentGroup().add(new Suggestion(text, strong));
+    return this;
+  }
+
+  /**
+   * @return elements from current group
+   */
+  @NotNull
+  private List<Suggestion> getCurrentGroup() {
+    return myList.get(myList.size() - 1);
+  }
+
+  /**
+   * @return all suggestions in format [group1[sugg1, sugg2]]
+   */
+  @NotNull
+  List<List<Suggestion>> getList() {
+    return Collections.unmodifiableList(myList);
+  }
+
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof SuggestionsBuilder)) return false;
+
+    SuggestionsBuilder builder = (SuggestionsBuilder)o;
+
+    if (!myList.equals(builder.myList)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return myList.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "SuggestionsBuilder{" +
+           "myList=" + myList +
+           '}';
+  }
+}
diff --git a/python/src/com/jetbrains/python/suggestionList/package-info.java b/python/src/com/jetbrains/python/suggestionList/package-info.java
new file mode 100644 (file)
index 0000000..c96030b
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * List of suggestions to be displayed to user
+ * Start from {@link com.jetbrains.python.suggestionList.SuggestionList}
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.suggestionList;
\ No newline at end of file