mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Refactor numpy/scipy documentation link provider; add tests.
This commit is contained in:
@@ -785,6 +785,7 @@
|
||||
<typeProvider implementation="com.jetbrains.numpy.codeInsight.NumpyDocStringTypeProvider"/>
|
||||
<resolveResultRater implementation="com.jetbrains.numpy.codeInsight.NumpyResolveRater"/>
|
||||
<pyClassMembersProvider implementation="com.jetbrains.numpy.codeInsight.NumpyClassMembersProvider"/>
|
||||
<documentationLinkProvider implementation="com.jetbrains.numpy.codeInsight.SciPyDocumentationLinkProvider"/>
|
||||
|
||||
<!-- PyDocstring -->
|
||||
<typeProvider implementation="com.jetbrains.python.documentation.docstrings.PyDocStringTypeProvider"/>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.numpy.codeInsight
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.jetbrains.python.documentation.PythonDocumentationLinkProvider
|
||||
import com.jetbrains.python.documentation.PythonDocumentationProvider
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
|
||||
|
||||
class SciPyDocumentationLinkProvider : PythonDocumentationLinkProvider {
|
||||
|
||||
private val nameToWebpageName: Map<String, String> by lazy {
|
||||
val b = ImmutableMap.builder<String, String>()
|
||||
try {
|
||||
BufferedReader(
|
||||
InputStreamReader(
|
||||
SciPyDocumentationLinkProvider::class.java
|
||||
.getResourceAsStream("/com/jetbrains/numpy/codeInsight/scipyNameMapping.tsv"), // generated by scipy_doc_mapping.py
|
||||
Charsets.UTF_8
|
||||
)
|
||||
).use { inputStream ->
|
||||
inputStream.lines().forEach { line ->
|
||||
val kv = line.split("\t".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
b.put(kv[0], kv[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: IOException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
b.build()
|
||||
}
|
||||
|
||||
override fun getExternalDocumentationUrl(element: PsiElement?, originalElement: PsiElement?): String? {
|
||||
val qname = PythonDocumentationProvider.getFullQualifiedName(element)
|
||||
|
||||
return if (qname != null && qname.firstComponent in listOf("numpy", "scipy")) {
|
||||
val webPage = nameToWebpageName.get(qname.toString())
|
||||
if (webPage != null) {
|
||||
"https://docs.scipy.org/doc/${qname.firstComponent}/reference/generated/$webPage.html"
|
||||
} else {
|
||||
"https://docs.scipy.org/doc/${qname.firstComponent}/reference/"
|
||||
}
|
||||
}
|
||||
else null
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Can't render this file because it is too large.
|
@@ -2,7 +2,7 @@
|
||||
package com.jetbrains.python.documentation;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
@@ -17,17 +17,11 @@ import com.jetbrains.python.psi.PyFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -46,28 +40,6 @@ public class PythonDocumentationMap implements PersistentStateComponent<PythonDo
|
||||
return ServiceManager.getService(PythonDocumentationMap.class);
|
||||
}
|
||||
|
||||
private static class ScipyExternalDocumentationMap {
|
||||
private static final Map<String, String> nameToWebpageName = ((Supplier<Map<String, String>>)() -> {
|
||||
final ImmutableMap.Builder<String, String> b = ImmutableMap.builder();
|
||||
try (final BufferedReader inputStream = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
ScipyExternalDocumentationMap.class
|
||||
.getResourceAsStream("/com/jetbrains/python/documentation/scipyNameMapping.tsv"),
|
||||
StandardCharsets.US_ASCII
|
||||
)
|
||||
)) {
|
||||
inputStream.lines().forEach(line -> {
|
||||
final String[] kv = line.split("\t");
|
||||
b.put(kv[0], kv[1]);
|
||||
});
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return b.build();
|
||||
}).get();
|
||||
}
|
||||
|
||||
public static class Entry {
|
||||
private String myPrefix;
|
||||
private String myUrlPattern;
|
||||
@@ -124,10 +96,9 @@ public class PythonDocumentationMap implements PersistentStateComponent<PythonDo
|
||||
addEntry(PyQt4, PYQT4_DOC_URL);
|
||||
addEntry("PyQt5", "http://doc.qt.io/qt-5/{class.name.lower}.html#{functionOrProp.name}");
|
||||
addEntry("PySide", "http://pyside.github.io/docs/pyside/{module.name.slashes}/{class.name}.html#{module.name}.{element.qname}");
|
||||
addEntry("gtk", "http://library.gnome.org/devel/pygtk/stable/class-gtk{class.name.lower}.html#method-gtk{class.name.lower}--{function.name.dashes}");
|
||||
addEntry("gtk",
|
||||
"http://library.gnome.org/devel/pygtk/stable/class-gtk{class.name.lower}.html#method-gtk{class.name.lower}--{function.name.dashes}");
|
||||
addEntry("wx", "http://www.wxpython.org/docs/api/{module.name}.{class.name}-class.html#{function.name}");
|
||||
addEntry("numpy", "https://docs.scipy.org/doc/numpy/reference/generated/{module.name}.{element.name}.html");
|
||||
addEntry("scipy", "https://docs.scipy.org/doc/scipy/reference/generated/{module.name}.{element.name}.html");
|
||||
addEntry("kivy", "http://kivy.org/docs/api-{module.name}.html");
|
||||
addEntry("matplotlib", "http://matplotlib.org/api/{module.basename}_api.html#{element.qname}");
|
||||
addEntry("pyramid", "http://docs.pylonsproject.org/projects/pyramid/en/latest/api/{module.basename}.html#{element.qname}");
|
||||
@@ -171,18 +142,25 @@ public class PythonDocumentationMap implements PersistentStateComponent<PythonDo
|
||||
@Override
|
||||
public void loadState(@NotNull State state) {
|
||||
myState = state;
|
||||
for (Entry e: myState.getEntries()) {
|
||||
for (Entry e : myState.getEntries()) {
|
||||
if (PyQt4.equals(e.myPrefix) && PYQT4_DOC_URL_OLD.equals(e.myUrlPattern)) {
|
||||
// old URL is broken, switch to new one
|
||||
e.setUrlPattern(PYQT4_DOC_URL);
|
||||
}
|
||||
}
|
||||
addAbsentEntriesFromDefaultState(myState);
|
||||
removeEntriesThatHandledSpecially(myState);
|
||||
}
|
||||
|
||||
private static void removeEntriesThatHandledSpecially(@NotNull State state) {
|
||||
ArrayList<String> strings = Lists.newArrayList("django", "numpy", "scipy");
|
||||
// those packages are handled by implementations of PythonDocumentationLinkProvider
|
||||
state.setEntries(state.getEntries().stream().filter((entry -> !strings.contains(entry.myPrefix))).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static void addAbsentEntriesFromDefaultState(@NotNull State state) {
|
||||
State defaultState = new State();
|
||||
for (Entry e: defaultState.myEntries) {
|
||||
for (Entry e : defaultState.myEntries) {
|
||||
if (state.myEntries.stream().noneMatch(entry -> entry.myPrefix.equals(e.myPrefix))) {
|
||||
state.addEntry(e.getPrefix(), e.getUrlPattern());
|
||||
}
|
||||
@@ -239,10 +217,6 @@ public class PythonDocumentationMap implements PersistentStateComponent<PythonDo
|
||||
if (pattern == null) {
|
||||
return rootForPattern(urlPattern);
|
||||
}
|
||||
final Matcher match = Pattern.compile("\\A(.+/)((?:numpy|scipy)\\..+)\\.html\\Z").matcher(pattern);
|
||||
if (match.find()) {
|
||||
return match.group(1) + ScipyExternalDocumentationMap.nameToWebpageName.getOrDefault(match.group(2), match.group(2)) + ".html";
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@@ -268,7 +242,6 @@ public class PythonDocumentationMap implements PersistentStateComponent<PythonDo
|
||||
.replace("{" + entry.getKey() + ".lower}", entry.getValue().toLowerCase())
|
||||
.replace("{" + entry.getKey() + ".slashes}", entry.getValue().replace(".", "/"))
|
||||
.replace("{" + entry.getKey() + ".dashes}", entry.getValue().replace("_", "-"));
|
||||
|
||||
}
|
||||
return urlPattern.replace("{}", "");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PythonDialectsTokenSetProvider;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.console.PydevConsoleRunner;
|
||||
import com.jetbrains.python.console.PydevDocumentationProvider;
|
||||
@@ -604,12 +606,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
|
||||
|
||||
if (file == null) return null;
|
||||
|
||||
if (file instanceof PyiFile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final QualifiedName moduleQName = QualifiedNameFinder.findCanonicalImportPath(element, element);
|
||||
if (moduleQName == null) {
|
||||
if (file instanceof PyiFile) { //TODO: why???
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -651,15 +648,16 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiNamedElement getNamedElement(PsiElement element) {
|
||||
public static PsiNamedElement getNamedElement(@Nullable PsiElement element) {
|
||||
PsiNamedElement namedElement = (element instanceof PsiNamedElement) ? (PsiNamedElement)element : null;
|
||||
if (namedElement instanceof PyFunction && PyNames.INIT.equals(namedElement.getName())) {
|
||||
final PyClass containingClass = ((PyFunction)namedElement).getContainingClass();
|
||||
if (containingClass != null) {
|
||||
namedElement = containingClass;
|
||||
}
|
||||
} else {
|
||||
namedElement = (PsiNamedElement) PyUtil.turnInitIntoDir(namedElement);
|
||||
}
|
||||
else {
|
||||
namedElement = (PsiNamedElement)PyUtil.turnInitIntoDir(namedElement);
|
||||
}
|
||||
return namedElement;
|
||||
}
|
||||
@@ -756,4 +754,47 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
|
||||
describeTypeWithLinks(type, context, anchor, holder);
|
||||
return holder.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static QualifiedName getFullQualifiedName(@Nullable final PsiElement element) {
|
||||
final String name =
|
||||
(element instanceof PsiNamedElement) ? ((PsiNamedElement)element).getName() : element != null ? element.getText() : null;
|
||||
if (name != null) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(element);
|
||||
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(element);
|
||||
if (owner instanceof PyClass) {
|
||||
final QualifiedName importQName = QualifiedNameFinder.findCanonicalImportPath(element, element);
|
||||
if (importQName != null) {
|
||||
return QualifiedName.fromDottedString(importQName.toString() + "." + owner.getName() + "." + name);
|
||||
}
|
||||
}
|
||||
else if (owner instanceof PyFunction && PyNames.INIT.equals(owner.getName()) && ((PyFunction)owner).getContainingClass() != null) {
|
||||
final QualifiedName importQName = QualifiedNameFinder.findCanonicalImportPath(owner, element);
|
||||
if (importQName != null) {
|
||||
return QualifiedName
|
||||
.fromDottedString(importQName.toString() + "." + ((PyFunction)owner).getContainingClass().getName() + "." + name);
|
||||
}
|
||||
}
|
||||
else if (owner instanceof PyFile) {
|
||||
if (builtinCache.isBuiltin(element)) {
|
||||
return QualifiedName.fromDottedString(name);
|
||||
}
|
||||
else {
|
||||
final VirtualFile virtualFile = ((PyFile)owner).getVirtualFile();
|
||||
if (virtualFile != null) {
|
||||
final QualifiedName fileQName = QualifiedNameFinder.findCanonicalImportPath(element, element);
|
||||
if (fileQName != null) {
|
||||
return QualifiedName.fromDottedString(fileQName.toString() + "." + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (element instanceof PyFile) {
|
||||
return QualifiedNameFinder.findCanonicalImportPath(element, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,14 @@ print(os.path.isf<caret>ile)
|
||||
""".trimIndent(), """<dt id="os.path.isfile">""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNumpyNdarray() {
|
||||
myFixture.copyDirectoryToProject("/inspections/PyNumpyType/numpy", "numpy")
|
||||
|
||||
doTestDocumentationUrl("""import numpy
|
||||
a = numpy.multiarray.ndarr<caret>ay()
|
||||
""".trimMargin(), "https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html", myFixture)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user