mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -48,3 +48,4 @@ python-rest
|
||||
pycharm-flask
|
||||
pycharm-numpy
|
||||
textmate
|
||||
restClient
|
||||
|
||||
@@ -13,4 +13,6 @@ public interface PyFrameworkProjectGenerator<T> extends DirectoryProjectGenerato
|
||||
boolean isFrameworkInstalled(Project project, Sdk sdk);
|
||||
|
||||
boolean acceptsRemoteSdk();
|
||||
|
||||
boolean supportsPython3();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -23,9 +24,11 @@ import static com.intellij.openapi.util.text.StringUtil.startsWith;
|
||||
|
||||
/**
|
||||
* @author Alexei Orischenko
|
||||
* Date: Nov 26, 2009
|
||||
* @author vlan
|
||||
*/
|
||||
public class PythonStringUtil {
|
||||
private static final ImmutableList<String> QUOTES = ImmutableList.of("'''", "\"\"\"", "'", "\"");
|
||||
|
||||
private PythonStringUtil() {
|
||||
}
|
||||
|
||||
@@ -44,12 +47,11 @@ public class PythonStringUtil {
|
||||
|
||||
|
||||
public static TextRange getStringValueTextRange(@NotNull String s) {
|
||||
if ((s.charAt(0) == '\'' || s.charAt(0) == '"') && (s.charAt(0) == s.charAt(s.length() - 1)) && s.length() > 1) {
|
||||
return TextRange.create(1, s.length() - 1);
|
||||
}
|
||||
else {
|
||||
return TextRange.create(0, s.length());
|
||||
final Pair<String, String> quotes = getQuotes(s);
|
||||
if (quotes != null) {
|
||||
return TextRange.create(quotes.getFirst().length(), s.length() - quotes.getSecond().length());
|
||||
}
|
||||
return TextRange.allOf(s);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -169,23 +171,7 @@ public class PythonStringUtil {
|
||||
*/
|
||||
|
||||
public static boolean isQuoted(@Nullable String text) {
|
||||
if (text == null) {
|
||||
return false;
|
||||
}
|
||||
if (text.toLowerCase().startsWith("u")) {
|
||||
text = text.substring(1);
|
||||
}
|
||||
|
||||
assert text != null;
|
||||
if (text.toLowerCase().startsWith("r")) {
|
||||
text = text.substring(1);
|
||||
}
|
||||
|
||||
assert text != null;
|
||||
if (text.length() > 1 && text.charAt(0) == text.charAt(text.length() - 1) && (text.charAt(0) == '\'' || text.charAt(0) == '"')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return text != null && getQuotes(text) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,27 +183,42 @@ public class PythonStringUtil {
|
||||
* UR"unicode raw string" -> (UR", ")
|
||||
*/
|
||||
@Nullable
|
||||
public static Pair<String, String> getQuotes(@NotNull String text) {
|
||||
String first = "";
|
||||
|
||||
if (text.toLowerCase().startsWith("u")) {
|
||||
first += text.substring(0, 1);
|
||||
text = text.substring(1);
|
||||
public static Pair<String, String> getQuotes(@NotNull final String text) {
|
||||
boolean start = true;
|
||||
int pos = 0;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
final char c = Character.toLowerCase(text.charAt(i));
|
||||
if (start) {
|
||||
if (c == 'u' || c == 'r' || c == 'b') {
|
||||
pos = i + 1;
|
||||
}
|
||||
else {
|
||||
start = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.toLowerCase().startsWith("r")) {
|
||||
first += text.substring(0, 1);
|
||||
text = text.substring(1);
|
||||
final String prefix = text.substring(0, pos);
|
||||
final String mainText = text.substring(pos);
|
||||
for (String quote : QUOTES) {
|
||||
final Pair<String, String> quotes = getQuotes(mainText, prefix, quote);
|
||||
if (quotes != null) {
|
||||
return quotes;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int last = text.length() - 1;
|
||||
|
||||
if (text.length() > 2 && (text.charAt(0) == '\'' || text.charAt(0) == '"') && (text.charAt(last) == '\'' || text.charAt(last) == '"')) {
|
||||
return Pair.create(first + text.substring(0, 1), text.substring(last));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PythonStringUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -71,15 +72,42 @@ public class PyPsiUtils {
|
||||
@NotNull final PsiElement newExpression) {
|
||||
final Pair<PsiElement, TextRange> data = oldExpression.getUserData(SELECTION_BREAKS_AST_NODE);
|
||||
if (data != null) {
|
||||
final PsiElement parent = data.first;
|
||||
final PsiElement element = data.first;
|
||||
final TextRange textRange = data.second;
|
||||
final String parentText = parent.getText();
|
||||
final String parentText = element.getText();
|
||||
final String prefix = parentText.substring(0, textRange.getStartOffset());
|
||||
final String suffix = parentText.substring(textRange.getEndOffset(), parent.getTextLength());
|
||||
final String suffix = parentText.substring(textRange.getEndOffset(), element.getTextLength());
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(oldExpression.getProject());
|
||||
final LanguageLevel languageLevel = LanguageLevel.forElement(oldExpression);
|
||||
final PsiElement expression = generator.createFromText(languageLevel, parent.getClass(), prefix + newExpression.getText() + suffix);
|
||||
return parent.replace(expression);
|
||||
if (element instanceof PyStringLiteralExpression) {
|
||||
final Pair<String, String> quotes = PythonStringUtil.getQuotes(parentText);
|
||||
final PsiElement parent = element.getParent();
|
||||
final boolean parensNeeded = parent instanceof PyExpression && !(parent instanceof PyParenthesizedExpression);
|
||||
if (quotes != null) {
|
||||
final String leftQuote = quotes.getFirst();
|
||||
final String rightQuote = quotes.getSecond();
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
if (parensNeeded) {
|
||||
builder.append("(");
|
||||
}
|
||||
if (!leftQuote.endsWith(prefix)) {
|
||||
builder.append(prefix + rightQuote + " + ");
|
||||
}
|
||||
final int pos = builder.toString().length();
|
||||
builder.append(newExpression.getText());
|
||||
if (!rightQuote.startsWith(suffix)) {
|
||||
builder.append(" + " + leftQuote + suffix);
|
||||
}
|
||||
if (parensNeeded) {
|
||||
builder.append(")");
|
||||
}
|
||||
final PsiElement expression = generator.createExpressionFromText(languageLevel, builder.toString());
|
||||
final PsiElement newElement = element.replace(expression);
|
||||
return newElement.findElementAt(pos);
|
||||
}
|
||||
}
|
||||
final PsiElement expression = generator.createFromText(languageLevel, element.getClass(), prefix + newExpression.getText() + suffix);
|
||||
return element.replace(expression);
|
||||
}
|
||||
else {
|
||||
return oldExpression.replace(newExpression);
|
||||
|
||||
@@ -46,4 +46,6 @@ public interface QualifiedNameResolver {
|
||||
QualifiedNameResolver withoutForeign();
|
||||
|
||||
Module getModule();
|
||||
|
||||
QualifiedNameResolver withMembers();
|
||||
}
|
||||
|
||||
@@ -620,8 +620,8 @@ runcfg.attest.display_name=Attests
|
||||
runcfg.attest.description=Python's attests run configuration
|
||||
|
||||
### test run configuration
|
||||
runcfg.test.display_name=Python's test
|
||||
runcfg.test.description=Python's test run configuration
|
||||
runcfg.test.display_name=Python tests
|
||||
runcfg.test.description=Python frameworks supported by PyCharm
|
||||
|
||||
### unittest run configuration
|
||||
runcfg.unittest.display_name=Unittests
|
||||
|
||||
@@ -30,7 +30,9 @@ import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.StreamUtil;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -39,6 +41,7 @@ import com.intellij.psi.impl.source.tree.FileElement;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IJSwingUtilities;
|
||||
import com.intellij.util.io.IOUtil;
|
||||
import com.intellij.util.net.NetUtils;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.django.run.Runner;
|
||||
@@ -72,15 +75,17 @@ import static com.jetbrains.python.sdk.PythonEnvUtil.setPythonUnbuffered;
|
||||
public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonConsoleView> {
|
||||
private static final Logger LOG = Logger.getInstance(PydevConsoleRunner.class.getName());
|
||||
public static final String PYDEV_PYDEVCONSOLE_PY = "pydev/pydevconsole.py";
|
||||
public static final int PORTS_WAITING_TIMEOUT = 20000;
|
||||
|
||||
private Sdk mySdk;
|
||||
@NotNull private final CommandLineArgumentsProvider myCommandLineArgumentsProvider;
|
||||
private final int[] myPorts;
|
||||
@NotNull private CommandLineArgumentsProvider myCommandLineArgumentsProvider;
|
||||
private int[] myPorts;
|
||||
private PydevConsoleCommunication myPydevConsoleCommunication;
|
||||
private PyConsoleProcessHandler myProcessHandler;
|
||||
private PydevConsoleExecuteActionHandler myConsoleExecuteActionHandler;
|
||||
private List<ConsoleListener> myConsoleListeners = Lists.newArrayList();
|
||||
private final PyConsoleType myConsoleType;
|
||||
private Map<String, String> myEnvironmentVariables;
|
||||
private String myCommandLine;
|
||||
private String[] myStatementsToExecute = ArrayUtil.EMPTY_STRING_ARRAY;
|
||||
|
||||
@@ -92,14 +97,12 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
|
||||
protected PydevConsoleRunner(@NotNull final Project project,
|
||||
@NotNull Sdk sdk, @NotNull final PyConsoleType consoleType,
|
||||
@NotNull final CommandLineArgumentsProvider commandLineArgumentsProvider,
|
||||
@Nullable final String workingDir,
|
||||
int[] ports) {
|
||||
Map<String, String> environmentVariables) {
|
||||
super(project, consoleType.getTitle(), workingDir);
|
||||
mySdk = sdk;
|
||||
myConsoleType = consoleType;
|
||||
myCommandLineArgumentsProvider = commandLineArgumentsProvider;
|
||||
myPorts = ports;
|
||||
myEnvironmentVariables = environmentVariables;
|
||||
}
|
||||
|
||||
public void setStatementsToExecute(String... statementsToExecute) {
|
||||
@@ -132,7 +135,7 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
return actions;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
public static PydevConsoleRunner createAndRun(@NotNull final Project project,
|
||||
@NotNull final Sdk sdk,
|
||||
@NotNull final PyConsoleType consoleType,
|
||||
@@ -140,13 +143,18 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
@NotNull final Map<String, String> environmentVariables,
|
||||
final String... statements2execute) {
|
||||
final PydevConsoleRunner consoleRunner = create(project, sdk, consoleType, workingDirectory, environmentVariables);
|
||||
if (consoleRunner == null) return null;
|
||||
consoleRunner.setStatementsToExecute(statements2execute);
|
||||
consoleRunner.run();
|
||||
return consoleRunner;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
myPorts = findAvailablePorts(getProject(), myConsoleType);
|
||||
|
||||
assert myPorts != null;
|
||||
|
||||
myCommandLineArgumentsProvider = createCommandLineArgumentsProvider(mySdk, myEnvironmentVariables, myPorts);
|
||||
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(getProject(), "Connecting to console", false) {
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
indicator.setText("Connecting to console...");
|
||||
@@ -168,11 +176,16 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
return create(project, sdk, consoleType, workingDirectory, Maps.<String, String>newHashMap());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PydevConsoleRunner create(@NotNull final Project project,
|
||||
@NotNull final Sdk sdk,
|
||||
@NotNull final PyConsoleType consoleType,
|
||||
@Nullable final String workingDirectory,
|
||||
@NotNull final Map<String, String> environmentVariables) {
|
||||
return new PydevConsoleRunner(project, sdk, consoleType, workingDirectory, environmentVariables);
|
||||
}
|
||||
|
||||
private static int[] findAvailablePorts(Project project, PyConsoleType consoleType) {
|
||||
final int[] ports;
|
||||
try {
|
||||
// File "pydev/console/pydevconsole.py", line 223, in <module>
|
||||
@@ -183,6 +196,12 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
ExecutionHelper.showErrors(project, Arrays.<Exception>asList(e), consoleType.getTitle(), null);
|
||||
return null;
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
private static CommandLineArgumentsProvider createCommandLineArgumentsProvider(final Sdk sdk,
|
||||
final Map<String, String> environmentVariables,
|
||||
int[] ports) {
|
||||
final ArrayList<String> args = new ArrayList<String>();
|
||||
args.add(sdk.getHomePath());
|
||||
final String versionString = sdk.getVersionString();
|
||||
@@ -193,7 +212,7 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
for (int port : ports) {
|
||||
args.add(String.valueOf(port));
|
||||
}
|
||||
final CommandLineArgumentsProvider provider = new CommandLineArgumentsProvider() {
|
||||
return new CommandLineArgumentsProvider() {
|
||||
public String[] getArguments() {
|
||||
return ArrayUtil.toStringArray(args);
|
||||
}
|
||||
@@ -206,8 +225,6 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
return addDefaultEnvironments(sdk, environmentVariables);
|
||||
}
|
||||
};
|
||||
|
||||
return new PydevConsoleRunner(project, sdk, consoleType, provider, workingDirectory, ports);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,8 +240,8 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
if (PySdkUtil.isRemote(mySdk)) {
|
||||
PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
|
||||
if (manager != null) {
|
||||
return createRemoteConsoleProcess(manager, myCommandLineArgumentsProvider.getArguments(),
|
||||
myCommandLineArgumentsProvider.getAdditionalEnvs());
|
||||
return createRemoteConsoleProcess(manager, myCommandLineArgumentsProvider.getArguments(),
|
||||
myCommandLineArgumentsProvider.getAdditionalEnvs());
|
||||
}
|
||||
throw new PythonRemoteInterpreterManager.PyRemoteInterpreterExecutionException();
|
||||
}
|
||||
@@ -264,16 +281,33 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
manager.createRemoteProcess(getProject(), data, commandLine, false);
|
||||
|
||||
|
||||
Scanner s = new Scanner(remoteProcess.getInputStream());
|
||||
boolean received = false;
|
||||
Pair<Integer, Integer> remotePorts = getRemotePortsFromProcess(remoteProcess);
|
||||
|
||||
remoteProcess.addLocalTunnel(myPorts[0], data.getHost(), remotePorts.first);
|
||||
remoteProcess.addRemoteTunnel(remotePorts.second, "localhost", myPorts[1]);
|
||||
|
||||
|
||||
try {
|
||||
myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]);
|
||||
return remoteProcess;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ExecutionException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Pair<Integer, Integer> getRemotePortsFromProcess(RemoteSshProcess process) throws ExecutionException {
|
||||
Scanner s = new Scanner(process.getInputStream());
|
||||
|
||||
|
||||
long started = System.currentTimeMillis();
|
||||
while (!received && (System.currentTimeMillis() - started < 2000)) {
|
||||
|
||||
while (System.currentTimeMillis() - started < PORTS_WAITING_TIMEOUT) {
|
||||
try {
|
||||
int port = s.nextInt();
|
||||
int port2 = s.nextInt();
|
||||
received = true;
|
||||
remoteProcess.addLocalTunnel(myPorts[0], data.getHost(), port);
|
||||
remoteProcess.addRemoteTunnel(port2, "localhost", myPorts[1]);
|
||||
|
||||
return Pair.create(port, port2);
|
||||
}
|
||||
catch (Exception e) {
|
||||
try {
|
||||
@@ -282,17 +316,27 @@ public class PydevConsoleRunner extends AbstractConsoleRunnerWithHistory<PythonC
|
||||
catch (InterruptedException e1) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (process.exitValue() != 0) {
|
||||
String error;
|
||||
try {
|
||||
error = "Console process terminated with error:\n" + StreamUtil.readText(process.getErrorStream());
|
||||
}
|
||||
catch (Exception e) {
|
||||
error = "Console process terminated with exit code " + process.exitValue();
|
||||
}
|
||||
throw new ExecutionException(error);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IllegalThreadStateException e) {
|
||||
//continue
|
||||
}
|
||||
}
|
||||
if (!received) {
|
||||
throw new ExecutionException("Couldn't get remote ports for console connection.");
|
||||
}
|
||||
try {
|
||||
myPydevConsoleCommunication = new PydevConsoleCommunication(getProject(), myPorts[0], remoteProcess, myPorts[1]);
|
||||
return remoteProcess;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ExecutionException(e.getMessage());
|
||||
}
|
||||
|
||||
throw new ExecutionException("Couldn't get remote ports for console connection.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -55,7 +55,7 @@ public class RunPythonConsoleAction extends AnAction implements DumbAware {
|
||||
runPythonConsole(project, e.getData(LangDataKeys.MODULE));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NotNull
|
||||
public static PydevConsoleRunner runPythonConsole(Project project, Module contextModule) {
|
||||
assert project != null : "Project is null";
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiFileSystemItem;
|
||||
import com.jetbrains.django.facet.DjangoFacetType;
|
||||
import com.jetbrains.python.console.PydevConsoleRunner;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
@@ -42,6 +41,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
private int myRelativeLevel = -1;
|
||||
private boolean myWithoutRoots;
|
||||
private boolean myWithoutForeign;
|
||||
private boolean myWithMembers;
|
||||
|
||||
public QualifiedNameResolverImpl(@NotNull String qNameString) {
|
||||
myQualifiedName = PyQualifiedName.fromDottedString(qNameString);
|
||||
@@ -130,6 +130,12 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver withMembers() {
|
||||
myWithMembers = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that we're looking for a file in a directory hierarchy, not a module in the Python package hierarchy
|
||||
* (so we don't need to check for existence of __init__.py)
|
||||
@@ -146,7 +152,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
if (!root.isValid()) {
|
||||
return true;
|
||||
}
|
||||
PsiFileSystemItem resolveResult = resolveInRoot(root);
|
||||
PsiElement resolveResult = resolveInRoot(root);
|
||||
if (resolveResult != null) {
|
||||
addRoot(resolveResult, isModuleSource);
|
||||
}
|
||||
@@ -161,7 +167,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addRoot(PsiFileSystemItem resolveResult, boolean isModuleSource) {
|
||||
private void addRoot(PsiElement resolveResult, boolean isModuleSource) {
|
||||
if (isModuleSource) {
|
||||
mySourceResults.add(resolveResult);
|
||||
}
|
||||
@@ -185,7 +191,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
dir = ResolveImportUtil.stepBackFrom(footholdFile, myRelativeLevel);
|
||||
}
|
||||
|
||||
PsiFileSystemItem module = resolveModuleAt(dir);
|
||||
PsiElement module = resolveModuleAt(dir);
|
||||
if (module != null) {
|
||||
addRoot(module, true);
|
||||
}
|
||||
@@ -302,7 +308,7 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiFileSystemItem resolveInRoot(VirtualFile root) {
|
||||
private PsiElement resolveInRoot(VirtualFile root) {
|
||||
if (!root.isDirectory()) {
|
||||
// if we have added a file as a root, it's unlikely that we'll be able to resolve anything under it in 'files only' resolve mode
|
||||
return null;
|
||||
@@ -316,16 +322,16 @@ public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameReso
|
||||
* @param directory where to start from; top qualifier will be searched for here.
|
||||
*/
|
||||
@Nullable
|
||||
private PsiFileSystemItem resolveModuleAt(@Nullable PsiDirectory directory) {
|
||||
private PsiElement resolveModuleAt(@Nullable PsiDirectory directory) {
|
||||
// prerequisites
|
||||
if (directory == null || !directory.isValid()) return null;
|
||||
|
||||
PsiFileSystemItem seeker = directory;
|
||||
PsiElement seeker = directory;
|
||||
for (String name : myQualifiedName.getComponents()) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
seeker = (PsiFileSystemItem)ResolveImportUtil.resolveChild(seeker, name, myContext.getFootholdFile(), true, myCheckForPackage);
|
||||
seeker = ResolveImportUtil.resolveChild(seeker, name, myContext.getFootholdFile(), !myWithMembers, myCheckForPackage);
|
||||
}
|
||||
return seeker;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,22 @@ public class PyRefactoringUtil {
|
||||
occurrences.add(element);
|
||||
return;
|
||||
}
|
||||
if (element instanceof PyStringLiteralExpression) {
|
||||
final Pair<PsiElement, TextRange> selection = pattern.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
|
||||
if (selection != null) {
|
||||
final String substring = selection.getSecond().substring(pattern.getText());
|
||||
final PyStringLiteralExpression expr = (PyStringLiteralExpression)element;
|
||||
final String text = element.getText();
|
||||
if (text != null && expr.getStringNodes().size() == 1) {
|
||||
final int start = text.indexOf(substring);
|
||||
if (start >= 0) {
|
||||
element.putUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE, Pair.create(element, TextRange.from(start, substring.length())));
|
||||
occurrences.add(element);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
element.acceptChildren(this);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PythonStringUtil;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
@@ -44,22 +46,23 @@ import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Alexey.Ivanov
|
||||
* @author vlan
|
||||
*/
|
||||
abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
protected static PsiElement findAnchor(List<PsiElement> occurrences) {
|
||||
PsiElement anchor = occurrences.get(0);
|
||||
next:
|
||||
do {
|
||||
PyStatement statement = PsiTreeUtil.getParentOfType(anchor, PyStatement.class);
|
||||
|
||||
final PsiElement parent = statement.getParent();
|
||||
for (PsiElement element : occurrences) {
|
||||
if (!PsiTreeUtil.isAncestor(parent, element, true)) {
|
||||
anchor = statement;
|
||||
continue next;
|
||||
final PyStatement statement = PsiTreeUtil.getParentOfType(anchor, PyStatement.class);
|
||||
if (statement != null) {
|
||||
final PsiElement parent = statement.getParent();
|
||||
for (PsiElement element : occurrences) {
|
||||
if (!PsiTreeUtil.isAncestor(parent, element, true)) {
|
||||
anchor = statement;
|
||||
continue next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
while (true);
|
||||
@@ -112,7 +115,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
protected PsiElement replaceExpression(PsiElement expression, PyExpression newExpression, IntroduceOperation operation) {
|
||||
PyExpressionStatement statement = PsiTreeUtil.getParentOfType(expression, PyExpressionStatement.class);
|
||||
if (statement != null) {
|
||||
if (statement.getExpression() == expression) {
|
||||
if (statement.getExpression() == expression && expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE) == null) {
|
||||
statement.delete();
|
||||
return null;
|
||||
}
|
||||
@@ -169,6 +172,10 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
}
|
||||
};
|
||||
String text = expression.getText();
|
||||
final Pair<PsiElement, TextRange> selection = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
|
||||
if (selection != null) {
|
||||
text = selection.getSecond().substring(text);
|
||||
}
|
||||
if (expression instanceof PyCallExpression) {
|
||||
final PyExpression callee = ((PyCallExpression)expression).getCallee();
|
||||
if (callee != null) {
|
||||
@@ -263,13 +270,26 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Introduce refactoring for substrings is not supported yet
|
||||
TextRange r = element1.getTextRange();
|
||||
if (singleElementSelection && element1 instanceof PyStringLiteralExpression &&
|
||||
(r.getStartOffset() < selectionModel.getSelectionStart() || r.getEndOffset() > selectionModel.getSelectionEnd())) {
|
||||
showCannotPerformError(project, editor);
|
||||
return;
|
||||
if (singleElementSelection && element1 instanceof PyStringLiteralExpression) {
|
||||
// TODO: Protect against escapes
|
||||
// TODO: Protect against substrings with format characters
|
||||
// TODO: Handle extracting substring from a string with formatting
|
||||
final PyStringLiteralExpression literal = (PyStringLiteralExpression)element1;
|
||||
if (literal.getStringNodes().size() > 1) {
|
||||
showCannotPerformError(project, editor);
|
||||
return;
|
||||
}
|
||||
final int offset = element1.getTextOffset();
|
||||
final TextRange selectionRange = TextRange.create(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
|
||||
final TextRange elementRange = element1.getTextRange();
|
||||
if (!elementRange.equals(selectionRange) && elementRange.contains(selectionRange)) {
|
||||
final TextRange innerRange = literal.getStringValueTextRange();
|
||||
final TextRange intersection = selectionRange.shiftRight(-offset).intersection(innerRange);
|
||||
final TextRange finalRange = intersection != null ? intersection : selectionRange;
|
||||
element1.putUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE, Pair.create(element1, finalRange));
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkIntroduceContext(file, editor, element1)) {
|
||||
return;
|
||||
}
|
||||
@@ -357,10 +377,10 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
(PyExpression)element;
|
||||
operation.setInitializer(initializer);
|
||||
|
||||
if (initializer.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE) == null) {
|
||||
if (initializer != null) {
|
||||
operation.setOccurrences(getOccurrences(element, initializer));
|
||||
operation.setSuggestedNames(getSuggestedNames(initializer));
|
||||
}
|
||||
operation.setSuggestedNames(getSuggestedNames(initializer));
|
||||
if (operation.getOccurrences().size() == 0) {
|
||||
operation.setReplaceAll(false);
|
||||
}
|
||||
@@ -453,21 +473,33 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
|
||||
@Override
|
||||
public void visitPyStringLiteralExpression(PyStringLiteralExpression node) {
|
||||
ASTNode child = node.getNode().getFirstChildNode();
|
||||
while (child != null) {
|
||||
String text = child.getText();
|
||||
if (child.getElementType() == TokenType.WHITE_SPACE) {
|
||||
if (text.contains("\n")) {
|
||||
if (!text.contains("\\")) {
|
||||
myResult.append("\\");
|
||||
final Pair<PsiElement, TextRange> data = node.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
|
||||
if (data != null) {
|
||||
final PsiElement parent = data.getFirst();
|
||||
final String text = parent.getText();
|
||||
final Pair<String, String> detectedQuotes = PythonStringUtil.getQuotes(text);
|
||||
final Pair<String, String> quotes = detectedQuotes != null ? detectedQuotes : Pair.create("'", "'");
|
||||
final TextRange range = data.getSecond();
|
||||
final String substring = range.substring(text);
|
||||
myResult.append(quotes.getFirst() + substring + quotes.getSecond());
|
||||
}
|
||||
else {
|
||||
ASTNode child = node.getNode().getFirstChildNode();
|
||||
while (child != null) {
|
||||
String text = child.getText();
|
||||
if (child.getElementType() == TokenType.WHITE_SPACE) {
|
||||
if (text.contains("\n")) {
|
||||
if (!text.contains("\\")) {
|
||||
myResult.append("\\");
|
||||
}
|
||||
myResult.append(text);
|
||||
}
|
||||
}
|
||||
else {
|
||||
myResult.append(text);
|
||||
}
|
||||
child = child.getTreeNext();
|
||||
}
|
||||
else {
|
||||
myResult.append(text);
|
||||
}
|
||||
child = child.getTreeNext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,14 +530,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
}
|
||||
|
||||
protected List<PsiElement> getOccurrences(PsiElement element, @NotNull final PyExpression expression) {
|
||||
PsiElement context = PsiTreeUtil.getParentOfType(expression, PyFunction.class);
|
||||
if (context == null) {
|
||||
context = PsiTreeUtil.getParentOfType(expression, PyClass.class);
|
||||
}
|
||||
if (context == null) {
|
||||
context = expression.getContainingFile();
|
||||
}
|
||||
return PyRefactoringUtil.getOccurrences(expression, context);
|
||||
return PyRefactoringUtil.getOccurrences(expression, ScopeUtil.getScopeOwner(expression));
|
||||
}
|
||||
|
||||
private PsiElement performReplace(@NotNull final PsiElement declaration,
|
||||
@@ -551,7 +576,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
}
|
||||
|
||||
protected PyExpression createExpression(Project project, String name, PsiElement declaration) {
|
||||
return PyElementGenerator.getInstance(project).createExpressionFromText(name);
|
||||
return PyElementGenerator.getInstance(project).createExpressionFromText(LanguageLevel.forElement(declaration), name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -26,8 +26,8 @@ public class PythonProcessHandler extends KillableColoredProcessHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canKillProcess() {
|
||||
return super.canKillProcess() && myShouldTryToKillSoftly;
|
||||
protected boolean shouldKillProcessSoftly() {
|
||||
return myShouldTryToKillSoftly;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,10 +11,17 @@ import java.util.Comparator;
|
||||
* @author yole
|
||||
*/
|
||||
public class PreferredSdkComparator implements Comparator<Sdk> {
|
||||
public static PreferredSdkComparator INSTANCE = new PreferredSdkComparator();
|
||||
|
||||
@Override
|
||||
public int compare(Sdk o1, Sdk o2) {
|
||||
final PythonSdkFlavor flavor1 = PythonSdkFlavor.getFlavor(o1);
|
||||
final PythonSdkFlavor flavor2 = PythonSdkFlavor.getFlavor(o2);
|
||||
int venv1weight = PythonSdkType.isVirtualEnv(o1) ? 0 : 1;
|
||||
int venv2weight = PythonSdkType.isVirtualEnv(o2) ? 0 : 1;
|
||||
if (venv1weight != venv2weight) {
|
||||
return venv2weight - venv1weight;
|
||||
}
|
||||
int flavor1weight = flavor1 instanceof CPythonSdkFlavor ? 1 : 0;
|
||||
int flavor2weight = flavor2 instanceof CPythonSdkFlavor ? 1 : 0;
|
||||
if (flavor1weight != flavor2weight) {
|
||||
|
||||
@@ -801,6 +801,7 @@ public class PythonSdkType extends SdkType {
|
||||
return moduleSDK;
|
||||
}
|
||||
List<Sdk> allSdks = getAllSdks();
|
||||
Collections.sort(allSdks, PreferredSdkComparator.INSTANCE);
|
||||
for (Sdk sdk : allSdks) {
|
||||
if (!getLanguageLevelForSdk(sdk).isPy3K()) {
|
||||
return sdk;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
a = b'bar'
|
||||
b'foo' + a + b'baz'
|
||||
@@ -0,0 +1 @@
|
||||
b'foo<selection>bar</selection>baz'
|
||||
@@ -0,0 +1,2 @@
|
||||
a = "hello"
|
||||
print(a + " world")
|
||||
@@ -0,0 +1 @@
|
||||
print(<selection>"hello</selection> world")
|
||||
@@ -0,0 +1,2 @@
|
||||
a = "hello"
|
||||
print(a + " world")
|
||||
@@ -0,0 +1 @@
|
||||
print("<selection>hello</selection> world")
|
||||
@@ -0,0 +1,2 @@
|
||||
a = "lo wor"
|
||||
print("hel" + a + "ld")
|
||||
@@ -0,0 +1 @@
|
||||
print("hel<selection>lo wor</selection>ld")
|
||||
@@ -0,0 +1,2 @@
|
||||
a = "world"
|
||||
print("hello " + a)
|
||||
@@ -0,0 +1 @@
|
||||
print("hello <selection>world</selection>")
|
||||
@@ -0,0 +1,2 @@
|
||||
a = 'foo'
|
||||
print((a + 'bar').upper())
|
||||
@@ -0,0 +1 @@
|
||||
print('<selection>foo</selection>bar'.upper())
|
||||
@@ -0,0 +1,2 @@
|
||||
a = "two"
|
||||
"one " + a + " three"
|
||||
@@ -0,0 +1 @@
|
||||
"one <selection>two</selection> three"
|
||||
@@ -0,0 +1,2 @@
|
||||
a = 'foo'
|
||||
x = a + 'bar'
|
||||
@@ -0,0 +1 @@
|
||||
x = '<selection>foo</selection>bar'
|
||||
@@ -0,0 +1,6 @@
|
||||
a = """Three"""
|
||||
print(""""One two
|
||||
|
||||
* """ + a + """
|
||||
* Four
|
||||
* Five""")
|
||||
@@ -0,0 +1,5 @@
|
||||
print(""""One two
|
||||
|
||||
* <selection>Three</selection>
|
||||
* Four
|
||||
* Five""")
|
||||
@@ -88,6 +88,51 @@ public class PyIntroduceVariableTest extends PyIntroduceTestCase {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testLeftSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testRightSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testMiddleSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testLeftQuoteSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testSubstringInExpression() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testSubstringInStatement() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testTripleQuotedSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-3654
|
||||
public void testSubstringInExpressionStatement() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-6354
|
||||
public void testBytesSubstring() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTestCannotPerform() {
|
||||
boolean thrownExpectedException = false;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user