From ad9ea4d7ed0b9a0c47fbdeed68491170b86a6c35 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 10 Feb 2017 16:01:49 +0300 Subject: [PATCH] PY-22449 Fixed: Breadcrumbs are incorrect when caret placed after the last character of the block Update BreadcrumbsXmlWrapper to use BreadcrumbsInfoProvider.getParent while searching for the first breadcrumbed element. Update PyBreadcrumbsInfoProvider to return parent of the closest element before on the same line in case of white space, comma or close bracket. --- .../breadcrumbs/PyBreadcrumbsInfoProvider.kt | 57 +++++++++++++++++-- .../testData/breadcrumbs/functionBodyCaret.py | 3 + .../breadcrumbs/functionBodyCaret_crumbs.txt | 8 +++ .../breadcrumbs/functionBodySpaceCaret.py | 3 + .../functionBodySpaceCaretNewLineFunction.py | 5 ++ ...onBodySpaceCaretNewLineFunction_crumbs.txt | 8 +++ .../functionBodySpaceCaret_crumbs.txt | 8 +++ .../functionNewLineCaretNewLineFunction.py | 6 ++ ...tionNewLineCaretNewLineFunction_crumbs.txt | 4 ++ .../functionNewLineCaretSpaceFunction.py | 5 ++ ...nctionNewLineCaretSpaceFunction_crumbs.txt | 4 ++ ...unctionNewLineSpaceCaretNewLineFunction.py | 6 ++ ...ewLineSpaceCaretNewLineFunction_crumbs.txt | 4 ++ .../functionNewLineSpaceCaretSpaceFunction.py | 5 ++ ...nNewLineSpaceCaretSpaceFunction_crumbs.txt | 4 ++ ...iteralCaretAfterFirstKeyValueExpression.py | 4 ++ ...retAfterFirstKeyValueExpression_crumbs.txt | 4 ++ .../singleLineDictLiteralCaretAfterComma.py | 1 + ...eLineDictLiteralCaretAfterComma_crumbs.txt | 0 ...iteralCaretAfterFirstKeyValueExpression.py | 1 + ...retAfterFirstKeyValueExpression_crumbs.txt | 4 ++ ...teralCaretAfterSecondKeyValueExpression.py | 1 + ...etAfterSecondKeyValueExpression_crumbs.txt | 4 ++ .../python/breadcrumbs/PyBreadcrumbsTest.java | 46 ++++++++++++++- .../breadcrumbs/BreadcrumbsXmlWrapper.java | 9 ++- 25 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 python/testData/breadcrumbs/functionBodyCaret.py create mode 100644 python/testData/breadcrumbs/functionBodyCaret_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionBodySpaceCaret.py create mode 100644 python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction.py create mode 100644 python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionBodySpaceCaret_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionNewLineCaretNewLineFunction.py create mode 100644 python/testData/breadcrumbs/functionNewLineCaretNewLineFunction_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionNewLineCaretSpaceFunction.py create mode 100644 python/testData/breadcrumbs/functionNewLineCaretSpaceFunction_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction.py create mode 100644 python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction_crumbs.txt create mode 100644 python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction.py create mode 100644 python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction_crumbs.txt create mode 100644 python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression.py create mode 100644 python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma.py create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma_crumbs.txt create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression.py create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression.py create mode 100644 python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression_crumbs.txt diff --git a/python/src/com/jetbrains/python/breadcrumbs/PyBreadcrumbsInfoProvider.kt b/python/src/com/jetbrains/python/breadcrumbs/PyBreadcrumbsInfoProvider.kt index 3974ffdc4e51..e76ddd51f721 100644 --- a/python/src/com/jetbrains/python/breadcrumbs/PyBreadcrumbsInfoProvider.kt +++ b/python/src/com/jetbrains/python/breadcrumbs/PyBreadcrumbsInfoProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -15,9 +15,16 @@ */ package com.jetbrains.python.breadcrumbs +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.impl.source.tree.LeafElement +import com.intellij.psi.util.PsiTreeUtil import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider +import com.jetbrains.python.PyTokenTypes import com.jetbrains.python.PythonLanguage import com.jetbrains.python.psi.* @@ -27,10 +34,10 @@ class PyBreadcrumbsInfoProvider : BreadcrumbsInfoProvider() { private val LANGUAGES = arrayOf(PythonLanguage.getInstance()) private val HELPERS = listOf>( LambdaHelper, - SimpleHelper(PyTryPart::class.java, "try"), + SimpleHelper(PyTryPart::class.java, "try"), ExceptHelper, - SimpleHelper(PyFinallyPart::class.java, "finally"), - SimpleHelper(PyElsePart::class.java, "else"), + SimpleHelper(PyFinallyPart::class.java, "finally"), + SimpleHelper(PyElsePart::class.java, "else"), IfHelper, ForHelper, WhileHelper, @@ -42,7 +49,24 @@ class PyBreadcrumbsInfoProvider : BreadcrumbsInfoProvider() { } override fun getLanguages() = LANGUAGES + override fun acceptElement(e: PsiElement) = getHelper(e) != null + + override fun getParent(e: PsiElement): PsiElement? { + val default = e.parent + + val currentOffset = currentOffset(e) ?: return default + if (!isElementToMoveBackward(e, currentOffset)) return default + + val nonWhiteSpace = moveBackward(e, currentOffset) ?: return default + + val psiFile = e.containingFile ?: return default + val document = PsiDocumentManager.getInstance(e.project).getDocument(psiFile) ?: return default + val sameLine = document.getLineNumber(nonWhiteSpace.textOffset) == document.getLineNumber(currentOffset) + + return if (sameLine) nonWhiteSpace.parent else default + } + override fun getElementInfo(e: PsiElement) = getHelper(e)!!.elementInfo(e as PyElement) override fun getElementTooltip(e: PsiElement) = getHelper(e)!!.elementTooltip(e as PyElement) @@ -53,7 +77,30 @@ class PyBreadcrumbsInfoProvider : BreadcrumbsInfoProvider() { return HELPERS.firstOrNull { it.type.isInstance(e) && (it as Helper).accepts(e) } as Helper? } - private abstract class Helper(val type: Class) { + private fun currentOffset(e: PsiElement): Int? { + val virtualFile = e.containingFile?.virtualFile ?: return null + val selectedEditor = FileEditorManager.getInstance(e.project).getSelectedEditor(virtualFile) as? TextEditor ?: return null + + return selectedEditor.editor.caretModel.offset + } + + private fun isElementToMoveBackward(e: PsiElement, currentOffset: Int): Boolean { + if (e is PsiWhiteSpace) return true + if (e !is LeafElement || e.startOffset < currentOffset) return false + + val elementType = e.elementType + return elementType == PyTokenTypes.COMMA || elementType in PyTokenTypes.CLOSE_BRACES + } + + private fun moveBackward(e: PsiElement, currentOffset: Int): PsiElement? { + var result = PsiTreeUtil.prevLeaf(e) + while (result != null && isElementToMoveBackward(result, currentOffset)) { + result = PsiTreeUtil.prevLeaf(result) + } + return result + } + + private abstract class Helper(val type: Class) { abstract fun accepts(e: T): Boolean abstract fun elementInfo(e: T): String abstract fun elementTooltip(e: T): String diff --git a/python/testData/breadcrumbs/functionBodyCaret.py b/python/testData/breadcrumbs/functionBodyCaret.py new file mode 100644 index 000000000000..1620d30f3c01 --- /dev/null +++ b/python/testData/breadcrumbs/functionBodyCaret.py @@ -0,0 +1,3 @@ +class A: + def foo(self): + pass diff --git a/python/testData/breadcrumbs/functionBodyCaret_crumbs.txt b/python/testData/breadcrumbs/functionBodyCaret_crumbs.txt new file mode 100644 index 000000000000..4f8eab84e78c --- /dev/null +++ b/python/testData/breadcrumbs/functionBodyCaret_crumbs.txt @@ -0,0 +1,8 @@ +Crumb: +A +Tooltip: +A +Crumb: +foo() +Tooltip: +foo() \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionBodySpaceCaret.py b/python/testData/breadcrumbs/functionBodySpaceCaret.py new file mode 100644 index 000000000000..5774a6368434 --- /dev/null +++ b/python/testData/breadcrumbs/functionBodySpaceCaret.py @@ -0,0 +1,3 @@ +class A: + def foo(self): + pass diff --git a/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction.py b/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction.py new file mode 100644 index 000000000000..f9b9f8f2bafe --- /dev/null +++ b/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction.py @@ -0,0 +1,5 @@ +class A: + def foo(self): + pass + def abc(self): + pass diff --git a/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction_crumbs.txt b/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction_crumbs.txt new file mode 100644 index 000000000000..4f8eab84e78c --- /dev/null +++ b/python/testData/breadcrumbs/functionBodySpaceCaretNewLineFunction_crumbs.txt @@ -0,0 +1,8 @@ +Crumb: +A +Tooltip: +A +Crumb: +foo() +Tooltip: +foo() \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionBodySpaceCaret_crumbs.txt b/python/testData/breadcrumbs/functionBodySpaceCaret_crumbs.txt new file mode 100644 index 000000000000..4f8eab84e78c --- /dev/null +++ b/python/testData/breadcrumbs/functionBodySpaceCaret_crumbs.txt @@ -0,0 +1,8 @@ +Crumb: +A +Tooltip: +A +Crumb: +foo() +Tooltip: +foo() \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction.py b/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction.py new file mode 100644 index 000000000000..578d515d468f --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction.py @@ -0,0 +1,6 @@ +class A: + def foo(self): + pass + + def abc(self): + pass \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction_crumbs.txt b/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction_crumbs.txt new file mode 100644 index 000000000000..ef0370e0adab --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineCaretNewLineFunction_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +A +Tooltip: +A \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction.py b/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction.py new file mode 100644 index 000000000000..82a5bacccb0a --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction.py @@ -0,0 +1,5 @@ +class A: + def foo(self): + pass + def abc(self): + pass \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction_crumbs.txt b/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction_crumbs.txt new file mode 100644 index 000000000000..ef0370e0adab --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineCaretSpaceFunction_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +A +Tooltip: +A \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction.py b/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction.py new file mode 100644 index 000000000000..59209c7fabef --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction.py @@ -0,0 +1,6 @@ +class A: + def foo(self): + pass + + def abc(self): + pass \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction_crumbs.txt b/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction_crumbs.txt new file mode 100644 index 000000000000..ef0370e0adab --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineSpaceCaretNewLineFunction_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +A +Tooltip: +A \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction.py b/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction.py new file mode 100644 index 000000000000..c6c605c2e7a3 --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction.py @@ -0,0 +1,5 @@ +class A: + def foo(self): + pass + def abc(self): + pass \ No newline at end of file diff --git a/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction_crumbs.txt b/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction_crumbs.txt new file mode 100644 index 000000000000..ef0370e0adab --- /dev/null +++ b/python/testData/breadcrumbs/functionNewLineSpaceCaretSpaceFunction_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +A +Tooltip: +A \ No newline at end of file diff --git a/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression.py b/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression.py new file mode 100644 index 000000000000..56a7d6b6dc09 --- /dev/null +++ b/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression.py @@ -0,0 +1,4 @@ +dct = { + "a": 1, + "b": 2 +} \ No newline at end of file diff --git a/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt b/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt new file mode 100644 index 000000000000..322fb118f311 --- /dev/null +++ b/python/testData/breadcrumbs/multiLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +"a" +Tooltip: +"a" \ No newline at end of file diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma.py b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma.py new file mode 100644 index 000000000000..a9b64205ec69 --- /dev/null +++ b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma.py @@ -0,0 +1 @@ +dct = {"a": 1, "b": 2} \ No newline at end of file diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma_crumbs.txt b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterComma_crumbs.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression.py b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression.py new file mode 100644 index 000000000000..62b85729cad4 --- /dev/null +++ b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression.py @@ -0,0 +1 @@ +dct = {"a": 1, "b": 2} \ No newline at end of file diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt new file mode 100644 index 000000000000..322fb118f311 --- /dev/null +++ b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterFirstKeyValueExpression_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +"a" +Tooltip: +"a" \ No newline at end of file diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression.py b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression.py new file mode 100644 index 000000000000..d22e702ac9f5 --- /dev/null +++ b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression.py @@ -0,0 +1 @@ +dct = {"a": 1, "b": 2} \ No newline at end of file diff --git a/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression_crumbs.txt b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression_crumbs.txt new file mode 100644 index 000000000000..bb72e77e46f1 --- /dev/null +++ b/python/testData/breadcrumbs/singleLineDictLiteralCaretAfterSecondKeyValueExpression_crumbs.txt @@ -0,0 +1,4 @@ +Crumb: +"b" +Tooltip: +"b" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/breadcrumbs/PyBreadcrumbsTest.java b/python/testSrc/com/jetbrains/python/breadcrumbs/PyBreadcrumbsTest.java index c40c2a0f3a7f..ad6f0d6becf5 100644 --- a/python/testSrc/com/jetbrains/python/breadcrumbs/PyBreadcrumbsTest.java +++ b/python/testSrc/com/jetbrains/python/breadcrumbs/PyBreadcrumbsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -147,6 +147,50 @@ public class PyBreadcrumbsTest extends PyTestCase { doTest(); } + public void testFunctionBodySpaceCaret() { + doTest(); + } + + public void testFunctionBodyCaret() { + doTest(); + } + + public void testFunctionBodySpaceCaretNewLineFunction() { + doTest(); + } + + public void testFunctionNewLineSpaceCaretNewLineFunction() { + doTest(); + } + + public void testFunctionNewLineCaretNewLineFunction() { + doTest(); + } + + public void testFunctionNewLineSpaceCaretSpaceFunction() { + doTest(); + } + + public void testFunctionNewLineCaretSpaceFunction() { + doTest(); + } + + public void testMultiLineDictLiteralCaretAfterFirstKeyValueExpression() { + doTest(); + } + + public void testSingleLineDictLiteralCaretAfterFirstKeyValueExpression() { + doTest(); + } + + public void testSingleLineDictLiteralCaretAfterComma() { + doTest(); + } + + public void testSingleLineDictLiteralCaretAfterSecondKeyValueExpression() { + doTest(); + } + private void doTest() { final String testName = getTestName(true); diff --git a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java index bcc7fbd998f7..bc27fb1a3a5b 100644 --- a/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java +++ b/xml/impl/src/com/intellij/xml/breadcrumbs/BreadcrumbsXmlWrapper.java @@ -291,7 +291,7 @@ public class BreadcrumbsXmlWrapper implements BreadcrumbsItemListener