diff --git a/build/conf/classVersions.txt b/build/conf/classVersions.txt index 7ca31d1d871c..377431bd105c 100644 --- a/build/conf/classVersions.txt +++ b/build/conf/classVersions.txt @@ -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 \ No newline at end of file diff --git a/build/scripts/dist.gant b/build/scripts/dist.gant index 59e7c9a0beb0..ad1031309fb5 100644 --- a/build/scripts/dist.gant +++ b/build/scripts/dist.gant @@ -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]) } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java index 46175c5d55a5..ebd364b26965 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/AnnotationsHighlightUtil.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java index 0e35626a89fd..a457768abeac 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/GenericsHighlightUtil.java @@ -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(), resolveScope, factory); if (notAccessibleErrorMessage != null) { return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .descriptionAndTooltip(notAccessibleErrorMessage) - .range(HighlightNamesUtil.getClassDeclarationTextRange(aClass)) + .range(range) .create(); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index 383d6292ecdf..c08d7d988ffa 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -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 diff --git a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java index 91b40d12a837..5f0ee4f20e85 100644 --- a/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java +++ b/java/java-impl/src/com/intellij/application/options/CodeStyleGenerationConfigurable.java @@ -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() { diff --git a/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java index 7a3ec7efc825..85bbd0fe4436 100644 --- a/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java +++ b/java/java-impl/src/com/intellij/refactoring/safeDelete/SafeDeleteJavaCallerChooser.java @@ -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 ref = new Ref(false); if (ReferencesSearch.search(parameter, new LocalSearchScope(scope)).forEach(new Processor() { @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; diff --git a/java/java-impl/src/com/intellij/refactoring/ui/JavaVisibilityPanel.java b/java/java-impl/src/com/intellij/refactoring/ui/JavaVisibilityPanel.java index 414c44212367..641e6d92d9d9 100644 --- a/java/java-impl/src/com/intellij/refactoring/ui/JavaVisibilityPanel.java +++ b/java/java-impl/src/com/intellij/refactoring/ui/JavaVisibilityPanel.java @@ -46,7 +46,13 @@ public class JavaVisibilityPanel extends VisibilityPanelBase { 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, diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiAnnotationImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiAnnotationImpl.java index bdc3a2f1003b..0bf175361425 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiAnnotationImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiAnnotationImpl.java @@ -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 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); } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/annotations/typeAnnotations.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/annotations/typeAnnotations.java index 01f7486ea89a..c1fbcaf07cd6 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/annotations/typeAnnotations.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/annotations/typeAnnotations.java @@ -59,6 +59,7 @@ class Outer { { new @TA Object(); new @TA ArrayList(); + new @TA Runnable() { public void run() { } }.run(); ArrayList var = new @TA ArrayList(); new @TA Outer().new @TA Middle(); diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java7/p/DeclaredTypeOfVariableImplementsUnknownType.java b/java/java-tests/testData/codeInsight/multipleJdks/java7/p/DeclaredTypeOfVariableImplementsUnknownType.java new file mode 100644 index 000000000000..0622320a9c9e --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java7/p/DeclaredTypeOfVariableImplementsUnknownType.java @@ -0,0 +1,6 @@ +package p; +abstract class B { + void f(A a) { + a.foo(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java7/p/NoOverriding.java b/java/java-tests/testData/codeInsight/multipleJdks/java7/p/NoOverriding.java new file mode 100644 index 000000000000..219a93539fc9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java7/p/NoOverriding.java @@ -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 { + @Override + public String getOrDefault(Object key, String defaultValue) { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java8/p/DeclaredTypeOfVariableImplementsUnknownType.java b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/DeclaredTypeOfVariableImplementsUnknownType.java new file mode 100644 index 000000000000..07e72962683a --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/DeclaredTypeOfVariableImplementsUnknownType.java @@ -0,0 +1,7 @@ +package p; +import java.util.stream.Stream; +import java.util.List; + +public abstract class A implements List> { + public void foo() {} +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/multipleJdks/java8/p/NoOverriding.java b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/NoOverriding.java new file mode 100644 index 000000000000..477a992f88cc --- /dev/null +++ b/java/java-tests/testData/codeInsight/multipleJdks/java8/p/NoOverriding.java @@ -0,0 +1,5 @@ +package p; +import java.util.Map; + +public abstract class A implements Map {} + diff --git a/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression.java b/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression.java new file mode 100644 index 000000000000..2d8c46d19ae2 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression.java @@ -0,0 +1,7 @@ +class Test { + void foo(String s, String p) {} + + void bar(String s, String p) { + foo(s, s.substring(0)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression_after.java b/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression_after.java new file mode 100644 index 000000000000..4c649c4dc527 --- /dev/null +++ b/java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression_after.java @@ -0,0 +1,7 @@ +class Test { + void foo(String p) {} + + void bar(String s, String p) { + foo(s.substring(0)); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java index 4167b01e2bf2..6d536928038b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java @@ -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"); diff --git a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java index 6d24883fad47..5cf1fc5876df 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/SafeDeleteTest.java @@ -92,6 +92,10 @@ public class SafeDeleteTest extends MultiFileTestCase { doSingleFileTest(); } + public void testNoDeepDeleteParameterUsedInNextArgumentExpression() throws Exception { + doSingleFileTest(); + } + public void testToDeepDeleteParameterOverriders() throws Exception { doSingleFileTest(); } diff --git a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java index b175aea4046e..9fdc4c29cb0e 100644 --- a/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java +++ b/java/structuralsearch-java/src/com/intellij/structuralsearch/impl/matcher/JavaMatchingVisitor.java @@ -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(); } diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiChannelHandler.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java similarity index 97% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiChannelHandler.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java index 42accb022188..977d33e641da 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiChannelHandler.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiChannelHandler.java @@ -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 { diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiConstants.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiConstants.java similarity index 71% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiConstants.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiConstants.java index df41bda38987..23a85dead1f9 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiConstants.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiConstants.java @@ -1,4 +1,4 @@ -package org.jetbrains.fastCgi; +package org.jetbrains.io.fastCgi; public final class FastCgiConstants { public static final int HEADER_LENGTH = 8; diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiDecoder.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java similarity index 97% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiDecoder.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java index 57b2326477e8..cb48cc18c185 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiDecoder.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiDecoder.java @@ -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 { diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiRequest.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.java similarity index 99% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiRequest.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.java index 4ac48e273204..e92d20eebffc 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiRequest.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiRequest.java @@ -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; diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiResponse.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiResponse.java similarity index 90% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiResponse.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiResponse.java index dbad397489c4..e249f7152c7c 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiResponse.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiResponse.java @@ -1,4 +1,4 @@ -package org.jetbrains.fastCgi; +package org.jetbrains.io.fastCgi; import io.netty.buffer.ByteBuf; diff --git a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiService.java b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.java similarity index 99% rename from platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiService.java rename to platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.java index aef0c3f0770e..8849e6cb7a76 100644 --- a/platform/built-in-server/src/org/jetbrains/fastCgi/FastCgiService.java +++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.java @@ -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; diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java index a45b2deb4b86..9f134812ae2e 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java @@ -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> POINTERS_KEY; + private static final Object lock = new Object(); + private static final ReferenceQueue ourQueue = new ReferenceQueue(); + private final Key> POINTERS_KEY; private final Key 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 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 pointers = getPointers(file); - if (pointers == null) return; + List 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 strongPointers = ((UnsafeWeakList)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 pointers = getPointers(file); - if (pointers == null) return; + List 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 strongPointers = ((UnsafeWeakList)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 pointer = getCachedPointer(element); if (pointer != null) { containingFile = containingFile == null ? element.getContainingFile() : containingFile; @@ -197,12 +208,12 @@ public class SmartPointerManagerImpl extends SmartPointerManager { private void initPointer(@NotNull SmartPointerEx pointer, @NotNull VirtualFile containingFile) { synchronized (lock) { - List pointers = getPointers(containingFile); + Set pointers = getPointers(containingFile); if (pointers == null) { - pointers = new UnsafeWeakList(); // 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 pointers = getPointers(containingFile.getViewProvider().getVirtualFile()); + Set pointers = getPointers(containingFile.getViewProvider().getVirtualFile()); if (pointers == null) return false; SmartPointerElementInfo info = ((SmartPsiElementPointerImpl)pointer).getElementInfo(); info.cleanup(); - return pointers.remove(pointer); + + for (Iterator iterator = pointers.iterator(); iterator.hasNext(); ) { + if (pointer.equals(iterator.next().get())) { + iterator.remove(); + return true; + } + } + return false; } } } return false; } - private List getPointers(@NotNull VirtualFile containingFile) { + @Nullable + private Set getPointers(@NotNull VirtualFile containingFile) { return containingFile.getUserData(POINTERS_KEY); } + @NotNull + private List getStrongPointers(@NotNull VirtualFile containingFile) { + Set refs = getPointers(containingFile); + if (refs == null) return Collections.emptyList(); + + List result = ContainerUtil.newArrayList(); + for (PointerReference reference : refs) { + ContainerUtil.addIfNotNull(result, reference.get()); + } + return result; + } + @TestOnly public int getPointersNumber(@NotNull PsiFile containingFile) { synchronized (lock) { - List 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 { + private final VirtualFile file; + private final Key> key; + + public PointerReference(SmartPointerEx pointer, + VirtualFile containingFile, + ReferenceQueue queue, + Key> key) { + super(pointer, queue); + file = containingFile; + this.key = key; + } + } + } diff --git a/platform/icons/src/diff/arrow@2x_dark.png b/platform/icons/src/diff/arrow@2x_dark.png index db6d35cadcd5..5f3b65f83663 100644 Binary files a/platform/icons/src/diff/arrow@2x_dark.png and b/platform/icons/src/diff/arrow@2x_dark.png differ diff --git a/platform/icons/src/diff/arrowLeftDown@2x_dark.png b/platform/icons/src/diff/arrowLeftDown@2x_dark.png new file mode 100644 index 000000000000..f7d0791c2f1b Binary files /dev/null and b/platform/icons/src/diff/arrowLeftDown@2x_dark.png differ diff --git a/platform/icons/src/diff/arrowLeftDown_dark.png b/platform/icons/src/diff/arrowLeftDown_dark.png new file mode 100644 index 000000000000..e54c525c98e6 Binary files /dev/null and b/platform/icons/src/diff/arrowLeftDown_dark.png differ diff --git a/platform/icons/src/diff/arrowRight@2x_dark.png b/platform/icons/src/diff/arrowRight@2x_dark.png new file mode 100644 index 000000000000..7c83eae20f87 Binary files /dev/null and b/platform/icons/src/diff/arrowRight@2x_dark.png differ diff --git a/platform/icons/src/diff/arrowRightDown@2x_dark.png b/platform/icons/src/diff/arrowRightDown@2x_dark.png new file mode 100644 index 000000000000..9c0c3a5b6d1d Binary files /dev/null and b/platform/icons/src/diff/arrowRightDown@2x_dark.png differ diff --git a/platform/icons/src/diff/arrowRightDown_dark.png b/platform/icons/src/diff/arrowRightDown_dark.png new file mode 100644 index 000000000000..6df2507058f8 Binary files /dev/null and b/platform/icons/src/diff/arrowRightDown_dark.png differ diff --git a/platform/icons/src/diff/arrowRight_dark.png b/platform/icons/src/diff/arrowRight_dark.png new file mode 100644 index 000000000000..6d1d8e054343 Binary files /dev/null and b/platform/icons/src/diff/arrowRight_dark.png differ diff --git a/platform/icons/src/diff/arrow_dark.png b/platform/icons/src/diff/arrow_dark.png index f6ee4dca52e6..7bebe9b18758 100644 Binary files a/platform/icons/src/diff/arrow_dark.png and b/platform/icons/src/diff/arrow_dark.png differ diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java index 749a0038e048..7ceacd2eddf9 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/WrappingAndBracesPanel.java @@ -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); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/CtrlMouseHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/CtrlMouseHandler.java index 44368e996ca4..1d95b055d70b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/CtrlMouseHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/CtrlMouseHandler.java @@ -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; } diff --git a/platform/lang-impl/src/com/intellij/execution/console/RunIdeConsoleAction.java b/platform/lang-impl/src/com/intellij/execution/console/RunIdeConsoleAction.java index 0f1e722acb08..723a9b690ab7 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/RunIdeConsoleAction.java +++ b/platform/lang-impl/src/com/intellij/execution/console/RunIdeConsoleAction.java @@ -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> DESCRIPTOR_KEY = Key.create("DESCRIPTOR_KEY"); private static final Key HISTORY_CONTROLLER_KEY = Key.create("HISTORY_CONTROLLER_KEY"); + private static final Logger LOG = Logger.getInstance(RunIdeConsoleAction.class); static class Engines { - static final Map ourEngines = ContainerUtil.newLinkedHashMap(); - static { - for (ScriptEngineFactory factory : new ScriptEngineManager().getEngineFactories()) { - ourEngines.put(factory.getLanguageName(), factory); - } + static final List ourEngines = new CopyOnWriteArrayList(); + 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() { + ContainerUtil.map(Engines.ourEngines, new NotNullFunction() { @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) { diff --git a/platform/lang-impl/src/com/intellij/ide/scratch/NewScratchFileAction.java b/platform/lang-impl/src/com/intellij/ide/scratch/NewScratchFileAction.java index 05a0a7bf5996..57c4b14ddaa1 100644 --- a/platform/lang-impl/src/com/intellij/ide/scratch/NewScratchFileAction.java +++ b/platform/lang-impl/src/com/intellij/ide/scratch/NewScratchFileAction.java @@ -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)); diff --git a/platform/lang-impl/src/com/intellij/openapi/editor/actions/NamedElementDuplicateHandler.java b/platform/lang-impl/src/com/intellij/openapi/editor/actions/NamedElementDuplicateHandler.java index ae12790519dc..baeb0d5bed09 100644 --- a/platform/lang-impl/src/com/intellij/openapi/editor/actions/NamedElementDuplicateHandler.java +++ b/platform/lang-impl/src/com/intellij/openapi/editor/actions/NamedElementDuplicateHandler.java @@ -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(); diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java index 2f6c6dfd2ec5..b9a61567620d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapManagerImpl.java @@ -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 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 diff --git a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java index f56fe49f19f2..fcb584bf709f 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java @@ -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; } }; diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index e74333c58c50..df4aa168e8fa 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -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 diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 187ff0ae6ae5..57275fd67331 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -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 diff --git a/platform/platform-resources-en/src/messages/RefactoringBundle.properties b/platform/platform-resources-en/src/messages/RefactoringBundle.properties index 2b63d9ab4638..b3e85fae875f 100644 --- a/platform/platform-resources-en/src/messages/RefactoringBundle.properties +++ b/platform/platform-resources-en/src/messages/RefactoringBundle.properties @@ -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: diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index eb649da603af..9db13bc456b1 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -342,7 +342,7 @@ - + diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GrListOrMapInfo.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GrListOrMapInfo.java index 1b4a0f2f5379..2918126327b9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GrListOrMapInfo.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GrListOrMapInfo.java @@ -119,7 +119,7 @@ public class GrListOrMapInfo implements ConstructorCallInfo { 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}; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/findUsages/LiteralConstructorReference.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/findUsages/LiteralConstructorReference.java index fd4b090e6940..315488a085b1 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/findUsages/LiteralConstructorReference.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/findUsages/LiteralConstructorReference.java @@ -208,7 +208,7 @@ public class LiteralConstructorReference extends PsiReferenceBase.Poly { /** * 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 { 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 range) { + this(word, range.getFrom(), range.getTo()); + } + @NotNull public String getText() { return myWord; @@ -121,7 +131,7 @@ public final class WordWithPosition extends Range { * @return parse result */ @NotNull - static List splitText(@NotNull final String text) { + public static List splitText(@NotNull final String text) { // TODO: Rewrite using regex or scanner? int position = 0; int wordStart = -1; diff --git a/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java b/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java index 79eb731c4481..1eeecac550e5 100644 --- a/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java +++ b/python/src/com/jetbrains/python/commandInterface/CommandInterfacePresenter.java @@ -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(); } diff --git a/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java b/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java index 3377431712c3..317d4caeed49 100644 --- a/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java +++ b/python/src/com/jetbrains/python/commandInterface/CommandInterfaceView.java @@ -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 AFTER_LAST_CHARACTER_RANGE = new Range(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 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: + *
    + *
  1. 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
  2. + *
  3. Default text: In all other cases view displays default text (if available).
  4. + *
+ *

+ * 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> 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 infoBalloons, @NotNull final Collection 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. - * Caution: Each call removes previuos balloons! + * 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 balloons); + void insertTextAfterCaret(@NotNull String text); /** - * @return true if current caret position is on the word (no on whitespace) + * Replaces current text with another one. + * + * @param from from + * @param to to + * @param newText text to replace */ - boolean isCaretOnWord(); - + void replaceText(final int from, final int to, @NotNull String newText); /** - * Special place that may be underlined + * @return position of caret (in chars) */ - enum SpecialErrorPlace { - /** - * Whole text (from start to end) - */ - WHOLE_TEXT, - /** - * Only after last character - */ - AFTER_LAST_CHAR - } + int getCaretPosition(); } diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java new file mode 100644 index 000000000000..92a6910fdd5a --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java @@ -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 { + @Nullable + private final WordWithPosition myChunk; + @NotNull + private final ChunkInfo myChunkInfo; + + ChunkAndInfo(@Nullable final WordWithPosition chunk, @NotNull final ChunkInfo chunkInfo) { + myChunk = chunk; + myChunkInfo = chunkInfo; + } + + /** + * @return chunk (word). may be null + */ + @Nullable + WordWithPosition getChunk() { + return myChunk; + } + + /** + * @return chunk info. + */ + @NotNull + ChunkInfo getChunkInfo() { + return myChunkInfo; + } + + @Override + public int compareTo(@NotNull final ChunkAndInfo o) { + if (myChunk == null && o.myChunk == null) { + return 0; + } + if (myChunk == null) { + return 1; + } + if (o.myChunk == null) { + return -1; + } + return myChunk.getFrom().compareTo(o.myChunk.getFrom()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ChunkAndInfo info = (ChunkAndInfo)o; + + if (myChunk != null ? !myChunk.equals(info.myChunk) : info.myChunk != null) return false; + + return true; + } + + @Override + public int hashCode() { + return myChunk != null ? myChunk.hashCode() : 0; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java new file mode 100644 index 000000000000..12740a530052 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java @@ -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 always 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 chunks); +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java new file mode 100644 index 000000000000..946b2718d20c --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java @@ -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 myChunkAndInfos = new TreeSet(); + @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 chunks = WordWithPosition.splitText(myView.getText()); + final ParseInfo parseInfo = myChunkDriver.parse(chunks); + myExecutor = parseInfo.getExecutor(); + final List 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 infoBalloons = new ArrayList(); + final Collection errorBalloons = new ArrayList(); + + + for (final ChunkAndInfo chunkInfoPair : myChunkAndInfos) { + final ChunkInfo chunkInfo = chunkInfoPair.getChunkInfo(); + Range 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> placesWhereSuggestionAvailable = new ArrayList>(); + for (final ChunkAndInfo chunkAndInfo : myChunkAndInfos) { + Range 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 suggestions = new ArrayList(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 suggestions, @NotNull final String textToMatch) { + // TODO: use guava instead? + final Iterator 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 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 suggestions = new ArrayList(suggestionInfo.getSuggestions()); + if (chunk != null) { + filterLeaveOnlyMatching(suggestions, chunk.getText()); + } + if (suggestions.size() == 1) { + // Exclusive! + if (chunk != null) { + myView.replaceText(chunk.getFrom(), chunk.getTo(), suggestions.get(0)); + } + else { + myView.insertTextAfterCaret(suggestions.get(0)); + } + } + } + + @Override + public void executionRequested() { + if (myExecutor == null) { + // TODO: Display error somehow + } + else { + myExecutor.run(); + } + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java new file mode 100644 index 000000000000..4397751ed011 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java @@ -0,0 +1,81 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter; + +import org.jetbrains.annotations.Nullable; + +/** + * Information about certain place in text, provided by chunk driver. + * + * @author Ilya.Kazakevich + */ +public final class ChunkInfo { + @Nullable + private final String myInfoBalloon; + @Nullable + private final String myError; + @Nullable + private final SuggestionInfo mySuggestions; + + + public ChunkInfo(@Nullable final String infoBalloon, + @Nullable final String error) { + this(infoBalloon, error, null); + } + + + /** + * + * @param infoBalloon Info balloon to display when caret meets this place (null if display nothing) + * @param error Error balloon to display when caret meets this place and underline text as error (null if no error) + * @param suggestions list of suggestions available in this place (if any) + */ + public ChunkInfo(@Nullable final String infoBalloon, + @Nullable final String error, + @Nullable final SuggestionInfo suggestions) { + myInfoBalloon = infoBalloon; + myError = error; + mySuggestions = suggestions; + } + + + /** + * + * @return Info balloon to display when caret meets this place (null if display nothing) + */ + @Nullable + public String getInfoBalloon() { + return myInfoBalloon; + } + + /** + * + * @return Error balloon to display when caret meets this place and underline text as error (null if no error) + */ + @Nullable + public String getError() { + return myError; + } + + /** + * + * @return list of suggestions available in this place (if any) + */ + @Nullable + public SuggestionInfo getSuggestions() { + return mySuggestions; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java new file mode 100644 index 000000000000..005c4dae6150 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java @@ -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 myChunkInfo = new ArrayList(); + @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, + @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) { + 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 getChunkInfo() { + return Collections.unmodifiableList(myChunkInfo); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java new file mode 100644 index 000000000000..59bea9abd12f --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java @@ -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 mySuggestions = new ArrayList(); + 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 suggestions) { + myShowSuggestionsAutomatically = showSuggestionsAutomatically; + myShowAbsolute = showAbsolute; + mySuggestions.addAll(suggestions); + } + + /** + * @return List of suggestions to display + */ + @NotNull + public List getSuggestions() { + return Collections.unmodifiableList(mySuggestions); + } + + /** + * @return true it suggestions should be displayed even if user did not ask for that + */ + public boolean isShowSuggestionsAutomatically() { + return myShowSuggestionsAutomatically; + } + + /** + * @return show suggestions at the absolute position (not relative to caret). + */ + public boolean isShowAbsolute() { + return myShowAbsolute; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java new file mode 100644 index 000000000000..9281bc7f630c --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java @@ -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 chunk + * and {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver}. + * This presenter explodes command line into several parts or chunks. + * Chunks then passed to {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver driver} and it returns + * all information it has about each chunk. Presenter uses this information to display chunks correctly using view. + * To use this package, {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} should be implemented. + * + * See {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriverBasedPresenter} as entry point + * + * + * @author Ilya.Kazakevich + */ +package com.jetbrains.python.commandInterface.chunkDriverBasedPresenter; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java similarity index 96% rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java index 5826a45a4dd4..fc3f64192c17 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Argument.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Argument.java @@ -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; diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java similarity index 51% rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java index 594b7ac37a70..ad43da45adcd 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/ArgumentsInfo.java @@ -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 values. - * Values should be provided as list. I.e. for

my_command foo bar
there should be list of "foo, bar". - * - * @param argumentValuesToCheck values to check - * @return validation result - */ - @NotNull - ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List argumentValuesToCheck); + Pair getArgument(int argumentPosition); } diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java similarity index 69% rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java index fb139df02ca2..694021964fea 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/Command.java @@ -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); } diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java new file mode 100644 index 000000000000..02fd1d3194c0 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java @@ -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 myCommands = new TreeMap(); // 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 commands) { + myCommandLineParser = commandLineParser; + for (final Command command : commands) { + myCommands.put(command.getName(), command); + } + myModule = module; + } + + @Override + @NotNull + public ParseInfo parse(@NotNull final List 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 = new ArrayList(); + // 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 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 availableValues = argument.getAvailableValues(); + final Pair 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 nextArgumentPair = commandArgumentsInfo.getArgument(commandLine.getParts().size()); + if (nextArgumentPair != null) { + // Next arg exists + final Argument nextArgument = nextArgumentPair.getSecond(); + final List 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 result = new ArrayList(); + // We know that first chunk command line, but we can't say anything about outher chunks except they are bad. + // How ever, we must say something according to contract (number of infos should be equal or greater than number of chunks) + result + .add(new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()))); + for (int i = 1; i < numberOfChunks; i++) { + result.add( + new ChunkInfo(null, PyBundle.message("commandLine.validation.badCommand"))); + } + + return new ParseInfo(result); + } + + + /** + * Adapter that executes command using {@link Command#execute(com.intellij.openapi.module.Module, com.jetbrains.python.commandLineParser.CommandLineParseResult)} + */ + private class MyExecutor implements Runnable { + @NotNull + private final Command myCommand; + @NotNull + private final CommandLineParseResult myCommandLine; + + MyExecutor(@NotNull final Command command, @NotNull final CommandLineParseResult line) { + myCommand = command; + myCommandLine = line; + } + + @Override + public void run() { + myCommand.execute(myModule, myCommandLine); + } + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/KnownArgumentsInfo.java similarity index 73% rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/KnownArgumentsInfo.java index 2e8762d10001..4c3a50295f78 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/KnownArgumentsInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/KnownArgumentsInfo.java @@ -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 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 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 argumentValuesToCheck) { - final Map errors = new HashMap(); - - 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 availableValues = argument.getAvailableValues(); - if (availableValues != null && !availableValues.contains(userValue)) { - errors.put(i, ArgumentValueError.BAD_VALUE); - } - } - - return new ArgumentsValuesValidationInfo(errors, argumentValuesToCheck.size() < myMinArguments); - } } diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java new file mode 100644 index 000000000000..9aaa0d5503b2 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java @@ -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 getArgument(final int argumentPosition) { + return null; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/UnknownArgumentsInfo.java similarity index 72% rename from python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java rename to python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/UnknownArgumentsInfo.java index 3199c3431d15..04889e4109c9 100644 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/UnknownArgumentsInfo.java +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/UnknownArgumentsInfo.java @@ -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 argumentValuesToCheck) { - return ArgumentsValuesValidationInfo.NO_ERROR; // Actually, we have no idea + public Pair getArgument(final int argumentPosition) { + return Pair.create(false, new Argument(myHelp)); } } diff --git a/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java new file mode 100644 index 000000000000..cc72ae21e259 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * {@link com.jetbrains.python.commandInterface.chunkDriverBasedPresenter.ChunkDriver} implementation based on idea of + * {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Command command} and its {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.Argument arguments}. + * + * See {@link com.jetbrains.python.commandInterface.commandBasedChunkDriver.CommandBasedChunkDriver} as entry point. + * It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments + * provided by user + * + * @author Ilya.Kazakevich + */ +package com.jetbrains.python.commandInterface.commandBasedChunkDriver; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java deleted file mode 100644 index 239074741896..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.util.containers.hash.HashMap; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.Map; - -/** - * Information about {@link com.jetbrains.python.commandInterface.commandsWithArgs.Argument arguments} values validation - * - * @author Ilya.Kazakevich - */ -public final class ArgumentsValuesValidationInfo { - /** - * Validation with out of any error - */ - @NotNull - static final ArgumentsValuesValidationInfo - NO_ERROR = new ArgumentsValuesValidationInfo(Collections.emptyMap(), false); - - private final Map myPositionOfErrorArguments = new HashMap(); - 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 positionOfErrorArguments, - final boolean notEnoughArguments) { - myPositionOfErrorArguments.putAll(positionOfErrorArguments); - myNotEnoughArguments = notEnoughArguments; - } - - /** - * @return map of [argument_position, its_value_error] - */ - @NotNull - Map getPositionOfErrorArguments() { - return Collections.unmodifiableMap(myPositionOfErrorArguments); - } - - /** - * @return if not enough argument values provided (i.e. some required arg missed) - */ - boolean isNotEnoughArguments() { - return myNotEnoughArguments; - } - - /** - * Type of argument value error. - */ - enum ArgumentValueError { - /** - * This argument is redundant - */ - EXCESS, - /** - * Argument has bad value - */ - BAD_VALUE - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java deleted file mode 100644 index 51d0a42942f0..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Simple command implementation - * - * @author Ilya.Kazakevich - */ -public class CommandAdapter implements Command { - @NotNull - private final String myName; - @Nullable - private final String myHelp; - @NotNull - private final ArgumentsInfo myArgumentsInfo; - - /** - * @param help help text - * @param name command name - * @param argumentsInfo arguments info - */ - public CommandAdapter(@NotNull final String name, @Nullable final String help, @NotNull ArgumentsInfo argumentsInfo) { - myName = name; - myHelp = help; - myArgumentsInfo = argumentsInfo; - } - - /** - * @return command name - */ - @Override - @NotNull - public final String getName() { - return myName; - } - - - @Override - @Nullable - public final String getHelp() { - return myHelp; - } - - - @NotNull - @Override - public final ArgumentsInfo getArgumentsInfo() { - return myArgumentsInfo; - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java deleted file mode 100644 index b7e533b7da67..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ArrayUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -/** - * Information about command and its arguments to save in history / pass to execution etc. - * - * @author Ilya.Kazakevich - */ -public class CommandExecutionInfo { - /** - * Command and arguments separator. - */ - private static final String SEPARATOR = " "; - @NotNull - private final String myCommandName; - @NotNull - private final String[] myArguments; - - /** - * @param commandName command - * @param arguments its arguments - */ - public CommandExecutionInfo(@NotNull final String commandName, @NotNull final String... arguments) { - myCommandName = commandName; - myArguments = arguments.clone(); - } - - /** - * @return command - */ - @NotNull - public String getCommandName() { - return myCommandName; - } - - /** - * @return command arguments - */ - @NotNull - public String[] getArguments() { - return myArguments.clone(); - } - - /** - * @return command in format "command arg1 arg2". Opposite to {@link #fromString(String)} - * @see #fromString(String) - */ - @NotNull - public String toString() { - // TODO: What if command or argument has space in it? Escape somehow! - return StringUtil.join(ArrayUtil.mergeArrays(new String[]{myCommandName}, myArguments), SEPARATOR); - } - - /** - * @param stringToUnserialize string created by {@link #toString()} - * @return command parsed from string - * @see #toString() - */ - @Nullable - public static CommandExecutionInfo fromString(@NotNull final String stringToUnserialize) { - // TODO: What if command or argument has space in it? Escape somehow! - final List strings = StringUtil.split(stringToUnserialize, SEPARATOR); - if (strings.isEmpty()) { - return null; - } - return new CommandExecutionInfo(strings.get(0), ArrayUtil.toStringArray(strings.subList(1, strings.size()))); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof CommandExecutionInfo)) return false; - - CommandExecutionInfo info = (CommandExecutionInfo)o; - - if (!Arrays.equals(myArguments, info.myArguments)) return false; - if (!myCommandName.equals(info.myCommandName)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myCommandName.hashCode(); - result = 31 * result + Arrays.hashCode(myArguments); - return result; - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java deleted file mode 100644 index 0090a9e13d65..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.ArrayUtil; -import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter; -import com.jetbrains.python.commandInterface.CommandInterfaceView; -import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace; -import com.jetbrains.python.optParse.MalformedCommandLineException; -import com.jetbrains.python.optParse.ParsedCommandLine; -import com.jetbrains.python.optParse.WordWithPosition; -import com.jetbrains.python.suggestionList.SuggestionsBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * Command-line interface presenter that is command-based - * - * @param Command type - * @author Ilya.Kazakevich - */ -public class CommandInterfacePresenterCommandBased extends CommandInterfacePresenterAdapter { - /** - * [name] -> command. Linked is used to preserve order. - */ - private final Map myCommands = new LinkedHashMap(); - /** - * 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 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> errorInfo = myStrategy.getErrorInfo(); - myView.showErrors(errorInfo.getSecond(), errorInfo.first); - myView.setBalloons(myStrategy.getBalloonsToShow()); - - final SuggestionInfo suggestionInfo = myStrategy.getSuggestionInfo(); - final List suggestions = new ArrayList(suggestionInfo.getSuggestions()); - - final String lastPart = getLastPart(); - if ((lastPart != null) && myStrategy.isUnknownTextExists()) { - //Filter to starts from - final Iterator 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 history = getHistory(); - final Collection historyCommands = new LinkedHashSet(); - 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 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 words = commandLine != null ? commandLine.getAsWords() : new ArrayList(); - 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 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 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 words = commandLine.getAsWords(); - return words.get(words.size() - 1); - } - - /** - * @return view - */ - @NotNull - CommandInterfaceView getView() { - return myView; - } - -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java deleted file mode 100644 index 1d3be479937a..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.openapi.util.Pair; -import com.intellij.util.ArrayUtil; -import com.jetbrains.python.PyBundle; -import com.jetbrains.python.commandInterface.CommandInterfaceView; -import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace; -import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError; -import com.jetbrains.python.optParse.ParsedCommandLine; -import com.jetbrains.python.optParse.WordWithPosition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * Strategy implementation for case when user entered command - * - * @author Ilya.Kazakevich - */ -final class InCommandStrategy extends Strategy { - @NotNull - private final List myArguments = new ArrayList(); - @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 availableValues = nextArgument.getAvailableValues(); - if (availableValues != null) { // If has available values - return new SuggestionInfo(false, false, availableValues); - } - } - return new SuggestionInfo(false, false, Collections.emptyList()); - } - - @NotNull - @Override - List getBalloonsToShow() { - // Display argument balloons right from command end to last argument end - final ArgumentsInfo argumentsInfo = myCommand.getArgumentsInfo(); - final List 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 result = new ArrayList(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> getErrorInfo() { - final List userProvidedValues = myCommandLine.getArguments(); - SpecialErrorPlace specialError = null; - final List errors = new ArrayList(); - - final ArgumentsValuesValidationInfo validation = - myCommand.getArgumentsInfo().validateArgumentValues(WordWithPosition.fetchText(userProvidedValues)); - if (validation.isNotEnoughArguments()) { - specialError = SpecialErrorPlace.AFTER_LAST_CHAR; - } - for (final Entry errorEntry : validation.getPositionOfErrorArguments().entrySet()) { - final String errorText = (errorEntry.getValue() == ArgumentValueError.BAD_VALUE ? - PyBundle.message("commandsWithArgs.validation.badValue") : - PyBundle.message("commandsWithArgs.validation.excess") ); - errors.add(userProvidedValues.get(errorEntry.getKey()).copyWithDifferentText(errorText)); - } - - - return Pair.create(specialError, errors); - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java deleted file mode 100644 index d08e8ccb16f8..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.google.common.collect.ContiguousSet; -import com.google.common.collect.DiscreteDomain; -import com.google.common.collect.Range; -import com.intellij.util.containers.hash.HashMap; -import com.jetbrains.python.commandInterface.commandsWithArgs.ArgumentsValuesValidationInfo.ArgumentValueError; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Map; - -/** - * Case when command has no arguments (for sure!) - * - * @author Ilya.Kazakevich - */ -public final class NoArgumentsInfo implements ArgumentsInfo { - /** - * Instance to use when command has no arguments - */ - public static final ArgumentsInfo INSTANCE = new NoArgumentsInfo(); - - private NoArgumentsInfo() { - } - - @Nullable - @Override - public Argument getArgument(final int argumentPosition) { - return null; - } - - @NotNull - @Override - public ArgumentsValuesValidationInfo validateArgumentValues(@NotNull final List argumentValuesToCheck) { - if (argumentValuesToCheck.isEmpty()) { - return ArgumentsValuesValidationInfo.NO_ERROR; - } - final Map errors = - new HashMap(); - for (final int errorPosition : ContiguousSet.create(Range.closedOpen(0, argumentValuesToCheck.size()), DiscreteDomain.integers())) { - errors.put(errorPosition, ArgumentValueError.EXCESS); - } - return new ArgumentsValuesValidationInfo(errors, false); - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java deleted file mode 100644 index 16ea46fe9fc6..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.openapi.util.Pair; -import com.jetbrains.python.PyBundle; -import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace; -import com.jetbrains.python.optParse.WordWithPosition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Strategy implementation for case when no command parsed - * - * @author Ilya.Kazakevich - */ -final class NoCommandStrategy extends Strategy { - - private static final Pair> - NO_ERROR = Pair.create(null, Collections.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(myPresenter.getCommands().keySet())); - } - - @Override - boolean isUnknownTextExists() { - return !myPresenter.getView().getText().isEmpty(); - } - - - @NotNull - @Override - Pair> 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.emptyList()); - } - - private boolean isTextBoxEmpty() { - return myPresenter.getView().getText().isEmpty(); - } - - @Nullable - @Override - CommandExecutionInfo getCommandToExecute() { - return null; - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java deleted file mode 100644 index 231cf0207c17..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import com.intellij.openapi.util.Pair; -import com.jetbrains.python.commandInterface.CommandInterfaceView.SpecialErrorPlace; -import com.jetbrains.python.optParse.WordWithPosition; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -/** - * Strategy that helps {@link com.jetbrains.python.commandInterface.commandsWithArgs.CommandInterfacePresenterCommandBased} - * to deal with 2 states: when no text entered (or some junk enetered) or command name entered - * - * @author Ilya.Kazakevich - */ -abstract class Strategy { - @NotNull - protected final CommandInterfacePresenterCommandBased myPresenter; - - /** - * @param presenter presenter - */ - protected Strategy(@NotNull final CommandInterfacePresenterCommandBased presenter) { - myPresenter = presenter; - } - - /** - * @return sub text to display - */ - @NotNull - abstract String getSubText(); - - /** - * @return suggestions - */ - @NotNull - abstract SuggestionInfo getSuggestionInfo(); - - // TODO: Merge baloon and error (actually the same) - @NotNull - List getBalloonsToShow() { - return Collections.emptyList(); - } - - /** - * @return command that entered in box, or null of just entered - */ - @Nullable - abstract CommandExecutionInfo getCommandToExecute(); - - - /** - * @return errors - */ - @NotNull - abstract Pair> getErrorInfo(); - - /** - * @return if text entered by user contains some unknown commands - */ - abstract boolean isUnknownTextExists(); -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java deleted file mode 100644 index c761d8d34619..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Information abouyt suggestion, provided by {@link com.jetbrains.python.commandInterface.commandsWithArgs.Strategy} - * - * @author Ilya.Kazakevich - */ -@SuppressWarnings("PackageVisibleField") -// No do not need to hide field: everything is internal API in package, anyway -final class SuggestionInfo { - /** - * Suggestions - */ - private final List mySuggestions = new ArrayList(); - /** - * 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 suggestions) { - myAbsolute = absolute; - myShowOnlyWhenRequested = showOnlyWhenRequested; - mySuggestions.addAll(suggestions); - } - - /** - * @return suggestions - */ - @NotNull - List getSuggestions() { - return Collections.unmodifiableList(mySuggestions); - } -} diff --git a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java b/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java deleted file mode 100644 index 2198b00dea60..000000000000 --- a/python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - *

Optparse-based commandline interface presenter

- *

- * 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 optparse terminology, so - * read it first and use {@link com.jetbrains.python.optParse} package - *

- *

Arguments and validation

- *

- * Optparse arguments are positional and unnamed. - * 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 - *

- * - * - * @see com.jetbrains.python.optParse - * @author Ilya.Kazakevich - */ -package com.jetbrains.python.commandInterface.commandsWithArgs; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/commandInterface/package-info.java b/python/src/com/jetbrains/python/commandInterface/package-info.java index 19c0b13ac979..7f1581af3bc7 100644 --- a/python/src/com/jetbrains/python/commandInterface/package-info.java +++ b/python/src/com/jetbrains/python/commandInterface/package-info.java @@ -19,7 +19,7 @@ * It has several abilities, including (but not limited): *
    *
  1. Suggestion box
  2. - *
  3. Error marking
  4. + *
  5. Error emphasising
  6. *
  7. Popups
  8. *
  9. AutoCompletion
  10. *
@@ -31,9 +31,15 @@ * *

* 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} *

* + *

+ * 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. + *

+ * + * * * @author Ilya.Kazakevich */ diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java b/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java index 3071a8dc8665..955d07ee6580 100644 --- a/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java +++ b/python/src/com/jetbrains/python/commandInterface/swingView/CommandInterfaceViewSwingImpl.java @@ -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> myPlacesWhereSuggestionsAvailable = new ArrayList>(); @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 myBalloons = new ArrayList(); @NotNull + private final List myInfoBalloons = new ArrayList(); + /** + * 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 myErrorBalloons = new ArrayList(); + /** + * 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 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 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 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 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 infoBalloons, + @NotNull final Collection 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 special case {@link #AFTER_LAST_CHARACTER_RANGE}. + * + * @param range range to check + * @return true if special case + */ + private static boolean isAfterLastCharRange(@NotNull final Range 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 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> 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 myCurrentBaloons = new HashSet(); + + void registerBalloon(final Balloon balloon) { + synchronized (myCurrentBaloons) { + myCurrentBaloons.add(balloon); + balloon.addListener(this); + } + } + + @Override + public void onClosed(final LightweightWindowEvent event) { + synchronized (myCurrentBaloons) { + myCurrentBaloons.remove(event.asBalloon()); + } + super.onClosed(event); + } + + void closeAllBalloons() { + synchronized (myCurrentBaloons) { + for (final Balloon balloon : myCurrentBaloons) { + balloon.dispose(); + } + } + } + } } diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java new file mode 100644 index 000000000000..8ed0c5af06ed --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.commandInterface.swingView; + +import java.awt.event.ActionEvent; + +/** + * "Complete current command or argument" action + * + * @author Ilya.Kazakevich + */ +final class CompletionKeyStrokeAction extends KeyStrokeAction { + CompletionKeyStrokeAction() { + super(KeyStrokeInfo.COMPLETION); + } + + @Override + public void actionPerformed(final ActionEvent e) { + myPresenter.completionRequested(mySuggestionList.getValue()); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java new file mode 100644 index 000000000000..7f258822a968 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.commandInterface.swingView; + +import java.awt.event.ActionEvent; + +/** + * "Execute command" action + * + * @author Ilya.Kazakevich + */ +final class ExecutionKeyStrokeAction extends KeyStrokeAction { + ExecutionKeyStrokeAction() { + super(KeyStrokeInfo.EXECUTION); + } + + @Override + public void actionPerformed(final ActionEvent e) { + myPresenter.executionRequested(); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java new file mode 100644 index 000000000000..a801580a5bf6 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.commandInterface.swingView; + +import com.jetbrains.python.commandInterface.CommandInterfacePresenter; +import com.jetbrains.python.suggestionList.SuggestionList; +import org.jetbrains.annotations.NotNull; + +import javax.swing.text.TextAction; + +/** + * Action that should be taken for certain {@link javax.swing.KeyStroke} (wrapped in {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}). + * You need to call {@link #configure(com.jetbrains.python.commandInterface.CommandInterfacePresenter, com.jetbrains.python.suggestionList.SuggestionList)} + * to enable one. + * + * @author Ilya.Kazakevich + */ +@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "NonSerializableFieldInSerializableClass"}) // Will never serialize +abstract class KeyStrokeAction extends TextAction { + @NotNull + private final String myName; + @NotNull + private final KeyStrokeInfo myStroke; + + protected CommandInterfacePresenter myPresenter; + protected SuggestionList mySuggestionList; + + /** + * @param stroke key stroke to bind this info to + */ + KeyStrokeAction(@NotNull final KeyStrokeInfo stroke) { + super(stroke.name()); + myName = stroke.name(); + myStroke = stroke; + } + + + /** + * Configures action. + * + * @param presenter presenter to be used for call back. + * @param suggestionList list of suggestions to be used for call back + * @return name of this action to add to {@link javax.swing.InputMap} + */ + @NotNull + final String configure(@NotNull final CommandInterfacePresenter presenter, @NotNull final SuggestionList suggestionList) { + myPresenter = presenter; + mySuggestionList = suggestionList; + return myName; + } + + /** + * @return stroke bound to this action + */ + @NotNull + final KeyStrokeInfo getStroke() { + return myStroke; + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java new file mode 100644 index 000000000000..dcf8278e6612 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java @@ -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()); + } +} diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java b/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java index 0a247bbcec81..2e7ff1a07352 100644 --- a/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java +++ b/python/src/com/jetbrains/python/commandInterface/swingView/SmartTextField.java @@ -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 myUnderlineInfo = new ArrayList(); private int myPreferredWidth; - /** - * (color, special_place) tuple to underline special place, or null if no underline required - */ - @Nullable - private Pair mySpecialUnderlinePlace; public SmartTextField() { setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN)); @@ -66,15 +57,9 @@ public class SmartTextField extends JTextField { synchronized (myUnderlineInfo) { for (final UnderlineInfo underlineInfo : myUnderlineInfo) { g.setColor(underlineInfo.myColor); + // To prevent too long underlines: last char should really be last underline(g, underlineInfo.getFrom(), underlineInfo.getTo()); } - if (mySpecialUnderlinePlace != null) { - final SpecialErrorPlace place = mySpecialUnderlinePlace.second; - g.setColor(mySpecialUnderlinePlace.first); - final int endPosition = getTextEndPosition(); - final int from = (place == SpecialErrorPlace.WHOLE_TEXT ? 0 : endPosition - getColumnWidth()); - underline(g, from, endPosition); - } } } @@ -90,17 +75,11 @@ public class SmartTextField extends JTextField { g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition); } - /** - * @return place (in px) where entered text ends. - */ - int getTextEndPosition() { - return (getText().length() + 1) * getColumnWidth(); - } /** * @return place (in px) where caret. */ - int getTextCursorPosition() { + int getTextCaretPositionInPx() { return (getCaretPosition() + 1) * getColumnWidth(); } @@ -139,19 +118,9 @@ public class SmartTextField extends JTextField { void hideUnderline() { synchronized (myUnderlineInfo) { myUnderlineInfo.clear(); - mySpecialUnderlinePlace = null; } } - /** - * Sets appropriate width in chars - * - * @param widthInChars num of chars - */ - void setPreferredWidthInChars(final int widthInChars) { - setColumns(widthInChars); - } - /** * Sets appropriate width in pixels * @@ -161,18 +130,6 @@ public class SmartTextField extends JTextField { myPreferredWidth = width; } - /** - * Display underline in special place - * - * @param color color to underline - * @param specialUnderlinePlace special place to underline - */ - void underlineText(@NotNull final SpecialErrorPlace specialUnderlinePlace, - @NotNull final Color color) { - synchronized (myUnderlineInfo) { - mySpecialUnderlinePlace = Pair.create(color, specialUnderlinePlace); - } - } /** * Wrapper to display placeholder diff --git a/python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java b/python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java new file mode 100644 index 000000000000..088ff8c3fe27 --- /dev/null +++ b/python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.commandInterface.swingView; + +import java.awt.event.ActionEvent; + +/** + * "Suggestion request" action + * + * @author Ilya.Kazakevich + */ +final class SuggestionKeyStrokeAction extends KeyStrokeAction { + SuggestionKeyStrokeAction() { + super(KeyStrokeInfo.SUGGESTION); + } + + @Override + public void actionPerformed(final ActionEvent e) { + myPresenter.suggestionRequested(); + } +} diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java new file mode 100644 index 000000000000..a2a9756b0c26 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java @@ -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> myParts = new ArrayList>(); + @NotNull + private final WordWithPosition myCommand; + + CommandLineParseResult( + @NotNull final WordWithPosition command, + @NotNull final Collection> 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> 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 getPartsNoType() { + final Collection result = new ArrayList(); + for (final Pair part : myParts) { + result.add(part.second); + } + return result; + } +} diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java b/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java new file mode 100644 index 000000000000..b3813d8dd616 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java @@ -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 commandLineParts) throws MalformedCommandLineException; +} diff --git a/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java b/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java new file mode 100644 index 000000000000..0f2359e15087 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java @@ -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 +} diff --git a/python/src/com/jetbrains/python/optParse/MalformedCommandLineException.java b/python/src/com/jetbrains/python/commandLineParser/MalformedCommandLineException.java similarity index 94% rename from python/src/com/jetbrains/python/optParse/MalformedCommandLineException.java rename to python/src/com/jetbrains/python/commandLineParser/MalformedCommandLineException.java index 3e2185230360..8f69e96bf96e 100644 --- a/python/src/com/jetbrains/python/optParse/MalformedCommandLineException.java +++ b/python/src/com/jetbrains/python/commandLineParser/MalformedCommandLineException.java @@ -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; diff --git a/python/src/com/jetbrains/python/optParse/ParsedCommandLine.java b/python/src/com/jetbrains/python/commandLineParser/OptParseCommandLineParser.java similarity index 56% rename from python/src/com/jetbrains/python/optParse/ParsedCommandLine.java rename to python/src/com/jetbrains/python/commandLineParser/OptParseCommandLineParser.java index f4cab570ee45..ddcd0822e53c 100644 --- a/python/src/com/jetbrains/python/optParse/ParsedCommandLine.java +++ b/python/src/com/jetbrains/python/commandLineParser/OptParseCommandLineParser.java @@ -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 myArguments = new ArrayList(); - - /** - * @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 parts = new LinkedList(WordWithPosition.splitText(commandLine)); + @Override + public CommandLineParseResult parse(@NotNull final List commandLineParts) throws MalformedCommandLineException { + final Deque parts = new ArrayDeque(commandLineParts); if (parts.isEmpty()) { throw new MalformedCommandLineException("No command provided"); } - myCommand = parts.pop(); - if (myCommand.getText().startsWith("-")) { + final WordWithPosition command = parts.pop(); + final List> resultParts = new ArrayList>(); + 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 getArguments() { - return Collections.unmodifiableList(myArguments); - } - - /** - * @return all parts for command line as simple words - */ - @NotNull - public List getAsWords() { - final List result = new ArrayList(); - - result.add(myCommand.getText()); - - for (final WordWithPosition argument : myArguments) { - result.add(argument.getText()); - } - // TODO: Add options as well - return result; - } - - // TODO: Add options, arguments and option arguments } diff --git a/python/src/com/jetbrains/python/commandLineParser/package-info.java b/python/src/com/jetbrains/python/commandLineParser/package-info.java new file mode 100644 index 000000000000..bbdd192167f4 --- /dev/null +++ b/python/src/com/jetbrains/python/commandLineParser/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Engine to parse command-line. + * Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT arguments} + * and {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION options}. + * Use need to split command line into {@link com.jetbrains.python.WordWithPosition chunks}, pass them to + * {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLineParseResult}. + * + * Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of + * command line is command or argument. That helps you to underline or emphisize some parts. + * + * + * @author Ilya.Kazakevich + */ +package com.jetbrains.python.commandLineParser; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java index ee2c4abb7939..f2c60e9db582 100644 --- a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java +++ b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java @@ -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> myCommentReplacements = new ArrayList>(); 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 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')); + } } diff --git a/python/src/com/jetbrains/python/optParse/package-info.java b/python/src/com/jetbrains/python/optParse/package-info.java deleted file mode 100644 index 3be946b72036..000000000000 --- a/python/src/com/jetbrains/python/optParse/package-info.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - *

- * An optparse module twin, that parses command line. - * Unlike any other GNU/Posix parsers, it knows how to:

- *
    - *
  1. Parse options and args with out of any knowledge about required args
  2. - *
  3. Provide actual places in command line where exactly such args or opts exist./li> - *
- *

- * Be sure to read optparse manual - * (epecially terminology) part. - *

- *

Package entry point is {@link com.jetbrains.python.optParse.ParsedCommandLine}

- * @author Ilya.Kazakevich - */ -package com.jetbrains.python.optParse; \ No newline at end of file diff --git a/python/src/com/jetbrains/python/suggestionList/SuggestionList.java b/python/src/com/jetbrains/python/suggestionList/SuggestionList.java index c43f23809806..d209235bec74 100644 --- a/python/src/com/jetbrains/python/suggestionList/SuggestionList.java +++ b/python/src/com/jetbrains/python/suggestionList/SuggestionList.java @@ -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(); } /** diff --git a/python/testData/formatter/commentedCodeFragmentIgnored.py b/python/testData/formatter/commentedCodeFragmentIgnored.py new file mode 100644 index 000000000000..64d378e829aa --- /dev/null +++ b/python/testData/formatter/commentedCodeFragmentIgnored.py @@ -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' \ No newline at end of file diff --git a/python/testData/formatter/commentedCodeFragmentIgnored_after.py b/python/testData/formatter/commentedCodeFragmentIgnored_after.py new file mode 100644 index 000000000000..64d378e829aa --- /dev/null +++ b/python/testData/formatter/commentedCodeFragmentIgnored_after.py @@ -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' \ No newline at end of file diff --git a/python/testData/formatter/docCommentIgnored.py b/python/testData/formatter/docCommentIgnored.py new file mode 100644 index 000000000000..08a8959972a7 --- /dev/null +++ b/python/testData/formatter/docCommentIgnored.py @@ -0,0 +1,4 @@ +#: :type: dict of (str, C) +d = {'foo': C()} +#: :type: list of str +xs = ['bar'] \ No newline at end of file diff --git a/python/testData/formatter/docCommentIgnored_after.py b/python/testData/formatter/docCommentIgnored_after.py new file mode 100644 index 000000000000..08a8959972a7 --- /dev/null +++ b/python/testData/formatter/docCommentIgnored_after.py @@ -0,0 +1,4 @@ +#: :type: dict of (str, C) +d = {'foo': C()} +#: :type: list of str +xs = ['bar'] \ No newline at end of file diff --git a/python/testData/formatter/doubleHashCommentIgnored.py b/python/testData/formatter/doubleHashCommentIgnored.py new file mode 100644 index 000000000000..f1501b360660 --- /dev/null +++ b/python/testData/formatter/doubleHashCommentIgnored.py @@ -0,0 +1,6 @@ +########### +## +## MANUAL +## + +print('Spam') \ No newline at end of file diff --git a/python/testData/formatter/doubleHashCommentIgnored_after.py b/python/testData/formatter/doubleHashCommentIgnored_after.py new file mode 100644 index 000000000000..f1501b360660 --- /dev/null +++ b/python/testData/formatter/doubleHashCommentIgnored_after.py @@ -0,0 +1,6 @@ +########### +## +## MANUAL +## + +print('Spam') \ No newline at end of file diff --git a/python/testData/formatter/shebang.py b/python/testData/formatter/shebangCommentIgnored.py similarity index 100% rename from python/testData/formatter/shebang.py rename to python/testData/formatter/shebangCommentIgnored.py diff --git a/python/testData/formatter/shebang_after.py b/python/testData/formatter/shebangCommentIgnored_after.py similarity index 100% rename from python/testData/formatter/shebang_after.py rename to python/testData/formatter/shebangCommentIgnored_after.py diff --git a/python/testData/formatter/trailingComment.py b/python/testData/formatter/trailingComment.py new file mode 100644 index 000000000000..59839dfe2b29 --- /dev/null +++ b/python/testData/formatter/trailingComment.py @@ -0,0 +1,3 @@ +# Whole-line commend that starts with whitespace. +if True: #Trailing comment with no whitespace + print 'Spam' # Trailing comment with excess whitespace \ No newline at end of file diff --git a/python/testData/formatter/trailingComment_after.py b/python/testData/formatter/trailingComment_after.py new file mode 100644 index 000000000000..ca734099069c --- /dev/null +++ b/python/testData/formatter/trailingComment_after.py @@ -0,0 +1,3 @@ +# Whole-line commend that starts with whitespace. +if True: # Trailing comment with no whitespace + print 'Spam' # Trailing comment with excess whitespace \ No newline at end of file diff --git a/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment.py b/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment.py new file mode 100644 index 000000000000..814f4774b27a --- /dev/null +++ b/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment.py @@ -0,0 +1,4 @@ +#some +#invalid +#pep-8 +#comment \ No newline at end of file diff --git a/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment_after.py b/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment_after.py new file mode 100644 index 000000000000..80d188f7dd63 --- /dev/null +++ b/python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment_after.py @@ -0,0 +1,4 @@ +# some +# invalid +# pep-8 +# comment \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index a9db8eb0f4de..ff821d6b524f 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -401,10 +401,6 @@ public class PyFormatterTest extends PyTestCase { doTest(); } - public void testShebang() { //PY-12775 - doTest(); - } - public void testParenthesisAroundGeneratorExpression() { doTest(); } @@ -483,13 +479,43 @@ public class PyFormatterTest extends PyTestCase { // PY-12748 public void testIndentCommentariesInsideFromImportStatement() { - doTest(); + doTest(); } public void testClosingParenthesisInFromImportStatementWithNoHangingIndent() { doTest(); } + // PY-12932 + public void testCommentedCodeFragmentIgnored() { + doTest(); + } + + // PY-12932 + public void testTrailingComment() { + doTest(); + } + + // PY-12938 + public void testDoubleHashCommentIgnored() { + doTest(); + } + + // PY-12938 + public void testDocCommentIgnored() { + doTest(); + } + + // PY-12775 + public void testShebangCommentIgnored() { + doTest(); + } + + // PY-13232 + public void testWhitespaceInsertedAfterHashSignInMultilineComment() { + doTest(); + } + /** * This test merely checks that call to {@link com.intellij.psi.codeStyle.CodeStyleManager#reformat(com.intellij.psi.PsiElement)} * is possible for Python sources. diff --git a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java index aa80aee4d8f9..5cfbc8e5e7b0 100644 --- a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java +++ b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlTagNameSynchronizer.java @@ -17,6 +17,8 @@ package com.intellij.codeInsight.editorActions; import com.intellij.application.options.editor.WebEditorOptions; import com.intellij.codeInsight.completion.XmlTagInsertHandler; +import com.intellij.codeInsight.lookup.LookupManager; +import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.codeInspection.htmlInspections.RenameTagBeginOrEndIntentionAction; import com.intellij.lang.Language; import com.intellij.lang.html.HTMLLanguage; @@ -287,8 +289,7 @@ public class XmlTagNameSynchronizer extends CommandAdapter implements Applicatio myState = State.APPLYING; final Document document = myEditor.getDocument(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override + final Runnable apply = new Runnable() { public void run() { for (Couple couple : myMarkers) { final RangeMarker leader = couple.first; @@ -297,6 +298,17 @@ public class XmlTagNameSynchronizer extends CommandAdapter implements Applicatio document.replaceString(support.getStartOffset(), support.getEndOffset(), name); } } + }; + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor); + if (lookup != null) { + lookup.performGuardedChange(apply); + } else { + apply.run(); + } + } }); myState = State.TRACKING; diff --git a/xml/tests/src/com/intellij/codeInsight/completion/XmlSyncTagCommunityTest.java b/xml/tests/src/com/intellij/codeInsight/completion/XmlSyncTagCommunityTest.java index ed8f50cf3280..9df16cda1e24 100644 --- a/xml/tests/src/com/intellij/codeInsight/completion/XmlSyncTagCommunityTest.java +++ b/xml/tests/src/com/intellij/codeInsight/completion/XmlSyncTagCommunityTest.java @@ -82,6 +82,12 @@ public class XmlSyncTagCommunityTest extends XmlSyncTagTest { ""); } + public void testCompletionWithLookupAfterTyping() { + doTestCompletion(">", "e", + ""); + assertNotNull(myFixture.getLookup()); + } + public void testUndo() { doTest(">", "v", ""); myFixture.performEditorAction(IdeActions.ACTION_UNDO);