All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jetbrains.python.refactoring.changeSignature;

import com.intellij.lang.LanguageNamesValidation;
import com.intellij.lang.refactoring.NamesValidator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiCodeFragment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.changeSignature.CallerChooserBase;
import com.intellij.refactoring.changeSignature.ChangeSignatureDialogBase;
import com.intellij.refactoring.changeSignature.ParameterTableModelItemBase;
import com.intellij.refactoring.ui.ComboBoxVisibilityPanel;
import com.intellij.refactoring.ui.VisibilityPanelBase;
import com.intellij.ui.EditorTextField;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Consumer;
import com.intellij.util.IJSwingUtilities;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.table.JBListTable;
import com.intellij.util.ui.table.JBTableRow;
import com.intellij.util.ui.table.JBTableRowEditor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.refactoring.introduce.IntroduceValidator;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * User : ktisha
 */

public class PyChangeSignatureDialog extends ChangeSignatureDialogBase {

  public PyChangeSignatureDialog(Project project,
                                 PyMethodDescriptor method) {
    super(project, method, false, method.getMethod().getContext());
  }

  @Override
  protected LanguageFileType getFileType() {
    return PythonFileType.INSTANCE;
  }

  @Override
  protected PyParameterTableModel createParametersInfoModel(PyMethodDescriptor method) {
    final PyParameterList parameterList = PsiTreeUtil.getChildOfType(method.getMethod(), PyParameterList.class);
    return new PyParameterTableModel(parameterList, myDefaultValueContext, myProject);
  }

  @Override
  protected BaseRefactoringProcessor createRefactoringProcessor() {
    final List parameters = getParameters();
    return new PyChangeSignatureProcessor(myProject, myMethod.getMethod(), getMethodName(),
                                          parameters.toArray(new PyParameterInfo[parameters.size()]));
  }

  @Nullable
  @Override
  protected PsiCodeFragment createReturnTypeCodeFragment() {
    return null;
  }

  @Nullable
  @Override
  protected CallerChooserBase createCallerChooser(String title, Tree treeToReuse, Consumer> callback) {
    return null;
  }

