From 7d1c0fcf9571ee53b30e28b20107b4864e87fb9b Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Mon, 9 Feb 2015 17:52:54 +0100 Subject: [PATCH 01/28] EA-64632 (NPE: JavaMatchingVisitor.matchType) --- .../impl/matcher/JavaMatchingVisitor.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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(); } From 914559b1c3f54552d68fd0f44103b62e0a5f68db Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Mon, 9 Feb 2015 20:00:30 +0300 Subject: [PATCH 02/28] EA-64504 changed according convention --- .../groovy/codeInspection/assignment/GrListOrMapInfo.java | 2 +- .../groovy/findUsages/LiteralConstructorReference.java | 2 +- .../plugins/groovy/lang/psi/impl/GroovyResolveResultImpl.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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 Date: Fri, 30 Jan 2015 18:39:52 +0300 Subject: [PATCH 03/28] PY-12932 New policy for pre-formatting comments in Python code In addition to existing detection of shebang/double hash/doc comments we consider the following new cases when trying to insert space after '#' in Python line comments: * Comments that already start with whitespace are ignored by formatter. * Comments that span several lines (up to the first empty line or non-whitespace element) are not altered either because they may contain Python code fragments. * Trailing comments however can be safely adjusted because even if they contain any code, there cannot be any indentation inside. --- .../formatter/PyPreFormatProcessor.java | 104 +++++++++++++++--- .../formatter/commentedCodeFragmentIgnored.py | 5 + .../commentedCodeFragmentIgnored_after.py | 5 + .../formatter/multilineCommentIgnored.py | 7 ++ .../multilineCommentIgnored_after.py | 7 ++ python/testData/formatter/trailingComment.py | 3 + .../formatter/trailingComment_after.py | 3 + .../com/jetbrains/python/PyFormatterTest.java | 17 ++- 8 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 python/testData/formatter/commentedCodeFragmentIgnored.py create mode 100644 python/testData/formatter/commentedCodeFragmentIgnored_after.py create mode 100644 python/testData/formatter/multilineCommentIgnored.py create mode 100644 python/testData/formatter/multilineCommentIgnored_after.py create mode 100644 python/testData/formatter/trailingComment.py create mode 100644 python/testData/formatter/trailingComment_after.py diff --git a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java index ee2c4abb7939..c40b543eb92c 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,98 @@ 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 + } + if (commentSpansSeveralLines(comment)) { 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)); + 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; + } + 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 commentSpansSeveralLines(@NotNull PsiComment comment) { + + for (PsiElement prevElement = comment.getPrevSibling(); prevElement != null; prevElement = prevElement.getPrevSibling()) { + if (prevElement instanceof PsiWhiteSpace) { + if (StringUtil.countNewLines(prevElement.getText()) > 1) { + break; + } + } + else if (prevElement instanceof PsiComment) { + return true; + } + else { + break; + } + } + + for (PsiElement nextElement = comment.getNextSibling(); nextElement != null; nextElement = nextElement.getNextSibling()) { + if (nextElement instanceof PsiWhiteSpace) { + if (StringUtil.countNewLines(nextElement.getText()) > 1) { + break; + } + } + else if (nextElement instanceof PsiComment) { + return true; + } + else { + break; + } + } + return false; + } + + private static boolean isTrailingComment(@NotNull PsiComment comment) { + final PsiElement prevElement = comment.getPrevSibling(); + return !(prevElement instanceof PsiWhiteSpace) || !prevElement.textContains('\n'); + } } diff --git a/python/testData/formatter/commentedCodeFragmentIgnored.py b/python/testData/formatter/commentedCodeFragmentIgnored.py new file mode 100644 index 000000000000..03d0d18d02d3 --- /dev/null +++ b/python/testData/formatter/commentedCodeFragmentIgnored.py @@ -0,0 +1,5 @@ +# 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..03d0d18d02d3 --- /dev/null +++ b/python/testData/formatter/commentedCodeFragmentIgnored_after.py @@ -0,0 +1,5 @@ +# 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/multilineCommentIgnored.py b/python/testData/formatter/multilineCommentIgnored.py new file mode 100644 index 000000000000..50a25959707b --- /dev/null +++ b/python/testData/formatter/multilineCommentIgnored.py @@ -0,0 +1,7 @@ +# This line is not considered as part of multiline comment. + +#for c in string.ascii_lowercase: +# if c not in 'aeiou': +# print(c) + +#And this one too. \ No newline at end of file diff --git a/python/testData/formatter/multilineCommentIgnored_after.py b/python/testData/formatter/multilineCommentIgnored_after.py new file mode 100644 index 000000000000..b6ee7834d415 --- /dev/null +++ b/python/testData/formatter/multilineCommentIgnored_after.py @@ -0,0 +1,7 @@ +# This line is not considered as part of multiline comment. + +#for c in string.ascii_lowercase: +# if c not in 'aeiou': +# print(c) + +# And this one too. \ No newline at end of file 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..a7ddeca0fd94 --- /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/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index a9db8eb0f4de..9933085dbdd7 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -483,13 +483,28 @@ 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-12932 + public void testMultilineCommentIgnored() { + doTest(); + } + /** * This test merely checks that call to {@link com.intellij.psi.codeStyle.CodeStyleManager#reformat(com.intellij.psi.PsiElement)} * is possible for Python sources. From 1fcf101da89e35fd2938697856601f0964df141f Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 9 Feb 2015 19:56:33 +0300 Subject: [PATCH 04/28] Add test cases for comment formatting scenarios described in PY-12938 --- .../testData/formatter/docCommentIgnored.py | 4 ++++ .../formatter/docCommentIgnored_after.py | 4 ++++ .../formatter/doubleHashCommentIgnored.py | 6 ++++++ .../doubleHashCommentIgnored_after.py | 6 ++++++ .../{shebang.py => shebangCommentIgnored.py} | 0 ...fter.py => shebangCommentIgnored_after.py} | 0 .../com/jetbrains/python/PyFormatterTest.java | 19 +++++++++++++++---- 7 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 python/testData/formatter/docCommentIgnored.py create mode 100644 python/testData/formatter/docCommentIgnored_after.py create mode 100644 python/testData/formatter/doubleHashCommentIgnored.py create mode 100644 python/testData/formatter/doubleHashCommentIgnored_after.py rename python/testData/formatter/{shebang.py => shebangCommentIgnored.py} (100%) rename python/testData/formatter/{shebang_after.py => shebangCommentIgnored_after.py} (100%) 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/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 9933085dbdd7..4a5f55e0b35b 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(); } @@ -505,6 +501,21 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-12938 + public void testDoubleHashCommentIgnored() { + doTest(); + } + + // PY-12938 + public void testDocCommentIgnored() { + doTest(); + } + + // PY-12775 + public void testShebangCommentIgnored() { + doTest(); + } + /** * This test merely checks that call to {@link com.intellij.psi.codeStyle.CodeStyleManager#reformat(com.intellij.psi.PsiElement)} * is possible for Python sources. From d150d454cd8efa6234726a27092139af74615924 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 9 Feb 2015 19:57:09 +0300 Subject: [PATCH 05/28] Request issues from Redmine with most recently updated first (support 51649) --- .../src/com/intellij/tasks/redmine/RedmineRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java index 8a5f6e2ba0fa..e377e14f5d00 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/redmine/RedmineRepository.java @@ -167,6 +167,7 @@ public class RedmineRepository extends NewBaseRepositoryImpl { URIBuilder builder = new URIBuilder(getRestApiUrl("issues.json")) .addParameter("offset", String.valueOf(offset)) .addParameter("limit", String.valueOf(limit)) + .addParameter("sort", "updated_on:desc") .addParameter("status_id", withClosed ? "*" : "open") .addParameter("assigned_to_id", "me"); // If project was not chosen, all available issues still fetched. Such behavior may seems strange to user. From 909ed2837ed2419298ccaa985d8f89a428a8a9a5 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 9 Feb 2015 20:30:34 +0300 Subject: [PATCH 06/28] PY-13232 Remove ignoring of multiline comments that was previously added for PY-12932 It would not help to preseve indentation of code fragments commented without whitespace after '#' (i.e. commented not in PyCharm) anyway. --- .../formatter/PyPreFormatProcessor.java | 36 ------------------- .../formatter/multilineCommentIgnored.py | 7 ---- .../multilineCommentIgnored_after.py | 7 ---- ...InsertedAfterHashSignInMultilineComment.py | 4 +++ ...edAfterHashSignInMultilineComment_after.py | 4 +++ .../com/jetbrains/python/PyFormatterTest.java | 10 +++--- 6 files changed, 13 insertions(+), 55 deletions(-) delete mode 100644 python/testData/formatter/multilineCommentIgnored.py delete mode 100644 python/testData/formatter/multilineCommentIgnored_after.py create mode 100644 python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment.py create mode 100644 python/testData/formatter/whitespaceInsertedAfterHashSignInMultilineComment_after.py diff --git a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java index c40b543eb92c..e2c30e560014 100644 --- a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java +++ b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java @@ -109,10 +109,6 @@ public class PyPreFormatProcessor implements PreFormatProcessor { if (charAfterDash == '#' || charAfterDash == ':') { return; // doc comment } - if (commentSpansSeveralLines(comment)) { - return; - } - final String commentTextWithoutDash = origText.substring(commentStart + 1); final String newText; if (isTrailingComment(comment)) { @@ -134,38 +130,6 @@ public class PyPreFormatProcessor implements PreFormatProcessor { } } - private static boolean commentSpansSeveralLines(@NotNull PsiComment comment) { - - for (PsiElement prevElement = comment.getPrevSibling(); prevElement != null; prevElement = prevElement.getPrevSibling()) { - if (prevElement instanceof PsiWhiteSpace) { - if (StringUtil.countNewLines(prevElement.getText()) > 1) { - break; - } - } - else if (prevElement instanceof PsiComment) { - return true; - } - else { - break; - } - } - - for (PsiElement nextElement = comment.getNextSibling(); nextElement != null; nextElement = nextElement.getNextSibling()) { - if (nextElement instanceof PsiWhiteSpace) { - if (StringUtil.countNewLines(nextElement.getText()) > 1) { - break; - } - } - else if (nextElement instanceof PsiComment) { - return true; - } - else { - break; - } - } - return false; - } - private static boolean isTrailingComment(@NotNull PsiComment comment) { final PsiElement prevElement = comment.getPrevSibling(); return !(prevElement instanceof PsiWhiteSpace) || !prevElement.textContains('\n'); diff --git a/python/testData/formatter/multilineCommentIgnored.py b/python/testData/formatter/multilineCommentIgnored.py deleted file mode 100644 index 50a25959707b..000000000000 --- a/python/testData/formatter/multilineCommentIgnored.py +++ /dev/null @@ -1,7 +0,0 @@ -# This line is not considered as part of multiline comment. - -#for c in string.ascii_lowercase: -# if c not in 'aeiou': -# print(c) - -#And this one too. \ No newline at end of file diff --git a/python/testData/formatter/multilineCommentIgnored_after.py b/python/testData/formatter/multilineCommentIgnored_after.py deleted file mode 100644 index b6ee7834d415..000000000000 --- a/python/testData/formatter/multilineCommentIgnored_after.py +++ /dev/null @@ -1,7 +0,0 @@ -# This line is not considered as part of multiline comment. - -#for c in string.ascii_lowercase: -# if c not in 'aeiou': -# print(c) - -# And this one too. \ 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 4a5f55e0b35b..ff821d6b524f 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -496,11 +496,6 @@ public class PyFormatterTest extends PyTestCase { doTest(); } - // PY-12932 - public void testMultilineCommentIgnored() { - doTest(); - } - // PY-12938 public void testDoubleHashCommentIgnored() { doTest(); @@ -516,6 +511,11 @@ public class PyFormatterTest extends PyTestCase { 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. From 041a7c1c3ddf866aed9f169c7b15a1cd0bb0a912 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 9 Feb 2015 19:04:18 +0100 Subject: [PATCH 07/28] Cleanup (formatting; warnings) --- .../analysis/AnnotationsHighlightUtil.java | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) 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); From 2c738eea52b867e2dc8ef8f69fa508094f29ca92 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 9 Feb 2015 19:23:15 +0100 Subject: [PATCH 08/28] IDEA-136162 (type annotations in anonymous classes) --- .../psi/impl/source/tree/java/PsiAnnotationImpl.java | 7 +++++-- .../daemonCodeAnalyzer/annotations/typeAnnotations.java | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) 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(); From d11625bd079bc27819904ad7c4f82dfba42be2fb Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 9 Feb 2015 16:42:45 +0100 Subject: [PATCH 09/28] title changed: default visibility in generate settings --- .../options/CodeStyleGenerationConfigurable.java | 3 ++- .../com/intellij/refactoring/ui/JavaVisibilityPanel.java | 8 +++++++- .../src/messages/RefactoringBundle.properties | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) 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/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/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: From 69588b4a641ae3ddd113570a73cc96f133ec97b9 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 9 Feb 2015 16:48:47 +0100 Subject: [PATCH 10/28] multiple jdks: test method signatures hierarchy --- .../multipleJdks/java7/p/NoOverriding.java | 14 ++++++++++++++ .../multipleJdks/java8/p/NoOverriding.java | 5 +++++ .../codeInsight/MultipleJdksHighlightingTest.java | 5 +++++ 3 files changed, 24 insertions(+) create mode 100644 java/java-tests/testData/codeInsight/multipleJdks/java7/p/NoOverriding.java create mode 100644 java/java-tests/testData/codeInsight/multipleJdks/java8/p/NoOverriding.java 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/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/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java index 4167b01e2bf2..7f8a1b869b16 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java @@ -126,6 +126,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"); From d46f76eda19b2d733a9145cda278ca165f6cebf5 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 9 Feb 2015 18:31:38 +0100 Subject: [PATCH 11/28] deep delete parameter: ensure parameter used in the same call passed to another params is not suggested --- .../safeDelete/SafeDeleteJavaCallerChooser.java | 17 ++++++++++++----- ...teParameterUsedInNextArgumentExpression.java | 7 +++++++ ...meterUsedInNextArgumentExpression_after.java | 7 +++++++ .../intellij/refactoring/SafeDeleteTest.java | 4 ++++ 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression.java create mode 100644 java/java-tests/testData/refactoring/safeDelete/NoDeepDeleteParameterUsedInNextArgumentExpression_after.java 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-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/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(); } From 4ca4d0d396125b3b871eed2f6bd550eeda69a9ea Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 9 Feb 2015 21:39:21 +0300 Subject: [PATCH 12/28] PY-12932 Fix trailing comment detection when it's the first child element --- .../com/jetbrains/python/formatter/PyPreFormatProcessor.java | 2 +- python/testData/formatter/commentedCodeFragmentIgnored.py | 1 + python/testData/formatter/commentedCodeFragmentIgnored_after.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java index e2c30e560014..f2c60e9db582 100644 --- a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java +++ b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java @@ -132,6 +132,6 @@ public class PyPreFormatProcessor implements PreFormatProcessor { private static boolean isTrailingComment(@NotNull PsiComment comment) { final PsiElement prevElement = comment.getPrevSibling(); - return !(prevElement instanceof PsiWhiteSpace) || !prevElement.textContains('\n'); + return prevElement != null && (!(prevElement instanceof PsiWhiteSpace) || !prevElement.textContains('\n')); } } diff --git a/python/testData/formatter/commentedCodeFragmentIgnored.py b/python/testData/formatter/commentedCodeFragmentIgnored.py index 03d0d18d02d3..64d378e829aa 100644 --- a/python/testData/formatter/commentedCodeFragmentIgnored.py +++ b/python/testData/formatter/commentedCodeFragmentIgnored.py @@ -1,3 +1,4 @@ +# This normal comment won't be touched as well # for a in [1, 2, 3]: # if a == 1: # print 'x' diff --git a/python/testData/formatter/commentedCodeFragmentIgnored_after.py b/python/testData/formatter/commentedCodeFragmentIgnored_after.py index 03d0d18d02d3..64d378e829aa 100644 --- a/python/testData/formatter/commentedCodeFragmentIgnored_after.py +++ b/python/testData/formatter/commentedCodeFragmentIgnored_after.py @@ -1,3 +1,4 @@ +# This normal comment won't be touched as well # for a in [1, 2, 3]: # if a == 1: # print 'x' From e033602143bf87ca61808a1a4ef16d0bb6b2812f Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 9 Feb 2015 21:48:34 +0300 Subject: [PATCH 13/28] PY-12932 Fix wrong test data --- python/testData/formatter/trailingComment_after.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/testData/formatter/trailingComment_after.py b/python/testData/formatter/trailingComment_after.py index a7ddeca0fd94..ca734099069c 100644 --- a/python/testData/formatter/trailingComment_after.py +++ b/python/testData/formatter/trailingComment_after.py @@ -1,3 +1,3 @@ -# Whole-line commend that starts with whitespace. +# 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 From 5a99c1ae0464914768c542b2405a0b70f55199b7 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 6 Feb 2015 20:09:48 +0300 Subject: [PATCH 14/28] performance: load engines in background --- .../console/RunIdeConsoleAction.java | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) 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) { From 16de3f88c68e683cb0ab0c5b1db032659fb2faa7 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 6 Feb 2015 20:10:17 +0300 Subject: [PATCH 15/28] move text to ActionBundle.properties --- .../src/com/intellij/ide/scratch/NewScratchFileAction.java | 4 ---- .../src/messages/ActionsBundle.properties | 4 ++++ platform/platform-resources/src/idea/LangActions.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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/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/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 @@ - + From f6289a9c0c4d3b3ed1ad2789aa42eb7dcc9d37f7 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Fri, 6 Feb 2015 20:11:09 +0300 Subject: [PATCH 16/28] performance: quit earlier --- .../navigation/CtrlMouseHandler.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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; } From ea04d1b6c157368e1db94a2a090c0c87d3b5ea69 Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 9 Feb 2015 22:10:06 +0300 Subject: [PATCH 17/28] check if original handler is enabled --- .../editor/actions/NamedElementDuplicateHandler.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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(); From 241b81e3548c8e72be7f53694e280bb1407cdf4b Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 9 Feb 2015 22:22:51 +0300 Subject: [PATCH 18/28] allow use-shortcut-of chaining --- .../intellij/openapi/keymap/impl/KeymapManagerImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 From 2e438a4f7c1ca80db538992634419075b4bdadb9 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 9 Feb 2015 20:29:26 +0100 Subject: [PATCH 19/28] multiple jdks/not exported dependencies: check that declared type of the qualifier doesn't contain unknown types --- .../daemon/impl/analysis/GenericsHighlightUtil.java | 13 +++++++++++-- .../daemon/impl/analysis/HighlightVisitorImpl.java | 9 ++++++++- ...DeclaredTypeOfVariableImplementsUnknownType.java | 6 ++++++ ...DeclaredTypeOfVariableImplementsUnknownType.java | 7 +++++++ .../codeInsight/MultipleJdksHighlightingTest.java | 4 ++++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/multipleJdks/java7/p/DeclaredTypeOfVariableImplementsUnknownType.java create mode 100644 java/java-tests/testData/codeInsight/multipleJdks/java8/p/DeclaredTypeOfVariableImplementsUnknownType.java 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-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/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/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/MultipleJdksHighlightingTest.java index 7f8a1b869b16..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(); From a431dfe358478db74f1ac7ab5bc9c433882e8223 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 10 Feb 2015 01:04:51 +0300 Subject: [PATCH 20/28] PY-11855 Run manage.py task improvements Refactored to new architecture (Presenter / ChunkDriver). --- .../com/jetbrains/python/PyBundle.properties | 13 +- .../{optParse => }/WordWithPosition.java | 14 +- .../CommandInterfacePresenter.java | 10 +- .../CommandInterfaceView.java | 96 +++--- .../ChunkAndInfo.java | 83 ++++++ .../ChunkDriver.java | 39 +++ .../ChunkDriverBasedPresenter.java | 247 +++++++++++++++ .../chunkDriverBasedPresenter/ChunkInfo.java | 81 +++++ .../chunkDriverBasedPresenter/ParseInfo.java | 88 ++++++ .../SuggestionInfo.java | 70 +++++ .../package-info.java | 30 ++ .../Argument.java | 2 +- .../ArgumentsInfo.java | 29 +- .../Command.java | 14 +- .../CommandBasedChunkDriver.java | 187 ++++++++++++ .../KnownArgumentsInfo.java | 49 ++- .../NoArgumentsInfo.java | 41 +++ .../UnknownArgumentsInfo.java | 16 +- .../commandBasedChunkDriver/package-info.java | 27 ++ .../ArgumentsValuesValidationInfo.java | 78 ----- .../commandsWithArgs/CommandAdapter.java | 67 ----- .../CommandExecutionInfo.java | 110 ------- ...CommandInterfacePresenterCommandBased.java | 247 --------------- .../commandsWithArgs/InCommandStrategy.java | 150 ---------- .../commandsWithArgs/NoArgumentsInfo.java | 62 ---- .../commandsWithArgs/NoCommandStrategy.java | 78 ----- .../commandsWithArgs/Strategy.java | 79 ----- .../commandsWithArgs/SuggestionInfo.java | 65 ---- .../commandsWithArgs/package-info.java | 42 --- .../python/commandInterface/package-info.java | 10 +- .../CommandInterfaceViewSwingImpl.java | 281 ++++++++++++------ .../swingView/CompletionKeyStrokeAction.java | 34 +++ .../swingView/ExecutionKeyStrokeAction.java | 34 +++ .../swingView/KeyStrokeAction.java | 72 +++++ .../swingView/KeyStrokeInfo.java | 99 ++++++ .../swingView/SmartTextField.java | 47 +-- .../swingView/SuggestionKeyStrokeAction.java | 34 +++ .../CommandLineParseResult.java | 78 +++++ .../commandLineParser/CommandLineParser.java | 38 +++ .../CommandLinePartType.java | 36 +++ .../MalformedCommandLineException.java | 2 +- .../OptParseCommandLineParser.java} | 68 ++--- .../commandLineParser/package-info.java | 30 ++ .../python/optParse/package-info.java | 32 -- .../python/suggestionList/SuggestionList.java | 9 +- 45 files changed, 1699 insertions(+), 1319 deletions(-) rename python/src/com/jetbrains/python/{optParse => }/WordWithPosition.java (91%) create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkAndInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriver.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkDriverBasedPresenter.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ChunkInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/ParseInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/SuggestionInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/chunkDriverBasedPresenter/package-info.java rename python/src/com/jetbrains/python/commandInterface/{commandsWithArgs => commandBasedChunkDriver}/Argument.java (96%) rename python/src/com/jetbrains/python/commandInterface/{commandsWithArgs => commandBasedChunkDriver}/ArgumentsInfo.java (51%) rename python/src/com/jetbrains/python/commandInterface/{commandsWithArgs => commandBasedChunkDriver}/Command.java (69%) create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/CommandBasedChunkDriver.java rename python/src/com/jetbrains/python/commandInterface/{commandsWithArgs => commandBasedChunkDriver}/KnownArgumentsInfo.java (73%) create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/NoArgumentsInfo.java rename python/src/com/jetbrains/python/commandInterface/{commandsWithArgs => commandBasedChunkDriver}/UnknownArgumentsInfo.java (72%) create mode 100644 python/src/com/jetbrains/python/commandInterface/commandBasedChunkDriver/package-info.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/ArgumentsValuesValidationInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandAdapter.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandExecutionInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/CommandInterfacePresenterCommandBased.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoArgumentsInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/NoCommandStrategy.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Strategy.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/SuggestionInfo.java delete mode 100644 python/src/com/jetbrains/python/commandInterface/commandsWithArgs/package-info.java create mode 100644 python/src/com/jetbrains/python/commandInterface/swingView/CompletionKeyStrokeAction.java create mode 100644 python/src/com/jetbrains/python/commandInterface/swingView/ExecutionKeyStrokeAction.java create mode 100644 python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeAction.java create mode 100644 python/src/com/jetbrains/python/commandInterface/swingView/KeyStrokeInfo.java create mode 100644 python/src/com/jetbrains/python/commandInterface/swingView/SuggestionKeyStrokeAction.java create mode 100644 python/src/com/jetbrains/python/commandLineParser/CommandLineParseResult.java create mode 100644 python/src/com/jetbrains/python/commandLineParser/CommandLineParser.java create mode 100644 python/src/com/jetbrains/python/commandLineParser/CommandLinePartType.java rename python/src/com/jetbrains/python/{optParse => commandLineParser}/MalformedCommandLineException.java (94%) rename python/src/com/jetbrains/python/{optParse/ParsedCommandLine.java => commandLineParser/OptParseCommandLineParser.java} (56%) create mode 100644 python/src/com/jetbrains/python/commandLineParser/package-info.java delete mode 100644 python/src/com/jetbrains/python/optParse/package-info.java diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 03014b095ff9..08238498648c 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -852,7 +852,14 @@ remote.interpreter.configure.temp.files.path.label=PyCharm helpers path: # Message we display for inspection if user uses custom class type members that do not exist custom.type.mimic.name=Dynamic class based on {0} + # Values for command argument value validation -commandsWithArgs.validation.badValue=Argument can't have this value -commandsWithArgs.validation.excess=Excess argument -commandsWithArgs.enterCommand.label=Enter command here \ No newline at end of file +commandLine.validation.badCommand=Unknown command +commandLine.validation.argMissing=Required argument value is missing +commandLine.validation.argBadValue=Argument can't have this value +commandLine.validation.excessArg=Excess argument value +# And for labels +commandLine.subText.key.complete=Use {0} to complete selected variant +commandLine.subText.key.suggestions=Use {0} to view available values +commandLine.subText.key.executeUnknown=Click {0} to execute +commandLine.subText.key.executeCommand=Click {0} to execute "{1}" diff --git a/python/src/com/jetbrains/python/optParse/WordWithPosition.java b/python/src/com/jetbrains/python/WordWithPosition.java similarity index 91% rename from python/src/com/jetbrains/python/optParse/WordWithPosition.java rename to python/src/com/jetbrains/python/WordWithPosition.java index cdfdb27e8ed9..35f10c3a2e81 100644 --- a/python/src/com/jetbrains/python/optParse/WordWithPosition.java +++ b/python/src/com/jetbrains/python/WordWithPosition.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; import com.intellij.util.Range; import org.jetbrains.annotations.NotNull; @@ -33,6 +33,7 @@ public final class WordWithPosition extends Range { /** * 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/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(); } /** From 2f94abff06dcf366a895a34ecbdef16a688ee141 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Tue, 10 Feb 2015 11:24:45 +0300 Subject: [PATCH 21/28] performGuardedChange when lookup is present #IDEA-136252 fixed --- .../editorActions/XmlTagNameSynchronizer.java | 16 ++++++++++++++-- .../completion/XmlSyncTagCommunityTest.java | 6 ++++++ 2 files changed, 20 insertions(+), 2 deletions(-) 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); From 429e75e4287d66b234dde57863865553a5c6a10d Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 10 Feb 2015 09:37:26 +0100 Subject: [PATCH 22/28] use a single reference queue to avoid smart pointer list leaks in vfs user data --- .../SmartPointerManagerImpl.java | 111 ++++++++++++------ 1 file changed, 78 insertions(+), 33 deletions(-) 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; + } + } + } From 57273b66d601aeb88736b82fc492d31bff3414c6 Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 10 Feb 2015 11:47:05 +0300 Subject: [PATCH 23/28] fixed class-versions check: some *.class files from groovy-all-2.3.7.jar have 1.7 java version --- build/conf/classVersions.txt | 3 +++ 1 file changed, 3 insertions(+) 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 From 9ea9b9c3c276b0772835bf0e2699cae7a450e8d4 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Tue, 10 Feb 2015 09:48:43 +0100 Subject: [PATCH 24/28] fastCgi should be nin io package as before --- .../org/jetbrains/{ => io}/fastCgi/FastCgiChannelHandler.java | 4 ++-- .../src/org/jetbrains/{ => io}/fastCgi/FastCgiConstants.java | 2 +- .../src/org/jetbrains/{ => io}/fastCgi/FastCgiDecoder.java | 4 ++-- .../src/org/jetbrains/{ => io}/fastCgi/FastCgiRequest.java | 2 +- .../src/org/jetbrains/{ => io}/fastCgi/FastCgiResponse.java | 2 +- .../src/org/jetbrains/{ => io}/fastCgi/FastCgiService.java | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiChannelHandler.java (97%) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiConstants.java (71%) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiDecoder.java (97%) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiRequest.java (99%) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiResponse.java (90%) rename platform/built-in-server/src/org/jetbrains/{ => io}/fastCgi/FastCgiService.java (99%) 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; From eb6a2e269bf78a0a86e6de41ab393d414c0ec30e Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Tue, 10 Feb 2015 12:59:10 +0300 Subject: [PATCH 25/28] diff: darcula arrow icons --- platform/icons/src/diff/arrow@2x_dark.png | Bin 292 -> 283 bytes .../icons/src/diff/arrowLeftDown@2x_dark.png | Bin 0 -> 152 bytes platform/icons/src/diff/arrowLeftDown_dark.png | Bin 0 -> 124 bytes platform/icons/src/diff/arrowRight@2x_dark.png | Bin 0 -> 292 bytes .../icons/src/diff/arrowRightDown@2x_dark.png | Bin 0 -> 158 bytes platform/icons/src/diff/arrowRightDown_dark.png | Bin 0 -> 129 bytes platform/icons/src/diff/arrowRight_dark.png | Bin 0 -> 170 bytes platform/icons/src/diff/arrow_dark.png | Bin 169 -> 169 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 platform/icons/src/diff/arrowLeftDown@2x_dark.png create mode 100644 platform/icons/src/diff/arrowLeftDown_dark.png create mode 100644 platform/icons/src/diff/arrowRight@2x_dark.png create mode 100644 platform/icons/src/diff/arrowRightDown@2x_dark.png create mode 100644 platform/icons/src/diff/arrowRightDown_dark.png create mode 100644 platform/icons/src/diff/arrowRight_dark.png diff --git a/platform/icons/src/diff/arrow@2x_dark.png b/platform/icons/src/diff/arrow@2x_dark.png index db6d35cadcd5c04c76decafcdaee2e052fa814a7..5f3b65f836636e0f4ada51c3cb3508041391c0f0 100644 GIT binary patch delta 255 zcmVO)Q_W}5F=g)6Tla&Af002ovPDHLk FV1h27dCdR- delta 265 zcmV+k0rviz0;B?vB!BBkL_t(|+SQe>4#F@P#7jsJ7!-~*YtdVznI8jQh#Cvk%nt?6 zMGt^(nI8h4iEa=&h(^q3;Hl^ep^In|;`_kngENHg_V0px7k_vxIzp(i#R>I0!RG#U z5Hjtvf)Mka;0!_^(WVt*z7w32VBQyOj=FChb=kTKpHGW-g7B-Ocs`%DTS?ljjBPg@ zfQk%A6+0j}vNaj$I(DRS$vh#PF+XN)CS+|Ueb(krDOvQXbJ73JoA(90lk2IY+88(h P000099I 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 0000000000000000000000000000000000000000..f7d0791c2f1b8a8571328441bf78da824696041b GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4f@t!V@Ar-gYUNq!8puppN@lH>S zMfw8sV$N4#OI=v?J09j=i`HQ{V!V09rB%9rZ|s@G(zEi95zI*X(j}J&% zOs)@+*igPP;p~C^x7hvmpKtj6TPLFZ!>f!By!MkeFI-~1EDmTDgQu&X%Q~loCIDWk BJTm|Q literal 0 HcmV?d00001 diff --git a/platform/icons/src/diff/arrowLeftDown_dark.png b/platform/icons/src/diff/arrowLeftDown_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e54c525c98e620260a52d88753c0e9b3b3589033 GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqh&YmugAr-fhfBgS%fB4qc?1_c8 z0>2k^HX1UE9sS=KxW;WwQp##ht$+S2lX!Z(4b5WueGUD3)=5Z*9bP7}`l^JQa5tmz X360n_+N|q=#xZ!h`njxgN@xNA8&D}1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7c83eae20f8775f589ba6dd3fe1364c4db78f4fa GIT binary patch literal 292 zcmV+<0o(qGP)q9dNg>=%vWOSnUEe09uQk*J`)pWLV7gV zG9r%5d?Looi1cW%a$j*_=7S3q>8W7l9_PW#J7Or&QLtt1)~hjZ+?0L%qfKe2ZQP{`1Gt)lsctO3*x|LwvO3AwU1*kwlDoH{7%GRLLwY1VXGu;A5 q2I;>)o6RB5z@g7t9s0ld=Y0TKC+E)_)tWQ_0000gbsz%4c(TUa4Y!;A8c$=}{Ng<97Sz*HJeLV!5S{ zNflK6(a2x_LCzs|!?`9&UB32fj~bn`KOV4RxzD~k^8=&R)y)fYH!|=6EoAU?^>bP0 Hl+XkKboV>a literal 0 HcmV?d00001 diff --git a/platform/icons/src/diff/arrowRightDown_dark.png b/platform/icons/src/diff/arrowRightDown_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6df2507058f828013840b3935c7010e6c03a85f9 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqh9-c0aAr-fhBX*bRGK;<8KJxWM z(*OVe(-RIX{$X#~b=4z~r%Xt*>;GYq-;WMNh+cPA-H^z0w8bFoXx5K8EoW2a_%Ug- c2_8vec(Yq0_GSD`L7zopr09M5=c>n+a literal 0 HcmV?d00001 diff --git a/platform/icons/src/diff/arrowRight_dark.png b/platform/icons/src/diff/arrowRight_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6d1d8e0543434a5ab7150e69910974c195cebb97 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhg`O^sAr-fhfBgS%&%9B>XHD$x zZ|VZojE2l2i$4d7lrwz{SJ>9-wFt;IX8I_k@Q>jLTf`5IF2@QVmJCR2-FNEf1BV2BVVLPbr+tK;B j!r2=woV`)PnOF<}yJ$J-8e`6Q00000NkvXXu0mjf30WYe delta 77 zcmZ3zmvv4FO#tfjAD;jK From 3db89f259c4d2e1d8edb50dafe276ac78796482c Mon Sep 17 00:00:00 2001 From: "Vladimir.Orlov" Date: Tue, 10 Feb 2015 13:42:01 +0300 Subject: [PATCH 26/28] relocate publishing of .mac.zip artifact from layoutAll to not keep the one in IDEA based product where there is a .sit. --- build/scripts/dist.gant | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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]) } From 3dd6a8542409ad88d1a49054fcbe265af9c95520 Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Tue, 10 Feb 2015 13:36:01 +0300 Subject: [PATCH 27/28] Code Style Settings: renamed "field groups" settings group into "Group declarations" --- .../options/codeStyle/WrappingAndBracesPanel.java | 3 ++- .../src/messages/ApplicationBundle.properties | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) 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/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 From 0ffaa2e36f9635d8fa1874b5c5a21155b75e6507 Mon Sep 17 00:00:00 2001 From: Sergey Savenko Date: Tue, 10 Feb 2015 13:55:07 +0300 Subject: [PATCH 28/28] AbstractPopup: defer final runnable execution myFinalRunnable can contain logic which removes focus listeners (e.g. the runnable can invoke change color scheme action). In case we're called from a focus listener, which is called from java.awt.AWTEventMulticaster, removal of focus listeners will not have effect as the focus listeners list is copied on each modification. So focus listeners which have already been removed, will get invoked. Hence, we defer the final runnable execution. --- .../platform-impl/src/com/intellij/ui/popup/AbstractPopup.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 c5e94cbf32d3..99a8739a63ba 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java @@ -1348,9 +1348,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; } };