Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.jetbrains.python.impl.codeInsight.highlighting;

import com.jetbrains.python.impl.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import consulo.annotation.access.RequiredReadAction;
import consulo.codeEditor.Editor;
import consulo.externalService.statistic.FeatureUsageTracker;
import consulo.ide.impl.idea.codeInsight.highlighting.HighlightUsagesHandler;
import consulo.language.controlFlow.ControlFlow;
import consulo.language.controlFlow.Instruction;
import consulo.language.editor.CodeInsightBundle;
import consulo.language.editor.highlight.usage.HighlightUsagesHandlerBase;
import consulo.language.editor.localize.CodeInsightLocalize;
import consulo.language.editor.util.ProductivityFeatureNames;
import consulo.language.psi.PsiElement;
import consulo.language.psi.PsiFile;
Expand All @@ -42,87 +42,93 @@
* @author oleg
*/
public class PyHighlightExitPointsHandler extends HighlightUsagesHandlerBase<PsiElement> {
private final PsiElement myTarget;

public PyHighlightExitPointsHandler(Editor editor, PsiFile file, PsiElement target) {
super(editor, file);
myTarget = target;
}

public List<PsiElement> getTargets() {
return Collections.singletonList(myTarget);
}
private final PsiElement myTarget;

protected void selectTargets(List<PsiElement> targets, Consumer<List<PsiElement>> selectionConsumer) {
selectionConsumer.accept(targets);
}

public void computeUsages(List<PsiElement> targets) {
FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_HIGHLIGHT_RETURN);
public PyHighlightExitPointsHandler(Editor editor, PsiFile file, PsiElement target) {
super(editor, file);
myTarget = target;
}

PsiElement parent = myTarget.getParent();
if (!(parent instanceof PyReturnStatement)) {
return;
@Override
@RequiredReadAction
public List<PsiElement> getTargets() {
return Collections.singletonList(myTarget);
}

PyFunction function = PsiTreeUtil.getParentOfType(myTarget, PyFunction.class);
if (function == null) {
return;
@Override
protected void selectTargets(List<PsiElement> targets, Consumer<List<PsiElement>> selectionConsumer) {
selectionConsumer.accept(targets);
}

highlightExitPoints((PyReturnStatement)parent, function);
}
@Override
@RequiredReadAction
public void computeUsages(List<PsiElement> targets) {
FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_HIGHLIGHT_RETURN);

@Nullable
private static PsiElement getExitTarget(PsiElement exitStatement) {
if (exitStatement instanceof PyReturnStatement) {
return PsiTreeUtil.getParentOfType(exitStatement, PyFunction.class);
}
else if (exitStatement instanceof PyBreakStatement) {
return ((PyBreakStatement)exitStatement).getLoopStatement();
}
else if (exitStatement instanceof PyContinueStatement) {
return ((PyContinueStatement)exitStatement).getLoopStatement();
}
else if (exitStatement instanceof PyRaiseStatement) {
// TODO[oleg]: Implement better logic here!
return null;
PsiElement parent = myTarget.getParent();
if (!(parent instanceof PyReturnStatement)) {
return;
}

PyFunction function = PsiTreeUtil.getParentOfType(myTarget, PyFunction.class);
if (function == null) {
return;
}

highlightExitPoints((PyReturnStatement) parent, function);
}
return null;
}

private void highlightExitPoints(PyReturnStatement statement,
PyFunction function) {
ControlFlow flow = ControlFlowCache.getControlFlow(function);
Collection<PsiElement> exitStatements = findExitPointsAndStatements(flow);
if (!exitStatements.contains(statement)) {
return;
@Nullable
private static PsiElement getExitTarget(PsiElement exitStatement) {
if (exitStatement instanceof PyReturnStatement returnStmt) {
return PsiTreeUtil.getParentOfType(returnStmt, PyFunction.class);
}
else if (exitStatement instanceof PyBreakStatement breakStmt) {
return breakStmt.getLoopStatement();
}
else if (exitStatement instanceof PyContinueStatement continueStmt) {
return continueStmt.getLoopStatement();
}
else if (exitStatement instanceof PyRaiseStatement) {
// TODO[oleg]: Implement better logic here!
return null;
}
return null;
}

PsiElement originalTarget = getExitTarget(statement);
for (PsiElement exitStatement : exitStatements) {
if (getExitTarget(exitStatement) == originalTarget) {
addOccurrence(exitStatement);
}
@RequiredReadAction
private void highlightExitPoints(PyReturnStatement statement, PyFunction function) {
ControlFlow flow = ControlFlowCache.getControlFlow(function);
Collection<PsiElement> exitStatements = findExitPointsAndStatements(flow);
if (!exitStatements.contains(statement)) {
return;
}

PsiElement originalTarget = getExitTarget(statement);
for (PsiElement exitStatement : exitStatements) {
if (getExitTarget(exitStatement) == originalTarget) {
addOccurrence(exitStatement);
}
}
myStatusText = CodeInsightLocalize.statusBarExitPointsHighlightedMessage(
exitStatements.size(),
HighlightUsagesHandler.getShortcutText()
);
}
myStatusText = CodeInsightBundle.message("status.bar.exit.points.highlighted.message",
exitStatements.size(),
HighlightUsagesHandler.getShortcutText());
}

private static Collection<PsiElement> findExitPointsAndStatements(ControlFlow flow) {
List<PsiElement> statements = new ArrayList<PsiElement>();
Instruction[] instructions = flow.getInstructions();
for (Instruction instruction : instructions[instructions.length - 1].allPred()){
PsiElement element = instruction.getElement();
if (element == null){
continue;
}
PsiElement statement = PyPsiUtils.getStatement(element);
if (statement != null){
statements.add(statement);
}
private static Collection<PsiElement> findExitPointsAndStatements(ControlFlow flow) {
List<PsiElement> statements = new ArrayList<>();
Instruction[] instructions = flow.getInstructions();
for (Instruction instruction : instructions[instructions.length - 1].allPred()) {
PsiElement element = instruction.getElement();
if (element == null) {
continue;
}
PsiElement statement = PyPsiUtils.getStatement(element);
if (statement != null) {
statements.add(statement);
}
}
return statements;
}
return statements;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.jetbrains.python.impl.codeInsight.liveTemplates;

import consulo.annotation.component.ExtensionImpl;
Expand All @@ -24,6 +23,7 @@
import consulo.language.editor.template.Result;
import consulo.language.editor.template.TextResult;
import consulo.language.editor.template.macro.Macro;
import consulo.localize.LocalizeValue;
import consulo.util.lang.StringUtil;

import java.util.ArrayList;
Expand All @@ -33,65 +33,69 @@
* @author yole
*/
@ExtensionImpl
public class CollectionElementNameMacro extends Macro
{
public String getName() {
return "collectionElementName";
}

public String getPresentableName() {
return "collectionElementName()";
}

public String getDefaultValue() {
return "a";
}

public Result calculateResult(Expression[] params, ExpressionContext context) {
if (params.length != 1) {
return null;
public class CollectionElementNameMacro extends Macro {
@Override
public String getName() {
return "collectionElementName";
}
Result paramResult = params[0].calculateResult(context);
if (paramResult == null) {
return null;
}
String param = paramResult.toString();
int lastDot = param.lastIndexOf('.');
if (lastDot >= 0) {
param = param.substring(lastDot+1);

@Override
public LocalizeValue getPresentableName() {
return LocalizeValue.of("collectionElementName()");
}
if (param.endsWith(")")) {
int lastParen = param.lastIndexOf('(');
if (lastParen > 0) {
param = param.substring(0, lastParen);
}

@Override
public String getDefaultValue() {
return "a";
}
String result = smartUnpluralize(param);
return new TextResult(result);
}

private static String smartUnpluralize(String param) {
if (param.endsWith("_list")) {
return param.substring(0, param.length()-5);
@Override
public Result calculateResult(Expression[] params, ExpressionContext context) {
if (params.length != 1) {
return null;
}
Comment on lines +52 to +56
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateResult can return null (e.g., when params.length != 1 or paramResult == null), but the override isn't annotated as nullable. Consider adding @Nullable (and importing it if needed) to match the other macros in this package and keep nullability analysis accurate.

Copilot uses AI. Check for mistakes.
Result paramResult = params[0].calculateResult(context);
if (paramResult == null) {
return null;
}
String param = paramResult.toString();
int lastDot = param.lastIndexOf('.');
if (lastDot >= 0) {
param = param.substring(lastDot + 1);
}
if (param.endsWith(")")) {
int lastParen = param.lastIndexOf('(');
if (lastParen > 0) {
param = param.substring(0, lastParen);
}
}
String result = smartUnpluralize(param);
return new TextResult(result);
}
String result = StringUtil.unpluralize(param);
return result == null ? param : result;
}

public LookupElement[] calculateLookupItems(Expression[] params, ExpressionContext context) {
Result result = calculateResult(params, context);
if (result == null) {
return null;
private static String smartUnpluralize(String param) {
if (param.endsWith("_list")) {
return param.substring(0, param.length() - 5);
}
String result = StringUtil.unpluralize(param);
return result == null ? param : result;
}
String[] words = result.toString().split("_");
if (words.length > 1) {
List<LookupElement> lookup = new ArrayList<LookupElement>();
for(int i=0; i<words.length; i++) {
String element = StringUtil.join(words, i, words.length, "_");
lookup.add(LookupElementBuilder.create(element));
}
return lookup.toArray(new LookupElement[lookup.size()]);

@Override
public LookupElement[] calculateLookupItems(Expression[] params, ExpressionContext context) {
Result result = calculateResult(params, context);
if (result == null) {
return null;
}
String[] words = result.toString().split("_");
if (words.length > 1) {
List<LookupElement> lookup = new ArrayList<>();
for (int i = 0; i < words.length; i++) {
String element = StringUtil.join(words, i, words.length, "_");
lookup.add(LookupElementBuilder.create(element));
}
return lookup.toArray(new LookupElement[lookup.size()]);
}
return null;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.jetbrains.python.impl.codeInsight.liveTemplates;

import com.jetbrains.python.psi.PyClass;
import consulo.annotation.access.RequiredReadAction;
import consulo.annotation.component.ExtensionImpl;
import consulo.language.editor.template.Expression;
import consulo.language.editor.template.ExpressionContext;
Expand All @@ -27,37 +27,39 @@
import consulo.language.psi.PsiElement;
import consulo.language.psi.util.PsiTreeUtil;

import consulo.localize.LocalizeValue;
import org.jspecify.annotations.Nullable;

/**
* @author yole
*/
@ExtensionImpl
public class PyClassNameMacro extends Macro {
@Override
public String getName() {
return "pyClassName";
}
@Override
public String getName() {
return "pyClassName";
}

@Override
public String getPresentableName() {
return "pyClassName()";
}
@Override
public LocalizeValue getPresentableName() {
return LocalizeValue.of("pyClassName()");
}

@Nullable
@Override
public Result calculateResult(Expression[] params, ExpressionContext context) {
PsiElement place = context.getPsiElementAtStartOffset();
PyClass pyClass = PsiTreeUtil.getParentOfType(place, PyClass.class);
if (pyClass == null) {
return null;
@Nullable
@Override
@RequiredReadAction
public Result calculateResult(Expression[] params, ExpressionContext context) {
PsiElement place = context.getPsiElementAtStartOffset();
PyClass pyClass = PsiTreeUtil.getParentOfType(place, PyClass.class);
if (pyClass == null) {
return null;
}
String name = pyClass.getName();
return name == null ? null : new TextResult(name);
}
String name = pyClass.getName();
return name == null ? null : new TextResult(name);
}

@Override
public boolean isAcceptableInContext(TemplateContextType context) {
return context instanceof PythonTemplateContextType;
}
@Override
public boolean isAcceptableInContext(TemplateContextType context) {
return context instanceof PythonTemplateContextType;
}
}
Loading
Loading