  public boolean isNameValid(final String name, final Project project) {
    final NamesValidator validator = LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance());
    return (name != null) &&
           (validator.isIdentifier(name, project)) &&
           !(validator.isKeyword(name, project));
  }

  @Nullable
  @Override
  protected String validateAndCommitData() {
    final String functionName = myNameField.getText().trim();
    if (!functionName.equals(myMethod.getName())) {
      final boolean defined = IntroduceValidator.isDefinedInScope(functionName, myMethod.getMethod());
      if (defined) {
        return PyBundle.message("refactoring.change.signature.dialog.validation.name.defined");
      }
      if (!isNameValid(functionName, myProject)) {
        return PyBundle.message("refactoring.change.signature.dialog.validation.function.name");
      }
    }
    final List parameters = myParametersTableModel.getItems();
    Set parameterNames = new HashSet();
    boolean hadPositionalContainer = false;
    boolean hadKeywordContainer = false;
    boolean hadDefaultValue = false;
    boolean hadSingleStar = false;
    boolean hadParamsAfterSingleStar = false;
    LanguageLevel languageLevel = LanguageLevel.forElement(myMethod.getMethod());

    int parametersLength = parameters.size();

    for (int index = 0; index != parametersLength; ++index) {
      PyParameterTableModelItem info = parameters.get(index);
      final PyParameterInfo parameter = info.parameter;
      final String name = parameter.getName();
      final String nameWithoutStars = StringUtil.trimLeading(name, '*').trim();
      if (parameterNames.contains(nameWithoutStars)) {
        return PyBundle.message("ANN.duplicate.param.name");
      }
      parameterNames.add(nameWithoutStars);

      if (name.equals("*")) {
        hadSingleStar = true;
        if (index == parametersLength-1) {
          return PyBundle.message("ANN.named.arguments.after.star");
        }
      }
      else if (name.startsWith("*") && !name.startsWith("**")) {
        if (hadKeywordContainer) {
          return PyBundle.message("ANN.starred.param.after.kwparam");
        }
        if (hadSingleStar || hadPositionalContainer) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.multiple.star");
        }
        if (!isNameValid(name.substring(1), myProject)) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name");
        }
        hadPositionalContainer = true;
      }
      else if (name.startsWith("**")) {
        if (hadSingleStar && !hadParamsAfterSingleStar) {
          return PyBundle.message("ANN.named.arguments.after.star");
        }
        if (hadKeywordContainer) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.multiple.double.star");
        }
        if (!isNameValid(name.substring(2), myProject)) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name");
        }
        hadKeywordContainer = true;
      }
      else {
        if (!isNameValid(name, myProject)) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.name");
        }
        if (hadSingleStar) {
          hadParamsAfterSingleStar = true;
        }
        if (hadPositionalContainer && !languageLevel.isPy3K()) {
          return PyBundle.message("ANN.regular.param.after.vararg");
        }
        else if (hadKeywordContainer) {
          return PyBundle.message("ANN.regular.param.after.keyword");
        }
        final String defaultValue = info.getDefaultValue();
        if (defaultValue != null && !StringUtil.isEmptyOrSpaces(defaultValue) && parameter.getDefaultInSignature()) {
          hadDefaultValue = true;
        }
        else {
          if (hadDefaultValue && !hadSingleStar && (!languageLevel.isPy3K() || !hadPositionalContainer)) {
            return PyBundle.message("ANN.non.default.param.after.default");
          }
        }
      }
      if (parameter.getOldIndex() < 0) {
        if (!parameter.getName().startsWith("*")) {
          if (StringUtil.isEmpty(info.defaultValueCodeFragment.getText())) {
            return PyBundle.message("refactoring.change.signature.dialog.validation.default.missing");
          }
          if (StringUtil.isEmptyOrSpaces(parameter.getName())) {
            return PyBundle.message("refactoring.change.signature.dialog.validation.parameter.missing");
          }
        }
      }
      else if (myMethod.getParameters().get(parameter.getOldIndex()).getDefaultInSignature() &&
               StringUtil.isEmptyOrSpaces(parameter.getDefaultValue())) {
          return PyBundle.message("refactoring.change.signature.dialog.validation.default.missing");
      }
    }

    return null;
  }

  @Override
  protected ValidationInfo doValidate() {
    final String message = validateAndCommitData();
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        getRefactorAction().setEnabled(message == null);
        getPreviewAction().setEnabled(message == null);
      }
    });
    if (message != null) return new ValidationInfo(message);
    return super.doValidate();
  }

  @Override
  public JComponent getPreferredFocusedComponent() {
    return myNameField;
  }

  @Override
  protected String calculateSignature() {
    @NonNls StringBuilder builder = new StringBuilder();
    builder.append(getMethodName());
    builder.append("(");
    final List parameters = myParametersTableModel.getItems();
    for (int i = 0; i != parameters.size(); ++i) {
      PyParameterTableModelItem parameterInfo = parameters.get(i);
      builder.append(parameterInfo.parameter.getName());
      final String defaultValue = parameterInfo.defaultValueCodeFragment.getText();
      if (!defaultValue.isEmpty() && parameterInfo.isDefaultInSignature()) {
        builder.append(" = " + defaultValue);
      }
      if (i != parameters.size()-1)
        builder.append(", ");
    }
    builder.append(")");
    return builder.toString();
  }

  @Override
  protected VisibilityPanelBase createVisibilityControl() {
    return new ComboBoxVisibilityPanel(new String[0]);
  }

  @Override
  protected JComponent getRowPresentation(ParameterTableModelItemBase item, boolean selected, final boolean focused) {
    String text = item.parameter.getName();
    final String defaultCallValue = item.defaultValueCodeFragment.getText();
    PyParameterTableModelItem pyItem = (PyParameterTableModelItem)item;
    final String defaultValue = pyItem.isDefaultInSignature()? pyItem.defaultValueCodeFragment.getText() : "";

    if (StringUtil.isNotEmpty(defaultValue)) {
      text += " = " + defaultValue;
    }

    String tail = "";
    if (StringUtil.isNotEmpty(defaultCallValue)) {
      tail += " default value = " + defaultCallValue;
    }
    if (!StringUtil.isEmpty(tail)) {
      text += " //" + tail;
    }
    return JBListTable.createEditorTextFieldPresentation(getProject(), getFileType(), " " + text, selected, focused);
  }

  @Override
  protected boolean isListTableViewSupported() {
    return true;
  }

  @Override
  protected JBTableRowEditor getTableEditor(final JTable t, final ParameterTableModelItemBase item) {
    return new JBTableRowEditor() {
      private EditorTextField myNameEditor;
      private EditorTextField myDefaultValueEditor;
      private JCheckBox myDefaultInSignature;

      @Override
      public void prepareEditor(JTable table, int row) {
        setLayout(new GridLayout(1, 3));
        final JPanel parameterPanel = createParameterPanel();
        add(parameterPanel);
        final JPanel defaultValuePanel = createDefaultValuePanel();
        add(defaultValuePanel);
        final JPanel defaultValueCheckBox = createDefaultValueCheckBox();
        add(defaultValueCheckBox);

        final String nameText = myNameEditor.getText();
        myDefaultValueEditor.setEnabled(!nameText.startsWith("*")
                                        && !PyNames.CANONICAL_SELF.equals(nameText));
        myDefaultInSignature.setEnabled(!nameText.startsWith("*")
                                        && !PyNames.CANONICAL_SELF.equals(nameText));
      }

      private JPanel createDefaultValueCheckBox() {
        final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false));

        final JBLabel inSignatureLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.checkbox"),
                                                     UIUtil.ComponentStyle.SMALL);
        IJSwingUtilities.adjustComponentsOnMac(inSignatureLabel,
                                               myDefaultInSignature);
        defaultValuePanel.add(inSignatureLabel, BorderLayout.WEST);
        myDefaultInSignature = new JCheckBox();
        myDefaultInSignature.setSelected(
          ((PyParameterTableModelItem)item).isDefaultInSignature());
        myDefaultInSignature.addItemListener(new ItemListener() {
          @Override
          public void itemStateChanged(ItemEvent event) {
            ((PyParameterTableModelItem)item)
              .setDefaultInSignature(myDefaultInSignature.isSelected());
          }
        });
        myDefaultInSignature.addChangeListener(mySignatureUpdater);
        myDefaultInSignature.setEnabled(item.parameter.getOldIndex() == -1);
        defaultValuePanel.add(myDefaultInSignature, BorderLayout.EAST);
        return defaultValuePanel;
      }

      private JPanel createDefaultValuePanel() {
        final JPanel defaultValuePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false));
        final Document doc = PsiDocumentManager.getInstance(getProject()).getDocument(item.defaultValueCodeFragment);
        myDefaultValueEditor = new EditorTextField(doc, getProject(), getFileType());
        final JBLabel defaultValueLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.default.value.label"),
                                                      UIUtil.ComponentStyle.SMALL);
        IJSwingUtilities.adjustComponentsOnMac(defaultValueLabel, myDefaultValueEditor);
        defaultValuePanel.add(defaultValueLabel);
        defaultValuePanel.add(myDefaultValueEditor);
        myDefaultValueEditor.setPreferredWidth(t.getWidth() / 2);
        myDefaultValueEditor.addDocumentListener(mySignatureUpdater);
        return defaultValuePanel;
      }

      private JPanel createParameterPanel() {
        final JPanel namePanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 4, 2, true, false));
        myNameEditor = new EditorTextField(item.parameter.getName(), getProject(), getFileType());
        final JBLabel nameLabel = new JBLabel(PyBundle.message("refactoring.change.signature.dialog.name.label"),
                                              UIUtil.ComponentStyle.SMALL);
        IJSwingUtilities.adjustComponentsOnMac(nameLabel, myNameEditor);
        namePanel.add(nameLabel);
        namePanel.add(myNameEditor);
        myNameEditor.setPreferredWidth(t.getWidth() / 2);
        myNameEditor.addDocumentListener(new DocumentAdapter() {
          @Override
          public void documentChanged(DocumentEvent event) {
            fireDocumentChanged(event, 0);
            myDefaultValueEditor.setEnabled(!myNameEditor.getText().startsWith("*"));
            myDefaultInSignature.setEnabled(!myNameEditor.getText().startsWith("*"));
          }
        });

        myNameEditor.addDocumentListener(mySignatureUpdater);
        return namePanel;
      }

      @Override
      public JBTableRow getValue() {
        return new JBTableRow() {
          @Override
          public Object getValueAt(int column) {
            switch (column) {
              case 0: return myNameEditor.getText().trim();
              case 1: return new Pair(item.defaultValueCodeFragment,
                                                                ((PyParameterTableModelItem)item).isDefaultInSignature());
            }
            return null;
          }
        };
      }

      @Override
      public JComponent getPreferredFocusedComponent() {
        return myNameEditor.getFocusTarget();
      }

      @Override
      public JComponent[] getFocusableComponents() {
        final List focusable = new ArrayList();
        focusable.add(myNameEditor.getFocusTarget());
        if (myDefaultValueEditor != null) {
          focusable.add(myDefaultValueEditor.getFocusTarget());
        }
        return focusable.toArray(new JComponent[focusable.size()]);
      }
    };
  }

  @Override
  protected boolean mayPropagateParameters() {
    return false;
  }

  @Override
  protected boolean postponeValidation() {
    return false;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy