Merge remote-tracking branch 'origin/master'
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Wed, 2 Apr 2014 15:38:34 +0000 (19:38 +0400)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Wed, 2 Apr 2014 15:38:34 +0000 (19:38 +0400)
12 files changed:
python/src/META-INF/python-core.xml
python/src/com/jetbrains/python/findUsages/PythonFindUsagesProvider.java
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralElementDescriptionProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralExtensionPoint.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralFindUsagesHandlerFactory.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralReferenceSearcher.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralRenameProcessor.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTargetElementUtil.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralUsageTargetProvider.java [new file with mode: 0644]
python/src/com/jetbrains/python/magicLiteral/package-info.java [new file with mode: 0644]
python/src/com/jetbrains/python/psi/PyUtil.java

index 5cfefea9c6846794b8a4d0c445876b78d5cbc70c..cd901032d776bd48615bd7e9a5fde0d6de3d14ba 100644 (file)
     <filePropertyPusher implementation="com.jetbrains.python.psi.impl.PythonLanguageLevelPusher"/>
 
     <elementDescriptionProvider implementation="com.jetbrains.python.findUsages.PyElementDescriptionProvider"/>
+    <elementDescriptionProvider implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralElementDescriptionProvider"/>
     <fileStructureGroupRuleProvider implementation="com.jetbrains.python.findUsages.PyFunctionGroupingRuleProvider" id="py-function"/>
     <usageTypeProvider implementation="com.jetbrains.python.findUsages.PyUsageTypeProvider"/>
+    <usageTargetProvider implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralUsageTargetProvider"/>
     <importFilteringRule implementation="com.jetbrains.python.findUsages.PyImportFilteringRule"/>
 
     <multiHostInjector implementation="com.jetbrains.python.codeInsight.regexp.PythonRegexpInjector"/>
                   order="first, before completionAutoPopup"/>
 
     <referencesSearch implementation="com.jetbrains.python.psi.search.PyInitReferenceSearchExecutor"/>
+    <referencesSearch implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralReferenceSearcher"/>
     <referencesSearch implementation="com.jetbrains.python.psi.search.PyKeywordArgumentSearchExecutor"/>
     <referencesSearch implementation="com.jetbrains.python.psi.search.PyStringReferenceSearch"/>
     <findUsagesHandlerFactory implementation="com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory" id="Python"
                               order="last, before default"/>
     <readWriteAccessDetector implementation="com.jetbrains.python.findUsages.PyReadWriteAccessDetector"/>
+    <findUsagesHandlerFactory implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralFindUsagesHandlerFactory"/>
 
     <renamePsiElementProcessor implementation="com.jetbrains.python.refactoring.rename.RenamePyVariableProcessor" order="last" id="pyvar"/>
     <renamePsiElementProcessor implementation="com.jetbrains.python.refactoring.rename.RenamePyFunctionProcessor" order="before pyvar"/>
     <renamePsiElementProcessor implementation="com.jetbrains.python.refactoring.rename.RenamePyClassProcessor" order="before pyvar"/>
+    <renamePsiElementProcessor implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralRenameProcessor" order="before pyvar"/>
     <renamePsiElementProcessor implementation="com.jetbrains.python.refactoring.rename.RenamePyFileProcessor" order="first"/>
 
     <automaticRenamerFactory implementation="com.jetbrains.python.refactoring.rename.PyContainingFileRenamerFactory"/>
                    serviceImplementation="com.jetbrains.python.packaging.PyPackageRequirementsSettings"/>
   </extensions>
 
-  <extensionPoints>
+  <extensionPoints>    
     <extensionPoint qualifiedName="Pythonid.importResolver" interface="com.jetbrains.python.psi.impl.PyImportResolver"/>
+    <extensionPoint qualifiedName="Pythonid.magicLiteral" interface="com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint"/>
     <extensionPoint qualifiedName="Pythonid.resolveResultRater" interface="com.jetbrains.python.psi.impl.PyResolveResultRater"/>
     <extensionPoint qualifiedName="Pythonid.typeProvider" interface="com.jetbrains.python.psi.impl.PyTypeProvider"/>
     <extensionPoint qualifiedName="Pythonid.pySuperMethodsSearch" interface="com.intellij.util.QueryExecutor"/>
