mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-63802 Implement PyChangeLocalityDetector to fix disappearing highlighting when changing whitespace at the top level
By default, when only changing whitespace at the top level, DefaultChangeLocalityDetector would trigger and say that only the changed whitespace should be rehighlighted. However, highlighting for a whole file would still be dropped. After that, PyUnusedLocalInspection would not be triggered for any function in that file, meaning it would not re-highlight them, leading to an inspection warning disappearing. Now, PyChangeLocalityDetector triggers when changing a whitespace on a top level and triggers re-highlighting of the whole file Performance consideration: re-highlighting the whole file might be expensive (that's why ChangeLocalityDetector was introduced in the first place). However, no other ChangeLocalityDetector is implemented for Python, so most of the time the whole file is re-highlighted anyways. Re-highlighting the whole file in a new niche case of changing whitespace at the end of the file shouldn't lead to any significant performance impact. GitOrigin-RevId: 8f26c59da8501f8eac78d1280888dd49ffef28d4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
43b8e822c8
commit
bcf0ff8249
@@ -189,6 +189,8 @@
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.PyBuiltinHighlightingAnnotator"/>
|
||||
<annotator language="Python" implementationClass="com.jetbrains.python.validation.UnsupportedFeatures"/>
|
||||
|
||||
<daemon.changeLocalityDetector implementation="com.jetbrains.python.inspections.PyChangeLocalityDetector"/>
|
||||
|
||||
<localInspection language="Python" shortName="PyUnusedLocalInspection" suppressId="PyUnusedLocal" bundle="messages.PyPsiBundle" key="INSP.NAME.unused" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.unusedLocal.PyUnusedLocalInspection"/>
|
||||
<localInspection language="Python" shortName="PyRedundantParenthesesInspection" suppressId="PyRedundantParentheses" bundle="messages.PyPsiBundle" key="INSP.NAME.redundant.parentheses" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyRedundantParenthesesInspection"/>
|
||||
<localInspection language="Python" shortName="PySimplifyBooleanCheckInspection" suppressId="PySimplifyBooleanCheck" bundle="messages.PyPsiBundle" key="INSP.NAME.check.can.be.simplified" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PySimplifyBooleanCheckInspection"/>
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.jetbrains.python.inspections
|
||||
|
||||
import com.intellij.codeInsight.daemon.ChangeLocalityDetector
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
|
||||
class PyChangeLocalityDetector : ChangeLocalityDetector {
|
||||
override fun getChangeHighlightingDirtyScopeFor(changedElement: PsiElement): PsiElement? {
|
||||
if (changedElement is PsiWhiteSpace) {
|
||||
val parent = changedElement.parent
|
||||
if (parent is PyFile) {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.jetbrains.python.inspections
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.jetbrains.python.fixtures.PyTestCase
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import junit.framework.TestCase
|
||||
|
||||
class PyChangeLocalityDetectorTest : PyTestCase() {
|
||||
|
||||
fun `test top level function body change`() = checkChangeScope("""
|
||||
def f():
|
||||
<selection>x = 1</selection>
|
||||
""", null)
|
||||
|
||||
fun `test top level function whitespace at the start change`() = checkChangeScope("""
|
||||
<selection>
|
||||
</selection>def f():
|
||||
x = 1
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""".trimIndent(), """
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""".trimIndent())
|
||||
|
||||
fun `test top level function whitespace at the end change`() = checkChangeScope("""
|
||||
def f():
|
||||
x = 1<selection>
|
||||
|
||||
</selection>def bar():
|
||||
pass
|
||||
""".trimIndent(), """
|
||||
def f():
|
||||
x = 1
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""".trimIndent())
|
||||
|
||||
fun `test top level class whitespace at the end change`() = checkChangeScope("""
|
||||
class A:
|
||||
x = 1<selection>
|
||||
|
||||
</selection>def bar():
|
||||
pass
|
||||
""".trimIndent(), """
|
||||
class A:
|
||||
x = 1
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""".trimIndent())
|
||||
|
||||
private fun checkChangeScope(text: String, expectedScope: String?) {
|
||||
myFixture.configureByText("main.py", text)
|
||||
|
||||
val selectionModel = myFixture.editor.selectionModel
|
||||
assertTrue("changed item has to be specified in <selection></selection>", selectionModel.hasSelection())
|
||||
|
||||
var element = PsiTreeUtil.findElementOfClassAtRange(
|
||||
myFixture.file,
|
||||
selectionModel.selectionStart,
|
||||
selectionModel.selectionEnd,
|
||||
PsiElement::class.java
|
||||
) ?: error("No PsiElement at selection range")
|
||||
|
||||
val changeLocalityDetector = PyChangeLocalityDetector()
|
||||
|
||||
/**
|
||||
* Emulate [com.intellij.codeInsight.daemon.impl.PsiChangeHandler.updateByChange] logic
|
||||
* by walking up the tree and calling [com.intellij.codeInsight.daemon.ChangeLocalityDetector.getChangeHighlightingDirtyScopeFor]
|
||||
* until non-null is returned
|
||||
*/
|
||||
while (element !is PyFile) {
|
||||
val dirtyScope = changeLocalityDetector.getChangeHighlightingDirtyScopeFor(element)
|
||||
if (dirtyScope != null) {
|
||||
if (expectedScope == null) {
|
||||
throw IllegalStateException("scope was calculated when no scope was expected")
|
||||
}
|
||||
|
||||
TestCase.assertEquals(expectedScope.trimIndent(), dirtyScope.text)
|
||||
return
|
||||
}
|
||||
element = element.parent
|
||||
}
|
||||
|
||||
if (expectedScope != null) {
|
||||
throw IllegalStateException("scope wasn't calculated")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user