Merge remote-tracking branch 'origin/master'

This commit is contained in:
Sergey Malenkov
2015-02-10 14:15:18 +03:00
110 changed files with 2134 additions and 1458 deletions
+3
View File
@@ -36,6 +36,9 @@
1.5 => plugins/Groovy/lib/groovy_rt.jar
1.5 => lib/annotations.jar
1.7 => lib/groovy-all-2.3.7.jar!org/codehaus/groovy/runtime/WritablePath.class
1.7 => lib/groovy-all-2.3.7.jar!org/codehaus/groovy/runtime/NioGroovyMethods.class
1.6 => plugins/gradle/lib/gradle-jps-plugin.jar
1.3 => plugins/junit/lib/junit-rt.jar
+4 -1
View File
@@ -85,6 +85,8 @@ target('default': 'The default target') {
system_selector: "IdeaIC${p("component.version.major")}",
tools_jar: true],
home, null, paths, true)
String macZip = "$paths.artifacts/idea${args.buildNumber}.mac.zip"
notifyArtifactBuilt(macZip)
}
target('build-dist-jars' : 'Target to build jars from locally compiled classes') {
@@ -99,6 +101,8 @@ target('build-dist-jars' : 'Target to build jars from locally compiled classes')
system_selector: "IdeaIC${p("component.version.major")}",
tools_jar: true],
home, null, paths)
String macZip = "$paths.artifacts/idea${args.buildNumber}.mac.zip"
notifyArtifactBuilt(macZip)
}
private void indexSearchableOptions() {
@@ -152,7 +156,6 @@ def layoutAll(Map args, String home, String out, Paths _paths = null, buildJps =
String macZip = "$paths.artifacts/idea${args.buildNumber}.mac.zip"
buildMacZip(macAppRoot, macZip, [paths.distAll], paths.distMac)
notifyArtifactBuilt(macZip)
buildTarGz("idea-${args.buildNumber}", "$paths.artifacts/idea${args.buildNumber}.tar", [paths.distAll, paths.distUnix])
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -137,11 +137,13 @@ public class AnnotationsHighlightUtil {
formatReference(nameRef), JavaHighlightUtil.formatType(expectedType));
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
}
if (value instanceof PsiArrayInitializerMemberValue) {
if (expectedType instanceof PsiArrayType) return null;
String description = JavaErrorMessages.message("annotation.illegal.array.initializer", JavaHighlightUtil.formatType(expectedType));
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
}
if (value instanceof PsiExpression) {
PsiExpression expr = (PsiExpression)value;
PsiType type = expr.getType();
@@ -160,8 +162,7 @@ public class AnnotationsHighlightUtil {
String description = JavaErrorMessages.message("annotation.incompatible.types",
JavaHighlightUtil.formatType(type), JavaHighlightUtil.formatType(expectedType));
final HighlightInfo info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(value).descriptionAndTooltip(description).create();
QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createSurroundWithQuotesAnnotationParameterValueFix(value, expectedType));
return info;
}
@@ -185,8 +186,7 @@ public class AnnotationsHighlightUtil {
String containedElementFQN = contained == null ? null : contained.getQualifiedName();
if (containedElementFQN != null) {
PsiClass container = annotationType;
String containerName = container.getQualifiedName();
String containerName = annotationType.getQualifiedName();
if (isAnnotationRepeatedTwice(owner, containedElementFQN)) {
String description = JavaErrorMessages.message("annotation.container.wrong.place", containerName);
return annotationError(annotationToCheck, description);
@@ -197,8 +197,8 @@ public class AnnotationsHighlightUtil {
String description = JavaErrorMessages.message("annotation.duplicate.annotation");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create();
}
PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
PsiAnnotation metaAnno = PsiImplUtil.findAnnotation(annotationType.getModifierList(), CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE);
if (metaAnno == null) {
String explanation = JavaErrorMessages.message("annotation.non.repeatable", annotationType.getQualifiedName());
String description = JavaErrorMessages.message("annotation.duplicate.explained", explanation);
@@ -223,15 +223,6 @@ public class AnnotationsHighlightUtil {
}
}
for (PsiAnnotation annotation : owner.getAnnotations()) {
if (annotation == annotationToCheck) continue;
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
if (nameRef == null) continue;
PsiElement aClass = nameRef.resolve();
if (!resolved.equals(aClass)) continue;
}
return null;
}
@@ -252,15 +243,14 @@ public class AnnotationsHighlightUtil {
return contained;
}
private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @NotNull String qualifiedName) {
private static boolean isAnnotationRepeatedTwice(@NotNull PsiAnnotationOwner owner, @Nullable String qualifiedName) {
int count = 0;
for (PsiAnnotation annotation : owner.getAnnotations()) {
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
if (nameRef == null) continue;
PsiElement resolved = nameRef.resolve();
if (!(resolved instanceof PsiClass) || !qualifiedName.equals(((PsiClass)resolved).getQualifiedName())) continue;
count++;
if (count == 2) return true;
if (!(resolved instanceof PsiClass) || !Comparing.equal(qualifiedName, ((PsiClass)resolved).getQualifiedName())) continue;
if (++count == 2) return true;
}
return false;
}
@@ -721,7 +711,7 @@ public class AnnotationsHighlightUtil {
if (field instanceof PsiEnumConstant) {
String name = ((PsiEnumConstant)field).getName();
try {
return RetentionPolicy.valueOf(RetentionPolicy.class, name);
return Enum.valueOf(RetentionPolicy.class, name);
}
catch (Exception e) {
LOG.warn("Unknown policy: " + name);
@@ -1336,14 +1336,23 @@ public class GenericsHighlightUtil {
}
public static HighlightInfo areSupersAccessible(@NotNull PsiClass aClass) {
return areSupersAccessible(aClass, aClass.getResolveScope(), HighlightNamesUtil.getClassDeclarationTextRange(aClass));
}
public static HighlightInfo areSupersAccessible(@NotNull PsiClass aClass, PsiElement ref) {
return areSupersAccessible(aClass, ref.getResolveScope(), ref.getTextRange());
}
private static HighlightInfo areSupersAccessible(@NotNull PsiClass aClass,
GlobalSearchScope resolveScope,
TextRange range) {
final JavaPsiFacade factory = JavaPsiFacade.getInstance(aClass.getProject());
final GlobalSearchScope resolveScope = aClass.getResolveScope();
for (PsiClassType superType : aClass.getSuperTypes()) {
final String notAccessibleErrorMessage = isSuperTypeAccessible(superType, new HashSet<PsiClass>(), resolveScope, factory);
if (notAccessibleErrorMessage != null) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.descriptionAndTooltip(notAccessibleErrorMessage)
.range(HighlightNamesUtil.getClassDeclarationTextRange(aClass))
.range(range)
.create();
}
}
@@ -1221,7 +1221,14 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
}
if (!myHolder.hasErrorResults()) myHolder.add(GenericsHighlightUtil.checkAccessStaticFieldFromEnumConstructor(expression, result));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkClassReferenceAfterQualifier(expression, resolved));
myHolder.add(HighlightUtil.checkUnqualifiedSuperInDefaultMethod(myLanguageLevel, expression, expression.getQualifierExpression()));
final PsiExpression qualifierExpression = expression.getQualifierExpression();
myHolder.add(HighlightUtil.checkUnqualifiedSuperInDefaultMethod(myLanguageLevel, expression, qualifierExpression));
if (!myHolder.hasErrorResults() && qualifierExpression != null) {
final PsiClass psiClass = PsiUtil.resolveClassInType(qualifierExpression.getType());
if (psiClass != null) {
myHolder.add(GenericsHighlightUtil.areSupersAccessible(psiClass, qualifierExpression));
}
}
}
@Override
@@ -25,6 +25,7 @@ import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.JavaVisibilityPanel;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.ToolbarDecorator;
@@ -67,7 +68,7 @@ public class CodeStyleGenerationConfigurable implements Configurable {
mySettings = settings;
myMembersOrderList = new MembersOrderList();
myPanel.setBorder(IdeBorderFactory.createEmptyBorder(2, 2, 2, 2));
myJavaVisibilityPanel = new JavaVisibilityPanel(false, true);
myJavaVisibilityPanel = new JavaVisibilityPanel(false, true, RefactoringBundle.message("default.visibility.border.title"));
}
public JComponent createComponent() {
@@ -105,7 +105,7 @@ abstract class SafeDeleteJavaCallerChooser extends JavaCallerChooser {
/**
* @return parameter if it is used inside method only as argument in nodeMethod call at parameterIndex
*/
static PsiParameter isTheOnlyOneParameterUsage(PsiElement call, int parameterIndex, final PsiMethod nodeMethod) {
static PsiParameter isTheOnlyOneParameterUsage(PsiElement call, final int parameterIndex, final PsiMethod nodeMethod) {
if (call instanceof PsiCallExpression) {
final PsiExpressionList argumentList = ((PsiCallExpression)call).getArgumentList();
if (argumentList != null) {
@@ -129,6 +129,7 @@ abstract class SafeDeleteJavaCallerChooser extends JavaCallerChooser {
if (parameter != null && !parameter.isVarArgs()) {
final PsiElement scope = parameter.getDeclarationScope();
if (scope instanceof PsiMethod && ((PsiMethod)scope).findDeepestSuperMethods().length == 0) {
final int scopeParamIdx = ((PsiMethod)scope).getParameterList().getParameterIndex(parameter);
final Ref<Boolean> ref = new Ref<Boolean>(false);
if (ReferencesSearch.search(parameter, new LocalSearchScope(scope)).forEach(new Processor<PsiReference>() {
@Override
@@ -139,11 +140,11 @@ abstract class SafeDeleteJavaCallerChooser extends JavaCallerChooser {
while (parent != null) {
final PsiMethod resolved = parent.resolveMethod();
if (scope.equals(resolved)) {
if (usedInQualifier(element, parent)) return false;
if (usedInQualifier(element, parent, scopeParamIdx)) return false;
return true;
}
if (nodeMethod.equals(resolved)) {
if (usedInQualifier(element, parent)) return false;
if (usedInQualifier(element, parent, parameterIndex)) return false;
ref.set(true);
return true;
}
@@ -154,7 +155,7 @@ abstract class SafeDeleteJavaCallerChooser extends JavaCallerChooser {
return true;
}
private boolean usedInQualifier(PsiElement element, PsiCallExpression parent) {
private boolean usedInQualifier(PsiElement element, PsiCallExpression parent, int parameterIndex) {
PsiExpression qualifier = null;
if (parent instanceof PsiMethodCallExpression) {
qualifier = ((PsiMethodCallExpression)parent).getMethodExpression();
@@ -162,7 +163,13 @@ abstract class SafeDeleteJavaCallerChooser extends JavaCallerChooser {
else if (parent instanceof PsiNewExpression) {
qualifier = ((PsiNewExpression)parent).getQualifier();
}
return PsiTreeUtil.isAncestor(qualifier, element, true);
if (PsiTreeUtil.isAncestor(qualifier, element, true)) {
return true;
}
final PsiExpressionList list = parent.getArgumentList();
return list != null && !PsiTreeUtil.isAncestor(list.getExpressions()[parameterIndex], element, false);
}
}) && ref.get()) {
return parameter;
@@ -46,7 +46,13 @@ public class JavaVisibilityPanel extends VisibilityPanelBase<String> {
private final JRadioButton myRbPublic;
public JavaVisibilityPanel(boolean hasAsIs, final boolean hasEscalate) {
setBorder(IdeBorderFactory.createTitledBorder(RefactoringBundle.message("visibility.border.title"), true,
this(hasAsIs, hasEscalate, RefactoringBundle.message("visibility.border.title"));
}
public JavaVisibilityPanel(boolean hasAsIs,
final boolean hasEscalate,
String visibilityTitle) {
setBorder(IdeBorderFactory.createTitledBorder(visibilityTitle, true,
new Insets(IdeBorderFactory.TITLED_BORDER_TOP_INSET,
UIUtil.DEFAULT_HGAP,
IdeBorderFactory.TITLED_BORDER_BOTTOM_INSET,
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -132,7 +132,10 @@ public class PsiAnnotationImpl extends JavaStubPsiElement<PsiAnnotationStub> imp
}
else if (parent instanceof PsiJavaCodeReferenceElement) {
PsiElement ctx = PsiTreeUtil.skipParentsOfType(parent, PsiJavaCodeReferenceElement.class);
if (ctx instanceof PsiReferenceList || ctx instanceof PsiNewExpression || ctx instanceof PsiTypeElement) {
if (ctx instanceof PsiReferenceList ||
ctx instanceof PsiNewExpression ||
ctx instanceof PsiTypeElement ||
ctx instanceof PsiAnonymousClass) {
return new PsiClassReferenceType((PsiJavaCodeReferenceElement)parent, null);
}
}
@@ -59,6 +59,7 @@ class Outer {
{
new @TA Object();
new @TA ArrayList<String>();
new @TA Runnable() { public void run() { } }.run();
ArrayList<String> var = new <String> @TA ArrayList();
new @TA Outer().new @TA Middle();
@@ -0,0 +1,6 @@
package p;
abstract class B {
void f(A a) {
<error descr="Cannot access java.util.stream.Stream">a</error>.foo();
}
}
@@ -0,0 +1,14 @@
package p;
abstract class B extends A {
public static String getOrDefault(Object key, String defaultValue) {
return null;
}
}
abstract class C extends A {
<error descr="Method does not override method from its superclass">@Override</error>
public String getOrDefault(Object key, String defaultValue) {
return null;
}
}
@@ -0,0 +1,7 @@
package p;
import java.util.stream.Stream;
import java.util.List;
public abstract class A implements List<Stream<String>> {
public void foo() {}
}
@@ -0,0 +1,5 @@
package p;
import java.util.Map;
public abstract class A implements Map<String, String> {}
@@ -0,0 +1,7 @@
class Test {
void foo(String <caret>s, String p) {}
void bar(String s, String p) {
foo(s, s.substring(0));
}
}
@@ -0,0 +1,7 @@
class Test {
void foo(String p) {}
void bar(String s, String p) {
foo(s.substring(0));
}
}
@@ -117,6 +117,10 @@ public class MultipleJdksHighlightingTest extends UsefulTestCase {
public void testSuperclassImplementsUnknownType() throws Exception {
doTestWithoutLibrary();
}
public void testDeclaredTypeOfVariableImplementsUnknownType() throws Exception {
doTestWithoutLibrary();
}
public void testSuperclassImplementsGenericsOfUnknownType() throws Exception {
doTestWithoutLibrary();
@@ -126,6 +130,11 @@ public class MultipleJdksHighlightingTest extends UsefulTestCase {
doTestWithoutLibrary();
}
@Bombed(day = 20, month = Calendar.FEBRUARY)
public void testNoOverriding() throws Exception {
doTestWithoutLibrary();
}
private void doTestWithoutLibrary() {
final String name = getTestName(false);
myFixture.configureByFiles("java7/p/" + name + ".java", "java8/p/" + name + ".java");
@@ -92,6 +92,10 @@ public class SafeDeleteTest extends MultiFileTestCase {
doSingleFileTest();
}
public void testNoDeepDeleteParameterUsedInNextArgumentExpression() throws Exception {
doSingleFileTest();
}
public void testToDeepDeleteParameterOverriders() throws Exception {
doSingleFileTest();
}
@@ -740,18 +740,19 @@ public class JavaMatchingVisitor extends JavaElementVisitor {
PsiElement[] typeparams = null;
if (el2 instanceof PsiJavaCodeReferenceElement) {
typeparams = ((PsiJavaCodeReferenceElement)el2).getParameterList().getTypeParameterElements();
final PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement)el2).getParameterList();
if (parameterList != null) {
typeparams = parameterList.getTypeParameterElements();
}
}
else if (el2 instanceof PsiTypeParameter) {
el2 = ((PsiTypeParameter)el2).getNameIdentifier();
}
else if (el2 instanceof PsiClass && ((PsiClass)el2).hasTypeParameters()
) {
else if (el2 instanceof PsiClass && ((PsiClass)el2).hasTypeParameters()) {
typeparams = ((PsiClass)el2).getTypeParameters();
el2 = ((PsiClass)el2).getNameIdentifier();
}
else if (el2 instanceof PsiMethod && ((PsiMethod)el2).hasTypeParameters()
) {
else if (el2 instanceof PsiMethod && ((PsiMethod)el2).hasTypeParameters()) {
typeparams = ((PsiMethod)_type2).getTypeParameters();
el2 = ((PsiMethod)_type2).getNameIdentifier();
}
@@ -1,4 +1,4 @@
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.StringUtilRt;
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.io.Responses;
import org.jetbrains.io.SimpleChannelInboundHandlerAdapter;
import static org.jetbrains.fastCgi.FastCgiService.LOG;
import static org.jetbrains.io.fastCgi.FastCgiService.LOG;
@ChannelHandler.Sharable
public class FastCgiChannelHandler extends SimpleChannelInboundHandlerAdapter<FastCgiResponse> {
@@ -1,4 +1,4 @@
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
public final class FastCgiConstants {
public static final int HEADER_LENGTH = 8;
@@ -1,4 +1,4 @@
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
import com.intellij.util.Consumer;
import gnu.trove.TIntObjectHashMap;
@@ -10,7 +10,7 @@ import io.netty.util.CharsetUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.io.Decoder;
import static org.jetbrains.fastCgi.FastCgiService.LOG;
import static org.jetbrains.io.fastCgi.FastCgiService.LOG;
public class FastCgiDecoder extends Decoder {
private enum State {
@@ -1,4 +1,4 @@
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
@@ -1,4 +1,4 @@
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
import io.netty.buffer.ByteBuf;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.fastCgi;
package org.jetbrains.io.fastCgi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
@@ -32,20 +32,26 @@ import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.tree.MarkersHolderFileViewProvider;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.UnsafeWeakList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class SmartPointerManagerImpl extends SmartPointerManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl");
private final Project myProject;
private final Object lock = new Object();
private final Key<List<SmartPointerEx>> POINTERS_KEY;
private static final Object lock = new Object();
private static final ReferenceQueue<SmartPointerEx> ourQueue = new ReferenceQueue<SmartPointerEx>();
private final Key<Set<PointerReference>> POINTERS_KEY;
private final Key<Boolean> POINTERS_ARE_FASTENED_KEY;
public SmartPointerManagerImpl(Project project) {
@@ -54,23 +60,33 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
POINTERS_ARE_FASTENED_KEY = Key.create("SMART_POINTERS_ARE_FASTENED for "+project);
}
private static void processQueue() {
while (true) {
PointerReference reference = (PointerReference)ourQueue.poll();
if (reference == null) break;
synchronized (lock) {
Set<PointerReference> pointers = reference.file.getUserData(reference.key);
if (pointers != null) {
pointers.remove(reference);
if (pointers.isEmpty()) {
reference.file.putUserData(reference.key, null);
}
}
}
}
}
public void fastenBelts(@NotNull VirtualFile file, int offset, @Nullable RangeMarker[] cachedRangeMarkers) {
ApplicationManager.getApplication().assertIsDispatchThread();
processQueue();
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(file);
if (pointers == null) return;
List<SmartPointerEx> pointers = getStrongPointers(file);
if (pointers.isEmpty()) return;
if (getAndFasten(file)) return;
if (pointers.isEmpty()) {
file.putUserData(POINTERS_KEY, null);
}
else {
// pointers might change in fastenBelt()
List<SmartPointerEx> strongPointers = ((UnsafeWeakList<SmartPointerEx>)pointers).toStrongList();
for (SmartPointerEx pointer : strongPointers) {
pointer.fastenBelt(offset, cachedRangeMarkers);
}
for (SmartPointerEx pointer : pointers) {
pointer.fastenBelt(offset, cachedRangeMarkers);
}
PsiFile psiFile = ((PsiManagerEx)PsiManager.getInstance(myProject)).getFileManager().getCachedPsiFile(file);
@@ -103,21 +119,15 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
public void unfastenBelts(@NotNull VirtualFile file, int offset) {
ApplicationManager.getApplication().assertIsDispatchThread();
processQueue();
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(file);
if (pointers == null) return;
List<SmartPointerEx> pointers = getStrongPointers(file);
if (pointers.isEmpty()) return;
if (!getAndUnfasten(file)) return;
if (pointers.isEmpty()) {
file.putUserData(POINTERS_KEY, null);
}
else {
// pointers might change in unfastenBelt()
List<SmartPointerEx> strongPointers = ((UnsafeWeakList<SmartPointerEx>)pointers).toStrongList();
for (SmartPointerEx pointer : strongPointers) {
pointer.unfastenBelt(offset);
}
for (SmartPointerEx pointer : pointers) {
pointer.unfastenBelt(offset);
}
PsiFile psiFile = ((PsiManagerEx)PsiManager.getInstance(myProject)).getFileManager().getCachedPsiFile(file);
@@ -147,6 +157,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
PsiUtilCore.ensureValid(element);
LOG.error("Invalid element:" + element);
}
processQueue();
SmartPointerEx<E> pointer = getCachedPointer(element);
if (pointer != null) {
containingFile = containingFile == null ? element.getContainingFile() : containingFile;
@@ -197,12 +208,12 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
private <E extends PsiElement> void initPointer(@NotNull SmartPointerEx<E> pointer, @NotNull VirtualFile containingFile) {
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(containingFile);
Set<PointerReference> pointers = getPointers(containingFile);
if (pointers == null) {
pointers = new UnsafeWeakList<SmartPointerEx>(); // we synchronise access anyway
pointers = ContainerUtil.newTroveSet(); // we synchronise access anyway
containingFile.putUserData(POINTERS_KEY, pointers);
}
pointers.add(pointer);
pointers.add(new PointerReference(pointer, containingFile, ourQueue, POINTERS_KEY));
if (areBeltsFastened(containingFile)) {
pointer.fastenBelt(0, null);
@@ -222,26 +233,45 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
}
PsiFile containingFile = pointer.getContainingFile();
if (containingFile == null) return false;
List<SmartPointerEx> pointers = getPointers(containingFile.getViewProvider().getVirtualFile());
Set<PointerReference> pointers = getPointers(containingFile.getViewProvider().getVirtualFile());
if (pointers == null) return false;
SmartPointerElementInfo info = ((SmartPsiElementPointerImpl)pointer).getElementInfo();
info.cleanup();
return pointers.remove(pointer);
for (Iterator<PointerReference> iterator = pointers.iterator(); iterator.hasNext(); ) {
if (pointer.equals(iterator.next().get())) {
iterator.remove();
return true;
}
}
return false;
}
}
}
return false;
}
private List<SmartPointerEx> getPointers(@NotNull VirtualFile containingFile) {
@Nullable
private Set<PointerReference> getPointers(@NotNull VirtualFile containingFile) {
return containingFile.getUserData(POINTERS_KEY);
}
@NotNull
private List<SmartPointerEx> getStrongPointers(@NotNull VirtualFile containingFile) {
Set<PointerReference> refs = getPointers(containingFile);
if (refs == null) return Collections.emptyList();
List<SmartPointerEx> result = ContainerUtil.newArrayList();
for (PointerReference reference : refs) {
ContainerUtil.addIfNotNull(result, reference.get());
}
return result;
}
@TestOnly
public int getPointersNumber(@NotNull PsiFile containingFile) {
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(containingFile.getViewProvider().getVirtualFile());
return pointers == null ? 0 : ((UnsafeWeakList)pointers).toStrongList().size();
return getStrongPointers(containingFile.getViewProvider().getVirtualFile()).size();
}
}
@@ -263,4 +293,19 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
public boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
return SmartPsiElementPointerImpl.pointsToTheSameElementAs(pointer1, pointer2);
}
private static class PointerReference extends WeakReference<SmartPointerEx> {
private final VirtualFile file;
private final Key<Set<PointerReference>> key;
public PointerReference(SmartPointerEx<?> pointer,
VirtualFile containingFile,
ReferenceQueue<SmartPointerEx> queue,
Key<Set<PointerReference>> key) {
super(pointer, queue);
file = containingFile;
this.key = key;
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 169 B

@@ -75,7 +75,8 @@ public class WrappingAndBracesPanel extends OptionTableWithPreviewPanel {
addOption("METHOD_CALL_CHAIN_WRAP", WRAPPING_CALL_CHAIN, WRAP_OPTIONS, WRAP_VALUES);
addOption("ALIGN_MULTILINE_CHAINED_METHODS", ApplicationBundle.message("wrapping.align.when.multiline"), WRAPPING_CALL_CHAIN);
addOption("ALIGN_GROUP_FIELD_DECLARATIONS", ApplicationBundle.message("wrapping.align.in.columns"), WRAPPING_FIELDS_VARIABLES_GROUPS);
addOption("ALIGN_GROUP_FIELD_DECLARATIONS", ApplicationBundle.message("wrapping.align.fields.in.columns"), WRAPPING_FIELDS_VARIABLES_GROUPS);
addOption("ALIGN_CONSECUTIVE_VARIABLE_DECLARATIONS", ApplicationBundle.message("wrapping.align.variables.in.columns"), WRAPPING_FIELDS_VARIABLES_GROUPS);
addOption("IF_BRACE_FORCE", ApplicationBundle.message("wrapping.force.braces"), WRAPPING_IF_STATEMENT, BRACE_OPTIONS, BRACE_VALUES);
addOption("ELSE_ON_NEW_LINE", ApplicationBundle.message("wrapping.else.on.new.line"), WRAPPING_IF_STATEMENT);
@@ -185,12 +185,21 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
}
MouseEvent mouseEvent = e.getMouseEvent();
Point prevLocation = myPrevMouseLocation;
myPrevMouseLocation = mouseEvent.getLocationOnScreen();
if (isMouseOverTooltip(mouseEvent.getLocationOnScreen())
|| ScreenUtil.isMovementTowards(myPrevMouseLocation, mouseEvent.getLocationOnScreen(), getHintBounds())) {
myPrevMouseLocation = mouseEvent.getLocationOnScreen();
|| ScreenUtil.isMovementTowards(prevLocation, mouseEvent.getLocationOnScreen(), getHintBounds())) {
return;
}
cancelPreviousTooltip();
myStoredModifiers = mouseEvent.getModifiers();
BrowseMode browseMode = getBrowseMode(myStoredModifiers);
if (browseMode == BrowseMode.None) {
disposeHighlighter();
return;
}
myPrevMouseLocation = mouseEvent.getLocationOnScreen();
Editor editor = e.getEditor();
if (editor.getProject() != null && editor.getProject() != myProject) return;
@@ -208,12 +217,7 @@ public class CtrlMouseHandler extends AbstractProjectComponent {
int selStart = editor.getSelectionModel().getSelectionStart();
int selEnd = editor.getSelectionModel().getSelectionEnd();
myStoredModifiers = mouseEvent.getModifiers();
BrowseMode browseMode = getBrowseMode(myStoredModifiers);
cancelPreviousTooltip();
if (browseMode == BrowseMode.None || offset >= selStart && offset < selEnd) {
if (offset >= selStart && offset < selEnd) {
disposeHighlighter();
return;
}
@@ -28,6 +28,7 @@ import com.intellij.ide.scratch.ScratchFileService;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
@@ -50,6 +51,7 @@ import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.PooledThreadExecutor;
import javax.script.*;
import javax.swing.*;
@@ -58,6 +60,7 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author gregsh
@@ -68,12 +71,22 @@ public class RunIdeConsoleAction extends DumbAwareAction {
private static final Key<WeakReference<RunContentDescriptor>> DESCRIPTOR_KEY = Key.create("DESCRIPTOR_KEY");
private static final Key<ConsoleHistoryController> HISTORY_CONTROLLER_KEY = Key.create("HISTORY_CONTROLLER_KEY");
private static final Logger LOG = Logger.getInstance(RunIdeConsoleAction.class);
static class Engines {
static final Map<String, ScriptEngineFactory> ourEngines = ContainerUtil.newLinkedHashMap();
static {
for (ScriptEngineFactory factory : new ScriptEngineManager().getEngineFactories()) {
ourEngines.put(factory.getLanguageName(), factory);
}
static final List<ScriptEngineFactory> ourEngines = new CopyOnWriteArrayList<ScriptEngineFactory>();
static {
PooledThreadExecutor.INSTANCE.submit(
new Runnable() {
@Override
public void run() {
try {
ourEngines.addAll(new ScriptEngineManager().getEngineFactories());
}
catch (Throwable e) {
LOG.error(e);
}
}
});
}
}
@@ -86,11 +99,11 @@ public class RunIdeConsoleAction extends DumbAwareAction {
@Override
public void actionPerformed(AnActionEvent e) {
if (Engines.ourEngines.size() == 1) {
runConsole(e, Engines.ourEngines.values().iterator().next());
runConsole(e, Engines.ourEngines.get(0));
}
else {
DefaultActionGroup actions = new DefaultActionGroup(
ContainerUtil.map(Engines.ourEngines.values(), new NotNullFunction<ScriptEngineFactory, AnAction>() {
ContainerUtil.map(Engines.ourEngines, new NotNullFunction<ScriptEngineFactory, AnAction>() {
@NotNull
@Override
public AnAction fun(final ScriptEngineFactory engine) {
@@ -119,7 +132,8 @@ public class RunIdeConsoleAction extends DumbAwareAction {
FileEditorManager.getInstance(project).openFile(virtualFile, true);
}
}
catch (IOException ignored) {
catch (IOException ex) {
LOG.error(ex);
}
}
@@ -137,7 +151,7 @@ public class RunIdeConsoleAction extends DumbAwareAction {
@Nullable
private static ScriptEngine findScriptEngine(@NotNull VirtualFile file) {
for (ScriptEngineFactory factory : Engines.ourEngines.values()) {
for (ScriptEngineFactory factory : Engines.ourEngines) {
if (factory.getExtensions().contains(file.getExtension())) {
return factory.getScriptEngine();
}
@@ -149,7 +163,6 @@ public class RunIdeConsoleAction extends DumbAwareAction {
@NotNull VirtualFile file,
@NotNull Editor editor,
@NotNull ScriptEngine engine) {
TextRange selectedRange = EditorUtil.getSelectionInAnyMode(editor);
Document document = editor.getDocument();
if (selectedRange.getLength() == 0) {
@@ -66,10 +66,6 @@ import java.util.concurrent.TimeUnit;
public class NewScratchFileAction extends DumbAwareAction {
public static final int MAX_VISIBLE_SIZE = 20;
public NewScratchFileAction() {
super("New Scratch File...", null, null);
}
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabledAndVisible(isEnabled(e));
@@ -16,6 +16,7 @@
package com.intellij.openapi.editor.actions;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.VisualPosition;
@@ -25,8 +26,12 @@ import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNameIdentifierOwner;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -40,6 +45,11 @@ public class NamedElementDuplicateHandler extends EditorWriteActionHandler {
myOriginal = original;
}
@Override
protected boolean isEnabledForCaret(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) {
return myOriginal.isEnabled(editor, caret, dataContext);
}
@Override
public void executeWriteAction(Editor editor, DataContext dataContext) {
Project project = editor.getProject();
@@ -24,6 +24,7 @@ import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.options.BaseSchemeProcessor;
import com.intellij.openapi.options.SchemesManager;
import com.intellij.openapi.options.SchemesManagerFactory;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
@@ -154,7 +155,13 @@ public class KeymapManagerImpl extends KeymapManagerEx implements PersistentStat
@Override
public String getActionBinding(String actionId) {
return myBoundShortcuts.get(actionId);
Set<String> visited = null;
String id = actionId, next;
while ((next = myBoundShortcuts.get(id)) != null) {
if (visited == null) visited = ContainerUtil.newHashSet();
if (!visited.add(id = next)) break;
}
return Comparing.equal(id, actionId) ? null : id;
}
@Override
@@ -1349,9 +1349,10 @@ public class AbstractPopup implements JBPopup {
Runnable runFinal = new Runnable() {
@Override
public void run() {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(myFinalRunnable);
//noinspection SSBasedInspection
SwingUtilities.invokeLater(typeAheadDone.createSetDoneRunnable());
myFinalRunnable.run();
myFinalRunnable = null;
}
};
@@ -575,6 +575,10 @@ action.ShowModulesDependencies.text=Analyze _Module Dependencies...
action.ShowModulesDependencies.description=Show dependencies between modules in project
action.Unscramble.text=Analyze _Stacktrace...
action.Unscramble.description=Open console with the navigatable stacktrace
action.IdeScriptingConsole.text=IDE Scripting Console
action.IdeScriptingConsole.description=Open IDE scripting console
action.NewScratchFile.text=New Scratch File...
action.NewScratchFile.description=Creates new scratch file with chosen language
group.RefactoringMenu.text=_Refactor
action.RenameElement.text=_Rename...
action.RenameElement.description=Rename the selected symbol and correct all references
@@ -165,7 +165,8 @@ wrapping.brace.placement.next.line.each.shifted=Next line, each shifted
wrapping.align.when.multiline=Align when multiline
wrapping.align.parenthesised.when.multiline=Align parenthesised when multiline
wrapping.align.in.columns=Align in columns
wrapping.align.fields.in.columns=Align fields in columns
wrapping.align.variables.in.columns=Align variables in columns
wrapping.else.on.new.line='else' on new line
wrapping.while.on.new.line='while' on new line
@@ -226,7 +227,7 @@ checkbox.align.multiline.method.parameters=Method parameters
checkbox.align.multiline.call.arguments=Call arguments
checkbox.align.multiline.extends.list=Extends list
checkbox.align.multiline.throws.list=Throws list
checkbox.align.multiline.fields.groups=Field groups
checkbox.align.multiline.fields.groups=Group declarations
checkbox.align.multiline.parenthesized.expression=Parenthesized expression
checkbox.align.multiline.binary.operation=Binary operation
checkbox.align.multiline.ternary.operation=Ternary operation
@@ -71,6 +71,7 @@ visibility.protected=Pr&otected
visibility.private=Pri&vate
visibility.package.local=Pac&kage local
visibility.border.title=Visibility
default.visibility.border.title=Default Visibility
visibility.combo.title=&Visibility:
moveInstanceMethod.select.an.instance.parameter=Select an &instance parameter:
destination.package=Destination &package:
@@ -342,7 +342,7 @@
<add-to-group group-id="ToolsMenu" anchor="first"/>
<action id="NewScratchFile" class="com.intellij.ide.scratch.NewScratchFileAction" />
<action id="IdeScriptingConsole" class="com.intellij.execution.console.RunIdeConsoleAction" text="Open IDE Scripting Console"/>
<action id="IdeScriptingConsole" class="com.intellij.execution.console.RunIdeConsoleAction"/>
</group>
<group id="ExternalToolsGroup" class="com.intellij.tools.ExternalToolsGroup">
@@ -119,7 +119,7 @@ public class GrListOrMapInfo implements ConstructorCallInfo<GrListOrMap> {
if (type == null) return GroovyResolveResult.EMPTY_ARRAY;
final GroovyResolveResult result = GroovyResolveResultImpl.from(type.resolveGenerics());
if (result == null) return GroovyResolveResult.EMPTY_ARRAY;
if (result == GroovyResolveResult.EMPTY_RESULT) return GroovyResolveResult.EMPTY_ARRAY;
return new GroovyResolveResult[]{result};
}
@@ -208,7 +208,7 @@ public class LiteralConstructorReference extends PsiReferenceBase.Poly<GrListOrM
if (constructorCandidates.length == 0) {
final GroovyResolveResult result = GroovyResolveResultImpl.from(classResolveResult);
if (result != null) return new GroovyResolveResult[]{result};
if (result != GroovyResolveResult.EMPTY_RESULT) return new GroovyResolveResult[]{result};
}
return constructorCandidates;
}
@@ -148,9 +148,9 @@ public class GroovyResolveResultImpl implements GroovyResolveResult {
'}';
}
@Nullable
@NotNull
public static GroovyResolveResult from(@NotNull PsiClassType.ClassResolveResult classResolveResult) {
if (classResolveResult.getElement() == null) return null;
if (classResolveResult.getElement() == null) return GroovyResolveResult.EMPTY_RESULT;
return new GroovyResolveResultImpl(
classResolveResult.getElement(),
null,
@@ -167,6 +167,7 @@ public class RedmineRepository extends NewBaseRepositoryImpl {
URIBuilder builder = new URIBuilder(getRestApiUrl("issues.json"))
.addParameter("offset", String.valueOf(offset))
.addParameter("limit", String.valueOf(limit))
.addParameter("sort", "updated_on:desc")
.addParameter("status_id", withClosed ? "*" : "open")
.addParameter("assigned_to_id", "me");
// If project was not chosen, all available issues still fetched. Such behavior may seems strange to user.
@@ -852,7 +852,14 @@ remote.interpreter.configure.temp.files.path.label=PyCharm helpers path:
# Message we display for inspection if user uses custom class type members that do not exist
custom.type.mimic.name=Dynamic class based on {0}
# Values for command argument value validation
commandsWithArgs.validation.badValue=Argument can't have this value
commandsWithArgs.validation.excess=Excess argument
commandsWithArgs.enterCommand.label=Enter command here
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}"
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.optParse;
package com.jetbrains.python;
import com.intellij.util.Range;
import org.jetbrains.annotations.NotNull;
@@ -33,6 +33,7 @@ public final class WordWithPosition extends Range<Integer> {
/**
* Creates word with beam (it has start, but it is infinite)
*
* @param word word
* @param from start
*/
@@ -50,6 +51,15 @@ public final class WordWithPosition extends Range<Integer> {
myWord = word;
}
/**
* Creates instance with certain text and range (start/end)
* @param word text
* @param range range
*/
public WordWithPosition(@NotNull final String word, @NotNull final Range<Integer> range) {
this(word, range.getFrom(), range.getTo());
}
@NotNull
public String getText() {
return myWord;
@@ -121,7 +131,7 @@ public final class WordWithPosition extends Range<Integer> {
* @return parse result
*/
@NotNull
static List<WordWithPosition> splitText(@NotNull final String text) {
public static List<WordWithPosition> splitText(@NotNull final String text) {
// TODO: Rewrite using regex or scanner?
int position = 0;
int wordStart = -1;
@@ -27,9 +27,8 @@ public interface CommandInterfacePresenter extends Presenter {
/**
* Called by view when user types new text or text changed by some other reason
*
* @param inForcedTextMode text changed not by user but by calling {@link com.jetbrains.python.commandInterface.CommandInterfaceView#forceText(String)}
*/
void textChanged(boolean inForcedTextMode);
void textChanged();
/**
* Called by view when user requests for completion (like tab)
@@ -39,14 +38,13 @@ public interface CommandInterfacePresenter extends Presenter {
void completionRequested(@Nullable String valueFromSuggestionList);
/**
* Called by view when user asks for suggestions (CTRL+Space)
* Called by view when user asks for suggestions (like CTRL+Space)
*/
void suggestionRequested();
/**
* Called by view when user wants to execute command (Enter is presed)
* Called by view when user wants to execute command (like enter)
*
* @param valueFromSuggestionList value selected from suggestion list (if any selected)
*/
void executionRequested(@Nullable String valueFromSuggestionList);
void executionRequested();
}
@@ -15,7 +15,8 @@
*/
package com.jetbrains.python.commandInterface;
import com.jetbrains.python.optParse.WordWithPosition;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,6 +31,13 @@ import java.util.List;
*/
public interface CommandInterfaceView {
/**
* Special place in command line that represents "after the last character" place.
* To be used in methods like {@link #setInfoAndErrors(java.util.Collection, java.util.Collection)} to mark it.
*/
@NotNull
Range<Integer> AFTER_LAST_CHARACTER_RANGE = new Range<Integer>(Integer.MAX_VALUE, Integer.MAX_VALUE);
/**
* Launches view
*/
@@ -39,43 +47,34 @@ public interface CommandInterfaceView {
* Suggests user some elements (for completion reason)
*
* @param suggestions what to suggest (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
* @param absolute display list in its main position, or directly near the text
* @param toSelect word to select if list (if any)
* @param absolute display list in its main position, or directly near the caret
* @param toSelect word to select in list (if any)
*/
void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
/**
* Emphasize errors (like red line and special message).
*
* @param errors list of errors (coordinates and error message. Message may be empty not to display any text)
* @param specialErrorPlace if you want to underline special place, you may provide it here
*/
void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable SpecialErrorPlace specialErrorPlace);
/**
* Change text to the one provided
* 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 newText text to display
* @param defaultSubText default text
* @param suggestionAvailablePlaces list of places where suggestions are available in format [from, to].
*/
void forceText(@NotNull String newText);
/**
* Display text in sub part (like hint)
*
* @param subText text to display
*/
void setSubText(@NotNull String subText);
void configureSubTexts(@Nullable String defaultSubText,
@NotNull List<Range<Integer>> suggestionAvailablePlaces);
/**
* Hide suggestion list
*/
void removeSuggestions();
/**
* Displays baloon with message right under the last letter.
*
* @param message text to display
*/
/**
* @return text, entered by user
@@ -83,41 +82,40 @@ public interface CommandInterfaceView {
@NotNull
String getText();
/**
* Enlarges view to make it as big as required to display appropriate number of chars
* When caret meets certain place, view may display some info and some errors.
* Errors, how ever, may always be emphasized (with something like red line).
* This function configures view with pack of ranges and texts to display.
* Special place {@link #AFTER_LAST_CHARACTER_RANGE} may also be used.
* Each place is described as start-end position (in chars) where it should be enabled.
* Each call removes previously enabled information.
*
* @param widthInChars number of chars
* @param errors places to be marked as errors with error text.
* @param infoBalloons places to display info balloon
* @see #AFTER_LAST_CHARACTER_RANGE
*/
void setPreferredWidthInChars(int widthInChars);
void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons, @NotNull final Collection<WordWithPosition> errors);
/**
* Displays help balloon when cursor meets certain place.
* Each balloon is described as start-end position (in chars) where it should be enabled
* and test to display.
* <strong>Caution: Each call removes previuos balloons!</strong>
* Inserts text after caret moving next chars to the right
*
* @param balloons list of balloons to display (i.e. you want to text 'foo' be displayed when user sets cursor on position
* from 1 to 3, so you add 'foo',1,4 here)
* @param text text to insert
*/
void setBalloons(@NotNull final Collection<WordWithPosition> balloons);
void insertTextAfterCaret(@NotNull String text);
/**
* @return true if current caret position is on the word (no on whitespace)
* Replaces current text with another one.
*
* @param from from
* @param to to
* @param newText text to replace
*/
boolean isCaretOnWord();
void replaceText(final int from, final int to, @NotNull String newText);
/**
* Special place that may be underlined
* @return position of caret (in chars)
*/
enum SpecialErrorPlace {
/**
* Whole text (from start to end)
*/
WHOLE_TEXT,
/**
* Only after last character
*/
AFTER_LAST_CHAR
}
int getCaretPosition();
}
@@ -0,0 +1,83 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Chunk and its info pair. Chunk may be null, while chunk info always present.
* @author Ilya.Kazakevich
*/
final class ChunkAndInfo implements Comparable<ChunkAndInfo> {
@Nullable
private final WordWithPosition myChunk;
@NotNull
private final ChunkInfo myChunkInfo;
ChunkAndInfo(@Nullable final WordWithPosition chunk, @NotNull final ChunkInfo chunkInfo) {
myChunk = chunk;
myChunkInfo = chunkInfo;
}
/**
* @return chunk (word). may be null
*/
@Nullable
WordWithPosition getChunk() {
return myChunk;
}
/**
* @return chunk info.
*/
@NotNull
ChunkInfo getChunkInfo() {
return myChunkInfo;
}
@Override
public int compareTo(@NotNull final ChunkAndInfo o) {
if (myChunk == null && o.myChunk == null) {
return 0;
}
if (myChunk == null) {
return 1;
}
if (o.myChunk == null) {
return -1;
}
return myChunk.getFrom().compareTo(o.myChunk.getFrom());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChunkAndInfo info = (ChunkAndInfo)o;
if (myChunk != null ? !myChunk.equals(info.myChunk) : info.myChunk != null) return false;
return true;
}
@Override
public int hashCode() {
return myChunk != null ? myChunk.hashCode() : 0;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Driver that knows how to parse pack of chunks into chunk info.
*
* @author Ilya.Kazakevich
*/
public interface ChunkDriver {
/**
* Parses chunks into pack of chunks. There <strong>always</strong> should be chunk+1 chunkInfos (one for the tail like
* {@link com.jetbrains.python.commandInterface.CommandInterfaceView#AFTER_LAST_CHARACTER_RANGE}).
* So, at least one chunk info should also exist!
*
* @param chunks chunks (parts of command line)
* @return parse info with chunks info. Warning: do not return less chunk infos than chunks provided. That leads to runtime error
*/
@NotNull
ParseInfo parse(@NotNull List<WordWithPosition> chunks);
}
@@ -0,0 +1,247 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
// TODO: Test
/**
* Presenter that uses {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} to parse pack of chunks
* to obtain {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo}.
* Each chunk may be paired with certain chunk info. Such info tells presenter whether this chunk has info, error, suggestions and so on.
* If caret situated far from chunks, then next neariest chunk should be found (see {@link #findNearestChunkAndInfo()}.
*
*
* @author Ilya.Kazakevich
* @see com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo
*/
public final class ChunkDriverBasedPresenter extends CommandInterfacePresenterAdapter {
@NotNull
private final ChunkDriver myChunkDriver;
@NotNull
private final SortedSet<ChunkAndInfo> myChunkAndInfos = new TreeSet<ChunkAndInfo>();
@Nullable
private Runnable myExecutor;
public ChunkDriverBasedPresenter(@NotNull final CommandInterfaceView view,
@NotNull final ChunkDriver chunkDriver) {
super(view);
myChunkDriver = chunkDriver;
}
@Override
public void launch() {
super.launch();
reparseText(true);
}
@Override
public void textChanged() {
reparseText(false);
}
private void reparseText(final boolean skipSuggestions) {
final List<WordWithPosition> chunks = WordWithPosition.splitText(myView.getText());
final ParseInfo parseInfo = myChunkDriver.parse(chunks);
myExecutor = parseInfo.getExecutor();
final List<ChunkInfo> chunkInfos = parseInfo.getChunkInfo();
assert chunkInfos.size() >= chunks.size() : "Driver did not return enough chunks";
assert !chunkInfos.isEmpty() : "At least one chunk info should exist";
myChunkAndInfos.clear();
for (int i = 0; i < chunkInfos.size(); i++) {
final ChunkInfo chunkInfo = chunkInfos.get(i);
final WordWithPosition chunk = chunks.size() > i ? chunks.get(i) : null;
myChunkAndInfos.add(new ChunkAndInfo(chunk, chunkInfo));
}
// configure Errors And Balloons
final Collection<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
for (final ChunkAndInfo chunkInfoPair : myChunkAndInfos) {
final ChunkInfo chunkInfo = chunkInfoPair.getChunkInfo();
Range<Integer> chunk = chunkInfoPair.getChunk();
if (chunk == null) {
// After the last!
chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
}
final String error = chunkInfo.getError();
if (error != null) {
errorBalloons.add(new WordWithPosition(error, chunk));
}
final String info = chunkInfo.getInfoBalloon();
if (info != null) {
infoBalloons.add(new WordWithPosition(info, chunk));
}
}
myView.setInfoAndErrors(infoBalloons, errorBalloons);
if (!skipSuggestions) {
configureSuggestion(false);
}
// Configure subtexts
final List<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) {
Range<Integer> chunk = chunkAndInfo.getChunk();
// If some place has suggestions, then add it
if (chunkAndInfo.getChunkInfo().getSuggestions() != null) {
if (chunk == null) {
// If there is no such chunk, that means we are after the last character, so use "special case" here
//noinspection ReuseOfLocalVariable
chunk = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
}
placesWhereSuggestionAvailable.add(chunk);
}
}
myView.configureSubTexts(parseInfo.getStatusText(), placesWhereSuggestionAvailable);
}
@Override
public void suggestionRequested() {
configureSuggestion(true); // Show or hide
}
/**
* Displays suggestions if needed.
*
* @param requestedExplicitly is suggesions where requested by user explicitly or not
*/
private void configureSuggestion(final boolean requestedExplicitly) {
myView.removeSuggestions();
final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo();
final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo();
final WordWithPosition chunk = chunkAndInfo.getChunk();
final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions();
if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) {
return;
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (chunk != null && !requestedExplicitly) {
filterLeaveOnlyMatching(suggestions, chunk.getText());
}
// TODO: Place to add history
if (!suggestions.isEmpty()) {
// No need to display empty suggestions
myView
.displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), (chunk == null ? null : chunk.getText()));
}
}
/**
* Filters collection of suggestions leaving only those starts with certain text.
* @param suggestions list to filter
* @param textToMatch leave only parts that start with this param
*/
private static void filterLeaveOnlyMatching(@NotNull final Iterable<String> suggestions, @NotNull final String textToMatch) {
// TODO: use guava instead?
final Iterator<String> iterator = suggestions.iterator();
while (iterator.hasNext()) {
if (!iterator.next().startsWith(textToMatch)) {
iterator.remove();
}
}
}
/**
* Searches for the nearest chunk and info to use. It may or may not find chunk, but it should always provide some chunk info.
*
* @return nearest chunk info and, probably, chunk.
*/
@NotNull
private ChunkAndInfo findNearestChunkAndInfo() {
final int caretPosition = myView.getCaretPosition();
for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) {
final Range<Integer> range = chunkAndInfo.getChunk();
if (range != null && range.isWithin(caretPosition)) {
return chunkAndInfo;
}
if (range != null && range.getFrom() > caretPosition) {
return new ChunkAndInfo(null, chunkAndInfo.getChunkInfo());
}
}
return new ChunkAndInfo(null, myChunkAndInfos.last().getChunkInfo());
}
@Override
public void completionRequested(@Nullable final String valueFromSuggestionList) {
final ChunkAndInfo chunkAndInfo = findNearestChunkAndInfo();
final WordWithPosition chunk = chunkAndInfo.getChunk();
if (valueFromSuggestionList != null) {
// Just insert it
if (chunk != null) { // If caret is on the chunk itself
myView.replaceText(chunk.getFrom(), chunk.getTo(), valueFromSuggestionList);
}
else {
myView.insertTextAfterCaret(valueFromSuggestionList);
}
return;
}
//User did not provide text no insert, do our best to find one
final ChunkInfo chunkInfo = chunkAndInfo.getChunkInfo();
final SuggestionInfo suggestionInfo = chunkInfo.getSuggestions();
if (suggestionInfo == null) {
return; // No suggestion available for this chunk
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (chunk != null) {
filterLeaveOnlyMatching(suggestions, chunk.getText());
}
if (suggestions.size() == 1) {
// Exclusive!
if (chunk != null) {
myView.replaceText(chunk.getFrom(), chunk.getTo(), suggestions.get(0));
}
else {
myView.insertTextAfterCaret(suggestions.get(0));
}
}
}
@Override
public void executionRequested() {
if (myExecutor == null) {
// TODO: Display error somehow
}
else {
myExecutor.run();
}
}
}
@@ -0,0 +1,81 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import org.jetbrains.annotations.Nullable;
/**
* Information about certain place in text, provided by chunk driver.
*
* @author Ilya.Kazakevich
*/
public final class ChunkInfo {
@Nullable
private final String myInfoBalloon;
@Nullable
private final String myError;
@Nullable
private final SuggestionInfo mySuggestions;
public ChunkInfo(@Nullable final String infoBalloon,
@Nullable final String error) {
this(infoBalloon, error, null);
}
/**
*
* @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
* @param error Error balloon to display when caret meets this place and underline text as error (null if no error)
* @param suggestions list of suggestions available in this place (if any)
*/
public ChunkInfo(@Nullable final String infoBalloon,
@Nullable final String error,
@Nullable final SuggestionInfo suggestions) {
myInfoBalloon = infoBalloon;
myError = error;
mySuggestions = suggestions;
}
/**
*
* @return Info balloon to display when caret meets this place (null if display nothing)
*/
@Nullable
public String getInfoBalloon() {
return myInfoBalloon;
}
/**
*
* @return Error balloon to display when caret meets this place and underline text as error (null if no error)
*/
@Nullable
public String getError() {
return myError;
}
/**
*
* @return list of suggestions available in this place (if any)
*/
@Nullable
public SuggestionInfo getSuggestions() {
return mySuggestions;
}
}
@@ -0,0 +1,88 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Pack of {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo} for certain chunks and other parsing info.
*
* @author Ilya.Kazakevich
*/
public final class ParseInfo {
@Nullable
private final String myStatusText;
@NotNull
private final List<ChunkInfo> myChunkInfo = new ArrayList<ChunkInfo>();
@Nullable
private final Runnable myExecutor;
/**
* @param chunkInfo Chunk info should match chunks in 1-to-1 manner:
* If "chunk1 chunk2 chunk3" were provided, you then need to return list where first chunkInfo matches first chunk ets.
* And there also should be one more chunkInfo for tail.
* @param statusText Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display.
* @param executor Engine to process command-line execution
*/
public ParseInfo(@NotNull final Collection<ChunkInfo> chunkInfo,
@Nullable final String statusText,
@Nullable final Runnable executor) {
myStatusText = statusText;
myChunkInfo.addAll(chunkInfo);
myExecutor = executor;
}
/**
* Simple parse info with out of status text and executor
*
* @param chunkInfo Chunk info (See {@link #ParseInfo(java.util.Collection, String, Runnable)}
* @see #ParseInfo(java.util.Collection, String, Runnable)
*/
public ParseInfo(@NotNull final Collection<ChunkInfo> chunkInfo) {
this(chunkInfo, null, null);
}
/**
* @return Status text {@link com.jetbrains.python.commandInterface.CommandInterfaceView view} may display.
*/
@Nullable
String getStatusText() {
return myStatusText;
}
/**
* @return Engine to process command-line execution
*/
@Nullable
Runnable getExecutor() {
return myExecutor;
}
/**
* @return Chunk info should match chunks in 1-to-1 manner, and there also should be one more chunkInfo for tail (
* see {@link #ParseInfo(java.util.Collection, String, Runnable) ctor} manual)
*/
@NotNull
List<ChunkInfo> getChunkInfo() {
return Collections.unmodifiableList(myChunkInfo);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Information about suggestions
*
* @author Ilya.Kazakevich
*/
public final class SuggestionInfo {
@NotNull
private final List<String> mySuggestions = new ArrayList<String>();
private final boolean myShowSuggestionsAutomatically;
private final boolean myShowAbsolute;
/**
* @param showSuggestionsAutomatically true it suggestions should be displayed even if user did not ask for that
* @param showAbsolute show suggestions at the absolute position (not relative to caret).
* @param suggestions List of suggestions to display
*/
public SuggestionInfo(final boolean showSuggestionsAutomatically,
final boolean showAbsolute,
@NotNull final Collection<String> suggestions) {
myShowSuggestionsAutomatically = showSuggestionsAutomatically;
myShowAbsolute = showAbsolute;
mySuggestions.addAll(suggestions);
}
/**
* @return List of suggestions to display
*/
@NotNull
public List<String> getSuggestions() {
return Collections.unmodifiableList(mySuggestions);
}
/**
* @return true it suggestions should be displayed even if user did not ask for that
*/
public boolean isShowSuggestionsAutomatically() {
return myShowSuggestionsAutomatically;
}
/**
* @return show suggestions at the absolute position (not relative to caret).
*/
public boolean isShowAbsolute() {
return myShowAbsolute;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <strong>chunk</strong>
* and {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver}.
* This presenter explodes command line into several parts or chunks.
* Chunks then passed to {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver driver} and it returns
* all information it has about each chunk. Presenter uses this information to display chunks correctly using view.
* To use this package, {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} should be implemented.
*
* See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriverBasedPresenter} as entry point
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter;
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -13,39 +13,28 @@
* 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);
}
@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.intellij.openapi.module.Module;
import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,8 +42,16 @@ public interface Command {
/**
* @return Information about command positional, unnamed {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} (not options!)
* @return Information about command positional, unnamed {@link Argument arguments} (not options!)
*/
@NotNull
ArgumentsInfo getArgumentsInfo();
/**
* Execute command
*
* @param module module to execute command against
* @param commandLine command's command line
*/
void execute(@NotNull final Module module, @NotNull final CommandLineParseResult commandLine);
}
@@ -0,0 +1,187 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkInfo;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ParseInfo;
import com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandLineParser.CommandLineParseResult;
import com.jetbrains.python.commandLineParser.CommandLineParser;
import com.jetbrains.python.commandLineParser.CommandLinePartType;
import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Chunk driver that uses pack of commands.
*
* @author Ilya.Kazakevich
*/
public final class CommandBasedChunkDriver implements ChunkDriver {
@NotNull
private final CommandLineParser myCommandLineParser;
@NotNull
private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
@NotNull
private final Module myModule;
/**
* @param commandLineParser parser to use
* @param module module parsing takes place in
* @param commands available commands
*/
public CommandBasedChunkDriver(@NotNull final CommandLineParser commandLineParser,
@NotNull final Module module,
@NotNull final Collection<? extends Command> commands) {
myCommandLineParser = commandLineParser;
for (final Command command : commands) {
myCommands.put(command.getName(), command);
}
myModule = module;
}
@Override
@NotNull
public ParseInfo parse(@NotNull final List<WordWithPosition> chunks) {
// TODO: Refactor to add command first to prevent copy/paste
if (chunks.isEmpty()) {
return createBadCommandInfo(chunks.size());
}
try {
final CommandLineParseResult commandLine = myCommandLineParser.parse(chunks);
final Command command = myCommands.get(commandLine.getCommand().getText());
if (command == null) {
// Bad command inserted
return createBadCommandInfo(chunks.size());
}
// Command exists, lets check its arguments
// TODO: Support options as well
// First, validate values
final ArgumentsInfo commandArgumentsInfo = command.getArgumentsInfo();
final List<ChunkInfo> chunkInfo = new ArrayList<ChunkInfo>();
// First chunk iscommand and it seems to be ok
chunkInfo.add(new ChunkInfo(null, null, new SuggestionInfo(false, true, myCommands.keySet())));
// Now add balloons, info and suggestions
for (int i = 0; i < commandLine.getParts().size(); i++) {
final Pair<Boolean, Argument> argumentPair = commandArgumentsInfo.getArgument(i);
if (argumentPair == null) { // Excess argument!
chunkInfo.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.excessArg")));
continue;
}
final Argument argument = argumentPair.getSecond();
final List<String> availableValues = argument.getAvailableValues();
final Pair<CommandLinePartType, WordWithPosition> part = commandLine.getParts().get(i);
if (part.first != CommandLinePartType.ARGUMENT) {
// Only arguments are supported now, so we have nothing to say about this chunk
chunkInfo.add(new ChunkInfo(null, null));
}
final String argumentValue = part.second.getText();
String errorMessage = null;
if (availableValues != null && !availableValues.contains(argumentValue)) {
// Bad value
errorMessage = PyBundle.message("commandLine.validation.argBadValue");
}
// Argument seems to be ok. We suggest values automatically only if value is bad
chunkInfo.add(new ChunkInfo(argument.getHelpText(), errorMessage,
(availableValues != null ? new SuggestionInfo(errorMessage != null, false, availableValues) : null)));
}
final Pair<Boolean, Argument> nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size());
if (nextArgumentPair != null) {
// Next arg exists
final Argument nextArgument = nextArgumentPair.getSecond();
final List<String> availableValues = nextArgument.getAvailableValues();
// Only add error if required
final String error = nextArgumentPair.first ? PyBundle.message("commandLine.validation.argMissing") : null;
final ChunkInfo lastArgInfo =
new ChunkInfo(nextArgument.getHelpText(), error,
(availableValues != null ? new SuggestionInfo(false, false, availableValues) : null));
chunkInfo.add(lastArgInfo);
}
else {
// Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
// This is a hack, but with out of it last chunkinfo will always be used, even 200 chars after last place
chunkInfo.add(new ChunkInfo(null, null));
}
assert chunkInfo.size() >= chunks.size() : "Contract broken: not enough chunks";
return new ParseInfo(chunkInfo, command.getHelp(), new MyExecutor(command, commandLine));
}
catch (final MalformedCommandLineException ignored) {
// Junk enetered!
return createBadCommandInfo(chunks.size());
}
}
/**
* Creates parse info signaling command is bad or junk
*
* @param numberOfChunks number of chunks provided by user (we must return chunk info for each chunk + 1, accroding to contract)
* @return parse info to return
*/
@NotNull
private ParseInfo createBadCommandInfo(final int numberOfChunks) {
final List<ChunkInfo> result = new ArrayList<ChunkInfo>();
// We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
// How ever, we must say something according to contract (number of infos should be equal or greater than number of chunks)
result
.add(new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet())));
for (int i = 1; i < numberOfChunks; i++) {
result.add(
new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand")));
}
return new ParseInfo(result);
}
/**
* Adapter that executes command using {@link Command#execute(com.intellij.openapi.module.Module, com.jetbrains.python.commandLineParser.CommandLineParseResult)}
*/
private class MyExecutor implements Runnable {
@NotNull
private final Command myCommand;
@NotNull
private final CommandLineParseResult myCommandLine;
MyExecutor(@NotNull final Command command, @NotNull final CommandLineParseResult line) {
myCommand = command;
myCommandLine = line;
}
@Override
public void run() {
myCommand.execute(myModule, myCommandLine);
}
}
}
@@ -13,18 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.google.common.base.Preconditions;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* In some special cases we have insight about command arguments.
@@ -61,6 +59,15 @@ public final class KnownArgumentsInfo implements ArgumentsInfo {
*/
private final int myMaxArguments;
/**
* For command with fixed number of arguments. In this case all arguments are fixed and required!
*
* @param arguments arguments this command have
*/
public KnownArgumentsInfo(@NotNull final Collection<Argument> arguments) {
this(arguments, arguments.size(), arguments.size());
}
/**
* For commands with infinite number of values last argument accepts (my_command VAL1 VAL2 .. VALN)
*
@@ -91,36 +98,20 @@ public final class KnownArgumentsInfo implements ArgumentsInfo {
@Nullable
@Override
public Argument getArgument(final int argumentPosition) {
public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
if (argumentPosition >= myMaxArguments) {
return null;
}
final boolean optional = argumentPosition >= myMinArguments;
if (myArguments.size() > argumentPosition) {
return myArguments.get(argumentPosition);
return Pair.create(!optional, myArguments.get(argumentPosition));
}
// We may need last one
if (argumentPosition <= myMaxArguments) {
return myArguments.get(myArguments.size() - 1);
if (argumentPosition < myMaxArguments) {
return Pair.create(false, myArguments.get(myArguments.size() - 1));
}
return null;
}
@NotNull
@Override
public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
final Map<Integer, ArgumentValueError> errors = new HashMap<Integer, ArgumentValueError>();
for (int i = 0; i < argumentValuesToCheck.size(); i++) {
final String userValue = argumentValuesToCheck.get(i);
final Argument argument = getArgument(i);
if (argument == null) {
errors.put(i, ArgumentValueError.EXCESS);
continue;
}
final List<String> availableValues = argument.getAvailableValues();
if (availableValues != null && !availableValues.contains(userValue)) {
errors.put(i, ArgumentValueError.BAD_VALUE);
}
}
return new ArgumentsValuesValidationInfo(errors, argumentValuesToCheck.size() < myMinArguments);
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.Nullable;
/**
* TODO: Redundant?!
* Case when command has no arguments (for sure!)
*
* @author Ilya.Kazakevich
*/
public final class NoArgumentsInfo implements ArgumentsInfo {
/**
* Instance to use when command has no arguments
*/
public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo();
private NoArgumentsInfo() {
}
@Nullable
@Override
public Pair<Boolean, Argument> getArgument(final int argumentPosition) {
return null;
}
}
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* For many commands we know nothing about arguments but their help text.
* This strategy is for this case
@@ -40,15 +39,10 @@ public final class UnknownArgumentsInfo implements ArgumentsInfo {
myHelp = allArgumentsHelpText;
}
@Nullable
@Override
public Argument getArgument(final int argumentPosition) {
return new Argument(myHelp); // We can't say argument does not exist.
}
@NotNull
@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));
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of
* {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Command command} and its {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Argument arguments}.
*
* See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point.
* It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
* provided by user
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.commandBasedChunkDriver;
@@ -1,78 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Map;
/**
* Information about {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} values validation
*
* @author Ilya.Kazakevich
*/
public final class ArgumentsValuesValidationInfo {
/**
* Validation with out of any error
*/
@NotNull
static final ArgumentsValuesValidationInfo
NO_ERROR = new ArgumentsValuesValidationInfo(Collections.<Integer, ArgumentValueError>emptyMap(), false);
private final Map<Integer, ArgumentValueError> myPositionOfErrorArguments = new HashMap<Integer, ArgumentValueError>();
private final boolean myNotEnoughArguments;
/**
* @param positionOfErrorArguments map of [argument_position, its_value_error]
* @param notEnoughArguments true if not enough arguments values provided (i.e. some required arg missed)
*/
ArgumentsValuesValidationInfo(@NotNull final Map<Integer, ArgumentValueError> positionOfErrorArguments,
final boolean notEnoughArguments) {
myPositionOfErrorArguments.putAll(positionOfErrorArguments);
myNotEnoughArguments = notEnoughArguments;
}
/**
* @return map of [argument_position, its_value_error]
*/
@NotNull
Map<Integer, ArgumentValueError> getPositionOfErrorArguments() {
return Collections.unmodifiableMap(myPositionOfErrorArguments);
}
/**
* @return if not enough argument values provided (i.e. some required arg missed)
*/
boolean isNotEnoughArguments() {
return myNotEnoughArguments;
}
/**
* Type of argument value error.
*/
enum ArgumentValueError {
/**
* This argument is redundant
*/
EXCESS,
/**
* Argument has bad value
*/
BAD_VALUE
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Simple command implementation
*
* @author Ilya.Kazakevich
*/
public class CommandAdapter implements Command {
@NotNull
private final String myName;
@Nullable
private final String myHelp;
@NotNull
private final ArgumentsInfo myArgumentsInfo;
/**
* @param help help text
* @param name command name
* @param argumentsInfo arguments info
*/
public CommandAdapter(@NotNull final String name, @Nullable final String help, @NotNull ArgumentsInfo argumentsInfo) {
myName = name;
myHelp = help;
myArgumentsInfo = argumentsInfo;
}
/**
* @return command name
*/
@Override
@NotNull
public final String getName() {
return myName;
}
@Override
@Nullable
public final String getHelp() {
return myHelp;
}
@NotNull
@Override
public final ArgumentsInfo getArgumentsInfo() {
return myArgumentsInfo;
}
}
@@ -1,110 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
/**
* Information about command and its arguments to save in history / pass to execution etc.
*
* @author Ilya.Kazakevich
*/
public class CommandExecutionInfo {
/**
* Command and arguments separator.
*/
private static final String SEPARATOR = " ";
@NotNull
private final String myCommandName;
@NotNull
private final String[] myArguments;
/**
* @param commandName command
* @param arguments its arguments
*/
public CommandExecutionInfo(@NotNull final String commandName, @NotNull final String... arguments) {
myCommandName = commandName;
myArguments = arguments.clone();
}
/**
* @return command
*/
@NotNull
public String getCommandName() {
return myCommandName;
}
/**
* @return command arguments
*/
@NotNull
public String[] getArguments() {
return myArguments.clone();
}
/**
* @return command in format "command arg1 arg2". Opposite to {@link #fromString(String)}
* @see #fromString(String)
*/
@NotNull
public String toString() {
// TODO: What if command or argument has space in it? Escape somehow!
return StringUtil.join(ArrayUtil.mergeArrays(new String[]{myCommandName}, myArguments), SEPARATOR);
}
/**
* @param stringToUnserialize string created by {@link #toString()}
* @return command parsed from string
* @see #toString()
*/
@Nullable
public static CommandExecutionInfo fromString(@NotNull final String stringToUnserialize) {
// TODO: What if command or argument has space in it? Escape somehow!
final List<String> strings = StringUtil.split(stringToUnserialize, SEPARATOR);
if (strings.isEmpty()) {
return null;
}
return new CommandExecutionInfo(strings.get(0), ArrayUtil.toStringArray(strings.subList(1, strings.size())));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CommandExecutionInfo)) return false;
CommandExecutionInfo info = (CommandExecutionInfo)o;
if (!Arrays.equals(myArguments, info.myArguments)) return false;
if (!myCommandName.equals(info.myCommandName)) return false;
return true;
}
@Override
public int hashCode() {
int result = myCommandName.hashCode();
result = 31 * result + Arrays.hashCode(myArguments);
return result;
}
}
@@ -1,247 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import com.jetbrains.python.optParse.MalformedCommandLineException;
import com.jetbrains.python.optParse.ParsedCommandLine;
import com.jetbrains.python.optParse.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Command-line interface presenter that is command-based
*
* @param <C> Command type
* @author Ilya.Kazakevich
*/
public class CommandInterfacePresenterCommandBased<C extends Command> extends CommandInterfacePresenterAdapter {
/**
* [name] -> command. Linked is used to preserve order.
*/
private final Map<String, C> myCommands = new LinkedHashMap<String, C>();
/**
* currenly used strategy (see interface for more info)
*/
private Strategy myStrategy;
/**
* @param view view
* @param commands available commands
*/
public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
@NotNull final Iterable<C> commands) {
super(view);
for (final C command : commands) {
myCommands.put(command.getName(), command);
}
}
/**
* @param view view
* @param commands available commands
*/
public CommandInterfacePresenterCommandBased(@NotNull final CommandInterfaceView view,
@NotNull final C... commands) {
this(view, Arrays.asList(commands));
}
@Override
public void launch() {
/*myView.setPreferredWidthInChars(getMaximumCommandWithArgsLength());*/
super.launch();
myStrategy = new NoCommandStrategy(this);
}
@Override
public void textChanged(final boolean inForcedTextMode) {
configureStrategy();
myView.setSubText(myStrategy.getSubText());
final Pair<SpecialErrorPlace, List<WordWithPosition>> errorInfo = myStrategy.getErrorInfo();
myView.showErrors(errorInfo.getSecond(), errorInfo.first);
myView.setBalloons(myStrategy.getBalloonsToShow());
final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
final String lastPart = getLastPart();
if ((lastPart != null) && myStrategy.isUnknownTextExists()) {
//Filter to starts from
final Iterator<String> iterator = suggestions.iterator();
while (iterator.hasNext()) {
final String textToCheck = iterator.next();
if (!textToCheck.startsWith(lastPart)) {
iterator.remove();
}
}
}
if (!suggestionInfo.myShowOnlyWhenRequested && !suggestions.isEmpty()) {
final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
suggestionsBuilder.add(suggestions);
myView
.displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
}
else {
myView.removeSuggestions();
}
}
/**
* @return builder that already has history in its prefix group (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
*/
@NotNull
private SuggestionsBuilder getBuilderWithHistory() {
return new SuggestionsBuilder();
// TODO: Uncomment when history would be fixed
/*final SuggestionsBuilder suggestionsBuilder = new SuggestionsBuilder();
final List<CommandExecutionInfo> history = getHistory();
final Collection<String> historyCommands = new LinkedHashSet<String>();
for (final CommandExecutionInfo info : history) {
historyCommands.add(info.toString());
}
if (!historyCommands.isEmpty()) {
// TODO: Later implement folding by name
suggestionsBuilder.changeGroup(false);
suggestionsBuilder
.add(ArrayUtil.toStringArray(historyCommands));
suggestionsBuilder.changeGroup(true);
}
return suggestionsBuilder;*/
}
/**
* @return execution info from history. It is empty by default, child should implement it.
*/
@NotNull
protected List<CommandExecutionInfo> getHistory() {
return Collections.emptyList();
}
/**
* @return command that entered in box, or null of just entered
*/
@Nullable
protected CommandExecutionInfo getCommandToExecute() {
return myStrategy.getCommandToExecute();
}
/**
* Finds and sets appropriate strategy
*/
private void configureStrategy() {
final ParsedCommandLine line = getParsedCommandLine();
if (line != null) {
final Command command = myCommands.get(line.getCommand().getText());
if (command != null) {
myStrategy = new InCommandStrategy(command, line, this);
return;
}
}
myStrategy = new NoCommandStrategy(this); // No command or bad command found
}
@Override
public void completionRequested(@Nullable final String valueFromSuggestionList) {
if (valueFromSuggestionList != null) {
final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
if (suggestionInfo.getSuggestions().contains(valueFromSuggestionList)) {
final ParsedCommandLine commandLine = getParsedCommandLine();
final List<String> words = commandLine != null ? commandLine.getAsWords() : new ArrayList<String>();
if (!words.isEmpty() && myView.isCaretOnWord()) {
words.remove(words.size() - 1);
}
words.add(valueFromSuggestionList);
myView.forceText(StringUtil.join(words, " "));
}
}
myView.removeSuggestions();
}
@Override
public void suggestionRequested() {
final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo();
final List<String> suggestions = suggestionInfo.getSuggestions();
if (!suggestions.isEmpty()) {
final SuggestionsBuilder suggestionsBuilder = getBuilderWithHistory();
suggestionsBuilder.add(suggestions);
myView.displaySuggestions(suggestionsBuilder, suggestionInfo.myAbsolute, null);
}
}
@Override
public void executionRequested(@Nullable final String valueFromSuggestionList) {
}
/**
* @return [command_name => command] all available commands
*/
@NotNull
protected final Map<String, C> getCommands() {
return Collections.unmodifiableMap(myCommands);
}
/**
* @return parsed commandline entered by user
*/
@Nullable
final ParsedCommandLine getParsedCommandLine() {
try {
return new ParsedCommandLine(myView.getText());
}
catch (final MalformedCommandLineException ignored) {
return null;
}
}
/**
* @return last part of splitted text (if any). I.e. "foo bar spam" will return "spam"
*/
@Nullable
final String getLastPart() {
final ParsedCommandLine commandLine = getParsedCommandLine();
if (commandLine == null || commandLine.getAsWords().isEmpty()) {
return null;
}
final List<String> words = commandLine.getAsWords();
return words.get(words.size() - 1);
}
/**
* @return view
*/
@NotNull
CommandInterfaceView getView() {
return myView;
}
}
@@ -1,150 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.openapi.util.Pair;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
import com.jetbrains.python.optParse.ParsedCommandLine;
import com.jetbrains.python.optParse.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Strategy implementation for case when user entered command
*
* @author Ilya.Kazakevich
*/
final class InCommandStrategy extends Strategy {
@NotNull
private final List<String> myArguments = new ArrayList<String>();
@NotNull
private final Command myCommand;
@NotNull
private final ParsedCommandLine myCommandLine;
/**
* @param command command enrtered by user
* @param presenter presenter
*/
InCommandStrategy(@NotNull final Command command,
@NotNull final ParsedCommandLine commandLine,
@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
super(presenter);
myArguments.addAll(WordWithPosition.fetchText(commandLine.getArguments()));
myCommand = command;
myCommandLine = commandLine;
}
@NotNull
@Override
public String getSubText() {
final String help = myCommand.getHelp();
if (help != null) {
return help;
}
return "Place to display help";
}
@NotNull
@Override
SuggestionInfo getSuggestionInfo() {
final Argument nextArgument = myCommand.getArgumentsInfo().getArgument(myCommandLine.getArguments().size());
if (nextArgument != null) {// TODO: Check options!
// If next arg exists
final List<String> availableValues = nextArgument.getAvailableValues();
if (availableValues != null) { // If has available values
return new SuggestionInfo(false, false, availableValues);
}
}
return new SuggestionInfo(false, false, Collections.<String>emptyList());
}
@NotNull
@Override
List<WordWithPosition> getBalloonsToShow() {
// Display argument balloons right from command end to last argument end
final ArgumentsInfo argumentsInfo = myCommand.getArgumentsInfo();
final List<WordWithPosition> arguments = myCommandLine.getArguments();
if (arguments.isEmpty()) {
// If no arguments provided, then display first argument popup right after command
final Argument firstArgument = argumentsInfo.getArgument(0);
if (firstArgument == null) {
return Collections.emptyList(); // Looks like no argument required
}
return Collections
.singletonList(new WordWithPosition(firstArgument.getHelpText(), myCommandLine.getCommand().getTo() + 1));
}
final List<WordWithPosition> result = new ArrayList<WordWithPosition>(arguments.size());
for (int i = 0; i < arguments.size(); i++) {
final WordWithPosition argEnteredByUser = arguments.get(i);
final Argument argument = argumentsInfo.getArgument(i);
if (argument != null) {
// Display argument help
result.add(argEnteredByUser.copyWithDifferentText(argument.getHelpText()));
}
}
return result;
}
@Override
boolean isUnknownTextExists() {
if (myCommandLine.getAsWords().isEmpty()) {
return false; // Command only
}
final String lastPart = myPresenter.getLastPart();
return ((lastPart != null) && !getSuggestionInfo().getSuggestions().contains(lastPart));
}
@Nullable
@Override
CommandExecutionInfo getCommandToExecute() {
return new CommandExecutionInfo(myCommand.getName(), ArrayUtil.toStringArray(myArguments));
}
@NotNull
@Override
Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
final List<WordWithPosition> userProvidedValues = myCommandLine.getArguments();
SpecialErrorPlace specialError = null;
final List<WordWithPosition> errors = new ArrayList<WordWithPosition>();
final ArgumentsValuesValidationInfo validation =
myCommand.getArgumentsInfo().validateArgumentValues(WordWithPosition.fetchText(userProvidedValues));
if (validation.isNotEnoughArguments()) {
specialError = SpecialErrorPlace.AFTER_LAST_CHAR;
}
for (final Entry<Integer, ArgumentValueError> errorEntry : validation.getPositionOfErrorArguments().entrySet()) {
final String errorText = (errorEntry.getValue() == ArgumentValueError.BAD_VALUE ?
PyBundle.message("commandsWithArgs.validation.badValue") :
PyBundle.message("commandsWithArgs.validation.excess") );
errors.add(userProvidedValues.get(errorEntry.getKey()).copyWithDifferentText(errorText));
}
return Pair.create(specialError, errors);
}
}
@@ -1,62 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* Case when command has no arguments (for sure!)
*
* @author Ilya.Kazakevich
*/
public final class NoArgumentsInfo implements ArgumentsInfo {
/**
* Instance to use when command has no arguments
*/
public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo();
private NoArgumentsInfo() {
}
@Nullable
@Override
public Argument getArgument(final int argumentPosition) {
return null;
}
@NotNull
@Override
public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List<String> argumentValuesToCheck) {
if (argumentValuesToCheck.isEmpty()) {
return ArgumentsValuesValidationInfo.NO_ERROR;
}
final Map<Integer, ArgumentValueError> errors =
new HashMap<Integer, ArgumentsValuesValidationInfo.ArgumentValueError>();
for (final int errorPosition : ContiguousSet.create(Range.closedOpen(0, argumentValuesToCheck.size()), DiscreteDomain.integers())) {
errors.put(errorPosition, ArgumentValueError.EXCESS);
}
return new ArgumentsValuesValidationInfo(errors, false);
}
}
@@ -1,78 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import com.jetbrains.python.optParse.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Strategy implementation for case when no command parsed
*
* @author Ilya.Kazakevich
*/
final class NoCommandStrategy extends Strategy {
private static final Pair<SpecialErrorPlace, List<WordWithPosition>>
NO_ERROR = Pair.create(null, Collections.<WordWithPosition>emptyList());
NoCommandStrategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
super(presenter);
}
@NotNull
@Override
String getSubText() {
return PyBundle.message("commandsWithArgs.enterCommand.label");
}
@NotNull
@Override
SuggestionInfo getSuggestionInfo() {
return new SuggestionInfo(true, isTextBoxEmpty(), new ArrayList<String>(myPresenter.getCommands().keySet()));
}
@Override
boolean isUnknownTextExists() {
return !myPresenter.getView().getText().isEmpty();
}
@NotNull
@Override
Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo() {
// No error if textbox empty, but mark everything as error if some text entered: it is junk (it can't be command,
// InCommand strategy were selected otherwise)
return isTextBoxEmpty() ? NO_ERROR : Pair.create(SpecialErrorPlace.WHOLE_TEXT, Collections.<WordWithPosition>emptyList());
}
private boolean isTextBoxEmpty() {
return myPresenter.getView().getText().isEmpty();
}
@Nullable
@Override
CommandExecutionInfo getCommandToExecute() {
return null;
}
}
@@ -1,79 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import com.jetbrains.python.optParse.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* Strategy that helps {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
* to deal with 2 states: when no text entered (or some junk enetered) or command name entered
*
* @author Ilya.Kazakevich
*/
abstract class Strategy {
@NotNull
protected final CommandInterfacePresenterCommandBased<?> myPresenter;
/**
* @param presenter presenter
*/
protected Strategy(@NotNull final CommandInterfacePresenterCommandBased<?> presenter) {
myPresenter = presenter;
}
/**
* @return sub text to display
*/
@NotNull
abstract String getSubText();
/**
* @return suggestions
*/
@NotNull
abstract SuggestionInfo getSuggestionInfo();
// TODO: Merge baloon and error (actually the same)
@NotNull
List<WordWithPosition> getBalloonsToShow() {
return Collections.emptyList();
}
/**
* @return command that entered in box, or null of just entered
*/
@Nullable
abstract CommandExecutionInfo getCommandToExecute();
/**
* @return errors
*/
@NotNull
abstract Pair<SpecialErrorPlace, List<WordWithPosition>> getErrorInfo();
/**
* @return if text entered by user contains some unknown commands
*/
abstract boolean isUnknownTextExists();
}
@@ -1,65 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Information abouyt suggestion, provided by {@link com.jetbrains.python.commandInterface.commandsWithArgs.Strategy}
*
* @author Ilya.Kazakevich
*/
@SuppressWarnings("PackageVisibleField")
// No do not need to hide field: everything is internal API in package, anyway
final class SuggestionInfo {
/**
* Suggestions
*/
private final List<String> mySuggestions = new ArrayList<String>();
/**
* Display them at absolute location or relative to last letter
*/
final boolean myAbsolute;
/**
* Show then any time, or only when user requests them
*/
final boolean myShowOnlyWhenRequested;
/**
* @param absolute Display them at absolute location or relative to last letter
* @param showOnlyWhenRequested Show then any time, or only when user requests them
* @param suggestions Suggestions
*/
SuggestionInfo(final boolean absolute,
final boolean showOnlyWhenRequested,
@NotNull final List<String> suggestions) {
myAbsolute = absolute;
myShowOnlyWhenRequested = showOnlyWhenRequested;
mySuggestions.addAll(suggestions);
}
/**
* @return suggestions
*/
@NotNull
List<String> getSuggestions() {
return Collections.unmodifiableList(mySuggestions);
}
}
@@ -1,42 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* <h1>Optparse-based commandline interface presenter</h1>
* <p>
* Command-line like interface presenter that uses conception of command and its arguments.
* See {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased}
* and its arguments: {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument}.
*
* It supports <a href="https://docs.python.org/2/library/optparse.html">optparse</a> terminology, so
* read it first and use {@link com.jetbrains.python.optParse} package
* </p>
* <h2>Arguments and validation</h2>
* <p>
* Optparse arguments are <strong>positional and unnamed</strong>.
* Each {@link com.jetbrains.python.commandInterface.commandsWithArgs.Command command} provides
* {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo arguments info}.
* It can be used to obtain information about argument (like list of possible values) and it also used to validate argument values,
* provided by user. In most cases we have no idea about arguments: due to optparse limitations only help test is available.
* But sometimes we do know (like when args are documented).
* Different strategies exist, so be sure to check {@link com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsInfo} children
* </p>
*
*
* @see com.jetbrains.python.optParse
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.commandsWithArgs;
@@ -19,7 +19,7 @@
* It has several abilities, including (but not limited):
* <ol>
* <li>Suggestion box</li>
* <li>Error marking</li>
* <li>Error emphasising</li>
* <li>Popups</li>
* <li>AutoCompletion</li>
* </ol>
@@ -31,9 +31,15 @@
*
* <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
*/
@@ -17,16 +17,18 @@ package com.jetbrains.python.commandInterface.swingView;
import com.google.common.base.Preconditions;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.fileEditor.impl.EditorWindow;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Range;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.optParse.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionList;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
@@ -37,19 +39,31 @@ import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import java.awt.*;
import java.awt.event.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Command-interface view implementation based on Swing
* Command-interface view implementation based on Swing.
* It uses balloons to display errors and infos, drop-down for suggestions and also underlines errors
*
* @author Ilya.Kazakevich
*/
public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener, CaretListener {
private static final JBColor ERROR_COLOR = JBColor.RED;
/**
* We need to track balloons, so we have field with callback
*/
@NotNull
private final BalloonManager myBalloonManager = new BalloonManager();
/**
* Pop-up we displayed in
*/
@@ -68,27 +82,38 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
* Lower (sub) label
*/
private JLabel mySubLabel;
/**
* "Suggestion area". Suggestion status is displayed when caret meets this area
*/
private final List<Range<Integer>> myPlacesWhereSuggestionsAvailable = new ArrayList<Range<Integer>>();
@NotNull
private final CommandInterfacePresenter myPresenter;
/**
* List to display suggestions
*/
@NotNull
private final SuggestionList myList;
private final SuggestionList mySuggestionList;
/**
* Displayed when there is no text
*/
@Nullable
private final String myPlaceHolderText;
/**
* Flag that indicates we are in "test forced" mode: current text set by presenter, not by user
* Information balloons that should be displayed when caret meets their boundaries.
*/
private boolean myInForcedTextMode;
// TODO: Doc
@NotNull
private final List<WordWithPosition> myBalloons = new ArrayList<WordWithPosition>();
@NotNull
private final List<WordWithPosition> myInfoBalloons = new ArrayList<WordWithPosition>();
/**
* Error balloons that should be displayed when caret meets their boundaries.
* Errors are always underlined, but balloons are displayed only if caret meets error
*/
private final List<WordWithPosition> myErrorBalloons = new ArrayList<WordWithPosition>();
/**
* Default subtext to display when caret is out of {@link #myPlacesWhereSuggestionsAvailable "suggestion" area}
*/
@Nullable
private String myDefaultSubText;
/**
* @param presenter our presenter
@@ -112,18 +137,11 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
myMainTextField.setFocusable(true);
final EditorWindow window = FileEditorManagerEx.getInstanceEx(project).getCurrentWindow();
final int windowSize;
if (window != null) {
windowSize = window.getSize().width;
}
else {
windowSize = 0; // Windows size is unknown
}
final int windowWidth = FileEditorManagerEx.getInstanceEx(project).getComponent().getRootPane().getWidth() - 10; // Little gap
myMainTextField
.setPreferredWidthInPx(windowSize);
myList = new SuggestionList(new MySuggestionListListener());
.setPreferredWidthInPx(windowWidth);
mySuggestionList = new SuggestionList(new MySuggestionListListener());
}
@@ -145,7 +163,12 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
}
});
myMainTextField.setFocusTraversalKeysEnabled(false);
myMainTextField.addKeyListener(new MyKeyListener());
myMainTextField.addKeyListener(new MyKeyListener()); // Up/down arrows are not handles with actions
// Register all available actions
for (final KeyStrokeInfo strokeInfo : KeyStrokeInfo.values()) {
strokeInfo.register(myPresenter, mySuggestionList, myMainTextField);
}
myMainPopUp.showInFocusCenter();
}
@@ -154,32 +177,18 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
public void displaySuggestions(@NotNull final SuggestionsBuilder suggestions, final boolean absolute, @Nullable final String toSelect) {
int left = 0;
// Display text right after line ends if not in "absolute" mode
// Display text right after caret
if (!absolute) {
left = myMainTextField.getTextEndPosition();
left = myMainTextField.getTextCaretPositionInPx();
}
myList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
mySuggestionList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
configureAppropriateStatus();
}
@Override
public void onClosed(final LightweightWindowEvent event) {
super.onClosed(event);
myList.close();
}
@Override
public final void showErrors(@NotNull final List<WordWithPosition> errors, @Nullable final SpecialErrorPlace specialErrorPlace) {
for (final WordWithPosition error : errors) {
myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
}
if (specialErrorPlace != null) {
myMainTextField.underlineText(specialErrorPlace, ERROR_COLOR);
}
synchronized (myErrorBalloons) {
myErrorBalloons.clear();
myErrorBalloons.addAll(errors);
}
mySuggestionList.close();
}
@@ -188,6 +197,7 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
processDocumentChange();
}
@Override
public void removeUpdate(final DocumentEvent e) {
processDocumentChange();
@@ -195,7 +205,7 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
private void processDocumentChange() {
myMainTextField.hideUnderline();
myPresenter.textChanged(myInForcedTextMode);
myPresenter.textChanged();
}
@Override
@@ -204,75 +214,155 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
}
@Override
public void forceText(@NotNull final String newText) {
myInForcedTextMode = true;
myMainTextField.setText(newText);
myInForcedTextMode = false;
public void removeSuggestions() {
mySuggestionList.close();
configureAppropriateStatus();
}
@Override
public void removeSuggestions() {
myList.close();
}
@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
synchronized (myErrorBalloons) {
showBaloons(myErrorBalloons, Position.below, MessageType.ERROR);
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;
}
synchronized (myBalloons) {
showBaloons(myBalloons, Position.above, MessageType.INFO);
// 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()));
}
}
// TODO: Doc
private void showBaloons(@NotNull final List<WordWithPosition> balloons,
@NotNull final Position popUpPosition,
@NotNull final MessageType messageType) {
private void displayBalloonsIfRequired() {
synchronized (myErrorBalloons) {
if (mySuggestionList.isClosed()) { // No need to display error popups when suggestion list is displayed. It intersects.
showBalloons(myErrorBalloons, Position.below, MessageType.ERROR);
}
}
synchronized (myInfoBalloons) {
showBalloons(myInfoBalloons, Position.above, MessageType.INFO);
}
}
/**
* 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);
}
return !Character.isWhitespace(myMainTextField.getText().toCharArray()[caretPosition - 1]);
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());
}
}
}
/**
* Checks if some range is <strong>special case</strong> {@link #AFTER_LAST_CHARACTER_RANGE}.
*
* @param range range to check
* @return true if special case
*/
private static boolean isAfterLastCharRange(@NotNull final Range<Integer> range) {
return AFTER_LAST_CHARACTER_RANGE.getFrom().equals(range.getFrom()) && AFTER_LAST_CHARACTER_RANGE.getTo().equals(range.getTo());
}
@Override
public void setBalloons(@NotNull final Collection<WordWithPosition> balloons) {
synchronized (myBalloons) {
myBalloons.clear();
myBalloons.addAll(balloons);
public final void insertTextAfterCaret(@NotNull final String text) {
try {
myMainTextField.getDocument().insertString(myMainTextField.getCaretPosition(), text, null);
}
catch (final BadLocationException e) {
// TODO: Display error somehow!
e.printStackTrace();
}
}
@Override
public void setSubText(@NotNull final String subText) {
mySubLabel.setText(subText);
public final void replaceText(final int from, final int to, @NotNull final String newText) {
myMainTextField.select(from, to);
myMainTextField.replaceSelection(newText);
myBalloonManager.closeAllBalloons();
myPresenter.textChanged();
displayBalloonsIfRequired(); // This crunch but we need to recalculate balloons in this case (position is changed!)
}
@Override
public final int getCaretPosition() {
return myMainTextField.getCaretPosition();
}
@Override
public final void configureSubTexts(@Nullable final String defaultSubText,
@NotNull final List<Range<Integer>> suggestionAvailablePlaces) {
synchronized (myPlacesWhereSuggestionsAvailable) {
myPlacesWhereSuggestionsAvailable.clear();
myPlacesWhereSuggestionsAvailable.addAll(suggestionAvailablePlaces);
myDefaultSubText = defaultSubText;
}
configureAppropriateStatus();
}
/**
* Reacts on keys, pressed by user
@@ -281,21 +371,13 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
@Override
public void keyPressed(final KeyEvent e) {
super.keyPressed(e);
final int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP) {
myList.moveSelection(true);
mySuggestionList.moveSelection(true);
}
else if (keyCode == KeyEvent.VK_DOWN) {
myList.moveSelection(false);
}
else if (keyCode == KeyEvent.VK_ENTER) {
myPresenter.executionRequested(myList.getValue());
}
else if (keyCode == KeyEvent.VK_TAB) {
myPresenter.completionRequested(myList.getValue());
}
else if ((keyCode == KeyEvent.VK_SPACE) && (e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK)) {
myPresenter.suggestionRequested();
mySuggestionList.moveSelection(false);
}
}
}
@@ -306,10 +388,6 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
return myMainTextField.getText();
}
@Override
public void setPreferredWidthInChars(final int widthInChars) {
myMainTextField.setPreferredWidthInChars(widthInChars);
}
/**
* Listener for suggestion list
@@ -322,4 +400,35 @@ public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements Com
removeSuggestions();
}
}
/**
* Keeps tracks for baloons to close all of them in case of text inserion
*/
private static final class BalloonManager extends JBPopupAdapter {
@NotNull
private final Set<Balloon> myCurrentBaloons = new HashSet<Balloon>();
void registerBalloon(final Balloon balloon) {
synchronized (myCurrentBaloons) {
myCurrentBaloons.add(balloon);
balloon.addListener(this);
}
}
@Override
public void onClosed(final LightweightWindowEvent event) {
synchronized (myCurrentBaloons) {
myCurrentBaloons.remove(event.asBalloon());
}
super.onClosed(event);
}
void closeAllBalloons() {
synchronized (myCurrentBaloons) {
for (final Balloon balloon : myCurrentBaloons) {
balloon.dispose();
}
}
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
import java.awt.event.ActionEvent;
/**
* "Complete current command or argument" action
*
* @author Ilya.Kazakevich
*/
final class CompletionKeyStrokeAction extends KeyStrokeAction {
CompletionKeyStrokeAction() {
super(KeyStrokeInfo.COMPLETION);
}
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.completionRequested(mySuggestionList.getValue());
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
import java.awt.event.ActionEvent;
/**
* "Execute command" action
*
* @author Ilya.Kazakevich
*/
final class ExecutionKeyStrokeAction extends KeyStrokeAction {
ExecutionKeyStrokeAction() {
super(KeyStrokeInfo.EXECUTION);
}
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.executionRequested();
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.suggestionList.SuggestionList;
import org.jetbrains.annotations.NotNull;
import javax.swing.text.TextAction;
/**
* Action that should be taken for certain {@link javax.swing.KeyStroke} (wrapped in {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}).
* You need to call {@link #configure(com.jetbrains.python.commandInterface.CommandInterfacePresenter, com.jetbrains.python.suggestionList.SuggestionList)}
* to enable one.
*
* @author Ilya.Kazakevich
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "NonSerializableFieldInSerializableClass"}) // Will never serialize
abstract class KeyStrokeAction extends TextAction {
@NotNull
private final String myName;
@NotNull
private final KeyStrokeInfo myStroke;
protected CommandInterfacePresenter myPresenter;
protected SuggestionList mySuggestionList;
/**
* @param stroke key stroke to bind this info to
*/
KeyStrokeAction(@NotNull final KeyStrokeInfo stroke) {
super(stroke.name());
myName = stroke.name();
myStroke = stroke;
}
/**
* Configures action.
*
* @param presenter presenter to be used for call back.
* @param suggestionList list of suggestions to be used for call back
* @return name of this action to add to {@link javax.swing.InputMap}
*/
@NotNull
final String configure(@NotNull final CommandInterfacePresenter presenter, @NotNull final SuggestionList suggestionList) {
myPresenter = presenter;
mySuggestionList = suggestionList;
return myName;
}
/**
* @return stroke bound to this action
*/
@NotNull
final KeyStrokeInfo getStroke() {
return myStroke;
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
import com.intellij.openapi.keymap.KeymapUtil;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.suggestionList.SuggestionList;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
/**
* Key strokes to be used with view.
* Strokes paired with action. You need to register each action via {@link javax.swing.InputMap}
*
* @author Ilya.Kazakevich
*/
enum KeyStrokeInfo {
/**
* "Execute command" keystroke
*/
EXECUTION(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)),
/**
* "Complete current command or argument" keystroke
*/
COMPLETION(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)),
/**
* "Display suggestions" keystroke.
*/
SUGGESTION(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)),;
/**
* List of actions. Each action should be bound to some {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}
*/
@NotNull
private static final KeyStrokeAction[] ACTIONS = {
new CompletionKeyStrokeAction(),
new ExecutionKeyStrokeAction(),
new SuggestionKeyStrokeAction()};
private final KeyStroke myStroke;
KeyStrokeInfo(@NotNull final KeyStroke stroke) {
myStroke = stroke;
}
/**
* Registers action and binds it appropriate stroke. Call if for all instances to make all actions available.
*
* @param presenter presenter to be used as call back
* @param suggestionList suggestion list to be used as call back
* @param source Component with {@link javax.swing.InputMap} and {@link javax.swing.ActionMap} (swing view itself)
*/
void register(@NotNull final CommandInterfacePresenter presenter,
@NotNull final SuggestionList suggestionList,
@NotNull final JComponent source) {
final KeyStrokeAction action = getAction();
final String strokeName = action.configure(presenter, suggestionList);
source.getInputMap().put(myStroke, strokeName);
source.getActionMap().put(strokeName, action);
}
/**
* @return Human-readable name of this action (to display it to user)
*/
@NotNull
String getText() {
return KeymapUtil.getKeystrokeText(myStroke);
}
/**
* @return action paired with stroke
*/
@NotNull
private KeyStrokeAction getAction() {
for (final KeyStrokeAction action : ACTIONS) {
if (action.getStroke() == this) {
return action;
}
}
throw new IllegalStateException("Failed to find action for " + name());
}
}
@@ -17,13 +17,9 @@ package com.jetbrains.python.commandInterface.swingView;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorFontType;
import com.intellij.openapi.util.Pair;
import com.intellij.util.Range;
import com.intellij.util.ui.StatusText;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
@@ -47,11 +43,6 @@ public class SmartTextField extends JTextField {
@NotNull
private final Collection<UnderlineInfo> myUnderlineInfo = new ArrayList<UnderlineInfo>();
private int myPreferredWidth;
/**
* (color, special_place) tuple to underline special place, or null if no underline required
*/
@Nullable
private Pair<Color, SpecialErrorPlace> mySpecialUnderlinePlace;
public SmartTextField() {
setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN));
@@ -66,15 +57,9 @@ public class SmartTextField extends JTextField {
synchronized (myUnderlineInfo) {
for (final UnderlineInfo underlineInfo : myUnderlineInfo) {
g.setColor(underlineInfo.myColor);
// To prevent too long underlines: last char should really be last
underline(g, underlineInfo.getFrom(), underlineInfo.getTo());
}
if (mySpecialUnderlinePlace != null) {
final SpecialErrorPlace place = mySpecialUnderlinePlace.second;
g.setColor(mySpecialUnderlinePlace.first);
final int endPosition = getTextEndPosition();
final int from = (place == SpecialErrorPlace.WHOLE_TEXT ? 0 : endPosition - getColumnWidth());
underline(g, from, endPosition);
}
}
}
@@ -90,17 +75,11 @@ public class SmartTextField extends JTextField {
g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition);
}
/**
* @return place (in px) where entered text ends.
*/
int getTextEndPosition() {
return (getText().length() + 1) * getColumnWidth();
}
/**
* @return place (in px) where caret.
*/
int getTextCursorPosition() {
int getTextCaretPositionInPx() {
return (getCaretPosition() + 1) * getColumnWidth();
}
@@ -139,19 +118,9 @@ public class SmartTextField extends JTextField {
void hideUnderline() {
synchronized (myUnderlineInfo) {
myUnderlineInfo.clear();
mySpecialUnderlinePlace = null;
}
}
/**
* Sets appropriate width in chars
*
* @param widthInChars num of chars
*/
void setPreferredWidthInChars(final int widthInChars) {
setColumns(widthInChars);
}
/**
* Sets appropriate width in pixels
*
@@ -161,18 +130,6 @@ public class SmartTextField extends JTextField {
myPreferredWidth = width;
}
/**
* Display underline in special place
*
* @param color color to underline
* @param specialUnderlinePlace special place to underline
*/
void underlineText(@NotNull final SpecialErrorPlace specialUnderlinePlace,
@NotNull final Color color) {
synchronized (myUnderlineInfo) {
mySpecialUnderlinePlace = Pair.create(color, specialUnderlinePlace);
}
}
/**
* Wrapper to display placeholder
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
import java.awt.event.ActionEvent;
/**
* "Suggestion request" action
*
* @author Ilya.Kazakevich
*/
final class SuggestionKeyStrokeAction extends KeyStrokeAction {
SuggestionKeyStrokeAction() {
super(KeyStrokeInfo.SUGGESTION);
}
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.suggestionRequested();
}
}
@@ -0,0 +1,78 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Command line parse result.
* It consists of command itself and its parts.
* Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or
* {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else.
*
* @author Ilya.Kazakevich
*/
public final class CommandLineParseResult {
@NotNull
private final List<Pair<CommandLinePartType, WordWithPosition>> myParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
@NotNull
private final WordWithPosition myCommand;
CommandLineParseResult(
@NotNull final WordWithPosition command,
@NotNull final Collection<Pair<CommandLinePartType, WordWithPosition>> parts) {
myCommand = command;
myParts.addAll(parts);
}
/**
* @return command (i.e. "startapp" in "startapp my_app")
*/
@NotNull
public WordWithPosition getCommand() {
return myCommand;
}
/**
* @return list of parts in format [part_type, value].
* For example (rm my_folder): [{@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument}, my_folder]
*/
@NotNull
public List<Pair<CommandLinePartType, WordWithPosition>> getParts() {
return Collections.unmodifiableList(myParts);
}
/**
* @return all command line parts with out of part information (just words and positions).
* Note tha command itself is not part, only args and options are
* @see #getParts()
*/
@NotNull
public Collection<WordWithPosition> getPartsNoType() {
final Collection<WordWithPosition> result = new ArrayList<WordWithPosition>();
for (final Pair<CommandLinePartType, WordWithPosition> part : myParts) {
result.add(part.second);
}
return result;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Engine to parse command line. It understands how options and arguments are coded in certain commandline.
* It supportd {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
* command line is command or argument. That helps you to underline or emphisize some parts.
*
* @author Ilya.Kazakevich
*/
public interface CommandLineParser {
/**
* @param commandLineParts command line splitted into words.
* @return command line information
* @throws MalformedCommandLineException in case of bad commandline
*/
@NotNull
CommandLineParseResult parse(@NotNull List<WordWithPosition> commandLineParts) throws MalformedCommandLineException;
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
/**
* Types of command line parts.
*
* @author Ilya.Kazakevich
*/
public enum CommandLinePartType {
/**
* Argument (or positional, or unnamed argument) something that has only value. Like "my_folder" in "rm my_folder"
*/
ARGUMENT,
/**
* Option is named but optional parameter. Like "-l" in "ls -l".
*/
OPTION,
/**
* Some part of command line that {@link com.jetbrains.python.commandLineParser.CommandLineParser} does not understand
*/
UNKNOWN
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.optParse;
package com.jetbrains.python.commandLineParser;
import org.jetbrains.annotations.NotNull;
@@ -13,11 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.optParse;
package com.jetbrains.python.commandLineParser;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
// TODO: Support options and their arguments
@@ -36,23 +41,17 @@ import java.util.*;
*
* @author Ilya.Kazakevich
*/
public final class ParsedCommandLine {
public final class OptParseCommandLineParser implements CommandLineParser {
@NotNull
private final WordWithPosition myCommand;
@NotNull
private final List<WordWithPosition> myArguments = new ArrayList<WordWithPosition>();
/**
* @param commandLine command line to parse
* @throws MalformedCommandLineException if malformed commandline can't be parsed
*/
public ParsedCommandLine(@NotNull final String commandLine) throws MalformedCommandLineException {
final Deque<WordWithPosition> parts = new LinkedList<WordWithPosition>(WordWithPosition.splitText(commandLine));
@Override
public CommandLineParseResult parse(@NotNull final List<WordWithPosition> commandLineParts) throws MalformedCommandLineException {
final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(commandLineParts);
if (parts.isEmpty()) {
throw new MalformedCommandLineException("No command provided");
}
myCommand = parts.pop();
if (myCommand.getText().startsWith("-")) {
final WordWithPosition command = parts.pop();
final List<Pair<CommandLinePartType, WordWithPosition>> resultParts = new ArrayList<Pair<CommandLinePartType, WordWithPosition>>();
if (command.getText().startsWith("-")) {
throw new MalformedCommandLineException("Command can't start with option prefix");
}
@@ -60,46 +59,13 @@ public final class ParsedCommandLine {
for (final WordWithPosition part : parts) {
if (part.getText().startsWith("-")) {
// This is option!
resultParts.add(Pair.create(CommandLinePartType.OPTION, part));
}
else {
// TODO: Check optopn argument!
myArguments.add(part);
resultParts.add(Pair.create(CommandLinePartType.ARGUMENT, part));
}
}
return new CommandLineParseResult(command, resultParts);
}
/**
* @return command (i.e. "startapp" in "startapp my_app")
*/
@NotNull
public WordWithPosition getCommand() {
return myCommand;
}
/**
* @return all arguments (not options or option arguments!)
*/
@NotNull
public List<WordWithPosition> getArguments() {
return Collections.unmodifiableList(myArguments);
}
/**
* @return all parts for command line as simple words
*/
@NotNull
public List<String> getAsWords() {
final List<String> result = new ArrayList<String>();
result.add(myCommand.getText());
for (final WordWithPosition argument : myArguments) {
result.add(argument.getText());
}
// TODO: Add options as well
return result;
}
// TODO: Add options, arguments and option arguments
}
@@ -0,0 +1,30 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Engine to parse command-line.
* Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT arguments}
* and {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION options}.
* Use need to split command line into {@link com.jetbrains.python.WordWithPosition chunks}, pass them to
* {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLineParseResult}.
*
* Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
* command line is command or argument. That helps you to underline or emphisize some parts.
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandLineParser;
@@ -16,12 +16,12 @@
package com.jetbrains.python.formatter;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor;
@@ -31,6 +31,9 @@ import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyRecursiveElementVisitor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @author traff
*/
@@ -58,6 +61,7 @@ public class PyPreFormatProcessor implements PreFormatProcessor {
private final Project myProject;
private final CodeStyleSettings mySettings;
private final PyCodeStyleSettings myPyCodeStyleSettings;
private final List<Couple<PsiComment>> myCommentReplacements = new ArrayList<Couple<PsiComment>>();
private TextRange myRange;
private int myDelta = 0;
@@ -72,34 +76,62 @@ public class PyPreFormatProcessor implements PreFormatProcessor {
return range;
}
myRange = range;
element.accept(this);
final PsiDocumentManager manager = PsiDocumentManager.getInstance(myProject);
final Document document = manager.getDocument(element.getContainingFile());
if (document != null) {
manager.doPostponedOperationsAndUnblockDocument(document);
try {
// collect all comments
element.accept(this);
for (Couple<PsiComment> pair : myCommentReplacements) {
pair.getFirst().replace(pair.getSecond());
}
}
finally {
manager.commitDocument(document);
}
}
return TextRange.create(range.getStartOffset(), range.getEndOffset() + myDelta);
}
@Override
public void visitComment(PsiComment element) {
if (!myRange.contains(element.getTextRange())) {
public void visitComment(PsiComment comment) {
if (!myRange.contains(comment.getTextRange())) {
return;
}
String text = element.getText();
int commentStart = text.indexOf('#');
if (commentStart != -1 && (commentStart + 1) < text.length()) {
char charAfterDash = text.charAt(commentStart + 1);
if (charAfterDash == '!' && element.getTextRange().getStartOffset() == 0) {
return; //shebang
final String origText = comment.getText();
final int commentStart = origText.indexOf('#');
if (commentStart != -1 && (commentStart + 1) < origText.length()) {
final char charAfterDash = origText.charAt(commentStart + 1);
if (charAfterDash == '!' && comment.getTextRange().getStartOffset() == 0) {
return; // shebang
}
if (charAfterDash == '#' || charAfterDash == ':') {
return; // doc comment
}
final String commentTextWithoutDash = origText.substring(commentStart + 1);
final String newText;
if (isTrailingComment(comment)) {
newText = "# " + StringUtil.trimLeading(commentTextWithoutDash);
}
else if (!StringUtil.isWhiteSpace(charAfterDash)) {
newText = "# " + commentTextWithoutDash;
}
else {
return;
}
String commentText = StringUtil.trimLeading(text.substring(commentStart + 1));
String newText = "# " + commentText;
if (!newText.equals(text)) {
myDelta += newText.length() - text.length();
element.replace(
PyElementGenerator.getInstance(myProject).createFromText(LanguageLevel.getDefault(), PsiComment.class, newText));
if (!newText.equals(origText)) {
myDelta += newText.length() - origText.length();
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(myProject);
final PsiComment newComment = elementGenerator.createFromText(LanguageLevel.forElement(comment), PsiComment.class, newText);
myCommentReplacements.add(Couple.of(comment, newComment));
}
}
}
}
private static boolean isTrailingComment(@NotNull PsiComment comment) {
final PsiElement prevElement = comment.getPrevSibling();
return prevElement != null && (!(prevElement instanceof PsiWhiteSpace) || !prevElement.textContains('\n'));
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* <p>
* An <a href="https://docs.python.org/2/library/optparse.html">optparse</a> module twin, that parses command line.
* Unlike any other GNU/Posix parsers, it knows how to:</p>
* <ol>
* <li>Parse options and args with out of any knowledge about required args</li>
* <li>Provide actual places in command line where exactly such args or opts exist./li>
* </ol>
* <p>
* Be sure to read optparse manual
* (epecially <a href="https://docs.python.org/2/library/optparse.html#terminology">terminology</a>) part.
* </p>
* <p>Package entry point is {@link com.jetbrains.python.optParse.ParsedCommandLine}</p>
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.optParse;
@@ -166,7 +166,14 @@ public class SuggestionList {
return null; // Nothing is selected if list is invisible
}
final Object value = myList.getSelectedValue();
return ((value == null) ? "" : getElement(value).mySuggestion.getText());
return ((value == null) ? null : getElement(value).mySuggestion.getText());
}
/**
* @return true if no suggestion list is displayed now.
*/
public final synchronized boolean isClosed() {
return myListPopUp == null || myListPopUp.isDisposed();
}
/**
@@ -0,0 +1,6 @@
# This normal comment won't be touched as well
# for a in [1, 2, 3]:
# if a == 1:
# print 'x'
# elif a == 2:
# print 'y'
@@ -0,0 +1,6 @@
# This normal comment won't be touched as well
# for a in [1, 2, 3]:
# if a == 1:
# print 'x'
# elif a == 2:
# print 'y'
@@ -0,0 +1,4 @@
#: :type: dict of (str, C)
d = {'foo': C()}
#: :type: list of str
xs = ['bar']
@@ -0,0 +1,4 @@
#: :type: dict of (str, C)
d = {'foo': C()}
#: :type: list of str
xs = ['bar']
@@ -0,0 +1,6 @@
###########
##
## MANUAL
##
print('Spam')

Some files were not shown because too many files have changed in this diff Show More