From 3c6c774996c1709382c1898cdda918b25d57ca6f Mon Sep 17 00:00:00 2001 From: Azim Akhmadjonov Date: Wed, 14 Jan 2026 22:58:04 +0000 Subject: [PATCH] PY-83580 support for word selection in f-string fragments GitOrigin-RevId: 8f5a27929a1111c80831d3e49d7acec5626b8e66 --- .../resources/intellij.python.syntax.xml | 1 + .../selectWord/PyFStringSelectionHandler.kt | 57 +++++++++++++++++++ .../after1.py | 1 + .../after2.py | 1 + .../after3.py | 1 + .../after4.py | 1 + .../before.py | 1 + .../jetbrains/python/PySelectWordTest.java | 5 ++ 8 files changed, 68 insertions(+) create mode 100644 python/python-syntax/src/com/jetbrains/python/editor/selectWord/PyFStringSelectionHandler.kt create mode 100644 python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after1.py create mode 100644 python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after2.py create mode 100644 python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after3.py create mode 100644 python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after4.py create mode 100644 python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/before.py diff --git a/python/python-syntax/resources/intellij.python.syntax.xml b/python/python-syntax/resources/intellij.python.syntax.xml index dc695ea19edd..a6da3c9d748c 100644 --- a/python/python-syntax/resources/intellij.python.syntax.xml +++ b/python/python-syntax/resources/intellij.python.syntax.xml @@ -17,6 +17,7 @@ + diff --git a/python/python-syntax/src/com/jetbrains/python/editor/selectWord/PyFStringSelectionHandler.kt b/python/python-syntax/src/com/jetbrains/python/editor/selectWord/PyFStringSelectionHandler.kt new file mode 100644 index 000000000000..86f9d256d25b --- /dev/null +++ b/python/python-syntax/src/com/jetbrains/python/editor/selectWord/PyFStringSelectionHandler.kt @@ -0,0 +1,57 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.editor.selectWord + +import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandlerBase +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import com.jetbrains.python.ast.PyAstExpression +import com.jetbrains.python.ast.PyAstFStringFragment +import com.jetbrains.python.ast.PyAstFormattedStringElement + + +class PyFStringSelectionHandler : ExtendWordSelectionHandlerBase() { + + override fun canSelect(e: PsiElement): Boolean { + return PsiTreeUtil.getParentOfType(e, PyAstFStringFragment::class.java, false) != null + } + + override fun select( + e: PsiElement, + editorText: CharSequence, + cursorOffset: Int, + editor: Editor + ): List? { + val fragment: PyAstFStringFragment = + PsiTreeUtil.getParentOfType(e, PyAstFStringFragment::class.java, false) + ?: return null + + val expression: PyAstExpression = fragment.expression ?: return null + val exprRange: TextRange = expression.textRange + + val result = mutableListOf() + + result.add(exprRange) + + val typeConversionPsi = fragment.typeConversion + if (typeConversionPsi != null) { + val typeConversionRange = typeConversionPsi.textRange + result.add(TextRange(exprRange.startOffset, typeConversionRange.endOffset)) + } + + val formatPartPsi = fragment.formatPart + if (formatPartPsi != null) { + val formatTextRange = formatPartPsi.textRange + result.add(TextRange(exprRange.startOffset, formatTextRange.endOffset)) + } + + result.add(fragment.textRange) + + val parent = fragment.parent + if (parent is PyAstFormattedStringElement) { + result.add(parent.textRange) + } + return result + } +} \ No newline at end of file diff --git a/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after1.py b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after1.py new file mode 100644 index 000000000000..0f49d1318349 --- /dev/null +++ b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after1.py @@ -0,0 +1 @@ +s = f"pipeline:1:{ref!s:{width}}" \ No newline at end of file diff --git a/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after2.py b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after2.py new file mode 100644 index 000000000000..c58f5a8860a8 --- /dev/null +++ b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after2.py @@ -0,0 +1 @@ +s = f"pipeline:1:{ref!s:{width}}" \ No newline at end of file diff --git a/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after3.py b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after3.py new file mode 100644 index 000000000000..da3ec501776d --- /dev/null +++ b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after3.py @@ -0,0 +1 @@ +s = f"pipeline:1:{ref!s:{width}}" \ No newline at end of file diff --git a/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after4.py b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after4.py new file mode 100644 index 000000000000..52f865292a2b --- /dev/null +++ b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/after4.py @@ -0,0 +1 @@ +s = f"pipeline:1:{ref!s:{width}}" \ No newline at end of file diff --git a/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/before.py b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/before.py new file mode 100644 index 000000000000..918dde9b7caf --- /dev/null +++ b/python/testData/selectWord/fStringFragmentExpressionTypeConversionAndFormatSelection/before.py @@ -0,0 +1 @@ +s = f"pipeline:1:{ref!s:{width}}" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PySelectWordTest.java b/python/testSrc/com/jetbrains/python/PySelectWordTest.java index 39c443ddc861..11b03008f54a 100644 --- a/python/testSrc/com/jetbrains/python/PySelectWordTest.java +++ b/python/testSrc/com/jetbrains/python/PySelectWordTest.java @@ -38,6 +38,11 @@ public class PySelectWordTest extends PyTestCase { doTest(); } + // PY-83580 + public void testFStringFragmentExpressionTypeConversionAndFormatSelection() { + doTest(); + } + private void doTest() { CodeInsightTestUtil.doWordSelectionTestOnDirectory(myFixture, "selectWord/" + getTestName(true), "py"); }