PY-40996 Add ability to collapse and expand python type annotations

"Collapse/Expand Python Type Annotations" actions, an intention and a setting

GitOrigin-RevId: e055538b29b07c1836399f52754b786a15351050
This commit is contained in:
lada.gagina
2023-06-16 19:08:16 +00:00
committed by intellij-monorepo-bot
parent 531290213f
commit e768aa3785
12 changed files with 121 additions and 5 deletions
@@ -574,6 +574,10 @@ action.PyDebugger.ViewArray.text=View as Array
action.PyDebugger.CustomizeDataView.text=Customize Data View
action.PythonGenerateDictionaries.text=Generate Python Spellchecker Dictionaries
group.PyPackagingMenu.text=Packaging
action.CollapsePythonTypeAnnotations.text=Collapse Python Type Annotations
action.CollapsePythonTypeAnnotations.description=Collapse python type annotations
action.ExpandPythonTypeAnnotations.text=Expand Python Type Annotations
action.ExpandPythonTypeAnnotations.description=Expand python type annotations
action.CleanPyc.text=Clean Python Compiled Files
action.CleanPyc.description=Delete compiled bytecode files in selected directory and its subdirectories
action.CleanPyc.status.bar.text.deleted.bytecode.files=Deleted {0} bytecode {0,choice,0#files|1#file|2#files}
@@ -1138,6 +1142,7 @@ python.folding.options.title=Python
python.long.string.literals=Long string literals
python.long.collection.literals=Long collection literals
python.sequential.comments=Sequential comments
python.type.annotations=Type annotations
python.provide.a.qualified.name.of.a.module=Provide a qualified name of a module
python.input.file.doesn.t.exist=Input file doesn't exist
python.call.graph=Call Graph
@@ -249,6 +249,12 @@
<categoryKey>INTN.category.python</categoryKey>
</intentionAction>
<intentionAction>
<className>com.jetbrains.python.codeInsight.intentions.PyHideTypeAnnotationsIntention</className>
<bundleName>messages.PyPsiBundle</bundleName>
<categoryKey>INTN.category.python</categoryKey>
</intentionAction>
<intentionAction>
<className>com.jetbrains.python.codeInsight.intentions.SpecifyTypeInPy3AnnotationsIntention</className>
<bundleName>messages.PyPsiBundle</bundleName>
@@ -434,7 +440,7 @@
<bundleName>messages.PyPsiBundle</bundleName>
<categoryKey>INTN.category.python</categoryKey>
</intentionAction>
<registryKey defaultValue="false"
description="Enable pyi stubs distributed with numpy (see https://github.com/numpy/numpy/issues/18565)"
key="enable.numpy.pyi.stubs"/>
@@ -0,0 +1,2 @@
def foo(var: ...) -> ...:
x: ...
@@ -0,0 +1,2 @@
def foo(var: str) -> str:
x: str
@@ -0,0 +1,7 @@
<html>
<body>
<span>
Collapses type annotations in .py files
</span>
</body>
</html>
@@ -371,6 +371,10 @@ INTN.NAME.specify.type.in.docstring=Specify type for reference in docstring
INTN.specify.type.in.docstring=Specify type for the reference in docstring
INTN.specify.return.type.in.docstring=Specify return type in docstring
#HideTypeAnnotationsIntention
INTN.NAME.hide.type.annotations=Hide type annotations
INTN.hide.type.annotations=Hide type annotations
#SpecifyTypeInPy3AnnotationsIntention
INTN.NAME.specify.type.in.annotation=Specify type for reference using annotation
INTN.specify.type.in.annotation=Specify type for the reference using annotation
@@ -0,0 +1,43 @@
// Copyright 2000-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.codeInsight.intentions
import com.intellij.codeInsight.TargetElementUtilBase
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.psi.PyAnnotation
import com.jetbrains.python.psi.PyUtil
/**
* Folds type annotations in Python code
*/
class PyHideTypeAnnotationsIntention : PyBaseIntentionAction() {
override fun getFamilyName(): String {
return PyPsiBundle.message("INTN.NAME.hide.type.annotations")
}
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
val offset = TargetElementUtilBase.adjustOffset(file, editor?.document ?: return false, editor.caretModel.offset)
val element = PyUtil.findNonWhitespaceAtOffset(file, offset)
val annotation = PsiTreeUtil.getParentOfType(element, PyAnnotation::class.java)
text = PyPsiBundle.message("INTN.hide.type.annotations")
return annotation != null
}
override fun doInvoke(project: Project, editor: Editor, file: PsiFile) {
val allRegions = editor.foldingModel.allFoldRegions
val result = allRegions.filter { it.group != null && it.group.toString() == "Python type annotation" }
editor.foldingModel.runBatchFoldingOperation {
for (region in result) {
region.isExpanded = false
}
}
}
override fun startInWriteAction(): Boolean {
return false
}
}
@@ -766,6 +766,9 @@
<add-to-group group-id="ProjectViewPopupMenu" anchor="after" relative-to-action="ProjectViewPopupMenuRefactoringGroup"/>
</action>
<action id="CollapsePythonTypeAnnotations" class="com.jetbrains.python.actions.PyCollapseTypeAnnotationsAction"/>
<action id="ExpandPythonTypeAnnotations" class="com.jetbrains.python.actions.PyExpandTypeAnnotationsAction"/>
<group id="PyPackagingMenu">
<action id="PySyncPythonRequirements" class="com.jetbrains.python.packaging.PySyncPythonRequirementsAction"/>
<action id="CreateSetupPy" class="com.jetbrains.python.packaging.setupPy.CreateSetupPyAction"/>
@@ -6,6 +6,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.lang.folding.CustomFoldingBuilder;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.FoldingGroup;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
@@ -34,6 +35,8 @@ public class PythonFoldingBuilder extends CustomFoldingBuilder implements DumbAw
PyElementTypes.LIST_COMP_EXPRESSION,
PyElementTypes.TUPLE_EXPRESSION);
public static final String PYTHON_TYPE_ANNOTATION_GROUP_NAME = "Python type annotation";
@Override
protected void buildLanguageFoldRegions(@NotNull List<FoldingDescriptor> descriptors,
@NotNull PsiElement root,
@@ -65,6 +68,13 @@ public class PythonFoldingBuilder extends CustomFoldingBuilder implements DumbAw
else if (elementType == PyTokenTypes.END_OF_LINE_COMMENT) {
foldSequentialComments(node, descriptors);
}
else if (elementType == PyElementTypes.ANNOTATION) {
var annotation = node.getPsi();
if (annotation instanceof PyAnnotation pyAnnotation && pyAnnotation.getValue() != null) {
descriptors.add(new FoldingDescriptor(node, pyAnnotation.getValue().getTextRange(),
FoldingGroup.newGroup(PYTHON_TYPE_ANNOTATION_GROUP_NAME)));
}
}
ASTNode child = node.getFirstChildNode();
while (child != null) {
appendDescriptors(child, descriptors);
@@ -220,7 +230,8 @@ public class PythonFoldingBuilder extends CustomFoldingBuilder implements DumbAw
if (isImport(node)) {
return CodeFoldingSettings.getInstance().COLLAPSE_IMPORTS;
}
if (node.getElementType() == PyElementTypes.STRING_LITERAL_EXPRESSION) {
var elementType = node.getElementType();
if (elementType == PyElementTypes.STRING_LITERAL_EXPRESSION) {
if (getDocStringOwnerType(node) == PyElementTypes.FUNCTION_DECLARATION && CodeFoldingSettings.getInstance().COLLAPSE_METHODS) {
// method will be collapsed, no need to also collapse docstring
return false;
@@ -230,13 +241,16 @@ public class PythonFoldingBuilder extends CustomFoldingBuilder implements DumbAw
}
return PythonFoldingSettings.getInstance().isCollapseLongStrings();
}
if (node.getElementType() == PyTokenTypes.END_OF_LINE_COMMENT) {
if (elementType == PyTokenTypes.END_OF_LINE_COMMENT) {
return PythonFoldingSettings.getInstance().isCollapseSequentialComments();
}
if (node.getElementType() == PyElementTypes.STATEMENT_LIST && node.getTreeParent().getElementType() == PyElementTypes.FUNCTION_DECLARATION) {
if (elementType == PyElementTypes.ANNOTATION) {
return PythonFoldingSettings.getInstance().isCollapseTypeAnnotations();
}
if (elementType == PyElementTypes.STATEMENT_LIST && node.getTreeParent().getElementType() == PyElementTypes.FUNCTION_DECLARATION) {
return CodeFoldingSettings.getInstance().COLLAPSE_METHODS;
}
if (FOLDABLE_COLLECTIONS_LITERALS.contains(node.getElementType())) {
if (FOLDABLE_COLLECTIONS_LITERALS.contains(elementType)) {
return PythonFoldingSettings.getInstance().isCollapseLongCollections();
}
return false;
@@ -14,5 +14,6 @@ final class PythonFoldingOptionsProvider extends BeanConfigurable<PythonFoldingS
checkBox(PyBundle.message("python.long.string.literals"), settings::isCollapseLongStrings, v -> settings.COLLAPSE_LONG_STRINGS = v);
checkBox(PyBundle.message("python.long.collection.literals"), settings::isCollapseLongCollections, v -> settings.COLLAPSE_LONG_COLLECTIONS = v);
checkBox(PyBundle.message("python.sequential.comments"), settings::isCollapseSequentialComments, v -> settings.COLLAPSE_SEQUENTIAL_COMMENTS = v);
checkBox(PyBundle.message("python.type.annotations"), settings::isCollapseTypeAnnotations, v -> settings.COLLAPSE_TYPE_ANNOTATIONS = v);
}
}
@@ -15,6 +15,7 @@ public class PythonFoldingSettings implements PersistentStateComponent<PythonFol
public boolean COLLAPSE_LONG_STRINGS;
public boolean COLLAPSE_LONG_COLLECTIONS;
public boolean COLLAPSE_SEQUENTIAL_COMMENTS;
public boolean COLLAPSE_TYPE_ANNOTATIONS;
@Nullable
@Override
@@ -44,4 +45,7 @@ public class PythonFoldingSettings implements PersistentStateComponent<PythonFol
return COLLAPSE_SEQUENTIAL_COMMENTS;
}
public boolean isCollapseTypeAnnotations() {
return COLLAPSE_TYPE_ANNOTATIONS;
}
}
@@ -0,0 +1,25 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.actions
import com.intellij.codeInsight.folding.impl.actions.BaseFoldingHandler
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.jetbrains.python.PythonFoldingBuilder
class PyCollapseTypeAnnotationsAction : EditorAction(PyTypeAnnotationsFoldingHandler(false))
class PyExpandTypeAnnotationsAction : EditorAction(PyTypeAnnotationsFoldingHandler(true))
class PyTypeAnnotationsFoldingHandler(val expand: Boolean) : BaseFoldingHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext) {
val pythonTypeRegions = getFoldRegionsForSelection(editor, caret)
.filter { it.group != null && it.group.toString() == PythonFoldingBuilder.PYTHON_TYPE_ANNOTATION_GROUP_NAME }
editor.foldingModel.runBatchFoldingOperation {
for (region in pythonTypeRegions) {
region.isExpanded = expand
}
}
}
}