mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
Django: Extract Django/Jinja2 commons
GitOrigin-RevId: 3f6bb7b7cd80cbf6007e1930d4d018e1b1cff44a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f148162c19
commit
c443410d79
@@ -7,8 +7,12 @@
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.python.community" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.platform.analysis.impl" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.python.psi" />
|
||||
<orderEntry type="module" module-name="intellij.platform.analysis.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.regexp" />
|
||||
<orderEntry type="library" name="Guava" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.psi.impl" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -13,18 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python;
|
||||
package com.jetbrains.extensions
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
|
||||
/**
|
||||
* Reference that may be resolved differently (i.e use better {@link TypeEvalContext}) when resolved by user request
|
||||
* @author Ilya.Kazakevich
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PyUserInitiatedResolvableReference {
|
||||
@Nullable
|
||||
PsiElement userInitiatedResolve();
|
||||
}
|
||||
|
||||
fun Module.getSdk(): Sdk? = ModuleRootManager.getInstance(this).sdk
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.extensions
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.OrderRootType
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.psi.PyClass
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.psi.resolve.*
|
||||
import com.jetbrains.python.psi.stubs.PyModuleNameIndex
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
import com.jetbrains.python.sdk.PySdkUtil
|
||||
import java.util.*
|
||||
|
||||
|
||||
interface ContextAnchor {
|
||||
val sdk: Sdk?
|
||||
val project: Project
|
||||
val qualifiedNameResolveContext: PyQualifiedNameResolveContext?
|
||||
val scope: GlobalSearchScope
|
||||
fun getRoots(): Array<VirtualFile> {
|
||||
return sdk?.rootProvider?.getFiles(OrderRootType.CLASSES) ?: emptyArray()
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleBasedContextAnchor(val module: Module) : ContextAnchor {
|
||||
override val sdk: Sdk? = module.getSdk()
|
||||
override val project: Project = module.project
|
||||
override val qualifiedNameResolveContext: PyQualifiedNameResolveContext = fromModule(module)
|
||||
override val scope: GlobalSearchScope = module.moduleContentScope
|
||||
override fun getRoots(): Array<VirtualFile> {
|
||||
val manager = ModuleRootManager.getInstance(module)
|
||||
return super.getRoots() + manager.contentRoots + manager.sourceRoots
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectSdkContextAnchor(override val project: Project, override val sdk: Sdk?) : ContextAnchor {
|
||||
override val qualifiedNameResolveContext: PyQualifiedNameResolveContext? = sdk?.let { fromSdk(project, it) }
|
||||
override val scope: GlobalSearchScope = GlobalSearchScope.projectScope(project) //TODO: Check if project scope includes SDK
|
||||
override fun getRoots(): Array<VirtualFile> {
|
||||
val manager = ProjectRootManager.getInstance(project)
|
||||
return super.getRoots() + manager.contentRoots + manager.contentSourceRoots
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class QNameResolveContext(
|
||||
val contextAnchor: ContextAnchor,
|
||||
/**
|
||||
* Used for language level etc
|
||||
*/
|
||||
val sdk: Sdk? = contextAnchor.sdk,
|
||||
|
||||
val evalContext: TypeEvalContext,
|
||||
/**
|
||||
* If not provided resolves against roots only. Resolved also against this folder otherwise
|
||||
*/
|
||||
val folderToStart: VirtualFile? = null,
|
||||
/**
|
||||
* Use index, plain dirs with Py2 and so on. May resolve names unresolvable in other cases, but may return false results.
|
||||
*/
|
||||
val allowInaccurateResult: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* @return qname part relative to root
|
||||
*/
|
||||
fun QualifiedName.getRelativeNameTo(root: QualifiedName): QualifiedName? {
|
||||
if (Collections.indexOfSubList(components, root.components) == -1) {
|
||||
return null
|
||||
}
|
||||
return subQualifiedName(root.componentCount, componentCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves qname of any symbol to appropriate PSI element.
|
||||
* Shortcut for [getElementAndResolvableName]
|
||||
* @see [getElementAndResolvableName]
|
||||
*/
|
||||
fun QualifiedName.resolveToElement(context: QNameResolveContext, stopOnFirstFail: Boolean = false): PsiElement? {
|
||||
return getElementAndResolvableName(context, stopOnFirstFail)?.element
|
||||
}
|
||||
|
||||
|
||||
data class NameAndElement(val name: QualifiedName, val element: PsiElement)
|
||||
|
||||
/**
|
||||
* Resolves qname of any symbol to PSI element popping tail until element becomes resolved or only one time if stopOnFirstFail
|
||||
* @return element and longest name that was resolved successfully.
|
||||
* @see [resolveToElement]
|
||||
*/
|
||||
fun QualifiedName.getElementAndResolvableName(context: QNameResolveContext, stopOnFirstFail: Boolean = false): NameAndElement? {
|
||||
var currentName = QualifiedName.fromComponents(this.components)
|
||||
|
||||
|
||||
var element: PsiElement? = null
|
||||
|
||||
|
||||
var lastElement: String? = null
|
||||
var psiDirectory: PsiDirectory? = null
|
||||
|
||||
var resolveContext = context.contextAnchor.qualifiedNameResolveContext?.copyWithMembers() ?: return null
|
||||
if (PySdkUtil.getLanguageLevelForSdk(context.sdk).isPy3K || context.allowInaccurateResult) {
|
||||
resolveContext = resolveContext.copyWithPlainDirectories()
|
||||
}
|
||||
|
||||
if (context.folderToStart != null) {
|
||||
psiDirectory = PsiManager.getInstance(context.contextAnchor.project).findDirectory(context.folderToStart)
|
||||
}
|
||||
|
||||
|
||||
// Drill as deep, as we can
|
||||
while (currentName.componentCount > 0 && element == null) {
|
||||
if (psiDirectory != null) { // Resolve against folder
|
||||
// There could be folder and module on the same level. Empty folder should be ignored in this case.
|
||||
element = resolveModuleAt(currentName, psiDirectory, resolveContext).filterNot {
|
||||
it is PsiDirectory && it.children.filterIsInstance<PyFile>().isEmpty()
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
if (element == null) { // Resolve against roots
|
||||
element = resolveQualifiedName(currentName, resolveContext).firstOrNull()
|
||||
}
|
||||
|
||||
if (element != null || stopOnFirstFail) {
|
||||
break
|
||||
}
|
||||
lastElement = currentName.lastComponent!!
|
||||
currentName = currentName.removeLastComponent()
|
||||
}
|
||||
|
||||
if (lastElement != null && element is PyClass) {
|
||||
// Drill in class
|
||||
|
||||
//TODO: Support nested classes
|
||||
val method = element.findMethodByName(lastElement, true, context.evalContext)
|
||||
if (method != null) {
|
||||
return NameAndElement(currentName.append(lastElement), method)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (element == null && this.firstComponent != null && context.allowInaccurateResult) {
|
||||
// If name starts with file which is not in root nor in folders -- use index.
|
||||
val nameToFind = this.firstComponent!!
|
||||
val pyFile = PyModuleNameIndex.find(nameToFind, context.contextAnchor.project, false).firstOrNull() ?: return element
|
||||
|
||||
val folder =
|
||||
if (pyFile.name == PyNames.INIT_DOT_PY) { // We are in folder
|
||||
pyFile.virtualFile.parent.parent
|
||||
}
|
||||
else {
|
||||
pyFile.virtualFile.parent
|
||||
}
|
||||
return getElementAndResolvableName(context.copy(folderToStart = folder))
|
||||
}
|
||||
return if (element != null) NameAndElement(currentName, element) else null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2000-2017 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.extensions
|
||||
|
||||
import org.intellij.lang.regexp.psi.RegExpPattern
|
||||
import java.util.regex.Pattern
|
||||
|
||||
fun RegExpPattern.asPattern(): Pattern = Pattern.compile(text)!!
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2000-2019 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.extensions.python
|
||||
|
||||
import com.jetbrains.python.nameResolver.FQNamesProvider
|
||||
import com.jetbrains.python.nameResolver.NameResolverTools
|
||||
import com.jetbrains.python.psi.PyAssignmentStatement
|
||||
import com.jetbrains.python.psi.PyCallExpression
|
||||
import com.jetbrains.python.psi.PyPossibleClassMember
|
||||
|
||||
/**
|
||||
* Checks if ``foo = SomeExpr()`` where foo is class attribute
|
||||
*/
|
||||
val PyCallExpression.isClassAttribute: Boolean
|
||||
get() =
|
||||
(parent as? PyAssignmentStatement)?.targets?.filterIsInstance<PyPossibleClassMember>()?.any { it.containingClass != null } == true
|
||||
|
||||
/**
|
||||
* Checks if callee has certain name. Only name is checked, so import aliases aren't supported, but it works pretty fast
|
||||
*/
|
||||
fun PyCallExpression.isCalleeName(vararg names: FQNamesProvider): Boolean = NameResolverTools.isCalleeShortCut(this, *names)
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2000-2018 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;
|
||||
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Alexei Orischenko
|
||||
*/
|
||||
public abstract class BaseReference implements PsiReference, PyUserInitiatedResolvableReference {
|
||||
protected final PsiElement myElement;
|
||||
|
||||
protected BaseReference(@NotNull PsiElement element) {
|
||||
myElement = element;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PsiElement getElement() {
|
||||
return myElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TextRange getRangeInElement() {
|
||||
return new TextRange(0, myElement.getTextLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getCanonicalText() {
|
||||
return myElement.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
|
||||
if (myElement instanceof PsiNamedElement) {
|
||||
return ((PsiNamedElement)myElement).setName(newElementName);
|
||||
}
|
||||
throw new IncorrectOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReferenceTo(@NotNull PsiElement element) {
|
||||
return resolve() == element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSoft() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement userInitiatedResolve() {
|
||||
// Override this method if your reference may benefit from this knowledge
|
||||
return resolve();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2000-2020 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;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.jetbrains.python.codeInsight.PyCustomMember;
|
||||
import com.jetbrains.python.psi.AccessDirection;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.impl.ResolveResultList;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class PySyntheticType implements PyType {
|
||||
private final List<PyCustomMember> myMembers;
|
||||
private final String myName;
|
||||
private final PsiElement myContext;
|
||||
|
||||
public PySyntheticType(String name, PsiElement context, PyCustomMember... members) {
|
||||
myName = name;
|
||||
myContext = context;
|
||||
myMembers = Lists.newArrayList(members);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
|
||||
@Nullable PyExpression location,
|
||||
@NotNull AccessDirection direction,
|
||||
@NotNull PyResolveContext resolveContext) {
|
||||
final ResolveResultList result = new ResolveResultList();
|
||||
for (PyCustomMember member : myMembers) {
|
||||
if (name.equals(member.getName())) {
|
||||
result.poke(member.resolve(myContext, resolveContext), RatedResolveResult.RATE_NORMAL);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
|
||||
final List<Object> result = new ArrayList<>();
|
||||
for (PyCustomMember member : myMembers) {
|
||||
result.add(LookupElementBuilder.create(member.getName()));
|
||||
}
|
||||
return result.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuiltin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertValid(String message) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2000-2020 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;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.jetbrains.python.psi.PyStringLiteralCoreUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Alexei Orischenko
|
||||
* @author vlan
|
||||
*/
|
||||
public final class PythonStringUtil {
|
||||
|
||||
private PythonStringUtil() {
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public static String removeFirstPrefix(@Nullable String s, String separator) {
|
||||
if (s != null) {
|
||||
int pos = s.indexOf(separator);
|
||||
if (pos != -1) {
|
||||
return s.substring(pos + separator.length());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String removeLastSuffix(@Nullable String s, String separator) {
|
||||
if (s != null) {
|
||||
int pos = s.lastIndexOf(separator);
|
||||
if (pos != -1) {
|
||||
return s.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean isPath(@Nullable String s) {
|
||||
if (!StringUtil.isEmpty(s)) {
|
||||
s = Objects.requireNonNull(s);
|
||||
s = FileUtil.toSystemIndependentName(s);
|
||||
final List<String> components = StringUtil.split(s, "/");
|
||||
for (String name : components) {
|
||||
if (name == components.get(0) && SystemInfo.isWindows && name.endsWith(":")) {
|
||||
continue;
|
||||
}
|
||||
if (!PathUtil.isValidFileName(name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEmail(String s) {
|
||||
if (!StringUtil.isEmpty(s)) {
|
||||
Pattern p = Pattern.compile("^[\\w\\.-]+@([\\w\\-]+\\.)+[a-z]{2,4}$");
|
||||
Matcher m = p.matcher(s);
|
||||
return m.matches();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getFirstPrefix(String s, String separator) {
|
||||
if (s != null) {
|
||||
int pos = s.indexOf(separator);
|
||||
if (pos != -1) {
|
||||
return s.substring(0, pos);
|
||||
}
|
||||
}
|
||||
return s != null ? s : "";
|
||||
}
|
||||
|
||||
public static String getLastSuffix(String s, String separator) {
|
||||
if (s != null) {
|
||||
int pos = s.lastIndexOf(separator);
|
||||
if (pos != -1) {
|
||||
return s.substring(pos + 1);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces word after last dot from string s to newElementName.
|
||||
* For example 'yy.xxx.foo' can be renamed to 'yy.xxx.bar'
|
||||
*
|
||||
*/
|
||||
public static String replaceLastSuffix(String s, String separator, String newElementName) {
|
||||
|
||||
Pair<String, String> quotes = null;
|
||||
if (PyStringLiteralCoreUtil.isQuoted(s)) {
|
||||
quotes = PyStringLiteralCoreUtil.getQuotes(s);
|
||||
s = PyStringLiteralCoreUtil.stripQuotesAroundValue(s);
|
||||
}
|
||||
|
||||
s = removeLastSuffix(s, separator);
|
||||
if (s.length() > 0) {
|
||||
s += separator;
|
||||
}
|
||||
s += newElementName;
|
||||
if (quotes != null) {
|
||||
s = quotes.first + s + quotes.second;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
public static TextRange lastSuffixTextRange(@NotNull String text, String separator) {
|
||||
int offset = text.lastIndexOf(separator) + 1;
|
||||
int length = text.length() - offset;
|
||||
|
||||
return TextRange.from(offset + 1, length);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static String intersect(String fullName, String elementStringValue) {
|
||||
QualifiedName fullQName = QualifiedName.fromDottedString(fullName);
|
||||
QualifiedName stringQName = QualifiedName.fromDottedString(elementStringValue);
|
||||
String[] s1 = stringQName.getComponents().toArray(new String[stringQName.getComponentCount()]);
|
||||
String[] s2 = fullQName.getComponents().toArray(new String[fullQName.getComponentCount()]);
|
||||
|
||||
for (int i = s1.length - 1; i >= 0; i--) {
|
||||
boolean flag = true;
|
||||
if (i > s2.length - 1) {
|
||||
continue;
|
||||
}
|
||||
for (int j = 0; j <= i; j++) {
|
||||
if (!s1[i - j].equals(s2[s2.length - j - 1])) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
StringBuilder res = new StringBuilder();
|
||||
for (int j = 0; j <= i; j++) {
|
||||
if (j > 0) {
|
||||
res.append(".");
|
||||
}
|
||||
res.append(s1[j]);
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2000-2021 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;
|
||||
|
||||
import com.intellij.ide.util.PsiElementListCellRenderer;
|
||||
import com.intellij.navigation.ItemPresentation;
|
||||
import com.intellij.navigation.NavigationItem;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
|
||||
|
||||
public class PyElementListCellRenderer extends PsiElementListCellRenderer {
|
||||
@Override
|
||||
public String getElementText(final PsiElement element) {
|
||||
if (element instanceof PsiNamedElement) {
|
||||
final String name = ((PsiNamedElement)element).getName();
|
||||
return name == null ? "" : name;
|
||||
}
|
||||
return element.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getContainerText(final PsiElement element, final String name) {
|
||||
if (element instanceof NavigationItem) {
|
||||
final ItemPresentation presentation = ((NavigationItem)element).getPresentation();
|
||||
if (presentation != null) {
|
||||
return presentation.getLocationString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PyStringLiteralCoreUtil {
|
||||
/**
|
||||
* Valid string prefix characters (lowercased) as defined in Python lexer.
|
||||
*/
|
||||
public static final String PREFIX_CHARACTERS = "ubcrf";
|
||||
|
||||
/**
|
||||
* Maximum length of a string prefix as defined in Python lexer.
|
||||
*/
|
||||
public static final int MAX_PREFIX_LENGTH = 3;
|
||||
|
||||
private static final ImmutableList<String> QUOTES = ImmutableList.of("'''", "\"\"\"", "'", "\"");
|
||||
|
||||
protected PyStringLiteralCoreUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pair where the first element is the prefix combined with the opening quote and the second is the closing quote.
|
||||
* <p>
|
||||
* If the given string literal is not properly quoted, e.g. the closing quote has fewer quotes as opposed to the
|
||||
* opening one, or it's missing altogether this method returns null.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <pre>
|
||||
* ur"foo" -> ("ur, ")
|
||||
* ur'bar -> null
|
||||
* """baz""" -> (""", """)
|
||||
* '''quux' -> null
|
||||
* </pre>
|
||||
*/
|
||||
@Nullable
|
||||
public static Pair<String, String> getQuotes(@NotNull String text) {
|
||||
final String prefix = getPrefix(text);
|
||||
final String mainText = text.substring(prefix.length());
|
||||
for (String quote : QUOTES) {
|
||||
final Pair<String, String> quotes = getQuotes(mainText, prefix, quote);
|
||||
if (quotes != null) {
|
||||
return quotes;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the end offset of the string prefix starting from {@code startOffset} in the given char sequence.
|
||||
* String prefix may contain only up to {@link #MAX_PREFIX_LENGTH} characters from {@link #PREFIX_CHARACTERS}
|
||||
* (case insensitively).
|
||||
*
|
||||
* @return end offset of found string prefix
|
||||
*/
|
||||
public static int getPrefixEndOffset(@NotNull CharSequence text, int startOffset) {
|
||||
int offset;
|
||||
for (offset = startOffset; offset < Math.min(startOffset + MAX_PREFIX_LENGTH, text.length()); offset++) {
|
||||
if (PREFIX_CHARACTERS.indexOf(Character.toLowerCase(text.charAt(offset))) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getPrefix(@NotNull CharSequence text) {
|
||||
return getPrefix(text, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts string prefix from the given char sequence using {@link #getPrefixEndOffset(CharSequence, int)}.
|
||||
*
|
||||
* @return extracted string prefix
|
||||
* @see #getPrefixEndOffset(CharSequence, int)
|
||||
*/
|
||||
@NotNull
|
||||
public static String getPrefix(@NotNull CharSequence text, int startOffset) {
|
||||
return text.subSequence(startOffset, getPrefixEndOffset(text, startOffset)).toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Pair<String, String> getQuotes(@NotNull String text, @NotNull String prefix, @NotNull String quote) {
|
||||
final int length = text.length();
|
||||
final int n = quote.length();
|
||||
if (length >= 2 * n && text.startsWith(quote) && text.endsWith(quote)) {
|
||||
return Pair.create(prefix + text.substring(0, n), text.substring(length - n));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String stripQuotesAroundValue(String text) {
|
||||
Pair<String, String> quotes = getQuotes(text);
|
||||
if (quotes == null) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.substring(quotes.first.length(), text.length() - quotes.second.length());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user