index 4805297cecf3bffd4a9452554bce601532fa8ab9..ffc0c5875d4f517813cad9f403b09483dd7d7328 100644 (file)
@@ -20,15 +20,24 @@ import com.intellij.lang.findUsages.FindUsagesProvider;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiNamedElement;
 import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint;
+import com.jetbrains.python.magicLiteral.PyMagicLiteralTools;
 import com.jetbrains.python.psi.*;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
+ * TODO: Create strategies instead of chain of instanceof
+ *
  * @author yole
  */
 public class PythonFindUsagesProvider implements FindUsagesProvider {
-  public boolean canFindUsagesFor(@NotNull PsiElement psiElement) {
-    return psiElement instanceof PsiNamedElement || psiElement instanceof PyReferenceExpression;
+  @Override
+  public boolean canFindUsagesFor(@NotNull final PsiElement psiElement) {
+    if (PyMagicLiteralTools.isMagicLiteral(psiElement)) {
+      return true;
+    }
+    return (psiElement instanceof PsiNamedElement) || (psiElement instanceof PyReferenceExpression);
   }
 
   public String getHelpId(@NotNull PsiElement psiElement) {
@@ -46,9 +55,14 @@ public class PythonFindUsagesProvider implements FindUsagesProvider {
 
   @NotNull
   public String getType(@NotNull PsiElement element) {
+    String literalString = tryFindMagicLiteralString(element, false);
+    if (literalString != null) {
+      return literalString;
+    }
+
     if (element instanceof PyNamedParameter) return "parameter";  //TODO: replace strings to messages
     if (element instanceof PyFunction) {
-      if (((PyFunction) element).getContainingClass() != null) {
+      if (((PyFunction)element).getContainingClass() != null) {
         return "method";
       }
       return "function";
@@ -70,6 +84,11 @@ public class PythonFindUsagesProvider implements FindUsagesProvider {
 
   @NotNull
   public String getDescriptiveName(@NotNull PsiElement element) {
+    String literalString = tryFindMagicLiteralString(element, true);
+    if (literalString != null) {
+      return literalString;
+    }
+
     if (element instanceof PsiNamedElement) {
       final String name = ((PsiNamedElement)element).getName();
       return name == null ? "<unnamed>" : name;
@@ -111,7 +130,31 @@ public class PythonFindUsagesProvider implements FindUsagesProvider {
     }
   }
 
+  @Override
   public WordsScanner getWordsScanner() {
     return new PyWordsScanner();
   }
+
+
+  /**
+   * Finds text to display to user for element if element is magic literal
+   * @param element element to check
+   * @param obtainValue display element value (will display element type otherwise)
+   * @return text (if found) or null
+   */
+  @Nullable
+  private static String tryFindMagicLiteralString(@NotNull final PsiElement element, final boolean obtainValue) {
+    if (element instanceof PyStringLiteralExpression) {
+      final PyMagicLiteralExtensionPoint point = PyMagicLiteralTools.getPoint((PyStringLiteralExpression)element);
+      if (point != null) {
+        if (obtainValue) {
+          return ((StringLiteralExpression)element).getStringValue();
+        }
+        else {
+          return point.getLiteralType();
+        }
+      }
+    }
+    return null;
+  }
 }
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralElementDescriptionProvider.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralElementDescriptionProvider.java
new file mode 100644 (file)
index 0000000..9b303da
--- /dev/null
@@ -0,0 +1,37 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.psi.ElementDescriptionLocation;
+import com.intellij.psi.ElementDescriptionProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.usageView.UsageViewLongNameLocation;
+import com.intellij.usageView.UsageViewShortNameLocation;
+import com.intellij.usageView.UsageViewTypeLocation;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import com.jetbrains.python.psi.StringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Provides description for magic literals.
+ * <strong>Install it</strong> as {@link ElementDescriptionProvider#EP_NAME}
+ *
+ * @author Ilya.Kazakevich
+ */
+class PyMagicLiteralElementDescriptionProvider implements ElementDescriptionProvider {
+  @Nullable
+  @Override
+  public String getElementDescription(@NotNull final PsiElement element, @NotNull final ElementDescriptionLocation location) {
+    if (element instanceof PyStringLiteralExpression) {
+      final PyMagicLiteralExtensionPoint point = PyMagicLiteralTools.getPoint((PyStringLiteralExpression)element);
+      if (point != null) {
+        if (location instanceof UsageViewTypeLocation) {
+          return point.getLiteralType();
+        }
+        if ((location instanceof UsageViewShortNameLocation) || (location instanceof UsageViewLongNameLocation)) {
+          return ((StringLiteralExpression)element).getStringValue();
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralExtensionPoint.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralExtensionPoint.java
new file mode 100644 (file)
index 0000000..3adf05f
--- /dev/null
@@ -0,0 +1,32 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Any magic literal extension point should imlement this interface and be installed as extesnion point
+ * using {@link #EP_NAME}
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface PyMagicLiteralExtensionPoint {
+
+  ExtensionPointName<PyMagicLiteralExtensionPoint> EP_NAME = ExtensionPointName.create("Pythonid.magicLiteral");
+
+
+  /**
+   * Checks if literal is magic and supported by this extension point.
+   * @param element element to check
+   * @return true if magic.
+   */
+  boolean isMagicLiteral(@NotNull PyStringLiteralExpression element);
+
+
+  /**
+   * @return human-readable type of this literal. Actually, that is extension point name
+   */
+  @NotNull
+  String getLiteralType();
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralFindUsagesHandlerFactory.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralFindUsagesHandlerFactory.java
new file mode 100644 (file)
index 0000000..006f4f3
--- /dev/null
@@ -0,0 +1,41 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.find.findUsages.FindUsagesHandler;
+import com.intellij.find.findUsages.FindUsagesHandlerFactory;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.StringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Handles find usage for magic literals.
+ * <strong>Install it</strong> as {@link FindUsagesHandlerFactory#EP_NAME}
+ * @author Ilya.Kazakevich
+ */
+public class PyMagicLiteralFindUsagesHandlerFactory extends FindUsagesHandlerFactory {
+  @Override
+  public boolean canFindUsages(@NotNull final PsiElement element) {
+    return PyMagicLiteralTools.isMagicLiteral(element);
+  }
+
+  @Nullable
+  @Override
+  public FindUsagesHandler createFindUsagesHandler(@NotNull final PsiElement element, final boolean forHighlightUsages) {
+    return new MyFindUsagesHandler(element);
+  }
+
+  private static class MyFindUsagesHandler extends FindUsagesHandler {
+    MyFindUsagesHandler(final PsiElement element) {
+      super(element);
+    }
+
+    @Nullable
+    @Override
+    protected Collection<String> getStringsToSearch(final PsiElement element) {
+      return Collections.singleton(((StringLiteralExpression)element).getStringValue());
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralReferenceSearcher.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralReferenceSearcher.java
new file mode 100644 (file)
index 0000000..bde6596
--- /dev/null
@@ -0,0 +1,38 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.openapi.application.QueryExecutorBase;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.search.searches.ReferencesSearch;
+import com.intellij.util.Processor;
+import com.jetbrains.python.psi.StringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Searches for string usages on magic literals.
+ * <strong>Install it</strong> as "referencesSearch" !
+ * @author Ilya.Kazakevich
+ */
+class PyMagicLiteralReferenceSearcher extends QueryExecutorBase<PsiReference, ReferencesSearch.SearchParameters>  {
+
+  @Override
+  public void processQuery(@NotNull final ReferencesSearch.SearchParameters queryParameters, @NotNull final Processor<PsiReference> consumer) {
+    new ReadAction() {
+      @Override
+      protected void run(@NotNull final Result result) throws Throwable {
+        final PsiElement refElement = queryParameters.getElementToSearch();
+        if (PyMagicLiteralTools.isMagicLiteral(refElement)) {
+          final String refText = ((StringLiteralExpression)refElement).getStringValue();
+          if (!StringUtil.isEmpty(refText)) {
+            final SearchScope searchScope = queryParameters.getEffectiveSearchScope();
+            queryParameters.getOptimizer().searchWord(refText, searchScope, true, refElement);
+          }
+        }
+      }
+    }.execute();
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralRenameProcessor.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralRenameProcessor.java
new file mode 100644 (file)
index 0000000..a6b66d4
--- /dev/null
@@ -0,0 +1,39 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.google.common.base.Preconditions;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.refactoring.listeners.RefactoringElementListener;
+import com.intellij.refactoring.rename.RenamePsiElementProcessor;
+import com.intellij.usageView.UsageInfo;
+import com.jetbrains.python.psi.PyElementGenerator;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Supports renaming for magic literals.
+ * <strong>Install it</strong> as {@link com.intellij.refactoring.rename.RenamePsiElementProcessor#EP_NAME}
+ * @author Ilya.Kazakevich
+ */
+class PyMagicLiteralRenameProcessor extends RenamePsiElementProcessor {
+  @Override
+  public boolean canProcessElement(@NotNull final PsiElement element) {
+    return (PyMagicLiteralTools.isMagicLiteral(element));
+  }
+
+  @Override
+  public void renameElement(final PsiElement element,
+                            final String newName,
+                            final UsageInfo[] usages,
+                            @Nullable final RefactoringElementListener listener) {
+    Preconditions.checkArgument(canProcessElement(element), "Element can't be renamed, call #canProcessElement first " + element);
+    element.replace(PyElementGenerator.getInstance(element.getProject()).createStringLiteral((PyStringLiteralExpression)element, newName));
+    for (final UsageInfo usage : usages) {
+      final PsiReference reference = usage.getReference();
+      if (reference != null) {
+        reference.handleElementRename(newName);
+      }
+    }
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTargetElementUtil.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTargetElementUtil.java
new file mode 100644 (file)
index 0000000..926b758
--- /dev/null
@@ -0,0 +1,26 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.codeInsight.TargetElementUtilBase;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Supports name literal obtaining for magic literals.
+ * <strong>Install it</strong> as {@link com.intellij.codeInsight.TargetElementUtilBase} service implementation
+ * @author Ilya.Kazakevich
+ */
+class PyMagicLiteralTargetElementUtil extends TargetElementUtilBase {
+
+
+  @Nullable
+  @Override
+  protected PsiElement getNamedElement(@Nullable final PsiElement element) {
+    final PyStringLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(element, PyStringLiteralExpression.class);
+    if ((literalExpression != null) && (PyMagicLiteralTools.isMagicLiteral(literalExpression))) {
+      return literalExpression;
+    }
+    return super.getNamedElement(element);
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralTools.java
new file mode 100644 (file)
index 0000000..58118a9
--- /dev/null
@@ -0,0 +1,47 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Tools that help user to work with magic literals.
+ *
+ * @author Ilya.Kazakevich
+ */
+public final class PyMagicLiteralTools {
+  private PyMagicLiteralTools() {
+  }
+
+
+  /**
+   * Checks if literal is magic (there is some extension point that supports it)
+   *
+   * @param element element to check
+   * @return true if magic
+   */
+  public static boolean isMagicLiteral(@NotNull final PsiElement element) {
+    return (element instanceof PyStringLiteralExpression) && (getPoint((PyStringLiteralExpression)element) != null);
+  }
+
+  /**
+   * Gets extension point by literal.
+   *
+   * @param element literal
+   * @return extension point (if any) or null if literal is unknown to all installed magic literal extesnion points
+   */
+  @Nullable
+  public static PyMagicLiteralExtensionPoint getPoint(@NotNull final PyStringLiteralExpression element) {
+    final PyMagicLiteralExtensionPoint[] magicLiteralExtPoints =
+      ApplicationManager.getApplication().getExtensions(PyMagicLiteralExtensionPoint.EP_NAME);
+
+    for (final PyMagicLiteralExtensionPoint magicLiteralExtensionPoint : magicLiteralExtPoints) {
+      if (magicLiteralExtensionPoint.isMagicLiteral(element)) {
+        return magicLiteralExtensionPoint;
+      }
+    }
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralUsageTargetProvider.java b/python/src/com/jetbrains/python/magicLiteral/PyMagicLiteralUsageTargetProvider.java
new file mode 100644 (file)
index 0000000..2457206
--- /dev/null
@@ -0,0 +1,39 @@
+package com.jetbrains.python.magicLiteral;
+
+import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.usages.UsageTarget;
+import com.intellij.usages.UsageTargetProvider;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Supports usage info for magic literals.
+ * <p/>
+ * <strong>Install it</strong> as "usageTargetProvider" !
+ *
+ * @author Ilya.Kazakevich
+ */
+class PyMagicLiteralUsageTargetProvider implements UsageTargetProvider {
+  @Nullable
+  @Override
+  public UsageTarget[] getTargets(final Editor editor, final PsiFile file) {
+    final PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
+    if (element != null) {
+      final PyStringLiteralExpression literal = PsiTreeUtil.getParentOfType(element, PyStringLiteralExpression.class);
+      if ((literal != null) && PyMagicLiteralTools.isMagicLiteral(literal)) {
+        return new UsageTarget[]{new PsiElement2UsageTargetAdapter(literal)};
+      }
+    }
+    return UsageTarget.EMPTY_ARRAY;
+  }
+
+  @Nullable
+  @Override
+  public UsageTarget[] getTargets(final PsiElement psiElement) {
+    return UsageTarget.EMPTY_ARRAY;
+  }
+}
diff --git a/python/src/com/jetbrains/python/magicLiteral/package-info.java b/python/src/com/jetbrains/python/magicLiteral/package-info.java
new file mode 100644 (file)
index 0000000..e0c1447
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Magic literals are literals that could be used as reference targets.
+ * There are some references that resolve to literals.
+ * Find Usage and Rename should work for them.
+ * This package provides full support for such literals allowing developers to "inject" special knowledge via extension points.
+ * To add new "magic literal" just implement {@link com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint} and
+ * add it as extension point.
+ * To check for some literal if it is magic, use {@link com.jetbrains.python.magicLiteral.PyMagicLiteralTools}
+ * or obtain extension point directly (tools class simplifies usages).
+ *
+ * Several components and extension point implementations exist in this package. Be sure to install all of them to make
+ * this package work.
+ * After that, you only need to implement and inject  {@link com.jetbrains.python.magicLiteral.PyMagicLiteralTools}
+ *
+ *
+ *
+ * <p>
+ *   <h2>1. To make your app support this package</h2>
+ *   Be sure to read all classes in this package and install them to proper places.
+ *   For example, package would not work with out of {@link com.jetbrains.python.magicLiteral.PyMagicLiteralReferenceSearcher}
+ *   installed
+ * </p>
+ * <p>
+ *   <h2>2. To support new magic literal</h2>
+ *   Say, you know that literal assigned to variable named "spam" is called "spam literal" and some references around the
+ *   code are resolved to it. You want "find usage" and "rename" work for it.
+ *   Just implement {@link com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint} and inject it as extension point.
+ *   <strong>Warning</strong>: install this package as described in first step
+ * </p>
+ * <p>
+ *   <h2>3. To check if literal is magic</h2>
+ *   Say, you have some {@link com.jetbrains.python.psi.PyStringLiteralExpression} and want to check if it is magic or not,
+ *   and if it is -- find its name ("spam literal" for previous example).
+ *   You use {@link com.jetbrains.python.magicLiteral.PyMagicLiteralTools} for that, because may magic literal extension points
+ *   could be installed.
+ *   <strong>Warning</strong>: install this package as described in first step
+ * </p>
+ *
+ *
+ * @see com.jetbrains.python.magicLiteral.PyMagicLiteralTools
+ * @see com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint
+ * */
+package com.jetbrains.python.magicLiteral;
\ No newline at end of file
index 2c06bfdc9b078e9a075fc1065bc08ed417e0b1fd..ec2ac31cb5eae5f2cfd631c066fdfd44151bb845 100644 (file)
@@ -64,6 +64,7 @@ import com.jetbrains.python.codeInsight.completion.OverwriteEqualsInsertHandler;
 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
 import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType;
+import com.jetbrains.python.magicLiteral.PyMagicLiteralTools;
 import com.jetbrains.python.psi.impl.PyBuiltinCache;
 import com.jetbrains.python.psi.impl.PyPsiUtils;
 import com.jetbrains.python.psi.types.*;
@@ -502,7 +503,7 @@ public class PyUtil {
    * @param start element presumably inside a method
    * @param deep  if true, allow 'start' to be inside functions nested in a method; else, 'start' must be directly inside a method.
    * @return if not 'deep', [0] is the method and [1] is the class; if 'deep', first several elements may be the nested functions,
-   *         the last but one is the method, and the last is the class.
+   * the last but one is the method, and the last is the class.
    */
   @Nullable
   public static List<PsiElement> searchForWrappingMethod(PsiElement start, boolean deep) {
@@ -636,8 +637,9 @@ public class PyUtil {
       final PyExpression callee = ((PyCallExpression)qualifier).getCallee();
       if (callee instanceof PyReferenceExpression) {
         final PyExpression calleeQualifier = ((PyReferenceExpression)callee).getQualifier();
-        if (calleeQualifier != null)
+        if (calleeQualifier != null) {
           newElement.insert(0, calleeQualifier.getText() + ".");
+        }
       }
       final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject());
       final PyExpression expression = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), newElement.toString());
@@ -650,19 +652,34 @@ public class PyUtil {
     }
   }
 
-  public static String computeElementNameForStringSearch(PsiElement element) {
+  /**
+   * Returns string that represents element in string search.
+   *
+   * @param element element to search
+   * @return string that represents element
+   */
+  @NotNull
+  public static String computeElementNameForStringSearch(@NotNull final PsiElement element) {
     if (element instanceof PyFile) {
       return FileUtil.getNameWithoutExtension(((PyFile)element).getName());
     }
-    else if (element instanceof PsiDirectory) {
+    if (element instanceof PsiDirectory) {
       return ((PsiDirectory)element).getName();
     }
-    else if (element instanceof PyElement) {
-      return ((PyElement)element).getName();
+    // Magic literals are always represented by their string values
+    if ((element instanceof PyStringLiteralExpression) && PyMagicLiteralTools.isMagicLiteral(element)) {
+      final String name = ((StringLiteralExpression)element).getStringValue();
+      if (name != null) {
+        return name;
+      }
     }
-    else {
-      return element.getNode().getText();
+    if (element instanceof PyElement) {
+      final String name = ((PyElement)element).getName();
+      if (name != null) {
+        return name;
+      }
     }
+    return element.getNode().getText();
   }
 
   public static boolean isOwnScopeComprehension(@NotNull PyComprehensionElement comprehension) {
@@ -708,6 +725,7 @@ public class PyUtil {
 
   /**
    * Finds element declaration by resolving its references top the top but not further than file (to prevent unstubing)
+   *
    * @param element element to resolve
    * @return its declaration
    */
@@ -730,6 +748,7 @@ public class PyUtil {
 
   /**
    * Gets class init method
+   *
    * @param pyClass class where to find init
    * @return class init method if any
    */
@@ -746,8 +765,9 @@ public class PyUtil {
   @NotNull
   public static LanguageLevel getLanguageLevelForVirtualFile(@NotNull Project project,
                                                              @NotNull VirtualFile virtualFile) {
-    if (virtualFile instanceof VirtualFileWindow)
+    if (virtualFile instanceof VirtualFileWindow) {
       virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
+    }
 
     // Most of the cases should be handled by this one, PyLanguageLevelPusher pushes folders only
     final VirtualFile folder = virtualFile.getParent();
@@ -838,7 +858,7 @@ public class PyUtil {
 
   @Nullable
   public static PsiElement turnInitIntoDir(PsiElement target) {
-    if (target instanceof PyFile && isPackage((PsiFile) target)) {
+    if (target instanceof PyFile && isPackage((PsiFile)target)) {
       return ((PsiFile)target).getContainingDirectory();
     }
     return target;
@@ -911,7 +931,6 @@ public class PyUtil {
    *
    * @param elt starting point of search.
    * @return 'class' or 'def' element, or null if not found.
-   *
    * @deprecated Use {@link ScopeUtil#getScopeOwner} instead.
    */
   @Deprecated
@@ -993,7 +1012,6 @@ public class PyUtil {
   }
 
   /**
-   *
    * @return Source roots <strong>and</strong> content roots for element's project
    */
   @NotNull
@@ -1006,7 +1024,6 @@ public class PyUtil {
   }
 
   /**
-   *
    * @return Source roots <strong>and</strong> content roots for module
    */
   @NotNull
@@ -1180,7 +1197,7 @@ public class PyUtil {
 
     //TODO: Doc
     public boolean isInstanceMethod() {
-      return ! (myIsClassMethod || myIsStaticMethod);
+      return !(myIsClassMethod || myIsStaticMethod);
     }
   }
 
@@ -1231,9 +1248,11 @@ public class PyUtil {
                                                   StringUtil.notNullize(
                                                     file.getParent(),
                                                     baseDir != null ? baseDir
-                                                      .getPath() : "."),
+                                                      .getPath() : "."
+                                                  ),
                                                   file.getName(),
-                                                  content);
+                                                  content
+        );
       }
       catch (IOException e) {
         throw new IncorrectOperationException(String.format("Cannot create file '%s'", path));
@@ -1289,7 +1308,7 @@ public class PyUtil {
   }
 
   @Nullable
-  public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class ... toSkip) {
+  public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) {
     PsiElement element = psiFile.findElementAt(caretOffset);
     if (element == null || caretOffset < 0) {
       return null;
@@ -1303,28 +1322,31 @@ public class PyUtil {
     do {
       caretOffset--;
       element = psiFile.findElementAt(caretOffset);
-    } while (caretOffset >= lineStartOffset && instanceOf(element, toSkip));
+    }
+    while (caretOffset >= lineStartOffset && instanceOf(element, toSkip));
     return instanceOf(element, toSkip) ? null : element;
   }
 
   @Nullable
   public static PsiElement findNonWhitespaceAtOffset(PsiFile psiFile, int caretOffset) {
     PsiElement element = findNextAtOffset(psiFile, caretOffset, PsiWhiteSpace.class);
-    if (element == null)
-      element = findPrevAtOffset(psiFile, caretOffset-1, PsiWhiteSpace.class);
+    if (element == null) {
+      element = findPrevAtOffset(psiFile, caretOffset - 1, PsiWhiteSpace.class);
+    }
     return element;
   }
 
   @Nullable
   public static PsiElement findElementAtOffset(PsiFile psiFile, int caretOffset) {
     PsiElement element = findPrevAtOffset(psiFile, caretOffset);
-    if (element == null)
+    if (element == null) {
       element = findNextAtOffset(psiFile, caretOffset);
+    }
     return element;
   }
 
   @Nullable
-  public static PsiElement findNextAtOffset(@NotNull final PsiFile psiFile, int caretOffset, Class ... toSkip) {
+  public static PsiElement findNextAtOffset(@NotNull final PsiFile psiFile, int caretOffset, Class... toSkip) {
     PsiElement element = psiFile.findElementAt(caretOffset);
     if (element == null) {
       return null;
@@ -1345,18 +1367,20 @@ public class PyUtil {
 
   /**
    * Adds element to statement list to the correct place according to its dependencies.
-   * @param element to insert
+   *
+   * @param element       to insert
    * @param statementList where element should be inserted
    * @return inserted element
    */
-  public static <T extends PyElement>T addElementToStatementList(@NotNull final T element,
-                                                     @NotNull final PyStatementList statementList) {
+  public static <T extends PyElement> T addElementToStatementList(@NotNull final T element,
+                                                                  @NotNull final PyStatementList statementList) {
     PsiElement before = null;
     PsiElement after = null;
     for (final PyStatement statement : statementList.getStatements()) {
       if (PyDependenciesComparator.depends(element, statement)) {
         after = statement;
-      }else if (PyDependenciesComparator.depends(statement, element)) {
+      }
+      else if (PyDependenciesComparator.depends(statement, element)) {
         before = statement;
       }
     }
@@ -1364,9 +1388,11 @@ public class PyUtil {
     if (after != null) {
 
       result = statementList.addAfter(element, after);
-    }else if (before != null) {
+    }
+    else if (before != null) {
       result = statementList.addBefore(element, before);
-    } else {
+    }
+    else {
       result = addElementToStatementList(element, statementList, true);
     }
     @SuppressWarnings("unchecked") // Inserted element can't have different type
@@ -1405,9 +1431,13 @@ public class PyUtil {
               }
               anchor = next;
             }
-            else break;
+            else {
+              break;
+            }
+          }
+          else {
+            break;
           }
-          else break;
         }
         element = statementList.addBefore(element, anchor);
       }
@@ -1530,7 +1560,6 @@ public class PyUtil {
       return true;
     }
     return false;
-
   }
 
   /**