From 460b260c93e19776bd5fc3bd7b7e39cd8b5ec34c Mon Sep 17 00:00:00 2001 From: Azim Akhmadjonov Date: Wed, 22 Oct 2025 10:43:29 +0000 Subject: [PATCH] [python] PY-36230 fix duplicate "at" in live decorator template GitOrigin-RevId: f69aae360eac73da13c313d84be8890c3f758cf5 --- .../intellij.python.community.impl.xml | 1 + .../PyDecoratorTemplatePreprocessor.kt | 31 +++++++++++++++++++ .../PyLiveTemplatesExpandingTest.java | 14 +++++++++ 3 files changed, 46 insertions(+) create mode 100644 python/src/com/jetbrains/python/codeInsight/liveTemplates/PyDecoratorTemplatePreprocessor.kt diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index f8024a34b6a3..621c4f9ebcee 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -278,6 +278,7 @@ implementation="com.jetbrains.python.codeInsight.liveTemplates.PythonTemplateContextType$Class"/> + diff --git a/python/src/com/jetbrains/python/codeInsight/liveTemplates/PyDecoratorTemplatePreprocessor.kt b/python/src/com/jetbrains/python/codeInsight/liveTemplates/PyDecoratorTemplatePreprocessor.kt new file mode 100644 index 000000000000..d02b76aa29ce --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/liveTemplates/PyDecoratorTemplatePreprocessor.kt @@ -0,0 +1,31 @@ +// 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.codeInsight.liveTemplates + +import com.intellij.codeInsight.template.impl.TemplatePreprocessor +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiFile +import com.intellij.util.text.CharArrayUtil + +class PyDecoratorTemplatePreprocessor : TemplatePreprocessor { + override fun preprocessTemplate(editor: Editor, file: PsiFile, caretOffset: Int, textToInsert: String, templateText: String) { + if (!templateText.startsWith('@')) { + return + } + + val document: Document = editor.getDocument() + if (caretOffset <= 0) { + return + } + + val text: CharSequence = document.charsSequence + val checkPos = CharArrayUtil.shiftBackward(text, caretOffset - 1, " \t") + + if (checkPos >= 0 && text[checkPos] == '@') { + WriteAction.run { + document.deleteString(checkPos, checkPos + 1) + } + } + } +} diff --git a/python/testSrc/com/jetbrains/python/codeInsight/liveTemplates/PyLiveTemplatesExpandingTest.java b/python/testSrc/com/jetbrains/python/codeInsight/liveTemplates/PyLiveTemplatesExpandingTest.java index f4f5d493c90d..46ba063d98c9 100644 --- a/python/testSrc/com/jetbrains/python/codeInsight/liveTemplates/PyLiveTemplatesExpandingTest.java +++ b/python/testSrc/com/jetbrains/python/codeInsight/liveTemplates/PyLiveTemplatesExpandingTest.java @@ -85,6 +85,20 @@ public class PyLiveTemplatesExpandingTest extends PyTestCase { }); } + // PY-36230 + public void testPropertyDecoratorNoDuplicate() { + myFixture.configureByText("a.py", "class MyClass:\n @prop"); + myFixture.type("\t"); + myFixture.checkResult("class MyClass:\n @property\n def (self):\n return "); + } + + // PY-36230 + public void testPropertyDecoratorNoAtSymbol() { + myFixture.configureByText("a.py", "class MyClass:\n prop"); + myFixture.type("\t"); + myFixture.checkResult("class MyClass:\n @property\n def (self):\n return "); + } + // PY-41231 public void testIterableVariableWithTypeAnnotation() { doMultiFileTest();