From e099a51b94cdb03f99515fa7ab11612bf3f7d83e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 16:51:05 +0300 Subject: [PATCH 01/13] fix two big prodolbs --- python/src/com/jetbrains/python/sdk/PythonSdkType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 6c1d8963d588..32ae406987d8 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -79,7 +79,7 @@ public class PythonSdkType extends SdkType { @NotNull @NonNls public String getBuiltinsFileName(Sdk sdk) { - final String version = getVersionString(sdk); + final String version = sdk.getVersionString(); if (version != null && version.startsWith("Python 3")) { return "builtins.py"; } From 5738de3b1197cf71e751199336441d75896ac340 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 17:09:00 +0300 Subject: [PATCH 02/13] fix build of missing skeletons --- .../src/com/jetbrains/python/psi/impl/PyBuiltinCache.java | 2 +- python/src/com/jetbrains/python/sdk/PythonSdkType.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java index 6e989c34acc7..0c97a308f5f0 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java +++ b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java @@ -59,7 +59,7 @@ public class PyBuiltinCache { } if (sdk != null) { // dig out the builtins file, create an instance based on it - final String[] urls = sdk.getRootProvider().getUrls(PythonSdkType.BUITLIN_ROOT_TYPE); + final String[] urls = sdk.getRootProvider().getUrls(PythonSdkType.BUILTIN_ROOT_TYPE); for (String url : urls) { if (url.contains(PythonSdkType.SKELETON_DIR_NAME)) { final String builtins_url = url + "/" + ((PythonSdkType)sdk.getSdkType()).getBuiltinsFileName(sdk); diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 32ae406987d8..cd1bc404c714 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -218,7 +218,7 @@ public class PythonSdkType extends SdkType { @Override public SdkAdditionalData loadAdditionalData(final Sdk currentSdk, final Element additional) { - final String[] urls = currentSdk.getRootProvider().getUrls(OrderRootType.SOURCES); + final String[] urls = currentSdk.getRootProvider().getUrls(BUILTIN_ROOT_TYPE); for (String url : urls) { if (url.contains(SKELETON_DIR_NAME)) { final String path = VfsUtil.urlToPath(url); @@ -266,7 +266,7 @@ public class PythonSdkType extends SdkType { /** * In which root type built-in skeletons are put. */ - public static final OrderRootType BUITLIN_ROOT_TYPE = OrderRootType.CLASSES; + public static final OrderRootType BUILTIN_ROOT_TYPE = OrderRootType.CLASSES; public static void setupSdkPaths(SdkModificator sdkModificator, ProgressIndicator indicator) { String sdk_path = sdkModificator.getHomePath(); @@ -305,7 +305,7 @@ public class PythonSdkType extends SdkType { indicator.setText2(""); } generateBuiltinStubs(sdk_path, stubs_path); - sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(stubs_path), BUITLIN_ROOT_TYPE); + sdkModificator.addRoot(LocalFileSystem.getInstance().refreshAndFindFileByPath(stubs_path), BUILTIN_ROOT_TYPE); } generateBinaryStubs(sdk_path, stubs_path, indicator); } From 99c686c7d4725ab9494c9cb2fb07dd79c8980445 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 1 Feb 2010 17:08:23 +0300 Subject: [PATCH 03/13] introduce field --- .../python/actions/AddFieldQuickFix.java | 48 +++++++++-- .../refactoring/RefactoringProvider.java | 6 ++ .../classes/PyClassRefactoringUtil.java | 5 +- .../introduce/IntroduceHandler.java | 43 +++++++--- .../introduce/PyIntroduceDialog.form | 16 +++- .../introduce/PyIntroduceDialog.java | 31 ++++--- .../constant/ConstantIntroduceHandler.java | 3 +- .../field/FieldIntroduceHandler.java | 85 +++++++++++++++++++ .../field/IntroduceFieldValidator.java | 37 ++++++++ .../variable/VariableIntroduceHandler.java | 7 +- 10 files changed, 241 insertions(+), 40 deletions(-) create mode 100644 python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java create mode 100644 python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java diff --git a/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java b/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java index 4355e2de8689..e54eeba3c4dc 100644 --- a/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java +++ b/python/src/com/jetbrains/python/actions/AddFieldQuickFix.java @@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; +import com.intellij.util.Function; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonLanguage; @@ -39,7 +40,7 @@ public class AddFieldQuickFix implements LocalQuickFix { return PyBundle.message("INSP.GROUP.python"); } - private static PsiElement appendToInit(PyFunction init, String item_name, PyElementGenerator generator, Project project) { + private static PsiElement appendToInit(PyFunction init, Function callback) { // add this field as the last stmt of the constructor final PyStatementList stmt_list = init.getStatementList(); PyStatement[] stmts = stmt_list.getStatements(); // NOTE: rather wasteful, consider iterable stmt list @@ -51,7 +52,7 @@ public class AddFieldQuickFix implements LocalQuickFix { if (params.length > 0) { self_name = params[0].getName(); } - PyStatement new_stmt = generator.createFromText(project, PyStatement.class, self_name + "." +item_name + " = None"); + PyStatement new_stmt = callback.fun(self_name); PyUtil.ensureWritable(stmt_list); return stmt_list.addAfter(new_stmt, last_stmt); } @@ -60,15 +61,24 @@ public class AddFieldQuickFix implements LocalQuickFix { // expect the descriptor to point to the unresolved identifier. PyClass cls = myQualifierClass; String item_name = myIdentifier; + if (cls != null) { + if (addFieldToInit(project, cls, item_name, new CreateFieldCallback(project, item_name))) return; + } + // somehow we failed. tell about this + PyUtil.showBalloon(project, PyBundle.message("QFIX.failed.to.add.field"), MessageType.ERROR); + } + + public static boolean addFieldToInit(Project project, PyClass cls, String item_name, FieldCallback callback) { if (cls != null && item_name != null) { PyFunction init = cls.findMethodByName(PyNames.INIT, false); Language language = cls.getLanguage(); if (language instanceof PythonLanguage) { PythonLanguage pythonLanguage = (PythonLanguage)language; PyElementGenerator generator = pythonLanguage.getElementGenerator(); + callback.setGenerator(generator); if (init != null) { - appendToInit(init, item_name, generator, project); - return; + appendToInit(init, callback); + return true; } else { // no init! boldly copy ancestor's. for (PyClass ancestor : cls.iterateAncestors()) { @@ -77,7 +87,7 @@ public class AddFieldQuickFix implements LocalQuickFix { } PyFunction new_init = createInitMethod(project, cls, init, generator); - appendToInit(new_init, item_name, generator, project); + appendToInit(new_init, callback); new_init.add(generator.createFromText(project, PsiWhiteSpace.class, "\n\n")); // after the last line PsiElement add_anchor = null; @@ -87,13 +97,12 @@ public class AddFieldQuickFix implements LocalQuickFix { cls_content.addAfter(new_init, add_anchor); PyUtil.showBalloon(project, PyBundle.message("QFIX.added.constructor.$0.for.field.$1", cls.getName(), item_name), MessageType.INFO); - return; + return true; //else // well, that can't be } } } - // somehow we failed. tell about this - PyUtil.showBalloon(project, PyBundle.message("QFIX.failed.to.add.field"), MessageType.ERROR); + return false; } private static PyFunction createInitMethod(Project project, PyClass cls, @Nullable PyFunction ancestorInit, PyElementGenerator generator) { @@ -125,4 +134,27 @@ public class AddFieldQuickFix implements LocalQuickFix { } return new_init; } + + public abstract static class FieldCallback implements Function { + protected PyElementGenerator myGenerator; + + public void setGenerator(PyElementGenerator generator) { + myGenerator = generator; + } + } + + private static class CreateFieldCallback extends FieldCallback { + private Project myProject; + private String myItemName; + + + private CreateFieldCallback(Project project, String itemName) { + myProject = project; + myItemName = itemName; + } + + public PyStatement fun(String self_name) { + return myGenerator.createFromText(myProject, PyStatement.class, self_name + "." + myItemName + " = None"); + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java index 5b90f320af17..a08ff16299b1 100644 --- a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java +++ b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java @@ -7,6 +7,7 @@ import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHandler; import com.jetbrains.python.refactoring.classes.pushDown.PyPushDownHandler; import com.jetbrains.python.refactoring.extractmethod.PyExtractMethodHandler; import com.jetbrains.python.refactoring.introduce.constant.ConstantIntroduceHandler; +import com.jetbrains.python.refactoring.introduce.field.FieldIntroduceHandler; import com.jetbrains.python.refactoring.introduce.variable.VariableIntroduceHandler; /** @@ -26,6 +27,11 @@ public class RefactoringProvider extends DefaultRefactoringSupportProvider { return new ConstantIntroduceHandler(); } + @Override + public RefactoringActionHandler getIntroduceFieldHandler() { + return new FieldIntroduceHandler(); + } + @Override public RefactoringActionHandler getPullUpHandler() { return new PyPullUpHandler(); diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index b918627cea5c..09e3efabcc2b 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -122,9 +122,8 @@ public class PyClassRefactoringUtil { @Nullable public static String prepareClassText(PyClass superClass, PyElement[] elements, boolean up, boolean ignoreNoChanges, final String preparedClassName) { - PsiElement sibling = elements[0].getPrevSibling(); - sibling = sibling == null ? elements[0].getParent().getPrevSibling() : sibling; - final String white = sibling.getText(); + PsiElement sibling = superClass.getPrevSibling(); + final String white = sibling != null ? "\n" + sibling.getText() + " ": "\n "; final StringBuilder builder = new StringBuilder("class "); if (preparedClassName != null) { builder.append(preparedClassName).append(":"); diff --git a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java index 971f9b620229..fc9f10b29030 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java @@ -46,7 +46,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { } public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { - performAction(project, editor, file, null, false); + performAction(project, editor, file, null, false, false); } public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { @@ -76,7 +76,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { return ArrayUtil.toStringArray(res); } - protected void performAction(@NotNull final Project project, Editor editor, PsiFile file, String name, boolean replaceAll) { + protected void performAction(@NotNull final Project project, Editor editor, PsiFile file, String name, boolean replaceAll, boolean hasConstructor) { if (!CommonRefactoringUtil.checkReadOnlyStatus(file)) { return; } @@ -109,6 +109,11 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { "refactoring.extractMethod"); return; } + + if (!checkEnabled(project, editor, element1, myDialogTitle)) { + return; + } + final PyExpression expression = (PyExpression)element1; final List occurrences; @@ -119,21 +124,30 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { occurrences = Collections.emptyList(); } String[] possibleNames = getSuggestedNames(expression); + boolean initInConstructor = false; if (name == null) { - PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames); + PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames, hasConstructor); dialog.show(); if (!dialog.isOK()) { return; } name = dialog.getName(); replaceAll = dialog.doReplaceAllOccurrences(); + initInConstructor = dialog.initInConstructor(); } String assignmentText = name + " = " + expression.getText(); - final PyAssignmentStatement declaration = - PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, assignmentText); + final PyAssignmentStatement declaration = createDeclaration(project, assignmentText); assert name != null; - performReplace(project, declaration, expression, occurrences, name, replaceAll); + performReplace(project, declaration, expression, occurrences, name, replaceAll, initInConstructor); + } + + protected PyAssignmentStatement createDeclaration(Project project, String assignmentText) { + return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, assignmentText); + } + + protected boolean checkEnabled(Project project, Editor editor, PsiElement element1, String dialogTitle) { + return true; } private static List getOccurrences(@NotNull final PyExpression expression) { @@ -152,19 +166,19 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { @NotNull final PsiElement expression, @NotNull final List occurrences, @NotNull final String name, - final boolean replaceAll) { + final boolean replaceAll, + final boolean initInConstructor) { new WriteCommandAction(project, expression.getContainingFile()) { protected void run(final Result result) throws Throwable { final Pair data = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE); if (data == null) { - addDeclaration(expression, declaration, occurrences, replaceAll); + addDeclaration(expression, declaration, occurrences, replaceAll, initInConstructor); } else { - addDeclaration(data.first, declaration, occurrences, replaceAll); + addDeclaration(data.first, declaration, occurrences, replaceAll, initInConstructor); } - PyExpressionStatement newExpression = - PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, name); + PyExpressionStatement newExpression = createExpression(project, name, declaration); if (replaceAll) { for (PsiElement occurrence : occurrences) { @@ -178,8 +192,13 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { }.execute(); } + protected PyExpressionStatement createExpression(Project project, String name, PyAssignmentStatement declaration) { + return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, name); + } + protected abstract PsiElement addDeclaration(@NotNull final PsiElement expression, @NotNull final PsiElement declaration, @NotNull final List occurrences, - final boolean replaceAll); + final boolean replaceAll, + boolean initInConstructor); } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form index 508979c86873..202c4f8ac632 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form +++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.form @@ -43,7 +43,7 @@ - + @@ -51,13 +51,21 @@ - + + + + + + + + + + - - + diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java index f8c590f93df9..62054dbaa7c2 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java @@ -5,6 +5,7 @@ import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.refactoring.RefactoringBundle; import com.intellij.ui.EditorComboBoxEditor; import com.intellij.ui.EditorComboBoxRenderer; import com.intellij.ui.EditorTextField; @@ -28,7 +29,8 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti private JPanel myContentPane; private JLabel myNameLabel; private ComboBox myNameComboBox; - private JCheckBox myCheckBox; + private JCheckBox myReplaceAll; + private JCheckBox myInitialzeInConstructor; private final Project myProject; private final int myOccurrencesCount; @@ -40,7 +42,8 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti @NotNull final String caption, @NotNull final IntroduceValidator validator, final int occurrencesCount, - final String[] possibleNames) { + final String[] possibleNames, + boolean hasConstructor) { super(project, true); myOccurrencesCount = occurrencesCount; myValidator = validator; @@ -51,7 +54,7 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti setModal(true); setTitle(caption); init(); - setupDialog(); + setupDialog(hasConstructor); updateControls(); } @@ -89,19 +92,21 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti } } - private void setupDialog() { - myCheckBox.setMnemonic(KeyEvent.VK_A); + private void setupDialog(boolean hasConstructor) { + myReplaceAll.setMnemonic(KeyEvent.VK_A); myNameLabel.setLabelFor(myNameComboBox); + myInitialzeInConstructor.setText(RefactoringBundle.message("initialize.in.border.title") + " " + RefactoringBundle.message("class.constructors.radio")); + myInitialzeInConstructor.setVisible(hasConstructor); // Replace occurences check box setup if (myOccurrencesCount > 1) { - myCheckBox.setSelected(false); - myCheckBox.setEnabled(true); - myCheckBox.setText(myCheckBox.getText() + " (" + myOccurrencesCount + " occurrences)"); + myReplaceAll.setSelected(false); + myReplaceAll.setEnabled(true); + myReplaceAll.setText(myReplaceAll.getText() + " (" + myOccurrencesCount + " occurrences)"); } else { - myCheckBox.setSelected(false); - myCheckBox.setEnabled(false); + myReplaceAll.setSelected(false); + myReplaceAll.setEnabled(false); } } @@ -131,7 +136,7 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti } public boolean doReplaceAllOccurrences() { - return myCheckBox.isSelected(); + return myReplaceAll.isSelected(); } private void updateControls() { @@ -143,4 +148,8 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti } setErrorText(myValidator.check(this)); } + + public boolean initInConstructor() { + return myInitialzeInConstructor.isSelected(); + } } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java index c1f7b6d469a4..38b7c045612a 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java @@ -26,7 +26,8 @@ public class ConstantIntroduceHandler extends IntroduceHandler { protected PsiElement addDeclaration(@NotNull final PsiElement expression, @NotNull final PsiElement declaration, @NotNull final List occurrences, - final boolean replaceAll) { + final boolean replaceAll, + boolean initInConstructor) { PsiElement anchor; anchor = expression.getContainingFile(); assert anchor instanceof PyFile; diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java new file mode 100644 index 000000000000..329e44792f55 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java @@ -0,0 +1,85 @@ +package com.jetbrains.python.refactoring.introduce.field; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.actions.AddFieldQuickFix; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.refactoring.introduce.IntroduceHandler; +import com.jetbrains.python.refactoring.introduce.variable.VariableIntroduceHandler; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * @author Dennis.Ushakov + */ +public class FieldIntroduceHandler extends IntroduceHandler { + + public FieldIntroduceHandler() { + super(new IntroduceFieldValidator(), RefactoringBundle.message("introduce.field.title")); + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { + performAction(project, editor, file, null, false, true); + } + + @Override + protected boolean checkEnabled(Project project, Editor editor, PsiElement element1, String dialogTitle) { + if (PyUtil.getContainingClassOrSelf(element1) == null) { + CommonRefactoringUtil.showErrorHint(project, editor, "Cannot introduce field: not in class", dialogTitle, + "refactoring.extractMethod"); + return false; + } + return true; + } + + @Override + protected PsiElement addDeclaration(@NotNull PsiElement expression, @NotNull PsiElement declaration, @NotNull List occurrences, + boolean replaceAll, boolean initInConstructor) { + final PsiElement expr = expression instanceof PyClass ? expression : expression.getParent(); + PsiElement anchor = PyUtil.getContainingClassOrSelf(expr); + assert anchor instanceof PyClass; + if (initInConstructor) { + final Project project = anchor.getProject(); + AddFieldQuickFix.addFieldToInit(project, (PyClass)anchor, "", new AddFieldDeclaration(project, declaration)); + } + return VariableIntroduceHandler.doIntroduceVariable(expression, declaration, occurrences, replaceAll); + } + + @Override + protected PyExpressionStatement createExpression(Project project, String name, PyAssignmentStatement declaration) { + final String text = declaration.getText(); + final String self_name = text.substring(0, text.indexOf('.')); + return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, self_name + "." + name); + } + + @Override + protected PyAssignmentStatement createDeclaration(Project project, String assignmentText) { + return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, PyNames.CANONICAL_SELF + "." + assignmentText); + } + + private static class AddFieldDeclaration extends AddFieldQuickFix.FieldCallback { + private final Project myProject; + private final PsiElement myDeclaration; + + private AddFieldDeclaration(Project project, PsiElement declaration) { + myProject = project; + myDeclaration = declaration; + } + + public PyStatement fun(String self_name) { + if (PyNames.CANONICAL_SELF.equals(self_name)) { + return (PyStatement)myDeclaration; + } + final String text = myDeclaration.getText(); + return myGenerator.createFromText(myProject, PyStatement.class, text.replaceFirst(PyNames.CANONICAL_SELF + "\\.", self_name + ".")); + } + } +} diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java b/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java new file mode 100644 index 000000000000..3e9a7ce2a1a1 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/IntroduceFieldValidator.java @@ -0,0 +1,37 @@ +package com.jetbrains.python.refactoring.introduce.field; + +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.refactoring.PyRefactoringUtil; +import com.jetbrains.python.refactoring.introduce.IntroduceValidator; + +/** + * @author Dennis.Ushakov + */ +public class IntroduceFieldValidator extends IntroduceValidator { + @Override + protected String simpleCheck(String name, PsiElement psiElement) { + if (psiElement.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE) != null) { + final Pair data = psiElement.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE); + psiElement = data.first; + } + PsiElement context = PsiTreeUtil.getParentOfType(psiElement, PyFunction.class); + if (context == null) { + context = PsiTreeUtil.getParentOfType(psiElement, PyClass.class); + } + if (context == null) { + context = psiElement.getContainingFile(); + } + + if (PyRefactoringUtil.collectScopeVariables(context).contains(name)) { + return PyBundle.message("refactoring.introduce.constant.scope.error"); + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java index cdaca71ea201..3f13e9454b47 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java @@ -25,7 +25,12 @@ public class VariableIntroduceHandler extends IntroduceHandler { protected PsiElement addDeclaration(@NotNull final PsiElement expression, @NotNull final PsiElement declaration, @NotNull final List occurrences, - final boolean replaceAll) { + final boolean replaceAll, + boolean initInConstructor) { + return doIntroduceVariable(expression, declaration, occurrences, replaceAll); + } + + public static PsiElement doIntroduceVariable(PsiElement expression, PsiElement declaration, List occurrences, boolean replaceAll) { PyStatement anchorStatement; if (replaceAll) { final PsiElement parent = PsiTreeUtil.findCommonParent(occurrences.toArray(new PsiElement[occurrences.size()])); From da11e8d48018f1042e73d12e6f6470bd21cd0232 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 1 Feb 2010 17:14:53 +0300 Subject: [PATCH 04/13] introduce field --- .../python/refactoring/introduce/IntroduceHandler.java | 2 ++ .../introduce/field/FieldIntroduceHandler.java | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java index fc9f10b29030..82d8c06da930 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java @@ -25,6 +25,7 @@ import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.refactoring.NameSuggestorUtil; import com.jetbrains.python.refactoring.PyRefactoringUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; @@ -196,6 +197,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyExpressionStatement.class, name); } + @Nullable protected abstract PsiElement addDeclaration(@NotNull final PsiElement expression, @NotNull final PsiElement declaration, @NotNull final List occurrences, diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java index 329e44792f55..97c632c79046 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java @@ -14,6 +14,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.refactoring.introduce.IntroduceHandler; import com.jetbrains.python.refactoring.introduce.variable.VariableIntroduceHandler; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; @@ -40,6 +41,7 @@ public class FieldIntroduceHandler extends IntroduceHandler { return true; } + @Nullable @Override protected PsiElement addDeclaration(@NotNull PsiElement expression, @NotNull PsiElement declaration, @NotNull List occurrences, boolean replaceAll, boolean initInConstructor) { @@ -48,7 +50,11 @@ public class FieldIntroduceHandler extends IntroduceHandler { assert anchor instanceof PyClass; if (initInConstructor) { final Project project = anchor.getProject(); - AddFieldQuickFix.addFieldToInit(project, (PyClass)anchor, "", new AddFieldDeclaration(project, declaration)); + final PyClass clazz = (PyClass)anchor; + AddFieldQuickFix.addFieldToInit(project, clazz, "", new AddFieldDeclaration(project, declaration)); + final PyFunction init = clazz.findMethodByName(PyNames.INIT, false); + final PyStatementList statements = init != null ? init.getStatementList() : null; + return statements != null ? statements.getLastChild() : null; } return VariableIntroduceHandler.doIntroduceVariable(expression, declaration, occurrences, replaceAll); } From bfe7375466c4768636cbb43025591cbbf41bd60e Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 1 Feb 2010 17:25:18 +0300 Subject: [PATCH 05/13] help id's --- .../python/refactoring/introduce/IntroduceHandler.java | 4 +++- .../python/refactoring/introduce/PyIntroduceDialog.java | 8 ++++++++ .../introduce/constant/ConstantIntroduceHandler.java | 5 +++++ .../introduce/field/FieldIntroduceHandler.java | 5 +++++ .../introduce/variable/VariableIntroduceHandler.java | 5 +++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java index 82d8c06da930..b27ffc5c4b6a 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java @@ -127,7 +127,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { String[] possibleNames = getSuggestedNames(expression); boolean initInConstructor = false; if (name == null) { - PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames, hasConstructor); + PyIntroduceDialog dialog = new PyIntroduceDialog(project, expression, myDialogTitle, myValidator, occurrences.size(), possibleNames, getHelpId(), hasConstructor); dialog.show(); if (!dialog.isOK()) { return; @@ -143,6 +143,8 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { performReplace(project, declaration, expression, occurrences, name, replaceAll, initInConstructor); } + protected abstract String getHelpId(); + protected PyAssignmentStatement createDeclaration(Project project, String assignmentText) { return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, assignmentText); } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java index 62054dbaa7c2..20d2ffcf8d50 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java @@ -36,6 +36,7 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti private final int myOccurrencesCount; private final IntroduceValidator myValidator; private final PyExpression myExpression; + private final String myHelpId; public PyIntroduceDialog(@NotNull final Project project, @NotNull PyExpression expression, @@ -43,12 +44,14 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti @NotNull final IntroduceValidator validator, final int occurrencesCount, final String[] possibleNames, + final String helpId, boolean hasConstructor) { super(project, true); myOccurrencesCount = occurrencesCount; myValidator = validator; myProject = project; myExpression = expression; + myHelpId = helpId; setUpNameComboBox(possibleNames); setModal(true); @@ -58,6 +61,11 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti updateControls(); } + @Override + protected String getHelpId() { + return myHelpId; + } + private void setUpNameComboBox(String[] possibleNames) { final EditorComboBoxEditor comboEditor = new StringComboboxEditor(myProject, PythonFileType.INSTANCE, myNameComboBox); diff --git a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java index 38b7c045612a..1a55ddcd7e3d 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/constant/ConstantIntroduceHandler.java @@ -41,4 +41,9 @@ public class ConstantIntroduceHandler extends IntroduceHandler { } return ArrayUtil.toStringArray(names); } + + @Override + protected String getHelpId() { + return "refactoring.introduceConstant"; + } } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java index 97c632c79046..83595e262459 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java @@ -71,6 +71,11 @@ public class FieldIntroduceHandler extends IntroduceHandler { return PythonLanguage.getInstance().getElementGenerator().createFromText(project, PyAssignmentStatement.class, PyNames.CANONICAL_SELF + "." + assignmentText); } + @Override + protected String getHelpId() { + return "refactoring.introduceField"; + } + private static class AddFieldDeclaration extends AddFieldQuickFix.FieldCallback { private final Project myProject; private final PsiElement myDeclaration; diff --git a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java index 3f13e9454b47..7d843ca4c95d 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/variable/VariableIntroduceHandler.java @@ -44,4 +44,9 @@ public class VariableIntroduceHandler extends IntroduceHandler { anchorStatement.getParent().addBefore(declaration, anchorStatement); return anchorStatement.getParent().getParent(); } + + @Override + protected String getHelpId() { + return "refactoring.introduceVariable"; + } } From c6fd7c84b254480c4ae849f1e741211a2868ad56 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Mon, 1 Feb 2010 17:31:22 +0300 Subject: [PATCH 06/13] minor --- .../python/refactoring/introduce/PyIntroduceDialog.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java index 20d2ffcf8d50..3546e262767f 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/PyIntroduceDialog.java @@ -150,11 +150,7 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti private void updateControls() { final boolean nameValid = myValidator.isNameValid(this); setOKActionEnabled(nameValid); - if (!nameValid) { - setErrorText(PyBundle.message("refactoring.introduce.name.error")); - return; - } - setErrorText(myValidator.check(this)); + setErrorText(!nameValid ? PyBundle.message("refactoring.introduce.name.error") : null); } public boolean initInConstructor() { From 9dab6500936fd8c9c461cfe5debb1157167b64df Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 18:11:42 +0300 Subject: [PATCH 07/13] some more optimizations for iterateAncestors() --- .../python/actions/AddMethodQuickFix.java | 5 +- .../src/com/jetbrains/python/psi/PyClass.java | 4 +- .../python/psi/impl/PyCallExpressionImpl.java | 2 +- .../python/psi/impl/PyClassImpl.java | 142 +++++++++++------- 4 files changed, 90 insertions(+), 63 deletions(-) diff --git a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java index 8bdd0bafbc02..869ec58be292 100644 --- a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java +++ b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java @@ -14,12 +14,13 @@ import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.*; -import static com.jetbrains.python.psi.PyUtil.sure; import com.jetbrains.python.psi.impl.ParamHelper; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; import org.jetbrains.annotations.NotNull; +import static com.jetbrains.python.psi.PyUtil.sure; + /** * Adds a method foo to class X if X.foo() is unresolved. * User: dcheryasov @@ -73,7 +74,7 @@ public class AddMethodQuickFix implements LocalQuickFix { if (call_by_class) { if (args.length > 0) { PyType first_arg_type = args[0].getType(); - if (first_arg_type instanceof PyClassType && ((PyClassType)first_arg_type).getPyClass().isSublclass(cls)) { + if (first_arg_type instanceof PyClassType && ((PyClassType)first_arg_type).getPyClass().isSubclass(cls)) { // class, first arg ok: instance method param_buf.append("self"); // NOTE: might use a name other than 'self', according to code style. made_instance = true; diff --git a/python/src/com/jetbrains/python/psi/PyClass.java b/python/src/com/jetbrains/python/psi/PyClass.java index 86d7c50ea2d9..3e9b30d603c0 100644 --- a/python/src/com/jetbrains/python/psi/PyClass.java +++ b/python/src/com/jetbrains/python/psi/PyClass.java @@ -19,7 +19,7 @@ public interface PyClass extends PsiNamedElement, PyStatement, NameDefiner, PyDo @NotNull PyExpression[] getSuperClassExpressions(); - @Nullable + @NotNull PsiElement[] getSuperClassElements(); @NotNull @@ -56,7 +56,7 @@ public interface PyClass extends PsiNamedElement, PyStatement, NameDefiner, PyDo * @param parent * @return True iff this and parent are the same or parent is one of our superclasses. */ - boolean isSublclass(PyClass parent); + boolean isSubclass(PyClass parent); @Nullable PyDecoratorList getDecoratorList(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java index 09a3a1639344..e954bc57a04d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java @@ -104,7 +104,7 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress // imitate isinstance(second_arg, possible_class) PyClass second_class = ((PyClassType)second_type).getPyClass(); assert second_class != null; - if (second_class.isSublclass(first_class)) { + if (second_class.isSubclass(first_class)) { // TODO: super(Foo, Bar) is a superclass of Foo directly preceding Bar in MRO return new PyClassType(first_class, false); // super(Foo, self) has type of Foo, modulo __get__() } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 01bf4d804b8c..b24505357513 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -90,6 +90,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement return PyExpression.EMPTY_ARRAY; } + @NotNull public PsiElement[] getSuperClassElements() { final PyExpression[] superExpressions = getSuperClassExpressions(); List superClasses = new ArrayList(); @@ -109,60 +110,10 @@ public class PyClassImpl extends PyPresentableElementImpl implement It must be more efficient on deep and wide hierarchies, but it was more fun than efficiency that produced it. */ public Iterable iterateAncestors() { - return new Iterable() { - public Iterator iterator() { - return new Iterator() { - List pending = new LinkedList(); - Set seen = new HashSet(); - Iterator percolator = getSuperClassesList().iterator(); - PyClass prefetch = null; - - public boolean hasNext() { - // due to already-seen filtering, there's no way but to try and see. - if (prefetch != null) return true; - try { - prefetch = next(); - return true; - } - catch (NoSuchElementException e) { - return false; - } - } - - public PyClass next() { - iterations: - while (true) { - if (prefetch != null) { - PyClass ret = prefetch; - prefetch = null; - return ret; - } - if (percolator.hasNext()) { - PyClass it = percolator.next(); - if (seen.contains(it)) continue iterations; // loop back is equivalent to return next(); - pending.add(it); - seen.add(it); - return it; - } - else if (pending.size() > 0) { - PyClass it = pending.get(0); - pending.remove(0); // t, ts* = pending - percolator = it.iterateAncestors().iterator(); - // loop back is equivalent to return next(); - } - else throw new NoSuchElementException(); - } - } - - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - }; + return new AncestorsIterable(this); } - public boolean isSublclass(PyClass parent) { + public boolean isSubclass(PyClass parent) { if (this == parent) return true; for (PyClass superclass : iterateAncestors()) { if (parent == superclass) return true; @@ -175,32 +126,35 @@ public class PyClassImpl extends PyPresentableElementImpl implement } protected List getSuperClassesList() { + if (PyNames.FAKE_OLD_BASE.equals(getName())) { + return Collections.emptyList(); + } PsiElement[] superClassElements = getSuperClassElements(); - if (superClassElements != null) { + if (superClassElements.length > 0) { List result = new ArrayList(); // maybe a bare old-style class? // TODO: depend on language version: py3k does not do old style classes PsiElement paren = PsiTreeUtil.getChildOfType(this, PyArgumentList.class).getFirstChild(); // no NPE, we always have the par expr if (paren != null && "(".equals(paren.getText())) { // "()" after class name, it's new style - for(PsiElement element: superClassElements) { + for (PsiElement element : superClassElements) { if (element instanceof PyClass) { - result.add((PyClass) element); + result.add((PyClass)element); } } } - else if (! PyBuiltinCache.BUILTIN_FILE.equals(getContainingFile().getName())) { // old-style *and* not builtin object() + else if (!PyBuiltinCache.BUILTIN_FILE.equals(getContainingFile().getName())) { // old-style *and* not builtin object() PyClass oldstyler = PyBuiltinCache.getInstance(this).getClass(PyNames.FAKE_OLD_BASE); if (oldstyler != null) result.add(oldstyler); } return result; } - return new ArrayList(0); + return Collections.emptyList(); } @NotNull public PyClass[] getSuperClasses() { PsiElement[] superClassElements = getSuperClassElements(); - if (superClassElements != null) { + if (superClassElements .length > 0) { List result = new ArrayList(); for(PsiElement element: superClassElements) { if (element instanceof PyClass) { @@ -388,4 +342,76 @@ public class PyClassImpl extends PyPresentableElementImpl implement private static T getRefValue(final SoftReference reference){ return reference != null ? reference.get() : null; } + + private static class AncestorsIterable implements Iterable { + private PyClassImpl myClass; + + public AncestorsIterable(final PyClassImpl pyClass) { + myClass = pyClass; + } + + public Iterator iterator() { + return new AncestorsIterator(myClass); + } + } + + private static class AncestorsIterator implements Iterator { + List pending = new LinkedList(); + Set seen = new HashSet(); + Iterator percolator; + PyClass prefetch = null; + private PyClassImpl myAClass; + + public AncestorsIterator(PyClassImpl aClass) { + myAClass = aClass; + percolator = myAClass.getSuperClassesList().iterator(); + } + + public boolean hasNext() { + // due to already-seen filtering, there's no way but to try and see. + if (prefetch != null) return true; + prefetch = getNext(); + return prefetch != null; + } + + public PyClass next() { + final PyClass nextClass = getNext(); + if (nextClass == null) throw new NoSuchElementException(); + return nextClass; + } + + @Nullable + private PyClass getNext() { + iterations: + while (true) { + if (prefetch != null) { + PyClass ret = prefetch; + prefetch = null; + return ret; + } + if (percolator.hasNext()) { + PyClass it = percolator.next(); + if (seen.contains(it)) { + continue iterations; // loop back is equivalent to return next(); + } + pending.add(it); + seen.add(it); + return it; + } + else if (pending.size() > 0) { + PyClass it = pending.get(0); + pending.remove(0); // t, ts* = pending + percolator = it.iterateAncestors().iterator(); + // loop back is equivalent to return next(); + } + else return null; + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + } From 8b503072601a3d45204f1d8ea487432a3ff3b2fc Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 18:19:52 +0300 Subject: [PATCH 08/13] no need to register PyBuiltinCache as project service any more --- python/src/META-INF/python-plugin-common.xml | 2 -- .../src/com/jetbrains/python/psi/impl/PyBuiltinCache.java | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index f37b87ce43e1..0522981269bd 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -52,8 +52,6 @@ - diff --git a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java index 0c97a308f5f0..c725738696a5 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java +++ b/python/src/com/jetbrains/python/psi/impl/PyBuiltinCache.java @@ -1,6 +1,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.openapi.components.ComponentManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.Project; @@ -29,7 +30,6 @@ import java.util.Map; * Provides access to Python builtins via skeletons. */ public class PyBuiltinCache { - public static final @NonNls String BUILTIN_FILE = "__builtin__.py"; @NotNull @@ -89,11 +89,9 @@ public class PyBuiltinCache { */ private static final Map ourInstanceCache = new HashMap(); - private Project myProject; private PyFile myBuiltinsFile; - public PyBuiltinCache(final Project project) { - myProject = project; + public PyBuiltinCache() { } private PyBuiltinCache(@Nullable final PyFile builtins) { From 143e26693ee06928dfa2f4bbf6da849e2aed9fab Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 18:53:22 +0300 Subject: [PATCH 09/13] disambiguate between importing file and name more correctly (PY-381) --- .../com/jetbrains/python/psi/resolve/ResolveImportUtil.java | 6 +++++- .../resolve/multiFile/directoryVsClass/DirectoryVsClass.py | 3 +++ .../resolve/multiFile/directoryVsClass/foo/__init__.py | 3 +++ .../resolve/multiFile/directoryVsClass/foo/context.py | 1 + .../com/jetbrains/python/PyMultiFileResolveTest.java | 6 ++++++ 5 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py create mode 100644 python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py create mode 100644 python/testData/resolve/multiFile/directoryVsClass/foo/context.py diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 54b113f52807..79f6effdeb0f 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -5,6 +5,7 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -580,7 +581,10 @@ public class ResolveImportUtil { ) { if (referencedName == null) return null; final PsiFile file = dir.findFile(referencedName + PyNames.DOT_PY); - if (file != null) return file; + // findFile() does case-insensitive search, and we need exactly matching case (see PY-381) + if (file != null && FileUtil.getNameWithoutExtension(file.getName()).equals(referencedName)) { + return file; + } final PsiDirectory subdir = dir.findSubdirectory(referencedName); if (subdir != null) return subdir; else if (! isFileOnly) { diff --git a/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py b/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py new file mode 100644 index 000000000000..292aff1d1111 --- /dev/null +++ b/python/testData/resolve/multiFile/directoryVsClass/DirectoryVsClass.py @@ -0,0 +1,3 @@ +from foo import Context +c = Context() +# diff --git a/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py b/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py new file mode 100644 index 000000000000..927f901d4d1e --- /dev/null +++ b/python/testData/resolve/multiFile/directoryVsClass/foo/__init__.py @@ -0,0 +1,3 @@ +__all__ = ['Context'] + +from foo.context import Context diff --git a/python/testData/resolve/multiFile/directoryVsClass/foo/context.py b/python/testData/resolve/multiFile/directoryVsClass/foo/context.py new file mode 100644 index 000000000000..4f2b7dad98e5 --- /dev/null +++ b/python/testData/resolve/multiFile/directoryVsClass/foo/context.py @@ -0,0 +1 @@ +class Context(object): pass diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index d7303047fabb..6d81ad89be44 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -128,6 +128,12 @@ public class PyMultiFileResolveTest extends PyLightFixtureTestCase { assertEquals("silicate", ((PyStringLiteralExpression)value).getStringValue()); } + public void testDirectoryVsClass() throws Exception { + PsiElement element = doResolve(); + assertTrue(element instanceof PyClass); + assertEquals("Context", ((PyClass) element).getName()); + } + private PsiFile prepareFile() throws Exception { String testName = getTestName(true); String fileName = getTestName(false) + ".py"; From 67d486ebcd8164b8b8d2570437f81e9530cb2625 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 19:24:51 +0300 Subject: [PATCH 10/13] show class quick doc if constructor isn't documented (part of PY-381) --- ...Finder.java => PythonDocStringFinder.java} | 4 +- .../python/PythonDocumentationProvider.java | 172 +++++++++--------- .../python/actions/AddImportHelper.java | 4 +- .../python/psi/impl/PyClassImpl.java | 4 +- .../jetbrains/python/psi/impl/PyFileImpl.java | 4 +- .../python/psi/impl/PyFunctionImpl.java | 6 +- .../python/validation/DocStringAnnotator.java | 8 +- .../ClassUndocumentedConstructor.html | 1 + .../quickdoc/ClassUndocumentedConstructor.py | 7 + .../com/jetbrains/python/PyQuickDocTest.java | 22 ++- .../python/fixtures/LightMarkedTestCase.java | 7 +- 11 files changed, 128 insertions(+), 111 deletions(-) rename python/src/com/jetbrains/python/{PythonDosStringFinder.java => PythonDocStringFinder.java} (93%) create mode 100644 python/testData/quickdoc/ClassUndocumentedConstructor.html create mode 100644 python/testData/quickdoc/ClassUndocumentedConstructor.py diff --git a/python/src/com/jetbrains/python/PythonDosStringFinder.java b/python/src/com/jetbrains/python/PythonDocStringFinder.java similarity index 93% rename from python/src/com/jetbrains/python/PythonDosStringFinder.java rename to python/src/com/jetbrains/python/PythonDocStringFinder.java index 6d3d03f4e780..f9a25a7cadfb 100644 --- a/python/src/com/jetbrains/python/PythonDosStringFinder.java +++ b/python/src/com/jetbrains/python/PythonDocStringFinder.java @@ -13,8 +13,8 @@ import org.jetbrains.annotations.Nullable; * User: dcheryasov * Date: Jun 7, 2009 5:06:12 AM */ -public class PythonDosStringFinder { - private PythonDosStringFinder() {} +public class PythonDocStringFinder { + private PythonDocStringFinder() {} /** * Looks for a doc string under given parent. diff --git a/python/src/com/jetbrains/python/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/PythonDocumentationProvider.java index cb26042cd1cc..0681154a8c86 100644 --- a/python/src/com/jetbrains/python/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/PythonDocumentationProvider.java @@ -166,7 +166,43 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider { cat.add(prolog_cat).addWith(TagCode, doc_cat).add(epilog_cat); // pre-assemble; then add stuff to individual cats as needed cat = wrapInTag("html", wrapInTag("body", cat)); + element = resolveToDocStringOwner(element, originalElement, prolog_cat); + // now element may contain a doc string + if (element instanceof PyDocStringOwner) { + String docString = null; + PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression(); + if (doc_expr != null) docString = doc_expr.getStringValue(); + // doc of what? + if (element instanceof PyClass) { + PyClass cls = (PyClass)element; + doc_cat.addWith(TagSmall, describeClass(cls, TagBold)); + } + else if (element instanceof PyFunction) { + PyFunction fun = (PyFunction)element; + PyClass cls = fun.getContainingClass(); + if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR)); + doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp)); + if (docString == null) { + addInheritedDocString(fun, cls, doc_cat, epilog_cat); + } + } + else if (element instanceof PyFile) { + // what to prepend to a module description?? + } + else { // not a func, not a class + doc_cat.add(combUp(PyUtil.getReadableRepr(element, false))); + } + if (docString != null) { + doc_cat.add(BR).add(combUpDocString(docString)); + } + else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say! + return cat.toString(); + } + return null; + } + + private static PsiElement resolveToDocStringOwner(PsiElement element, PsiElement originalElement, ChainIterable prolog_cat) { // here the ^Q target is already resolved; the resolved element may point to intermediate assignments boolean reassignment_marked = false; if (element instanceof PyTargetExpression) { @@ -199,84 +235,66 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider { } } - // now element may contain a doc string - if (element instanceof PyDocStringOwner) { - String docString = null; - PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression(); - if (doc_expr != null) docString = doc_expr.getStringValue(); - // doc of what? - if (element instanceof PyClass) { - PyClass cls = (PyClass)element; - doc_cat.addWith(TagSmall, describeClass(cls, TagBold)); + if (element instanceof PyFunction && PyNames.INIT.equals(((PyFunction)element).getName())) { + final PyStringLiteralExpression expression = ((PyFunction)element).getDocStringExpression(); + if (expression == null) { + PyClass containingClass = ((PyFunction) element).getContainingClass(); + if (containingClass != null) { + element = containingClass; + } } - else if (element instanceof PyFunction) { - PyFunction fun = (PyFunction)element; - PyClass cls = fun.getContainingClass(); - if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR)); - doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp)); - boolean not_found = true; - if (docString == null) { - String meth_name = fun.getName(); - if (cls != null && meth_name != null ) { - // look for inherited and its doc - for (PyClass ancestor : cls.iterateAncestors()) { - PyFunction inherited = ancestor.findMethodByName(meth_name, false); - if (inherited != null) { - PyStringLiteralExpression doc_elt = inherited.getDocStringExpression(); - if (doc_elt != null) { - String inherited_doc = doc_elt.getStringValue(); - if (inherited_doc.length() > 1) { - epilog_cat - .add(BR).add(BR) - .add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name)) - .add(BR).add(BR) - .addWith(TagCode, $(inherited_doc)) - ; - not_found = false; - break; - } - } - } - } + } + return element; + } - if (not_found) { - // above could have not worked because inheritance is not searched down to 'object'. - // for well-known methods, copy built-in doc string. - // TODO: also handle predefined __xxx__ that are not part of 'object'. - if (PyNames.UnderscoredAttributes.contains(meth_name)) { - PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff - if (objtype != null) { - PyClass objcls = objtype.getPyClass(); - if (objcls != null) { - PyFunction obj_underscored = objcls.findMethodByName(meth_name, false); - if (obj_underscored != null) { - PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression(); - String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null; - if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts - doc_cat.add(predefined_doc); - epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin")); - } - } - } + private static void addInheritedDocString(PyFunction fun, PyClass cls, ChainIterable doc_cat, ChainIterable epilog_cat) { + boolean not_found = true; + String meth_name = fun.getName(); + if (cls != null && meth_name != null ) { + // look for inherited and its doc + for (PyClass ancestor : cls.iterateAncestors()) { + PyFunction inherited = ancestor.findMethodByName(meth_name, false); + if (inherited != null) { + PyStringLiteralExpression doc_elt = inherited.getDocStringExpression(); + if (doc_elt != null) { + String inherited_doc = doc_elt.getStringValue(); + if (inherited_doc.length() > 1) { + epilog_cat + .add(BR).add(BR) + .add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name)) + .add(BR).add(BR) + .addWith(TagCode, $(inherited_doc)) + ; + not_found = false; + break; + } + } + } + } + + if (not_found) { + // above could have not worked because inheritance is not searched down to 'object'. + // for well-known methods, copy built-in doc string. + // TODO: also handle predefined __xxx__ that are not part of 'object'. + if (PyNames.UnderscoredAttributes.contains(meth_name)) { + PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff + if (objtype != null) { + PyClass objcls = objtype.getPyClass(); + if (objcls != null) { + PyFunction obj_underscored = objcls.findMethodByName(meth_name, false); + if (obj_underscored != null) { + PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression(); + String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null; + if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts + doc_cat.add(predefined_doc); + epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin")); } } } } } } - else if (element instanceof PyFile) { - // what to prepend to a module description?? - } - else { // not a func, not a class - doc_cat.add(combUp(PyUtil.getReadableRepr(element, false))); - } - if (docString != null) { - doc_cat.add(BR).add(combUpDocString(docString)); - } - else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say! - return cat.toString(); } - return null; } private static FP.Lambda1 LCombUp = new FP.Lambda1() { @@ -332,22 +350,6 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider { } }; - /* - private static StringBuilder join(String delimiter, Iterable list, StringBuilder cat) { - boolean is_next = false; - for (Object item : list) { - if (is_next) cat.append(delimiter); - else is_next = true; - cat.append(item.toString()); - } - return cat; - } - - private static String join(String delimiter, List list) { - return join(delimiter, list, new StringBuilder()).toString(); - } - */ - private static Iterable interleave(Iterable source, T filler) { List ret = new LinkedList(); boolean is_next = false; diff --git a/python/src/com/jetbrains/python/actions/AddImportHelper.java b/python/src/com/jetbrains/python/actions/AddImportHelper.java index e85caf071f5d..0b149cc83127 100644 --- a/python/src/com/jetbrains/python/actions/AddImportHelper.java +++ b/python/src/com/jetbrains/python/actions/AddImportHelper.java @@ -7,7 +7,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.util.IncorrectOperationException; -import com.jetbrains.python.PythonDosStringFinder; +import com.jetbrains.python.PythonDocStringFinder; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.Nullable; @@ -40,7 +40,7 @@ class AddImportHelper { } // maybe we arrived at the doc comment stmt; skip over it, too else if (!skipped_over_imports && ! skipped_over_doc && file instanceof PyFile) { - PsiElement doc_elt = PythonDosStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered + PsiElement doc_elt = PythonDocStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered if (doc_elt != null && doc_elt.getParent() == seeker) { feeler = seeker.getNextSibling(); seeker = feeler; // skip over doc even if there's nothing below it diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index b24505357513..cc21c2da389d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -12,7 +12,7 @@ import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.PythonDosStringFinder; +import com.jetbrains.python.PythonDocStringFinder; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl; import com.jetbrains.python.psi.*; @@ -285,7 +285,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement } public PyStringLiteralExpression getDocStringExpression() { - return PythonDosStringFinder.find(getStatementList()); + return PythonDocStringFinder.find(getStatementList()); } public String toString() { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index bb02e373c51f..a4adc88198c4 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -14,7 +14,7 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.indexing.FileBasedIndex; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PythonDosStringFinder; +import com.jetbrains.python.PythonDocStringFinder; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; @@ -250,7 +250,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } public PyStringLiteralExpression getDocStringExpression() { - return PythonDosStringFinder.find(this); + return PythonDocStringFinder.find(this); } public void subtreeChanged() { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index e449da049135..a90015d22c41 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -27,11 +27,11 @@ import com.intellij.util.Icons; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.PythonDosStringFinder; +import com.jetbrains.python.PythonDocStringFinder; +import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl; import com.jetbrains.python.psi.*; -import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder; import com.jetbrains.python.psi.stubs.PyClassStub; import com.jetbrains.python.psi.stubs.PyFunctionStub; import com.jetbrains.python.toolbox.SingleIterable; @@ -149,7 +149,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp } public PyStringLiteralExpression getDocStringExpression() { - return PythonDosStringFinder.find(getStatementList()); + return PythonDocStringFinder.find(getStatementList()); } protected String getElementLocation() { diff --git a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java index e9840e1d53d3..e3bb9b7335e0 100644 --- a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java +++ b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java @@ -18,7 +18,7 @@ package com.jetbrains.python.validation; import com.intellij.lang.annotation.Annotation; import com.jetbrains.python.PyHighlighter; -import com.jetbrains.python.PythonDosStringFinder; +import com.jetbrains.python.PythonDocStringFinder; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; @@ -31,18 +31,18 @@ public class DocStringAnnotator extends PyAnnotator { @Override public void visitPyFile(PyFile node) { - annotateDocStringStmt(PythonDosStringFinder.find(node)); + annotateDocStringStmt(PythonDocStringFinder.find(node)); } @Override public void visitPyFunction(PyFunction node) { - annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList())); + annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList())); } @Override public void visitPyClass(PyClass node) { - annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList())); + annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList())); } private void annotateDocStringStmt(PyStringLiteralExpression stmt) { diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.html b/python/testData/quickdoc/ClassUndocumentedConstructor.html new file mode 100644 index 000000000000..8991500da1d8 --- /dev/null +++ b/python/testData/quickdoc/ClassUndocumentedConstructor.html @@ -0,0 +1 @@ +class Foo(object)
Doc of Foo.
diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.py b/python/testData/quickdoc/ClassUndocumentedConstructor.py new file mode 100644 index 000000000000..6ad68e58ac2b --- /dev/null +++ b/python/testData/quickdoc/ClassUndocumentedConstructor.py @@ -0,0 +1,7 @@ +# direct class doc +class Foo(object): + "Doc of Foo." + def __init__(self): + pass + +Foo() diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 511ae3acce9a..5f3a6fb0b7d3 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -28,21 +28,22 @@ public class PyQuickDocTest extends LightMarkedTestCase { myProvider = new PythonDocumentationProvider(); } - protected String getTestDataPath() { - return PythonTestUtil.getTestDataPath() + "/quickdoc/"; - } - private void checkByHTML(String text) throws Exception { assertNotNull(text); - String filePath = getTestName(false) + ".html"; + String filePath = "/quickdoc/" + getTestName(false) + ".html"; final String fullPath = getTestDataPath() + filePath; final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - assertNotNull("file " + filePath + " not found", vFile); + assertNotNull("file " + fullPath + " not found", vFile); String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n"); assertEquals(fileText.trim(), text.trim()); } + @Override + protected Map loadTest() throws Exception { + return configureByFile("/quickdoc/" + getTestName(false) + ".py"); + } + private void processRefDocPair() throws Exception { Map marks = loadTest(); assertEquals(2, marks.size()); @@ -53,7 +54,7 @@ public class PyQuickDocTest extends LightMarkedTestCase { PsiElement ref_elt = marks.get("").getParent(); // ident -> expr final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve(); - assertEquals(doc_owner.getDocStringExpression(), doc_elt); + assertEquals(doc_elt, doc_owner.getDocStringExpression()); checkByHTML(myProvider.generateDoc(doc_owner, null)); } @@ -70,6 +71,13 @@ public class PyQuickDocTest extends LightMarkedTestCase { processRefDocPair(); } + public void testClassUndocumentedConstructor() throws Exception { + Map marks = loadTest(); + PsiElement ref_elt = marks.get("").getParent(); // ident -> expr + final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve(); + checkByHTML(myProvider.generateDoc(doc_owner, null)); + } + public void testCallFunc() throws Exception { processRefDocPair(); } diff --git a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java index 888b51e6bf5b..479e1474c7be 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java @@ -7,6 +7,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.testFramework.TestDataFile; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NonNls; @@ -47,12 +48,12 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase { * @return a mapping of markers to PSI elements * @throws Exception */ - protected Map configureByFile(@NonNls String filePath, @NonNls String markerRegexp) + protected Map configureByFile(@TestDataFile @NonNls String filePath, @NonNls String markerRegexp) throws Exception { final String fullPath = getTestDataPath() + filePath; final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); - assertNotNull("file " + filePath + " not found", vFile); + assertNotNull("file " + fullPath + " not found", vFile); String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n"); @@ -111,6 +112,4 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase { String fname = getTestName(false) + ".py"; return configureByFile(fname); } - - protected abstract String getTestDataPath(); } \ No newline at end of file From 616e3e9e5952f7b3d2860daef9ce2d51b2db73d8 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 19:39:22 +0300 Subject: [PATCH 11/13] special case of show implementations for Python: show initializer for target expressions (PY-237) --- .../jetbrains/python/psi/search/PyDefinitionsSearch.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java b/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java index fc3beca03807..26c952fe32c4 100644 --- a/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java +++ b/python/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java @@ -4,8 +4,10 @@ import com.intellij.psi.PsiElement; import com.intellij.util.Processor; import com.intellij.util.Query; import com.intellij.util.QueryExecutor; +import com.jetbrains.python.psi.PyAssignmentStatement; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyTargetExpression; /** * @author yole @@ -28,6 +30,12 @@ public class PyDefinitionsSearch implements QueryExecutor Date: Mon, 1 Feb 2010 19:58:26 +0300 Subject: [PATCH 12/13] correctly track argument count for inherited __init__ calls (PY-312) --- .../python/psi/impl/PyCallExpressionHelper.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index d140d9e10761..67c631aa089a 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -114,7 +114,14 @@ public class PyCallExpressionHelper { } // decorators? PyFunction method = (PyFunction)resolved; // constructor call? - if (PyNames.INIT.equals(method.getName())) implicit_offset += 1; + if (PyNames.INIT.equals(method.getName())) { + String refName = us.getCallee() instanceof PyReferenceExpression + ? ((PyReferenceExpression) us.getCallee()).getReferencedName() + : null; + if (!PyNames.INIT.equals(refName)) { // PY-312 + implicit_offset += 1; + } + } // look for closest decorator PyDecoratorList decolist = method.getDecoratorList(); if (decolist != null) { From b1404bc1e158ee06c65b5e9ff404434db6bc7324 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 1 Feb 2010 20:04:07 +0300 Subject: [PATCH 13/13] disable inapplicable controls --- .../python/run/PyCommonOptionsForm.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java b/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java index f608d9730666..c326252a170a 100644 --- a/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java +++ b/python/src/com/jetbrains/python/run/PyCommonOptionsForm.java @@ -7,7 +7,6 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.SdkListCellRenderer; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.util.io.FileUtil; -import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName; import com.intellij.ui.CollectionComboBoxModel; import com.intellij.ui.ColoredListCellRenderer; import com.intellij.ui.RawCommandLineEditor; @@ -16,6 +15,8 @@ import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -23,7 +24,7 @@ import java.util.Map; /** * @author yole */ -public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams { + public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams { private TextFieldWithBrowseButton myWorkingDirectoryTextField; private EnvironmentVariablesComponent myEnvsComponent; private RawCommandLineEditor myInterpreterOptionsTextField; @@ -56,6 +57,18 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams myWorkingDirectoryTextField.addBrowseFolderListener("Select Working Directory", "", configuration.getProject(), new FileChooserDescriptor(false, true, false, false, false, false)); + ActionListener listener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + updateControls(); + } + }; + myUseSpecifiedSdkRadioButton.addActionListener(listener); + myUseModuleSdkRadioButton.addActionListener(listener); + } + + private void updateControls() { + myModuleComboBox.setEnabled(myUseModuleSdkRadioButton.isSelected()); + myInterpreterComboBox.setEnabled(myUseSpecifiedSdkRadioButton.isSelected()); } public JPanel getMainPanel() { @@ -75,7 +88,7 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams } public String getWorkingDirectory() { - return toSystemIndependentName(myWorkingDirectoryTextField.getText().trim()); + return FileUtil.toSystemIndependentName(myWorkingDirectoryTextField.getText().trim()); } public void setWorkingDirectory(String workingDirectory) { @@ -122,6 +135,7 @@ public class PyCommonOptionsForm implements AbstractPythonRunConfigurationParams else { myUseSpecifiedSdkRadioButton.setSelected(true); } + updateControls(); } public boolean isPassParentEnvs() {